Modular objects

  • I think most of recently added non generic handlers except “moddables”(art, hero, monster. town …) should be turned into modules
  • we shouldn`t ever pass “create ship of type X” from outside of “object-and-its-gui”. So module will have server and client parts and object should have only generic API (player, AI, RMG etc interraction).

So lets start new thread :slight_smile:

Shipyard is just an example on how provide parameters to objects. We can just send json config to it but this will hardly work for parameters changes in runtime.

Another example - town dwellings. We can turn dwelling into modules either as:

  • one module per building. In this case - how to handle upgrades? They should be available on the same window but in this case we have separate modules since they come from different buildings.
  • one module per creature level - most logical organization but in this case problem is providing list of available creatures to module. And complete blackboxing of modules will make this impossible.
  • one module per town - same as above but we also must provide selected level.

What should we do with modules like Shroud of Darkness? What should be responsibility of object and what is responsibility of module?
Adv. map shroud gets activated only on hero visit while town shroud should be activated on new turn AND on construction. While in the same time resource production buildings become active only on next day so we can’t just call newTurn() on all new objects during contruction.

hero visit becomes more complex with towns as well - bonusing buildings like Stables should be activated automatically once hero enters town. But buildings like harbor should be activated only on request.

Possible solution for this would be to specify list of triggers for each module (e.g. “newDay”, “onVisit”, “playerRequest”). But this is largely town-specific solution which won’t work for most of map objects. Perhaps allow only one module for “generic object” that does not have any custom script/dedicated class that handles this object while more complex objects will define their own rules of modules interaction.

May be some “variation” of SetObjectProperty.

“one module per creature level” - this one.
If dwelling is a module then set of creatures is property of that module but not town. (module is still blackbox, but town has API to provide creatures).

  • send also construction event.
  • make set of activating events configurable. Default behaviour: if module has GUI then it recieve onVisit on actual visit on adv map and on interface click in town. If module has no gui then it receive onVisit on actual visit.

see above.

Why town-specific?
newDay - not only town but also mines
onVisit - obviously any visitable object
playerRequest - useless. see above.

This definitely won’t work - this would mean that scriptable object should provide API (with placeholder implementation) for every module.Because if CPythonScriptObject won’t have getAvailableCreatures() method it will be impossible to use Dwelling module from scripts.
And idea of modules started precisely to remove such “specialized” API from objects.

BTW - creature growth have the same problem. Probably we should convert remaining sources into bonuses and propagate that info to modules.

setProperty should work here but it is only applicable to simple data. If we’re going to convert such complex objects like army into module there is no way it would work there.

I still think that object should be aware of available modules - hero must have access to “shipyard” module because he can summon new boats via spell but this is done without any access to GUI - just call to createBoat() method or whatever its name is.

Maybe we should consider two use cases:
a) “named” modules - like hero-shipyard. They are always present but used only if object triggers them one way or another.
b) list of modules - like town buildings, access to which is done only via common interface. Such modules are always active and created on request (e.g. when structure built in town)

No need to have getAvailableCreatures() in generic object API, we may add setAviableCreatures to TOWN API. Town will most likely remain “special object”, so it may have own API and modules will use it. Module API will remain generic but inside we will have some code for town API interraction.

Current setProperty is obviously not enough.
As we also need to synchronize arbitrary mod data, we may introduce setModData(object, modId, json).

I dont like current design. Boat creation should be deep implementation detail (of shipyard module and summonBoat spell mechanics). IBoatGenerator + ancestors become single CBoatGenerator + may be some interface implemented by module and spell mechanics.

Lets try to avoid this as far as possible.

Then how will be scriptable dwelling be implemented? It also would need to provide such method. And since scriptable object should derive from CGObject it won’t have town specific methods. But it should be able to use town modules like dwelling.

I guess we have different view on purpose of such modules. From my point of view main purpose of modules is to provide API with certain functionality to scriptable objects.
You want to add trade to scriptable object? Add Market module and use API provided by it. You want guards or possibility to grant creatures? Add Army module and use its API. I consider town buildings as a similar (but not identical) concept.

Probably the only thing they’ll have in common with town buildings is that neither of those derives from CGObjectInstance. Instead they have access to parent-object and can only access gamestate via it.

My view:

  1. there are no scriptable objects
  2. there are some built-in modules and scriptable modules may be added
  3. module is single solid reusable function of object (f.e. Stables on map has two modules: “bonusingObject” and cavalry upgrade, and in town it as one bonusingObject module)
  4. in town config each building is assigned to module (and may be independent modules like WoG`s Bank). IOW building itself is complitely defined in JSON while mechanics and GUI come from assigned module.
  5. town know almost nothing about particular modules it only redirects events (such as newTurn, heroVisit).
  6. town has API to alter things like aviable creatures and income.
  7. module should use interface querying (or just dynamic case in native code) on their owner to access town api. We can`t foresee how exactly town mechnics can be altered by scripts so we are granting full access to town object to modules.

Ah. The we’re obviously talking about different things.

Actually I’ve been considering something like this before working on “configurable object”. But in the end I haven’t found any easy way to implement something like this without breaking H3 behavior.

For example banks - they consist from 2 parts: guards and reward. But they are not separate. For example to generate correct reward message you need information about guards (“You have defeated %creature name% and claimed reward: %reward%”). Messages like this need information from multiple modules which were supposed to be black boxes. Delegating this function to object means that object would need script.

Stables have this as well - cavalry upgrade message also contains text about movement bonus. And this situation is global - right now I’m fighting with text placeholders in my configurable objects.

Another issue is rules on order in which these modules should be activated. For example Crypt should have scheme like this:

visit confirmation -> object looted? no -> guards -> guards defeated? -> morale hit & reward
                                     yes -> morale hit

So there also should be logic code that will determine activation order, which modules are active and which should be removed and such. And this means need of scriptable objects.

I think that such modules will only work for some specific cases - like market or harbor. Using it on larger scale is not worth efforts. Especially if we may actually get scripts where most of these modules can be implemented in 1-2 calls to game state.

What may work here (relatively easy) is removing town building - map object duality. For example by implementing something like this:

class ITownBuilding  // aka module
    const CGObjectInstance * parent; // either town or map object, but town building does NOT "knows" that
    //some interface, can be similar to IObjectInterface

class CGTownInstance : public CGObjectInstance // or whatever its parent is
    std::map<BuildingID, ITownBuilding*> buildings; // list of town buildings, blackboxes

class CMapBuilding : public CGObjectInstance // for objects that have identical town building
    ITownBuilding* building; // ONE building, with some configuration on when it gets active

ITownBuilding can be easily renamed to something like “object module”. In such way removing such duality should be easy but I don’t think that composing objects from modules is feasible.

And of course it won’t help with this issue:

This is what I wanted to fix with modules that have API known to objects-parents.

Yes this is an issue. So there is a place for scripts. Tier between Object itself and its modules. (Modules are responsible to alter gamestate, while "script" has access only to public interface of modules but know exact types. And object itself still know almost nothing) Also Id like to get rid of callbacks - module algorithms should operate on NETPACK level.

This is one exact things that I want to avoid with this redesign. I want to get rid of such interfaces (it might remain as implementation detail of different markets but not on map object level in current design).

So what we have right now:

  • IGameCallback class with clearly defined API (huge plus) but oversized (downside)
  • CGObjectInstance and several interfaces like IMarket with (again huge plus) clearly defined API but inheritance is not really suitable for scripts (downside).

Giving script access to such API will allow objects with pretty much any functionality allowed by the game.

Why having abstract modules with predefined functionality is better approach here? I would agree on reorganizing callback/map object interfaces but only if “clearly defined API” part will remain - e.g. turn huge callback in several “packages” which can be “imported” but I don’t see a way for such integral to game modules like Army to function with generic API.

As I understand you idea is that instead of writing object code using game callback API modders/developers will have to play Tetris with modules and then glue them together with some logic code.

How module “Shroud of Darkness” or even more generic “FoW controller” would be more easy to use than 1 call to IGameCallback::hideTiles()?

“clearly defined API” of IGameCallback is already not enough for object mechanics. Just see how many low level calls ( sendAndApply() ). So layered design is broken (there is in fact no layer abstracting netkacks). And I also hate huge classes like IGameCallback. So I`d like to grant access to scripts for directly use Packs (but also restore layer of netpacks abstraction however allowing scripts in this layer.)

To be fair ~50% of direct access to net packs is for open window/ show info messages. So this can be fixed by introducing two methods to callback.
Although something like this template would be much better solution at least for native code:

cb->sendPack(FoWChange(tiles, player, mode));

(mostly to avoid repetitive create pack, init its field one by one, make sure that nothing is missing, send pack)

Anuway - why would modder prefer to use modules instead of accessing net packs directly? Let me rephrase:

Or using stables example - why should I prefer to construct it from two modules and configure them if I can just send two netpacks in code - give bonus and change creatures?

Time-wise netpacks looks to be more easy for me (at least now). This may be useful for “complex” objects but definitely not as far as turning every object in such module and not with generic API.

Great solution also for scripting code, because it requires only exporting of CPack classes to python and callback. The amount of classes to be exported to python is so high only for Scriptable MapObject feature that we must automate it somehow(pygccxml,Py++). With automatic python generation you will just tell generator to export your new pack.

Agreed. This is better than modules. Clean callback of numerous helpers. Make them kinda library (with responsibility of creating single packs or sequences for particular cases) and Callback will shrink to small API mostly only for sending packs.

The problem here is that (IIRC) such method has to be template. Othervice it will send base class (CPack) without any payload. Not a problem for native code but this won’t allow defining new net packs in scripts and we definitely would need client-server interaction for scripts.

Possible solution would be to create one pack for scripts that will contain pointer to payload with Python data and let serializer do its job.

Sending method is normal method, OTOH packet class shall be registered and registration is pure template magic.
To allow adding new packet directly in scripts we need invoke pythons serializer in base scripted packet classs “serialize” method and AFAIK direct base class for python object is some class template in binding library so it might be tricky and depends on binding library obviously.