From 92ff373f09de78f5a9aa88140be8af50c7238a13 Mon Sep 17 00:00:00 2001 From: phobos2077 Date: Wed, 15 May 2024 18:13:09 +0200 Subject: [PATCH] Returning Boomerangs - Add new mechanic to gl_throwing_bonus for returning Boomerangs - Return on successful roll vs Throwing skill regardless of hit/miss - On return to dude, new message is displayed --- root/data/text/english/game/pbs_combat.msg | 2 + root/data/text/russian/game/pbs_combat.msg | 2 + root/mods/ecco/combat.ini | 6 ++ scripts_src/_pbs_headers/ecco.h | 2 + scripts_src/_pbs_headers/ecco_ini.h | 6 +- .../_pbs_main/gl_melee_gore_unlock.ssl | 2 +- scripts_src/_pbs_main/gl_pbs_critter_loot.ssl | 2 +- scripts_src/_pbs_main/gl_throwing_bonus.ssl | 99 ++++++++++++++++++- .../_pbs_main/tests/gl_cheat_enemyspawner.ssl | 19 +++- scripts_src/_pbs_main/tests/gl_test.ssl | 19 ++-- 10 files changed, 141 insertions(+), 18 deletions(-) diff --git a/root/data/text/english/game/pbs_combat.msg b/root/data/text/english/game/pbs_combat.msg index 669a7c0..d44a732 100644 --- a/root/data/text/english/game/pbs_combat.msg +++ b/root/data/text/english/game/pbs_combat.msg @@ -2,6 +2,8 @@ # stealth {100}{}{You caught %s off guard with unexpected attack.} +# boomerang +{120}{}{You manage to catch a %s on return.} # destroy weapon - male {200}{}{%s damaged his %s beyond repair.} diff --git a/root/data/text/russian/game/pbs_combat.msg b/root/data/text/russian/game/pbs_combat.msg index 15d45a8..69661cf 100644 --- a/root/data/text/russian/game/pbs_combat.msg +++ b/root/data/text/russian/game/pbs_combat.msg @@ -2,6 +2,8 @@ # stealth {100}{}{Вы застали %s врасплох неожиданной атакой.} +# boomerang +{120}{}{Вам удалось поймать %s на обратной траектории.} # destroy weapon - мужской {200}{}{%s повредил свой %s до неузнаваемости.} diff --git a/root/mods/ecco/combat.ini b/root/mods/ecco/combat.ini index e8e58d5..d4d5b6c 100644 --- a/root/mods/ecco/combat.ini +++ b/root/mods/ecco/combat.ini @@ -58,6 +58,12 @@ bonus_crit_chance_luck_mult=3 ; Set 1 to add Melee Damage to max damage for melee-based throwing weapons, just like Melee/Unarmed. apply_melee_dmg=1 +; List of weapon PIDs that will return to thrower at the end of his turn, if roll vs Throwing skill is successfull +return_weapon_pids=637 + +; Skill mod for catching Boomerang on return +return_catch_skill_mod=-20 + [CRITTER_LOOT] ;set to 1 to enable weapons dropping to the ground on death, set to 0 to disable diff --git a/scripts_src/_pbs_headers/ecco.h b/scripts_src/_pbs_headers/ecco.h index 1127a9a..4ecd79b 100644 --- a/scripts_src/_pbs_headers/ecco.h +++ b/scripts_src/_pbs_headers/ecco.h @@ -11,7 +11,9 @@ #include "../sfall/define_extra.h" #include "ecco_ids.h" +#include "ecco_ini.h" #include "ecco_log.h" +#include "ecco_msg.h" #define dude_skill(x) (has_skill(dude_obj, x)) diff --git a/scripts_src/_pbs_headers/ecco_ini.h b/scripts_src/_pbs_headers/ecco_ini.h index 5e8d61c..d4e9255 100644 --- a/scripts_src/_pbs_headers/ecco_ini.h +++ b/scripts_src/_pbs_headers/ecco_ini.h @@ -1,6 +1,8 @@ #ifndef ECCO_INI_H #define ECCO_INI_H +#include "../sfall/lib.strings.h" + #define INI_SFALL "ddraw.ini" #define INI_COMBAT "ecco\\combat.ini" #define INI_ECONOMY "ecco\\barter.ini" @@ -9,13 +11,13 @@ #define get_int_from_ini(name, section, setting) get_ini_setting(name + "|" + section + "|" + setting) #define get_str_from_ini(name, section, setting) get_ini_string(name + "|" + section + "|" + setting) #define get_float_from_ini(name, section, setting) atof(get_ini_string(name + "|" + section + "|" + setting)) -#define get_int_list_from_ini(name, section, setting) string_split_ints(get_ini_string(name + "|" + section + "|" + setting), ",") +#define get_int_list_from_ini(name, section, setting) string_split_ints(get_ini_string(name + "|" + section + "|" + setting), ",") #define int_from_ini_file(name, file, section) ini_##name := get_int_from_ini(file, section, #name) #define str_from_ini_file(name, file, section) ini_##name := get_str_from_ini(file, section, #name) #define float_from_ini_file(name, file, section) ini_##name := get_float_from_ini(file, section, #name) -#define int_list_from_ini_file(name, file, section) ini_##name := get_int_list_from_ini(file, section, #name) +#define int_list_from_ini_file(name, file, section) ini_##name := array_fixed(get_int_list_from_ini(file, section, #name)) #define int_from_ini_file_clamped(name, file, section, min, max) ini_##name := math_clamp(get_int_from_ini(file, section, #name), min, max) #define float_from_ini_file_clamped(name, file, section, min, max) ini_##name := math_clamp(get_float_from_ini(file, section, #name), min, max) diff --git a/scripts_src/_pbs_main/gl_melee_gore_unlock.ssl b/scripts_src/_pbs_main/gl_melee_gore_unlock.ssl index e0cd650..63a332c 100644 --- a/scripts_src/_pbs_main/gl_melee_gore_unlock.ssl +++ b/scripts_src/_pbs_main/gl_melee_gore_unlock.ssl @@ -50,7 +50,7 @@ procedure deathanim2_handler begin damageType := get_proto_data(weaponPID, PROTO_WP_DMG_TYPE), wpnAnim := get_proto_data(weaponPID, PROTO_WP_ANIM); - display_msg(string_format("weapon %d mode = %d, dmgType = %d, atkType = %d, violence filter = %d", weaponPID, weaponMode, damageType, attackType, violence_filter_setting)); + //display_msg(string_format("weapon %d mode = %d, dmgType = %d, atkType = %d, violence filter = %d", weaponPID, weaponMode, damageType, attackType, violence_filter_setting)); if ((weaponMode == ATTACK_MODE_PUNCH and damageType == DMG_normal_dam) or weaponMode == ATTACK_MODE_KICK or weaponMode == ATTACK_MODE_THRUST diff --git a/scripts_src/_pbs_main/gl_pbs_critter_loot.ssl b/scripts_src/_pbs_main/gl_pbs_critter_loot.ssl index 0613a29..d7882ae 100644 --- a/scripts_src/_pbs_main/gl_pbs_critter_loot.ssl +++ b/scripts_src/_pbs_main/gl_pbs_critter_loot.ssl @@ -47,7 +47,7 @@ procedure reduce_item_count(variable invenobj, variable item, variable newCount) #define load_ini_int(name) int_from_ini_file(name, INI_COMBAT, INI_SECTION) #define load_ini_str(name) str_from_ini_file(name, INI_COMBAT, INI_SECTION) #define load_ini_int_clamped(name, min, max) int_from_ini_file_clamped(name, INI_COMBAT, INI_SECTION, min, max) -#define load_ini_int_list(name) int_list_from_ini_file(name, INI_COMBAT, INI_SECTION); fix_array(ini_##name) +#define load_ini_int_list(name) int_list_from_ini_file(name, INI_COMBAT, INI_SECTION) /* HOOK_ONDEATH diff --git a/scripts_src/_pbs_main/gl_throwing_bonus.ssl b/scripts_src/_pbs_main/gl_throwing_bonus.ssl index f1aa3ff..baa2642 100644 --- a/scripts_src/_pbs_main/gl_throwing_bonus.ssl +++ b/scripts_src/_pbs_main/gl_throwing_bonus.ssl @@ -2,6 +2,7 @@ INI-configurable throwing weapons buffs: - Damage bonus based on Throwing Skill - Flat crit-chance bonus + Luck-based bonus + - Returning Boomerang mechanic */ #define SCRIPT_REALNAME "gl_throwing_bonus" @@ -13,17 +14,25 @@ #include "../sfall/lib.arrays.h" #include "../sfall/lib.math.h" #include "../sfall/lib.inven.h" +#include "../sfall/lib.obj.h" + #include "../_pbs_headers/ecco.h" variable ini_damage_bonus_skill_mult, ini_bonus_crit_chance, ini_bonus_crit_chance_luck_mult, - ini_apply_melee_dmg; + ini_apply_melee_dmg, + ini_return_weapon_pids, + ini_return_catch_skill_mod, + thrown_owner, + thrown_items; procedure start; procedure itemdamage_handler; procedure rollcheck_handler; +procedure combatturn_handler; +procedure removeinvenobj_handler; procedure start begin if game_loaded then begin @@ -31,6 +40,8 @@ procedure start begin int_from_ini_file_clamped(bonus_crit_chance, INI_COMBAT, INI_SECTION, 0, 100); int_from_ini_file_clamped(bonus_crit_chance_luck_mult, INI_COMBAT, INI_SECTION, 0, 100); int_from_ini_file(apply_melee_dmg, INI_COMBAT, INI_SECTION); + int_list_from_ini_file(return_weapon_pids, INI_COMBAT, INI_SECTION); + int_from_ini_file(return_catch_skill_mod, INI_COMBAT, INI_SECTION); if (ini_apply_melee_dmg > 0 or ini_damage_bonus_skill_mult > 0.01) then begin register_hook_proc(HOOK_ITEMDAMAGE, itemdamage_handler); @@ -38,6 +49,11 @@ procedure start begin if (ini_bonus_crit_chance > 0) then begin register_hook_proc(HOOK_ROLLCHECK, rollcheck_handler); end + + if (len_array(ini_return_weapon_pids) > 0) then begin + register_hook_proc(HOOK_COMBATTURN, combatturn_handler); + register_hook_proc(HOOK_REMOVEINVENOBJ, removeinvenobj_handler); + end end end @@ -159,3 +175,84 @@ procedure itemdamage_handler begin end end end + +/*procedure return_thrown_item begin + //display_msg("Timed called at "+game_time); + if (thrown_item and thrown_owner) then begin + debug_log_fmt("Returning thrown weapon %s back to %s", obj_name(thrown_item), obj_name(thrown_owner)); + add_obj_to_inven(thrown_owner, thrown_item); + wield_obj_critter(thrown_owner, thrown_item); + end +end*/ + + +/* +Runs before and after each turn in combat (for both PC and NPC). + +int arg0 - event type: + 1 - start of turn + 0 - normal end of turn + -1 - combat ends abruptly (by script or by pressing Enter during PC turn) + -2 - combat ends normally (hook always runs at the end of combat) +Critter arg1 - critter doing the turn +int arg2 - 1 at the start/end of the player's turn after loading a game saved in combat mode, 0 otherwise + +int ret0 - pass 1 at the start of turn to skip the turn, pass -1 at the end of turn to force end of combat +*/ +procedure combatturn_handler begin + variable + eventType := get_sfall_arg, + critter := get_sfall_arg, + item, isFirst; + + if (critter == thrown_owner and thrown_items and eventType <= 0) then begin + isFirst := true; + foreach (item in thrown_items) begin + if (not is_success(roll_vs_skill(critter, SKILL_THROWING, ini_return_catch_skill_mod))) then continue; + + debug_log_fmt("Returning thrown weapon %s back to %s", obj_name(item), obj_name(thrown_owner)); + add_obj_to_inven(thrown_owner, item); + if (isFirst) then begin + if (critter == dude_obj) then begin + display_msg(sprintf(mstr_ecco_combat(120), obj_name(item))); + end + if (not get_active_weapon(critter)) then + wield_obj_critter(thrown_owner, item); + isFirst = false; + end + end + end + thrown_owner := 0; + if (thrown_items) then + clear_array(thrown_items); +end + + +/* +Runs when an object is removed from a container or critter's inventory for any reason. + +Obj arg0 - the owner that the object is being removed from +Item arg1 - the item that is being removed +int arg2 - the number of items to remove +int arg3 - The reason the object is being removed (see RMOBJ_* constants) +Obj arg4 - The destination object when the item is moved to another object, 0 otherwise +*/ +procedure removeinvenobj_handler begin + variable + owner := get_sfall_arg, + item := get_sfall_arg, + num := get_sfall_arg, + reason := get_sfall_arg; + + //display_msg(string_format("removeinvenobj %s(%d) from %s (reason: %d, flag: %x)", obj_name(item), num, obj_name(owner), reason, get_object_data(item, OBJ_DATA_FLAGS))); + // Return thrown Boomerangs back to owner + if (reason == RMOBJ_THROW and is_in_array(obj_pid(item), ini_return_weapon_pids) and num == 1) then begin + if (not thrown_items) then + thrown_items := create_array_list(0); + thrown_owner := owner; + call array_push(thrown_items, item); + //thrown_flags := get_object_data(item, OBJ_DATA_FLAGS); + //call return_thrown_item in 3; // very unreliable, needs to happen after attack ends, but animation length varies... + //display_msg("Called scheduled at "+game_time); + end +end \ No newline at end of file diff --git a/scripts_src/_pbs_main/tests/gl_cheat_enemyspawner.ssl b/scripts_src/_pbs_main/tests/gl_cheat_enemyspawner.ssl index 9d860cd..73a104c 100644 --- a/scripts_src/_pbs_main/tests/gl_cheat_enemyspawner.ssl +++ b/scripts_src/_pbs_main/tests/gl_cheat_enemyspawner.ssl @@ -24,9 +24,12 @@ #define ENEMY_GECKO (8) #define ENEMY_BOSS (9) #define ENEMY_DEATHCLAW (10) +#define ENEMY_THROWER (11) variable enemy_types; +#define is_throwing_weapon_pid(pid) (weapon_attack_mode1(pid) == ATTACK_MODE_THROW) + procedure spawn_enemy(variable type, variable tile) begin variable critter, pid, sid, weaponPid, weapon, ammoPid, stimpaks, female; switch (type) begin @@ -40,7 +43,7 @@ procedure spawn_enemy(variable type, variable tile) begin weaponPid := PID_DESERT_EAGLE; // DE case ENEMY_FARMER: pid := PID_MALE_FARMER + random(0, 1); - sid := SCRIPT_DCPEASNT; + sid := SCRIPT_ECFARMER; weaponPid := PID_SAWED_OFF_SHOTGUN; case ENEMY_EYEBOT: pid := PID_EYEBALL; @@ -61,6 +64,12 @@ procedure spawn_enemy(variable type, variable tile) begin case ENEMY_DEATHCLAW: pid := PID_MOTHER_DEATHCLAW; // PID_TOUGH_DEATHCLAW; sid := SCRIPT_ECDTHCLW; + case ENEMY_THROWER: + female := random(0, 1); + pid := PID_AGILE_GUARD_MALE + female; + sid := SCRIPT_ECRAIDER; + stimpaks := random(1, 3); + weaponPid := array_random_value([PID_THROWING_KNIFE, PID_PBS_BOOMERANG] if female else [PID_PBS_THROWING_AXE]); case ENEMY_MELEE: female := random(0, 1); pid := PID_MELEE_GUARD_MALE + female; @@ -73,7 +82,7 @@ procedure spawn_enemy(variable type, variable tile) begin critter := create_object_sid(pid, tile, dude_elevation, sid); if (weaponPid) then begin - weapon := add_item_pid(critter, weaponPid); + weapon := add_items_pid(critter, weaponPid, 3 if is_throwing_weapon_pid(weaponPid) else 1); //wield_obj_critter(critter, weapon); ammoPid := get_proto_data(weaponPid, PROTO_WP_AMMO_PID); if (ammoPid > 0) then @@ -112,7 +121,7 @@ procedure keypress_handler begin //display_msg("key pressed "+keyCode); if (keyCode == DIK_5) then begin //call spawn_enemy(ENEMY_BOSS, tile_under_cursor); - call spawn_enemies_circle(ENEMY_ENCLAVE, 2); + call spawn_enemies_circle(ENEMY_FARMER, 2); end if (keyCode == DIK_6) then begin variable crit := spawn_enemy(ENEMY_DEATHCLAW, tile_under_cursor); @@ -122,9 +131,9 @@ procedure keypress_handler begin reg_anim_end(); end if (keyCode == DIK_7) then - call spawn_enemy(ENEMY_FARMER, tile_under_cursor); + call spawn_enemy(ENEMY_ENCLAVE, tile_under_cursor); if (keyCode == DIK_8) then - call spawn_enemy(ENEMY_EYEBOT, tile_under_cursor); + call spawn_enemy(ENEMY_THROWER, tile_under_cursor); if (keyCode == DIK_9) then call spawn_enemies_circle(ENEMY_ALIEN, 1); end diff --git a/scripts_src/_pbs_main/tests/gl_test.ssl b/scripts_src/_pbs_main/tests/gl_test.ssl index 87a44fe..e23240d 100644 --- a/scripts_src/_pbs_main/tests/gl_test.ssl +++ b/scripts_src/_pbs_main/tests/gl_test.ssl @@ -107,14 +107,17 @@ procedure keypress_hook begin end end if (key == DIK_F3) then begin - move_to(dude_obj, tile_under_cursor, dude_elevation); - if (get_cursor_mode == 1 and obj_under_cursor(true, false)) then begin - //obj := obj_under_cursor(true, false); - //variable tile := tile_num(obj); - //destroy_object(obj); - //obj := create_object_sid(16777224, tile, dude_elevation, 1313); - //set_script(obj, 1049); // NIBASDOR - //display_msg(string_format("Set script for %s to %d", obj_name(obj), 1049)); + if (get_cursor_mode == 1) then begin + if (obj_under_cursor(true, false)) then begin + obj := obj_under_cursor(true, false); + //variable tile := tile_num(obj); + destroy_object(obj); + //obj := create_object_sid(16777224, tile, dude_elevation, 1313); + //set_script(obj, 1049); // NIBASDOR + //display_msg(string_format("Set script for %s to %d", obj_name(obj), 1049)); + end + end else begin + move_to(dude_obj, tile_under_cursor, dude_elevation); end //variable block := obj_blocking_line(Greeting_Watcher, tile_under_cursor, BLOCKING_TYPE_SHOOT); //display_msg(string_format("%s blocking: %s", obj_name_safe(Greeting_Watcher), obj_name_safe(block)));