-------------------------------- ----- Written by Darryl123 ----- -------------------------------- --[[ - Modified by Tronex - 2019/4/29 - NPC rank and rep progression by different stats --]] ------------------ ----- Tables ----- ------------------ -- Achievements unlocked. actor_achievements = { ["bookworm_food"] = { unlocked = false, rank = 1000, rept = 0 }, ["completionist"] = { unlocked = false, rank = 1000, rept = 0 }, ["down_to_earth"] = { unlocked = false, rank = 1000, rept = 0 }, ["duga_free"] = { unlocked = false, rank = 1000, rept = 0 }, ["geologist"] = { unlocked = false, rank = 1000, rept = 0 }, ["heavy_pockets"] = { unlocked = false, rank = 1000, rept = 0 }, ["infopreneur"] = { unlocked = false, rank = 1000, rept = 0 }, ["mechanized_warfare"] = { unlocked = false, rank = 1000, rept = 0 }, ["patriarch"] = { unlocked = false, rank = 1000, rept = 0 }, ["radiotherapy"] = { unlocked = false, rank = 1000, rept = 0 }, ["rag_and_bone"] = { unlocked = false, rank = 1000, rept = 0 }, ["silver_or_lead"] = { unlocked = false, rank = 1000, rept = 0 }, ["tourist"] = { unlocked = false, rank = 1000, rept = 0 }, ["well_dressed"] = { unlocked = false, rank = 1000, rept = 0 }, ["wishful_thinking"] = { unlocked = false, rank = 1000, rept = 0 }, ["infantile_pleasure"] = { unlocked = false, rank = 1000, rept = 0 }, ["recycler"] = { unlocked = false, rank = 1000, rept = 0 }, ["artificer_eagerness"] = { unlocked = false, rank = 1000, rept = 0 }, ["unforeseen_guest"] = { unlocked = false, rank = 1000, rept = 0 }, ["absolver"] = { unlocked = false, rank = 1000, rept = 0 }, ["collaborator"] = { unlocked = false, rank = 1000, rept = 0 }, ["iron_curtain"] = { unlocked = false, rank = 1000, rept = 0 }, ["murky_spirit"] = { unlocked = false, rank = 1000, rept = 0 }, ["invictus"] = { unlocked = false, rank = 1000, rept = 0 }, } -- Artefacts found and detected. -- Should be empty by default. actor_artefacts = { } -- Unlocked Anomaly maps. Should be empty by default. actor_anomaly_maps = { } -- Miscellaneous data that requires recording. actor_miscellaneous = { ["actual_rank"] = 0, ["actual_rept"] = 0, } -- Levels visited. actor_visited_levels = { ["jupiter"] = false, ["jupiter_underground"] = false, ["k00_marsh"] = false, ["k01_darkscape"] = false, ["k02_trucks_cemetery"] = false, ["l01_escape"] = false, ["l02_garbage"] = false, ["l03_agroprom"] = false, ["l03u_agr_underground"] = false, ["l04_darkvalley"] = false, ["l04u_labx18"] = false, ["l05_bar"] = false, ["l06_rostok"] = false, ["l07_military"] = false, ["l08_yantar"] = false, ["l08u_brainlab"] = false, ["l09_deadcity"] = false, ["l10_limansk"] = false, ["l10_radar"] = false, ["l10_red_forest"] = false, ["l10u_bunker"] = false, ["l11_hospital"] = false, ["l11_pripyat"] = false, ["l12_stancia"] = false, ["l12_stancia_2"] = false, ["l12u_control_monolith"] = false, ["l12u_sarcofag"] = false, ["l13_generators"] = false, ["l13u_warlab"] = false, ["labx8"] = false, ["pripyat"] = false, ["zaton"] = false, ["y04_pole"] = false, } -- Smart terrains visited. -- Should be empty by default. actor_visited_smarts = { } -- Tracked player statistics and their rank and reputation rewards. actor_statistics = { ["arena_battles"] = { count = 0, rank = 7, rept = 8 }, ["artefacts_detected"] = { count = 0, rank = 4, rept = 0 }, ["artefacts_found"] = { count = 0, rank = 2, rept = 0 }, ["articles"] = { count = 0, rank = 2, rept = 0 }, -- Counter is incremented but isn't used. ["boxes_smashed"] = { count = 0, rank = 1, rept = 0 }, ["deaths"] = { count = 0, rank = 0, rept = 0 }, ["donations"] = { count = 0, rank = 3, rept = 1 }, -- Actual points given depends on item condition. ["emissions"] = { count = 0, rank = 14, rept = 0 }, ["enemies_surrendered"] = { count = 0, rank = 4, rept = 0 }, ["field_dressings"] = { count = 0, rank = 1, rept = 0 }, ["helicopters_downed"] = { count = 0, rank = 100, rept = 25 }, ["helicopters_downed2"] = { count = 0, rank = 50, rept = 0 }, -- Bonus points for using explosives. ["killed_companions"] = { count = 0, rank = 0, rept = -75 }, ["killed_monsters"] = { count = 0, rank = 2, rept = 0 }, ["killed_prisoners"] = { count = 0, rank = 0, rept = -25 }, ["killed_stalkers"] = { count = 0, rank = 0, rept = 0 }, -- Unused; see below and the "rank_kill_points" config section. ["killed_stalkers_e"] = { count = 0, rank = 0, rept = 2 }, -- Enemy relations. ["killed_stalkers_f"] = { count = 0, rank = 0, rept = -100 }, -- Friendly relations. ["killed_stalkers_n"] = { count = 0, rank = 0, rept = -75 }, -- Neutral relations. ["level_changes"] = { count = 0, rank = 0, rept = 0 }, ["pdas_delivered"] = { count = 0, rank = 3, rept = 2 }, ["psi_storms"] = { count = 0, rank = 7, rept = 0 }, ["stashes_found"] = { count = 0, rank = 2, rept = 0 }, ["tasks_cancelled"] = { count = 0, rank = 0, rept = -25 }, ["tasks_completed"] = { count = 0, rank = 10, rept = 8 }, ["tasks_failed"] = { count = 0, rank = 0, rept = -25 }, ["wounded_helped"] = { count = 0, rank = 5, rept = 8 }, -- new ["items_crafted"] = { count = 0, rank = 1, rept = 0 }, ["items_disassembled"] = { count = 0, rank = 1, rept = 0 }, ["self_repairs"] = { count = 0, rank = 1, rept = 0 }, ["minutes_disguised"] = { count = 0, rank = 1, rept = 0 }, } -- Saved NPC statistics and their rank and reputation rewards. npc_statistics = { ["artefacts_found"] = { save = true, rank = 150, rept = 0 }, ["boxes_smashed"] = { save = false, rank = 10, rept = 0 }, ["field_dressings"] = { save = false, rank = 25, rept = 0 }, ["helicopters_downed"] = { save = false, rank = 2500, rept = 50 }, ["helicopters_downed2"] = { save = false, rank = 1000, rept = 50 }, ["killed_monsters"] = { save = true, rank = 100, rept = 0 }, ["killed_stalkers"] = { save = true, rank = 0, rept = 0 }, -- All ["killed_stalkers_e"] = { save = false, rank = 100, rept = 10 }, -- Enemy relations. ["killed_stalkers_f"] = { save = false, rank = 0, rept = -100 }, -- Friendly relations. ["killed_stalkers_n"] = { save = false, rank = 0, rept = -75 }, -- Neutral relations. ["corpse_looted"] = { save = true, rank = 10, rept = 0 }, ["wounded_helped"] = { save = true, rank = 50, rept = 100 }, ["items_sold"] = { save = true, rank = 10, rept = 0 }, } --------------------- ----- Callbacks ----- --------------------- -- Called when actor takes an item. function actor_on_item_take(item) if not (item and IsArtefact(item)) then return end -- Hack to prevent player from exploting Artefacts Containers (gaining rank by recieving artefacts) if _G.ARTY_FROM_CONT then _G.ARTY_FROM_CONT = nil return end local artefact = item:get_artefact() local anomaly = bind_anomaly_zone.parent_zones_by_artefact_id[item:id()] -- Artefacts found counter incrementation. -- Checks to make sure artefact id hasn't already been logged. if (artefact) then artefact:FollowByPath("NULL", 0, vector():set(500,500,500)) if not (actor_artefacts[item:id()]) then actor_artefacts[item:id()] = true increment_statistic("artefacts_found") else return end end -- Artefacts detected counter incrementation. -- Requires the artefact be associated with an anomaly. if (artefact and anomaly) then anomaly:on_artefact_take(item) increment_statistic("artefacts_detected") else bind_anomaly_zone.artefact_ways_by_id[item:id()] = nil end end -- Called when an NPC is killed. function npc_on_death_callback(victim, killer) -- Return if the actor is not the killer. if not (killer and victim) then return end local id = killer:id() local killer_faction = character_community(killer) local victim_faction = character_community(victim) --if not (killer:id() == AC_ID) then return end -- Increment statistics based on faction relations. local stat if (xr_conditions.is_factions_friends(nil, nil, { killer_faction, victim_faction })) then stat = "killed_stalkers_f" elseif (xr_conditions.is_factions_enemies(nil, nil, { killer_faction, victim_faction })) then stat = "killed_stalkers_e" else stat = "killed_stalkers_n" end if (id == AC_ID) then increment_statistic("killed_stalkers") increment_statistic(stat) else increment_npc_statistic(killer,"killed_stalkers") increment_npc_statistic(killer,stat) end end -- Called when an achievement is earned. function actor_on_achievement_earned(achievement, message) if (achievement and message) then news_manager.send_tip(db.actor, message, nil, achievement, nil, nil) if actor_achievements[achievement] then actor_achievements[achievement].unlocked = true if actor_achievements[achievement].rank then increment_rank( actor_achievements[achievement].rank ) end if actor_achievements[achievement].rept then increment_reputation( actor_achievements[achievement].rept ) end else actor_achievements[achievement] = {} actor_achievements[achievement].unlocked = true end end end -- Called when the game is loaded. function on_game_load() if (not IsTestMode()) then actor_visited_levels[level.name()] = true end if DEV_DEBUG and (not has_alife_info("debug_mode_flag_on")) then give_info("debug_mode_flag_on") printf("~ Player used debug mode!") end end -- Called when the game starts. function on_game_start() RegisterScriptCallback("actor_on_item_take", actor_on_item_take) RegisterScriptCallback("npc_on_death_callback", npc_on_death_callback) RegisterScriptCallback("actor_on_achievement_earned", actor_on_achievement_earned) RegisterScriptCallback("on_game_load", on_game_load) RegisterScriptCallback("on_level_changing", on_level_changing) RegisterScriptCallback("actor_on_interaction", actor_on_interaction) create_relations_tables() end -- Called when changing levels. function on_level_changing() if (not IsTestMode()) then increment_statistic("level_changes") end end ----------------------- ----- Incrementors ---- ----------------------- -- Increments actor rank value. -- Impose restrictions at double the minimum and maximum ranks' values. function increment_rank(value) db.actor:change_character_rank(value or 0) if (db.actor:character_rank() < 0) then db.actor:set_character_rank(0) end if (db.actor:character_rank() > 1000000000) then db.actor:set_character_rank(1000000000) end check_for_rank_change() end function increment_npc_rank(npc, value) if (not npc) then return end --npc:change_character_rank(value or 0) npc:set_character_rank(npc:character_rank() + (value or 0)) if (npc:character_rank() < 0) then npc:set_character_rank(0) end if (npc:character_rank() > 1000000000) then npc:set_character_rank(1000000000) end --printf("/ NPC stats | +Rank = %s | id: %s - name: %s", npc:character_rank(), npc:id(), npc:character_name()) end -- Increments actor reputation value. -- Impose restrictions at double the minimum and maximum reputations' values. function increment_reputation(value) -- Fix for arena causing reputation changes if has_alife_info("bar_arena_fight") then return end db.actor:change_character_reputation(value or 0) if (db.actor:character_reputation() < -4000) then db.actor:set_character_reputation(-4000) end if (db.actor:character_reputation() > 4000) then db.actor:set_character_reputation(4000) end check_for_reputation_change() end function increment_npc_reputation(npc,value) if (not npc) then return end --npc:change_character_reputation(value or 0) npc:set_character_reputation(npc:character_reputation() + (value or 0)) if (npc:character_reputation() < -4000) then npc:set_character_reputation(-4000) end if (npc:character_reputation() > 4000) then npc:set_character_reputation(4000) end --printf("/ NPC stats | +Rep = %s | id: %s - name: %s", npc:character_reputation(), npc:id(), npc:character_name()) end -- Increments a statistic counter. -- Also updates actor rank and reputation values. function increment_statistic(value, custom_rank, custom_rept) local statistic = value and actor_statistics[value] or nil if (statistic and statistic.count and statistic.rank and statistic.rept) then statistic.count = statistic.count + 1 increment_rank(custom_rank or statistic.rank) increment_reputation(custom_rept or statistic.rept) end end function increment_npc_statistic(npc, value, custom_rank, custom_rept) local statistic = value and npc_statistics[value] or nil if (npc and IsStalker(npc) and npc:alive() and statistic and statistic.rank and statistic.rept) then if statistic.save then local se_npc = alife_object(npc:id()) if se_npc then local m_data = alife_storage_manager.get_se_obj_state(se_npc,true) if (m_data) then m_data[value] = m_data[value] and m_data[value] + 1 or 1 end end end increment_npc_rank(npc, custom_rank or statistic.rank) increment_npc_reputation(npc, custom_rept or statistic.rept) end end ------------------------- ----- Miscellaneous ----- ------------------------- -- Gets the count for unlocked achievements. function get_actor_achievements_count() local count = 0 for k, v in pairs(actor_achievements) do if (v.unlocked == true) then count = count + 1 end end return count end -- Gets the count for visited levels. function get_actor_visited_levels_count() local count = 0 for k, v in pairs(actor_visited_levels) do if (v == true) then count = count + 1 end end return count end -- Gets the count for visited smarts. function get_actor_visited_smarts_count() local count = 0 for k, v in pairs(actor_visited_smarts) do if (v == true) then count = count + 1 end end return count end -- Gets the count value for a statistic. function get_statistic_count(value) local statistic = value and actor_statistics[value] or nil return (statistic and statistic.count) and statistic.count or 0 end -- Gets whether the actor has an achievement unlocked. function has_actor_achievement(value) local achievement = value and actor_achievements[value] return achievement and achievement.unlocked or false end -- Gets whether the actor has visited all levels. function has_actor_visitied_all_levels() for k, v in pairs (actor_visited_levels) do if (v == false) then return false end end return true end -- Gets whether the actor has visited a specific level. function has_actor_visited_level(value) return value and actor_visited_levels[value] or false end -- Gets whether the actor has visited a specific smart. function has_actor_visited_smart(value) return value and actor_visited_smarts[value] or false end -- Called when a smart terrain is visited. function actor_on_interaction(typ, obj, name) if (typ ~= "smarts") then return end actor_visited_smarts[name] = true end ----------------------------- ----- Rank & Reputation ----- ----------------------------- -- Creates the rank and reputation tables. local rank_table, rept_table function create_relations_tables() -- Check if the rank and reputation tables are initialised. if not (rank_table and rept_table) then -- Matches each rank and reputation to a value. local function parse_string(value) local result = {} for name in string.gmatch(value, "([%w_%-.\\]+)[%,%s]*") do result[#result + 1] = name end return result end -- Retrieve the rank and reputation values from configs. local ini = ini_file("creatures\\game_relations.ltx") local rank_table_temp = parse_string(ini:r_string_ex("game_relations", "rating")) local rept_table_temp = parse_string(ini:r_string_ex("game_relations", "reputation")) -- Fill the contents of the rank table. rank_table = {} for index = 2, #rank_table_temp, 2 do rank_table[#rank_table + 1] = tonumber(rank_table_temp[index]) end -- Fill the contents of the reputation table. rept_table = {} for index = 2, #rept_table_temp, 2 do rept_table[#rept_table + 1] = tonumber(rept_table_temp[index]) end end end -- Checks for changes the actor's rank. function check_for_rank_change(suppress) local current_rank = db.actor:character_rank() local new_rank, old_rank = 0, 0 for index = 1, #rank_table do if (current_rank <= rank_table[index]) then break end new_rank = new_rank + 1 end for index = 1, #rank_table do if (actor_miscellaneous.actual_rank <= rank_table[index]) then break end old_rank = old_rank + 1 end if (not suppress) then if (old_rank > new_rank) then news_manager.send_tip(db.actor, "st_rank_decreased", nil, "rank_change", nil, nil) elseif (new_rank > old_rank) then news_manager.send_tip(db.actor, "st_rank_increased", nil, "rank_change", nil, nil) end end actor_miscellaneous.actual_rank = current_rank end -- Checks for changes to the actor's reputation. function check_for_reputation_change(suppress) local current_rept = db.actor:character_reputation() local new_rept, old_rept = 0, 0 for index = 1, #rept_table do if (current_rept <= rept_table[index]) then break end new_rept = new_rept + 1 end for index = 1, #rept_table do if (actor_miscellaneous.actual_rept <= rept_table[index]) then break end old_rept = old_rept + 1 end if (not suppress) then if (old_rept > new_rept) then news_manager.send_tip(db.actor, "st_reputation_decreased", nil, "rep_change", nil, nil) elseif (new_rept > old_rept) then news_manager.send_tip(db.actor, "st_reputation_increased", nil, "rep_change", nil, nil) end end actor_miscellaneous.actual_rept = current_rept end ---------------------------- ----- Saving & Loading ----- ---------------------------- -- Loads the contents of the tables the regular way. -- Unimplemented as Call of Chernobyl doesn't use this. function load(packet) if (USE_MARSHAL) then return end end -- Loads the contents of the tables using MARSHAL. function load_state(data) -- If no saved data exists then return. if not (data.game_statistics) then return end -- See the comments in the save_state method as to how this works. actor_artefacts = data.game_statistics.actor_artefacts or actor_artefacts actor_anomaly_maps = data.game_statistics.actor_anomaly_maps or actor_anomaly_maps actor_miscellaneous = data.game_statistics.actor_miscellaneous or actor_miscellaneous actor_visited_levels = data.game_statistics.actor_visited_levels or actor_visited_levels actor_visited_smarts = data.game_statistics.actor_visited_smarts or actor_visited_smarts if (data.game_statistics.actor_achievements) then for k, v in pairs(data.game_statistics.actor_achievements) do if (actor_achievements[k]) then actor_achievements[k].unlocked = v end end end if (data.game_statistics.actor_statistics) then for k, v in pairs(data.game_statistics.actor_statistics) do if (actor_statistics[k]) then actor_statistics[k].count = v end end end end -- Saves the contents of the tables the regular way. -- Unimplemented as Call of Chernobyl doesn't use this. function save(packet) if (USE_MARSHAL) then return end end -- Saves the contents of the tables using MARSHAL. function save_state(data) -- Create a table to store saved data. if (not data.game_statistics) then data.game_statistics = {} end -- Save tables as they are if they only record true/false values. data.game_statistics.actor_artefacts = actor_artefacts data.game_statistics.actor_anomaly_maps = actor_anomaly_maps data.game_statistics.actor_miscellaneous = actor_miscellaneous data.game_statistics.actor_visited_levels = actor_visited_levels data.game_statistics.actor_visited_smarts = actor_visited_smarts -- Couple of things to be noted here. -- Rank and reputation values are stored in these tables as well, and we really don't want to save those. -- If we did, it would mean any changes to their values in this script file would be ignored by existing saves. -- However, saving the data this way also prevents any potential nil value crashes if new statistics and such are added. data.game_statistics.actor_achievements = {} for k, v in pairs(actor_achievements) do data.game_statistics.actor_achievements[k] = v.unlocked end data.game_statistics.actor_statistics = {} for k, v in pairs(actor_statistics) do data.game_statistics.actor_statistics[k] = v.count end end