Added New Mods and Profiles Folders

This is a complete rebuild of the modpack, with all new mods and updates for 1.5.3 of Anomaly.
This commit is contained in:
2025-01-14 05:07:53 -05:00
parent 85c665b107
commit 376b4b9689
21217 changed files with 546254 additions and 0 deletions
@@ -0,0 +1,2 @@
[dirty_water]:flask
[boiled_water]:flask
@@ -0,0 +1,105 @@
;Haruka fillable canteen------------------------------------------------
[empty_canteen]:identity_immunities
GroupControlSection = spawn_group
discovery_dependency =
class = II_ATTCH
$spawn = "food and drugs\energy_drink"
cform = skeleton
visual = dynamics\drink\drink_flask
kind = i_drink
description = st_empty_canteen_descr
cost = 1
inv_name = st_empty_canteen
inv_name_short = st_empty_canteen
inv_weight = 0.33
icons_texture = ui\ui_canteens
inv_grid_width = 1
inv_grid_height = 1
inv_grid_x = 0
inv_grid_y = 0
attach_angle_offset = 0.000000, 0.000000, 0.000000
attach_position_offset = 0.087266, -0.078540, 0.052360
attach_bone_name = bip01_r_hand
auto_attach = false
bone_name = bip01_r_hand
position_offset = 0.0,0.0,0.0
angle_offset = 1.570790,1.570790,3.92699
use1_allow_db = false
tier = 1
;-----------------------------------------------------------------------
[dirty_water]:booster_multi
$spawn = "food and drugs\energy_drink"
cform = skeleton
visual = dynamics\drink\drink_flask
kind = i_drink
description = st_dirty_water_descr
cost = 10
inv_name = st_dirty_water
inv_name_short = st_dirty_water
use_condition = true
script_binding = bind_item.bind
max_uses = 10
inv_weight = 2.0
empty_weight = 0.1
icons_texture = ui\ui_canteens
inv_grid_width = 1
inv_grid_height = 1
inv_grid_x = 0
inv_grid_y = 0
use1_functor =
use1_action_functor =
attach_angle_offset = 0.000000, 0.000000, 0.000000
attach_position_offset = 0.087266, -0.078540, 0.052360
attach_bone_name = bip01_r_hand
auto_attach = false
repair_part_bonus = 0.01
boost_time = 31
eat_alcohol = -0.06
eat_satiety = 0.0
eat_sleepiness = 0.0
eat_thirstiness = -0.85
eat_radiation = 0.0155
use_sound = interface\inv_drink_flask_2
tier = 1
;-----------------------------------------------------------------------
[boiled_water]:booster_multi
$spawn = "food and drugs\energy_drink"
cform = skeleton
visual = dynamics\drink\drink_flask
kind = i_drink
description = st_boiled_water_descr
cost = 10
inv_name = st_boiled_water
inv_name_short = st_boiled_water
use_condition = true
script_binding = bind_item.bind
max_uses = 10
inv_weight = 2.0
empty_weight = 0.1
icons_texture = ui\ui_canteens
inv_grid_width = 1
inv_grid_height = 1
inv_grid_x = 0
inv_grid_y = 0
use1_functor =
use1_action_functor =
attach_angle_offset = 0.000000, 0.000000, 0.000000
attach_position_offset = 0.087266, -0.078540, 0.052360
attach_bone_name = bip01_r_hand
auto_attach = false
repair_part_bonus = 0.01
boost_time = 31
eat_alcohol = -0.1
eat_satiety = 0.0
eat_sleepiness = 0.0
eat_thirstiness = -1.0
eat_radiation = 0.0075
use_sound = interface\inv_drink_flask_2
tier = 1
@@ -0,0 +1,3 @@
![food_toxicity]
dirty_water = 1, 2
boiled_water = 1, 1
@@ -0,0 +1,2 @@
![supplies_1]
purification_tablet = 3, 1
@@ -0,0 +1,2 @@
![supplies_1]
purification_tablet = 3, 1
@@ -0,0 +1,55 @@
![flask]
max_uses = 10
inv_weight = 2.0
empty_weight = 0.1
icons_texture = ui\ui_canteens
inv_grid_width = 1
inv_grid_height = 1
inv_grid_x = 0
inv_grid_y = 0
use1_functor =
use1_action_functor =
boost_time = 15 ;31
boost_radiation_restore = 0.00014 ;0.00007
eat_alcohol = -0.15
eat_satiety = 0.005
eat_sleepiness = -0.01
eat_thirstiness = -1.15
[purification_tablet]:booster_multi
$spawn = "food and drugs\antirad"
visual = dynamics\equipments\medical\item_akvatab.ogf
kind = i_medical
description = st_akvatab2_descr
cost = 600
inv_name = st_akvatab2
inv_name_short = st_akvatab2
use_condition = true
script_binding = bind_item.bind
max_uses = 6
empty_weight = 0.001
inv_weight = 0.007
icons_texture = ui\ui_canteens
inv_grid_width = 1
inv_grid_height = 1
inv_grid_x = 4
inv_grid_y = 0
upgr_icon_path = ui\actor_items\ui_actor_items_2
upgr_icon_x = 150
upgr_icon_y = 700
upgr_icon_width = 100
upgr_icon_height = 100
remove_after_use = false
;boost_time = 10
;boost_radiation_restore = 0.000056
;eat_alcohol = -0.21
;eat_satiety = 0.202
;eat_sleepiness = -0.062
;eat_thirstiness = 0.2
;use_sound = interface\inv_eat_pills
snd_on_take = pills
tier = 4
@@ -0,0 +1,40 @@
[tch_prop_water_pump]:tch_junk
class = II_ATTCH
kind = i_tool
visual = dynamics\efp_props\prop_water_pump.ogf
description = st_prop_water_pump_descr
inv_name = st_prop_water_pump
inv_name_short = st_prop_water_pump
icons_texture = ui\ui_water_pump
inv_grid_x = 0
inv_grid_y = 0
inv_grid_width = 2
inv_grid_height = 2
cost = 1000
inv_weight = 1.0
use1_functor = placeable_furniture.place_item
use1_action_functor = placeable_furniture.func_place_item
placeable_type = prop
placeable_section = placeable_water_pump
[placeable_water_pump]:physic_object
visual = dynamics\efp_props\prop_water_pump.ogf
placeable_type = prop
base_rotation = 0
script_binding = bind_water_pump_furniture.init
item_section = tch_prop_water_pump
ui_texture = ui_efp_water_pump
bounding_box_size = 0.750898, 0.751868, 0.112984
bounding_box_origin = 0, 0.055049, 0
ui_on_simple_interaction = bind_water_pump_furniture.on_simple_interaction
fixed_bones = link1, link2, link3, link4, link5, link6, link7, link8
@@ -0,0 +1,2 @@
[dirty_water]:flask
[boiled_water]:flask
@@ -0,0 +1,24 @@
![jupiter]
water_pump_Jupiter_EcologB = placeable_water_pump, -207.80764770508, -3.7818555831909, 70.726493835449, 364988, 4486, 0, -0.078100934624672, -0
![k02_trucks_cemetery]
water_pump_TruckC_Bandit = placeable_water_pump, 221.98561096191, 7.857373714447, 318.55191040039, 730418, 5167, 0, -0.67875927686691, -0
![l01_escape]
water_pump_Cordon_Farm = placeable_water_pump, -18.613451004028, 0.71599459648132, 273.91046142578, 276886, 632, 0, -0.35538795590401, -0
![l05_bar]
water_pump_Rostok_Duty = placeable_water_pump, 197.68939208984, -2.6583671569824e-05, 101.35947418213, 48737, 1693, 0, 1.5761995315552, -0
![l07_military]
water_pump_ArmyW_BloodSV = placeable_water_pump, -326.11465454102, -22.582513809204, 230.07440185547, 20977, 2022, 0, -1.6397918462753, -0
![l09_deadcity]
water_pump_DeadC_Merc1 = placeable_water_pump, -152.43914794922, 1.0796680450439, -105.94775390625, 45153, 2411, 0, -0.053645428270102, -0
water_pump_DeadC_Merc2 = placeable_water_pump, 168.40650939941, 3.6379494667053, -115.36225128174, 238076, 2329, 0, 3.0618057250977, -0
![l10_limansk]
water_pump_Linmask_House1 = placeable_water_pump, -14.138078689575, -4.9838800430298, -125.83104705811, 17191, 2452, 0, -1.4782202243805, -0
![l10_red_forest]
water_pump_RedF_Forester = placeable_water_pump, 10.47575378418, 0.089358687400818, -37.425666809082, 71813, 2768, 0, -3.0728776454926, -0
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="windows-1251"?>
<string_table>
<string id="st_haruka_roast_unlit_campfire">
<text>You can not use an unlit campfire</text>
</string>
<string id="st_roast_all">
<text>roast all</text>
</string>
<string id="st_boil_all">
<text>boil all</text>
</string>
<string id="st_boil">
<text>boil</text>
</string>
<string id="st_roast">
<text>roast</text>
</string>
</string_table>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="windows-1251"?>
<string_table>
<string id="st_empty_canteen">
<text>Empty flask</text>
</string>
<string id="st_empty_canteen_descr">
<text>This empty flask is not much to look at. You have a strange feeling that throwing it away would improve the aesthetics somewhat; might as well fill it with something first. \n \n</text>
</string>
<string id="st_dirty_water">
<text>Dirty water</text>
</string>
<string id="st_dirty_water_descr">
<text>Military flask filled with dirty water. You can purify it with purifying pills or by boiling. \n \n
%c[ui_gray_3]PROPERTIES:\n
%c[d_blue] • %c[ui_gray_2] drink\n</text>
</string>
<string id="st_boiled_water">
<text>Boiled water</text>
</string>
<string id="st_boiled_water_descr">
<text>Military flask filled with boiled water. You can purify it with purifying pills. \n \n
%c[ui_gray_3]PROPERTIES:\n
%c[d_blue] • %c[ui_gray_2] drink\n</text>
</string>
<string id="st_haruka_fill">
<text>fill</text>
</string>
<string id="st_akvatab2">
<text>Purifying Pills</text>
</string>
<string id="st_akvatab2_descr">
<text>Small plastic bag with pills intended for disinfecting water containing harmful bacteria and viruses. Use two tablets at a time.\n \n
%c[ui_gray_3]PROPERTIES:\n
%c[d_red] • %c[ui_gray_2] slow usage (dispersible tablets)\n
</text>
</string>
</string_table>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="windows-1251"?>
<string_table>
<string id="st_water_pump_spot">
<text>Water resource</text>
</string>
<string id="st_water_pump_fill">
<text>Fill canteen ($$ACTION_USE$$)</text>
</string>
<string id="st_water_pump_no_canteen">
<text>You need an empty canteen to get water</text>
</string>
</string_table>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="windows-1251"?>
<string_table>
<string id="st_haruka_roast_unlit_campfire">
<text> </text>
</string>
<string id="st_roast_all">
<text> </text>
</string>
<string id="st_boil_all">
<text> </text>
</string>
<string id="st_boil">
<text></text>
</string>
<string id="st_roast">
<text></text>
</string>
</string_table>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="windows-1251"?>
<string_table>
<string id="st_empty_canteen">
<text>Ïóñòàÿ ôëÿãà</text>
</string>
<string id="st_empty_canteen_descr">
<text>Ýòà ïóñòàÿ ôëÿãà íå ïðåäñòàâëÿåò èç ñåáÿ íè÷åãî îñîáåííîãî. Âàì äàæå êàæåòñÿ, ÷òî âûáðîñèâ å¸ ïîäàëüøå, âû íåñêîëüêî îáëàãîðîäèòå îáñòàíîâêó. Âïðî÷åì, ïðè æåëàíèè å¸ ìîæíî ÷åì-íèáóäü íàïîëíèòü. \n \n</text>
</string>
<string id="st_dirty_water">
<text>Ãðÿçíàÿ âîäà</text>
</string>
<string id="st_dirty_water_descr">
<text>Âîåííàÿ ôëÿãà, íàïîëíåííàÿ ãðÿçíîé âîäîé. Ïðè æåëàíèè ìîæíî äåçèíôèöèðîâàòü, áðîñèâ ïàðó «Î÷èùàþùèõ òàáëåòîê» èëè âñêèïÿòèòü íà êîñòðå. \n \n
%c[ui_gray_3]ÕÀÐÀÊÒÅÐÈÑÒÈÊÈ:\n
%c[d_blue] • %c[ui_gray_2] íàïèòîê\n</text>
</string>
<string id="st_boiled_water">
<text>Êèïÿ÷åíàÿ âîäà</text>
</string>
<string id="st_boiled_water_descr">
<text>Âîåííàÿ ôëÿãà, íàïîëíåííàÿ êèïÿ÷åíîé âîäîé. Ïðè æåëàíèè ìîæíî äåçèíôèöèðîâàòü, áðîñèâ ïàðó «Î÷èùàþùèõ òàáëåòîê». \n \n
%c[ui_gray_3]ÕÀÐÀÊÒÅÐÈÑÒÈÊÈ:\n
%c[d_blue] • %c[ui_gray_2] íàïèòîê\n</text>
</string>
<string id="st_akvatab2">
<text>Î÷èùàþùèå òàáëåòêè</text>
</string>
<string id="st_akvatab2_descr">
<text>Íåáîëüøîé ïëàñòèêîâûé ïàêåòèê ñ òàáëåòêàìè, ïðåäíàçíà÷åííûé äëÿ îáåççàðàæèâàíèÿ âîäû, ñîäåðæàùåé âðåäíûå áàêòåðèè è âèðóñû. Èñïîëüçóéòå ïî äâå òàáëåòêè çà ðàç.\n \n
%c[ui_gray_3]ÕÀÐÀÊÒÅÐÈÑÒÈÊÈ:\n
%c[d_red] • %c[ui_gray_2] ìåäëåííîå ïðèìåíåíèå\n
</text>
</string>
<string id="st_haruka_fill">
<text>íàïîëíèòü</text>
</string>
</string_table>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="windows-1251"?>
<string_table>
<string id="st_water_pump_spot">
<text>Òî÷êà ïîïîëíåíèÿ âîäû</text>
</string>
<string id="st_water_pump_fill">
<text>Íàïîëíèòü ôëÿãó ($$ACTION_USE$$)</text>
</string>
<string id="st_water_pump_no_canteen">
<text>Âàì íóæíà ïóñòàÿ ôëÿãà ÷òîáû íàáðàòü âîäû</text>
</string>
</string_table>
@@ -0,0 +1,5 @@
<w>
<file name="ui\ui_maid_water_spot">
<texture id="ui_maid_water_spot" x="0" y="0" width="17" height="17" />
</file>
</w>
@@ -0,0 +1,126 @@
-------------------------------------------------------
--Arszi's Campfire Roasting
--By Arszi
--Last update 2020.09.21.
--[[
Edited by HarukaSai for EFP
- Refactoring
- Roast\Boil all
- Usage of custom functor autoinject
- Removed pass of time
11-02-2022
]]
-------------------------------------------------------
foods = {
["mutant_part_chimera_meat"] = "meat_chimera",
["mutant_part_psysucker_meat"] = "meat_psysucker",
["mutant_part_krovosos_meat"] = "meat_bloodsucker",
["mutant_part_psevdodog_meat"] = "meat_pseudodog",
["mutant_part_lurker_meat"] = "meat_lurker",
["mutant_part_boar_chop"] = "meat_boar",
["mutant_part_flesh_meat"] = "meat_flesh",
["mutant_part_dog_meat"] = "meat_dog",
["mutant_part_snork_hand"] = "meat_snork",
["mutant_part_tushkano_meat"] = "meat_tushkano",
["dirty_water"] = "boiled_water"
}
local dist = 10
function use_roast(item)
if not (can_use_campfire()) then return end
local section = item:section()
if (foods[section]) then
if (IsItem("multiuse", nil, item)) then
manage_food(foods[section], item, item:get_remaining_uses())
else
manage_food(foods[section], item)
end
end
xr_effects.play_inv_cooking_stove()
end
function use_roast_all(item)
if not (can_use_campfire()) then return end
local section = item:section()
db.actor:iterate_inventory(function(owner, obj)
local sec = obj and obj:section()
if (foods[sec] and sec == section) then
if (IsItem("multiuse", nil, obj)) then
manage_food(foods[section], obj, obj:get_remaining_uses())
else
manage_food(foods[section], obj)
end
end
end, db.actor)
actor_effects.play_item_fx("cooking_dummy")
end
function manage_food(food_to_create, mutant_part_to_remove, uses)
alife_create_item(food_to_create, db.actor, {["uses"] = uses})
alife_release(mutant_part_to_remove)
end
function can_use_campfire()
--Hide inventory
hide_hud_all()
--Check for lit campfire
if (not get_nearby_lit_campfire()) then
actor_menu.set_msg(1, game.translate_string("st_haruka_roast_unlit_campfire"), 4)
return false
end
return true
end
function get_nearby_lit_campfire()
local pos = db.actor:position()
for id,binder in pairs(bind_campfire.campfires_all) do
if (binder and binder.campfire and binder.campfire:is_on()) then
if (pos:distance_to_sqr(binder.object:position()) <= dist) then
return true
end
end
end
return false
end
function roast_condition_function(obj, bag, mode)
local campfire = bind_campfire.get_nearby_campfire(dist, result)
if (campfire) then
if foods[obj:section()] then
return true
end
end
end
function roast_name_function(obj, bag, mode)
if obj:section() == "dirty_water" then
return game.translate_string("st_boil")
else
return game.translate_string("st_roast")
end
end
function roast_all_name_function(obj, bag, mode)
if obj:section() == "dirty_water" then
return game.translate_string("st_boil_all")
else
return game.translate_string("st_roast_all")
end
end
local add_functor = custom_functor_autoinject.add_functor
add_functor("haruka_roast", roast_condition_function, roast_name_function, nil, use_roast)
add_functor("haruka_roast_all", roast_condition_function, roast_all_name_function, nil, use_roast_all)
@@ -0,0 +1,41 @@
--[[
Functionality for water pump furniture
This work is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0).
Author: HarukaSai (visit us at: https://discord.gg/efp)
19-06-2023
]]
function init(obj)
obj:bind_object(placeable_pump_binder_wrapper(obj).binder)
end
class "placeable_pump_binder_wrapper" (bind_hf_base.hf_binder_wrapper)
function placeable_pump_binder_wrapper:__init(obj) super(obj) end
function placeable_pump_binder_wrapper:update(delta)
bind_hf_base.hf_binder_wrapper.update(self, delta)
self.object:set_tip_text(game.translate_string(db.actor:object("empty_canteen") and "st_water_pump_fill" or "st_water_pump_no_canteen"))
end
function placeable_pump_binder_wrapper:net_spawn()
bind_hf_base.hf_binder_wrapper.net_spawn(self)
level.map_add_object_spot(self.object:id(), "water_spot", "st_water_pump_spot")
end
function placeable_pump_binder_wrapper:net_destroy()
bind_hf_base.hf_binder_wrapper.net_destroy(self)
level.map_remove_object_spot(id, "water_spot")
end
function on_simple_interaction()
local canteen = db.actor:object("empty_canteen")
if canteen then
haruka_fill.fill(canteen)
end
end
@@ -0,0 +1,538 @@
--[[
Custom Dynamic Functors, written by demonized
Allows to add/remove item functors dynamically from script
Can override ltx-defined functors and can remove the override to return back to ltx functor
To use in your script look at example below
---------------------------------------------------------------------------------------------------------------------
local function name_condition_function(obj, bag, mode)
if obj then
return true
end
end
local function name_function(obj, bag, mode)
return "st_my_functor_string_in_xml"
end
local function action_condition_function(obj, bag, mode)
if obj then
return true
end
end
local function action_function(obj, bag, mode)
alife_create_item(obj:section(), db.actor)
end
local add_functor = custom_functor_autoinject.add_functor
add_functor("my_name_of_functor", name_condition_function, name_function, action_condition_function, action_function, override_bags<true, false>)
---------------------------------------------------------------------------------------------------------------------
to add your functor you use add_functor function which requires these arguments in order:
name: string, your name of your functor, can be any string.
If the name of functor already exists this function will overwrite functions for it
name_condition_function: function, the condition at which you will get your right-click option for item
name_function: function, the name itself, must return string ID defined in XML files for your option.
if set to return nil, the option won't appear
action_condition_function: function, the condition at which the action will be performed, usually its the same as name_condition_function
you can put nil into argument to use the same function as name_condition_function
action_function: function, the action itself to perform
override_bags: boolean. If its true, then you can override bags and modes to check for condition, otherwise it will use defaults (mode == "inventory" and bag == "actor_bag" or bag == "actor_equ" or bag == "actor_belt")
functions themselves accept obj, bag and mode arguments
obj: current object you right-clicked
bag: current bag, list of possible bags: {"actor_equ","actor_belt","actor_bag","actor_trade_bag","actor_trade","npc_bag","npc_trade","npc_trade_bag"}
mode: current mode, list of possible modes: {"inventory" , "loot" , "trade" , "repair"}
bag and mode is not enabled unless you set override_bags flag to true
removal of functor is done by calling this
--------------------------------------------------------------------------------------
local remove_functor = custom_functor_autoinject.remove_functor
remove_functor("my_name_of_functor")
--------------------------------------------------------------------------------------
you can also override existing functors defined in item's ltx by using this
---------------------------------------------------------------------------------------------------------------------
local override_functor = custom_functor_autoinject.override_functor
override_functor(slot, name_condition_function, name_function, action_condition_function, action_function, override_bags<true, false>)
---------------------------------------------------------------------------------------------------------------------
arguments are same, except you have to provide the slot (first argument) for your override. The slots are 1-10
for example if item's ltx have use1_functor and use1_action_functor then
if you want to override it you have to provide slot 1
be aware that if your name_condition_function or action_condition_function may return false
then old functor will be fired
if you don't want that behaviour, you can define generic "return true" function for those
and check condition for firing in name_function and action_function
removal of override is done by calling this
--------------------------------------------------------------------------------------
local remove_override = custom_functor_autoinject.remove_override
remove_override(slot)
--------------------------------------------------------------------------------------
--]]
local function func_index(t,a,b)
return (t[a].index) < (t[b].index)
end
local function func_index_reverse(t,a,b)
return (t[a].index) > (t[b].index)
end
local function func_value(t, a, b)
return t[a] < t[b]
end
local spairs = spairs
local string_find = string.find
local string_gsub = string.gsub
local table_remove = table.remove
local tonumber = tonumber
local unpack = unpack
--Recursive print of tables similar to PHP print_r function
local function print_r(t)
local print_r_cache={}
local function sub_print_r(t,indent)
if (print_r_cache[tostring(t)]) then
printf(indent.."*"..tostring(t))
else
print_r_cache[tostring(t)]=true
if (type(t)=="table") then
for pos,val in pairs(t) do
if (type(val)=="table") then
printf(indent.."["..pos.."] => "..tostring(t).." {")
sub_print_r(val,indent..string.rep(" ",string.len(pos)+8))
printf(indent..string.rep(" ",string.len(pos)+6).."}")
else
printf(indent.."["..pos.."] => "..tostring(val))
end
end
else
printf(indent..tostring(t))
end
end
end
sub_print_r(t," ")
end
local function print_table(table, subs)
local sub
if subs ~= nil then
sub = subs
else
sub = ""
end
for k,v in pairs(table) do
if type(v) == "table" then
print_table(v, sub.."["..k.."]----->")
elseif type(v) == "function" then
printf(sub.."%s = function",k)
elseif type(v) == "userdata" then
if (v.x) then
printf(sub.."%s = %s",k,utils_data.vector_to_string(v))
else
printf(sub.."%s = userdata", k)
end
elseif type(v) == "boolean" then
if v == true then
if(type(k)~="userdata") then
printf(sub.."%s = true",k)
else
printf(sub.."userdata = true")
end
else
if(type(k)~="userdata") then
printf(sub.."%s = false", k)
else
printf(sub.."userdata = false")
end
end
else
if v ~= nil then
printf(sub.."%s = %s", k,v)
else
printf(sub.."%s = nil", k,v)
end
end
end
end
-- Removing element from table and shifting down both key and value
-- Modes: 0 - index, 1 - value, 2 - key, 3 - key-index
local function table_remove_shift(t, val, mode)
local removed = false
local res
if mode == 0 then
for k, v in spairs(t, func_index) do
if removed then
t[k - 1] = v
if t[k - 1].index then
t[k - 1].index = t[k - 1].index - 1
end
if t[k - 1].properties_index then
t[k - 1].properties_index = t[k - 1].properties_index - 1
end
t[k] = nil
elseif v.index and v.index == val then
res = t[k]
t[k] = nil
removed = true
end
end
elseif mode == 1 then
for k, v in spairs(t, func_value) do
if removed then
t[k - 1] = v - 1
t[k] = nil
elseif v == val then
res = t[k]
t[k] = nil
removed = true
end
end
elseif mode == 2 then
for k, v in spairs(t, func_value) do
if removed then
t[k] = v - 1
elseif v == val then
res = t[k]
t[k] = nil
removed = true
end
end
elseif mode == 3 then
for k, v in spairs(t, func_index) do
if removed then
if t[k].index then
t[k].index = t[k].index - 1
end
elseif v.index and v.index == val then
res = t[k]
t[k] = nil
removed = true
end
end
end
return res
end
local ui_inventory_init = ui_inventory.UIInventory.__init
ui_inventory.UIInventory.__init = function(self)
ui_inventory_init(self)
self.custom_functor = {}
self.custom_functor_names = {}
end
local NameCustom = ui_inventory.UIInventory.Name_Custom
function ui_inventory.UIInventory:Name_Custom(obj, bag, temp, i)
obj = self:CheckItem(obj,"Name_Custom " .. i)
if self.custom_functor[i] and self.custom_functor[i].cond_name(obj, bag, self.mode) then
return self.custom_functor[i].func_name(obj, bag, self.mode)
else
return NameCustom(self, obj, bag, temp, i)
end
end
local ActionCustom = ui_inventory.UIInventory.Action_Custom
function ui_inventory.UIInventory:Action_Custom(obj, bag, temp, i)
obj = self:CheckItem(obj,"Action_Custom " .. i)
if self.custom_functor[i] and self.custom_functor[i].cond_action(obj, bag, self.mode) then
return self.custom_functor[i].func_action(obj, bag, self.mode)
else
return ActionCustom(self, obj, bag, temp, i)
end
end
ui_inventory.UIInventory.get_max_custom_functor = function(self)
local max = 0
local max_custom = {}
local max_index = 0
for k, v in pairs(self.properties) do
if string_find(k, "custom_.*") then
if v.index > max_index then
max_index = v.index
end
local x = tonumber(string_gsub(k, "custom_(.*)", "%1"), nil)
if x > max then
max = x
max_custom = v
end
end
end
return max, max_custom, max_index
end
local modes = {
["inventory"] = true,
["loot"] = true,
["trade"] = true,
["repair"] = true
}
local bags = {
["actor_equ"] = true,
["actor_belt"] = true,
["actor_bag"] = true,
["actor_trade_bag"] = true,
["actor_trade"] = true,
["npc_bag"] = true,
["npc_trade"] = true,
["npc_trade_bag"] = true
}
ui_inventory.UIInventory.Mode_Custom_Functor = function(self, obj, bag, temp, i)
return modes[self.mode]
end
ui_inventory.UIInventory.Cont_Custom_Functor = function(self, obj, bag, temp, i)
return bags[bag]
end
ui_inventory.UIInventory.add_custom_functor = function(self, name, cond_name, func_name, cond_action, func_action, override_bags)
local custom_functor_slot
if self.custom_functor_names[name] then
custom_functor_slot = self.custom_functor_names[name]
self.custom_functor[custom_functor_slot].cond_name = cond_name
self.custom_functor[custom_functor_slot].func_name = func_name
self.custom_functor[custom_functor_slot].cond_action = cond_action
self.custom_functor[custom_functor_slot].func_action = func_action
self.properties["custom_" .. custom_functor_slot].mode_func[1] = override_bags and "Mode_Custom_Functor" or "Mode_Custom"
self.properties["custom_" .. custom_functor_slot].cont_func[1] = override_bags and "Cont_Custom_Functor" or "Cont_Custom"
else
local max, max_custom, max_index = self:get_max_custom_functor()
local custom_num = max + 1
local properties_num = max_index + 1
for k, v in spairs(self.properties, func_index_reverse) do
if v.index > max_index then
printf("%s, %s", k, v.index)
v.index = v.index + 1
else
printf("%s, %s, max custom_functor reached", k, v.index)
break
end
end
self.properties["custom_" .. custom_num] = {
index = properties_num,
name_func = {"Name_Custom", max_custom["name_func"][2] + 1},
mode_func = {override_bags and "Mode_Custom_Functor" or "Mode_Custom", max_custom["mode_func"][2] + 1},
cont_func = {override_bags and "Cont_Custom_Functor" or "Cont_Custom", max_custom["cont_func"][2] + 1},
precondition1 = {"Name_Custom", max_custom["precondition1"][2] + 1},
action = {"Action_Custom", max_custom["action"][2] + 1}
}
custom_functor_slot = custom_num
self.custom_functor[custom_functor_slot] = {
index = custom_functor_slot,
properties_index = properties_num,
name = name,
cond_name = cond_name,
func_name = func_name,
cond_action = cond_action,
func_action = func_action
}
self.custom_functor_names[name] = custom_functor_slot
end
end
ui_inventory.UIInventory.remove_custom_functor = function(self, name)
if not self.custom_functor_names[name] then return end
local index = table_remove_shift(self.custom_functor_names, self.custom_functor_names[name], 2)
if not index then return end
local custom_functor = table_remove_shift(self.custom_functor, index, 0)
if not custom_functor then return end
local removed = false
for k, v in spairs(self.properties, func_index) do
if string_find(k, "custom_.*") then
if removed then
local x = tonumber(string_gsub(k, "custom_(.*)", "%1"), nil) - 1
self.properties["custom_" .. x] = self.properties[k]
self.properties["custom_" .. x].index = self.properties["custom_" .. x].index - 1
self.properties["custom_" .. x].name_func[2] = self.properties["custom_" .. x].name_func[2] - 1
self.properties["custom_" .. x].mode_func[2] = self.properties["custom_" .. x].mode_func[2] - 1
self.properties["custom_" .. x].cont_func[2] = self.properties["custom_" .. x].cont_func[2] - 1
self.properties["custom_" .. x].precondition1[2] = self.properties["custom_" .. x].precondition1[2] - 1
self.properties["custom_" .. x].action[2] = self.properties["custom_" .. x].action[2] - 1
self.properties[k] = nil
elseif v.index == custom_functor.properties_index then
self.properties[k] = nil
removed = true
end
elseif removed then
v.index = v.index - 1
end
end
end
ui_inventory.UIInventory.override_functor = function(self, slot, cond_name, func_name, cond_action, func_action, override_bags)
self.custom_functor[slot] = {
cond_name = cond_name,
func_name = func_name,
cond_action = cond_action,
func_action = func_action
}
self.properties["custom_" .. slot].mode_func[1] = override_bags and "Mode_Custom_Functor" or "Mode_Custom"
self.properties["custom_" .. slot].cont_func[1] = override_bags and "Cont_Custom_Functor" or "Cont_Custom"
end
ui_inventory.UIInventory.remove_override = function(self, slot)
self.custom_functor[slot] = nil
self.properties["custom_" .. slot].mode_func[1] = "Mode_Custom"
self.properties["custom_" .. slot].cont_func[1] = "Cont_Custom"
end
ui_inventory.UIInventory.get_functor_at_slot = function(self, slot)
return self.custom_functor[slot]
end
ui_inventory.UIInventory.get_functor_by_name = function(self, name)
if self.custom_functor_names[name] then
return self.custom_functor[self.custom_functor_names[name]], self.custom_functor_names[name]
end
end
-- Adding
local first_update_pending = true
local functor_queue = {}
local function add_to_queue(type, ...)
functor_queue[#functor_queue + 1] = {
type = type,
data = {...}
}
end
local function process_queue()
if not ui_inventory.GUI then
ui_inventory.GUI = ui_inventory.UIInventory()
end
printf("Custom functors, processing queue")
for i, v in ipairs(functor_queue) do
printf("Custom functors, type %s", v.type)
ui_inventory.GUI[v.type](ui_inventory.GUI, unpack(v.data))
end
functor_queue = {}
first_update_pending = false
end
local function actor_on_first_update()
process_queue()
end
function add_functor(name, cond_name, func_name, cond_action, func_action, override_bags)
if not name then return end
local cond_action = cond_action or cond_name
if first_update_pending then
add_to_queue("add_custom_functor", name, cond_name, func_name, cond_action, func_action, override_bags)
return
end
ui_inventory.GUI:add_custom_functor(name, cond_name, func_name, cond_action, func_action, override_bags)
end
function remove_functor(name)
if not name then return end
if first_update_pending then
add_to_queue("remove_custom_functor", name)
return
end
ui_inventory.GUI:remove_custom_functor(name)
end
function override_functor(slot, cond_name, func_name, cond_action, func_action, override_bags)
if slot < 1 or slot > 10 then
printf("functor slot is not valid, min 1, max 10, your slot: %s", slot)
return
end
local cond_action = cond_action or cond_name
if first_update_pending then
add_to_queue("override_functor", slot, cond_name, func_name, cond_action, func_action, override_bags)
return
end
ui_inventory.GUI:override_functor(slot, cond_name, func_name, cond_action, func_action, override_bags)
end
function remove_override(slot)
if slot < 1 or slot > 10 then
printf("functor slot is not valid min 1, max 10, your slot: %s", slot)
return
end
if first_update_pending then
add_to_queue("remove_override", slot)
return
end
ui_inventory.GUI:remove_override(slot)
end
function get_functor_at_slot(slot)
if first_update_pending then
add_to_queue("get_functor_at_slot", slot)
return
end
return ui_inventory.GUI:get_functor_at_slot(slot)
end
function get_functor_by_name(name)
if first_update_pending then
add_to_queue("get_functor_by_name", name)
return
end
return ui_inventory.GUI:get_functor_by_name(name)
end
function print_properties()
if not ui_inventory.GUI then
ui_inventory.GUI = ui_inventory.UIInventory()
end
for k, v in spairs(ui_inventory.GUI.properties, func_index) do
printf("[" .. k .. "] => ")
print_r(v)
end
end
function print_custom_functor()
if not ui_inventory.GUI then
ui_inventory.GUI = ui_inventory.UIInventory()
end
print_r(ui_inventory.GUI.custom_functor)
end
function print_custom_functor_names()
if not ui_inventory.GUI then
ui_inventory.GUI = ui_inventory.UIInventory()
end
print_r(ui_inventory.GUI.custom_functor_names)
end
function on_game_start()
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
end
@@ -0,0 +1,351 @@
local enable_debug = false
local print_tip = function(s, ...)
local f = print_tip or printf
if enable_debug then
return f("Geometry Ray: " .. s, ...)
end
end
local function throttle(func, tg_throttle)
local tg = 0
if not tg_throttle or tg_throttle == 0 then
return function(...)
local t = time_global()
if t ~= tg then
tg = t
return func(...)
end
end
else
return function(...)
local t = time_global()
if t < tg then return end
tg = t + tg_throttle
return func(...)
end
end
end
-- Check for material (engine edit required)
function lshift(x, by)
return x * 2 ^ by
end
function test(x, mask)
return bit_and(x, mask) == mask
end
local flags_test = {
["flBreakable"] = lshift(1, 0),
["flBounceable"] = lshift(1, 2),
["flSkidmark"] = lshift(1, 3),
["flBloodmark"] = lshift(1, 4),
["flClimable"] = lshift(1, 5),
["flPassable"] = lshift(1, 7),
["flDynamic"] = lshift(1, 8),
["flLiquid"] = lshift(1, 9),
["flSuppressShadows"] = lshift(1, 10),
["flSuppressWallmarks"] = lshift(1, 11),
["flActorObstacle"] = lshift(1, 12),
["flNoRicoshet"] = lshift(1, 13),
["flInjurious"] = lshift(1, 28),
["flShootable"] = lshift(1, 29),
["flTransparent"] = lshift(1, 30),
["flSlowDown"] = lshift(1, 31),
}
--[[
// material exports
.def_readonly( "material_name" , &script_rq_result::pMaterialName )
.def_readonly( "material_flags" , &script_rq_result::pMaterialFlags )
.def_readonly( "material_phfriction" , &script_rq_result::fPHFriction )
.def_readonly( "material_phdamping" , &script_rq_result::fPHDamping )
.def_readonly( "material_phspring" , &script_rq_result::fPHSpring )
.def_readonly( "material_phbounce_start_velocity" , &script_rq_result::fPHBounceStartVelocity )
.def_readonly( "material_phbouncing" , &script_rq_result::fPHBouncing )
.def_readonly( "material_flotation_factor" , &script_rq_result::fFlotationFactor )
.def_readonly( "material_shoot_factor" , &script_rq_result::fShootFactor )
.def_readonly( "material_shoot_factor_mp" , &script_rq_result::fShootFactorMP )
.def_readonly( "material_bounce_damage_factor" , &script_rq_result::fBounceDamageFactor )
.def_readonly( "material_injurious_speed" , &script_rq_result::fInjuriousSpeed )
.def_readonly( "material_vis_transparency_factor" , &script_rq_result::fVisTransparencyFactor )
.def_readonly( "material_snd_occlusion_factor" , &script_rq_result::fSndOcclusionFactor )
.def_readonly( "material_density_factor" , &script_rq_result::fDensityFactor )
]]
-- Geometry Ray class by Thial, edited by demonized
class "geometry_ray"
--[[
(At least one range parameter should be specified)
ray_range:
Defines the total range of the ray. If you want to attach the ray to
a fast moving object it is good to extend the ray so that you can reduce
the polling rate by using the get function.
contact_range:
Defines the distance at which the result will report being in contact.
You can skip it or set it to a value lower than the ray_range to
still be able to get the intersection position from the result while
marking the ray as not being in contact yet
distance_offset:
Defines how much the intersection position is offset.
You can use both positive and negative values or you can leave it blank.
flags (bit map = values can be added together for combined effect):
0 : None
1 : Objects
2 : Statics
4 : Shapes
8 : Obstacles
]]--
function geometry_ray:__init(args)
local args = args or {}
if args.ray_range == nil and args.contact_range == nil then
return nil
end
self.ray_range = args.ray_range or args.contact_range
self.contact_range = args.contact_range or args.ray_range
self.distance_offset = args.distance_offset ~= nil and args.distance_offset or 0
self.ray = ray_pick()
self.ray:set_flags(args.flags or 2)
self.ray:set_range(self.ray_range)
self.visualize = args.visualize
self.visualize_end_pos_only = args.visualize_end_pos_only
if args.ignore_object then
self.ray:set_ignore_object(args.ignore_object)
end
end
--[[
position:
position from which the ray will start
direction:
direction in which the ray will be fired
]]--
function geometry_ray:get(position, direction)
if position == nil or direction == nil then
return nil
end
local position = vector():set(position)
local direction = vector():set(direction)
self.ray:set_position(position)
self.ray:set_direction(direction)
local res = self.ray:query()
local distance = res and self.ray:get_distance() or self.ray_range
local result = {}
if self.visualize then
local init_pos = vector():set(position)
local end_pos = vector():mad(init_pos, direction, distance)
VisualizeRay(init_pos, end_pos, nil, nil, nil, self.visualize_end_pos_only)
end
result.in_contact = distance <= self.contact_range
result.position = position:add(direction:mul(distance + self.distance_offset))
result.distance = distance
result.raw_distance = self.ray:get_distance()
result.success = res
result.object = self.ray:get_object()
result.element = self.ray:get_element()
result.result = self.ray:get_result()
-- Cast to Lua table
-- if result.result then
-- local r = {}
-- r.material_name = result.result.material_name
-- r.material_flags = result.result.material_flags
-- r.material_phfriction = result.result.material_phfriction
-- r.material_phdamping = result.result.material_phdamping
-- r.material_phspring = result.result.material_phspring
-- r.material_phbounce_start_velocity = result.result.material_phbounce_start_velocity
-- r.material_phbouncing = result.result.material_phbouncing
-- r.material_flotation_factor = result.result.material_flotation_factor
-- r.material_shoot_factor = result.result.material_shoot_factor
-- r.material_shoot_factor_mp = result.result.material_shoot_factor_mp
-- r.material_bounce_damage_factor = result.result.material_bounce_damage_factor
-- r.material_injurious_speed = result.result.material_injurious_speed
-- r.material_vis_transparency_factor = result.result.material_vis_transparency_factor
-- r.material_snd_occlusion_factor = result.result.material_snd_occlusion_factor
-- r.material_density_factor = result.result.material_density_factor
-- result.result = r
-- end
return result
end
-- Engine edit required for testing materials
-- If not possible to get material - return nil
function geometry_ray:isMaterialFlag(flag)
local result = self.ray:get_result()
if not result then
return
end
if not result.material_flags then
return
end
if not flags_test[flag] then
return
end
return test(result.material_flags, flags_test[flag])
end
function geometry_ray:getMaterialFlags()
local result = self.ray:get_result()
if not result then
return
end
if not result.material_flags then
return
end
local res = {}
for k, v in pairs(flags_test) do
res[k] = test(result.material_flags, v)
end
return res
end
-- Utils
-- Check if values are similar to a precision
function similar(float1, float2, epsilon)
return math.abs(float1 - float2) <= (epsilon or 0.0001)
end
function vec_similar(vec1, vec2, epsilon)
return similar(vec1.x, vec2.x, epsilon) and similar(vec1.y, vec2.y, epsilon) and similar(vec1.z, vec2.z, epsilon)
end
-- Linear inter/extrapolation
function lerp(a, b, f)
if a and b and f then
return a + f * (b - a)
else
return a or b or 0
end
end
-- Visualize ray from one point to other with particles playing at setted step
class "VisualizeRay"
function VisualizeRay:__init(init_pos, end_pos, particle_step, visualize_time, force_stop, draw_end_only)
self.init_pos = init_pos
self.end_pos = end_pos
self.visualize_time = visualize_time or 3000
self.particle_step = particle_step or 0.02
self.force_stop = force_stop
self.force_stop_default = force_stop
self.time = 0
self.draw_end_only = draw_end_only
self.start = function()
local begin = self.draw_end_only and 1 or 0
for i = begin, 1, self.particle_step do
local p = particles_object("amik\\hit_fx\\metal\\hit_sparks_glow")
local x = lerp(self.init_pos.x, self.end_pos.x, i)
local y = lerp(self.init_pos.y, self.end_pos.y, i)
local z = lerp(self.init_pos.z, self.end_pos.z, i)
p:play_at_pos(vector():set(x, y, z))
local time = 0
local stopped = false
AddUniqueCall(throttle(function()
if self.time > self.visualize_time then
if not stopped then
if self.force_stop then p:stop() else p:stop_deffered() end
stopped = true
end
if not p:playing() then
p = nil
return true
end
else
if not p:playing() then
p:play_at_pos(vector():set(x, y, z))
end
time = time + device().time_delta
self.time = time
end
end))
end
end
self.start()
self.reset_time = function()
self.time = 0
end
self.reset = function()
self.time = 0
self.force_stop = self.force_stop_default
self.start()
end
self.stop = function()
self.time = self.visualize_time + 1
self.force_stop = true
end
end
-- Get surface normals by Aoldri, edited by demonized
function get_surface_normal(pos, dir)
local ray = geometry_ray({
ray_range = 1000,
visualize = false,
flags = 1+2,
ignore_object = db.actor,
})
-- Get player's camera position and direction in world space
local pos0 = pos and vector():set(pos) or device().cam_pos
local angle1 = dir and vector():set(dir) or device().cam_dir
-- Generate two positions orthogonal to camera direction and each other
local pos01 = vector():set(0, 1, 0)
pos01 = pos01:sub(vector():set(angle1):mul(pos01:dotproduct(angle1)))
pos01:normalize()
local pos02 = vector_cross(angle1, pos01)
pos01 = pos01:mul(0.01)
pos02 = pos02:mul(0.01)
pos01 = pos01:add(pos0)
pos02 = pos02:add(pos0)
-- Get positions of intersections of rays around pos0
local res = ray:get(pos0, angle1)
local pos1 = res.position
local pos2 = ray:get(pos01, angle1).position
local pos3 = ray:get(pos02, angle1).position
if not res.success then
-- print_tip("cant get normal by pos %s, dir %s", pos0, angle1)
return
end
-- VisualizeRay(pos0, pos1, nil, 300)
-- VisualizeRay(pos01, pos2, nil, 300)
-- VisualizeRay(pos02, pos3, nil, 300)
-- Get vectors from intersection points from pos1
local vec2 = vec_sub(pos1, pos2)
local vec3 = vec_sub(pos1, pos3)
-- Find normal vector of surface by taking cross product of intersection vectors
local cross = (vector_cross(vec2, vec3)):normalize()
-- If the direction and normal vectors heading in similar direction - invert normal
local deg = angle1:dotproduct(cross)
if deg > 0 then
cross:invert()
end
-- VisualizeRay(pos1, vector():set(pos1):add(cross), nil, 300)
return cross
end
@@ -0,0 +1,115 @@
--[[
Fillable canteens
This work is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0).
Author: HarukaSai (visit us at: https://discord.gg/efp)
2023-06-28
]]
local string_find = string.find
local geometry_ray = demonized_geometry_ray.geometry_ray
waters = {
["flask"] = true,
["dirty_water"] = true,
["boiled_water"] = true
}
local sound_objs = {
boiling_water = sound_object("fillable_canteens\\boiling_water"),
filling_water = sound_object("fillable_canteens\\filling_water")
}
function is_liquid(ray, result)
if (not result.material_name) then
return false
end
return (string.find(result.material_name, "water") or ray:isMaterialFlag("flLiquid"))
end
function check_water(material)
local ray = geometry_ray({
ray_range = 4,
flags = 1 + 2 + 4 + 8,
ignore_object = db.actor
})
local res = ray:get(device().cam_pos, device().cam_dir)
return is_liquid(ray, res.result)
end
function actor_on_item_use(obj)
local sec = obj:section()
if (waters[sec]) and obj:get_remaining_uses() == 0 then
alife_create_item("empty_canteen", db.actor)
end
end
function find_object(t, sec)
for i = 1, #t do
local result = t[i]:section() == sec and t[i]
if result then
return result
end
end
end
function find_object_t(t, secs)
for i = 1, #secs do
local result = find_object(t, secs[i])
if result then
return result
end
end
end
function on_item_drag_dropped(obj_1, obj_2, slot_from, slot_to)
local p_1, p_2 = obj_1:parent(), obj_2:parent()
if not ((p_1 and p_1:id() == AC_ID) and (p_2 and p_2:id() == AC_ID)) then
return
end
local akvatab = find_object({obj_1, obj_2}, "purification_tablet")
local dirty_water = find_object_t({obj_1, obj_2}, {"dirty_water","boiled_water"})
if not (akvatab and dirty_water) then
return
end
alife_create_item("flask", db.actor, {["uses"] = dirty_water:get_remaining_uses()})
alife_release(dirty_water)
utils_item.discharge(akvatab, 1)
end
function fill(obj, sec)
alife_create_item("dirty_water", obj:parent())
sound_objs.filling_water:play(db.actor, 0, sound_object.s2d)
alife_release(obj)
end
local function fill_condition_function(obj, bag, mode)
local p = obj:parent()
if not (p and p:id() == AC_ID) then
return
end
local sec = obj:section()
if (sec == "empty_canteen" or waters[sec]) and check_water() then
return true
end
end
local add_functor = custom_functor_autoinject.add_functor
add_functor("haruka_fill", fill_condition_function, function()
return game.translate_string("st_haruka_fill")
end, nil, fill)
function on_game_start()
RegisterScriptCallback("ActorMenu_on_item_drag_drop", on_item_drag_dropped)
RegisterScriptCallback("actor_on_item_use", actor_on_item_use)
end
@@ -0,0 +1,22 @@
function on_xml_read()
RegisterScriptCallback("on_xml_read",
function(xml_file_name, xml_obj)
if xml_file_name == "ui\\map_spots.xml" or xml_file_name == "ui\\map_spots_16.xml" then
xml_obj:insertFromXMLString([[
<water_spot>
<level_map spot="water_spot_tex"/>
<mini_map spot="water_spot_mini_tex"/>
</water_spot>
<water_spot_tex width="19" height="19" stretch="1" alignment="c" location_level="5">
<texture>ui_maid_water_spot</texture>
</water_spot_tex>
<water_spot_mini_tex x="0" y="0" width="19" height="19" alignment="c" stretch="1" location_level="10" scale_min="3.0" scale_max="5.1" scale="0">
<texture>ui_maid_water_spot</texture>
</water_spot_mini_tex>
]])
end
end)
end
+28
View File
@@ -0,0 +1,28 @@
[General]
gameName=stalkeranomaly
modid=0
version=1.0.0.0
newestVersion=
category="-1,"
nexusFileStatus=1
installationFile=HaruFillableCanteens-2.0.2.3.zip
repository=Nexus
ignoredVersion=
comments=
notes=
nexusDescription=
url=
hasCustomURL=true
lastNexusQuery=
lastNexusUpdate=
nexusLastModified=2024-12-07T20:40:57Z
nexusCategory=0
converted=false
validated=false
color=@Variant(\0\0\0\x43\0\xff\xff\0\0\0\0\0\0\0\0)
tracked=0
[installedFiles]
1\modid=0
1\fileid=0
size=1