Sunday, October 10, 2021

Do Gloves Protect You From Broken Glass?

Yes, gloves protect you from handling broken glass - any pair of gloves.  But gloves are not needed when removing broken glass from a smashed window, just when picking up broken glass off the ground.

You deal with broken glass whenever you smash a window.  There's a bit of broken glass remaining in the window frame, and glass on the ground by the smashed window.  You might want to get rid of that broken glass, and I used to think that required leather gloves to prevent injuries.  I was wrong.

Picking Up Broken Glass - Wear Gloves (Any)

You might want to pick up the glass off the ground.  When you or a zombie walks over it, it makes a sound.  Some people strategically place the glass as a sort of alarm for when zombies are near.  Some people just don't like the mess.

If you're not wearing gloves when you pick up the glass, you may injure yourself.

It starts in ISWorldObjectContextMenu.  If you are interacting with brokenGlass, you start executing some code:

    -- broken glass interaction
--    if brokenGlass and playerObj:getClothingItem_Hands() then
	if brokenGlass then
--        local itemName = playerObj:getClothingItem_Hands():getName()
--        if itemName ~= "Fingerless Gloves" then
            context:addOption(getText("ContextMenu_PickupBrokenGlass"), worldObjects, ISWorldObjectContextMenu.onPickupBrokenGlass, brokenGlass, player)
--        end
    end

You notice that there's some commented out code where Fingerless Gloves don't protect you when you pick up broken glass.  I don't know if this is old code or just code that hasn't been implemented yet.  If they put this functionality in (not here, though), I hope they include surgical gloves and long gloves.

The code calls onPickupBrokenGlass

ISWorldObjectContextMenu.onPickupBrokenGlass = function(worldobjects, brokenGlass, player)
    local playerObj = getSpecificPlayer(player)
    if luautils.walkAdj(playerObj, brokenGlass:getSquare()) then
        ISTimedActionQueue.add(ISPickupBrokenGlass:new(playerObj, brokenGlass, 100));
    end
end

That calls ISPickupBrokenGlass, and in the perform() method we see what happens:

function ISPickupBrokenGlass:perform()
	-- add random damage to hands if no gloves (done in pickUpMoveable)
	if ISMoveableTools.isObjectMoveable(self.glass) then
        local moveable = ISMoveableTools.isObjectMoveable(self.glass)
        moveable:pickUpMoveable( self.character, self.square, self.glass, true )
    end

	-- needed to remove from queue / start next.
	ISBaseTimedAction.perform(self)
end

That calls ISMoveableTools.isObjectMoveable to retrieve the glass (a ISMoveableSpriteProps object) (code not included here), and then actually picks it up in ISMoveableSpriteProps:

        elseif self.isoType == "IsoBrokenGlass" then
            -- add random damage to hands if no gloves
            if not _character:getClothingItem_Hands() and ZombRand(3) == 0 then
                local handPart = _character:getBodyDamage():getBodyPart(BodyPartType.FromIndex(ZombRand(BodyPartType.ToIndex(BodyPartType.Hand_L),BodyPartType.ToIndex(BodyPartType.Hand_R) + 1)))
                handPart:setScratched(true, true);
                -- possible glass in hands
                if ZombRand(5) == 0 then
                    handPart:setHaveGlass(true);
                end
            end

This means that ANY clothing on the hands protects you when picking up broken glass.  So you don't need leather gloves - fingerless gloves or surgical gloves or whatever will protect your hands completely from the broken glass.

Removing Broken Glass From a Window - Don't Need Gloves

You should all already know that if you climb through a broken window, there's a good chance you're going to get injured from the broken glass.  So you want to remove the glass from the window before you climb through, especially if it's a window you're going to be climbing through regularly (like at your base).

I haven't found any code that might injure a character removing glass from a broken window.

function ISRemoveBrokenGlass:perform()
	self.window:removeBrokenGlass()

	-- needed to remove from queue / start next.
	ISBaseTimedAction.perform(self)
end

Unlike what we found in the ISPickupBrokenGlass:perform() method, nothing here has any opportunity to injure the character.  If the removeBrokenGlass() method could injure the character, it would pass in the character object.

Afterword

So wearing any gloves is better than wearing no gloves.  Sure, long gloves don't offer any scratch or bite resistance, but they'll protect your hands when you pick up glass.


Thanks for reading!  If you notice a mistake or have any questions I might answer by looking through the code, let me know!

Sunday, October 3, 2021

Do the Fast/Slow Healer Traits Affect Fractures?


The wiki says that the Fast Healer and Slow Healer traits do not apply to fractures.

Does not apply to fractures and exercise fatigues. Recently inflicted injuries starts with +20% less severity.

But the wiki is wrong.

Fast Healer and Slow Healer do apply to fractures, both from falling from high distances and from vehicle crashes.

They're done a little differently, so let's look at the code.

Fractures From Falling

This comes from the IsoGameCharacter class, in the DoLand method:

        if (this.fallTime > 70.0f) {
            int n = 100 - (int)((double)this.fallTime * 0.6);
            if (this.getInventory().getMaxWeight() - this.getInventory().getCapacityWeight() < 2.0f) {
                n = (int)((float)n - this.getInventory().getCapacityWeight() / this.getInventory().getMaxWeight() * 100.0f / 5.0f);
            }
            if (this.Traits.Obese.isSet() || this.Traits.Emaciated.isSet()) {
                n -= 20;
            }
            if (this.Traits.Overweight.isSet() || this.Traits.VeryUnderweight.isSet()) {
                n -= 10;
            }
            if (this.getPerkLevel(PerkFactory.Perks.Fitness) > 4) {
                n += (this.getPerkLevel(PerkFactory.Perks.Fitness) - 4) * 3;
            }
            if (Rand.Next(100) >= n) {
                if (!SandboxOptions.instance.BoneFracture.getValue()) {
                    return;
                }
                float f2 = Rand.Next(50, 80);
                if (this.Traits.FastHealer.isSet()) {
                    f2 = Rand.Next(30, 50);
                } else if (this.Traits.SlowHealer.isSet()) {
                    f2 = Rand.Next(80, 150);
                }
                switch (SandboxOptions.instance.InjurySeverity.getValue()) {
                    case 1: {
                        f2 *= 0.5f;
                        break;
                    }
                    case 3: {
                        f2 *= 1.5f;
                    }
                }
                this.getBodyDamage().getBodyPart(BodyPartType.FromIndex(Rand.Next(BodyPartType.ToIndex(BodyPartType.UpperLeg_L), BodyPartType.ToIndex(BodyPartType.Foot_R) + 1))).setFractureTime(f2);
            }

It's an interesting read, but I'm not going to walk through the whole thing.

The value n gets modified by a few different factors, and if the random 1-100 roll is equal to or larger than n, you have a fracture.

The value f2 is assigned a random number that can be generated based on having FastHealer or SlowHealer.  You can see that FastHealers get a smaller f2 and SlowHealers get a larger f2.  Eventually, f2 is sent to setFractureTime for a random body part.  Clearly, the wiki is wrong here - whether you have FastHealer or SlowHealer affects how long your body part will be fractured.

Fractures From Vehicle Crashes

In the BaseVehicle class, we see in addRandomDamageFromCrash that setFractureTime is handled differently:

            float f2 = Math.max(Rand.Next(f - 15.0f, f), 5.0f);
            if (isoGameCharacter.Traits.FastHealer.isSet()) {
                f2 = (float)((double)f2 * 0.8);
            } else if (isoGameCharacter.Traits.SlowHealer.isSet()) {
                f2 = (float)((double)f2 * 1.2);
            }
            switch (SandboxOptions.instance.InjurySeverity.getValue()) {
                case 1: {
                    f2 *= 0.5f;
                    break;
                }
                case 3: {
                    f2 *= 1.5f;
                }
            }
            f2 *= this.getScript().getPlayerDamageProtection();
            f2 = (float)((double)f2 * 0.9);
            bodyPart.AddDamage(f2);
            if (f2 > 40.0f && Rand.Next(12) == 0) {
                bodyPart.generateDeepWound();
            } else if (f2 > 50.0f && Rand.Next(10) == 0 && SandboxOptions.instance.BoneFracture.getValue()) {
                if (bodyPart.getType() == BodyPartType.Neck || bodyPart.getType() == BodyPartType.Groin) {
                    bodyPart.generateDeepWound();
                } else {
                    bodyPart.setFractureTime(Rand.Next(Rand.Next(10.0f, f2 + 10.0f), Rand.Next(f2 + 20.0f, f2 + 30.0f)));
                }
            }

In the DoLand code, we saw that slow healers chose a random number between 30 and 50 to determine fracture time, while fast healers chose a random number between 80 and 150 to determine fracture time.  

Here, we see that we start with a random number between f-15 and f, and then we take the the maximum of that and 5.  (I'll talk about what f is later - it's complicated)

So that random number, f2, is multiplied by 0.8 if you are a fast healer and by 1.2 if you are a slow healer.

It gets modified a bit, and IF you took a lot of damage and it randomly determines there is a bad wound and the wound location isn't the neck or groin, it is used to determine fracture time.

So you can see that Fast/Slow Healer traits do affect fractures.

Extra:  Calculating Vehicle Fracture Time (f)

Ok, this is complicated.  I'm not taking the time to dive deep into this calculation.

  • f is one of the values passed in to addRandomDamageFromCrash
  • That value is one of the values passed in to damagePlayers
  • That value is set by one of the values passed in to crash
  • crash gets called by Damage, HitByVehicle, and update

I'm guessing that if you're driving and crash your car, we are looking at update, so here's how it works there:

            if (this.jniIsCollide) {
                this.jniIsCollide = false;
                Vector3f vector3f = (Vector3f)TL_vector3f_pool.get().alloc();
                if (GameServer.bServer) {
                    vector3f.set((Vector3fc)this.netLinearVelocity);
                } else {
                    vector3f.set((Vector3fc)this.jniLinearVelocity);
                }
                vector3f.negate();
                vector3f.add((Vector3fc)this.lastLinearVelocity);
                vector3f.y = 0.0f;
                float f6 = Math.abs(vector3f.length());
                if (f6 > 2.0f) {
                    if (this.lastLinearVelocity.length() < 6.0f) {
                        f6 /= 3.0f;
                    }
                    this.jniTransform.getRotation(this.tempQuat4f);
                    this.tempQuat4f.invert(this.tempQuat4f);
                    if (this.lastLinearVelocity.rotate((Quaternionfc)this.tempQuat4f).z < 0.0f) {
                        f6 *= -1.0f;
                    }
                    if (Core.bDebug) {
                        DebugLog.log("CRASH lastSpeed=" + this.lastLinearVelocity.length() + " speed=" + vector3f + " delta=" + f6 + " netLinearVelocity=" + this.netLinearVelocity.length());
                    }
                    Vector3f vector3f2 = this.getForwardVector((Vector3f)TL_vector3f_pool.get().alloc());
                    float f7 = vector3f.normalize().dot((Vector3fc)vector3f2);
                    TL_vector3f_pool.get().release(vector3f2);
                    this.crash(Math.abs(f6 * 3.0f), f7 > 0.0f);

I'm doing some guesswork here!  f6 * 3.0 is what gets passed to crash.  f6 is a "delta", so the absolute value of the speed vector (so, just the magnitude part of the vector, not the direction).

So it looks like the fracture time is directly related to the speed when you crash.  Modified by things like Fast/Slow healer.

And fractures from being hit by a car (like in multiplayer) are handled differently, but also use Fast/Slow healer to determine fracture time.


Thanks for reading!  If you see a mistake I made or have a question I can answer by looking at the code, let me know!

How Strong Are My Walls? (How Wall Health is Calculated in Build 41.55)

Build 41.55 changed how wall health is calculated.  There are three main differences:

  • The health increase per level was reduced from 40 to 20 (under default settings)
  • The level at which the wall could first be built is no longer a factor in calculating health
  • Upgraded walls no longer have more health than originally built walls

If you want more details about how wall health was calculated in 41.54 and earlier, check out my old blog post.

Here are the factors that influence wall health:

  • Wall Type
  • Character Skill Level
  • Handy Trait (only for log walls)
  • For upgrades, the health of the original wall
  • Sandbox Option: Player-Built Construction Strength

For the purposes of this entire post, let's assume the Sandbox Options are all set to default.

The strongest wall is the Level 2 Metal Wall, built with a metal frame, but considering the scarcity of the resources required to build them, I think most people will stick with wooden walls.  Log walls (without the handy trait) are only sometimes stronger than wooden walls, and require much more wood (but fewer nails).

How Wall Health is Determined in the Code

As in older builds, the code for wood (and log) walls starts in ISBuildMenu.lua.  Metal walls are handled in ISBlacksmithMenu.lua.  Metal walls are handled similarly to wood walls in lua, and use the same code when we get to the java, so we'll only look at the wood wall code here.

Once we get past determining the skills and materials required to build the wall, the stats for the wall frame gets set in onWoodenWallFrame:

ISBuildMenu.onWoodenWallFrame = function(worldobjects, sprite, player)
-- sprite, northSprite, corner
    local wall = ISWoodenWall:new(sprite.sprite, sprite.northSprite, sprite.corner, ISBuildMenu.isNailsBoxNeededOpening(2));
    wall.canBarricade = false
    wall.name = "WoodenWallFrame";
    -- set up the required material
    wall.modData["xp:Woodwork"] = 5;
    wall.modData["need:Base.Plank"] = "2";
    wall.modData["need:Base.Nails"] = "2";
    wall.health = 50;
    wall.player = player;
    getCell():setDrag(wall, player);
end

So the frame's health is 50.  

Wall frames are built in ISBuildMenu like a lot of constructions (stairs, crates, rain collectors).  Unlike in older builds, there is no health difference between upgrading a wall and building the wall outright.  They are handled in ISBuildMenu:

ISBuildMenu.onMultiStageBuild = function(worldobjects, stage, item, player)

The definitions of these are in the scripts, in multistagebuild.txt.

Here is the definition of a level 1 wall:

    multistagebuild CreateWoodenWall_1
    {
        PreviousStage:WoodenWallFrame;MetalWallFrame,
        Name:WoodenWallLvl1,
        TimeNeeded:250,
        BonusHealth:400,
        SkillRequired:Woodwork=2,
        ItemsRequired:Base.Plank=2;Base.Nails=4,
        ItemsToKeep:Base.Hammer,
        Sprite:walls_exterior_wooden_01_44,
        NorthSprite:walls_exterior_wooden_01_45,
        CraftingSound:Hammering,
        ID:Create Wooden Wall Lvl 1,
        XP:Woodwork=10,
    }

It's pretty clear what most of the items in this definition means.  BonusHealth is what is important for this discussion.

You can look at multistagebuild.txt if you want to see all the values, but here are the health values for the walls and upgrades:

Bonus Health

Level 1 Wall: 400
Level 2 Wall: 500
Level 3 Wall: 600
Level 1 Metal Wall: 650
Level 2 Metal Wall: 750

Upgrade from L1 Wall to L2 Wall: +100
Upgrade from L1 Wall to L3 Wall: +200
Upgrade from L2 Wall to L3 Wall: +100
Upgrade from L1 Metal Wall to L2 Metal Wall: +100

The code that handles setting the stats of these walls isn't in the lua, it is in the java.  It is in the MultiStageBuilding class.  This is used for both wood and metal walls:

        public void doStage(IsoGameCharacter isoGameCharacter, IsoThumpable isoThumpable, boolean bl) {
            int n = isoThumpable.getHealth();
            int n2 = isoThumpable.getMaxHealth();
            String string = this.sprite;
            if (isoThumpable.north) {
                string = this.northSprite;
            }
            IsoThumpable isoThumpable2 = new IsoThumpable(IsoWorld.instance.getCell(), isoThumpable.square, string, isoThumpable.north, isoThumpable.getTable());
            isoThumpable2.setCanBePlastered(this.canBePlastered);
            if ("doorframe".equals(this.wallType)) {
                isoThumpable2.setIsDoorFrame(true);
                isoThumpable2.setCanPassThrough(true);
                isoThumpable2.setIsThumpable(isoThumpable.isThumpable());
            }
            int n3 = this.bonusHealth;
            switch (SandboxOptions.instance.ConstructionBonusPoints.getValue()) {
                // Code edited out by Ed because we are using default values
            }
            Iterator<String> iterator = this.perks.keySet().iterator();
            int n4 = 20;
            switch (SandboxOptions.instance.ConstructionBonusPoints.getValue()) {
                // Code edited out by Ed because we are using default values
            }
            int n5 = 0;
            if (this.bonusHealthSkill) {
                while (iterator.hasNext()) {
                    String string2 = iterator.next();
                    n5 += isoGameCharacter.getPerkLevel(PerkFactory.Perks.FromString(string2)) * n4;
                }
            }
            isoThumpable2.setMaxHealth(n2 + n3 + n5);
            isoThumpable2.setHealth(n + n3 + n5);

When you build a level 1 wood wall from a wood frame here are the steps for setting the health:

  • Get the health of the wood frame (this includes any damage taken)
  • Get the maximum health of the wood frame (we know this is 50)
  • Get the Bonus Health value of the level 1 wall (we know this is 400)

Then we look at the "perks" which is basically the skills required to build this wall.  In this case, it is carpentry.  We take the character's level, multiplied by 20.  

Then for the maximum health we add up the max health, bonus health, and multiplied level modifier.

And for the current health, we add up health, bonus health, and multiplied level modifier.

Remember that when you are upgrading a wall, it is starting with the health of the original wall.  So if you build a L1 wall when you have L2 Carpentry, it will start out at 490 HP.  If you wait til L10 to upgrade the wall to L2, it will still only have 590 HP.

Note that except for log walls, the handy trait does not factor in calculating wall health.

What About Metal Walls and Log Walls?

Although metal walls get their start in the ISBlacksmithMenu.lua code, they also end up in the MultiStageBuilding class so they have the same calculations, with different numbers.  

By the way, metal frames have a health of 120 compared to the wood frame health of 50.  And you can build wood walls over metal frames.  So if you want to build strongest wall, start with the strongest frame.

Log walls fall under what appears to be the old system of calculating health.  Much of the code for the old system of constructing wood walls is still there, just unreachable because of comments.  When we want to calculate a log wall's health:

-- return the health of the new wall, it's 200 + 100 per carpentry lvl
function ISWoodenWall:getHealth()
    if self.sprite == "carpentry_02_80" then -- log walls are stronger
	    return 400 + buildUtil.getWoodHealth(self);
    else
        return 200 + buildUtil.getWoodHealth(self);
    end
end

The comment there is in the 41.55 version of the file, and is wrong.

-- give the health of the item, depending on you're carpentry level + if you're a construction worker
buildUtil.getWoodHealth = function(ISItem)
	if not ISItem or not ISItem.player then
		return 100;
	end
	local playerObj = getSpecificPlayer(ISItem.player)
	local health = (playerObj:getPerkLevel(Perks.Woodwork) * 50);
	if playerObj:HasTrait("Handy") then
		health = health + 100;
	end
	return health;
end

We can see here that for the log walls, 50 per carpentry level gets added to the health, with an additional 100 if the character is handy.  So a handy character with level 10 carpentry makes 1000 health log walls.  

Although handy isn't currently being used to calculate wall health, maybe they'll add it back in in the future.

Mod: How Strong Is That Wall?



After my first post on wall health, I created a mod that lets you see how strong your walls are.  Look at the mod through this link!


Thanks for reading this!  If you see any mistakes, or if you have any questions that I might be able to answer by looking at the code, let me know!

Do Gloves Protect You From Broken Glass?

Yes, gloves protect you from handling broken glass - any pair of gloves.  But gloves are not needed when removing broken glass from a smashe...