Map Scripting Brainstorming

For discussion of the code running behind the game

Moderator: Staff

User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Map Scripting Brainstorming

Postby Roots » Mon May 16, 2011 2:35 pm

Map scripting is kind of a pain in the ass, I'm finding out. I'd like to use this thread as a sort of center for us to think of ideas to make scripting easier, more efficient, and less error-prone. I've already gotten a head start by making dialogues and map zones easier to define, but where I'm really starting to struggle are map events. I'm scripting the cave map for the prologue at the moment, so here are some scenarios where I've been scratching my head. :huh:

Events that artificially "block" a path
In the cave, if the player goes the wrong way, there is a nearby NPC who calls out to the party "Over here!", at which the party automatically walks backward, outside of the zone where this event is triggered. (Later, another event that occurs on the map nullifies this event so that it does not happen again). What I need to do is the following:

1) Display the dialogue
2) Release control of the party from the player
3) Move the party outside of the zone that triggered the event
4) Give control back to the player

Sounds simple enough, right? Well, if the party is in the zone, how do we determine whether to start the event chain or not? Currently the only way to do this is to write a big if statement for four events in this chain. If any of these events are active, we don't want to launch the first event. It would be easier if we could either check if the event chain is active (a single if statement that represents all events in the chain), or if we could check whether the sprite entered (or exited) the zone instead. We don't have any good means of checking whether a sprite is entering/leaving a zone, other than for the script to maintain a boolean state for each sprite and each zone, which is not a practical solution.

Another issue that comes to mind is that map events are often going to be tied to global events (a global event is visible across all maps and all modes, and this data is saved to the player's save file). Using my example, there's a global event where a passage in the cave collapses. If this global event has already happened, I never want my event chain to be started that blocks the path, because I want to let the player take this path now that the other path is cut off. Likewise, there will be an event chain which causes the passage collapse, and I want that event chain to register the global event (and for the collapse event chain to never be repeated a second time). Is there a sensible way that we can associate map events and global events to alleviate some of the scripting burden?



Map Alterations
Another issue I'm dealing with are events which cause a permanent change in the map environment itself. The cave map has two such events. The first is the collapse of the passage I mentioned earlier. This changes some of the wall tiles as well as throws down rocks along the passage that is now blocked. So both the tile grid and collision grid are changed. The second change is where a dry riverbed that was previously walkable becomes filled with water and becomes unwalkable. Again, both the tile and the collision grid change.

The way I'm planning to handle these two map changes right now is to use different map contexts for all three states of the map. I think for this purpose it works okay since there aren't any houses or other structures which swap contexts regularly (if there were, the situation would get really hairy). But what if we were in a town, say, and we wanted an event where say a wall collapsed when the player got near or something? If we had two different contexts for the outside of the town (initial and post-collapse), when we entered a house and exited, we'd need each context zone to somehow be aware that the "initial" outside map context is no longer valid and it should instead switch to the post-collapse one. I don't see any good way to do this at the moment, meaning the best solution would probably just be to change the tile and collision grid manually from this event instead of relying on a context.

Why am I bringing this up? Well, the plan is to eventually have more interactive environments on our maps (being able to cut away blocking bushes, move rocks, etc). So we'll be changing the tile and collision grids very frequently when/if we have such features in place.


Ideas
So in summary, I'd like to discuss possible solutions for the following. If the following are even necessary at all.

- Is there a good way for us to check for enter/exit conditions from zones?

- Should we implement code that can track whether any event in an event chain is active?

- Is there a good way to alter the tile and collision grids on a map other than scripting each tile change by hand or using a map context?
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Map Scripting Brainstorming

Postby Roots » Mon May 16, 2011 3:07 pm

I can't stop thinking about these problems, particularly the zone enter/exit issue. I think it will be very worthwhile to implement this feature and will make scripting a whole lot easier. Maybe we can even attach map events to zones, so that the event launches automatically whenever a zone enters or exits? :shrug: But I'm getting ahead of myself. We already register created zones to maps and update the zones every frame, so lets take advantage of that. Here's my proposed solution for how we can handle enter/exit zone occurrences.

First, zones have to have a bit-mask of which contexts the zone applies to. This will have to be declared in the map script when the zone is created. Only objects which exist in the same context as the the zone are looked at when determining enter/exit events.

Each map zone will hold a container of map object pointers. All objects that are currently located within the zone reside in this container. Whenever a map object changes its position, all zones that are in the same context as the object are examined. If the object is inside the zone, it is added to the container. If it was inside the zone and now is not, it is removed from the container. Add/remove correspond to enter/exit conditions. The zone also holds two additional containers (probably linked lists) that hold the objects that entered or exited the zone. These containers are cleared on each update loop, so the event can only be observed once.

In the map script, the update function will call "ObjectEnteredZone" or "ObjectExitedZone". These functions will return pointers to the objects which entered or exited the zone, if any did. The script can then know what object entered/exited what zone and react accordingly.

My one reservation with this is that some zones we probably don't care about this at all. For example, enemy zones should not care to track this information. Maybe we should have another abstract zone class that derives from MapZone (which is also an abstract class) that implements this zone enter/exit processing code? It would save both memory and processing power.


So what do you guys think? I'm itching to start implementing this feature right away, but I really want second opinions on whether this is a worthwhile endeavor. When I think about an interesting problem my brain can get over-excited and I have trouble looking at the practicality of the solution I'm thinking of. :heh:
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Map Scripting Brainstorming

Postby Roots » Wed May 18, 2011 2:21 am

I was hoping to get the zone work finished today but I was feeling tired so I pretty much took the entire day off. I did finish the API for a new zone class though that will implement the sprite inside/entering/exiting feature. I originally had these new members and methods as part of the MapZone class, but later decided to make a new class as I'm sure there are zones which we do not care to monitor whether a sprite is entering or leaving (such as the EnemyZone class). Here's the API, and all of these functions will be available to be called in Lua. I suspect that most if not all of our existing MapZones instances created in map scripts will be replaced by the new ResidentZone.

Code: Select all

/** ****************************************************************************
*** \brief An advanced map zone which keeps track of its resident sprites
***
*** This zone class enhances the skeletal MapZone by adding features which keep
*** track of the sprites which exist in this zone, as well as when those sprites
*** enter and exit the zone area. This is very useful information for zones which
*** trigger map events, for instance, when a sprite interacts with the zone area.
*** Sprites inhabiting the zone are called "residents", hence the nomenclature for
*** this class.
***
*** ResidentZones are updated by the ObjectSupervisor. Every time a sprite's position
*** is changed, it is marked and all zones that share the same context as the sprite
*** are examined to see if the sprite has entered or left any zones. Because it is
*** quite common to want to determine if the sprite pointed to by the camera has interacted
*** with the zone area, there are specific functions that provide for that ability, to
*** ease the burden on the map script writers.
***
*** It is necessary to define which map contexts the zone takes effect on so that
*** only sprites that share a common context with a zone are taken into account. Note that
*** a zone can be active in any number of the available map contexts, since it stores a bit
*** mask value of contexts where the zone is active. Also note that this class examines
*** resident sprites, but not lower classes of objects (which typically do not move and
*** likewise we would not want to trigger any sort of event).
*** ***************************************************************************/
class ResidentZone : public class MapZone {
   ResidentZone()
      {}

   /** \brief Constructs a map zone that is initialized with a single zone section
   *** \param left_col The left edge of the section to add
   *** \param right_col The right edge of the section to add
   *** \param top_row The top edge of the section to add
   *** \param bottom_row The bottom edge of the section to add
   **/
   ResidentZone(uint16 left_col, uint16 right_col, uint16 top_row, uint16 bottom_row);

   /** \brief Constructs a map zone that is initialized with a single zone section
   *** \param left_col The left edge of the section to add
   *** \param right_col The right edge of the section to add
   *** \param top_row The top edge of the section to add
   *** \param bottom_row The bottom edge of the section to add
   *** \param contexts A bit-mask of which contexts this zone is active in
   **/
   ResidentZone(uint16 left_col, uint16 right_col, uint16 top_row, uint16 bottom_row, MAP_CONTEXT contexts);

   ~ResidentZone()
      {}

   //! \brief Returns true if any sprites have recently entered this zone
   bool IsResidentEntering() const
      { return !_entering_residents.empty(); }

   //! \brief Returns true if any sprites have recently exited this zone
   bool IsResidentExiting() const
      { return !_exiting_residents.empty(); }
   
   /** \brief Returns true if a specific sprite resides inside the zone
   *** \param object_id The object ID number of the sprite to check for
   *** \return True if the object ID is valid and the sprite is inside the zone
   **/
   bool IsSpriteResident(uint32 object_id) const;

   /** \brief Returns true if a specific sprite resides inside the zone
   *** \param sprite A pointer to the sprite to check
   *** \return True if the sprite is inside the zone
   **/
   bool IsSpriteResident(VirtualSprite* sprite) const;

   //! \brief Returns true if the sprite pointed to by the map camera is inside the zone
   bool IsCameraResident() const;

   /** \brief Returns true if a specific sprite is currently entering the zone
   *** \param object_id The object ID number of the sprite to check for
   *** \return True if the object ID is valid and the sprite is entering the zone
   **/
   bool IsSpriteEntering(uint32 object_id) const;

   /** \brief Returns true if a specific sprite is currently entering the zone
   *** \param sprite A pointer to the sprite to check
   *** \return True if the sprite is entering the zone
   **/
   bool IsSpriteEntering(VirtualSprite* sprite) const;

   //! \brief Returns true if the sprite pointed to by the camera has recently entered this zone
   bool IsCameraEntering() const;

   /** \brief Returns true if a specific sprite is currently exiting the zone
   *** \param object_id The object ID number of the sprite to check for
   *** \return True if the object ID is valid and the sprite is exitingg the zone
   **/
   bool IsSpritExiting(uint32 object_id) const;

   /** \brief Returns true if a specific sprite is currently exiting the zone
   *** \param sprite A pointer to the sprite to check
   *** \return True if the sprite is exiting the zone
   **/
   bool IsSpriteEntering(VirtualSprite* sprite) const;

   //! \brief Returns true of the sprite pointed to by the camera has recently exited this zone
   bool IsCameraExiting() const;

   /** \brief Retrieves a pointer to a sprite that resides inside the zone
   *** \param index The index of the resident sprite to retrieve
   *** \return A pointer to the sprite, or NULL if there is no resident for the given index
   ***
   *** \note This function is designed to allow Lua access to all sprite residents
   **/
   VirtualSprite* GetSpriteInsideZone(uint32 index) const;

   /** \brief Retrieves a pointer to a sprite has recently entered the zone
   *** \param index The index of the entering resident to retrieve
   *** \return A pointer to the sprite, or NULL if there is no entering resident for the given index
   ***
   *** \note This function is designed to allow Lua access to all entering residents
   **/
   VirtualSprite* GetSpriteEnteringZone(uint32 index) const;

   /** \brief Retrieves a pointer to a sprite that recently exited the zone
   *** \param index The index of the exiting sprite to retrieve
   *** \return A pointer to the sprite, or NULL if there is no exiting resident for the given index
   ***
   *** \note This function is designed to allow Lua access to all exiting residents
   **/
   VirtualSprite* GetSpriteExitingZone(uint32 index) const;

   //! \brief Returns the number of sprites that are currently located within the zone boundaries
   uint32 GetNumberResidents() const
      { return _residents.size(); }
   
   //! \name Class member accessor methods
   //@{
   MAP_CONTEXT GetActiveContexts() const
      { return _active_contexts; }

   void SetActiveContexts(MAP_CONTEXT contexts)
      { _active_contexts = contexts; }
   //@}

protected:
   //! \brief A bit mask used to determine on which contexts this zone is valid
   MAP_CONTEXT _active_contexts;

   //! \brief A container that retains pointers to all sprites which occupy this zone
   std::list<VirtualSprite*> _residents;

   /** \brief Temporarily retains all sprites which have entered the zone
   *** \note This list is cleared on every update, so there is only one opportunity to
   *** observe which sprites have entered the zone.
   **/
   std::list<VirtualSprite*> _entering_residents;

   /** \brief Temporarily retains all sprites which have exited the zone
   *** \note This list is cleared on every update, so there is only one opportunity to
   *** observe which sprites have exited the zone.
   **/
   std::list<VirtualSprite*> _exiting_residents;
}; // class ResidentZone : public class MapZone


I've also been pondering whether it would be worthwhile to create a class derived from ResidentZone that can be used to automatically launch events when certain sprites enter/exit/reside in the zone. I'm noticing a pattern in our scripts where we often use zones to do the following:

1) Check if a sprite (usually the camera) is inside the zone
2) Check if a global event "my_event" has occurred
3) If "my_event" has not occurred, add "my_event" to global event group and launch map event #n

I'm trying not to get carried away with this stuff though. I only want to write a zone like this if its going to be used and its flexible enough to meet most of our needs for these types of situations.
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Map Scripting Brainstorming

Postby Roots » Sat May 21, 2011 8:13 pm

So the zone work is pretty much complete. The ResidentZone clas is implemented, but it will require some changes to the ObjectSupervisor class before it is ready to be used. However, there is a new CameraZone class which works similar to resident zones, but focuses only on the camera enter/exit events and is therefore much more simple to implement. All of my scripting has been making use of camera zones and it has met all my needs thus far. We'll only require resident zones when we get to a point where we want NPCs to be triggering events.


Anyway, another change I realized we needed yesterday was an improvement to the way we handle map treasures. Yes, we had treasure chests working in the past. But other than those chests, there's no way to "give" an item to the player via the treasure menu, which I wanted to do for a certain event. I'm splitting the MapTreasure class into two classes: MapTreasure will retain the actual contents of the treasure, and the new TreasureObject is a type of MapObject which basically serves to represent treasure chests and other treasure-like objects.

-----

I'm also started to think that maybe we should split our maps up into two Lua scripts. One contains all of the tile, tileset, map context, and collision data produced by the editor. The second contains the secondary data definitions like music filenames and the script functions. Why should we do this? We're going to be re-visiting maps throughout the game where we want the same environment, but vastly different things to happen. We may need completely different dialogue for NPCs, new events, a new set of enemies, etc. We're not prevented from doing any of this right now by continuing to use a single file for everything, but things could get messy.

I'm also not proposing we do this right away either, because its going to take a little work. But its an idea I wanted to throw out there for now to see what others think about it. I'm still thinking about it myself.
Image

Return to “Programming”

Who is online

Users browsing this forum: No registered users and 2 guests