Giter VIP home page Giter VIP logo

cwsdk's Introduction

CWSDK

Defines structures, functions, and callbacks for interacting with Cube World in C++.

Mods produced with CWSDK are for use with the Cube World Mod Launcher.

Getting started

Although modding Cube World, even with CWSDK, is not for the faint of heart, CWSDK provides common-use functionality to assist development and cooperation of mods. All projects which use CWSDK should be compiled using Visual Studio and Clang.

  1. Set up and install Visual Studio 2019.

  2. Configure it for Clang: https://devblogs.microsoft.com/cppblog/clang-llvm-support-in-visual-studio/

  3. Create a folder for your source code, and add:

    a) A released snapshot of CWSDK.

    b) A main.cpp for your code.

    c) A CMakeLists.txt. See CommandsMod for an example and a script which can auto-generate this for you in simpler projects.

  4. Open your project in Visual Studio using the Open Folder option. Configure Visual Studio to build with x64-Clang-Release according to the page in step 2.

  5. Include cwsdk.h from CWSDK to use its features.

Note that you'll probably want to change the output from Build + IntelliSense to Build only.

Basic mod structure

You must create a class for your mod which inherits from GenericMod from cwsdk.h. To instantiate the class and provide it to the Cube World Mod Launcher, you must export a function MakeMod which returns a pointer to a new instance of your mod. To utilize event handlers, override the appropriate virtual function.

#include "CWSDK/cwsdk.h"

class Mod : GenericMod {
   virtual int OnChat(std::wstring* message) override {
   	// This will be called when the player sends a chat message
   }
   virtual void OnGameTick(cube::Game* game) override {
   	// This will be called every frame
   }
   virtual void Initialize() override {
   	// This will be called after your mod is loaded. CWSDK internals are initialized at this point, so it's safe to use CWBase() and CWOffset().
   }
};

EXPORT Mod* MakeMod() {
   return new Mod();
}

Event Handlers

To use an event handler, override them as per the example above.


virtual void Initialize()

Called after mod is loaded.

Return values: None.


virtual int OnChat(std::wstring* message)

Called when the player sends a message.

Return values:

0 - Normal behavior

1 - Cancel message


virtual int OnCheckInventoryFull(cube::Creature* player, cube::Item* item)

Called when the game checks to see if a creature can carry more of an item.

Return values:

0 - Normal behavior

1 - Override, CANNOT carry more.

2 - Override, CAN carry more.


virtual int OnP2PRequest(uint64_t steamID)

Called when a new Steam peer to peer request is made.

Return values:

0 - Normal behavior

1 - Force block request

2 - Force accept request


virtual void OnGameTick(cube::Game* game)

Called (prior to) every frame.

Return values: None.


virtual void OnZoneGenerated(cube::Zone* zone)

Called after a zone is generated. Note that zones belong to worlds, and there are two cube::World objects. One world belongs to cube::Game, and one world belongs to cube::Game::Host.

Return values: None.


virtual void OnZoneDestroy(cube::Zone* zone)

Called before a zone is destroyed.

Return values: None.


virtual int OnWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

Called before a WindowProc callback is performed. See Microsoft's documentation.

Return values:

0 - Normal behavior

1 - Cancel WindowProc


virtual void OnGetKeyboardState(BYTE* diKeys)

Called when GetDeviceState is called for the keyboard. See Microsoft's documentation. To modify a key state, modify the values in diKeys.

Return values: None.


virtual void OnGetMouseState(DIMOUSESTATE* diMouse)

Called when GetDeviceState is called for the mouse. See Microsoft's documentation. To modify a mouse state, modify the values belonging to diMouse.

Return values: None.


virtual void OnPresent(IDXGISwapChain* SwapChain, UINT SyncInterval, UINT Flags)

Called before Present is called on the game's IDXGISwapChain. See Microsoft's documentation.

Return values: None.


virtual void OnCreatureArmorCalculated(cube::Creature* creature, float* armor)

Called after a creature's armor stat is calculated. To modify the result, modify the value pointed to by armor.

Return values: None.


virtual void OnCreatureCriticalCalculated(cube::Creature* creature, float* critical)

Called after a creature's critical stat is calculated. To modify the result, modify the value pointed to by critical.

Return values: None.


virtual void OnCreatureAttackPowerCalculated(cube::Creature* creature, float* power)

Called after a creature's attack power stat is calculated. To modify the result, modify the value pointed to by power.

Return values: None.


virtual void OnCreatureSpellPowerCalculated(cube::Creature* creature, float* power)

Called after a creature's spell power stat is calculated. To modify the result, modify the value pointed to by power.

Return values: None.


virtual void OnCreatureHasteCalculated(cube::Creature* creature, float* haste)

Called after a creature's haste stat is calculated. To modify the result, modify the value pointed to by haste.

Return values: None.


virtual void OnCreatureResistanceCalculated(cube::Creature* creature, float* resistance)

Called after a creature's haste stat is calculated. To modify the result, modify the value pointed to by resistance.

Return values: None.


virtual void OnCreatureRegenerationCalculated(cube::Creature* creature, float* regeneration)

Called after a creature's stamina regeneration stat is calculated. To modify the result, modify the value pointed to by regeneration.

Return values: None.


virtual void OnCreatureManaGenerationCalculated(cube::Creature* creature, float* manaGeneration)

Called after a creature's mana regeneration stat is calculated. To modify the result, modify the value pointed to by manaGeneration.

Return values: None.


virtual void OnChunkRemesh(cube::Zone* zone)

Called when a gfx::Chunk starts to be remeshed.

Return values: None.


virtual void OnChunkRemeshed(cube::Zone* zone)

Called when a gfx::Chunk is finished remeshing.

Return values: None.


Event Priorities

Any event handler can have its priority set or changed at any time. An event handler with a higher priority gets called before those with a lower priority. The priority associated with any given event is named {Event Handler}Priority. For example, the priority for OnChat is OnChatPriority. The priority variables are of type enum GenericMod::Priority and there are 5 possible priorities:

VeryHighPriority,
HighPriority,
NormalPriority,
LowPriority,
VeryLowPriority

By default, all priorities are NormalPriority.

Setting a priority is simple. Here is an example of setting the priority of a mod's OnChat handler to be very low as soon as the mod is initialized:

#include "CWSDK/cwsdk.h"
class Mod : GenericMod {
   virtual void Initialize() override {
   	OnChatPriority = VeryLowPriority;
   }
   virtual int OnChat(std::wstring* message) override {
   	//I will be called after mods with low, normal, high, or very high priority.
   	return 0;
   }
};
EXPORT Mod* MakeMod() {
   return new Mod();
}

Data members and methods

No documentation on these, and they are especially prone to change. Explore the source.

Special thanks

Andoryuuta - Reverse engineering work and mapping structs. Much is based on his CWTCore.
matpow2 - Structs for the alpha that are still useful in the beta
ZaneYork - Fields and contributions to the mod loader and commandsmod

cwsdk's People

Contributors

coremaze avatar lord-nightmare avatar nicokandut avatar

Stargazers

Edgar Sayohm Takamura avatar Ethan Lafrenais avatar Grant Johnson avatar Paul Stähle avatar Tyler Jaacks avatar Cat Stevens avatar Gijs avatar Brian avatar widberg avatar Scorbutics avatar  avatar  avatar geni avatar Diogo Antunes avatar Marcus R. Brown avatar cabbagesensual6457250 avatar Matheus Felizola Freires avatar Phosphorus Moscu avatar Marnix Massar avatar Yukita Mayako avatar João Seidel avatar Luis avatar Sucsynct avatar Tim avatar Stolym avatar Antoine Karcher avatar  avatar Augusto Cezar avatar Callum Carmicheal avatar

Watchers

James Cloos avatar  avatar  avatar João Seidel avatar Trevor Crow avatar Ray avatar ninjasploit avatar  avatar  avatar

cwsdk's Issues

Implementing a system for adding new IDs to the game

Here is a proposed system for managing mods that would want to add additional ids into the game, whether that be for new items, new blocks, etc. that borrows a bit of inspiration from Minecraft's Forge modding API.

Issues with integer IDs

The game currently uses integer ids to represent things. There are a few issues with using integer ids.

The first issue is conflicts between mods, where two mods both try and add something that corresponds to the same number. These issues are usually resolved through tedious config file management by users.

The second issue is conflicts between ids between host and user. This stems from the fact that configs can be used to change the value of ids, such as the host changing the ids for a mod and the user not having the changed config files. This is usually resolved by passing the config information around between users, whether that be done by the user or whoever distributes the mods.

The last issue is when new content is added to the game. If new content is ever added to the game, mods and the base game can end up trying to use the same integer id, causing compatibility issues. This is usually resolved by starting the floor of allowed ids sufficiently far away from the base game's ids.

Representing a new addition

Everything that would normally have an integer id in the game would also be required to have a string id. The SDK would support interactions only in terms of this string id. The formatting for these ids would be something like:

mod_prefix:item_name

Each mod would its on prefix, specified however it is appropriate, and would register its new additions with this prefix attached.

The base game would have its own reserved prefix, such as base or cubeworld, and the SDK would provide a standard mapping for this (e.g. a grass block is base:block_grass as defined by the SDK).

Registries

To store these new string ids, we will utilize some form of Registry. Each respective system that uses ids would hold its own registry. A registry, in simplest terms, is a mapping of string id to integer id. By default, this will contain the base game's mappings and will be populated during mod initialization.

In order to prevent changes in mods from changing the meaning of an integer id, persistence of each registry is needed. To implement persistence of registries, each registry would be saved alongside characters. This has a few implications alongside it, the major one being each character (and subsequently each client) can have different integer ids for the same string ids. This won't cause issues when playing alone, as the registries get saved/loaded with each character. However, in multiplayer, differences in integer ids can occur and need to be handled

The default registry

The default registry is how the loader will populate save files that don't already have a registry attached. To start, each default registry is populated with the base game data. Then, each registry will hold the next "free" integer id available, based on which id was last registered by the base game (potentially adding a large section of padded space in case Wollay ever returns to the living). During mod initialization phase, mods will be allowed to access registries and add their new string ids. When a new string id is registered, it takes the next available integer id for its mapping. After initialization, the default registry made read-only.

Initializing and modifying a Save's Registries

Registries attached to saves should only be modified when mods are added/removed. The cases for when this happen are below:

The very first time a save file is loaded/created, no registry will be present. The Loader will then copy the default registry to the save file and the save file will use that as its new registry.

If a save file already has a registry present, the loader will look to see if any new mods have been added to its default registry that aren't present in the save file's registry. If there isn't anything new, proceed normally. If there are new entries in the default registry, we will attempt to add them to the save file's registry. If an integer id conflict occurs, we let the save file registry allocate a new integer id for the string id that is being added.

In the event that the save file registry has an entry that isn't present in the default registry (e.g. a mod was removed), some sort of error prompt may be needed for the user to respond. Potential ways of handling this include removing anything that uses a removed mod's items and/or telling the user that a save was created/accessed with a mod and it is not actually loaded

Registries and Multiplayer Sessions

For handling multiplayer properly, we introduce a method of interpreting the host's registry. This method assumes that both users have the same set of mods that involve registering new things.

To start, when a user connects to a host, the host sends out to the connecting user all of its registry information. The user then stores this and uses it to interpret every related packet that the host sends out (e.g. item drops, block changes, etc). Likewise, the user will send its registry information to the host as well, and the host stores this and uses it to interpret anything the user might send to the host.

Whenever a relevant packet is sent/received, the recipient will look to the sender's register and interpret what string id the packet's respective integer id is referring to. It then uses this and looks up what integer id the recipient is using for that string id, replaces it, and then allows the packet to be processed.

Example 1: Interpreting a Packet

The host receives an item dropped packet from User A. Currently, the host has the following registries:

  • Host Item Registry:
    • "items_mod:new_item" maps to id 56
  • User A Item Registry
    • "items_mod:new_item" maps to id 59
  • User B Item Registry
    • "items_mod:new_item" maps to id 60

The packet received from User A is a packet saying they dropped an item with id 59. The host gives this packet to the interpreter, where it looks to User A's item registry and discovers that they dropped a "new_item" from "items_mod". It then looks up what integer id the host is using for this item and substitutes that value into the packet. It then allows the host to continue processing this packet and handles the item drop. It then broadcasts that an item was dropped to other users, and User B receives the item drop packet from the Host that says the item dropped was an item with id 56. The process repeats, and User B successfully sees that a "new_item" is on the ground where User A drops it.

How differences in registries can occur

Suppose the Host and User A have the following sets of Mods:

  • Host mods:
    • A B C
  • User A mods:
    • A B C D

The following is true for the above mods:

  • Mod A depends on Mod B
  • Mod D optionally depends on mod C
  • Mod B optionally depends on mod D
  • Mod D is a client-sided mod only (it functions even with the host not having it present)
  • Mod A, B, and C all register new items

The mod loading order would be the following, assuming mods are loaded by dependency and then by alphabetical order

  • Host load order;
    • B A C
  • User A load order:
    • C D B A

The end result of this is that both the Host and User A have the same set of required mods to connect to each other, but User A's default registry will have C's entries before B and A, whereas Host's default registry will have C's entries after B and A. Therefore, when User A creates a new save file and joins Host, they have an inconsistent set of registries and will need to use the interpreter to communicate.

End remarks

All these ideas are fairly abstract in nature and may ignore some finer implementation specific details. Additionally, it is open for discussion whether or not this is a viable way to tackle the issue.

Any and all input is appreciated.

Add Readme

Hi there,

I'm just new to modding and I'm trying to understand what's going on.
I'm using your Cube-World-Commands-Mod as an example, but for now I'm not even sure how to compile all that.

Could you maybe add a small Readme to explain how to setup a basic environment to start modding ?

Any help would be appreciated !

Return values of OnCheckInventoryFull

I was messing around with this a bit today and realized the return values of OnCheckInventoryFull don't work as they are documented.

Readme currently says:

  • 1 - Override, CAN carry more.
  • 2 - Override, CANNOT carry more.

But actually it is:

  • 1 - Override, CANNOT carry more.
  • 2 - Override, CAN carry more.

If that's not the case and I am just missing something, let me know.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.