1. Hey Guest, is it this your first time on the forums?

    Visit the Beginner's Box

    Introduce yourself, read some of the ins and outs of the community, access to useful links and information.

    Dismiss Notice

[Solved] Hook call order

Discussion in 'Modding Help' started by Vermilicious, Mar 12, 2016.

  1. Vermilicious

    Vermilicious Ballista Bolt Thrower

    Messages:
    232
    Another short question. In what order are different hooks called? Is onTick(CSprite) called before or after onTick(CBlob) for instance? I would assume the blob is initialized before the sprite, but how about onTick for example? Is the order even guaranteed?

    For the curious, the reason I ask this, is that I check for certain conditions in my blob script, and update some variables in the blob object (in a data object). I call them "events". For the most part these should be handled in the sprite script (such as updating a sprite, or playing a sound), but not necessarily everything. For instance, one case could be restoring health to a blob/building when it changes ownership, and so I need to handle that in a blob script, not a sprite script. So if the sprite script handles the event and disables it when it's done, I wonder if my other scripts will be triggered at all. I guess I could add one flag for each type of script and make each script do the same thing.

    Code:
    namespace Entity {
    
      const string NAME = "Entity";
    
      shared class Data {
     
        //Options
     
        EventData@[]  oAutoDoOnceEvents;                                            //Array of automatically handled do-once events
     
        //EventData (do-once)
     
        EventData     eBlobWasInitialized;
        EventData     eBlobWasDestroyed;
        EventData     eBlobChangedOwnership;
     
        //Internals
    
        u32           iGameTime;
     
      }
    
    
    
      shared class EventData {
    
        string        oSound                    = "";                               //Sound file path/name
        string        oSpriteMode               = "";                               //Sprite mode (animation name)
        int           oSpriteState              = -1;                               //Sprite state (animation frame number)
        f32           oBlobHealth               = -1.0f;                            //Blob health
        int           iTime                     = -1;                               //Time of event
     
      }
    
      //...
    
    }
    Code:
    void onChangeTeam(CBlob@ this, const int oldTeam) {
      //Retrieve data
      Entity::Data@ data = Entity::Blob::retrieveData(this);
      //Set event
      data.eBlobChangedOwnership.iTime = data.iGameTime;
    }
    
    void onInit(CBlob@ this) {
    
      Entity::Data entityData();
      entityData.eBlobChangedOwnership.oSound = "/VehicleCapture";
      entityData.eBlobChangedOwnership.oSpriteMode = "occupied";
      entityData.eBlobChangedOwnership.oSpriteState = 0;
      entityData.eBlobChangedOwnership.oBlobHealth = this.getInitialHealth();       //Full heal on changed ownership
      entityData.oAutoDoOnceEvents.push_back(entityData.eBlobChangedOwnership);
      Entity::Blob::storeData(this, @entityData);
    }
    
    Code:
    void onTick(CSprite@ this) {
    
      //Obtain blob reference
      CBlob@ blob = this.getBlob();
    
      //Retrieve data
      Entity::Data@ data = Entity::Blob::retrieveData(blob);
    
      //Iterate through all auto do-once events
      for(uint i=0; i<data.oAutoDoOnceEvents.length; i++) {
    
        //Keep a reference to event
        Entity::EventData@ eventData = data.oAutoDoOnceEvents[i];
     
        //Check if active event
        if(eventData.iTime >= 0) {
     
          //Check if sound is set
          if(eventData.oSound != "") {
       
            //Play sound
            Sound::Play(eventData.oSound);
       
          }
       
          //Check if sprite mode is set
          if(eventData.oSpriteMode != "") {
       
            //Change mode
            Entity::Sprite::setMode(this, eventData.oSpriteMode);
         
          }
       
          //Check if sprite set is set
          if(eventData.oSpriteState >= 0) {
       
            //Change state
            Entity::Sprite::setState(this, eventData.oSpriteState);
         
          }
       
          //Unset
          eventData.iTime = -1; //I need to do stuff in the blob script too!
       
        }
     
      }
    
    }
     
  2. Asu

    Asu THD Team THD Team Forum Moderator

    Messages:
    1,580
    Since KAG shouldn't thread this behavior, it's fair enough to assume called hooks, except for onRender, are called in a specific order. You could try printing messages for every hook you want to test. However, this is largely subject to change between updates, especially considering that since TR's engine is pretty much merged with KAG's now. You definitively shouldn't base your code behavior on the hook call order. For what it's worth, if you need a strict order, do this for example :

    Code:
    void onTick(CBlob@ this)
    {
         CSprite@ sprite = this.getSprite();
         if (sprite !is null)
         {
              // Sprite stuff here
         }
    }
     
  3. Geti

    Geti Please avoid PMing me (poke a mod instead) THD Team Administrator Global Moderator

    Messages:
    3,730
    You can indeed find this out from just printing stuff but I'll throw you a bone.

    Order of update per blob: brain, movement, shape, inventory, attachment, blob scripts. THEN death processing, quantity changed and health changed, reading the message/command queue (but all of those "can" happen at different points (eg the command hook can be called right away) depending on client/server setup). Sprite update seems to be done in map.

    As Asumagic says, you probably shouldn't really "rely" on this order for proper behaviour. Furthermore, there are specific hooks for a lot of what you're talking about. Check out manual/interface/hooks.txt

    It "is" technically possible that this might change in future but like, not very likely at all. Hasn't changed since about 2013 ;)
     
  4. Vermilicious

    Vermilicious Ballista Bolt Thrower

    Messages:
    232
    Yeah, I guess I could place it in the blob's onTick instead, and check for the presence of the particular type of object, be it sprite, brain or whatever. Makes sense. It isn't very important whether it's handled during the same tick or the next either. Thanks. Tagged as solved, and archived for the future modders :o)

    Solution:
    Code:
    //Called on every tick.
    void onTick(CBlob@ this) {
    
      //Retrieve data
      Entity::Data@ data = Entity::Blob::retrieveData(this);
    
      //Set time
      data.iGameTime = getGameTime();
    
      //Obtain references to other objects
      CSprite@ sprite = this.getSprite();
    
      //Create EventData handle
      Entity::EventData@ eventData;
    
      //Iterate through all auto do-once events
      for(uint i=0; i<data.oAutoDoOnceEvents.length; i++) {
    
        //Keep a reference to event
        eventData = data.oAutoDoOnceEvents[i];
    
        //Check if active event
        if(eventData.iTime >= 0) {
    
          //Check if valid sprite object
          if(sprite !is null) {
    
            //Check if sound is set
            if(eventData.oSound != "") {
       
              //Play sound
              Sound::Play(eventData.oSound);
       
            }
       
            //Check if sprite mode is set
            if(eventData.oSpriteMode != "") {
       
              //Change mode
              Entity::Sprite::setMode(sprite, eventData.oSpriteMode); //CSprite.SetAnimation
         
            }
       
            //Check if sprite state is set
            if(eventData.oSpriteState >= 0) {
       
              //Change state
              Entity::Sprite::setState(sprite, eventData.oSpriteState); //CSprite.animation.frame
         
            }
       
          }
     
          //Check if blob health is set
          if(eventData.oBlobHealth >= 0) {
     
            //Change health
            blob.server_SetHealth(eventData.oBlobHealth);
       
          }
     
          //Unset
          eventData.iTime = -1;
     
        }
    
      }
    
    }
    As for hooks, I'm using them all (those that have some possible use-case anyway) to set events. Like so:
    Code:
        //EventData (do-once)
    
        EventData     eBlobWasInitialized;
        EventData     eBlobWasDestroyed;
        EventData     eBlobDidCollide;
        EventData     eBlobDidStopCollide;
        EventData     eBlobWasHit;
        EventData     eBlobDidHitBlob;
        EventData     eBlobDidHitMap;
        EventData     eBlobDidCreateInventoryMenu;
        EventData     eBlobDidInventoryAdd;
        EventData     eBlobWasInventoryAdded;
        EventData     eBlobDidInventoryRemove;
        EventData     eBlobWasInventoryRemoved;
        EventData     eBlobChangedQuantity;
        EventData     eBlobChangedHealth;
        EventData     eBlobDidAttach;
        EventData     eBlobDidDetach;
        EventData     eBlobChangedOwnership;
        EventData     eBlobBubbleClicked;
        EventData     eBlobWasStatic;
        EventData     eBlobDidReload;
        EventData     eBlobWasPlayerSet;
        EventData     eSpriteWasInitialized;
        EventData     eSpriteWasScaled;
        EventData     eSpriteWasGibbed;
    
        //EventData (do-while)
    
        EventData     eBlobIsInitialized;
        EventData     eBlobIsDestroyed;
        EventData     eBlobHasInInventory;
        EventData     eBlobIsInInventory;
        EventData     eBlobHasAttachment;
        EventData     eBlobIsStatic;
        EventData     eSpriteIsInitialized;
    Code:
    void onDie(CBlob@ this) {
    
      //Retrieve data
      Entity::Data@ data = Entity::Blob::retrieveData(this);
    
      //Set event
      data.eBlobWasDestroyed.iTime = data.iGameTime;
      data.eBlobIsDestroyed.iTime = data.iGameTime;
    
    }
    
    void onCollision(CBlob@ this, CBlob@ blob, bool solid) {
    
      //Retrieve data
      Entity::Data@ data = Entity::Blob::retrieveData(this);
    
      //Set event
      data.eBlobDidCollide.iTime = data.iGameTime;
    
    }
    
    void onEndCollision(CBlob@ this, CBlob@ blob) {
    
      //Retrieve data
      Entity::Data@ data = Entity::Blob::retrieveData(this);
    
      //Set event
      data.eBlobDidStopCollide.iTime = data.iGameTime;
    
    }
    
    void onAttach(CBlob@ this, CBlob@ attached, AttachmentPoint @attachedPoint) {
    
      //Retrieve data
      Entity::Data@ data = Entity::Blob::retrieveData(this);
    
      //Set event
      data.eBlobDidAttach.iTime = data.iGameTime;
    
      //Check if event not set
      if(data.eBlobHasAttachment.iTime < 0) {
    
        //Set event
        data.eBlobHasAttachment.iTime = data.iGameTime;
    
      }
    
    }
    
    //And so on...
    (I'm not sure if I want to store any of the actual event data, because they're very specific to each type of event, and I won't ever be able to create an automatic handler that can tackle every conceivable thing. I wrote it just to handle a few common things that could be relevant to most entites, such as playing a sound or changing the sprite when it's hit, added to an inventory, attached, detached, changed ownership, was set static, was gibbed etc.)
     
    Last edited: Mar 14, 2016
  5. Geti

    Geti Please avoid PMing me (poke a mod instead) THD Team Administrator Global Moderator

    Messages:
    3,730
    Just a mild convenience thing if you only want to handle rendering and animation update in a sprite script, you can put them both there and not have them duplicated in the config.

    @verm this sounds a lot like reinventing the wheel though. Why not just write a script with the hook in it? why go to all this trouble to build an "event" system that's already handled by the hooks provided?
     
  6. Vermilicious

    Vermilicious Ballista Bolt Thrower

    Messages:
    232
    Just the convenience of using the generic "Entity" type for simple behavior through options rather than code. No need to look up the signature, no need for logic, a somewhat better naming scheme (subjective), and introduces events that lasts (not just that something was added or removed to/from the inventory, but if it was the first or last item). It might also be possible to invent entirely new types of events, or combinations. Let's say a player that dies plays a different sound depending on whether his pockets were full or not (maybe not the best example). If you create a new entity type you can supply your own types of events too. But, if this simple behavior isn't sufficient, you don't have to use it. You don't even have to attach the script at all. But by all means, I realize this is patchwork at best. Something like this should've been supported by the engine.

    (I honestly think I'm pretty happy about the way to go about things now, but I'm really starting to lose a bit of the motivation I had. I have to re-do both my projects before really making any progress. Is it worth it? I'm not sure. Feels like a rabbit hole. The worst of the starting-out frustrations are over as I've figured out how things work and how to best do basic stuff (data object etc), but the engine shortcomings are starting to gnaw on me a bit, and the code I have written is a real mess with new glasses.)
     
    Last edited: Mar 15, 2016
  7. Geti

    Geti Please avoid PMing me (poke a mod instead) THD Team Administrator Global Moderator

    Messages:
    3,730
    It is. That's exactly what scripts are.
    Code:
    void onDie(CBlob@ this)
    {
    	if(this.getInventory().getItemsCount() > 0)
    	{
    		//play one sound
    	}
    	else
    	{
    		//play the other
    	}
    }
    This is what I mean by solving a problem that's not there :p You're writing a lot of code just to serve your claimed benefit of avoiding writing code.

    All of it seems like it's somewhat motivated by a lack of actual gameplay focus - instead of writing fun things, you're building a generic event system in the hope that it'll make building gameplay behaviour simpler or more fun. It doesn't sound like something all that fun to work on in its own right though, so I'm not surprised you're getting demotivated.
     
    Fuzzle likes this.
  8. Vermilicious

    Vermilicious Ballista Bolt Thrower

    Messages:
    232
    You are restricted to certain specific events. They cannot be extended. You cannot invent new ones. And each handler require a very specific signature involving data you won't actually use anyway. An awful lot to get right just to play a sound or change the sprite when a building/structure is placed - a situation all too common. My example wasn't good, and I said as much, so there's no need to state the obvious with a code example of an if-statement. You can go along with this kind of MacGuyver-ism, and it can indeed be fun that way, but I just want something a little better - something that doesn't slap you in the face one day. It might have been naive. It might have been a bad idea. I'm sorry for trying, to you all, and to myself.

    I don't really want to be doing this, but it's not my fault things are like they are. When I first talked in this forum about how hard it was to mod stuff, I was told how lazy I was. Now that I've worked with it a couple of months (at times as a full-time activity) and found some flaws (in my view anyway), I try to improve upon it, for myself and others after me, because, as I've pointed out, there's not a whole lot of modded content being made, and being kept alive. In some heads around here, it seems that the modders, or would-be-modders, are themselves 100% at fault. I complained early about the lack of documentation for example, and the wiki and docs are still so thin, it's not even funny. I've received a lot of useful pointers lately, in particular from you Geti, which is just really basic know-how about how to best go about modding, but there's not enough spare time to slap it together into a little guide (it could've possibly saved me several weeks of meaningless work). Not for all the years KAG has been out there. To be honest, the game has slowly been dying for a long time. Sure, it happens to every game, we all know that, but it had every promise to flourish. It just wasn't taken seriously enough. Instead, people were driven away by frustrations over gameplay changes - things that should have easily been reverted with simple modding, or so one would think, but did it happen? No. Heck, even MM himself didn't want to work on the game anymore, and you, Geti, just recently stated yourself that you don't want to use this engine again. Sorry for the rant, but I'm just saying, perhaps the fault is not entirely ours.
     
  9. Geti

    Geti Please avoid PMing me (poke a mod instead) THD Team Administrator Global Moderator

    Messages:
    3,730
    I kinda agree with this. I've been working on my craft for some time but Michal's definitely focused on just getting shit done. I try to strike a balance between that and some holy architecture, but having worked on enterprise style code all through uni, I much prefer the hacky approach.

    With KAG, the reason so many of the scripts are so sprawling is because much of the gameplay development was done by feel. There was (and still is) no big design document with all the planned relationships laid out, balance checks and possible config vars planned. Starting from nothing and working towards no goal, working by how fun it feels is a sure way to make a mess.

    I think people give us a strange combination of too much credit and too little - a lot of work went into building KAG, especially for two people, but it's not well designed for anything other than blind enjoyment. Honestly, I think this is why the competitive scene has so much trouble, and why the game sits in such a funny niche (and perhaps why role-play is the most successful mod). It's just a big collection of fun ideas, re-balanced a few hundred times after the fact.

    This is all very dramatic. My point is that all the code you're writing has to be there in some form. You cant write the code for a behaviour without, at some point, writing the code for that behaviour. I definitely see the appeal of some sort of combinatorial event system - but I'm not sure you have a use case for it yet :)

    Wouldn't we all, though? You can't see everything coming. You're free to try, I'd just strongly encourage you to make sure your time is going somewhere practical.

    I'd argue that this is not the fault of code complexity - all the changes complained about (infinite shield jumping, slash timings, shield angles, archer speed, stun times, resource amounts, build speed, coin rewards, item costs, stomping) were generally a few lines of code or just a number somewhere.
    The quoted reason of the modders "back in the day" was that noone played modded servers, cause it warned you so much. Now that it doesn't warn you each time, and modded servers are commonly in the top player count servers, this doesn't hold as much water.
     
  10. Vermilicious

    Vermilicious Ballista Bolt Thrower

    Messages:
    232
    That's kinda funny, because the two are so far apart. I would say good luck to anyone who want to tackle a modern development project with a clear design plan (waterfall model), especially for a game. That doesn't mean you can't still be a Java evangelist (even though I would question it) or that you have to use duct tape (or whatever casual tools are at hand) like good old Angus. You can come a long way though, by thinking a little ahead and come up with some basic rules. For a game like KAG, where, as you say, balancing is important, it would've been a good idea to pull all relevant variables out of the code, for example. And, if you find out somewhere along the way that something isn't working too well, the longer you wait fixing it, the harder it's going to be. Might as well try to get it right the first time, within reason of course. Better spend 2 hours today than 10 hours next month, or a week next year.

    The reason, I think, that modded servers are doing better now than before, is simply because of the age of the game, and that most players are seasoned.. perhaps a bit tired of vanilla. The player base is small. And it's only going to shrink. Small peaks when the game is on sale, of course.

    Few games ever grow. The best examples I can think of are all made and run by Valve. How do they do it? By constantly adding "value" to its players. If people want cosmetics, give them cosmetics. KAG has cosmetics, but it was/is finite. KAG had new content for a while, but no longer. What exactly is going to bring people in? If the developer has given up on it, how are its players going to do anything else? THD's focus these days seem to be Trench Run. Is it going to be any different? Personally, I'm not a fan of it, and I suspect it won't be anywhere near the hit KAG was. If the philosophy, and "plan" is also in the same alley as KAG, well, I don't think it's going to last long. I would've spent more time on KAG instead. Perhaps, in the same time that was spent on Trench Run, KAG could've been cleaned up so much it could be called a sequel. But hey, that's just my opinion.
     
    Last edited: Mar 17, 2016
    Fuzzle likes this.
  11. Geti

    Geti Please avoid PMing me (poke a mod instead) THD Team Administrator Global Moderator

    Messages:
    3,730
    By having maintenance teams larger than our entire studio working on their games, actually.

    In terms of MM and I, that's true, though we're pretty done with it. I've got 2 non-THD projects that I'm looking to in the future. MM has Transmigration to "save". TR has been interesting, and if a community forms around it we'll be happy to sink a bit more time into it - but it was meant to be a much shorter project. You're right that it probably wont pull the same sales and so on as KAG. But it's been lovely not working on KAG, so there's that.

    If another few years of full time work was put into KAG, we'd not be able to reap the reward, honestly.

    The community in its current state simply wouldn't handle a re-release (OMG SHILL MONEY GRAB BULLSHIT), it'd be classic all over again (FINISH KAG), and not releasing it separately would result in at best a few tens of percent increase in sales. I'm a believer in the "polish up a" sequel model these days, but if we were going to do it for KAG, the date of first release would have been december 2012 or earlier. In my eyes, that ship has sailed.

    I said it to a similar comment on facebook (about putting the time into classic) - we (MM and I) make games because we are interested in making games. We are not at all interested in classic any more, and not very interested in KAG - it's just hard to want to work on something after almost 5 years. The intent with the interns is to bring in talented folks who care about the game to continue working on it. I'm looking at taking a management role at most, MM honestly only pays attention KAG-side if something is on fire :)

    This is true. It's also not how it works with KAG. The values are all there for the tweaking, and if they're tweaked often enough they get pulled into a constant at the top of a script or in some include. There's no reason for us to use a config file other than convenience for you guys.

    On that note, as we've already established that this is a two way street, let me be clear about this - every time we've done the latter, it's been completely ignored by the modding scene at least for months at a time if not eternity.

    The biggest example of this is the TDM vars, and swapping tdm over to a rapid deathmatch model. I moved everything (even the shop costs) out to a config file, and added a "default" behaviour for all of the vars, commented it extensively, and wrote the code so that the gamemode could work both ways. To this day I have not seen a "normal deathmatch" server since that patch. To this day I have not seen a server play with those variables, add some respawns, heck even custom map-making is limited to like two servers.

    The modding reception has been much, much, MUCH colder in release KAG even though people asked for all the modding features (fully scripted objects, gamemode control, custom map features, config file access, syncing files from server to client, easy networking from script, sprite layers, tcpr features, seclevs from script...) - from our perspective this makes it incredibly hard to justify putting in anything more to help modders. We put in so much stuff as requested by the community. It was met with crickets and requests for it to be simpler.

    This is the reason we feel modders "could do more" (on a good day) or "are downright lazy bastards" (on a bad day).

    Now, some of this is changing, but as we're already working on other projects, its pretty hard to justify things like fleshing out the documentation, writing modding guides, moving stuff to config vars, etc. The modders who want to do stuff (8x, aphelion, chrispin, mak, verra/skinney pre-intern, maybe a handful more) get it done with the current tools. We were able to build KAG using the current tools. It's not "easy", but that's because it's gamedev.
     
    Asu and Fuzzle like this.
  12. Vermilicious

    Vermilicious Ballista Bolt Thrower

    Messages:
    232
    Well, we're just not at the same page. Valve might have lots of people, but they have also managed to motivate their player base to take part in the content creation. Still, I think it's more a problem of the more mundane work nature of day-to-day "keeping the game alive" and maintenance, versus exciting new development projects. I think you are too pessimistic about the room for further progress with KAG (and possibly a sequel/reboot). And I hear your experiences thus far with the community. I didn't know you had put such work into a part of the game, just to see it be ignored. That could take away the motivation from anyone, and leave you with a grudge (not saying that's necessarily the case, but I sense that). The question is if that particular part of the game was really the right part to invest time in. If you haven't seen it used, well, that could be a strong indicator. Vanilla modes CTF and TTH are excellent. They have been since I started playing this game. When I had "played them out", though, one of the first custom modes that really struct a nerve with me was eanmig's Zombie mod. When I learned this was originally a part of the original game, and that it was scrapped I was stumped. Now, I understand a lot of ideas were scrapped during the development process - which is natural and a GOOD thing, sometimes you might actually throw away a hidden gem. I think a zombie/co-op mode could've been just that. The trick is identifying what is worth investing time in. It is a fairly easy thing to involve the community in this, but always with a critical eye. In any case, it's not something best discussed on two-mans hand over a cup of coffee, or even in your own head a late Friday evening :o) Even so, sometimes you miss, and hopefully some times you score. But hey, it's probably not going to matter much what I say. You've been in this for a long time, and ultimately it's your feelings and your decision. Maybe this seemingly negative spiral has just gone too far. As a fan of the game, that would make me a bit sad.
     
  13. Geti

    Geti Please avoid PMing me (poke a mod instead) THD Team Administrator Global Moderator

    Messages:
    3,730
    (paragraphs go a long way for readability, fwiw)

    The TDM stuff was explicitly requested for a good month or so, which is why it was done, announced deliberately upon release and made a fuss of. It was a case of deliberately liaising with the community, doing what they requested (including adding the shop stuff to the spec), and getting a very very poor response. As the list in brackets above kind of hints at, there's been more than just the one case of this happening. It definitely eats at morale each time.

    Zombies was scrapped precisely because the community was talking about the modding possibilities, and it's the kind of thing that fits so well as a mod. The port job directly from classic would have been a lot of work, so that time was spent (arguably wasted) on vehicles to bring something fresh and new (and widely requested) to the game. A bad call imo but we can't really take it back. I definitely agree that a co-op mode would be fun. Challenges was meant to be that but was so half-arsed that it never got picked up.

    The "sometimes you miss, sometimes you score aspect" is definitely the truth. When it comes down to it, I want to make more than a handful of games in my time. That sadly means I need to move on from past projects, and move a lot faster on existing projects. I understand it's frustrating to hear for KAG fans that I'm more or less "done", but I can only do one thing for so long.
     
  14. Vermilicious

    Vermilicious Ballista Bolt Thrower

    Messages:
    232
    What can I say.. people don't always know what they want. There's also the danger that it's just the hype talking, when every idea sounds cool, but a week later they've moved on and forgotten about it. I think that's just something one has to cope with one way or another. I would, for example, wait a while after release, before adding anything, until the player base has stabilized somewhat. I would also try to plan what to do next long beforehand. The big companies can often be far into the development of their DLCs already before the base game is released. In any case, official content always tastes better than improved modding support - it's more of a replacement thing, and primarily reserved for the most dedicated players. Both would be best, of course.