If you mena scripts, they should return map object serial (or wrapper) when it’s added to the map. or we cna poll the engine for an object with specific coordinates for example.
If you mean map editor, it should be initalized the same way as modded game, and from the outside we don’t need to know object IDs - but of course can check them, as they are part of object itself.
“it should be initalized the same way as modded game” - nearly impossible - this requires that both editior and all game installations where map will be used must use exactly same mods (not just have required, but dont have other. Also this brakes possible forward compatibility between map and newer versions of mod - adding new object change IDs of objects in other mods). So its a very bad idea to use dynamically allocated IDs in map file.
Yes, that’s exactly what I mean. Obviously you need to have all the mods installed and configured before the map using it runs.
But the trick is to configure mods automatically (including turning unnecessary mods off), according to information included in the map itself.
Now it works just the same way, just you need to have separate installation for each map-mod and pray it’s correct
Does mods that map not using explicitly are really “unnecessary” for a player? I wish to able to play (f.e. ) a map with random towns with new town that I just created.
BTW, no separate installation is needed with ERAII.
I believe it’s good idea to give mapmaker an option: allow custom mods or allow ONLY mods used for map.
But we’re not going to see map editor anytime soon
reads this page
And this is why I’m against numeric id’s - it requires too much magic like “correct load order”.
regarding wiki:
Numeric Id’s - not recommend but available.
Strings - for access to specific object\creature
Wrappers - for cases where string id may be unknown (iteration over all creatures, to get creature type in slot #X of hero army)
I may repeat myself a bit but:
Accessing all creatures in game - for example increase HP for all level 7 creatures:
for creature in engine.creatures.all()
if (creature.level == 7) #or get_level(creature). Whatever
creature.hp += 10
Id’s are not needed here (neither numeric nor strings). Modder can’t know what mods user have installed and how many creature he has.
2) accessing specific creature - by string
unicorn = engine.creatures("UNICORN") # I prefer lower-case here but doesn't matters much
unicorn.hp += 10
Numeric ID’s are a bad idea here - if modder will add or remove creature he will have to check all usages of specific id’s in his mod
Resolving conflicts of string id’s:
mod does not have access to string id’s of other mods (unless they’re marked as required or something like “optional”)
all id’s are prefixed by mod name. If this ID doesn’t have any conflicts it will be aliased to short version without prefix (typing modname.creature each time will be annoying)
Maps can be considered as mod. All objects on (saved) map will keep their id’s as string so there won’t be need to have specific load order for mods or to have exact same versions for them and so on.
Good point but it shouldn’t be strict. What if map have some bugs that were not fixed by author and somebody released patch as separate mod? Some warning would be enough.
I see you’re mising the point. Modders may know well what does their own mod contain, but there are also scripts. A script should be universal, at most allow checking if some other mods are avaliable.
A script (such as growing banks, Artificer) may not know exactly the list of packages installed, but should be able to handle it with backward and forward compatibility. Forward copatibility makes no use of string IDs over numeric IDs.
The scripter may need IDs, the modder shouldn’t. The author of package can just have wrappers all the time.
Well, that’s why there are multiple conversion / access function - they return ID at run time. Also, every “create” function also returns new ID. You should know that good programming practice already
Also, there is another isue - ERM handling. In ERM only numeric IDs are avaliable and people are used to them, so they should stay just for that reason.
The same is true for VCMI. Armed instances, dwellings - they all use numeric IDs to store information about creatures. If we want to read or change teh creature type, it still will be integer.
unicorn.hp += 10
Bah, no such thing. It’s all handled by Bonus system.
I don`t think that Artificer can be fully forward compatible (It has settings for particular artifacts), but will be backward compatible with string IDs and will work with new artifacts but ignore them.
But Artificer generally upgrades/changes arts for a price. The script can read tables about artifacts and use it properly independently what is inside.
So if some artifacts belong to same group (ie. Artifact Set) and can be sorted by some value (ie. Artifact Merchant cost), it can surely work with this (most actual upgrades in artificer already work this way, but stuff is precalculated).
Well, it makes me think that universal Artificer is actually possible thanks to new bonus system :). Would need a lot of smart scripting, but that’s amazing!
And how that my “iterate over all creatures” script is not universal? Can you post some examples of what scripts should look like?
Now you’ll just have to explain good practices to every modder!
I was rather complaining about “WoG.creatures(3)” code. If somebody will modify or just update WoG and creature with id 3 will change - that may cause troubles.
And I won’t surprise that some modders will use code like this:
archangel = 150 # id from wog
give_bonus(archangel, some_bonus)
Why? They can be either some novice modders that just want to make it work or “uber-c00l” modders who want to optimize 0.0001 ms from function call
Such code is obviously incorrect - WoG is not necessary first mod to load but it will work for modder. With strings ID’s and wrappers such code will be impossible.
VCMI uses id’s from time to time (e.g. dwellings) but unit types in armed instances are pointers to CCreature (url ). And most of rewrites usually include replacing ID’s with pointers (like replacing artifacts ID’s with art instances)
One more issue we need to solve - mods updating. If we have saved game from old version it should be possible to load it even with changed version of mod (as long as changes are not significant). Restarting long campaign\map only because there was minor update to mod is silly. So there should be either some way to update all ID’s or make sure that new objects will be located at the end of list.
I think storing all scripts along with save (but not other mod resources) would solve this problem much better.
And pointers to instances will have to be replaced by smart pointers… because some info may leak through these pointers (and it’s dangerous too – some pointers are deleted). ATM the only safe way to obtain info is callback. Calling methods on objects in the way we do it is a hole in safety.
That’s right. If I were to write dwellings code now, I’d use ptr to CCreature. Most likely they were implemented before I devised that template monstrosities allowing seamless serialization of CCreature*.
I certainly wouldn’t call using IDs instead of ptrs (or some other kind of handle if talk about scripts) a good programming practice. ID is a position of a thing in vector, this is an implementation detail that script authors shouldn’t care about. When there’ll be many mods (possibly having multiple versions) IDs order won’t be something that should be relied on. String IDs are both more expressive and robust.
Yes, I do. We can make simple wrappers for pointers but then we have to forget about cheatproofness and we have trouble when something is done with those pointers. It would be just a quick hack that sometimes fails – not an elegant solution to the problem.
I think Objects should be exposed (inc class meaning - like derivating from engine classes etc.) but pointers shouldn’t since they are common source of crashes and instability (if modder didn’t use them enough well), and are subject to hackish memory editing (exploit in wrapper for pointers theoretically allow changing any data without informing the engine).
I disagree on hiding internal integer indexes from scripting language. Instead I suggest to just protect them from “Array out of range”, and perhaps even give them a custom type so type control can get the problems of mixing MonsterID with ObjectID away. Of course the numeric ID should be said “implementation details” - like some practices work in C/C++ but isn’t portable if they depend on implementation detail.
Numeric IDs with perhaps very simple wrappers are good enough to not use pointers, and is easiest substance to make pointerless pointer-replacement. If you insist to have pointers in script like smart pointers in engine - please ask me about Virtual Adress Machine concept which would make almost all benefits from real pointers, have full 64bit adress range without wasting memory for unallocated things, is much more safe than real pointer, and making real adresses internal implementation detail the pointers from Virtual Adress Machine are trivial to (de)serialize as deep as you want granted they aren’t mixed with ordinal pointers or non-compatible wrappers for them. It would make a bit CPU overhead since resigning (mostly) from direct pointers - but it may be worth to see since it simplifies working with any pointers very much. Note that it may require wrappers for vectors/lists/C_tables until we use only virtualized pointers or no pointers inside (so if only virtualized pointers are member of containers - no problem - else we may want to use benefits of these containers with a little risk on the model (but bugs wouldn’t be hard to fix) but also inability to use any direct pointer inside the structure which is not just implementation detail).
It’s not a requirement. Object-oriented API with objects that are not simple wrappers for game objects will be cleaner, unless you are writing in purely functional language. By complicated wrappers I mean wrappers that perform quite extensive checks every time we want to set/get something – if we are allowed to do so.
So (4) becomes
for creature in engine.creatures.all()
if creature.level == 7
creature.hp = creature.hp + 10
(creature.hp and substituion are not simply forwarded to C++ objects, but trigger C++ wrapper class methods that perform appropriate checks, and then forward the action to CStack*).