Created
December 17, 2024 12:36
-
-
Save colinbellino/b3a269c32341a39c824a4c97bc1ae858 to your computer and use it in GitHub Desktop.
The game code for https://colinbellino.itch.io/monstrum-prison (engine/renderer and platform code not included)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "Basic"; | |
#import "Random"; | |
#import "Math"; | |
#import "Sort"; | |
#import "Flat_Pool"; | |
#import "String"; | |
#import "Tagged_Union"(DEBUG); | |
#import,dir "../engine"( | |
DEBUG = DEBUG, | |
MAX_SPRITES = 256, | |
MAX_LAYERS = 4, | |
GRID_SIZE = GRID_SIZE, | |
ENABLE_GAME_VIEW = true, | |
ENABLE_3D = false, | |
EMBED_ASSETS = !DEBUG, | |
ASSETS_PATH = ASSETS_PATH | |
); | |
#if SUPER_DEBUG { | |
WINDOW_SIZE :: Vector2s.{ 1920, 1080 }; | |
} else { | |
WINDOW_SIZE :: Vector2s.{ 1280, 720 }; | |
} | |
UI_RESOLUTION :: Vector2s.{ 1920/4, 1080/4 }; | |
#if SUPER_DEBUG { | |
VOLUME_MAIN :: 0.1; | |
VOLUME_MUSIC :: 0.0; | |
} else { | |
VOLUME_MAIN :: 0.5; | |
VOLUME_MUSIC :: 0.5; | |
} | |
FONT_SIZE : s32 : 8; | |
VOLUME_SFX :: 0.5; | |
VOLUME_FOOTSTEP :: 0.5; | |
GRID_SIZE :: cast(s32) 32; | |
GRID_SIZE_V2 :: Vector2.{ xx GRID_SIZE, xx GRID_SIZE }; | |
GRID_SIZE_V2S :: Vector2s.{ GRID_SIZE, GRID_SIZE }; | |
PLAYER_CONTROLLER :: 0; | |
PLAYER_INDEX :: 0; | |
OFFSCREEN_POSITION :: Vector2s.{ 999, 999 }; | |
COLOR_RED2 :: #run(hex_string_to_color("#8e3438ff")); | |
COLOR_RED3 :: #run(hex_string_to_color("#af4146ff")); | |
COLOR_GREEN2 :: #run(hex_string_to_color("#325546ff")); | |
COLOR_GREEN3 :: #run(hex_string_to_color("#376c55ff")); | |
MAX_HEALTH :: 99; | |
GAME_VIEW_CENTER :: Vector2s.{ 151, 135 }; | |
PARTY_ICON_SCALE :: 1; | |
HUD_PADDING :: 4; | |
WALLS_OFFSET :: Vector2.[ | |
.{ +0.0, +0.0 }, // .NORTH | |
.{ +0.9, +0.0 }, // .EAST | |
.{ +0.0, +0.9 }, // .SOUTH | |
.{ +0.0, +0.0 }, // .WEST | |
]; | |
PARTY_HEADER_Y :: 40; | |
PARTY_MEMBER_SIZE :: Vector2s.{ 88, 28 }; | |
TILE_TEXTURES_DATA :: Tile_Texture.[ | |
.{ west_pos = .{ 88, 32 }, east_pos = .{ 64, 32 }, side_size = .{ 8, 32 }, front_pos = .{ 64, 80 }, front_size = .{ 16, 16 } }, | |
.{ west_pos = .{ 88, 32 }, east_pos = .{ 64, 32 }, side_size = .{ 8, 32 }, front_pos = .{ 64, 80 }, front_size = .{ 16, 16 } }, | |
.{ west_pos = .{ 272, 32 }, east_pos = .{ 240, 32 }, side_size = .{ 16, 64 }, front_pos = .{ 240, 96 }, front_size = .{ 32, 32 } }, | |
.{ west_pos = .{ 112, 160 }, east_pos = .{ 64, 160 }, side_size = .{ 16, 96 }, front_pos = .{ 64, 256 }, front_size = .{ 64, 64 } }, | |
]; | |
using Sounds :: enum u32 { | |
SFX_ERROR; | |
SFX_MONSTER_DEATH; | |
SFX_CONFIRM; | |
SFX_LAND; | |
SFX_DASH1; | |
SFX_HIT1; | |
SFX_HIT2; | |
SFX_FOUNTAIN_HEAL; | |
SFX_CREATURE_DRAGON; | |
SFX_CREATURE_CENTIPEDE; | |
SFX_CREATURE_MOUSE; | |
SFX_CREATURE_FROG; | |
SFX_CREATURE_BUNNY; | |
SFX_SPIKE_TRAP; | |
SFX_KEY_BUTTON; | |
SFX_COLLAR; | |
SFX_ATTACK_READY; | |
SFX_STAIRS; | |
SFX_FOOTSTEP1; | |
SFX_FOOTSTEP2; | |
SFX_FOOTSTEP3; | |
SFX_MUSIC1; | |
SFX_MUSIC1_INTRO; | |
SFX_NONE; | |
SFX_COUNT; | |
} | |
using Images :: enum u32 { | |
IMG_PLACEHOLDER; | |
IMG_SPRITESHEET; | |
IMG_TILESET1; | |
IMG_TILESET1_DOOR_C; | |
IMG_TILESET1_DOOR_O; | |
IMG_CREATURE1; | |
IMG_CREATURE1_PART1; | |
IMG_CREATURE1_PART1_O; | |
IMG_CREATURE1_PART2; | |
IMG_CREATURE1_PART2_O; | |
IMG_CREATURE1_PART3; | |
IMG_CREATURE1_PART3_O; | |
IMG_CREATURE2; | |
IMG_CREATURE2_PART1; | |
IMG_CREATURE2_PART1_O; | |
IMG_CREATURE2_PART2; | |
IMG_CREATURE2_PART2_O; | |
IMG_CREATURE2_PART3; | |
IMG_CREATURE2_PART3_O; | |
IMG_CREATURE3; | |
IMG_CREATURE3_PART1; | |
IMG_CREATURE3_PART1_O; | |
IMG_CREATURE3_PART2; | |
IMG_CREATURE3_PART2_O; | |
IMG_CREATURE3_PART3; | |
IMG_CREATURE3_PART3_O; | |
IMG_CREATURE4; | |
IMG_CREATURE4_PART1; | |
IMG_CREATURE4_PART1_O; | |
IMG_CREATURE4_PART2; | |
IMG_CREATURE4_PART2_O; | |
IMG_CREATURE4_PART3; | |
IMG_CREATURE4_PART3_O; | |
IMG_CREATURE5; | |
IMG_CREATURE5_PART1; | |
IMG_CREATURE5_PART1_O; | |
IMG_CREATURE5_PART2; | |
IMG_CREATURE5_PART2_O; | |
IMG_CREATURE5_PART3; | |
IMG_CREATURE5_PART3_O; | |
IMG_TRAP_SPIKE_C; | |
IMG_TRAP_SPIKE_O; | |
IMG_FOUNTAIN_HEAL_F; | |
IMG_FOUNTAIN_HEAL_E; | |
IMG_KEY1; | |
IMG_COLLAR; | |
IMG_CREATURE_ICONS; | |
IMG_UI_LAYOUT; | |
IMG_GAME_OVER; | |
IMG_TITLE; | |
IMG_STAIRS; | |
IMG_STARTER1; | |
IMG_STARTER2; | |
IMG_STARTER3; | |
IMG_STARTER4; | |
IMG_STARTER5; | |
IMG_ATTACK1; | |
IMG_ATTACK2; | |
IMG_ATTACK3; | |
IMG_NONE; | |
IMG_COUNT; | |
} | |
using Fonts :: enum u32 { | |
FONT_BABYBLOCKS; | |
FONT_COUNT; | |
} | |
CREATURE_TILE_POSITIONS :: Vector2s.[.{ 0, 16 }, .{ 16, 16 }, .{ 48, 16 }]; | |
CREATURE_TILE_SIZES :: Vector2s.[.{ 16, 16 }, .{ 32, 32 }, .{ 64, 64 }]; | |
Damage_Type :: enum { | |
TAIL; | |
CLAW; | |
FANG; | |
} | |
ATTACK_OFFSETS :: Vector2s.[ | |
.{ -27, -35 }, | |
.{ -28, -25 }, | |
.{ -23, -22 }, | |
]; | |
Creature :: struct { | |
id: Creature_Id; | |
name: Fixed_Size_String(16); | |
image: Images; | |
encounter_sfx: Sounds; | |
color: Color; | |
health: u8; | |
parts: Fixed_Size_Array(Creature_Part, MAX_PART_SIZE); | |
attack_type: Damage_Type; | |
attack_cooldown: s64; | |
attack_damage: u8; | |
attack_sfx: Sounds; | |
required_for_capture: int; // index int parts[] | |
} | |
Creature_Part :: struct { | |
position: Vector2s; | |
size: Vector2s; | |
image: Images; | |
health: u8; | |
z_index: s32; | |
priority: s32; | |
weakness: Damage_Type; | |
type: Creature_Part_Type; | |
} | |
Creature_Part_Type :: enum { TAIL; BODY; LEGS; HEAD; } | |
// :creatures | |
CREATURES :: Creature.[ | |
.{ id = 0, name = #run(string_to_fixed_string("DUMMY")), }, | |
.{ | |
id = 1, name = #run(string_to_fixed_string("LIZZY")), image = IMG_CREATURE1, encounter_sfx = SFX_CREATURE_DRAGON, color = #run(hex_string_to_color("#233a31ff")), health = 45, | |
attack_type = .TAIL, attack_cooldown = 8000, attack_damage = 8, attack_sfx = SFX_LAND, required_for_capture = 0, | |
parts.data[0] = .{ type = .HEAD, health = 16, z_index = 2, priority = 2, weakness = .FANG, image = IMG_CREATURE1_PART1, position = .{ 0, -18 }, size = .{ 32, 32 } }, // head | |
parts.data[1] = .{ type = .BODY, health = 20, z_index = 1, priority = 0, weakness = .TAIL, image = IMG_CREATURE1_PART2, position = .{ 0, 8 }, size = .{ 64, 48 } }, // body | |
parts.data[2] = .{ type = .TAIL, health = 8, z_index = 0, priority = 1, weakness = .CLAW, image = IMG_CREATURE1_PART3, position = .{ -16, 0 }, size = .{ 36, 48 } }, // tail | |
parts.count = 3, | |
}, | |
.{ | |
id = 2, name = #run(string_to_fixed_string("CENTI")), image = IMG_CREATURE2, encounter_sfx = SFX_CREATURE_CENTIPEDE, color = #run(hex_string_to_color("#45191bff")), health = 50, | |
attack_type = .CLAW, attack_cooldown = 5000, attack_damage = 5, attack_sfx = SFX_DASH1, required_for_capture = 1, | |
parts.data[0] = .{ type = .HEAD, health = 16, z_index = 1, priority = 2, weakness = .FANG, image = IMG_CREATURE2_PART1, position = .{ 4, -10 }, size = .{ 36, 32 } }, // head | |
parts.data[1] = .{ type = .LEGS, health = 8, z_index = 2, priority = 1, weakness = .TAIL, image = IMG_CREATURE2_PART2, position = .{ 0, 7 }, size = .{ 64, 36 } }, // legs | |
parts.data[2] = .{ type = .BODY, health = 20, z_index = 0, priority = 0, weakness = .CLAW, image = IMG_CREATURE2_PART3, position = .{ 0, 6 }, size = .{ 44, 36 } }, // body | |
parts.count = 3, | |
}, | |
.{ | |
id = 3, name = #run(string_to_fixed_string("MOWSE")), image = IMG_CREATURE3, encounter_sfx = SFX_CREATURE_MOUSE, color = #run(hex_string_to_color("#33394aff")), health = 43, | |
attack_type = .FANG, attack_cooldown = 3000, attack_damage = 5, attack_sfx = SFX_HIT2, required_for_capture = 1, | |
parts.data[0] = .{ type = .HEAD, health = 16, z_index = 2, priority = 2, weakness = .FANG, image = IMG_CREATURE3_PART1, position = .{ 0, 14 }, size = .{ 32, 32 } }, // head | |
parts.data[1] = .{ type = .BODY, health = 8, z_index = 1, priority = 0, weakness = .TAIL, image = IMG_CREATURE3_PART2, position = .{ 0, 0 }, size = .{ 64, 64 } }, // body | |
parts.data[2] = .{ type = .TAIL, health = 20, z_index = 0, priority = 1, weakness = .CLAW, image = IMG_CREATURE3_PART3, position = .{ 0, -18 }, size = .{ 48, 22 } }, // tail | |
parts.count = 3, | |
}, | |
.{ | |
id = 4, name = #run(string_to_fixed_string("FROGGY")), image = IMG_CREATURE4, encounter_sfx = SFX_CREATURE_FROG, color = #run(hex_string_to_color("#163f7cff")), health = 50, | |
attack_type = .TAIL, attack_cooldown = 3000, attack_damage = 4, attack_sfx = SFX_HIT2, required_for_capture = 2, | |
parts.data[0] = .{ type = .HEAD, health = 8, z_index = 2, priority = 2, weakness = .FANG, image = IMG_CREATURE4_PART1, position = .{ -6, -16 }, size = .{ 32, 32 } }, // head | |
parts.data[1] = .{ type = .BODY, health = 22, z_index = 1, priority = 0, weakness = .TAIL, image = IMG_CREATURE4_PART2, position = .{ 0, 0 }, size = .{ 64, 48 } }, // body | |
parts.data[2] = .{ type = .TAIL, health = 22, z_index = 0, priority = 1, weakness = .CLAW, image = IMG_CREATURE4_PART3, position = .{ 18, 8 }, size = .{ 28, 22 } }, // tail | |
parts.count = 3, | |
}, | |
.{ | |
id = 5, name = #run(string_to_fixed_string("BUN-BUN")), image = IMG_CREATURE5, encounter_sfx = SFX_CREATURE_BUNNY, color = #run(hex_string_to_color("#394f96ff")), health = 56, | |
attack_type = .CLAW, attack_cooldown = 3000, attack_damage = 4, attack_sfx = SFX_HIT2, required_for_capture = 0, | |
parts.data[0] = .{ type = .HEAD, health = 16, z_index = 2, priority = 1, weakness = .FANG, image = IMG_CREATURE5_PART1, position = .{ -1, -14 }, size = .{ 24, 32 } }, // head | |
parts.data[1] = .{ type = .LEGS, health = 16, z_index = 1, priority = 2, weakness = .TAIL, image = IMG_CREATURE5_PART2, position = .{ -8, 4 }, size = .{ 36, 36 } }, // arms | |
parts.data[2] = .{ type = .BODY, health = 16, z_index = 0, priority = 0, weakness = .CLAW, image = IMG_CREATURE5_PART3, position = .{ 0, 14 }, size = .{ 48, 42 } }, // body | |
parts.count = 3, | |
}, | |
]; | |
Pickup :: struct { | |
id: Pickup_Id; | |
image: Images; | |
encounter_sfx: Sounds; | |
color: Color; | |
effect: Pickup_Effect; | |
} | |
Pickup_Effect :: enum { | |
NONE; | |
DAMAGE; | |
HEAL; | |
DOOR; | |
KEY; | |
COLLAR; | |
STAIRS; | |
} | |
PICKUPS :: Pickup.[ | |
.{ id = 0 }, | |
.{ id = 1, image = IMG_TRAP_SPIKE_C, encounter_sfx = SFX_SPIKE_TRAP, color = #run(hex_string_to_color("#736246ff")), effect = .DAMAGE }, | |
.{ id = 2, image = IMG_FOUNTAIN_HEAL_F, encounter_sfx = SFX_FOUNTAIN_HEAL, color = #run(hex_string_to_color("#542121ff")), effect = .HEAL }, | |
.{ id = 3, image = IMG_NONE, encounter_sfx = SFX_NONE, color = #run(hex_string_to_color("#72723eff")), effect = .DOOR }, | |
.{ id = 4, image = IMG_KEY1, encounter_sfx = SFX_KEY_BUTTON, color = #run(hex_string_to_color("#5b664aff")), effect = .KEY }, | |
.{ id = 5, image = IMG_COLLAR, encounter_sfx = SFX_COLLAR, color = #run(hex_string_to_color("#31c7ecff")), effect = .COLLAR }, | |
.{ id = 6, image = IMG_TRAP_SPIKE_O, encounter_sfx = SFX_SPIKE_TRAP, color = #run(hex_string_to_color("#736246ff")), effect = .DAMAGE }, | |
.{ id = 7, image = IMG_FOUNTAIN_HEAL_E, encounter_sfx = SFX_FOUNTAIN_HEAL, color = #run(hex_string_to_color("#542121ff")), effect = .NONE }, | |
.{ id = 8, image = IMG_STAIRS, encounter_sfx = SFX_STAIRS, color = #run(hex_string_to_color("#31c7ecff")), effect = .STAIRS }, | |
]; | |
#run { for PICKUPS { assert(it.id == it_index); } } | |
Tile_Texture :: struct { | |
west_pos: Vector2s; | |
east_pos: Vector2s; | |
side_size: Vector2s; | |
front_pos: Vector2s; | |
front_size: Vector2s; | |
} | |
TILE_TEXTURES :: Images.[ | |
IMG_PLACEHOLDER, | |
IMG_TILESET1, | |
IMG_TILESET1_DOOR_C, | |
IMG_TILESET1_DOOR_O, | |
]; | |
VIEW_WIDTH :: 5; | |
VIEW_HEIGHT :: 4; | |
VIEW_SIZE :: Vector2s.{ VIEW_WIDTH, VIEW_HEIGHT }; | |
Level_Layers :: enum u8 { | |
ENTITIES :: 0; | |
GRID :: 1; | |
} | |
Level_Grid_Value :: enum u8 { | |
EMPTY :: 0; | |
WALL_1 :: 1; | |
WALL_2 :: 2; | |
WALL_3 :: 3; | |
} | |
Cell_Instance :: struct { | |
id: Cell_Id; | |
dir: Direction_Cardinal; | |
position: Vector2s; | |
encounter: Encounter; | |
opened: bool; | |
} | |
Cell_Id :: u8; | |
Cell :: struct { | |
floor: u8; | |
walls: [4]Tile_Id; | |
} | |
Level :: struct { | |
size: Vector2s; | |
player_spawn_position: Vector2s; | |
player_spawn_direction: Direction_Cardinal; | |
cells: []Cell_Instance; | |
hidden_cells: []Cell_Debug_State; | |
} | |
Encounter_Type :: enum { | |
NONE; | |
CREATURE; | |
PICKUP; | |
STARTER; | |
} | |
Encounter :: struct { | |
type: Encounter_Type; | |
creature_id: Creature_Id; | |
pickup_id: Pickup_Id; | |
target_cell_index: int; | |
} | |
Tile_Id :: enum u8 { | |
TILE_EMPTY; | |
TILE_BRICK; | |
TILE_METAL; | |
TILE_BUBBLE; | |
}; | |
Cell_Debug_State :: struct { | |
hidden: bool; | |
walls_hidden: [4]bool; | |
} | |
CELLS :: Cell.[ | |
/* 00 */.{ walls = .[ 0, 0, 0, 0 ] }, | |
/* 01 */.{ walls = .[ 1, 1, 1, 1 ], floor = 1 }, | |
/* 02 */.{ walls = .[ 2, 2, 2, 2 ], floor = 1 }, | |
/* 03 */.{ walls = .[ 3, 3, 3, 3 ], floor = 1 }, | |
]; | |
DIRECTIONS_CARDINAL :: Vector2s.[ | |
.{ +0, -1 }, // .NORTH | |
.{ +1, +0 }, // .EAST | |
.{ +0, +1 }, // .SOUTH | |
.{ -1, +0 }, // .WEST | |
]; | |
Direction_Cardinal :: enum { NORTH; EAST; SOUTH; WEST; } | |
Effect :: struct { | |
using transform: Transform; | |
using sprite: Sprite; | |
layer_id: s32; | |
} | |
Sprite :: struct { | |
active: bool; | |
size: Vector2s; | |
rect_index: s32; | |
texture_position: Vector2s; | |
texture_size: Vector2s; | |
texture_padding: s32; | |
z_index: s32; | |
color: Color; @UI(Color) | |
palette: s32; // FIXME: delete this | |
} | |
Creature_Id :: u32; | |
Creature_Part_Id :: u32; | |
Pickup_Id :: u32; | |
Party_Member :: struct { | |
creature_id: Creature_Id; | |
creature_part_id: Creature_Part_Id; | |
health: Stat; | |
} | |
Stat :: struct { current: u8; max: u8; } | |
World_Mode :: enum { | |
EXPLORATION; | |
BATTLE; | |
} | |
MAX_PARTY_SIZE :: 4; | |
MAX_PART_SIZE :: 4; | |
// :battle | |
Battle :: struct { | |
started: bool; | |
cell_index: int; // index into game.level.cells | |
foe_creature_id: Creature_Id; | |
foe_attack_timer: Timer; | |
foe_parts_health: Fixed_Size_Array(Stat, MAX_PART_SIZE); | |
party_attack_timers: Fixed_Size_Array(Timer, MAX_PARTY_SIZE); | |
party_flash_timers: Fixed_Size_Array(Timer, MAX_PARTY_SIZE); | |
party_attack_sound_played: Fixed_Size_Array(bool, MAX_PARTY_SIZE); | |
party_member_current: int; // index into game.party | |
foe_part_highlighted: int; // index into creature.parts | |
foe_part_flashing: int; // index into creature.parts | |
foe_part_flashing_timer: Timer; | |
} | |
Log_Type :: enum { NORMAL; CRITICAL; } | |
Log :: struct { | |
text: Fixed_Size_String(64); | |
type: Log_Type; | |
} | |
// :game | |
Game :: struct { | |
engine: *Engine; | |
ctx: Context; | |
// gamelevel | |
arena: *Arena_Allocator; | |
battle_arena: *Arena_Allocator; | |
mode: Mode(Game_Mode); | |
mouse_position_grid: Vector2s; | |
mouse_position_world: Vector2; | |
skip_title: bool = SUPER_DEBUG; | |
player_inputs: [4]Player_Input; | |
clear_color: Color; | |
fade_color: Color; | |
random: Random_State; | |
seed: u64 = 2; // This number was fairly chosen with a 1D20 roll... | |
effects: Fixed_Size_Array(Effect, 128); | |
force_fov_update: bool; | |
levels: Fixed_Size_Array(Fixed_Size_String(16), 32); | |
party: Fixed_Size_Array(Party_Member, MAX_PARTY_SIZE); | |
collar: bool; | |
current_level: int; | |
logs: Fixed_Size_Array(Log, 9); | |
using world: struct { | |
world_mode: Mode(World_Mode); | |
level: Level; | |
field_of_view: [VIEW_WIDTH*VIEW_HEIGHT]Cell_Instance; | |
player_position: Vector2s; | |
player_direction: Direction_Cardinal; | |
battle: Battle; | |
}; | |
floating_messages: Fixed_Size_Array(Floating_Message, 32); | |
ui: struct { | |
scale: float; | |
hovered: bool; | |
hud_position: Vector2; | |
hud_size: Vector2; | |
party_size: Vector2; | |
party_position: Vector2; | |
minimap_size: Vector2; | |
minimap_position: Vector2; | |
inventory_size: Vector2; | |
inventory_position: Vector2; | |
inventory_collar_pressed: bool; | |
foe_size: Vector2; | |
foe_position: Vector2; | |
logs_size: Vector2; | |
logs_position: Vector2; | |
settings: bool; | |
}; | |
cursor: SystemCursor; | |
// debug stuff | |
debug_ui_debug: bool; | |
// assets | |
sounds: [SFX_COUNT]*Asset_Sound; | |
images: [IMG_COUNT]*Asset_Image; | |
fonts: [FONT_COUNT]*Asset_Font; | |
ldtk: *LDTK_Root; | |
} | |
Game_Mode :: enum { INIT; TITLE; WORLD; GAME_OVER; } | |
Player_Input :: struct { | |
move: Input_Repeater = .{ threshold = 200, rate = 150 }; | |
aim: Input_Repeater = .{ threshold = 200, rate = 150 }; | |
shoulder_left: Key_State; | |
shoulder_right: Key_State; | |
confirm: Key_State; | |
cancel: Key_State; | |
actions: [12]Key_State; | |
} | |
game: *Game; | |
#program_export | |
update :: () -> bool { | |
context.allocator = panic_allocator; | |
context._Tracy.ZoneScoped("frame"); | |
if engine == null { | |
engine = engine_init(); | |
engine.game_version = BUILD_VERSION; | |
arena := make_arena("game_main"); | |
game = New(Game,, arena.allocator); | |
game.arena = arena; | |
// game.arena.logging = true; | |
game.engine = engine; | |
game.battle_arena = make_arena("game_battle"); | |
game_context := context; | |
game_context.logger = engine.logger.procedure; | |
game_context.logger_data = engine.logger; | |
game_context.allocator = game.arena.allocator; | |
game_context.assertion_failed = engine_support_assertion_failed; | |
game.ctx = game_context; | |
push_context,defer_pop game.ctx; | |
log("BUILD_VERSION: %", engine.game_version); | |
log("DEBUG: %", DEBUG); | |
window_open("Monstrum Prison", WINDOW_SIZE); | |
// :assets | |
game.images[IMG_PLACEHOLDER] = image_load(#run load_asset_path("images/placeholder.aseprite")); | |
game.images[IMG_SPRITESHEET] = image_load(#run load_asset_path("images/spritesheet.aseprite")); | |
game.images[IMG_TILESET1] = image_load(#run load_asset_path("images/7dfps_tileset1.aseprite")); | |
game.images[IMG_TILESET1_DOOR_C] = image_load(#run load_asset_path("images/7dfps_tileset1_door_closed.aseprite")); | |
game.images[IMG_TILESET1_DOOR_O] = image_load(#run load_asset_path("images/7dfps_tileset1_door_opened.aseprite")); | |
game.images[IMG_CREATURE1] = image_load(#run load_asset_path("images/mon_01.aseprite")); | |
game.images[IMG_CREATURE1_PART1] = image_load(#run load_asset_path("images/mon_03_part_01.aseprite")); | |
game.images[IMG_CREATURE1_PART1_O] = image_load(#run load_asset_path("images/mon_03_part_01_outline.aseprite")); | |
game.images[IMG_CREATURE1_PART2] = image_load(#run load_asset_path("images/mon_03_part_02.aseprite")); | |
game.images[IMG_CREATURE1_PART2_O] = image_load(#run load_asset_path("images/mon_03_part_02_outline.aseprite")); | |
game.images[IMG_CREATURE1_PART3] = image_load(#run load_asset_path("images/mon_03_part_03.aseprite")); | |
game.images[IMG_CREATURE1_PART3_O] = image_load(#run load_asset_path("images/mon_03_part_03_outline.aseprite")); | |
game.images[IMG_CREATURE2] = image_load(#run load_asset_path("images/mon_02.aseprite")); | |
game.images[IMG_CREATURE2_PART1] = image_load(#run load_asset_path("images/mon_02_part_01.aseprite")); | |
game.images[IMG_CREATURE2_PART1_O] = image_load(#run load_asset_path("images/mon_02_part_01_outline.aseprite")); | |
game.images[IMG_CREATURE2_PART2] = image_load(#run load_asset_path("images/mon_02_part_02.aseprite")); | |
game.images[IMG_CREATURE2_PART2_O] = image_load(#run load_asset_path("images/mon_02_part_02_outline.aseprite")); | |
game.images[IMG_CREATURE2_PART3] = image_load(#run load_asset_path("images/mon_02_part_03.aseprite")); | |
game.images[IMG_CREATURE2_PART3_O] = image_load(#run load_asset_path("images/mon_02_part_03_outline.aseprite")); | |
game.images[IMG_CREATURE3] = image_load(#run load_asset_path("images/mon_03.aseprite")); | |
game.images[IMG_CREATURE3_PART1] = image_load(#run load_asset_path("images/mon_01_part_01.aseprite")); | |
game.images[IMG_CREATURE3_PART1_O] = image_load(#run load_asset_path("images/mon_01_part_01_outline.aseprite")); | |
game.images[IMG_CREATURE3_PART2] = image_load(#run load_asset_path("images/mon_01_part_02.aseprite")); | |
game.images[IMG_CREATURE3_PART2_O] = image_load(#run load_asset_path("images/mon_01_part_02_outline.aseprite")); | |
game.images[IMG_CREATURE3_PART3] = image_load(#run load_asset_path("images/mon_01_part_03.aseprite")); | |
game.images[IMG_CREATURE3_PART3_O] = image_load(#run load_asset_path("images/mon_01_part_03_outline.aseprite")); | |
game.images[IMG_CREATURE4] = image_load(#run load_asset_path("images/mon_04.aseprite")); | |
game.images[IMG_CREATURE4_PART1] = image_load(#run load_asset_path("images/mon_04_part_01.aseprite")); | |
game.images[IMG_CREATURE4_PART1_O] = image_load(#run load_asset_path("images/mon_04_part_01_outline.aseprite")); | |
game.images[IMG_CREATURE4_PART2] = image_load(#run load_asset_path("images/mon_04_part_02.aseprite")); | |
game.images[IMG_CREATURE4_PART2_O] = image_load(#run load_asset_path("images/mon_04_part_02_outline.aseprite")); | |
game.images[IMG_CREATURE4_PART3] = image_load(#run load_asset_path("images/mon_04_part_03.aseprite")); | |
game.images[IMG_CREATURE4_PART3_O] = image_load(#run load_asset_path("images/mon_04_part_03_outline.aseprite")); | |
game.images[IMG_CREATURE5] = image_load(#run load_asset_path("images/mon_05.aseprite")); | |
game.images[IMG_CREATURE5_PART1] = image_load(#run load_asset_path("images/mon_05_part_01.aseprite")); | |
game.images[IMG_CREATURE5_PART1_O] = image_load(#run load_asset_path("images/mon_05_part_01_outline.aseprite")); | |
game.images[IMG_CREATURE5_PART2] = image_load(#run load_asset_path("images/mon_05_part_02.aseprite")); | |
game.images[IMG_CREATURE5_PART2_O] = image_load(#run load_asset_path("images/mon_05_part_02_outline.aseprite")); | |
game.images[IMG_CREATURE5_PART3] = image_load(#run load_asset_path("images/mon_05_part_03.aseprite")); | |
game.images[IMG_CREATURE5_PART3_O] = image_load(#run load_asset_path("images/mon_05_part_03_outline.aseprite")); | |
game.images[IMG_TRAP_SPIKE_C] = image_load(#run load_asset_path("images/trap_closed.aseprite")); | |
game.images[IMG_TRAP_SPIKE_O] = image_load(#run load_asset_path("images/trap.aseprite")); | |
game.images[IMG_FOUNTAIN_HEAL_F] = image_load(#run load_asset_path("images/fountain_health.aseprite")); | |
game.images[IMG_FOUNTAIN_HEAL_E] = image_load(#run load_asset_path("images/fountain_empty.aseprite")); | |
game.images[IMG_KEY1] = image_load(#run load_asset_path("images/key1.aseprite")); | |
game.images[IMG_COLLAR] = image_load(#run load_asset_path("images/collar_collectable.aseprite")); | |
game.images[IMG_CREATURE_ICONS] = image_load(#run load_asset_path("images/spr_icon_monsters.aseprite")); | |
game.images[IMG_UI_LAYOUT] = image_load(#run load_asset_path("images/7dfps_ui-elements_project_iteration2.aseprite")); | |
game.images[IMG_GAME_OVER] = image_load(#run load_asset_path("images/game_over_screen.aseprite")); | |
game.images[IMG_TITLE] = image_load(#run load_asset_path("images/logo_title_bg.aseprite")); | |
game.images[IMG_STAIRS] = image_load(#run load_asset_path("images/stairs.aseprite")); | |
game.images[IMG_STARTER1] = image_load(#run load_asset_path("images/starter_01.aseprite")); | |
game.images[IMG_STARTER2] = image_load(#run load_asset_path("images/starter_02.aseprite")); | |
game.images[IMG_STARTER3] = image_load(#run load_asset_path("images/starter_03.aseprite")); | |
game.images[IMG_STARTER4] = image_load(#run load_asset_path("images/starter_04.aseprite")); | |
game.images[IMG_STARTER5] = image_load(#run load_asset_path("images/starter_05.aseprite")); | |
game.images[IMG_ATTACK1] = image_load(#run load_asset_path("images/attack_tail.png"), is_aseprite = false); | |
game.images[IMG_ATTACK2] = image_load(#run load_asset_path("images/attack_claw.png"), is_aseprite = false); | |
game.images[IMG_ATTACK3] = image_load(#run load_asset_path("images/attack_fangs.png"), is_aseprite = false); | |
game.images[IMG_NONE] = image_load(#run load_asset_path("images/empty.png"), is_aseprite = false); | |
game.sounds[SFX_ERROR] = audio_load(#run load_asset_path("audio/sounds/invalid.wav"), "invalid", .GENERAL_SFX); | |
game.sounds[SFX_MONSTER_DEATH] = audio_load(#run load_asset_path("audio/sounds/death_monster.wav"), "death_monster", .GENERAL_SFX); | |
game.sounds[SFX_CONFIRM] = audio_load(#run load_asset_path("audio/sounds/confirm.wav"), "confirm", .GENERAL_SFX); | |
game.sounds[SFX_LAND] = audio_load(#run load_asset_path("audio/sounds/land.wav"), "land", .GENERAL_SFX); | |
game.sounds[SFX_DASH1] = audio_load(#run load_asset_path("audio/sounds/dash1.wav"), "dash1", .GENERAL_SFX); | |
game.sounds[SFX_HIT1] = audio_load(#run load_asset_path("audio/sounds/hit.wav"), "hit1", .GENERAL_SFX); | |
game.sounds[SFX_HIT2] = audio_load(#run load_asset_path("audio/sounds/hit2.wav"), "hit2", .GENERAL_SFX); | |
game.sounds[SFX_FOUNTAIN_HEAL] = audio_load(#run load_asset_path("audio/sounds/fountain.wav"), "fountain", .GENERAL_SFX); | |
game.sounds[SFX_CREATURE_DRAGON] = audio_load(#run load_asset_path("audio/sounds/creature03_drag.wav"), "creature03_drag", .GENERAL_SFX); | |
game.sounds[SFX_CREATURE_CENTIPEDE] = audio_load(#run load_asset_path("audio/sounds/creature04_cent.wav"), "creature04_cent", .GENERAL_SFX); | |
game.sounds[SFX_CREATURE_MOUSE] = audio_load(#run load_asset_path("audio/sounds/creature01_mouse.wav"), "creature01_mouse", .GENERAL_SFX); | |
game.sounds[SFX_CREATURE_FROG] = audio_load(#run load_asset_path("audio/sounds/creature02_frog.wav"), "creature02_frog", .GENERAL_SFX); | |
game.sounds[SFX_CREATURE_BUNNY] = audio_load(#run load_asset_path("audio/sounds/creature05_rabb.wav"), "creature05_rabb", .GENERAL_SFX); | |
game.sounds[SFX_SPIKE_TRAP] = audio_load(#run load_asset_path("audio/sounds/spike_trap.wav"), "spike_trap", .GENERAL_SFX); | |
game.sounds[SFX_KEY_BUTTON] = audio_load(#run load_asset_path("audio/sounds/door.wav"), "door", .GENERAL_SFX); | |
game.sounds[SFX_COLLAR] = audio_load(#run load_asset_path("audio/sounds/leash.wav"), "leash", .GENERAL_SFX); | |
game.sounds[SFX_ATTACK_READY] = audio_load(#run load_asset_path("audio/sounds/ATB.wav"), "leash", .GENERAL_SFX); | |
game.sounds[SFX_STAIRS] = audio_load(#run load_asset_path("audio/sounds/ALttP_Stairs_Sound_3.wav"), "stairs", .GENERAL_SFX); | |
game.sounds[SFX_FOOTSTEP1] = audio_load(#run load_asset_path("audio/sounds/footsteps01.wav"), "footsteps01", .FOOTSTEPS); | |
game.sounds[SFX_FOOTSTEP2] = audio_load(#run load_asset_path("audio/sounds/footsteps02.wav"), "footsteps02", .FOOTSTEPS); | |
game.sounds[SFX_FOOTSTEP3] = audio_load(#run load_asset_path("audio/sounds/footsteps03.wav"), "footsteps03", .FOOTSTEPS); | |
game.sounds[SFX_MUSIC1] = audio_load(#run load_asset_path("audio/musics/dungeoncrawl1.ogg"), "music_level1", .MUSIC); | |
game.sounds[SFX_MUSIC1_INTRO] = audio_load(#run load_asset_path("audio/musics/dungeoncrawl1_intro.ogg"), "music_level1_intro", .MUSIC); | |
game.sounds[SFX_NONE] = audio_load(#run load_asset_path("audio/sounds/empty.wav"), "empty", .GENERAL_SFX); | |
game.fonts[FONT_BABYBLOCKS] = font_load (#run load_asset_path("fonts/ChevyRay - Babyblocks.ttf"), "babyblocks"); | |
game.ldtk = ldtk_load (#run load_asset_path("levels.ldtk"), game.arena.allocator,, logger = noop_logger); | |
#if DEBUG { | |
for game.images { assert(it != null, tprint("Image not loaded: %", cast(Images) it_index)); } | |
for game.sounds { assert(it != null, tprint("Sound not loaded: %", cast(Sounds) it_index)); } | |
for game.fonts { assert(it != null, tprint("Font not loaded: %", cast(Fonts) it_index)); } | |
} | |
renderer_init(engine.window); | |
load_cursor(#run(assets_path("images/cursor_crosshair.png")), .CROSSHAIR, 16, 15); | |
load_cursor(#run(assets_path("images/cursor_arrow.png")), .ARROW, 0, 0); | |
load_cursor(#run(assets_path("images/cursor_hand.png")), .HAND, 13, 0); | |
audio_init(); | |
audio_set_master_volume (VOLUME_MAIN); | |
audio_set_music_volume (VOLUME_MUSIC); | |
audio_set_sound_volume (VOLUME_SFX); | |
audio_set_footstep_volume(VOLUME_FOOTSTEP); | |
update_vsync(); | |
} | |
push_context,defer_pop game.ctx; | |
engine_frame_start(); | |
defer engine_frame_end(); | |
game.ui.hovered = false; | |
ratio := cast(float) engine.window_size.x / engine.window_size.y; | |
ui_ratio := cast(float) UI_RESOLUTION.x / UI_RESOLUTION.y; | |
if ratio > ui_ratio { | |
game.ui.scale = cast(float) engine.window_size.y / UI_RESOLUTION.y; | |
} else { | |
game.ui.scale = cast(float) engine.window_size.x / UI_RESOLUTION.x; | |
} | |
push_pop_text_font(xx FONT_BABYBLOCKS); | |
push_pop_text_size(FONT_SIZE); | |
// FIXME: use the proper pixel art font! | |
game.mouse_position_world = window_to_world_position(engine.inputs.mouse_position); | |
game.mouse_position_grid = world_to_grid_position(game.mouse_position_world, .{ GRID_SIZE, GRID_SIZE }); | |
engine.clear_pass_action.colors[0].clear_value = xx,force game.clear_color; | |
if engine.inputs.keys[SDL_SCANCODE_F1].pressed { | |
game.ui.settings = !game.ui.settings; | |
} | |
#if DEBUG { | |
// Debug inputs | |
if engine.inputs.keys[SDL_SCANCODE_LCTRL].down { | |
// :restart | |
if engine.inputs.keys[SDL_SCANCODE_R].pressed { | |
clear_console(engine.logger); | |
log_content("Game restarted."); | |
game.party = .{}; | |
game.collar = false; | |
mode_transition(*game.mode, .WORLD); | |
} | |
} | |
if engine.inputs.keys[SDL_SCANCODE_F12].pressed { | |
for :for_ptr game.party { | |
it.health.current = it.health.max; | |
} | |
battle_log("[CHEAT] Party fully healed."); | |
} | |
if engine.inputs.keys[SDL_SCANCODE_LSHIFT].down { | |
camera_move: Vector2; | |
camera_zoom: float; | |
if engine.inputs.keys[SDL_SCANCODE_A].down { | |
camera_move.x -= 1; | |
} else if engine.inputs.keys[SDL_SCANCODE_D].down { | |
camera_move.x += 1; | |
} | |
if engine.inputs.keys[SDL_SCANCODE_W].down { | |
camera_move.y -= 1; | |
} else if engine.inputs.keys[SDL_SCANCODE_S].down { | |
camera_move.y += 1; | |
} | |
if engine.inputs.mouse_wheel.y > 0 { | |
camera_zoom = 1; | |
} else if engine.inputs.mouse_wheel.y < 0 { | |
camera_zoom = -1; | |
} | |
if camera_move != .{} { | |
engine.camera_main.position += xx (camera_move * engine.frame_stat.delta_time / 4); | |
} | |
if camera_zoom != 0 { | |
engine.camera_main.zoom = clamp(engine.camera_main.zoom + (camera_zoom * engine.frame_stat.delta_time / 100), 0.2, 6); | |
} | |
} | |
} | |
// :player inputs | |
player_inputs := *game.player_inputs[PLAYER_INDEX]; | |
if engine.inputs.keys[SDL_SCANCODE_LSHIFT].down == false && ImGui.GetIO().WantCaptureMouse == false { | |
player_inputs_move: Vector2; | |
player_inputs_aim: Vector2; | |
if engine.inputs.controller_was_used { | |
// if engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_LEFTX] < -CONTROLLER_DEADZONE { | |
// player_inputs_move.x = -(cast(float) engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_LEFTX] / S16_MIN); | |
// } else if engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_LEFTX] > CONTROLLER_DEADZONE { | |
// player_inputs_move.x = +(cast(float) engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_LEFTX] / S16_MAX); | |
// } | |
// if engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_LEFTY] < -CONTROLLER_DEADZONE { | |
// player_inputs_move.y = -(cast(float) engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_LEFTY] / S16_MIN); | |
// } else if engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_LEFTY] > CONTROLLER_DEADZONE { | |
// player_inputs_move.y = +(cast(float) engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_LEFTY] / S16_MAX); | |
// } | |
if engine.inputs.controllers[PLAYER_CONTROLLER].buttons[SDL_CONTROLLER_BUTTON_DPAD_LEFT].down { | |
player_inputs_move.x = -1; | |
} else if engine.inputs.controllers[PLAYER_CONTROLLER].buttons[SDL_CONTROLLER_BUTTON_DPAD_RIGHT].down { | |
player_inputs_move.x = +1; | |
} | |
if engine.inputs.controllers[PLAYER_CONTROLLER].buttons[SDL_CONTROLLER_BUTTON_DPAD_UP].down { | |
player_inputs_move.y = -1; | |
} else if engine.inputs.controllers[PLAYER_CONTROLLER].buttons[SDL_CONTROLLER_BUTTON_DPAD_DOWN].down { | |
player_inputs_move.y = +1; | |
} | |
if engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_RIGHTX] < -CONTROLLER_DEADZONE { | |
player_inputs_aim.x = -(cast(float) engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_RIGHTX] / S16_MIN); | |
} else if engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_RIGHTX] > CONTROLLER_DEADZONE { | |
player_inputs_aim.x = +(cast(float) engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_RIGHTX] / S16_MAX); | |
} | |
if engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_RIGHTY] < -CONTROLLER_DEADZONE { | |
player_inputs_aim.y = -(cast(float) engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_RIGHTY] / S16_MIN); | |
} else if engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_RIGHTY] > CONTROLLER_DEADZONE { | |
player_inputs_aim.y = +(cast(float) engine.inputs.controllers[PLAYER_CONTROLLER].axes[SDL_CONTROLLER_AXIS_RIGHTY] / S16_MAX); | |
} | |
player_inputs.confirm = engine.inputs.controllers[PLAYER_CONTROLLER].buttons[SDL_CONTROLLER_BUTTON_A]; | |
player_inputs.cancel = engine.inputs.controllers[PLAYER_CONTROLLER].buttons[SDL_CONTROLLER_BUTTON_B]; | |
player_inputs.shoulder_left = engine.inputs.controllers[PLAYER_CONTROLLER].buttons[SDL_CONTROLLER_BUTTON_LEFTSHOULDER]; | |
player_inputs.shoulder_right = engine.inputs.controllers[PLAYER_CONTROLLER].buttons[SDL_CONTROLLER_BUTTON_RIGHTSHOULDER]; | |
} else { | |
if engine.inputs.keys[SDL_SCANCODE_LEFT].down { | |
player_inputs_aim.x -= 1; | |
} else if engine.inputs.keys[SDL_SCANCODE_RIGHT].down { | |
player_inputs_aim.x += 1; | |
} | |
if engine.inputs.keys[SDL_SCANCODE_UP].down { | |
player_inputs_aim.y -= 1; | |
} else if engine.inputs.keys[SDL_SCANCODE_DOWN].down { | |
player_inputs_aim.y += 1; | |
} | |
if engine.inputs.keys[SDL_SCANCODE_A].down { | |
player_inputs_move.x -= 1; | |
} else if engine.inputs.keys[SDL_SCANCODE_D].down { | |
player_inputs_move.x += 1; | |
} | |
if engine.inputs.keys[SDL_SCANCODE_W].down { | |
player_inputs_move.y -= 1; | |
} else if engine.inputs.keys[SDL_SCANCODE_S].down { | |
player_inputs_move.y += 1; | |
} | |
player_inputs.confirm = engine.inputs.keys[SDL_SCANCODE_SPACE]; | |
player_inputs.cancel = engine.inputs.keys[SDL_SCANCODE_ESCAPE]; | |
player_inputs.shoulder_left = engine.inputs.keys[SDL_SCANCODE_Q]; | |
player_inputs.shoulder_right = engine.inputs.keys[SDL_SCANCODE_E]; | |
player_inputs.actions[0] = engine.inputs.keys[SDL_SCANCODE_1]; | |
player_inputs.actions[1] = engine.inputs.keys[SDL_SCANCODE_2]; | |
player_inputs.actions[2] = engine.inputs.keys[SDL_SCANCODE_3]; | |
player_inputs.actions[3] = engine.inputs.keys[SDL_SCANCODE_4]; | |
player_inputs.actions[4] = engine.inputs.keys[SDL_SCANCODE_5]; | |
player_inputs.actions[5] = engine.inputs.keys[SDL_SCANCODE_6]; | |
player_inputs.actions[6] = engine.inputs.keys[SDL_SCANCODE_7]; | |
player_inputs.actions[7] = engine.inputs.keys[SDL_SCANCODE_8]; | |
player_inputs.actions[8] = engine.inputs.keys[SDL_SCANCODE_9]; | |
player_inputs.actions[9] = engine.inputs.keys[SDL_SCANCODE_0]; | |
player_inputs.actions[10] = engine.inputs.keys[SDL_SCANCODE_MINUS]; | |
player_inputs.actions[11] = engine.inputs.keys[SDL_SCANCODE_EQUALS]; | |
} | |
process_repeater(*player_inputs.move, normalize(player_inputs_move)); | |
process_repeater(*player_inputs.aim, normalize(player_inputs_aim)); | |
} | |
// :camera main | |
engine.camera_main.zoom = game.ui.scale * 3; | |
engine.camera_main.position.x = 88 / 3; | |
// :camera ui | |
engine.camera_ui.zoom = game.ui.scale; | |
hud_offset: Vector2; | |
if ratio > ui_ratio { | |
ideal := engine.window_size.y * ui_ratio; | |
diff := engine.window_size.x - ideal; | |
hud_offset = .{ engine.window_size.x - ideal, 0 }; | |
} else { | |
ideal := engine.window_size.x / ui_ratio; | |
diff := engine.window_size.y - ideal; | |
hud_offset = .{ 0, engine.window_size.y - ideal }; | |
} | |
engine.camera_ui.position.x = cast(float) +engine.window_size.x / 2 - cast(float)hud_offset.x/2; | |
engine.camera_ui.position.y = cast(float) -engine.window_size.y / 2 + cast(float)hud_offset.y/2; | |
game.ui.hud_size = Vector2.{ 192, 270 }; | |
game.ui.hud_position = hud_offset/2/game.ui.scale; | |
previous_player_position := game.player_position; | |
previous_player_direction := game.player_direction; | |
game.cursor = SystemCursor.ARROW; | |
defer set_cursor(game.cursor); | |
GAME_VIEW_SIZE : s32 : 80; | |
game_size := GAME_VIEW_SIZE * 3 * game.ui.scale; | |
if game.ui.settings && ImGui.Begin("Settings (F1)", null, .NoResize | .NoMove | .NoCollapse | .NoSavedSettings | .NoBringToFrontOnFocus) { | |
// ImGui.SetWindowSize(.{ cast(float) engine.window_size.x * pixel_density, console_height }, .Always); | |
ImGui.SetWindowPos(.{ 40, 40 }, .Always); | |
ImGui.Text("Controls:"); | |
ImGui.Indent(); | |
ImGui.Text("WASD: move"); | |
ImGui.Text("QE: turn"); | |
ImGui.Text("1-4 / wheel: change creature"); | |
ImGui.Text("Mouse: aim (during battles)"); | |
ImGui.Text("F1: toggle this menu"); | |
ImGui.Unindent(); | |
ImGui.Text("Video:"); | |
ImGui.Indent(); | |
if ImGui.RadioButton("Windowed", engine.window_fullscreen_mode == .WINDOWED) { | |
set_fullscreen_mode(.WINDOWED); | |
} | |
if ImGui.RadioButton("Fullscreen", engine.window_fullscreen_mode == .FULLSCREEN) { | |
set_fullscreen_mode(.FULLSCREEN); | |
} | |
ImGui.Text("Resolution:"); | |
if ImGui.Button("960x540 ") { set_window_size(.{ 960, 540 }); } | |
if ImGui.Button("1280x720 ") { set_window_size(.{ 1280, 720 }); } | |
if ImGui.Button("1920x1080") { set_window_size(.{ 1920, 1080 }); } | |
if ImGui.Button("2560x1440") { set_window_size(.{ 2560, 1440 }); } | |
if ImGui.Button("3840x2160") { set_window_size(.{ 3840, 2160 }); } | |
ImGui.Unindent(); | |
ImGui.Text("Audio:"); | |
ImGui.Indent(); | |
{ | |
ImGui.SetNextItemWidth(100); | |
volume := engine.audio.volume_main; | |
if ImGui.SliderFloat("Main", *volume, 0, 1) { | |
audio_set_master_volume(volume); | |
} | |
} | |
{ | |
ImGui.SetNextItemWidth(100); | |
volume := Sound_Player.mix_levels[Sound_Category.MUSIC]; | |
if ImGui.SliderFloat("Music", *volume, 0, 1) { | |
audio_set_music_volume(volume); | |
} | |
} | |
{ | |
ImGui.SetNextItemWidth(100); | |
volume := Sound_Player.mix_levels[Sound_Category.GENERAL_SFX]; | |
if ImGui.SliderFloat("Sounds", *volume, 0, 1) { | |
audio_set_sound_volume(volume); | |
} | |
} | |
{ | |
ImGui.SetNextItemWidth(100); | |
volume := Sound_Player.mix_levels[Sound_Category.FOOTSTEPS]; | |
if ImGui.SliderFloat("Footsteps", *volume, 0, 1) { | |
audio_set_footstep_volume(volume); | |
} | |
} | |
ImGui.Unindent(); | |
ImGui.End(); | |
} | |
// :render letterbox | |
ui_push_rect(.{ -cast(s32)game.ui.hud_position.x, 0 }, .{ cast(s32)game.ui.hud_position.x, engine.window_size.y}, COLOR_BLACK, layer_id = 3, z_index = 71); | |
ui_push_rect(.{ UI_RESOLUTION.x, 0 }, .{ cast(s32)game.ui.hud_position.x, engine.window_size.y}, COLOR_BLACK, layer_id = 3, z_index = 71); | |
// :render hud | |
ui_push_sprite(.{ 0, 0 }, UI_RESOLUTION, .{ 0, 0 }, UI_RESOLUTION, rect_index = IMG_UI_LAYOUT, layer_id = 2); | |
// :render party | |
{ | |
game.ui.party_size = Vector2.{ 92, 128 }; | |
game.ui.party_position = .{ 293, 21 }; | |
// ui_push_rect(make_vector2s(game.ui.party_position), make_vector2s(game.ui.party_size), .{ 1, 1, 1, 0.1 }, z_index = 71); | |
position := game.ui.party_position; | |
party_member_selected := game.battle.party_member_current; | |
member_position := make_vector2s(position); | |
for party_member, party_index : game.party { | |
creature := CREATURES[party_member.creature_id]; | |
creature_part := creature.parts[party_member.creature_part_id]; | |
icon_size := Vector2s.{ PARTY_MEMBER_SIZE.y, PARTY_MEMBER_SIZE.y }; | |
is_selected_member := game.battle.party_member_current == party_index; | |
ui_push_sprite(member_position + .{ 7, 5 }, CREATURE_TILE_SIZES[0] * PARTY_ICON_SCALE, .{ CREATURE_TILE_SIZES[0].x * cast(s32)party_member.creature_id, 0 }, CREATURE_TILE_SIZES[0], z_index = 83, rect_index = IMG_CREATURE_ICONS); | |
if party_member.health.current <= 0 { | |
ui_push_sprite(member_position + .{ 7, 5 }, CREATURE_TILE_SIZES[0] * PARTY_ICON_SCALE, .{ 0, 64 }, CREATURE_TILE_SIZES[0], z_index = 84, rect_index = IMG_SPRITESHEET); | |
} | |
if is_selected_member { | |
ui_push_sprite(member_position - .{ 1, 1 }, .{ 96, 32 }, .{ 16, 64 }, .{ 96, 32 }, z_index = 81, rect_index = IMG_SPRITESHEET); | |
} | |
x := member_position.x + icon_size.x + 2; | |
y := member_position.y + 7; | |
ui_push_text(to_temp_string(creature.name), .{ x, y }); | |
ui_push_sprite(.{ x + 47, y - 3 }, .{ 8, 8 }, .{ 32 + 16 * cast(s32)creature.attack_type, 48 }, .{ 8, 8 }, z_index = 82, rect_index = IMG_SPRITESHEET); | |
y += 6; | |
progress_h : s32 = 4; | |
progress_w := PARTY_MEMBER_SIZE.x - icon_size.x - 6; | |
{ | |
// ui_push_text("hp", .{ x + 48, y - 1 }); | |
progress := cast(float) party_member.health.current / party_member.health.max; | |
w := cast(s32)(progress_w * progress); if w % 2 != 0 { w += 1; }; // quick hack to make sure we have only pair number | |
ui_push_rect(.{ x, y }, .{ progress_w, progress_h }, color = #run(hex_string_to_color("#4d402dff")), z_index = 82); | |
ui_push_rect(.{ x, y }, .{ w, progress_h }, color = COLOR_RED2, z_index = 83); | |
y += 6; | |
} | |
{ | |
// ui_push_text("cd", .{ x + 48, y - 1 }); | |
color := lerp(COLOR_GREEN2, COLOR_GREEN3, timer_progress(game.battle.party_flash_timers[party_index])); | |
progress := timer_progress(game.battle.party_attack_timers[party_index]); | |
w := cast(s32)(progress_w * progress); if w % 2 != 0 { w += 1; }; // quick hack to make sure we have only pair number | |
ui_push_rect(.{ x, y }, .{ progress_w, progress_h }, color = #run(hex_string_to_color("#4d402dff")), z_index = 82); | |
ui_push_rect(.{ x, y }, .{ w, progress_h }, color = color, z_index = 83); | |
y += 6; | |
} | |
{ | |
button_position := member_position; | |
button_size := Vector2s.{ PARTY_MEMBER_SIZE.x, icon_size.y }; | |
pressed, hovered := ui_button(button_position, button_size); | |
if pressed { | |
party_member_selected = party_index; | |
} | |
} | |
member_position.y += PARTY_MEMBER_SIZE.y + 2; | |
} | |
for action_index : 0..3 { | |
if player_inputs.actions[action_index].pressed { | |
party_member_selected = action_index; | |
break; | |
} | |
} | |
if engine.inputs.mouse_wheel.y != 0 { | |
if engine.inputs.mouse_wheel.y > 0 { | |
party_member_selected -= 1; | |
if party_member_selected < 0 { | |
party_member_selected = game.party.count-1; | |
} | |
} else { | |
party_member_selected += 1; | |
if party_member_selected > game.party.count-1 { | |
party_member_selected = 0; | |
} | |
} | |
} | |
if party_member_selected != game.battle.party_member_current { | |
game.battle.party_member_current = party_member_selected; | |
// play_sound(SFX_CONFIRM, false); | |
} | |
} | |
// :render minimap | |
{ | |
game.ui.minimap_size = Vector2.{ 57, 57 }; | |
game.ui.minimap_position = Vector2.{ 406, 21 }; | |
player_rotation_radian := vector2_to_radians(to_vector2(DIRECTIONS_CARDINAL[xx game.player_direction])); | |
cell_size := Vector2s.{ 10, 10 }; | |
// ui_push_rect(make_vector2s(game.ui.minimap_position), make_vector2s(game.ui.minimap_size), .{ 1, 1, 1, 0.5 }, z_index = 71, layer_id = 3); | |
view_distance := Vector2s.{ 2, 2 }; | |
spacing := Vector2s.{ 1, 1 }; | |
if game.level.cells.count { | |
for y : game.player_position.y-view_distance.x .. game.player_position.y+view_distance.x { | |
for x : game.player_position.x-view_distance.y .. game.player_position.x+view_distance.y { | |
cell_position := Vector2s.{ x, y }; | |
cell_relative_position := cell_position - game.player_position + view_distance; | |
if !is_in_bounds(cell_position, game.level.size) { | |
cell := CELLS[0]; | |
ui_push_sprite( | |
window_position = make_vector2s(game.ui.minimap_position) + cell_relative_position * (cell_size + spacing), | |
size = cell_size, | |
texture_position = .{ 48, 32 }, | |
texture_size = cell_size, | |
rect_index = xx IMG_SPRITESHEET, | |
color = COLOR_WHITE, | |
z_index = 101, | |
layer_id = 2, | |
); | |
continue; | |
} | |
cell_index := grid_position_to_index(cell_position, game.level.size.x); | |
cell_instance := game.level.cells[cell_index]; | |
cell := CELLS[cell_instance.id]; | |
ui_push_sprite( | |
window_position = make_vector2s(game.ui.minimap_position) + cell_relative_position * (cell_size + spacing), | |
size = cell_size, | |
texture_position = .{ 32, 32 }, | |
texture_size = cell_size, | |
rect_index = xx IMG_SPRITESHEET, | |
color = COLOR_WHITE, | |
z_index = 101, | |
layer_id = 2, | |
); | |
if cell_instance.opened { | |
continue; | |
} | |
for wall, wall_index : cell.walls { | |
if wall == 0 { continue; } | |
wall_position := WALLS_OFFSET[wall_index]; | |
wall_scale := ifx(wall_index % 2 == 0) then Vector2.{ 1, 0.2 } else Vector2.{ 0.2, 1 }; | |
wall_color := COLOR_GREY; | |
if wall == 2 { wall_color = COLOR_RED; } | |
if wall == 3 { wall_color = COLOR_BLUE; } | |
ui_push_sprite( | |
window_position = make_vector2s(game.ui.minimap_position) + cell_relative_position * (cell_size + spacing), | |
size = cell_size, | |
texture_position = .{ 48, 32 }, | |
texture_size = cell_size, | |
rect_index = xx IMG_SPRITESHEET, | |
color = COLOR_WHITE, | |
z_index = 102, | |
layer_id = 2, | |
); | |
} | |
// encounter | |
if cell_instance.encounter.type { | |
color: Color; | |
if #complete cell_instance.encounter.type == { | |
case .NONE; { } | |
case .CREATURE; { color = CREATURES[cell_instance.encounter.creature_id].color; } | |
case .STARTER; { color = CREATURES[cell_instance.encounter.creature_id].color; } | |
case .PICKUP; { color = PICKUPS[cell_instance.encounter.pickup_id].color; } | |
} | |
ui_push_sprite( | |
window_position = make_vector2s(game.ui.minimap_position) + cell_relative_position * (cell_size + spacing), | |
size = cell_size, | |
texture_position = .{ 80, 32 }, | |
texture_size = cell_size, | |
rect_index = xx IMG_SPRITESHEET, | |
color = color, | |
z_index = 110, | |
layer_id = 2, | |
); | |
} | |
} | |
} | |
} | |
// player | |
ui_push_sprite( | |
window_position = make_vector2s(game.ui.minimap_position) + (cell_size + spacing) * view_distance, | |
size = cell_size, | |
rotation = player_rotation_radian, | |
texture_position = .{ 64, 32 }, | |
texture_size = cell_size, | |
rect_index = xx IMG_SPRITESHEET, | |
color = COLOR_WHITE, | |
z_index = 111, | |
layer_id = 2, | |
); | |
#if false { | |
fov_position := Vector2s.{ HUD_PADDING*2, HUD_PADDING*2 }; | |
for cell_instance, cell_index : game.field_of_view { | |
cell := CELLS[cell_instance.id]; | |
cell_view_position := grid_index_to_position(cell_index, VIEW_SIZE); | |
// floor | |
ui_push_sprite( | |
window_position = fov_position + cell_view_position * GRID_SIZE_V2S, | |
size = GRID_SIZE_V2S, | |
texture_position = .{ 0, 0 }, | |
texture_size = GRID_SIZE_V2S, | |
rect_index = xx IMG_SPRITESHEET, | |
color = .{ 0.2, 0.2, 0.2, 0.8}, | |
z_index = 100, | |
layer_id = 2, | |
); | |
for wall, wall_index : cell.walls { | |
if wall == 0 { continue; } | |
wall_position := WALLS_OFFSET[wall_index]; | |
wall_scale := ifx(wall_index % 2 == 0) then Vector2.{ 1, 0.2 } else Vector2.{ 0.2, 1 }; | |
wall_color := COLOR_GREY; | |
if wall == 2 { wall_color = COLOR_RED; } | |
if wall == 3 { wall_color = COLOR_BLUE; } | |
ui_push_sprite( | |
window_position = fov_position + make_vector2s((to_vector2(cell_view_position) + wall_position) * GRID_SIZE_V2), | |
size = make_vector2s(GRID_SIZE_V2 * wall_scale), | |
texture_position = .{ 0, 0 }, | |
texture_size = GRID_SIZE_V2S, | |
rect_index = xx IMG_SPRITESHEET, | |
color = wall_color, | |
z_index = 101, | |
layer_id = 2, | |
); | |
} | |
} | |
} | |
} | |
// :render inventory | |
{ | |
game.ui.inventory_size = Vector2.{ 50, 32 }; | |
game.ui.inventory_position = Vector2.{ 407, 104 }; | |
game.ui.inventory_collar_pressed = false; | |
if game.collar { | |
icon_position := make_vector2s(game.ui.inventory_position) + .{ 10, 1 }; | |
icon_size := Vector2s.{ 32, 32 }; | |
is_in_battle := game.battle.foe_creature_id; | |
foe_creature := CREATURES[game.battle.foe_creature_id]; | |
required_part_broken := game.battle.foe_parts_health[foe_creature.required_for_capture].current == 0; | |
if is_in_battle && required_part_broken { | |
ui_push_sprite(icon_position, icon_size, .{ 32, 96 }, .{ 32, 32 }, z_index = 100); | |
push_pop_text_color(#run(hex_string_to_color("#ec3157ff"))); | |
ui_push_text("CAPTURE", .{ 418, 98 }, layer_id = 2); | |
} else { | |
ui_push_sprite(icon_position, icon_size, .{ 0, 96 }, .{ 32, 32 }, z_index = 100); | |
} | |
pressed, hovered := ui_button(icon_position, icon_size); | |
if pressed { | |
if !is_in_battle { | |
battle_log("No creature to use this on."); | |
} else if !required_part_broken { | |
battle_log(tprint("% is not weak enough!", to_temp_string(foe_creature.name))); | |
} else { | |
game.ui.inventory_collar_pressed = pressed; | |
} | |
} | |
} | |
} | |
// :render foe | |
for render_foe : 0..0 { | |
game.ui.foe_size = Vector2.{ 55, 44 }; | |
game.ui.foe_position = Vector2.{ 297, 151 }; | |
// ui_push_rect(make_vector2s(game.ui.foe_position), make_vector2s(game.ui.foe_size), .{ 1, 1, 1, 0.1 }, z_index = 71); | |
if game.battle.foe_creature_id == 0 { | |
break render_foe; | |
} | |
foe_creature := CREATURES[game.battle.foe_creature_id]; | |
base_x := cast(s32)game.ui.foe_position.x + HUD_PADDING; | |
base_y := cast(s32)game.ui.foe_position.y + HUD_PADDING; | |
ui_push_sprite(.{ base_x, base_y - 1 }, CREATURE_TILE_SIZES[0] * PARTY_ICON_SCALE, .{ CREATURE_TILE_SIZES[0].x * cast(s32) game.battle.foe_creature_id, 0 }, CREATURE_TILE_SIZES[0], z_index = 81, rect_index = IMG_CREATURE_ICONS); | |
x: s32 = base_x + CREATURE_TILE_SIZES[0].x * PARTY_ICON_SCALE + HUD_PADDING; | |
y: s32 = base_y + 3; | |
ui_push_text(to_temp_string(foe_creature.name), .{ x, y }); | |
x += 34; | |
ui_push_sprite(.{ x, y-4 }, .{ 8, 8 }, .{ 32 + 16 * cast(s32)foe_creature.attack_type, 48 }, .{ 8, 8 }, z_index = 84, rect_index = IMG_SPRITESHEET); | |
x += 12; | |
progress_h: s32 = 4; | |
{ | |
y -= 1; | |
progress_w: s32 = 82; | |
progress := timer_progress(game.battle.foe_attack_timer); | |
w := cast(s32)(progress_w * progress); if w % 2 != 0 { w += 1; }; // quick hack to make sure we have only pair number | |
// w = clamp(w, 0, progress_w); | |
ui_push_rect(.{ x, y }, .{ progress_w, progress_h }, color = #run(hex_string_to_color("#05060eff")), z_index = 81); | |
ui_push_rect(.{ x, y }, .{ w, progress_h }, color = COLOR_GREEN2, z_index = 82); | |
y += 9; | |
x -= 34 + 12; | |
} | |
for health : game.battle.foe_parts_health { | |
progress_w: s32 = 41; | |
progress := cast(float)health.current / health.max; | |
w := cast(s32)(progress_w * progress); // quick hack to make sure we have only pair number | |
ui_push_rect(.{ x, y }, .{ progress_w, progress_h }, color = #run(hex_string_to_color("#05060eff")), z_index = 81); | |
ui_push_rect(.{ x, y }, .{ w, progress_h }, color = ifx(game.battle.foe_part_highlighted == it_index) then COLOR_RED3 else COLOR_RED2, z_index = 82); | |
x += progress_w + 3; | |
} | |
} | |
// :render logs | |
{ | |
game.ui.logs_size = Vector2.{ 163, 60 }; | |
game.ui.logs_position = Vector2.{ 290, 183 }; | |
x := cast(s32)game.ui.logs_position.x + HUD_PADDING; | |
y := cast(s32)game.ui.logs_position.y + HUD_PADDING; | |
for log_index : 0 .. game.logs.count-1 { | |
color := COLOR_WHITE; | |
if game.logs[log_index].type == .CRITICAL { color = #run(hex_string_to_color("#90834dff")); } | |
push_pop_text_color(color); | |
ui_push_text(to_temp_string(game.logs[log_index].text), .{ x, y }); | |
y += cast(s32) (cast(float) FONT_SIZE * 1.2); | |
} | |
// ui_push_rect(make_vector2s(game.ui.logs_position), make_vector2s(game.ui.logs_size), .{ 1, 1, 1, 0.1 }, z_index = 71); | |
} | |
defer mode_transition_current_to_next(*game.mode); | |
if #complete game.mode.current == { | |
case .INIT; { | |
if mode_enter(*game.mode) { | |
game.party = .{}; | |
game.collar = false; | |
game.fade_color = COLOR_BLACK; | |
game.clear_color = COLOR_BLACK; | |
game.levels = .{}; | |
for level : game.ldtk.levels { | |
fixed_array_add(*game.levels, string_to_fixed_string(level.identifier)); | |
} | |
game.current_level = level_name_to_index("THE_BEGINNING"); | |
#if SUPER_DEBUG { | |
game.current_level = level_name_to_index("Test_Encounters"); | |
} | |
} | |
if mode_running(game.mode) { | |
timer_done := engine.now >= game.mode.entered_at + milliseconds_to_apollo(50); | |
if timer_done { | |
if game.skip_title { | |
mode_transition(*game.mode, .WORLD); | |
} else { | |
mode_transition(*game.mode, .TITLE); | |
} | |
} | |
} | |
} | |
// :title | |
case .TITLE; { | |
if mode_enter(*game.mode) { | |
fade_out(duration = ifx(game.skip_title) then cast(u64)0); | |
game.party = .{}; | |
game.battle = .{}; | |
game.collar = false; | |
game.logs = .{}; | |
game.level = .{}; | |
game.player_direction = .NORTH; | |
} | |
if mode_running(game.mode) { | |
level_selected: bool; | |
push_text_size(8*3); | |
pop_text_size(); | |
ui_push_text("F1 = Settings / Help", .{ 37, 248 }, layer_id = 0); | |
ui_push_text(BUILD_VERSION, .{ 242, 248 }, layer_id = 0); | |
ui_push_text("made by Colin Bellino, Beowulfus Universum, Squirrelsquid", .{ 56, 112 }, layer_id = 0); | |
push_sprite(.{ | |
position = .{ 0, 0 }, | |
scale = .{ 80, 80 }, | |
texture_size = .{ 80, 80 }, | |
z_index = 10, | |
layer_id = 0, | |
rect_index = xx IMG_TITLE, | |
color = COLOR_WHITE, | |
}); | |
{ // Start button | |
button_size := Vector2s.{ 8*9, 14 }; | |
button_position := Vector2s.{ 120, 155 }; | |
pressed, hovered := ui_push_rect_button(button_position, " START", button_size, .{ 0, 10 }, layer_id = 0); | |
if pressed { | |
level_selected = true; | |
} | |
} | |
{ // Quit button | |
button_size := Vector2s.{ 8*9, 14 }; | |
button_position := Vector2s.{ 120, 175 }; | |
if ui_push_rect_button(button_position, " QUIT", button_size, .{ 0, 10 }, layer_id = 0) { | |
engine.inputs.quit_requested = true; | |
} | |
} | |
if player_inputs.confirm.pressed { | |
level_selected = true; | |
} | |
if level_selected { | |
fade_in((target: *Effect, step_progress: float, user_data: *void) { | |
battle_log("You wake up in a cell..."); | |
battle_log("What creature will you save?"); | |
mode_transition(*game.mode, .WORLD); | |
}); | |
} | |
} | |
} | |
// :world | |
case .WORLD; { | |
if mode_enter(*game.mode) { | |
game.world = .{}; | |
// random_seed(*game.random, game.seed); | |
random_seed(*game.random, cast(u64) to_milliseconds(current_time_consensus())); | |
for :for_ptr party_member : game.party { | |
party_member.health.current = party_member.health.max; | |
} | |
game.effects = .{}; | |
#if DEBUG { | |
game.ldtk = ldtk_load(#run load_asset_path("levels.ldtk"), game.arena.allocator,, logger = noop_logger); | |
game.levels = .{}; | |
for level : game.ldtk.levels { | |
fixed_array_add(*game.levels, string_to_fixed_string(level.identifier)); | |
} | |
if game.current_level == level_name_to_index("Test_Encounters") { | |
fixed_array_add(*game.party, Party_Member.{ creature_id = 1, health = .{ CREATURES[1].health, CREATURES[1].health } }); | |
fixed_array_add(*game.party, Party_Member.{ creature_id = 2, health = .{ CREATURES[2].health, CREATURES[2].health } }); | |
fixed_array_add(*game.party, Party_Member.{ creature_id = 3, health = .{ CREATURES[3].health, CREATURES[3].health } }); | |
} | |
} | |
assert(game.levels.count > 0); | |
assert(game.current_level < game.levels.count); | |
load_level(game.ldtk, *game.level, to_temp_string(game.levels[game.current_level])); | |
game.player_position = game.level.player_spawn_position; | |
game.player_direction = game.level.player_spawn_direction; | |
calculate_field_of_view(*game.field_of_view, game.level, game.player_position, game.player_direction); | |
if game.sounds[SFX_MUSIC1_INTRO].stream == null && game.sounds[SFX_MUSIC1].stream == null { | |
play_music(SFX_MUSIC1_INTRO, repeating = false); | |
} | |
fade_out((target: *Effect, step_progress: float, user_data: *void) { | |
}); | |
} | |
if mode_running(game.mode) { | |
if is_game_over() { | |
battle_log("Your party was wiped..."); | |
mode_transition(*game.mode, .GAME_OVER); | |
} | |
if audio_stream_progress(game.sounds[SFX_MUSIC1_INTRO]) >= 1 && game.sounds[SFX_MUSIC1].stream == null { | |
audio_stop(game.sounds[SFX_MUSIC1_INTRO]); | |
play_music(SFX_MUSIC1); | |
} | |
{ | |
defer mode_transition_current_to_next(*game.world_mode); | |
if #complete game.world_mode.current == { | |
case .EXPLORATION; { | |
if mode_enter(*game.world_mode) { | |
game.force_fov_update = true; | |
} | |
if mode_running(*game.world_mode) { | |
if player_inputs.move.value != .{} { | |
player_move: Vector2s; | |
if player_inputs.move.value.y < 0 { | |
player_move = DIRECTIONS_CARDINAL[game.player_direction]; | |
} | |
if player_inputs.move.value.y > 0 { | |
player_move = DIRECTIONS_CARDINAL[(game.player_direction+2)%4]; | |
} | |
if player_inputs.move.value.x > 0 { | |
dir := game.player_direction + 1; | |
if dir > 3 { dir = 0; } | |
player_move = DIRECTIONS_CARDINAL[dir]; | |
} | |
if player_inputs.move.value.x < 0 { | |
dir := game.player_direction - 1; | |
if dir < 0 { dir = 3; } | |
player_move = DIRECTIONS_CARDINAL[dir]; | |
} | |
if player_move != .{} { | |
destination := game.player_position + player_move; | |
valid_destination := true; | |
valid_destination &= is_in_bounds(destination, game.level.size); | |
cell_index := grid_position_to_index(destination, game.level.size.x); | |
cell := *game.level.cells[cell_index]; | |
valid_destination &= cell.id == 0 || cell.opened; | |
has_encounter := cell.encounter.type; | |
if has_encounter { | |
direction := destination - game.player_position; | |
turn_direction: Direction_Cardinal; | |
for DIRECTIONS_CARDINAL { | |
if it == direction { | |
turn_direction = cast(Direction_Cardinal) it_index; | |
break; | |
} | |
} | |
if #complete cell.encounter.type == { | |
case .NONE; {} | |
case .CREATURE; { | |
if game.party.count == 0 { | |
battle_log("Too dangerous to approach by yourself!"); | |
valid_destination = false; | |
} else { | |
creature := CREATURES[cell.encounter.creature_id]; | |
game.player_direction = turn_direction; | |
game.battle = .{}; | |
game.battle.cell_index = cell_index; | |
game.battle.foe_creature_id = cell.encounter.creature_id; | |
game.battle.party_attack_timers.count = game.party.count; | |
for part : creature.parts { | |
fixed_array_add(*game.battle.foe_parts_health, .{ part.health, part.health }); | |
} | |
battle_log(tprint("% approaches!", to_temp_string(creature.name))); | |
play_sound(creature.encounter_sfx); | |
mode_transition(*game.world_mode, .BATTLE); | |
valid_destination = false; | |
} | |
} | |
// :pickup | |
case .PICKUP; { | |
pickup := PICKUPS[cell.encounter.pickup_id]; | |
if #complete pickup.effect == { | |
case .NONE; {} | |
case .DAMAGE; { | |
play_sound(PICKUPS[cell.encounter.pickup_id].encounter_sfx); | |
battle_log("Your party triggered a trap!", .CRITICAL); | |
for :for_ptr party_member, party_member_index : game.party { | |
damage_party_member(party_member_index, 5, .TAIL); | |
} | |
cell.encounter.pickup_id = 6; | |
valid_destination &= true; | |
} | |
case .HEAL; { | |
play_sound(PICKUPS[cell.encounter.pickup_id].encounter_sfx); | |
battle_log("Your party was healed."); | |
for :for_ptr party_member : game.party { | |
heal_party_member(party_member, MAX_HEALTH); | |
} | |
cell.encounter.pickup_id = 7; | |
valid_destination = false; | |
} | |
case .DOOR; { | |
play_sound(SFX_ERROR); | |
battle_log("The door is locked..."); | |
valid_destination = false; | |
} | |
case .KEY; { | |
play_sound(PICKUPS[cell.encounter.pickup_id].encounter_sfx); | |
battle_log("Somewhere, a door opened..."); | |
game.level.cells[cell.encounter.target_cell_index].id = 3; // 1 = wall, 2 = door closed, 3 = door opened | |
game.level.cells[cell.encounter.target_cell_index].opened = true; | |
game.level.cells[cell.encounter.target_cell_index].encounter = .{}; | |
cell.encounter = .{}; | |
} | |
case .COLLAR; { | |
if game.collar { | |
play_sound(SFX_ERROR); | |
battle_log("You already have a collar..."); | |
valid_destination = false; | |
} else { | |
play_sound(PICKUPS[cell.encounter.pickup_id].encounter_sfx); | |
battle_log("You pickup a collar..."); | |
cell.encounter = .{}; | |
game.collar = true; | |
valid_destination = false; | |
} | |
} | |
case .STAIRS; { | |
current_world_depth := game.ldtk.levels[game.current_level].worldDepth; | |
next_levels: [..]int; | |
for level, level_index : game.ldtk.levels { | |
if level.worldDepth == current_world_depth + 1 { | |
array_add(*next_levels, level_index); | |
} | |
} | |
if next_levels.count == 0 { | |
battle_log("Your party escaped the dungeon! \\o/"); | |
mode_transition(*game.mode, .TITLE); | |
} else { | |
play_sound(PICKUPS[cell.encounter.pickup_id].encounter_sfx); | |
battle_log("Your party moves on to the next floor..."); | |
next_level := next_levels[cast(int) random_get_within_range(*game.random, 0, xx next_levels.count)]; | |
game.current_level = next_level; | |
fade_in((target: *Effect, step_progress: float, user_data: *void) { | |
mode_transition(*game.mode, .WORLD); | |
}); | |
} | |
} | |
} | |
} | |
// :starter effect | |
case .STARTER; { | |
creature := CREATURES[cell.encounter.creature_id]; | |
target_cell_index := cell.encounter.target_cell_index; | |
// FIXME: get the stats from the CREATURES data | |
fixed_array_add(*game.party, Party_Member.{ creature_id = cell.encounter.creature_id, health = .{ creature.health, creature.health } }); | |
for *cell : game.level.cells { | |
if cell.encounter.type == .STARTER { | |
cell.encounter = .{}; | |
} | |
} | |
play_sound(creature.encounter_sfx); | |
battle_log(tprint("% joined your party.", to_temp_string(creature.name))); | |
// copy pasted from .KEY | |
// play_sound(PICKUPS[cell.encounter.pickup_id].encounter_sfx); | |
battle_log("Somewhere, a door opened..."); | |
game.level.cells[target_cell_index].id += 1; // n = wall, n+1 = door closed, n+2 = door opened | |
game.level.cells[target_cell_index].opened = true; | |
game.level.cells[target_cell_index].encounter = .{}; | |
cell.encounter = .{}; | |
valid_destination = false; | |
} | |
} | |
game.force_fov_update = true; | |
} | |
if valid_destination { | |
game.player_position = destination; | |
audio_play_sound(.[game.sounds[SFX_FOOTSTEP1], game.sounds[SFX_FOOTSTEP2], game.sounds[SFX_FOOTSTEP3]], true); | |
} | |
} | |
} | |
if player_inputs.shoulder_left.pressed { | |
game.player_direction -= 1; | |
if game.player_direction < 0 { game.player_direction = 3; } | |
} | |
if player_inputs.shoulder_right.pressed { | |
game.player_direction += 1; | |
if game.player_direction > 3 { game.player_direction = 0; } | |
} | |
game.force_fov_update |= previous_player_position != game.player_position || previous_player_direction != game.player_direction; | |
if game.force_fov_update { | |
calculate_field_of_view(*game.field_of_view, game.level, game.player_position, game.player_direction); | |
game.force_fov_update = false; | |
} | |
} | |
} | |
case .BATTLE; { | |
party_member := game.party[game.battle.party_member_current]; | |
party_creature := CREATURES[party_member.creature_id]; | |
foe_creature := CREATURES[game.battle.foe_creature_id]; | |
if mode_enter(*game.world_mode) { | |
game.battle.started = true; | |
timer_start_now(*game.battle.foe_attack_timer, foe_creature.attack_cooldown); | |
for party_member, party_index : game.party { | |
timer_start_now(*game.battle.party_attack_timers[party_index], CREATURES[party_member.creature_id].attack_cooldown); | |
} | |
} | |
// :battle running | |
if mode_running(*game.world_mode) { | |
fire_pressed: bool; | |
quit_pressed: bool; | |
capture_pressed: bool; | |
cursor_window_position := engine.inputs.mouse_position; | |
cursor_position := window_to_world_position(engine.inputs.mouse_position); | |
if engine.inputs.mouse_keys[Mouse_Button.LEFT].released && !ui_is_hovering_anything() { | |
fire_pressed = true; | |
} | |
if game.ui.inventory_collar_pressed { | |
capture_pressed = true; | |
} | |
#if DEBUG { | |
if player_inputs.cancel.down { | |
quit_pressed = true; | |
} | |
} | |
foe_attack_progress := timer_progress(game.battle.foe_attack_timer); | |
if foe_attack_progress == 1 { | |
target_party_index := cast(int) random_get_within_range(*game.random, 0, xx game.party.count); | |
// We just can just retry a random target next frame if this one is dead... It's not great but this is a game jam | |
if game.party.data[target_party_index].health.current > 0 { | |
damage_party_member(target_party_index, foe_creature.attack_damage, foe_creature.attack_type); | |
play_sound(foe_creature.attack_sfx, false); | |
timer_start_now(*game.battle.foe_attack_timer, foe_creature.attack_cooldown); | |
} | |
} | |
foe_part_id := -1; | |
for part, part_index : foe_creature.parts { | |
part_position := to_vector2(part.position); | |
part_size := to_vector2(part.size); | |
// #if DEBUG { | |
// PART_COLORS :: Color.[ | |
// .{ 1, 0, 0, 0.2 }, | |
// .{ 0, 1, 0, 0.2 }, | |
// .{ 0, 0, 1, 0.2 }, | |
// .{ 0, 1, 1, 0.2 }, | |
// ]; | |
// push_sprite(.{ | |
// position = part_position, | |
// scale = part_size, | |
// texture_position = .{ 0, 0 }, | |
// texture_size = GRID_SIZE_V2S, | |
// rect_index = xx IMG_SPRITESHEET, | |
// color = PART_COLORS[part_index], | |
// layer_id = 3, | |
// z_index = 999, | |
// }); | |
// } | |
collides := aabb_collides(make_vector2s(cursor_position), .{ 1, 1 }, make_vector2s(part_position - part_size/2), make_vector2s(part_size)); | |
if collides { | |
if foe_part_id > -1 && foe_creature.parts[foe_part_id].priority > foe_creature.parts[part_index].priority { | |
continue; | |
} | |
if game.battle.foe_parts_health[part_index].current == 0 { | |
continue; | |
} | |
foe_part_id = part_index; | |
} | |
} | |
game.battle.foe_part_highlighted = foe_part_id; | |
if game.battle.foe_part_highlighted > -1 { | |
ui_push_text(tprint("TARGET: %", foe_creature.parts[foe_part_id].type), .{ 226, 248 }, layer_id = 0); | |
} | |
attack_ready := -1; | |
for attack_timer, party_index : game.battle.party_attack_timers { | |
if timer_progress(attack_timer) == 1 && !game.battle.party_attack_sound_played[party_index] { | |
game.battle.party_attack_sound_played[party_index] = true; | |
attack_ready = party_index; | |
} | |
} | |
if attack_ready > -1 { | |
play_sound(SFX_ATTACK_READY); | |
timer_start_now(*game.battle.party_flash_timers[attack_ready], 200); | |
} | |
if fire_pressed { | |
attack_timer := *game.battle.party_attack_timers[game.battle.party_member_current]; | |
if timer_progress(attack_timer) == 1 && foe_part_id > -1 { | |
timer_start_now(*game.battle.foe_part_flashing_timer, 200); | |
for_creature_part := foe_creature.parts[foe_part_id]; | |
game.battle.foe_part_flashing = foe_part_id; | |
game.battle.party_attack_sound_played[game.battle.party_member_current] = false; | |
damage_foe_part(party_creature.id, game.battle.foe_creature_id, foe_part_id, party_creature.attack_damage, party_creature.attack_type, to_vector2(GAME_VIEW_CENTER + make_vector2s(cursor_position * engine.camera_ui.zoom))); | |
play_sound(party_creature.attack_sfx, false); | |
broken_parts: int; | |
for health : game.battle.foe_parts_health { | |
if health.current <= 0 { | |
broken_parts += 1; | |
} | |
} | |
if broken_parts == game.battle.foe_parts_health.count { | |
log_error("End of battle."); | |
game.level.cells[game.battle.cell_index].encounter = .{}; | |
play_sound(foe_creature.encounter_sfx, true); | |
mode_transition(*game.world_mode, .EXPLORATION); | |
} | |
timer_start_now(attack_timer, party_creature.attack_cooldown); | |
} else { | |
timer_start_now(*game.battle.party_flash_timers[game.battle.party_member_current], 200); | |
play_sound(SFX_ERROR); | |
battle_log(tprint("% is not ready yet!", to_temp_string(party_creature.name))); | |
} | |
} | |
if capture_pressed { | |
if game.party.count == game.party.data.count { | |
battle_log("Your party is full!", .CRITICAL); | |
play_sound(SFX_ERROR, false); | |
} else { | |
foe_creature := CREATURES[game.battle.foe_creature_id]; | |
game.collar = false; | |
play_sound(foe_creature.encounter_sfx); | |
battle_log(tprint("You tamed %.", to_temp_string(foe_creature.name))); | |
health_current := 0; | |
health_max := 0; | |
for health : game.battle.foe_parts_health { | |
health_current += health.current; | |
health_max += health.max; | |
} | |
health_progress := cast(float) health_current / health_max; | |
fixed_array_add(*game.party, Party_Member.{ creature_id = game.battle.foe_creature_id, health = .{ cast(u8) (health_progress * foe_creature.health), foe_creature.health } }); | |
mode_transition(*game.world_mode, .EXPLORATION); | |
cell := *game.level.cells[game.battle.cell_index]; | |
cell.encounter = .{}; | |
} | |
} | |
if quit_pressed { | |
battle_log("[CHEAT] You fled from the monster D:"); | |
mode_transition(*game.world_mode, .EXPLORATION); | |
} | |
} | |
if mode_exit(*game.world_mode) { | |
new_party: Fixed_Size_Array(Party_Member, MAX_PARTY_SIZE); | |
for party_member, party_index : game.party { | |
if party_member.health.current > 0 { | |
fixed_array_add(*new_party, party_member); | |
} | |
} | |
game.party = new_party; | |
game.battle = .{}; | |
arena_reset(game.battle_arena); | |
} | |
} | |
} | |
} | |
// :render level name | |
ui_push_text(to_temp_string(game.levels[game.current_level]), .{ 37, 248 }, layer_id = 0); | |
// :render game view | |
{ | |
// background | |
push_sprite(.{ | |
position = .{ 0, 0 }, | |
scale = .{ 80, 80 }, | |
texture_position = .{ 336+8, 176+8 }, | |
texture_size = .{ 80, 80 }, | |
// scale = .{ 96, 96 }, | |
// texture_position = .{ 160, 176 }, | |
// texture_size = .{ 96, 96 }, | |
rect_index = xx IMG_TILESET1, // TODO: get this from tile data | |
layer_id = 0, | |
color = COLOR_WHITE, | |
z_index = 0, | |
}); | |
for y : 0..VIEW_SIZE.y-1 { | |
for x : 0..VIEW_SIZE.x-1 { | |
cell_index := grid_position_to_index(.{ x, y }, VIEW_SIZE.x); | |
cell_view_position := Vector2s.{ x-VIEW_SIZE.x/2, y }; | |
cell_instance := game.field_of_view[cell_index]; | |
cell := CELLS[cell_instance.id]; | |
if game.level.hidden_cells[cell_index].hidden { continue; } | |
// ImGui.Text("cell_view_position: %", cell_view_position); | |
for wall_id, wall_index : cell.walls { | |
if game.level.hidden_cells[cell_index].walls_hidden[wall_index] { continue; } | |
position: Vector2; | |
texture_position := Vector2s.{ 0, 0 }; | |
texture_size := Vector2s.{ 1, 1 }; | |
z_index := y*3; | |
texture_data := TILE_TEXTURES_DATA[y]; | |
wall_offset_side: float; | |
// TODO: hmmmm this is ugly but good enough for now i guess | |
if cell_view_position.x == 0 { | |
// if y == 1 { wall_offset_side = cast(float) (texture_data.front_size.x - texture_data.side_size.x/2); } | |
// if y == 2 { wall_offset_side = 27.5; } | |
// if y == 3 { wall_offset_side = 42.5; } | |
} else { | |
if y == 1 { wall_offset_side = 4; } | |
if y == 2 { wall_offset_side = 8; } | |
if y == 3 { wall_offset_side = 24; } | |
} | |
is_current_cell := x == VIEW_WIDTH/2 && y == VIEW_HEIGHT-1; | |
// :render walls | |
wall_direction := cast(Direction_Cardinal) wall_index; | |
if #complete wall_direction == { | |
case .NORTH; { | |
if cell_view_position.y == 0 { continue; } | |
if is_current_cell { continue; } | |
position.x = cast(float) (cell_view_position.x * texture_data.front_size.x); | |
texture_position += texture_data.front_pos; | |
texture_size = texture_data.front_size; | |
z_index -= 1; | |
} | |
case .SOUTH; { | |
if y >= VIEW_SIZE.y-1 { continue; } | |
wall_i := ifx(cell_view_position.y == 0) then y else y+1; | |
position.x = cast(float) (cell_view_position.x * TILE_TEXTURES_DATA[wall_i].front_size.x); | |
texture_position = TILE_TEXTURES_DATA[wall_i].front_pos; | |
texture_size = TILE_TEXTURES_DATA[wall_i].front_size; | |
z_index += 1; | |
// TODO: is there a reason we are doing this in .SOUTH?? | |
// :render encounter | |
if cell_instance.encounter.type { | |
texture_position: Vector2s; | |
texture_size: Vector2s; | |
creature := CREATURES[cell_instance.encounter.creature_id]; | |
image: Images; | |
if #complete cell_instance.encounter.type == { | |
case .NONE; { } | |
case .STARTER; { | |
texture_position = CREATURE_TILE_POSITIONS[y]; | |
texture_size = CREATURE_TILE_SIZES[y]; | |
image = cast(Images) IMG_STARTER1 + cast(Images) cell_instance.encounter.creature_id-1; | |
}; | |
case .CREATURE; { | |
texture_position = CREATURE_TILE_POSITIONS[y]; | |
texture_size = CREATURE_TILE_SIZES[y]; | |
image = creature.image; | |
} | |
case .PICKUP; { | |
texture_position = CREATURE_TILE_POSITIONS[y]; | |
texture_size = CREATURE_TILE_SIZES[y]; | |
image = PICKUPS[cell_instance.encounter.pickup_id].image; | |
} | |
} | |
if image != IMG_NONE { | |
// :render parts | |
render_individual_parts := x == VIEW_WIDTH/2 && y == VIEW_HEIGHT/2 && cell_instance.encounter.type == .CREATURE; | |
render_starter_name := x == VIEW_WIDTH/2 && y == VIEW_HEIGHT/2 && cell_instance.encounter.type == .STARTER; | |
if render_individual_parts { // render each parts (for battle) | |
part_image_size :: Vector2s.{ 96, 96 }; | |
part_image_offset :: Vector2s.{ 0, 0 }; | |
// Yes the code is ugly, stolen from https://easings.net/#easeInOutElastic | |
ease_in_out_elastic :: (x: float) -> float { | |
c5 := (2 * PI) / 4.5; | |
return ifx(x == 0) | |
then 0 | |
else ifx(x == 1) | |
then 1 | |
else ifx(x < 0.5) | |
then -(pow(2, 20 * x - 10) * sin((20 * x - 11.125) * c5)) / 2 | |
else (pow(2, -20 * x + 10) * sin((20 * x - 11.125) * c5)) / 2 + 1; | |
} | |
for part, part_index : creature.parts { | |
flash_progress := timer_progress(game.battle.foe_part_flashing_timer); | |
is_part_flashing := part_index == game.battle.foe_part_flashing && flash_progress < 1; | |
is_part_dead := game.battle.foe_parts_health[part_index].current == 0; | |
animation_offset := Vector2.{ 0, 0 }; | |
if is_part_flashing { animation_offset.x = ease_in_out_elastic(flash_progress); } | |
color := COLOR_WHITE; | |
if is_part_flashing { color = COLOR_RED3; } | |
if is_part_dead { color = COLOR_GREY; } | |
push_sprite(.{ | |
position = position + to_vector2(part_image_offset) + animation_offset, | |
scale = to_vector2(part_image_size), | |
texture_position = .{ 0, 0 }, | |
texture_size = part_image_size, | |
rect_index = xx part.image, | |
layer_id = 0, | |
color = color, | |
z_index = 10 + z_index + part.z_index*3, | |
}); | |
is_part_highlighted := game.battle.started && part_index == game.battle.foe_part_highlighted; | |
if is_part_highlighted && !is_part_dead { | |
push_sprite(.{ | |
position = position + to_vector2(part_image_offset) + animation_offset, | |
scale = to_vector2(part_image_size), | |
texture_position = .{ 0, 0 }, | |
texture_size = part_image_size, | |
rect_index = xx (part.image+1), | |
layer_id = 0, | |
color = .{ 1, 1, 1, 0.5 }, | |
z_index = 10 + z_index + part.z_index*3 + 1, | |
}); | |
} | |
} | |
} else { | |
push_sprite(.{ | |
position = position, | |
scale = to_vector2(texture_size), | |
texture_position = texture_position, | |
texture_size = texture_size, | |
rect_index = xx image, | |
layer_id = 0, | |
color = .{ 1, 1, 1, 1 }, | |
z_index = 10 + z_index, | |
}); | |
} | |
if render_starter_name { | |
ui_push_text(to_temp_string(CREATURES[cell_instance.encounter.creature_id].name), .{ 140, 80 }); | |
} | |
} | |
} | |
} | |
case .WEST; { | |
if cell_view_position.y == 0 { continue; } | |
if cell_view_position.x < 0 { continue; } | |
if is_current_cell { continue; } | |
position.x = cast(float) (cell_view_position.x * texture_data.front_size.x); | |
offset := 0.0; | |
if cell_view_position.x == 0 { | |
offset -= wall_offset_side; | |
} else { | |
offset += wall_offset_side; | |
if abs(cell_view_position.x) == 2 { offset -= 32; } | |
} | |
position.x += offset * -1; | |
texture_position += texture_data.west_pos; | |
texture_size = texture_data.side_size; | |
} | |
case .EAST; { | |
if cell_view_position.y == 0 { continue; } | |
if cell_view_position.x > 0 { continue; } | |
if is_current_cell { continue; } | |
position.x = cast(float) (cell_view_position.x * texture_data.front_size.x); | |
offset := 0.0; | |
if cell_view_position.x == 0 { | |
offset -= wall_offset_side; | |
} else { | |
offset += wall_offset_side; | |
if abs(cell_view_position.x) == 2 { offset -= 32; } | |
} | |
position.x += offset; | |
texture_position += texture_data.east_pos; | |
texture_size = texture_data.side_size; | |
} | |
} | |
// ImGui.Text("- wall: % tpos: % tsize: % pos: %", wall_direction, texture_position, texture_size, position); | |
if wall_id > 0 { | |
push_sprite(.{ | |
position = position, | |
scale = to_vector2(texture_size), | |
texture_position = texture_position, | |
texture_size = texture_size, | |
rect_index = xx TILE_TEXTURES[wall_id], | |
layer_id = 0, | |
color = .{ 1, 1, 1, 1 }, | |
z_index = 10 + z_index, | |
}); | |
} | |
} | |
} | |
} | |
} | |
// :render effects | |
for effect : game.effects { | |
context._Tracy.ZoneScoped("render_effects"); | |
if effect.active == false { continue; } | |
ui_push_sprite( | |
window_position = make_vector2s(effect.position), | |
size = effect.size, | |
texture_position = effect.texture_position, | |
texture_size = effect.texture_size, | |
rect_index = xx effect.rect_index, | |
layer_id = effect.layer_id, | |
color = effect.color, | |
z_index = effect.z_index, | |
); | |
} | |
} | |
if mode_exit(*game.mode) { | |
audio_stop(game.sounds[SFX_MUSIC1]); | |
} | |
} | |
// :game over | |
case .GAME_OVER; { | |
if mode_enter(*game.mode) { | |
audio_stop(game.sounds[SFX_MUSIC1]); | |
} | |
if mode_running(game.mode) { | |
push_sprite(.{ | |
position = .{ 0, 0 }, | |
scale = .{ 80, 80 }, | |
texture_size = .{ 80, 80 }, | |
z_index = 999, | |
layer_id = 1, | |
rect_index = xx IMG_GAME_OVER, | |
color = COLOR_WHITE, | |
}); | |
if player_inputs.confirm.pressed || engine.inputs.mouse_keys[Mouse_Button.LEFT].pressed { | |
fade_in((target: *Effect, step_progress: float, user_data: *void) { | |
mode_transition(*game.mode, .TITLE); | |
}); | |
} | |
} | |
} | |
} | |
animation_update(); | |
// if ImGui.Begin("Help") { | |
// ImGui.Text("Controls:"); | |
// ImGui.Indent(); | |
// ImGui.Text("WASD: move"); | |
// ImGui.Text("QE: turn"); | |
// ImGui.Text("~: open the console/logs"); | |
// ImGui.Text("F1: open the debug menu"); | |
// ImGui.Text("F2: open the animation menu"); | |
// ImGui.Text("CTRL + R: restart the game (reload the levels as well)"); | |
// ImGui.Unindent(); | |
// } | |
// ImGui.End(); | |
// :render screen transition | |
ui_push_rect(.{ 0, 0 }, UI_RESOLUTION*2, color = game.fade_color, z_index = MAX_Z_INDEX-1, layer_id = 1); | |
// :render floating messages | |
for :for_ptr message : game.floating_messages { | |
if message.timer.start == .{} { continue; } | |
if message.timer.start > engine.now { continue; } | |
push_pop_text_color(message.color); | |
push_pop_text_size(message.size); | |
ui_push_text(to_temp_string(message.text), make_vector2s(message.window_position)); | |
progress := timer_progress(message.timer); | |
if progress == 1 { | |
message.timer = .{}; | |
} | |
} | |
engine_ui_main_menu_push_window("Game Debug", *game.debug_ui_debug, game_ui_window_debug); | |
engine_ui_main_menu_push_window("Animation", *engine.debug_ui_animation, animation_ui_window); | |
return engine.inputs.quit_requested; | |
} | |
#program_export | |
unload :: () -> *Game { | |
push_context,defer_pop game.ctx; | |
engine_unload(); | |
return game; | |
} | |
#program_export | |
reload :: (memory: *Game) { | |
assert(memory != null); | |
game = cast(*Game) memory; | |
push_context,defer_pop game.ctx; | |
engine_reload(game.engine); | |
} | |
play_sound :: (sound_id: Sounds, perturb := false, loc := #caller_location) { | |
audio_play_sound(game.sounds[sound_id], perturb, loc); | |
} | |
play_music :: (sound: Sounds, repeating := true, loc := #caller_location) { | |
audio_play_music(game.sounds[sound], repeating, loc); | |
} | |
// :game_ui debug | |
game_ui_window_debug :: () { | |
if ImGui.Begin("Game", *game.debug_ui_debug) { | |
defer ImGui.End(); | |
if ImGui.CollapsingHeader("Common", .DefaultOpen) { | |
imgui_inspect("game", game); | |
imgui_inspect("engine", engine); | |
if ImGui.Button("Title") { | |
game.skip_title = false; | |
mode_transition(*game.mode, .TITLE); | |
} | |
for level, level_index : game.levels { | |
ImGui.SameLine(); | |
if level_index && level_index % 5 == 0 { ImGui.NewLine(); } | |
if ImGui.Button(to_temp_c_string(level)) { | |
game.current_level = level_index; | |
mode_transition(*game.mode, .WORLD); | |
} | |
} | |
} | |
if ImGui.CollapsingHeader("Memory", .DefaultOpen) { | |
imgui_arena_progress(engine.arena); | |
imgui_arena_progress(game.arena); | |
} | |
if ImGui.CollapsingHeader("Camera: Main", .DefaultOpen) { | |
ImGui.InputFloat2("position###camera_camera_main_position", xx *engine.camera_main.position); | |
ImGui.InputFloat("zoom###camera_main_zoom", *engine.camera_main.zoom); | |
ImGui.InputFloat("rotation###camera_main_rotation", *engine.camera_main.rotation); | |
imgui_inspect("details", *engine.camera_main); | |
} | |
if ImGui.CollapsingHeader("Camera: UI", .DefaultOpen) { | |
ImGui.InputFloat2("position###camera_camera_ui_position", xx *engine.camera_ui.position); | |
ImGui.InputFloat("zoom###camera_ui_zoom", *engine.camera_ui.zoom); | |
ImGui.InputFloat("rotation###camera_ui_rotation", *engine.camera_ui.rotation); | |
imgui_inspect("details", *engine.camera_ui); | |
} | |
if ImGui.CollapsingHeader("Mouse", .DefaultOpen) { | |
ImGui.Text("position_window: %", engine.inputs.mouse_position); | |
ImGui.Text("position_world: %", game.mouse_position_world); | |
ImGui.Text("position_grid: %", game.mouse_position_grid); | |
} | |
if ImGui.CollapsingHeader("Inputs") { | |
imgui_inspect("quit_requested", *engine.inputs.quit_requested); | |
imgui_inspect("window_resized", *engine.inputs.window_resized); | |
imgui_inspect("window_is_focused", *engine.inputs.window_is_focused); | |
imgui_inspect("keyboard_was_used", *engine.inputs.keyboard_was_used); | |
imgui_inspect("keys", *engine.inputs.keys); | |
imgui_inspect("mouse_was_used", *engine.inputs.mouse_was_used); | |
imgui_inspect("mouse_keys", *engine.inputs.mouse_keys); | |
imgui_inspect("mouse_position", *engine.inputs.mouse_position); | |
imgui_inspect("mouse_wheel", *engine.inputs.mouse_wheel); | |
imgui_inspect("mouse_moved", *engine.inputs.mouse_moved); | |
imgui_inspect("controller_was_used", *engine.inputs.controller_was_used); | |
imgui_inspect("controllers", *engine.inputs.controllers); | |
imgui_inspect("player_inputs", *game.player_inputs); | |
} | |
if ImGui.CollapsingHeader("Audio", .DefaultOpen) { | |
ImGui.Text("Musics:"); | |
if ImGui.BeginTable("musics_table", 3, .BordersInnerH | .SizingStretchProp) { | |
defer ImGui.EndTable(); | |
for sound_asset, sound_id : game.sounds { | |
if sound_asset == null || sound_asset.category != .MUSIC { continue; } | |
ImGui.TableNextRow(); | |
ImGui.TableSetColumnIndex(0); | |
ImGui.Text(tprint("%", cast(Sounds) sound_id)); | |
ImGui.TableNextColumn(); | |
if sound_asset.stream { | |
ImGui.ProgressBar(audio_stream_progress(sound_asset), .{ 200, 18 }); | |
// imgui_inspect("stream", sound_asset.stream, true); | |
} else { | |
ImGui.Dummy(.{ 200, 0 }); | |
} | |
ImGui.TableNextColumn(); | |
if sound_asset.stream { | |
if ImGui.Button("STOP") { audio_stop(game.sounds[sound_id]); } | |
} else { | |
if ImGui.Button("START") { audio_play_music(game.sounds[sound_id]); } | |
} | |
} | |
} | |
ImGui.Text("Sounds:"); | |
if ImGui.BeginTable("sounds_table", 2, .BordersInnerH | .SizingStretchProp) { | |
defer ImGui.EndTable(); | |
for sound_asset, sound_id : game.sounds { | |
if sound_asset == null || sound_asset.category == .MUSIC { continue; } | |
ImGui.TableNextRow(); | |
ImGui.TableSetColumnIndex(0); | |
ImGui.Text(tprint("%", cast(Sounds) sound_id)); | |
ImGui.TableNextColumn(); | |
if ImGui.Button("PLAY") { | |
audio_play_sound(game.sounds[sound_id]); | |
} | |
} | |
} | |
} | |
if ImGui.CollapsingHeader("Level", .DefaultOpen) { | |
if ImGui.Button("show_all") { | |
for game.level.hidden_cells { game.level.hidden_cells[it_index] = .{}; } | |
} | |
for layer : 0..VIEW_HEIGHT-1 { | |
ImGui.SameLine(); | |
if ImGui.Button(tprint_c("show_layer_%", layer)) { | |
for cell, cell_index : game.level.hidden_cells { | |
p := grid_index_to_position(cell_index, VIEW_SIZE); | |
if p.y == layer { | |
game.level.hidden_cells[cell_index].hidden = false; | |
} | |
} | |
} | |
} | |
if ImGui.Button("hide_all") { | |
for cell, cell_index : game.level.hidden_cells { | |
game.level.hidden_cells[cell_index].hidden = true; | |
for wall, wall_index : game.level.hidden_cells[cell_index].walls_hidden { | |
// game.level.hidden_cells[cell_index].walls_visible[wall_index] = true; | |
} | |
} | |
} | |
for layer : 0..VIEW_HEIGHT-1 { | |
ImGui.SameLine(); | |
if ImGui.Button(tprint_c("hide_layer_%", layer)) { | |
for cell, cell_index : game.level.hidden_cells { | |
p := grid_index_to_position(cell_index, VIEW_SIZE); | |
if p.y == layer { | |
game.level.hidden_cells[cell_index].hidden = true; | |
} | |
} | |
} | |
} | |
if game.level.cells.count { | |
for cell_instance, view_index : game.field_of_view { | |
cell := CELLS[cell_instance.id]; | |
cell_state := *game.level.hidden_cells[view_index]; | |
view_position := grid_index_to_position(view_index, VIEW_SIZE); | |
ImGui.PushID(cast(s32) view_index); | |
defer ImGui.PopID(); | |
text_color := ImGui.GetStyleColorVec4(.Text).*; | |
color := text_color; | |
if cell.walls[0] == 0 && cell.walls[1] == 0 && cell.walls[2] == 0 && cell.walls[3] == 0 { color = ImGui.GetStyleColorVec4(.PlotLines).*; } | |
ImGui.PushStyleColor(.Text, color); | |
ImGui.Text("%", view_position); | |
ImGui.SameLine(); | |
ImGui.Text("| HIDE:"); | |
ImGui.SameLine(); | |
ImGui.Checkbox("CELL", *cell_state.hidden); | |
for cell_state.walls_hidden { | |
ImGui.SameLine(); | |
ImGui.Checkbox(tprint_c("% %", cast(Direction_Cardinal) it_index, cast(int) cell.walls[it_index]), *cell_state.walls_hidden[it_index]); | |
} | |
ImGui.PopStyleColor(1); | |
} | |
} | |
} | |
} | |
} | |
grid_to_world_position_center :: (grid_position: Vector2s) -> Vector2 { | |
return grid_to_world_position_center(grid_position, .{ GRID_SIZE, GRID_SIZE }); | |
} | |
// https://en.wikipedia.org/wiki/Midpoint_circle_algorithm | |
// https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm | |
plot_circle :: (center: Vector2s, radius: s32) -> []Vector2s { | |
result: [..]Vector2s; | |
x: s32; | |
y: s32 = radius; | |
d: s32 = 3 - 2 * radius; | |
array_add(*result, center + .{ +x, +y }); | |
array_add(*result, center + .{ -x, +y }); | |
array_add(*result, center + .{ +x, -y }); | |
array_add(*result, center + .{ -x, -y }); | |
array_add(*result, center + .{ +y, +x }); | |
array_add(*result, center + .{ -y, +x }); | |
array_add(*result, center + .{ +y, -x }); | |
array_add(*result, center + .{ -y, -x }); | |
while y >= x { | |
if d > 0 { | |
y -= 1; | |
d = d + 4 * (x - y) + 10; | |
} else { | |
d = d + 4 * x + 6; | |
} | |
x += 1; | |
array_add(*result, center + .{ +x, +y }); | |
array_add(*result, center + .{ -x, +y }); | |
array_add(*result, center + .{ +x, -y }); | |
array_add(*result, center + .{ -x, -y }); | |
array_add(*result, center + .{ +y, +x }); | |
array_add(*result, center + .{ -y, +x }); | |
array_add(*result, center + .{ +y, -x }); | |
array_add(*result, center + .{ -y, -x }); | |
} | |
return result; | |
} | |
manhathan_distance :: (a: Vector2s, b: Vector2s) -> u32 { | |
return cast(u32) (abs(a.x - b.x) + abs(a.y - b.y)); | |
} | |
make_effect :: (ui_position := Vector2.{ 0, 0 }, texture_position := Vector2s.{ 0, 0 }, scale := Vector2.{ 1, 1 }, z_index: s32 = 80) -> Effect { | |
return .{ | |
transform = .{ | |
position = ui_position, | |
scale = scale, | |
}, | |
sprite = .{ | |
active = true, | |
size = GRID_SIZE_V2S, | |
texture_size = GRID_SIZE_V2S, | |
texture_position = texture_position, | |
z_index = z_index, | |
color = COLOR_WHITE, | |
}, | |
}; | |
} | |
// FIXME: enable pan_camera again when we go back to larger levels | |
pan_camera :: (camera: *Camera_Orthographic, world_position: Vector2, animation_id: Animation_Id) { | |
// final_animation_id := animation_id; | |
// if final_animation_id == 0 { | |
// final_animation_id = animation_make("pan_camera", active = true, self_destroy = true); | |
// } | |
// assert(final_animation_id != 0); | |
// pan_curve := animation_make_curve(final_animation_id, *camera.position, "camera_world_position"); | |
// move_distance := distance(camera.position, world_position) / GRID_SIZE; | |
// animation_make_frame(pan_curve, camera.position, 0); | |
// animation_make_frame(pan_curve, world_position, cast(u64) (ANIMATION_DURATION_CAMERA_PAN * move_distance)); | |
} | |
Done_Callback :: #type (target: *Effect, step_progress: float, user_data: *void); | |
fade_in :: (callback: Done_Callback = null, user_data: *void = null, color: Color = COLOR_BLACK, duration: u64 = 1000) -> Animation_Id { | |
return _fade(callback, user_data, "fade_in", COLOR_TRANSPARENT, color, duration); | |
} | |
fade_out :: (callback: Done_Callback = null, user_data: *void = null, color: Color = COLOR_BLACK, duration: u64 = 1000) -> Animation_Id { | |
return _fade(callback, user_data, "fade_out", game.fade_color, COLOR_TRANSPARENT, duration); | |
} | |
_fade :: (callback: Done_Callback, user_data: *void, name: string, color_start: Color, color_end: Color, duration: u64 = 1000) -> Animation_Id { | |
animation_id := animation_make(name, active = true, self_destroy = true); | |
assert(animation_id != 0); | |
color_curve := animation_make_curve(animation_id, *game.fade_color, "color"); | |
animation_make_frame(color_curve, color_start, 0); | |
animation_make_frame(color_curve, color_end, duration); | |
event_curve := animation_make_curve(animation_id, *game, "events"); | |
animation_make_frame(event_curve, Frame_Event.{ xx callback, user_data }, duration); | |
return animation_id; | |
} | |
ui_push_rect :: (window_position: Vector2s, size: Vector2s = GRID_SIZE_V2S, color : Color = COLOR_WHITE, z_index: s32 = 80, layer_id: s32 = 2, loc := #caller_location) { | |
push_sprite(.{ | |
position = (to_vector2(window_position) + to_vector2(size/2)) * -1, | |
scale = to_vector2(size) * -1, | |
z_index = z_index, | |
layer_id = layer_id, | |
rect_index = xx IMG_SPRITESHEET, | |
color = color, | |
camera = .UI, | |
}, loc); | |
} | |
ui_push_sprite :: (window_position: Vector2s, size: Vector2s, texture_position: Vector2s, texture_size: Vector2s, rect_index : Images = IMG_SPRITESHEET, z_index : s32 = 80, layer_id: s32 = 2, color := COLOR_WHITE, rotation : float = 0, loc := #caller_location) { | |
push_sprite(.{ | |
position = (to_vector2(window_position) + to_vector2(size/2)) * -1, | |
scale = to_vector2(size) * -1, | |
texture_position = texture_position, | |
texture_size = texture_size, | |
rect_index = xx rect_index, | |
rotation = rotation, | |
z_index = z_index, | |
layer_id = layer_id, | |
color = color, | |
camera = .UI, | |
}, loc); | |
} | |
ui_push_text :: (text: string, window_position: Vector2s, layer_id: s32 = 2) { | |
position := Vector2s.{ | |
cast(s32) ((cast(float) window_position.x )), | |
cast(s32) ((cast(float) window_position.y + engine.text_current_size[engine.text_current_color.count] / 2)), | |
} + make_vector2s(game.ui.hud_position); | |
push_text_window(text, position, layer_id); | |
} | |
ui_button :: (position: Vector2s, size: Vector2s) -> (pressed: bool, hovered: bool) { | |
mouse_position := engine.inputs.mouse_position; | |
scaled_position := make_vector2s((game.ui.hud_position + to_vector2(position)) * game.ui.scale); | |
scaled_size := Vector2s.{ cast(s32) (size.x * game.ui.scale), cast(s32) (size.y * game.ui.scale) }; | |
hovered := aabb_collides(mouse_position, .{ 1, 1 }, scaled_position, scaled_size); | |
if hovered { | |
game.ui.hovered = true; | |
// game.cursor = .HAND; | |
} | |
pressed := hovered && engine.inputs.mouse_keys[Mouse_Button.LEFT].pressed && !ImGui.GetIO().WantCaptureMouse; | |
return pressed, hovered; | |
} | |
ui_push_rect_button :: (button_position: Vector2s, label: string, button_size: Vector2s = .{ 96, 48 }, offset : Vector2s = .{ 28, 30 }, layer_id : s32 = 1) -> (pressed: bool, hovered: bool) { | |
pressed, hovered := ui_button(button_position, button_size); | |
button_color := color_alpha(COLOR_WHITE, 0.15); | |
if hovered { button_color = color_alpha(COLOR_WHITE, 0.3); } | |
// if pressed { play_sound(SFX_CONFIRM, false); } | |
ui_push_rect(button_position, button_size, color = button_color, layer_id = layer_id); | |
push_text_window(label, button_position + offset + make_vector2s(game.ui.hud_position), layer_id = layer_id); | |
return pressed, hovered; | |
} | |
print_cell_instances :: (cells: []Cell_Instance, width: s32) -> string { | |
new_context := context; | |
new_context.allocator = temp; | |
new_context.print_style.default_format_int.minimum_digits = 2; | |
push_context,defer_pop new_context; | |
b: String_Builder; | |
for cell_instance, cell_index : cells { | |
if cell_index % width == 0 { append(*b, #char "\n"); } | |
append(*b, tprint("% ", cell_instance.id)); | |
} | |
return builder_to_string(*b); | |
} | |
print_cells :: (cells: []Cell_Id, width: s32) -> string { | |
new_context := context; | |
new_context.allocator = temp; | |
new_context.print_style.default_format_int.minimum_digits = 2; | |
push_context,defer_pop new_context; | |
b: String_Builder; | |
for cell_id, cell_index : cells { | |
if cell_index % width == 0 { append(*b, #char "\n"); } | |
append(*b, tprint("% ", cell_id)); | |
} | |
return builder_to_string(*b); | |
} | |
print_level :: (level: *Level, width: s32) -> string { | |
new_context := context; | |
new_context.allocator = temp; | |
new_context.print_style.default_format_int.minimum_digits = 2; | |
push_context,defer_pop new_context; | |
b: String_Builder; | |
for cell, cell_index : level.cells { | |
if cell_index % width == 0 { append(*b, #char "\n"); } | |
append(*b, tprint("% ", cell.id)); | |
} | |
return builder_to_string(*b); | |
} | |
rotate_2d_array :: (arr: *[]Cell_Instance, width: s32, diff: s32) { | |
s := width-1; | |
for 0 .. diff-1 { | |
arr_temp := array_copy(arr.*,, temp); | |
for #v2 < y : 0..s { | |
for x : 0..s { | |
dest := grid_position_to_index(.{ cast(s32) x, cast(s32) (s-y) }, width); | |
src := grid_position_to_index(.{ cast(s32) y, cast(s32) x }, width); | |
arr_temp[dest] = arr.*[src]; | |
} | |
} | |
arr.* = arr_temp; | |
} | |
} | |
calculate_field_of_view :: (field_of_view: *[VIEW_WIDTH*VIEW_HEIGHT]Cell_Instance, level: Level, player_position: Vector2s, player_direction: Direction_Cardinal) { | |
diff := cast(s32) (player_direction - Direction_Cardinal.NORTH); | |
// log("% -> % (diff: %)", Direction_Cardinal.NORTH, player_direction, diff); | |
forward := DIRECTIONS_CARDINAL[player_direction]; | |
right := DIRECTIONS_CARDINAL[(player_direction+1) % 4]; | |
// log("forward: %, right: %", forward, right); | |
view_index := 0; | |
for #v2 < y : 0..VIEW_SIZE.y-1 { | |
for x : -VIEW_SIZE.x/2..VIEW_SIZE.x/2 { | |
defer view_index += 1; | |
level_position := player_position + forward*cast(s32)y + right*cast(s32)x; | |
// log("view_pos: %,% view_index: % level_pos: %", x, y, view_index, level_position); | |
// log("player_pos: % forward: % right: %", player_position, y, x); | |
// position := Vector2s.{ x + player_position.x-1, cast(s32)y + player_position.y-2 }; | |
if !is_in_bounds(level_position, level.size) { | |
field_of_view.*[view_index] = .{}; | |
continue; | |
} | |
level_index := grid_position_to_index(level_position, level.size.x); | |
field_of_view.*[view_index] = level.cells[level_index]; | |
} | |
} | |
// log("\nfield_of_view: \n"); | |
// log(print_cells(field_of_view.*, VIEW_SIZE.x)); | |
} | |
load_level :: (root: *LDTK_Root, level: *Level, level_identifier: string) { | |
level_indexes: [..]int; | |
level_indexes.allocator = temp; | |
level_index := -1; | |
for level : root.levels { | |
if level.identifier == level_identifier { | |
level_index = it_index; | |
break; | |
} | |
} | |
load_level(root, level, level_index); | |
} | |
// :load level | |
load_level :: (root: *LDTK_Root, level: *Level, level_index: int) { | |
assert(level_index < root.levels.count, tprint("Level out of bounds: % / %", level_index, root.levels.count)); | |
ldtk_level := *root.levels[level_index]; | |
{ | |
layer := root.defs.layers[Level_Layers.GRID]; | |
layer_instance := ldtk_level.layerInstances[Level_Layers.GRID]; | |
scale := GRID_SIZE / layer.gridSize; // In case the grid size in the tilemap is different from the one we use in the game/renderer | |
level.size.x = ldtk_level.pxWid / layer.gridSize; | |
level.size.y = ldtk_level.pxHei / layer.gridSize; | |
level.cells = NewArray(level.size.x * level.size.y, Cell_Instance); | |
level.hidden_cells = NewArray(VIEW_WIDTH * VIEW_HEIGHT, Cell_Debug_State); | |
for grid_value, local_grid_index : layer_instance.intGridCsv { | |
local_position := grid_index_to_position(local_grid_index, level.size); | |
level.cells[local_grid_index].id = xx grid_value; | |
level.cells[local_grid_index].position = local_position; | |
// level.cells[grid_index].flags = int_grid_csv_to_flags(grid_value); | |
} | |
} | |
{ | |
player_spawn_found := false; | |
layer_instance := ldtk_level.layerInstances[Level_Layers.ENTITIES]; | |
entity_layer_index := -1; | |
for layer, i : root.defs.layers { | |
if layer.uid == layer_instance.layerDefUid { | |
entity_layer_index = i; | |
break; | |
} | |
} | |
assert(entity_layer_index > -1, tprint("Can't find layer with uid: %", layer_instance.layerDefUid)); | |
entity_layer := root.defs.layers[entity_layer_index]; | |
for entity_instance : layer_instance.entityInstances { | |
local_position := to_vector2s(entity_instance.px) / entity_layer.gridSize; | |
local_grid_index := grid_position_to_index(local_position, level.size.x); | |
cell := *level.cells[local_grid_index]; | |
entity: LDTK_Entity_Def; | |
for e : root.defs.entities { | |
if e.uid == entity_instance.defUid { | |
entity = e; | |
break; | |
} | |
} | |
assert(entity.uid != 0, "Couldn't find entity."); | |
if entity.identifier == { | |
case "Player_Spawn"; { | |
assert(!player_spawn_found, tprint("Can't have multiple player spawn per level: %, position: %.", ldtk_level.identifier, local_position)); | |
player_spawn_found = true; | |
for field_instance, field_index : entity_instance.fieldInstances { | |
if field_instance.__identifier == { | |
case "direction"; { | |
direction := Direction_Cardinal.NORTH; | |
if field_instance.__value.str == "EAST" direction = .EAST; | |
if field_instance.__value.str == "SOUTH" direction = .SOUTH; | |
if field_instance.__value.str == "WEST" direction = .WEST; | |
level.player_spawn_direction = direction; | |
level.player_spawn_position = local_position; | |
} | |
} | |
} | |
} | |
case "Battle"; { | |
cell.encounter.type = .CREATURE; | |
for field_instance, field_index : entity_instance.fieldInstances { | |
if field_instance.__identifier == { | |
case "creature_id"; { | |
creature_id: u32; | |
if field_instance.__value.str == "NONE" creature_id = 0; | |
if field_instance.__value.str == "DRAGON" creature_id = 1; | |
if field_instance.__value.str == "CENTIPEDE" creature_id = 2; | |
if field_instance.__value.str == "MOUSE" creature_id = 3; | |
if field_instance.__value.str == "FROG" creature_id = 4; | |
if field_instance.__value.str == "BUNNY" creature_id = 5; | |
cell.encounter.creature_id = creature_id; | |
} | |
} | |
} | |
} | |
case "Starter"; { | |
cell.encounter.type = .STARTER; | |
for field_instance, field_index : entity_instance.fieldInstances { | |
if field_instance.__identifier == { | |
case "creature_id"; { | |
creature_id: u32; | |
if field_instance.__value.str == "NONE" creature_id = 0; | |
if field_instance.__value.str == "DRAGON" creature_id = 1; | |
if field_instance.__value.str == "CENTIPEDE" creature_id = 2; | |
if field_instance.__value.str == "MOUSE" creature_id = 3; | |
if field_instance.__value.str == "FROG" creature_id = 4; | |
if field_instance.__value.str == "BUNNY" creature_id = 5; | |
cell.encounter.creature_id = creature_id; | |
} | |
case "target"; { | |
if field_instance.__value.object == null { | |
if cell.encounter.type == .STARTER { | |
assert(false, tprint("Missing target at level: %, position: %.", ldtk_level.identifier, local_position)); | |
} | |
continue; | |
} | |
#import "Hash_Table"; | |
#import "jaison"; | |
value, success := table_find(field_instance.__value.object, "entityIid"); | |
assert(success); | |
target_entity_iid := value.str; | |
target_cell_index := -1; | |
for other : layer_instance.entityInstances { | |
if other.iid == target_entity_iid { | |
local_position := to_vector2s(other.px) / entity_layer.gridSize; | |
local_grid_index := grid_position_to_index(local_position, level.size.x); | |
target_cell_index = local_grid_index; | |
break; | |
} | |
} | |
assert(target_cell_index > -1, tprint("Invalid target at level: %, position: %.", ldtk_level.identifier, local_position)); | |
cell.encounter.target_cell_index = target_cell_index; | |
} | |
} | |
} | |
} | |
case "Trap"; { | |
cell.encounter.type = .PICKUP; | |
cell.encounter.pickup_id = 1; | |
} | |
case "Fountain"; { | |
cell.encounter.type = .PICKUP; | |
cell.encounter.pickup_id = 2; | |
} | |
case "Door"; { | |
cell.encounter.type = .PICKUP; | |
cell.encounter.pickup_id = 3; | |
} | |
case "Key"; { | |
cell.encounter.type = .PICKUP; | |
cell.encounter.pickup_id = 4; | |
for field_instance, field_index : entity_instance.fieldInstances { | |
if field_instance.__identifier == { | |
case "target"; { | |
if field_instance.__value.object == null { | |
if cell.encounter.type == .STARTER { | |
assert(false, tprint("Missing target at level: %, position: %.", ldtk_level.identifier, local_position)); | |
} | |
continue; | |
} | |
#import "Hash_Table"; | |
#import "jaison"; | |
value, success := table_find(field_instance.__value.object, "entityIid"); | |
assert(success); | |
target_entity_iid := value.str; | |
target_cell_index := -1; | |
for other : layer_instance.entityInstances { | |
if other.iid == target_entity_iid { | |
local_position := to_vector2s(other.px) / entity_layer.gridSize; | |
local_grid_index := grid_position_to_index(local_position, level.size.x); | |
target_cell_index = local_grid_index; | |
break; | |
} | |
} | |
assert(target_cell_index > -1, tprint("Invalid target at level: %, position: %.", ldtk_level.identifier, local_position)); | |
cell.encounter.target_cell_index = target_cell_index; | |
} | |
} | |
} | |
} | |
case "Collar"; { | |
cell.encounter.type = .PICKUP; | |
cell.encounter.pickup_id = 5; | |
} | |
case "Stairs"; { | |
cell.encounter.type = .PICKUP; | |
cell.encounter.pickup_id = 8; | |
} | |
case; { | |
assert(false, tprint("Invalid entity type at level: %, position: %.", ldtk_level.identifier, local_position)); | |
} | |
} | |
} | |
assert(player_spawn_found, "Missing player spawn in level."); | |
} | |
} | |
Floating_Message :: struct { | |
text: Fixed_Size_String(32); | |
window_position: Vector2; | |
timer: Timer; | |
size: s32; | |
color: Color; @UI(Color); | |
} | |
ui_push_floating_message :: (text: string, window_position: Vector2, duration := 800, font_size: s32 = FONT_SIZE, color: Color = COLOR_WHITE, delay := 0, animate_y := -6, loc := #caller_location) -> *Floating_Message { | |
message: *Floating_Message; | |
// TODO: That's a quick and dirty estimation, we'll want to do the real calculation later down the line | |
text_size := Vector2.{ cast(float) text.count * 4.8, cast(float) font_size * 4 }; | |
message_data := Floating_Message.{ | |
text = string_to_fixed_string(text, 32), | |
window_position = window_position - text_size/2, | |
timer = timer_make(duration, cast(s64) (cast(float)delay / engine.time_scale)), | |
size = font_size, | |
color = color, | |
}; | |
// Quick and dirty way to reuse floating messages, might need to do a proper pool later down the line. | |
for game.floating_messages { | |
if it.timer.start == .{} { | |
message = *game.floating_messages[it_index]; | |
message.* = .{}; | |
break; | |
} | |
} | |
if message == null { | |
message = fixed_array_add(*game.floating_messages, .{}, loc = loc); | |
} | |
message.* = message_data; | |
message_animation_id, message_animation := animation_make("message", self_destroy = true); | |
window_position_curve := animation_make_curve(message_animation_id, *message.window_position.y, "message.y"); | |
animation_make_frame(window_position_curve, message.window_position.y, cast(u64) delay); | |
animation_make_frame(window_position_curve, message.window_position.y + animate_y, cast(u64) delay + cast(u64) message.timer.duration); | |
return message; | |
} | |
ui_is_hovering_anything :: () -> bool { | |
return ImGui.GetIO().WantCaptureMouse || game.ui.hovered; | |
} | |
damage_party_member :: (party_member_index: int, amount: u8, damage_type: Damage_Type) { | |
party_member := *game.party[party_member_index]; | |
party_creature := CREATURES[party_member.creature_id]; | |
party_member.health.current = cast(u8) clamp(cast(s32)party_member.health.current - amount, 0, party_member.health.max); | |
// TODO: animate health bar | |
message_position := game.ui.party_position + (.{ 0, cast(float) PARTY_MEMBER_SIZE.y } * cast(float) party_member_index) + Vector2.{ 16, 30 }; | |
// ui_push_floating_message(tprint("%", amount), message_position, duration = 1000, animate_y = 0); | |
battle_log(tprint("% received % damage.", to_temp_string(party_creature.name), amount)); | |
{ | |
animation_id := animation_make("attack", self_destroy = true); | |
effect := fixed_array_add(*game.effects, make_effect()); | |
effect.size = .{ 81, 80 }; | |
effect.position = game.ui.party_position + .{ 0, cast(float)PARTY_MEMBER_SIZE.y } * cast(float)party_member_index + to_vector2(ATTACK_OFFSETS[damage_type]); | |
effect.texture_size = .{ 81, 80 }; | |
if #complete damage_type == { | |
case .TAIL; { effect.rect_index = xx IMG_ATTACK1; } | |
case .CLAW; { effect.rect_index = xx IMG_ATTACK2; } | |
case .FANG; { effect.rect_index = xx IMG_ATTACK3; } | |
} | |
effect.layer_id = 2; | |
effect.z_index = 999; | |
effect_curve := animation_make_curve(animation_id, effect, "flipbook"); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*0, 0 }), 100*0); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*1, 0 }), 100*1); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*2, 0 }), 100*2); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*3, 0 }), 100*3); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*4, 0 }), 100*4); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*5, 0 }), 100*5); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*6, 0 }), 100*6); | |
} | |
} | |
damage_foe_part :: (party_creature_id: Creature_Id, foe_creature_id: Creature_Id, part_id: int, amount: u8, damage_type: Damage_Type, position: Vector2) { | |
party_creature := CREATURES[party_creature_id]; | |
foe_creature := CREATURES[foe_creature_id]; | |
health := *game.battle.foe_parts_health[part_id]; | |
multiplier : float = 1.0; | |
if party_creature.attack_type == party_creature.parts[part_id].weakness { multiplier = 1.5; } | |
damage := cast(u8) (cast(float)amount * multiplier); | |
health.current = cast(u8) max(cast(s32)health.current - damage, 0); | |
battle_log(tprint("%1 hit %2 for %3%4.", to_temp_string(party_creature.name), to_temp_string(foe_creature.name), damage, ifx(multiplier == 1.0) then "" else " (weakness)"), ifx(multiplier == 1.0) then .NORMAL else .CRITICAL); | |
{ | |
animation_id := animation_make("foe_attack", self_destroy = true); | |
effect := fixed_array_add(*game.effects, make_effect()); | |
effect.size = .{ 81*3, 80*3 }; | |
effect.texture_size = .{ 81, 80 }; | |
effect.position = position - to_vector2(effect.size/2); | |
if #complete damage_type == { | |
case .TAIL; { effect.rect_index = xx IMG_ATTACK1; } | |
case .CLAW; { effect.rect_index = xx IMG_ATTACK2; } | |
case .FANG; { effect.rect_index = xx IMG_ATTACK3; } | |
} | |
effect.layer_id = 2; | |
effect.z_index = 999; | |
effect_curve := animation_make_curve(animation_id, effect, "flipbook"); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*0, 0 }), 100*0); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*1, 0 }), 100*1); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*2, 0 }), 100*2); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*3, 0 }), 100*3); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*4, 0 }), 100*4); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*5, 0 }), 100*5); | |
animation_make_frame(effect_curve, make_sprite_animation_event(.{ 81*6, 0 }), 100*6); | |
} | |
} | |
heal_party_member :: (party_member: *Party_Member, amount: u8) { | |
party_member.health.current = cast(u8) clamp(party_member.health.current + amount, 0, party_member.health.max); | |
} | |
level_name_to_index :: (name: string) -> int { | |
for game.levels { | |
if to_temp_string(it) == name { | |
return it_index; | |
} | |
} | |
assert(false, "Couldn't find level!"); | |
return -1; | |
} | |
is_game_over :: () -> bool { | |
if game.party.count == 0 { return false; } | |
party_member_dead: int; | |
for party_member : game.party { | |
if party_member.health.current <= 0 { | |
party_member_dead += 1; | |
} | |
} | |
return party_member_dead == game.party.count; | |
} | |
battle_log :: (text: string, type := Log_Type.NORMAL) { | |
if game.logs.count == game.logs.data.count-1 { | |
for 0 .. game.logs.count-2 { | |
game.logs[it] = game.logs[it+1]; | |
} | |
game.logs.count -= 1; | |
} | |
fixed_array_add(*game.logs, Log.{ string_to_fixed_string(text, 64), type }); | |
} | |
make_sprite_animation_event :: (texture_position: Vector2s) -> Frame_Event { | |
animate_sprite :: (target: *Effect, step_progress: float, user_data: *Vector2s) { | |
target.sprite.texture_position = user_data; | |
} | |
push_arena(game.battle_arena); | |
user_data := New(Vector2s); | |
user_data.* = texture_position; | |
return Frame_Event.{ xx animate_sprite, user_data }; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment