• 9
• 10
• 9
• 10
• 10

# is there an easier way to do this?

This topic is 765 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

WARNING: LONG POST

game: caveman v3.0

game type: open world sandbox survival RPG. stone age setting w/ emphasis on realism. not storyline mission based.

i'm in the process of adding bedrolls and teepees to the game.

adding new objects, actions, and or skills to the game can be a bit of a project.

i wondering if there's something i can do to make things easier.

an example:

to add an object, you have to add a bunch of new info for the object. for bedrolls:

object type ID number, weight, value, name, collision radius, whether its repairable, sfx to play when making/repairing, object to draw in hand in animations when making/repairing.

other types of objects such as weapons and foods require additional info or different info.

then there's all the requirements to make or fix the object:

number of skills required, the list of skills required, and the exp required in each skill,

number of part types required, the list of part types, and the quantities of each.

same idea for tools required, skills that provide a bonus when making/fixing, and skills you earn exp in when making/fixing the object.

and then there's how to draw it:

type: mesh, rigid body, skinned mesh, 2d bilboard, or 3d billboard

flags like cull and alphatest

mesh or model #

texture or animation #

scale rotation translation

texture scale

adding an action to make a bedroll is pretty painless. there is a generic make_object action handler that uses the info listed above. so you just add a menu pick to the make_shelter_menu that calls trymake(BEDROLL) this check all the pre-requisites, and sets the player's action to MAKEOBJ if they pass.

as an inventory item, bedrolls can automatically be picked up, dropped, abandoned, inspected, and repaired, with no changes required.

then i have to add a way to use the object.

so a new line gets added to select():

if selected_bedroll() return;

selected_bedroll checks to see if a bedroll on the ground was selected, and runs the bedroll menu if one was. bedroll menu choices are pickup, rest, rest and heal, sleep, and cancel. selected_bedroll and bedroll_menu are similar to those for the existing bedding object and are trivial to implement. the pickup option does an encumbrance check, and if passed, removes the bedroll from the list of dropped objects and adds it to the player's inventory.

so the major changes required for bedding is the initial data (general stats, crafting info, drawing info), adding a call to make it, a call to select it, a routine to see if one was selected, a menu of interactions, and a couple lines of code to pick one up.

thats an easy object.   : (

the teepee:

same idea, lots of upfront data to be entered: general stats, crating info, drawing info.

adding the make action is again trivial as is selected_teepee, and the teepee_menu.

a teepee can actually be in two states, setup, and not setup. two object types was the easiest way to do this. so there's a TEEPEE object (setup) and a TEEPEEKIT object (not setup). the only real difference being the drawing info and collision radius.

but now it gets ugly...

the teepeekit_menu (IE not setup) options are: pickup, setup, cancel. pickup picks up the kit just like any other dropped inventory item. setup triggers a new action: SETUP_TEEPEE

the teepee_menu (IE already setup) options are: takedown and cancel.  takedown triggers a new action: TAKEDOWN_TEEPEE

when in kit form and picked up and placed in the player's inventory, a teepee can be inspected, repaired, and abandoned just like any other inventory item with no changes required.

but to add an action, again a bunch of info must be added up front:

action name

bool: fatigue recharges during the action

bool: the action can cause disease from ingestion

bool: the action can cause disease from contact

the method to call as action handler

all the pre-requisites for the action, same as objects: skills, exp levels, tools and parts required, and quantities thereof.

sfx to play when doing the action

object to draw in hand when doing the action

and then you have to write an action handler method, unless you already have a generic one you can use.

for setup and takedown teepee the algo is basically: inc counter, if counter > limit, check success, if success, end action, else reset counter and try again. pretty straight forward in this case.

the teepee_menu and teepeekit_menu will trigger the setup and takedown actions. so adding ways to trigger the actions is done when those are written.

then the actions need to be added to a bunch of lookup tables:

not_moving - you don't movie around enough while doing this action to blow your camouflaged surprise bonus

learn_joke_story_talk - action involves a NPC who should not wander away

get_bandmember_height - what height to draw the player off the ground, based on what they're doing (standing, kneeling, lying down, etc)

get_action_animation - what animation to use when drawing the player, based on what they're doing (like setting up a teepee)

get_action_message - an optional custom progress message to display when the player performs the action - used for long actions.

most of the data about actions and objects is in databases. lookup tables were simply an alternate implementation method for adding additional fields to the databases.

the final step in adding a new action is to add any new objects, stats, or skills it requires.  for setup and take down teepee, the new teepee object is the only new object required.

so its a lot of data to enter.  and its not all together in one place.   object  ID numbers get defined one place,   object general stats, pre-requisites, and drawing info get set in a second, make actions get added in a third place, and selection code and the interactions menu is in a fourth place.   same idea for actions, with the lookup tables placed at appropriate scope levels. needless to say, i navigate the source via "find". and i actually have checklists in comments in the code: to add action, to add object, to add skill, etc.

so when its time to add something, i go down the checklist, dutifully slogging my way though to the end, to make sure i haven't missed anything. i often have to keep a list of the parts i still need to do.

sometimes i'll add an object or action that requires a number of other new objects or actions. sometimes generic make object action handlers can't be used - and so on.

obviously this can all be re-organized, but i don't really see that making it simpler, just closer to each other in the source file perhaps.

any ideas?

Edited by Norman Barrows

##### Share on other sites

For the basic information that every object needs filled in, you could write a simple editor (if theres a ton of items) that asks the data in efficient way and writes it to all the places necessary.

Ensure the common case has a sensible default that allows you to skip providing the data (probably applies to everything there). Make it so, that when adding some entirely new functionality, you can skip all the common data (appearance, physical properties...) and focus on the functionality. So you dont need to finish/decide every aspect of the entire item before it even exists. That should make it less annoying to add new things. Fill in the details later.

See if theres any way you can add more templates/helpers to minimize amount of data/boilerplate you need to input. Try to bring all the information in a single place, in correct order (having to jump around multiple places adding stuff is slow). (this is what an editor would do - even if all the data was in code, you could have some text console editor insert the data and write the boilerplate for you)

##### Share on other sites

I'd be heavily tempted to either throw it in a data file with JSON or YAML format, or directly in a Python list/dict.

First step is to make it work probably, so you can express the properties in a more convenient syntax.

Second step is to see if you can group things, or derive properties from higher level concepts, ie "carryable" implies you can pick it and put it down.

"repairable: [cloth, scissors]" could mean you can repair with cloth and scissors.

Python, JSON, and YAML are sufficiently light weight for experimenting, although making a plan, especially what higher level concepts should be used, is probably useful.

It could also do correctness checking. "He, you need a weight for picking it up."

##### Share on other sites

adding new objects, actions, and or skills to the game can be a bit of a project.

What other games have you studied?

As open source games, NetHack and family are easy to study. Source is readily available.

You've mentioned some of your systems in Caveman before. It seems you generally have systems, but what you are describing, with a bunch of common properties across all items, common commands, these seem similar to items in NetHack and related games.

Map levels have lines that are readable enough. They allow for specific items or random items. What is allowed on a level by 'random' placement is defined by various algorithms, but any type of object is eligible except those specifically marked as non-random creation. So some level description:
...
DOOR:closed,(28,07)
DOOR:locked,(34,10)
...
MONSTER:'B',random,(60,09),hostile
MONSTER:'W',random,(60,10),hostile
MONSTER:'B',random,(60,12),hostile
MONSTER:'i',random,(60,13),hostile
MONSTER:'B',random,random,hostile
MONSTER:'B',random,random,hostile
MONSTER:'B',random,random,hostile
...
TRAP:random,random
TRAP:random,random
TRAP:"spiked pit",(72,12)
...
OBJECT:'(',"crystal ball",(17,08),blessed,5,"The Orb of Fate"
OBJECT:random,random,random
OBJECT:random,random,random
OBJECT:random,random,random
...
RANDOM_PLACES:(03,01),(07,01),(11,01),(01,03),(13,03),
(01,07),(13,07),(03,09),(07,09),(11,09)
MONSTER:'&',random,place[0]
MONSTER:'&',random,place[1]
MONSTER:'d',"hell hound pup",place[2]
MONSTER:'d',"hell hound pup",place[3]
MONSTER:'d',"winter wolf",place[4]
CONTAINER:'(',"chest",place[5]
OBJECT:'"',"amulet of life saving",contained
CONTAINER:'(',"chest",place[6]
OBJECT:'"',"amulet of strangulation",contained

The objects themselves can contain a long list of properties, like fuses/timers, states like locked/unlocked, trapped, recharged, in use, nutrition left in food, age of food, color, count, etc. They can also be flagged on discovered or not (such as telling the difference between an emerald or green glass). Put together there are all kinds of objects. While all types don't necessarily use every flag, the structures are in common.

Then you've got a list of all the objects:

WEAPON("spear", (char *)0, 1, 1, 0, 50, 30, 3, 6, 8, 0, P, P_SPEAR, IRON, HI_METAL),
WEAPON("elven spear", "runed spear", 0, 1, 0, 10, 30, 3, 7, 8, 0, P, P_SPEAR, WOOD, HI_WOOD),
WEAPON("orcish spear", "crude spear", 0, 1, 0, 13, 30, 3, 5, 8, 0, P, P_SPEAR, IRON, CLR_BLACK),
...
WEAPON("scimitar", "curved sword", 0, 0, 0, 15, 40, 15, 8, 8, 0, S, P_SCIMITAR, IRON, HI_METAL),
WEAPON("silver saber", (char *)0, 1, 0, 0, 6, 40, 75, 8, 8, 0, S, P_SABER, SILVER, HI_SILVER),
WEAPON("broadsword", (char *)0, 1, 0, 0, 8, 70, 10, 4, 6, 0, S, P_BROAD_SWORD, IRON, HI_METAL),
...
BOW("orcish bow", "crude bow", 0, 12, 30, 60, 0, WOOD, P_BOW, CLR_BLACK),
BOW("sling", (char *)0, 1, 40, 3, 20, 0, LEATHER, P_SLING, HI_LEATHER),
BOW("crossbow", (char *)0, 1, 45, 50, 40, 0, WOOD, P_CROSSBOW, HI_WOOD),
...
HELM("fedora", (char *)0, 1, 0, 0, 0, 0, 3, 1,10, 0, CLOTH, CLR_BROWN),
HELM("cornuthaum", "conical hat", 0, 1, CLAIRVOYANT, 3, 1, 4, 80,10, 2, CLOTH, CLR_BLUE),
HELM("dunce cap", "conical hat", 0, 1, 0, 3, 1, 4, 1,10, 0, CLOTH, CLR_BLUE),
HELM("dented pot", (char *)0, 1, 0, 0, 2, 0, 10, 8, 9, 0, IRON, CLR_BLACK),
...
DRGN_ARMR("blue dragon scale mail", 1, SHOCK_RES, 900, 1, CLR_BLUE),
DRGN_ARMR("green dragon scale mail", 1, POISON_RES, 900, 1, CLR_GREEN),
DRGN_ARMR("yellow dragon scale mail", 1, ACID_RES, 900, 1, CLR_YELLOW),
...
RING("gain strength", 0, "granite", 150, 1, 1, 7, MINERAL, HI_MINERAL),
RING("gain constitution", 0, "opal", 150, 1, 1, 7, MINERAL, HI_MINERAL),
RING("increase damage", 0, "coral", 150, 1, 1, 4, MINERAL, CLR_ORANGE),
...
CONTAINER("large box", (char *)0, 1, 0, 0, 40,350, 8, WOOD, HI_WOOD),
CONTAINER("chest", (char *)0, 1, 0, 0, 35,600, 16, WOOD, HI_WOOD),
CONTAINER("ice box", (char *)0, 1, 0, 0, 5,900, 42, PLASTIC, CLR_WHITE),
CONTAINER("sack", "bag", 0, 0, 0, 35, 15, 2, CLOTH, HI_CLOTH),
CONTAINER("oilskin sack", "bag", 0, 0, 0, 5, 15, 100, CLOTH, HI_CLOTH),
CONTAINER("bag of holding", "bag", 0, 1, 0, 20, 15, 100, CLOTH, HI_CLOTH),
CONTAINER("bag of tricks", "bag", 0, 1, 1, 20, 15, 100, CLOTH, HI_CLOTH),
...
FOOD("apple", 15, 1, 2, 0, VEGGY, 50, CLR_RED),
FOOD("orange", 10, 1, 2, 0, VEGGY, 80, CLR_ORANGE),
FOOD("pear", 10, 1, 2, 0, VEGGY, 50, CLR_BRIGHT_GREEN),
FOOD("melon", 10, 1, 5, 0, VEGGY, 100, CLR_BRIGHT_GREEN),
FOOD("banana", 10, 1, 2, 0, VEGGY, 80, CLR_YELLOW),
FOOD("carrot", 15, 1, 2, 0, VEGGY, 50, CLR_ORANGE),
...
GEM("diamond", "white",                3,  1, 4000, 15, 10, GEMSTONE, CLR_WHITE),
GEM("ruby", "red",                     4,  1, 3500, 15,  9, GEMSTONE, CLR_RED),
GEM("jacinth", "orange",               3,  1, 3250, 15,  9, GEMSTONE, CLR_ORANGE),
GEM("sapphire", "blue",                4,  1, 3000, 15,  9, GEMSTONE, CLR_BLUE),
...

Then in code, most properties are shared, but some are unique to different types of objects.

When adding a new class of objects, in your case a tent and bedrolls, you figure out what properties you can share, what properties are new and unique. Create your structures for it, create your data table, then go through all the actions involved and add a line for the object type referencing your data tables.

##### Share on other sites

Ensure the common case has a sensible default that allows you to skip providing the data

everything gets init'ed to default values. so you only have to set non-default values to define an object type, action, skill, or animal type. all info only needs to be entered at one point in the code, but not all necessarily at the same point    : (           a little re-org there might help a bit. page down is faster than "find"  <g>.

So you dont need to finish/decide every aspect of the entire item before it even exists.

i try to just knock them out.      god - how many times today did i look up the wave ID number for the make leather sound effect?   <g>.

See if theres any way you can add more templates/helpers to minimize amount of data/boilerplate you need to input

i keep some boilerplate code of various types in comments, ready to copy, paste, and edit.

i suppose once the game is done, i'd know everything i need, and could then write an engine to do it <g>.

i've been toying with the idea of user defined keywords in cscript. cscript is an in-house c++ macro processor/codegen i use. its uses a shorthand syntax that expands to c++ code, and can be mixed freely with c++ code.  so i code in a combo of shorthand cscript and c++, then run it through the cscript macro processor to generate the c++ file for the build. only adds a second to the build pipeline. saves a LOT of keystrokes on code entry.  the idea is the user can define a keyword in the code that expands into whatever text they want (such as boilerplate code). token replacement is even possible.

as i identify ways to improve it i implement them. i discovered that learning skills, crafting objects, and undertaking actions all have pre-requisites in the form of skills, tools, and or parts. so i created a requirements_struct to hold such info,and methods to use it. newer code stores requirements in a requirements_struct and can usually use generic routines.

##### Share on other sites

I'd be heavily tempted to either throw it in a data file with JSON or YAML format, or directly in a Python list/dict.

the issue isn't really about how to get the data into the game.  no matter what, from hard coded to custom editor, i still have a fair amount of data to enter. the current methods aren't really that bad.

its more about the fact that new objects requires new actions, etc, and so adding stuff becomes a bit of a project. maybe its just a complex game. in my OP i forgot to mention drawing.  by default, objects are drawn as a rotating glowing green pillar, an obvious placeholder graphic in a stone age setting. existing code used routines that took a bunch of drawing info parameters, stuffed them into a drawinfo_struct, then sent them off to the render queue. so i made a generic routine that took the drawing info for a dropped object and its location, stuffs them into a drawinfo_struct, and sends them to the render queue. then i added a line of code for each new object to call it - wait i think i forgot bedroll! and only did teepee and teepeekit. i'll have to check.    and i still have to add collision checks for teepees, and sheltering effects of teepees: exposure, fires and torches not going out in the rain, rain and snow not waking you up, being out in the rain or snow not reducing your mood, etc. all that code will have to be modified too. this is where it gets ugly. everything affects everything in this game. so adding anything new can affect many aspects of the existing simulation. the basic rule is "if it should do it, it does do it - or its on the todo list still".

Edited by Norman Barrows

##### Share on other sites

these seem similar to items in NetHack and related games

yes there are similarities. those you find in games with objects (inventory items).

Map levels have lines that are readable enough

the world map and cavern (dungeon) maps (all 20,000 of them!) are procedurally generated in caveman. cavern maps are generated on the fly when you first enter, then stored on disk. all encounters are random. the only spawn points are random ones in caverns, and at shelters occupied by other bands of cavemen. the game will have 50 quest generators, of which 20 have been implemented (compare to two for skyrim) so i don't really have to make that much in the way of level map or canned hard coded content, just the parts the generated content is made from.

The objects themselves can contain a long list of properties

the entire list of properties for an object in caveman is about 3-4 times that size. : (      i might have mentioned perhaps half of them in my posts here.  there's a whole slew of them related to weapons, and another whole slew related to food stuffs.

Then you've got a list of all the objects:

the teepee kit is the 241st type of object i've added to the game   : (

takedown teepee is the 108th action i've added.    : (      note that many actions are generic, such as one learn action for all 50 skills, one training action for all 65 types of weapons, and one cook action for all 31 types of food you can prepare in the game.  despite doing that as much a possible, i still have over 100 action handlers.

all i have to say is i'm glad this game is almost done.  <g>.

but part of my posting is with an eye towards the future, and making it easier to add new stuff.

i guess if you have to define a bunch of info (and maybe some code) for an object (or action, or skill, or animal type), you have to define a bunch of info (and maybe some code) for an object (or action, or skill, or animal type). just no way around it perhaps?    sometimes you gotta break a few eggs to make a _real_ mayonnaise, and rome wasn't built in a day?

##### Share on other sites

i guess if you have to define a bunch of info [..]. for an object [...], you have to define a bunch of info .. for an object. just no way around it perhaps?

The best solutions are the ones already mentioned, which it seems you already know.

Cluster them and implement as much as you can as individual systems. Then you only implement the system once and feed it the data from the data tables.

241st type of object i've added ... the 108th action i've added.

That is quite a few types.  I think the only game I've worked on that even approached that number of object types was The Sims 3.

Having that many actions may be a big deal or not, depending on your design.  (I seem to recall your system is made in C. You can still implement virtual dispatch, that came from C and was generalized into C++'s vtables.)

It is probably a bit late now that you're so far along, but I've seen a pattern work well on many of the games I've worked on. With virtual dispatch (or function pointer tables in c) and a visitor pattern, interactions can be straightforward. Each new object you create can define its own set of interactions. A query function (function pointer on your generic object) can return a collection filled with interaction structures, each structure with virtual dispatch functions that help perform all the interactions. So a function to test if an interaction is valid, another to get the interaction's name, another to create an instance of the interaction, whatever you need. Interactions usually take an actor (the player or monster) and target object (the thing being used) and a collection of other objects (additional items being used), then have a function to do the thing.

With that pattern, adding new actions to each class of items is fairly simple, and the sets of available interactions can be composed on the objects. Potions might be marked as having drinkable, throwable, stowable, flamable, dippable, etc.  Swords might be marked as weildable, throwable, stowable, etc.  With interaction systems in place, and customizeable interactions families for each, adding new interactions becomes a simpler process of composing objects.  Programming to base classes (or the equivalent structures and function pointer system in c) lets all object instances be replaceable, letting anything that implements the interface gain the action.

##### Share on other sites

Cluster them and implement as much as you can as individual systems. Then you only implement the system once and feed it the data from the data tables.

yes, i try to find commonalities in the code and collapse things together when possible, such as the new generic location_is_in_teepee() routine. that's the low level API call. the higher level ones are world_object_in_teepee() and bandmember_in_teepee(), and they simply call location_is_in_teepee() with a bandmember or world object location. by adding bandmember_in_teepee() to bandmember_has_shelter() and adding world_object_in_teepee(fire) to fire_has_shelter(), i was able to handle all the effects of shelters with just a handful of code.

teepees are a new kind of collide-able world object (IE building) whose collision checks are different from all existing types of buildings such as huts and lean-to's.  a separate teepee_in_way() routine is used for movement collision checks. the algo is similar, but its all contained in a single loop in a single routine for speed. i was lazy. i should have added setup teepees as collision map objects. for now i just loop thru the 500 world objects looking for teepees in the way.

unlike your typical level based shooter which stores dropped objects in a per level change list (i assume), caveman has just one list for the entire world, and dropped objects are considered abandoned if you move far from them. buildings are the exception, they persist until they weather away or are demolished or explicitly abandoned by the player.

I think the only game I've worked on that even approached that number of object types was The Sims 3.

Your work (The Sims 3) is one of the two titles used as inspiration for Caveman v3.0.  Needs (water food, hygiene, mood, etc) and Actions were both inspired by The SIMs.

but see, Will was smart. he setup an engine based around about half a dozen needs, and made it use programmable game objects that only really affect those variables (at least in the original design of the system).  so you get a great deal of plug and play modularity with games objects, with little or no integration issues.  the limitation of course, being that objects can only affect those basic needs. they can't keep your fire from going out in the rain for example, without extra coding.

i think one of the fundamental issues with caveman is the "everything affects everything" aspect of the game rules. if caveman were a tabletop rpg, it would have complex in-depth rules for everything imaginable. and generally speaking, the more complex the rules of a game are, the more code is required to implement those rules.  so all one can do is try to code smart and efficiently. generic routines to the rescue.  i find it interesting that as i identify and implement generic data structures and routines, the code takes on a bit more of a component type flavor, with "has-a" composition type relations. IE an object "has a"  requirements_struct, a drawinfo_struct, and a location_struct - that sort of thing.

a related issue is re-factoring. when i identify and implement new generic components, i try to use them for all new work, but generally don't go back and refactor the exiting code to use the new generic APIs, as it produces no new work. if i later change some older code, i might rewrite it using the generic APIs. so i more or less port stuff to the newer APIs as needed. re-factoring working exiting features doesn't add new features to the game.

one issue i always have with this is when i need to add a new capability to a system (such as texture scaling for rigid body models), and i already have a bunch of assets created that have no data related to that new capability and would have to be "ported" to the new "format". i never have found a good solution to this. its the old issue of you have to change a data format during development, and port existing data to the new format.   about the only solution i've found for that is to init data structures to default values, then read in / set the data, overwriting the default values. any data not present to be read in (or not set) keeps the default values. this makes assets sort of automatically backward compatible.

c++ compiler for stronger compile time type checking. but the syntax is pretty much old school PODs and stand alone functions, organized into ADTs.

You can still implement virtual dispatch

i did go the function variable route for action handlers. but that's the only "virtual method call" <g>   so it didn't save a whole lot of code.   next time i'd probably just do a big switch on action_type.  whether the action handler address is stored in a variable in the actions database, or in a switch statement, its still the same basic amount of constant data that has to be entered somehow somewhere - hard coded, loaded from a file created with a custom editor, or something in-between (json, xml, lua, etc).

With that pattern [visitor + dynamic dispatch], adding new actions to each class of items is fairly simple, and the sets of available interactions can be composed on the objects.

hmm...

i don't really have issues with assigning action handlers to game objects.  either i can use or create a new generic handler routine, or its some type of new action which is unique to the game object type in question.

but one thing i haven't done is go though and see where i can find commonalities such as those between selected_bedding and selected_bedroll for example (which are the same except for collision checks basically).  as i continue to complete the final features, i should probably try to make such things generic as well.

Edited by Norman Barrows

##### Share on other sites

postmortem

i reviewed the processes for adding skills, actions, animal types, object types, quest types, and terrain types to the game, and made what simplifications i could.

instead of lookup table functions spread throughout the code, stats will now be stored in the databases (AoS's) for "one stop" editing.

a generic runaction() routine can be used for action handlers that are only affected by things specified in a requirements struct (IE tools, parts and skills required).  action handlers that are affected by things like the presence of some inventory item still require custom code.

wherever possible, variables are set to default values for the most common case, so only special cases require non-default values.

wherever possible, functions such as draw_dropped _object default to the most common case, so only special cases require custom coding.

i started using switch(action_type) to call action handlers, but a procedural variable is probably less work once you have all your typedefs for all required types of parameter lists.

and now its time to add new objects, animals, and actions - hopefully a bit more quickly and easily.