SPAWNING CREATURES! (A little weird but works)

Discussion in 'General Modification' started by Agetian, Aug 14, 2004.

Remove all ads!
  1. Agetian

    Agetian Attorney General Administrator

    Joined:
    Aug 14, 2004
    Messages:
    2,526
    Likes Received:
    0
    Hello, everyone!

    I thought it might be interesting for people to finally figure out at least
    some way to spawn new creatures and items on maps. Well, I've found out one
    of the possible ways to do that - it's a little weird (it's definitely not
    the way it's supposed to be ;) but at least it WORKS.

    So...

    1. You have to have patch 2.0 installed so that you have access to the console
    (you need the console to find out the player's coordinates).
    2. You have to know which creatures already exist on which map.

    The first thing to do is to find out the (x,y) coordinates for the point where
    you want to spawn a creature or an item. To do this, you'll have to actually
    go to that map in the game and position your first hero in the exact place where
    you want the creature/item to appear. Then, open up the console and type the
    following:

    from utilities import *
    location_to_axis (game.party[0].location)

    Note that the first line is essential to plug in the necessary python library.
    I think you can type it only once per game.

    You'll be given a pair of (x,y) coordinates with a letter "L" attached to them,
    e.g. (404L, 513L). I think that the letter "L" stands for "long" (the data type).
    Anyway, now you'll have to find a script for ANY creature that's already on the
    map. For example, let's modify the Young Acolyte that appears in the very begin-
    ning of the game if you play as a Neutral Evil party. The creature we modify
    will be used as a "spawner" (its heartbeat script will be modified in such a
    way to spawn creatures into the map, but only once).

    Find the file "py00195young acolyte.py" in "data\scr" (you should unpack it from
    TOEE3.dat if you haven't done that before). Open it for editing. Inside, you
    should find a string that looks like this:

    def san_heartbeat ( attachee, triggerer ):

    This line is an entry point for an event which happens when the creature first
    appears on the map and at certain moments in the future (quite often anyway,
    every 5-6 seconds or so). So, we have to modify this script. Originally, the heartbeat
    script for the Young Acolyte looks like this:

    Code:
    def san_heartbeat( attachee, triggerer ):
    	if (not game.combat_is_active()):
    		for obj in game.obj_list_vicinity(attachee.location,OLC_PC):
    			if (is_safe_to_talk(attachee,obj)):
    				obj.begin_dialog(attachee,1)
    				game.new_sid = 0
    				return RUN_DEFAULT
    	return RUN_DEFAULT
    
    We'll add an extra "if" branch to spawn our creatures. We're going to use a
    global game variable to store the information if the creatures were already
    spawned or not (this is done to prevent consequential spawn of creatures every
    three-five seconds). I'm using global variable #500 since I think it's not
    used by anything else in the game. I think it's OK to use numbers 2000-10000
    anyway (anything above 10000 might not work correctly !)
    So, modify our script to look like this:


    Code:
    def san_heartbeat( attachee, triggerer ):
    	if (not game.combat_is_active()):
    		for obj in game.obj_list_vicinity(attachee.location,OLC_PC):
    			if (is_safe_to_talk(attachee,obj)):
    				obj.begin_dialog(attachee,1)
    				game.new_sid = 0
    				return RUN_DEFAULT
    	if (game.global_vars[500] == 0):
    		game.obj_create( 14252, location_from_axis (483L, 503L) )
    		game.global_vars[500] = game.global_vars[500] + 1
    	return RUN_DEFAULT
    
    The number 14252 is the proto ID of our creature (14252 is a bugbear, by the
    way). The coordinates (483L, 503L) are close to the starting position on the
    map. The global variable #500 is tested if it's zero or not. If it's zero, the
    creature is spawned and the variable is modified in such a way that more
    bugbears won't be spawned.

    Note that you can also add a variable to store our creature object so we can
    make further modifications to it (e.g., rotate it). For example, modify our
    IF branch to look like this:

    Code:
    	if (game.global_vars[500] == 0):
    		mycre = game.obj_create( 14252, location_from_axis (483L, 503L) )
    		mycre.rotation = 1.5
    		game.global_vars[500] = game.global_vars[500] + 1
    
    You'll see that the creature will have its starting angle modified.

    P.S. In order to test the new creature, start a new game and create a Neutral Evil party. The new bugbear should walk near the starting point of the neutral evil party!

    Creature spawning as it should be
    ---------------------------------

    Currently I'm very close to disclosing how the creature spawning works in ToEE
    (I mean, the "real" way of spawning creatures through creating .mob files). It
    turns out that all the .mob files are named like this:

    G_xxxxxxxx_xxxx_xxxx_xxxx_xxxxxxxxxxxx.mob

    Where "x" is a hexadecimal digit. These numbers serve as a GUID - they don't
    seem to contain any unit data. The internal structure of .mob files is still
    shrouded for me, though I know where the creature coordinates and creature
    type are located. By adding a .mob file you spawn a unit. I don't want to
    disclose all the material I have right now because I might be mistaken in
    something, but I'll try to keep you informed about my progress.
     
    Last edited: Aug 14, 2004
  2. Raaagh

    Raaagh Member

    Joined:
    Dec 4, 2003
    Messages:
    46
    Likes Received:
    0
    Holy crap. Good job!!

    One question that I have though.. In a few of the conversations, such as the Hedrack spawning, it uses attachee.location -2 or something. Now, where does the -2 fit in? Does it take -2 off both X/Y coordinates? Or does it do a radial thing according to the rotation of the creature?
     
  3. Agetian

    Agetian Attorney General Administrator

    Joined:
    Aug 14, 2004
    Messages:
    2,526
    Likes Received:
    0
    OK, here's what I did to find out the answer to your question (console commands):

    from utilities import *
    loc = location_from_axis (2L, 1L)
    location_to_axis(loc - 1)
    location_to_axis(loc - 2)
    location_to_axis(loc - 3)
    location_to_axis(loc - 4)

    Here's what I saw in the console as a reply:

    (1L, 1L)
    (0L, 1L)
    (4294967295L, 0L)
    (4294967294L, 0L)

    Turns out that when you subtract 1 from the whole location, it first subtracts it from the X coordinate until it's zero. Afterwards it subtracts 1 from Y and changes X into the maximum value of the X coordinate. Then it once again removes 1 from X until it reaches zero, and this can be repeated until (X=0,Y=0) state is reached. Such behavior takes place because a location as such (the "loc" variable in my example) is, in fact, one huge number created from 2 coordinates. By the way, it's easy to deduce how those location_from_axis and location_to_axis commands work by looking at the "utilities.py" script, and if you look at the formulas used there it'll be easy to find out that the above statements I gave are true.

    Hope that helped.
    - Agetian.
     
  4. Raaagh

    Raaagh Member

    Joined:
    Dec 4, 2003
    Messages:
    46
    Likes Received:
    0
    In that case, is it possible to add/subtract from the y-coordinate first instead of the x-coordinate without messing with the huge number needed for the Y-coordinate?
     
  5. Agetian

    Agetian Attorney General Administrator

    Joined:
    Aug 14, 2004
    Messages:
    2,526
    Likes Received:
    0
    Sure, why not?
    Try this:

    from utilities import *
    loc = location_from_axis (10L, 10L)

    Suppose you have a location stored in "loc" (in this case it's (10,10) and you need to change it to (10, 11). To do this, try the following:

    x, y = location_to_axis (loc)

    Now, variables "x" and "y" (could be anything, anyway) store the individual components of the variable "loc". So...

    y = y + 1
    loc2 = location_from_axis (x, y)

    Now, "y" will be changed and "loc2" will store the newly modified coordinate! Enjoy!

    Hope I helped once again. ;)
    - Agetian
     
  6. zhuge

    zhuge Established Member Veteran

    Joined:
    Sep 27, 2003
    Messages:
    484
    Likes Received:
    0
    3 cheers!! :)
    Once we get the details of this problem sorted out, we can make a post at Atari and finally get some more people working on mods.

    I was thinking whether the spawning stuff could be written in a file that is accessed only at the start of the game (that is to prevent respawning). Such a file would be pc_start.py

    I think someone posted a list of map numbers sometime ago. If we can use a function that specifies map number (which map we want it to spawn to), and also specifies map coordinates (which you guys are working on) then I believe we can spawn stuff where we want it to be.

    Here's a related thread:
    http://www.co8.org/forum/showthread.php?s=&threadid=271
     
  7. Agetian

    Agetian Attorney General Administrator

    Joined:
    Aug 14, 2004
    Messages:
    2,526
    Likes Received:
    0
    I think that the creatures shouldn't respawn when I spawn them my way because I use a global game variable to check whether we've already spawned our creatures or not.
    I haven't found a function which would take a map number as a parameter... But I'll look into it. If such a function exists, I'll try to locate it. Experimenting with pc_start.py might prove interesting (but only if we find a way to spawn creatures wherever we want to).

    Anyway, I'll be posting some more updates in the next several days. Hopefully I'll get some more facts about the .mob files too...

    P.S. Maybe someone wants to make this topic sticky? (In case it's important, of course).

    Thanks for being responsive and pointing out important questions! Keep 'em coming, so I'll know what areas I'll have to work on! :)

    - Agetian
     
  8. zhuge

    zhuge Established Member Veteran

    Joined:
    Sep 27, 2003
    Messages:
    484
    Likes Received:
    0
    I think the function that I saw previously was:
    game.fade_and_teleport( time#, #, #, map#, x#, y# )

    which is actually used to transport PCs from one map to another (used when you agree to let one the temple priests, eg Romag, blindfold you and lead you to Hedrack). Wonder if there is another similar function for spawning creatures.

    For a list of map numbers, I believe by opening up maplist file in modules you can see a listing of these.

    Phalzyr's list of Python commands:
    http://www.co8.org/forum/showthread.php?&threadid=372
    command example:
    obj.move( location_from_axis( 491, 541 ) )

    Can we use game.obj_create() to make chests and other interactable items? Would be great if we could make chests and get its contents customized (perhaps by making an entry in InvenSource.mes?)

    If we enable a new NPC in protos.tab, can we make this new NPC a store vendor via dialog scripting? If so we would have opened up lots of new modding opportunities.


    Looked through the code. Provided game.global_vars[500] does not flag something else and the game saves the variable intact, it should be good to go.
    To be safe we really should have a good listing of all game.global_vars and what they code for.

    EDIT: Stickied as requested. I sometimes forget my duties when I get carried away. :)
     
  9. Agetian

    Agetian Attorney General Administrator

    Joined:
    Aug 14, 2004
    Messages:
    2,526
    Likes Received:
    0
    Yes, you can create chests and all the interactive objects with that...
    As long as a proto ID exists for it. You can make doors, chests, etc.

    By the way, here are the latest updates on my research:

    1) The scripts that you use for spawning via a heartbeat event MUST
    contain the "from utilities import *" line somewhere in the top (near
    the "from toee import *" line), because it's the line that is needed
    for location_to_axis and location_from_axis to work. If the line is
    absent, the whole script (not just the spawning) may not work correctly.
    I'm posting this because certain people from my ToEE research team reported
    script errors after making a spawning script, and the above solution
    fixed the problem.

    2) I did some research in MOB file editing. I unpacked the whole module
    (ToEE.dat in modules/ folder) so that all the files are visible and accessible
    for me. Then, I played a little bit with the *.mob files in Tutorial Map 1.
    So:

    WARNING: EVEN THOUGH THIS INFORMATION WAS THOROUGHLY TESTED, IT MIGHT STILL
    BE INACCURATE. I POST IT HERE BECAUSE I WANT SOMEONE ELSE TO TRY IT! USE AT
    YOUR OWN RISK!

    NOTE: Offsets for X/Y coordinates seem to be valid ONLY for the 192-byte
    .mob file in Tutorial Map 1 designated G_0247AC97_992C_4CA6_B5F4_D29EB2CF755E (the tutorial chest A). The coordinates in other mob files are located at different offsets.

    2.1) Deleting a .mob file removes a creature/item from the game.
    2.2) Creating a .mob file with any compatible file name adds a creature/item
    into the game.
    2.3) The creature type (proto ID) is stored in a mob file at the offset
    0x0000000C. It is stored in the "reversed" (little-endian ??) form. For example,
    the object "Tutorial Room1" has the proto ID of 14434 (hexadecimal 0x3862),
    but the bytes are stored in the following order: 0x62 0x38
    2.4) The offset 0x0000004B stores the coordinate X (4 bytes), also in the
    reverse form (if the coordinate is 450 = 0x000001C2, then it's stored as
    0xC2010000).
    2.5) The offset 0x0000004F stores the coordinate Y (4 bytes) in the same way
    as the coordinate X.

    I have no idea what the other bytes in the *.mob files mean, but I'll try to
    figure it out later. Most of them either have no effect or just crash ToEE
    at the startup. It's interesting that if ToEE starts to crash at startup, it'll
    go on crashing even if the proper bytes are restored. The solution for this is
    the deletion of the weird-named files in the "maps" folder (the weird-named ones
    are the ones like: yynU-cvnz-6-enZ

    I've been successful in spawning new items and creatures by copying the MOB file I've provided above (the tutorial chest A) to a different-named MOB file in the same directory (the name should be formatted as follows: G_xxxxxxxx_xxxx_xxxx_xxxx_xxxxxxxxxxxx.mob, where "x" is a hexadecimal digit) and then changing its ID, X and Y values.

    Alrighty then, I'm off for several days to do some more research. :)
    If you have any questions, feel free to post them.

    - Agetian.
     
    Last edited: Aug 15, 2004
  10. Agetian

    Agetian Attorney General Administrator

    Joined:
    Aug 14, 2004
    Messages:
    2,526
    Likes Received:
    0
    A quick update about the inventory:

    When dynamically spawning creatures/items/chests/etc. (using the scripting method I explained above), you can add items to the inventory by issuing a "create_item_in_inventory" command.
    For example, the following creates a Lareth's Dresser and adds a chainmail to it:

    Code:
    		mybox = game.obj_create ( 1045, location_from_axis (483L, 506L) )
    		create_item_in_inventory ( 6049, mybox )
    
    I haven't figured out how to replace the inventory of objects spawned through .MOB files yet, maybe next time I'll come up with some ideas...

    By the way, there's also a way to delete a creature/item/object you don't need via scripting. Just call this in its heartbeat script:

    triggerer.destroy ()

    You can also use that object's heartbeat to spawn all the creatures and items you need and then get rid of him via destroy().

    Have fun!

    - Agetian
     
    Last edited: Aug 15, 2004
  11. Agetian

    Agetian Attorney General Administrator

    Joined:
    Aug 14, 2004
    Messages:
    2,526
    Likes Received:
    0
    A quick update about *.mob files:

    Turns out that mob files are a lot more complex than I thought. There are different-sized *.mob files in the game and they seem to have different structure (different offsets are used for different things, and I can't figure out the whole system behind all this yet). I can't find the way to modify the inventory of a creature/object using the .mob editing right now, and if I change the object type (from container to creature or vice-versa) the game crashes. So, until I make further (very deep) research, I propose that everyone use scripting to modify dynamic objects. So far, we can do this:

    SCRIPTING WAY
    --------------------
    1) Adding and removing creatures/objects/items
    2) Modifying creature/object inventory
    3) Modifying creature/object/item starting properties
    The objects use scripts associated with them in protos.tab

    MOB-EDITING WAY
    -----------------------
    1) Removing creatures/items/objects (provided that the whole module is unpacked)
    2) Adding creatures/items/objects (very limited due to unknown file structure)

    So...
    Anyway, I'm off to do some more research. Since now I'll mainly research the *.mob file structure. If you have questions about scripting the spawning, then feel free to ask.

    P.S. One of my friends has created a correct vendor via scripting (he spawned him my way, created his inventory, and provided a scripted dialog to do the barter).

    Have fun!
    - Agetian
     
  12. Agetian

    Agetian Attorney General Administrator

    Joined:
    Aug 14, 2004
    Messages:
    2,526
    Likes Received:
    0
    Here's a small update on global game variables (global_vars[]):

    I've checked all the game scripts to check the global game variables
    that the standard game campaign might use. The following numbers are
    used in the official scripts:

    0, 2, 3, 4, 5, 9, 10, 11, 12, 13, 14, 15, 18, 21, 22, 23, 24, 25,
    26, 27, 28, 29, 30, 31, 32

    No other numbers are used in the official scripts. Anyway, I assume
    that it's fairly safe to use numbers above 100 for object spawning
    purposes. Global game variables are preserved by the game intact and
    are correctly restored during game loading (I tried saving/quitting
    ToEE and rebooting/loading), therefore, it's also safe to use my
    method for object spawning.

    One can also try using global.game_flags[] instead of global.game_vars[],
    but I haven't researched that yet (the game itself uses A LOT more flags
    than variables - I've seen flag numbers 340+ used by the game).

    By the way, you can also define an entry in protos.tab for the event
    called "san_first_heartbeat" and create that event in the script file.
    San_first_heartbeat runs only once per game (I think, though I might be
    wrong, so using global variables is SAFER).

    An update on scripting itself:

    If the script you want to modify DOES NOT contain the "san_heartbeat"
    event, you'll have to add the event to the script yourself. However,
    simply adding the event to the script text does not enable it in the
    game. You'll have to modify the protos.tab file as well to enable the
    "san_heartbeat" event (there's a column in protos.tab that contains
    the script file ID for the san_heartbeat).

    AN IMPORTANT NOTICE

    Currently I've found out that on larger maps the game may delay the execution of the heartbeats (both san_heartbeat and san_first_heartbeat) until the player comes closer to the creature. Therefore, spawning creatures using objects which are far from the party in the beginning might not always work.
    HOWEVER, currently I am working on a small expansion pack which will include a modified protos.tab and several *.mob files. This expansion pack could be used to spawn creatures easily on most maps. It'll also override the recently found problem with the delay of heartbeat events.

    I'll need your help in order to finish the project. I'd like to know what exactly is needed to make a 100% correct new NPC except making an entry in protos.tab and description.mes.

    Thanks in advance.
    - Agetian.
     
  13. Agetian

    Agetian Attorney General Administrator

    Joined:
    Aug 14, 2004
    Messages:
    2,526
    Likes Received:
    0
    In case nobody noticed, I posted a new version (0.3) of a thing called "The Spawner Mod" in the corresponding thread. This mod allows to make the process of spawning creatures on all ToEE maps easy as a piece of cake (well, about that, anyway).

    I won't be posting more updates in the next few weeks, but later (maybe in the next several months) I'll come up with a whole modding studio for ToEE which will include a protos editor, a mes editor, a dialog editor, an object spawner, and graphical area cutter/combiner (to combine the individual 256x256 parts into a map and vice-versa).

    - Agetian
     
  14. Raaagh

    Raaagh Member

    Joined:
    Dec 4, 2003
    Messages:
    46
    Likes Received:
    0
    Phalzyr's Proto Editor pretty much has all the fields labelled for NPCs. The thread on protos.tab which is stickied below should help too.

    Basically, it's pretty straight-forward. You just have to define the name, "number name" for the find_npc function, stats, natural armor, etc etc.
     
  15. zhuge

    zhuge Established Member Veteran

    Joined:
    Sep 27, 2003
    Messages:
    484
    Likes Received:
    0
    Having some problems getting the spawner mod installed, I think I missed out on a file or two. You mentioned that the protoID, x and y coordinates are figured out for mob files. Does that apply to all mob files? Or only for the 192-byte .mob file in Tutorial Map 1.
    Any new info on figuring out the rest of the bytes for mob files?
    Thanks.
     
Our Host!