FILES ----- [NEW] behaviors\monsters\modular\basemonster.behavior [NEW] behaviors\monsters\modular\actions\blink.behavior [NEW] behaviors\monsters\modular\actions\hop.behavior behaviors\npc\idle.behavior [NEW] items\active\effects\laserbeam.lua items\active\weapons\weapon.lua items\active\weapons\melee\meleeweapon.lua items\active\weapons\melee\altabilities\broadsword\dashattack.lua items\active\weapons\melee\altabilities\broadsword\leapattack.lua items\active\weapons\melee\altabilities\broadsword\bladecharge\bladecharge.altability items\active\weapons\melee\altabilities\broadsword\bladecharge\bladecharge.lua items\active\weapons\melee\altabilities\broadsword\downstab\downstab.altability items\active\weapons\melee\altabilities\broadsword\downstab\downstab.lua items\active\weapons\melee\altabilities\broadsword\giantsword\giantsword.altability items\active\weapons\melee\altabilities\broadsword\giantsword\giantsword.lua items\active\weapons\melee\altabilities\broadsword\parry\parry.altability items\active\weapons\melee\altabilities\broadsword\parry\parry.lua items\active\weapons\melee\altabilities\broadsword\traildash\traildash.lua items\active\weapons\melee\altabilities\hammer\elementalaura\elementalaura.lua items\active\weapons\melee\altabilities\hammer\elementalpillar\elementalpillar.altability items\active\weapons\melee\altabilities\hammer\elementalpillar\elementalpillar.lua items\active\weapons\melee\altabilities\hammer\groundslam\groundslam.lua items\active\weapons\melee\altabilities\hammer\shockwave\physicalshockwave.altability items\active\weapons\melee\altabilities\hammer\shockwave\shockwave.altability items\active\weapons\melee\altabilities\hammer\shockwave\shockwave.lua items\active\weapons\melee\altabilities\hammer\uppercut\uppercut.lua items\active\weapons\melee\altabilities\spear\barrier\barrier.altability items\active\weapons\melee\altabilities\spear\barrier\barrier.lua items\active\weapons\melee\altabilities\spear\charge\charge.lua items\active\weapons\melee\altabilities\spear\flurry\flurry.lua items\active\weapons\melee\altabilities\spear\rocketspear\rocketspear.lua items\active\weapons\melee\hammer\hammer.lua items\active\weapons\melee\spear\spear.lua items\active\weapons\ranged\gun.lua items\active\weapons\ranged\altabilities\explosiveburst\explosiveburst.lua items\active\weapons\ranged\altabilities\markedshot\markedshot.altability items\active\weapons\ranged\altabilities\markedshot\markedshot.lua monsters\bmonster.lua [NEW] monsters\walkers\gleap\body.monsterpart [NEW] monsters\walkers\gleap\gleap.animation [NEW] monsters\walkers\gleap\gleap.frames [NEW] monsters\walkers\gleap\gleap.monstertype [NEW] monsters\walkers\gleap\gleap.png npcs\base.npctype npcs\default_reactions.config [NEW] particles\explosions\deathpoof\deathember.particle [NEW] particles\explosions\deathpoof\deathfizz1left.particle [NEW] particles\explosions\deathpoof\deathfizz1right.particle [NEW] particles\explosions\deathpoof\deathfizz2left.particle [NEW] particles\explosions\deathpoof\deathfizz2right.particle [NEW] particles\explosions\deathpoof\deathfizz3left.particle [NEW] particles\explosions\deathpoof\deathfizz3right.particle [NEW] particles\explosions\deathpoof\deathfizz4left.particle [NEW] particles\explosions\deathpoof\deathfizz4right.particle [NEW] particles\explosions\deathpoof\monstersplosion.particle projectiles\pillar\electricpillar.projectile projectiles\pillar\firepillar.projectile projectiles\pillar\icepillar.projectile projectiles\pillar\poisonpillar.projectile quests\generated\subquests.config scripts\behavior.lua scripts\util.lua [NEW] scripts\actions\builders.lua scripts\actions\movement.lua scripts\actions\quests.lua scripts\actions\sensor.lua scripts\quest\manager.lua scripts\quest\participant.lua scripts\questgen\planner.lua scripts\questgen\plannertests.lua stats\monster_primary.lua DIFFS ----- behaviors\npc\idle.behavior 3d2 < "description": "", 5a5 > "/scripts/actions/quests.lua", 9d8 < "/scripts/actions/quests.lua", 11c10,12 < "/scripts/actions/reaction.lua" --- > "/scripts/actions/reaction.lua", > "/scripts/actions/time.lua", > "/scripts/actions/position.lua" 18c19,21 < "statusRandomizationCooldown": 60 --- > "statusRandomizationCooldown": 60, > "questReactionCooldown": 15, > "questGiverWaitTime": 10 107a111,116 > "title": "hasQuest", > "type": "action", > "name": "hasQuest", > "parameters": {} > }, > { 112c121 < "chance": 0.95 --- > "chance": 0.1 398a408,504 > "cooldown": "", > "onFail": false, > "onSuccess": true > }, > "child": { > "title": "parallel", > "type": "composite", > "name": "parallel", > "parameters": { > "fail": 1, > "success": -1 > }, > "children": [ > { > "title": "hasQuest", > "type": "action", > "name": "hasQuest", > "parameters": {} > }, > { > "title": "sequence", > "type": "composite", > "name": "sequence", > "parameters": {}, > "children": [ > { > "title": "Tenant - Return Home", > "type": "module", > "name": "Tenant - Return Home", > "parameters": {} > }, > { > "title": "succeeder", > "type": "decorator", > "name": "succeeder", > "parameters": {}, > "child": { > "title": "sequence", > "type": "composite", > "name": "sequence", > "parameters": {}, > "children": [ > { > "title": "setInfluence", > "type": "action", > "name": "setInfluence", > "parameters": { > "influence": "hasquest" > }, > "output": { > "influence": "influence" > } > }, > { > "title": "unset", > "type": "action", > "name": "unset", > "parameters": { > "type": "entity", > "key": "reactTarget" > } > }, > { > "title": "react", > "type": "module", > "name": "react", > "parameters": { > "sendInfluence": false > } > }, > { > "title": "finalreact", > "type": "module", > "name": "finalreact", > "parameters": {} > } > ] > } > }, > { > "title": "timer", > "type": "action", > "name": "timer", > "parameters": { > "time": "" > } > } > ] > } > ] > } > }, > { > "title": "cooldown", > "type": "decorator", > "name": "cooldown", > "parameters": { 424a531,572 > { > "title": "sequence", > "type": "composite", > "name": "sequence", > "parameters": { > "type": "sliding", > "state": "off", > "fromEntity": "attackTarget", > "toEntity": "fleeTarget" > }, > "children": [ > { > "title": "hasQuest", > "type": "action", > "name": "hasQuest", > "parameters": {} > }, > { > "type": "composite", > "name": "selector", > "parameters": {}, > "children": [ > { > "title": "inRange", > "type": "action", > "name": "inRange", > "parameters": { > "position": "self", > "target": "spawn", > "range": 8 > } > }, > { > "title": "Tenant - Return Home", > "type": "module", > "name": "Tenant - Return Home", > "parameters": {} > } > ] > } > ] > }, items\active\weapons\weapon.lua 1c1 < weapon = {} --- > require "/scripts/util.lua" 3,7c3,4 < function weapon.activate(fireMode, shiftHeld) < if self.altAbility and self.altAbility.activate then < self.altAbility.activate(fireMode, shiftHeld) < end < end --- > -- handles weapon stances, animations, and abilities > Weapon = {} 9,12c6,11 < function weapon.update(dt, fireMode, shiftHeld) < if self.altAbility then < self.altAbility.update(dt, fireMode, shiftHeld) < end --- > function Weapon:new(config) > local newWeapon = copy(config) or {} > newWeapon.abilities = {} > newWeapon.transformationGroups = {} > setmetatable(newWeapon, extend(self)) > return newWeapon 15,24c14,17 < function weapon.setAltAbility() < local altAbilityConfig = item.instanceValue("altAbility") < if altAbilityConfig then < for _, script in ipairs(altAbilityConfig.scripts) do < require(script) < end < self.altAbility = setupAltAbility(altAbilityConfig) < self.altAbility.init() < end < end --- > function Weapon:init() > self.attackTimer = 0 > self.aimAngle = 0 > self.aimDirection = 1 26,31c19 < function weapon.setStance(stance) < self.relativeArmRotation = (stance.armRotation and util.toRadians(stance.armRotation)) or self.relativeArmRotation or 0 < self.relativeWeaponRotation = (stance.weaponRotation and util.toRadians(stance.weaponRotation)) or self.relativeWeaponRotation or 0 < self.weaponOffset = stance.weaponOffset or {0,0} < activeItem.setTwoHandedGrip(stance.twoHanded) < end --- > animator.setGlobalTag("elementalType", self.elementalType or "") 33,35c21,22 < function weapon.uninit() < if self.altAbility and self.altAbility.uninit then < self.altAbility.uninit() --- > for _,ability in pairs(self.abilities) do > ability:init() 39,44c26,27 < ----------------------------------------------- < -- MELEE < ----------------------------------------------- < < function weapon.setGeneratedMeleeStats() < math.randomseed(item.instanceValue("seed")) --- > function Weapon:update(dt, fireMode, shiftHeld) > self.attackTimer = math.max(0, self.attackTimer - dt) 46,47c29,31 < self.elementalType = item.instanceValue("elementalType") < animator.setGlobalTag("elementalType", self.elementalType) --- > -- clear damage sources > self:setDamage() > self:setOwnerDamage() 49,52c33,34 < -- Fire time variation < self.fireTime = item.instanceValue("fireTime") < if type(self.fireTime) == "table" then < self.fireTime = self.fireTime[1] + (math.random() * (self.fireTime[2] - self.fireTime[1])) --- > for _,ability in pairs(self.abilities) do > ability:update(dt, fireMode, shiftHeld) 55,60c37,48 < -- Damage variation < self.damageConfig = item.instanceValue("damageConfig") or {} < local dps = item.instanceValue("baseDps") < if dps then < if type(dps) == "table" then < dps = dps[1] + (math.random() * (dps[2] - dps[1])) --- > if self.currentState then > if coroutine.status(self.stateThread) ~= "dead" then > local status, result = coroutine.resume(self.stateThread) > if not status then error(result) end > else > self.currentAbility:uninit() > self.currentAbility = nil > self.currentState = nil > self.stateThread = nil > if self.onLeaveAbility then > self.onLeaveAbility() > end 62d49 < self.damageConfig.baseDamage = dps * self.fireTime 65,71c52,53 < -- Elemental status effects < self.damageConfig.statusEffects = self.damageConfig.statusEffects or {} < local elementalStatusEffects = item.instanceValue("elementalStatusEffects") < if elementalStatusEffects and elementalStatusEffects[self.elementalType] then < for _,statusEffect in ipairs(elementalStatusEffects[self.elementalType]) do < table.insert(self.damageConfig.statusEffects, statusEffect) < end --- > if self.stance then > self:updateAim() 73,76d54 < < self.aimDirection = activeItem.aimAngleAndDirection(0, activeItem.ownerAimPosition())[2] < self.aimAngle = 0 < activeItem.setFacingDirection(self.aimDirection) 79,87c57,61 < function weapon.setDamage(extraDamageConfig, swooshPart, bladePart, damageTimeout) < local swooshArea = swooshPart ~= "" and animator.partPoly(swooshPart or "swoosh", "damageArea") < local bladeArea = bladePart ~= "" and animator.partPoly(bladePart or "blade", "damageArea") < local damageArea = swooshArea or bladeArea < if damageArea then < local damageConfig = sb.jsonMerge(self.damageConfig, extraDamageConfig or {}) < if damageConfig.knockbackDirectional ~= false then < damageConfig.knockback = weapon.knockbackMomentum(damageConfig.knockback) < end --- > function Weapon:uninit() > if self.currentAbility and self.currentAbility.uninit then > self.currentAbility:uninit(true) > end > end 89c63,71 < local damageAmount = damageConfig.baseDamage * root.evalFunction("swordDamageLevelMultiplier", item.instanceValue("level", 1)) * activeItem.ownerPowerMultiplier() --- > function Weapon:setAbilityState(ability, state, ...) > self.currentAbility = ability > self.currentState = state > self.stateThread = coroutine.create(state) > local status, result = coroutine.resume(self.stateThread, ability, ...) > if not status then > error(result) > end > end 91,96c73,76 < -- Blade/hold damage < local repeatTimeout = self.fireTime < if not swooshArea then < damageAmount = damageAmount * (item.instanceValue("bladeDamageMultiplier") or 1.0) < repeatTimeout = item.instanceValue("bladeDamageTimeout", repeatTimeout) < end --- > function Weapon:addAbility(newAbility) > newAbility.weapon = self > table.insert(self.abilities, newAbility) > end 98,112c78,80 < activeItem.setWeaponDamageSources({{ < poly = damageArea, < damage = damageAmount, < sourceEntity = activeItem.ownerEntityId(), < team = activeItem.ownerTeam(), < damageSourceKind = damageConfig.damageSourceKind, < statusEffects = damageConfig.statusEffects, < knockback = damageConfig.knockback or 0, < rayCheck = true, < damageRepeatGroup = weapon.damageRepeatGroup(damageMode), < damageRepeatTimeout = damageTimeout or repeatTimeout < }}) < else < activeItem.setWeaponDamageSources({}) < end --- > function Weapon:addTransformationGroup(name, offset, rotation) > self.transformationGroups = self.transformationGroups or {} > table.insert(self.transformationGroups, {name = name, offset = offset, rotation = rotation}) 115,122c83,90 < function weapon.meleeUpdateAim(allowRotate, allowFlip) < animator.resetTransformationGroup("weapon") < animator.resetTransformationGroup("swoosh") < animator.translateTransformationGroup("weapon", self.weaponOffset) < animator.rotateTransformationGroup("weapon", self.relativeWeaponRotation) < animator.rotateTransformationGroup("swoosh", (math.pi / 2)) -- Align swoosh with weapon < animator.translateTransformationGroup("swoosh", self.weaponOffset) < animator.rotateTransformationGroup("swoosh", self.relativeWeaponRotation) --- > function Weapon:updateAim() > for _,group in pairs(self.transformationGroups) do > animator.resetTransformationGroup(group.name) > animator.translateTransformationGroup(group.name, group.offset) > animator.rotateTransformationGroup(group.name, group.rotation) > animator.translateTransformationGroup(group.name, self.weaponOffset) > animator.rotateTransformationGroup(group.name, self.relativeWeaponRotation) > end 124c92 < local aimAngle, aimDirection = table.unpack(activeItem.aimAngleAndDirection(item.instanceValue("aimOffset", 0), activeItem.ownerAimPosition())) --- > local aimAngle, aimDirection = table.unpack(activeItem.aimAngleAndDirection(self.aimOffset, activeItem.ownerAimPosition())) 126c94 < if allowRotate then --- > if self.stance.allowRotate then 129,130c97 < aimAngle = self.aimAngle + self.relativeArmRotation < activeItem.setArmAngle(aimAngle) --- > activeItem.setArmAngle(self.aimAngle + self.relativeArmRotation) 132c99 < if allowFlip then --- > if self.stance.allowFlip then 138,139c105,106 < function weapon.clearDamage() < activeItem.setWeaponDamageSources({}) --- > function Weapon:setOwnerDamage(damageConfig, damageArea, damageTimeout) > activeItem.setDamageSources({ self:damageSource(damageConfig, damageArea, damageTimeout) }) 142,144c109,110 < function weapon.damageRepeatGroup(mode) < mode = mode or "" < return activeItem.ownerEntityId() .. item.instanceValue("itemName") .. activeItem.hand() .. mode --- > function Weapon:setDamage(damageConfig, damageArea, damageTimeout) > activeItem.setWeaponDamageSources({ self:damageSource(damageConfig, damageArea, damageTimeout) }) 147,148c113,119 < function weapon.knockbackMomentum(knockback) < local knockbackMode = item.instanceValue("knockbackMode") or "default" --- > function Weapon:damageSource(damageConfig, damageArea, damageTimeout) > if damageArea then > local knockback = damageConfig.knockback > if knockback and damageConfig.knockbackDirectional ~= false then > knockback = knockbackMomentum(damageConfig.knockback, damageConfig.knockbackMode, self.aimAngle, self.aimDirection) > end > local damage = damageConfig.baseDamage * root.evalFunction("swordDamageLevelMultiplier", item.instanceValue("level", 1)) * activeItem.ownerPowerMultiplier() 150,151c121,132 < if type(knockback) == "table" then < return knockback --- > return { > poly = damageArea, > damage = damage, > sourceEntity = activeItem.ownerEntityId(), > team = activeItem.ownerTeam(), > damageSourceKind = damageConfig.damageSourceKind, > statusEffects = damageConfig.statusEffects, > knockback = knockback or 0, > rayCheck = true, > damageRepeatGroup = damageRepeatGroup(damageConfig.timeoutGroup), > damageRepeatTimeout = damageTimeout or damageConfig.timeout > } 152a134 > end 154,161c136,142 < if knockbackMode == "facing" then < return {mcontroller.facingDirection() * knockback, 0} < elseif knockbackMode == "aim" then < local aimVector = vec2.rotate({knockback, 0}, self.aimAngle) < aimVector[1] = mcontroller.facingDirection() * aimVector[1] < return aimVector < end < return knockback --- > function Weapon:setStance(stance) > self.stance = stance > self.weaponOffset = stance.weaponOffset or {0,0} > self.relativeWeaponRotation = util.toRadians(stance.weaponRotation) or 0 > self.relativeArmRotation = util.toRadians(stance.armRotation) or 0 > > activeItem.setTwoHandedGrip(stance.twoHanded or false) 164,166c145 < ----------------------------------------------- < -- RANGED < ----------------------------------------------- --- > -- Weapon abilities, state machines for weapon functionality 168,169c147 < function weapon.setGeneratedRangedStats() < math.randomseed(item.instanceValue("seed")) --- > WeaponAbility = {} 171,172c149,154 < self.elementalType = item.instanceValue("elementalType") < animator.setGlobalTag("elementalType", self.elementalType) --- > function WeaponAbility:new(config, stances) > local newAbility = config or {} > newAbility.stances = stances or {} > setmetatable(newAbility, extend(self)) > return newAbility > end 174,178c156,158 < -- Fire time variation < self.fireTime = item.instanceValue("fireTime") < if type(self.fireTime) == "table" then < self.fireTime = self.fireTime[1] + (math.random() * (self.fireTime[2] - self.fireTime[1])) < end --- > function WeaponAbility:update(dt, fireMode, shiftHeld) > self.dt, self.fireMode, self.shiftHeld = dt, fireMode, shiftHeld > end 180,185c160,162 < -- Damage variation < self.damageConfig = item.instanceValue("damageConfig") or {} < local dps = item.instanceValue("baseDps") < if type(dps) == "table" then < dps = dps[1] + (math.random() * (dps[2] - dps[1])) < end --- > function WeaponAbility:setState(state, ...) > self.weapon:setAbilityState(self, state, ...) > end 187,190c164,168 < self.pCount = item.instanceValue("projectileCount", 1) < if type(self.pCount) == "table" then < self.pCount = math.random(self.pCount[1], self.pCount[2]) < end --- > -- Helpers > function meleeWeaponConfig() > local config = {} > math.randomseed(item.instanceValue("seed")) > config.elementalType = item.instanceValue("elementalType") 192,194c170 < self.pParams = item.instanceValue("projectileParameters", {}) < self.pParams.power = ((dps * self.fireTime) / self.pCount) * root.evalFunction("gunDamageLevelMultiplier", item.instanceValue("level", 1)) < self.energyPerShot = root.evalFunction("gunLevelEnergyCostPerDamage", item.instanceValue("level", 1)) * weapon.rangedDamageAmount() --- > config.aimOffset = item.instanceValue("aimOffset") or 0 196c172,173 < self.muzzleOffset = item.instanceValue("muzzleOffset") --- > return config > end 198,205c175,177 < -- Fire mode < self.fireType = item.instanceValue("fireType") or "auto" < if type(self.fireType) == "table" then self.fireType = self.fireType[math.random(1,#self.fireType)] end < if self.fireType == "burst" then < self.burstCount = item.instanceValue("burstCount") or 3 < if type(self.burstCount) == "table" then self.burstCount = math.random(self.burstCount[1], self.burstCount[2]) end < self.burstTime = item.instanceValue("burstTime") or 3 < if type(self.burstTime) == "table" then self.burstTime = math.random(self.burstTime[1], self.burstTime[2]) end --- > function meleeAttackConfig(config, elementalType) > local parsed = copy(config) > math.randomseed(item.instanceValue("seed")) 206a179,180 > -- fire rate > parsed.fireTime = util.randomInRange(config.fireTime) 208c182,191 < self.fireTime = self.fireTime * self.burstCount --- > -- damage > parsed.damageConfig = config.damageConfig or {} > parsed.damageConfig = sb.jsonMerge(parsed.damageConfig, item.instanceValue("damageConfig", {})) > local dps = util.randomInRange(config.baseDps) > parsed.damageConfig.baseDamage = dps * parsed.fireTime > > -- status effects > local elementalEffects = config.elementalStatusEffects and config.elementalStatusEffects[elementalType] > if elementalEffects then > parsed.damageConfig.statusEffects = util.mergeLists(parsed.damageConfig.statusEffects or {}, elementalEffects) 211,213c194,195 < self.aimDirection = activeItem.aimAngleAndDirection(0, activeItem.ownerAimPosition())[2] < self.aimAngle = 0 < activeItem.setFacingDirection(self.aimDirection) --- > return parsed > end 215,218c197,203 < local elementalTypes = item.instanceValue("elementalProjectiles") or {} < self.projectileType = elementalTypes[self.elementalType] or item.instanceValue("projectileType") < if type(self.projectileType) == "table" then < self.projectileType = self.projectileType[math.random(1,#self.projectileType)] --- > function getAltAbility(elementalType) > local altAbilityConfig = item.instanceValue("altAbility") > if altAbilityConfig then > for _, script in ipairs(altAbilityConfig.scripts) do > require(script) > end > return setupAltAbility(altAbilityConfig, elementalType) 222,223c207,208 < function weapon.rangedDamageAmount() < return self.pParams.power * activeItem.ownerPowerMultiplier() * self.pCount --- > function partDamageArea(partName, polyName) > return animator.partPoly(partName, polyName or "damageArea") 226,234c211,214 < function weapon.fireProjectile(projectileType, projectileParams, inaccuracy, firePosition, pCount) < local pParams = sb.jsonMerge(self.pParams, projectileParams or {}) < pParams.powerMultiplier = activeItem.ownerPowerMultiplier() < < local id = 0 < for i = 1, (pCount or self.pCount) do < if projectileParams and projectileParams.timeToLive and type(projectileParams.timeToLive) == "table" then < pParams.timeToLive = projectileParams.timeToLive[1] + (math.random() * (projectileParams.timeToLive[2] - projectileParams.timeToLive[1])) < end --- > function damageRepeatGroup(mode) > mode = mode or "" > return activeItem.ownerEntityId() .. item.instanceValue("itemName") .. activeItem.hand() .. mode > end 236,259c216,217 < id = world.spawnProjectile( < projectileType or self.projectileType, < firePosition or weapon.firePosition(), < activeItem.ownerEntityId(), < weapon.aimVector(inaccuracy or item.instanceValue("inaccuracy", 0)), < false, < pParams < ) < end < return id < end < < function weapon.rangedUpdateAim(allowRotate, allowFlip) < animator.resetTransformationGroup("weapon") < animator.resetTransformationGroup("muzzle") < animator.rotateTransformationGroup("weapon", self.relativeWeaponRotation) < animator.translateTransformationGroup("muzzle", self.muzzleOffset) < animator.rotateTransformationGroup("muzzle", self.relativeWeaponRotation) < < world.debugPoint(vec2.add(mcontroller.position(), activeItem.handPosition(self.muzzleOffset)), "yellow") < < local aimAngle < local aimDirection < aimAngle, aimDirection = table.unpack(activeItem.aimAngleAndDirection(self.muzzleOffset[2], activeItem.ownerAimPosition())) --- > function knockbackMomentum(knockback, knockbackMode, aimAngle, aimDirection) > knockbackMode = knockbackMode or "aim" 261,262c219,220 < if allowRotate then < self.aimAngle = aimAngle --- > if type(knockback) == "table" then > return knockback 264d221 < activeItem.setArmAngle(self.aimAngle + self.relativeArmRotation) 266,267c223,228 < if allowFlip then < self.aimDirection = aimDirection --- > if knockbackMode == "facing" then > return {aimDirection * knockback, 0} > elseif knockbackMode == "aim" then > local aimVector = vec2.rotate({knockback, 0}, aimAngle) > aimVector[1] = aimDirection * aimVector[1] > return aimVector 269c230 < activeItem.setFacingDirection(self.aimDirection) --- > return knockback 272,273c233,242 < function weapon.firePosition() < return vec2.add(mcontroller.position(), activeItem.handPosition(self.muzzleOffset)) --- > -- Helpers > function rangedWeaponConfig() > local config = {} > math.randomseed(item.instanceValue("seed")) > config.elementalType = item.instanceValue("elementalType") > > config.aimOffset = item.instanceValue("aimOffset") or 0 > config.muzzleOffset = item.instanceValue("muzzleOffset") or {0,0} > > return config 276,293c245,247 < function weapon.aimVector(inaccuracy) < local aimVector = vec2.rotate({1, 0}, self.aimAngle + sb.nrand(inaccuracy, 0)) < aimVector[1] = aimVector[1] * self.aimDirection < return aimVector < end < < -- for use with coroutine < function weapon.burstFire(fireFunc, burstCount, burstTime) < burstCount = burstCount or self.burstCount < burstTime = burstTime or self.burstTime < local burstTimer = 0 < local burstShots = burstCount < < while burstShots > 0 do < while burstTimer > 0 do < coroutine.yield(burstShots) < burstTimer = math.max(0, burstTimer - script.updateDt()) < end --- > function rangedAttackConfig(config, elementalType) > local parsed = copy(config) > math.randomseed(item.instanceValue("seed")) 295,299c249 < if not status.overConsumeResource("energy", self.energyPerShot) then return false end < burstShots = burstShots - 1 < burstTimer = burstTime < fireFunc() < end --- > parsed.fireTime = util.randomInRange(config.fireTime) 301,302c251,265 < return false < end --- > parsed.projectileCount = util.randomIntInRange(config.projectileCount or 1) > > parsed.projectileParameters = config.projectileParameters or {} > parsed.projectileParameters.power = ((util.randomInRange(config.baseDps) * parsed.fireTime) / parsed.projectileCount) * root.evalFunction("gunDamageLevelMultiplier", item.instanceValue("level", 1)) > > -- Fire mode > parsed.fireType = util.randomFromList(config.fireType or "auto") > parsed.burstCount = util.randomIntInRange(config.burstCount or 3) > parsed.burstTime = util.randomInRange(config.burstTime or 0.075) > > local elementalTypes = config.elementalProjectiles or {} > parsed.projectileType = util.randomFromList(elementalTypes[elementalType] or config.projectileType) > > return parsed > end \ No newline at end of file items\active\weapons\melee\meleeweapon.lua 3c3 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\melee\altabilities\broadsword\dashattack.lua 2c2 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\melee\altabilities\broadsword\leapattack.lua 2c2 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\melee\altabilities\broadsword\bladecharge\bladecharge.altability 9,10c9 < "idle" : { < }, --- > "idle" : { }, items\active\weapons\melee\altabilities\broadsword\bladecharge\bladecharge.lua 2c2 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" 49a50 > animator.setAnimationState("bladeCharge", "idle") items\active\weapons\melee\altabilities\broadsword\downstab\downstab.altability 35d34 < "duration" : 0.2, items\active\weapons\melee\altabilities\broadsword\downstab\downstab.lua 3c3 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" 19c19,20 < and self.fireMode == "alt" then --- > and self.fireMode == "alt" > and not mcontroller.onGround() then 51,54c52 < local duration = self.stances.stab.duration < while duration > 0 or not mcontroller.onGround() do < duration = math.max(0, duration - self.dt) < --- > while self.fireMode == "alt" and not mcontroller.onGround() do items\active\weapons\melee\altabilities\broadsword\giantsword\giantsword.altability 9,10c9 < "idle" : { < }, --- > "idle" : { }, 14,17c13 < "transition" : "fullwindup", < "properties" : { < "persistentSound" : "/sfx/melee/charge_combo3.ogg" < } --- > "transition" : "fullwindup" 23,26c19 < "transition" : "full", < "properties" : { < "immediateSound" : "/sfx/melee/charge_full3.ogg" < } --- > "transition" : "full" 31,34c24 < "mode" : "loop", < "properties" : { < "persistentSound" : "/sfx/melee/charge_full_hold2.ogg" < } --- > "mode" : "loop" 41,42c31 < "idle" : { < }, --- > "idle" : { }, 162a152,165 > }, > "sounds" : { > "firecharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "firefullwindup" : [ "/sfx/melee/charge_full3.ogg" ], > "firefull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "electriccharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "electricfullwindup" : [ "/sfx/melee/charge_full3.ogg" ], > "electricfull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "icecharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "icefullwindup" : [ "/sfx/melee/charge_full3.ogg" ], > "icefull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "poisoncharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "poisonfullwindup" : [ "/sfx/melee/charge_full3.ogg" ], > "poisonfull" : [ "/sfx/melee/charge_full_hold2.ogg" ] items\active\weapons\melee\altabilities\broadsword\giantsword\giantsword.lua 2c2 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" 27a28 > animator.playSound(self.weapon.elementalType.."charge") 28a30 > local bladeState = "charge" 30a33,44 > -- manually update sounds so that we can use elemental variations > local newState = animator.animationState("giantBlade") > if newState ~= bladeState then > if newState == "fullwindup" then > animator.stopAllSounds(self.weapon.elementalType.."charge") > animator.playSound(self.weapon.elementalType.."fullwindup") > elseif newState == "full" then > animator.playSound(self.weapon.elementalType.."full", -1) > end > bladeState = newState > end > 45a60 > animator.stopAllSounds(self.weapon.elementalType.."full") 65a81,82 > animator.stopAllSounds(self.weapon.elementalType.."charge") > animator.stopAllSounds(self.weapon.elementalType.."full") items\active\weapons\melee\altabilities\broadsword\parry\parry.altability 3c3,7 < "animationCustom" : { }, --- > "animationCustom" : { > "sounds" : { > "parry" : [ "/sfx/melee/sword_parry.ogg" ] > } > }, items\active\weapons\melee\altabilities\broadsword\parry\parry.lua 1a2 > require "/scripts/status.lua" 29a31,34 > local damageListener = damageListener("damageTakenSince", function(notifications) > animator.playSound("parry") > end) > 30a36 > damageListener:update() items\active\weapons\melee\altabilities\broadsword\traildash\traildash.lua 2c2 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" 44c44 < mcontroller.setVelocity({self.weapon.aimDirection * self.dashSpeed, 0}) --- > mcontroller.setVelocity({self.weapon.aimDirection * self.dashSpeed, -200}) items\active\weapons\melee\altabilities\hammer\elementalaura\elementalaura.lua 4c4 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\melee\altabilities\hammer\elementalpillar\elementalpillar.altability 51c51,62 < "createPillar" : [ "/sfx/gun/grenadeblast3.ogg" ] --- > "firecharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "firefull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "fireimpact" : [ "/sfx/gun/grenadeblast3.ogg" ], > "electriccharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "electricfull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "electricimpact" : [ "/sfx/gun/grenadeblast3.ogg" ], > "icecharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "icefull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "iceimpact" : [ "/sfx/gun/grenadeblast3.ogg" ], > "poisoncharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "poisonfull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "poisonimpact" : [ "/sfx/gun/grenadeblast3.ogg" ] 75c86 < "pillarBaseDistance" : 4, --- > "pillarBaseDistance" : 2, 78c89 < "pillarDuration" : 3.0, --- > "pillarDuration" : 1.0, items\active\weapons\melee\altabilities\hammer\elementalpillar\elementalpillar.lua 3c3 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" 24a25 > animator.playSound(self.weapon.elementalType.."charge") 25a27 > local wasFull = false 29a32,37 > if chargeTimer == self.chargeTime and not wasFull then > wasFull = true > animator.stopAllSounds(self.weapon.elementalType.."charge") > animator.playSound(self.weapon.elementalType.."full", -1) > end > 37a46 > 40a50,52 > animator.stopAllSounds(self.weapon.elementalType.."charge") > animator.stopAllSounds(self.weapon.elementalType.."full") > 57a70,71 > animator.stopAllSounds(self.weapon.elementalType.."charge") > animator.stopAllSounds(self.weapon.elementalType.."full") 88c102 < animator.playSound("createPillar") --- > animator.playSound(self.weapon.elementalType.."impact") 92c106 < projectileParameters.actionOnTimeout[1].config.timeToLive = self.pillarDuration - projectileParameters.timeToLive --- > projectileParameters.actionOnTimeout[1].config.timeToLive = self.pillarDuration - (2 * projectileParameters.timeToLive) items\active\weapons\melee\altabilities\hammer\groundslam\groundslam.lua 3c3 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\melee\altabilities\hammer\shockwave\physicalshockwave.altability 12c12,14 < "shockwave" : [ "/sfx/gun/grenadeblast3.ogg" ] --- > "physicalcharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "physicalfull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "physicalimpact" : [ "/sfx/gun/grenadeblast3.ogg" ] items\active\weapons\melee\altabilities\hammer\shockwave\shockwave.altability 51c51,62 < "shockwave" : [ "/sfx/gun/grenadeblast3.ogg" ] --- > "firecharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "firefull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "fireimpact" : [ "/sfx/gun/grenadeblast3.ogg" ], > "electriccharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "electricfull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "electricimpact" : [ "/sfx/gun/grenadeblast3.ogg" ], > "icecharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "icefull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "iceimpact" : [ "/sfx/gun/grenadeblast3.ogg" ], > "poisoncharge" : [ "/sfx/melee/charge_combo3.ogg" ], > "poisonfull" : [ "/sfx/melee/charge_full_hold2.ogg" ], > "poisonimpact" : [ "/sfx/gun/grenadeblast3.ogg" ] items\active\weapons\melee\altabilities\hammer\shockwave\shockwave.lua 3c3 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" 24a25 > animator.playSound(self.weapon.elementalType.."charge") 25a27 > local wasFull = false 29a32,37 > if chargeTimer == self.chargeTime and not wasFull then > wasFull = true > animator.stopAllSounds(self.weapon.elementalType.."charge") > animator.playSound(self.weapon.elementalType.."full", -1) > end > 37a46 > 40a50,52 > animator.stopAllSounds(self.weapon.elementalType.."charge") > animator.stopAllSounds(self.weapon.elementalType.."full") > 57a70,71 > animator.stopAllSounds(self.weapon.elementalType.."charge") > animator.stopAllSounds(self.weapon.elementalType.."full") 84c98 < animator.playSound("shockwave") --- > animator.playSound(self.weapon.elementalType.."impact") items\active\weapons\melee\altabilities\hammer\uppercut\uppercut.lua 2c2 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\melee\altabilities\spear\barrier\barrier.altability 61a62,68 > }, > > "sounds" : { > "fire" : [ "/sfx/objects/teslaspike.ogg" ], > "electric" : [ "/sfx/objects/teslaspike.ogg" ], > "ice" : [ "/sfx/objects/teslaspike.ogg" ], > "poison" : [ "/sfx/objects/teslaspike.ogg" ] items\active\weapons\melee\altabilities\spear\barrier\barrier.lua 2c2 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" 23a24 > animator.playSound(self.weapon.elementalType, -1) 60a62 > animator.stopAllSounds(self.weapon.elementalType) items\active\weapons\melee\altabilities\spear\charge\charge.lua 2c2 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\melee\altabilities\spear\flurry\flurry.lua 2c2 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\melee\altabilities\spear\rocketspear\rocketspear.lua 2c2 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\melee\hammer\hammer.lua 5c5 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\melee\spear\spear.lua 3c3 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\ranged\gun.lua 4c4 < require "/items/active/weapons/weapon2.lua" --- > require "/items/active/weapons/weapon.lua" items\active\weapons\ranged\altabilities\explosiveburst\explosiveburst.lua 37d36 < world.logInfo("self.projectileParameters.timeToLive = %s", self.projectileParameters.timeToLive) items\active\weapons\ranged\altabilities\markedshot\markedshot.altability 6c6,8 < "targetAcquired" : [ "/sfx/interface/sniper_mark_target3.ogg" ], --- > "targetAcquired1" : [ "/sfx/interface/sniper_mark_target3.ogg" ], > "targetAcquired2" : [ "/sfx/interface/sniper_mark_target3.ogg" ], > "targetAcquired3" : [ "/sfx/interface/sniper_mark_target3.ogg" ], items\active\weapons\ranged\altabilities\markedshot\markedshot.lua 37c37 < animator.playSound("targetAcquired") --- > animator.playSound("targetAcquired"..#self.targets) 46c46 < if #self.targets > 0 and not world.lineTileCollision(mcontroller.position(), weapon.firePosition()) then --- > if #self.targets > 0 and not world.lineTileCollision(mcontroller.position(), self:firePosition()) then 89c89 < if world.lineTileCollision(weapon.firePosition(), world.entityPosition(entityId)) then --- > if world.lineTileCollision(self:firePosition(), world.entityPosition(entityId)) then monsters\bmonster.lua 28a29,31 > if entity.configParameter("deathParticles") then > entity.setDeathParticleBurst(entity.configParameter("deathParticles")) > end 29a33,36 > mcontroller.setAutoClearControls(false) > self.behaviorTickRate = 1 > self.behaviorTick = math.random(1, self.behaviorTickRate) > 36,42c43,52 < if self.behavior:run(dt) ~= "running" then < self.behavior:reset() < end < < self.interacted = false < self.damaged = false < self.notifications = {} --- > if self.behaviorTick >= self.behaviorTickRate then > self.behaviorTick = self.behaviorTick - self.behaviorTickRate > mcontroller.clearControls() > > self.tradingEnabled = false > self.setFacingDirection = false > > BData:setEntity("self", entity.id()) > BData:setPosition("self", mcontroller.position()) > BData:setNumber("facingDirection", mcontroller.facingDirection()) 44,46c54,56 < mcontroller.controlParameters({ < collisionPoly = self.collisionPoly < }) --- > if self.behavior and self.behavior:run(dt * self.behaviorTickRate) ~= "running" then > self.behavior:reset() > end 48,49c58,60 < if entity.configParameter("lockFacingDirection") then < mcontroller.controlFace(1) --- > self.interacted = false > self.damaged = false > self.notifications = {} 50a62,64 > self.behaviorTick = self.behaviorTick + 1 > > runWorkers() 59a74,75 > > world.logInfo("Damage notification: %s", args) 88,89c104,105 < type = "movement", < state = "idle" --- > type = nil, > state = nil 137a154,163 > -- param touchDamage > function setDamageOnTouch(args, output) > args = parseArgs(args, { > touchDamage = false > }) > > entity.setDamageOnTouch(args.touchDamage) > return true > end > 191c217 < end --- > end \ No newline at end of file npcs\base.npctype 78a79,81 > "hasquest" : [ > [1.0, "smile"] > ], 263a267,269 > "hasquest" : [ > [1.0, "panic"] > ], 384a391,393 > "hasquest" : [ > [1.0, "jumpforjoy"] > ], 470a480,482 > "hasquest" : [ > [1.0, "hop"] > ], 553a566,568 > "hasquest" : [ > [1.0, "cry"] > ], 618a634,636 > "hasquest" : [ > [1.0, "hop"] > ], 662a681,683 > ], > "hasquest" : [ > [1.0, "sleep"] npcs\default_reactions.config 134a135,140 > // hasquest reactions > // Happens when the npc is at home, waiting for a player to take up and > // complete its quest. > "hasquest" : [ > ], > projectiles\pillar\electricpillar.projectile 7c7 < "lightColor" : [160, 50, 50], --- > "lightColor" : [50, 50, 160], 23a24,70 > }, > { > "action" : "loop", > "count" : 2, > "body" : [ > { > "action" : "particle", > "specification" : { > "type" : "animated", > "looping" : true, > "animation" : "/animations/teslabolt/teslabolt.animation", > "size" : 0.5, > "fade" : 0.9, > "light" : [50, 50, 160], > "timeToLive" : 0.5, > "destructionAction" : "shrink", > "destructionTime" : 0.425, > "layer" : "middle", > "variance" : { > "position" : [1.5, 1.0], > "timeToLive" : 0.3, > "fade" : 0.3 > } > } > }, > { > "action" : "particle", > "specification" : { > "type" : "ember", > "size" : 1.5, > "color" : [100, 100, 255, 255], > "fade" : 0.9, > "finalVelocity" : [0, -2.0], > "approach" : [0, 20], > "light" : [50, 50, 160], > "timeToLive" : 0.5, > "layer" : "middle", > "variance" : { > "position" : [2.0, 1.0], > "initialVelocity" : [2, 2], > "size" : 0.5, > "timeToLive" : 0.3, > "fade" : 0.3 > } > } > } > ] projectiles\pillar\firepillar.projectile 23a24,84 > }, > { > "action" : "loop", > "count" : 2, > "body" : [ > { > "action" : "particle", > "specification" : { > "type" : "animated", > "looping" : true, > "animation" : "/animations/statuseffects/burning/burning.animation", > "approach" : [50, 50], > "destructionAction" : "shrink", > "destructionTime" : 0.425, > "fade" : 1, > "light" : [191, 103, 2], > "size" : 0.5, > "layer" : "middle", > "timeToLive" : 0.425, > "variance" : { > "position" : [1.5, 1.0], > "finalVelocity" : [1.5, 1.5] > } > } > }, > { > "action" : "particle", > "specification" : { > "type" : "animated", > "animation" : "/animations/dust2/dust2.animation", > "approach" : [50, 50], > "destructionAction" : "shrink", > "destructionTime" : 0.425, > "fade" : 1, > "layer" : "back", > "timeToLive" : 0.225, > "variance" : { > "position" : [1.5, 1.0], > "finalVelocity" : [1.5, 1.5] > } > } > }, > { > "action" : "particle", > "specification" : { > "type" : "ember", > "size" : 1.0, > "color" : [253, 143, 77, 255], > "fade" : 0.9, > "approach" : [20, 20], > "light" : [191, 103, 2], > "destructionTime" : 0.8, > "layer" : "back", > "variance" : { > "position" : [2.0, 1.0], > "finalVelocity" : [1.5, 1.5], > "size" : 0.5 > } > } > } > ] projectiles\pillar\icepillar.projectile 7c7 < "lightColor" : [160, 50, 50], --- > "lightColor" : [100, 100, 160], 23a24,87 > }, > { > "action" : "loop", > "count" : 2, > "body" : [ > { > "action" : "particle", > "specification" : { > "type" : "animated", > "looping" : true, > "animation" : "/animations/statuseffects/frozen/frozen.animation", > "size" : 0.5, > "approach" : [30, 30], > "timeToLive" : 0.5, > "destructionAction" : "shrink", > "destructionTime" : 0.425, > "layer" : "middle", > "variance" : { > "position" : [1.5, 1.0], > "finalVelocity" : [1.5, 1.5], > "timeToLive" : 0.3 > } > } > }, > { > "action" : "particle", > "specification" : { > "type" : "animated", > "looping" : true, > "animation" : "/animations/frozendust2/frozendust2.animation", > "size" : 0.5, > "approach" : [30, 30], > "timeToLive" : 0.5, > "destructionAction" : "shrink", > "destructionTime" : 0.425, > "layer" : "middle", > "variance" : { > "position" : [1.5, 1.0], > "finalVelocity" : [1.5, 1.5], > "timeToLive" : 0.3 > } > } > }, > { > "action" : "particle", > "specification" : { > "type" : "ember", > "size" : 1.5, > "color" : [99, 216, 232, 255], > "fade" : 0.9, > "approach" : [30, 30], > "light" : [50, 140, 160], > "timeToLive" : 0.5, > "layer" : "middle", > "variance" : { > "position" : [2.0, 1.0], > "finalVelocity" : [1.5, 1.5], > "size" : 0.5, > "timeToLive" : 0.3, > "fade" : 0.3 > } > } > } > ] projectiles\pillar\poisonpillar.projectile 7c7 < "lightColor" : [160, 50, 50], --- > "lightColor" : [50, 160, 50], 23a24,65 > }, > { > "action" : "loop", > "count" : 2, > "body" : [ > { > "action" : "particle", > "specification" : { > "type" : "animated", > "animation" : "/animations/statuseffects/poison/poisonshort.animation", > "finalVelocity" : [0, 2], > "timeToLive" : 0.45, > "destructionAction" : "shrink", > "destructionTime" : 0.425, > "layer" : "middle", > "variance" : { > "initialVelocity" : [2.0, 2.0], > "position" : [1.5, 1.0] > } > } > }, > { > "action" : "particle", > "specification" : { > "type" : "animated", > "animation" : "/animations/poisondrip/poisondrip1.animation", > "initialVelocity" : [0, -5], > "finalVelocity" : [0, -10], > "timeToLive" : 0.45, > "fade" : 0.5, > "light" : [70, 160, 30], > "destructionAction" : "shrink", > "destructionTime" : 1, > "layer" : "middle", > "variance" : { > "initialVelocity" : [1.0, 3.0], > "position" : [2.0, 1.0], > "timeToLive" : 0.3 > } > } > } > ] quests\generated\subquests.config 320d319 < ["criminal", "thief"], scripts\behavior.lua 290,293c290 < newNode.children = {} < for _,child in ipairs(node.children) do < table.insert(newNode.children, nodeFromJson(child, parameters, moduleName)) < end --- > newNode.children = node.children 298c295 < newNode.child = nodeFromJson(node.child, parameters, moduleName) --- > newNode.child = node.child 303a301,309 > > if newNode.children then > newNode.children = util.map(newNode.children, function(child) > return nodeFromJson(child, parameters, moduleName) > end) > elseif newNode.child then > newNode.child = nodeFromJson(newNode.child, parameters, moduleName) > end > scripts\util.lua 385a386,391 > function util.appendLists(first, second) > for _,item in ipairs(second) do > table.insert(first, item) > end > end > scripts\actions\movement.lua 59a60,83 > -- param direction > function canMove(args, output) > args = parseArgs(args, { > direction = "facingDirection" > }) > > local direction = BData:getNumber(args.direction) > if direction == nil then return false end > > local position = vec2.add(mcontroller.position(), {direction, 0}) > if direction > 0 then > position[1] = math.ceil(position[1]) > else > position[1] = math.floor(position[1]) > end > local groundPosition = findGroundPosition(position, -1, 1, true, {"Null", "Block", "Dynamic"}) > > if groundPosition then > return true > else > return false > end > end > 212a237,250 > return true > end > > -- output velocity > -- output x > -- output y > function velocity(args, output) > args = parseArgs(args, {}) > > local velocity = mcontroller.velocity() > BData:setVec2(output.velocity, velocity) > BData:setNumber(output.x, velocity[1]) > BData:setNumber(output.y, velocity[2]) > scripts\actions\quests.lua 77a78,82 > function hasQuest(args, output) > args = parseArgs(args, { }) > return self.quest.isOfferingQuests > end > scripts\actions\sensor.lua 0a1,2 > require "/scripts/rect.lua" > 31a34,59 > end > > -- param direction > function wallCollision(args, output) > args = parseArgs(args, { > direction = "facingDirection" > }) > > world.logInfo("wallCollision") > > local direction = BData:getNumber(args.direction) > if not direction then return false end > > local bounds = rect.translate(mcontroller.boundBox(), mcontroller.position()) > if direction > 0 then > bounds[1] = bounds[3] - 0.2 > bounds[3] = bounds[3] + 0.2 > else > bounds[3] = bounds[1] + 0.2 > bounds[1] = bounds[1] - 0.2 > end > bounds[2] = bounds[2] + 1.0 > > util.debugRect(bounds, "yellow") > > return world.rectTileCollision(bounds, {"Null","Block","Dynamic"}) scripts\quest\manager.lua 45a46 > self:resetExpiration() 50a52,55 > function QuestManager:resetExpiration() > self.data.expiration = world.time() + entity.configParameter("quest.expiration", 300) > end > 117a123,126 > > if isEmpty(self.data.playerProgress) and world.time() > self.data.expiration then > self:stopOffering() > end 233a243 > self:resetExpiration() 240a251 > self:resetExpiration() scripts\quest\participant.lua 104d103 < self.isOfferingQuests = false 111d109 < self.isOfferingQuests = true 113a112 > self.isOfferingQuests = #offeredQuests > 0 scripts\questgen\planner.lua 23,33c23 < if type(value) == "number" then < predicands[#predicands+1] = value < elseif type(value) == "table" and value.literal then < predicands[#predicands+1] = value.literal < else < local name = value < if not symbols[name] then < symbols[name] = Variable.new(name, vartable) < end < predicands[#predicands+1] = symbols[name] < end --- > predicands[#predicands+1] = Predicand.fromJson(value, symbols, vartable) 448a439,454 > function Predicand.fromJson(value, symbols, vartable) > if type(value) == "number" then > return value > elseif type(value) == "table" and value.literal then > return value.literal > elseif type(value) == "string" then > local name = value > if not symbols[name] then > symbols[name] = Variable.new(name, vartable) > end > return symbols[name] > else > error("Cannot parse predicand "..tostring(value)) > end > end > 825,838d830 < function Planner:pop(stack) < -- returns (top value, new stack) < return table.unpack(stack) < end < < function Planner:push(value, stack) < -- returns the new stack < return {value, stack} < end < < function Planner:createStack() < return nil < end < 892a885,887 > -- Assigning some variables can make it easier to generate examples for > -- other variables, so we keep trying to generate assignments until we've > -- been through every term without making any changes. 914c909 < Term = createClass("Term") --- > local Term = createClass("Term") 919,924c914,923 < function Planner:generatePlan(initialState, goal, maxCost) < -- Nondeterministic regression planning algorithm, based on STRIPS < local state = initialState < local plan = {} < local failure = nil < maxCost = maxCost or self.maxCost --- > local GoalStackPlan = createClass("GoalStackPlan") > function GoalStackPlan:init(planner, state, maxCost) > self.planner = planner > self.debug = planner.debug > self.state = state > self.maxCost = maxCost > self.goalStack = {} > self.failure = nil > self.plan = {} > end 926c925,932 < self:generateAssignments(state, goal) --- > function GoalStackPlan:debugLog(...) > return self.planner:debugLog(...) > end > > function GoalStackPlan:fail(message) > self.failure = message > self:debugLog("Quest planning failed: %s", message) > end 927a934,978 > function GoalStackPlan:interleaveTerms(conjunction) > local interleaving = shuffled(conjunction:terms()) > > -- Heuristic: push non-static relations first, static relations second. > -- Reasoning: static relations are applied as constraints anyway, so > -- they can never be violated by the non-static relations, whereas > -- the reverse order *could* result in unsatisifable assignments. > table.sort(interleaving, function (a, b) > return not a.static and b.static > end) > > return util.map(interleaving, Term.new) > end > > function GoalStackPlan:achieveConjunction(conj) > -- Push all the terms in conj onto the goal stack. > -- When all the terms have been achieved, the conjunction is achieved. > > local interleaving = self:interleaveTerms(conj) > > if self.debug then > for _,term in ipairs(interleaving) do > self:debugLog("Pushing term %s", term) > end > end > > util.appendLists(self.goalStack, interleaving) > end > > function GoalStackPlan:achieveTerm(term) > -- If the term is not already achieved, push an operation onto the goal stack > -- that would achieve it. > > local predicate = term.predicate > if predicate:satisfyWithState(self.state) then > return > end > > self:debugLog("Term not immediately satisfiable %s", predicate) > local op = self.planner:chooseOperation(predicate) > if not op then > self:fail("No operator to produce "..tostring(predicate)) > return > end > 930,931c981 < while not failure and not goal:test(state) do < local goalStack = self:push(goal, self:createStack()) --- > self.planner:generateAssignments(self.state, op:preconditions()) 933,946c983,985 < while not failure and goalStack do < local top < top, goalStack = self:pop(goalStack) < self:debugLog("Popped %s", top) < < match (top) { < [Conjunction] = function (conj) < local interleaving = shuffled(conj:terms()) < for _,predicate in ipairs(interleaving) do < if not predicate.static then < self:debugLog("Pushing term %s", predicate) < goalStack = self:push(Term.new(predicate), goalStack) < end < end --- > self:debugLog("Pushing op %s", op) > table.insert(self.goalStack, op) > self.maxCost = self.maxCost - op.cost 948c987 < coroutine.yield() --- > coroutine.yield() 950,955c989,992 < for _,predicate in ipairs(interleaving) do < if predicate.static then < self:debugLog("Pushing term %s", predicate) < goalStack = self:push(Term.new(predicate), goalStack) < end < end --- > local preconds = op:dynamicPreconditions():withImplications() > self:debugLog("Pushing preconds %s", preconds) > table.insert(self.goalStack, preconds) > end 957,958c994,995 < coroutine.yield() < end, --- > function GoalStackPlan:applyOperation(op) > self.planner:generateAssignments(self.state, op:preconditions()) 960,981c997,1001 < [Term] = function (term) < local predicate = term.predicate < if not predicate:satisfyWithState(state) then < self:debugLog("Term not immediately satisfiable %s", predicate) < local op = self:chooseOperation(predicate) < if not op then < failure = "No operator to produce "..tostring(predicate) < return < end < < coroutine.yield() < < self:generateAssignments(state, op:preconditions()) < self:debugLog("Pushing op %s", op) < goalStack = self:push(op, goalStack) < maxCost = maxCost - op.cost < < coroutine.yield() < < local preconds = op:dynamicPreconditions():withImplications() < self:debugLog("Pushing preconds %s", preconds) < goalStack = self:push(preconds, goalStack) --- > self:debugLog("Applying op %s", op) > if not op:isGround() then > self:fail("Operation contains free variables") > return > end 983,985c1003,1010 < end < coroutine.yield() < end, --- > self:debugLog("Old state: %s", self.state) > local newState = op:apply(self.state) > if newState == nil then > -- Op's preconds (a subgoal) were clobbered by another op. > -- Try to bridge the gap between the current state and the preconds > -- by generating a subplan taking us from the current state to > -- the preconds. > self:debugLog("Generating subplan. Cost allowed: %s", self.maxCost) 987,1025c1012,1023 < [Operation] = function (op) < self:debugLog("Applying op %s", op) < if not op:isGround() then < failure = "Operation contains free variables" < return < end < self:debugLog("Old state: %s", state) < if not op:constraints():test(state) then < failure = "Operation constraints not satisfied" < return < end < local newState = op:apply(state) < if newState == nil then < -- Op's preconds (a subgoal) were clobbered by another op. < -- Try to bridge the gap between the current state and the preconds < -- by generating a subplan taking us from the current state to < -- the preconds. < self:debugLog("Generating subplan. Cost allowed: %s", maxCost) < local subplan < subplan, newState = self:generatePlan(state, op:dynamicPreconditions():withImplications(), maxCost) < if subplan ~= nil then < if subplan ~= nil then < self:debugLog("Adding subplan:") < for _,step in ipairs(subplan) do < plan[#plan+1] = step < self:debugLog("Added op %s to plan", op) < end < end < newState = op:apply(newState) < end < if newState == nil then < failure = "Subgoal was clobbered" < return < end < end < state = newState < self:debugLog("New state: %s", state) < plan[#plan+1] = op < self:debugLog("Added op %s to plan", op) --- > self:achieveGoal(op:dynamicPreconditions():withImplications()) > newState = op:apply(self.state) > if self.failure or not newState then > self:fail("Subgoal was clobbered") > return > end > end > self.state = newState > self:debugLog("New state: %s", self.state) > self.plan[#self.plan+1] = op > self:debugLog("Added op %s to plan", op) > end 1027,1029c1025,1026 < coroutine.yield() < end < } --- > function GoalStackPlan:achieveGoal(goal) > -- Nondeterministic regression planning algorithm, based on STRIPS 1031,1034c1028,1046 < if maxCost <= 0 then < failure = "Plan cost too high" < break < end --- > self.planner:generateAssignments(self.state, goal) > self:debugLog("Pushing goal %s", goal) > table.insert(self.goalStack, goal) > > while not self.failure and #self.goalStack > 0 do > coroutine.yield() > > local top = table.remove(self.goalStack) > self:debugLog("Popped %s", top) > > match (top) { > [Conjunction] = function () self:achieveConjunction(top) end, > [Term] = function () self:achieveTerm(top) end, > [Operation] = function() self:applyOperation(top) end > } > > if self.maxCost <= 0 then > self:fail("Plan cost too high") > break 1038,1039c1050,1054 < if failure then < self:debugLog("Quest planning failed: %s", failure) --- > if not self.failure and not goal:isGround() then > self.planner:generateAssignments(self.state, goal) > end > > if self.failure then 1042a1058,1063 > if not self.failure and not goal:test(self.state) then > -- Subgoal was clobbered. Generate a subplan to bridge the gap between > -- the current state and the goal state. > return self:achieveGoal(goal) > end > 1045c1066 < for _,op in ipairs(plan) do --- > for _,op in ipairs(self.plan) do 1052c1073,1077 < return plan, state --- > return self.plan > end > > function Planner:generatePlan(initialState, goal, maxCost) > return GoalStackPlan.new(self, initialState, maxCost or self.maxCost):achieveGoal(goal) scripts\questgen\plannertests.lua 16c16 < world.logInfo("Planner broke: %s", result) --- > world.logInfo("Planner broke: %s", debug.traceback(co, result)) stats\monster_primary.lua 29c29 < if vec2.mag(momentum) > 0 then --- > if status.resourcePositive("health") and vec2.mag(momentum) > 0 then