Tribe Rework

From PSwiki
Jump to navigation Jump to search

Idea

Prerequisite

For this system to work, an npctype named "AbstractTribesman" needs to be declared in 'data/npcbehave.xml'.

The functioning of the tribe system is based on the following steps:

Step #1 (Loading Stuff)

(executed only once, on NPCClient's startup. No further interrogation on the database should be done)

On NPCClient's startup everything is loaded into the classes.

The Recipe Manager loads all available recipes from mysql:'tribe_recipes'.

During load, the Recipe Manager parses the requirements and keeps them in the Recipe Class. (Recipe::requirements<Recipe::Requirement>)

The Tribal Object receives all the information regarding the tribe:

  • resources from mysql:'sc_tribe_resources'
  • memories from mysql:'sc_tribe_memories'
  • knowledge from mysql:'sc_tribe_knowledge'
  • basic tribe information from mysql:'tribes' (including the ID of the tribal recipe, more details below)
  • npc's are loaded by the psNPCClient and assigned to the tribe

Further on, each Tribal Object is linked to the Recipe Manager. (a pointer is sent and loaded into a csArray) For each tribe loaded, the Recipe Manager does the following:

  • assembles a data structure named 'TribeData'
  • links the tribal recipe belonging to this tribe to 'TribeData'
  • parses the information in the tribal recipe and assign the stats to csStrings in 'TribeData'
  • computes a tribal npctype having as parent the 'AbstractTribesman' npctype. The difference is done by adding reactions to whatever the tribe reacts to. (attacking any player nearby or simply greeting them, sleeping at day or at night, etc)

Step #2 (RunTime)

(executed once on startup and each time a recipe is completed)

The Tribal Object selects the top recipe which needs execution and sends it to the Recipe Manager.

The Recipe Manager parses the contents of the recipe:

  • checks for requirements. If requirements are met it proceeds with parsing the algorithm (see step #3). If requirements are not met, it pushes the Recipes which achieves the requirement on top of the tribe's recipe list


Step #3 (Recipe's Algorithm)

(executed for each step the algorithm has, each time a recipe needs execution)

The Recipe has an algorithm stored as a csArray<csString>. When an algorithm needs an execution, the Recipe Manager takes each string and parses it. Depending on the rules we set, the Recipe Manager will tell the tribal object which perceptions to fire on their npcs.

(E.g.: If the recipe's algorithm require us to send 10 workers to build a town hall, the tribal object will be told to fire a "go to work" perception on 10 worker tribesman)

Tribal Object

The tribal object will suffer some changes in order to support the above-mentioned capabilities:

  • a knowledge array of strings: it contains only the name of the knowledge received by the tribe from completing recipes. They act more as tokens to prove completion. They are needed because recipes may require knowledge. (E.g.: if we want our tribe to build axes for it's warriors, we need the ironworks knowledge)
  • an array of Recipe pointers which acts more as a priority queue for keeping recipes. Initially, only the tribal recipe will be there. After initialization, persistent recipes (which will never expire) will be pushed there and act as goals for the tribe. Based on the goals, normal recipes will be pushed there... and if requirements are not met... recipes to meet the requirements... and the list can go on forever.
  • some memory entries which act as registers for sending data to tribe members. (E.g.: the memory named activeLocation, in which the tribal object will place location data for the tribe members to locateOp on them... as the place of the new building, or the place of a resource, or the place of a nearby tribe) Using this we can load data in the register, and fire a reaction to send members to work on "tribe:memory:activeLocation", for example.
  • an array of Assets (see below)

Recipe Manager

Recipe

Definitions:

  • Unique Recipe: recipe which can be executed only once. Like recipes to gain a knowledge, or build unique buildings.
  • Persistent Recipe: recipe which never ends. Usually, these recipes will contain tribe information or goals which tribe will follow forever.

A recipe object holds all details regarding the process of doing tasks.

It holds:

  • flag to signal a unique recipe
  • flag to signal a persistent recipe
  • a requirement data structure which flags it's name, type and number of entitites required
  • csStringArray for holding algorithm steps
  • csArray for holding requirements


Recipe Manager

It holds:

  • a list of enrolled tribes and a tribeData structure for each of them
  • a list of recipes loaded

Main operations the Recipe Manager does are explained above in the 'idea' tab.

It's in development how the recipe manager should track recipes. I'll add more details as soon as I got a good picture of it.

Database changes

The database needs to suffer some changes for this to work.

tribe table

Added a column to hold the tribal recipe.


tribe_recipes table

It will contain all the recipes described by:

  • ID
  • Recipe Name
  • Requirement Script
  • Algorithm
  • Persistent
  • Uniqueness


ID Name Requirement Algorithm
1 "Mine Coal" "tribesman(gatherer,1);item(pickaxe,1);" "goToMemory(coal);equip(pickaxe);dig;goTo(hoilme);alterResource(coal,1)" 0 0
2 "Build Well" "tribesman(builder,3);item(shovel,3);" "selectLocation(x,y,z);goTo(x,y,z);alterResource(food,-1);dig;alterResource(food,-2);addBuilding(well)" 0 0


sc_tribe_knowledge table

It keeps the knowledge gained by the tribe.

id tribe_id knowledge
1 1 "Leatherwork"
2 1 "Mineworks"

Provisional Functions

Below is a list of functions which will be used to script recipes. Please note that this list is provisional. It will become final as soon as we have a solid list which allows us to do anything tribe-related.

function type arguments effect
brain(x) tribe info { civilised, savage, inhuman } decides the advancement of this tribe
aggressivity(x) tribe info { warlike, neutral, peaceful } decides how tribe members will react to entities coming near them
growth(x) tribe info { expansionist, normal, conservatory } decides the expansion rate of the tribe
unity(x) tribe info { organised, normal, cowards } decides how members will react if a fellow is attacked
loadRecipe(x) tribe info x is a recipe name loads a recipe at the initialization of the tribe
tribesman(x,y) requirement x is a tribe member type, y is a number used by recipes to require able-to-work tribesman
resource(x,y) requirement x is a resource type, y is a number used by recipes to require an amount of resources
knowledge(x) requirement name of knowledge (knowledges discussed above) requires a knowledge
item(x,y) requirement x is an item's name, y is the number of items requires items
recipe(x,y) requirement x is a recipe's name, y is the number of recipes require a recipe
trader(x) requirement x is an item name requires a memory containing the position of another npc trading items of type x
alterResource(x,y) algorithm step x is the resources name, y is the value to alter with Pretty self-explaining :D
loadLocation(x,y,z) algorithm step x,y,z are float numbers loads this location into tribe:memory:activeLocation
go() algorithm step none makes the npcs go to tribe:memory:activeLocation
locateMemory(x) algorithm step x is a memory's name loads the memory's location into tribe:memory:activeLocation
locateResource(x) algorithm step x is a resource name loads the nearest resource of this type into tribe:memory:activeLocation
addKnowledge(x) algorithm step x is a name of knowledge (e.g: Ironworks, Pottery) adds the knowledge in the tribe knowledge array
addBuilding(x) algorithm step x is the name of a building finds a good location for the building (in respect of it's size) and spawns it
attack() algorithm step none sends all selected members to attack a previous selected location (to be used with locateMemory(enemy))
gather() algorithm step none sends all selected members to gather a previous selected resource
mine() algorithm step none sends all selected members to mine a previous selected resource
select(x,y) algorithm step x is the type of npcs, y is the number Important: Selects the npcs on which to sends the following commands. It should always be used as a first command.
guard() algorithm step none Makes the previous selected NPCs guard the previous selected Location. (using select(x,y) and locateMemory(z))
trade(x,y) algorithm step x is the item/tria quantity given by the tribesman, y is the item/tria quantity given by the other npc Performs a trade between the tribesman and the npc.
wait(x) algorithm step x is a number of ticks sets the recipe on pause for the given csTicks. Useful when waiting for buildings to finish.
setBuffer(x) algorithm step x is any value sets the tribe (string)recipeBuffer to the given value
setAmountBuffer(x) algorithm step x is any value sets the tribe secondary buffer (recipeAmountBuffer) to the given value
addMember(x) algorithm step x is a member type adds a new member of type 'x' to the tribe

More algorithm steps might be added.

AbstractTribesman npctype

This npctype is a parent for all npctypes tribe members will have. It holds general behaviors and reactions.

 <npctype name="AbstractTribesman" vel="2.5">
   <behavior name="do nothing" initial="80" resume="yes">
     <wait anim="stand" duration="100" />
   </behavior>
   <behavior name="peace_meet" completion_decay="100">
     <locate obj="target" />
     <emote cmd="/bow" />
   </behavior>
   <behavior name="aggressive_meet" completion_decay="150">
     <locate obj="target" />
     <melee seek_range="20" melee_range="3" />
   </behavior>
   <behavior name="coward_attacked" decay="10">
     <emote cmd="/me flees in panic." />
     <wander anim="walk" />
   </behavior>
   <behavior name="normal_attacked" decay="10">
     <locate obj="target" />
     <melee seek_range="20" melee_range="3" />
   </behavior>
   <behavior name="united_attacked" decay="10">
     <locate obj="enemy" />
     <percept event="tribesman attacked" target="tribe" />
     <melee seek_range="20" melee_range="3" />
   </behavior>
   <behavior name="Chase" initial="0" growth="0" decay="1" complection_decay="-1" >
     <chase type="target" chase_range="20" anim="run" vel="5" />
   </behavior>
   <behavior name="GoToWork" loop="yes" resume="yes" resume="yes">
     <locate obj="tribe:memory:activeBuilding" />
     <moveto coords="tribe:memory:activeLocation" anim="walk" />
     <wander anim="walk" />
   </behavior>
   <behavior name="Explore" completion_decay="100" resume="yes">
     <locate obj="waypoint" static="no" />
     <navigate anim="walk" />
     <locate obj="waypoint" static="no" random="yes" range="80" />
     <wander anim="walk" />
     <wait anim="stand" duration="3" />
     <locate obj="tribe:home" static="no" />
     <wander anim="walk" />
     <navigate anim="walk" />
     <share_memories />
   </behavior>
   <behavior name="GatherResource" completion_decay="100" resume="yes">
     <locate obj="tribe:memory:activeResource" />
     <wander anim="walk" />
     <equip item="Rock Pick" slot="righthand" />
     <dig resource="tribe:wealth" />
     <locate obj="tribe:home"/>
     <wander anim="walk" />
     <transfer item="tribe:wealth" target="tribe" />
     <share_memories />
   </behavior>
   <behavior name="GoToSleep" resume="yes">
     <locate obj="tribe:home" />
     <wander anim="walk" />
     <wait anim="sit" duration="180" />
   </behavior>
   <behavior name="Breed" completion_decay="100">
     <invisible />
     <wait anim="sit" duration="180" />
     <visible />
   </behavior>
   <behavior name="Resurrect" when_dead="yes" completion_decay="200">
     <resurrect />
   </behavior>
   <behavior name="Guard" resume="yes">
       <locate obj="tribe:memory:activeLocation" />
       <wander anim="walk" />
       <circle anim="walk" radius="2" />
   </behavior>
   <react event="tribe:resurrect" behavior="Resurrect" delta="200" when_dead="yes" />
   <react event="tribe:gotowork" behavior="GoToWork" delta="100" />
   <react event="tribe:gather" behavior="GatherResource" delta="100" />
   <react event="tribe:breed" behavior="Breed" delta="100" />
   <react event="tribe:explore" behavior="Explore" delta="100" />
   <react event="target out of range" behavior="Chase" />
   <react event="target out of chase" behavior="Chase" absolute="0" only_interrupt="chase" />
 </npctype>

Assets

Assets are items and buildings that belong to the tribe. They are held in the 'assets' array (which acts like a bank) in the tribal object.

An Asset data structure contains the following:

  • name
  • item - a gemNPCItem* that will hold how the item/building looks
  • quantity - an int which will hold how many items of a kind the tribe has (used only for items)
  • pos - a csVector3 holding the position of the item (used only for buildings)
  • building - a boolean value (flagged as true for building entries)

I found that keeping positions & multiple entries for items would prove quite useless... and thought of a better practice to remove all tribal items from the world and keeping them as numbers.

Pros & Cons

Pros

  • It won't use any other data sources besides the sql database
  • Independent pieces can be altered without the need to change the whole system. Adding moods will only affect the Parser Class that will slightly change the behavior before assembling and sending to the Tribal Object.
  • There's no need to change the code in order to add or change tribes.
  • The parser class can assemble NPCTypes for regular non-tribe npcs too.

Cons

  • It will put a higher load on the NPCClient. I'm not to good at predicting but... it shouldn't make it "a piece of 20tons rock" :).
  • For the start... I might be needed to assembly the NPCType and send it as an xml IDocumentNode so I can use the already-made API there.

Notes

  • It would be great if MathScript could be used for the tasks above. Please let me know if it's possible. I like to avoid reinventing the wheel.
  • Awaiting the green light.