From c3763495f63f300881e7d173427ba7ba0b5b8136 Mon Sep 17 00:00:00 2001 From: AleWin32 <7621682+AleWin32@users.noreply.github.com> Date: Wed, 22 Jul 2026 01:46:54 +0200 Subject: [PATCH 01/28] =?UTF-8?q?Complete=20UTF-8:=20Allow=20typing=20non-?= =?UTF-8?q?ASCII=20characters=20(=C3=B1,=20=C3=A7,=20=C3=A9,=20=C2=BF?= =?UTF-8?q?=C2=A1=20etc)=20in=20text=20input=20fields=20(#5039)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/launcher-auto-file-removal.txt | 1 + src/bflib_string.c | 9 +++++++-- src/front_highscore.c | 30 +++++++++++++++++++++-------- src/front_input.c | 3 +++ src/frontend.cpp | 2 +- src/frontmenu_net.c | 3 +++ src/kjm_input.c | 21 ++++++++++++++------ 7 files changed, 52 insertions(+), 17 deletions(-) diff --git a/docs/launcher-auto-file-removal.txt b/docs/launcher-auto-file-removal.txt index 325df5ef6a..d1c2f56017 100644 --- a/docs/launcher-auto-file-removal.txt +++ b/docs/launcher-auto-file-removal.txt @@ -1556,6 +1556,7 @@ fxdata/lanternpost.zip fxdata/mushrooms.zip fxdata/torches.zip fxdata/trapcolors.zip +fxdata/trapdoors.zip fxdata/waterplants.zip fxdata/whiteflag.zip fxdata/windbanner.zip diff --git a/src/bflib_string.c b/src/bflib_string.c index a4fc1d6b86..908288fed9 100644 --- a/src/bflib_string.c +++ b/src/bflib_string.c @@ -42,7 +42,7 @@ TbCharCount LbLocTextStringLength(const TbLocChar *s) TbSize i = 0; while (s[i] != 0) { - //if ((s[i] & 0xc0) != 0x80) // enable when/if UTF-8 is supported + if ((s[i] & 0xc0) != 0x80) // don't count UTF-8 continuation bytes { j++; } @@ -76,12 +76,17 @@ TbSize LbLocTextPosToLength(const TbLocChar *s, TbCharCount pos) TbSize i = 0; while ((s[i] != 0) && (j < pos)) { - //if ((s[i] & 0xc0) != 0x80) // enable when/if UTF-8 is supported + if ((s[i] & 0xc0) != 0x80) // don't count UTF-8 continuation bytes { j++; } i++; } + // Advance past the continuation bytes of the last counted character + while ((s[i] & 0xc0) == 0x80) + { + i++; + } return i; } diff --git a/src/front_highscore.c b/src/front_highscore.c index ff90720cac..3864bb38d8 100644 --- a/src/front_highscore.c +++ b/src/front_highscore.c @@ -201,12 +201,15 @@ TbBool frontend_high_score_table_input(void) // Delete previous character if (high_score_entry_index > 0) { - i = high_score_entry_index-1; - while (high_score_entry[i] != '\0') { - high_score_entry[i] = high_score_entry[i+1]; - i++; + // Step back over UTF-8 continuation bytes to the start of the previous character + unsigned long start = high_score_entry_index - 1; + while ((start > 0) && ((high_score_entry[start] & 0xc0) == 0x80)) { + start--; } - high_score_entry_index--; + unsigned long clen = high_score_entry_index - start; + unsigned long slen = strlen(high_score_entry); + memmove(&high_score_entry[start], &high_score_entry[start+clen], slen - (start+clen) + 1); + high_score_entry_index = start; } clear_key_pressed(KC_BACK); return true; @@ -215,9 +218,14 @@ TbBool frontend_high_score_table_input(void) { // Delete next character i = high_score_entry_index; - while (high_score_entry[i] != '\0') { - high_score_entry[i] = high_score_entry[i+1]; - i++; + if (high_score_entry[i] != '\0') + { + unsigned long clen = 1; + while ((high_score_entry[i+clen] & 0xc0) == 0x80) { + clen++; + } + unsigned long slen = strlen(high_score_entry); + memmove(&high_score_entry[i], &high_score_entry[i+clen], slen - (i+clen) + 1); } clear_key_pressed(KC_DELETE); return true; @@ -227,6 +235,9 @@ TbBool frontend_high_score_table_input(void) // Move cursor left if (high_score_entry_index > 0) { high_score_entry_index--; + while ((high_score_entry_index > 0) && ((high_score_entry[high_score_entry_index] & 0xc0) == 0x80)) { + high_score_entry_index--; + } } clear_key_pressed(KC_LEFT); return true; @@ -237,6 +248,9 @@ TbBool frontend_high_score_table_input(void) i = high_score_entry_index; if (high_score_entry[i] != '\0') { high_score_entry_index++; + while ((high_score_entry[high_score_entry_index] & 0xc0) == 0x80) { + high_score_entry_index++; + } } clear_key_pressed(KC_RIGHT); return true; diff --git a/src/front_input.c b/src/front_input.c index ff118e7d00..0c99895535 100644 --- a/src/front_input.c +++ b/src/front_input.c @@ -451,6 +451,9 @@ static short get_players_message_inputs(void) clear_key_pressed(KC_UP); } else if (is_key_pressed(KC_BACK,KMod_DONTCARE)){ int chpos = strlen(player->mp_message_text); + // Skip UTF-8 continuation bytes so the whole last character is removed + while ((chpos > 0) && ((player->mp_message_text[chpos-1] & 0xc0) == 0x80)) + chpos--; if (chpos > 0) player->mp_message_text[chpos-1] = '\0'; clear_key_pressed(KC_BACK); diff --git a/src/frontend.cpp b/src/frontend.cpp index 4ec2b45011..589136d05b 100644 --- a/src/frontend.cpp +++ b/src/frontend.cpp @@ -619,7 +619,7 @@ TbBool get_button_area_input(struct GuiButton *gbtn, int modifiers) if (insert_text[0] != '\0') { if (LbLocTextStringInsert(str, insert_text, input_field_pos, gbtn->maxval) != NULL) { - input_field_pos += strlen(insert_text); + input_field_pos += LbLocTextStringLength(insert_text); } } } diff --git a/src/frontmenu_net.c b/src/frontmenu_net.c index e16946e7c2..1072bb3833 100644 --- a/src/frontmenu_net.c +++ b/src/frontmenu_net.c @@ -125,6 +125,9 @@ TbBool frontnet_start_input(void) player->mp_message_text[0] = '\0'; } else if (is_key_pressed(KC_BACK,KMod_DONTCARE)){ int chpos = strlen(player->mp_message_text); + // Skip UTF-8 continuation bytes so the whole last character is removed + while ((chpos > 0) && ((player->mp_message_text[chpos-1] & 0xc0) == 0x80)) + chpos--; if (chpos > 0) player->mp_message_text[chpos-1] = '\0'; clear_key_pressed(KC_BACK); diff --git a/src/kjm_input.c b/src/kjm_input.c index cfe60270af..5e32da62fb 100644 --- a/src/kjm_input.c +++ b/src/kjm_input.c @@ -30,6 +30,7 @@ #include "bflib_planar.h" #include "bflib_math.h" #include "bflib_sprfnt.h" +#include "bflib_text.h" #include "bflib_inputctrl.h" #include "bflib_datetm.h" @@ -751,19 +752,27 @@ TbBool add_input_text_to_message(char *message, int max_message_length, struct T return false; int chpos = strlen(message); - for (int ti = 0; ti < text_len && chpos < max_message_length - 1; ++ti) { - unsigned char c = (unsigned char)text_input[ti]; - // Limit it to ASCII characters, to ignore codepage differences and multibyte stuff. - if (c >= 0x20 && c < 0x7f) { - message[chpos++] = (char)c; + int ti = 0; + while (ti < text_len) + { + size_t seq_len = 0; + uint32_t codepoint = read_utf_8_codepoint(&text_input[ti], &seq_len); + // Accept any printable character; rendering falls back on unifont + // for glyphs missing from the sprite fonts. + TbBool acceptable = (codepoint >= 0x20 && codepoint != 0x7f); + if (acceptable && (chpos + (int)seq_len < max_message_length)) { + memcpy(&message[chpos], &text_input[ti], seq_len); + chpos += seq_len; message[chpos] = '\0'; // Enforce max_width even when multiple characters arrive in one frame. if (pixel_size * LbTextStringWidth(message) >= max_width) { - message[--chpos] = '\0'; + chpos -= seq_len; + message[chpos] = '\0'; break; } } + ti += seq_len; } return true; } From 3e6e06b2741754e4731b473f7381390bdd53ac9d Mon Sep 17 00:00:00 2001 From: rainlizard <15337628+rainlizard@users.noreply.github.com> Date: Thu, 23 Jul 2026 10:24:49 +1000 Subject: [PATCH 02/28] Allow loading string subtypes in .tngfx files (#5052) --- src/thing_factory.c | 12 ++++++++---- src/value_util.c | 29 ++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/thing_factory.c b/src/thing_factory.c index 750037a284..01bb45e731 100644 --- a/src/thing_factory.c +++ b/src/thing_factory.c @@ -40,6 +40,7 @@ #include "dungeon_data.h" #include "gui_topmsg.h" #include "config_magic.h" +#include "game_merge.h" #include "game_legacy.h" #include "keeperfx.hpp" @@ -217,7 +218,8 @@ TbBool thing_create_thing_adv(VALUE *init_data) { int owner = value_int32(value_dict_get(init_data, "Ownership")); int oclass = value_parse_class(value_dict_get(init_data, "ThingType")); - ThingModel model = value_parse_model(oclass, value_dict_get(init_data, "Subtype")); + VALUE *subtype = value_dict_get(init_data, "Subtype"); + ThingModel model = value_parse_model(oclass, subtype); struct Coord3d mappos; mappos.x.val = value_read_stl_coord(value_dict_get(init_data, "SubtileX")); mappos.y.val = value_read_stl_coord(value_dict_get(init_data, "SubtileY")); @@ -227,9 +229,11 @@ TbBool thing_create_thing_adv(VALUE *init_data) ERRORLOG("Thing ThingType is not set"); return false; } - if (model == -1) - { - ERRORLOG("Thing Subtype is not set"); + if (model == -1) { + if (value_type(subtype) == VALUE_STRING) + ERRORMSG("map%05u.tngfx: Tried to load unrecognized Thing subtype \"%s\"", (unsigned int)get_selected_level_number(), value_string(subtype)); + else + ERRORLOG("Thing Subtype is not set"); return false; } if (owner == -1) diff --git a/src/value_util.c b/src/value_util.c index 59a08dc356..1d7e8ff6d4 100644 --- a/src/value_util.c +++ b/src/value_util.c @@ -5,7 +5,11 @@ #include "pre_inc.h" #include "value_util.h" #include "config.h" +#include "config_creature.h" +#include "config_effects.h" +#include "config_magic.h" #include "config_objects.h" +#include "config_trapdoor.h" #include "bflib_basics.h" #include "bflib_fileio.h" #include "bflib_dernc.h" @@ -90,7 +94,30 @@ int value_parse_model(int oclass, VALUE *value) { if (value_type(value) == VALUE_INT32) return value_int32(value); - // TODO: model names for different classes + if (value_type(value) != VALUE_STRING) + return -1; + const char *name = value_string(value); + switch (oclass) + { + case TCls_Object: + case TCls_AmbientSnd: + return get_id(object_desc, name); + case TCls_Shot: + return get_id(shot_desc, name); + case TCls_EffectElem: + return get_id(effectelem_desc, name); + case TCls_DeadCreature: + case TCls_Creature: + return get_id(creature_desc, name); + case TCls_Effect: + return get_id(effect_desc, name); + case TCls_EffectGen: + return get_id(effectgen_desc, name); + case TCls_Trap: + return get_id(trap_desc, name); + case TCls_Door: + return get_id(door_desc, name); + } return -1; } From 971bc46d065f74c1e28200154024afbc64868817 Mon Sep 17 00:00:00 2001 From: Pieter Vandecandelaere Date: Fri, 24 Jul 2026 20:04:06 +0200 Subject: [PATCH 03/28] Restore value_stringId usage (#5057) --- src/config_compp.c | 2 +- src/config_magic.c | 4 ++-- src/config_objects.c | 2 +- src/config_terrain.c | 6 +++--- src/config_trapdoor.c | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/config_compp.c b/src/config_compp.c index b11d6c9156..191f54b90f 100644 --- a/src/config_compp.c +++ b/src/config_compp.c @@ -189,7 +189,7 @@ const struct NamedFieldSet compp_event_named_fields_set = { static const struct NamedField compp_computer_named_fields[] = { //name //pos //field //default //min //max //NamedCommand {"NAME", -1, field_t(struct ComputerType, name), 0, INT32_MIN,UINT32_MAX, NULL, value_name, assign_null}, - {"TOOLTIPTEXTID", 0, field_t(struct ComputerType, tooltip_stridx),GUIStr_Empty, INT32_MIN,UINT32_MAX, NULL, value_default, assign_default}, + {"TOOLTIPTEXTID", 0, field_t(struct ComputerType, tooltip_stridx),GUIStr_Empty, INT32_MIN,UINT32_MAX, NULL, value_stringId, assign_default}, {"ASSISTANTICON", 0, field_t(struct ComputerType, sprite_idx), 0, INT32_MIN,UINT32_MAX, NULL, value_icon, assign_icon}, {"VALUES", 0, field_t(struct ComputerType, dig_stack_size), 0, INT32_MIN,UINT32_MAX, NULL, value_default, assign_default}, {"VALUES", 1, field_t(struct ComputerType, processes_time), 0, INT32_MIN,UINT32_MAX, NULL, value_default, assign_default}, diff --git a/src/config_magic.c b/src/config_magic.c index 8fb6d4dc2e..06a9da6859 100644 --- a/src/config_magic.c +++ b/src/config_magic.c @@ -503,8 +503,8 @@ static const struct NamedField magic_powers_named_fields[] = { {"DURATION", 0, field_t(struct PowerConfigStats, duration), 0, INT32_MIN,UINT32_MAX, NULL, value_default, assign_default}, {"CASTABILITY", -1, field_t(struct PowerConfigStats, can_cast_flags), 0, 0,UINT64_MAX, (struct NamedCommand*)powermodel_castability_commands, value_longflagsfield, assign_default}, {"ARTIFACT", 0, field_t(struct PowerConfigStats, artifact_model), 0, INT32_MIN,UINT32_MAX, object_desc, value_default, assign_artifact}, - {"NAMETEXTID", 0, field_t(struct PowerConfigStats, name_stridx), 0, INT32_MIN,UINT32_MAX, NULL, value_default, assign_default}, - {"TOOLTIPTEXTID", 0, field_t(struct PowerConfigStats, tooltip_stridx), 0, INT32_MIN,UINT32_MAX, NULL, value_default, assign_default}, + {"NAMETEXTID", 0, field_t(struct PowerConfigStats, name_stridx), 0, INT32_MIN,UINT32_MAX, NULL, value_stringId, assign_default}, + {"TOOLTIPTEXTID", 0, field_t(struct PowerConfigStats, tooltip_stridx), 0, INT32_MIN,UINT32_MAX, NULL, value_stringId, assign_default}, {"SYMBOLSPRITES", 0, field_t(struct PowerConfigStats, bigsym_sprite_idx), 0, INT32_MIN,UINT32_MAX, NULL, value_icon, assign_icon}, {"SYMBOLSPRITES", 1, field_t(struct PowerConfigStats, medsym_sprite_idx), 0, INT32_MIN,UINT32_MAX, NULL, value_icon, assign_icon}, {"POINTERSPRITES", 0, field_t(struct PowerConfigStats, pointer_sprite_idx), 0, INT32_MIN,UINT32_MAX, NULL, value_icon, assign_icon}, diff --git a/src/config_objects.c b/src/config_objects.c index d1bdce1d36..8b6ce69163 100644 --- a/src/config_objects.c +++ b/src/config_objects.c @@ -105,7 +105,7 @@ static const struct NamedField objects_named_fields[] = { {"HANDICON", 0, field_t(struct ObjectConfigStats, hand_icon), 0, INT32_MIN,UINT32_MAX, NULL, value_icon, assign_icon}, {"PICKUPOFFSET", 0, field_t(struct ObjectConfigStats, object_picked_up_offset.delta_x), 0,SHRT_MIN,SHRT_MAX, NULL, value_default, assign_default}, {"PICKUPOFFSET", 1, field_t(struct ObjectConfigStats, object_picked_up_offset.delta_y), 0,SHRT_MIN,SHRT_MAX, NULL, value_default, assign_default}, - {"TOOLTIPTEXTID", 0, field_t(struct ObjectConfigStats, tooltip_stridx), GUIStr_Empty, SHRT_MIN, SHRT_MAX, NULL, value_default, assign_default}, + {"TOOLTIPTEXTID", 0, field_t(struct ObjectConfigStats, tooltip_stridx), GUIStr_Empty, SHRT_MIN, SHRT_MAX, NULL, value_stringId, assign_default}, {"TOOLTIPTEXTID", 1, field_t(struct ObjectConfigStats, tooltip_optional), 0, 0, 1, NULL, value_default, assign_default}, {"AMBIENCESOUND", 0, field_t(struct ObjectConfigStats, fp_smpl_idx), 0, 0,UINT32_MAX, NULL, value_sound_id, assign_default}, {"UPDATEFUNCTION", 0, field_t(struct ObjectConfigStats, updatefn_idx), 0, INT32_MIN,UINT32_MAX, object_update_functions_desc,value_function, assign_default}, diff --git a/src/config_terrain.c b/src/config_terrain.c index 3139eb0b68..2e7e93a181 100644 --- a/src/config_terrain.c +++ b/src/config_terrain.c @@ -109,7 +109,7 @@ const struct NamedCommand terrain_room_total_capacity_func_type[] = { static const struct NamedField terrain_slab_named_fields[] = { //name //field //default //min //max //NamedCommand {"NAME", 0, field_t(struct SlabConfigStats, code_name), 0, INT32_MIN,UINT32_MAX, slab_desc, value_name, assign_null}, - {"TOOLTIPTEXTID", 0, field_t(struct SlabConfigStats, tooltip_stridx), GUIStr_Empty, INT32_MIN,UINT32_MAX, NULL, value_default, assign_default}, + {"TOOLTIPTEXTID", 0, field_t(struct SlabConfigStats, tooltip_stridx), GUIStr_Empty, INT32_MIN,UINT32_MAX, NULL, value_stringId, assign_default}, {"BLOCKFLAGSHEIGHT", 0, field_t(struct SlabConfigStats, block_flags_height), 0, INT32_MIN,UINT32_MAX, NULL, value_default, assign_default}, {"BLOCKHEALTHINDEX", 0, field_t(struct SlabConfigStats, block_health_index), 0, INT32_MIN,UINT32_MAX, NULL, value_default, assign_default}, {"BLOCKFLAGS", -1, field_t(struct SlabConfigStats, block_flags), 0, INT32_MIN,UINT32_MAX, terrain_flags, value_flagsfield, assign_default}, @@ -153,8 +153,8 @@ static const struct NamedField terrain_room_named_fields[] = { {"MESSAGES", 0, field_t(struct RoomConfigStats, msg_needed), 0, INT32_MIN,UINT32_MAX, NULL, value_speech_ref, assign_speech_ref}, {"MESSAGES", 1, field_t(struct RoomConfigStats, msg_too_small), 0, INT32_MIN,UINT32_MAX, NULL, value_speech_ref, assign_speech_ref}, {"MESSAGES", 2, field_t(struct RoomConfigStats, msg_no_route), 0, INT32_MIN,UINT32_MAX, NULL, value_speech_ref, assign_speech_ref}, - {"NAMETEXTID", 0, field_t(struct RoomConfigStats, name_stridx), GUIStr_Empty, INT32_MIN,UINT32_MAX, NULL, value_default, assign_default}, - {"TOOLTIPTEXTID", 0, field_t(struct RoomConfigStats, tooltip_stridx), GUIStr_Empty, INT32_MIN,UINT32_MAX, NULL, value_default, assign_update_room_tab}, + {"NAMETEXTID", 0, field_t(struct RoomConfigStats, name_stridx), GUIStr_Empty, INT32_MIN,UINT32_MAX, NULL, value_stringId, assign_default}, + {"TOOLTIPTEXTID", 0, field_t(struct RoomConfigStats, tooltip_stridx), GUIStr_Empty, INT32_MIN,UINT32_MAX, NULL, value_stringId, assign_update_room_tab}, {"SYMBOLSPRITES", 0, field_t(struct RoomConfigStats, bigsym_sprite_idx), 0, INT32_MIN,UINT32_MAX, NULL, value_icon, assign_icon}, {"SYMBOLSPRITES", 1, field_t(struct RoomConfigStats, medsym_sprite_idx), 0, INT32_MIN,UINT32_MAX, NULL, value_icon, assign_icon_update_room_tab}, {"POINTERSPRITES", 0, field_t(struct RoomConfigStats, pointer_sprite_idx), 0, INT32_MIN,UINT32_MAX, NULL, value_icon, assign_icon_update_room_tab}, diff --git a/src/config_trapdoor.c b/src/config_trapdoor.c index 3fdd147656..905d533f06 100644 --- a/src/config_trapdoor.c +++ b/src/config_trapdoor.c @@ -257,8 +257,8 @@ static void assign_refresh_trap_anim_anim_id(const struct NamedField* named_fiel const struct NamedField trapdoor_door_named_fields[] = { //name //pos //field //default //min //max //NamedCommand {"NAME", 0, field_t(struct DoorConfigStats, code_name), 0, INT32_MIN, UINT32_MAX, door_desc, value_name, assign_null}, - {"NAMETEXTID", 0, field_t(struct DoorConfigStats, name_stridx), GUIStr_Empty, INT32_MIN, UINT32_MAX, NULL, value_default, assign_default}, - {"TOOLTIPTEXTID", 0, field_t(struct DoorConfigStats, tooltip_stridx),GUIStr_Empty, INT32_MIN, UINT32_MAX, NULL, value_default, assign_tooltip_idx_door}, + {"NAMETEXTID", 0, field_t(struct DoorConfigStats, name_stridx), GUIStr_Empty, INT32_MIN, UINT32_MAX, NULL, value_stringId, assign_default}, + {"TOOLTIPTEXTID", 0, field_t(struct DoorConfigStats, tooltip_stridx),GUIStr_Empty, INT32_MIN, UINT32_MAX, NULL, value_stringId, assign_tooltip_idx_door}, {"SYMBOLSPRITES", 0, field_t(struct DoorConfigStats, bigsym_sprite_idx), 0, INT32_MIN, UINT32_MAX, NULL, value_icon, assign_icon}, {"SYMBOLSPRITES", 1, field_t(struct DoorConfigStats, medsym_sprite_idx), 0, INT32_MIN, UINT32_MAX, NULL, value_icon, assign_icon_update_trap_tab}, {"POINTERSPRITES", 0, field_t(struct DoorConfigStats, pointer_sprite_idx), 0, INT32_MIN, UINT32_MAX, NULL, value_icon, assign_icon_update_trap_tab}, @@ -299,8 +299,8 @@ const struct NamedField trapdoor_trap_named_fields[] = { {"SHOTS", 0, field_t(struct TrapConfigStats, shots), 0, INT32_MIN, UINT32_MAX, NULL, value_default, assign_default}, {"TIMEBETWEENSHOTS", 0, field_t(struct TrapConfigStats, shots_delay), 0, INT32_MIN, UINT32_MAX, NULL, value_default, assign_default}, {"SELLINGVALUE", 0, field_t(struct TrapConfigStats, selling_value), 0, INT32_MIN, UINT32_MAX, NULL, value_default, assign_default}, - {"NAMETEXTID", 0, field_t(struct TrapConfigStats, name_stridx), 0, INT32_MIN, UINT32_MAX, NULL, value_default, assign_default}, - {"TOOLTIPTEXTID", 0, field_t(struct TrapConfigStats, tooltip_stridx), 0, INT32_MIN, UINT32_MAX, NULL, value_default, assign_tooltip_idx_trap}, + {"NAMETEXTID", 0, field_t(struct TrapConfigStats, name_stridx), 0, INT32_MIN, UINT32_MAX, NULL, value_stringId, assign_default}, + {"TOOLTIPTEXTID", 0, field_t(struct TrapConfigStats, tooltip_stridx), 0, INT32_MIN, UINT32_MAX, NULL, value_stringId, assign_tooltip_idx_trap}, {"CRATE", 0, NULL,0, 0, INT32_MIN, UINT32_MAX, object_desc, value_default, assign_crate_trap}, {"SYMBOLSPRITES", 0, field_t(struct TrapConfigStats, bigsym_sprite_idx), 0, INT32_MIN, UINT32_MAX, NULL, value_icon, assign_icon}, {"SYMBOLSPRITES", 1, field_t(struct TrapConfigStats, medsym_sprite_idx), 0, INT32_MIN, UINT32_MAX, NULL, value_icon, assign_icon_update_trap_tab}, From f4009bdf9598c65fc0fe552b53338d08d5e2b827 Mon Sep 17 00:00:00 2001 From: Peter Lockett <1760289+cerwym@users.noreply.github.com> Date: Fri, 24 Jul 2026 22:36:01 +0100 Subject: [PATCH 04/28] Landview overlay no longer has missing edge pixel on high res (#5058) --- src/bflib_vidraw.c | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/src/bflib_vidraw.c b/src/bflib_vidraw.c index b97e179896..803ed2fa8a 100644 --- a/src/bflib_vidraw.c +++ b/src/bflib_vidraw.c @@ -1344,6 +1344,7 @@ void LbSpriteSetScalingWidthClippedArray(int32_t * xsteps_arr, long x, long swid long factor = (dwidth<<16)/swidth; long tmp = (factor >> 1) + (x << 16); pxpos = tmp >> 16; + pxpos = min(pxpos, max(0, x)); long w = swidth; do { tmp += factor; @@ -1353,21 +1354,12 @@ void LbSpriteSetScalingWidthClippedArray(int32_t * xsteps_arr, long x, long swid pxend = tmp>>16; // Remember unclipped difference long wdiff = pxend - pxstart; - // Now clip to graphics line bounds - if (pxstart < 0) { - pxstart = 0; - pxend = pxstart; - } else - if (pxstart >= gwidth) { - pxstart = gwidth-1; - pxend = pxstart; - } else - if (pxend < 0) { - pxend = 0; - } else - if (pxend > gwidth) { - pxend = gwidth; - } + // Clip both endpoints independently to [0, gwidth] + if (pxstart < 0) pxstart = 0; + else if (pxstart > gwidth) pxstart = gwidth; + if (pxend < 0) pxend = 0; + else if (pxend > gwidth) pxend = gwidth; + if (pxend < pxstart) pxend = pxstart; // Set clipped difference to be drawn pwidth[0] = pxstart; pwidth[1] = pxend - pxstart; @@ -1433,6 +1425,7 @@ void LbSpriteSetScalingHeightClippedArray(int32_t * ysteps_arr, long y, long she long factor = (dheight<<16)/sheight; long tmp = (factor >> 1) + (y << 16); lnpos = tmp >> 16; + lnpos = min(lnpos, max(0, y)); if (lnpos < 0) lnpos = 0; if (lnpos >= gheight) @@ -1446,21 +1439,12 @@ void LbSpriteSetScalingHeightClippedArray(int32_t * ysteps_arr, long y, long she lnend = tmp>>16; // Remember unclipped difference long hdiff = lnend - lnstart; - // Now clip to graphics line bounds - if (lnstart < 0) { - lnstart = 0; - lnend = lnstart; - } else - if (lnstart >= gheight) { - lnstart = gheight-1; - lnend = lnstart; - } else - if (lnend < 0) { - lnend = 0; - } else - if (lnend > gheight) { - lnend = gheight; - } + // Clip both endpoints independently to [0, gheight] + if (lnstart < 0) lnstart = 0; + else if (lnstart > gheight) lnstart = gheight; + if (lnend < 0) lnend = 0; + else if (lnend > gheight) lnend = gheight; + if (lnend < lnstart) lnend = lnstart; // Set clipped difference to be drawn pheight[0] = lnstart; pheight[1] = lnend - lnstart; From 02b48bf23018e7f8ebffad582513167c81b385e5 Mon Sep 17 00:00:00 2001 From: rainlizard <15337628+rainlizard@users.noreply.github.com> Date: Sun, 26 Jul 2026 09:28:57 +1000 Subject: [PATCH 05/28] Fix imp desync even more (#5059) --- src/spdigger_stack.c | 135 +++++++++++++++++++++++-------------------- src/spdigger_stack.h | 2 - 2 files changed, 72 insertions(+), 65 deletions(-) diff --git a/src/spdigger_stack.c b/src/spdigger_stack.c index 248138b059..40112aa306 100644 --- a/src/spdigger_stack.c +++ b/src/spdigger_stack.c @@ -1204,11 +1204,13 @@ long get_nearest_undug_area_position_for_digger(struct Thing *thing, MapSubtlCoo MapSubtlCoord best_dist; MapSubtlCoord best_stl_x; MapSubtlCoord best_stl_y; + SubtlCodedCoords best_task_stl; int best_tsk_id; best_dist = UNDUG_MAX_DIST; best_tsk_id = -1; best_stl_x = -1; best_stl_y = -1; + best_task_stl = 0; for (i=0; i < tsk_max; i++) { mtask = &dungeon->task_list[i]; @@ -1220,7 +1222,7 @@ long get_nearest_undug_area_position_for_digger(struct Thing *thing, MapSubtlCoo MapSubtlCoord tsk_dist; tsk_stl_num = mtask->coords; tsk_dist = chessboard_distance(digstl_x, digstl_y, stl_num_decode_x(tsk_stl_num), stl_num_decode_y(tsk_stl_num)); - if (tsk_dist < best_dist) + if ((tsk_dist < best_dist) || ((tsk_dist == best_dist) && (best_tsk_id >= 0) && (tsk_stl_num < best_task_stl))) { MapSubtlCoord tsk_stl_x; MapSubtlCoord tsk_stl_y; @@ -1230,6 +1232,7 @@ long get_nearest_undug_area_position_for_digger(struct Thing *thing, MapSubtlCoo best_tsk_id = i; best_stl_x = tsk_stl_x; best_stl_y = tsk_stl_y; + best_task_stl = tsk_stl_num; } } } @@ -1274,72 +1277,75 @@ long check_out_undug_area(struct Thing *thing) return 1; } -int add_undug_to_imp_stack(struct Dungeon *dungeon, int max_tasks) +enum DigTaskStackFilter { + DigTaskStack_Undug, + DigTaskStack_Gems, +}; + +static int add_dig_tasks_to_imp_stack(struct Thing *anchor_imp, struct Dungeon *dungeon, int max_tasks, enum DigTaskStackFilter filter) { - struct MapTask* mtask; - long stl_x; - long stl_y; - long i; SYNCDBG(18,"Starting"); - int remain_num; - remain_num = max_tasks; - i = -1; - while ((remain_num > 0) && (dungeon->digger_stack_length < DIGGER_TASK_MAX_COUNT)) - { - i = find_next_dig_in_dungeon_task_list(dungeon, i); - if (i < 0) - break; - mtask = get_dungeon_task_list_entry(dungeon, i); - stl_x = stl_num_decode_x(mtask->coords); - stl_y = stl_num_decode_y(mtask->coords); - struct SlabMap *slb; - slb = get_slabmap_for_subtile(stl_x, stl_y); - if (!slab_kind_is_indestructible(slb->kind)) // Add only blocks which can be destroyed by digging - { - if ( block_has_diggable_side(subtile_slab(stl_x), subtile_slab(stl_y)) ) - { - add_to_dungeon_imp_stack_using_pos(mtask->coords, DigTsk_DigOrMine, dungeon); - remain_num--; + int32_t task_limit; + int32_t tasks_added; + int32_t last_distance; + SubtlCodedCoords last_coords; + task_limit = dungeon->highest_task_number; + if (task_limit > MAPTASKS_COUNT) { + task_limit = MAPTASKS_COUNT; + } + tasks_added = 0; + last_distance = -1; + last_coords = 0; + while ((tasks_added < max_tasks) && (dungeon->digger_stack_length < DIGGER_TASK_MAX_COUNT)) { + struct MapTask *best_task; + int32_t best_distance; + best_task = NULL; + best_distance = 0; + for (int32_t task_idx = 0; task_idx < task_limit; task_idx++) { + struct MapTask *task; + MapSubtlCoord task_stl_x; + MapSubtlCoord task_stl_y; + struct SlabMap *slab; + int32_t distance; + task = &dungeon->task_list[task_idx]; + if (task->kind == SDDigTask_None) { + continue; + } + task_stl_x = stl_num_decode_x(task->coords); + task_stl_y = stl_num_decode_y(task->coords); + if ((filter == DigTaskStack_Gems) && !subtile_revealed(task_stl_x, task_stl_y, dungeon->owner)) { + continue; + } + slab = get_slabmap_for_subtile(task_stl_x, task_stl_y); + if (slab_kind_is_indestructible(slab->kind) != (filter == DigTaskStack_Gems)) { + continue; + } + if (!block_has_diggable_side(subtile_slab(task_stl_x), subtile_slab(task_stl_y))) { + continue; + } + distance = chessboard_distance(anchor_imp->mappos.x.stl.num, anchor_imp->mappos.y.stl.num, task_stl_x, task_stl_y); + if ((distance < last_distance) || ((distance == last_distance) && (task->coords <= last_coords))) { + continue; + } + if ((best_task != NULL) && (distance > best_distance)) { + continue; } + if ((best_task != NULL) && (distance == best_distance) && (task->coords >= best_task->coords)) { + continue; + } + best_task = task; + best_distance = distance; } - } - SYNCDBG(8,"Done, added %d tasks",(int)(max_tasks-remain_num)); - return (max_tasks-remain_num); -} -int add_gems_to_imp_stack(struct Dungeon *dungeon, int max_tasks) -{ - struct MapTask* mtask; - long stl_x; - long stl_y; - long i; - SYNCDBG(18,"Starting"); - int remain_num; - remain_num = max_tasks; - i = -1; - while ((remain_num > 0) && (dungeon->digger_stack_length < DIGGER_TASK_MAX_COUNT)) - { - i = find_next_dig_in_dungeon_task_list(dungeon, i); - if (i < 0) + if (best_task == NULL) { break; - mtask = get_dungeon_task_list_entry(dungeon, i); - stl_x = stl_num_decode_x(mtask->coords); - stl_y = stl_num_decode_y(mtask->coords); - if ( subtile_revealed(stl_x, stl_y, dungeon->owner) ) - { - struct SlabMap *slb; - slb = get_slabmap_for_subtile(stl_x, stl_y); - if (slab_kind_is_indestructible(slb->kind)) // Add only blocks which cannot be destroyed by digging - { - if ( block_has_diggable_side(subtile_slab(stl_x), subtile_slab(stl_y)) ) - { - add_to_dungeon_imp_stack_using_pos(mtask->coords, DigTsk_DigOrMine, dungeon); - remain_num--; - } - } } + add_to_dungeon_imp_stack_using_pos(best_task->coords, DigTsk_DigOrMine, dungeon); + last_distance = best_distance; + last_coords = best_task->coords; + tasks_added++; } - SYNCDBG(8,"Done, added %d tasks",(int)(max_tasks-remain_num)); - return (max_tasks-remain_num); + SYNCDBG(8,"Done, added %d tasks",(int)tasks_added); + return tasks_added; } TbBool add_to_reinforce_stack(long slb_x, long slb_y, SpDiggerTaskType task_type) @@ -1576,6 +1582,9 @@ long add_pretty_and_convert_to_imp_stack_starting_from_pos(struct Dungeon *dunge } } + if (slblipos >= slblicount) { + break; + } base_slb_x = slblist[slblipos].x; base_slb_y = slblist[slblipos].y; slblipos++; @@ -2905,11 +2914,11 @@ TbBool imp_stack_update(struct Thing *creatng) add_unclaimed_spells_to_imp_stack(dungeon, DIGGER_TASK_MAX_COUNT/4 - 1); add_empty_traps_to_imp_stack(dungeon, DIGGER_TASK_MAX_COUNT/6); add_pretty_and_convert_to_imp_stack(dungeon, DIGGER_TASK_MAX_COUNT/64); - add_undug_to_imp_stack(dungeon, DIGGER_TASK_MAX_COUNT/16 - 1); + add_dig_tasks_to_imp_stack(creatng, dungeon, DIGGER_TASK_MAX_COUNT/16 - 1, DigTaskStack_Undug); add_unclaimed_gold_to_imp_stack(dungeon, DIGGER_TASK_MAX_COUNT/64); - add_gems_to_imp_stack(dungeon, DIGGER_TASK_MAX_COUNT*5/8); + add_dig_tasks_to_imp_stack(creatng, dungeon, DIGGER_TASK_MAX_COUNT*5/8, DigTaskStack_Gems); add_unclaimed_traps_to_imp_stack(dungeon, DIGGER_TASK_MAX_COUNT/4); - add_undug_to_imp_stack(dungeon, DIGGER_TASK_MAX_COUNT*5/8); + add_dig_tasks_to_imp_stack(creatng, dungeon, DIGGER_TASK_MAX_COUNT*5/8, DigTaskStack_Undug); add_pretty_and_convert_to_imp_stack(dungeon, DIGGER_TASK_MAX_COUNT*5/8); add_unclaimed_gold_to_imp_stack(dungeon, DIGGER_TASK_MAX_COUNT/3); add_reinforce_to_imp_stack(dungeon, DIGGER_TASK_MAX_COUNT); diff --git a/src/spdigger_stack.h b/src/spdigger_stack.h index 8106f7caf8..e027f00769 100644 --- a/src/spdigger_stack.h +++ b/src/spdigger_stack.h @@ -93,8 +93,6 @@ long find_in_dungeon_imp_stack_starting_at(SpDiggerTaskType task_type, long star TbBool add_to_dungeon_imp_stack_using_pos(SubtlCodedCoords stl_num, SpDiggerTaskType task_type, struct Dungeon *dungeon); TbBool add_object_for_trap_to_imp_stack(struct Dungeon *dungeon, struct Thing *thing); void setup_imp_stack(struct Dungeon *dungeon); -int add_undug_to_imp_stack(struct Dungeon *dungeon, int max_tasks); -int add_gems_to_imp_stack(struct Dungeon *dungeon, int max_tasks); int add_pretty_and_convert_to_imp_stack(struct Dungeon *dungeon, int max_tasks); int add_unclaimed_gold_to_imp_stack(struct Dungeon *dungeon, int max_tasks); int add_unclaimed_unconscious_bodies_to_imp_stack(struct Dungeon *dungeon, int max_tasks); From dd363568226d4e7e0afe90f7724795929ebb9047 Mon Sep 17 00:00:00 2001 From: Peter Lockett <1760289+cerwym@users.noreply.github.com> Date: Sun, 26 Jul 2026 12:33:32 +0100 Subject: [PATCH 06/28] Prevent Heart clipping in straight view when zoomed in (#5060) --- src/engine_render.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine_render.c b/src/engine_render.c index c5d93eca57..341ef35ebc 100644 --- a/src/engine_render.c +++ b/src/engine_render.c @@ -7075,16 +7075,16 @@ static TbBool project_point_helper(struct PlayerInfo *player, int zoom, MapCoord { int vertical_shift; int64_t new_zoom; - uint8_t offset; short window_width = player->engine_window_width; short window_height = player->engine_window_height; *x_out = (zoom * horizontal_delta >> 16) + (*(uint16_t *)&window_width / 2); vertical_shift = zoom * vertical_delta >> 8; *z_out = window_height - ((vertical_shift + ((uint16_t)(window_height & PPH_EVEN_ALIGN_MASK) << 7)) >> 8) + 64; - new_zoom = (zoom * ((int16_t) pos_z)) << 7; - offset = *((uint8_t *)&new_zoom + 4); - *y_out = (vertical_shift + ((uint16_t)(window_height & PPH_EVEN_ALIGN_MASK) << 7) - ((offset + (signed int)new_zoom) >> 16)) >> 8; + // prevent 32bit int overflow for the big sprites. + new_zoom = ((int64_t)zoom * (int16_t)pos_z) << 7; + *y_out = (int32_t)((vertical_shift + ((uint16_t)(window_height & PPH_EVEN_ALIGN_MASK) << 7) + - (int32_t)(new_zoom >> 16)) >> 8); return (*x_out >= 0 && *x_out < window_width && *y_out >= 0 && *y_out < window_height); } From d126833a0676882ff0cb265d3315faf500a772c7 Mon Sep 17 00:00:00 2001 From: Loobinex Date: Sun, 26 Jul 2026 14:00:46 +0200 Subject: [PATCH 07/28] Added COPY_CREATURE_TYPE script command (#5050) --- config/fxdata/lua/bindings/config.lua | 7 +++++- src/lua_api.c | 7 ++++++ src/lvl_script.c | 1 + src/lvl_script_commands.c | 7 ++++++ src/lvl_script_lib.c | 33 +++++++++++++++++++++++++++ src/lvl_script_lib.h | 2 ++ 6 files changed, 56 insertions(+), 1 deletion(-) diff --git a/config/fxdata/lua/bindings/config.lua b/config/fxdata/lua/bindings/config.lua index 127196d237..c8899dd7b6 100644 --- a/config/fxdata/lua/bindings/config.lua +++ b/config/fxdata/lua/bindings/config.lua @@ -7,8 +7,13 @@ function SetGameRule(rulename,val1) end ---Creates a new creature type by loading in a creature config file with this name present in the creatures folder and adds it to the creature list. Creature name will not be recognized in DKscript. +---@param creature creature_type Creature model to be copied. +---@param creaturename string the name of the new creature type +function CopyCreatureType(creature,creaturename) end + +---Creates a new creature type by copying an existing creature type and adds it to the creature list. Creature name will not be recognized in DKscript. ---@param creaturename string the name of the creature config file you want to load -function NewCreatureType(name) end +function NewCreatureType(creaturename) end ---Allows you to make changes to door values set in trapdoor.cfg. Look in that file for explanations on the numbers. ---@param doorname door_type The name of the door as defined in trapdoor.cfg diff --git a/src/lua_api.c b/src/lua_api.c index 0a6f53691e..a24a3fccde 100644 --- a/src/lua_api.c +++ b/src/lua_api.c @@ -1209,6 +1209,12 @@ static int lua_New_creature_type(lua_State* L) return 0; } +static int lua_Copy_creature_type(lua_State* L) +{ + script_copy_creature_type(luaL_checkNamedCommand(L, 1, creature_desc),luaL_checkstring(L, 2)); + return 0; +} + static int lua_Set_door_configuration(lua_State *L) { set_configuration(L, &trapdoor_door_named_fields_set, "SET_DOOR_CONFIGURATION"); @@ -2474,6 +2480,7 @@ static const luaL_Reg global_methods[] = { //Manipulating Configs {"NewCreatureType" ,lua_New_creature_type }, + {"CopyCreatureType" ,lua_Copy_creature_type }, //{"NewObjectType" ,lua_New_object_type }, //{"NewTrapType" ,lua_New_trap_type }, //{"NewRoomType" ,lua_New_room_type }, diff --git a/src/lvl_script.c b/src/lvl_script.c index 7b7af9364b..4fea473140 100644 --- a/src/lvl_script.c +++ b/src/lvl_script.c @@ -202,6 +202,7 @@ TbBool script_is_preloaded_command(long cmnd_index) case Cmd_NEW_OBJECT_TYPE: case Cmd_NEW_ROOM_TYPE: case Cmd_NEW_CREATURE_TYPE: + case Cmd_COPY_CREATURE_TYPE: return true; default: return false; diff --git a/src/lvl_script_commands.c b/src/lvl_script_commands.c index 791c800c68..f13d9ad662 100644 --- a/src/lvl_script_commands.c +++ b/src/lvl_script_commands.c @@ -1676,6 +1676,12 @@ static void count_creatures_at_action_point_check(const struct ScriptLine* sclin PROCESS_SCRIPT_VALUE(scline->command); } +static void copy_creature_type_check(const struct ScriptLine* scline) +{ + script_copy_creature_type(scline->np[0],scline->tp[1]); + return; +} + static void new_creature_type_check(const struct ScriptLine* scline) { script_new_creature_type(scline->tp[0]); @@ -6836,6 +6842,7 @@ const struct CommandDesc command_desc[] = { {"NEW_OBJECT_TYPE", "A ", Cmd_NEW_OBJECT_TYPE, &new_object_type_check, &null_process}, {"NEW_ROOM_TYPE", "A ", Cmd_NEW_ROOM_TYPE, &new_room_type_check, &null_process}, {"NEW_CREATURE_TYPE", "A ", Cmd_NEW_CREATURE_TYPE, &new_creature_type_check, &null_process}, + {"COPY_CREATURE_TYPE", "CA ", Cmd_COPY_CREATURE_TYPE, ©_creature_type_check, &null_process }, {"SET_HAND_GRAPHIC", "PA ", Cmd_SET_HAND_GRAPHIC, &set_power_hand_check, &set_power_hand_process}, {"ADD_EFFECT_GENERATOR_TO_LEVEL", "AAN ", Cmd_ADD_EFFECT_GENERATOR_TO_LEVEL, &add_effectgen_to_level_check, &add_effectgen_to_level_process}, {"SET_EFFECT_GENERATOR_CONFIGURATION","AAAnn ", Cmd_SET_EFFECT_GENERATOR_CONFIGURATION, &set_effectgen_configuration_check, &set_effectgen_configuration_process}, diff --git a/src/lvl_script_lib.c b/src/lvl_script_lib.c index 0a195ee067..2e82fc75d0 100644 --- a/src/lvl_script_lib.c +++ b/src/lvl_script_lib.c @@ -239,6 +239,39 @@ TbBool script_new_creature_type(const char *name) return false; } +TbBool script_copy_creature_type(ThingModel source_id, const char* name) +{ + if (game.conf.crtr_conf.model_count >= CREATURE_TYPES_MAX) + { + SCRPTERRLOG("Cannot increase creature type count for creature type '%s', already at maximum %d types.", name, CREATURE_TYPES_MAX); + return false; + } + for (int j = 0; j < (game.conf.crtr_conf.model_count - 1); j++) + { + if (strcmp(creature_desc[j].name, name) == 0) + { + SCRPTERRLOG("Trying to add creature type that already exists: %s", name); + return false; + } + } + int i = game.conf.crtr_conf.model_count; + game.conf.crtr_conf.model_count++; + + +// init_creature_model_stats(i); + game.conf.crtr_conf.model[i] = game.conf.crtr_conf.model[source_id]; + snprintf(game.conf.crtr_conf.model[i].name, COMMAND_WORD_LEN, "%s", name); + creature_desc[i - 1].name = game.conf.crtr_conf.model[i].name; + creature_desc[i - 1].num = i; + for (int k = 0; k < CREATURE_GRAPHICS_INSTANCES; k++) + { + game.conf.crtr_conf.creature_graphics[i][k] = game.conf.crtr_conf.creature_graphics[source_id][k]; + } + game.conf.crtr_conf.creature_sounds[i] = game.conf.crtr_conf.creature_sounds[source_id]; + + return true; +} + void set_variable(int player_idx, long var_type, long var_idx, long new_val) { struct Dungeon *dungeon = get_dungeon(player_idx); diff --git a/src/lvl_script_lib.h b/src/lvl_script_lib.h index 696f017dbe..ff6c734478 100644 --- a/src/lvl_script_lib.h +++ b/src/lvl_script_lib.h @@ -205,6 +205,7 @@ enum TbScriptCommands { Cmd_QUICK_PLAYER_INFORMATION = 193, Cmd_QUICK_PLAYER_OBJECTIVE_WITH_POS = 194, Cmd_QUICK_PLAYER_INFORMATION_WITH_POS = 195, + Cmd_COPY_CREATURE_TYPE = 196, }; struct ScriptLine { @@ -340,6 +341,7 @@ struct Thing *script_process_new_object(ThingModel tngmodel, MapSubtlCoord stl_x struct Thing* script_process_new_effectgen(ThingModel crmodel, TbMapLocation location, long range); struct Thing* script_process_new_corpse(ThingModel tngmodel, MapSubtlCoord stl_x, MapSubtlCoord stl_y, PlayerNumber plyr_idx, CrtrExpLevel exp_level, TbBool dying); TbBool script_new_creature_type(const char* name); +TbBool script_copy_creature_type(ThingModel source_id,const char* name); void command_init_value(struct ScriptValue* value, unsigned long var_index, unsigned long plr_range_id); void command_add_value(unsigned long var_index, unsigned long plr_range_id, long param1, long param2, long param3); void set_variable(int player_idx, long var_type, long var_idx, long new_val); From e99da804491c550b1744c454e2f45a885f23d9a8 Mon Sep 17 00:00:00 2001 From: AleWin32 <7621682+AleWin32@users.noreply.github.com> Date: Sun, 26 Jul 2026 14:05:12 +0200 Subject: [PATCH 08/28] Add also FLAC/MP3/WAV support for music, replace hardcoded "keeperNN.ogg" requirement with now automatic music file detection --- src/bflib_sndlib.cpp | 81 +++++++++++++++++++++++++++++++++++++++++++- src/windows.cpp | 6 ++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/bflib_sndlib.cpp b/src/bflib_sndlib.cpp index 43a38759ed..edb2ea6f7e 100644 --- a/src/bflib_sndlib.cpp +++ b/src/bflib_sndlib.cpp @@ -760,6 +760,80 @@ extern "C" TbBool play_music_fgroup(short fgroup, const char * fname) { return play_music(fpath); } +// Music container extensions, in order of preference. Files of other extensions +// (and non-audio files like MusicReadme.txt) are ignored, so they can't shift +// the track-to-file mapping. +// The preferred option is to preserve 100% of the original audio quality while +// using as little storage space as possible, that's why FLAC is first candidate. +static const char *const music_file_extensions[] = { + ".flac", ".wav", ".ogg", ".mp3" +}; + +static int music_extension_priority(const char *filename) { + const char *ext = strrchr(filename, '.'); + if (ext == NULL) { + return -1; + } + for (size_t i = 0; i < sizeof(music_file_extensions) / sizeof(music_file_extensions[0]); i++) { + if (strcasecmp(ext, music_file_extensions[i]) == 0) { + return (int)i; + } + } + return -1; +} + +// Resolve which music file plays for a given redbook track number +static TbBool resolve_track_music_path(int track, char *dst, int dst_size) { + if (track < 2) { + return false; + } + const int wanted = track - 2; // 0-based position within the chosen format's files + + char filespec[2048]; + prepare_file_path_buf(filespec, sizeof(filespec), FGrp_Music, "*"); + if (filespec[0] == '\0') { + return false; + } + + struct TbFileEntry fe; + struct TbFileFind *ff = LbFileFindFirst(filespec, &fe); + if (ff == NULL) { + return false; + } + + std::vector> files; // (priority rank, filename) + int best_priority = -1; + do { + const int prio = music_extension_priority(fe.Filename); + if (prio < 0) { + continue; // not a recognized music file + } + files.emplace_back(prio, fe.Filename); + if (best_priority < 0 || prio < best_priority) { + best_priority = prio; + } + } while (LbFileFindNext(ff, &fe) >= 0); + LbFileFindEnd(ff); + + if (best_priority < 0) { + return false; + } + + // Map the track within the winning format's files only, keeping sorted order + int index = 0; + for (const auto & f : files) { + if (f.first != best_priority) { + continue; + } + if (index == wanted) { + prepare_file_path_buf(dst, dst_size, FGrp_Music, f.second.c_str()); + return (dst[0] != '\0'); + } + index++; + } + return false; +} + extern "C" TbBool play_music_track(int track) { game.music_track = track; memset(game.music_fname, 0, sizeof(game.music_fname)); @@ -769,7 +843,12 @@ extern "C" TbBool play_music_track(int track) { } else if (features_enabled & Ft_NoCdMusic) { // play_music() itself skips restarting if this exact resolved file is // already the one actually playing (e.g. reloading a save for the same level). - return play_music(prepare_file_fmtpath(FGrp_Music, "keeper%02d.ogg", track)); + char fpath[2048]; + if (!resolve_track_music_path(track, fpath, sizeof(fpath))) { + WARNLOG("No music file found for track %d in the music folder", track); + return false; + } + return play_music(fpath); } else { if (track == g_current_music_track) { // Already playing this exact numbered track — skip restarting it. diff --git a/src/windows.cpp b/src/windows.cpp index 2e80f08a1a..4daccbb137 100644 --- a/src/windows.cpp +++ b/src/windows.cpp @@ -142,6 +142,12 @@ extern "C" TbFileFind * LbFileFindFirst(const char * filespec, struct TbFileEntr return nullptr; } do { + // Only return regular files. Skipping directories also drops "." and + // ".."; this matches the Linux implementation (which keeps S_ISREG + // entries only) so a plain "*" enumerates files on both platforms alike. + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + continue; + } std::string key = fd.cFileName; for (size_t i = 0; i < key.size(); i++) { key[i] = (char)tolower((unsigned char)key[i]); From 8875eeb0498c3546a62cd3afc7414dd335ce014d Mon Sep 17 00:00:00 2001 From: RupixTalahone <304036411+RupixTalahone@users.noreply.github.com> Date: Mon, 27 Jul 2026 01:09:19 +0200 Subject: [PATCH 09/28] Fix big sprites clipped by floor tiles in straight view (#5062) --- src/engine_render.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine_render.c b/src/engine_render.c index 341ef35ebc..79cda55f40 100644 --- a/src/engine_render.c +++ b/src/engine_render.c @@ -8896,7 +8896,8 @@ static void draw_frontview_thing_on_element(struct Thing *thing, struct Map *map convert_world_coord_to_front_view_screen_coord(&interp.mappos, cam, &cx, &cy, &cz); if (is_free_space_in_poly_pool(1)) { - add_thing_sprite_to_polypool(thing, cx, cy, cy, cz-3); + int size_on_screen = thing->sprite_size * ((camera_zoom << 13) / 0x10000 / pixel_size) / 0x10000; + add_thing_sprite_to_polypool(thing, cx, cy, cy, cz - 3 - (size_on_screen >> 1)); if ((thing->class_id == TCls_Creature) && is_free_space_in_poly_pool(1)) { create_status_box_element(thing, cx, cy, cy, 1); From 23d9254c49f6b88b52d7643e2878fd9e52b4ef2f Mon Sep 17 00:00:00 2001 From: rainlizard <15337628+rainlizard@users.noreply.github.com> Date: Tue, 28 Jul 2026 07:20:40 +1000 Subject: [PATCH 10/28] Fix message button drop animation (#5069) --- src/frontmenu_ingame_evnt.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/frontmenu_ingame_evnt.c b/src/frontmenu_ingame_evnt.c index cccb13abb3..8fd7916379 100644 --- a/src/frontmenu_ingame_evnt.c +++ b/src/frontmenu_ingame_evnt.c @@ -61,18 +61,14 @@ int debug_display_network_stats = 0; /******************************************************************************/ EventIndex get_my_event_button_index(unsigned int button_idx) { - struct Dungeon* dungeon = get_my_dungeon(); - for (int i = 0; i <= EVENT_BUTTONS_COUNT; i++) { - EventIndex evidx = dungeon->event_button_index[i]; - if (!evidx || (my_event_button_state[evidx] & EvBtnS_Hidden)) { - continue; - } - if (!button_idx) { - return evidx; - } - button_idx--; + if (button_idx > EVENT_BUTTONS_COUNT) { + return 0; + } + EventIndex evidx = get_my_dungeon()->event_button_index[button_idx]; + if (my_event_button_state[evidx] & EvBtnS_Hidden) { + return 0; } - return 0; + return evidx; } void gui_open_event(struct GuiButton *gbtn) From b9b57684dd67ae66991b67a14023ebc1c3e0500b Mon Sep 17 00:00:00 2001 From: jwt27 Date: Mon, 27 Jul 2026 23:20:55 +0200 Subject: [PATCH 11/28] Process local camera controls before logic update (#5067) --- src/local_camera.c | 13 ++----------- src/local_camera.h | 3 +-- src/main.cpp | 3 +-- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/local_camera.c b/src/local_camera.c index 051827a796..9cff1b5471 100644 --- a/src/local_camera.c +++ b/src/local_camera.c @@ -54,17 +54,11 @@ static TbBool local_camera_move_active; static struct Packet* get_packet_for_local_camera_update(void) { - GameTurn turn; struct PlayerInfo *player = get_my_player(); if (player_invalid(player)) { return NULL; } - if (flag_is_set(game.operation_flags, GOF_Paused) && game.game_kind == GKind_LocalGame) { - turn = get_gameturn(); - } else { - turn = get_gameturn() - 1; - } - return (struct Packet *)get_history_packet(player->packet_num, turn); + return get_packet_direct(player->packet_num); } void send_camera_catchup_packets(struct PlayerInfo *player) @@ -217,17 +211,14 @@ void update_camera_deviations(int active_cam_idx) } } -void update_local_cameras_pre(void) +void update_local_cameras(void) { for (int i = 0; i < 4; i++) { previous_local_cameras[i] = destination_local_cameras[i]; } previous_deviation_x = destination_deviation_x; previous_deviation_y = destination_deviation_y; -} -void update_local_cameras_post(void) -{ if (!local_camera_ready) { return; } diff --git a/src/local_camera.h b/src/local_camera.h index f7ddc411c0..6f6ec90941 100644 --- a/src/local_camera.h +++ b/src/local_camera.h @@ -39,8 +39,7 @@ extern struct Camera destination_local_cameras[4]; extern TbBool local_camera_ready; /******************************************************************************/ void init_local_cameras(struct PlayerInfo *player); -void update_local_cameras_pre(void); -void update_local_cameras_post(void); +void update_local_cameras(void); void interpolate_local_cameras(void); void sync_local_camera(struct PlayerInfo *player); void set_local_camera_destination(struct PlayerInfo *player); diff --git a/src/main.cpp b/src/main.cpp index f12e89bb9c..d00b45948e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2696,7 +2696,7 @@ void update(void) struct PlayerInfo *player; SYNCDBG(4,"Starting for turn %ld",(long)get_gameturn()); - update_local_cameras_pre(); + update_local_cameras(); process_packets(); api_update_server(); @@ -2757,7 +2757,6 @@ void update(void) message_update(); update_all_players_cameras(); - update_local_cameras_post(); update_player_sounds(); SYNCDBG(6,"Finished"); } From 96f619a024f3bd73420ff53d656ab5641e68460f Mon Sep 17 00:00:00 2001 From: RupixTalahone <304036411+RupixTalahone@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:54:02 +0200 Subject: [PATCH 12/28] Fix all sprites vanishing in straight view at max zoom in 4k (#5066) --- src/engine_render.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine_render.c b/src/engine_render.c index 79cda55f40..94c116edf6 100644 --- a/src/engine_render.c +++ b/src/engine_render.c @@ -4951,7 +4951,7 @@ static void draw_fastview_mapwho(struct Camera *cam, struct BucketKindJontySprit shade_intensity = get_thing_shade(thing); shade_intensity >>= 8; - int size_on_screen = thing->sprite_size * ((camera_zoom << 13) / 0x10000 / pixel_size) / 0x10000; + int size_on_screen = thing->sprite_size * (int)((((int64_t)camera_zoom << 13) / 0x10000) / pixel_size) / 0x10000; if ( thing->rendering_flags & TRF_Tint_Flags ) { lbDisplay.DrawFlags |= Lb_SPRITE_REMAP; @@ -8896,7 +8896,7 @@ static void draw_frontview_thing_on_element(struct Thing *thing, struct Map *map convert_world_coord_to_front_view_screen_coord(&interp.mappos, cam, &cx, &cy, &cz); if (is_free_space_in_poly_pool(1)) { - int size_on_screen = thing->sprite_size * ((camera_zoom << 13) / 0x10000 / pixel_size) / 0x10000; + int size_on_screen = thing->sprite_size * (int)((((int64_t)camera_zoom << 13) / 0x10000) / pixel_size) / 0x10000; add_thing_sprite_to_polypool(thing, cx, cy, cy, cz - 3 - (size_on_screen >> 1)); if ((thing->class_id == TCls_Creature) && is_free_space_in_poly_pool(1)) { From 8d79e4fb8c0f1c732d37f3cab5d15509d89a790c Mon Sep 17 00:00:00 2001 From: RupixTalahone <304036411+RupixTalahone@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:54:48 +0200 Subject: [PATCH 13/28] Fixed units visible through walls in straight view (#5068) --- src/engine_render.c | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/engine_render.c b/src/engine_render.c index 94c116edf6..6446219463 100644 --- a/src/engine_render.c +++ b/src/engine_render.c @@ -7071,6 +7071,7 @@ static void display_fast_drawlist(struct Camera *cam) // Draws frontview only. N */ #define PPH_EVEN_ALIGN_MASK 0xFFFE +#define FRONTVIEW_BUCKET_MARGIN 1024 static TbBool project_point_helper(struct PlayerInfo *player, int zoom, MapCoordDelta vertical_delta, MapCoordDelta horizontal_delta, MapCoord pos_z, int32_t *x_out, int32_t *y_out, int32_t *z_out) { int vertical_shift; @@ -7080,7 +7081,7 @@ static TbBool project_point_helper(struct PlayerInfo *player, int zoom, MapCoord *x_out = (zoom * horizontal_delta >> 16) + (*(uint16_t *)&window_width / 2); vertical_shift = zoom * vertical_delta >> 8; - *z_out = window_height - ((vertical_shift + ((uint16_t)(window_height & PPH_EVEN_ALIGN_MASK) << 7)) >> 8) + 64; + *z_out = window_height - ((vertical_shift + ((uint16_t)(window_height & PPH_EVEN_ALIGN_MASK) << 7)) >> 8) + FRONTVIEW_BUCKET_MARGIN; // prevent 32bit int overflow for the big sprites. new_zoom = ((int64_t)zoom * (int16_t)pos_z) << 7; *y_out = (int32_t)((vertical_shift + ((uint16_t)(window_height & PPH_EVEN_ALIGN_MASK) << 7) @@ -7365,7 +7366,7 @@ static void draw_element(struct Map *map, long lightness, long stl_x, long stl_y myplyr = get_my_player(); cube_itm = (qdrant + 2) & 3; delta_y = (zoom << 7) / 256; - bckt_idx = myplyr->engine_window_height - (pos_y >> 8) + 64; + bckt_idx = myplyr->engine_window_height - (pos_y >> 8) + FRONTVIEW_BUCKET_MARGIN; // Check if there's enough place to draw if (!is_free_space_in_poly_pool(8)) return; @@ -7430,6 +7431,23 @@ static void draw_element(struct Map *map, long lightness, long stl_x, long stl_y // Draw the columns cubes + long bckt_face = bckt_idx; + long bckt_top = bckt_idx; + if (((mapblk->flags & SlbAtFlg_Blocking) != 0) + && (get_column_floor_filled_subtiles(col) >= 3)) + { + bckt_face = bckt_idx - (zoom >> 8); + bckt_top = bckt_face; + MapSubtlCoord sstl_x = stl_x + (qdrant == 3) - (qdrant == 1); + MapSubtlCoord sstl_y = stl_y + (qdrant == 0) - (qdrant == 2); + struct Map *smapblk = get_map_block_at(sstl_x, sstl_y); + if (((smapblk->flags & SlbAtFlg_Blocking) != 0) + && (!map_block_revealed(smapblk, my_player_number) + || (get_floor_filled_subtiles_at(sstl_x, sstl_y) >= 3))) { + bckt_top = bckt_face - 2 * (zoom >> 8); + } + } + y = zoom + pos_y; cube_config_stats = NULL; for (tc=0; tc < COLUMN_STACK_HEIGHT; tc++) @@ -7443,7 +7461,7 @@ static void draw_element(struct Map *map, long lightness, long stl_x, long stl_y *ymax = y; textr_idx = engine_remap_texture_blocks(stl_x, stl_y, cube_config_stats->texture_id[cube_itm]); add_lgttextrdquad_to_polypool(pos_x, y, textr_idx, zoom, delta_y, 0, - lightness_arr[3][tc+1], lightness_arr[2][tc+1], lightness_arr[2][tc], lightness_arr[3][tc], bckt_idx); + lightness_arr[3][tc+1], lightness_arr[2][tc+1], lightness_arr[2][tc], lightness_arr[3][tc], bckt_face); } } @@ -7456,15 +7474,15 @@ static void draw_element(struct Map *map, long lightness, long stl_x, long stl_y textr_idx = engine_remap_texture_blocks(stl_x, stl_y, cube_config_stats->texture_id[4]); if ((mapblk->flags & SlbAtFlg_TaggedValuable) != 0) { - add_textruredquad_to_polypool(pos_x, i, textr_idx, zoom, qdrant, 2097152, 1, bckt_idx); + add_textruredquad_to_polypool(pos_x, i, textr_idx, zoom, qdrant, 2097152, 1, bckt_top); } else if ((mapblk->flags & SlbAtFlg_Unexplored) != 0) { - add_textruredquad_to_polypool(pos_x, i, textr_idx, zoom, qdrant, 2097152, 0, bckt_idx); + add_textruredquad_to_polypool(pos_x, i, textr_idx, zoom, qdrant, 2097152, 0, bckt_top); } else { add_lgttextrdquad_to_polypool(pos_x, i, textr_idx, zoom, zoom, qdrant, - lightness_arr[0][tc], lightness_arr[1][tc], lightness_arr[2][tc], lightness_arr[3][tc], bckt_idx); + lightness_arr[0][tc], lightness_arr[1][tc], lightness_arr[2][tc], lightness_arr[3][tc], bckt_top); } } } @@ -7490,7 +7508,7 @@ static void draw_element(struct Map *map, long lightness, long stl_x, long stl_y { textr_idx = engine_remap_texture_blocks(stl_x, stl_y, cube_config_stats->texture_id[cube_itm]); add_lgttextrdquad_to_polypool(pos_x, y, textr_idx, zoom, delta_y, 0, - lightness_arr[3][tc+1], lightness_arr[2][tc+1], lightness_arr[2][tc], lightness_arr[3][tc], bckt_idx); + lightness_arr[3][tc+1], lightness_arr[2][tc+1], lightness_arr[2][tc], lightness_arr[3][tc], bckt_face); } } if (cube_config_stats != NULL) @@ -7500,7 +7518,7 @@ static void draw_element(struct Map *map, long lightness, long stl_x, long stl_y { textr_idx = engine_remap_texture_blocks(stl_x, stl_y, cube_config_stats->texture_id[4]); add_lgttextrdquad_to_polypool(pos_x, i, textr_idx, zoom, zoom, qdrant, - lightness_arr[0][tc], lightness_arr[1][tc], lightness_arr[2][tc], lightness_arr[3][tc], bckt_idx); + lightness_arr[0][tc], lightness_arr[1][tc], lightness_arr[2][tc], lightness_arr[3][tc], bckt_top); } } } @@ -8449,7 +8467,7 @@ void create_frontview_map_volume_box(struct Camera *cam, unsigned char stl_width coord_x -= box_width; break; } - coord_z -= (stl_width >> 1); + coord_z -= (11 * (long)stl_width) >> 2; // Draw 4 horizonal line elements create_line_element(coord_x, coord_y, coord_x + box_width, coord_y, coord_z, line_color); create_line_element(coord_x, coord_y + box_height, coord_x + box_width, coord_y + box_height, coord_z - box_height, line_color); @@ -8542,7 +8560,7 @@ void create_fancy_frontview_map_volume_box(struct RoomSpace roomspace, struct Ca } break; } - coord_z -= (stl_width >> 1); + coord_z -= (11 * (long)stl_width) >> 2; for (int roomspace_y = 0; roomspace_y < room_slab_height; roomspace_y += 1) { int y_start = (box_height * roomspace_y / room_slab_height) + ((((box_height * roomspace_y) % room_slab_height) >= room_slab_height) ? 1 : 0); From 1ef7bbae64c20155b60a796861b167ae3c8611ad Mon Sep 17 00:00:00 2001 From: jwt27 Date: Thu, 30 Jul 2026 23:06:29 +0200 Subject: [PATCH 14/28] Rewrite bflib_render_gpoly.c (#5071) --- src/bflib_render.c | 6 - src/bflib_render.h | 7 - src/bflib_render_gpoly.c | 1327 ++++++++++++++------------------------ 3 files changed, 492 insertions(+), 848 deletions(-) diff --git a/src/bflib_render.c b/src/bflib_render.c index 57770666a7..8363b94ed3 100644 --- a/src/bflib_render.c +++ b/src/bflib_render.c @@ -27,15 +27,9 @@ /******************************************************************************/ TbPixel vec_colour = 112; unsigned char vec_mode; -unsigned char *LOC_poly_screen; -unsigned char *LOC_vec_map; unsigned char *render_fade_tables = NULL; unsigned char *render_ghost = NULL; unsigned char *render_alpha = NULL; -unsigned char *LOC_vec_screen; -long LOC_vec_screen_width; -long LOC_vec_window_width; -long LOC_vec_window_height; struct PolyPoint *polyscans = NULL; /******************************************************************************/ diff --git a/src/bflib_render.h b/src/bflib_render.h index 3ab32ed435..c0289b2789 100644 --- a/src/bflib_render.h +++ b/src/bflib_render.h @@ -96,13 +96,6 @@ extern unsigned char *render_fade_tables; extern unsigned char *render_ghost; extern unsigned char *render_alpha; extern struct PolyPoint *polyscans; -// Rename pending for these entries -extern unsigned char *LOC_poly_screen; -extern unsigned char *LOC_vec_map; -extern unsigned char *LOC_vec_screen; -extern long LOC_vec_screen_width; -extern long LOC_vec_window_width; -extern long LOC_vec_window_height; /******************************************************************************/ void draw_gpoly(struct PolyPoint *point_a, struct PolyPoint *point_b, struct PolyPoint *point_c); /******************************************************************************/ diff --git a/src/bflib_render_gpoly.c b/src/bflib_render_gpoly.c index 43a17c050e..b8dfe66e10 100644 --- a/src/bflib_render_gpoly.c +++ b/src/bflib_render_gpoly.c @@ -6,8 +6,6 @@ * Rendering function draw_gpoly() for drawing 3D view elements. * @par Purpose: * Function for rendering 3D elements. - * @par Comment: - * Go away from here, you bad optimizer! Do not compile this with optimizations. * @author Tomasz Lis * @date 20 Mar 2009 - 14 Feb 2010 * @par Copying and copyrights: @@ -27,44 +25,50 @@ #include "bflib_vidraw.h" #include "post_inc.h" +#ifdef __GNUC__ + #pragma GCC optimize "Ofast", "omit-frame-pointer" + #define ALWAYS_INLINE __attribute__((always_inline)) inline +#else + #define ALWAYS_INLINE inline +#endif + /******************************************************************************/ -/******************************************************************************/ -static const long gpoly_reptable[] = { - 0x0,0x7FFFFFFF,0x3FFFFFFF,0x2AAAAAAA,0x1FFFFFFF,0x19999999,0x15555555,0x12492492, - 0x0FFFFFFF,0x0E38E38E,0x0CCCCCCC,0x0BA2E8BA,0x0AAAAAAA, 0x9D89D89, 0x9249249, 0x8888888, - 0x7FFFFFF, 0x7878787, 0x71C71C7, 0x6BCA1AF, 0x6666666, 0x6186186, 0x5D1745D, 0x590B216, - 0x5555555, 0x51EB851, 0x4EC4EC4, 0x4BDA12F, 0x4924924, 0x469EE58, 0x4444444, 0x4210842, - 0x3FFFFFF, 0x3E0F83E, 0x3C3C3C3, 0x3A83A83, 0x38E38E3, 0x3759F22, 0x35E50D7, 0x3483483, - 0x3333333, 0x31F3831, 0x30C30C3, 0x2FA0BE8, 0x2E8BA2E, 0x2D82D82, 0x2C8590B, 0x2B93105, - 0x2AAAAAA, 0x29CBC14, 0x28F5C28, 0x2828282, 0x2762762, 0x26A439F, 0x25ED097, 0x253C825, - 0x2492492, 0x23EE08F, 0x234F72C, 0x22B63CB, 0x2222222, 0x2192E29, 0x2108421, 0x2082082, - 0x1FFFFFF, 0x1F81F81, 0x1F07C1F, 0x1E9131A, 0x1E1E1E1, 0x1DAE607, 0x1D41D41, 0x1CD8568, - 0x1C71C71, 0x1C0E070, 0x1BACF91, 0x1B4E81B, 0x1AF286B, 0x1A98EF6, 0x1A41A41, 0x19EC8E9, - 0x1999999, 0x1948B0F, 0x18F9C18, 0x18ACB90, 0x1861861, 0x1818181, 0x17D05F4, 0x178A4C8, - 0x1745D17, 0x1702E05, 0x16C16C1, 0x1681681, 0x1642C85, 0x1605816, 0x15C9882, 0x158ED23, - 0x1555555, 0x151D07E, 0x14E5E0A, 0x14AFD6A, 0x147AE14, 0x1446F86, 0x1414141, 0x13E22CB, - 0x13B13B1, 0x1381381, 0x13521CF, 0x1323E34, 0x12F684B, 0x12C9FB4, 0x129E412, 0x127350B, - 0x1249249, 0x121FB78, 0x11F7047, 0x11CF06A, 0x11A7B96, 0x1181181, 0x115B1E5, 0x1135C81, - 0x1111111, 0x10ECF56, 0x10C9714, 0x10A6810, 0x1084210, 0x10624DD, 0x1041041, 0x1020408, - 0x0FFFFFF, 0x0FE03F8, 0x0FC0FC0, 0x0FA232C, 0x0F83E0F, 0x0F6603D, 0x0F4898D, 0x0F2B9D6, - 0x0F0F0F0, 0x0EF2EB7, 0x0ED7303, 0x0EBBDB2, 0x0EA0EA0, 0x0E865AC, 0x0E6C2B4, 0x0E52598, - 0x0E38E38, 0x0E1FC78, 0x0E07038, 0x0DEE95C, 0x0DD67C8, 0x0DBEB61, 0x0DA740D, 0x0D901B2, - 0x0D79435, 0x0D62B80, 0x0D4C77B, 0x0D3680D, 0x0D20D20, 0x0D0B69F, 0x0CF6474, 0x0CE168A, - 0x0CCCCCC, 0x0CB8727, 0x0CA4587, 0x0C907DA, 0x0C7CE0C, 0x0C6980C, 0x0C565C8, 0x0C4372F, - 0x0C30C30, 0x0C1E4BB, 0x0C0C0C0, 0x0BFA02F, 0x0BE82FA, 0x0BD6910, 0x0BC5264, 0x0BB3EE7, - 0x0BA2E8B, 0x0B92143, 0x0B81702, 0x0B70FBB, 0x0B60B60, 0x0B509E6, 0x0B40B40, 0x0B30F63, - 0x0B21642, 0x0B11FD3, 0x0B02C0B, 0x0AF3ADD, 0x0AE4C41, 0x0AD602B, 0x0AC7691, 0x0AB8F69, - 0x0AAAAAA, 0x0A9C84A, 0x0A8E83F, 0x0A80A80, 0x0A72F05, 0x0A655C4, 0x0A57EB5, 0x0A4A9CF, - 0x0A3D70A, 0x0A3065E, 0x0A237C3, 0x0A16B31, 0x0A0A0A0, 0x09FD809, 0x09F1165, 0x09E4CAD, - 0x09D89D8, 0x09CC8E1, 0x09C09C0, 0x09B4C6F, 0x09A90E7, 0x099D722, 0x0991F1A, 0x09868C8, - 0x097B425, 0x097012E, 0x0964FDA, 0x095A025, 0x094F209, 0x0944580, 0x0939A85, 0x092F113, - 0x0924924, 0x091A2B3, 0x090FDBC, 0x0905A38, 0x08FB823, 0x08F1779, 0x08E7835, 0x08DDA52, - 0x08D3DCB, 0x08CA29C, 0x08C08C0, 0x08B7034, 0x08AD8F2, 0x08A42F8, 0x089AE40, 0x0891AC7, - 0x0888888, 0x087F780, 0x08767AB, 0x086D905, 0x0864B8A, 0x085BF37, 0x0853408, 0x084A9F9, - 0x0842108, 0x0839930, 0x083126E, 0x0828CBF, 0x0820820, 0x081848D, 0x0810204, 0x0808080, - 0x0, 0x0 }; - -static const long gpoly_divtable[][64] = { +static const int32_t gpoly_reptable[] = { + 0x00000000, 0x7FFFFFFF, 0x3FFFFFFF, 0x2AAAAAAA, 0x1FFFFFFF, 0x19999999, 0x15555555, 0x12492492, + 0x0FFFFFFF, 0x0E38E38E, 0x0CCCCCCC, 0x0BA2E8BA, 0x0AAAAAAA, 0x09D89D89, 0x09249249, 0x08888888, + 0x07FFFFFF, 0x07878787, 0x071C71C7, 0x06BCA1AF, 0x06666666, 0x06186186, 0x05D1745D, 0x0590B216, + 0x05555555, 0x051EB851, 0x04EC4EC4, 0x04BDA12F, 0x04924924, 0x0469EE58, 0x04444444, 0x04210842, + 0x03FFFFFF, 0x03E0F83E, 0x03C3C3C3, 0x03A83A83, 0x038E38E3, 0x03759F22, 0x035E50D7, 0x03483483, + 0x03333333, 0x031F3831, 0x030C30C3, 0x02FA0BE8, 0x02E8BA2E, 0x02D82D82, 0x02C8590B, 0x02B93105, + 0x02AAAAAA, 0x029CBC14, 0x028F5C28, 0x02828282, 0x02762762, 0x026A439F, 0x025ED097, 0x0253C825, + 0x02492492, 0x023EE08F, 0x0234F72C, 0x022B63CB, 0x02222222, 0x02192E29, 0x02108421, 0x02082082, + 0x01FFFFFF, 0x01F81F81, 0x01F07C1F, 0x01E9131A, 0x01E1E1E1, 0x01DAE607, 0x01D41D41, 0x01CD8568, + 0x01C71C71, 0x01C0E070, 0x01BACF91, 0x01B4E81B, 0x01AF286B, 0x01A98EF6, 0x01A41A41, 0x019EC8E9, + 0x01999999, 0x01948B0F, 0x018F9C18, 0x018ACB90, 0x01861861, 0x01818181, 0x017D05F4, 0x0178A4C8, + 0x01745D17, 0x01702E05, 0x016C16C1, 0x01681681, 0x01642C85, 0x01605816, 0x015C9882, 0x0158ED23, + 0x01555555, 0x0151D07E, 0x014E5E0A, 0x014AFD6A, 0x0147AE14, 0x01446F86, 0x01414141, 0x013E22CB, + 0x013B13B1, 0x01381381, 0x013521CF, 0x01323E34, 0x012F684B, 0x012C9FB4, 0x0129E412, 0x0127350B, + 0x01249249, 0x0121FB78, 0x011F7047, 0x011CF06A, 0x011A7B96, 0x01181181, 0x0115B1E5, 0x01135C81, + 0x01111111, 0x010ECF56, 0x010C9714, 0x010A6810, 0x01084210, 0x010624DD, 0x01041041, 0x01020408, + 0x00FFFFFF, 0x00FE03F8, 0x00FC0FC0, 0x00FA232C, 0x00F83E0F, 0x00F6603D, 0x00F4898D, 0x00F2B9D6, + 0x00F0F0F0, 0x00EF2EB7, 0x00ED7303, 0x00EBBDB2, 0x00EA0EA0, 0x00E865AC, 0x00E6C2B4, 0x00E52598, + 0x00E38E38, 0x00E1FC78, 0x00E07038, 0x00DEE95C, 0x00DD67C8, 0x00DBEB61, 0x00DA740D, 0x00D901B2, + 0x00D79435, 0x00D62B80, 0x00D4C77B, 0x00D3680D, 0x00D20D20, 0x00D0B69F, 0x00CF6474, 0x00CE168A, + 0x00CCCCCC, 0x00CB8727, 0x00CA4587, 0x00C907DA, 0x00C7CE0C, 0x00C6980C, 0x00C565C8, 0x00C4372F, + 0x00C30C30, 0x00C1E4BB, 0x00C0C0C0, 0x00BFA02F, 0x00BE82FA, 0x00BD6910, 0x00BC5264, 0x00BB3EE7, + 0x00BA2E8B, 0x00B92143, 0x00B81702, 0x00B70FBB, 0x00B60B60, 0x00B509E6, 0x00B40B40, 0x00B30F63, + 0x00B21642, 0x00B11FD3, 0x00B02C0B, 0x00AF3ADD, 0x00AE4C41, 0x00AD602B, 0x00AC7691, 0x00AB8F69, + 0x00AAAAAA, 0x00A9C84A, 0x00A8E83F, 0x00A80A80, 0x00A72F05, 0x00A655C4, 0x00A57EB5, 0x00A4A9CF, + 0x00A3D70A, 0x00A3065E, 0x00A237C3, 0x00A16B31, 0x00A0A0A0, 0x009FD809, 0x009F1165, 0x009E4CAD, + 0x009D89D8, 0x009CC8E1, 0x009C09C0, 0x009B4C6F, 0x009A90E7, 0x0099D722, 0x00991F1A, 0x009868C8, + 0x0097B425, 0x0097012E, 0x00964FDA, 0x0095A025, 0x0094F209, 0x00944580, 0x00939A85, 0x0092F113, + 0x00924924, 0x0091A2B3, 0x0090FDBC, 0x00905A38, 0x008FB823, 0x008F1779, 0x008E7835, 0x008DDA52, + 0x008D3DCB, 0x008CA29C, 0x008C08C0, 0x008B7034, 0x008AD8F2, 0x008A42F8, 0x0089AE40, 0x00891AC7, + 0x00888888, 0x0087F780, 0x008767AB, 0x0086D905, 0x00864B8A, 0x0085BF37, 0x00853408, 0x0084A9F9, + 0x00842108, 0x00839930, 0x0083126E, 0x00828CBF, 0x00820820, 0x0081848D, 0x00810204, 0x00808080 +}; + +static const int32_t gpoly_divtable[][64] = { {-8388607,-8388607,-8388607,-8388607,-8388607,-8388607,-8388607,-8388607, -8388607,-8388607,-8388607,-8388607,-8388607,-8388607,-8388607,-8388607, -8388607,-8388607,-8388607,-8388607,-8388607,-8388607,-8388607,-8388607, @@ -323,899 +327,552 @@ static const long gpoly_divtable[][64] = { 50737, 52851, 54965, 57079, 59193, 61307, 63421, 65536,}, }; -static long factor_ca,factor_ba,factor_cb,factor_chk; -static long gploc_point_c; -static long shadingtop_deltashade; -static long maptexturetop_deltau,mapxveltop,maptexturetop_deltav,mapyveltop,scanlinescounter; -static long triangle_point_a_y,triangle_point_a_x,triangle_point_a_shade_x,triangle_point_a_shade,triangle_point_a_texture_u,triangle_point_a_texture_v; -static long triangle_point_b_y,triangle_point_b_x,triangle_point_b_shade_x,triangle_point_b_shade,triangle_point_b_texture_u,triangle_point_b_texture_v; -static long triangle_point_c_y,triangle_point_c_x,triangle_point_c_shade_x,triangle_point_c_shade,triangle_point_c_texture_u,triangle_point_c_texture_v; -static long shadingfactor_primary,shadingfactor_secondary,screenbuffer_linestride,g_shadeAccumulator,g_shadeAccumulatorNext,texture_xaccumulator_backup; -static uint8_t * screenbuffer_lineptr; -static long texture_xaccumulator_high_backup,texture_yaccumulator_low,texture_yaccumulator_high_combined,scanline_span_count,shade_interpolation_top_low,shade_interpolation_top_high_combined,mapxhstep,mapyhstep,shadehstep,texture_pointc_interpolation_low,texture_pointc_interpolation_high_combined; -static long shade_interpolation_bottom_low,shade_interpolation_bottom_high_combined,startpos_top_shade_texture_combined,startpos_top_texturex_texturey_combined,startpos_bottom_shade_texture_combined,startpos_bottom_texturex_texturey_combined,current_scanline_xposition,shade_interpolation_pointc_high,shade_interpolation_pointc_low,texture_xaccumulator_low; -static long shade_interpolation_bottom_combined,startposshadetop,startposmapxtop,startposmapytop,startposshadebottom,startposmapxbottom,startposmapybottom,texture_xaccumulator_low_backup,shade_interpolation_top_shifted,texture_delta_bottom_high_combined; /******************************************************************************/ -#undef __ROL4__ -#define __ROL4__(val, shift) \ - (uint32_t)( ((uint32_t)(val) << (shift)) | ((uint32_t)(val) >> (32 - (shift))) ) +// Triangle vertex info. These are sorted in Y direction, A is on top. +static int32_t vertex_a_y, vertex_a_x, vertex_a_shade, vertex_a_texture_u, vertex_a_texture_v; +static int32_t vertex_b_y, vertex_b_x, vertex_b_shade, vertex_b_texture_u, vertex_b_texture_v; +static int32_t vertex_c_y, vertex_c_x, vertex_c_shade, vertex_c_texture_u, vertex_c_texture_v; +static bool vertex_b_on_left_side; + +// Slope between vertices in 16.16 (horizontal pixels per scanline). +static int32_t slope_ac, slope_ab, slope_bc, slope_left, slope_right; + +// Texture mapping deltas in 16.16 for Shade, texture U, texture V. +static int32_t delta_s_x, delta_u_x, delta_v_x; +static int32_t delta_s_y_top, delta_u_y_top, delta_v_y_top; // Along edge AB or AC +static int32_t delta_s_y_bottom, delta_u_y_bottom, delta_v_y_bottom; // Along edge BC + +// Bit layout for packed texture coordinates (fractional part in lowercase): +// msb lsb +// Shade : 00000000 0000FFff ff000000 +// Texture V: 000000FF ffff0000 00000000 +// Texture U: FFffff00 00000000 00000000 +// +// This layout was apparently chosen to minimize shifting in the inner loop: +// Texture U and V can be combined to an array index by a single rotate, and the +// shade index is already in the right position to combine with the texture +// color, which goes in the lower byte. +typedef struct { uint32_t word[3]; } TexCoord; + +// Same as above, but the least significant word is omitted. +typedef struct { uint32_t word[2]; } TexCoordShort; + +// Start position for vertex A +static TexCoordShort texcoord_start_a; +// Start position for vertex B +static TexCoordShort texcoord_start_b; +// X delta used in the inner loop, shorter to save one ADC instruction +static TexCoordShort texcoord_delta_x; +// X delta used for X-clipping +static TexCoord texcoord_delta_x_exact; +// Currently used Y delta (set to one of the values below) +static TexCoord texcoord_delta_y; +// Y delta along the top left edge: either AB or AC +static TexCoord texcoord_delta_y_top; +// Y delta along edge BC +static TexCoord texcoord_delta_y_bottom; + +struct GPolyDrawState +{ + TexCoord texcoord; + int32_t x_left; // 16.16 + int32_t x_right; // 16.16 + int x; + int y; + int y_end; + uint8_t *dst_line; +}; -static inline uint64_t CFADD64(uint64_t a_low, uint64_t b_low) +/******************************************************************************/ + +// Rotate left +static uint32_t rol32(uint32_t val, uint8_t shift) { - // Return 1 if (a_low + b_low) overflows 32 bits - uint64_t sum = a_low + b_low; - return (sum < a_low) ? 1u : 0u; + return (val << shift) | (val >> (32 - shift)); } -static inline uint64_t PAIR64(uint32_t high32, uint32_t low32) { - return ((uint64_t)high32 << 32) | (uint64_t)low32; +// Add with carry +static uint32_t adc32(uint32_t lhs, uint32_t rhs, bool *carry) +{ +#ifdef __GNUC__ + uint32_t r; + const bool c = __builtin_add_overflow(lhs, rhs, &r); + *carry = c | __builtin_add_overflow(r, *carry, &r); + return r; +#else + #warning "missing optimized adc32 implementation for this compiler" + const uint32_t r1 = lhs + rhs; + const bool c = r1 < lhs; + const uint32_t r2 = r1 + *carry; + *carry = c | (r2 < r1); + return r2; +#endif } -void draw_gpoly_sub7a(); -void draw_gpoly_sub7b(); -void draw_gpoly_sub13(); -void draw_gpoly_sub14(); +// Subtract with carry +static uint32_t sbc32(uint32_t lhs, uint32_t rhs, bool *carry) +{ +#ifdef __GNUC__ + uint32_t r; + const bool c = __builtin_sub_overflow(lhs, rhs, &r); + *carry = c | __builtin_sub_overflow(r, *carry, &r); + return r; +#else + #warning "missing optimized sbc32 implementation for this compiler" + const uint32_t r1 = lhs - rhs; + const bool c = r1 > lhs; + const uint32_t r2 = r1 - *carry; + *carry = c | (r2 > r1); + return r2; +#endif +} -void draw_gpoly(struct PolyPoint *point_a, struct PolyPoint *point_b, struct PolyPoint *point_c) +static TexCoord texcoord_extend(TexCoordShort src) { - LOC_poly_screen = poly_screen; - LOC_vec_map = vec_map; - LOC_vec_screen = vec_screen; - LOC_vec_screen_width = vec_screen_width; - LOC_vec_window_width = vec_window_width; - LOC_vec_window_height = vec_window_height; - { // Check for outranged poly size - // test lengths - int edge_bc_length_x = point_b->X - point_c->X; - if ((edge_bc_length_x < -16383) || (edge_bc_length_x > 16383)) - return; - int edge_bc_length_y = point_b->Y - point_c->Y; - if ((edge_bc_length_y < -16383) || (edge_bc_length_y > 16383)) - return; - int edge_ba_length_x = point_b->X - point_a->X; - if ((edge_ba_length_x < -16383) || (edge_ba_length_x > 16383)) - return; - int edge_ca_length_y = point_c->Y - point_a->Y; - if ((edge_ca_length_y < -16383) || (edge_ca_length_y > 16383)) - return; - int edge_ca_length_x = point_c->X - point_a->X; - if ((edge_ca_length_x < -16383) || (edge_ca_length_x > 16383)) - return; - int edge_ba_length_y = point_b->Y - point_a->Y; - if ((edge_ba_length_y < -16383) || (edge_ba_length_y > 16383)) - return; - // test area - if ((edge_ca_length_x * edge_ba_length_y) - (edge_ba_length_x * edge_ca_length_y) >= 0) - return; - } - long exceeds_window = ((point_a->X | point_b->X | point_c->X) < 0) || (point_a->X > vec_window_width) || (point_b->X > vec_window_width) || (point_c->X > vec_window_width); - { // Reorder points - int min_y = point_a->Y; - struct PolyPoint* point_tmp; - if (min_y > point_b->Y) - { - min_y = point_b->Y; - point_tmp = point_a; - point_a = point_b; - point_b = point_tmp; - } - if (min_y > point_c->Y) - { - point_tmp = point_a; - point_a = point_c; - point_c = point_tmp; - } - if (point_b->Y > point_c->Y) - { - point_tmp = point_b; - point_b = point_c; - point_c = point_tmp; - } - } - // Check if y coord is same for all of them - if (point_a->Y == point_c->Y) - return; - { - long len_y = point_c->Y - point_a->Y; - long len_x = point_c->X - point_a->X; - if (len_y != 0) - { - if ((len_y < 0) || (len_y > 31) || (len_x < -32) || (len_x > 31)) - factor_ca = (len_x << 16) / len_y; - else - factor_ca = gpoly_divtable[len_y][len_x+32]; - } else - { - if (len_x < -32) - factor_ca = gpoly_divtable[len_y][-32+32]; - else - if (len_x > 31) - factor_ca = gpoly_divtable[len_y][31+32]; - else - factor_ca = gpoly_divtable[len_y][len_x+32]; - } - len_y = point_b->Y - point_a->Y; - len_x = point_b->X - point_a->X; - if (len_y != 0) - { - if ((len_y < 0) || (len_y > 31) || (len_x < -32) || (len_x > 31)) - factor_ba = (len_x << 16) / len_y; - else - factor_ba = gpoly_divtable[len_y][len_x+32]; - } else - { - if (len_x < -32) - factor_ba = gpoly_divtable[len_y][-32+32]; - else - if (len_x > 31) - factor_ba = gpoly_divtable[len_y][31+32]; - else - factor_ba = gpoly_divtable[len_y][len_x+32]; - } - len_y = point_c->Y - point_b->Y; - len_x = point_c->X - point_b->X; - if (len_y != 0) - { - if ((len_y < 0) || (len_y > 31) || (len_x < -32) || (len_x > 31)) - factor_cb = (len_x << 16) / len_y; - else - factor_cb = gpoly_divtable[len_y][len_x+32]; - } else - { - if (len_x < -32) - factor_cb = gpoly_divtable[len_y][-32+32]; - else - if (len_x > 31) - factor_cb = gpoly_divtable[len_y][31+32]; - else - factor_cb = gpoly_divtable[len_y][len_x+32]; - } - len_x = (point_a->X << 16) - (point_b->X << 16); - len_y = (point_b->Y - point_a->Y); - factor_chk = len_y * factor_ca + len_x; - } + TexCoord result; + result.word[0] = 0; + result.word[1] = src.word[0]; + result.word[2] = src.word[1]; + return result; +} - triangle_point_a_x = point_a->X; - triangle_point_a_y = point_a->Y; - triangle_point_a_shade_x = point_a->X << 16; - triangle_point_b_x = point_b->X; - triangle_point_b_y = point_b->Y; - triangle_point_b_shade_x = point_b->X << 16; - triangle_point_c_x = point_c->X; - triangle_point_c_y = point_c->Y; - triangle_point_c_shade_x = point_c->X << 16; - triangle_point_a_shade = point_a->S >> 16; - triangle_point_b_shade = point_b->S >> 16; - triangle_point_c_shade = point_c->S >> 16; - triangle_point_a_texture_u = point_a->U >> 16; - triangle_point_a_texture_v = point_a->V >> 16; - triangle_point_b_texture_u = point_b->U >> 16; - triangle_point_b_texture_v = point_b->V >> 16; - triangle_point_c_texture_u = point_c->U >> 16; - triangle_point_c_texture_v = point_c->V >> 16; - - if(vec_mode != 5) - { - ERRORLOG("unexpected vec_mode %d in draw_gpoly", vec_mode); - return; - } +static TexCoordShort texcoord_truncate(TexCoord src) +{ + TexCoordShort result; + result.word[0] = src.word[1]; + result.word[1] = src.word[2]; + return result; +} - draw_gpoly_sub7a(); - draw_gpoly_sub7b(); +static TexCoord texcoord_pack(uint32_t u, uint32_t v, uint32_t s) +{ + TexCoord result; + result.word[0] = (s << 24); + result.word[1] = (v << 16) | (uint16_t)(s >> 8); + result.word[2] = (u << 8) | (uint8_t )(v >> 16); + return result; +} - screenbuffer_linestride = LOC_vec_screen_width; - scanlinescounter = 2; - texture_xaccumulator_low = shade_interpolation_pointc_high; - texture_yaccumulator_low = texture_pointc_interpolation_low; - texture_yaccumulator_high_combined = texture_pointc_interpolation_high_combined; - if (factor_chk < 0) - { - shadingfactor_primary = factor_ca; - shadingfactor_secondary = factor_ba; - } else - { - shadingfactor_primary = factor_ba; - shadingfactor_secondary = factor_ca; - } +static TexCoord texcoord_pack_signed(int32_t u, int32_t v, int32_t s) +{ + // Sign-extend lower fields into higher fields. + v += s >> 31; + u += v >> 31; + return texcoord_pack(u, v, s); +} - if (exceeds_window) - { - draw_gpoly_sub13(); - } else // not exceeds_window - { - draw_gpoly_sub14(); - } +static TexCoord texcoord_add(TexCoord lhs, TexCoord rhs) +{ + bool cf = 0; + TexCoord result; + result.word[0] = adc32(lhs.word[0], rhs.word[0], &cf); + result.word[1] = adc32(lhs.word[1], rhs.word[1], &cf); + result.word[2] = adc32(lhs.word[2], rhs.word[2], &cf); + return result; } -void draw_gpoly_sub7a() +static TexCoord texcoord_subtract(TexCoord lhs, TexCoord rhs) { - int triangle_height_ac = triangle_point_c_y - triangle_point_a_y; - int cross_product_adjustment = (triangle_point_b_x - triangle_point_a_x) * (triangle_point_c_y - triangle_point_a_y); - if (factor_chk >= 0) - cross_product_adjustment -= 2 * triangle_height_ac; + bool cf = 0; + TexCoord result; + result.word[0] = sbc32(lhs.word[0], rhs.word[0], &cf); + result.word[1] = sbc32(lhs.word[1], rhs.word[1], &cf); + result.word[2] = sbc32(lhs.word[2], rhs.word[2], &cf); + return result; +} - int triangle_area_determinant = (triangle_point_c_x - triangle_point_a_x) * (triangle_point_b_y - triangle_point_a_y) - (triangle_height_ac + cross_product_adjustment); +static uint64_t texcoord_as_uint64(TexCoordShort src) +{ + return (((uint64_t)src.word[1]) << 32) | src.word[0]; +} - if (triangle_area_determinant != 0) +static bool validate_triangle(void) +{ + const int ab_x = vertex_b_x - vertex_a_x; + const int ab_y = vertex_b_y - vertex_a_y; + const int ac_x = vertex_c_x - vertex_a_x; + const int ac_y = vertex_c_y - vertex_a_y; + const int bc_x = vertex_c_x - vertex_b_x; + const int bc_y = vertex_c_y - vertex_b_y; + + // Zero height, skip it. + if (ac_y == 0) + return false; + + // Range check [-16384, 16383] to prevent arithmetic overflow. + if (( (1u + (unsigned)(ab_x >> 14)) + | (1u + (unsigned)(ab_y >> 14)) + | (1u + (unsigned)(ac_x >> 14)) + | (1u + (unsigned)(ac_y >> 14)) + | (1u + (unsigned)(bc_x >> 14)) + | (1u + (unsigned)(bc_y >> 14))) > 1u) { - int division_factor = 0x7FFFFFFF / triangle_area_determinant; - int triangle_height_ac_copy = triangle_point_c_y - triangle_point_a_y; - int triangle_height_ab = triangle_point_b_y - triangle_point_a_y; + return false; + } - // First component: shadehstep - { - int64_t num = (int64_t)triangle_height_ab * (triangle_point_c_shade - triangle_point_a_shade) - (int64_t)triangle_height_ac_copy * (triangle_point_b_shade - triangle_point_a_shade); - int64_t result = 2 * num * division_factor; - shadehstep = (int)((result >> 16) + ((result < 0) ? 1 : 0)); - } + return true; +} - // Second component: mapxhstep - { - int64_t num = (int64_t)triangle_height_ab * (triangle_point_c_texture_u - triangle_point_a_texture_u) - (int64_t)triangle_height_ac_copy * (triangle_point_b_texture_u - triangle_point_a_texture_u); - int64_t result = 2 * num * division_factor; - mapxhstep = (int)((result >> 16) + ((result < 0) ? 1 : 0)); - } +static int32_t slope_div(int dx, int dy) +{ + assert(dy >= 0); - // Third component: mapyhstep - { - int64_t num = (int64_t)triangle_height_ab * (triangle_point_c_texture_v - triangle_point_a_texture_v) - (int64_t)triangle_height_ac_copy * (triangle_point_b_texture_v - triangle_point_a_texture_v); - int64_t result = 2 * num * division_factor; - mapyhstep = (int)((result >> 16) + ((result < 0) ? 1 : 0)); - } + const int idx_x = clamp(dx + 32, 0, 63); + + if ((dy != 0) && ((dy > 31) || (dx + 32 != idx_x))) + { + return (dx << 16) / dy; } else { - shadehstep = 0; - mapxhstep = 0; - mapyhstep = 0; + return gpoly_divtable[dy][idx_x]; } } -void draw_gpoly_sub7b_block1(void); -void draw_gpoly_sub7b_block2(void); -void draw_gpoly_sub7b_block3(void); +static void calculate_slopes(void) +{ + const int ab_x = vertex_b_x - vertex_a_x; + const int ab_y = vertex_b_y - vertex_a_y; + const int ac_x = vertex_c_x - vertex_a_x; + const int ac_y = vertex_c_y - vertex_a_y; + const int bc_x = vertex_c_x - vertex_b_x; + const int bc_y = vertex_c_y - vertex_b_y; + + slope_ab = slope_div(ab_x, ab_y); + slope_ac = slope_div(ac_x, ac_y); + slope_bc = slope_div(bc_x, bc_y); + + // Check if vertex B is to the left or right of line AC. + vertex_b_on_left_side = (ab_y * slope_ac) > (ab_x << 16); + + slope_left = vertex_b_on_left_side ? slope_ab : slope_ac; + slope_right = vertex_b_on_left_side ? slope_ac : slope_ab; +} -void draw_gpoly_sub7b() +// Return 1.0/val (actually 0.999...) in signed 1.31, argument must be positive. +static int32_t reciprocal(uint32_t val) { + if (val < 256) + return gpoly_reptable[val]; + else + return 0x7FFFFFFFul / val; +} - if (factor_chk < 0) +// Multiply signed integer (32.0) by signed reciprocal (1.31), shift to 16.16. +static int32_t mul_shift(int32_t val, int32_t rcp) +{ + const int64_t result = (int64_t)val * rcp; + const int32_t shifted = result >> 15; + const int32_t sign = result >> 63; + return shifted - sign; +} + +static void calculate_texture_mapping(void) +{ + const int ab_x = vertex_b_x - vertex_a_x; + const int ab_y = vertex_b_y - vertex_a_y; + const int ac_x = vertex_c_x - vertex_a_x; + const int ac_y = vertex_c_y - vertex_a_y; + const int bc_y = vertex_c_y - vertex_b_y; + + const int ab_u = vertex_b_texture_u - vertex_a_texture_u; + const int ac_u = vertex_c_texture_u - vertex_a_texture_u; + const int bc_u = vertex_c_texture_u - vertex_b_texture_u; + const int ab_v = vertex_b_texture_v - vertex_a_texture_v; + const int ac_v = vertex_c_texture_v - vertex_a_texture_v; + const int bc_v = vertex_c_texture_v - vertex_b_texture_v; + const int ab_s = vertex_b_shade - vertex_a_shade; + const int ac_s = vertex_c_shade - vertex_a_shade; + const int bc_s = vertex_c_shade - vertex_b_shade; + + // Calculate texture deltas for X step. + + const int ab_x_biased = ab_x + (vertex_b_on_left_side ? -1 : +1); + const int cross_product = ab_y * ac_x - ac_y * ab_x_biased; + + if (cross_product != 0) { - draw_gpoly_sub7b_block2(); + const int32_t factor = 0x7FFFFFFF / cross_product; + + delta_u_x = mul_shift(ab_y * ac_u - ac_y * ab_u, factor); + delta_v_x = mul_shift(ab_y * ac_v - ac_y * ab_v, factor); + delta_s_x = mul_shift(ab_y * ac_s - ac_y * ab_s, factor); } else { - draw_gpoly_sub7b_block1(); + delta_u_x = 0; + delta_v_x = 0; + delta_s_x = 0; } - draw_gpoly_sub7b_block3(); + // Calculate texture deltas for Y step. + if (vertex_b_on_left_side) + { + const int32_t factor1 = reciprocal(ab_y); + delta_u_y_top = mul_shift(ab_u, factor1); + delta_v_y_top = mul_shift(ab_v, factor1); + delta_s_y_top = mul_shift(ab_s, factor1); + + const int32_t factor2 = reciprocal(bc_y); + delta_u_y_bottom = mul_shift(bc_u, factor2); + delta_v_y_bottom = mul_shift(bc_v, factor2); + delta_s_y_bottom = mul_shift(bc_s, factor2); + } + else + { + const int32_t factor = reciprocal(ac_y); + delta_u_y_top = mul_shift(ac_u, factor); + delta_v_y_top = mul_shift(ac_v, factor); + delta_s_y_top = mul_shift(ac_s, factor); + } } -static inline int32_t shift_mul(int32_t delta, int32_t scale) +static void pack_texcoords(void) { - // 64-bit result of signed multiplication - int64_t result = (int64_t)delta * scale; - - // Split into low and high 32-bit words - uint32_t lo = (uint32_t)(result & 0xFFFFFFFF); - uint32_t hi = (uint32_t)((uint64_t)result >> 32); - - // Overwrite low 16 bits of lo with low 16 bits of hi - lo = (lo & 0xFFFF0000) | (hi & 0x0000FFFF); - - // Rotate left by 16 bits - uint32_t rotated = (lo << 16) | (lo >> 16); + { + const int32_t u = delta_u_x; + const int32_t v = delta_v_x; + const int32_t s = delta_s_x; + texcoord_delta_x_exact = texcoord_pack_signed(u, v, s); + } + { + // Shade field is truncated by 8 bits, round towards 0. + const int32_t u = delta_u_x; + const int32_t v = delta_v_x; + const int32_t s = delta_s_x - (delta_s_x >> 31 << 8); + texcoord_delta_x = texcoord_truncate(texcoord_pack_signed(u, v, s)); + } + { + const int32_t u = delta_u_y_top; + const int32_t v = delta_v_y_top; + const int32_t s = delta_s_y_top; + texcoord_delta_y_top = texcoord_pack_signed(u, v, s); + } + { + const int32_t u = vertex_a_texture_u << 16; + const int32_t v = vertex_a_texture_v << 16; + const int32_t s = vertex_a_shade << 16; + texcoord_start_a = texcoord_truncate(texcoord_pack(u, v, s)); + } - // If result is negative, increment - if ((int32_t)rotated < 0) - rotated++; + if (vertex_b_on_left_side) + { + { + const int32_t u = delta_u_y_bottom; + const int32_t v = delta_v_y_bottom; + const int32_t s = delta_s_y_bottom; + texcoord_delta_y_bottom = texcoord_pack_signed(u, v, s); + } + { + const int32_t u = vertex_b_texture_u << 16; + const int32_t v = vertex_b_texture_v << 16; + const int32_t s = vertex_b_shade << 16; + texcoord_start_b = texcoord_truncate(texcoord_pack(u, v, s)); + } + } - return (int32_t)rotated; + texcoord_delta_y = texcoord_delta_y_top; } -void draw_gpoly_sub7b_block1(void) +static void draw_gpoly_line(uint8_t *restrict pixel_dst, int32_t length, TexCoord texcoord) { - int32_t dy_ab = triangle_point_b_y - triangle_point_a_y; - int32_t scale1 = (dy_ab > 255) ? (0x7FFFFFFF / dy_ab) : gpoly_reptable[dy_ab]; - - gploc_point_c = shift_mul(2 * (triangle_point_b_shade - triangle_point_a_shade), scale1); - mapxveltop = shift_mul(2 * (triangle_point_b_texture_u - triangle_point_a_texture_u), scale1); - mapyveltop = shift_mul(2 * (triangle_point_b_texture_v - triangle_point_a_texture_v), scale1); + const uint8_t *const restrict texture = vec_map; + const uint8_t *const restrict fade_table = render_fade_tables; + const uint64_t texture_step = texcoord_as_uint64(texcoord_delta_x); + uint64_t texture_position = texcoord_as_uint64(texcoord_truncate(texcoord)); - int32_t dy_bc = triangle_point_c_y - triangle_point_b_y; - int32_t scale2 = (dy_bc > 255) ? (0x7FFFFFFF / dy_bc) : gpoly_reptable[dy_bc]; - - shadingtop_deltashade = shift_mul(2 * (triangle_point_c_shade - triangle_point_b_shade), scale2); - maptexturetop_deltau = shift_mul(2 * (triangle_point_c_texture_u - triangle_point_b_texture_u), scale2); - maptexturetop_deltav = shift_mul(2 * (triangle_point_c_texture_v - triangle_point_b_texture_v), scale2); + for (int i = 0; i < length; i++) + { + const uint16_t uv = rol32(texture_position >> 32, 8); + const uint16_t shade = texture_position & 0xFF00; + const uint8_t texel = texture[uv]; + pixel_dst[i] = fade_table[texel | shade]; + texture_position += texture_step; + } } -static inline int rol16_from_product(int64_t product) +ALWAYS_INLINE +static void next_line(struct GPolyDrawState *state) { - uint32_t eax = (uint32_t)(product & 0xFFFFFFFF); - uint16_t dx = (uint16_t)(product >> 32); - eax = (dx << 16) | (eax >> 16); // this mimics: movw dx, ax; rol eax, 16 - if ((int32_t)eax < 0) - ++eax; - return eax; + state->texcoord = texcoord_add(state->texcoord, texcoord_delta_y); + state->x -= state->x_left >> 16; + state->x_left += slope_left; + state->x_right += slope_right; + state->x += state->x_left >> 16; + state->dst_line += vec_screen_width; + state->y += 1; } -void draw_gpoly_sub7b_block2(void) +ALWAYS_INLINE +static void draw_gpoly_clipped_half(struct GPolyDrawState *state) { - int dy = triangle_point_c_y - triangle_point_a_y; - int factor = (dy > 255) ? (0x7FFFFFFF / dy) : gpoly_reptable[dy]; + for (; state->y < state->y_end; next_line(state)) + { + if (state->y < 0) + continue; + + const int x_left_int = max(state->x_left >> 16, 0); + const int x_right_int = min(state->x_right >> 16, vec_window_width); + const int length = x_right_int - x_left_int; + uint8_t *const dst = state->dst_line + x_left_int; - int delta = triangle_point_c_shade - triangle_point_a_shade; - int64_t product = (int64_t)factor * (delta * 2); - gploc_point_c = rol16_from_product(product); + for (; x_left_int > state->x; ++state->x) + state->texcoord = texcoord_add(state->texcoord, texcoord_delta_x_exact); - delta = triangle_point_c_texture_u - triangle_point_a_texture_u; - product = (int64_t)factor * (delta * 2); - mapxveltop = rol16_from_product(product); + for (; x_left_int < state->x; --state->x) + state->texcoord = texcoord_subtract(state->texcoord, texcoord_delta_x_exact); - delta = triangle_point_c_texture_v - triangle_point_a_texture_v; - product = (int64_t)factor * (delta * 2); - mapyveltop = rol16_from_product(product); + draw_gpoly_line(dst, length, state->texcoord); + } } -void draw_gpoly_sub7b_block3(void) +ALWAYS_INLINE +static void draw_gpoly_whole_half(struct GPolyDrawState *state) { - //---------------------------------------------------------------- - // 1) Write the six “startpos…” values exactly as in the ASM: - //---------------------------------------------------------------- - startposshadetop = (uint32_t)( (int32_t)triangle_point_a_shade << 16 ); - startposmapxtop = (uint32_t)( (int32_t)triangle_point_a_texture_u << 16 ); - startposmapytop = (uint32_t)( (int32_t)triangle_point_a_texture_v << 16 ); - startposshadebottom = (uint32_t)( (int32_t)triangle_point_b_shade << 16 ); - startposmapxbottom = (uint32_t)( (int32_t)triangle_point_b_texture_u << 16 ); - startposmapybottom = (uint32_t)( (int32_t)triangle_point_b_texture_v << 16 ); - - //---------------------------------------------------------------- - // 2) TOP‐SHADING INTERPOLATION → shade_interpolation_top_shifted, shade_interpolation_top_low, shade_interpolation_top_high_combined - //---------------------------------------------------------------- + for (; state->y < state->y_end; next_line(state)) { + if (state->y < 0) + continue; - int32_t m_y = (int32_t)mapyhstep; - int32_t s = (int32_t)(shadehstep >> 8); + const int x_left_int = state->x_left >> 16; + const int x_right_int = state->x_right >> 16; + const int length = x_right_int - x_left_int; + uint8_t *const dst = state->dst_line + x_left_int; - // Build the 48-bit fixed‐point value in a 64‐bit container: - int64_t val = ((int64_t)m_y << 16); + draw_gpoly_line(dst, length, state->texcoord); + } +} - // If (shadehstep>>8) is negative, emulate the “andl $0x0FFFF; subl $0x10000; sbbl $0,EDX” exactly: - if (s < 0) { - // EBX = (uint16_t)s - uint32_t unsigned_lower_bits = (uint32_t)( (uint16_t)s ); - // EAX -= 0x10000 → val -= 0x10000 - val -= ((int64_t)0x10000); - // Add back (uint16_t)s - val += (int64_t)unsigned_lower_bits; - } - else { - // s ≥ 0 → just add s - val += (int64_t)s; - } +static void draw_gpoly_clipped(void) +{ + struct GPolyDrawState state; - // Now split val into “low32bits” = EAX and “high32bits” = EDX (signed): - uint32_t low32 = (uint32_t)val; - int32_t high32 = (int32_t)( val >> 32 ); // arithmetic shift + state.texcoord = texcoord_extend(texcoord_start_a); + state.x_left = vertex_a_x << 16; + state.x_right = vertex_a_x << 16; + state.x = vertex_a_x; + state.y = vertex_a_y; + state.y_end = min(vertex_b_y, vec_window_height); + state.dst_line = &vec_screen[vec_screen_width * state.y]; - // shade_interpolation_top_shifted ← (shadehstep << 24): - shade_interpolation_top_shifted = (uint32_t)( (int32_t)shadehstep << 24 ); - // shade_interpolation_top_low ← low‐word (EAX): - shade_interpolation_top_low = low32; + draw_gpoly_clipped_half(&state); - // Next: shade_interpolation_top_high_combined = ( (mapxhstep + (high32<0 ? -1 : 0)) << 8 ) | (high32 & 0xFF) - int32_t mx = (int32_t)mapxhstep; - if (high32 < 0) { - mx -= 1; - } - shade_interpolation_top_high_combined = ( (uint32_t)mx << 8 ) | ( (uint32_t)high32 & 0xFF ); + if (vertex_b_on_left_side) + { + slope_left = slope_bc; + state.x_left = vertex_b_x << 16; + state.x = vertex_b_x; + texcoord_delta_y = texcoord_delta_y_bottom; + state.texcoord = texcoord_extend(texcoord_start_b); } - - //---------------------------------------------------------------- - // 3) BOTTOM‐SHADING INTERPOLATION → shade_interpolation_bottom_combined, shade_interpolation_bottom_high_combined - //---------------------------------------------------------------- + else { + slope_right = slope_bc; + state.x_right = vertex_b_x << 16; + } - int32_t m_y = (int32_t)mapyhstep; - int32_t s = (int32_t)(shadehstep >> 8); + state.y = vertex_b_y; + state.y_end = min(vertex_c_y, vec_window_height); - int64_t val = ((int64_t)m_y << 16); + draw_gpoly_clipped_half(&state); +} - if (s < 0) { - // EBX = (uint16_t)s - uint32_t unsigned_lower_bits = (uint32_t)((uint16_t)s); - // EAX -= 0x0FFFF - val -= ((int64_t)0x0FFFF); - // add EBX - val += (int64_t)unsigned_lower_bits; - } - else { - val += (int64_t)s; - } +static void draw_gpoly_whole(void) +{ + // state.x is not used here. + struct GPolyDrawState state; - uint32_t low32 = (uint32_t)val; - int32_t high32 = (int32_t)(val >> 32); + state.texcoord = texcoord_extend(texcoord_start_a); + state.x_left = vertex_a_x << 16; + state.x_right = vertex_a_x << 16; + state.y = vertex_a_y; + state.y_end = min(vertex_b_y, vec_window_height); + state.dst_line = &vec_screen[vec_screen_width * state.y]; - // Store EAX→shade_interpolation_bottom_combined - shade_interpolation_bottom_combined = low32; + draw_gpoly_whole_half(&state); - // shade_interpolation_bottom_high_combined = ( (mapxhstep + (high32<0 ? -1 : 0)) << 8 ) | (high32 & 0xFF) - int32_t mx = (int32_t)mapxhstep; - if (high32 < 0) { - mx -= 1; - } - shade_interpolation_bottom_high_combined = ( (uint32_t)mx << 8 ) | ( (uint32_t)high32 & 0xFF ); - } - - //---------------------------------------------------------------- - // 4) TOP “POINT‐C” INTERPOLATION → shade_interpolation_pointc_high, texture_pointc_interpolation_low, texture_pointc_interpolation_high_combined - //---------------------------------------------------------------- + if (vertex_b_on_left_side) { - int32_t m_y = (int32_t)mapyveltop; - int64_t val = ((int64_t)m_y << 16); - - // Build shade_interpolation_pointc_high = (gploc_point_c << 24) - int32_t ptc = (int32_t)gploc_point_c; - shade_interpolation_pointc_high = (uint32_t)(ptc << 24); - - int32_t s = (int32_t)(ptc >> 8); - if (s < 0) { - uint32_t unsigned_lower_bits = (uint32_t)((uint16_t)s); - val -= ((int64_t)0x10000); - val += (int64_t)unsigned_lower_bits; - } - else { - val += (int64_t)s; - } - - uint32_t low32 = (uint32_t)val; - int32_t high32 = (int32_t)(val >> 32); - texture_pointc_interpolation_low = low32; - - // texture_pointc_interpolation_high_combined = ( (mapxveltop + (high32<0 ? -1 : 0)) << 8 ) | (high32 & 0xFF) - int32_t mx = (int32_t)mapxveltop; - if (high32 < 0) { - mx -= 1; - } - texture_pointc_interpolation_high_combined = ( (uint32_t)mx << 8 ) | ( (uint32_t)high32 & 0xFF ); + slope_left = slope_bc; + state.x_left = vertex_b_x << 16; + texcoord_delta_y = texcoord_delta_y_bottom; + state.texcoord = texcoord_extend(texcoord_start_b); } - - //---------------------------------------------------------------- - // 5) COMBINE STARTPOS FOR TOP: → startpos_top_shade_texture_combined, startpos_top_texturex_texturey_combined - //---------------------------------------------------------------- + else { - uint32_t sp_y = startposmapytop; - uint32_t sp_s = startposshadetop; - uint32_t sp_x = startposmapxtop; - - // startpos_top_shade_texture_combined = (sp_s >> 8) | (sp_y << 16) - startpos_top_shade_texture_combined = ( (uint32_t)sp_s >> 8 ) | ( (uint32_t)sp_y << 16 ); - - // startpos_top_texturex_texturey_combined = (sp_x << 8) | ((sp_y >> 16) & 0xFF) - startpos_top_texturex_texturey_combined = ( (uint32_t)sp_x << 8 ) | ( ((uint32_t)sp_y >> 16) & 0xFF ); + slope_right = slope_bc; + state.x_right = vertex_b_x << 16; } - //---------------------------------------------------------------- - // 6) “IF (factor_chk >= 0) THEN…” → BOTTOM “POINT‐C” BLOCK - //---------------------------------------------------------------- - if ( (int32_t)factor_chk >= 0 ) - { - int32_t m_y = (int32_t)maptexturetop_deltav; - int64_t val = ((int64_t)m_y << 16); - - // shade_interpolation_pointc_low = (shadingtop_deltashade << 24) - int32_t signed_shade_delta = (int32_t)shadingtop_deltashade; - shade_interpolation_pointc_low = (uint32_t)(signed_shade_delta << 24); - - int32_t shifted_shade_delta = (int32_t)(signed_shade_delta >> 8); - if (shifted_shade_delta < 0) { - uint32_t unsigned_lower_bits = (uint32_t)((uint16_t)shifted_shade_delta); - val -= ((int64_t)0x10000); - val += (int64_t)unsigned_lower_bits; - } - else { - val += (int64_t)shifted_shade_delta; - } - - uint32_t low32 = (uint32_t)val; - int32_t high32 = (int32_t)(val >> 32); - shade_interpolation_bottom_low = low32; + state.y = vertex_b_y; + state.y_end = min(vertex_c_y, vec_window_height); - // texture_delta_bottom_high_combined = ( (maptexturetop_deltau + (high32<0 ? -1 : 0)) << 8 ) | (high32 & 0xFF) - int32_t mx = (int32_t)maptexturetop_deltau; - if (high32 < 0) { - mx -= 1; - } - texture_delta_bottom_high_combined = ( (uint32_t)mx << 8 ) | ( (uint32_t)high32 & 0xFF ); - - //---------------------------------------------------------------- - // Finally, combine “bottom” startpos → startpos_bottom_shade_texture_combined, startpos_bottom_texturex_texturey_combined - //---------------------------------------------------------------- - { - uint32_t sp_yb = startposmapybottom; - uint32_t sp_sb = startposshadebottom; - uint32_t sp_xb = startposmapxbottom; - - // startpos_bottom_shade_texture_combined = (sp_sb >> 8) | (sp_yb << 16) - startpos_bottom_shade_texture_combined = ( (uint32_t)sp_sb >> 8 ) | ( (uint32_t)sp_yb << 16 ); - - // startpos_bottom_texturex_texturey_combined = (sp_xb << 8) | ((sp_yb >> 16) & 0xFF) - startpos_bottom_texturex_texturey_combined = ( (uint32_t)sp_xb << 8 ) | ( ((uint32_t)sp_yb >> 16) & 0xFF ); - } - } + draw_gpoly_whole_half(&state); } -static void draw_gpoly_span(int32_t pixel_span_len, uint32_t texture_position_low, uint32_t texture_position_high, uint8_t *restrict pixel_dst) +void draw_gpoly(struct PolyPoint *point_a, struct PolyPoint *point_b, struct PolyPoint *point_c) { - uint64_t texture_position; - uint64_t texture_step; - uint32_t texture_index; - const uint8_t *restrict texture_map; - const uint8_t *restrict fade_table; - int32_t i; - - if (pixel_dst < LOC_vec_screen) { + if (vec_mode != VM_QuadTextured) + { + ERRORLOG("unexpected vec_mode %d in draw_gpoly", vec_mode); return; } - texture_position = PAIR64(texture_position_high, texture_position_low); - texture_step = PAIR64(shade_interpolation_bottom_high_combined, shade_interpolation_bottom_combined); - texture_index = __ROL4__(texture_position_high, 8) & 0xFFFF; - texture_map = LOC_vec_map; - fade_table = render_fade_tables; - for (i = 0; i < pixel_span_len; i++) { - pixel_dst[i] = fade_table[texture_map[texture_index] | (texture_position & 0xFF00)]; - texture_index = __ROL4__(texture_position >> 32, 8) & 0xFFFF; - texture_position += texture_step; - } -} - -void draw_gpoly_sub13() -{ - int tex_x_accum_low; // ecx - int tex_x_accum_high; // edx - int tex_x_accum_combined; // ebx - uchar *screen_line_ptr; // edi - int clamped_by; // eax - bool skip_render; // zf - int spanCount; // eax - int xStart; // esi - int shadeAccumulator; // eax - int shadeAccumulatorNext; // ebp - int scanline_y_esi; // esi - bool range_check_passed; // cc - int shade_position_adjustment; // esi - int shade_pixel_position; // eax - int next_shade_pixel_position; // ebp - int pixel_span_len; // ebp - bool carry_flag; // cf - int clipped_end_y; // eax - int clipped_triangle_end_y; // eax - - tex_x_accum_low = 0; - tex_x_accum_high = startpos_top_shade_texture_combined; - tex_x_accum_combined = startpos_top_texturex_texturey_combined; - screen_line_ptr = (uchar *)(LOC_vec_screen + triangle_point_a_y * LOC_vec_screen_width); - if ( triangle_point_a_y <= LOC_vec_window_height ) - { - clamped_by = triangle_point_b_y; - if ( triangle_point_b_y > LOC_vec_window_height ) - clamped_by = LOC_vec_window_height; - spanCount = clamped_by - triangle_point_a_y; - skip_render = spanCount == 0; - scanline_span_count = spanCount; - xStart = triangle_point_a_x; - current_scanline_xposition = triangle_point_a_x; - shadeAccumulator = triangle_point_a_shade_x; - shadeAccumulatorNext = triangle_point_a_shade_x; - if ( !skip_render ) + // Sort points: a.Y < b.Y < c.Y + struct PolyPoint *point_tmp; + if (point_a->Y > point_b->Y) { - scanline_y_esi = triangle_point_a_y; - if ( triangle_point_a_y < 0 ) - goto SKEWED_SCAN_ADJUST; - xStart = current_scanline_xposition; - goto REMAINDER_SCANLINE_STEP; + point_tmp = point_a; + point_a = point_b; + point_b = point_tmp; } - while ( 1 ) + if (point_a->Y > point_c->Y) { - if ( !--scanlinescounter ) - return; - g_shadeAccumulator = shadeAccumulator; - if ( factor_chk >= 0 ) - break; - shadingfactor_secondary = factor_cb; - shadeAccumulatorNext = triangle_point_b_shade_x; - clipped_triangle_end_y = triangle_point_c_y; - if ( triangle_point_c_y > LOC_vec_window_height ) - clipped_triangle_end_y = LOC_vec_window_height; - range_check_passed = clipped_triangle_end_y <= triangle_point_b_y; - scanline_span_count = clipped_triangle_end_y - triangle_point_b_y; - shadeAccumulator = g_shadeAccumulator; - if ( range_check_passed ) - return; - current_scanline_xposition = xStart; - scanline_y_esi = triangle_point_b_y; - if ( triangle_point_b_y >= 0 ) - { - xStart = current_scanline_xposition; - do - { -REMAINDER_SCANLINE_STEP: - g_shadeAccumulator = shadeAccumulator; - g_shadeAccumulatorNext = shadeAccumulatorNext; - screenbuffer_lineptr = screen_line_ptr; - shade_pixel_position = shadeAccumulator >> 16; - if ( shade_pixel_position < 0 ) - { - if ( xStart ) - { - if ( xStart >= 0 ) - { - do - { - carry_flag = PAIR64(tex_x_accum_high, tex_x_accum_low) < PAIR64(shade_interpolation_top_low, shade_interpolation_top_shifted); - tex_x_accum_high = (PAIR64(tex_x_accum_high, tex_x_accum_low) - PAIR64(shade_interpolation_top_low, shade_interpolation_top_shifted)) >> 32; - tex_x_accum_low -= shade_interpolation_top_shifted; - tex_x_accum_combined -= carry_flag + shade_interpolation_top_high_combined; - --xStart; - } - while ( xStart ); - } - else - { - do - { - carry_flag = CFADD64(PAIR64(shade_interpolation_top_low, shade_interpolation_top_shifted), PAIR64(tex_x_accum_high, tex_x_accum_low)); - tex_x_accum_high = (PAIR64(shade_interpolation_top_low, shade_interpolation_top_shifted) + PAIR64(tex_x_accum_high, tex_x_accum_low)) >> 32; - tex_x_accum_low += shade_interpolation_top_shifted; - tex_x_accum_combined += shade_interpolation_top_high_combined + carry_flag; - ++xStart; - } - while ( xStart ); - } - } - } - else if ( shade_pixel_position > xStart ) - { - do - { - carry_flag = CFADD64(PAIR64(shade_interpolation_top_low, shade_interpolation_top_shifted), PAIR64(tex_x_accum_high, tex_x_accum_low)); - tex_x_accum_high = (PAIR64(shade_interpolation_top_low, shade_interpolation_top_shifted) + PAIR64(tex_x_accum_high, tex_x_accum_low)) >> 32; - tex_x_accum_low += shade_interpolation_top_shifted; - tex_x_accum_combined += shade_interpolation_top_high_combined + carry_flag; - ++xStart; - } - while ( shade_pixel_position > xStart ); - } - else - { - for ( ; shade_pixel_position < xStart; --xStart ) - { - carry_flag = PAIR64(tex_x_accum_high, tex_x_accum_low) < PAIR64(shade_interpolation_top_low, shade_interpolation_top_shifted); - tex_x_accum_high = (PAIR64(tex_x_accum_high, tex_x_accum_low) - PAIR64(shade_interpolation_top_low, shade_interpolation_top_shifted)) >> 32; - tex_x_accum_low -= shade_interpolation_top_shifted; - tex_x_accum_combined -= carry_flag + shade_interpolation_top_high_combined; - } - } - current_scanline_xposition = xStart; - texture_xaccumulator_low_backup = tex_x_accum_low; - texture_xaccumulator_high_backup = tex_x_accum_high; - texture_xaccumulator_backup = tex_x_accum_combined; - next_shade_pixel_position = shadeAccumulatorNext >> 16; - if ( next_shade_pixel_position > LOC_vec_window_width ) - next_shade_pixel_position = LOC_vec_window_width; - range_check_passed = next_shade_pixel_position <= xStart; - pixel_span_len = next_shade_pixel_position - xStart; - if ( !range_check_passed ) - { - draw_gpoly_span(pixel_span_len,tex_x_accum_high,tex_x_accum_combined,screenbuffer_lineptr + xStart); - } - shade_position_adjustment = current_scanline_xposition - (g_shadeAccumulator >> 16); - shadeAccumulatorNext = shadingfactor_secondary + g_shadeAccumulatorNext; - g_shadeAccumulator += shadingfactor_primary; - xStart = (g_shadeAccumulator >> 16) + shade_position_adjustment; - shadeAccumulator = g_shadeAccumulator; - tex_x_accum_high = (PAIR64(texture_yaccumulator_low, texture_xaccumulator_low) + PAIR64(texture_xaccumulator_high_backup, texture_xaccumulator_low_backup)) >> 32; - tex_x_accum_low = texture_xaccumulator_low + texture_xaccumulator_low_backup; - tex_x_accum_combined = texture_yaccumulator_high_combined - + CFADD64(PAIR64(texture_yaccumulator_low, texture_xaccumulator_low), PAIR64(texture_xaccumulator_high_backup, texture_xaccumulator_low_backup)) - + texture_xaccumulator_backup; - screen_line_ptr = &screenbuffer_lineptr[screenbuffer_linestride]; - --scanline_span_count; - } - while ( scanline_span_count ); - continue; - } -SKEWED_SCAN_ADJUST: - while ( 1 ) - { - carry_flag = CFADD64(PAIR64(texture_yaccumulator_low, texture_xaccumulator_low), PAIR64(tex_x_accum_high, tex_x_accum_low)); - tex_x_accum_high = (PAIR64(texture_yaccumulator_low, texture_xaccumulator_low) + PAIR64(tex_x_accum_high, tex_x_accum_low)) >> 32; - tex_x_accum_low += texture_xaccumulator_low; - tex_x_accum_combined += texture_yaccumulator_high_combined + carry_flag; - current_scanline_xposition -= shadeAccumulator >> 16; - shadeAccumulatorNext += shadingfactor_secondary; - g_shadeAccumulator = shadingfactor_primary + shadeAccumulator; - current_scanline_xposition += (shadingfactor_primary + shadeAccumulator) >> 16; - shadeAccumulator += shadingfactor_primary; - screen_line_ptr += screenbuffer_linestride; - if ( !--scanline_span_count ) - break; - if ( ++scanline_y_esi >= 0 ) - { - xStart = current_scanline_xposition; - goto REMAINDER_SCANLINE_STEP; - } - } - xStart = current_scanline_xposition; + point_tmp = point_a; + point_a = point_c; + point_c = point_tmp; } - shadingfactor_primary = factor_cb; - texture_xaccumulator_low = shade_interpolation_pointc_low; - texture_yaccumulator_low = shade_interpolation_bottom_low; - texture_yaccumulator_high_combined = texture_delta_bottom_high_combined; - tex_x_accum_low = 0; - tex_x_accum_high = startpos_bottom_shade_texture_combined; - tex_x_accum_combined = startpos_bottom_texturex_texturey_combined; - clipped_end_y = triangle_point_c_y; - if ( triangle_point_c_y > LOC_vec_window_height ) - clipped_end_y = LOC_vec_window_height; - range_check_passed = clipped_end_y <= triangle_point_b_y; - scanline_span_count = clipped_end_y - triangle_point_b_y; - current_scanline_xposition = triangle_point_b_x; - shadeAccumulator = triangle_point_b_shade_x; - if ( !range_check_passed ) + if (point_b->Y > point_c->Y) { - scanline_y_esi = triangle_point_b_y; - if ( triangle_point_b_y < 0 ) - goto SKEWED_SCAN_ADJUST; - xStart = triangle_point_b_x; - goto REMAINDER_SCANLINE_STEP; + point_tmp = point_b; + point_b = point_c; + point_c = point_tmp; } - } -} -// this function draws all polygons except the ones cut off by the screen edges -void draw_gpoly_sub14() -{ + vertex_a_x = point_a->X; + vertex_a_y = point_a->Y; + vertex_b_x = point_b->X; + vertex_b_y = point_b->Y; + vertex_c_x = point_c->X; + vertex_c_y = point_c->Y; - if ( triangle_point_a_y > LOC_vec_window_height ) + if (! validate_triangle()) return; - int scanline_y; // esi - - int tex_x_accum_low = 0; - int tex_x_accum_high = startpos_top_shade_texture_combined; - int tex_x_accum_combined = startpos_top_texturex_texturey_combined; - uchar *screen_line_ptr = &LOC_vec_screen[triangle_point_a_y * LOC_vec_screen_width]; - - int clamped_by = triangle_point_b_y; - if ( triangle_point_b_y > LOC_vec_window_height ) - clamped_by = LOC_vec_window_height; - int spanCount = clamped_by - triangle_point_a_y; - bool skip_render = spanCount == 0; - scanline_span_count = spanCount; - int xStart = triangle_point_a_x; - current_scanline_xposition = triangle_point_a_x; - int shadeAccumulator = triangle_point_a_shade_x; - int shadeAccumulatorNext = triangle_point_a_shade_x; - if ( !skip_render ) - { - scanline_y = triangle_point_a_y; - if ( triangle_point_a_y < 0 ) - { - goto SKEWED_SCAN_ADJUST; - - } - do - { -REMAINDER_SCANLINE_STEP: - g_shadeAccumulator = shadeAccumulator; - g_shadeAccumulatorNext = shadeAccumulatorNext; - screenbuffer_lineptr = screen_line_ptr; - int x_start_int = shadeAccumulator >> 16; - texture_xaccumulator_low_backup = tex_x_accum_low; - texture_xaccumulator_high_backup = tex_x_accum_high; - texture_xaccumulator_backup = tex_x_accum_combined; - int x_end_int = shadeAccumulatorNext >> 16; - int clipped_x_start = x_start_int; - int clipped_x_end = x_end_int; - if (clipped_x_start < 0) clipped_x_start = 0; - if (clipped_x_end > LOC_vec_screen_width) clipped_x_end = LOC_vec_screen_width; - bool span_too_small_or_complete = clipped_x_end <= clipped_x_start; - int pixel_span_len = clipped_x_end - clipped_x_start; - if ( !span_too_small_or_complete ) - { - int skip_left = clipped_x_start - x_start_int; - if (skip_left > 0) { - for (int i = 0; i < skip_left; i++) { - tex_x_accum_combined = (PAIR64(shade_interpolation_bottom_high_combined, shade_interpolation_bottom_combined) + PAIR64(tex_x_accum_combined, tex_x_accum_high)) >> 32; - tex_x_accum_high += shade_interpolation_bottom_combined; - } - } - uint8_t *screen_line_offset = &screen_line_ptr[clipped_x_start]; - draw_gpoly_span(pixel_span_len,tex_x_accum_high,tex_x_accum_combined,screen_line_offset); - } - xStart = current_scanline_xposition; - shadeAccumulator = shadingfactor_primary + g_shadeAccumulator; - shadeAccumulatorNext = shadingfactor_secondary + g_shadeAccumulatorNext; - tex_x_accum_high = (PAIR64(texture_yaccumulator_low, texture_xaccumulator_low) + PAIR64(texture_xaccumulator_high_backup, texture_xaccumulator_low_backup)) >> 32; - tex_x_accum_low = texture_xaccumulator_low + texture_xaccumulator_low_backup; - tex_x_accum_combined = texture_yaccumulator_high_combined + CFADD64(PAIR64(texture_yaccumulator_low, texture_xaccumulator_low), PAIR64(texture_xaccumulator_high_backup, texture_xaccumulator_low_backup)) + texture_xaccumulator_backup; - screen_line_ptr = (uchar *)(screenbuffer_linestride + screenbuffer_lineptr); - --scanline_span_count; - } - while ( scanline_span_count ); - goto EDGE_ADVANCE_CHECK; - } - while ( 1 ) - { -EDGE_ADVANCE_CHECK: - if ( !--scanlinescounter ) - return; - g_shadeAccumulator = shadeAccumulator; - if ( factor_chk >= 0 ) - break; - shadingfactor_secondary = factor_cb; - shadeAccumulatorNext = triangle_point_b_shade_x; - int clamped_cy2 = triangle_point_c_y; - if ( triangle_point_c_y > LOC_vec_window_height ) - clamped_cy2 = LOC_vec_window_height; - bool span_too_small_or_complete = clamped_cy2 <= triangle_point_b_y; - scanline_span_count = clamped_cy2 - triangle_point_b_y; - shadeAccumulator = g_shadeAccumulator; - if ( span_too_small_or_complete ) - return; - current_scanline_xposition = xStart; - scanline_y = triangle_point_b_y; - if ( triangle_point_b_y >= 0 ) - goto REMAINDER_SCANLINE_STEP; - -SKEWED_SCAN_ADJUST: - while ( 1 ) - { - bool carryLow32 = CFADD64(PAIR64(texture_yaccumulator_low, texture_xaccumulator_low), PAIR64(tex_x_accum_high, tex_x_accum_low)); - tex_x_accum_high = (PAIR64(texture_yaccumulator_low, texture_xaccumulator_low) + PAIR64(tex_x_accum_high, tex_x_accum_low)) >> 32; - tex_x_accum_low += texture_xaccumulator_low; - tex_x_accum_combined += texture_yaccumulator_high_combined + carryLow32; - current_scanline_xposition -= shadeAccumulator >> 16; - shadeAccumulatorNext += shadingfactor_secondary; - g_shadeAccumulator = shadingfactor_primary + shadeAccumulator; - current_scanline_xposition += (shadingfactor_primary + shadeAccumulator) >> 16; - shadeAccumulator += shadingfactor_primary; - screen_line_ptr += screenbuffer_linestride; - if ( !--scanline_span_count ) - break; - if ( ++scanline_y >= 0 ) - { - goto REMAINDER_SCANLINE_STEP; - } - } - xStart = current_scanline_xposition; - } - shadingfactor_primary = factor_cb; - texture_xaccumulator_low = shade_interpolation_pointc_low; - texture_yaccumulator_low = shade_interpolation_bottom_low; - texture_yaccumulator_high_combined = texture_delta_bottom_high_combined; - tex_x_accum_low = 0; - tex_x_accum_high = startpos_bottom_shade_texture_combined; - tex_x_accum_combined = startpos_bottom_texturex_texturey_combined; - int clamped_cy = triangle_point_c_y; - if ( triangle_point_c_y > LOC_vec_window_height ) - clamped_cy = LOC_vec_window_height; - bool span_too_small_or_complete = clamped_cy <= triangle_point_b_y; - scanline_span_count = clamped_cy - triangle_point_b_y; - current_scanline_xposition = triangle_point_b_x; - shadeAccumulator = triangle_point_b_shade_x; - if ( !span_too_small_or_complete ) - { - scanline_y = triangle_point_b_y; - if ( triangle_point_b_y < 0 ) - goto SKEWED_SCAN_ADJUST; - goto REMAINDER_SCANLINE_STEP; - } - + vertex_a_shade = point_a->S >> 16; + vertex_b_shade = point_b->S >> 16; + vertex_c_shade = point_c->S >> 16; + vertex_a_texture_u = point_a->U >> 16; + vertex_a_texture_v = point_a->V >> 16; + vertex_b_texture_u = point_b->U >> 16; + vertex_b_texture_v = point_b->V >> 16; + vertex_c_texture_u = point_c->U >> 16; + vertex_c_texture_v = point_c->V >> 16; + + const bool clip_x = ( (vertex_a_x) | (vec_window_width - vertex_a_x) + | (vertex_b_x) | (vec_window_width - vertex_b_x) + | (vertex_c_x) | (vec_window_width - vertex_c_x) ) < 0; + + calculate_slopes(); + calculate_texture_mapping(); + pack_texcoords(); + + if (clip_x) + draw_gpoly_clipped(); + else + draw_gpoly_whole(); } /******************************************************************************/ From de75c5a36f24f515b54efea9e67c6afa8365b7a4 Mon Sep 17 00:00:00 2001 From: RupixTalahone <304036411+RupixTalahone@users.noreply.github.com> Date: Fri, 31 Jul 2026 00:38:19 +0200 Subject: [PATCH 15/28] Fixed invisible door keys in straight view (#5073) --- src/engine_render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine_render.c b/src/engine_render.c index 6446219463..db4663fed3 100644 --- a/src/engine_render.c +++ b/src/engine_render.c @@ -8968,7 +8968,7 @@ static void draw_frontview_thing_on_element(struct Thing *thing, struct Map *map convert_world_coord_to_front_view_screen_coord(&interp.mappos, cam, &cx, &cy, &cz); if (is_free_space_in_poly_pool(1)) { - add_spinning_key_to_polypool(thing, cx, cy, cy, cz-3); + add_spinning_key_to_polypool(thing, cx, cy, cy, cz - 3 - 4 * (camera_zoom >> 11)); } break; default: From 0b6bbe01bf817f948e40da491c2e1cbb2bf9d082 Mon Sep 17 00:00:00 2001 From: jwt27 Date: Fri, 31 Jul 2026 22:37:47 +0200 Subject: [PATCH 16/28] Handle camera catchup packets after logic update (#5075) --- src/engine_camera.c | 3 +++ src/local_camera.c | 33 +++++++++++++++++++++++---------- src/local_camera.h | 2 +- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/engine_camera.c b/src/engine_camera.c index 1a9d6aade3..3ccd032aa0 100644 --- a/src/engine_camera.c +++ b/src/engine_camera.c @@ -725,5 +725,8 @@ void update_all_players_cameras(void) update_player_camera(player); } } + + // Send catchup packets if local camera has drifted too far from packet-based camera + send_camera_catchup_packets(); } /******************************************************************************/ diff --git a/src/local_camera.c b/src/local_camera.c index 9cff1b5471..56bd0d9d40 100644 --- a/src/local_camera.c +++ b/src/local_camera.c @@ -61,25 +61,40 @@ static struct Packet* get_packet_for_local_camera_update(void) return get_packet_direct(player->packet_num); } -void send_camera_catchup_packets(struct PlayerInfo *player) +void send_camera_catchup_packets(void) { // Threshold distance before sending catchup packets (in map coordinates) #define CAMERA_DESYNC_THRESHOLD 512 - if (!is_my_player(player) || !local_camera_ready) { + if (!local_camera_ready) { return; } - + struct PlayerInfo* player = get_my_player(); + // Determine which camera to compare based on view mode - int cam_idx = (player->view_mode == PVM_FrontView) ? CamIV_FrontView : CamIV_Isometric; - + int cam_idx; + switch (player->view_mode) + { + case PVM_FrontView: + cam_idx = CamIV_FrontView; + break; + + case PVM_IsoStraightView: + case PVM_IsoWibbleView: + cam_idx = CamIV_Isometric; + break; + + default: + return; + } + struct Camera* local_cam = &destination_local_cameras[cam_idx]; struct Camera* packet_cam = &player->cameras[cam_idx]; struct Packet* pckt = get_packet(player->id_number); - + long diff_map_x = local_cam->mappos.x.val - packet_cam->mappos.x.val; long diff_map_y = local_cam->mappos.y.val - packet_cam->mappos.y.val; - + long angle = local_cam->rotation_angle_x; long cos_angle = LbCosL(angle); long sin_angle = LbSinL(angle); @@ -244,9 +259,7 @@ void update_local_cameras(void) process_camera_controls(cam, local_packet, my_player, true); view_process_camera_inertia(cam); } - - // Send catchup packets if local camera has drifted too far from packet-based camera - send_camera_catchup_packets(my_player); + update_camera_deviations(active_cam_idx); } } diff --git a/src/local_camera.h b/src/local_camera.h index 6f6ec90941..af4f053c04 100644 --- a/src/local_camera.h +++ b/src/local_camera.h @@ -45,7 +45,7 @@ void sync_local_camera(struct PlayerInfo *player); void set_local_camera_destination(struct PlayerInfo *player); void move_local_camera_to_position(MapCoord x, MapCoord y); struct Camera* get_local_camera(struct Camera* cam); -void send_camera_catchup_packets(struct PlayerInfo *player); +void send_camera_catchup_packets(void); /******************************************************************************/ #ifdef __cplusplus From f8e39bca101dc991dec1dc6a9fbe0f31d1b5e524 Mon Sep 17 00:00:00 2001 From: Pieter Vandecandelaere Date: Sun, 2 Aug 2026 01:54:11 +0200 Subject: [PATCH 17/28] Clean up main.cpp (#5064) Should be no functional changes. --- Makefile | 2 + keeperfx_vs2010.vcxproj | 3 + keeperfx_vs2010.vcxproj.filters | 3 + linux.mk | 2 + src/bflib_datetm.h | 1 - src/config_keeperfx.c | 1 + src/console_cmd.c | 8 +- src/engine_arrays.c | 15 +- src/engine_arrays.h | 4 +- src/engine_camera.c | 10 + src/engine_camera.h | 1 + src/front_input.c | 6 +- src/front_lvlstats.c | 1 + src/frontend.cpp | 4 + src/frontmenu_ingame_evnt.c | 1 + src/game_loop.c | 849 +++++++++- src/game_loop.h | 11 +- src/game_update.cpp | 586 +++++++ src/gui_msgs.c | 1 + src/gui_parchment.c | 245 +++ src/gui_tooltips.c | 11 +- src/keeperfx.hpp | 50 +- src/main.cpp | 2641 ++----------------------------- src/main_game.c | 33 +- src/net_exchange_gameplay.c | 5 +- src/net_exchange_gameplay.h | 2 + src/net_resync.cpp | 1 + src/packets.c | 21 - src/packets.h | 1 - src/packets_input.c | 11 +- src/player_instances.c | 18 + src/player_instances.h | 1 + src/player_utils.c | 1 + src/power_hand.h | 2 + src/room_entrance.c | 6 + src/room_library.c | 17 +- src/room_library.h | 2 +- src/room_workshop.c | 17 +- src/room_workshop.h | 2 +- src/slab_data.c | 49 + src/thing_creature.c | 50 + src/thing_effects.c | 257 +++ src/thing_effects.h | 5 +- src/thing_list.c | 206 ++- src/thing_list.h | 1 - src/thing_shots.c | 247 ++- src/thing_shots.h | 4 +- src/thing_traps.c | 2 +- src/timer.c | 62 + src/timer.h | 52 + tests/tst_fixes.cpp | 276 ---- 51 files changed, 2825 insertions(+), 2982 deletions(-) create mode 100644 src/game_update.cpp create mode 100644 src/timer.c create mode 100644 src/timer.h delete mode 100644 tests/tst_fixes.cpp diff --git a/Makefile b/Makefile index 76d04d6bc5..a55e45feb5 100644 --- a/Makefile +++ b/Makefile @@ -238,6 +238,7 @@ obj/game_loop.o \ obj/game_lghtshdw.o \ obj/game_merge.o \ obj/game_saves.o \ +obj/game_update.o \ obj/gui_boxmenu.o \ obj/gui_draw.o \ obj/gui_frontbtns.o \ @@ -350,6 +351,7 @@ obj/thing_physics.o \ obj/thing_shots.o \ obj/thing_stats.o \ obj/thing_traps.o \ +obj/timer.o \ obj/value_util.o \ obj/vidfade.o \ obj/vidmode_data.o \ diff --git a/keeperfx_vs2010.vcxproj b/keeperfx_vs2010.vcxproj index 18f3637d05..2edb652a93 100644 --- a/keeperfx_vs2010.vcxproj +++ b/keeperfx_vs2010.vcxproj @@ -49,6 +49,7 @@ + @@ -278,6 +279,7 @@ + @@ -519,6 +521,7 @@ + diff --git a/keeperfx_vs2010.vcxproj.filters b/keeperfx_vs2010.vcxproj.filters index 697791d6ca..535cf031ae 100644 --- a/keeperfx_vs2010.vcxproj.filters +++ b/keeperfx_vs2010.vcxproj.filters @@ -271,6 +271,8 @@ + + @@ -512,6 +514,7 @@ + diff --git a/linux.mk b/linux.mk index ab43856748..48aec25ebf 100644 --- a/linux.mk +++ b/linux.mk @@ -161,6 +161,7 @@ src/game_loop.c \ src/game_lghtshdw.c \ src/game_merge.c \ src/game_saves.c \ +src/game_update.cpp \ src/gui_boxmenu.c \ src/gui_draw.c \ src/gui_frontbtns.c \ @@ -277,6 +278,7 @@ src/thing_physics.c \ src/thing_shots.c \ src/thing_stats.c \ src/thing_traps.c \ +src/timer.c \ src/value_util.c \ src/vidfade.c \ src/vidmode_data.cpp \ diff --git a/src/bflib_datetm.h b/src/bflib_datetm.h index b06a1eaca4..a18f1993b6 100644 --- a/src/bflib_datetm.h +++ b/src/bflib_datetm.h @@ -22,7 +22,6 @@ #include #include "bflib_basics.h" -#include "keeperfx.hpp" #include "game_legacy.h" #ifdef __cplusplus diff --git a/src/config_keeperfx.c b/src/config_keeperfx.c index f3e9aa81a1..fac9fae8ee 100644 --- a/src/config_keeperfx.c +++ b/src/config_keeperfx.c @@ -40,6 +40,7 @@ #include "sounds.h" #include "vidmode.h" #include "moonphase.h" +#include "keeperfx.hpp" #include "post_inc.h" #ifdef __cplusplus diff --git a/src/console_cmd.c b/src/console_cmd.c index 0ecea91399..585a3dddda 100644 --- a/src/console_cmd.c +++ b/src/console_cmd.c @@ -72,6 +72,8 @@ #include #include "lua_base.h" #include "net_resync.h" +#include "kjm_input.h" +#include "timer.h" #include "post_inc.h" #ifdef __cplusplus @@ -757,7 +759,7 @@ TbBool cmd_comp_procs(PlayerNumber plyr_idx, char * args) i++; cmd_comp_procs_data[i].label = "!"; cmd_comp_procs_data[i].is_enabled = 0; - gui_cheat_box_2 = gui_create_box(my_mouse_x, 20, cmd_comp_procs_data); + gui_cheat_box_2 = gui_create_box(GetMouseX(), 20, cmd_comp_procs_data); return true; } @@ -781,7 +783,7 @@ TbBool cmd_comp_events(PlayerNumber plyr_idx, char * args) cmd_comp_events_data, cmd_comp_events_label, &get_event_name, &get_event_flags, NULL); cmd_comp_events_data[0].active_cb = NULL; - gui_cheat_box_2 = gui_create_box(my_mouse_x, 20, cmd_comp_events_data); + gui_cheat_box_2 = gui_create_box(GetMouseX(), 20, cmd_comp_events_data); return true; } @@ -805,7 +807,7 @@ TbBool cmd_comp_checks(PlayerNumber plyr_idx, char * args) cmd_comp_checks_data, cmd_comp_checks_label, &get_check_name, &get_check_flags, &cmd_comp_checks_click); cmd_comp_checks_data[0].active_cb = NULL; - gui_cheat_box_2 = gui_create_box(my_mouse_x, 20, cmd_comp_checks_data); + gui_cheat_box_2 = gui_create_box(GetMouseX(), 20, cmd_comp_checks_data); return true; } diff --git a/src/engine_arrays.c b/src/engine_arrays.c index 8c2f2969cc..20c1083975 100644 --- a/src/engine_arrays.c +++ b/src/engine_arrays.c @@ -1000,7 +1000,7 @@ void setup_mesh_randomizers(void) } } -void fill_floor_heights_table(void) +static void fill_floor_heights_table(void) { long top_height; long btm_height; @@ -1047,7 +1047,7 @@ void fill_floor_heights_table(void) /** * Modification of LB_RANDOM() which allows generating Wibble values same to original game. */ -unsigned short wibble_random(unsigned short range, unsigned short *seed) +static unsigned short wibble_random(unsigned short range, unsigned short *seed) { if (range == 0) return 0; @@ -1057,7 +1057,7 @@ unsigned short wibble_random(unsigned short range, unsigned short *seed) return i; } -void generate_wibble_table(void) +static void generate_wibble_table(void) { struct WibbleTable *wibl; struct WibbleTable *empty_wibl; @@ -1099,7 +1099,7 @@ void generate_wibble_table(void) } } -TbBool load_ceiling_table(void) +static TbBool load_ceiling_table(void) { char *fname; TbFileHandle fh; @@ -1148,4 +1148,11 @@ TbBool load_ceiling_table(void) return true; } + +void engine_init(void) +{ + fill_floor_heights_table(); + generate_wibble_table(); + load_ceiling_table(); +} /******************************************************************************/ diff --git a/src/engine_arrays.h b/src/engine_arrays.h index 66421d233c..2406c8047c 100644 --- a/src/engine_arrays.h +++ b/src/engine_arrays.h @@ -67,9 +67,7 @@ unsigned short get_render_animation_sprite(unsigned short animation_sprite); void init_fp_td_animation_conversion_tables(void); void setup_mesh_randomizers(void); -TbBool load_ceiling_table(void); -void generate_wibble_table(void); -void fill_floor_heights_table(void); +void engine_init(void); /******************************************************************************/ #ifdef __cplusplus diff --git a/src/engine_camera.c b/src/engine_camera.c index 3ccd032aa0..94d9595475 100644 --- a/src/engine_camera.c +++ b/src/engine_camera.c @@ -729,4 +729,14 @@ void update_all_players_cameras(void) // Send catchup packets if local camera has drifted too far from packet-based camera send_camera_catchup_packets(); } + +void set_player_cameras_position(struct PlayerInfo *player, int32_t pos_x, int32_t pos_y) +{ + player->cameras[CamIV_Parchment].mappos.x.val = pos_x; + player->cameras[CamIV_FrontView].mappos.x.val = pos_x; + player->cameras[CamIV_Isometric].mappos.x.val = pos_x; + player->cameras[CamIV_Parchment].mappos.y.val = pos_y; + player->cameras[CamIV_FrontView].mappos.y.val = pos_y; + player->cameras[CamIV_Isometric].mappos.y.val = pos_y; +} /******************************************************************************/ diff --git a/src/engine_camera.h b/src/engine_camera.h index c6448d3f2a..eb1962ec0e 100644 --- a/src/engine_camera.h +++ b/src/engine_camera.h @@ -126,6 +126,7 @@ TbBool view_move_camera_to_position(struct Camera *cam, MapCoord x, MapCoord y, void update_all_players_cameras(void); void init_player_cameras(struct PlayerInfo *player); void update_first_person_position(struct Camera *cam, struct Thing *thing, int eye_height); +void set_player_cameras_position(struct PlayerInfo *player, int32_t pos_x, int32_t pos_y); /******************************************************************************/ #ifdef __cplusplus diff --git a/src/front_input.c b/src/front_input.c index 0c99895535..2c9dfe6d5a 100644 --- a/src/front_input.c +++ b/src/front_input.c @@ -76,6 +76,7 @@ #include "packets.h" #include "console_cmd.h" #include "engine_redraw.h" +#include "timer.h" #include "keeperfx.hpp" @@ -96,11 +97,14 @@ unsigned short const zoom_key_room_order[] = // define the current GUI layer as the default struct GuiLayer gui_layer = {GuiLayer_Default}; -TbBool first_person_see_item_desc = false; +static TbBool first_person_see_item_desc = false; static TbBool move_camera_this_turn; static GameTurn hand_pick_pending_turn; +static int32_t my_mouse_x; +static int32_t my_mouse_y; + long old_mx; long old_my; diff --git a/src/front_lvlstats.c b/src/front_lvlstats.c index 673fffe7aa..c249ad2a14 100644 --- a/src/front_lvlstats.c +++ b/src/front_lvlstats.c @@ -44,6 +44,7 @@ #include "game_legacy.h" #include "sprites.h" #include "custom_sprites.h" +#include "timer.h" #include "keeperfx.hpp" #include "post_inc.h" diff --git a/src/frontend.cpp b/src/frontend.cpp index 589136d05b..a56433c5cf 100644 --- a/src/frontend.cpp +++ b/src/frontend.cpp @@ -438,6 +438,10 @@ int fe_computer_players; long old_mouse_over_button; long frontend_mouse_over_button; + +static int32_t last_mouse_x; +static int32_t last_mouse_y; + /******************************************************************************/ short menu_is_active(short idx) { diff --git a/src/frontmenu_ingame_evnt.c b/src/frontmenu_ingame_evnt.c index 8fd7916379..c82508f29c 100644 --- a/src/frontmenu_ingame_evnt.c +++ b/src/frontmenu_ingame_evnt.c @@ -46,6 +46,7 @@ #include "map_events.h" #include "local_camera.h" #include "sprites.h" +#include "timer.h" #include "keeperfx.hpp" #include "post_inc.h" diff --git a/src/game_loop.c b/src/game_loop.c index 633ec239f5..edd2adf470 100644 --- a/src/game_loop.c +++ b/src/game_loop.c @@ -3,8 +3,6 @@ /******************************************************************************/ /** @file game_loop.c * Module which contains functions from the main game loop. - * @author Loobinex - * @date 14 Jul 2021 * @par Copying and copyrights: * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,7 +22,6 @@ #include "thing_navigate.h" #include "thing_objects.h" #include "room_data.h" -#include "room_library.h" #include "room_workshop.h" #include "map_columns.h" #include "creature_states.h" @@ -34,11 +31,60 @@ #include "game_legacy.h" #include "game_loop.h" #include "lua_triggers.h" + + +#include "kjm_input.h" +#include "moonphase.h" +#include "vidmode.h" +#include "frontend.h" +#include "bflib_mouse.h" +#include "front_simple.h" +#include "bflib_datetm.h" +#include "bflib_inputctrl.h" +#include "bflib_sndlib.h" +#include "vidfade.h" +#include "config_keeperfx.h" +#include "api.h" +#include "player_instances.h" +#include "lvl_filesdk1.h" +#include "config_sounds.h" +#include "lens_api.h" +#include "scrcapt.h" +#include "frontmenu_ingame_evnt.h" +#include "engine_redraw.h" +#include "bflib_crash.h" +#include "gui_topmsg.h" +#include "front_easter.h" +#include "front_input.h" +#include "net_exchange_gameplay.h" +#include "timer.h" + #include "post_inc.h" #ifdef __cplusplus extern "C" { #endif + +extern void startup_network_game(CoroutineLoop *context, TbBool local); +extern void faststartup_network_game(CoroutineLoop *context); +extern CoroutineLoopState set_not_has_quit(CoroutineLoop *context); +void update_frontend_delta_time(); +void update_gameplay_delta_time(); +bool use_delta_time(); + +/******************************************************************************/ +static short do_draw; +static long double average_frame_draw_time = 1; + +float interpolate_time = 0; + +extern int32_t fps_limit_secondary; +extern long double process_frame_time; +extern long double time_since_last_draw; +extern long double multiplayer_clock_adjust; +extern long double host_packet_received; + + /******************************************************************************/ /* * Dungeon core destruction @@ -267,36 +313,803 @@ void process_dungeon_destroy(struct Thing* heartng) /******************************************************************************/ -void update_research(void) + + + + +// Using Alt-F4, or similar operating system close requests +static void force_application_close() { - int i; - struct PlayerInfo *player; - SYNCDBG(6,"Starting"); - for (i = 0; i < PLAYERS_COUNT; i++) + extern int frontend_menu_state; + + if (frontend_menu_state == 0) + { + struct PlayerInfo* player = get_my_player(); + if (player != INVALID_PLAYER) + { + set_players_packet_action(player, PckA_ForceApplicationClose, 0, 0, 0, 0); + } + else + { + exit_keeper = 1; + } + } + else + { + exit_keeper = 1; + } +} + +static bool keeper_wait_for_screen_focus() +{ + do { + if ( !poll_inputs() ) + { + force_application_close(); + break; + } + if (LbIsActive()) + return true; + if (network_is_active()) + return true; + if (!freeze_game_on_focus_lost()) + return true; + LbSleepFor(50); + update_gameplay_delta_time(); + game.process_turn_time = 1.0; + time_since_last_draw = 1.0; + } while ((!exit_keeper) && (!quit_game)); + return false; +} + +static void find_frame_rate(void) +{ + static TbClockMSec prev_time2=0; + static TbClockMSec cntr_time2=0; + unsigned long curr_time; + curr_time = LbTimerClock(); + cntr_time2++; + if (curr_time-prev_time2 >= 1000) + { + double time_fdelta = 1000.0*((double)(cntr_time2))/(curr_time-prev_time2); + prev_time2 = curr_time; + game.time_delta = (unsigned long)(time_fdelta*256.0); + cntr_time2 = 0; + } +} + +static void packet_load_find_frame_rate(unsigned long incr) +{ + static TbClockMSec start_time=0; + static TbClockMSec extra_frames=0; + TbClockMSec curr_time; + curr_time = LbTimerClock(); + if ((curr_time-start_time) < 5000) + { + extra_frames += incr; + } else + { + double time_fdelta = 1000.0*((double)(extra_frames+incr))/(curr_time-start_time); + start_time = curr_time; + game.time_delta = (unsigned long)(time_fdelta*256.0); + extra_frames = 0; + } +} + +/** + * Checks if the game screen needs redrawing. + */ +static short display_should_be_updated_this_turn(void) +{ + if ((game.operation_flags & GOF_Paused) != 0) + return true; + if ( (game.turns_fastforward == 0) && (!game.packet_loading_in_progress) ) + { + find_frame_rate(); + if ( (game.frame_skip == 0) || ((get_gameturn() % game.frame_skip) == 0) ) + return true; + } else + if ( ((get_gameturn() & 0x3F)==0) || + ((game.packet_loading_in_progress) && ((get_gameturn() & 7)==0)) ) + { + packet_load_find_frame_rate(64); + return true; + } + return false; +} + +// this one isn't static for now, because it's used in the network code +// if networking had its own thread, it wouldn't need the yield that calls this function, but for now it does +void gameplay_loop_draw() +{ + if (use_delta_time()) + do_draw = true; + + update_gameplay_delta_time(); + + if (game.process_turn_time > 1.0 && time_since_last_draw < 1.0) + do_draw = false; + + // Frame rate limiter + if (fps_limit_current > 0) + { + frametime_start_measurement(Frametime_Sleep); + if (process_frame_time < 1.0) + { + if (game.process_turn_time < 1.0) + SDL_Delay(1); + do_draw = false; + } + else + { + process_frame_time = min(1.L, process_frame_time - 1.L); + } + frametime_end_measurement(Frametime_Sleep); + } + + // Floats are used a lot in the drawing related functions. But keep in mind integers are typically preferred for logic related functions. + frametime_start_measurement(Frametime_Draw); + + // Update lights + update_light_render_area(); + + if (quit_game || exit_keeper) { + do_draw = false; + } + if ( do_draw ) { + if (frametime_enabled()) + framerate_measurement_capture(Framerate_Draw); + game.delta_time = min(time_since_last_draw, 1.L); + time_since_last_draw = 0; + interpolate_time = min(max(game.process_turn_time, 0.L), 1.L); + keeper_screen_redraw(); + } + keeper_wait_for_screen_focus(); + // Direct information/error messages + if (LbScreenLock() == Lb_SUCCESS) { + if ( do_draw ) { + perform_any_screen_capturing(); + } + draw_onscreen_direct_messages(); + LbScreenUnlock(); + } + // Move the graphics window to center of screen buffer and swap screen + if ( do_draw ) { + LbScreenSwap(); + } + frametime_end_measurement(Frametime_Draw); + + if ( do_draw ) { + update_gameplay_delta_time(); + const long double delta = time_since_last_draw - average_frame_draw_time; + average_frame_draw_time += delta * max(average_frame_draw_time, .05L) / 20; + } +} + +static void gameplay_loop_logic() +{ + if(flag_is_set(start_params.debug_flags, DFlg_PauseAtGameTurn)) + { + static GameTurn previous_gameturn = 0; + if(get_gameturn() >= start_params.pause_at_gameturn && get_gameturn() != previous_gameturn) + { + if(!game.paused_at_gameturn) + { + game.paused_at_gameturn = true; + + game.frame_skip = 0; + if(game.packet_load_enable) + { + disable_packet_mode(); + } + set_packet_pause_toggle(); + } + } + previous_gameturn = get_gameturn(); + } + + if (use_delta_time()) + { + update_gameplay_delta_time(); + if (game.input_lag_turns == 0 && network_is_active()) + { + // Aim to exchange network packets before the turn ends. If drawing + // another frame could miss this deadline, skip it. + // In a 3-4 player game, clients must be 2 frames early. + const int frames = 1 + (netstate.my_id != SERVER_ID && game.active_players_count > 2); + const long double offset = frames * average_frame_draw_time * multiplayer_clock_adjust * max(game.frame_skip, 1); + if (game.process_turn_time + offset < 1.0) + return; + } + else + { + if (game.process_turn_time < 1.0) + return; + } + } + + frametime_start_measurement(Frametime_Logic); + if (frametime_enabled()) + framerate_measurement_capture(Framerate_Logic); + +#ifdef FUNCTESTING + if(flag_is_set(start_params.functest_flags, FTF_Enabled)) { - player = get_player(i); - if (player_exists(player) && (player->is_active == 1)) + FTestFrameworkState ftstate = ftest_update(NULL); + if(ftstate == FTSt_InvalidState || ftstate == FTSt_TestsCompletedSuccessfully) { - process_player_research(i); + quit_game = true; + exit_keeper = true; + return; + } + } +#endif // FUNCTESTING + do_draw = display_should_be_updated_this_turn() || (!LbIsActive()); + poll_inputs(); + input_eastegg(); + input(); + exchange_packets(); + + update_gameplay_delta_time(); + if (game.process_turn_time > turns_per_second + 1) + game.process_turn_time = turns_per_second + 1; + + // Adjust client time scaling + if (netstate.my_id != SERVER_ID && network_is_active()) + { + if (game.input_lag_turns == 0) + { + // Adjust the clock rate so that the host packet is received at + // process_turn_time == 1.0 (on average). If it is received later, + // reduce the scaling factor (< 1.0) so that the next turn takes a + // little longer in real time. Vice-versa if it is early. + + multiplayer_clock_adjust = 1 + (1 - host_packet_received) / 20; + } + else + { + const long double tick_ns_one_turn = 1e9L / turns_per_second; + const long double tick_ns_adjusted_turn = tick_ns_one_turn + multiplayer_speed_adjustment_ns; + assert (tick_ns_adjusted_turn > 0); + multiplayer_clock_adjust = tick_ns_one_turn / tick_ns_adjusted_turn; + } + } + else multiplayer_clock_adjust = 1.0; + host_packet_received = 1.0; + + while (game.process_turn_time < 1.0) + { + gameplay_loop_draw(); + update_gameplay_delta_time(); + } + game.process_turn_time -= 1.0; + + update(); + + frametime_end_measurement(Frametime_Logic); + + if(game.frame_step) + { + game.frame_step = false; + set_packet_pause_toggle(); + } +} + +static void gameplay_loop_network() +{ + if (! network_is_active()) + return; + + network_update(game.packets, sizeof(struct Packet)); +} + +static TbBool keeper_wait_for_next_turn(void) +{ + const long double tick_ns_one_sec = 1000000000.0; + long double tick_ns_one_frame = -1; + if ((game.view_mode_flags & GNFldD_WaitSleepMode) != 0) + { + // No idea when such situation occurs + tick_ns_one_frame = tick_ns_one_sec; + } + if (game.frame_skip >= 0) + { + // Standard delaying system + int32_t num_fps = turns_per_second; + if (game.frame_skip > 0) + num_fps *= game.frame_skip; + + tick_ns_one_frame = tick_ns_one_sec/num_fps; + } + + if (tick_ns_one_frame >= 0) { + static long double tick_ns_last_turn = 0; + + long double tick_ns_cur = get_time_tick_ns(); + long double tick_ns_used = tick_ns_cur - tick_ns_last_turn; + long double tick_ns_delay = tick_ns_one_frame - tick_ns_used; + if (multiplayer_speed_adjustment_ns != 0) { + tick_ns_delay += multiplayer_speed_adjustment_ns; + } + + long double tick_ns_end = tick_ns_cur; + // tick_ns_used: every level, initialized_time_point will be reset, so tick_ns_used may be less than 0 when enter level for the non-first time, Skip it directly to solve the problem. + if (tick_ns_delay > 0 && tick_ns_used >= 0) { + tick_ns_end = tick_ns_cur + tick_ns_delay; + LbSleepUntilExt(tick_ns_end); + } + tick_ns_last_turn = tick_ns_end; + return true; + } + + return false; +} + +static void gameplay_loop_timestep() +{ + if (! use_delta_time()) { + frametime_start_measurement(Frametime_Sleep); + // Make delay if the machine is too fast + if ( (!game.packet_load_enable) || (game.turns_fastforward == 0) ) { + keeper_wait_for_next_turn(); } + frametime_end_measurement(Frametime_Sleep); + } +} + +static void keeper_gameplay_loop(void) +{ + struct PlayerInfo *player; + SYNCDBG(5,"Starting"); + player = get_my_player(); + PaletteSetPlayerPalette(player, engine_palette); + if ((game.operation_flags & GOF_SingleLevel) != 0) { + initialise_eye_lenses(); + } + SYNCDBG(0,"Entering the gameplay loop for level %d",(int)get_loaded_level_number()); + LbErrorParachuteUpdate(); // For some reasone parachute keeps changing; Remove when won't be needed anymore + + initial_time_point(); + LbSleepExtInit(); + + //the main gameplay loop starts + while ((!quit_game) && (!exit_keeper)) + { + frametime_start_measurement(Frametime_FullFrame); + if (frametime_enabled()) + framerate_measurement_capture(Framerate_FullFrame); + gameplay_loop_logic(); + gameplay_loop_draw(); + gameplay_loop_network(); + gameplay_loop_timestep(); + + frametime_end_measurement(Frametime_FullFrame); + } // end while + SYNCDBG(0,"Gameplay loop finished after %lu turns",(unsigned long)get_gameturn()); + + // Reset the game kind because we are not in a game anymore at this point + game.game_kind = GKind_Unset; + + api_event("GAME_ENDED"); +} + + +static TbBool should_use_delta_time_on_menu() +{ + switch (frontend_menu_state) { + case FeSt_MAIN_MENU: + case FeSt_FELOAD_GAME: + case FeSt_NET_SERVICE: /**< Network service selection, where player can select Serial/Modem/IPX/TCP IP/1 player. */ + case FeSt_NET_SESSION: /**< Network session selection screen, where list of games is displayed, with possibility to join or create own game. */ + case FeSt_NET_START: /**< Network game start screen (the menu with chat), when created new session or joined existing session. */ + case FeSt_LEVEL_STATS: + case FeSt_HIGH_SCORES: + case FeSt_FEDEFINE_KEYS: + case FeSt_FEOPTIONS: + case FeSt_LEVEL_SELECT: + case FeSt_CAMPAIGN_SELECT: + case FeSt_MAPPACK_SELECT: + case FeSt_MP_MAPPACK_SELECT: + case FeSt_LAND_VIEW: + case FeSt_NETLAND_VIEW: + case FeSt_TORTURE: + return true; + default: + return false; + } +} + +static void faststartup_saved_packet_game(void) +{ + reenter_video_mode(); + startup_saved_packet_game(); + { + struct PlayerInfo *player; + player = get_my_player(); + player->display_flags &= ~PlaF6_PlyrHasQuit; } + set_gui_visible(false); + clear_flag(game.operation_flags, GOF_ShowPanel); } -void update_manufacturing(void) +static TbBool wait_at_frontend(void) { - int i; struct PlayerInfo *player; - SYNCDBG(16,"Starting"); - for (i=0; iis_active == 1)) + player = get_my_player(); + player->display_flags &= ~PlaF6_PlyrHasQuit; + return true; + } + reenter_video_mode(); + + display_loading_screen(); + + short flgmem; + switch (prev_state) + { + case FeSt_START_KPRLEVEL: + my_player_number = default_loc_player; + game.game_kind = GKind_LocalGame; + clear_flag(game.system_flags, GSF_NetworkActive); + player = get_my_player(); + player->is_active = 1; + startup_network_game(&loop, true); + break; + case FeSt_START_MPLEVEL: + set_flag(game.system_flags, GSF_NetworkActive); + skip_high_score_screen = 1; + game.game_kind = GKind_MultiGame; + player = get_my_player(); + player->is_active = 1; + startup_network_game(&loop, false); + break; + case FeSt_LOAD_GAME: + flgmem = game.save_game_slot; + clear_flag(game.system_flags, GSF_NetworkActive); + LbScreenClear(0); + LbScreenSwap(); + if (!load_game(game.save_game_slot)) + { + ERRORLOG("Loading game %d failed; quitting.",(int)game.save_game_slot); + quit_game = 1; + } + game.save_game_slot = flgmem; + break; + case FeSt_PACKET_DEMO: + game.mode_flags |= MFlg_IsDemoMode; + startup_saved_packet_game(); + set_gui_visible(false); + clear_flag(game.operation_flags, GOF_ShowPanel); + break; + } + + coroutine_add(&loop, &set_not_has_quit); + coroutine_process(&loop); + if (loop.error) + { + frontend_set_state(FeSt_INITIAL); + return false; + } + return true; +} + +void game_loop(void) +{ +#if (BFDEBUG_LEVEL > 0) + unsigned long playtime = 0; +#endif + SYNCDBG(0,"Entering gameplay loop."); + + while ( !exit_keeper ) + { + update_mouse(); + while (!wait_at_frontend()) + { + if (exit_keeper) + break; + } + if ( exit_keeper ) + break; + + int32_t mspos_x_bak = lbDisplay.MMouseX; + int32_t mspos_y_bak = lbDisplay.MMouseY; + + if (game.game_kind == GKind_LocalGame) + { + if (game.save_game_slot == -1) + { + if (is_feature_on(Ft_SkipHeartZoom) == false) { + for (int i = 0; i < PLAYERS_COUNT; i++) { + struct PlayerInfo *player = get_player(i); + if (player_exists(player) && ((player->allocflags & PlaF_CompCtrl) == 0)) { + set_player_instance(player, PI_HeartZoom, 0); + } + } + } else { + if (!game.packet_load_enable) { + toggle_status_menu(1); // Required when skipping PI_HeartZoom + } + } + } else { - process_player_manufacturing(i); + game.save_game_slot = -1; } + } else { + for (int i = 0; i < PLAYERS_COUNT; i++) { + struct PlayerInfo *player = get_player(i); + if (player_exists(player) && ((player->allocflags & PlaF_CompCtrl) == 0)) { + set_player_instance(player, PI_HeartZoom, 0); + } + } + } + + // Try to keep the mouse position unchanged when entering the level. + // The main considerations are: + // 1. SKIP_HEART_ZOOM: the mouse icon position will be reset to the top-left corner (0, 0), but the actual mouse position remains unchanged. + // 2. PI_HeartZoom: the mouse will be moved to the center of the screen. + LbMouseSetPosition(mspos_x_bak, mspos_y_bak); + + unsigned long starttime; +#if (BFDEBUG_LEVEL > 0) + unsigned long endtime; +#endif + struct Dungeon *dungeon; + // get_my_dungeon() can't be used here because players are not initialized yet + dungeon = get_dungeon(my_player_number); + starttime = LbTimerClock(); + dungeon->lvstats.start_time = starttime; + dungeon->lvstats.end_time = starttime; + if (!TimerNoReset) + { + if (is_feature_on(Ft_SkipHeartZoom)) + { + timerstarttime = starttime; + } + else + { + TimerFreeze = true; + } + memset(&Timer, 0, sizeof(Timer)); + } + LbScreenClear(0); + LbScreenSwap(); + game.frame_skip = 0; + keeper_gameplay_loop(); + set_pointer_graphic_none(); + LbScreenClear(0); + LbScreenSwap(); + stop_atmos_sounds(); + stop_music(true); + stop_streamed_samples(); + free_level_strings_data(); + turn_off_all_menus(); + delete_all_structures(); + clear_mapwho(); + // Reset sounds back to the fxdata baseline so the main menu (and any + // subsequent campaign/freeplay selection) hears unmodified defaults. + sound_reset_to_fxdata_baseline(); +#if (BFDEBUG_LEVEL > 0) + endtime = LbTimerClock(); +#endif + quit_game = 0; + if ((game.operation_flags & GOF_SingleLevel) != 0) + exit_keeper=true; +#if (BFDEBUG_LEVEL > 0) + playtime += endtime-starttime; +#endif + SYNCDBG(0,"Play time is %lu seconds",playtime>>10); + reset_eye_lenses(); + close_packet_file(); + game.packet_load_enable = false; + game.packet_save_enable = false; + } // end while + + // Stop the movie recording if it's on + if ((game.system_flags & GSF_CaptureMovie) != 0) { + movie_record_stop(); } + ShutDownSDLAudio(); + SYNCDBG(7,"Done"); } + + /******************************************************************************/ #ifdef __cplusplus } diff --git a/src/game_loop.h b/src/game_loop.h index ef26adc595..1afb8d3ff5 100644 --- a/src/game_loop.h +++ b/src/game_loop.h @@ -1,10 +1,8 @@ /******************************************************************************/ // Free implementation of Bullfrog's Dungeon Keeper strategy game. /******************************************************************************/ -/** @file game_legacy.h +/** @file game_loop.h * Header file for game_loop.c. - * @author Loobinex - * @date 14 Jul 2021 * @par Copying and copyrights: * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,11 +15,7 @@ #define DK_GAMELOOP_H #include "bflib_basics.h" -#include "keeperfx.hpp" -#include "game_legacy.h" -#include "game_merge.h" #include "globals.h" -#include "thing_effects.h" #ifdef __cplusplus @@ -30,9 +24,6 @@ extern "C" { /******************************************************************************/ void process_dungeon_destroy(struct Thing* heartng); void initialise_devastate_dungeon_from_heart(PlayerNumber plyr_idx); -void update_manufacturing(void); -void update_research(void); -/******************************************************************************/ /******************************************************************************/ #ifdef __cplusplus } diff --git a/src/game_update.cpp b/src/game_update.cpp new file mode 100644 index 0000000000..15ff351ccb --- /dev/null +++ b/src/game_update.cpp @@ -0,0 +1,586 @@ +/******************************************************************************/ +// Free implementation of Bullfrog's Dungeon Keeper strategy game. +/******************************************************************************/ +/** @file game_update.cpp + * Module which contains functions for updating the game state. + * @par Copying and copyrights: + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +/******************************************************************************/ +#include "pre_inc.h" +#include "platform.h" +#include "keeperfx.hpp" + +#include "bflib_video.h" +#include "bflib_sound.h" +#include "bflib_planar.h" + +#include "api.h" +#include "version.h" +#include "gui_msgs.h" +#include "packets.h" +#include "config_terrain.h" +#include "config_creature.h" +#include "lua_triggers.h" +#include "lvl_script.h" +#include "thing_list.h" +#include "player_utils.h" +#include "player_computer.h" +#include "engine_camera.h" +#include "local_camera.h" +#include "engine_textures.h" +#include "thing_stats.h" +#include "thing_creature.h" +#include "thing_objects.h" +#include "thing_effects.h" +#include "thing_doors.h" +#include "slab_data.h" +#include "room_entrance.h" +#include "room_util.h" +#include "map_columns.h" +#include "map_events.h" +#include "map_blocks.h" +#include "creature_control.h" +#include "creature_states.h" +#include "light_data.h" +#include "magic_powers.h" +#include "power_process.h" +#include "power_hand.h" +#include "game_merge.h" +#include "gui_soundmsgs.h" +#include "sounds.h" +#include "vidfade.h" +#include "game_legacy.h" +#include "game_loop.h" +#include "room_library.h" +#include "room_workshop.h" +#include + +#include "post_inc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static void check_players_won(void) +{ + SYNCDBG(8,"Starting"); + + if (!network_is_active()) + return; + + struct PlayerInfo* curPlayer; + for (PlayerNumber playerIdx = 0; playerIdx < PLAYERS_COUNT; ++playerIdx) + { + curPlayer = get_player(playerIdx); + if (!player_exists(curPlayer) || (curPlayer->is_active != 1) || (curPlayer->victory_state != VicS_Undecided)) + continue; + + // check if any other player is still alive + TbBool LivingOpponent = false; + for (PlayerNumber secondPlayerIdx = 0; secondPlayerIdx < PLAYERS_COUNT; ++secondPlayerIdx) + { + if (secondPlayerIdx == playerIdx) + continue; + + struct PlayerInfo* otherPlayer = get_player(secondPlayerIdx); + if (player_exists(otherPlayer) && otherPlayer->victory_state == VicS_Undecided) + { + struct Thing* heartng = get_player_soul_container(secondPlayerIdx); + if (heartng->active_state != ObSt_BeingDestroyed) + { + LivingOpponent = true; + break; + } + } + } + if (LivingOpponent == false) + { + set_player_as_won_level(curPlayer); + return; + } + } +} + +static void check_players_lost(void) +{ + long i; + SYNCDBG(8,"Starting"); + struct PlayerInfo* player; + struct Dungeon* dungeon; + for (i=0; i < PLAYERS_COUNT; i++) + { + player = get_player(i); + dungeon = get_players_dungeon(player); + if (player_exists(player) && (player->is_active == 1)) + { + struct Thing *heartng; + heartng = get_player_soul_container(i); + if (heartng->owner != i) + { + init_player_start(player, true); + if (dungeon->dnheart_idx == 0) + { + initialise_devastate_dungeon_from_heart(player->id_number); + } + } + if ((!thing_exists(heartng) || ((heartng->active_state == ObSt_BeingDestroyed) && !(dungeon->backup_heart_idx > 0))) && (player->victory_state == VicS_Undecided)) + { + event_kill_all_players_events(i); + set_player_as_lost_level(player); + //this would easily prevent computer player activities on dead player, but it also makes dead player unable to use + //floating spirit, so it can't be done this way: player->is_active = 0; + if (is_my_player_number(i)) { + LbPaletteSet(engine_palette); + } + } + } + } +} + +static void blast_slab(MapSlabCoord slb_x, MapSlabCoord slb_y, PlayerNumber plyr_idx) +{ + struct SlabMap *slb; + slb = get_slabmap_block(slb_x, slb_y); + if (slabmap_block_invalid(slb)) { + return; + } + if (slabmap_owner(slb) != plyr_idx) { + return; + } + struct Thing *doortng; + doortng = get_door_for_position(slab_subtile_center(slb_x), slab_subtile_center(slb_y)); + if (!thing_is_invalid(doortng)) { + destroy_door(doortng); + } + struct SlabConfigStats *slabst; + slabst = get_slab_stats(slb); + if (slabst->category == SlbAtCtg_FortifiedGround) + { + place_slab_type_on_map(SlbT_PATH, slab_subtile_center(slb_x), slab_subtile_center(slb_y), game.neutral_player_num, 1); + decrease_dungeon_area(plyr_idx, 1); + do_unprettying(game.neutral_player_num, slb_x, slb_y); + do_slab_efficiency_alteration(slb_x, slb_y); + struct Coord3d pos; + pos.x.val = subtile_coord_center(slab_subtile_center(slb_x)); + pos.y.val = subtile_coord_center(slab_subtile_center(slb_y)); + pos.z.val = get_floor_height_at(&pos); + create_effect_element(&pos, TngEffElm_RedFlameBig, plyr_idx); + } +} + +static void process_dungeon_devastation_effects(void) +{ + SYNCDBG(8,"Starting"); + int plyr_idx; + for (plyr_idx=0; plyr_idx < PLAYERS_COUNT; plyr_idx++) + { + struct Dungeon *dungeon; + dungeon = get_players_num_dungeon(plyr_idx); + if (dungeon->devastation_turn == 0) + continue; + if ((get_gameturn() & 1) != 0) + continue; + dungeon->devastation_turn++; + if (dungeon->devastation_turn >= max(game.map_tiles_x,game.map_tiles_y)) + continue; + MapSlabCoord slb_x; + MapSlabCoord slb_y; + int i; + int range; + slb_x = subtile_slab(dungeon->devastation_centr_x) - dungeon->devastation_turn; + slb_y = subtile_slab(dungeon->devastation_centr_y) - dungeon->devastation_turn; + range = 2*dungeon->devastation_turn; + for (i = 0; i <= range; i++) + { + blast_slab(slb_x + i, slb_y, dungeon->owner); + blast_slab(slb_x + i, slb_y + range, dungeon->owner); + } + for (i = 0; i <= range; i++) + { + blast_slab(slb_x, slb_y + i, dungeon->owner); + blast_slab(slb_x + range, slb_y + i, dungeon->owner); + } + } +} + +/** + * Increments paydays_owed for all players creatures + * returns amount of creatures needing payday for player + */ +static int set_players_creatures_to_get_paid(PlayerNumber plyr_idx) +{ + unsigned long k; + long i; + int count = 0; + const struct StructureList *slist; + slist = get_list_for_thing_class(TCls_Creature); + i = slist->index; + k = 0; + while (i != 0) + { + struct Thing *thing; + thing = thing_get(i); + if (thing_is_invalid(thing)) + { + ERRORLOG("Jump to invalid thing detected"); + break; + } + i = thing->next_of_class; + // Per-thing code + if (thing->owner == plyr_idx) + { + struct CreatureModelConfig *crconf; + crconf = creature_stats_get_from_thing(thing); + if (crconf->pay != 0) + { + struct CreatureControl *cctrl; + cctrl = creature_control_get_from_thing(thing); + if (cctrl->paydays_advanced > 0) + { + cctrl->paydays_advanced--; + } else + { + if (!creature_is_kept_in_custody_by_enemy(thing)) + { + cctrl->paydays_owed++; + count++; + } + else + { + cctrl->paydays_advanced--; + } + } + } + } + // Per-thing code ends + k++; + if (k > THINGS_COUNT) + { + ERRORLOG("Infinite loop detected when sweeping things list"); + break; + } + } + return count; +} + +static void process_payday(void) +{ + PlayerNumber plyr_idx; + for (plyr_idx=0; plyr_idx < PLAYERS_COUNT; plyr_idx++) + { + game.pay_day_progress[plyr_idx] = game.pay_day_progress[plyr_idx] + (game.conf.rules[plyr_idx].gameplay.pay_day_speed / 100); + if (player_is_roaming(plyr_idx) || (plyr_idx == game.neutral_player_num)) { + continue; + } + struct PlayerInfo *player; + player = get_player(plyr_idx); + if (player_exists(player) && (player->is_active == 1)) + { + compute_and_update_player_payday_total(plyr_idx); + compute_and_update_player_backpay_total(plyr_idx); + } + } + int player_paid_creatures_count; + for (plyr_idx = 0; plyr_idx < PLAYERS_COUNT; plyr_idx++) + { + if (game.conf.rules[plyr_idx].gameplay.pay_day_gap <= game.pay_day_progress[plyr_idx]) + { + if (is_my_player_number(plyr_idx)) + output_message(SMsg_Payday, 0); + game.pay_day_progress[plyr_idx] = 0; + player_paid_creatures_count = set_players_creatures_to_get_paid(plyr_idx); + if (player_paid_creatures_count > 0) + { + struct Dungeon *dungeon = get_players_num_dungeon(plyr_idx); + event_create_event_or_update_nearby_existing_event(0, 0, EvKind_CreaturePayday, plyr_idx, dungeon->creatures_total_pay); + } + } + } +} + +static void process_dungeons(void) +{ + SYNCDBG(7,"Starting"); + check_players_won(); + check_players_lost(); + process_dungeon_power_magic(); + process_dungeon_devastation_effects(); + process_entrance_generation(); + process_payday(); + process_things_in_dungeon_hand(); + SYNCDBG(9,"Finished"); +} + +static void update_near_creatures_for_footsteps(int32_t *near_creatures, const struct Coord3d *srcpos) +{ + long near_distance[3]; + // Don't allow creatures which are far by over 20 subtiles + near_distance[0] = subtile_coord(20,0); + near_distance[1] = subtile_coord(20,0); + near_distance[2] = subtile_coord(20,0); + near_creatures[0] = 0; + near_creatures[1] = 0; + near_creatures[2] = 0; + // Find the closest thing for footsteps + struct Thing *thing; + unsigned long k; + long i; + const struct StructureList *slist; + slist = get_list_for_thing_class(TCls_Creature); + i = slist->index; + k = 0; + while (i != 0) + { + thing = thing_get(i); + if (thing_is_invalid(thing)) + { + ERRORLOG("Jump to invalid thing detected"); + break; + } + i = thing->next_of_class; + // Per-thing code + thing->state_flags &= ~TF1_DoFootsteps; + if ( (!thing_is_picked_up(thing)) && (!thing_is_dragged_or_pulled(thing)) ) + { + struct CreatureSound *crsound; + crsound = get_creature_sound(thing, CrSnd_Foot); + if (crsound->index != 0) + { + struct CreatureControl *cctrl; + cctrl = creature_control_get_from_thing(thing); + long ndist; + ndist = get_chessboard_distance(srcpos, &thing->mappos); + if (ndist < near_distance[0]) + { + if (((cctrl->distance_to_destination != 0) && ((int)thing->floor_height >= (int)thing->mappos.z.val)) + || ((thing->movement_flags & TMvF_Flying) != 0)) + { + // Insert the new item to our list + int n; + for (n = 2; n>0; n--) + { + near_creatures[n] = near_creatures[n-1]; + near_distance[n] = near_distance[n-1]; + } + near_distance[0] = ndist; + near_creatures[0] = thing->index; + } + } + } + } + // Per-thing code ends + k++; + if (k > THINGS_COUNT) + { + ERRORLOG("Infinite loop detected when sweeping things list"); + break; + } + } +} + +static long stop_playing_flight_sample_in_all_flying_creatures(void) +{ + struct Thing *thing; + unsigned long k; + long i; + long naffected; + naffected = 0; + const struct StructureList *slist; + slist = get_list_for_thing_class(TCls_Creature); + i = slist->index; + k = 0; + while (i != 0) + { + thing = thing_get(i); + if (thing_is_invalid(thing)) + { + ERRORLOG("Jump to invalid thing detected"); + break; + } + i = thing->next_of_class; + // Per-thing code + if ((get_creature_model_flags(thing) & CMF_IsDiptera) && ((thing->state_flags & TF1_DoFootsteps) == 0)) + { + if ( S3DEmitterIsPlayingSample(thing->snd_emitter_id, 25) ) { + S3DDeleteSampleFromEmitter(thing->snd_emitter_id, 25); + } + } + // Per-thing code ends + k++; + if (k > THINGS_COUNT) + { + ERRORLOG("Infinite loop detected when sweeping things list"); + break; + } + } + return naffected; +} + +static void update_footsteps_nearest_camera(struct Camera *cam) +{ + static long timeslice = 0; + static int32_t near_creatures[3]; + struct Coord3d srcpos; + SYNCDBG(6,"Starting"); + if (cam == NULL) + return; + srcpos.x.val = cam->mappos.x.val; + srcpos.y.val = cam->mappos.y.val; + srcpos.z.val = cam->mappos.z.val; + if (timeslice == 0) { + update_near_creatures_for_footsteps(near_creatures, &srcpos); + } + long i; + for (i=0; i < 3; i++) + { + struct Thing *thing; + if (near_creatures[i] == 0) + break; + thing = thing_get(near_creatures[i]); + if (thing_is_creature(thing)) { + thing->state_flags |= TF1_DoFootsteps; + play_thing_walking(thing); + } + } + if (timeslice == 0) + { + stop_playing_flight_sample_in_all_flying_creatures(); + } + timeslice = (timeslice + 1) % 4; +} + +static int clear_active_dungeons_stats(void) +{ + struct Dungeon *dungeon; + int i; + for (i=0; i < PLAYERS_COUNT; i++) + { + dungeon = get_dungeon(i); + if (dungeon_invalid(dungeon)) + break; + memset((char *)dungeon->crmodel_state_type_count, 0, game.conf.crtr_conf.model_count * STATE_TYPES_COUNT * sizeof(uint16_t)); + memset((char *)dungeon->guijob_all_creatrs_count, 0, game.conf.crtr_conf.model_count *3*sizeof(uint16_t)); + memset((char *)dungeon->guijob_angry_creatrs_count, 0, game.conf.crtr_conf.model_count *3*sizeof(uint16_t)); + } + return i; +} + + + + + +/** + * rules can change by dkscript/lua. + * Checks if a gamerule for lighting has changed and updates the lights if they are. + * This function also refreshes the light status of the map. +*/ +static void update_global_lighting() +{ + if (!game.lish.light_auto_sync) + return; + + // Check if any values have changed + if ( + game.conf.rules[0].gameplay.global_ambient_light != game.lish.global_ambient_light || + game.conf.rules[0].gameplay.light_enabled != game.lish.light_enabled + ){ + + // GlobalAmbientLight + if (game.conf.rules[0].gameplay.global_ambient_light != game.lish.global_ambient_light) + { + game.lish.global_ambient_light = game.conf.rules[0].gameplay.global_ambient_light; + } + + // LightEnabled + if (game.conf.rules[0].gameplay.light_enabled != game.lish.light_enabled) + { + game.lish.light_enabled = game.conf.rules[0].gameplay.light_enabled; + } + + // Refresh the lights + light_stat_refresh(); + } +} + +void update(void) +{ + struct PlayerInfo *player; + SYNCDBG(4,"Starting for turn %ld",(long)get_gameturn()); + + update_local_cameras(); + process_packets(); + api_update_server(); + + if (quit_game || exit_keeper) { + return; + } + if (game.game_kind == GKind_NonInteractiveState) + { + game.map_changed_for_navigation = 0; + return; + } + player = get_my_player(); + + if (!flag_is_set(game.operation_flags,GOF_Paused)) + { + if (flag_is_set(player->additional_flags,PlaAF_LightningPaletteIsActive)) + { + PaletteSetPlayerPalette(player, engine_palette); + clear_flag(player->additional_flags, PlaAF_LightningPaletteIsActive); + } + clear_active_dungeons_stats(); + update_creature_pool_state(); + if ((get_gameturn() & 0x01) != 0) + update_animating_texture_maps(); + update_things(); + process_rooms(); + process_dungeons(); + update_research(); + update_manufacturing(); + event_process_events(); + update_all_events(); + process_level_script(); + process_fx_lines(); + lua_on_game_tick(); + if ((game.view_mode_flags & GNFldD_ComputerPlayerProcessing) != 0) + process_computer_players2(); + process_players(); + process_action_points(); + player = get_my_player(); + if (player->view_mode == PVM_CreatureView) + { + struct Thing *thing = thing_get(player->controlled_thing_idx); + update_first_person_object_ambience(thing); + } + update_footsteps_nearest_camera(get_player_active_camera(player)); + PaletteFadePlayer(player); + process_armageddon(); + update_global_lighting(); +#if (BFDEBUG_LEVEL > 9) + lights_stats_debug_dump(); + things_stats_debug_dump(); + creature_stats_debug_dump(); +#endif + game.play_gameturn++; + if (game.turns_packetoff == game.play_gameturn) + exit_keeper = 1; + } + + message_update(); + update_all_players_cameras(); + update_player_sounds(); + SYNCDBG(6,"Finished"); +} + + + +/******************************************************************************/ +#ifdef __cplusplus +} +#endif +/******************************************************************************/ +/******************************************************************************/ diff --git a/src/gui_msgs.c b/src/gui_msgs.c index 5b4d8c784a..63963af92b 100644 --- a/src/gui_msgs.c +++ b/src/gui_msgs.c @@ -34,6 +34,7 @@ #include "sprites.h" #include "custom_sprites.h" #include "keeperfx.hpp" +#include "timer.h" #include "post_inc.h" /******************************************************************************/ diff --git a/src/gui_parchment.c b/src/gui_parchment.c index 671bdb2294..190eff93e8 100644 --- a/src/gui_parchment.c +++ b/src/gui_parchment.c @@ -221,6 +221,11 @@ enum OverheadMapStyle { OMapSt_Wall, }; +static TbPixel get_player_path_colour(unsigned short owner) +{ + return player_path_colours[get_player_color_idx(owner % PLAYERS_COUNT)]; +} + static int get_overhead_mapblock_style(const struct Map* mapblk, const struct SlabMap* slb, MapSlabCoord slb_x, MapSlabCoord slb_y, PlayerNumber plyr_idx, int gui_frame, TbPixel neutral_colour) { PlayerNumber owner = slb->owner; @@ -768,6 +773,246 @@ void draw_zoom_box_things_on_mapblk(struct Map *mapblk,unsigned short subtile_si } } +static void scale_tmap2(long texture_block_index, long flags, long fade_level, long screen_x, long screen_y, long scaled_width, long scaled_height) +{ + if ((scaled_width == 0) || (scaled_height == 0)) { + return; + } + long xstart; + long ystart; + long xend; + long yend; + char orient; + switch (flags) + { + case 0: + xstart = 0; + ystart = 0; + xend = 2097151 / scaled_width; + yend = 2097151 / scaled_height; + orient = 0; + break; + case 0x10: + xstart = 2097151; + ystart = 0; + xend = -2097151 / scaled_width; + yend = 2097151 / scaled_height; + orient = 0; + break; + case 0x20: + xstart = 0; + ystart = 2097151; + xend = 2097151 / scaled_width; + yend = -2097151 / scaled_height; + orient = 0; + break; + case 0x30: + xstart = 2097151; + ystart = 2097151; + xend = -2097151 / scaled_width; + yend = -2097151 / scaled_height; + orient = 0; + break; + case 0x40: + ystart = 0; + xstart = 0; + yend = 2097151 / scaled_height; + xend = 2097151 / scaled_width; + orient = 1; + break; + case 0x50: + ystart = 0; + xstart = 2097151; + yend = 2097151 / scaled_height; + xend = -2097151 / scaled_width; + orient = 1; + break; + case 0x60: + ystart = 2097151; + xstart = 0; + yend = -2097151 / scaled_height; + xend = 2097151 / scaled_width; + orient = 1; + break; + case 0x70: + xstart = 2097151; + ystart = 2097151; + yend = -2097151 / scaled_height; + xend = -2097151 / scaled_width; + orient = 1; + break; + default: + return; + } + long local_screen_x; + long local_screen_y; + local_screen_x = screen_x; + if (local_screen_x < 0) + { + scaled_width += local_screen_x; + if (scaled_width < 0) { + return; + } + xstart -= xend * local_screen_x; + local_screen_x = 0; + } + if (local_screen_x + scaled_width > vec_window_width) + { + scaled_width = vec_window_width - local_screen_x; + if (scaled_width < 0) { + return; + } + } + local_screen_y = screen_y; + if (local_screen_y < 0) + { + scaled_height += local_screen_y; + if (scaled_height < 0) { + return; + } + ystart -= local_screen_y * yend; + local_screen_y = 0; + } + if (local_screen_y + scaled_height > vec_window_height) + { + scaled_height = vec_window_height - local_screen_y; + if (scaled_height < 0) { + return; + } + } + int i; + int32_t hlimits[480]; + int32_t wlimits[640]; + int32_t *xlim; + int32_t *ylim; + unsigned char *dbuf; + unsigned char *block; + if (!orient) + { + xlim = wlimits; + for (i = scaled_width; i > 0; i--) + { + *xlim = xstart; + xlim++; + xstart += xend; + } + ylim = hlimits; + for (i = scaled_height; i > 0; i--) + { + *ylim = ystart; + ylim++; + ystart += yend; + } + dbuf = &vec_screen[local_screen_x + local_screen_y * vec_screen_width]; + block = block_ptrs[texture_block_index]; + ylim = hlimits; + long px; + long py; + int srcx; + int srcy; + unsigned char *d; + if ( fade_level >= 0 ) + { + for (py = scaled_height; py > 0; py--) + { + xlim = wlimits; + d = dbuf; + srcy = (((*ylim) & 0xFF0000u) >> 16); + for (px = scaled_width; px > 0; px--) + { + srcx = (((*xlim) & 0xFF0000u) >> 16); + xlim++; + *d = pixmap.fade_tables[256 * fade_level + block[(srcy << 8) + srcx]]; + ++d; + } + dbuf += vec_screen_width; + ylim++; + } + } else + { + for (py = scaled_height; py > 0; py--) + { + xlim = wlimits; + d = dbuf; + srcy = (((*ylim) & 0xFF0000u) >> 16); + for (px = scaled_width; px > 0; px--) + { + srcx = (((*xlim) & 0xFF0000u) >> 16); + xlim++; + *d = block[(srcy << 8) + srcx]; + ++d; + } + dbuf += vec_screen_width; + ylim++; + } + } + } else + { + ylim = wlimits; + for (i = scaled_height; i > 0; i--) + { + *ylim = ystart; + ylim++; + ystart += yend; + } + xlim = hlimits; + for (i = scaled_width; i > 0; i--) + { + *xlim = xstart; + xlim++; + xstart += xend; + } + dbuf = &vec_screen[local_screen_x + local_screen_y * vec_screen_width]; + block = block_ptrs[texture_block_index]; + ylim = wlimits; + long px; + long py; + int srcx; + int srcy; + unsigned char *d; + if ( fade_level >= 0 ) + { + for (py = scaled_height; py > 0; py--) + { + xlim = hlimits; + d = dbuf; + srcy = (((*ylim) & 0xFF0000u) >> 16); + for (px = scaled_width; px > 0; px--) + { + srcx = (((*xlim) & 0xFF0000u) >> 16); + xlim++; + *d = pixmap.fade_tables[256 * fade_level + block[(srcx << 8) + srcy]]; + ++d; + } + dbuf += vec_screen_width; + ylim++; + } + } else + { + for (py = scaled_height; py > 0; py--) + { + xlim = hlimits; + d = dbuf; + srcy = (((*ylim) & 0xFF0000u) >> 16); + for (px = scaled_width; px > 0; px--) + { + srcx = (((*xlim) & 0xFF0000u) >> 16); + xlim++; + *d = block[(srcx << 8) + srcy]; + ++d; + } + dbuf += vec_screen_width; + ylim++; + } + } + } +} + +static void draw_texture(int32_t texture_x, int32_t texture_y, int32_t texture_width, int32_t texture_height, int32_t texture_block_index, int32_t flags, int32_t fade_level) +{ + scale_tmap2(texture_block_index, flags, fade_level, texture_x / pixel_size, texture_y / pixel_size, texture_width / pixel_size, texture_height / pixel_size); +} + void draw_zoom_box_terrain(long scrtop_x, long scrtop_y, int stl_x, int stl_y, PlayerNumber plyr_idx, long draw_tiles_x, long draw_tiles_y, int subtile_size) { lbDisplay.DrawFlags = 0; diff --git a/src/gui_tooltips.c b/src/gui_tooltips.c index de687a0b1d..f97fc1fd77 100644 --- a/src/gui_tooltips.c +++ b/src/gui_tooltips.c @@ -58,13 +58,14 @@ const char jtytext[] = "Jonty here : ...I am writing this at 4am on Keepers la "and the little one, Crofty, Scooper, Jason Stanton [a cup of coffee], Aaron Senna, Mike Dorell, Ian Howie, Helen Thain, Alex Forest-Hay, Lee Hazelwood, Vicky Arnold, Guy Simmons, Shin, Val Taylor.... If I forgot you I am sorry... but sleep is due to me... and I have a dream to live..."; /******************************************************************************/ - -float render_tooltip_scroll_offset; // Rendering float -float render_tooltip_scroll_timer; // Rendering float struct ToolTipBox tool_tip_box; - - struct TooltipDebugInfo tool_tip_dbg = {0}; +/******************************************************************************/ + +static float render_tooltip_scroll_offset; // Rendering float +static float render_tooltip_scroll_timer; // Rendering float +static unsigned short tool_tip_time; +static unsigned short help_tip_time; /******************************************************************************/ static inline void reset_scrolling_tooltip(void) diff --git a/src/keeperfx.hpp b/src/keeperfx.hpp index cd4ad7524d..bdf51a25e1 100644 --- a/src/keeperfx.hpp +++ b/src/keeperfx.hpp @@ -161,15 +161,11 @@ extern unsigned char exit_keeper; extern unsigned char quit_game; extern unsigned char is_running_under_wine; extern int continue_game_option_available; -extern int32_t last_mouse_x; -extern int32_t last_mouse_y; extern int FatalError; extern int32_t define_key_scroll_offset; extern uint32_t time_last_played_demo; extern short drag_menu_x; extern short drag_menu_y; -extern unsigned short tool_tip_time; -extern unsigned short help_tip_time; extern int32_t pointer_x; extern int32_t pointer_y; extern int32_t block_pointed_at_x; @@ -189,8 +185,6 @@ extern int32_t total_lights; extern unsigned char do_lights; extern struct Thing *thing_pointed_at; extern struct Map *me_pointed_at; -extern int32_t my_mouse_x; -extern int32_t my_mouse_y; extern char *level_names_data; extern char *end_level_names_data; extern unsigned char *frontend_backup_palette; @@ -212,33 +206,21 @@ extern struct StartupParameters start_params; //Functions - reworked short setup_game(void); void game_loop(void); -short reset_game(void); void update(void); TbBool can_thing_be_queried(struct Thing *thing, PlayerNumber plyr_idx); -struct Thing *get_queryable_object_near(MapCoord pos_x, MapCoord pos_y, PlayerNumber plyr_idx); -long packet_place_door(MapSubtlCoord stl_x, MapSubtlCoord stl_y, PlayerNumber plyr_idx, ThingModel dormodel, TbBool allowed); TbBool all_dungeons_destroyed(const struct PlayerInfo *win_player); void reset_gui_based_on_player_mode(void); void reinit_tagged_blocks_for_player(PlayerNumber plyr_idx); -void draw_flame_breath(struct Coord3d *pos1, struct Coord3d *pos2, long delta_step, long num_per_step, short ef_or_efel_model, ThingIndex parent_idx); -void draw_lightning(const struct Coord3d* pos1, const struct Coord3d* pos2, long eeinterspace, short ef_or_efel_model); void toggle_hero_health_flowers(void); -void check_players_won(void); -void check_players_lost(void); -void process_things_in_dungeon_hand(void); -void process_payday(void); + TbBool toggle_computer_player(PlayerNumber plyr_idx); void PaletteSetPlayerPalette(struct PlayerInfo *player, unsigned char *pal); -void set_player_cameras_position(struct PlayerInfo *player, int32_t pos_x, int32_t pos_y); -void init_player_types(); -void init_keepers_map_exploration(void); void clear_creature_pool(void); void reset_creature_max_levels(void); void reset_script_timers_and_flags(void); void add_creature_to_pool(ThingModel kind, int32_t amount); -void draw_texture(int32_t a1, int32_t a2, int32_t a3, int32_t a4, int32_t a5, int32_t a6, int32_t a7); short zoom_to_next_annoyed_creature(void); @@ -258,13 +240,7 @@ void draw_gold_total(PlayerNumber plyr_idx, int32_t scr_x, int32_t scr_y, int32_ void draw_mini_things_in_hand(long x, long y); TbBool screen_to_map(struct Camera *camera, int32_t screen_x, int32_t screen_y, struct Coord3d *mappos); void update_creatr_model_activities_list(TbBool forced); -TbBool any_player_close_enough_to_see(const struct Coord3d *pos); -void affect_nearby_stuff_with_vortex(struct Thing *thing); -void affect_nearby_friends_with_alarm(struct Thing *thing); -long apply_wallhug_force_to_boulder(struct Thing *thing); -long process_boulder_collision(struct Thing *boulder, struct Coord3d *pos, int direction_x, int direction_y); void lightning_modify_palette(struct Thing *thing); -unsigned long lightning_is_close_to_player(struct PlayerInfo *player, struct Coord3d *pos); unsigned long seed_check_random(unsigned long range, uint32_t *seed, const char *func_name, unsigned long place); void place_single_slab_type_on_map(SlabKind slbkind, MapSlabCoord slb_x, MapSlabCoord slb_y, PlayerNumber plyr_idx); @@ -289,43 +265,19 @@ void dump_thing_held_by_any_player(struct Thing *thing); void instant_instance_selected(CrInstance check_inst_id); void centre_engine_window(void); void change_engine_window_relative_size(long w_delta, long h_delta); -void update_thing_animation(struct Thing *thing); -long update_cave_in(struct Thing *thing); void initialise_map_collides(void); void initialise_map_health(void); void setup_mesh_randomizers(void); void setup_stuff(void); void give_shooter_drained_health(struct Thing *shooter, HitPoints health_delta); long get_foot_creature_has_down(struct Thing *thing); -void process_keeper_spell_aura(struct Thing *thing); void init_seeds(); -TbPixel get_player_path_colour(unsigned short owner); - void startup_saved_packet_game(void); -void faststartup_saved_packet_game(void); void reinit_level_after_load(void); void redetect_screen_refresh_rate_for_draw(); -void update_time(void); -extern TbClockMSec timerstarttime; -struct TimerTime { - unsigned char Hours; - unsigned char Minutes; - unsigned char Seconds; - unsigned short MSeconds; -}; -extern struct TimerTime Timer; -extern TbBool TimerGame; -extern TbBool TimerNoReset; -extern TbBool TimerFreeze; -struct GameTime { - unsigned char Seconds; - unsigned char Minutes; - unsigned char Hours; -}; -struct GameTime get_game_time(unsigned long turns, unsigned long fps); #ifdef __cplusplus } diff --git a/src/main.cpp b/src/main.cpp index d00b45948e..4b7cd6cd51 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -132,7 +132,9 @@ #include "net_input_lag.h" #include "moonphase.h" #include "frontmenu_ingame_map.h" +#include "room_library.h" #include +#include "timer.h" #ifdef FUNCTESTING #include "ftests/ftest.h" @@ -144,7 +146,7 @@ #define strcasecmp _stricmp #endif -short do_draw; + short default_loc_player = 0; struct StartupParameters start_params; char autostart_multiplayer_campaign[80] = ""; @@ -158,15 +160,12 @@ unsigned char exit_keeper; unsigned char quit_game; unsigned char is_running_under_wine = false; int continue_game_option_available; -int32_t last_mouse_x; -int32_t last_mouse_y; + int FatalError; int32_t define_key_scroll_offset; uint32_t time_last_played_demo; short drag_menu_x; short drag_menu_y; -unsigned short tool_tip_time; -unsigned short help_tip_time; int32_t pointer_x; int32_t pointer_y; int32_t block_pointed_at_x; @@ -186,8 +185,6 @@ int32_t total_lights; unsigned char do_lights; struct Thing *thing_pointed_at; struct Map *me_pointed_at; -int32_t my_mouse_x; -int32_t my_mouse_y; char *level_names_data; char *end_level_names_data; unsigned char *frontend_backup_palette; @@ -204,35 +201,19 @@ extern "C" { TbBool force_player_num = false; /******************************************************************************/ -extern void faststartup_network_game(CoroutineLoop *context); -extern void faststartup_saved_packet_game(void); -extern TngUpdateRet damage_creatures_with_physical_force(struct Thing *thing, ModTngFilterParam param); -extern CoroutineLoopState set_not_has_quit(CoroutineLoop *context); -extern void startup_network_game(CoroutineLoop *context, TbBool local); -/******************************************************************************/ -TbClockMSec timerstarttime = 0; -struct TimerTime Timer; -TbBool TimerGame = false; -TbBool TimerNoReset = false; -TbBool TimerFreeze = false; + /******************************************************************************/ int32_t fps_limit_current = 0; int32_t fps_limit_main = 0; // -1 if auto int32_t fps_limit_secondary = 0; -static long double process_frame_time = 0; -static long double time_since_last_draw = 0; -static long double average_frame_draw_time = 1; -static long double multiplayer_clock_adjust = 1; +long double process_frame_time = 0; +long double time_since_last_draw = 0; +long double multiplayer_clock_adjust = 1; long double host_packet_received = 1; -float interpolate_time = 0; -/******************************************************************************/ -TbPixel get_player_path_colour(unsigned short owner) -{ - return player_path_colours[get_player_color_idx(owner % PLAYERS_COUNT)]; -} +/******************************************************************************/ void setup_stuff(void) { @@ -241,31 +222,6 @@ void setup_stuff(void) init_alpha_table(); } -TbBool should_use_delta_time_on_menu() -{ - switch (frontend_menu_state) { - case FeSt_MAIN_MENU: - case FeSt_FELOAD_GAME: - case FeSt_NET_SERVICE: /**< Network service selection, where player can select Serial/Modem/IPX/TCP IP/1 player. */ - case FeSt_NET_SESSION: /**< Network session selection screen, where list of games is displayed, with possibility to join or create own game. */ - case FeSt_NET_START: /**< Network game start screen (the menu with chat), when created new session or joined existing session. */ - case FeSt_LEVEL_STATS: - case FeSt_HIGH_SCORES: - case FeSt_FEDEFINE_KEYS: - case FeSt_FEOPTIONS: - case FeSt_LEVEL_SELECT: - case FeSt_CAMPAIGN_SELECT: - case FeSt_MAPPACK_SELECT: - case FeSt_MP_MAPPACK_SELECT: - case FeSt_LAND_VIEW: - case FeSt_NETLAND_VIEW: - case FeSt_TORTURE: - return true; - default: - return false; - } -} - TbBool all_dungeons_destroyed(const struct PlayerInfo *win_player) { long win_plyr_idx; @@ -282,620 +238,6 @@ TbBool all_dungeons_destroyed(const struct PlayerInfo *win_player) return true; } -void clear_creature_pool(void) -{ - memset(&game.pool,0,sizeof(struct CreaturePool)); - game.pool.is_empty = true; -} - -void give_shooter_drained_health(struct Thing *shooter, HitPoints health_delta) -{ - struct CreatureControl *cctrl; - HitPoints max_health; - HitPoints health; - if ( !thing_exists(shooter) ) - return; - cctrl = creature_control_get_from_thing(shooter); - max_health = cctrl->max_health; - health = shooter->health + health_delta; - if (health < max_health) { - shooter->health = health; - } else { - shooter->health = max_health; - } -} - -long get_foot_creature_has_down(struct Thing *thing) -{ - struct CreatureControl *cctrl; - unsigned short val; - long i; - int n; - cctrl = creature_control_get_from_thing(thing); - val = thing->current_frame; - if (val == (cctrl->anim_time >> 8)) - return 0; - unsigned short frame = (creature_is_dragging_something(thing)) ? CGI_Drag : CGI_Ambulate; - n = get_creature_model_graphics(thing->model, frame); - i = get_td_animation_sprite(n); - if (i != thing->anim_sprite) - return 0; - if (val == 1) - return 1; - if (val == 4) - return 2; - return 0; -} - -void process_keeper_spell_aura(struct Thing *thing) -{ - struct CreatureControl *cctrl; - TRACE_THING(thing); - cctrl = creature_control_get_from_thing(thing); - cctrl->spell_aura_duration--; - if (cctrl->spell_aura_duration <= 0) - { - cctrl->spell_aura = 0; - return; - } - struct Coord3d pos; - long amp; - long direction; - long delta_x; - long delta_y; - amp = 5 * thing->clipbox_size_xy / 8; - direction = THING_RANDOM(thing, DEGREES_360); - delta_x = (amp * LbSinL(direction) >> 8); - delta_y = (amp * LbCosL(direction) >> 8); - pos.x.val = thing->mappos.x.val + (delta_x >> 8); - pos.y.val = thing->mappos.y.val - (delta_y >> 8); - pos.z.val = thing->mappos.z.val; - - create_used_effect_or_element(&pos, cctrl->spell_aura, thing->owner, thing->index); -} - -unsigned long lightning_is_close_to_player(struct PlayerInfo *player, struct Coord3d *pos) -{ - struct Camera *camera = get_player_active_camera(player); - if (camera == NULL) - return false; - return get_chessboard_distance(&camera->mappos, pos) < subtile_coord(45,0); -} - -void affect_nearby_stuff_with_vortex(struct Thing *thing) -{ - //TODO implement vortex; it's not implemented in original DK - WARNLOG("Not implemented"); -} - -void affect_nearby_friends_with_alarm(struct Thing *traptng) -{ - SYNCDBG(8,"Starting"); - if (is_neutral_thing(traptng)) { - return; - } - struct Dungeon *dungeon; - unsigned long k; - int i; - dungeon = get_players_num_dungeon(traptng->owner); - k = 0; - i = dungeon->creatr_list_start; - while (i != 0) - { - struct CreatureControl *cctrl; - struct Thing *thing; - thing = thing_get(i); - TRACE_THING(thing); - cctrl = creature_control_get_from_thing(thing); - if (creature_control_invalid(cctrl)) - { - ERRORLOG("Jump to invalid creature detected"); - break; - } - i = cctrl->players_next_creature_idx; - // Thing list loop body - if (!thing_is_picked_up(thing) && !is_thing_directly_controlled(thing) && - !creature_is_being_unconscious(thing) && !creature_is_kept_in_custody(thing) && - (cctrl->combat_flags == 0) && !creature_is_dragging_something(thing) && !creature_is_dying(thing) && !creature_is_leaving_and_cannot_be_stopped(thing)) - { - struct CreatureStateConfig *stati; - stati = get_thing_state_info_num(get_creature_state_besides_interruptions(thing)); - if (stati->react_to_cta && (get_chessboard_distance(&traptng->mappos, &thing->mappos) < 4096)) - { - creature_mark_if_woken_up(thing); - if (external_set_thing_state(thing, CrSt_ArriveAtAlarm)) - { - if (setup_person_move_to_position(thing, traptng->mappos.x.stl.num, traptng->mappos.y.stl.num, 0)) - { - thing->continue_state = CrSt_ArriveAtAlarm; - cctrl->alarm_over_turn = get_gameturn() + 800; - cctrl->alarm_stl_x = traptng->mappos.x.stl.num; - cctrl->alarm_stl_y = traptng->mappos.y.stl.num; - } - } - } - } - // Thing list loop body ends - k++; - if (k > CREATURES_COUNT) - { - ERRORLOG("Infinite loop detected when sweeping creatures list"); - break; - } - } -} - -long apply_wallhug_force_to_boulder(struct Thing *thing) -{ - unsigned short angle; - long collide; - unsigned short new_angle; - struct Coord3d pos2; - struct Coord3d pos; - struct ShotConfigStats *shotst = get_shot_model_stats(thing->model); - short speed = shotst->speed; - pos.x.val = move_coord_with_angle_x(thing->mappos.x.val,speed,thing->move_angle_xy); - pos.y.val = move_coord_with_angle_y(thing->mappos.y.val,speed,thing->move_angle_xy); - pos.z.val = thing->mappos.z.val; - if ( (GAME_RANDOM(8) == 0) && (!thing->velocity.z.val ) ) - { - if ( thing_touching_floor(thing) ) - { - long top_cube = get_top_cube_at(thing->mappos.x.stl.num, thing->mappos.y.stl.num, NULL); - if ( ((top_cube & 0xFFFFFFFE) != 0x28) && (top_cube != 39) ) - { - thing->veloc_push_add.z.val += 48; - thing->state_flags |= TF1_PushAdd; - } - } - } - if ( thing_in_wall_at(thing, &pos) ) - { - long blocked_flags = get_thing_blocked_flags_at(thing, &pos); - if ( blocked_flags & SlbBloF_WalledX ) - { - angle = thing->move_angle_xy; - if ( (angle) && (angle <= ANGLE_SOUTH) ) - collide = process_boulder_collision(thing, &pos, 1, 0); - else - collide = process_boulder_collision(thing, &pos, -1, 0); - } - else if ( blocked_flags & SlbBloF_WalledY ) - { - angle = thing->move_angle_xy; - if ( (angle <= ANGLE_EAST) || (angle > ANGLE_WEST) ) - collide = process_boulder_collision(thing, &pos, 0, -1); - else - collide = process_boulder_collision(thing, &pos, 0, 1); - } - else - { - collide = 0; - } - if ( collide != 1 ) - { - if ( (thing->model != ShM_SolidBoulder) && (collide == 0) ) - { - thing->health -= game.conf.rules[thing->owner].gameplay.boulder_reduce_health_wall; - } - slide_thing_against_wall_at(thing, &pos, blocked_flags); - if ( blocked_flags & SlbBloF_WalledX ) - { - angle = thing->move_angle_xy; - if ( (angle) && ( (angle <= ANGLE_EAST) || (angle > ANGLE_WEST) ) ) - { - MapCoord y = thing->mappos.y.val; - pos2.x.val = thing->mappos.x.val; - pos2.z.val = 0; - pos2.y.val = y - STL_PER_SLB * speed; - pos2.z.val = get_thing_height_at(thing, &pos2); - new_angle = (thing_in_wall_at(thing, &pos2) < 1) ? ANGLE_NORTH : ANGLE_SOUTH; - } - else - { - pos2.x.val = thing->mappos.x.val; - pos2.z.val = 0; - pos2.y.val = thing->mappos.y.val + STL_PER_SLB * speed; - pos2.z.val = get_thing_height_at(thing, &pos2); - new_angle = (thing_in_wall_at(thing, &pos2) < 1) ? ANGLE_SOUTH : ANGLE_NORTH; - } - } - else if ( blocked_flags & SlbBloF_WalledY ) - { - angle = thing->move_angle_xy; - if ( (angle) && (angle <= ANGLE_SOUTH) ) - { - pos2.z.val = 0; - pos2.y.val = thing->mappos.y.val; - pos2.x.val = thing->mappos.x.val + STL_PER_SLB * speed; - pos2.z.val = get_thing_height_at(thing, &pos2); - new_angle = (thing_in_wall_at(thing, &pos2) < 1) ? ANGLE_EAST : ANGLE_WEST; - } - else - { - MapCoord x = thing->mappos.x.val; - pos2.z.val = 0; - pos2.y.val = thing->mappos.y.val; - pos2.x.val = x - STL_PER_SLB * speed; - pos2.z.val = get_thing_height_at(thing, &pos2); - new_angle = (thing_in_wall_at(thing, &pos2) < 1) ? ANGLE_WEST : ANGLE_EAST; - } - } - else - { - ERRORLOG("Cannot find boulder wall hug angle!"); - new_angle = 0; - } - thing->move_angle_xy = new_angle; - } - } - angle = thing->move_angle_xy; - thing->velocity.x.val = distance_with_angle_to_coord_x(shotst->speed,angle); - thing->velocity.y.val = distance_with_angle_to_coord_y(shotst->speed,angle); - return 0; -} - -long process_boulder_collision(struct Thing *boulder, struct Coord3d *pos, int direction_x, int direction_y) -{ - unsigned short boulder_radius = (boulder->clipbox_size_xy >> 1); - MapSubtlCoord pos_x = (pos->x.val + boulder_radius * direction_x) >> 8; - MapSubtlCoord pos_y = (pos->y.val + boulder_radius * direction_y) >> 8; - MapSubtlCoord stl_x = stl_slab_center_subtile(pos_x); - MapSubtlCoord stl_y = stl_slab_center_subtile(pos_y); - - struct Room *room = subtile_room_get(stl_x, stl_y); - if (room_exists(room)) - { - if (room->kind == RoK_GUARDPOST) // Collide with Guardposts - { - if (room->owner != game.neutral_player_num) - { - struct Dungeon *dungeon = get_dungeon(room->owner); - if (!dungeon_invalid(dungeon)) - { - dungeon->rooms_destroyed++; // add to player stats - } - } - delete_room_slab(subtile_slab(stl_x), subtile_slab(stl_y), 0); // destroy guardpost - for (int16_t k = 0; k < AROUND_TILES_COUNT; k++) - { - create_dirt_rubble_for_dug_block(stl_x + around[k].delta_x, stl_y + around[k].delta_y, 4, room->owner); - } - if (boulder->model != ShM_SolidBoulder) // Solid Boulder (shot20) takes no damage when destroying guardposts - { - boulder->health -= game.conf.rules[boulder->owner].gameplay.boulder_reduce_health_room; // decrease boulder health - } - return 1; // guardpost destroyed - } - } - else - { - if (subtile_has_door_thing_on(stl_x, stl_y)) // Collide with Doors - { - struct Thing *doortng = get_door_for_position(stl_x, stl_y); - if (collide_door_and_boulder(doortng, boulder) <= 0) - { - return 2; // door destroyed - } - } - } - return 0; // Default: No collision OR boulder destroyed on door -} - -void draw_flame_breath(struct Coord3d *pos1, struct Coord3d *pos2, long delta_step, long num_per_step, short ef_or_efel_model, ThingIndex parent_idx) -{ - MapCoordDelta dist_x; - MapCoordDelta dist_y; - MapCoordDelta dist_z; - dist_x = pos2->x.val - (MapCoordDelta)pos1->x.val; - dist_y = pos2->y.val - (MapCoordDelta)pos1->y.val; - dist_z = pos2->z.val - (MapCoordDelta)pos1->z.val; - int delta_x; - int delta_y; - int delta_z; - if (delta_step <= 0) - delta_step = 1; - if (dist_x >= 0) - { - delta_x = delta_step; - } else { - dist_x = -dist_x; - delta_x = -delta_step; - } - if (dist_y >= 0) { - delta_y = delta_step; - } else { - dist_y = -dist_y; - delta_y = -delta_step; - } - if (dist_z >= 0) { - delta_z = delta_step; - } else { - dist_z = -dist_z; - delta_z = -delta_step; - } - // Now our dist_x,dist_y,dist_z is always non-negative, - // and sign is stored in delta_x,delta_y,delta_z. - if ((dist_x != 0) || (dist_y != 0) || (dist_z != 0)) - { - int nsteps; - // Find max dimension, and scale deltas to it - if ((dist_z > dist_x) && (dist_z > dist_y)) - { - nsteps = dist_z / delta_step; - delta_y = dist_y * delta_y / dist_z; - delta_x = dist_x * delta_x / dist_z; - } else - if ((dist_x > dist_y) && (dist_x > dist_z)) - { - nsteps = dist_x / delta_step; - delta_y = dist_y * delta_y / dist_x; - delta_z = dist_z * delta_z / dist_x; - } else - if ((dist_y > dist_x) && (dist_y > dist_z)) - { - nsteps = dist_y / delta_step; - delta_x = dist_x * delta_x / dist_y; - delta_z = dist_z * delta_z / dist_y; - } else - { // No dominate direction - nsteps = (dist_x + dist_y + dist_z) / delta_step; - delta_x = dist_x * delta_x / (dist_x + dist_y + dist_z); - delta_y = dist_y * delta_y / (dist_x + dist_y + dist_z); - delta_z = dist_z * delta_z / (dist_x + dist_y + dist_z); - } - - int sprsize = 0; - int delta_size = 0; - - struct EffectElementConfigStats *eestat; - if (ef_or_efel_model < 0) - { - eestat = get_effect_element_model_stats(ef_or_efel_model * -1); - delta_size = ((eestat->sprite_size_max - eestat->sprite_size_min) << 8) / (nsteps+1); - sprsize = (eestat->sprite_size_min << 8); - } - - int deviat; - deviat = 1; - struct Coord3d curpos; - curpos.x.val = pos1->x.val; - curpos.y.val = pos1->y.val; - curpos.z.val = pos1->z.val; - int i; - for (i=nsteps+1; i > 0; i--) - { - int devrange; - devrange = 2 * deviat; - int k; - for (k = num_per_step; k > 0; k--) - { - struct Coord3d tngpos; - tngpos.x.val = curpos.x.val + deviat - UNSYNC_RANDOM(devrange); - tngpos.y.val = curpos.y.val + deviat - UNSYNC_RANDOM(devrange); - tngpos.z.val = curpos.z.val + deviat - UNSYNC_RANDOM(devrange); - if ((tngpos.x.val < subtile_coord(game.map_subtiles_x,0)) && (tngpos.y.val < subtile_coord(game.map_subtiles_y,0))) - { - struct Thing *eelemtng; - - eelemtng = create_used_effect_or_element(&tngpos, ef_or_efel_model, game.neutral_player_num, parent_idx); - if (!thing_is_invalid(eelemtng)) { - eelemtng->sprite_size = sprsize >> 8; - } - } - } - curpos.x.val += delta_x; - curpos.y.val += delta_y; - curpos.z.val += delta_z; - deviat += 16; - sprsize += delta_size; - } - } -} - -void draw_lightning(const struct Coord3d *pos1, const struct Coord3d *pos2, long eeinterspace, EffectOrEffElModel ef_or_efel_model) -{ - MapCoordDelta dist_x = pos2->x.val - pos1->x.val; - MapCoordDelta dist_y = pos2->y.val - pos1->y.val; - MapCoordDelta dist_z = pos2->z.val - pos1->z.val; - int delta_x; - int delta_y; - int delta_z; - if (eeinterspace <= 0) - eeinterspace = 1; - if (dist_x >= 0) { - delta_x = eeinterspace; - } else { - dist_x = -dist_x; - delta_x = -eeinterspace; - } - if (dist_y >= 0) { - delta_y = eeinterspace; - } else { - dist_y = -dist_y; - delta_y = -eeinterspace; - } - if (dist_z >= 0) { - delta_z = eeinterspace; - } else { - dist_z = -dist_z; - delta_z = -eeinterspace; - } - if ((dist_x != 0) || (dist_y != 0) || (dist_z != 0)) - { - int nsteps; - if ((dist_z >= dist_x) && (dist_z >= dist_y)) - { - nsteps = dist_z / eeinterspace; - delta_y = delta_y * dist_y / dist_z; - delta_x = dist_x * delta_x / dist_z; - } else - if ((dist_x >= dist_y) && (dist_x >= dist_z)) - { - nsteps = dist_x / eeinterspace; - delta_y = delta_y * dist_y / dist_x; - delta_z = delta_z * dist_z / dist_x; - } else - { - nsteps = dist_y / eeinterspace; - delta_x = dist_x * delta_x / dist_y; - delta_z = delta_z * dist_z / dist_y; - } - int deviat_x = 0; - int deviat_y = 0; - int deviat_z = 0; - struct Coord3d curpos; - curpos.x.val = pos1->x.val + UNSYNC_RANDOM(eeinterspace/4); - curpos.y.val = pos1->y.val + UNSYNC_RANDOM(eeinterspace/4); - curpos.z.val = pos1->z.val + UNSYNC_RANDOM(eeinterspace/4); - for (int i=nsteps+1; i > 0; i--) - { - struct Coord3d tngpos; - tngpos.x.val = curpos.x.val + deviat_x; - tngpos.y.val = curpos.y.val + deviat_y; - tngpos.z.val = curpos.z.val + deviat_z; - if ((tngpos.x.val < subtile_coord(game.map_subtiles_x,0)) && (tngpos.y.val < subtile_coord(game.map_subtiles_y,0))) - { - create_used_effect_or_element(&tngpos, ef_or_efel_model, game.neutral_player_num, 0); - } - if (UNSYNC_RANDOM(6) >= 3) { - deviat_x -= 32; - } else { - deviat_x += 32; - } - if (UNSYNC_RANDOM(6) >= 3) { - deviat_y -= 32; - } else { - deviat_y += 32; - } - if (UNSYNC_RANDOM(6) >= 3) { - deviat_z -= 32; - } else { - deviat_z += 32; - } - MapCoordDelta dist = get_chessboard_3d_distance(&curpos, pos2); - int deviat_limit = 128; - if (dist < 1024) - deviat_limit = (dist * 128) / 1024; - // Limit deviations - if (deviat_x < -deviat_limit) { - deviat_x = -deviat_limit; - } else - if (deviat_x > deviat_limit) { - deviat_x = deviat_limit; - } - if (deviat_y < -deviat_limit) { - deviat_y = -deviat_limit; - } else - if (deviat_y > deviat_limit) { - deviat_y = deviat_limit; - } - if (deviat_z < -deviat_limit) { - deviat_z = -deviat_limit; - } else - if (deviat_z > deviat_limit) { - deviat_z = deviat_limit; - } - curpos.x.val += delta_x; - curpos.y.val += delta_y; - curpos.z.val += delta_z; - } - } -} - -TbBool any_player_close_enough_to_see(const struct Coord3d *pos) -{ - struct PlayerInfo *player; - int i; - short limit = 24 * COORD_PER_STL; - for (i=0; i < PLAYERS_COUNT; i++) - { - player = get_player(i); - if ( (player_exists(player)) && ((player->allocflags & PlaF_CompCtrl) == 0)) - { - struct Camera *camera = get_player_active_camera(player); - if (camera == NULL) - continue; - if (camera->view_mode != PVM_FrontView) - { - if (camera->zoom >= CAMERA_ZOOM_MIN) - { - limit = SHRT_MAX - (2 * camera->zoom); - } - } - else - { - if (camera->zoom >= FRONTVIEW_CAMERA_ZOOM_MIN) - { - limit = SHRT_MAX - (camera->zoom / 3); - } - } - if (get_chessboard_distance(&camera->mappos, pos) <= limit) - { - return true; - } - } - } - return false; -} - -void update_thing_animation(struct Thing *thing) -{ - SYNCDBG(18,"Starting for %s",thing_model_name(thing)); - int i; - struct CreatureControl *cctrl; - if (thing->class_id == TCls_Creature) - { - cctrl = creature_control_get_from_thing(thing); - if (!creature_control_invalid(cctrl)) - cctrl->anim_time = thing->anim_time; - } - if ((thing->anim_speed != 0) && (thing->max_frames != 0)) - { - thing->anim_time += thing->anim_speed; - i = (thing->max_frames << 8); - if (i <= 0) i = 256; - while (thing->anim_time < 0) - { - thing->anim_time += i; - } - if (thing->anim_time > i-1) - { - if (thing->rendering_flags & TRF_AnimateOnce) - { - thing->anim_speed = 0; - thing->anim_time = i-1; - } else - { - thing->anim_time %= i; - } - } - thing->current_frame = thing->anim_time >> 8; - } - if (thing->transformation_speed != 0) - { - thing->sprite_size += thing->transformation_speed; - if (thing->sprite_size > thing->sprite_size_min) - { - if (thing->sprite_size >= thing->sprite_size_max) - { - thing->sprite_size = thing->sprite_size_max; - if ((thing->size_change & TSC_ChangeSizeContinuously) != 0) - thing->transformation_speed = -thing->transformation_speed; - else - thing->transformation_speed = 0; - } - } else - { - thing->sprite_size = thing->sprite_size_min; - if ((thing->size_change & TSC_ChangeSizeContinuously) != 0) - thing->transformation_speed = -thing->transformation_speed; - else - thing->transformation_speed = 0; - } - } -} - void init_censorship(void) { if ( censorship_enabled() ) @@ -905,13 +247,6 @@ void init_censorship(void) } } -void engine_init(void) -{ - fill_floor_heights_table(); - generate_wibble_table(); - load_ceiling_table(); -} - void init_keeper(void) { SYNCDBG(8,"Starting"); @@ -1666,25 +1001,9 @@ void clear_computer(void) } } -void init_keepers_map_exploration(void) -{ - struct PlayerInfo *player; - int i; - for (i=0; i < PLAYERS_COUNT; i++) - { - player = get_player(i); - if ((player_exists(player) && (player->is_active == 1)) || player_is_roaming(i)) - { - // Additional init - the main one is in init_player() - if ((player->allocflags & PlaF_CompCtrl) != 0) { - init_keeper_map_exploration_by_terrain(player); - init_keeper_map_exploration_by_creatures(player); - } - } - } -} - -void clear_players_for_save(void) + + +void clear_players_for_save(void) { struct PlayerInfo *player; unsigned short saved_player_id; @@ -2108,942 +1427,6 @@ void update_mouse_light(struct PlayerInfo *player) set_mouse_light(player, valid, pos); } -void check_players_won(void) -{ - SYNCDBG(8,"Starting"); - - if (!network_is_active()) - return; - - struct PlayerInfo* curPlayer; - for (PlayerNumber playerIdx = 0; playerIdx < PLAYERS_COUNT; ++playerIdx) - { - curPlayer = get_player(playerIdx); - if (!player_exists(curPlayer) || (curPlayer->is_active != 1) || (curPlayer->victory_state != VicS_Undecided)) - continue; - - // check if any other player is still alive - TbBool LivingOpponent = false; - for (PlayerNumber secondPlayerIdx = 0; secondPlayerIdx < PLAYERS_COUNT; ++secondPlayerIdx) - { - if (secondPlayerIdx == playerIdx) - continue; - - struct PlayerInfo* otherPlayer = get_player(secondPlayerIdx); - if (player_exists(otherPlayer) && otherPlayer->victory_state == VicS_Undecided) - { - struct Thing* heartng = get_player_soul_container(secondPlayerIdx); - if (heartng->active_state != ObSt_BeingDestroyed) - { - LivingOpponent = true; - break; - } - } - } - if (LivingOpponent == false) - { - set_player_as_won_level(curPlayer); - return; - } - } -} - -void check_players_lost(void) -{ - long i; - SYNCDBG(8,"Starting"); - struct PlayerInfo* player; - struct Dungeon* dungeon; - for (i=0; i < PLAYERS_COUNT; i++) - { - player = get_player(i); - dungeon = get_players_dungeon(player); - if (player_exists(player) && (player->is_active == 1)) - { - struct Thing *heartng; - heartng = get_player_soul_container(i); - if (heartng->owner != i) - { - init_player_start(player, true); - if (dungeon->dnheart_idx == 0) - { - initialise_devastate_dungeon_from_heart(player->id_number); - } - } - if ((!thing_exists(heartng) || ((heartng->active_state == ObSt_BeingDestroyed) && !(dungeon->backup_heart_idx > 0))) && (player->victory_state == VicS_Undecided)) - { - event_kill_all_players_events(i); - set_player_as_lost_level(player); - //this would easily prevent computer player activities on dead player, but it also makes dead player unable to use - //floating spirit, so it can't be done this way: player->is_active = 0; - if (is_my_player_number(i)) { - LbPaletteSet(engine_palette); - } - } - } - } -} - -void blast_slab(MapSlabCoord slb_x, MapSlabCoord slb_y, PlayerNumber plyr_idx) -{ - struct SlabMap *slb; - slb = get_slabmap_block(slb_x, slb_y); - if (slabmap_block_invalid(slb)) { - return; - } - if (slabmap_owner(slb) != plyr_idx) { - return; - } - struct Thing *doortng; - doortng = get_door_for_position(slab_subtile_center(slb_x), slab_subtile_center(slb_y)); - if (!thing_is_invalid(doortng)) { - destroy_door(doortng); - } - struct SlabConfigStats *slabst; - slabst = get_slab_stats(slb); - if (slabst->category == SlbAtCtg_FortifiedGround) - { - place_slab_type_on_map(SlbT_PATH, slab_subtile_center(slb_x), slab_subtile_center(slb_y), game.neutral_player_num, 1); - decrease_dungeon_area(plyr_idx, 1); - do_unprettying(game.neutral_player_num, slb_x, slb_y); - do_slab_efficiency_alteration(slb_x, slb_y); - struct Coord3d pos; - pos.x.val = subtile_coord_center(slab_subtile_center(slb_x)); - pos.y.val = subtile_coord_center(slab_subtile_center(slb_y)); - pos.z.val = get_floor_height_at(&pos); - create_effect_element(&pos, TngEffElm_RedFlameBig, plyr_idx); - } -} - -static void process_dungeon_devastation_effects(void) -{ - SYNCDBG(8,"Starting"); - int plyr_idx; - for (plyr_idx=0; plyr_idx < PLAYERS_COUNT; plyr_idx++) - { - struct Dungeon *dungeon; - dungeon = get_players_num_dungeon(plyr_idx); - if (dungeon->devastation_turn == 0) - continue; - if ((get_gameturn() & 1) != 0) - continue; - dungeon->devastation_turn++; - if (dungeon->devastation_turn >= max(game.map_tiles_x,game.map_tiles_y)) - continue; - MapSlabCoord slb_x; - MapSlabCoord slb_y; - int i; - int range; - slb_x = subtile_slab(dungeon->devastation_centr_x) - dungeon->devastation_turn; - slb_y = subtile_slab(dungeon->devastation_centr_y) - dungeon->devastation_turn; - range = 2*dungeon->devastation_turn; - for (i = 0; i <= range; i++) - { - blast_slab(slb_x + i, slb_y, dungeon->owner); - blast_slab(slb_x + i, slb_y + range, dungeon->owner); - } - for (i = 0; i <= range; i++) - { - blast_slab(slb_x, slb_y + i, dungeon->owner); - blast_slab(slb_x + range, slb_y + i, dungeon->owner); - } - } -} - -/** - * Increments paydays_owed for all players creatures - * returns amount of creatures needing payday for player - */ -int set_players_creatures_to_get_paid(PlayerNumber plyr_idx) -{ - unsigned long k; - long i; - int count = 0; - const struct StructureList *slist; - slist = get_list_for_thing_class(TCls_Creature); - i = slist->index; - k = 0; - while (i != 0) - { - struct Thing *thing; - thing = thing_get(i); - if (thing_is_invalid(thing)) - { - ERRORLOG("Jump to invalid thing detected"); - break; - } - i = thing->next_of_class; - // Per-thing code - if (thing->owner == plyr_idx) - { - struct CreatureModelConfig *crconf; - crconf = creature_stats_get_from_thing(thing); - if (crconf->pay != 0) - { - struct CreatureControl *cctrl; - cctrl = creature_control_get_from_thing(thing); - if (cctrl->paydays_advanced > 0) - { - cctrl->paydays_advanced--; - } else - { - if (!creature_is_kept_in_custody_by_enemy(thing)) - { - cctrl->paydays_owed++; - count++; - } - else - { - cctrl->paydays_advanced--; - } - } - } - } - // Per-thing code ends - k++; - if (k > THINGS_COUNT) - { - ERRORLOG("Infinite loop detected when sweeping things list"); - break; - } - } - return count; -} - -void process_payday(void) -{ - PlayerNumber plyr_idx; - for (plyr_idx=0; plyr_idx < PLAYERS_COUNT; plyr_idx++) - { - game.pay_day_progress[plyr_idx] = game.pay_day_progress[plyr_idx] + (game.conf.rules[plyr_idx].gameplay.pay_day_speed / 100); - if (player_is_roaming(plyr_idx) || (plyr_idx == game.neutral_player_num)) { - continue; - } - struct PlayerInfo *player; - player = get_player(plyr_idx); - if (player_exists(player) && (player->is_active == 1)) - { - compute_and_update_player_payday_total(plyr_idx); - compute_and_update_player_backpay_total(plyr_idx); - } - } - int player_paid_creatures_count; - for (plyr_idx = 0; plyr_idx < PLAYERS_COUNT; plyr_idx++) - { - if (game.conf.rules[plyr_idx].gameplay.pay_day_gap <= game.pay_day_progress[plyr_idx]) - { - if (is_my_player_number(plyr_idx)) - output_message(SMsg_Payday, 0); - game.pay_day_progress[plyr_idx] = 0; - player_paid_creatures_count = set_players_creatures_to_get_paid(plyr_idx); - if (player_paid_creatures_count > 0) - { - struct Dungeon *dungeon = get_players_num_dungeon(plyr_idx); - event_create_event_or_update_nearby_existing_event(0, 0, EvKind_CreaturePayday, plyr_idx, dungeon->creatures_total_pay); - } - } - } -} - -void process_dungeons(void) -{ - SYNCDBG(7,"Starting"); - check_players_won(); - check_players_lost(); - process_dungeon_power_magic(); - process_dungeon_devastation_effects(); - process_entrance_generation(); - process_payday(); - process_things_in_dungeon_hand(); - SYNCDBG(9,"Finished"); -} - -void update_near_creatures_for_footsteps(int32_t *near_creatures, const struct Coord3d *srcpos) -{ - long near_distance[3]; - // Don't allow creatures which are far by over 20 subtiles - near_distance[0] = subtile_coord(20,0); - near_distance[1] = subtile_coord(20,0); - near_distance[2] = subtile_coord(20,0); - near_creatures[0] = 0; - near_creatures[1] = 0; - near_creatures[2] = 0; - // Find the closest thing for footsteps - struct Thing *thing; - unsigned long k; - long i; - const struct StructureList *slist; - slist = get_list_for_thing_class(TCls_Creature); - i = slist->index; - k = 0; - while (i != 0) - { - thing = thing_get(i); - if (thing_is_invalid(thing)) - { - ERRORLOG("Jump to invalid thing detected"); - break; - } - i = thing->next_of_class; - // Per-thing code - thing->state_flags &= ~TF1_DoFootsteps; - if ( (!thing_is_picked_up(thing)) && (!thing_is_dragged_or_pulled(thing)) ) - { - struct CreatureSound *crsound; - crsound = get_creature_sound(thing, CrSnd_Foot); - if (crsound->index != 0) - { - struct CreatureControl *cctrl; - cctrl = creature_control_get_from_thing(thing); - long ndist; - ndist = get_chessboard_distance(srcpos, &thing->mappos); - if (ndist < near_distance[0]) - { - if (((cctrl->distance_to_destination != 0) && ((int)thing->floor_height >= (int)thing->mappos.z.val)) - || ((thing->movement_flags & TMvF_Flying) != 0)) - { - // Insert the new item to our list - int n; - for (n = 2; n>0; n--) - { - near_creatures[n] = near_creatures[n-1]; - near_distance[n] = near_distance[n-1]; - } - near_distance[0] = ndist; - near_creatures[0] = thing->index; - } - } - } - } - // Per-thing code ends - k++; - if (k > THINGS_COUNT) - { - ERRORLOG("Infinite loop detected when sweeping things list"); - break; - } - } -} - -long stop_playing_flight_sample_in_all_flying_creatures(void) -{ - struct Thing *thing; - unsigned long k; - long i; - long naffected; - naffected = 0; - const struct StructureList *slist; - slist = get_list_for_thing_class(TCls_Creature); - i = slist->index; - k = 0; - while (i != 0) - { - thing = thing_get(i); - if (thing_is_invalid(thing)) - { - ERRORLOG("Jump to invalid thing detected"); - break; - } - i = thing->next_of_class; - // Per-thing code - if ((get_creature_model_flags(thing) & CMF_IsDiptera) && ((thing->state_flags & TF1_DoFootsteps) == 0)) - { - if ( S3DEmitterIsPlayingSample(thing->snd_emitter_id, 25) ) { - S3DDeleteSampleFromEmitter(thing->snd_emitter_id, 25); - } - } - // Per-thing code ends - k++; - if (k > THINGS_COUNT) - { - ERRORLOG("Infinite loop detected when sweeping things list"); - break; - } - } - return naffected; -} - -void update_footsteps_nearest_camera(struct Camera *cam) -{ - static long timeslice = 0; - static int32_t near_creatures[3]; - struct Coord3d srcpos; - SYNCDBG(6,"Starting"); - if (cam == NULL) - return; - srcpos.x.val = cam->mappos.x.val; - srcpos.y.val = cam->mappos.y.val; - srcpos.z.val = cam->mappos.z.val; - if (timeslice == 0) { - update_near_creatures_for_footsteps(near_creatures, &srcpos); - } - long i; - for (i=0; i < 3; i++) - { - struct Thing *thing; - if (near_creatures[i] == 0) - break; - thing = thing_get(near_creatures[i]); - if (thing_is_creature(thing)) { - thing->state_flags |= TF1_DoFootsteps; - play_thing_walking(thing); - } - } - if (timeslice == 0) - { - stop_playing_flight_sample_in_all_flying_creatures(); - } - timeslice = (timeslice + 1) % 4; -} - -int clear_active_dungeons_stats(void) -{ - struct Dungeon *dungeon; - int i; - for (i=0; i < PLAYERS_COUNT; i++) - { - dungeon = get_dungeon(i); - if (dungeon_invalid(dungeon)) - break; - memset((char *)dungeon->crmodel_state_type_count, 0, game.conf.crtr_conf.model_count * STATE_TYPES_COUNT * sizeof(uint16_t)); - memset((char *)dungeon->guijob_all_creatrs_count, 0, game.conf.crtr_conf.model_count *3*sizeof(uint16_t)); - memset((char *)dungeon->guijob_angry_creatrs_count, 0, game.conf.crtr_conf.model_count *3*sizeof(uint16_t)); - } - return i; -} - -TngUpdateRet damage_creatures_with_physical_force(struct Thing *thing, ModTngFilterParam param) -{ - SYNCDBG(18,"Starting for %s index %d",thing_model_name(thing),(int)thing->index); - if (thing_is_picked_up(thing) || thing_is_dragged_or_pulled(thing)) - { - return TUFRet_Unchanged; - } - if (thing_is_creature(thing)) - { - apply_damage_to_thing_and_display_health(thing, param->secondary_number, param->primary_number); - if ((thing->health >= 0) && !creature_is_leaving_and_cannot_be_stopped(thing)) - { - if (((thing->alloc_flags & TAlF_IsControlled) == 0) && !creature_is_kept_in_custody(thing)) - { - if (get_creature_state_besides_interruptions(thing) != CrSt_CreatureEscapingDeath) - { - if (cleanup_current_thing_state(thing) && setup_move_out_of_cave_in(thing)) - thing->continue_state = CrSt_CreatureEscapingDeath; - } - } - return TUFRet_Modified; - } else - { - kill_creature(thing, INVALID_THING, param->primary_number, CrDed_NoEffects|CrDed_DiedInBattle); - return TUFRet_Deleted; - } - } - else if (thing_is_destructible_trap(thing) > 0) - { - apply_damage_to_thing(thing, param->secondary_number, param->primary_number); - return TUFRet_Modified; - } - return TUFRet_Unchanged; -} - -TbBool valid_cave_in_position(PlayerNumber plyr_idx, MapSubtlCoord stl_x, MapSubtlCoord stl_y) -{ - struct Map *mapblk; - mapblk = get_map_block_at(stl_x,stl_y); - if ((mapblk->flags & SlbAtFlg_Blocking) != 0) - return false; - struct SlabMap *slb; - slb = get_slabmap_for_subtile(stl_x,stl_y); - return (plyr_idx == game.neutral_player_num) || (slabmap_owner(slb) == game.neutral_player_num) || (slabmap_owner(slb) == plyr_idx); -} - -long update_cave_in(struct Thing *thing) -{ - thing->health--; - thing->rendering_flags |= TRF_Invisible; - if (thing->health < 1) - { - delete_thing_structure(thing, 0); - return 1; - } - - const struct PowerConfigStats *powerst; - powerst = get_power_model_stats(PwrK_CAVEIN); - struct Thing *efftng; - struct Coord3d pos; - PlayerNumber owner; - owner = thing->owner; - if ((get_gameturn() % 3) == 0) - { - int n; - n = GAME_RANDOM(AROUND_TILES_COUNT); - pos.x.val = thing->mappos.x.val + GAME_RANDOM(704) * around[n].delta_x; - pos.y.val = thing->mappos.y.val + GAME_RANDOM(704) * around[n].delta_y; - if (subtile_has_slab(coord_subtile(pos.x.val),coord_subtile(pos.y.val))) - { - pos.z.val = get_ceiling_height(&pos) - 128; - efftng = create_effect_element(&pos, TngEff_Flash, owner); - if (!thing_is_invalid(efftng)) { - efftng->health = powerst->duration; - } - } - } - - GameTurnDelta turns_between; - GameTurnDelta turns_alive; - turns_between = powerst->duration / 5; - turns_alive = get_gameturn() - thing->creation_turn; - if ((turns_alive != 0) && ((turns_between < 1) || (3 * turns_between / 4 == turns_alive % turns_between))) - { - pos.x.val = thing->mappos.x.val + THING_RANDOM(thing, 128); - pos.y.val = thing->mappos.y.val + THING_RANDOM(thing, 128); - pos.z.val = get_floor_height_at(&pos) + 384; - create_effect(&pos, TngEff_HarmlessGas4, owner); - } - - if ((turns_alive % game.conf.rules[owner].magic.turns_per_collapse_dngn_dmg) == 0) - { - pos.x.val = thing->mappos.x.val; - pos.y.val = thing->mappos.y.val; - pos.z.val = subtile_coord(1,0); - Thing_Modifier_Func do_cb; - struct CompoundTngFilterParam param; - param.plyr_idx = -1; - param.class_id = 0; - param.model_id = 0; - param.primary_number = thing->owner; - param.secondary_number = game.conf.rules[thing->owner].magic.collapse_dungeon_damage; - param.tertiary_pointer = 0; - do_cb = damage_creatures_with_physical_force; - do_to_things_with_param_around_map_block(&pos, do_cb, ¶m); - } - - if ((8 * powerst->duration / 10 >= thing->health) && (2 * powerst->duration / 10 <= thing->health)) - { - if ((powerst->duration < 10) || ((thing->health % (powerst->duration / 10)) == 0)) - { - int round_idx; - round_idx = THING_RANDOM(thing, AROUND_TILES_COUNT); - set_coords_to_slab_center(&pos, subtile_slab(thing->mappos.x.val + 3 * around[round_idx].delta_x), subtile_slab(thing->mappos.y.val + 3 * around[round_idx].delta_y)); - if (subtile_has_slab(coord_subtile(pos.x.val), coord_subtile(pos.y.val)) && valid_cave_in_position(thing->owner, coord_subtile(pos.x.val), coord_subtile(pos.y.val))) - { - struct Thing *ncavitng; - ncavitng = get_cavein_at_subtile_owned_by(coord_subtile(pos.x.val), coord_subtile(pos.y.val), -1); - if (thing_is_invalid(ncavitng)) - { - long dist; - struct Coord3d pos2; - pos2.x.val = subtile_coord(thing->cave_in.x,0); - pos2.y.val = subtile_coord(thing->cave_in.y,0); - pos2.z.val = subtile_coord(1,0); - dist = get_chessboard_distance(&pos, &pos2); - if (powerst->strength[thing->cave_in.model] >= coord_subtile(dist)) - { - ncavitng = create_thing(&pos, TCls_CaveIn, thing->cave_in.model, owner, -1); - if (!thing_is_invalid(ncavitng)) - { - thing->health += 5; - if (thing->health > 0) - { - ncavitng->cave_in.x = thing->cave_in.x; - ncavitng->cave_in.y = thing->cave_in.y; - } - } - } - } - } - } - } - return 1; -} - -/** - * rules can change by dkscript/lua. - * Checks if a gamerule for lighting has changed and updates the lights if they are. - * This function also refreshes the light status of the map. -*/ -void update_global_lighting() -{ - if (!game.lish.light_auto_sync) - return; - - // Check if any values have changed - if ( - game.conf.rules[0].gameplay.global_ambient_light != game.lish.global_ambient_light || - game.conf.rules[0].gameplay.light_enabled != game.lish.light_enabled - ){ - - // GlobalAmbientLight - if (game.conf.rules[0].gameplay.global_ambient_light != game.lish.global_ambient_light) - { - game.lish.global_ambient_light = game.conf.rules[0].gameplay.global_ambient_light; - } - - // LightEnabled - if (game.conf.rules[0].gameplay.light_enabled != game.lish.light_enabled) - { - game.lish.light_enabled = game.conf.rules[0].gameplay.light_enabled; - } - - // Refresh the lights - light_stat_refresh(); - } -} - -void update(void) -{ - struct PlayerInfo *player; - SYNCDBG(4,"Starting for turn %ld",(long)get_gameturn()); - - update_local_cameras(); - process_packets(); - api_update_server(); - - if (quit_game || exit_keeper) { - return; - } - if (game.game_kind == GKind_NonInteractiveState) - { - game.map_changed_for_navigation = 0; - return; - } - player = get_my_player(); - - if (!flag_is_set(game.operation_flags,GOF_Paused)) - { - if (flag_is_set(player->additional_flags,PlaAF_LightningPaletteIsActive)) - { - PaletteSetPlayerPalette(player, engine_palette); - clear_flag(player->additional_flags, PlaAF_LightningPaletteIsActive); - } - clear_active_dungeons_stats(); - update_creature_pool_state(); - if ((get_gameturn() & 0x01) != 0) - update_animating_texture_maps(); - update_things(); - process_rooms(); - process_dungeons(); - update_research(); - update_manufacturing(); - event_process_events(); - update_all_events(); - process_level_script(); - process_fx_lines(); - lua_on_game_tick(); - if ((game.view_mode_flags & GNFldD_ComputerPlayerProcessing) != 0) - process_computer_players2(); - process_players(); - process_action_points(); - player = get_my_player(); - if (player->view_mode == PVM_CreatureView) - { - struct Thing *thing = thing_get(player->controlled_thing_idx); - update_first_person_object_ambience(thing); - } - update_footsteps_nearest_camera(get_player_active_camera(player)); - PaletteFadePlayer(player); - process_armageddon(); - update_global_lighting(); -#if (BFDEBUG_LEVEL > 9) - lights_stats_debug_dump(); - things_stats_debug_dump(); - creature_stats_debug_dump(); -#endif - game.play_gameturn++; - if (game.turns_packetoff == game.play_gameturn) - exit_keeper = 1; - } - - message_update(); - update_all_players_cameras(); - update_player_sounds(); - SYNCDBG(6,"Finished"); -} - - -long near_map_block_thing_filter_queryable_object(const struct Thing *thing, MaxTngFilterParam param, long maximizer) -{ -/* Currently this only makes Dungeon Heart blinking; maybe I'll find a purpose for it later - long dist_x,dist_y; - if ((thing->class_id == TCls_Object) && (thing->model == 5)) - { - if (thing->owner == param->plyr_idx) - { - // note that abs() is not required because we're computing square of the values - dist_x = param->primary_number-(MapCoord)thing->mappos.x.val; - dist_y = param->secondary_number-(MapCoord)thing->mappos.y.val; - // This function should return max value when the distance is minimal, so: - return INT32_MAX-(dist_x*dist_x + dist_y*dist_y); - } - } -*/ - // If conditions are not met, return -1 to be sure thing will not be returned. - return -1; -} - -struct Thing *get_queryable_object_near(MapCoord pos_x, MapCoord pos_y, PlayerNumber plyr_idx) -{ - Thing_Maximizer_Filter filter; - struct CompoundTngFilterParam param; - SYNCDBG(19,"Starting"); - filter = near_map_block_thing_filter_queryable_object; - param.plyr_idx = plyr_idx; - param.primary_number = pos_x; - param.secondary_number = pos_y; - return get_thing_near_revealed_map_block_with_filter(pos_x, pos_y, filter, ¶m); -} - -void set_player_cameras_position(struct PlayerInfo *player, int32_t pos_x, int32_t pos_y) -{ - player->cameras[CamIV_Parchment].mappos.x.val = pos_x; - player->cameras[CamIV_FrontView].mappos.x.val = pos_x; - player->cameras[CamIV_Isometric].mappos.x.val = pos_x; - player->cameras[CamIV_Parchment].mappos.y.val = pos_y; - player->cameras[CamIV_FrontView].mappos.y.val = pos_y; - player->cameras[CamIV_Isometric].mappos.y.val = pos_y; -} - -void scale_tmap2(long texture_block_index, long flags, long fade_level, long screen_x, long screen_y, long scaled_width, long scaled_height) -{ - if ((scaled_width == 0) || (scaled_height == 0)) { - return; - } - long xstart; - long ystart; - long xend; - long yend; - char orient; - switch (flags) - { - case 0: - xstart = 0; - ystart = 0; - xend = 2097151 / scaled_width; - yend = 2097151 / scaled_height; - orient = 0; - break; - case 0x10: - xstart = 2097151; - ystart = 0; - xend = -2097151 / scaled_width; - yend = 2097151 / scaled_height; - orient = 0; - break; - case 0x20: - xstart = 0; - ystart = 2097151; - xend = 2097151 / scaled_width; - yend = -2097151 / scaled_height; - orient = 0; - break; - case 0x30: - xstart = 2097151; - ystart = 2097151; - xend = -2097151 / scaled_width; - yend = -2097151 / scaled_height; - orient = 0; - break; - case 0x40: - ystart = 0; - xstart = 0; - yend = 2097151 / scaled_height; - xend = 2097151 / scaled_width; - orient = 1; - break; - case 0x50: - ystart = 0; - xstart = 2097151; - yend = 2097151 / scaled_height; - xend = -2097151 / scaled_width; - orient = 1; - break; - case 0x60: - ystart = 2097151; - xstart = 0; - yend = -2097151 / scaled_height; - xend = 2097151 / scaled_width; - orient = 1; - break; - case 0x70: - xstart = 2097151; - ystart = 2097151; - yend = -2097151 / scaled_height; - xend = -2097151 / scaled_width; - orient = 1; - break; - default: - return; - } - long local_screen_x; - long local_screen_y; - local_screen_x = screen_x; - if (local_screen_x < 0) - { - scaled_width += local_screen_x; - if (scaled_width < 0) { - return; - } - xstart -= xend * local_screen_x; - local_screen_x = 0; - } - if (local_screen_x + scaled_width > vec_window_width) - { - scaled_width = vec_window_width - local_screen_x; - if (scaled_width < 0) { - return; - } - } - local_screen_y = screen_y; - if (local_screen_y < 0) - { - scaled_height += local_screen_y; - if (scaled_height < 0) { - return; - } - ystart -= local_screen_y * yend; - local_screen_y = 0; - } - if (local_screen_y + scaled_height > vec_window_height) - { - scaled_height = vec_window_height - local_screen_y; - if (scaled_height < 0) { - return; - } - } - int i; - int32_t hlimits[480]; - int32_t wlimits[640]; - int32_t *xlim; - int32_t *ylim; - unsigned char *dbuf; - unsigned char *block; - if (!orient) - { - xlim = wlimits; - for (i = scaled_width; i > 0; i--) - { - *xlim = xstart; - xlim++; - xstart += xend; - } - ylim = hlimits; - for (i = scaled_height; i > 0; i--) - { - *ylim = ystart; - ylim++; - ystart += yend; - } - dbuf = &vec_screen[local_screen_x + local_screen_y * vec_screen_width]; - block = block_ptrs[texture_block_index]; - ylim = hlimits; - long px; - long py; - int srcx; - int srcy; - unsigned char *d; - if ( fade_level >= 0 ) - { - for (py = scaled_height; py > 0; py--) - { - xlim = wlimits; - d = dbuf; - srcy = (((*ylim) & 0xFF0000u) >> 16); - for (px = scaled_width; px > 0; px--) - { - srcx = (((*xlim) & 0xFF0000u) >> 16); - xlim++; - *d = pixmap.fade_tables[256 * fade_level + block[(srcy << 8) + srcx]]; - ++d; - } - dbuf += vec_screen_width; - ylim++; - } - } else - { - for (py = scaled_height; py > 0; py--) - { - xlim = wlimits; - d = dbuf; - srcy = (((*ylim) & 0xFF0000u) >> 16); - for (px = scaled_width; px > 0; px--) - { - srcx = (((*xlim) & 0xFF0000u) >> 16); - xlim++; - *d = block[(srcy << 8) + srcx]; - ++d; - } - dbuf += vec_screen_width; - ylim++; - } - } - } else - { - ylim = wlimits; - for (i = scaled_height; i > 0; i--) - { - *ylim = ystart; - ylim++; - ystart += yend; - } - xlim = hlimits; - for (i = scaled_width; i > 0; i--) - { - *xlim = xstart; - xlim++; - xstart += xend; - } - dbuf = &vec_screen[local_screen_x + local_screen_y * vec_screen_width]; - block = block_ptrs[texture_block_index]; - ylim = wlimits; - long px; - long py; - int srcx; - int srcy; - unsigned char *d; - if ( fade_level >= 0 ) - { - for (py = scaled_height; py > 0; py--) - { - xlim = hlimits; - d = dbuf; - srcy = (((*ylim) & 0xFF0000u) >> 16); - for (px = scaled_width; px > 0; px--) - { - srcx = (((*xlim) & 0xFF0000u) >> 16); - xlim++; - *d = pixmap.fade_tables[256 * fade_level + block[(srcx << 8) + srcy]]; - ++d; - } - dbuf += vec_screen_width; - ylim++; - } - } else - { - for (py = scaled_height; py > 0; py--) - { - xlim = hlimits; - d = dbuf; - srcy = (((*ylim) & 0xFF0000u) >> 16); - for (px = scaled_width; px > 0; px--) - { - srcx = (((*xlim) & 0xFF0000u) >> 16); - xlim++; - *d = block[(srcx << 8) + srcy]; - ++d; - } - dbuf += vec_screen_width; - ylim++; - } - } - } -} - -void draw_texture(int32_t texture_x, int32_t texture_y, int32_t texture_width, int32_t texture_height, int32_t texture_block_index, int32_t flags, int32_t fade_level) -{ - scale_tmap2(texture_block_index, flags, fade_level, texture_x / pixel_size, texture_y / pixel_size, texture_width / pixel_size, texture_height / pixel_size); -} - void update_block_pointed(int i,long x, long x_frac, long y, long y_frac) { struct Map *mapblk; @@ -3189,427 +1572,83 @@ void engine(struct PlayerInfo *player, struct Camera *cam) setup_vecs(lbDisplay.GraphicsWindowPtr, 0, lbDisplay.GraphicsScreenWidth, ewnd.width, ewnd.height); camera_zoom = scale_camera_zoom_to_screen(cam->zoom); - draw_view(cam, 0); - lbDisplay.DrawFlags = flg_mem; - thing_being_displayed = 0; - LbScreenLoadGraphicsWindow(&grwnd); -} - -void find_frame_rate(void) -{ - static TbClockMSec prev_time2=0; - static TbClockMSec cntr_time2=0; - unsigned long curr_time; - curr_time = LbTimerClock(); - cntr_time2++; - if (curr_time-prev_time2 >= 1000) - { - double time_fdelta = 1000.0*((double)(cntr_time2))/(curr_time-prev_time2); - prev_time2 = curr_time; - game.time_delta = (unsigned long)(time_fdelta*256.0); - cntr_time2 = 0; - } -} - -void packet_load_find_frame_rate(unsigned long incr) -{ - static TbClockMSec start_time=0; - static TbClockMSec extra_frames=0; - TbClockMSec curr_time; - curr_time = LbTimerClock(); - if ((curr_time-start_time) < 5000) - { - extra_frames += incr; - } else - { - double time_fdelta = 1000.0*((double)(extra_frames+incr))/(curr_time-start_time); - start_time = curr_time; - game.time_delta = (unsigned long)(time_fdelta*256.0); - extra_frames = 0; - } -} - -/** - * Checks if the game screen needs redrawing. - */ -short display_should_be_updated_this_turn(void) -{ - if ((game.operation_flags & GOF_Paused) != 0) - return true; - if ( (game.turns_fastforward == 0) && (!game.packet_loading_in_progress) ) - { - find_frame_rate(); - if ( (game.frame_skip == 0) || ((get_gameturn() % game.frame_skip) == 0) ) - return true; - } else - if ( ((get_gameturn() & 0x3F)==0) || - ((game.packet_loading_in_progress) && ((get_gameturn() & 7)==0)) ) - { - packet_load_find_frame_rate(64); - return true; - } - return false; -} - -/** - * Makes last updates to the video buffer, and swaps buffers to show - * the new image. - */ -TbBool keeper_screen_swap(void) -{ -/* // For resolution 640x480, move the graphics data 40 lines lower - if ( lbDisplay.ScreenMode == Lb_SCREEN_MODE_640_480_8 ) - if (LbScreenLock() == Lb_SUCCESS) - { - int i; - int scrmove_x=0; - int scrmove_y=40; - int scanline_len=640; - for (i=400;i>=0;i--) - memcpy(lbDisplay.WScreen+scanline_len*(i+scrmove_y)+scrmove_x, lbDisplay.WScreen+scanline_len*i, scanline_len-scrmove_x); - memset(lbDisplay.WScreen, 0, scanline_len*scrmove_y); - LbScreenUnlock(); - }*/ - LbScreenSwap(); - return true; -} - -/** - * Waits until the next game turn. Delay is usually controlled by - * num_fps variable. - */ -extern "C" { -int32_t multiplayer_speed_adjustment_ns; -} - -TbBool keeper_wait_for_next_turn(void) -{ - const long double tick_ns_one_sec = 1000000000.0; - long double tick_ns_one_frame = -1; - if ((game.view_mode_flags & GNFldD_WaitSleepMode) != 0) - { - // No idea when such situation occurs - tick_ns_one_frame = tick_ns_one_sec; - } - if (game.frame_skip >= 0) - { - // Standard delaying system - int32_t num_fps = turns_per_second; - if (game.frame_skip > 0) - num_fps *= game.frame_skip; - - tick_ns_one_frame = tick_ns_one_sec/num_fps; - } - - if (tick_ns_one_frame >= 0) { - static long double tick_ns_last_turn = 0; - - long double tick_ns_cur = get_time_tick_ns(); - long double tick_ns_used = tick_ns_cur - tick_ns_last_turn; - long double tick_ns_delay = tick_ns_one_frame - tick_ns_used; - if (multiplayer_speed_adjustment_ns != 0) { - tick_ns_delay += multiplayer_speed_adjustment_ns; - } - - long double tick_ns_end = tick_ns_cur; - // tick_ns_used: every level, initialized_time_point will be reset, so tick_ns_used may be less than 0 when enter level for the non-first time, Skip it directly to solve the problem. - if (tick_ns_delay > 0 && tick_ns_used >= 0) { - tick_ns_end = tick_ns_cur + tick_ns_delay; - LbSleepUntilExt(tick_ns_end); - } - tick_ns_last_turn = tick_ns_end; - return true; - } - - return false; -} - -void redetect_screen_refresh_rate_for_draw() -{ - fps_limit_current = 0; - - if (fps_limit_main == -1) { - if (fps_limit_secondary > 0) - fps_limit_current = fps_limit_secondary; - - if (lbWindow != NULL) { - int display_index = SDL_GetWindowDisplayIndex(lbWindow); - if (display_index >= 0) { - SDL_DisplayMode mode; - if (SDL_GetCurrentDisplayMode(display_index, &mode) == 0 && mode.refresh_rate > 0) { - fps_limit_current = mode.refresh_rate; - } - } - } - - } else if (fps_limit_main > 0) { - fps_limit_current = fps_limit_main; - } -} - -static bool use_delta_time() -{ - // Always enable interpolation in multiplayer games. - return is_feature_on(Ft_DeltaTime) || network_is_active(); -} - -static void update_frontend_delta_time() -{ - static int64_t prev = 0; - const int64_t now = get_time_tick_ns(); - const int64_t ns = now - prev; - prev = now; - const long double dt = ns / 1e9L * turns_per_second; - game.delta_time = min(max(dt, 0.L), 1.L); -} - -static void update_gameplay_delta_time() -{ - if (use_delta_time()) { - static int64_t prev = 0; - const int64_t now = get_time_tick_ns(); - const int64_t ns = now - prev; - prev = now; - - const long double seconds = max(ns / 1e9L, 0.L); - const long double turns = seconds * turns_per_second; - const long double frames = seconds * fps_limit_current; - - game.process_turn_time += turns * multiplayer_clock_adjust * max(game.frame_skip, 1); - - // This sets game.delta_time, which is used to pace locally-displayed - // things (eg. tooltip scroll speed). It should not be affected by - // multiplayer clock adjustment or frameskip. - time_since_last_draw += turns; - - // Like process_turn_time, but for the video frame rate. - process_frame_time += frames; - } else { - // Set to 1 so that these variables don't affect anything. (if something is multiplied by 1 it doesn't change) - time_since_last_draw = 1; - game.delta_time = 1; - game.process_turn_time = 1; - process_frame_time = 1; - } -} - -static bool keeper_wait_for_screen_focus() -{ - do { - if ( !poll_inputs() ) - { - force_application_close(); - break; - } - if (LbIsActive()) - return true; - if (network_is_active()) - return true; - if (!freeze_game_on_focus_lost()) - return true; - LbSleepFor(50); - update_gameplay_delta_time(); - game.process_turn_time = 1.0; - time_since_last_draw = 1.0; - } while ((!exit_keeper) && (!quit_game)); - return false; -} - -static void gameplay_loop_draw() -{ - if (use_delta_time()) - do_draw = true; - - update_gameplay_delta_time(); - - if (game.process_turn_time > 1.0 && time_since_last_draw < 1.0) - do_draw = false; - - // Frame rate limiter - if (fps_limit_current > 0) - { - frametime_start_measurement(Frametime_Sleep); - if (process_frame_time < 1.0) - { - if (game.process_turn_time < 1.0) - SDL_Delay(1); - do_draw = false; - } - else - { - process_frame_time = min(1.L, process_frame_time - 1.L); - } - frametime_end_measurement(Frametime_Sleep); - } - - // Floats are used a lot in the drawing related functions. But keep in mind integers are typically preferred for logic related functions. - frametime_start_measurement(Frametime_Draw); - - // Update lights - update_light_render_area(); - - if (quit_game || exit_keeper) { - do_draw = false; - } - if ( do_draw ) { - if (frametime_enabled()) - framerate_measurement_capture(Framerate_Draw); - game.delta_time = min(time_since_last_draw, 1.L); - time_since_last_draw = 0; - interpolate_time = min(max(game.process_turn_time, 0.L), 1.L); - keeper_screen_redraw(); - } - keeper_wait_for_screen_focus(); - // Direct information/error messages - if (LbScreenLock() == Lb_SUCCESS) { - if ( do_draw ) { - perform_any_screen_capturing(); - } - draw_onscreen_direct_messages(); - LbScreenUnlock(); - } - // Move the graphics window to center of screen buffer and swap screen - if ( do_draw ) { - keeper_screen_swap(); - } - frametime_end_measurement(Frametime_Draw); - - if ( do_draw ) { - update_gameplay_delta_time(); - const long double delta = time_since_last_draw - average_frame_draw_time; - average_frame_draw_time += delta * max(average_frame_draw_time, .05L) / 20; - } -} - -static void gameplay_loop_logic() -{ - if(flag_is_set(start_params.debug_flags, DFlg_PauseAtGameTurn)) - { - static GameTurn previous_gameturn = 0; - if(get_gameturn() >= start_params.pause_at_gameturn && get_gameturn() != previous_gameturn) - { - if(!game.paused_at_gameturn) - { - game.paused_at_gameturn = true; - - game.frame_skip = 0; - if(game.packet_load_enable) - { - disable_packet_mode(); - } - set_packet_pause_toggle(); - } - } - previous_gameturn = get_gameturn(); - } - - if (use_delta_time()) - { - update_gameplay_delta_time(); - if (game.input_lag_turns == 0 && network_is_active()) - { - // Aim to exchange network packets before the turn ends. If drawing - // another frame could miss this deadline, skip it. - // In a 3-4 player game, clients must be 2 frames early. - const int frames = 1 + (netstate.my_id != SERVER_ID && game.active_players_count > 2); - const long double offset = frames * average_frame_draw_time * multiplayer_clock_adjust * max(game.frame_skip, 1); - if (game.process_turn_time + offset < 1.0) - return; - } - else - { - if (game.process_turn_time < 1.0) - return; - } - } - - frametime_start_measurement(Frametime_Logic); - if (frametime_enabled()) - framerate_measurement_capture(Framerate_Logic); - -#ifdef FUNCTESTING - if(flag_is_set(start_params.functest_flags, FTF_Enabled)) - { - FTestFrameworkState ftstate = ftest_update(NULL); - if(ftstate == FTSt_InvalidState || ftstate == FTSt_TestsCompletedSuccessfully) - { - quit_game = true; - exit_keeper = true; - return; - } - } -#endif // FUNCTESTING - do_draw = display_should_be_updated_this_turn() || (!LbIsActive()); - poll_inputs(); - input_eastegg(); - input(); - exchange_packets(); - - update_gameplay_delta_time(); - if (game.process_turn_time > turns_per_second + 1) - game.process_turn_time = turns_per_second + 1; - - // Adjust client time scaling - if (netstate.my_id != SERVER_ID && network_is_active()) - { - if (game.input_lag_turns == 0) - { - // Adjust the clock rate so that the host packet is received at - // process_turn_time == 1.0 (on average). If it is received later, - // reduce the scaling factor (< 1.0) so that the next turn takes a - // little longer in real time. Vice-versa if it is early. - - multiplayer_clock_adjust = 1 + (1 - host_packet_received) / 20; - } - else - { - const long double tick_ns_one_turn = 1e9L / turns_per_second; - const long double tick_ns_adjusted_turn = tick_ns_one_turn + multiplayer_speed_adjustment_ns; - assert (tick_ns_adjusted_turn > 0); - multiplayer_clock_adjust = tick_ns_one_turn / tick_ns_adjusted_turn; - } - } - else multiplayer_clock_adjust = 1.0; - host_packet_received = 1.0; + draw_view(cam, 0); + lbDisplay.DrawFlags = flg_mem; + thing_being_displayed = 0; + LbScreenLoadGraphicsWindow(&grwnd); +} - while (game.process_turn_time < 1.0) - { - gameplay_loop_draw(); - update_gameplay_delta_time(); - } - game.process_turn_time -= 1.0; +void redetect_screen_refresh_rate_for_draw() +{ + fps_limit_current = 0; - update(); + if (fps_limit_main == -1) { + if (fps_limit_secondary > 0) + fps_limit_current = fps_limit_secondary; - frametime_end_measurement(Frametime_Logic); + if (lbWindow != NULL) { + int display_index = SDL_GetWindowDisplayIndex(lbWindow); + if (display_index >= 0) { + SDL_DisplayMode mode; + if (SDL_GetCurrentDisplayMode(display_index, &mode) == 0 && mode.refresh_rate > 0) { + fps_limit_current = mode.refresh_rate; + } + } + } - if(game.frame_step) - { - game.frame_step = false; - set_packet_pause_toggle(); + } else if (fps_limit_main > 0) { + fps_limit_current = fps_limit_main; } } -static void gameplay_loop_network() +bool use_delta_time() { - if (! network_is_active()) - return; + // Always enable interpolation in multiplayer games. + return is_feature_on(Ft_DeltaTime) || network_is_active(); +} - network_update(game.packets, sizeof(struct Packet)); +void update_frontend_delta_time() +{ + static int64_t prev = 0; + const int64_t now = get_time_tick_ns(); + const int64_t ns = now - prev; + prev = now; + const long double dt = ns / 1e9L * turns_per_second; + game.delta_time = min(max(dt, 0.L), 1.L); } -static void gameplay_loop_timestep() +void update_gameplay_delta_time() { - if (! use_delta_time()) { - frametime_start_measurement(Frametime_Sleep); - // Make delay if the machine is too fast - if ( (!game.packet_load_enable) || (game.turns_fastforward == 0) ) { - keeper_wait_for_next_turn(); - } - frametime_end_measurement(Frametime_Sleep); + if (use_delta_time()) { + static int64_t prev = 0; + const int64_t now = get_time_tick_ns(); + const int64_t ns = now - prev; + prev = now; + + const long double seconds = max(ns / 1e9L, 0.L); + const long double turns = seconds * turns_per_second; + const long double frames = seconds * fps_limit_current; + + game.process_turn_time += turns * multiplayer_clock_adjust * max(game.frame_skip, 1); + + // This sets game.delta_time, which is used to pace locally-displayed + // things (eg. tooltip scroll speed). It should not be affected by + // multiplayer clock adjustment or frameskip. + time_since_last_draw += turns; + + // Like process_turn_time, but for the video frame rate. + process_frame_time += frames; + } else { + // Set to 1 so that these variables don't affect anything. (if something is multiplied by 1 it doesn't change) + time_since_last_draw = 1; + game.delta_time = 1; + game.process_turn_time = 1; + process_frame_time = 1; } } +void gameplay_loop_draw(); + extern "C" void network_yield_draw_gameplay() { gameplay_loop_draw(); @@ -3647,42 +1686,6 @@ extern "C" void network_yield_draw_frontend() LbScreenSwap(); } -void keeper_gameplay_loop(void) -{ - struct PlayerInfo *player; - SYNCDBG(5,"Starting"); - player = get_my_player(); - PaletteSetPlayerPalette(player, engine_palette); - if ((game.operation_flags & GOF_SingleLevel) != 0) { - initialise_eye_lenses(); - } - SYNCDBG(0,"Entering the gameplay loop for level %d",(int)get_loaded_level_number()); - LbErrorParachuteUpdate(); // For some reasone parachute keeps changing; Remove when won't be needed anymore - - initial_time_point(); - LbSleepExtInit(); - - //the main gameplay loop starts - while ((!quit_game) && (!exit_keeper)) - { - frametime_start_measurement(Frametime_FullFrame); - if (frametime_enabled()) - framerate_measurement_capture(Framerate_FullFrame); - gameplay_loop_logic(); - gameplay_loop_draw(); - gameplay_loop_network(); - gameplay_loop_timestep(); - - frametime_end_measurement(Frametime_FullFrame); - } // end while - SYNCDBG(0,"Gameplay loop finished after %lu turns",(unsigned long)get_gameturn()); - - // Reset the game kind because we are not in a game anymore at this point - game.game_kind = GKind_Unset; - - api_event("GAME_ENDED"); -} - TbBool can_thing_be_queried(struct Thing *thing, PlayerNumber plyr_idx) { if ( (!thing_is_creature(thing)) || !( (thing->owner == plyr_idx) || (creature_is_kept_in_custody_by_player(thing, plyr_idx)) ) || (thing->alloc_flags & TAlF_IsInLimbo) || (thing->state_flags & TF1_InCtrldLimbo) || (thing->active_state == CrSt_CreatureUnconscious) ) @@ -3700,463 +1703,7 @@ TbBool can_thing_be_queried(struct Thing *thing, PlayerNumber plyr_idx) } } -long packet_place_door(MapSubtlCoord stl_x, MapSubtlCoord stl_y, PlayerNumber plyr_idx, ThingModel tngmodel, TbBool allowed) -{ - if (!allowed) { - if (is_my_player_number(plyr_idx)) - play_non_3d_sample(snd_refusal); - return 0; - } - if (!player_place_door_at(stl_x, stl_y, plyr_idx, tngmodel)) { - return 0; - } - MapSlabCoord slb_x = subtile_slab(stl_x); - MapSlabCoord slb_y = subtile_slab(stl_y); - delete_room_slabbed_objects(get_slab_number(slb_x, slb_y)); - remove_dead_creatures_from_slab(slb_x, slb_y); - return 1; -} - -void initialise_map_collides(void) -{ - SYNCDBG(7,"Starting"); - MapSlabCoord slb_x; - MapSlabCoord slb_y; - for (slb_y=0; slb_y < game.map_tiles_y; slb_y++) - { - for (slb_x=0; slb_x < game.map_tiles_x; slb_x++) - { - struct SlabMap *slb; - slb = get_slabmap_block(slb_x, slb_y); - int ssub_x; - int ssub_y; - for (ssub_y=0; ssub_y < STL_PER_SLB; ssub_y++) - { - for (ssub_x=0; ssub_x < STL_PER_SLB; ssub_x++) - { - MapSubtlCoord stl_x; - MapSubtlCoord stl_y; - stl_x = slab_subtile(slb_x,ssub_x); - stl_y = slab_subtile(slb_y,ssub_y); - struct Map *mapblk; - mapblk = get_map_block_at(stl_x, stl_y); - mapblk->flags = 0; - update_map_collide(slb->kind, stl_x, stl_y); - } - } - } - } -} - -void initialise_map_health(void) -{ - SYNCDBG(7,"Starting"); - MapSlabCoord slb_x; - MapSlabCoord slb_y; - for (slb_y=0; slb_y < game.map_tiles_y; slb_y++) - { - for (slb_x=0; slb_x < game.map_tiles_x; slb_x++) - { - struct SlabMap *slb; - slb = get_slabmap_block(slb_x, slb_y); - struct SlabConfigStats *slabst; - slabst = get_slab_stats(slb); - slb->health = game.block_health[slabst->block_health_index]; - } - } -} - -static TbBool wait_at_frontend(void) -{ - struct PlayerInfo *player; - // This is an improvised coroutine-like stuff - CoroutineLoop loop; - memset(&loop, 0, sizeof(loop)); - - SYNCDBG(0,"Falling into frontend menu."); - // Moon phase calculation - calculate_moon_phase(true,false); - update_extra_levels_visibility(); - // Returning from Demo Mode - if (game.mode_flags & MFlg_IsDemoMode) - { - close_packet_file(); - game.packet_load_enable = 0; - } - game.save_game_slot = -1; - // Make sure campaigns are loaded - if (!load_campaigns_list(&campaigns_list ,FGrp_Campgn ,"campaigns","campgn_order.txt")) - { - ERRORLOG("No valid campaign files found"); - exit_keeper = 1; - return true; - } - // Make sure mappacks are loaded - if (!load_campaigns_list(&mappacks_list,FGrp_VarLevels,"mappacks","mappck_order.txt")) - { - WARNMSG("No valid mappack files found"); - } - if (!load_campaigns_list(&mp_mappacks_list,FGrp_MpLevels,"multiplayer mappacks","mp_mappck_order.txt")) - { - WARNMSG("No valid multiplayer mappack files found"); - } - //Set level number and campaign (for single level mode: GOF_SingleLevel) - if ((start_params.operation_flags & GOF_SingleLevel) != 0) - { - TbBool result = false; - if (start_params.selected_campaign[0] != '\0') - { - result = change_campaign(CampgnT_Default, start_params.selected_campaign); - } - if (!result) { - if (!change_campaign(CampgnT_Default,"")) { - WARNMSG("Unable to load default campaign for the specified level CMD Line parameter"); - } - else if (start_params.selected_campaign[0] != '\0') { // only show this log message if the user actually specified a campaign - WARNMSG("Unable to load campaign associated with the specified level CMD Line parameter, default loaded."); - } - else { - JUSTLOG("No campaign specified. Default campaign loaded for selected level (%u).", start_params.selected_level_number); - } - } - set_selected_level_number(start_params.selected_level_number); - //game.selected_level_number = start_params.selected_level_number; - } - else - { - set_selected_level_number(first_singleplayer_level()); - } - // Init load/save catalogue - initialise_load_game_slots(); - - #ifdef FUNCTESTING - if(flag_is_set(start_params.functest_flags, FTF_Enabled)) //override for functional tests - { - FTestFrameworkState ft_prev_state = FTSt_InvalidState; - FTestFrameworkState ft_current_state = ftest_update(&ft_prev_state); - - TbBool user_aborted_tests = ft_prev_state == FTSt_TestIsProcessingActions && ft_current_state == FTSt_TestIsProcessingActions; - if(user_aborted_tests) - { - FTEST_FAIL_TEST("User aborted tests"); - } - - if(ft_current_state == FTSt_InvalidState || ft_current_state == FTSt_TestsCompletedSuccessfully || user_aborted_tests) - { - quit_game = true; - exit_keeper = true; - return true; - } - faststartup_network_game(&loop); - coroutine_process(&loop); - return true; - } - #endif - - // Prepare to enter PacketLoad game - if ((game.packet_load_enable) && (!game.packet_load_initialized)) - { - faststartup_saved_packet_game(); - return true; - } - // Load single-player level directly from command line arguments (-server and -connect bypass this, autoloading a multiplayer map is handled elsewhere) - if ((game.operation_flags & GOF_SingleLevel) != 0 && !(game_flags2 & (GF2_Connect | GF2_Server))) - { - faststartup_network_game(&loop); - coroutine_process(&loop); - return true; - } - - if ( !setup_screen_mode_minimal(get_frontend_vidmode()) ) - { - FatalError = 1; - exit_keeper = 1; - return true; - } - LbScreenClear(0); - LbScreenSwap(); - if (frontend_load_data() != Lb_SUCCESS) - { - ERRORLOG("Unable to load frontend data"); - exit_keeper = 1; - return true; - } - memset(scratch, 0, PALETTE_SIZE); - LbPaletteSet(scratch); - frontend_set_state(get_startup_menu_state()); - - // Once the Mouse Sprite initialization is complete, the sprite's position needs to be reset because it defaults to (0, 0). - // Note that we cannot use LbMoveGameCursorToHostCursor for this, because the buffer position may remain unchanged. - LbMouseSetPositionInitial(lbDisplay.MMouseX, lbDisplay.MMouseY); - - try_restore_frontend_error_box(); - - poll_inputs(); - clear_mouse_pressed_lrbutton(); - - short finish_menu = 0; - clear_flag(game.mode_flags, MFlg_DemoMode); - // TODO move to separate function - // Begin the frontend loop - long fe_last_loop_time = LbTimerClock(); - do - { - if (!poll_inputs()) - { - force_application_close(); - SYNCDBG(0,"Windows Control exit condition invoked"); - break; - } - update_mouse(); - update_key_modifiers(); - old_mouse_over_button = frontend_mouse_over_button; - frontend_mouse_over_button = 0; - - frontend_input(); - if ( exit_keeper ) - { - SYNCDBG(0,"Frontend Input exit condition invoked"); - break; // end while - } - - frontend_update(&finish_menu); - if ( exit_keeper ) - { - SYNCDBG(0,"Frontend Update exit condition invoked"); - break; // end while - } - - if ((!finish_menu) && (LbIsActive())) - { - frontend_draw(); - LbScreenSwap(); - } - - if (!SoundDisabled) - { - process_3d_sounds(); - MonitorStreamedSoundTrack(); - } - - if (fade_palette_in) - { - fade_in(); - fade_palette_in = 0; - } else { - if (is_feature_on(Ft_DeltaTime) == true && should_use_delta_time_on_menu()) { - update_frontend_delta_time(); - } else { - int32_t frame_time; - frame_time = max(1, 1000 / turns_per_second); - game.delta_time = 1; - LbSleepUntil(fe_last_loop_time + frame_time); - } - } - fe_last_loop_time = LbTimerClock(); - - api_update_server(); - - } while (!finish_menu); - - LbPaletteFade(0, 8, Lb_PALETTE_FADE_CLOSED); - LbScreenClear(0); - LbScreenSwap(); - FrontendMenuState prev_state; - prev_state = frontend_menu_state; - frontend_set_state(FeSt_INITIAL); - if (exit_keeper) - { - player = get_my_player(); - player->display_flags &= ~PlaF6_PlyrHasQuit; - return true; - } - reenter_video_mode(); - - display_loading_screen(); - - short flgmem; - switch (prev_state) - { - case FeSt_START_KPRLEVEL: - my_player_number = default_loc_player; - game.game_kind = GKind_LocalGame; - clear_flag(game.system_flags, GSF_NetworkActive); - player = get_my_player(); - player->is_active = 1; - startup_network_game(&loop, true); - break; - case FeSt_START_MPLEVEL: - set_flag(game.system_flags, GSF_NetworkActive); - skip_high_score_screen = 1; - game.game_kind = GKind_MultiGame; - player = get_my_player(); - player->is_active = 1; - startup_network_game(&loop, false); - break; - case FeSt_LOAD_GAME: - flgmem = game.save_game_slot; - clear_flag(game.system_flags, GSF_NetworkActive); - LbScreenClear(0); - LbScreenSwap(); - if (!load_game(game.save_game_slot)) - { - ERRORLOG("Loading game %d failed; quitting.",(int)game.save_game_slot); - quit_game = 1; - } - game.save_game_slot = flgmem; - break; - case FeSt_PACKET_DEMO: - game.mode_flags |= MFlg_IsDemoMode; - startup_saved_packet_game(); - set_gui_visible(false); - clear_flag(game.operation_flags, GOF_ShowPanel); - break; - } - - coroutine_add(&loop, &set_not_has_quit); - coroutine_process(&loop); - if (loop.error) - { - frontend_set_state(FeSt_INITIAL); - return false; - } - return true; -} - -void game_loop(void) -{ -#if (BFDEBUG_LEVEL > 0) - unsigned long playtime = 0; -#endif - SYNCDBG(0,"Entering gameplay loop."); - - while ( !exit_keeper ) - { - update_mouse(); - while (!wait_at_frontend()) - { - if (exit_keeper) - break; - } - if ( exit_keeper ) - break; - - int32_t mspos_x_bak = lbDisplay.MMouseX; - int32_t mspos_y_bak = lbDisplay.MMouseY; - - if (game.game_kind == GKind_LocalGame) - { - if (game.save_game_slot == -1) - { - if (is_feature_on(Ft_SkipHeartZoom) == false) { - for (int i = 0; i < PLAYERS_COUNT; i++) { - struct PlayerInfo *player = get_player(i); - if (player_exists(player) && ((player->allocflags & PlaF_CompCtrl) == 0)) { - set_player_instance(player, PI_HeartZoom, 0); - } - } - } else { - if (!game.packet_load_enable) { - toggle_status_menu(1); // Required when skipping PI_HeartZoom - } - } - } else - { - game.save_game_slot = -1; - } - } else { - for (int i = 0; i < PLAYERS_COUNT; i++) { - struct PlayerInfo *player = get_player(i); - if (player_exists(player) && ((player->allocflags & PlaF_CompCtrl) == 0)) { - set_player_instance(player, PI_HeartZoom, 0); - } - } - } - - // Try to keep the mouse position unchanged when entering the level. - // The main considerations are: - // 1. SKIP_HEART_ZOOM: the mouse icon position will be reset to the top-left corner (0, 0), but the actual mouse position remains unchanged. - // 2. PI_HeartZoom: the mouse will be moved to the center of the screen. - LbMouseSetPosition(mspos_x_bak, mspos_y_bak); - - unsigned long starttime; -#if (BFDEBUG_LEVEL > 0) - unsigned long endtime; -#endif - struct Dungeon *dungeon; - // get_my_dungeon() can't be used here because players are not initialized yet - dungeon = get_dungeon(my_player_number); - starttime = LbTimerClock(); - dungeon->lvstats.start_time = starttime; - dungeon->lvstats.end_time = starttime; - if (!TimerNoReset) - { - if (is_feature_on(Ft_SkipHeartZoom)) - { - timerstarttime = starttime; - } - else - { - TimerFreeze = true; - } - memset(&Timer, 0, sizeof(Timer)); - } - LbScreenClear(0); - LbScreenSwap(); - game.frame_skip = 0; - keeper_gameplay_loop(); - set_pointer_graphic_none(); - LbScreenClear(0); - LbScreenSwap(); - stop_atmos_sounds(); - stop_music(true); - stop_streamed_samples(); - free_level_strings_data(); - turn_off_all_menus(); - delete_all_structures(); - clear_mapwho(); - // Reset sounds back to the fxdata baseline so the main menu (and any - // subsequent campaign/freeplay selection) hears unmodified defaults. - sound_reset_to_fxdata_baseline(); -#if (BFDEBUG_LEVEL > 0) - endtime = LbTimerClock(); -#endif - quit_game = 0; - if ((game.operation_flags & GOF_SingleLevel) != 0) - exit_keeper=true; -#if (BFDEBUG_LEVEL > 0) - playtime += endtime-starttime; -#endif - SYNCDBG(0,"Play time is %lu seconds",playtime>>10); - reset_eye_lenses(); - close_packet_file(); - game.packet_load_enable = false; - game.packet_save_enable = false; - } // end while - - // Stop the movie recording if it's on - if ((game.system_flags & GSF_CaptureMovie) != 0) { - movie_record_stop(); - } - ShutDownSDLAudio(); - SYNCDBG(7,"Done"); -} - -short reset_game(void) -{ - SYNCDBG(6,"Starting"); - - LbMouseSuspend(); - LbIKeyboardClose(); - LbScreenReset(false); - LbDataFreeAllV2(game_load_files); - free_gui_strings_data(); - free_level_strings_data(); - FreeAudio(); - return 1; -} - -short process_command_line(unsigned short argc, char *argv[]) +static short process_command_line(unsigned short argc, char *argv[]) { char fullpath[CMDLN_MAXLEN+1]; snprintf(fullpath, CMDLN_MAXLEN, "%s", argv[0]); @@ -4483,7 +2030,7 @@ short process_command_line(unsigned short argc, char *argv[]) return (bad_param==0); } -const char* determine_log_filename(unsigned short argument_count, char *argument_values[]) +static const char* determine_log_filename(unsigned short argument_count, char *argument_values[]) { for (int argument_index = 1; argument_index < argument_count; argument_index++) { if (argument_values[argument_index] && (argument_values[argument_index][0] == '-' || argument_values[argument_index][0] == '/')) { @@ -4497,6 +2044,20 @@ const char* determine_log_filename(unsigned short argument_count, char *argument return log_file_name; } +static short reset_game(void) +{ + SYNCDBG(6,"Starting"); + + LbMouseSuspend(); + LbIKeyboardClose(); + LbScreenReset(false); + LbDataFreeAllV2(game_load_files); + free_gui_strings_data(); + free_level_strings_data(); + FreeAudio(); + return 1; +} + int LbBullfrogMain(unsigned short argc, char *argv[]) { short retval; @@ -4594,28 +2155,6 @@ int kfxmain(int argc, char *argv[]) return 0; } -void update_time(void) -{ - unsigned long time = ((unsigned long)LbTimerClock()) - timerstarttime; - Timer.MSeconds = time % 1000; - time /= 1000; - Timer.Seconds = time % 60; - time /= 60; - Timer.Minutes = time % 60; - Timer.Hours = time / 60; -} - -struct GameTime get_game_time(unsigned long turns, unsigned long fps) -{ - struct GameTime GameT; - unsigned long time = turns / fps; - GameT.Seconds = time % 60; - time /= 60; - GameT.Minutes = time % 60; - GameT.Hours = time / 60; - return GameT; -} - #ifdef __cplusplus } #endif diff --git a/src/main_game.c b/src/main_game.c index 8bea1096a9..fad0301b6b 100644 --- a/src/main_game.c +++ b/src/main_game.c @@ -107,7 +107,7 @@ void reset_script_timers_and_flags(void) } } -void init_player_types() +static void init_player_types() { for (size_t plr_idx = 0; plr_idx < PLAYERS_COUNT; plr_idx++) { @@ -131,6 +131,24 @@ void init_player_types() } } +static void init_keepers_map_exploration(void) +{ + struct PlayerInfo *player; + int i; + for (i=0; i < PLAYERS_COUNT; i++) + { + player = get_player(i); + if ((player_exists(player) && (player->is_active == 1)) || player_is_roaming(i)) + { + // Additional init - the main one is in init_player() + if ((player->allocflags & PlaF_CompCtrl) != 0) { + init_keeper_map_exploration_by_terrain(player); + init_keeper_map_exploration_by_creatures(player); + } + } + } +} + /******************************************************************************/ static void init_level(void) @@ -423,19 +441,6 @@ CoroutineLoopState set_not_has_quit(CoroutineLoop *context) return CLS_CONTINUE; } -void faststartup_saved_packet_game(void) -{ - reenter_video_mode(); - startup_saved_packet_game(); - { - struct PlayerInfo *player; - player = get_my_player(); - player->display_flags &= ~PlaF6_PlyrHasQuit; - } - set_gui_visible(false); - clear_flag(game.operation_flags, GOF_ShowPanel); -} - /******************************************************************************/ /** diff --git a/src/net_exchange_gameplay.c b/src/net_exchange_gameplay.c index 5cad2c4899..19b5b5eb89 100644 --- a/src/net_exchange_gameplay.c +++ b/src/net_exchange_gameplay.c @@ -36,7 +36,10 @@ #include "post_inc.h" extern void network_yield_waiting_gameplay_packets(void); -extern int32_t multiplayer_speed_adjustment_ns; + +/******************************************************************************/ + +int32_t multiplayer_speed_adjustment_ns; /******************************************************************************/ diff --git a/src/net_exchange_gameplay.h b/src/net_exchange_gameplay.h index d9e5f5ed0a..81ae33e733 100644 --- a/src/net_exchange_gameplay.h +++ b/src/net_exchange_gameplay.h @@ -27,6 +27,8 @@ extern "C" { #endif +extern int32_t multiplayer_speed_adjustment_ns; + struct Packet; void initialize_packet_history(void); diff --git a/src/net_resync.cpp b/src/net_resync.cpp index 40a7eef521..cd39eb6987 100644 --- a/src/net_resync.cpp +++ b/src/net_resync.cpp @@ -31,6 +31,7 @@ #include "lua_base.h" #include "net_input_lag.h" #include "net_checksums.h" +#include "keeperfx.hpp" #include "post_inc.h" #ifdef __cplusplus diff --git a/src/packets.c b/src/packets.c index 7e8c69e2f0..bf3cbe4603 100644 --- a/src/packets.c +++ b/src/packets.c @@ -1636,28 +1636,7 @@ void process_packets(void) SYNCDBG(7,"Finished"); } -// Using Alt-F4, or similar operating system close requests -void force_application_close() -{ - extern int frontend_menu_state; - if (frontend_menu_state == 0) - { - struct PlayerInfo* player = get_my_player(); - if (player != INVALID_PLAYER) - { - set_players_packet_action(player, PckA_ForceApplicationClose, 0, 0, 0, 0); - } - else - { - exit_keeper = 1; - } - } - else - { - exit_keeper = 1; - } -} /******************************************************************************/ diff --git a/src/packets.h b/src/packets.h index 9b6a02a0ea..f6dded9815 100644 --- a/src/packets.h +++ b/src/packets.h @@ -333,7 +333,6 @@ void unset_packet_control(struct Packet *pckt, unsigned long flag); void unset_players_packet_control(struct PlayerInfo *player, unsigned long flag); void set_players_packet_position(struct Packet *pckt, long x, long y, unsigned char context); void set_packet_pause_toggle(void); -void force_application_close(void); struct Thing *get_thing_under_hand(struct PlayerInfo *player, MapCoord x, MapCoord y); TbBool process_dungeon_control_packet_clicks(long idx); TbBool process_players_dungeon_control_packet_action(long idx); diff --git a/src/packets_input.c b/src/packets_input.c index c5c62bf125..d6e556e50c 100644 --- a/src/packets_input.c +++ b/src/packets_input.c @@ -47,6 +47,7 @@ #include "cursor_tag.h" #include "engine_render.h" #include "config_settings.h" +#include "keeperfx.hpp" #include "post_inc.h" extern TbBool process_dungeon_control_packet_spell_overcharge(long plyr_idx); @@ -909,16 +910,6 @@ TbBool process_dungeon_control_packet_clicks(long plyr_idx) y = (pckt->pos_y); stl_x = coord_subtile(x); stl_y = coord_subtile(y); - if (player->thing_under_hand == 0) - { - if ((x != 0) && (y != 0)) //originally was (y == 0), but it was probably a mistake - { - thing = get_queryable_object_near(x, y, plyr_idx); - if (!thing_is_invalid(thing)) { - player->thing_under_hand = thing->index; - } - } - } struct PlayerStateConfigStats* plrst_cfg_stat = get_player_state_stats(player->work_state); if (((pckt->control_flags & PCtr_HeldAnyButton) != 0) && (plrst_cfg_stat->stop_own_units)) { diff --git a/src/player_instances.c b/src/player_instances.c index 665b863e71..c363fc2497 100644 --- a/src/player_instances.c +++ b/src/player_instances.c @@ -63,6 +63,7 @@ #include "map_blocks.h" #include "lua_triggers.h" #include "lens_api.h" +#include "timer.h" #include "keeperfx.hpp" #include "post_inc.h" @@ -1320,6 +1321,23 @@ TbBool player_place_door_at(MapSubtlCoord stl_x, MapSubtlCoord stl_y, PlayerNumb return player_place_door_without_check_at(stl_x, stl_y, plyr_idx, tngmodel,0); } +long packet_place_door(MapSubtlCoord stl_x, MapSubtlCoord stl_y, PlayerNumber plyr_idx, ThingModel tngmodel, TbBool allowed) +{ + if (!allowed) { + if (is_my_player_number(plyr_idx)) + play_non_3d_sample(snd_refusal); + return 0; + } + if (!player_place_door_at(stl_x, stl_y, plyr_idx, tngmodel)) { + return 0; + } + MapSlabCoord slb_x = subtile_slab(stl_x); + MapSlabCoord slb_y = subtile_slab(stl_y); + delete_room_slabbed_objects(get_slab_number(slb_x, slb_y)); + remove_dead_creatures_from_slab(slb_x, slb_y); + return 1; +} + TbBool is_thing_directly_controlled_by_player(const struct Thing *thing, PlayerNumber plyr_idx) { if (!thing_exists(thing)) diff --git a/src/player_instances.h b/src/player_instances.h index ebfacd2ba9..0acd814ccd 100644 --- a/src/player_instances.h +++ b/src/player_instances.h @@ -114,6 +114,7 @@ TbBool player_place_trap_at(MapSubtlCoord stl_x, MapSubtlCoord stl_y, PlayerNumb TbBool player_place_trap_without_check_at(MapSubtlCoord stl_x, MapSubtlCoord stl_y, PlayerNumber plyr_idx, ThingModel tngmodel, TbBool free); TbBool player_place_door_at(MapSubtlCoord stl_x, MapSubtlCoord stl_y, PlayerNumber plyr_idx, ThingModel tngmodel); TbBool player_place_door_without_check_at(MapSubtlCoord stl_x, MapSubtlCoord stl_y, PlayerNumber plyr_idx, ThingModel tngmodel, TbBool free); +long packet_place_door(MapSubtlCoord stl_x, MapSubtlCoord stl_y, PlayerNumber plyr_idx, ThingModel tngmodel, TbBool allowed); /******************************************************************************/ #ifdef __cplusplus } diff --git a/src/player_utils.c b/src/player_utils.c index 8898509db1..f3beaaa7fd 100644 --- a/src/player_utils.c +++ b/src/player_utils.c @@ -58,6 +58,7 @@ #include "gui_frontbtns.h" #include "keeperfx.hpp" #include "kjm_input.h" +#include "timer.h" #include "post_inc.h" /******************************************************************************/ diff --git a/src/power_hand.h b/src/power_hand.h index 9d2086480d..28eca8512a 100644 --- a/src/power_hand.h +++ b/src/power_hand.h @@ -88,6 +88,8 @@ TbBool thing_pickup_is_blocked_by_hand_rule(const struct Thing *thing_to_pick, P void reset_hand_rules(void); void script_set_hand_rule(PlayerNumber plyr_idx, long crtr_id,long hand_rule_action,long hand_rule_slot,long hand_rule_type,long param); +void process_things_in_dungeon_hand(void); + enum HandRuleType { // hand_rule_test_fns are indexed by these enum values -> reordering or adding new types affects test_fns HandRule_Unset, diff --git a/src/room_entrance.c b/src/room_entrance.c index 90efef5d3d..900f638ffe 100644 --- a/src/room_entrance.c +++ b/src/room_entrance.c @@ -451,3 +451,9 @@ void add_creature_to_pool(ThingModel kind, int32_t amount) game.pool.crtr_kind[kind] += amount; } } + +void clear_creature_pool(void) +{ + memset(&game.pool,0,sizeof(struct CreaturePool)); + game.pool.is_empty = true; +} diff --git a/src/room_library.c b/src/room_library.c index 6a3e833811..f681eb13e4 100644 --- a/src/room_library.c +++ b/src/room_library.c @@ -288,7 +288,7 @@ TbBool update_or_add_players_research_amount(PlayerNumber plyr_idx, long rtyp, l return add_research_to_player(plyr_idx, rtyp, rkind, amount); } -void process_player_research(PlayerNumber plyr_idx) +static void process_player_research(PlayerNumber plyr_idx) { struct Dungeon* dungeon = get_dungeon(plyr_idx); if (!player_has_room_of_role(plyr_idx, RoRoF_Research)) { @@ -426,6 +426,21 @@ void process_player_research(PlayerNumber plyr_idx) return; } +void update_research(void) +{ + int i; + struct PlayerInfo *player; + SYNCDBG(6,"Starting"); + for (i = 0; i < PLAYERS_COUNT; i++) + { + player = get_player(i); + if (player_exists(player) && (player->is_active == 1)) + { + process_player_research(i); + } + } +} + void research_found_room(PlayerNumber plyr_idx, RoomKind rkind) { struct Dungeon* dungeon = get_dungeon(plyr_idx); diff --git a/src/room_library.h b/src/room_library.h index 528cc91157..f5f6e86790 100644 --- a/src/room_library.h +++ b/src/room_library.h @@ -46,7 +46,7 @@ TbBool clear_research_for_all_players(void); TbBool research_overriden_for_player(PlayerNumber plyr_idx); TbBool update_players_research_amount(PlayerNumber plyr_idx, long rtyp, long rkind, long amount); TbBool update_or_add_players_research_amount(PlayerNumber plyr_idx, long rtyp, long rkind, long amount); -void process_player_research(PlayerNumber plyr_idx); +void update_research(void); EventIndex update_library_object_pickup_event(struct Thing *creatng, struct Thing *picktng); void research_found_room(PlayerNumber plyr_idx, RoomKind rkind); diff --git a/src/room_workshop.c b/src/room_workshop.c index 31518a556d..94e8a9ca38 100644 --- a/src/room_workshop.c +++ b/src/room_workshop.c @@ -601,7 +601,7 @@ long manufacture_points_required_f(long mfcr_type, unsigned long mfcr_kind, cons } } -short process_player_manufacturing(PlayerNumber plyr_idx) +static short process_player_manufacturing(PlayerNumber plyr_idx) { SYNCDBG(7,"Starting for player %d",(int)plyr_idx); @@ -672,6 +672,21 @@ short process_player_manufacturing(PlayerNumber plyr_idx) return false; } +void update_manufacturing(void) +{ + int i; + struct PlayerInfo *player; + SYNCDBG(16,"Starting"); + for (i=0; iis_active == 1)) + { + process_player_manufacturing(i); + } + } +} + EventIndex update_workshop_object_pickup_event(struct Thing *creatng, struct Thing *picktng) { EventIndex evidx; diff --git a/src/room_workshop.h b/src/room_workshop.h index 2301143679..9334d5054d 100644 --- a/src/room_workshop.h +++ b/src/room_workshop.h @@ -74,7 +74,7 @@ struct Thing *create_crate_in_workshop(struct Room *room, ThingModel cratngmodel TbBool remove_workshop_object_from_player(PlayerNumber owner, ThingModel objmodel); long get_doable_manufacture_with_minimal_amount_available(const struct Dungeon *dungeon, int * mnfctr_class, int * mnfctr_kind); TbBool get_next_manufacture(struct Dungeon *dungeon); -short process_player_manufacturing(PlayerNumber plyr_idx); +void update_manufacturing(void); EventIndex update_workshop_object_pickup_event(struct Thing *creatng, struct Thing *picktng); TbBool is_trap_buildable(PlayerNumber plyr_idx, long tngmodel); diff --git a/src/slab_data.c b/src/slab_data.c index a9aff3a919..48ee716c9d 100644 --- a/src/slab_data.c +++ b/src/slab_data.c @@ -904,6 +904,55 @@ void set_player_texture(PlayerNumber plyr_idx, long texture_id) } } } + +void initialise_map_collides(void) +{ + SYNCDBG(7,"Starting"); + MapSlabCoord slb_x; + MapSlabCoord slb_y; + for (slb_y=0; slb_y < game.map_tiles_y; slb_y++) + { + for (slb_x=0; slb_x < game.map_tiles_x; slb_x++) + { + struct SlabMap *slb; + slb = get_slabmap_block(slb_x, slb_y); + int ssub_x; + int ssub_y; + for (ssub_y=0; ssub_y < STL_PER_SLB; ssub_y++) + { + for (ssub_x=0; ssub_x < STL_PER_SLB; ssub_x++) + { + MapSubtlCoord stl_x; + MapSubtlCoord stl_y; + stl_x = slab_subtile(slb_x,ssub_x); + stl_y = slab_subtile(slb_y,ssub_y); + struct Map *mapblk; + mapblk = get_map_block_at(stl_x, stl_y); + mapblk->flags = 0; + update_map_collide(slb->kind, stl_x, stl_y); + } + } + } + } +} + +void initialise_map_health(void) +{ + SYNCDBG(7,"Starting"); + MapSlabCoord slb_x; + MapSlabCoord slb_y; + for (slb_y=0; slb_y < game.map_tiles_y; slb_y++) + { + for (slb_x=0; slb_x < game.map_tiles_x; slb_x++) + { + struct SlabMap *slb; + slb = get_slabmap_block(slb_x, slb_y); + struct SlabConfigStats *slabst; + slabst = get_slab_stats(slb); + slb->health = game.block_health[slabst->block_health_index]; + } + } +} /******************************************************************************/ #ifdef __cplusplus } diff --git a/src/thing_creature.c b/src/thing_creature.c index c01b680483..ac4a090baa 100644 --- a/src/thing_creature.c +++ b/src/thing_creature.c @@ -96,6 +96,7 @@ #include "thing_traps.h" #include "lua_triggers.h" #include "lua_cfg_funcs.h" +#include "room_workshop.h" #include "keeperfx.hpp" #include "post_inc.h" @@ -5880,6 +5881,28 @@ struct Thing *create_footprint_sine(struct Coord3d *crtr_pos, unsigned short pha return INVALID_THING; } +long get_foot_creature_has_down(struct Thing *thing) +{ + struct CreatureControl *cctrl; + unsigned short val; + long i; + int n; + cctrl = creature_control_get_from_thing(thing); + val = thing->current_frame; + if (val == (cctrl->anim_time >> 8)) + return 0; + unsigned short frame = (creature_is_dragging_something(thing)) ? CGI_Drag : CGI_Ambulate; + n = get_creature_model_graphics(thing->model, frame); + i = get_td_animation_sprite(n); + if (i != thing->anim_sprite) + return 0; + if (val == 1) + return 1; + if (val == 4) + return 2; + return 0; +} + void place_bloody_footprint(struct Thing *thing) { struct CreatureControl* cctrl = creature_control_get_from_thing(thing); @@ -6342,6 +6365,33 @@ long update_creature_levels(struct Thing *thing) return -1; } +static void process_keeper_spell_aura(struct Thing *thing) +{ + struct CreatureControl *cctrl; + TRACE_THING(thing); + cctrl = creature_control_get_from_thing(thing); + cctrl->spell_aura_duration--; + if (cctrl->spell_aura_duration <= 0) + { + cctrl->spell_aura = 0; + return; + } + struct Coord3d pos; + long amp; + long direction; + long delta_x; + long delta_y; + amp = 5 * thing->clipbox_size_xy / 8; + direction = THING_RANDOM(thing, DEGREES_360); + delta_x = (amp * LbSinL(direction) >> 8); + delta_y = (amp * LbCosL(direction) >> 8); + pos.x.val = thing->mappos.x.val + (delta_x >> 8); + pos.y.val = thing->mappos.y.val - (delta_y >> 8); + pos.z.val = thing->mappos.z.val; + + create_used_effect_or_element(&pos, cctrl->spell_aura, thing->owner, thing->index); +} + TngUpdateRet update_creature(struct Thing *thing) { SYNCDBG(19,"Starting for %s index %d",thing_model_name(thing),(int)thing->index); diff --git a/src/thing_effects.c b/src/thing_effects.c index ad108d47c5..23b9d19283 100644 --- a/src/thing_effects.c +++ b/src/thing_effects.c @@ -77,6 +77,42 @@ struct EffectElementConfigStats *get_effect_element_model_stats(ThingModel tngmo return &game.conf.effects_conf.effectelement_cfgstats[tngmodel]; } +static TbBool any_player_close_enough_to_see(const struct Coord3d *pos) +{ + struct PlayerInfo *player; + int i; + short limit = 24 * COORD_PER_STL; + for (i=0; i < PLAYERS_COUNT; i++) + { + player = get_player(i); + if ( (player_exists(player)) && ((player->allocflags & PlaF_CompCtrl) == 0)) + { + struct Camera *camera = get_player_active_camera(player); + if (camera == NULL) + continue; + if (camera->view_mode != PVM_FrontView) + { + if (camera->zoom >= CAMERA_ZOOM_MIN) + { + limit = SHRT_MAX - (2 * camera->zoom); + } + } + else + { + if (camera->zoom >= FRONTVIEW_CAMERA_ZOOM_MIN) + { + limit = SHRT_MAX - (camera->zoom / 3); + } + } + if (get_chessboard_distance(&camera->mappos, pos) <= limit) + { + return true; + } + } + } + return false; +} + struct Thing *create_effect_element(const struct Coord3d *pos, ThingModel eelmodel, PlayerNumber owner) { long i; @@ -1754,6 +1790,227 @@ void create_effects_line(TbMapLocation from, TbMapLocation to, char curvature, u fx_line->partial_steps = FX_LINE_TIME_PARTS; } +void draw_flame_breath(struct Coord3d *pos1, struct Coord3d *pos2, long delta_step, long num_per_step, short ef_or_efel_model, ThingIndex parent_idx) +{ + MapCoordDelta dist_x; + MapCoordDelta dist_y; + MapCoordDelta dist_z; + dist_x = pos2->x.val - (MapCoordDelta)pos1->x.val; + dist_y = pos2->y.val - (MapCoordDelta)pos1->y.val; + dist_z = pos2->z.val - (MapCoordDelta)pos1->z.val; + int delta_x; + int delta_y; + int delta_z; + if (delta_step <= 0) + delta_step = 1; + if (dist_x >= 0) + { + delta_x = delta_step; + } else { + dist_x = -dist_x; + delta_x = -delta_step; + } + if (dist_y >= 0) { + delta_y = delta_step; + } else { + dist_y = -dist_y; + delta_y = -delta_step; + } + if (dist_z >= 0) { + delta_z = delta_step; + } else { + dist_z = -dist_z; + delta_z = -delta_step; + } + // Now our dist_x,dist_y,dist_z is always non-negative, + // and sign is stored in delta_x,delta_y,delta_z. + if ((dist_x != 0) || (dist_y != 0) || (dist_z != 0)) + { + int nsteps; + // Find max dimension, and scale deltas to it + if ((dist_z > dist_x) && (dist_z > dist_y)) + { + nsteps = dist_z / delta_step; + delta_y = dist_y * delta_y / dist_z; + delta_x = dist_x * delta_x / dist_z; + } else + if ((dist_x > dist_y) && (dist_x > dist_z)) + { + nsteps = dist_x / delta_step; + delta_y = dist_y * delta_y / dist_x; + delta_z = dist_z * delta_z / dist_x; + } else + if ((dist_y > dist_x) && (dist_y > dist_z)) + { + nsteps = dist_y / delta_step; + delta_x = dist_x * delta_x / dist_y; + delta_z = dist_z * delta_z / dist_y; + } else + { // No dominate direction + nsteps = (dist_x + dist_y + dist_z) / delta_step; + delta_x = dist_x * delta_x / (dist_x + dist_y + dist_z); + delta_y = dist_y * delta_y / (dist_x + dist_y + dist_z); + delta_z = dist_z * delta_z / (dist_x + dist_y + dist_z); + } + + int sprsize = 0; + int delta_size = 0; + + struct EffectElementConfigStats *eestat; + if (ef_or_efel_model < 0) + { + eestat = get_effect_element_model_stats(ef_or_efel_model * -1); + delta_size = ((eestat->sprite_size_max - eestat->sprite_size_min) << 8) / (nsteps+1); + sprsize = (eestat->sprite_size_min << 8); + } + + int deviat; + deviat = 1; + struct Coord3d curpos; + curpos.x.val = pos1->x.val; + curpos.y.val = pos1->y.val; + curpos.z.val = pos1->z.val; + int i; + for (i=nsteps+1; i > 0; i--) + { + int devrange; + devrange = 2 * deviat; + int k; + for (k = num_per_step; k > 0; k--) + { + struct Coord3d tngpos; + tngpos.x.val = curpos.x.val + deviat - UNSYNC_RANDOM(devrange); + tngpos.y.val = curpos.y.val + deviat - UNSYNC_RANDOM(devrange); + tngpos.z.val = curpos.z.val + deviat - UNSYNC_RANDOM(devrange); + if ((tngpos.x.val < subtile_coord(game.map_subtiles_x,0)) && (tngpos.y.val < subtile_coord(game.map_subtiles_y,0))) + { + struct Thing *eelemtng; + + eelemtng = create_used_effect_or_element(&tngpos, ef_or_efel_model, game.neutral_player_num, parent_idx); + if (!thing_is_invalid(eelemtng)) { + eelemtng->sprite_size = sprsize >> 8; + } + } + } + curpos.x.val += delta_x; + curpos.y.val += delta_y; + curpos.z.val += delta_z; + deviat += 16; + sprsize += delta_size; + } + } +} + +void draw_lightning(const struct Coord3d *pos1, const struct Coord3d *pos2, long eeinterspace, EffectOrEffElModel ef_or_efel_model) +{ + MapCoordDelta dist_x = pos2->x.val - pos1->x.val; + MapCoordDelta dist_y = pos2->y.val - pos1->y.val; + MapCoordDelta dist_z = pos2->z.val - pos1->z.val; + int delta_x; + int delta_y; + int delta_z; + if (eeinterspace <= 0) + eeinterspace = 1; + if (dist_x >= 0) { + delta_x = eeinterspace; + } else { + dist_x = -dist_x; + delta_x = -eeinterspace; + } + if (dist_y >= 0) { + delta_y = eeinterspace; + } else { + dist_y = -dist_y; + delta_y = -eeinterspace; + } + if (dist_z >= 0) { + delta_z = eeinterspace; + } else { + dist_z = -dist_z; + delta_z = -eeinterspace; + } + if ((dist_x != 0) || (dist_y != 0) || (dist_z != 0)) + { + int nsteps; + if ((dist_z >= dist_x) && (dist_z >= dist_y)) + { + nsteps = dist_z / eeinterspace; + delta_y = delta_y * dist_y / dist_z; + delta_x = dist_x * delta_x / dist_z; + } else + if ((dist_x >= dist_y) && (dist_x >= dist_z)) + { + nsteps = dist_x / eeinterspace; + delta_y = delta_y * dist_y / dist_x; + delta_z = delta_z * dist_z / dist_x; + } else + { + nsteps = dist_y / eeinterspace; + delta_x = dist_x * delta_x / dist_y; + delta_z = delta_z * dist_z / dist_y; + } + int deviat_x = 0; + int deviat_y = 0; + int deviat_z = 0; + struct Coord3d curpos; + curpos.x.val = pos1->x.val + UNSYNC_RANDOM(eeinterspace/4); + curpos.y.val = pos1->y.val + UNSYNC_RANDOM(eeinterspace/4); + curpos.z.val = pos1->z.val + UNSYNC_RANDOM(eeinterspace/4); + for (int i=nsteps+1; i > 0; i--) + { + struct Coord3d tngpos; + tngpos.x.val = curpos.x.val + deviat_x; + tngpos.y.val = curpos.y.val + deviat_y; + tngpos.z.val = curpos.z.val + deviat_z; + if ((tngpos.x.val < subtile_coord(game.map_subtiles_x,0)) && (tngpos.y.val < subtile_coord(game.map_subtiles_y,0))) + { + create_used_effect_or_element(&tngpos, ef_or_efel_model, game.neutral_player_num, 0); + } + if (UNSYNC_RANDOM(6) >= 3) { + deviat_x -= 32; + } else { + deviat_x += 32; + } + if (UNSYNC_RANDOM(6) >= 3) { + deviat_y -= 32; + } else { + deviat_y += 32; + } + if (UNSYNC_RANDOM(6) >= 3) { + deviat_z -= 32; + } else { + deviat_z += 32; + } + MapCoordDelta dist = get_chessboard_3d_distance(&curpos, pos2); + int deviat_limit = 128; + if (dist < 1024) + deviat_limit = (dist * 128) / 1024; + // Limit deviations + if (deviat_x < -deviat_limit) { + deviat_x = -deviat_limit; + } else + if (deviat_x > deviat_limit) { + deviat_x = deviat_limit; + } + if (deviat_y < -deviat_limit) { + deviat_y = -deviat_limit; + } else + if (deviat_y > deviat_limit) { + deviat_y = deviat_limit; + } + if (deviat_z < -deviat_limit) { + deviat_z = -deviat_limit; + } else + if (deviat_z > deviat_limit) { + deviat_z = deviat_limit; + } + curpos.x.val += delta_x; + curpos.y.val += delta_y; + curpos.z.val += delta_z; + } + } +} + /******************************************************************************/ #ifdef __cplusplus } diff --git a/src/thing_effects.h b/src/thing_effects.h index 357bcd981e..66acc682db 100644 --- a/src/thing_effects.h +++ b/src/thing_effects.h @@ -292,7 +292,10 @@ long explosion_affecting_area(struct Thing *tngsrc, const struct Coord3d *pos, M HitPoints max_damage, long blow_strength, HitTargetFlags hit_targets); TbBool explosion_affecting_door(struct Thing *tngsrc, struct Thing *tngdst, const struct Coord3d *pos, - MapCoordDelta max_dist, HitPoints max_damage, long blow_strength, PlayerNumber owner); + MapCoordDelta max_dist, HitPoints max_damage, long blow_strength, PlayerNumber owner); + +void draw_flame_breath(struct Coord3d *pos1, struct Coord3d *pos2, long delta_step, long num_per_step, short ef_or_efel_model, ThingIndex parent_idx); +void draw_lightning(const struct Coord3d* pos1, const struct Coord3d* pos2, long eeinterspace, short ef_or_efel_model); /******************************************************************************/ #ifdef __cplusplus } diff --git a/src/thing_list.c b/src/thing_list.c index 3ff307dcde..a7ad9bc1c6 100644 --- a/src/thing_list.c +++ b/src/thing_list.c @@ -34,6 +34,7 @@ #include "thing_physics.h" #include "thing_creature.h" #include "thing_navigate.h" +#include "thing_factory.h" #include "creature_senses.h" #include "spdigger_stack.h" #include "power_hand.h" @@ -981,6 +982,63 @@ TngUpdateRet switch_object_on_destoyed_slab_to_new_owner(struct Thing *thing, Mo return TUFRet_Unchanged; } +static void update_thing_animation(struct Thing *thing) +{ + SYNCDBG(18,"Starting for %s",thing_model_name(thing)); + int i; + struct CreatureControl *cctrl; + if (thing->class_id == TCls_Creature) + { + cctrl = creature_control_get_from_thing(thing); + if (!creature_control_invalid(cctrl)) + cctrl->anim_time = thing->anim_time; + } + if ((thing->anim_speed != 0) && (thing->max_frames != 0)) + { + thing->anim_time += thing->anim_speed; + i = (thing->max_frames << 8); + if (i <= 0) i = 256; + while (thing->anim_time < 0) + { + thing->anim_time += i; + } + if (thing->anim_time > i-1) + { + if (thing->rendering_flags & TRF_AnimateOnce) + { + thing->anim_speed = 0; + thing->anim_time = i-1; + } else + { + thing->anim_time %= i; + } + } + thing->current_frame = thing->anim_time >> 8; + } + if (thing->transformation_speed != 0) + { + thing->sprite_size += thing->transformation_speed; + if (thing->sprite_size > thing->sprite_size_min) + { + if (thing->sprite_size >= thing->sprite_size_max) + { + thing->sprite_size = thing->sprite_size_max; + if ((thing->size_change & TSC_ChangeSizeContinuously) != 0) + thing->transformation_speed = -thing->transformation_speed; + else + thing->transformation_speed = 0; + } + } else + { + thing->sprite_size = thing->sprite_size_min; + if ((thing->size_change & TSC_ChangeSizeContinuously) != 0) + thing->transformation_speed = -thing->transformation_speed; + else + thing->transformation_speed = 0; + } + } +} + /** * Makes per game turn update of all things in given StructureList. * @param list List of things to process. @@ -1020,11 +1078,157 @@ void update_things_in_list(struct StructureList *list) SYNCDBG(19,"Finished, %d items",(int)k); } +static TngUpdateRet damage_creatures_with_physical_force(struct Thing *thing, ModTngFilterParam param) +{ + SYNCDBG(18,"Starting for %s index %d",thing_model_name(thing),(int)thing->index); + if (thing_is_picked_up(thing) || thing_is_dragged_or_pulled(thing)) + { + return TUFRet_Unchanged; + } + if (thing_is_creature(thing)) + { + apply_damage_to_thing_and_display_health(thing, param->secondary_number, param->primary_number); + if ((thing->health >= 0) && !creature_is_leaving_and_cannot_be_stopped(thing)) + { + if (((thing->alloc_flags & TAlF_IsControlled) == 0) && !creature_is_kept_in_custody(thing)) + { + if (get_creature_state_besides_interruptions(thing) != CrSt_CreatureEscapingDeath) + { + if (cleanup_current_thing_state(thing) && setup_move_out_of_cave_in(thing)) + thing->continue_state = CrSt_CreatureEscapingDeath; + } + } + return TUFRet_Modified; + } else + { + kill_creature(thing, INVALID_THING, param->primary_number, CrDed_NoEffects|CrDed_DiedInBattle); + return TUFRet_Deleted; + } + } + else if (thing_is_destructible_trap(thing) > 0) + { + apply_damage_to_thing(thing, param->secondary_number, param->primary_number); + return TUFRet_Modified; + } + return TUFRet_Unchanged; +} + +static TbBool valid_cave_in_position(PlayerNumber plyr_idx, MapSubtlCoord stl_x, MapSubtlCoord stl_y) +{ + struct Map *mapblk; + mapblk = get_map_block_at(stl_x,stl_y); + if ((mapblk->flags & SlbAtFlg_Blocking) != 0) + return false; + struct SlabMap *slb; + slb = get_slabmap_for_subtile(stl_x,stl_y); + return (plyr_idx == game.neutral_player_num) || (slabmap_owner(slb) == game.neutral_player_num) || (slabmap_owner(slb) == plyr_idx); +} + +static long update_cave_in(struct Thing *thing) +{ + thing->health--; + thing->rendering_flags |= TRF_Invisible; + if (thing->health < 1) + { + delete_thing_structure(thing, 0); + return 1; + } + + const struct PowerConfigStats *powerst; + powerst = get_power_model_stats(PwrK_CAVEIN); + struct Thing *efftng; + struct Coord3d pos; + PlayerNumber owner; + owner = thing->owner; + if ((get_gameturn() % 3) == 0) + { + int n; + n = GAME_RANDOM(AROUND_TILES_COUNT); + pos.x.val = thing->mappos.x.val + GAME_RANDOM(704) * around[n].delta_x; + pos.y.val = thing->mappos.y.val + GAME_RANDOM(704) * around[n].delta_y; + if (subtile_has_slab(coord_subtile(pos.x.val),coord_subtile(pos.y.val))) + { + pos.z.val = get_ceiling_height(&pos) - 128; + efftng = create_effect_element(&pos, TngEff_Flash, owner); + if (!thing_is_invalid(efftng)) { + efftng->health = powerst->duration; + } + } + } + + GameTurnDelta turns_between; + GameTurnDelta turns_alive; + turns_between = powerst->duration / 5; + turns_alive = get_gameturn() - thing->creation_turn; + if ((turns_alive != 0) && ((turns_between < 1) || (3 * turns_between / 4 == turns_alive % turns_between))) + { + pos.x.val = thing->mappos.x.val + THING_RANDOM(thing, 128); + pos.y.val = thing->mappos.y.val + THING_RANDOM(thing, 128); + pos.z.val = get_floor_height_at(&pos) + 384; + create_effect(&pos, TngEff_HarmlessGas4, owner); + } + + if ((turns_alive % game.conf.rules[owner].magic.turns_per_collapse_dngn_dmg) == 0) + { + pos.x.val = thing->mappos.x.val; + pos.y.val = thing->mappos.y.val; + pos.z.val = subtile_coord(1,0); + Thing_Modifier_Func do_cb; + struct CompoundTngFilterParam param; + param.plyr_idx = -1; + param.class_id = 0; + param.model_id = 0; + param.primary_number = thing->owner; + param.secondary_number = game.conf.rules[thing->owner].magic.collapse_dungeon_damage; + param.tertiary_pointer = 0; + do_cb = damage_creatures_with_physical_force; + do_to_things_with_param_around_map_block(&pos, do_cb, ¶m); + } + + if ((8 * powerst->duration / 10 >= thing->health) && (2 * powerst->duration / 10 <= thing->health)) + { + if ((powerst->duration < 10) || ((thing->health % (powerst->duration / 10)) == 0)) + { + int round_idx; + round_idx = THING_RANDOM(thing, AROUND_TILES_COUNT); + set_coords_to_slab_center(&pos, subtile_slab(thing->mappos.x.val + 3 * around[round_idx].delta_x), subtile_slab(thing->mappos.y.val + 3 * around[round_idx].delta_y)); + if (subtile_has_slab(coord_subtile(pos.x.val), coord_subtile(pos.y.val)) && valid_cave_in_position(thing->owner, coord_subtile(pos.x.val), coord_subtile(pos.y.val))) + { + struct Thing *ncavitng; + ncavitng = get_cavein_at_subtile_owned_by(coord_subtile(pos.x.val), coord_subtile(pos.y.val), -1); + if (thing_is_invalid(ncavitng)) + { + long dist; + struct Coord3d pos2; + pos2.x.val = subtile_coord(thing->cave_in.x,0); + pos2.y.val = subtile_coord(thing->cave_in.y,0); + pos2.z.val = subtile_coord(1,0); + dist = get_chessboard_distance(&pos, &pos2); + if (powerst->strength[thing->cave_in.model] >= coord_subtile(dist)) + { + ncavitng = create_thing(&pos, TCls_CaveIn, thing->cave_in.model, owner, -1); + if (!thing_is_invalid(ncavitng)) + { + thing->health += 5; + if (thing->health > 0) + { + ncavitng->cave_in.x = thing->cave_in.x; + ncavitng->cave_in.y = thing->cave_in.y; + } + } + } + } + } + } + } + return 1; +} + /** * Makes per game turn update of cave in things, using proper StructureList. * @return Returns amount of cave in things in list. */ -unsigned long update_cave_in_things(void) +static unsigned long update_cave_in_things(void) { unsigned long k = 0; const struct StructureList* slist = get_list_for_thing_class(TCls_CaveIn); diff --git a/src/thing_list.h b/src/thing_list.h index db2dc472df..df285dd132 100644 --- a/src/thing_list.h +++ b/src/thing_list.h @@ -271,7 +271,6 @@ TbBool creature_model_matches_model(ThingModel creatng_model, PlayerNumber plyr_ TbBool thing_matches_model(const struct Thing* thing, long crmodel); unsigned long update_things_sounds_in_list(struct StructureList *list); void stop_all_things_playing_samples(void); -unsigned long update_cave_in_things(void); unsigned long update_creatures_not_in_list(void); void update_things_in_list(struct StructureList *list); void init_player_start(struct PlayerInfo *player, TbBool keep_prev); diff --git a/src/thing_shots.c b/src/thing_shots.c index 63377c939b..9eaa6dd87a 100644 --- a/src/thing_shots.c +++ b/src/thing_shots.c @@ -30,6 +30,7 @@ #include "bflib_joyst.h" #include "creature_states.h" #include "creature_states_combt.h" +#include "creature_states_mood.h" #include "thing_data.h" #include "thing_factory.h" #include "thing_effects.h" @@ -52,6 +53,8 @@ #include "creature_groups.h" #include "game_legacy.h" #include "engine_lenses.h" +#include "room_util.h" +#include "player_instances.h" #include "keeperfx.hpp" #include "post_inc.h" @@ -805,6 +808,23 @@ long shot_kill_object(struct Thing *shotng, struct Thing *target) return 0; } +void give_shooter_drained_health(struct Thing *shooter, HitPoints health_delta) +{ + struct CreatureControl *cctrl; + HitPoints max_health; + HitPoints health; + if ( !thing_exists(shooter) ) + return; + cctrl = creature_control_get_from_thing(shooter); + max_health = cctrl->max_health; + health = shooter->health + health_delta; + if (health < max_health) { + shooter->health = health; + } else { + shooter->health = max_health; + } +} + static TbBool shot_hit_trap_at(struct Thing* shotng, struct Thing* target, struct Coord3d* pos) { struct ShotConfigStats* shotst = get_shot_model_stats(shotng->model); @@ -1613,6 +1633,71 @@ TngUpdateRet move_shot(struct Thing *shotng) return TUFRet_Modified; } +static TbBool lightning_is_close_to_player(struct PlayerInfo *player, struct Coord3d *pos) +{ + struct Camera *camera = get_player_active_camera(player); + if (camera == NULL) + return false; + return get_chessboard_distance(&camera->mappos, pos) < subtile_coord(45,0); +} + +static void affect_nearby_friends_with_alarm(struct Thing *traptng) +{ + SYNCDBG(8,"Starting"); + if (is_neutral_thing(traptng)) { + return; + } + struct Dungeon *dungeon; + unsigned long k; + int i; + dungeon = get_players_num_dungeon(traptng->owner); + k = 0; + i = dungeon->creatr_list_start; + while (i != 0) + { + struct CreatureControl *cctrl; + struct Thing *thing; + thing = thing_get(i); + TRACE_THING(thing); + cctrl = creature_control_get_from_thing(thing); + if (creature_control_invalid(cctrl)) + { + ERRORLOG("Jump to invalid creature detected"); + break; + } + i = cctrl->players_next_creature_idx; + // Thing list loop body + if (!thing_is_picked_up(thing) && !is_thing_directly_controlled(thing) && + !creature_is_being_unconscious(thing) && !creature_is_kept_in_custody(thing) && + (cctrl->combat_flags == 0) && !creature_is_dragging_something(thing) && !creature_is_dying(thing) && !creature_is_leaving_and_cannot_be_stopped(thing)) + { + struct CreatureStateConfig *stati; + stati = get_thing_state_info_num(get_creature_state_besides_interruptions(thing)); + if (stati->react_to_cta && (get_chessboard_distance(&traptng->mappos, &thing->mappos) < 4096)) + { + creature_mark_if_woken_up(thing); + if (external_set_thing_state(thing, CrSt_ArriveAtAlarm)) + { + if (setup_person_move_to_position(thing, traptng->mappos.x.stl.num, traptng->mappos.y.stl.num, 0)) + { + thing->continue_state = CrSt_ArriveAtAlarm; + cctrl->alarm_over_turn = get_gameturn() + 800; + cctrl->alarm_stl_x = traptng->mappos.x.stl.num; + cctrl->alarm_stl_y = traptng->mappos.y.stl.num; + } + } + } + } + // Thing list loop body ends + k++; + if (k > CREATURES_COUNT) + { + ERRORLOG("Infinite loop detected when sweeping creatures list"); + break; + } + } +} + TngUpdateRet update_shot(struct Thing *thing) { struct Thing *target; @@ -1736,11 +1821,6 @@ TngUpdateRet update_shot(struct Thing *thing) draw_god_lightning(thing); lightning_modify_palette(thing); break; - /**case ShUL_Vortex: - //Not implemented, due to limited amount of shots, replaced by Lizard - affect_nearby_stuff_with_vortex(thing); - break; - **/ case ShUL_Lizard: thing->move_angle_xy = (thing->move_angle_xy + DEGREES_20) & ANGLE_MASK; int skill = thing->shot_lizard.range; @@ -2019,6 +2099,163 @@ struct Thing* script_process_new_shot(ThingModel tngmodel, TbMapLocation locatio } return thing; } + +long apply_wallhug_force_to_boulder(struct Thing *thing) +{ + unsigned short angle; + long collide; + unsigned short new_angle; + struct Coord3d pos2; + struct Coord3d pos; + struct ShotConfigStats *shotst = get_shot_model_stats(thing->model); + short speed = shotst->speed; + pos.x.val = move_coord_with_angle_x(thing->mappos.x.val,speed,thing->move_angle_xy); + pos.y.val = move_coord_with_angle_y(thing->mappos.y.val,speed,thing->move_angle_xy); + pos.z.val = thing->mappos.z.val; + if ( (GAME_RANDOM(8) == 0) && (!thing->velocity.z.val ) ) + { + if ( thing_touching_floor(thing) ) + { + long top_cube = get_top_cube_at(thing->mappos.x.stl.num, thing->mappos.y.stl.num, NULL); + if ( ((top_cube & 0xFFFFFFFE) != 0x28) && (top_cube != 39) ) + { + thing->veloc_push_add.z.val += 48; + thing->state_flags |= TF1_PushAdd; + } + } + } + if ( thing_in_wall_at(thing, &pos) ) + { + long blocked_flags = get_thing_blocked_flags_at(thing, &pos); + if ( blocked_flags & SlbBloF_WalledX ) + { + angle = thing->move_angle_xy; + if ( (angle) && (angle <= ANGLE_SOUTH) ) + collide = process_boulder_collision(thing, &pos, 1, 0); + else + collide = process_boulder_collision(thing, &pos, -1, 0); + } + else if ( blocked_flags & SlbBloF_WalledY ) + { + angle = thing->move_angle_xy; + if ( (angle <= ANGLE_EAST) || (angle > ANGLE_WEST) ) + collide = process_boulder_collision(thing, &pos, 0, -1); + else + collide = process_boulder_collision(thing, &pos, 0, 1); + } + else + { + collide = 0; + } + if ( collide != 1 ) + { + if ( (thing->model != ShM_SolidBoulder) && (collide == 0) ) + { + thing->health -= game.conf.rules[thing->owner].gameplay.boulder_reduce_health_wall; + } + slide_thing_against_wall_at(thing, &pos, blocked_flags); + if ( blocked_flags & SlbBloF_WalledX ) + { + angle = thing->move_angle_xy; + if ( (angle) && ( (angle <= ANGLE_EAST) || (angle > ANGLE_WEST) ) ) + { + MapCoord y = thing->mappos.y.val; + pos2.x.val = thing->mappos.x.val; + pos2.z.val = 0; + pos2.y.val = y - STL_PER_SLB * speed; + pos2.z.val = get_thing_height_at(thing, &pos2); + new_angle = (thing_in_wall_at(thing, &pos2) < 1) ? ANGLE_NORTH : ANGLE_SOUTH; + } + else + { + pos2.x.val = thing->mappos.x.val; + pos2.z.val = 0; + pos2.y.val = thing->mappos.y.val + STL_PER_SLB * speed; + pos2.z.val = get_thing_height_at(thing, &pos2); + new_angle = (thing_in_wall_at(thing, &pos2) < 1) ? ANGLE_SOUTH : ANGLE_NORTH; + } + } + else if ( blocked_flags & SlbBloF_WalledY ) + { + angle = thing->move_angle_xy; + if ( (angle) && (angle <= ANGLE_SOUTH) ) + { + pos2.z.val = 0; + pos2.y.val = thing->mappos.y.val; + pos2.x.val = thing->mappos.x.val + STL_PER_SLB * speed; + pos2.z.val = get_thing_height_at(thing, &pos2); + new_angle = (thing_in_wall_at(thing, &pos2) < 1) ? ANGLE_EAST : ANGLE_WEST; + } + else + { + MapCoord x = thing->mappos.x.val; + pos2.z.val = 0; + pos2.y.val = thing->mappos.y.val; + pos2.x.val = x - STL_PER_SLB * speed; + pos2.z.val = get_thing_height_at(thing, &pos2); + new_angle = (thing_in_wall_at(thing, &pos2) < 1) ? ANGLE_WEST : ANGLE_EAST; + } + } + else + { + ERRORLOG("Cannot find boulder wall hug angle!"); + new_angle = 0; + } + thing->move_angle_xy = new_angle; + } + } + angle = thing->move_angle_xy; + thing->velocity.x.val = distance_with_angle_to_coord_x(shotst->speed,angle); + thing->velocity.y.val = distance_with_angle_to_coord_y(shotst->speed,angle); + return 0; +} + +long process_boulder_collision(struct Thing *boulder, struct Coord3d *pos, int direction_x, int direction_y) +{ + unsigned short boulder_radius = (boulder->clipbox_size_xy >> 1); + MapSubtlCoord pos_x = (pos->x.val + boulder_radius * direction_x) >> 8; + MapSubtlCoord pos_y = (pos->y.val + boulder_radius * direction_y) >> 8; + MapSubtlCoord stl_x = stl_slab_center_subtile(pos_x); + MapSubtlCoord stl_y = stl_slab_center_subtile(pos_y); + + struct Room *room = subtile_room_get(stl_x, stl_y); + if (room_exists(room)) + { + if (room->kind == RoK_GUARDPOST) // Collide with Guardposts + { + if (room->owner != game.neutral_player_num) + { + struct Dungeon *dungeon = get_dungeon(room->owner); + if (!dungeon_invalid(dungeon)) + { + dungeon->rooms_destroyed++; // add to player stats + } + } + delete_room_slab(subtile_slab(stl_x), subtile_slab(stl_y), 0); // destroy guardpost + for (int16_t k = 0; k < AROUND_TILES_COUNT; k++) + { + create_dirt_rubble_for_dug_block(stl_x + around[k].delta_x, stl_y + around[k].delta_y, 4, room->owner); + } + if (boulder->model != ShM_SolidBoulder) // Solid Boulder (shot20) takes no damage when destroying guardposts + { + boulder->health -= game.conf.rules[boulder->owner].gameplay.boulder_reduce_health_room; // decrease boulder health + } + return 1; // guardpost destroyed + } + } + else + { + if (subtile_has_door_thing_on(stl_x, stl_y)) // Collide with Doors + { + struct Thing *doortng = get_door_for_position(stl_x, stl_y); + if (collide_door_and_boulder(doortng, boulder) <= 0) + { + return 2; // door destroyed + } + } + } + return 0; // Default: No collision OR boulder destroyed on door +} /******************************************************************************/ #ifdef __cplusplus } diff --git a/src/thing_shots.h b/src/thing_shots.h index d06b665212..0a7fd35033 100644 --- a/src/thing_shots.h +++ b/src/thing_shots.h @@ -21,7 +21,6 @@ #include "bflib_basics.h" #include "globals.h" -#include "room_workshop.h" #include "thing_list.h" #ifdef __cplusplus @@ -118,6 +117,9 @@ void affect_nearby_enemy_creatures_with_wind(struct Thing *thing); struct Thing* script_process_new_shot(ThingModel tngmodel, TbMapLocation location, PlayerNumber owner, ThingIndex target, int hittype); void shot_kill_creature(struct Thing *shotng, struct Thing *creatng); + +long apply_wallhug_force_to_boulder(struct Thing *thing); +long process_boulder_collision(struct Thing *boulder, struct Coord3d *pos, int direction_x, int direction_y); /******************************************************************************/ #ifdef __cplusplus } diff --git a/src/thing_traps.c b/src/thing_traps.c index b582d6c827..bfa2c480c8 100644 --- a/src/thing_traps.c +++ b/src/thing_traps.c @@ -43,10 +43,10 @@ #include "engine_render.h" #include "gui_topmsg.h" -#include "keeperfx.hpp" #include "creature_senses.h" #include "cursor_tag.h" #include "player_instances.h" +#include "room_workshop.h" #include "post_inc.h" #ifdef __cplusplus diff --git a/src/timer.c b/src/timer.c new file mode 100644 index 0000000000..c77514d331 --- /dev/null +++ b/src/timer.c @@ -0,0 +1,62 @@ +/******************************************************************************/ +// Free implementation of Bullfrog's Dungeon Keeper strategy game. +/******************************************************************************/ +/** @file timer.c + * Timer support functions. + * @par Purpose: + * Definitions and functions to maintain timers. + * @par Comment: + * None. + * @par Copying and copyrights: + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +/******************************************************************************/ +#include "pre_inc.h" +#include "timer.h" + +#include + +#include "bflib_datetm.h" + +#include "post_inc.h" + +#ifdef __cplusplus +extern "C" { +#endif +/******************************************************************************/ +TbClockMSec timerstarttime = 0; +struct TimerTime Timer; +TbBool TimerGame = false; +TbBool TimerNoReset = false; +TbBool TimerFreeze = false; +/******************************************************************************/ + +void update_time(void) +{ + unsigned long time = ((unsigned long)LbTimerClock()) - timerstarttime; + Timer.MSeconds = time % 1000; + time /= 1000; + Timer.Seconds = time % 60; + time /= 60; + Timer.Minutes = time % 60; + Timer.Hours = time / 60; +} + +struct GameTime get_game_time(unsigned long turns, unsigned long fps) +{ + struct GameTime GameT; + unsigned long time = turns / fps; + GameT.Seconds = time % 60; + time /= 60; + GameT.Minutes = time % 60; + GameT.Hours = time / 60; + return GameT; +} + +/******************************************************************************/ +#ifdef __cplusplus +} +#endif diff --git a/src/timer.h b/src/timer.h new file mode 100644 index 0000000000..a9879bbeaf --- /dev/null +++ b/src/timer.h @@ -0,0 +1,52 @@ +/******************************************************************************/ +// Free implementation of Bullfrog's Dungeon Keeper strategy game. +/******************************************************************************/ +/** @file timer.h + * Header file for timer.c. + * Note that this file is a C header, while its code is CPP. + * @par Purpose: + * Timer functions. + * @par Comment: + * Just a header file - #defines, typedefs, function prototypes etc. + * @par Copying and copyrights: + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +/******************************************************************************/ + +#ifndef TIMER_H +#define TIMER_H + +#include "bflib_basics.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void update_time(void); +extern TbClockMSec timerstarttime; +struct TimerTime { + unsigned char Hours; + unsigned char Minutes; + unsigned char Seconds; + unsigned short MSeconds; +}; +extern struct TimerTime Timer; +extern TbBool TimerGame; +extern TbBool TimerNoReset; +extern TbBool TimerFreeze; +struct GameTime { + unsigned char Seconds; + unsigned char Minutes; + unsigned char Hours; +}; + +struct GameTime get_game_time(unsigned long turns, unsigned long fps); + +/******************************************************************************/ +#ifdef __cplusplus +} +#endif +#endif // TIMER_H diff --git a/tests/tst_fixes.cpp b/tests/tst_fixes.cpp deleted file mode 100644 index b21cfcbeb8..0000000000 --- a/tests/tst_fixes.cpp +++ /dev/null @@ -1,276 +0,0 @@ -// -// Created by Sim on 7/13/21. -// All this functions are located at main.cpp and should be moved out of there -// -#include -#include - -extern "C" { - -int test_variable; - -TbClockMSec timerstarttime = 0; -TbBool TimerGame = false; -TbBool TimerFreeze = false; -TbBool TimerNoReset = false; -struct TimerTime Timer; - -struct StartupParameters start_params; -TbClockMSec last_loop_time=0; - -void affect_nearby_enemy_creatures_with_wind(struct Thing *shotng) -{ -} - -void draw_lightning(const struct Coord3d *pos1, const struct Coord3d *pos2, long eeinterspace, long eemodel) -{ -} - -void affect_nearby_friends_with_alarm(struct Thing *traptng) -{ -} - -long apply_wallhug_force_to_boulder(struct Thing *thing) -{ - return 0; -} - -unsigned long lightning_is_close_to_player(struct PlayerInfo *player, struct Coord3d *pos) -{ - return 0; -} - -long update_cave_in(struct Thing *thing) -{ - return 0; -} - -void update_thing_animation(struct Thing *thing) -{ -} - -TbBool any_player_close_enough_to_see(const struct Coord3d *pos) -{ - return 1; -} - -long get_foot_creature_has_down(struct Thing *thing) -{ -} - -long packet_place_door(MapSubtlCoord stl_x, MapSubtlCoord stl_y, PlayerNumber plyr_idx, ThingModel tngmodel, unsigned char a5) -{ - return 0; -} - -void turn_off_query(PlayerNumber plyr_idx) -{ -} - -void clear_computer(void) -{ -} - -void PaletteSetPlayerPalette(struct PlayerInfo *player, unsigned char *pal) -{ -} - -void find_map_location_coords(long location, long *x, long *y, int plyr_idx, const char *func_name) -{ -} - -void clear_things_and_persons_data(void) -{ -} - -void draw_texture(long texture_x, long texture_y, long texture_width, long texture_height, long texture_block_index, long flags, long fade_level) -{ -} - -void give_shooter_drained_health(struct Thing *shooter, HitPoints health_delta) -{ -} - -void process_keeper_spell_effect(struct Thing *thing) -{ -} - -void draw_flame_breath(struct Coord3d *pos1, struct Coord3d *pos2, long delta_step, long num_per_step) -{ -} - -void update_time(void) -{ -} - -TbBool toggle_computer_player(PlayerNumber plyr_idx) -{ -} - -long ceiling_init(unsigned long a1, unsigned long a2) -{ - return 0; -} - -void set_mouse_light(struct PlayerInfo *player) -{ -} - -TbBool screen_to_map(struct Camera *camera, long screen_x, long screen_y, struct Coord3d *mappos) -{ - return 1; -} - -__attribute__((regparm(3))) struct GameTime get_game_time(unsigned long turns, unsigned long fps) -{ - struct GameTime GameT = {0}; - return GameT; -} - -void instant_instance_selected(CrInstance check_inst_id) -{ -} - -short complete_level(struct PlayerInfo *player) -{ - return 0; -} - -short lose_level(struct PlayerInfo *player) -{ - return 0; -} - -short resign_level(struct PlayerInfo *player) -{ - return 0; -} - -void clear_creature_pool(void) -{ -} - -TbBool can_thing_be_queried(struct Thing *thing, PlayerNumber plyr_idx) -{ - return 1; -} - -void set_player_cameras_position(struct PlayerInfo *player, long pos_x, long pos_y) -{ -} - -void clear_game_for_save(void) -{ -} - -TbBool set_gamma(char corrlvl, TbBool do_set) -{ - return 1; -} - -TbBool all_dungeons_destroyed(const struct PlayerInfo *win_player) -{ - return 0; -} - -void reinit_level_after_load(void) -{ -} - -void centre_engine_window(void) -{ -} - -void change_engine_window_relative_size(long w_delta, long h_delta) -{ -} - -void level_lost_go_first_person(PlayerNumber plyr_idx) -{ -} - -short winning_player_quitting(struct PlayerInfo *player, long *plyr_count) -{ - return 0; -} - -struct Thing *get_queryable_object_near(MapCoord pos_x, MapCoord pos_y, long plyr_idx) -{ - return NULL; -} - -void reset_creature_max_levels(void) -{ -} - -void set_quick_information(long msg_id, long target, long x, long y) -{ -} - -void process_objective(const char *msg_text, long target, long x, long y) -{ -} - -void set_general_objective(long msg_id, long target, long x, long y) -{ -} - -void set_general_information(long msg_id, long target, long x, long y) -{ -} - -short zoom_to_next_annoyed_creature(void) -{ - return 1; -} - -void toggle_hero_health_flowers(void) -{ -} - -void update_creatr_model_activities_list(void) -{ -} - -void reset_gui_based_on_player_mode(void) -{ -} - -extern TbPixel player_path_colours[]; -TbPixel get_player_path_colour(unsigned short owner) -{ - return player_path_colours[0]; -} - -void initialise_map_collides(void) -{ -} - -void clear_map(void) -{ -} - -void initialise_map_health(void) -{ -} - -void engine(struct PlayerInfo *player, struct Camera *cam) -{ -} - -void reset_hand_rules(void) -{ -} - -void init_keepers_map_exploration(void) -{ -} - -void clear_game(void) -{ -} - -TbBool force_player_num = 0; -short default_loc_player = 0; - -} //extern "C" From f83d98554a1b00c9012afaa508559d1011289bdc Mon Sep 17 00:00:00 2001 From: rainlizard <15337628+rainlizard@users.noreply.github.com> Date: Sun, 2 Aug 2026 23:11:27 +1000 Subject: [PATCH 18/28] Multiplayer: fix ctrl drag-click camera movement (#5078) --- src/front_input.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/front_input.c b/src/front_input.c index 2c9dfe6d5a..291b5326d6 100644 --- a/src/front_input.c +++ b/src/front_input.c @@ -277,13 +277,6 @@ static void update_gui_layer(void) { // Determine the current/correct GUI Layer to use at this moment - if (network_is_active()) // no one click on multiplayer. - { - //todo Make multiplayer work with 1-click - set_current_gui_layer(GuiLayer_Default); - return; - } - struct PlayerInfo* player = get_my_player(); if ( ((player->work_state == PSt_Sell) || (player->work_state == PSt_BuildRoom) || (player->render_roomspace.highlight_mode)) && (is_game_key_pressed(Gkey_BestRoomSpace, false, true) || is_game_key_pressed(Gkey_SquareRoomSpace, false, true)) ) From 2e7a02b285cbfdf59f333320cdd994690959f0e2 Mon Sep 17 00:00:00 2001 From: rainlizard <15337628+rainlizard@users.noreply.github.com> Date: Sun, 2 Aug 2026 23:12:10 +1000 Subject: [PATCH 19/28] Multiplayer: fixed being unable to chat in lobby (#5079) --- src/front_network.c | 3 --- src/frontend.cpp | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/front_network.c b/src/front_network.c index 2c55e262f1..dac3099fbf 100644 --- a/src/front_network.c +++ b/src/front_network.c @@ -31,8 +31,6 @@ #include "bflib_sprfnt.h" #include "bflib_datetm.h" #include "bflib_fileio.h" -#include "bflib_inputctrl.h" - #include "kjm_input.h" #include "gui_draw.h" #include "front_simple.h" @@ -713,7 +711,6 @@ void frontnet_start_setup(void) struct PlayerInfo* player = get_player(i); player->mp_message_text[0] = '\0'; } - LbStartTextInput(); } /******************************************************************************/ diff --git a/src/frontend.cpp b/src/frontend.cpp index a56433c5cf..60c4bdcdb3 100644 --- a/src/frontend.cpp +++ b/src/frontend.cpp @@ -2698,6 +2698,7 @@ FrontendMenuState frontend_setup_state(FrontendMenuState nstate) turn_on_menu(GMnu_FENET_START); if (frontend_menu_state != FeSt_MP_MAPPACK_SELECT) frontnet_start_setup(); + LbStartTextInput(); set_flag(game.system_flags, GSF_NetworkActive); set_pointer_graphic_menu(); break; From ea98b02e0cc1ee633689a7e066c81871d78684ba Mon Sep 17 00:00:00 2001 From: Loobinex Date: Sun, 2 Aug 2026 17:51:45 +0200 Subject: [PATCH 20/28] Just double frameskip each time. (#5076) --- src/front_input.c | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/front_input.c b/src/front_input.c index 291b5326d6..80def58a7a 100644 --- a/src/front_input.c +++ b/src/front_input.c @@ -523,14 +523,12 @@ static void clip_frame_skip(void) static void increaseFrameskip(void) { // Default no longer using frame_skip=1, which will not change the logic frame rate but the makes the game will less smooth. But it can still be passed in through parameters - int level = 16; - for (int i=0; i<10; i++) { - if (game.frame_skip < level) - break; - level <<= 1; - } - int adj = level/8; - game.frame_skip += adj; + + if (game.frame_skip <= 1) + game.frame_skip = 2; + else + game.frame_skip <<= 1; + clip_frame_skip(); char speed_txt[256] = "normal"; if (game.frame_skip > 0) @@ -541,14 +539,12 @@ static void increaseFrameskip(void) static void decreaseFrameskip(void) { // Defaul no longer using frame_skip=1, which will not change the logic frame rate but the makes the game will less smooth. But it can still be passed in through parameters - int level = 16; - for (int i=0; i<10; i++) { - if (game.frame_skip <= level) - break; - level <<= 1; - } - int adj = level/8; - game.frame_skip -= adj; + if (game.frame_skip <= 2) + game.frame_skip = 0; + else + game.frame_skip >>= 1; + + clip_frame_skip(); char speed_txt[256] = "normal"; if (game.frame_skip > 0) From 1034ec9f5e05c8226c7bd407df34447305b5bfba Mon Sep 17 00:00:00 2001 From: Loobinex Date: Sun, 2 Aug 2026 18:04:55 +0200 Subject: [PATCH 21/28] Added 3 new multiplayer maps to classic (#5081) Including a 2v1 --- multiplayer/classic/map00624.txt | 86 ++++++++++++++++++++++++++ multiplayer/classic/map00627.txt | 86 ++++++++++++++++++++++++++ multiplayer/classic/map01022.txt | 103 +++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 multiplayer/classic/map00624.txt create mode 100644 multiplayer/classic/map00627.txt create mode 100644 multiplayer/classic/map01022.txt diff --git a/multiplayer/classic/map00624.txt b/multiplayer/classic/map00624.txt new file mode 100644 index 0000000000..d873b3618d --- /dev/null +++ b/multiplayer/classic/map00624.txt @@ -0,0 +1,86 @@ +LEVEL_VERSION(1) +SET_GENERATE_SPEED(400) +START_MONEY(ALL_PLAYERS,2500) +MAX_CREATURES(ALL_PLAYERS,25) + +ADD_CREATURE_TO_POOL(FLY,20) +ADD_CREATURE_TO_POOL(BUG,20) +ADD_CREATURE_TO_POOL(DEMONSPAWN,20) +ADD_CREATURE_TO_POOL(TROLL,20) +ADD_CREATURE_TO_POOL(SPIDER,20) +ADD_CREATURE_TO_POOL(HELL_HOUND,20) +ADD_CREATURE_TO_POOL(TENTACLE,20) +ADD_CREATURE_TO_POOL(SORCEROR,20) +ADD_CREATURE_TO_POOL(ORC,20) +ADD_CREATURE_TO_POOL(BILE_DEMON,20) +ADD_CREATURE_TO_POOL(DRAGON,20) +ADD_CREATURE_TO_POOL(DARK_MISTRESS,20) +ADD_CREATURE_TO_POOL(DRUID,20) +ADD_CREATURE_TO_POOL(MAIDEN,20) + +CREATURE_AVAILABLE(ALL_PLAYERS,FLY,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,BUG,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DEMONSPAWN,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,TROLL,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,SPIDER,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,HELL_HOUND,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,TENTACLE,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,SORCEROR,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,ORC,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,BILE_DEMON,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DRAGON,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DARK_MISTRESS,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DRUID,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,MAIDEN,1,0) + +ROOM_AVAILABLE(ALL_PLAYERS,TREASURE,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,LAIR,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,GARDEN,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,TRAINING,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,RESEARCH,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,BRIDGE,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,GUARD_POST,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,WORKSHOP,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,PRISON,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,TORTURE,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,BARRACKS,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,TEMPLE,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,GRAVEYARD,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,SCAVENGER,1,0) + +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_HAND,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_SLAP,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_POSSESS,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_IMP,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_SIGHT,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_SPEED,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_OBEY,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CALL_TO_ARMS,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CONCEAL,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_HOLD_AUDIENCE,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CAVE_IN,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_HEAL_CREATURE,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_LIGHTNING,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_PROTECT,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CHICKEN,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_DISEASE,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_ARMAGEDDON,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_DESTROY_WALLS,1,0) + +TRAP_AVAILABLE(ALL_PLAYERS,ALARM,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,POISON_GAS,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,LIGHTNING,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,LAVA,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,BOULDER,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,WORD_OF_POWER,1,0) + +DOOR_AVAILABLE(ALL_PLAYERS,WOOD,1,0) +DOOR_AVAILABLE(ALL_PLAYERS,STEEL,1,0) +DOOR_AVAILABLE(ALL_PLAYERS,MAGIC,1,0) + +IF(PLAYER0,ALL_DUNGEONS_DESTROYED == 1) + WIN_GAME +ENDIF +IF(PLAYER1,ALL_DUNGEONS_DESTROYED == 1) + WIN_GAME +ENDIF \ No newline at end of file diff --git a/multiplayer/classic/map00627.txt b/multiplayer/classic/map00627.txt new file mode 100644 index 0000000000..d873b3618d --- /dev/null +++ b/multiplayer/classic/map00627.txt @@ -0,0 +1,86 @@ +LEVEL_VERSION(1) +SET_GENERATE_SPEED(400) +START_MONEY(ALL_PLAYERS,2500) +MAX_CREATURES(ALL_PLAYERS,25) + +ADD_CREATURE_TO_POOL(FLY,20) +ADD_CREATURE_TO_POOL(BUG,20) +ADD_CREATURE_TO_POOL(DEMONSPAWN,20) +ADD_CREATURE_TO_POOL(TROLL,20) +ADD_CREATURE_TO_POOL(SPIDER,20) +ADD_CREATURE_TO_POOL(HELL_HOUND,20) +ADD_CREATURE_TO_POOL(TENTACLE,20) +ADD_CREATURE_TO_POOL(SORCEROR,20) +ADD_CREATURE_TO_POOL(ORC,20) +ADD_CREATURE_TO_POOL(BILE_DEMON,20) +ADD_CREATURE_TO_POOL(DRAGON,20) +ADD_CREATURE_TO_POOL(DARK_MISTRESS,20) +ADD_CREATURE_TO_POOL(DRUID,20) +ADD_CREATURE_TO_POOL(MAIDEN,20) + +CREATURE_AVAILABLE(ALL_PLAYERS,FLY,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,BUG,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DEMONSPAWN,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,TROLL,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,SPIDER,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,HELL_HOUND,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,TENTACLE,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,SORCEROR,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,ORC,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,BILE_DEMON,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DRAGON,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DARK_MISTRESS,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DRUID,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,MAIDEN,1,0) + +ROOM_AVAILABLE(ALL_PLAYERS,TREASURE,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,LAIR,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,GARDEN,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,TRAINING,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,RESEARCH,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,BRIDGE,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,GUARD_POST,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,WORKSHOP,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,PRISON,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,TORTURE,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,BARRACKS,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,TEMPLE,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,GRAVEYARD,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,SCAVENGER,1,0) + +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_HAND,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_SLAP,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_POSSESS,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_IMP,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_SIGHT,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_SPEED,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_OBEY,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CALL_TO_ARMS,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CONCEAL,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_HOLD_AUDIENCE,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CAVE_IN,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_HEAL_CREATURE,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_LIGHTNING,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_PROTECT,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CHICKEN,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_DISEASE,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_ARMAGEDDON,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_DESTROY_WALLS,1,0) + +TRAP_AVAILABLE(ALL_PLAYERS,ALARM,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,POISON_GAS,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,LIGHTNING,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,LAVA,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,BOULDER,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,WORD_OF_POWER,1,0) + +DOOR_AVAILABLE(ALL_PLAYERS,WOOD,1,0) +DOOR_AVAILABLE(ALL_PLAYERS,STEEL,1,0) +DOOR_AVAILABLE(ALL_PLAYERS,MAGIC,1,0) + +IF(PLAYER0,ALL_DUNGEONS_DESTROYED == 1) + WIN_GAME +ENDIF +IF(PLAYER1,ALL_DUNGEONS_DESTROYED == 1) + WIN_GAME +ENDIF \ No newline at end of file diff --git a/multiplayer/classic/map01022.txt b/multiplayer/classic/map01022.txt new file mode 100644 index 0000000000..2fb0f1cd5e --- /dev/null +++ b/multiplayer/classic/map01022.txt @@ -0,0 +1,103 @@ +LEVEL_VERSION(1) +SET_GENERATE_SPEED(400) +START_MONEY(ALL_PLAYERS,7500) +MAX_CREATURES(ALL_PLAYERS,25) + +ADD_CREATURE_TO_POOL(FLY,20) +ADD_CREATURE_TO_POOL(BUG,20) +ADD_CREATURE_TO_POOL(DEMONSPAWN,20) +ADD_CREATURE_TO_POOL(TROLL,20) +ADD_CREATURE_TO_POOL(SPIDER,20) +ADD_CREATURE_TO_POOL(HELL_HOUND,20) +ADD_CREATURE_TO_POOL(TENTACLE,20) +ADD_CREATURE_TO_POOL(SORCEROR,20) +ADD_CREATURE_TO_POOL(ORC,20) +ADD_CREATURE_TO_POOL(BILE_DEMON,20) +ADD_CREATURE_TO_POOL(DRAGON,20) +ADD_CREATURE_TO_POOL(DARK_MISTRESS,20) +ADD_CREATURE_TO_POOL(DRUID,20) +ADD_CREATURE_TO_POOL(MAIDEN,20) + +CREATURE_AVAILABLE(ALL_PLAYERS,FLY,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,BUG,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DEMONSPAWN,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,TROLL,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,SPIDER,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,HELL_HOUND,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,TENTACLE,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,SORCEROR,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,ORC,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,BILE_DEMON,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DRAGON,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DARK_MISTRESS,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,DRUID,1,0) +CREATURE_AVAILABLE(ALL_PLAYERS,MAIDEN,1,0) + +ROOM_AVAILABLE(ALL_PLAYERS,TREASURE,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,LAIR,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,GARDEN,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,TRAINING,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,RESEARCH,1,1) +ROOM_AVAILABLE(ALL_PLAYERS,BRIDGE,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,GUARD_POST,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,WORKSHOP,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,PRISON,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,TORTURE,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,BARRACKS,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,TEMPLE,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,GRAVEYARD,1,0) +ROOM_AVAILABLE(ALL_PLAYERS,SCAVENGER,1,0) + +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_HAND,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_SLAP,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_POSSESS,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_IMP,1,1) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_SIGHT,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_SPEED,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_OBEY,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CALL_TO_ARMS,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CONCEAL,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_HOLD_AUDIENCE,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CAVE_IN,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_HEAL_CREATURE,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_LIGHTNING,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_PROTECT,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_CHICKEN,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_DISEASE,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_ARMAGEDDON,1,0) +MAGIC_AVAILABLE(ALL_PLAYERS,POWER_DESTROY_WALLS,1,0) + +TRAP_AVAILABLE(ALL_PLAYERS,ALARM,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,POISON_GAS,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,LIGHTNING,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,LAVA,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,BOULDER,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,WORD_OF_POWER,1,0) +TRAP_AVAILABLE(ALL_PLAYERS,SENTRY,1,0) + +DOOR_AVAILABLE(ALL_PLAYERS,WOOD,1,0) +DOOR_AVAILABLE(ALL_PLAYERS,BRACED,1,0) +DOOR_AVAILABLE(ALL_PLAYERS,STEEL,1,0) +DOOR_AVAILABLE(ALL_PLAYERS,MAGIC,1,0) +DOOR_AVAILABLE(ALL_PLAYERS,SECRET,1,0) + +IF(PLAYER1,GAME_TURN < 10) + IF(PLAYER1,VIEW_TYPE == 0) + ALLY_PLAYERS(PLAYER1,PLAYER2,1) + ENDIF +ENDIF +IF(PLAYER2,VIEW_TYPE == 0) + IF_ALLIED(PLAYER1,PLAYER2 == 1) + ALLY_PLAYERS(PLAYER1,PLAYER2,1) + ENDIF +ENDIF + +IF(PLAYER0,ALL_DUNGEONS_DESTROYED == 1) + WIN_GAME +ENDIF +IF(PLAYER1,ALL_DUNGEONS_DESTROYED == 1) + WIN_GAME +ENDIF +IF(PLAYER2,ALL_DUNGEONS_DESTROYED == 1) + WIN_GAME +ENDIF From 9f953aa6181588179a4e1e43887531be29b512a2 Mon Sep 17 00:00:00 2001 From: rainlizard <15337628+rainlizard@users.noreply.github.com> Date: Mon, 3 Aug 2026 07:02:57 +1000 Subject: [PATCH 22/28] Load default campaign when none is active (#5082) --- src/frontend.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/frontend.cpp b/src/frontend.cpp index 60c4bdcdb3..cb8344f5ad 100644 --- a/src/frontend.cpp +++ b/src/frontend.cpp @@ -2661,6 +2661,9 @@ FrontendMenuState frontend_setup_state(FrontendMenuState nstate) char* fname = prepare_file_path(FGrp_Save, continue_game_filename); LbFileDelete(fname); } + if (!is_campaign_loaded()) { + change_campaign(CampgnT_Default,""); + } turn_on_menu(GMnu_FEMAIN); last_mouse_x = GetMouseX(); last_mouse_y = GetMouseY(); From 7f2180e4f408859b6799a8e85e9f0a97de5cf22e Mon Sep 17 00:00:00 2001 From: Pieter Vandecandelaere Date: Tue, 4 Aug 2026 11:37:11 +0200 Subject: [PATCH 23/28] Fix linux boot crash (#5084) --- src/bflib_sprfnt.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/bflib_sprfnt.c b/src/bflib_sprfnt.c index 38a1fe2f10..d19c144c70 100644 --- a/src/bflib_sprfnt.c +++ b/src/bflib_sprfnt.c @@ -1488,7 +1488,6 @@ static short load_unifont_file(struct AsianFont * dbcfont) return 3; } LbFileClose(fhandle); - free(fpath); unsigned short *widths = (unsigned short *)malloc(UNIFONT_INDEX_COUNT * sizeof(*widths)); unsigned int *offsets = (unsigned int *)malloc(UNIFONT_INDEX_COUNT * sizeof(*offsets)); @@ -1504,9 +1503,8 @@ static short load_unifont_file(struct AsianFont * dbcfont) for (unsigned int i = 0; i < UNIFONT_INDEX_COUNT; ++i) { unsigned int pos = i * UNIFONT_INDEX_SIZE; - widths[i] = (unsigned short)index_buf[pos] | ((unsigned short)index_buf[pos + 1] << 8); - offsets[i] = (unsigned int)index_buf[pos + 2] | ((unsigned int)index_buf[pos + 3] << 8) - | ((unsigned int)index_buf[pos + 4] << 16) | ((unsigned int)index_buf[pos + 5] << 24); + widths[i] = (unsigned short)lword(&index_buf[pos]); + offsets[i] = (unsigned int)llong(&index_buf[pos + 2]); if (widths[i] != 0) { unsigned int row_bytes = (widths[i] + 7) >> 3; @@ -1531,6 +1529,7 @@ static short load_unifont_file(struct AsianFont * dbcfont) short load_unifont_files() { + SYNCDBG(7,"Starting"); for (int i = 0; i < sizeof(dbcfonts) / sizeof(dbcfonts[0]); ++i) { load_unifont_file(&dbcfonts[i]); From 9baef074c2c686d6f65f8ba2a2262fe42fa3bf13 Mon Sep 17 00:00:00 2001 From: Peter Lockett <1760289+cerwym@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:13:25 +0100 Subject: [PATCH 24/28] Enhance CMake build system with packaging and multi-platform support (#5083) --- .../workflows/build-alpha-patch-unsigned.yml | 14 +- .github/workflows/build-prototype.yml | 27 +- .../build-release-patch-unsigned.yml | 14 +- .gitignore | 4 +- CMakeLists.txt | 117 ++--- build-cmake.sh | 74 +++ build/cmake/modules/BuildTargets.cmake | 40 ++ build/cmake/modules/Dependencies.cmake | 177 ++++++++ build/cmake/modules/Helpers.cmake | 42 ++ build/cmake/modules/Packaging.cmake | 81 ++++ build/cmake/modules/Platforms.cmake | 17 + .../cmake/toolchains/mingw32.cmake | 4 +- deps/CMakeLists.txt | 127 ------ linux.mk | 425 ------------------ package.mk | 17 +- 15 files changed, 524 insertions(+), 656 deletions(-) create mode 100755 build-cmake.sh create mode 100644 build/cmake/modules/BuildTargets.cmake create mode 100644 build/cmake/modules/Dependencies.cmake create mode 100644 build/cmake/modules/Helpers.cmake create mode 100644 build/cmake/modules/Packaging.cmake create mode 100644 build/cmake/modules/Platforms.cmake rename mingw32.cmake => build/cmake/toolchains/mingw32.cmake (74%) delete mode 100644 deps/CMakeLists.txt delete mode 100644 linux.mk diff --git a/.github/workflows/build-alpha-patch-unsigned.yml b/.github/workflows/build-alpha-patch-unsigned.yml index 69cfdb4a28..fee20bd48a 100644 --- a/.github/workflows/build-alpha-patch-unsigned.yml +++ b/.github/workflows/build-alpha-patch-unsigned.yml @@ -28,7 +28,7 @@ jobs: run: | set -eux sudo apt update - sudo apt install -y build-essential g++-mingw-w64-i686 libpng16-16t64 7zip + sudo apt install -y build-essential g++-mingw-w64-i686 cmake ninja-build libpng16-16t64 7zip - name: Build gfx run: | @@ -42,9 +42,15 @@ jobs: set -eux BUILD_NUMBER=$(git rev-list --count HEAD) PACKAGE_SUFFIX=Alpha - make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX heavylog DEBUG=1 -k - make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX standard - make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX package + # Compile both binaries with CMake (MinGW-w64 i686 cross-compile). + cmake -S . -B out -G Ninja -DCMAKE_TOOLCHAIN_FILE=build/cmake/toolchains/mingw32.cmake \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DBUILD_NUMBER=$BUILD_NUMBER -DPACKAGE_SUFFIX=$PACKAGE_SUFFIX + cmake --build out --target keeperfx keeperfx_hvlog -j"$(nproc)" + # Assemble game data with the make pipeline (.dat data); the CMake build + # already provides the binaries and SDL runtime DLLs. Then archive with CPack. + make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX pkg-assemble + cmake --build out --target package echo "ZIP_NAME=$(basename -s .7z pkg/keeperfx*.7z)" >> $GITHUB_ENV rm pkg/keeperfx*.7z diff --git a/.github/workflows/build-prototype.yml b/.github/workflows/build-prototype.yml index ea3a54fea8..f11809931e 100644 --- a/.github/workflows/build-prototype.yml +++ b/.github/workflows/build-prototype.yml @@ -13,14 +13,14 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - + - uses: dkfans/setup-cpp@master - name: Update system run: | set -eux sudo apt update - sudo apt install -y build-essential g++-mingw-w64-i686 libpng16-16t64 7zip + sudo apt install -y build-essential g++-mingw-w64-i686 cmake ninja-build libpng16-16t64 7zip - name: Build run: | @@ -28,9 +28,15 @@ jobs: BUILD_NUMBER=$(git rev-list --count origin/master) GITHUB_SHA=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.head.sha) PACKAGE_SUFFIX=Prototype_$(git rev-parse --short=7 "$GITHUB_SHA") - make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX heavylog DEBUG=1 -k - make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX standard - make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX package + # Compile both binaries with CMake (MinGW-w64 i686 cross-compile). + cmake -S . -B out -G Ninja -DCMAKE_TOOLCHAIN_FILE=build/cmake/toolchains/mingw32.cmake \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DBUILD_NUMBER=$BUILD_NUMBER -DPACKAGE_SUFFIX=$PACKAGE_SUFFIX + cmake --build out --target keeperfx keeperfx_hvlog -j"$(nproc)" + # Assemble game data with the make pipeline (.dat data); the CMake build + # already provides the binaries and SDL runtime DLLs. Then archive with CPack. + make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX pkg-assemble + cmake --build out --target package echo "ZIP_NAME=$(basename -s .7z pkg/keeperfx*.7z)" >> $GITHUB_ENV rm pkg/keeperfx*.7z @@ -55,6 +61,8 @@ jobs: sudo apt update sudo apt install -y \ build-essential \ + cmake \ + ninja-build \ pkg-config \ curl \ libavcodec-dev \ @@ -71,6 +79,8 @@ jobs: libsdl2-net-dev \ libswresample-dev \ libminiupnpc-dev \ + libssl-dev \ + libzstd-dev \ zlib1g-dev - name: Build @@ -84,10 +94,13 @@ jobs: PACKAGE_SUFFIX=Prototype_$(git rev-parse --short=7 "$GITHUB_SHA") LINUX_ARTIFACT_NAME=keeperfx-linux_x86_64-${VER_MAJOR}_${VER_MINOR}_${VER_RELEASE}_${BUILD_NUMBER}_${PACKAGE_SUFFIX}-patch echo "LINUX_ARTIFACT_NAME=$LINUX_ARTIFACT_NAME" >> $GITHUB_ENV - make BUILD_NUMBER=$BUILD_NUMBER VER_SUFFIX=$PACKAGE_SUFFIX -f linux.mk + # Native Linux build with CMake (pkg-config deps + prebuilt lin64 static libs). + cmake -S . -B out -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DBUILD_NUMBER=$BUILD_NUMBER -DPACKAGE_SUFFIX=$PACKAGE_SUFFIX + cmake --build out --target keeperfx -j"$(nproc)" - name: Upload artifact uses: actions/upload-artifact@v4 with: name: ${{ env.LINUX_ARTIFACT_NAME }} - path: bin/keeperfx + path: out/keeperfx diff --git a/.github/workflows/build-release-patch-unsigned.yml b/.github/workflows/build-release-patch-unsigned.yml index 703a8ae948..b92ac61148 100644 --- a/.github/workflows/build-release-patch-unsigned.yml +++ b/.github/workflows/build-release-patch-unsigned.yml @@ -28,7 +28,7 @@ jobs: run: | set -eux sudo apt update - sudo apt install -y build-essential g++-mingw-w64-i686 libpng16-16t64 7zip + sudo apt install -y build-essential g++-mingw-w64-i686 cmake ninja-build libpng16-16t64 7zip - name: Build gfx run: | @@ -42,9 +42,15 @@ jobs: set -eux BUILD_NUMBER=$(git rev-list --count HEAD) PACKAGE_SUFFIX= - make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX heavylog DEBUG=1 -k - make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX standard - make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX package + # Compile both binaries with CMake (MinGW-w64 i686 cross-compile). + cmake -S . -B out -G Ninja -DCMAKE_TOOLCHAIN_FILE=build/cmake/toolchains/mingw32.cmake \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DBUILD_NUMBER=$BUILD_NUMBER -DPACKAGE_SUFFIX=$PACKAGE_SUFFIX + cmake --build out --target keeperfx keeperfx_hvlog -j"$(nproc)" + # Assemble game data with the make pipeline (.dat data); the CMake build + # already provides the binaries and SDL runtime DLLs. Then archive with CPack. + make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX pkg-assemble + cmake --build out --target package echo "ZIP_NAME=$(basename -s .7z pkg/keeperfx*.7z)" >> $GITHUB_ENV rm pkg/keeperfx*.7z diff --git a/.gitignore b/.gitignore index 05402f6ff6..bcb7ae8b3e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,9 @@ /config/fxdata/*.dat /config/fxdata/*.fon .vs/ -build*/ +# Build output directories (build/ itself now holds tracked CMake modules under +# build/cmake/, so ignore only alternative output dirs, not build/). +/build-*/ /out/* *.cache .vscode diff --git a/CMakeLists.txt b/CMakeLists.txt index 585294eb8a..5292b0bd30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,91 +10,38 @@ project(keeperfx C CXX) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 20) -# Get the abbreviated commit Id of the head. find_package(Git REQUIRED) execute_process(COMMAND "${GIT_EXECUTABLE}" describe --always OUTPUT_VARIABLE COMMIT_ID OUTPUT_STRIP_TRAILING_WHITESPACE) -set(VER_MAJOR 1) -set(VER_MINOR 2) -set(VER_RELEASE 0) -set(VER_BUILD 0) -set(VER_STRING "${VER_MAJOR}.${VER_MINOR}.${VER_RELEASE}.${VER_BUILD} ${PACKAGE_SUFFIX}") -set(PACKAGE_SUFFIX "") -set(GIT_REVISION "${COMMIT_ID}") - -# CMAKE_BINARY_DIR is defined in CMakePresets.json. -set(KEEPERFX_VER_DEFS_H_IN ${CMAKE_SOURCE_DIR}/ver_defs.h.in) -set(KEEPERFX_VER_DEFS_H_OUT ${CMAKE_SOURCE_DIR}/ver_defs.h) -configure_file(${KEEPERFX_VER_DEFS_H_IN} ${KEEPERFX_VER_DEFS_H_OUT}) - -find_package(SDL2 CONFIG REQUIRED) -find_package(SDL2_image CONFIG REQUIRED) -find_package(SDL2_mixer CONFIG REQUIRED) -find_package(SDL2_net CONFIG REQUIRED) - -# Global definitions. -add_compile_definitions(_CRT_NONSTDC_NO_WARNINGS _CRT_SECURE_NO_WARNINGS) - -file(GLOB_RECURSE KEEPERFX_SOURCES_C "src/*.c") -file(GLOB_RECURSE KEEPERFX_SOURCES_CXX "src/*.cpp") - -# Global definitions for all targets. -add_compile_definitions("DEBUG=$,1,0>") -add_compile_definitions("SPNG_STATIC=1") - -# Add two executable targets: keeperfx and keeperfx_hvlog. -add_executable(keeperfx ${KEEPERFX_SOURCES_C} ${KEEPERFX_SOURCES_CXX}) - -target_compile_definitions(keeperfx PUBLIC BFDEBUG_LEVEL=0) -target_sources(keeperfx PRIVATE "res/keeperfx_stdres.rc") - -add_executable(keeperfx_hvlog ${KEEPERFX_SOURCES_C} ${KEEPERFX_SOURCES_CXX}) - -target_compile_definitions(keeperfx_hvlog PUBLIC BFDEBUG_LEVEL=10) -target_sources(keeperfx_hvlog PRIVATE "res/keeperfx_stdres.rc") - -message(STATUS "We are using ${CMAKE_CXX_COMPILER_ID}") - -# The default bfd linker in MinGW is extremely slow. LLVM linker (LLD) is much much faster. -set_property(TARGET keeperfx PROPERTY LINKER_TYPE LLD) -set_property(TARGET keeperfx_hvlog PROPERTY LINKER_TYPE LLD) - -set(WARNFLAGS -Wall -W -Wshadow -Wno-sign-compare -Wno-unused-parameter -Wno-strict-aliasing -Wno-unknown-pragmas -Werror) -set(GNU_COMPILER_FLAG -march=x86-64 -fno-omit-frame-pointer -fmessage-length=0) -set(GNU_LINK_FLAG -mwindows -Wl,--enable-auto-import) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wimplicit") -target_compile_options(keeperfx PRIVATE ${WARNFLAGS} ${GNU_COMPILER_FLAG}) -target_compile_options(keeperfx_hvlog PRIVATE ${WARNFLAGS} ${GNU_COMPILER_FLAG}) -target_link_options(keeperfx PRIVATE ${GNU_LINK_FLAG} -Wl,-Map,keeperfx.map) -target_link_options(keeperfx_hvlog PRIVATE ${GNU_LINK_FLAG} -Wl,-Map,keeperfx_hvlog.map) -target_link_libraries (keeperfx PUBLIC -static stdc++ winpthread -dynamic) -target_link_libraries (keeperfx_hvlog PUBLIC -static stdc++ winpthread -dynamic) - -# System libraries. -target_link_libraries(keeperfx PRIVATE imagehlp dbghelp) -target_link_libraries(keeperfx_hvlog PRIVATE imagehlp dbghelp) - -# Go into submodules. -add_subdirectory(deps) -add_subdirectory(tools) - -# External libraries. -target_link_libraries(keeperfx - PRIVATE - $,SDL2::SDL2,SDL2::SDL2-static>) -target_link_libraries(keeperfx - PRIVATE $,SDL2_mixer::SDL2_mixer,SDL2_mixer::SDL2_mixer-static>) -target_link_libraries(keeperfx - PRIVATE $,SDL2_net::SDL2_net,SDL2_net::SDL2_net-static>) -target_link_libraries(keeperfx - PRIVATE $,SDL2_image::SDL2_image,SDL2_image::SDL2_image-static>) - -target_link_libraries(keeperfx_hvlog - PRIVATE - $,SDL2::SDL2,SDL2::SDL2-static>) -target_link_libraries(keeperfx_hvlog - PRIVATE $,SDL2_mixer::SDL2_mixer,SDL2_mixer::SDL2_mixer-static>) -target_link_libraries(keeperfx_hvlog - PRIVATE $,SDL2_net::SDL2_net,SDL2_net::SDL2_net-static>) -target_link_libraries(keeperfx_hvlog - PRIVATE $,SDL2_image::SDL2_image,SDL2_image::SDL2_image-static>) +file(STRINGS "${CMAKE_SOURCE_DIR}/version.mk" _kfx_version_lines) +foreach(_line IN LISTS _kfx_version_lines) + if(_line MATCHES "^(VER_MAJOR|VER_MINOR|VER_RELEASE|VER_BUILD)=([0-9]+)") + set(${CMAKE_MATCH_1} "${CMAKE_MATCH_2}") + endif() +endforeach() +foreach(_v VER_MAJOR VER_MINOR VER_RELEASE VER_BUILD) + if(NOT DEFINED ${_v}) + message(FATAL_ERROR "version.mk is missing ${_v}") + endif() +endforeach() +# Override via -DBUILD_NUMBER= -DPACKAGE_SUFFIX=; used for the CPack name. +set(BUILD_NUMBER "${VER_BUILD}" CACHE STRING "Monotonic build number") +set(PACKAGE_SUFFIX "" CACHE STRING "Distribution suffix (e.g. Alpha, Release)") +set(VER_STRING "${VER_MAJOR}.${VER_MINOR}.${VER_RELEASE}.${BUILD_NUMBER} ${PACKAGE_SUFFIX}") +set(GIT_REVISION "${COMMIT_ID}") + +# src/version.h includes "ver_defs.h" relative to src/ (matches GENSRC in Makefile). +configure_file(${CMAKE_SOURCE_DIR}/ver_defs.h.in ${CMAKE_SOURCE_DIR}/src/ver_defs.h) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/build/cmake/modules) +include(Helpers) +include(Platforms) +include(Dependencies) +include(BuildTargets) + +# Windows icon resource is generated by png2ico in tools/. +if(WIN32) + add_subdirectory(tools) +endif() + +include(Packaging) diff --git a/build-cmake.sh b/build-cmake.sh new file mode 100755 index 0000000000..e86aa02b87 --- /dev/null +++ b/build-cmake.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# +# Build keeperfx with CMake, at parity with the hand Makefiles. +# +# KFX_OS=windows (default) 32-bit MinGW-w64 (i686) Windows binary, using the +# same compiler/flags/prebuilt deps as `make`. +# KFX_OS=linux native x86_64 Linux ELF (pkg-config deps + prebuilt lin64 static libs) +# (system/pkg-config deps + prebuilt lin64 static libs). +# +# Third-party deps are downloaded automatically on first run (into deps/); the +# Windows build needs no vcpkg, the Linux build needs the usual -dev packages. +# +# Usage: +# ./build-cmake.sh # Windows keeperfx (standard log) +# KFX_OS=linux ./build-cmake.sh # Linux keeperfx +# ./build-cmake.sh keeperfx_hvlog # heavy-log variant +# USE_DOCKER=1 ./build-cmake.sh # build in an Ubuntu 24.04 container +# BUILD_DIR=out ./build-cmake.sh # override the build directory +# +# Requirements (native): +# windows: a MinGW-w64 i686 toolchain (Ubuntu: g++-mingw-w64-i686), cmake, ninja +# linux: gcc/g++, cmake, ninja, pkg-config + the SDL2/ffmpeg/openal/luajit/ +# spng/minizip/zlib/miniupnpc/natpmp/openssl/zstd -dev packages +# +set -euo pipefail + +TARGET="${1:-keeperfx}" +KFX_OS="${KFX_OS:-windows}" +# Output goes to out/ (git-ignored); build/ holds tracked CMake modules. +BUILD_DIR="${BUILD_DIR:-out}" + +# Run the whole thing inside a container that mirrors upstream CI (Ubuntu 24.04). +if [ "${USE_DOCKER:-0}" = "1" ]; then + if [ "$KFX_OS" = "linux" ]; then + PKGS="build-essential pkg-config cmake ninja-build git curl ca-certificates \ + libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-net-dev \ + libavcodec-dev libavformat-dev libavutil-dev libswresample-dev \ + libopenal-dev libluajit-5.1-dev libspng-dev libminizip-dev zlib1g-dev \ + libminiupnpc-dev libnatpmp-dev libssl-dev libzstd-dev" + else + PKGS="g++-mingw-w64-i686 cmake ninja-build git curl ca-certificates" + fi + exec docker run --rm -v "$PWD:/src" -w /src ubuntu:24.04 bash -c " + set -eux + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq $PKGS + git config --global --add safe.directory /src || true + KFX_OS='$KFX_OS' BUILD_DIR='$BUILD_DIR' bash build-cmake.sh '$TARGET' + " +fi + +if [ "$KFX_OS" = "linux" ]; then + cmake -S . -B "$BUILD_DIR" -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo +else + if ! command -v i686-w64-mingw32-gcc >/dev/null 2>&1; then + echo "error: i686-w64-mingw32-gcc not found on PATH." >&2 + echo " Install a MinGW-w64 i686 toolchain (Ubuntu: 'sudo apt install g++-mingw-w64-i686')," >&2 + echo " or re-run with USE_DOCKER=1, or KFX_OS=linux for a native Linux build." >&2 + exit 1 + fi + cmake -S . -B "$BUILD_DIR" -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=build/cmake/toolchains/mingw32.cmake \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo +fi + +cmake --build "$BUILD_DIR" --target "$TARGET" -j"$(nproc 2>/dev/null || echo 4)" + +echo +if [ "$KFX_OS" = "linux" ]; then + echo "Built: $BUILD_DIR/$TARGET" +else + echo "Built: $BUILD_DIR/$TARGET.exe" +fi diff --git a/build/cmake/modules/BuildTargets.cmake b/build/cmake/modules/BuildTargets.cmake new file mode 100644 index 0000000000..657ae923f5 --- /dev/null +++ b/build/cmake/modules/BuildTargets.cmake @@ -0,0 +1,40 @@ +# BuildTargets.cmake - source collection, executables, per-target flags + linking. + +file(GLOB_RECURSE KEEPERFX_SOURCES_C CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/src/*.c") +file(GLOB_RECURSE KEEPERFX_SOURCES_CXX CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/src/*.cpp") + +# Functional-test harness is off by default (FTEST_DEBUG defaults to 0). +list(FILTER KEEPERFX_SOURCES_C EXCLUDE REGEX "/src/ftests/") +list(FILTER KEEPERFX_SOURCES_CXX EXCLUDE REGEX "/src/ftests/") + +# Desktop platform filtering (matches the hand Makefiles). +if(WIN32) + list(FILTER KEEPERFX_SOURCES_CXX EXCLUDE REGEX "/src/linux\\.cpp$") +elseif(UNIX AND NOT APPLE) + list(FILTER KEEPERFX_SOURCES_CXX EXCLUDE REGEX "/src/(cdrom|steam_api|windows)\\.cpp$") +endif() + +add_executable(keeperfx ${KEEPERFX_SOURCES_C} ${KEEPERFX_SOURCES_CXX}) +add_executable(keeperfx_hvlog ${KEEPERFX_SOURCES_C} ${KEEPERFX_SOURCES_CXX}) +target_compile_definitions(keeperfx PUBLIC BFDEBUG_LEVEL=0) +target_compile_definitions(keeperfx_hvlog PUBLIC BFDEBUG_LEVEL=10) + +set(KFX_TARGETS keeperfx keeperfx_hvlog) + +if(WIN32) + foreach(_t IN LISTS KFX_TARGETS) + target_sources(${_t} PRIVATE "${CMAKE_SOURCE_DIR}/res/keeperfx_stdres.rc") + # bfd is slow; prefer LLD when available (LINKER_TYPE needs CMake >= 3.29, + # harmlessly ignored on older CMake, which uses the default linker). + set_property(TARGET ${_t} PROPERTY LINKER_TYPE LLD) + endforeach() +endif() + +foreach(_t IN LISTS KFX_TARGETS) + apply_keeperfx_warnings(${_t}) + apply_keeperfx_link_flags(${_t}) + kfx_link_dependencies(${_t}) + apply_windows_system_libs(${_t}) +endforeach() + +kfx_status("BUILD" "${CMAKE_CXX_COMPILER_ID} -> keeperfx, keeperfx_hvlog") diff --git a/build/cmake/modules/Dependencies.cmake b/build/cmake/modules/Dependencies.cmake new file mode 100644 index 0000000000..81c8c78467 --- /dev/null +++ b/build/cmake/modules/Dependencies.cmake @@ -0,0 +1,177 @@ +# Dependencies.cmake - third-party libraries, per platform. Mirrors the hand +# Makefiles: Windows/MinGW uses prebuilt kfx-deps mingw32 static libs + SDL2 dev +# tarballs (../Makefile); Linux uses system/pkg-config libs + a few prebuilt +# lin64 static libs (../linux.mk). Dep URLs/tags track those Makefiles. +# +# Targets are defined here; kfx_link_dependencies() links them onto the +# game executables (called from BuildTargets, once they exist). + +set(KFX_DEPS_BASE "https://github.com/dkfans/kfx-deps/releases/download") + +# Downloaded deps live under the BUILD dir, not the source tree, so Windows and +# Linux builds in one checkout get their own deps and never collide. +set(D "${CMAKE_BINARY_DIR}/deps") +set(KFX_CENTITOML_SRC "${CMAKE_SOURCE_DIR}/deps/centitoml") + +# kfx_fetch( ): download + extract into /deps// once. +function(kfx_fetch dir url) + set(_tgz "${D}/${dir}.tar.gz") + set(_dest "${D}/${dir}") + if(NOT EXISTS "${_dest}") + file(MAKE_DIRECTORY "${_dest}") + if(NOT EXISTS "${_tgz}") + message(STATUS "Downloading dep: ${dir} <- ${url}") + file(DOWNLOAD "${url}" "${_tgz}" SHOW_PROGRESS STATUS _st) + list(GET _st 0 _code) + if(NOT _code EQUAL 0) + message(FATAL_ERROR "Failed to download ${url}: ${_st}") + endif() + endif() + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xzf "${_tgz}" + WORKING_DIRECTORY "${_dest}" + RESULT_VARIABLE _rc) + if(NOT _rc EQUAL 0) + message(FATAL_ERROR "Failed to extract ${_tgz}") + endif() + endif() +endfunction() + +macro(kfx_imported name lib incdir) + add_library(${name} STATIC IMPORTED GLOBAL) + set_target_properties(${name} PROPERTIES + IMPORTED_LOCATION "${lib}" + INTERFACE_INCLUDE_DIRECTORIES "${incdir}") +endmacro() + +if(WIN32) + # --- SDL2 (prebuilt MinGW dev tarballs; each wraps -/i686-w64-mingw32/*) + set(SDL2_VER 2.30.7) + set(SDL2_NET_VER 2.2.0) + set(SDL2_MIX_VER 2.8.0) + set(SDL2_IMG_VER 2.8.2) + + kfx_fetch(sdl2 "${KFX_DEPS_BASE}/20260608/SDL2-devel-${SDL2_VER}-mingw.tar.gz") + kfx_fetch(sdl2_net "https://github.com/libsdl-org/SDL_net/releases/download/release-${SDL2_NET_VER}/SDL2_net-devel-${SDL2_NET_VER}-mingw.tar.gz") + kfx_fetch(sdl2_mixer "https://github.com/libsdl-org/SDL_mixer/releases/download/release-${SDL2_MIX_VER}/SDL2_mixer-devel-${SDL2_MIX_VER}-mingw.tar.gz") + kfx_fetch(sdl2_image "https://github.com/libsdl-org/SDL_image/releases/download/release-${SDL2_IMG_VER}/SDL2_image-devel-${SDL2_IMG_VER}-mingw.tar.gz") + + set(SDL2_PREFIX "${D}/sdl2/SDL2-${SDL2_VER}/i686-w64-mingw32") + set(SDL2_NET_PREFIX "${D}/sdl2_net/SDL2_net-${SDL2_NET_VER}/i686-w64-mingw32") + set(SDL2_MIX_PREFIX "${D}/sdl2_mixer/SDL2_mixer-${SDL2_MIX_VER}/i686-w64-mingw32") + set(SDL2_IMG_PREFIX "${D}/sdl2_image/SDL2_image-${SDL2_IMG_VER}/i686-w64-mingw32") + + add_library(kfx_sdl2 INTERFACE) + # Code uses both (include/SDL2) and (parent include/). + target_include_directories(kfx_sdl2 INTERFACE + "${SDL2_PREFIX}/include" "${SDL2_PREFIX}/include/SDL2" + "${SDL2_NET_PREFIX}/include" + "${SDL2_MIX_PREFIX}/include" + "${SDL2_IMG_PREFIX}/include") + target_link_libraries(kfx_sdl2 INTERFACE + "${SDL2_PREFIX}/lib/libSDL2.dll.a" + "${SDL2_MIX_PREFIX}/lib/libSDL2_mixer.dll.a" + "${SDL2_NET_PREFIX}/lib/libSDL2_net.dll.a" + "${SDL2_IMG_PREFIX}/lib/libSDL2_image.dll.a") + + # SDL2 links dynamically, so ship its runtime DLLs (CPack picks these up). + install(FILES + "${SDL2_PREFIX}/bin/SDL2.dll" + "${SDL2_MIX_PREFIX}/bin/SDL2_mixer.dll" + "${SDL2_NET_PREFIX}/bin/SDL2_net.dll" + "${SDL2_IMG_PREFIX}/bin/SDL2_image.dll" + DESTINATION .) + + # --- Static libs from kfx-deps (mirror ../Makefile URLs/tags) + kfx_fetch(enet6 "${KFX_DEPS_BASE}/20260212/enet6-mingw32.tar.gz") + kfx_fetch(zlib "${KFX_DEPS_BASE}/initial/zlib-mingw32.tar.gz") + kfx_fetch(spng "${KFX_DEPS_BASE}/initial/spng-mingw32.tar.gz") + kfx_fetch(astronomy "${KFX_DEPS_BASE}/astronomy_fix/astronomy-mingw32.tar.gz") + kfx_fetch(centijson "${KFX_DEPS_BASE}/initial/centijson-mingw32.tar.gz") + kfx_fetch(ffmpeg "${KFX_DEPS_BASE}/initial/ffmpeg-mingw32.tar.gz") + kfx_fetch(openal "${KFX_DEPS_BASE}/2024-11-14/openal-mingw32.tar.gz") + kfx_fetch(luajit "${KFX_DEPS_BASE}/20250418/luajit-mingw32.tar.gz") + kfx_fetch(miniupnpc "${KFX_DEPS_BASE}/20260102/miniupnpc-mingw32.tar.gz") + kfx_fetch(libnatpmp "${KFX_DEPS_BASE}/20260102/libnatpmp-mingw32.tar.gz") + kfx_fetch(libcurl "${KFX_DEPS_BASE}/20260310/libcurl-mingw32.tar.gz") + + kfx_imported(enet6_static "${D}/enet6/lib/libenet6.a" "${D}/enet6/include") + target_link_libraries(enet6_static INTERFACE ws2_32 winmm) + kfx_imported(spng_static "${D}/spng/libspng.a" "${D}/spng/include") + kfx_imported(centijson_static "${D}/centijson/libjson.a" "${D}/centijson/include") + kfx_imported(astronomy_static "${D}/astronomy/libastronomy.a" "${D}/astronomy/include") + kfx_imported(zlib_static "${D}/zlib/libz.a" "${D}/zlib/include") + kfx_imported(minizip_static "${D}/zlib/libminizip.a" "${D}/zlib/include") + target_link_libraries(minizip_static INTERFACE zlib_static) + kfx_imported(openal_static "${D}/openal/libOpenAL32.a" "${D}/openal/include") + target_link_libraries(openal_static INTERFACE winmm ole32 uuid) + kfx_imported(luajit_static "${D}/luajit/lib/libluajit.a" "${D}/luajit/include") + kfx_imported(miniupnpc_static "${D}/miniupnpc/libminiupnpc.a" "${D}/miniupnpc/include") + target_link_libraries(miniupnpc_static INTERFACE ws2_32 iphlpapi) + kfx_imported(natpmp_static "${D}/libnatpmp/libnatpmp.a" "${D}/libnatpmp/include") + target_link_libraries(natpmp_static INTERFACE ws2_32 iphlpapi) + kfx_imported(curl_static "${D}/libcurl/lib/libcurl.a" "${D}/libcurl/include") + target_compile_definitions(curl_static INTERFACE CURL_STATICLIB) + target_link_libraries(curl_static INTERFACE zlib_static wldap32 crypt32 secur32 bcrypt ws2_32 iphlpapi) + + kfx_imported(libavcodec_static "${D}/ffmpeg/libavcodec/libavcodec.a" "${D}/ffmpeg") + kfx_imported(libavformat_static "${D}/ffmpeg/libavformat/libavformat.a" "${D}/ffmpeg") + kfx_imported(libavutil_static "${D}/ffmpeg/libavutil/libavutil.a" "${D}/ffmpeg") + kfx_imported(libswresample_static "${D}/ffmpeg/libswresample/libswresample.a" "${D}/ffmpeg") + + add_library(centitoml OBJECT "${KFX_CENTITOML_SRC}/toml_api.c") + target_link_libraries(centitoml PUBLIC centijson_static) + target_include_directories(centitoml INTERFACE "${KFX_CENTITOML_SRC}") + +else() + find_package(PkgConfig REQUIRED) + pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2) + pkg_check_modules(SDL2_mixer REQUIRED IMPORTED_TARGET SDL2_mixer) + pkg_check_modules(SDL2_net REQUIRED IMPORTED_TARGET SDL2_net) + pkg_check_modules(SDL2_image REQUIRED IMPORTED_TARGET SDL2_image) + pkg_check_modules(FFMPEG REQUIRED IMPORTED_TARGET libavformat libavcodec libswresample libavutil) + pkg_check_modules(OPENAL REQUIRED IMPORTED_TARGET openal) + pkg_check_modules(LUAJIT REQUIRED IMPORTED_TARGET luajit) + pkg_check_modules(SPNG REQUIRED IMPORTED_TARGET spng) + pkg_check_modules(MINIZIP REQUIRED IMPORTED_TARGET minizip) + pkg_check_modules(ZLIB REQUIRED IMPORTED_TARGET zlib) + + # Not reliably packaged; use the prebuilt lin64 static libs (as linux.mk does). + kfx_fetch(astronomy "${KFX_DEPS_BASE}/20250418/astronomy-lin64.tar.gz") + kfx_fetch(centijson "${KFX_DEPS_BASE}/20250418/centijson-lin64.tar.gz") + kfx_fetch(enet6 "${KFX_DEPS_BASE}/20260213/enet6-lin64.tar.gz") + kfx_fetch(libcurl "${KFX_DEPS_BASE}/20260310/libcurl-lin64.tar.gz") + + kfx_imported(astronomy_static "${D}/astronomy/libastronomy.a" "${D}/astronomy/include") + kfx_imported(centijson_static "${D}/centijson/libjson.a" "${D}/centijson/include") + kfx_imported(enet6_static "${D}/enet6/libenet6.a" "${D}/enet6/include") + kfx_imported(curl_static "${D}/libcurl/lib/libcurl.a" "${D}/libcurl/include") + target_link_libraries(curl_static INTERFACE ssl crypto zstd) + + add_library(centitoml OBJECT "${KFX_CENTITOML_SRC}/toml_api.c") + target_link_libraries(centitoml PUBLIC centijson_static) + target_include_directories(centitoml INTERFACE "${KFX_CENTITOML_SRC}") +endif() + +# Link every dependency onto TARGET. +function(kfx_link_dependencies TARGET) + if(WIN32) + # Static archives have circular refs (curl<->zlib, ffmpeg internals), so + # link them in a group (RESCAN == --start-group/--end-group). + set(_static + libavformat_static libavcodec_static libswresample_static libavutil_static + openal_static astronomy_static enet6_static miniupnpc_static natpmp_static + curl_static spng_static centijson_static minizip_static zlib_static + luajit_static) + target_link_libraries(${TARGET} PRIVATE + kfx_sdl2 "$" centitoml) + else() + target_link_libraries(${TARGET} PRIVATE + PkgConfig::SDL2 PkgConfig::SDL2_mixer PkgConfig::SDL2_net PkgConfig::SDL2_image + PkgConfig::FFMPEG PkgConfig::OPENAL PkgConfig::LUAJIT + PkgConfig::SPNG PkgConfig::MINIZIP PkgConfig::ZLIB + astronomy_static centijson_static enet6_static curl_static + centitoml + miniupnpc natpmp dl) + endif() +endfunction() diff --git a/build/cmake/modules/Helpers.cmake b/build/cmake/modules/Helpers.cmake new file mode 100644 index 0000000000..1a84f14cc2 --- /dev/null +++ b/build/cmake/modules/Helpers.cmake @@ -0,0 +1,42 @@ +# Helpers.cmake - shared functions for the desktop (Windows/MinGW + Linux) build. + +function(kfx_status PREFIX MESSAGE) + message(STATUS "[${PREFIX}] ${MESSAGE}") +endfunction() + +# Warning + optimisation flags, per platform (mirrors the hand Makefiles). +function(apply_keeperfx_warnings TARGET) + if(WIN32) + target_compile_options(${TARGET} PRIVATE + -Wall -W -Wshadow -Wno-sign-compare -Wno-unused-parameter + -Wno-maybe-uninitialized -Wno-strict-aliasing -Wno-unknown-pragmas + -Werror -Wno-format-truncation + -march=x86-64 -fno-omit-frame-pointer -fmessage-length=0 -O3 + $<$:-Wimplicit>) + else() + target_compile_options(${TARGET} PRIVATE + -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unknown-pragmas + -Wno-format-truncation -Wno-sign-compare + -g -O3 -march=x86-64 + $<$:-Wno-absolute-value>) + endif() +endfunction() + +# Link flags, per platform. +function(apply_keeperfx_link_flags TARGET) + if(WIN32) + target_link_options(${TARGET} PRIVATE + -mwindows -Wl,--enable-auto-import -Wl,-Map,${TARGET}.map) + target_link_libraries(${TARGET} PUBLIC -static stdc++ winpthread -dynamic) + else() + target_link_options(${TARGET} PRIVATE -g -rdynamic) + endif() +endfunction() + +# Windows system libraries (matches the Makefile LINKLIB trailer). No-op elsewhere. +function(apply_windows_system_libs TARGET) + if(WIN32) + target_link_libraries(${TARGET} PRIVATE + winmm mingw32 imagehlp ws2_32 dbghelp bcrypt ole32 uuid) + endif() +endfunction() diff --git a/build/cmake/modules/Packaging.cmake b/build/cmake/modules/Packaging.cmake new file mode 100644 index 0000000000..df2ba4f7f4 --- /dev/null +++ b/build/cmake/modules/Packaging.cmake @@ -0,0 +1,81 @@ +# --------------------------------------------------------------------------- +# Packaging.cmake — CPack configuration for KeeperFX distribution packages +# --------------------------------------------------------------------------- +# +# Produces the same kind of release archive as `make package`, but takes the +# keeperfx binary from the CMake build and the game data from the make data +# pipeline. Usage: +# +# 1. Configure (pass the build number / suffix used for the filename): +# cmake -S . -B out -G Ninja -DCMAKE_TOOLCHAIN_FILE=build/cmake/toolchains/mingw32.cmake \ +# -DCMAKE_BUILD_TYPE=RelWithDebInfo \ +# -DBUILD_NUMBER=1234 -DPACKAGE_SUFFIX=Alpha +# 2. Build the binary: +# cmake --build build --target keeperfx +# 3. Assemble the game data (gfx/lang/sfx pipeline) into pkg/: +# make BUILD_NUMBER=1234 PACKAGE_SUFFIX=Alpha pkg-assemble +# 4. Create the archive: +# cmake --build build --target package +# +# +# Output: pkg/keeperfx-___[-]-patch.7z +# --------------------------------------------------------------------------- + +set(CPACK_PACKAGE_NAME "keeperfx") +set(CPACK_PACKAGE_VENDOR "KeeperFX Team") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "KeeperFX - Free implementation of Dungeon Keeper") +set(CPACK_PACKAGE_VERSION "${VER_MAJOR}.${VER_MINOR}.${VER_RELEASE}.${BUILD_NUMBER}") +set(CPACK_PACKAGE_VERSION_MAJOR "${VER_MAJOR}") +set(CPACK_PACKAGE_VERSION_MINOR "${VER_MINOR}") +set(CPACK_PACKAGE_VERSION_PATCH "${VER_RELEASE}") + +# Archive filename keeperfx-___[-]-patch +if(PACKAGE_SUFFIX AND NOT "${PACKAGE_SUFFIX}" STREQUAL "") + set(CPACK_PACKAGE_FILE_NAME + "keeperfx-${VER_MAJOR}_${VER_MINOR}_${VER_RELEASE}_${BUILD_NUMBER}-${PACKAGE_SUFFIX}-patch") +else() + set(CPACK_PACKAGE_FILE_NAME + "keeperfx-${VER_MAJOR}_${VER_MINOR}_${VER_RELEASE}_${BUILD_NUMBER}-patch") +endif() + +# Place generated archives in the source-tree pkg/ directory +set(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/pkg") + +# Flat archive layout (no top-level directory prefix) +set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) +set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY OFF) + +# 7Z for the Windows/MinGW target; TGZ otherwise. +if(WIN32 OR MINGW OR CMAKE_CROSSCOMPILING OR CMAKE_SYSTEM_NAME STREQUAL "Windows") + set(CPACK_GENERATOR "7Z") +else() + set(CPACK_GENERATOR "TGZ") +endif() + +# --- Install rules --------------------------------------------------------- + +install(TARGETS keeperfx RUNTIME DESTINATION .) +install(TARGETS keeperfx_hvlog RUNTIME DESTINATION . OPTIONAL) + +# The game data assembled by "make pkg-assemble" (configs, campaigns, levels, +# language/sound .dat files, SDL2 runtime DLLs, docs). Evaluated at pack time so +# pkg/ is read then, not at configure time. Skips any archive left in pkg/. +install(CODE " + set(_pkg_src \"${CMAKE_SOURCE_DIR}/pkg\") + if(EXISTS \"\${_pkg_src}\") + file(GLOB_RECURSE _pkg_files + LIST_DIRECTORIES false + RELATIVE \"\${_pkg_src}\" + \"\${_pkg_src}/*\") + foreach(_f IN LISTS _pkg_files) + if(NOT _f MATCHES \"keeperfx.*\\\\.(7z|tar\\\\.gz|tgz)\$\") + get_filename_component(_dir \"\${_f}\" DIRECTORY) + file(MAKE_DIRECTORY \"\${CMAKE_INSTALL_PREFIX}/\${_dir}\") + file(COPY \"\${_pkg_src}/\${_f}\" + DESTINATION \"\${CMAKE_INSTALL_PREFIX}/\${_dir}\") + endif() + endforeach() + endif() +") + +include(CPack) diff --git a/build/cmake/modules/Platforms.cmake b/build/cmake/modules/Platforms.cmake new file mode 100644 index 0000000000..6454107d25 --- /dev/null +++ b/build/cmake/modules/Platforms.cmake @@ -0,0 +1,17 @@ +# Platforms.cmake - platform detection + directory-scope compile definitions. + +if(WIN32) + kfx_status("PLATFORM" "Windows / MinGW-w64 (i686)") +elseif(UNIX AND NOT APPLE) + kfx_status("PLATFORM" "Linux (x86_64)") +else() + message(FATAL_ERROR "Unsupported platform (only Windows/MinGW and Linux are supported)") +endif() + +add_compile_definitions("DEBUG=$,1,0>") + +# Static-linkage defines for the prebuilt Windows dependencies. +if(WIN32) + add_compile_definitions(_CRT_NONSTDC_NO_WARNINGS _CRT_SECURE_NO_WARNINGS) + add_compile_definitions(SPNG_STATIC=1 AL_LIBTYPE_STATIC) +endif() diff --git a/mingw32.cmake b/build/cmake/toolchains/mingw32.cmake similarity index 74% rename from mingw32.cmake rename to build/cmake/toolchains/mingw32.cmake index c3380596d8..b70bbc83cc 100644 --- a/mingw32.cmake +++ b/build/cmake/toolchains/mingw32.cmake @@ -1,7 +1,7 @@ set(CMAKE_SYSTEM_NAME Windows) set(TOOLCHAIN_PREFIX i686-w64-mingw32) -set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc-posix) -set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++-posix) +set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc) +set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++) set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres) set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt deleted file mode 100644 index 4f6f3d7fc9..0000000000 --- a/deps/CMakeLists.txt +++ /dev/null @@ -1,127 +0,0 @@ -cmake_minimum_required(VERSION 3.20) - - -if( NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/enet ) - file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/enet) - if( NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/enet-mingw32.tar.gz ) - file(DOWNLOAD https://github.com/dkfans/kfx-deps/releases/download/initial/enet-mingw32.tar.gz ${CMAKE_SOURCE_DIR}/deps/enet-mingw32.tar.gz SHOW_PROGRESS) - endif() - - execute_process(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/deps/enet-mingw32.tar.gz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/enet) -endif() - -if( NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/zlib ) - file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/zlib) - if( NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/zlib-mingw32.tar.gz ) - file(DOWNLOAD https://github.com/dkfans/kfx-deps/releases/download/initial/zlib-mingw32.tar.gz ${CMAKE_SOURCE_DIR}/deps/zlib-mingw32.tar.gz SHOW_PROGRESS) - endif() - - execute_process(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/deps/zlib-mingw32.tar.gz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/zlib) -endif() - -if( NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/spng ) - file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/spng) - if( NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/spng-mingw32.tar.gz ) - file(DOWNLOAD https://github.com/dkfans/kfx-deps/releases/download/initial/spng-mingw32.tar.gz ${CMAKE_SOURCE_DIR}/deps/spng-mingw32.tar.gz SHOW_PROGRESS) - endif() - - execute_process(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/deps/spng-mingw32.tar.gz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/spng) -endif() - -if( NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/astronomy ) - file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/astronomy) - if( NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/astronomy-mingw32.tar.gz ) - file(DOWNLOAD https://github.com/dkfans/kfx-deps/releases/download/initial/astronomy-mingw32.tar.gz ${CMAKE_SOURCE_DIR}/deps/astronomy-mingw32.tar.gz SHOW_PROGRESS) - endif() - - execute_process(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/deps/astronomy-mingw32.tar.gz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/astronomy) -endif() - -if( NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/centijson ) - file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/centijson) - if( NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/centijson-mingw32.tar.gz ) - file(DOWNLOAD https://github.com/dkfans/kfx-deps/releases/download/initial/centijson-mingw32.tar.gz ${CMAKE_SOURCE_DIR}/deps/centijson-mingw32.tar.gz SHOW_PROGRESS) - endif() - - execute_process(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/deps/centijson-mingw32.tar.gz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/centijson) -endif() - -if(NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/ffmpeg) - file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/ffmpeg) - if(NOT EXISTS ${CMAKE_SOURCE_DIR}/deps/ffmpeg-mingw32.tar.gz) - file(DOWNLOAD https://github.com/dkfans/kfx-deps/releases/download/initial/ffmpeg-mingw32.tar.gz ${CMAKE_SOURCE_DIR}/deps/ffmpeg-mingw32.tar.gz SHOW_PROGRESS) - endif() - - execute_process(COMMAND ${CMAKE_COMMAND} -E tar xzf ${CMAKE_SOURCE_DIR}/deps/ffmpeg-mingw32.tar.gz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/deps/ffmpeg) -endif() - -## enet -add_library(enet_static STATIC IMPORTED) -set_target_properties(enet_static PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/enet/libenet.a) -set_target_properties(enet_static PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/enet/include) -target_link_libraries(enet_static INTERFACE ws2_32 winmm) -target_link_libraries(keeperfx PUBLIC enet_static) -target_link_libraries(keeperfx_hvlog PUBLIC enet_static) - -## spng -add_library(spng_static STATIC IMPORTED) -set_target_properties(spng_static PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/spng/libspng.a) -set_target_properties(spng_static PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/spng/include) -target_link_libraries(keeperfx PUBLIC spng_static) -target_link_libraries(keeperfx_hvlog PUBLIC spng_static) - -## centijson -add_library(centijson_static STATIC IMPORTED) -set_target_properties(centijson_static PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/centijson/libjson.a) -set_target_properties(centijson_static PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/centijson/include) -target_link_libraries(keeperfx PUBLIC centijson_static) -target_link_libraries(keeperfx_hvlog PUBLIC centijson_static) - -## astronomy. -add_library(astronomy_static STATIC IMPORTED) -set_target_properties(astronomy_static PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/astronomy/libastronomy.a) -set_target_properties(astronomy_static PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/astronomy/include) -target_link_libraries(keeperfx PUBLIC astronomy_static) -target_link_libraries(keeperfx_hvlog PUBLIC astronomy_static) - -## zlib -add_library(zlib_static STATIC IMPORTED) -set_target_properties(zlib_static PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/zlib/libz.a) -set_target_properties(zlib_static PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/zlib/include) -target_link_libraries(keeperfx PUBLIC zlib_static) -target_link_libraries(keeperfx_hvlog PUBLIC zlib_static) - -# Add minizip -add_library(minizip_static STATIC IMPORTED) -set_target_properties(minizip_static PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/zlib/libminizip.a) -set_target_properties(minizip_static PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/zlib/include) -target_link_libraries(minizip_static INTERFACE zlib_static) -target_link_libraries(keeperfx PUBLIC minizip_static) -target_link_libraries(keeperfx_hvlog PUBLIC minizip_static) - -## centitoml. -add_library(centitoml OBJECT "centitoml/toml_api.c") -target_link_libraries(centitoml PUBLIC centijson_static) -target_include_directories(centitoml INTERFACE "centitoml") -target_link_libraries(keeperfx PUBLIC centitoml) -target_link_libraries(keeperfx_hvlog PUBLIC centitoml) - -## ffmpeg -add_library(libavcodec_static STATIC IMPORTED) -set_target_properties(libavcodec_static PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg/libavcodec/libavcodec.a) -set_target_properties(libavcodec_static PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg) - -add_library(libavformat_static STATIC IMPORTED) -set_target_properties(libavformat_static PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg/libavformat/libavformat.a) -set_target_properties(libavformat_static PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg) - -add_library(libavutil_static STATIC IMPORTED) -set_target_properties(libavutil_static PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg/libavutil/libavutil.a) -set_target_properties(libavutil_static PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg) - -add_library(libswresample_static STATIC IMPORTED) -set_target_properties(libswresample_static PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg/libswresample/libswresample.a) -set_target_properties(libswresample_static PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/ffmpeg) - -target_link_libraries(keeperfx PUBLIC bcrypt libavcodec_static libavformat_static libavutil_static libswresample_static) -target_link_libraries(keeperfx_hvlog PUBLIC bcrypt libavcodec_static libavformat_static libavutil_static libswresample_static) diff --git a/linux.mk b/linux.mk deleted file mode 100644 index 48aec25ebf..0000000000 --- a/linux.mk +++ /dev/null @@ -1,425 +0,0 @@ -include version.mk - -BUILD_NUMBER ?= $(VER_BUILD) -VER_SUFFIX ?= Prototype -VER_STRING = $(VER_MAJOR).$(VER_MINOR).$(VER_RELEASE).$(BUILD_NUMBER) $(VER_SUFFIX) - -MKDIR ?= mkdir -p -STRIP ?= strip -ECHO ?= echo -MV ?= mv -f - -KFX_SOURCES = \ -src/actionpt.c \ -src/api.c \ -src/ariadne.c \ -src/ariadne_edge.c \ -src/ariadne_findcache.c \ -src/ariadne_naviheap.c \ -src/ariadne_navitree.c \ -src/ariadne_points.c \ -src/ariadne_regions.c \ -src/ariadne_tringls.c \ -src/ariadne_update.c \ -src/ariadne_wallhug.c \ -src/bflib_basics.c \ -src/bflib_coroutine.c \ -src/bflib_cpu.c \ -src/bflib_crash.c \ -src/bflib_datetm.cpp \ -src/bflib_dernc.c \ -src/bflib_enet.cpp \ -src/net_portforward.cpp \ -src/bflib_fileio.c \ -src/bflib_filelst.c \ -src/bflib_fmvids.cpp \ -src/bflib_guibtns.c \ -src/bflib_input_joyst.cpp \ -src/bflib_inputctrl.cpp \ -src/bflib_keybrd.c \ -src/bflib_main.cpp \ -src/bflib_math.c \ -src/bflib_mouse.cpp \ -src/bflib_mshandler.cpp \ -src/bflib_mspointer.cpp \ -src/bflib_netsession.c \ -src/bflib_netsp.cpp \ -src/net_exchange_common.c \ -src/net_exchange_gameplay.c \ -src/net_main.c \ -src/net_lobby.c \ -src/bflib_planar.c \ -src/bflib_render.c \ -src/bflib_render_gpoly.c \ -src/bflib_render_trig.c \ -src/bflib_sndlib.cpp \ -src/bflib_sound.c \ -src/bflib_sprfnt.c \ -src/bflib_string.c \ -src/bflib_text.c \ -src/bflib_video.c \ -src/bflib_vidraw.c \ -src/bflib_vidraw_spr_norm.c \ -src/bflib_vidraw_spr_onec.c \ -src/bflib_vidraw_spr_remp.c \ -src/bflib_vidsurface.c \ -src/button_snapping.c \ -src/config.c \ -src/config_campaigns.c \ -src/config_creature.c \ -src/config_crtrmodel.c \ -src/config_crtrstates.c \ -src/config_keeperfx.c \ -src/config_lenses.c \ -src/config_magic.c \ -src/config_mods.c \ -src/config_objects.c \ -src/config_players.c \ -src/config_powerhands.c \ -src/config_rules.c \ -src/config_sounds.c \ -src/config_settings.c \ -src/config_slabsets.c \ -src/config_strings.c \ -src/config_terrain.c \ -src/config_cubes.c \ -src/config_textures.c \ -src/config_translation.c \ -src/config_trapdoor.c \ -src/config_spritecolors.c \ -src/console_cmd.c \ -src/custom_sprites.c \ -src/custom_zip.c \ -src/creature_battle.c \ -src/creature_control.c \ -src/creature_graphics.c \ -src/creature_groups.c \ -src/creature_instances.c \ -src/creature_jobs.c \ -src/creature_senses.c \ -src/creature_states.c \ -src/creature_states_barck.c \ -src/creature_states_combt.c \ -src/creature_states_gardn.c \ -src/creature_states_guard.c \ -src/creature_states_hero.c \ -src/creature_states_lair.c \ -src/creature_states_mood.c \ -src/creature_states_pray.c \ -src/creature_states_prisn.c \ -src/creature_states_rsrch.c \ -src/creature_states_scavn.c \ -src/creature_states_spdig.c \ -src/creature_states_tortr.c \ -src/creature_states_train.c \ -src/creature_states_tresr.c \ -src/creature_states_wrshp.c \ -src/cursor_tag.c \ -src/dungeon_data.c \ -src/dungeon_stats.c \ -src/engine_arrays.c \ -src/engine_camera.c \ -src/local_camera.c \ -src/engine_lenses.c \ -src/engine_redraw.c \ -src/engine_render.c \ -src/engine_render_data.cpp \ -src/engine_textures.c \ -src/front_credits.c \ -src/front_easter.c \ -src/front_fmvids.c \ -src/front_highscore.c \ -src/front_input.c \ -src/front_landview.c \ -src/front_landview_multiplayer.c \ -src/front_lvlstats.c \ -src/front_lvlstats_data.cpp \ -src/front_network.c \ -src/front_simple.c \ -src/front_torture.c \ -src/front_torture_data.cpp \ -src/frontend.cpp \ -src/frontmenu_select.c \ -src/frontmenu_ingame_evnt.c \ -src/frontmenu_ingame_evnt_data.cpp \ -src/frontmenu_ingame_map.c \ -src/frontmenu_ingame_opts.c \ -src/frontmenu_ingame_opts_data.cpp \ -src/frontmenu_ingame_tabs.c \ -src/frontmenu_ingame_tabs_data.cpp \ -src/frontmenu_net.c \ -src/frontmenu_net_data.cpp \ -src/frontmenu_options.c \ -src/frontmenu_options_data.cpp \ -src/frontmenu_saves.c \ -src/frontmenu_saves_data.cpp \ -src/frontmenu_select_data.cpp \ -src/frontmenu_specials.c \ -src/game_heap.c \ -src/game_legacy.c \ -src/game_loop.c \ -src/game_lghtshdw.c \ -src/game_merge.c \ -src/game_saves.c \ -src/game_update.cpp \ -src/gui_boxmenu.c \ -src/gui_draw.c \ -src/gui_frontbtns.c \ -src/gui_frontmenu.c \ -src/gui_msgs.c \ -src/gui_parchment.c \ -src/gui_soundmsgs.cpp \ -src/gui_tooltips.c \ -src/gui_topmsg.c \ -src/highscores.c \ -src/kjm_input.c \ -src/lens_api.c \ -src/config_effects.c \ -src/kfx_memory.c \ -src/kfx/lense/DisplacementEffect.cpp \ -src/kfx/lense/FlyeyeEffect.cpp \ -src/kfx/lense/LensEffect.cpp \ -src/kfx/lense/LensManager.cpp \ -src/kfx/lense/LuaLensEffect.cpp \ -src/kfx/lense/MistEffect.cpp \ -src/kfx/lense/OverlayEffect.cpp \ -src/kfx/lense/PaletteEffect.cpp \ -src/light_data.c \ -src/linux.cpp \ -src/lua_api.c \ -src/lua_api_lens.c \ -src/lua_api_map.c \ -src/lua_api_player.c \ -src/lua_api_room.c \ -src/lua_api_things.c \ -src/lua_api_slabs.c \ -src/lua_api_sound.c \ -src/lua_base.c \ -src/lua_api_camera.c \ -src/lua_cfg_funcs.c \ -src/lua_params.c \ -src/lua_triggers.c \ -src/lua_utils.c \ -src/lvl_filesdk1.c \ -src/lvl_script.c \ -src/lvl_script_commands.c \ -src/lvl_script_commands_old.c \ -src/lvl_script_lib.c \ -src/lvl_script_conditions.c \ -src/lvl_script_value.c \ -src/magic_powers.c \ -src/main.cpp \ -src/main_game.c \ -src/map_blocks.c \ -src/map_columns.c \ -src/map_ceiling.c \ -src/map_data.c \ -src/map_events.c \ -src/map_locations.c \ -src/map_utils.c \ -src/moonphase.c \ -src/net_checksums.c \ -src/net_game.c \ -src/net_holepunch.c \ -src/net_matchmaking.c \ -src/net_lan.c \ -src/net_input_lag.c \ -src/net_resync.cpp \ -src/packets.c \ -src/packets_cheats.c \ -src/packets_input.c \ -src/packets_misc.c \ -src/player_compchecks.c \ -src/player_compevents.c \ -src/player_complookup.c \ -src/config_compp.c \ -src/player_compprocs.c \ -src/player_comptask.c \ -src/player_computer.c \ -src/player_computer_data.cpp \ -src/player_data.c \ -src/player_instances.c \ -src/player_utils.c \ -src/power_hand.c \ -src/power_process.c \ -src/power_specials.c \ -src/room_data.c \ -src/room_entrance.c \ -src/room_garden.c \ -src/room_graveyard.c \ -src/room_jobs.c \ -src/room_lair.c \ -src/room_library.c \ -src/room_list.c \ -src/room_scavenge.c \ -src/room_treasure.c \ -src/room_util.c \ -src/room_workshop.c \ -src/roomspace.c \ -src/roomspace_detection.c \ -src/roomspace_prediction.c \ -src/scrcapt.c \ -src/slab_data.c \ -src/sounds.c \ -src/sound_manager.cpp \ -src/spdigger_stack.c \ -src/spritesheet.cpp \ -src/tasks_list.c \ -src/thing_corpses.c \ -src/thing_creature.c \ -src/thing_data.c \ -src/thing_doors.c \ -src/thing_effects.c \ -src/thing_factory.c \ -src/thing_list.c \ -src/thing_navigate.c \ -src/thing_objects.c \ -src/thing_physics.c \ -src/thing_shots.c \ -src/thing_stats.c \ -src/thing_traps.c \ -src/timer.c \ -src/value_util.c \ -src/vidfade.c \ -src/vidmode_data.cpp \ -src/vidmode.c - -KFX_C_SOURCES = $(filter %.c,$(KFX_SOURCES)) -KFX_CXX_SOURCES = $(filter %.cpp,$(KFX_SOURCES)) -KFX_C_OBJECTS = $(patsubst src/%.c,obj/%.o,$(KFX_C_SOURCES)) -KFX_CXX_OBJECTS = $(patsubst src/%.cpp,obj/%.o,$(KFX_CXX_SOURCES)) -KFX_OBJECTS = $(KFX_C_OBJECTS) $(KFX_CXX_OBJECTS) - -KFX_INCLUDES = \ - -Ideps/centijson/include \ - -Ideps/centitoml \ - -Ideps/astronomy/include \ - -Ideps/enet6/include \ - -Ideps/libcurl/include \ - $(shell pkg-config --cflags-only-I luajit) \ - $(shell pkg-config --cflags-only-I libavformat) - -KFX_CFLAGS += -g -DDEBUG -DBFDEBUG_LEVEL=0 -O3 -march=x86-64 $(KFX_INCLUDES) -Wall -Wextra -Werror -Wno-unused-parameter -Wno-absolute-value -Wno-unknown-pragmas -Wno-format-truncation -Wno-sign-compare -KFX_CXXFLAGS += -g -DDEBUG -DBFDEBUG_LEVEL=0 -O3 -march=x86-64 $(KFX_INCLUDES) -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unknown-pragmas -Wno-format-truncation -Wno-sign-compare - -KFX_LDFLAGS += \ - -g \ - -rdynamic \ - -Wall -Wextra -Werror \ - -Ldeps/astronomy -lastronomy \ - -Ldeps/centijson -ljson \ - -Ldeps/enet6 -lenet6 \ - $(shell pkg-config --libs-only-l sdl2) \ - $(shell pkg-config --libs-only-l SDL2_mixer) \ - $(shell pkg-config --libs-only-l SDL2_net) \ - $(shell pkg-config --libs-only-l SDL2_image) \ - $(shell pkg-config --libs-only-l libavformat) \ - $(shell pkg-config --libs-only-l libavcodec) \ - $(shell pkg-config --libs-only-l libswresample) \ - $(shell pkg-config --libs-only-l libavutil) \ - $(shell pkg-config --libs-only-l openal) \ - $(shell pkg-config --libs-only-l luajit) \ - $(shell pkg-config --libs-only-l spng) \ - $(shell pkg-config --libs-only-l minizip) \ - $(shell pkg-config --libs-only-l zlib) \ - -lminiupnpc \ - -lnatpmp \ - -Ldeps/libcurl/lib -lcurl -lssl -lcrypto -lzstd \ - -ldl - -TOML_SOURCES = \ - deps/centitoml/toml_api.c - -TOML_OBJECTS = $(patsubst deps/centitoml/%.c,obj/centitoml/%.o,$(TOML_SOURCES)) - -TOML_INCLUDES = \ - -Ideps/centijson/include - -TOML_CFLAGS += -O3 -march=x86-64 $(TOML_INCLUDES) -Wall -Wextra -Werror -Wno-unused-parameter - -ifeq ($(ENABLE_LTO), 1) -KFX_CFLAGS += -flto -KFX_CXXFLAGS += -flto -KFX_LDFLAGS += -flto=auto -TOML_CFLAGS += -flto -endif - -# All downloaded dependencies must be unpacked before any object is compiled. -# Otherwise a parallel build (make -jN) can start compiling a source that -# includes a not-yet-extracted dependency header (e.g. ) and fail -# on the first run. Used as an order-only prerequisite of every object below. -DEPS_EXTRACTED = \ - deps/centijson/include/json.h \ - deps/astronomy/include/astronomy.h \ - deps/enet6/include/enet6/enet.h \ - deps/libcurl/lib/libcurl.a - -all: bin/keeperfx - -clean: - rm -rf obj bin src/ver_defs.h deps/astronomy deps/centijson deps/enet6 deps/libcurl - rm -f deps/libcurl-lin64.tar.gz - -.PHONY: all clean - -bin/keeperfx: $(KFX_OBJECTS) $(TOML_OBJECTS) deps/libcurl/lib/libcurl.a | bin - $(CXX) -o $@ $(KFX_OBJECTS) $(TOML_OBJECTS) $(KFX_LDFLAGS) - -$(KFX_C_OBJECTS): obj/%.o: src/%.c src/ver_defs.h | obj $(DEPS_EXTRACTED) - $(MKDIR) $(dir $@) - $(CC) $(KFX_CFLAGS) -c $< -o $@ - -$(KFX_CXX_OBJECTS): obj/%.o: src/%.cpp src/ver_defs.h | obj $(DEPS_EXTRACTED) - $(MKDIR) $(dir $@) - $(CXX) $(KFX_CXXFLAGS) -c $< -o $@ - -$(TOML_OBJECTS): obj/centitoml/%.o: deps/centitoml/%.c | obj/centitoml $(DEPS_EXTRACTED) - $(CC) $(TOML_CFLAGS) -c $< -o $@ - -bin obj deps/astronomy deps/centijson deps/enet6 deps/libcurl obj/centitoml: - $(MKDIR) $@ - -src/actionpt.c: deps/centijson/include/json.h -src/api.c: deps/centijson/include/json.h -src/bflib_enet.cpp: deps/enet6/include/enet6/enet.h -src/moonphase.c: deps/astronomy/include/astronomy.h -src/net_holepunch.c: deps/enet6/include/enet6/enet.h -src/net_matchmaking.c: deps/libcurl/include/curl/curl.h -deps/centitoml/toml_api.c: deps/centijson/include/json.h -deps/centitoml/toml_conv.c: deps/centijson/include/json.h - -deps/astronomy-lin64.tar.gz: - curl -Lso $@ "https://github.com/dkfans/kfx-deps/releases/download/20250418/astronomy-lin64.tar.gz" - -deps/astronomy/include/astronomy.h: deps/astronomy-lin64.tar.gz | deps/astronomy - tar xzmf $< -C deps/astronomy - -deps/centijson-lin64.tar.gz: - curl -Lso $@ "https://github.com/dkfans/kfx-deps/releases/download/20250418/centijson-lin64.tar.gz" - -deps/centijson/include/json.h: deps/centijson-lin64.tar.gz | deps/centijson - tar xzmf $< -C deps/centijson - -deps/enet6-lin64.tar.gz: - curl -Lso $@ "https://github.com/dkfans/kfx-deps/releases/download/20260213/enet6-lin64.tar.gz" - -deps/enet6/include/enet6/enet.h: deps/enet6-lin64.tar.gz | deps/enet6 - tar xzmf $< -C deps/enet6 - -deps/libcurl-lin64.tar.gz: - curl -Lso $@ "https://github.com/dkfans/kfx-deps/releases/download/20260310/libcurl-lin64.tar.gz" - -deps/libcurl/lib/libcurl.a: deps/libcurl-lin64.tar.gz | deps/libcurl - tar xzmf $< -C deps/libcurl - -deps/libcurl/include/curl/curl.h: deps/libcurl/lib/libcurl.a - -src/ver_defs.h: version.mk - $(ECHO) "#define VER_MAJOR $(VER_MAJOR)" > $@.swp - $(ECHO) "#define VER_MINOR $(VER_MINOR)" >> $@.swp - $(ECHO) "#define VER_RELEASE $(VER_RELEASE)" >> $@.swp - $(ECHO) "#define VER_BUILD $(BUILD_NUMBER)" >> $@.swp - $(ECHO) "#define VER_STRING \"$(VER_STRING)\"" >> $@.swp - $(ECHO) "#define PACKAGE_SUFFIX \"$(VER_SUFFIX)\"" >> $@.swp - $(ECHO) "#define GIT_REVISION \"$(shell git describe --always)\"" >> $@.swp - $(MV) $@.swp $@ diff --git a/package.mk b/package.mk index 214cb7a389..abaf7c2330 100644 --- a/package.mk +++ b/package.mk @@ -80,7 +80,16 @@ PKG_FILES = \ $(PKG_DOCS) \ $(PKG_DLL) -.PHONY: package +# Everything except the binaries, their linker maps, and the SDL runtime DLLs. +# When packaging with CMake/CPack the binaries and the SDL DLLs come from the +# CMake build (see build/cmake/modules/Packaging.cmake and deps/CMakeLists.txt), +# so "pkg-assemble" stages only the game data (configs, campaigns, levels, +# language/sound .dat files, docs) into pkg/ for CPack to archive. +PKG_DATA_FILES = $(filter-out \ + $(PKG_BIN) $(PKG_BIN_MAP) $(PKG_HVLOGBIN) $(PKG_HVLOGBIN_MAP) $(PKG_DLL), \ + $(PKG_FILES)) + +.PHONY: package pkg-assemble pkg pkg/creatrs pkg/fxdata pkg/campgns pkg/fxdata/lua $(PKG_MAPPACK_DIRS) $(PKG_MP_MAPPACK_DIRS) $(PKG_CAMPAIGN_DIRS) $(PKG_FXDATA_DIRS) $(PKG_MOD_DIRS): $(MKDIR) $@ @@ -167,5 +176,11 @@ $(PKG_NAME): $(PKG_FILES) | pkg package: $(PKG_NAME) +# Stage the game data into pkg/ without building the .7z. Used by the CMake +# packaging flow: run this, then "cmake --build --target package" (CPack) adds +# the CMake-built keeperfx.exe and creates the archive. +pkg-assemble: $(PKG_DATA_FILES) + @echo "Game data assembled into pkg/ - run 'cmake --build --target package' to create the archive" + clean-package: $(RM) -r pkg From 78351c0ff0bddaa262d931328cf390223e005780 Mon Sep 17 00:00:00 2001 From: Loobinex Date: Tue, 4 Aug 2026 14:13:49 +0200 Subject: [PATCH 25/28] Make hand rule work on prisoners (#5077) --- src/power_hand.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/power_hand.c b/src/power_hand.c index 017792f6fc..c83e15a2d6 100644 --- a/src/power_hand.c +++ b/src/power_hand.c @@ -1389,9 +1389,8 @@ void add_creature_to_sacrifice_list(PlayerNumber plyr_idx, long model, CrtrExpLe TbBool place_thing_in_power_hand(struct Thing *thing, PlayerNumber plyr_idx) { - struct PlayerInfo *player; - long i; - player = get_player(plyr_idx); + struct PlayerInfo *player = get_player(plyr_idx); + short i; if (!thing_is_pickable_by_hand(player, thing)) { ERRORLOG("The %s owned by player %d is not pickable by player %d",thing_model_name(thing),(int)thing->owner,(int)plyr_idx); return false; @@ -1708,7 +1707,7 @@ TbBool eval_hand_rule_for_thing(struct HandRule *rule, const struct Thing *thing TbBool thing_pickup_is_blocked_by_hand_rule(const struct Thing *thing_to_pick, PlayerNumber plyr_idx) { struct Dungeon* dungeon = get_dungeon(plyr_idx); - if (thing_is_creature(thing_to_pick) && thing_to_pick->owner == plyr_idx) + if (thing_is_creature(thing_to_pick)) { struct HandRule hand_rule; TbBool overwrite_default_block = false; From 1f0e2b60bce9be56a61abf068e1b2cfc49649419 Mon Sep 17 00:00:00 2001 From: Peter Lockett <1760289+cerwym@users.noreply.github.com> Date: Tue, 4 Aug 2026 17:14:26 +0100 Subject: [PATCH 26/28] Include keeperfx.exe and keeperfx_hvlog.exe in uploaded package artifact(#5086) --- .github/workflows/build-alpha-patch-unsigned.yml | 8 +++++++- .github/workflows/build-prototype.yml | 7 ++++++- .github/workflows/build-release-patch-unsigned.yml | 8 +++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-alpha-patch-unsigned.yml b/.github/workflows/build-alpha-patch-unsigned.yml index fee20bd48a..3b48eef7fd 100644 --- a/.github/workflows/build-alpha-patch-unsigned.yml +++ b/.github/workflows/build-alpha-patch-unsigned.yml @@ -52,6 +52,12 @@ jobs: make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX pkg-assemble cmake --build out --target package echo "ZIP_NAME=$(basename -s .7z pkg/keeperfx*.7z)" >> $GITHUB_ENV + # CPack stages the binaries (keeperfx.exe, keeperfx_hvlog.exe) and the SDL2 + # runtime DLLs only inside the .7z archive, not into the loose pkg/ tree, so + # uploading pkg/** would drop them and leave SignPath with nothing to sign. + # Install the full package layout into dist/ so the uploaded artifact holds + # the binaries + DLLs + game data. + cmake --install out --prefix dist rm pkg/keeperfx*.7z - name: Upload artifact @@ -59,7 +65,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ env.ZIP_NAME }} - path: pkg/** + path: dist/** outputs: artifact-id: ${{ steps.upload-artifact.outputs.artifact-id }} diff --git a/.github/workflows/build-prototype.yml b/.github/workflows/build-prototype.yml index f11809931e..500663eda2 100644 --- a/.github/workflows/build-prototype.yml +++ b/.github/workflows/build-prototype.yml @@ -38,13 +38,18 @@ jobs: make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX pkg-assemble cmake --build out --target package echo "ZIP_NAME=$(basename -s .7z pkg/keeperfx*.7z)" >> $GITHUB_ENV + # CPack stages the binaries (keeperfx.exe, keeperfx_hvlog.exe) and the SDL2 + # runtime DLLs only inside the .7z archive, not into the loose pkg/ tree, so + # uploading pkg/** would drop them. Install the full package layout into + # dist/ so the uploaded artifact holds the binaries + DLLs + game data. + cmake --install out --prefix dist rm pkg/keeperfx*.7z - name: Upload artifact uses: actions/upload-artifact@v4 with: name: ${{ env.ZIP_NAME }} - path: pkg/** + path: dist/** build-prototype-linux: name: "Build Prototype Linux x86_64" diff --git a/.github/workflows/build-release-patch-unsigned.yml b/.github/workflows/build-release-patch-unsigned.yml index b92ac61148..692b230d9f 100644 --- a/.github/workflows/build-release-patch-unsigned.yml +++ b/.github/workflows/build-release-patch-unsigned.yml @@ -52,6 +52,12 @@ jobs: make BUILD_NUMBER=$BUILD_NUMBER PACKAGE_SUFFIX=$PACKAGE_SUFFIX pkg-assemble cmake --build out --target package echo "ZIP_NAME=$(basename -s .7z pkg/keeperfx*.7z)" >> $GITHUB_ENV + # CPack stages the binaries (keeperfx.exe, keeperfx_hvlog.exe) and the SDL2 + # runtime DLLs only inside the .7z archive, not into the loose pkg/ tree, so + # uploading pkg/** would drop them and leave SignPath with nothing to sign. + # Install the full package layout into dist/ so the uploaded artifact holds + # the binaries + DLLs + game data. + cmake --install out --prefix dist rm pkg/keeperfx*.7z - name: Upload artifact @@ -59,7 +65,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: ${{ env.ZIP_NAME }} - path: pkg/** + path: dist/** outputs: artifact-id: ${{ steps.upload-artifact.outputs.artifact-id }} From ef2228fbc84ac34f31ed29d3baaf7c4922c73c7f Mon Sep 17 00:00:00 2001 From: AleWin32 <7621682+AleWin32@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:07:51 +0200 Subject: [PATCH 27/28] Add debug logging for music track loading --- src/bflib_sndlib.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bflib_sndlib.cpp b/src/bflib_sndlib.cpp index edb2ea6f7e..a12d49e61c 100644 --- a/src/bflib_sndlib.cpp +++ b/src/bflib_sndlib.cpp @@ -848,6 +848,7 @@ extern "C" TbBool play_music_track(int track) { WARNLOG("No music file found for track %d in the music folder", track); return false; } + LbJustLog("Playing track %d from: %s\n", track, fpath); return play_music(fpath); } else { if (track == g_current_music_track) { From e3445592dbec417fa2386febb98554e5b99ab15f Mon Sep 17 00:00:00 2001 From: Loobinex Date: Tue, 4 Aug 2026 22:31:44 +0200 Subject: [PATCH 28/28] Small log tweak --- src/bflib_sndlib.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bflib_sndlib.cpp b/src/bflib_sndlib.cpp index a12d49e61c..48c6692297 100644 --- a/src/bflib_sndlib.cpp +++ b/src/bflib_sndlib.cpp @@ -848,7 +848,7 @@ extern "C" TbBool play_music_track(int track) { WARNLOG("No music file found for track %d in the music folder", track); return false; } - LbJustLog("Playing track %d from: %s\n", track, fpath); + LbJustLog("Playing track %d: %s\n", track, fpath); return play_music(fpath); } else { if (track == g_current_music_track) {