Content Guide¶
Almost all data is held in the external json files, certainly as much as is possible without limiting gameplay diversity. This is a guide on how to add new content to the game, broken down by content type.
Playable Traits¶
Create a new entry in the traits.json and ensure the group is not set to “npc”. That’s it!
One thing to note is that the known_skills must match the class name of a Skill registered with the engine. See the Action section for more details.
Example:
"dandy": {
"__dataclass__": "TraitData",
"group":"savvy",
"bustle": 2,
"clout": 2,
"description": "Lounging around the taverns of the world they can always be found one word away from an indignant splutter and a grasp for their sword.",
"exactitude": -1,
"name": "irascible dandy",
"sight_range": 5,
"known_skills": [
"Lunge",
"TarAndFeather"
],
"permanent_afflictions": [
"none"
],
"skullduggery": 1,
"sprite_paths": {
"__dataclass__": "TraitSpritePathsData",
"idle": "actor/savvy/dandy_idle.png",
"attack": "actor/savvy/dandy_attack.png",
"hit": "actor/savvy/dandy_hit.png",
"dead": "actor/savvy/dandy_dead.png",
"icon": "actor/savvy/dandy_icon.png",
"move": "actor/savvy/dandy_move.png"
},
"vigour": -1
}
Actors (NPCs)¶
Create a new entry in traits.json and specify group as “npc”. Next, create an entry in actors.json and set the trait_names to include the key of the trait you just created. The behaviour_name must exactly match a Behaviour class registered with the engine. See the Action section for more information.
Trait example:
"dandy": {
"__dataclass__": "TraitData",
"group":"savvy",
"bustle": 2,
"clout": 2,
"description": "Lounging around the taverns of the world they can always be found one word away from an indignant splutter and a grasp for their sword.",
"exactitude": -1,
"name": "irascible dandy",
"sight_range": 5,
"known_skills": [
"Lunge",
"TarAndFeather"
],
"permanent_afflictions": [
"none"
],
"skullduggery": 1,
"sprite_paths": {
"__dataclass__": "TraitSpritePathsData",
"idle": "actor/savvy/dandy_idle.png",
"attack": "actor/savvy/dandy_attack.png",
"hit": "actor/savvy/dandy_hit.png",
"dead": "actor/savvy/dandy_dead.png",
"icon": "actor/savvy/dandy_icon.png",
"move": "actor/savvy/dandy_move.png"
},
"vigour": -1
}
Actor example:
"training_dummy": {
"__dataclass__": "ActorData",
"key": "training_dummy",
"possible_names": [
"sally dummy",
"steve dummy"
],
"description": "It just looks so darn punchable.",
"position_offsets": [
[0, 0]
],
"trait_names": [
"dummy"
],
"behaviour_name": "SkipTurn",
"height": "diminutive"
}
Terrain¶
Create a new entry in terrain.json.
Example:
"bog": {
"__dataclass__": "TerrainData",
"blocks_movement": false,
"height": "min",
"description": "This is a bog. It slows entities down.",
"name": "bog",
"sprite_paths": {
"__dataclass__": "TraitSpritePathsData",
"idle": "terrain/bog.png"
},
"position_offsets": [
[0, 0]
],
"light": null,
"reactions": {
"proximity": {
"__dataclass__": "ReactionData",
"required_opinion": null,
"reaction": {
"__dataclass__": "ApplyAfflictionEffectData",
"affliction_name": "BoggedDown",
"duration": 3
}
}
}
}
Map (a game level)¶
Create the rooms you want to include in the map within rooms.json. Next, create an entry for the map in maps.json.
Room example:
"combat": {
"name": "Combat Room",
"key": "combat",
"__dataclass__": "RoomConceptData",
"design": "square",
"min_actors": 1,
"max_actors": 3,
"min_width": 8,
"min_height": 8,
"max_width": 10,
"max_height": 10,
"chance_of_spawning_wall": 0.45,
"max_neighbouring_walls_in_room": 4,
"sprite_paths": {
"floor": "world/floor.png",
"wall": "world/wall.png"
},
"actors": {
"training_dummy": 0.2,
"crocturion": 0.8
}
}
Map example:
"cave": {
"name": "cave",
"key": "cave",
"__dataclass__": "MapData",
"min_rooms": 10,
"max_rooms": 30,
"max_tunnel_length": 20,
"min_path_distance_for_shortcut": 5,
"width": 40,
"height": 40,
"rooms": {
"combat": 0.2,
"empty": 0.1
},
"sprite_paths": {
"wall": "world/wall.png",
"floor": "world/floor.png"
},
"max_room_entrances": 2,
"extra_entrance_chance": 10,
"chance_of_tunnel_winding": 10
}
God¶
Create an entry in gods.json. The attitudes and reactions blocks can contain any number of entries.
Example:
"the_small_gods": {
"__dataclass__": "GodData",
"name": "the_small_gods",
"description": "Hordes of small gods banded together to ensure they were finally taken seriously.",
"attitudes": {
"deal_damage": -5
},
"reactions": {
"deal_damage": {
"__dataclass__": "ReactionData",
"required_opinion": -80,
"reaction": "BasicAttack",
"chance": 10
}
}
}
Action¶
As a type of Action, things are a little more involved here. In addition to the json entry there must also be a python class registered with the engine using register_action from scripts.engine.internal.action. The key in the json must also match the class name exactly.
Affliction¶
Example json:
"BoggedDown": {
"__dataclass__": "AfflictionData",
"category": "bane",
"description": "It weakens mundane defence and makes you a little bit slower.",
"icon_path": "skills/root.png",
"name": "bogged down",
"identity_tags": [
"affect_stat"
],
"triggers": [
"movement"
]
}
Example class:
class BoggedDown(Affliction):
"""
A demo of being stuck in the mud - it`ll ground you
"""
# targeting
target_tags: List[TileTagType] = [TileTag.OTHER_ENTITY]
shape: ShapeType = Shape.TARGET
shape_size: int = 1
def _build_effects(self, entity: EntityID, potency: float = 1.0) -> List[AffectStatEffect]: # type: ignore
affect_stat_effect = AffectStatEffect(
origin=self.origin,
cause_name=self.name,
success_effects=[],
failure_effects=[],
target=self.affected_entity,
stat_to_target=PrimaryStat.BUSTLE,
affect_amount=2,
)
return [affect_stat_effect]
Skill¶
Example json:
"Lightning": {
"__dataclass__": "SkillData",
"name": "Call Lightning",
"description": "Shocking.",
"cooldown": 1,
"icon_path": "",
"resource_cost": 10,
"resource_type": "stamina",
"time_cost": 35
}
Example class:
class Lightning(Skill):
"""
A demo of lightning - it`s shocking!
"""
# casting
cast_tags: List[TileTagType] = [TileTag.NO_BLOCKING_TILE]
# targeting
range: int = 3
target_tags: List[TileTagType] = [TileTag.ANY]
targeting_method: TargetingMethodType = TargetingMethod.TILE
target_directions: List[DirectionType] = [Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT,
Direction.UP_LEFT, Direction.UP_RIGHT, Direction.DOWN_LEFT, Direction.DOWN_RIGHT]
shape: ShapeType = Shape.TARGET
shape_size: int = 1
# delivery
uses_projectile: bool = False
projectile_data: Optional[ProjectileData] = None
is_delayed: bool = True
delayed_skill_data: Optional[DelayedSkillData] = DelayedSkillData(
duration=3
)
def _build_effects(self, entity: EntityID, potency: float = 1.0) -> List[DamageEffect]:
damage_effect = DamageEffect(
origin=self.user,
success_effects=[],
failure_effects=[],
target=entity,
stat_to_target=PrimaryStat.VIGOUR,
accuracy=library.GAME_CONFIG.base_values.accuracy + 20,
damage=int(library.GAME_CONFIG.base_values.damage * potency),
damage_type=DamageType.MUNDANE,
mod_stat=PrimaryStat.CLOUT,
mod_amount=0.1,
)
return [damage_effect]