/*
Example of skinning config which should be inside animal's base class:
class Skinning
{
// All classes in this scope are parsed, so they can have any name.
class ObtainedSteaks
{
item = "DeerSteakMeat"; // item to spawn
count = 10; // How many items to spawn
transferToolDamageCoef = 1; // Optional: How much tool's damage is transfered to item's damage.
// Make sure to have only 1 of the following quantity paramenter
quantity = 100; // Optional: Set item's quantity
quantityCoef = 1; // Optional: Set item's quantity (in coefficient)
quantityMinMax[] = {100, 200}; // Optional: Set item's quantity within min/max values
quantityMinMaxCoef[] = {0, 1}; // Optional: Set item's quantity (in coefficient) within min/max values
};
};
*/
class ActionSkinningCB : ActionContinuousBaseCB
{
override void CreateActionComponent()
{
m_ActionData.m_ActionComponent = new CAContinuousTime(UATimeSpent.SKIN);
}
}
class ActionSkinning: ActionContinuousBase
{
void ActionSkinning()
{
m_CallbackClass = ActionSkinningCB;
m_SpecialtyWeight = UASoftSkillsWeight.PRECISE_MEDIUM;
m_CommandUID = DayZPlayerConstants.CMD_ACTIONFB_ANIMALSKINNING;
m_StanceMask = DayZPlayerConstants.STANCEMASK_CROUCH;
m_FullBody = true;
m_Text = "#skin";
}
override void CreateConditionComponents()
{
m_ConditionItem = new CCINonRuined();
m_ConditionTarget = new CCTCursorNoRuinCheck();
}
override bool ActionCondition(PlayerBase player, ActionTarget target, ItemBase item)
{
Object targetObject = target.GetObject();
if (targetObject.CanBeSkinned() && !targetObject.IsAlive())
{
EntityAI target_EAI;
if (Class.CastTo(target_EAI, targetObject) && target_EAI.CanBeSkinnedWith(item))
return true;
}
return false;
}
// Spawns the loot according to the Skinning class in the dead agent's config
override void OnFinishProgressServer(ActionData action_data)
{
super.OnFinishProgressServer(action_data);
Object targetObject = action_data.m_Target.GetObject();
// Mark the body as skinned to forbid another skinning action on it
EntityAI body = EntityAI.Cast(targetObject);
body.SetAsSkinned();
MiscGameplayFunctions.RemoveAllAttachedChildrenByTypename(body, {Bolt_Base});
HandlePlayerBody(action_data);
SpawnItems(action_data);
if (body)
{
GetGame().GetCallQueue(CALL_CATEGORY_SYSTEM).Call(GetGame().ObjectDelete, body);
}
MiscGameplayFunctions.DealAbsoluteDmg(action_data.m_MainItem, UADamageApplied.SKINNING);
PluginLifespan moduleLifespan = PluginLifespan.Cast(GetPlugin(PluginLifespan));
moduleLifespan.UpdateBloodyHandsVisibility(action_data.m_Player, true);
action_data.m_Player.SetBloodyHandsPenaltyChancePerAgent(eAgents.SALMONELLA, body.GetSkinningBloodInfectionChance(eAgents.SALMONELLA));
}
// Spawns an organ defined in the given config
ItemBase CreateOrgan(PlayerBase player, vector body_pos, string item_to_spawn, string cfg_skinning_organ_class, ItemBase tool)
{
// Create item from config
ItemBase added_item;
vector posHead;
MiscGameplayFunctions.GetHeadBonePos(player,posHead);
vector posRandom = MiscGameplayFunctions.GetRandomizedPositionVerified(posHead,body_pos,UAItemsSpreadRadius.NARROW,player);
Class.CastTo(added_item, GetGame().CreateObjectEx(item_to_spawn, posRandom, ECE_PLACE_ON_SURFACE));
// Check if skinning is configured for this body
if (!added_item)
return null;
// Set item's quantity from config, if it's defined there.
float item_quantity = 0;
array<float> quant_min_max = new array<float>;
array<float> quant_min_max_coef = new array<float>;
GetGame().ConfigGetFloatArray(cfg_skinning_organ_class + "quantityMinMax", quant_min_max);
GetGame().ConfigGetFloatArray(cfg_skinning_organ_class + "quantityMinMaxCoef", quant_min_max_coef);
// Read config for quantity value
if (quant_min_max.Count() > 0)
{
float soft_skill_manipulated_value = (quant_min_max.Get(0)+ quant_min_max.Get(1)) / 2;
item_quantity = Math.RandomFloat(soft_skill_manipulated_value, quant_min_max.Get(1));
}
if (quant_min_max_coef.Count() > 0)
{
float coef = Math.RandomFloat(quant_min_max_coef.Get(0), quant_min_max_coef.Get(1));
item_quantity = added_item.GetQuantityMax() * coef;
}
if (GetGame().ConfigGetFloat(cfg_skinning_organ_class + "quantity") > 0)
item_quantity = g_Game.ConfigGetFloat(cfg_skinning_organ_class + "quantity");
if (GetGame().ConfigGetFloat(cfg_skinning_organ_class + "quantityCoef") > 0)
{
float coef2 = g_Game.ConfigGetFloat(cfg_skinning_organ_class + "quantityCoef");
item_quantity = added_item.GetQuantityMax() * coef2;
}
if (item_quantity > 0)
{
item_quantity = Math.Round(item_quantity);
added_item.SetQuantity(item_quantity, false);
}
// Transfer tool's damage to the item's condition
float item_apply_tool_damage_coef = GetGame().ConfigGetFloat(cfg_skinning_organ_class + "transferToolDamageCoef");
if (item_apply_tool_damage_coef > 0)
{
float tool_dmg_coef = 1 - tool.GetHealth01();
float organ_dmg_coef = tool_dmg_coef * item_apply_tool_damage_coef;
added_item.DecreaseHealthCoef(organ_dmg_coef);
}
added_item.InsertAgent(eAgents.SALMONELLA, 1);
return added_item;
}
override void OnFinishProgressClient(ActionData action_data)
{
super.OnFinishProgressClient(action_data);
if (action_data.m_Target)
{
Object target_obj = action_data.m_Target.GetObject();
if (target_obj.IsKindOf("Animal_CapreolusCapreolus") || target_obj.IsKindOf("Animal_CapreolusCapreolusF") || target_obj.IsKindOf("Animal_CervusElaphus") || target_obj.IsKindOf("Animal_CervusElaphusF"))
{
GetGame().GetAnalyticsClient().OnActionFinishedGutDeer();
}
}
}
//! This section drops all clothes (and attachments) from the dead player before deleting their body
void HandlePlayerBody(ActionData action_data)
{
PlayerBase body;
if (Class.CastTo(body,action_data.m_Target.GetObject()))
{
if (body.IsRestrained() && body.GetHumanInventory().GetEntityInHands())
MiscGameplayFunctions.TransformRestrainItem(body.GetHumanInventory().GetEntityInHands(), null, action_data.m_Player, body);
//Remove splint if target is wearing one
if (body.IsWearingSplint())
{
EntityAI entity = action_data.m_Player.SpawnEntityOnGroundOnCursorDir("Splint", 0.5);
ItemBase newItem = ItemBase.Cast(entity);
EntityAI attachment = body.GetItemOnSlot("Splint_Right");
if (attachment && attachment.GetType() == "Splint_Applied")
{
if (newItem)
{
MiscGameplayFunctions.TransferItemProperties(attachment, newItem);
//Lower health level of splint after use
if (newItem.GetHealthLevel() < 4)
{
int newDmgLevel = newItem.GetHealthLevel() + 1;
float max = newItem.GetMaxHealth("", "");
switch (newDmgLevel)
{
case GameConstants.STATE_BADLY_DAMAGED:
newItem.SetHealth("", "", max * GameConstants.DAMAGE_BADLY_DAMAGED_VALUE);
break;
case GameConstants.STATE_DAMAGED:
newItem.SetHealth("", "", max * GameConstants.DAMAGE_DAMAGED_VALUE);
break;
case GameConstants.STATE_WORN:
newItem.SetHealth("", "", max * GameConstants.DAMAGE_WORN_VALUE);
break;
case GameConstants.STATE_RUINED:
newItem.SetHealth("", "", max * GameConstants.DAMAGE_RUINED_VALUE);
break;
default:
break;
}
}
}
attachment.Delete();
}
}
int deadBodyLifetime;
if (GetCEApi())
{
deadBodyLifetime = GetCEApi().GetCEGlobalInt("CleanupLifetimeDeadPlayer");
if (deadBodyLifetime <= 0)
deadBodyLifetime = 3600;
}
DropInventoryItems(body,deadBodyLifetime);
}
}
void DropInventoryItems(PlayerBase body, float newLifetime)
{
array<EntityAI> children = new array<EntityAI>;
body.GetInventory().EnumerateInventory(InventoryTraversalType.LEVELORDER, children);
foreach (EntityAI child : children)
{
if (child)
{
child.SetLifetime(newLifetime);
body.GetInventory().DropEntity(InventoryMode.SERVER, body, child);
}
}
}
void SpawnItems(ActionData action_data)
{
EntityAI body = EntityAI.Cast(action_data.m_Target.GetObject());
// Get config path to the animal
string cfgAnimalClassPath = "cfgVehicles " + body.GetType() + " " + "Skinning ";
vector bodyPosition = body.GetPosition();
if (!g_Game.ConfigIsExisting(cfgAnimalClassPath))
{
Debug.Log("Failed to find class 'Skinning' in the config of: " + body.GetType());
return;
}
// Getting item type from the config
int childCount = g_Game.ConfigGetChildrenCount(cfgAnimalClassPath);
string itemToSpawn;
string cfgSkinningOrganClass;
// Parsing of the 'Skinning' class in the config of the dead body
for (int i1 = 0; i1 < childCount; i1++)
{
// To make configuration as convenient as possible, all classes are parsed and parameters are read
g_Game.ConfigGetChildName(cfgAnimalClassPath, i1, cfgSkinningOrganClass); // out cfgSkinningOrganClass
cfgSkinningOrganClass = cfgAnimalClassPath + cfgSkinningOrganClass + " ";
g_Game.ConfigGetText(cfgSkinningOrganClass + "item", itemToSpawn); // out itemToSpawn
if (itemToSpawn != "") // Makes sure to ignore incompatible parameters in the Skinning class of the agent
{
// Spawning items in action_data.m_Player's inventory
int itemSpawnCount = g_Game.ConfigGetInt(cfgSkinningOrganClass + "count");
array<string> itemZones = new array<string>;
array<float> itemCount = new array<float>;
float zoneDmg = 0;
GetGame().ConfigGetTextArray(cfgSkinningOrganClass + "itemZones", itemZones);
GetGame().ConfigGetFloatArray(cfgSkinningOrganClass + "countByZone", itemCount);
float transferedPartDmgCoef = GetGame().ConfigGetFloat(cfgSkinningOrganClass + "transferPartDamageCoef");
if (itemCount.Count() > 0)
{
itemSpawnCount = 0;
for (int z = 0; z < itemZones.Count(); z++)
{
zoneDmg = body.GetHealth01(itemZones[z], "Health");
zoneDmg *= itemCount[z]; //just re-using variable
itemSpawnCount += Math.Floor(zoneDmg);
}
}
if (transferedPartDmgCoef > 0 && itemSpawnCount <= 0) // Special behavior for headdress
itemSpawnCount = 1;
for (int i2 = 0; i2 < itemSpawnCount; i2++)
{
ItemBase spawnResult = CreateOrgan(action_data.m_Player, bodyPosition, itemToSpawn, cfgSkinningOrganClass, action_data.m_MainItem);
if (!spawnResult)
continue;
// Damage spawned result item based on the average health value of the body part
// It only works if the "transferPartDamageCoef" value in the config is higher than 0 and result item is from type Headdress
if (transferedPartDmgCoef > 0)
{
float partHealth = 0;
float partMaxHealth = 0;
for (int z2 = 0; z2 < itemZones.Count(); z2++)
{
partHealth = body.GetHealth(itemZones[z2], "Health");
partMaxHealth = body.GetMaxHealth(itemZones[z2], "Health");
break;
}
if (partMaxHealth > 0)
{
float itemMaxHealth = spawnResult.GetMaxHealth("", "Health");
float partHealthPercent = (partHealth / partMaxHealth) * 100;
float itemHealth = (itemMaxHealth / 100) * partHealthPercent;
if (transferedPartDmgCoef > 1) // Makes sure to adjust incompatible parameter in the Skinning class of the agent
transferedPartDmgCoef = 1;
float itemHealthCoef = itemHealth * transferedPartDmgCoef;
spawnResult.SetHealth("", "Health", itemHealthCoef);
}
}
//Damage pelts based on the average values on itemZones
//It only works if the "quantityCoef" in the config is more than 0
float qtCoeff = GetGame().ConfigGetFloat(cfgSkinningOrganClass + "quantityCoef");
if (qtCoeff > 0)
{
float avgDmgZones = 0;
for (int c2 = 0; c2 < itemZones.Count(); c2++)
{
avgDmgZones += body.GetHealth01(itemZones[c2], "Health");
}
avgDmgZones = avgDmgZones/itemZones.Count(); // Evaluate the average Health
spawnResult.SetHealth01("","", avgDmgZones);
}
// inherit temperature
if (body.CanHaveTemperature() && spawnResult.CanHaveTemperature())
{
spawnResult.SetTemperatureDirect(body.GetTemperature());
spawnResult.SetFrozen(body.GetIsFrozen());
}
// handle fat/guts from human bodies
if ((itemToSpawn == "Lard") || (itemToSpawn == "Guts"))
{
if (body.IsKindOf("SurvivorBase"))
{
spawnResult.InsertAgent(eAgents.BRAIN, 1);
}
}
}
}
}
}
//DEPRECATED
vector GetRandomPos(vector body_pos)
{
return body_pos + Vector(Math.RandomFloat01() - 0.5, 0, Math.RandomFloat01() - 0.5);
}
}