--*********************************************************** --** ROBERT JOHNSON ** --*********************************************************** buildUtil = {}; -- if a thumpable item already placed on this tile, we cannot place another buildUtil.canBePlace = function(ISItem, square) if not square then return false end if square:getZ() >= 8 then return false; end if isClient() and SafeHouse.isSafeHouse(square, getSpecificPlayer(ISItem.player):getUsername(), true) then return false; end for i = 0, square:getObjects():size() - 1 do local item = square:getObjects():get(i); if (item:getSprite() and ((item:getSprite():getProperties():Is(IsoFlagType.collideN) and ISItem.north) or (item:getSprite():getProperties():Is(IsoFlagType.collideW) and not ISItem.north))) or ((instanceof(item, "IsoThumpable") and (item:getNorth() == ISItem.north or ISItem.ignoreNorth)) and not item:isCorner() and not item:isFloor()) or (instanceof(item, "IsoWindow") and item:getNorth() == ISItem.north) or (instanceof(item, "IsoDoor") and item:getNorth() == ISItem.north) then -- even if we can place this item everywhere, we can't place 2 same objects on the same tile if ISItem.canBeAlwaysPlaced and ISItem:getSprite() == item:getTextureName() then return false; elseif ISItem.canBeAlwaysPlaced then return true; end return false; end end if square:isVehicleIntersecting() then return false end return true; end -- 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 -- check if a corner has to be added -- if you place a wall to make a corner for example, there is still a hole where you can walk, so your fort will not be zombie proof -- here we gonna check if, when you add a wall, we need to add a corner function buildUtil.checkCorner(x,y,z,north, thumpable, item) local found = false; local xoffset = 1; local yoffset = -1; local sx = x + 1; local sy = y; if not north then xoffset = -1; yoffset = 1; sx = x; sy = y + 1; end local sq2 = getCell():getGridSquare(x + xoffset, y + yoffset, z); for i = 0, sq2:getSpecialObjects():size() - 1 do local item = sq2:getSpecialObjects():get(i); if instanceof(item, "IsoThumpable") and item:getNorth() ~= north then found = true; break; end end if found then -- don't add a corner if there's a wall on the tile local sq2 = getCell():getGridSquare(sx, sy, z); if sq2 and sq2:getWallFull() then return; end buildUtil.addCorner(sx,sy,z,thumpable,item); end end -- add the needed corner so there will be no hole in your fort function buildUtil.addCorner(x,y,z, thumpable, item) if thumpable.corner ~= nil then local sq = getCell():getGridSquare(x, y, z); local corner = IsoThumpable.new(getCell(), sq, thumpable.corner, false, nil); corner:setCorner(true); corner:setCanBarricade(false); sq:AddSpecialObject(corner); sq:RecalcAllWithNeighbours(true); corner:transmitCompleteItemToServer(); end end function buildUtil.addWoodXp(ISItem) local playerObj = getSpecificPlayer(ISItem.player) playerObj:getXp():AddXP(Perks.Woodwork, 3); end function buildUtil.predicateMaterial(item) return not instanceof(item, "InventoryContainer") or item:getInventory():getItems():isEmpty() end function buildUtil.useDrainable(item, uses) local count = math.min(uses, item:getDrainableUsesInt()) for i=1,count do item:Use() end return count end -- we gonna remove from the inventory all the required material -- those material are in the mod data and they look like = ["need:Plank"] = "2", etc. -- those need: stuff are used when you dismantle the item too function buildUtil.consumeMaterial(ISItem) if not ISItem or not ISItem.player then return {}; end if ISBuildMenu.cheat then return {}; end local playerObj = getSpecificPlayer(ISItem.player) local playerInv = playerObj:getInventory() local modData = ISItem.modData; local removedFromGround = false local consumedItems = {} for index, value in pairs(modData) do if luautils.stringStarts(index, "need:") then local itemFullType = luautils.split(index, ":")[2]; local itemType = luautils.split(itemFullType, "%.")[2] local itemCount = tonumber(value); local items = playerInv:getSomeTypeEvalRecurse(itemFullType, buildUtil.predicateMaterial, itemCount) for i=1,items:size() do local item = items:get(i-1) playerObj:removeFromHands(item) if item:getContainer() then item:getContainer():Remove(item); else playerInv:Remove(item) end itemCount = itemCount - 1 table.insert(consumedItems, item) end -- if we didn't have all the required material inside our inventory, it's because the missing materials are on the ground, we gonna check them -- for each missing material in inventory if itemCount > 0 then -- check a 3x3 square around the building local groundItems = buildUtil.getMaterialOnGround(ISItem.sq) local items = groundItems[itemFullType] if items then local count = math.min(itemCount, #items) for i=1,count do local item = items[i] local worldObj = item:getWorldItem() table.insert(consumedItems, item) worldObj:getSquare():transmitRemoveItemFromSquare(worldObj) end itemCount = itemCount - count removedFromGround = true end end if itemCount > 0 and itemFullType == "Base.Nails" then buildUtil.openNailsBox(ISItem) items = playerInv:getSomeTypeEvalRecurse(itemFullType, buildUtil.predicateMaterial, itemCount) for i=1,items:size() do local item = items:get(i-1) playerObj:removeFromHands(item) if item:getContainer() then item:getContainer():Remove(item); else playerInv:Remove(item) end itemCount = itemCount - 1 table.insert(consumedItems, item) end end if itemCount > 0 then print('ERROR: consumeMaterial() did not find all required materials!') end end if luautils.stringStarts(index, "use:") then local itemFullType = luautils.split(index, ":")[2]; local itemType = luautils.split(itemFullType, "%.")[2] local uses = tonumber(value); local remaining = uses local items = playerInv:getAllTypeRecurse(itemFullType) for i=1,items:size() do local item = items:get(i-1) if item:getDrainableUsesInt() > 0 then remaining = remaining - buildUtil.useDrainable(item, remaining) table.insert(consumedItems, item) if remaining <= 0 then break end end end if remaining > 0 then local groundItems = buildUtil.getMaterialOnGround(ISItem.sq) local items = groundItems[itemFullType] if items then for _,item in ipairs(items) do if item:getDrainableUsesInt() > 0 then remaining = remaining - buildUtil.useDrainable(item, remaining) table.insert(consumedItems, item) removedFromGround = true if remaining <= 0 then break end end end end end end if luautils.stringStarts(index, "xp:") then local skill = luautils.split(index, ":")[2]; local xp = tonumber(value); playerObj:getXp():AddXP(Perks.FromString(skill), xp); end end if removedFromGround then ISInventoryPage.dirtyUI() end return consumedItems end function buildUtil.openNailsBox(ISItem) if not ISItem or not ISItem.player then return {}; end if ISBuildMenu.cheat then return {}; end local playerObj = getSpecificPlayer(ISItem.player) local consumedItems = {} local playerInv = playerObj:getInventory() local itemFullType = "Base.NailsBox" local itemCount = 1 local items = playerInv:getSomeTypeEvalRecurse(itemFullType, buildUtil.predicateMaterial, itemCount) for i=1,items:size() do local item = items:get(i-1) playerObj:removeFromHands(item) if item:getContainer() then item:getContainer():Remove(item); else playerInv:Remove(item) end itemCount = itemCount - 1 table.insert(consumedItems, item) end -- if we didn't have all the required material inside our inventory, it's because the missing materials are on the ground, we gonna check them -- for each missing material in inventory if itemCount > 0 then -- check a 3x3 square around the building local groundItems = buildUtil.getMaterialOnGround(ISItem.square) local items = groundItems[itemFullType] if items then local count = math.min(itemCount, #items) for i=1,count do local item = items[i] local worldObj = item:getWorldItem() table.insert(consumedItems, item) worldObj:getSquare():transmitRemoveItemFromSquare(worldObj) end itemCount = itemCount - count removedFromGround = true end end for i=1,#consumedItems do local nail = InventoryItemFactory.CreateItem("Base.Nails") playerObj:getInventory():AddItems(nail, 20) -- 20x5=100 end if removedFromGround then ISInventoryPage.dirtyUI() end end function buildUtil.removeFromGround(square) for i = 0, square:getSpecialObjects():size()-1 do local thump = square:getSpecialObjects():get(i); if instanceof(thump, "IsoThumpable") then square:transmitRemoveItemFromSquare(thump); end end end function buildUtil.getMaterialOnGround(squareToCheck) local result = {} for x=squareToCheck:getX()-1,squareToCheck:getX()+1 do for y=squareToCheck:getY()-1,squareToCheck:getY()+1 do local square = getCell():getGridSquare(x,y,squareToCheck:getZ()) local wobs = square and square:getWorldObjects() or nil if wobs ~= nil then for i = 1,wobs:size() do local obj = wobs:get(i-1) local item = obj:getItem() if buildUtil.predicateMaterial(item) then local items = result[item:getFullType()] or {} table.insert(items, item) result[item:getFullType()] = items result[item:getType()] = items end end end end end return result end function buildUtil.getMaterialOnGroundCounts(itemMap) local result = {} for type,items in pairs(itemMap) do result[type] = #items end return result end function buildUtil.getMaterialOnGroundUses(itemMap) local result = {} for type,items in pairs(itemMap) do local item = items[1] if instanceof(item, "DrainableComboItem") then local uses = 0 for _,item in ipairs(items) do uses = uses + item:getDrainableUsesInt() end result[type] = uses end end return result end function buildUtil.checkMaterialOnGround(squareToCheck) local itemMap = buildUtil.getMaterialOnGround(squareToCheck) return buildUtil.getMaterialOnGroundCounts(itemMap) end -- all those infos are saved in java function buildUtil.setInfo(javaObject, ISItem) javaObject:setCanPassThrough(ISItem.canPassThrough); javaObject:setCanBarricade(ISItem.canBarricade); javaObject:setThumpDmg(ISItem.thumpDmg); javaObject:setIsContainer(ISItem.isContainer); javaObject:setIsDoor(ISItem.isDoor); javaObject:setIsDoorFrame(ISItem.isDoorFrame); javaObject:setCrossSpeed(ISItem.crossSpeed); javaObject:setBlockAllTheSquare(ISItem.blockAllTheSquare); javaObject:setName(ISItem.name); javaObject:setIsDismantable(ISItem.dismantable); javaObject:setCanBePlastered(ISItem.canBePlastered); javaObject:setIsHoppable(ISItem.hoppable); javaObject:setModData(copyTable(ISItem.modData)); javaObject:setIsThumpable(ISItem.isThumpable); if ISItem.isCorner then javaObject:setCorner(ISItem.isCorner); end if ISItem.containerType and javaObject:getContainer() then javaObject:getContainer():setType(ISItem.containerType); end if ISItem.canBeLockedByPadlock then javaObject:setCanBeLockByPadlock(ISItem.canBeLockedByPadlock); end if ISItem.modData and ISItem.modData["use:Base.BlowTorch"] then javaObject:setThumpSound("ZombieThumpMetal") end end local function addStairObject(objects, x, y, z) local square = getCell():getGridSquare(x, y, z) if not square then return end for i = square:getObjects():size(),1,-1 do local object = square:getObjects():get(i-1) if object:isStairsObject() then table.insert(objects, object) end end end local function getFloorObject(x, y, z) local square = getCell():getGridSquare(x, y, z) return square and square:getFloor() or nil end local function hasAdjacentFloor(x, y, z) for dy = -1,1 do for dx = -1,1 do if dx ~= 0 or dy ~= 0 then if getFloorObject(x + dx, y + dy, z) then return true end end end end return false end local function hasStairElements( x, y, z, checkTypes) local square = getCell():getGridSquare(x, y, z) if square and square:getFloor() then for _,objType in pairs(checkTypes) do if square:Has(objType) then return true; end end end return false; end -- for solid objects such as crates and other furniture only pass square and doAdjacent, for wall-like objects pass a north value for the object -- behaviour is different based on above in the check for topstair elements function buildUtil.stairIsBlockingPlacement( _square, _doAdjacent, _north ) if _square then -- check if any kind of stair is on square if _square:HasStairs() then if _north == nil then return true end -- allow walls with the right orientation if _north == true and _square:HasStairsNorth() then return true end if _north == false and _square:HasStairsWest() then return true end end if not _doAdjacent then return false; end -- check for top and bot stairs on adjacent positions local x,y,z = _square:getX(), _square:getY(), _square:getZ(); --check for bottom stair elements on current level if (_north==nil or _north==true) and hasStairElements(x,y-1,z,{IsoObjectType.stairsBN}) then return true; end if (_north==nil or _north==false) and hasStairElements(x-1,y,z,{IsoObjectType.stairsBW}) then return true; end --check for top stair elements below current level, for wall objects only returns true if the wall is actually blocking the stair if z>0 then if _north==nil and (hasStairElements(x,y+1,z-1,{IsoObjectType.stairsTN}) or hasStairElements(x+1,y,z-1,{IsoObjectType.stairsTW})) then return true; end if _north==true and hasStairElements(x,y,z-1,{IsoObjectType.stairsTN}) then return true; end if _north==false and hasStairElements(x,y,z-1,{IsoObjectType.stairsTW}) then return true; end end end return false; end local function countAdjacentStairs(x, y, z) local count = 0 local square = getCell():getGridSquare(x + 1, y, z) if square and square:Has(IsoObjectType.stairsTW) then count = count + 1 end square = getCell():getGridSquare(x, y + 1, z) if square and square:Has(IsoObjectType.stairsTN) then count = count + 1 end return count end function buildUtil.getStairObjects(object) local objects = {} local x, y, z = object:getX(), object:getY(), object:getZ() if object:getType() == IsoObjectType.stairsTW then table.insert(objects, object) addStairObject(objects, x + 1, y, z) addStairObject(objects, x + 2, y, z) elseif object:getType() == IsoObjectType.stairsMW then addStairObject(objects, x - 1, y, z) table.insert(objects, object) addStairObject(objects, x + 1, y, z) elseif object:getType() == IsoObjectType.stairsBW then addStairObject(objects, x - 2, y, z) addStairObject(objects, x - 1, y, z) table.insert(objects, object) elseif object:getType() == IsoObjectType.stairsTN then table.insert(objects, object) addStairObject(objects, x, y + 1, z) addStairObject(objects, x, y + 2, z) elseif object:getType() == IsoObjectType.stairsMN then addStairObject(objects, x, y - 1, z) table.insert(objects, object) addStairObject(objects, x, y + 1, z) elseif object:getType() == IsoObjectType.stairsBN then addStairObject(objects, x, y - 2, z) addStairObject(objects, x, y - 1, z) table.insert(objects, object) end -- Also destroy the floor at the top of the stairs if it isn't adjacent to another floor tile. -- Don't destroy the floor if it is adjacent to another staircase. if #objects > 0 then for i=1,#objects do local floor = nil x, y, z = objects[i]:getX(), objects[i]:getY(), objects[i]:getZ() if objects[i]:getType() == IsoObjectType.stairsTW then floor = getFloorObject(x - 1, y, z + 1) elseif objects[i]:getType() == IsoObjectType.stairsTN then floor = getFloorObject(x, y - 1, z + 1) end if floor and not hasAdjacentFloor(floor:getX(), floor:getY(), floor:getZ()) and countAdjacentStairs(floor:getX(), floor:getY(), floor:getZ() - 1) == 1 then table.insert(objects, floor) end end end return objects end function buildUtil.getDoubleDoorObjects(object) local objects = {} for i=1,4 do local dd = IsoDoor.getDoubleDoorObject(object, i) if dd then table.insert(objects, dd) end end return objects end function buildUtil.getGarageDoorObjects(object) local objects = {} if IsoDoor.getGarageDoorIndex(object) ~= -1 then table.insert(objects, object) local prev = IsoDoor.getGarageDoorPrev(object) while prev do table.insert(objects, prev) prev = IsoDoor.getGarageDoorPrev(prev) end local next = IsoDoor.getGarageDoorNext(object) while next do table.insert(objects, next) next = IsoDoor.getGarageDoorNext(next) end end return objects end function buildUtil.getGraveObjects(object) local objects = {} if not object or object:getName() ~= "EmptyGraves" then return objects end table.insert(objects, object) local sq1 = object:getSquare() local sq2 = nil if object:getNorth() then if object:getModData()["spriteType"] == "sprite1" then sq2 = getCell():getGridSquare(sq1:getX(), sq1:getY() - 1, sq1:getZ()) elseif object:getModData()["spriteType"] == "sprite2" then sq2 = getCell():getGridSquare(sq1:getX(), sq1:getY() + 1, sq1:getZ()) end else if object:getModData()["spriteType"] == "sprite1" then sq2 = getCell():getGridSquare(sq1:getX() - 1, sq1:getY(), sq1:getZ()) elseif object:getModData()["spriteType"] == "sprite2" then sq2 = getCell():getGridSquare(sq1:getX() + 1, sq1:getY(), sq1:getZ()) end end for i=1,sq2:getSpecialObjects():size() do local object2 = sq2:getSpecialObjects():get(i-1) if object2:getName() == "EmptyGraves" then table.insert(objects, object2) break end end return objects end function buildUtil.setHaveConstruction(_square, _hasConstruction) if _square and _square:getZone() then _square:getZone():setHaveConstruction(_hasConstruction); if isClient() then _square:getZone():sendToServer(); end; end; end