phantasia

Phantasia - 2D SDL3 RPG prototype.
git clone git://git.beep.wimdupont.com/phantasia.git
Log | Files | Refs | README | LICENSE

content.c (16039B)


      1 #include "game/content.h"
      2 
      3 #include "config.h"
      4 
      5 const PhAudioDef ph_audio_defs[PH_AUDIO_COUNT] = {
      6 	[PH_MUSIC_MEADOW] = {
      7 		"assets/vendor/audio/calm-loop/meadow.wav", PH_MUSIC_GAIN * 0.85f },
      8 	[PH_MUSIC_TOWN] = {
      9 		"assets/vendor/audio/peacefull-ville/stonehollow.wav", PH_MUSIC_GAIN },
     10 	[PH_MUSIC_BATTLE] = {
     11 		"assets/vendor/audio/battle-theme/battle.wav", PH_MUSIC_GAIN * 0.80f },
     12 	[PH_SOUND_ATTACK] = {
     13 		"assets/vendor/audio/rpg-sound-pack/attack.wav", PH_EFFECT_GAIN },
     14 	[PH_SOUND_HIT] = {
     15 		"assets/vendor/audio/rpg-sound-pack/hit.wav", PH_EFFECT_GAIN },
     16 	[PH_SOUND_SPELL] = {
     17 		"assets/vendor/audio/rpg-sound-pack/spell.wav", PH_EFFECT_GAIN },
     18 	[PH_SOUND_MENU] = {
     19 		"assets/vendor/audio/rpg-sound-pack/menu.wav", PH_EFFECT_GAIN * 0.60f },
     20 	[PH_SOUND_PICKUP] = {
     21 		"assets/vendor/audio/rpg-sound-pack/pickup.wav", PH_EFFECT_GAIN * 0.80f },
     22 	[PH_SOUND_DROP] = {
     23 		"assets/vendor/audio/rpg-sound-pack/drop.wav", PH_EFFECT_GAIN * 0.80f },
     24 	[PH_SOUND_STEP] = {
     25 		"assets/vendor/audio/rpg-sound-pack/step.wav", PH_EFFECT_GAIN * 0.32f },
     26 	[PH_SOUND_INTERACT] = {
     27 		"assets/vendor/audio/rpg-sound-pack/interact.wav", PH_EFFECT_GAIN * 0.65f },
     28 	[PH_SOUND_TRADE] = {
     29 		"assets/vendor/audio/rpg-sound-pack/trade.wav", PH_EFFECT_GAIN * 0.75f },
     30 };
     31 
     32 const PhAssetDef ph_asset_defs[PH_ASSET_COUNT] = {
     33 	[PH_ASSET_TILESET] = { "obj/assets/overworld_tileset_grass.bmp", { 0, 0, 0 } },
     34 	[PH_ASSET_HERO] = { "obj/assets/hero.bmp", { 0, 0, 0 } },
     35 	[PH_ASSET_ITEMS] = { "obj/assets/items.bmp", { 111, 119, 109 } },
     36 	[PH_ASSET_CRITTERS] = { "obj/assets/critters.bmp", { 255, 147, 153 } },
     37 };
     38 
     39 const PhAreaDef ph_area_defs[PH_AREA_COUNT] = {
     40 	{ PH_AREA_MEADOW, "obj/maps/ashen-meadow.phmap" },
     41 	{ PH_AREA_STONEHOLLOW, "obj/maps/stonehollow.phmap" },
     42 	{ PH_AREA_MIRA_SHOP, "obj/maps/mira-shop.phmap" },
     43 	{ PH_AREA_MAGICK_SHOP, "obj/maps/magick-shop.phmap" },
     44 };
     45 
     46 const PhTileDef ph_tile_defs[PH_TILE_DEF_COUNT] = {
     47 	{ '.', 0, PH_ASSET_TILESET, 1, 1, 0, 0, 0, 0, 0, 0, 0 },
     48 	{ ':', 0, PH_ASSET_TILESET, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
     49 	{ ';', 0, PH_ASSET_TILESET, 2, 0, 0, 0, 0, 0, 0, 0, 0 },
     50 	{ ',', 0, PH_ASSET_TILESET, 6, 2, 0, 0, 0, 0, 0, 0, 0 },
     51 	{ 'T', 0, PH_ASSET_TILESET, 6, 2, 0, 0, 0, 0, 0, 0, 0 },
     52 	{ '#', 1, PH_ASSET_TILESET, 9, 4, 0, 0, 0,
     53 		PH_TILE_AUTOTILE_3X3, 0, 0, 0 },
     54 	{ .symbol = 'J', .blocks_movement = 1, .asset_id = PH_ASSET_TILESET,
     55 		.sprite_tile_x = 0, .sprite_tile_y = 16,
     56 		.background_asset_id = PH_ASSET_TILESET,
     57 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1,
     58 		.interaction_notice = "The door is locked." },
     59 	{ .symbol = 'K', .blocks_movement = 1, .asset_id = PH_ASSET_TILESET,
     60 		.sprite_tile_x = 1, .sprite_tile_y = 16,
     61 		.background_asset_id = PH_ASSET_TILESET,
     62 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1,
     63 		.interaction_notice = "The door is locked." },
     64 	{ .symbol = 'L', .blocks_movement = 1, .asset_id = PH_ASSET_TILESET,
     65 		.sprite_tile_x = 3, .sprite_tile_y = 16,
     66 		.background_asset_id = PH_ASSET_TILESET,
     67 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1,
     68 		.interaction_notice = "The door is locked." },
     69 	{ .symbol = 'M', .blocks_movement = 1, .asset_id = PH_ASSET_TILESET,
     70 		.sprite_tile_x = 0, .sprite_tile_y = 17,
     71 		.background_asset_id = PH_ASSET_TILESET,
     72 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1,
     73 		.interaction_notice = "The door is locked." },
     74 	{ .symbol = 'N', .blocks_movement = 1, .asset_id = PH_ASSET_TILESET,
     75 		.sprite_tile_x = 1, .sprite_tile_y = 17,
     76 		.background_asset_id = PH_ASSET_TILESET,
     77 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1,
     78 		.interaction_notice = "The door is locked." },
     79 	{ .symbol = 'O', .blocks_movement = 1, .asset_id = PH_ASSET_TILESET,
     80 		.sprite_tile_x = 3, .sprite_tile_y = 17,
     81 		.background_asset_id = PH_ASSET_TILESET,
     82 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1,
     83 		.interaction_notice = "The door is locked." },
     84 	{ .symbol = 'P', .blocks_movement = 1, .asset_id = PH_ASSET_TILESET,
     85 		.sprite_tile_x = 0, .sprite_tile_y = 18,
     86 		.background_asset_id = PH_ASSET_TILESET,
     87 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1,
     88 		.interaction_notice = "The door is locked." },
     89 	{ .symbol = 'Q', .blocks_movement = 1, .asset_id = PH_ASSET_TILESET,
     90 		.sprite_tile_x = 1, .sprite_tile_y = 18,
     91 		.background_asset_id = PH_ASSET_TILESET,
     92 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1,
     93 		.interaction_notice = "The door is locked." },
     94 	{ .symbol = 'R', .blocks_movement = 1, .asset_id = PH_ASSET_TILESET,
     95 		.sprite_tile_x = 3, .sprite_tile_y = 18,
     96 		.background_asset_id = PH_ASSET_TILESET,
     97 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1,
     98 		.interaction_notice = "The door is locked." },
     99 	{ .symbol = 'j', .asset_id = PH_ASSET_TILESET,
    100 		.sprite_tile_x = 0, .sprite_tile_y = 16,
    101 		.background_asset_id = PH_ASSET_TILESET,
    102 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1 },
    103 	{ .symbol = 'k', .asset_id = PH_ASSET_TILESET,
    104 		.sprite_tile_x = 1, .sprite_tile_y = 16,
    105 		.background_asset_id = PH_ASSET_TILESET,
    106 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1 },
    107 	{ '[', 0, PH_ASSET_TILESET, 9, 1, 0, 0, 0, 0, 0, 0, 0 },
    108 	{ '^', 0, PH_ASSET_TILESET, 10, 1, 0, 0, 0, 0, 0, 0, 0 },
    109 	{ ']', 0, PH_ASSET_TILESET, 11, 1, 0, 0, 0, 0, 0, 0, 0 },
    110 	{ '{', 0, PH_ASSET_TILESET, 9, 2, 0, 0, 0, 0, 0, 0, 0 },
    111 	{ '=', 0, PH_ASSET_TILESET, 10, 2, 0, 0, 0, 0, 0, 0, 0 },
    112 	{ '}', 0, PH_ASSET_TILESET, 11, 2, 0, 0, 0, 0, 0, 0, 0 },
    113 	{ '(', 1, PH_ASSET_TILESET, 9, 3, 0, 0, 0, 0, 0, 0, 0 },
    114 	{ '_', 1, PH_ASSET_TILESET, 10, 3, 0, 0, 0, 0, 0, 0, 0 },
    115 	{ ')', 1, PH_ASSET_TILESET, 11, 3, 0, 0, 0, 0, 0, 0, 0 },
    116 	{ '+', 0, PH_ASSET_TILESET, 5, 15, 0, 0, 0, 0, 0, 0, 0 },
    117 	{ .symbol = 'V', .blocks_movement = 1, .asset_id = PH_ASSET_TILESET,
    118 		.sprite_tile_x = 0, .sprite_tile_y = 13,
    119 		.background_asset_id = PH_ASSET_TILESET,
    120 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1,
    121 		.autotile = PH_TILE_AUTOTILE_FIXED,
    122 		.autotile_width = 4, .autotile_height = 3 },
    123 	{ .symbol = 'v', .asset_id = PH_ASSET_TILESET,
    124 		.sprite_tile_x = 0, .sprite_tile_y = 13,
    125 		.background_asset_id = PH_ASSET_TILESET,
    126 		.background_sprite_tile_x = 1, .background_sprite_tile_y = 1,
    127 		.autotile = PH_TILE_AUTOTILE_FIXED,
    128 		.autotile_width = 4, .autotile_height = 3 },
    129 };
    130 
    131 const PhSpellDef ph_spell_defs[PH_SPELL_DEF_COUNT] = {
    132 	{ .id = PH_SPELL_FLASH, .name = "Flash",
    133 		.description = "A blinding spark that hastens the caster and hinders its target.",
    134 		.battle_use = PH_SPELL_BATTLE_DAMAGE,
    135 		.target = PH_SPELL_TARGET_OPPONENT, .mp_cost = 15, .power = 5,
    136 		.delay_percent = 50, .accuracy = { 20, 3 } },
    137 	{ .id = PH_SPELL_HEAL, .name = "Heal",
    138 		.description = "Restorative magick that mends wounds in or out of battle.",
    139 		.battle_use = PH_SPELL_BATTLE_HEAL, .field_use = PH_SPELL_FIELD_HEAL,
    140 		.target = PH_SPELL_TARGET_ALLY,
    141 		.mp_cost = 10, .power = 18, .delay_percent = 100 },
    142 	{ .id = PH_SPELL_EMBER, .name = "Ember",
    143 		.description = "A small, reliable flame cast at one enemy.",
    144 		.battle_use = PH_SPELL_BATTLE_DAMAGE,
    145 		.target = PH_SPELL_TARGET_OPPONENT, .mp_cost = 8, .power = 7,
    146 		.delay_percent = 100, .buy_value = 24 },
    147 	{ .id = PH_SPELL_DAZE, .name = "Daze",
    148 		.description = "A disorienting blast that makes an enemy less accurate.",
    149 		.battle_use = PH_SPELL_BATTLE_DAMAGE,
    150 		.target = PH_SPELL_TARGET_OPPONENT, .mp_cost = 12, .power = 4,
    151 		.delay_percent = 85, .buy_value = 48, .accuracy = { 15, 2 } },
    152 	{ .id = PH_SPELL_INFERNO, .name = "Inferno",
    153 		.description = "A fierce conflagration with a long casting delay.",
    154 		.battle_use = PH_SPELL_BATTLE_DAMAGE,
    155 		.target = PH_SPELL_TARGET_OPPONENT, .mp_cost = 28, .power = 24,
    156 		.delay_percent = 140, .buy_value = 600 },
    157 	{ .id = PH_SPELL_METEOR, .name = "Meteor",
    158 		.description = "Forbidden starfire of overwhelming power and delay.",
    159 		.battle_use = PH_SPELL_BATTLE_DAMAGE,
    160 		.target = PH_SPELL_TARGET_OPPONENT, .mp_cost = 45, .power = 40,
    161 		.delay_percent = 180, .buy_value = 2400 },
    162 };
    163 
    164 const PhSpellBook ph_player_spellbook = {
    165 	.defs = ph_spell_defs,
    166 	.learned = { PH_SPELL_FLASH, PH_SPELL_HEAL },
    167 	.def_count = PH_SPELL_DEF_COUNT,
    168 	.learned_count = PH_PLAYER_SPELL_COUNT,
    169 };
    170 
    171 const PhEntityDef ph_entity_defs[PH_ENTITY_DEF_COUNT] = {
    172 	{
    173 		.id = PH_DEF_PLAYER,
    174 		.name = "Wayfarer",
    175 		.stats = { .max_hp = 40, .max_mp = 50, .strength = 5, .defense = 3,
    176 			.magic = 4, .magic_defense = 3, .agility = 10, .luck = 5,
    177 			.evasion = 2, .accuracy = 100 },
    178 		.move_speed = 1,
    179 		.starting_gold = 40,
    180 		.asset_id = PH_ASSET_HERO,
    181 		.sprite_tiles = {
    182 			[PH_SPRITE_DOWN] = { { 4, 0 }, { 5, 0 }, { 6, 0 }, { 7, 0 } },
    183 			[PH_SPRITE_UP] = { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 } },
    184 			[PH_SPRITE_SIDE] = { { 0, 1 }, { 1, 1 }, { 2, 1 }, { 3, 1 } },
    185 		},
    186 		.animation_frames = 4,
    187 		.blocks_movement = 0,
    188 		.kind = PH_ENTITY_PLAYER,
    189 	},
    190 	{
    191 		.id = PH_DEF_SLIME,
    192 		.name = "Mire Slime",
    193 		.stats = { .max_hp = 12, .strength = 3, .defense = 1,
    194 			.magic_defense = 1, .agility = 10, .luck = 1,
    195 			.evasion = 2, .accuracy = 100 },
    196 		.move_speed = 1,
    197 		.vision = 6,
    198 		.aggressive = 1,
    199 		.reward = { 8, PH_ITEM_HEALING_POTION, 1 },
    200 		.asset_id = PH_ASSET_CRITTERS,
    201 		.sprite_tiles = {
    202 			[PH_SPRITE_DOWN] = { { 12, 0 }, { 13, 0 }, { 14, 0 }, { 15, 0 } },
    203 			[PH_SPRITE_UP] = { { 12, 0 }, { 13, 0 }, { 14, 0 }, { 15, 0 } },
    204 			[PH_SPRITE_SIDE] = { { 12, 0 }, { 13, 0 }, { 14, 0 }, { 15, 0 } },
    205 		},
    206 		.animation_frames = 4,
    207 		.blocks_movement = 1,
    208 		.kind = PH_ENTITY_MONSTER,
    209 	},
    210 	{
    211 		.id = PH_DEF_SHOPKEEPER,
    212 		.name = "Mira",
    213 		.interaction_id = PH_INTERACTION_MIRA,
    214 		.asset_id = PH_ASSET_HERO,
    215 		.sprite_tiles = {
    216 			[PH_SPRITE_DOWN] = { { 5, 0 } },
    217 			[PH_SPRITE_UP] = { { 5, 0 } },
    218 			[PH_SPRITE_SIDE] = { { 5, 0 } },
    219 		},
    220 		.animation_frames = 1,
    221 		.blocks_movement = 1,
    222 		.kind = PH_ENTITY_NPC,
    223 	},
    224 	{
    225 		.id = PH_DEF_MAGICK_KEEPER,
    226 		.name = "Orin",
    227 		.interaction_id = PH_INTERACTION_MAGUS,
    228 		.asset_id = PH_ASSET_HERO,
    229 		.sprite_tiles = {
    230 			[PH_SPRITE_DOWN] = { { 0, 0 } },
    231 			[PH_SPRITE_UP] = { { 0, 0 } },
    232 			[PH_SPRITE_SIDE] = { { 0, 0 } },
    233 		},
    234 		.animation_frames = 1,
    235 		.blocks_movement = 1,
    236 		.kind = PH_ENTITY_NPC,
    237 	},
    238 };
    239 
    240 const PhItemDef ph_item_defs[PH_ITEM_DEF_COUNT] = {
    241 	{
    242 		.id = PH_ITEM_RUSTED_SPEAR,
    243 		.name = "Rusted Spear",
    244 		.description = "A worn but sturdy spear.",
    245 		.buy_value = 12,
    246 		.sell_value = 6,
    247 		.bonuses = { .strength = 2 },
    248 		.equip_slot = PH_EQUIP_WEAPON,
    249 		.asset_id = PH_ASSET_ITEMS,
    250 		.sprite_tile_x = 2,
    251 		.sprite_tile_y = 6,
    252 	},
    253 	{
    254 		.id = PH_ITEM_WAYFARER_BLADE,
    255 		.name = "Wayfarer Blade",
    256 		.description = "A reliable traveler's blade.",
    257 		.buy_value = 20,
    258 		.sell_value = 10,
    259 		.bonuses = { .strength = 3 },
    260 		.equip_slot = PH_EQUIP_WEAPON,
    261 		.asset_id = PH_ASSET_ITEMS,
    262 		.sprite_tile_x = 0,
    263 		.sprite_tile_y = 0,
    264 	},
    265 	{
    266 		.id = PH_ITEM_TRAVELER_COAT,
    267 		.name = "Traveler Coat",
    268 		.description = "A coat hardened by the road.",
    269 		.buy_value = 18,
    270 		.sell_value = 9,
    271 		.bonuses = { .max_hp = 5, .defense = 2 },
    272 		.equip_slot = PH_EQUIP_ARMOR,
    273 		.asset_id = PH_ASSET_ITEMS,
    274 		.sprite_tile_x = 4,
    275 		.sprite_tile_y = 6,
    276 	},
    277 	{
    278 		.id = PH_ITEM_HEALING_POTION,
    279 		.name = "Healing Potion",
    280 		.description = "Restores 15 HP.",
    281 		.buy_value = 8,
    282 		.sell_value = 4,
    283 		.use = PH_ITEM_USE_HEAL,
    284 		.use_power = 15,
    285 		.asset_id = PH_ASSET_ITEMS,
    286 		.sprite_tile_x = 3,
    287 		.sprite_tile_y = 0,
    288 	},
    289 	{
    290 		.id = PH_ITEM_ETHER,
    291 		.name = "Ether",
    292 		.description = "Restores 15 MP.",
    293 		.buy_value = 12,
    294 		.sell_value = 6,
    295 		.use = PH_ITEM_USE_RESTORE_MP,
    296 		.use_power = 15,
    297 		.asset_id = PH_ASSET_ITEMS,
    298 		.sprite_tile_x = 4,
    299 		.sprite_tile_y = 0,
    300 	},
    301 };
    302 
    303 static const int ph_player_equipment[PH_EQUIP_COUNT] = {
    304 	[PH_EQUIP_WEAPON] = PH_ITEM_WAYFARER_BLADE,
    305 	[PH_EQUIP_ARMOR] = PH_ITEM_TRAVELER_COAT,
    306 };
    307 
    308 const PhMarkerDef ph_marker_defs[PH_MARKER_DEF_COUNT] = {
    309 	{ PH_AREA_MEADOW, '@', '.', PH_MARKER_PLAYER, PH_DEF_PLAYER, .amount = 0 },
    310 	{ PH_AREA_MEADOW, 'a', '.', PH_MARKER_ENTITY, PH_DEF_SLIME,
    311 		.territory_radius = 5 },
    312 	{ PH_AREA_MEADOW, '1', '.', PH_MARKER_ITEM,
    313 		PH_ITEM_RUSTED_SPEAR, .amount = 1 },
    314 	{ PH_AREA_MEADOW, '2', '.', PH_MARKER_ITEM,
    315 		PH_ITEM_HEALING_POTION, .amount = 1 },
    316 	{ PH_AREA_MEADOW, '>', 'v', PH_MARKER_PORTAL,
    317 		.destination_area_id = PH_AREA_STONEHOLLOW, .destination_symbol = 'e' },
    318 	{ PH_AREA_MEADOW, 'e', '.', PH_MARKER_ARRIVAL, .content_id = 0 },
    319 	{ PH_AREA_STONEHOLLOW, '>', 'j', PH_MARKER_PORTAL,
    320 		.destination_area_id = PH_AREA_MIRA_SHOP, .destination_symbol = 'e' },
    321 	{ PH_AREA_STONEHOLLOW, '<', 'T', PH_MARKER_PORTAL,
    322 		.destination_area_id = PH_AREA_MEADOW, .destination_symbol = 'e' },
    323 	{ PH_AREA_STONEHOLLOW, 'e', ',', PH_MARKER_ARRIVAL, .content_id = 0 },
    324 	{ PH_AREA_STONEHOLLOW, 'i', ',', PH_MARKER_ARRIVAL, .content_id = 0 },
    325 	{ PH_AREA_STONEHOLLOW, '*', 'k', PH_MARKER_PORTAL,
    326 		.destination_area_id = PH_AREA_MAGICK_SHOP, .destination_symbol = 'e' },
    327 	{ PH_AREA_STONEHOLLOW, 'o', ',', PH_MARKER_ARRIVAL, .content_id = 0 },
    328 	{ PH_AREA_MIRA_SHOP, 'm', '=', PH_MARKER_ENTITY,
    329 		PH_DEF_SHOPKEEPER, .amount = 0 },
    330 	{ PH_AREA_MIRA_SHOP, 'e', '=', PH_MARKER_ARRIVAL, .content_id = 0 },
    331 	{ PH_AREA_MIRA_SHOP, '<', '+', PH_MARKER_PORTAL,
    332 		.destination_area_id = PH_AREA_STONEHOLLOW, .destination_symbol = 'i' },
    333 	{ PH_AREA_MAGICK_SHOP, 'n', '=', PH_MARKER_ENTITY,
    334 		PH_DEF_MAGICK_KEEPER, .amount = 0 },
    335 	{ PH_AREA_MAGICK_SHOP, 'e', '=', PH_MARKER_ARRIVAL, .content_id = 0 },
    336 	{ PH_AREA_MAGICK_SHOP, '<', '+', PH_MARKER_PORTAL,
    337 		.destination_area_id = PH_AREA_STONEHOLLOW, .destination_symbol = 'o' },
    338 };
    339 
    340 static const PhAreaCatalog ph_area_catalog = {
    341 	.areas = ph_area_defs,
    342 	.markers = ph_marker_defs,
    343 	.tiles = ph_tile_defs,
    344 	.area_count = PH_AREA_COUNT,
    345 	.marker_count = PH_MARKER_DEF_COUNT,
    346 	.tile_count = PH_TILE_DEF_COUNT,
    347 };
    348 
    349 const PhWorldSetup ph_game_world = {
    350 	.areas = &ph_area_catalog,
    351 	.equipment = ph_player_equipment,
    352 	.content = {
    353 		.entities = ph_entity_defs,
    354 		.items = ph_item_defs,
    355 		.entity_count = PH_ENTITY_DEF_COUNT,
    356 		.item_count = PH_ITEM_DEF_COUNT,
    357 	},
    358 	.start_area_id = PH_START_AREA,
    359 	.equipment_count = PH_EQUIP_COUNT,
    360 };
    361 
    362 const PhShopDef ph_shop_defs[PH_SHOP_COUNT] = {
    363 	{
    364 		.id = PH_SHOP_STONEHOLLOW,
    365 		.name = "Mira's Sundries",
    366 		.kind = PH_SHOP_ITEMS,
    367 		.stock = { PH_ITEM_HEALING_POTION, PH_ITEM_ETHER },
    368 		.stock_count = 2,
    369 		.talk = {
    370 			"Fine weather for adventuring.",
    371 			"Terrible weather for staying solvent.",
    372 		},
    373 	},
    374 	{
    375 		.id = PH_SHOP_MAGICK,
    376 		.name = "Orin's Magick",
    377 		.kind = PH_SHOP_SPELLS,
    378 		.stock = { PH_SPELL_EMBER, PH_SPELL_DAZE,
    379 			PH_SPELL_INFERNO, PH_SPELL_METEOR },
    380 		.stock_count = 4,
    381 		.talk = {
    382 			"A spell is a promise made to fire.",
    383 			"The costly promises answer last.",
    384 		},
    385 	},
    386 };
    387 
    388 const PhInteractionDef ph_interaction_defs[PH_INTERACTION_COUNT] = {
    389 	{ PH_INTERACTION_MIRA, PH_INTERACTION_SHOP, PH_SHOP_STONEHOLLOW },
    390 	{ PH_INTERACTION_MAGUS, PH_INTERACTION_SHOP, PH_SHOP_MAGICK },
    391 };
    392 
    393 const PhShopDef *
    394 ph_game_shop_def(int shop_id)
    395 {
    396 	int i;
    397 
    398 	for (i = 0; i < PH_SHOP_COUNT; ++i)
    399 		if (ph_shop_defs[i].id == shop_id) return &ph_shop_defs[i];
    400 	return NULL;
    401 }
    402 
    403 const PhInteractionDef *
    404 ph_game_interaction_def(int interaction_id)
    405 {
    406 	int i;
    407 
    408 	for (i = 0; i < PH_INTERACTION_COUNT; ++i)
    409 		if (ph_interaction_defs[i].id == interaction_id)
    410 			return &ph_interaction_defs[i];
    411 	return NULL;
    412 }
    413 
    414 int
    415 ph_game_area_music(int area_id)
    416 {
    417 	return area_id == PH_AREA_MEADOW ? PH_MUSIC_MEADOW : PH_MUSIC_TOWN;
    418 }
    419 
    420 int
    421 ph_game_battle_sound(PhBattleEvent event)
    422 {
    423 	switch (event) {
    424 	case PH_BATTLE_EVENT_ATTACK: return PH_SOUND_ATTACK;
    425 	case PH_BATTLE_EVENT_HIT: return PH_SOUND_HIT;
    426 	case PH_BATTLE_EVENT_SPELL: return PH_SOUND_SPELL;
    427 	default: return PH_AUDIO_NONE;
    428 	}
    429 }
    430 
    431 int
    432 ph_game_world_sound(const PhWorld *world, const PhWorldEvent *event)
    433 {
    434 	(void)world;
    435 	if (!event) return PH_AUDIO_NONE;
    436 	switch (event->kind) {
    437 	case PH_WORLD_EVENT_MOVE: return PH_SOUND_STEP;
    438 	case PH_WORLD_EVENT_INTERACT: return PH_SOUND_INTERACT;
    439 	case PH_WORLD_EVENT_ITEM_PICKUP: return PH_SOUND_PICKUP;
    440 	case PH_WORLD_EVENT_ITEM_DROP: return PH_SOUND_DROP;
    441 	case PH_WORLD_EVENT_BUY:
    442 	case PH_WORLD_EVENT_SELL: return PH_SOUND_TRADE;
    443 	default: return PH_AUDIO_NONE;
    444 	}
    445 }