From de9ca931f050e40a20c5781830d065350cba7aa6 Mon Sep 17 00:00:00 2001 From: jss2a98aj Date: Thu, 2 Sep 2021 01:43:45 -0400 Subject: [PATCH] Update 1.5.1 --- Bugs fixed.txt | 43 +- Changelog.txt | 22 + Starbound Patch Project/_metadata | 2 +- .../celestial/names.config.patch | 26 + .../apexcity/apexcitybuilding2clue.json.patch | 12 + .../humanoid/avian/femalebody.png | Bin 0 -> 3091 bytes .../humanoid/human/femalebody.png | Bin 0 -> 3963 bytes .../interface.config.patch | 29 + .../interface/collections.png | Bin 0 -> 397 bytes .../interface/optionsmenu/body_blank.png | Bin 0 -> 308 bytes .../optionsmenu/optionsmenu.config.patch | 74 + .../interface/statuses/electricblock.png | Bin 0 -> 203 bytes .../items/augments/back/fireblockaugment.png | Bin 0 -> 206 bytes .../monsters/boss/swansong/swansong.lua | 1479 +++++++++++++++++ .../sandstonestatueapex.object.patch | 12 + .../sandstonestatuefloran.object.patch | 26 + .../sandstonestatueglitch.object.patch | 31 + .../explosivebarrel.object.patch | 12 + .../upgradeablecraftingobject.lua | 106 -- Steam page description.txt | 17 + 20 files changed, 1774 insertions(+), 117 deletions(-) create mode 100644 Starbound Patch Project/celestial/names.config.patch create mode 100644 Starbound Patch Project/dungeons/apex/apexcity/apexcitybuilding2clue.json.patch create mode 100644 Starbound Patch Project/humanoid/avian/femalebody.png create mode 100644 Starbound Patch Project/humanoid/human/femalebody.png create mode 100644 Starbound Patch Project/interface.config.patch create mode 100644 Starbound Patch Project/interface/collections.png create mode 100644 Starbound Patch Project/interface/optionsmenu/body_blank.png create mode 100644 Starbound Patch Project/interface/optionsmenu/optionsmenu.config.patch create mode 100644 Starbound Patch Project/interface/statuses/electricblock.png create mode 100644 Starbound Patch Project/items/augments/back/fireblockaugment.png create mode 100644 Starbound Patch Project/monsters/boss/swansong/swansong.lua create mode 100644 Starbound Patch Project/objects/arttrophies/sandstonestatueapex/sandstonestatueapex.object.patch create mode 100644 Starbound Patch Project/objects/arttrophies/sandstonestatuefloran/sandstonestatuefloran.object.patch create mode 100644 Starbound Patch Project/objects/arttrophies/sandstonestatueglitch/sandstonestatueglitch.object.patch create mode 100644 Starbound Patch Project/objects/biome/scorchedcity/explosivebarrel/explosivebarrel.object.patch delete mode 100644 Starbound Patch Project/objects/crafting/upgradeablecraftingobjects/upgradeablecraftingobject.lua diff --git a/Bugs fixed.txt b/Bugs fixed.txt index f42f011..3b8f5ce 100644 --- a/Bugs fixed.txt +++ b/Bugs fixed.txt @@ -24,6 +24,7 @@ Misspelled words were corrected in the following: •cookedpoultry "Cooked Poultry" description //objects •cider "Mulled Fruit Drink" Avian description + •sandstonestatueapex "Apex Sandstone Statue" Apex description •obsidianbookcase "Large Obsidian Bookcase" description •flowerspring "Spring Flower" Human description •wheatseed "Wheat Seed" Apex description @@ -65,15 +66,19 @@ Incorrect capitalization was corrected in the following: •Space station expansion GUI "Place" button Erronius or duplicated characters were removed from the following: + //celestial + •names.config system name fragment three hundred sixty nine "Thalia" + •names.config system name fragment five hundred forty three "Waterosa" + //codex + •glitchhistory7-codex "Filling Up the Tank" page one //dialog •peacekeeperconverse.config default converse with default six - //codex - glitchhistory7-codex "Filling Up the Tank" page one //items - •ophidauntfossil1 "Ophidaunt Skull" description + •ophidauntfossil1 "Ophidaunt Skull" name •trilobitefossil "Trilobite Fossil" complete set description //objects •erchiuscrystalsample "Erchius Crystal Sample" description + •explosivebarrel "Explosive Barrel" name •fossildisplay1 "Small Fossil Display" Floran description •steampunkshelf "Steampunk Shelf" Apex description //quests @@ -193,6 +198,7 @@ Missing spaces were added to the following: •glitchmissionmanager firstBallista radio message Missing localization fields were added to the following: + •sandstonestatueglitch "Glitch Sandstone Statue" Avian description •dirtyfossil2 "Fossil" Apex description •dirtyfossil2 "Fossil" Avian description •dirtyfossil2 "Fossil" Floran description @@ -269,6 +275,9 @@ The wording of the following was changed: •trilobitefossil "Trilobite Fossil" name now fits in with other fossils. •trilobitefossil "Trilobite Fossil" complete set name now fits in with other fossils sets. //objects + •sandstonestatuefloran "Floran Sandstone Statue" Floran description no longer references cut lore. + •sandstonestatuefloran "Floran Sandstone Statue" Glitch description no longer references cut lore. + •sandstonestatueglitch "Glitch Sandstone Statue" Glitch description no longer references cut lore. •medievallamp "Medieval Lamp" Human description was changed to be much more coherent. •prisonshowerdripping "Dripping Rusty Shower" name now matches prisonshower "Rusty Shower" •prisonshowerdripping "Dripping Rusty Shower" description now matches prisonshower "Rusty Shower" @@ -453,18 +462,24 @@ The category assigned to the following things have been corrected so values from Problem with the following textures have been fixed: //humanoid •The Human hairstyle "fem41" had the coloration of a pixel changed so recoloring works correctly. + •The Avian female body had a miscolored border pixel corrected. + •The Human female body had a miscolored border pixel corrected. •The Novakid back arm had a miscolored border pixel corrected. •The Novakid front arm had the coloration of a pixel changed so recoloring works correctly. //interface - •Codex Library UI body drop shadow transparency and color were corrected to match its header and footer. - •Invite to Party UI header had a drop shadow added to match its body and footer. - •Bounty Board UI footer had a missing pixel added to its outline. + •Codex Library menu body drop shadow transparency and color were corrected to match its header and footer. + •The Invite to Party menu header had a drop shadow added to match its body and footer. + •The Options menu body had the height of its music volume background box changed to match the SFX volume background box . + •The Bounty Board menu footer had a missing pixel added to its outline. + •The Electric Resistance buff icon has been updated to use the newer Electrified debuff icon as a base. + •The button that opens the collections menu now has the correct border color when the UI is open. //items •apexofficerpants "Officer Trousers" had missing waistband pixels added to two frames. •baroncapeback "Baron's Cape" had a wrongly colored and shaped frame fixed. •mutantback "Mutagen Back Pipe" had an entire row of missing frames added. •humantier5mpants "Trailblazer's Greaves" had the coloration of several pixel changed so recoloring works correctly. •novakidsuspenderspants "Suspender Pants" had two missing pixels added to one frame. + •fireblockaugment "Fire Resist Augment" has been shifted one pixel over so it is aligned correctly in EPP tooltips. •specialrice "Special Fried Rice" had a missing pixel added to its outline. //monsters •adultpoptop "Adult Poptop" and motherpoptop "Mother Poptop" had a few miscolored pixels near the mouth corrected. @@ -552,6 +567,7 @@ Icons were improved for the following: ////////creatures and npcs//////// •A head windup frame was assigned to the sewerfly "Growfly" so its head would not disappear when making ranged attacks. •The damage team was removed from bonebird "Bone Bird" so it will not attack other creatures. +•The Swansong fight hat timers added to some attacks so they would not get stuck. Outfit items were added to the following NPC types: •villager Floran can now spawn with floransurvivalistchest "Survivalist Chest" chest items for consistency. @@ -897,8 +913,6 @@ Unique descriptions were added to the following: •Occlusion has been disabled on material 174 matterblock "Matter Block" because its texture is translucent. Drops have been changed on the following things: - //objects - •Objects that use upgradeablecraftingobject.lua and have not been upgraded will no longer drop with parameters so placed and non-placed ones will stack. //tiles •material 166 rustyblock "Rusty Block" now drops "rustyblock" because some crafting recipes indicate the wrong thing was assigned. //trees @@ -1007,9 +1021,12 @@ Corrected additional discrepancies in the following: •floranT8blocks ////////dungeons//////// -•The bookcase in apexcity building2 now uses the Apex city lore loot pool. •avianvillage object layer 3a had a curtain moved by one block so it would be in the proper spot and could rotate the correct direction. +Bookcases in the following dungeon now use lore loot pools: + •apexcity building2 + •apexcity building2clue + ////////user interface//////// •The shine layer in the Vending Machine interface was set to click through so the items to purchase counter can be typed in. @@ -1017,9 +1034,15 @@ S.A.I.L static paths were corrected on the following: •Floran S.A.I.L. interface •Novakid S.A.I.L. interface -Bad text alignment were fixed in the following: +Alignment was fixed in the following: •Fist Weapon item descriptions •Shield item descriptions + •Options menu SFX label + •Options menu SFX value + •Options menu music slider + •Options menu multiplayer via Steam/Discord label + •Options menu controls button + •Options menu graphics button •Fist Weapon tooltip pixel value •Shield tooltip pixel value diff --git a/Changelog.txt b/Changelog.txt index b39feb7..b97a66b 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,24 @@ +-------- 1.5.1 -------- +Several more typos were fixed. + +Some references to cut lore were removed thanks to Armok. + +The Swansong fight hat timers added to some attacks so they would not get stuck thanks to Kherae. + +Several more UI and menu issues were fixed thanks to Silver Sokolova. + +The Fire Resist Augment texture has been shifted one pixel over so it is aligned correctly in EPP tooltips. + +The Electric Resistance buff icon has been updated to use the newer Electrified debuff icon as a base. + +Apex City building2clue now has the same loot table fix as its non-clue counterpart. + +The Avian and human female body textures each had a miscolored border pixel corrected. + +Reverted a fix to upgradeablecraftingobject.lua because it was not working correctly for everyone. +This fix is likely to return with better implementation later on. + + -------- 1.5.0 -------- This update is effectively a total remake of the patch project from the ground up. Every single change has been re-evaluated to ensure it is working as intended. @@ -25,6 +46,7 @@ One upside is that the length of changelog entries no longer have to be constrai Please note that this changelog entry is not comprehensive due to the number of changes made. However, the list of bugs fixed is no longer missing anything and is far more detailed than before. + -------- 1.4.1 -------- Revert farmable.lua fixes -There were unexpected problems from implementation diff --git a/Starbound Patch Project/_metadata b/Starbound Patch Project/_metadata index 5626983..77fa491 100644 --- a/Starbound Patch Project/_metadata +++ b/Starbound Patch Project/_metadata @@ -7,5 +7,5 @@ "priority" : -9980, "steamContentId" : "1543219534", "tags" : "Crafting and Building|Miscellaneous|Planets and Environments|NPCs and Creatures|Quests|Dungeons|Ships|Species|User Interface|Furniture and Objects|Food and Farming|Armor and Clothes", - "version" : "1.5.0" + "version" : "1.5.1" } \ No newline at end of file diff --git a/Starbound Patch Project/celestial/names.config.patch b/Starbound Patch Project/celestial/names.config.patch new file mode 100644 index 0000000..bc6dc2d --- /dev/null +++ b/Starbound Patch Project/celestial/names.config.patch @@ -0,0 +1,26 @@ +[ + [ + { + "op" : "test", + "path" : "/systemNames/368/1", + "value" : "Thalia " + }, + { + "op" : "replace", + "path" : "/systemNames/368/1", + "value" : "Thalia" + } + ], + [ + { + "op" : "test", + "path" : "/systemNames/542/1", + "value" : "Waterosa " + }, + { + "op" : "replace", + "path" : "/systemNames/542/1", + "value" : "Waterosa" + } + ] +] diff --git a/Starbound Patch Project/dungeons/apex/apexcity/apexcitybuilding2clue.json.patch b/Starbound Patch Project/dungeons/apex/apexcity/apexcitybuilding2clue.json.patch new file mode 100644 index 0000000..7502a6c --- /dev/null +++ b/Starbound Patch Project/dungeons/apex/apexcity/apexcitybuilding2clue.json.patch @@ -0,0 +1,12 @@ +[ + { + "op" : "test", + "path" : "/layers/3/objects/1/properties/parameters", + "value" : "{\"treasurePools\":[\"apexcityTreasure\"]}" + }, + { + "op" : "replace", + "path" : "/layers/3/objects/1/properties/parameters", + "value" : "{\"treasurePools\":[\"apexcityLore\"]}" + } +] diff --git a/Starbound Patch Project/humanoid/avian/femalebody.png b/Starbound Patch Project/humanoid/avian/femalebody.png new file mode 100644 index 0000000000000000000000000000000000000000..25326be964a24aa7ad8f248cd78b57cdb1b89d4f GIT binary patch literal 3091 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 literal 0 HcmV?d00001 diff --git a/Starbound Patch Project/humanoid/human/femalebody.png b/Starbound Patch Project/humanoid/human/femalebody.png new file mode 100644 index 0000000000000000000000000000000000000000..0d5a1acbbc429c055dc216bd4e7a5197aa71a9ec GIT binary patch literal 3963 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 HqC4|H$X^-& literal 0 HcmV?d00001 diff --git a/Starbound Patch Project/interface.config.patch b/Starbound Patch Project/interface.config.patch new file mode 100644 index 0000000..02f90b1 --- /dev/null +++ b/Starbound Patch Project/interface.config.patch @@ -0,0 +1,29 @@ +//Fallback support for mods that change "/interface/collections.png" without including color fixes. +//Credit for this goes to Silver Sokolova. +//This is not needed for an "official" fix. +[ + [ + { + "op" : "test", + "path" : "/mainBar/collections/open", + "value" : "/interface/collections.png:open" + }, + { + "op" : "replace", + "path" : "/mainBar/collections/open", + "value" : "/interface/collections.png:open?replace;297e17=7721a0;1a510e=611f88" + } + ], + [ + { + "op" : "test", + "path" : "/mainBar/collections/openHover", + "value" : "/interface/collections.png:openHover" + }, + { + "op" : "replace", + "path" : "/mainBar/collections/openHover", + "value" : "/interface/collections.png:openHover?replace;3ea721=912fca;297e17=7721a0" + } + ] +] diff --git a/Starbound Patch Project/interface/collections.png b/Starbound Patch Project/interface/collections.png new file mode 100644 index 0000000000000000000000000000000000000000..6e9a4bfd4d26029d2aea4ea83532e0e7af2dbd6e GIT binary patch literal 397 zcmV;80doF{P)hhL==cQ5=}`T#J&E=`0Tixu)-P1w@Y>r7i^Qt#FexP8?%|3i?4{{7Gb#b<}*T-s`K$u4hpt4}b@|}WVafqlt4KL zA_~MDiRSc^{H>;!?U4^yPX)ZeO~5f=<{xK-GoRWq{tx-?bdR1Q=AG+w>J)QG8qc626%4}+s^vd z!H}23Od+D-^s=1Ii{{Ne(eUcG&IIp@={a>Xjv7CoH7`>l?6mYZT^+IRqw~sM-mDku z*4=9Cdvo3Un=LnQ&EB}NzjTJ~R_h2Iv(Nj_?q3?hXr}lpOf+HRudw?`zFD6lbgrG* z*z-{9hagaY(ncV+S+slamdMLIN6Pk`KNs&^!Y($ueo`powT2VIe)pekj;MWIbOfmI r4P*DwePVAmE=&LAFP5#>3e+6FXuTKKR~wpPT%Jkp?lxBJ5@IowKe vuURhX1boVyci(yLAAbgg+Ih29En$i~P$ypKztPbjXdi>8tDnm{r-UW|kDW=B literal 0 HcmV?d00001 diff --git a/Starbound Patch Project/items/augments/back/fireblockaugment.png b/Starbound Patch Project/items/augments/back/fireblockaugment.png new file mode 100644 index 0000000000000000000000000000000000000000..67829e11d1ca9d8aa37c21e7bbb9031b8940c934 GIT binary patch literal 206 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPGa3-AeX{r~?zkXc<_-KD~?B#&*| zwr$5^86F*Wc{zpQ`*ntMC%<$6r5Q_t{DK)Ap4~_Ta^gH)978JNLQfiUH5l-?J^aFU zR%xA6{s-rd;{~r6xt3lpm}p^L8GSqLldrsuQqnVzy$Na04@5PHu`dak+i+;1ZT$78 z7jDVyzRS0#-&sMJn?dryluKp;Md#T69 self.maxAdds do + i = i - self.maxAdds + end + return i + end + if i % 3 ~= 0 then + table.insert(adjacent, self.ophanims[wrap(i + 1)]) + end + table.insert(adjacent, self.ophanims[wrap(i + 3)]) + return adjacent + end) + + self.gravityDungeonId = config.getParameter("gravityDungeonId") + world.setTileProtection(self.gravityDungeonId, true) + local attackConfig = config.getParameter("attackConfig") + self.behavior = swansongBehavior(attackConfig) +end + +function update(dt) + mcontroller.controlFace(1) + updateTargets() + + tick(self.behavior) + + boostAnimation() + + if self.approachVelocity then + if self.approachVelocity[1] ~= 0 then + controlFace(util.toDirection(self.approachVelocity[1])) + end + mcontroller.controlApproachVelocity(self.approachVelocity, self.approachForce * self.boostForceModifier) + self.approachVelocity = nil + self.approachForce = nil + end + + local facing = {self.facing, 1.0} + local velocity = mcontroller.velocity() + + if self.facing then + animator.resetTransformationGroup("flip") + if self.facing < 0 then + animator.scaleTransformationGroup("flip", {-1, 1.0}) + end + end + + animator.resetTransformationGroup("body") + animator.translateTransformationGroup("body", self.body.anchor) + animator.rotateTransformationGroup("body", self.body.angle * self.facing) + mcontroller.setRotation(self.body.angle) + + animator.resetTransformationGroup("wings") + animator.translateTransformationGroup("wings", self.wings.anchor) + animator.rotateTransformationGroup("wings", self.wings.angle * self.facing, vec2.mul(animator.partPoint("body", "wingsAnchor"), facing)) + animator.translateTransformationGroup("wings", vec2.mul(vec2.mul(velocity, -0.025), facing)) + + local lhAngle = vec2.angle(vec2.mul(vec2.withAngle(self.lefthand.angle), facing)) + local lhAnchor = vec2.rotate(self.lefthand.anchor, self.body.angle * self.facing) + animator.resetTransformationGroup("lefthand") + animator.rotateTransformationGroup("lefthand", math.pi / 2) + animator.translateTransformationGroup("lefthand", lhAnchor) + animator.rotateTransformationGroup("lefthand", lhAngle, lhAnchor) + animator.translateTransformationGroup("lefthand", vec2.rotate({self.lefthand.distance, 0.0}, lhAngle)) + + local rhAngle = vec2.angle(vec2.mul(vec2.withAngle(self.righthand.angle), facing)) + local rhAnchor = vec2.rotate(self.righthand.anchor, self.body.angle * self.facing) + animator.resetTransformationGroup("righthand") + animator.rotateTransformationGroup("righthand", math.pi / 2) + animator.translateTransformationGroup("righthand", rhAnchor) + animator.rotateTransformationGroup("righthand", rhAngle, rhAnchor) + animator.translateTransformationGroup("righthand", vec2.rotate({self.righthand.distance, 0.0}, rhAngle)) +end + +function shouldDie() + return self.shouldDie and status.resource("health") <= 0 +end + +function die() + world.spawnMonster("noxcapture", mcontroller.position()) +end + +function damage(damageSource) +end + +function scalePower(power) + return power * root.evalFunction("spaceMonsterLevelPowerMultiplier", monster.level()) +end + +-- converts an absolute angle to a local one, adjusted by the facing direction +function localAngle(a) + return vec2.angle(vec2.mul(vec2.withAngle(a), {self.facing, 1.0})) +end + +function controlFace(dir) + self.facing = util.toDirection(dir) +end + +function setBoostSoundActive(active) + if active and self.boostSoundActive ~= true then + animator.playSound("thrustLoop", -1) + self.boostSoundActive = true + elseif not active then + animator.stopAllSounds("thrustLoop") + self.boostSoundActive = false + end +end + +function updateTargets() + local validTarget = function(e) + local pos = world.entityPosition(e) + if pos == nil then + return false + end + + return world.magnitude(pos, mcontroller.position()) < 100 and not world.lineTileCollision(mcontroller.position(), pos) + end + self.targets = util.filter(self.targets, validTarget) + + if #self.targets == 0 then + local players = world.entityQuery(mcontroller.position(), 50, {includedTypes={"player"}, orderBy="nearest"}) + self.targets = util.filter(players, validTarget) + end + self.target = self.targets[1] +end + +function controlApproachVelocity(velocity, force) + self.approachVelocity = velocity + self.approachForce = force +end + +-- moves hands and rotates body based on movement direction +function boostAnimation() + local approach = copy(self.approachVelocity) or {0.0, 10.0} + local handApproach = copy(self.approachVelocity) or {0.0, 0.1} + if world.gravity(mcontroller.position()) ~= 0 then + approach[2] = approach[2] + 50 + end + + local approachDir = vec2.norm(approach) + local velDiff = vec2.sub(approach, mcontroller.velocity()) + local forceDir = vec2.norm(velDiff) + -- braking if we're currently accelerating in the opposite direction of the approach vector + local braking = self.approachVelocity ~= nil and + vec2.dot(forceDir, approachDir) < 0.0 and + vec2.mag(velDiff) > 0.01 + + local speedMultiplier = math.min(1.0, vec2.mag(mcontroller.velocity()) / self.flySpeed) ^ 2 + local downAngle = -math.pi/2 + local thrustAngle = util.angleDiff(0, vec2.angle(approach) + math.pi) + local handThrustAngle = util.angleDiff(0, vec2.angle(handApproach) + math.pi) + + local bodyTargetAngle = util.angleDiff(downAngle, thrustAngle) * 0.75 + local lhTargetAngle = downAngle + util.angleDiff(downAngle, handThrustAngle) + local rhTargetAngle = downAngle + util.angleDiff(downAngle, handThrustAngle) + if braking then + -- move body and hands back toward their original angles when braking + bodyTargetAngle = util.angleDiff(0, bodyTargetAngle) * speedMultiplier + lhTargetAngle = downAngle + util.angleDiff(downAngle, lhTargetAngle) * speedMultiplier + rhTargetAngle = downAngle + util.angleDiff(downAngle, rhTargetAngle) * speedMultiplier + end + + local hands = { + left = {state = self.lefthand, origin = self.lhorig, part = "lefthand", boost = 0}, + right = {state = self.righthand, origin = self.rhorig, part = "righthand", boost = 0} + } + local boosting = false + for _, hand in pairs(hands) do + if not hand.state.lock then + hand.state.anchor = vec2.lerp(0.15, hand.state.anchor, hand.origin.anchor) + hand.state.angle = hand.state.angle + util.angleDiff(hand.state.angle, lhTargetAngle) * 0.15 + hand.state.distance = util.lerp(0.15, hand.state.distance, hand.origin.distance - (speedMultiplier * 2.0)) + hand.boost = (math.cos(handThrustAngle - hand.state.angle) - 0.8) * 5 + if not contains({"invisible", "spawn"}, animator.animationState(hand.part))then + local state = "idle" + if self.approachVelocity ~= nil and hand.boost > 0.0 then + state = "boost" + end + if animator.animationState(hand.part) == "idle" and state == "boost" then + boosting = true + end + animator.setAnimationState(hand.part, state) + end + end + end + if boosting then + animator.playSound("thrustBurst") + end + setBoostSoundActive(boosting or animator.animationState("body") == "idlegrav") + + -- only rotate body when the hands are boosting, we are braking, or sitting still + if not self.body.lock then + if hands.left.boost > 0.0 or hands.right.boost > 0.0 or braking or not self.approachVelocity then + self.body.angle = self.body.angle + util.angleDiff(self.body.angle, bodyTargetAngle) * 0.15 + self.wings.angle = util.angleDiff(0, self.body.angle) * -0.5 + end + end + + -- move with more force the more the hands and body are aligned with the approach direction + local bodyBoost = math.cos(bodyTargetAngle - self.body.angle) * 2 + local lhBoostMod = self.lefthand.lock and 1.0 or hands.left.boost + local rhBoostMod = self.lefthand.lock and 1.0 or hands.right.boost + self.boostForceModifier = math.min(1.0, math.max(0.0, (lhBoostMod + rhBoostMod + bodyBoost) / 2)) + if braking then + -- except when braking, always brake with full force + self.boostForceModifier = 1.0 + end +end + +function wallPoint(dir, distance) + local pos = mcontroller.position() + local wallPoint = world.lineTileCollisionPoint(pos, vec2.add(pos, vec2.mul(vec2.norm(dir), distance))) + if world.lineTileCollision(pos, vec2.add(pos, vec2.mul(vec2.norm(dir), distance))) then + world.debugLine(pos, vec2.add(pos, vec2.mul(vec2.norm(dir), distance)), "red") + else + world.debugLine(pos, vec2.add(pos, vec2.mul(vec2.norm(dir), distance)), "blue") + end + if wallPoint then + return wallPoint[1] + end +end + +function groundDistance() + local pos = mcontroller.position() + local groundPoint = world.lineTileCollisionPoint(pos, vec2.add(pos, {0, -self.hoverHeight * 2.0})) + if groundPoint then + return pos[2] - groundPoint[1][2] + else + return self.hoverHeight * 2 + end +end + +-- predicts the direction the target will be in in `time` seconds +-- trackRange optionally gives an optimal range for the prediction +function anglePrediction(sourcePosition, targetId, time, trackRange) + local targetVelocity = world.entityVelocity(targetId) + local toTarget = world.distance(world.entityPosition(targetId), sourcePosition) + + local trackRange = trackRange or vec2.mag(toTarget) + local perpendicular = vec2.rotate(vec2.norm(toTarget), math.pi / 2) + local angularVel = vec2.dot(perpendicular, vec2.norm(targetVelocity)) * (vec2.mag(targetVelocity) / trackRange) + return vec2.angle(toTarget) + angularVel * time +end + +function queryOphanims() + local ophanims = world.entityQuery(mcontroller.position(), 80, {includedTypes = {"monster"}}) + return util.filter(ophanims, function(entityId) + return world.monsterType(entityId) == "ophanim" + end) +end + +function breakAdds() + --SBPP - Filter out entities that do not exist in the world. Credit goes to Kherae for this. + local e = queryOphanims() + e = util.filter(e, world.entityExists) + for _, entityId in ipairs(e) do + world.sendEntityMessage(entityId, "break") + end +end + +function despawnAdds() + --SBPP - Filter out entities that do not exist in the world. Credit goes to Kherae for this. + local e = queryOphanims() + e = util.filter(e, world.entityExists) + for _, entityId in ipairs(e) do + world.sendEntityMessage(entityId, "despawn") + end +end + +function setMusicEnabled(enabled) + local res = util.await(world.sendEntityMessage("bossmusic", "setMusicEnabled", enabled)) + if not res:succeeded() then + sb.logInfo("Error starting boss music: %s", res:error()) + end +end + +-- TOP LEVEL BEHAVIOR + +-- swansongBehavior is an async function that should be ticked on every update +swansongBehavior = async(function(attackConfig) + animator.setAnimationState("body", "init") + animator.setAnimationState("lefthand", "invisible") + animator.setAnimationState("righthand", "invisible") + animator.setAnimationState("wings", "invisible") + + while self.target == nil do + coroutine.yield() + end + + world.setDungeonGravity(self.gravityDungeonId, 0) + await(select( + spawnAnimation(), + function() + while true do + status.addEphemeralEffect("invulnerable") + coroutine.yield() + end + end + )) + status.removeEphemeralEffect("invulnerable") + + while status.resource("health") > 0 or self.shouldDie == false do + world.setDungeonGravity(self.gravityDungeonId, 0) + status.setResourcePercentage("health", 1.0) + await(delay(1.0)) + + despawnAdds() + await(flyTo(storage.spawnPosition)) + + while self.target == nil do + coroutine.yield() + end + + setMusicEnabled(true) + local active = activeState(attackConfig) -- coroutine for the active state + while self.target ~= nil do + coroutine.yield(tick(active)) -- we don't expect the active state to finish + end + + await(resetBoss()) + await(deactivateGravity()) + end +end) + +-- run while the boss has targets +activeState = async(function(attackConfig) + monster.setDamageOnTouch(true) + + local attackSequence = async(function() + -- first do each attack once, with an ophanim wave between each + local attacks = { + rocketSwarmAttack(attackConfig.rocketSwarm), + deathLaserAttack(attackConfig.deathLaser), + coroutine.create(function() + for i = 1, 4 do + await(meleeSequenceAttack(attackConfig.melee)) + end + end) + } + for _, attack in ipairs(attacks) do + await(attack) + + await(activateGravity(attackConfig.activateGravity)) + + await(spawnOphanimsAttack(attackConfig.spawnOphanims)) + for i = 1, 2 do + await(hoverFireAttack(attackConfig.hoverFire)) + end + + await(deactivateGravity()) + end + + -- then do two attacks between each wave, repeating + local step = 0 + while true do + controlFace(1) + + if step == 0 or step == 2 then + await(rocketSwarmAttack(attackConfig.rocketSwarm)) + end + if step == 1 or step == 2 then + await(deathLaserAttack(attackConfig.deathLaser)) + end + if step == 0 or step == 1 then + for i = 1, 4 do + await(meleeSequenceAttack(attackConfig.melee)) + end + end + + await(activateGravity(attackConfig.activateGravity)) + + -- fly to the center of the room + await(flyTo({storage.spawnPosition[1], mcontroller.position()[2]})) + + await(spawnOphanimsAttack(attackConfig.spawnOphanims)) + for i = 1, 2 do + await(hoverFireAttack(attackConfig.hoverFire)) + end + + await(deactivateGravity()) + step = (step + 1) % 3 + end + end) + local finalAttackSequence = async(function() + while true do + await(finalFormMeleeAttack(attackConfig.finalMelee)) + end + end) + + + -- Run phase 1 until health is 0, but don't die + self.shouldDie = false + local mainAttacks = attackSequence() + while status.resource("health") > 0 do + coroutine.yield(tick(mainAttacks)) + end + + -- transition into phase 2 + await(finalFormTransition(attackConfig.transition)) + await(delay(1.0)) + + -- Run phase 2 until health is 0, die afterwards + local finalAttacks = finalAttackSequence() + while status.resource("health") > 0 do + status.addEphemeralEffect("maxhealthreduction") + coroutine.yield(tick(finalAttacks)) + end + + resetAttacks() + + setMusicEnabled(false) + + -- death animation + + self.lefthand.lock, self.righthand.lock, self.body.lock = true, true, true + animator.burstParticleEmitter("burst") + animator.playSound("shockLoop", -1) + animator.setParticleEmitterActive("shock", true) + animator.setParticleEmitterActive("smoke", true) + + await(delay(2.0)) + + await(blinkDash(storage.spawnPosition, 0.0, {body = "final", wings = "broken", lefthand = "idle", righthand = "idle"})) + + animator.playSound("deathLoop", -1) + await(select( + function() + while true do + mcontroller.controlApproachVelocity({0.0, -50.0}, 2) + coroutine.yield() + end + end, + function() + await(join( + moveLeftHand(self.lhorig.anchor, self.lhorig.angle, self.lhorig.distance, 0.2), + moveRightHand(self.rhorig.anchor, self.rhorig.angle, self.rhorig.distance, 0.2) + )) + + while true do + self.lefthand.angle = util.lerp(0.025, self.lefthand.angle, util.toRadians(90)) + self.righthand.angle = util.lerp(0.025, self.righthand.angle, util.toRadians(90)) + self.body.angle = util.lerp(0.025, self.body.angle, util.toRadians(-70)) + coroutine.yield() + end + end, + function() + await(delay(2.5)) + animator.burstParticleEmitter("rhburst") + animator.setAnimationState("righthand", "invisible") + + await(delay(0.5)) + animator.burstParticleEmitter("lhburst") + animator.setAnimationState("lefthand", "invisible") + + await(delay(1.5)) + animator.burstParticleEmitter("burst") + animator.burstParticleEmitter("deathBurst") + animator.setAnimationState("body", "invisible") + animator.setAnimationState("wings", "invisible") + animator.setParticleEmitterActive("shock", false) + animator.setParticleEmitterActive("smoke", false) + animator.stopAllSounds("shockLoop") + animator.stopAllSounds("deathLoop") + animator.playSound("deathBurst") + world.setDungeonGravity(self.gravityDungeonId, 50) + self.shouldDie = true + end + )) + +end) + +-- HELPERS + +-- flyTo smoothly flies to a position +flyTo = async(function(pos) + local maxAcc = self.airForce / mcontroller.mass() + local targetDir = vec2.norm(world.distance(pos, mcontroller.position())) + --SBPP - Added a failsafe so some attacks will not get stuck. Credit goes to Kherae for this. + local failsafeTimer = 0.0 + local forceTeleport = false + while true do + failsafeTimer = failsafeTimer + script.updateDt() + local toTarget = world.distance(pos, mcontroller.position()) + local distance = world.magnitude(toTarget) + if vec2.dot(toTarget, targetDir) < 0.0 or distance < 0.1 then + -- passed the target, or is very close + break + end + + if failsafeTimer > 10.0 then + forceTeleport=true + break + end + + -- approach the max speed that allows braking to a stop on the target + -- using the approximate distance one step ahead to err on the side of caution + local step = vec2.mag(mcontroller.velocity()) * script.updateDt() + local targetSpeed = math.min(math.sqrt(2 * maxAcc * (distance - step)), self.flySpeed) + + controlApproachVelocity(vec2.mul(vec2.norm(toTarget), targetSpeed), self.airForce) + coroutine.yield() + if forceTeleport then + mcontroller.setPosition(pos) + end + end + mcontroller.setVelocity({0, 0}) + + return true +end) + +-- stop smoothly comes to a stop +stop = async(function(force) + while vec2.mag(mcontroller.velocity()) > 0.1 do + controlApproachVelocity({0, 0}, force or self.airForce) + coroutine.yield() + end + mcontroller.setVelocity({0, 0}) +end) + +-- moveLeftHand smoothly moves the left hand to the desired position +moveLeftHand = async(function(anchor, angle, distance, time) + return await(moveHand(self.lefthand, anchor, angle, distance, time)) +end) + +-- moveRightHand smoothly moves the right hand to the desired position +moveRightHand = async(function(anchor, angle, distance, time) + return await(moveHand(self.righthand, anchor, angle, distance, time)) +end) + +moveHand = async(function(hand, anchor, angle, distance, time) + local timer = 0.0 + local start = copy(hand) + angle = start.angle + util.angleDiff(start.angle, angle) + while timer < time do + local ratio = util.easeInOutSin(timer / time, 0, 1.0) + hand.anchor = vec2.lerp(ratio, start.anchor, anchor) + hand.angle = util.lerp(ratio, start.angle, angle) + hand.distance = util.lerp(ratio, start.distance, distance) + + timer = timer + script.updateDt() + coroutine.yield() + end + + hand.anchor = anchor + hand.angle = angle + hand.distance = distance + + return true +end) + +-- Teleports the boss to the specified position, with a dash effect +-- dashPos - the position to dash to +-- endStates - states to set after the dash (returning from invisible) +-- onDash - callback function called when arriving at dashPos but while still invisible +blinkDash = async(function(dashPos, endSpeed, endStates, onDash) + local dashDir = vec2.norm(world.distance(dashPos, mcontroller.position())) + + animator.setEffectActive("teleport", true) + animator.burstParticleEmitter("teleport") + animator.playSound("blinkDash") + await(delay(0.1)) + monster.setAnimationParameter("dash", {first = mcontroller.position(), last = dashPos}) + + animator.setAnimationState("body", "invisible") + animator.setAnimationState("wings", "invisible") + animator.setAnimationState("lefthand", "invisible") + animator.setAnimationState("righthand", "invisible") + + coroutine.yield() + + monster.setAnimationParameter("dash", nil) + mcontroller.setPosition(dashPos) + mcontroller.setVelocity(vec2.mul(dashDir, endSpeed)) + + coroutine.yield() + + if onDash ~= nil then + onDash() + end + + await(join( + stop(50), + function() + animator.setAnimationState("body", endStates.body) + animator.setAnimationState("wings", endStates.wings) + animator.setAnimationState("lefthand", endStates.lefthand) + animator.setAnimationState("righthand", endStates.righthand) + + await(delay(0.1)) + animator.setEffectActive("teleport", false) + end + )) +end) + +-- shoots a beam out of either hand +-- handName - "left" or "right" +-- angle - firing angle +-- setDamagePartActive - a function(part, active) that sets the damage parts, required to +-- be able to set the damage parts for both hands if run in parallel +handBeam = async(function(handName, angle, setDamagePartActive) + local hand, animationPart, beamParam, damagePart, move + if handName == "left" then + hand, animationPart, beamParam, damagePart, move = self.lefthand, "lefthand", "lhbeam", "lhbeam", moveLeftHand + elseif handName == "right" then + hand, animationPart, beamParam, damagePart, move = self.righthand, "righthand", "rhbeam", "rhbeam", moveRightHand + else + error(string.format("Invalid hand %s for handBeam", handName)) + end + + hand.lock = true + await(move({0.0, 0.0}, angle, 6.0, 0.5)) + + -- windup + animator.setAnimationState(animationPart, "boost") + monster.setAnimationParameter(beamParam, true) + animator.playSound("beamStart") + await(delay(0.3)) -- windup + + animator.playSound("beamLoop", -1) + await(delay(0.2)) -- start firing + + -- damage + setDamagePartActive(damagePart, true) + await(delay(0.3)) + + -- winddown + setDamagePartActive(damagePart, false) + monster.setDamageParts({}) + animator.stopAllSounds("beamLoop", 0.5) + animator.setAnimationState(animationPart, "idle") + monster.setAnimationParameter(beamParam, false) + await(delay(0.5)) + + hand.lock = false +end) + +function resetAttacks() + -- remove rockets and all boss damage + local projectiles = world.entityQuery(mcontroller.position(), 50, {includedTypes={"projectile"}}) + projectiles = util.filter(projectiles, function(id) return world.entityName(id) == "swansongrocket" end) + --SBPP - Filter out entities that do not exist in the world. Credit goes to Kherae for this. + projectiles = util.filter(projectiles, world.entityExists) + for _, id in ipairs(projectiles) do + world.sendEntityMessage(id, "explode") + end + monster.setDamageSources({}) + monster.setDamageParts({}) + + -- stop all rendering effects + monster.setAnimationParameter("aimLaser", false) + monster.setAnimationParameter("rocketMarkers", nil) + monster.setAnimationParameter("dash", nil) + monster.setAnimationParameter("beam", false) + monster.setAnimationParameter("lhbeam", false) + monster.setAnimationParameter("rhbeam", false) + + animator.setAnimationState("chargeswoosh", "inactive") +end + +-- resets the boss back to its original idle state +resetBoss = async(function() + setMusicEnabled(false) + resetAttacks() + monster.setDamageOnTouch(false) + await(delay(0.5)) + + -- move everything back into place + self.lefthand.lock, self.righthand.lock = true, true + self.body.lock = false + await(join( + moveLeftHand(self.lhorig.anchor, self.lhorig.angle, self.lhorig.distance, 0.5), + moveRightHand(self.rhorig.anchor, self.rhorig.angle, self.rhorig.distance, 0.5), + function() + animator.setEffectActive("teleport", true) + + await(delay(0.1)) + + animator.setAnimationState("body", "idle") + if animator.animationState("wings") == "beamloop" then + animator.setAnimationState("wings", "beamwinddown") + end + animator.setAnimationState("wings", "idle") + animator.setAnimationState("lefthand", "idle") + animator.setAnimationState("righthand", "idle") + + animator.resetTransformationGroup("lhflip") + animator.resetTransformationGroup("rhflip") + + await(delay(0.1)) + + animator.setEffectActive("teleport", false) + end + )) + self.lefthand.lock, self.righthand.lock = false, false + + await(delay(1.0)) +end) + +-- MAIN ABILITIES + +spawnAnimation = async(function() + await(delay(2.0)) + + local dialog = config.getParameter("openingDialog") + local dialogTime = config.getParameter("dialogTime") + local portrait = config.getParameter("chatPortrait") + local waitTime = (dialogTime - 6.0) / (#dialog - 1) + for i, line in ipairs(dialog) do + monster.sayPortrait(line, portrait) + if i == 3 then + setMusicEnabled(true) + end + if i < #dialog then + await(delay(waitTime)) + end + end + await(delay(6.0)) + + monster.setDamageBar("Special") + + await(join( + function() + animator.setAnimationState("body", "spawn") + await(delay(1.0)) + + animator.setAnimationState("lefthand", "spawn") + await(delay(0.5)) + + animator.setAnimationState("righthand", "spawn") + await(delay(0.5)) + + animator.setAnimationState("wings", "spawn") + await(delay(0.75)) + end, + function() + for i = 1, 5 do + --animator.playSound("spawnClank") + await(delay(0.5)) + end + end + )) +end) + +activateGravity = async(function(conf) + if math.abs(world.distance(mcontroller.position(), storage.spawnPosition)[1]) > 20 then + await(flyTo(storage.spawnPosition)) + end + + animator.playSound("toggleGravityWarning") + await(delay(1.0)) + + animator.playSound("enableGravity") + world.setDungeonGravity(self.gravityDungeonId, 50) + + -- fall down a bit + local fallForce = mcontroller.mass() * world.gravity(mcontroller.position()) + while groundDistance() > conf.hoverHeight do + mcontroller.controlApproachVelocity({0, -100}, fallForce) + coroutine.yield() + end + + animator.setAnimationState("body", "idlegrav") + await(stop(150)) + await(flyTo(vec2.add(mcontroller.position(), {0, self.hoverHeight - groundDistance()}))) +end) + + +deactivateGravity = async(function() + self.ophanims = {} + self.ophanimStart = (math.pi / 8) + math.random(1, 4) * (math.pi / 2) + + animator.playSound("toggleGravityWarning") + await(delay(1.0)) + + animator.playSound("disableGravity") + world.setDungeonGravity(self.gravityDungeonId, 0) + animator.setAnimationState("body", "idle") + animator.setAnimationState("lefthand", "idle") + animator.setAnimationState("righthand", "idle") + + await(flyTo(storage.spawnPosition)) +end) + +spawnOphanimsAttack = async(function() + self.ophanims = util.filter(self.ophanims, world.entityExists) + local maxSpawn = 8 + local spawnCount = math.min(maxSpawn, self.maxAdds - #self.ophanims) + if spawnCount == 0 then + return + end + + self.righthand.lock = true + + local upAngle = math.pi/2 + local angleRange = ((math.pi - 0.2) / maxSpawn) * (spawnCount - 1) + local startAngle = upAngle - (angleRange / 2) + local endAngle = upAngle + (angleRange / 2) + await(moveRightHand({0, 2.0}, startAngle, 6.0, 0.5)) + + animator.setAnimationState("righthand", "firewindup") + await(delay(0.4)) + + local stepAngle = angleRange / math.max(1, spawnCount - 1) + local energyIndices = {} + for i = 1, maxSpawn do + table.insert(energyIndices, i) + end + shuffle(energyIndices) + energyIndices = util.take(3, energyIndices) + for i = 1, spawnCount do + animator.setAnimationState("righthand", "fire") + + local sourcePosition = vec2.add(mcontroller.position(), animator.partPoint("righthand", "projectileSource")) + local aimDir = vec2.withAngle(self.righthand.angle) + local params = {speed = 25} + if contains(energyIndices, i) then + params.spawnEnergyPickup = true + end + world.spawnProjectile("ophanimspawner", sourcePosition, entity.id(), aimDir, false, params) + + await(delay(0.1)) + + if i < spawnCount then + await(moveRightHand({0.0, 2.0}, startAngle + (i * stepAngle), 6.0, 0.3)) + end + end + + await(delay(0.2)) + animator.setAnimationState("righthand", "fireend") + await(delay(0.5)) + await(moveRightHand(self.rhorig.anchor, self.rhorig.angle, self.rhorig.distance, 0.6)) + self.righthand.lock = false +end) + +hoverFireAttack = async(function(conf) + local left = wallPoint({-1, 0}, 50) or vec2.add(mcontroller.position(), {-50, 0}) + local right = wallPoint({1, 0}, 50) or vec2.add(mcontroller.position(), {50, 0}) + left[1] = left[1] + 10 + right[1] = right[1] - 10 + + local hoverRange = right[1] - left[1] + local hoverY = mcontroller.position()[2] + self.hoverHeight - groundDistance() + hoverY = hoverY - 5 + math.random() * 10 + local hoverX + while hoverX == nil or world.magnitude(mcontroller.position(), {hoverX, hoverY}) < 20 do + hoverX = left[1] + math.random() * hoverRange + end + await(flyTo({hoverX, mcontroller.position()[2] + self.hoverHeight - groundDistance()})) + + self.righthand.lock = true + + animator.setAnimationState("righthand", "firewindup") + await(delay(0.5)) + monster.setAnimationParameter("aimLaser", true) + + local targetPosition = world.entityPosition(self.target) + local toTarget = world.distance(targetPosition, vec2.add(mcontroller.position(), self.rhorig.anchor)) + controlFace(util.toDirection(toTarget[1])) + await(moveRightHand(self.rhorig.anchor, vec2.angle(toTarget), 6.0, 0.5)) + + animator.playSound("targetRockets") + await(delay(0.5)) + monster.setAnimationParameter("aimLaser", false) + + for i = 1, 5 do + animator.setAnimationState("righthand", "fire", true) + local sourcePosition = vec2.add(mcontroller.position(), animator.partPoint("righthand", "projectileSource")) + local aimDir = vec2.withAngle(self.righthand.angle) + world.spawnProjectile("swansongbolt", sourcePosition, entity.id(), aimDir, false, { + speed = 35, + power = scalePower(conf.power) + }) + await(delay(0.25)) + end + + animator.setAnimationState("righthand", "fireend") + await(delay(0.6)) + + self.righthand.lock = false +end) + +rocketSwarmAttack = async(function(conf) + await(flyTo(storage.spawnPosition)) + + self.lefthand.lock, self.righthand.lock = true, true + await(join( + moveLeftHand(self.lhorig.anchor, util.toRadians(-90), 2.5, 1.0), + moveRightHand(self.rhorig.anchor, util.toRadians(-90), 2.5, 1.0) + )) + + animator.setAnimationState("body", "rocketwindup") + animator.resetTransformationGroup("lhflip") + animator.resetTransformationGroup("rhflip") + animator.scaleTransformationGroup("lhflip", {-1.0, 1.0}) + animator.scaleTransformationGroup("rhflip", {-1.0, 1.0}) + + local projectiles = {} + local markers = {} + local fired = false + local randomTargetPosition = function() + local e = util.randomFromList(self.targets) + pos = vec2.add(world.entityPosition(e), vec2.withAngle(math.random() * math.pi * 2, math.random() * 7)) + return pos + end + + await(select( + join( + moveLeftHand(vec2.add(self.lhorig.anchor, {0.0, -2}), localAngle(util.toRadians(-130)), 2.5, 2.0), + moveRightHand(vec2.add(self.rhorig.anchor, {0.0, -2}), localAngle(util.toRadians(-50)), 2.5, 2.0), + function() + await(delay(0.5)) + + for i = 1, conf.rocketCount do + -- spawn one rocket from each rocket orifice + local leftPos = vec2.add(mcontroller.position(), animator.partPoint("body", "leftRocketSource")) + local aimDir = vec2.rotate({-1, 0}, -util.toRadians(30) * math.random() * util.toRadians(60)) + local leftId = world.spawnProjectile("swansongrocket", leftPos, entity.id(), aimDir, false, {power = scalePower(conf.power)}) + table.insert(projectiles, leftId) + + local rightPos = vec2.add(mcontroller.position(), animator.partPoint("body", "rightRocketSource")) + local aimDir = vec2.rotate({1, 0}, -util.toRadians(30) * math.random() * util.toRadians(60)) + local rightId = world.spawnProjectile("swansongrocket", rightPos, entity.id(), aimDir, false, {power = scalePower(conf.power)}) + table.insert(projectiles, rightId) + + animator.playSound("fireRockets") + + await(delay(math.random() * conf.spawnDelay)) + end + + fired = true + end, + function() + await(delay(2.0)) + + while fired == false or #projectiles > 0 do + if #projectiles > 0 then + local popped = { + table.remove(projectiles, #projectiles), + table.remove(projectiles, #projectiles) + } + --SBPP - Filter out entities that do not exist in the world. Credit goes to Kherae for this. + popped = util.filter(popped, world.entityExists) + for _, e in ipairs(popped) do + local targetPos = randomTargetPosition() + table.insert(markers, {entity = e, position = targetPos}) + world.sendEntityMessage(e, "setTargetPosition", targetPos) + + markers = util.filter(markers, function(m) + return world.entityExists(m.entity) + end) + monster.setAnimationParameter("rocketMarkers", markers) + + animator.playSound("targetRockets") + + await(delay(conf.targetDelay)) + end + else + coroutine.yield() + end + end + end + ), + function() + local timer = 0.0 + while true do + jitter = 0.1 * timer / 2.0 + self.lefthand.anchor = vec2.add(self.lefthand.anchor, vec2.withAngle(math.random() * math.pi * 2, jitter)) + self.righthand.anchor = vec2.add(self.righthand.anchor, vec2.withAngle(math.random() * math.pi * 2, jitter)) + + timer = math.min(1.0, timer + script.updateDt()) + coroutine.yield() + end + end + )) + + monster.setAnimationParameter("rocketMarkers", nil) + + monster.setAnimationParameter("rockets", {}) + animator.resetTransformationGroup("lhflip") + animator.resetTransformationGroup("rhflip") + + animator.setAnimationState("body", "rocketwinddown") + + self.lefthand.lock, self.righthand.lock = false, false + await(delay(0.5)) +end) + +meleeSlashAttack = async(function(conf) + self.lefthand.lock = true + + local swordStartAngle = util.toRadians(160) + local targetAngle, facing + local rotateTime = conf.rotate + local windupTime = conf.windup + await(join( + moveLeftHand({0.5, 2.0}, self.lhorig.angle, 2.0, rotateTime), + stop(), + function() + local timer = 0.0 + local startAngle = self.lefthand.angle + while timer < windupTime do + local toTarget = world.distance(world.entityPosition(self.target), mcontroller.position()) + facing = util.toDirection(toTarget[1]) + targetAngle = vec2.angle(toTarget) + local newAngle = startAngle + util.angleDiff(startAngle, targetAngle + facing * swordStartAngle) + + controlFace(facing) + self.lefthand.angle = util.lerp(math.min(1.0, timer / rotateTime), startAngle, newAngle) + + timer = timer + script.updateDt() + coroutine.yield() + end + + controlFace(facing) + self.lefthand.angle = targetAngle + facing * swordStartAngle + end, + function() + await(delay(rotateTime)) + animator.setAnimationState("lefthand", "swordwindup") + end + )) + + animator.playSound("slash") + await(moveLeftHand({0.0, -1.0}, targetAngle + facing * util.toRadians(70), 2.0, 0.00)) + coroutine.yield() + await(moveLeftHand({0.0, -1.0}, targetAngle + facing * util.toRadians(-70), 4.0, 0.00)) + + local localTargetAngle = vec2.angle({math.cos(targetAngle) * facing, math.sin(targetAngle)}) + local offset = vec2.rotate({3.5 * facing, -2.75}, localTargetAngle * facing) + local pos = vec2.add(mcontroller.position(), offset) + world.spawnProjectile("swansongslashswoosh", pos, entity.id(), vec2.withAngle(targetAngle), false, {power = scalePower(conf.power)}) + + await(delay(conf.postslash)) + + animator.setAnimationState("lefthand", "swordwinddown") + await(delay(conf.winddown)) + + self.lefthand.lock = false +end) + +meleeChargeAttack = async(function(conf, bodyChargeState, bodyEndState) + self.lefthand.lock, self.righthand.lock, self.body.lock = true, true, true + + local bodyStart, lhStart, rhStart = self.body.angle, self.lefthand.angle, self.righthand.angle + local turnTime = conf.turn + local timer = 0.0 + local targetAngle, facing + animator.playSound("charge") + await(select( + function() + while timer < turnTime do + local toTarget = world.distance(world.entityPosition(self.target), mcontroller.position()) + facing = toTarget[1] < 0 and -1 or 1 + targetAngle = anglePrediction(mcontroller.position(), self.target, 1.0, 35) + + controlFace(facing) + local ratio = util.easeInOutSin(timer / turnTime, 0, 1) + local toAngle = bodyStart + util.angleDiff(bodyStart, targetAngle + util.toRadians(-90)) + self.body.angle = util.lerp(ratio, bodyStart, toAngle) + self.lefthand.angle = util.lerp(ratio, lhStart, lhStart + util.angleDiff(lhStart, targetAngle)) + self.righthand.angle = util.lerp(ratio, rhStart, self.body.angle + util.toRadians(-90)) + + timer = timer + script.updateDt() + coroutine.yield() + end + + self.body.angle = targetAngle + util.toRadians(-90) + end, + function() + await(join( + stop(150), + function() + animator.setAnimationState("lefthand", "swordwindup") + await(moveLeftHand({0.0, 0.0}, targetAngle, -3.0, turnTime)) + end, + function() + while true do + local ratio = util.easeInOutSin(timer / turnTime, 0, 1) + self.lefthand.angle = util.lerp(ratio, lhStart, lhStart + util.angleDiff(lhStart, targetAngle)) + coroutine.yield() + end + end + )) + while true do + self.lefthand.angle = targetAngle + coroutine.yield() + end + end + )) + + animator.setAnimationState("body", bodyChargeState) + animator.setAnimationState("righthand", "boost") + animator.setAnimationState("chargeswoosh", "active") + animator.playSound("thrustBurst") + animator.playSound("thrustLoop", -1) + monster.setDamageParts({"sword"}) + + local timer = 0.0 + local windupTime = conf.windup + while timer < windupTime do + local jitter = 0.125 * timer / windupTime + self.body.anchor = vec2.withAngle(math.random() * util.toRadians(360), jitter) + + timer = math.min(windupTime, timer + script.updateDt()) + coroutine.yield() + end + + local chargeDir = vec2.withAngle(targetAngle) + local wallPoint = nil + await(join( + moveLeftHand({0.0, 0.0}, targetAngle, 6.0, 0.2), + function() + local spawnedWallMelt = false + --SBPP - Added a failsafe so some attacks will not get stuck. Credit goes to Kherae for this. + local failsafeTimer = 0.0 + while true do + local speed = 75 + local force = 400 + local swordStart = vec2.add(mcontroller.position(), animator.partPoint("lefthand", "swordStart")) + local swordEnd = vec2.add(mcontroller.position(), animator.partPoint("lefthand", "swordEnd")) + local normal + failsafeTimer = failsafeTimer + script.updateDt() + wallPoint, normal = world.lineCollision(swordStart, swordEnd) + if wallPoint then + local wallDistance = world.magnitude(swordStart, wallPoint) + speed = speed * (wallDistance / world.magnitude(swordStart, swordEnd)) + force = 2000 + if (wallDistance < 0.1) or (failsafeTimer > 5.0) then + mcontroller.setVelocity({0.0, 0.0}) + return + end + + if normal ~= nil and spawnedWallMelt == false then + world.spawnProjectile("wallmelt", wallPoint, entity.id(), vec2.mul(normal, {-1, -1}), false) + spawnedWallMelt = true + animator.playSound("chargeBrake") + end + else + world.debugLine(swordStart, swordEnd, "green") + end + + controlApproachVelocity(vec2.mul(chargeDir, speed), force) + + coroutine.yield() + end + end + )) + + await(delay(0.1)) + + mcontroller.setVelocity({0.0, 0.0}) + animator.setAnimationState("body", bodyEndState) + animator.setAnimationState("righthand", "idle") + animator.setAnimationState("lefthand", "swordwinddown") + animator.setAnimationState("chargeswoosh", "inactive") + animator.stopAllSounds("thrustLoop") + monster.setDamageParts({}) + + await(delay(conf.winddown)) + + self.body.lock, self.lefthand.lock, self.righthand.lock = false, false, false +end) + +meleeDashAttack = async(function(conf, targetAngle) + self.lefthand.lock = true + self.righthand.lock = true + self.body.lock = true + + animator.setAnimationState("lefthand", "swordwindup") + animator.playSound("charge") + controlFace(math.cos(targetAngle)) + + local rotateTime = conf.rotate + await(join( + function() + --SBPP - Added a failsafe so some attacks will not get stuck. Credit goes to Kherae for this. + local failsafeTimer = 0.0 + while (vec2.mag(mcontroller.velocity()) > 1.0) and (failsafeTimer < 5.0) do + mcontroller.controlApproachVelocity({0.0, 0.0}, self.airForce) + coroutine.yield() + failsafeTimer = failsafeTimer + script.updateDt() + end + end, + moveLeftHand(self.lhorig.anchor, targetAngle + util.toRadians(-170) * self.facing, self.lhorig.distance, rotateTime), + function() + local timer = 0.0 + local startAngle = self.body.angle + local targetAngle = startAngle + util.angleDiff(startAngle, localAngle(targetAngle) * self.facing) + while timer < rotateTime do + self.body.angle = util.lerp(timer / rotateTime, startAngle, targetAngle) + self.righthand.angle = self.body.angle + util.toRadians(-90) + timer = timer + script.updateDt() + coroutine.yield() + end + + self.body.angle = targetAngle + end + )) + + local dashPos = vec2.add(mcontroller.position(), vec2.withAngle(targetAngle, 30)) + local dashMidPos = vec2.mul(vec2.add(mcontroller.position(), dashPos), 0.5) + local dashDir = vec2.norm(world.distance(dashPos, mcontroller.position())) + + await(delay(conf.windup)) + await(blinkDash( + dashPos, + 30.0, + {body = conf.bodyState, wings = conf.wingsState, lefthand = "swordloop", righthand = "idle"}, + function() + await(moveLeftHand(self.lhorig.anchor, targetAngle + util.toRadians(10) * self.facing, 6.0, 0.0)) + animator.playSound("slash") + local offset = {-2.25, 0.0} + local swooshPos = vec2.add(dashMidPos, vec2.mul(vec2.rotate(offset, localAngle(vec2.angle(dashDir))), {self.facing, 1.0})) + world.spawnProjectile("swansongdashswoosh", swooshPos, entity.id(), dashDir, false, {power = scalePower(conf.power)}) + end + )) + + await(delay(0.1)) + + animator.setAnimationState("lefthand", "swordwinddown") + + await(delay(conf.winddown)) + + self.lefthand.lock = false + self.righthand.lock = false + self.body.lock = false +end) + +meleeSequenceAttack = async(function(conf) + local targetPosition, targetDistance, targetInner, targetOuter + local attack + await(select( + function() + while true do + targetPosition = world.entityPosition(self.target) + local targetToSpawn = world.distance(targetPosition, storage.spawnPosition) + local distance = world.magnitude(targetToSpawn) + + targetInner = distance < conf.dashArea + targetOuter = distance > conf.chargeArea + targetDistance = world.magnitude(targetPosition, mcontroller.position()) + + coroutine.yield() + end + end, + function() + while true do + if targetDistance < conf.slashRange then + attack = meleeSlashAttack(conf.slash) + break + end + + local toTarget = world.distance(targetPosition, mcontroller.position()) + local toSpawn = world.distance(storage.spawnPosition, mcontroller.position()) + local spawnDistance = world.magnitude(toSpawn) + if spawnDistance < conf.chargeArea and targetOuter then + attack = meleeChargeAttack(conf.charge, "idlegrav", "idle") + break + end + if spawnDistance > conf.chargeArea and targetDistance < conf.dashArea and targetInner then + attack = meleeDashAttack(conf.dash, vec2.angle(toTarget)) + break + end + + if targetInner then + if spawnDistance < conf.chargeArea then + controlApproachVelocity(vec2.mul(vec2.norm(toSpawn), -self.flySpeed), self.airForce) + elseif targetDistance > conf.dashArea then + controlApproachVelocity(vec2.mul(vec2.norm(toTarget), self.flySpeed), self.airForce) + end + elseif targetOuter then + controlApproachVelocity(vec2.mul(vec2.norm(toSpawn), self.flySpeed), self.airForce) + end + + coroutine.yield() + end + end + )) + + await(attack) +end) + +deathLaserAttack = async(function(conf) + await(flyTo(vec2.add(storage.spawnPosition, {0.0, -6.0}))) + + local timer = 0.0 + while timer < 0.5 do + self.wings.anchor = vec2.lerp(util.easeInOutSin(timer / 0.5, 0, 1), {0.0, 0.0}, {0.0, 3.5}) + timer = timer + script.updateDt() + coroutine.yield() + end + animator.setAnimationState("wings", "beamwindup") + await(delay(1.0)) + + for i = 1, 6 do + local sourcePosition = vec2.add(mcontroller.position(), animator.partPoint("beam", "beamStart")) + local toTarget = world.distance(world.entityPosition(self.target), sourcePosition) + controlFace(toTarget[1]) + local targetAngle = anglePrediction(sourcePosition, self.target, 1.0, 45) + animator.resetTransformationGroup("beam") + animator.rotateTransformationGroup("beam", localAngle(targetAngle)) + coroutine.yield() + + monster.setAnimationParameter("beam", true) + animator.playSound("beamStart") + await(delay(0.3)) -- windup + + animator.playSound("beamLoop", -1) + await(delay(0.2)) -- start firing + + -- damage + monster.setDamageParts({"beam"}) + await(delay(0.3)) + + -- winddown + monster.setDamageParts({}) + monster.setAnimationParameter("beam", false) + animator.stopAllSounds("beamLoop", 0.5) + await(delay(0.5)) + end + + animator.setAnimationState("wings", "beamwinddown") + await(delay(1.0)) + + timer = 0.0 + while timer < 0.5 do + self.wings.anchor = vec2.lerp(util.easeInOutSin(timer / 0.5, 0, 1), {0.0, 3.5}, {0.0, 0.0}) + timer = timer + script.updateDt() + coroutine.yield() + end +end) + +finalFormTransition = async(function(conf) + animator.setParticleEmitterActive("shock", true) + animator.playSound("shockLoop", -1) + resetAttacks() + + world.setDungeonGravity(self.gravityDungeonId, 0) + await(flyTo(storage.spawnPosition)) + breakAdds() + + local damageParts = {} + local setDamagePartActive = function(part, active) + if active then + table.insert(damageParts, part) + else + damageParts = util.filter(damageParts, function(p) return p ~= part end) + end + monster.setDamageParts(damageParts) + end + + local timer = 0.0 + await(join( + function() + while timer < conf.chargeTime do + local ratio = timer / conf.chargeTime + status.addEphemeralEffect("maxprotectionnogrit") + animator.setLightColor("glow", {ratio * 0.75 * 255, ratio * 0.75 * 200, 100}) + status.setResourcePercentage("health", timer / conf.chargeTime) + + timer = timer + script.updateDt() + coroutine.yield() + end + end, + function() + for i = 1, conf.beams do + await(handBeam("left", anglePrediction(mcontroller.position(), self.target, 1.0, 35), setDamagePartActive)) + end + end, + function() + await(delay(0.75)) + for i = 1, conf.beams do + await(handBeam("right", anglePrediction(mcontroller.position(), self.target, 1.0, 35), setDamagePartActive)) + end + end + )) + + status.removeEphemeralEffect("maxprotectionnogrit") + status.setResourcePercentage("health", 1.0) + + animator.stopAllSounds("shockLoop") + animator.setParticleEmitterActive("shock", false) + animator.setLightColor("glow", {255, 200, 100, 255}) + animator.burstParticleEmitter("burst") + animator.playSound("transitionBurst") + animator.setAnimationState("body", "final") + animator.setAnimationState("wings", "broken") +end) + +finalFormMeleeAttack = async(function(conf) + local dash = async(function() + local spawnToTarget = world.distance(world.entityPosition(self.target), storage.spawnPosition) + local distance = vec2.mag(spawnToTarget) + if distance < 25 then + -- teleport to the outside of the area, placing the target between us and the center + local dashPos = vec2.add(storage.spawnPosition, vec2.mul(vec2.norm(spawnToTarget), math.min(distance + 10, 25))) + await(blinkDash(dashPos, 0.0, {body = "final", wings = "broken", lefthand = "idle", righthand = "idle"})) + local toSpawn = world.distance(storage.spawnPosition, mcontroller.position()) + await(meleeDashAttack(conf.dash, vec2.angle(toSpawn))) + end + end) + local charge = async(function() + local spawnToTarget = world.distance(world.entityPosition(self.target), storage.spawnPosition) + local distance = vec2.mag(spawnToTarget) + if distance > 10 then + -- teleport to the outside of the area, placing the target between us and the center + await(blinkDash(storage.spawnPosition, 0.0, {body = "final", wings = "broken", lefthand = "idle", righthand = "idle"})) + await(meleeChargeAttack(conf.charge, "final", "final")) + end + end) + local slash = async(function() + local targetPosition = world.entityPosition(self.target) + local slashDistance = 10 + local positions = shuffled({ + vec2.add(targetPosition, vec2.withAngle(util.toRadians(45), slashDistance)), + vec2.add(targetPosition, vec2.withAngle(util.toRadians(135), slashDistance)), + vec2.add(targetPosition, vec2.withAngle(util.toRadians(225), slashDistance)), + vec2.add(targetPosition, vec2.withAngle(util.toRadians(315), slashDistance)) + }) + for _, pos in ipairs(shuffled(positions)) do + local bounds = rect.translate(mcontroller.boundBox(), pos) + if not world.rectTileCollision(bounds) then + await(blinkDash(pos, 0.0, {body = "final", wings = "broken", lefthand = "idle", righthand = "idle"})) + await(meleeSlashAttack(conf.slash)) + return + end + end + end) + + local attack = util.randomFromList({dash, charge, slash}) + await(attack()) + + coroutine.yield() +end) \ No newline at end of file diff --git a/Starbound Patch Project/objects/arttrophies/sandstonestatueapex/sandstonestatueapex.object.patch b/Starbound Patch Project/objects/arttrophies/sandstonestatueapex/sandstonestatueapex.object.patch new file mode 100644 index 0000000..a7f9486 --- /dev/null +++ b/Starbound Patch Project/objects/arttrophies/sandstonestatueapex/sandstonestatueapex.object.patch @@ -0,0 +1,12 @@ +[ + { + "op" : "test", + "path" : "/apexDescription", + "value" : "This Apex looks familiar. I think she was a pro-wrestler." + }, + { + "op" : "replace", + "path" : "/apexDescription", + "value" : "This Apex looks familiar. I think he was a pro-wrestler." + } +] diff --git a/Starbound Patch Project/objects/arttrophies/sandstonestatuefloran/sandstonestatuefloran.object.patch b/Starbound Patch Project/objects/arttrophies/sandstonestatuefloran/sandstonestatuefloran.object.patch new file mode 100644 index 0000000..cca94fd --- /dev/null +++ b/Starbound Patch Project/objects/arttrophies/sandstonestatuefloran/sandstonestatuefloran.object.patch @@ -0,0 +1,26 @@ +[ + [ + { + "op" : "test", + "path" : "/floranDescription", + "value" : "Greenfinger. Ssspecial Floran. Sssmell funny." + }, + { + "op" : "replace", + "path" : "/floranDescription", + "value" : "Firssst Greenfinger. Ssspecial Floran. Sssmell funny." + } + ], + [ + { + "op" : "test", + "path" : "/glitchDescription", + "value" : "Hypothetical. I believe this statue depicts a great Floran leader." + }, + { + "op" : "replace", + "path" : "/glitchDescription", + "value" : "Hypothetical. I believe this statue depicts an important Floran." + } + ] +] \ No newline at end of file diff --git a/Starbound Patch Project/objects/arttrophies/sandstonestatueglitch/sandstonestatueglitch.object.patch b/Starbound Patch Project/objects/arttrophies/sandstonestatueglitch/sandstonestatueglitch.object.patch new file mode 100644 index 0000000..d081b1a --- /dev/null +++ b/Starbound Patch Project/objects/arttrophies/sandstonestatueglitch/sandstonestatueglitch.object.patch @@ -0,0 +1,31 @@ +[ + [ + { + "op" : "test", + "path" : "/genericDescription", + "inverse" : false + }, + { + "op" : "test", + "path" : "/avianDescription", + "inverse" : true + }, + { + "op" : "move", + "from": "/genericDescription", + "path": "/avianDescription" + } + ], + [ + { + "op" : "test", + "path" : "/glitchDescription", + "value" : "Informed. This statue depicts the first Glitch model, before the self-aware began to modify themselves." + }, + { + "op" : "replace", + "path" : "/glitchDescription", + "value" : "Informed. This statue depicts the first Glitch model." + } + ] +] diff --git a/Starbound Patch Project/objects/biome/scorchedcity/explosivebarrel/explosivebarrel.object.patch b/Starbound Patch Project/objects/biome/scorchedcity/explosivebarrel/explosivebarrel.object.patch new file mode 100644 index 0000000..3d3cc77 --- /dev/null +++ b/Starbound Patch Project/objects/biome/scorchedcity/explosivebarrel/explosivebarrel.object.patch @@ -0,0 +1,12 @@ +[ + { + "op" : "test", + "path" : "/shortdescription", + "value" : " Explosive Barrel" + }, + { + "op" : "replace", + "path" : "/shortdescription", + "value" : "Explosive Barrel" + } +] diff --git a/Starbound Patch Project/objects/crafting/upgradeablecraftingobjects/upgradeablecraftingobject.lua b/Starbound Patch Project/objects/crafting/upgradeablecraftingobjects/upgradeablecraftingobject.lua deleted file mode 100644 index c8cfd08..0000000 --- a/Starbound Patch Project/objects/crafting/upgradeablecraftingobjects/upgradeablecraftingobject.lua +++ /dev/null @@ -1,106 +0,0 @@ -require "/scripts/vec2.lua" -require "/scripts/util.lua" - -function init() - object.setInteractive(true) - - self.maxUpgradeStage = config.getParameter("maxUpgradeStage") - - if not storage.currentStage then - storage.currentStage = math.min(config.getParameter("startingUpgradeStage", 1), self.maxUpgradeStage) - end - - self.stageDataList = config.getParameter("upgradeStages") - - -- handle upgrade messages from the crafting interface - message.setHandler("requestUpgrade", function(_, _) - upgradeTo(storage.currentStage, storage.currentStage + 1) - end) - - updateStageData() - - if ObjectAddons then - local addonConfig = currentStageData().addonConfig - ObjectAddons:init(addonConfig or {}, updateStageData) - end -end - -function uninit() - if ObjectAddons then - ObjectAddons:uninit() - end -end - -function update(dt) - -end - -function onInteraction(args) - return { "OpenCraftingInterface", currentStageData().interactData} -end - -function currentStageData() - if self.stageDataList[storage.currentStage].addonConfig then - -- merge any data from connected addons - local res = copy(self.stageDataList[storage.currentStage]) - for _, addon in pairs(self.stageDataList[storage.currentStage].addonConfig.usesAddons or {}) do - if ObjectAddons:isConnectedTo(addon.name) then - res = util.mergeTable(res, addon.addonData) - end - end - return res - else - return self.stageDataList[storage.currentStage] - end -end - -function upgradeTo(oldStage, newStage) - if (newStage <= self.maxUpgradeStage and newStage ~= storage.currentStage) then - - showUpgradeEffects(oldStage) - - storage.currentStage = newStage - - if ObjectAddons then - local addonConfig = currentStageData().addonConfig - ObjectAddons:init(addonConfig or {}, updateStageData) - else - updateStageData() - end - end -end - -function showUpgradeEffects(stageIndex) - local stageData = currentStageData() - - if (stageData.upgradeSoundEffect ~= nil) then - animator.playSound(stageData.upgradeSoundEffect) - end - - if (stageData.upgradeParticleEffect ~= nil) then - animator.burstParticleEmitter(stageData.upgradeParticleEffect) - end -end - -function updateStageData() - local stageData = currentStageData() - animator.setAnimationState("stage", stageData.animationState) - object.setMaterialSpaces(stageData.materialSpaces) - - for k, v in pairs(stageData.itemSpawnParameters or {}) do - object.setConfigParameter(k, v) - end -end - -function die() - local stageData = currentStageData() - stageData.itemSpawnParameters.startingUpgradeStage = storage.currentStage - --SBPP - don't add any parameters if the object has not been upgraded so placed and non-placed ones will stack. - local itemDescriptor - if(storage.currentStage == config.getParameter("startingUpgradeStage", 1)) then - itemDescriptor = {name = config.getParameter("objectName"), count = 1} - else - itemDescriptor = {name = config.getParameter("objectName"), count = 1, parameters = stageData.itemSpawnParameters} - end - world.spawnItem(itemDescriptor, vec2.add(object.position(), {0, 3})) -end diff --git a/Steam page description.txt b/Steam page description.txt index 77eaadd..5dd7cd8 100644 --- a/Steam page description.txt +++ b/Steam page description.txt @@ -6,6 +6,23 @@ It is safe to install or uninstall this mod at any time. Recent Changelogs: [code] +-------- 1.5.1 -------- +Several more typos were fixed. + +Some references to cut lore were removed thanks to Armok. + +The Swansong fight hat timers added to some attacks so they would not get stuck thanks to Kherae. + +Several more UI and menu issues were fixed thanks to Silver Sokolova. + +Apex City building2clue now has the same loot table fix as its non-clue counterpart. + +The Avian and human female body textures each had a miscolored border pixel corrected. + +Reverted a fix to upgradeablecraftingobject.lua because it was not working correctly for everyone. +This fix is likely to return with better implementation later on. + + -------- 1.5.0 -------- This update is effectively a total remake of the patch project from the ground up. Every single change has been re-evaluated to ensure it is working as intended.