Compare commits

..

784 Commits

Author SHA1 Message Date
sigonasr2 4a4ffb1c6c Block off nonexistent <stacktrace> dependency for web builds. Fix up emscripten build errors with old CMakeCache. Update to C++23 in CMakeLists. Convert CanModifyEquipSlots function into a menu static function that returns a boolean (instead of being calculated only once on initialization. oops) Release Build 12111. 2 days ago
sigonasr2 842a32e947 Arrow indicator for monsters now adjustable. Additional zones added without a class now properly show up in the filterable zones list. Added coin toss, hide, and hit boss mechanic. Release Build 12109. 2 days ago
sigonasr2 dbf140e0bd Add pair argument conversion for Oscillator class. Collided with player flag gets set to false each frame. Implement Pirate's Coin/Curse mechanic and spinning/debuff effects. Release Build 12094. 1 week ago
sigonasr2 63a97958f7 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 3 weeks ago
sigonasr2 e1575081ce Add in Curse and coin debuffs. Add in Pirate's Treasure spawn and collision check for placing and removing the curse. Adding buffs to the player will not immediately apply them but put them in a buff list to be applied on the next tick. AddBuff now returns the buff. GetOrAddBuff now properly uses the AddBuff function to add a buff to avoid any behavior discrepancies. Added a new test to check for buffs to be added. Fixed unit tests that broke due to waiting an extra game tick to apply buffs. 222/222 Tests Passing. Release Build 12049. 3 weeks ago
Quapsel ead0648a62 Chapter 5 Presets progress. 4 weeks ago
sigonasr2 ded7e53fd7 Slash animation plays when ghost saber hits player. Release Build 12036. 4 weeks ago
sigonasr2 d24c016d3e Pirate Captain Ghost moves towards player. Release Build 12032. 4 weeks ago
sigonasr2 071e98aecd Added ghost saber expanding behavior. Fix Prediction bombardment pattern to properly use player's previous position. Release Build 12029. 1 month ago
sigonasr2 fdef405ed8 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 month ago
sigonasr2 51a3feb5b6 Add Ghost Saber attack. Release Build 12016. 1 month ago
Quapsel 95e51f2324 Chapter 5 Tilesets added. First Steps on Chapter 5 Presets. 1 month ago
sigonasr2 24d36da0ab Add afterimage rendering for monsters. Release Build 12003. 1 month ago
sigonasr2 4e099b71f9 Add placeholder item graphics for chapter 4 items. Setup map classes for chapter 4 maps. Add in force set position function for monsters. Release Build 12001. 1 month ago
sigonasr2 a7308f4ac5 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 2 months ago
sigonasr2 6c5648bac9 Add previous player position variable and getter for the player. Add in other cannon pattern types to rotation. 2 months ago
Quapsel 15b5b541e0 „Adventures in Lestoria/assets/config/levels.txt“ ändern 2 months ago
Quapsel f2681aebaf „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 2 months ago
sigonasr2 27bfd8e1d4 Add shrapnel shot attack. Fix random dispersion of cannon shots (incorrect math that overcorrected for random angle + random distance vector). Release Build 11980. 2 months ago
sigonasr2 c3261bb301 Remove extraneous log lines. Restore original cannon cycle. Release Build 11972. 2 months ago
sigonasr2 0d15ad2fa7 Fix Issue #74. Falling Bullet properly activates side effect instead of randomly choosing to due to order of IBullet vs child class call updates. Update credits with Special Thanks section. Release Build 11970. 2 months ago
sigonasr2 dc9717c07f Tweak and fix Falling Bullet behavior to use proper coloring. Release Build 11958. 2 months ago
sigonasr2 e2b6e9318a Add in initial cannonball attack. Setup Chapter 3 boss spawn. Release Build 11946. 2 months ago
sigonasr2 1a9d5b12d0 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 2 months ago
sigonasr2 9732c57983 Generalized FallingStone into FallingBullet to supply a custom image. Prepare graphics and add extra map items and basic AI structure for Chapter 3 boss. Release Build 11936. 2 months ago
Quapsel d85e062ed5 4_8 added. 2 months ago
sigonasr2 e893d3a54e Add in camp versions of the pause menu/item loadout menu. Allow the player to change loadout items from the camp. Make mana gain numbers not stack on top of health gain numbers (for items like Berries). Release Build 11935. 2 months ago
sigonasr2 48a35faf49 Use game state for camp hub checks instead of the direct map name. Prevent added velocity external factors from cancelling a player's spell cast. Release Build 11928. 2 months ago
sigonasr2 7d0b15f652 Allow loadout items to be used in the camp without consuming the item. Release Build 11922. 2 months ago
sigonasr2 d89b82bc41 Remove chapter settings from State_OverworldMap class' control and add a Chapter change and set player world location command to the Visual Novel parser. Add base camp location to Chapter 3 area. Release Build 11917. 2 months ago
sigonasr2 594dd352ec Add random submerge time between arm attacks. Release Build 11916. 2 months ago
sigonasr2 f6c76b3881 Added homing bullet behaviors. Added turn_towards_target util helper function; a higher level funcution derived from turn_towards_direction. Modify Ink slowdown to reset duration instead of stacking (per spec). Release Build 11914. 2 months ago
sigonasr2 6b0ea4422f Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 2 months ago
sigonasr2 8eb1878074 Add additive blending property to bullets. Ink bubble explosion. Ink Bubble bullet behavior added. Add Ink Slowdown debuff property. Release Build 11900. 2 months ago
Quapsel b160e9b421 4_7 added. 3 months ago
Quapsel 0cb1d0e6f3 4_5 & 4_6 added. 3 months ago
sigonasr2 b38b42f1d0 Setup structure, classes, graphics, and variables for Ink Bullet mechanics. 3 months ago
sigonasr2 8cf9d0b9dd Fix missing burst bullet in bullet ring. Reduce collision radius of ring bullets to better match the graphic. Release Build 11896. 3 months ago
sigonasr2 a006924fe2 Fix crash on collision with Octopus big bullet. Fix animation sheet directions and facing direction commands for GiantOctopus AI. Release Build 11895. 3 months ago
sigonasr2 7b779aa7e3 Add missing item and accessory graphics for Chapter 3. Include Sherman and Greg story character sprites in story. Release Build 11893. 3 months ago
sigonasr2 9c8906308c Fix unit testing paths to refer to correct directories. Added error handling and control return path for Entity functions. Release Build 11891. 3 months ago
sigonasr2 cb28ab82aa Update 'Adventures in Lestoria/TODO.txt' 3 months ago
Quapsel 4b0535d0db Stages 4_4 and 4_B1 added. 3 months ago
sigonasr2 dbeb9fbee7 Add burst bullet functionality, add generalized Entity connector helper class. Burst bullet explode sound effect. Burst Bullet extra bullets on explosion. Damage through Damage Data modification will cancel if set to zero if damage resolution is modified. RotatingBullet class and functionality added. Release Build 11878. 3 months ago
sigonasr2 8da0813ac3 Burst Bullet class structure. 3 months ago
sigonasr2 60b31a602c Fix Move Spd% typo on Octopus and Captain's Diamond Ring. Add missing item icons / hud icons. Fix typos in status effects and shards for accessories. Add in Octopus bullet shots. 3 months ago
Quapsel 2752f6630d „Adventures in Lestoria/assets/config/items/Weapons.txt“ ändern 3 months ago
Quapsel 4031b03974 „Adventures in Lestoria/assets/config/items/Equipment.txt“ ändern 3 months ago
Quapsel 0a9524c5e2 „Adventures in Lestoria/assets/config/levels.txt“ ändern 3 months ago
Quapsel 94f29020c6 „Adventures in Lestoria/assets/config/Monsters.txt“ ändern 3 months ago
Quapsel debe498570 „Adventures in Lestoria/assets/config/items/Accessories.txt“ ändern 3 months ago
Quapsel e1ad07ded2 „Adventures in Lestoria/assets/config/Monsters.txt“ ändern 3 months ago
Quapsel aa26348727 „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 3 months ago
sigonasr2 ef0b00e771 Attack arc ranges increased. Reset attack arc display when submerging occurs. Release Build 11865. 3 months ago
sigonasr2 c735f74a83 Takoyaki. 3 months ago
sigonasr2 6fb8556c79 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 3 months ago
sigonasr2 bec8c67ee9 Add staggered rise animations to Octopus arms. Add attack up buffs and animation speed up buffs to the boss and Octopus arms per tentacle death. Release Build 11852. 3 months ago
Quapsel eef449f2a3 4_3 added. 3 months ago
sigonasr2 58be1c8999 Octopus Arm now has proper detection for empty pool spawns and responsive to player moving around (spawn in nearby empty water pools). Release Build 11848. 3 months ago
sigonasr2 6ea6b9b88d Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 3 months ago
sigonasr2 442b22f640 Fix octopus arm transition damage to be true damage instead of player damage (no critical hits). Fix octopus arm transition jump target positions (return (0,0) now) 3 months ago
Quapsel bc6e19d5ed 4_2 added. 3 months ago
sigonasr2 54c57a692f Fix missing tile in 1-2. Fix Chapter 3 bonus stage stage plate. 3 months ago
sigonasr2 f94d22ff48 Fix Pirate Buccaneer's aiming sight remaining on the field after death. Make aiming sight target line pulse. Fix bug with Frog Tongue one-shotting the player. Release Build 11815. 3 months ago
sigonasr2 c9a29b2705 Fix arc not showing up. 3-5 had a missing spawn point set. More Octopus Arm behavior fixes. Release Build 11808. 4 months ago
sigonasr2 9b94a90326 Octopus Boss implementation 4 months ago
Quapsel b7e486690d Monstespawns for 3_7 & 3_8 finished. 4 months ago
Quapsel f1bf83eb17 Monster spawns done for 3_5, 3_6 & 3_B1 4 months ago
sigonasr2 ef1d4e5470 Make lerp functions require both arguments to be the same for deduced return type. Fix buggy monster spawns to account for monster sizes. BOTTOM RIGHT EDGE FOR COORDINATES ??? Release Build 11794. 4 months ago
sigonasr2 10b8e68b22 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 4 months ago
sigonasr2 f5038d4a24 Add in more AI. 4 months ago
Quapsel 6e435a5c54 Monster Spawns for 3_3 and 3_4 added. Spawn Group locations for all chapter 3 maps finished. 4 months ago
Quapsel 5e02dd6590 3_2 Monste spawns finished. 3_3 - 3_6 Spawn Groups placed. 4 months ago
Quapsel 19dcf0fad0 Monster Spawns for 3_1. 4 months ago
sigonasr2 debc23bfa3 Additional AI setup. 4 months ago
sigonasr2 81bfbfb860 Add map setup and monster templates for Octopus fight. 4 months ago
sigonasr2 7cf3c53e91 Fix all octopus arm AI config variable references to actually use the Config functions instead of direct config file parameters. Add in attack cone effect. 4 months ago
sigonasr2 139d4f4eac Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 4 months ago
sigonasr2 16708108e6 Move Attack Arc function into its own function for portability. 4 months ago
Quapsel 61b8efbb9f Chapter 4 presets completed. 4_1 Added. 4 months ago
Quapsel f181fca2a1 hub border, border extension in 1 random map, chapter 4 presets almost finished. 5 months ago
sigonasr2 46ba7a780d Octopus arm AI 5 months ago
sigonasr2 7b165a64d5 Arc implementation completed. Release Build 11776. 5 months ago
sigonasr2 d85cef1b86 Arcs, Cones, Polygon overlap code. Octopus Arm AI initialized. 5 months ago
sigonasr2 1bb0ceb096 Add monster entries and animations for Giant Octopus boss and Octopus Arm. Release Build 11756. 5 months ago
sigonasr2 3a3d405272 Implemented lingering effect item script capabilities. Added Molotov item and item graphic. Added burning sound effect. Release Build 11755. 5 months ago
sigonasr2 868a089666 Separate ThrowingProjectiles from PoisonBottle class to create generic throwable item bullets. Added throwable projectile item script. Made Bomb item functional. Release Build 11748. 5 months ago
sigonasr2 8c3dd0f071 [demo] Fix stage alpha transitions in story view. Release Build 11576. 5 months ago
sigonasr2 2b0c1c070a [demo] Add Warrior story image to list of files. Missing asset. Release Build 11575. 5 months ago
sigonasr2 598b3b66c7 Add new chapter 3 consumables to item database and placeholder icons. 5 months ago
sigonasr2 b11d209813 Implemented Sandworm AI. Change added velocity function to be a standalone velocity variable added to original player velocity. Release Build 11725. 5 months ago
sigonasr2 446be1e02f Add collision intangibility for monsters with a collision radius of zero. Add unit test to check for it. Release Build 11714. 5 months ago
sigonasr2 e0644fe809 Sandworm implementation + Monster collision radius changing implemented. 5 months ago
sigonasr2 e9f02ea885 Move player invulnerability frame transparency from affecting globally to all monsters to affecting only the player's sprite. Release Build 11711. 5 months ago
sigonasr2 2f441f76b7 Implement missing SetAbility4 function stub. Add HasBuff function to both the Player and Monster classes. Implemented Giant Crab AI. 219/219 tests passing. Release Build 11710. 5 months ago
sigonasr2 3684e0f211 Implemented Crab AI. Release Build 11683. 5 months ago
sigonasr2 94de6bb10e Remove hidden incorrect collision on various stages. Release Build 11674. 5 months ago
sigonasr2 9ec850fdd0 Implemented Seagull AI. Release Build 11673. 5 months ago
sigonasr2 6b0c17b5ff Add ambient wave and gull sounds to Chapter 3 stages. Add slight pitch variance to environmental audio. Release Build 11667. 5 months ago
sigonasr2 aae5726f3d Finish implementing Pirate Captain and Parrot behaviors. Refactor monster phase system to be per-strategy instead of a global phase for all strategies. Release Build 11666. 5 months ago
sigonasr2 a09ea9a9a7 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 5 months ago
sigonasr2 422c6f02cc Update Chapter 3 maps and entries to be playable. Added Chapter 3 stage and boss music. Pirate Captain base AI completed, mounted parrot AI added. Release Build 11657. 5 months ago
Quapsel 967af0fe5f Added details to the world map. 5 months ago
sigonasr2 3024111b4a Remove unnecessary variables from old monster update code, remove return value, create unnecessary ambiguity with why we would need to return true/false for no purpose. 6 months ago
sigonasr2 970b15ac5d Parrot Monster scripts initialized. 6 months ago
sigonasr2 20f8abf8b5 Fix Parrot spritesheet size. Pirate Captain behavior fixed (cannot directly inherit from Goblin Dagger, copy-paste AI instead due to conflicting states). Release Build 11653. 6 months ago
sigonasr2 5f63f23635 Add missing item icons (placeholders). Add mounted parrot to Pirate Captain. Release Build 11649. 6 months ago
sigonasr2 d432966e3e Worked on Pirate Captain base AI. (Mounted Parrot AI still needs implemented). 6 months ago
sigonasr2 4e1fa8d4e3 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 6 months ago
sigonasr2 c16dccc623 Begin Pirate Captain AI implementation. 6 months ago
Quapsel 888cea1581 „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 6 months ago
sigonasr2 da2640fbc1 Health and mana indicators don't get squished strangely anymore due to floating point values. Release Build 11638. 6 months ago
sigonasr2 85ba978793 Fix new crashes pertaining to unchecked 0 distance in line normalizing functions. 6 months ago
sigonasr2 0e3a4c0ba7 Implemented Pirate Buccaneer AI. Remove unnecessary Run Towards overrides in monsters list. Fix Charged Arrow to use proper last graphic when a custom one is specified. Release Build 11621. 6 months ago
sigonasr2 d6c2b6c87f Implement Pirate Marauder AI. Release Build 11609. 6 months ago
sigonasr2 9509f317c3 Implement Pirate strategy. Refactor Dagger Stab/Slash slightly. After loading a resource pack, if it previously failed to load attempt the load again to stop game crashes with modifications to the game pack. Release Build 11602. 6 months ago
sigonasr2 90d9bc86b1 Add in templates, strategies, and setup for Chapter 3 monsters. Fix tile collisions on Chapter 3 tiles. Release Build 11595. 6 months ago
sigonasr2 8f5973d836 Crab and Giant Crab monster entries added. 6 months ago
sigonasr2 21c5af80c2 Pirate Marauder entry added. 6 months ago
sigonasr2 612cda41c3 Pirate Marauder/Pirate Captain data added. 6 months ago
sigonasr2 07f431a5aa Prep Pirate monster entry. 6 months ago
sigonasr2 0f0a70b2b4 Implement new enchant rolling rule: Same enchant may be chosen if the result ends up increasing at least of the supported stats. Release Build 11592. 6 months ago
sigonasr2 880b8e4335 Implement default audio event property to stages. Add fanfare transition and post-boss song event to the game. Add appropriate triggers for when boss fight completes. Addresses Issue #66. Release Build 11576. 6 months ago
sigonasr2 2fe0991920 Push demo fixes into master branch. 6 months ago
sigonasr2 4d74d803b2 Linux fixes. Make sure audio doesn't require loading from filesystem (Use resource packs instead) 6 months ago
sigonasr2 57cc852033 Force all old decal instances to be cleared from the engine when the game is reset. This addresses a very random Linux crash that was occurring enough to be a problem when quitting the game. Release Build 11570. 6 months ago
sigonasr2 ca46f0b92e Update distribution scripts with cleaning out the monsters folder. 6 months ago
sigonasr2 004f896953 MonsterData should be loading graphics from Resource Packs instead. This should solve the issues with Issue #70. Release Build 11566. 6 months ago
sigonasr2 669a267a67 [demo] Remove hash verification functions from the game. See Issue #68. Release Build 11565. 6 months ago
sigonasr2 47ff285bef Remove songs from git index into commercial assets. Update distribution and build scripts to not include songs commercial folder. Fix early destruction of audio buffer data causing a crash. Release Build 11544. 6 months ago
sigonasr2 a16ca296c7 Linux changes and fixes 6 months ago
sigonasr2 d0db3412d9 Added controller compatibility to the refine result and enchant confirmation windows. Modify menu navigation behavior slightly for the three windows with accessory lists. Release Build 11665. 6 months ago
sigonasr2 36b9a071e3 Added controller compatibility to Artificer Enchant Window. Removed menu navigation using right analog stick. Release Build 11662. 6 months ago
sigonasr2 da5d596a37 Implemented controller compatbility for Artificer Disassembly Menu. Release Build 11653. 6 months ago
sigonasr2 86e2976549 Added some menu scrolling helper functions. Implemented Artificer Enchant window. Added controller compatibility to Artificer Refining Window. Release Build 11652. 6 months ago
sigonasr2 a5c10f35e6 Updated MenuItemLabel to accept either a copy or weak reference so that it can hold information not necessarily in our inventory. Release Build 11620. 6 months ago
sigonasr2 d007e2bbf5 Fix key display for Ability 4 to match the keybind of the 4th ability. Release Build 11599. 6 months ago
sigonasr2 1e7345d7b5 Implement skeleton for Enchant Confirm Window. Make original ability functions static for player class ability retrieval. Add private static access internal functions. Remove check for non-existing animations for player (would just not change the animation if it doesn't exist). Release Build 11598. 6 months ago
sigonasr2 0d7759b230 Fix a unit test calling BGM pitch change function (which doesn't need a test). Release Build 11564. 6 months ago
sigonasr2 796c0da6d0 Add back in correct World Map with stage plates for master branch. 6 months ago
sigonasr2 55f4347452 Update master build demo flag (set to false) and admin mode (set to true). Release Build 11562. 6 months ago
sigonasr2 8283147efe [demo] Add congratulatory message for completing the demo. Version updated to 1.3. Release Build 11561. 6 months ago
sigonasr2 fb66fcb04d [demo] Make the 3 class variations unselectable in demo build. Release Build 11558. 6 months ago
sigonasr2 e89dfd168e [demo] Update demo build to newest engine. Block off world map navigation beyond Camp II for demo build. Fix bug with file trimming ignoring escape backslash characters in hash causing invalid file hashes. Disable admin mode for demo release build. Release Build 11555. 6 months ago
sigonasr2 6788e3c523 Fix Charge Arrow's beam graphic to match its radius better. Release Build 11546. 6 months ago
sigonasr2 bc795ecb2b Remove songs from git index into commercial assets. Update distribution and build scripts to not include songs commercial folder. Fix early destruction of audio buffer data causing a crash. Release Build 11544. 6 months ago
sigonasr2 ce1caabc90 Merge pull request 'audio-buffer-through-resource-pack' (#65) from audio-buffer-through-resource-pack into master 6 months ago
sigonasr2 3682d9cf68 Add in raw audio buffer loading support from PGE resource packs in the MiniAudio PGEX. Ready for merge. Release Build 11541. 6 months ago
sigonasr2 9b69a4ebb3 ogg vorbis resource pack encoding/decoding implementation started (currently crashes during playback) 6 months ago
sigonasr2 3afeb7fa91 Only allow hovered over scrollable window components to be scrolled by the mouse wheel. Fix bug with display colors for accessory enchantment display names. Release Build 11508. 6 months ago
sigonasr2 a6876f2523 Implement random weighted roll enchant functions. Added unit tests for them. Release Build 11497. 6 months ago
sigonasr2 773c011eb9 Shift elements in refinement and enchanting windows around slightly. Add in framework for Accessory Enchanting window. Add in missing connection points for the boss stages of Chapter 3. Release Build 11493. 6 months ago
sigonasr2 564190d2c6 Finished implementing Accessory Refinement window. Fixed bug with accessory refining function not updating stats for accessories already equipped. Added appropriate unit test. 212/212 unit tests passing. Release Build 11482. 6 months ago
sigonasr2 01ec5f5390 Fixed menu labels not re-updating when a blank item was assigned. Made onMouseOut callback occur when the child selected item of a RowInventoryScrollableWindowComponent was cleared. Added refinement cost displays to the Refining interface. Ready for refining action. Release Build 11461. 6 months ago
sigonasr2 8303665828 Began implementing Artificer Refinement window. Release Build 11440. 6 months ago
sigonasr2 50820d34a5 Placeholder Story 2-1. Remove unncessary confirmation menus. Clear child item when reopening the Disassemble or Refine menus. Release Build 11417. 6 months ago
sigonasr2 da65f1525b Implemented the Artificer Accessory Disassembling menu. Added support for children of RowInventoryScrollingWindowComponents to be selected. Added a MenuDecal menu component. Release Build 11414. 6 months ago
sigonasr2 ac934eead5 Add in basic setup for Artificer Disassembly menu. Release Build 11382. 6 months ago
sigonasr2 ef5239657d Add tutorial task for Artificer introduction in camp. Release Build 11362. 6 months ago
sigonasr2 517f81c03f Handle multiple connection points with the same map names all being handled correctly when dealing with visit notifications. Release Build 11361. Prepare framework for Artificer unlocks. 6 months ago
sigonasr2 e0d58aef04 Add in Comet Flare and remove restriction on Summon Comet and Solar Flare equip combination denying. Release Build 11357. 6 months ago
sigonasr2 050821b1d2 Expand Wizard's Meteor ability configuration parameters. Implemented Summon Comet and Solar Flare enchants. Added incompatibility checks for these two enchants as equipment. Add in unit tests to check for validity of equipping accessories. Release Build 11350. 6 months ago
sigonasr2 fd1a7a6597 Cleanup and fixes to buff tests. 6 months ago
sigonasr2 bcaed9aed5 Updated all BGM tracks with newest versions. Release Build 11330. 6 months ago
sigonasr2 4f18cd41fb Fix sound effect bug when lightning bolt hits a player (doesn't currently happen in-game). Implement Chain Lightning. Release Build 11328. 6 months ago
sigonasr2 6fbb8a8ed1 Implemented Trail of Fire enchant, added Trail of Fire effect. Fix effects not having their type set with the new Effect constructor arrangement. Adjusted Trail of Fire damage potency from 10% to 30% per tick. Release Build 11309. 6 months ago
sigonasr2 901f2e38bc Prepare Trail of Fire graphics and structures. Update lerp util to be generalized and introduce generalized Oscillator class. Release Build 11271. 6 months ago
sigonasr2 5b7c25df46 Add in map_range util function. Fix up FadeInOutEffect to still behave with its old behavior for Poison Pool while enabling new oscillating behaviors for size/color. Add in unit test for map_range. Implement Black Hole Enchant. Release Build 11250. 6 months ago
sigonasr2 94b324e7a2 Restructure Effect class. Add in black hole and portal graphics. Added custom code analysis ruleset. Implemented Blink Portal enchant. Release Build 11230. 6 months ago
sigonasr2 57b59f943c Fix wrapped text on MenuLabel components bleeding out over the edges. Release build 11202. 6 months ago
sigonasr2 85124b8875 Prevent save file corruption while hashing by putting the hashing file in a temporary file instead... Release Build 11201. 6 months ago
sigonasr2 679dcdf490 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 6 months ago
sigonasr2 0288ccbd0c Fix bug with multi-hit bullets not adding hit targets to the hit list before monster hit / player hit bullet callbacks. Implemented Piercing Bolt enchant. 199/199 unit tests passing. Release Build 11200. 6 months ago
Quapsel dd6d86dd66 3_7, 3_8 and chapter 3 Boss Stage added. 6 months ago
sigonasr2 6d4de7940b Implemented Curse of Doom. Release Build 11188. 7 months ago
sigonasr2 a81dd94182 Implement Expunge enchant. Release Build 11183. 7 months ago
sigonasr2 88dea6fa84 Implement Poison Bounce enchant. Fix Poison Bounce physics when dealing with lower frame rates. Release Build 11180. 7 months ago
sigonasr2 e8ead7e07b Implemented Pooling Poison enchant. Fix True Damage and Dot flags ignoring dead monsters and still applying damage. 191/191 Unit Tests passing. Release Build 11173. 7 months ago
sigonasr2 b36e5b449d Refactor testing suite to reduce boilerplate. 7 months ago
sigonasr2 b0ee888b83 Implemented Spreading Pain enchant. Made applied buffs not immediately start ticking down on the same frame as they are applied since that causes some strange interactions with chained deaths. Release Build 11152. 7 months ago
sigonasr2 dd38b8d58e [demo] Add in safety checks for loading and saving files. Run saves/loads repeatedly until they actually complete. Demo Patch 5. Release Build 9546. 7 months ago
sigonasr2 a90c7d3c5d Fix Firebolt and Lightning bolt crashing the game. Implemented cat animation override for Nine Lives enchant. Implemented Nine Lives Enchant. Release Build 11130. 7 months ago
sigonasr2 f87aad6558 Merge new Bouncing Orb changes. (50% damage reduction applied.) 7 months ago
sigonasr2 a9078622f2 Fix bug with multi-hit bullets not allowing their collision checks to be disabled mid-sequence. Add capability for derived bullet types to re-activate themselves. Bouncing Orb Enchant implemented. 181/181 unit tests passing. Release Build 11106. 7 months ago
sigonasr2 976cc7a0b7 Fix bug with multi-hit bullets not allowing their collision checks to be disabled mid-sequence. Add capability for derived bullet types to re-activate themselves. 181/181 unit tests passing. Release Build 11106. 7 months ago
sigonasr2 220bf9d655 Sonic Upgrade Enchant implemented. 7 months ago
sigonasr2 beb8e38b36 Fix bug with reapplying Advance Shield not properly re-enabling the shield drop timer causing the shield to last permanently on the player character. Add test case to cover this edge case. Release Build 11094. 7 months ago
sigonasr2 182e492991 Added Training Dummies to camp. Release Build 11085. 7 months ago
sigonasr2 719ed53923 Change MapType to use an enum instead. Add a HUD message overlay system. Disable ability for players to swap equipment during a stage. Update all maps to use MapType enums. Release Build 11084. 7 months ago
sigonasr2 9da3e946e3 Implement Slam Shock enchant. Add stun effect to monsters. Release Build 11079. 7 months ago
sigonasr2 2129f3a4ff Implemented Battle Shout enchant. 7 months ago
sigonasr2 ad02c7b59f Fix Health and Mana display meters. Add shield damage number type. Fix shield display timer expiring the shield value. Fix unit tests relying on bad static ability state. Fix default attack range from being divided by 100. Force ChangePlayerClass to be called even if class is already set to same class due to additional side effects. Release Build 11074. 7 months ago
sigonasr2 d224740fd4 Implemented Shield mechanic into the game. Fix Special Mark Enchant test and Wizard's Soul Enchant test. Release Build 11062. 7 months ago
sigonasr2 f866a4598d Implement Heavy Guard enchant. 7 months ago
sigonasr2 ea752c14c6 Implemented Improved Ground Slam Enchant. 7 months ago
sigonasr2 97453baf61 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 7 months ago
sigonasr2 eadbdbe0e2 Implemented SwordEnchantment tests. Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 7 months ago
sigonasr2 f293b2ddce Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 7 months ago
sigonasr2 60175a20d2 Implemented Concussive Trap enchant. 7 months ago
Quapsel eece43fd7f 3_5 added. 7 months ago
Quapsel f9ffd044a5 3_6 & 3_B1 added. 7 months ago
Quapsel 95ccfe6b50 added Chapter 3 Bonus Boss Stage. 7 months ago
sigonasr2 9005771f77 Fix up Opportunity Shot test to check with actual accessory equipped. Move camera initialization to a function. Camera starts at actual target location immediately (instead of lazy follow). Fix bug with lazy follow camera becoming ridiculous at low frame rates (>0.25s updates). MonsterData now provides a default collisionRadius in the constructor. 159/159 Tests Passing. Release Build 11039. 7 months ago
sigonasr2 45e6027c9e Move the player outline repeating timer into a new OnLevelStart() callback for the player. Fixes a bug where the player outline will no longer update after the initial stage. Added more player timer helper functions for better control. Release Build 11031. 7 months ago
sigonasr2 f717abfa54 Lingering Scent enchant implemented. 7 months ago
sigonasr2 33ce153bc8 Implemented Trap Collector Enchant. 7 months ago
sigonasr2 6002f69dfa Long-Lasting Mark Enchant Implemented. 153/153 Tests Passing. Release Build 11020. 7 months ago
sigonasr2 58298839bf Implemented Enfeebled Target enchant. Tests are corrected to use the new DamageNumber methods. Release Build 11017. 7 months ago
sigonasr2 6d7240c765 Change starting location of DOT damage numbers. Fix velocity getting multiplied dramatically when damage numbers stack up and change size. Make gcc happy with unsigned long long cast syntax. Remove pthread flag from CMakeLists. Release Build 11014. 7 months ago
sigonasr2 7ae7d3dcb3 Fix DOT buffs not counting down if their timers are intentionally reset. Tick timers are now independent of their duration timers. Make ONE_OFF effect more clear with extra comments. Fix Damage reduction test, which is supposed to report a 75% max damage reduction, even if a piece of equipment has 100% damage reduction. Separated the main buff type and the restoration type variables for DOT buffs so we can still identify them if we need to. Added three new constructors. Added helper AddBuff functions to Monster and Player accordingly with the new changes. Fix SpawnMonster not returning the correct reference to an object (was returning the reference to the termporary storage container). Refactored Buff Blend colors with some lambda functions. Implemented Burning Arrow enchant. Release Build 11008. 7 months ago
sigonasr2 9df7016f2d Implemented Evasive Movement enchant. Added a player outline decal on a repeating timer. Release Build 10989. 7 months ago
sigonasr2 012f5de2a1 Implement Bloodlust enchant. Fix crash that occurs if the player class is not Warrior or Thief. Release Build 10981. 7 months ago
sigonasr2 c017c353db Normalized player's auto attack variable to now represent its range in units. Privatized it and added a GetAttackRange() function to use instead. Implemented Adrenaline Stim enchant. Release Build 10977. 7 months ago
sigonasr2 e8ffeef5c9 Add timer reset function for the player. Fix black box player afterimage being visible. Release Build 10971. 7 months ago
sigonasr2 d28b308ecb Fix Reaper of Souls enchant test. Release Build 10969. 7 months ago
sigonasr2 2e95355fb9 Added a timer class. Added timers and helper functions to the Player class. Implemented Deadly Mirage enchant. Release Build 10966. 7 months ago
sigonasr2 ab98adf094 Triple Toss enchant implemented. Fix obscure edge-case bug where the camera position, mouse position, and player positions are all equal causing a mouse aiming location length of (0,0) resulting in infinity size velocities. Release Build 10952. 7 months ago
sigonasr2 2d3dbfac3c Tumble enchant implemented. Release Build 10947. 7 months ago
sigonasr2 6cbcbdb6b4 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 7 months ago
sigonasr2 9ba98a032e Backstabber enchant implemented. Fix audio transition fades suddenly not working. Release Build 10944. 7 months ago
Quapsel 7ea1027483 Added 3_2, 3_3, 3_4. 7 months ago
sigonasr2 a6b4caad02 Correct a bug in olcPGEX_MiniAudio: GetCursorMilliseconds() was not returning properly. Added loop repeating start point capabilities to the audio engine and bgm config file. Release Build 10936. 7 months ago
sigonasr2 2667cc526e Add [[unlikely]] to DEBUG_ACCESS_OPTIONS to help the compiler. Add a distance check so monsters extremely far away on the map no longer run their AI routines. Add keyboard stage plate connections for the in between zones on the world map in case the player ends up there and uses the keyboard controls. Release Build 10918. 7 months ago
sigonasr2 bc31ba5862 Implement the DEBUG_ACCESS_OPTIONS when attempting to read from config (for debugging purposes). Release Build 10915. 7 months ago
sigonasr2 68744adf54 Add stage plates for Chapter 3 and setup 3-1 in levels. Release Build 10911. 7 months ago
Quapsel d5b8a0a697 added Player spawn zones for 3_1. Changed map size from unlimited to limited. 7 months ago
Quapsel 107045ab4e fixed some errors in chapter 3 presets. added 3_1. 7 months ago
Quapsel 9c8fc148f6 added End of Map Wall behind every Stone Blockade in Chapter 2. Fixed missing wall at the end of 2_3. 7 months ago
sigonasr2 7bf1c90e77 Enable threads support on emscripten build. 7 months ago
sigonasr2 e43798fa9a Add cooldown charge system. Abilities can now only be used when they are holding charges. One charge gets restored for each full cooldown duration. The cooldown timer stops going down when maximum charges are reached. Fix item tests to use cooldown charges. Add in item checks for CDR test (Items should not be affected by CDR). Add GetPlayerAbilities function to collect and manipulate all player abilities at the same time (cleaner code structure + easier test functionality). Add helper functions util::vformat and util::wformat and to_wstring to make code using the std:: versions much more concise. Add unit test for cooldown charges system. Add helper functions to reset abilities to their original stats before enchant modifications tweak them. Added a helper function to retrieve which ability an enchant affects. Adapt ability HUD with new charge count features. Add Mountain theme to Chapter 2 maps. Multi-Multishot Enchant implemented. Release Build 10901. 7 months ago
sigonasr2 214759c606 Map UVs to original cooldown circle indicator instead. Create a DrawPieArc function to render pies with a UV mapped texture from the center outwards. Remove donut circle indicator generating code. Reduce max vertex count of engine back from 256 -> 128 to maintain low memory footprint. Release Build 10846. 7 months ago
sigonasr2 97a9025830 Donut circle cooldown indicator test code. 7 months ago
sigonasr2 7fa02b0f7a Implement Mega Charged Shot. Release Build 10827. 7 months ago
sigonasr2 10b849e906 Implement Charge Beam. Make beam sprite effect distance twice as large so segments have smaller gaps. Release Build 10824. 7 months ago
sigonasr2 ab963293d1 Add niconiconii user profile to solution include directories. Implement Extreme Rapid Fire enchant. Release Build 10819. 7 months ago
sigonasr2 332d21b846 Merge git amend. 7 months ago
sigonasr2 3d1d14ea2d Implemented Poisonous Arrow enchant. Include unit tests. Release Build 10813. 7 months ago
sigonasr2 150a72db73 Implemented Poisonous Arrow enchant. Release Build 10810. 7 months ago
sigonasr2 8ac625660d Implemented Stealthy Retreat enchant. Release Build 10803. 7 months ago
sigonasr2 78956e986c Added helper functions for auto attack timer. Added unit test for auto attack timer functions. Implemented Quickdraw enchant. 132/132 tests passing. Release Build 10800. 7 months ago
sigonasr2 859ff52ac2 Last Reserve enchant implemented. Release Build 10798. 7 months ago
sigonasr2 333acfe149 Money displays in inventory windows use smaller shadow text outlines. Release Build 10793. 7 months ago
sigonasr2 d49e7ff6bb Change shadow text rendering system to use a pre-generated shadow font sprite to improve text rendering speeds dramatically. Fix bug from commit cfd73ab036 where custom font colored shadow text kept creating new versionf of itself causing memory leaks. Release Build 10791. 7 months ago
sigonasr2 00e22877c4 Integrate display color provided in Item Enchant configuration to show up for enchanted items displayed in menus and their descriptions. Release Build 10737. 7 months ago
sigonasr2 0e3d57be24 Move ItemEnchant's attributes into separate private variable allowing us to add a Setter and Getter for the attributes to call UpdateDescription function whenever an attribute is modified. Fix rounding display bug (use round instead of ceil) when comparing improvements/downgrade attributes in the character equip menu. (This handles values like 15.000001 and 14.99999 from screwing up the differential displays). Release Build 10729. 7 months ago
sigonasr2 d28f27f59d Added newline character parsing to olcDataFile config parser. Fix bug with enchants not being able to choose the actual highest value roll on stat enchants. Added stat descriptions to Magical Protection and Aura of the Beast. Release Build 10720. 7 months ago
sigonasr2 5dc46664eb Add public Getters for loadout item functions. Implement Wizard's Soul Enchant. Release Build 10708. 7 months ago
sigonasr2 4ca3cf4426 Fixed dumb forced const_cast on GetPos() for player (converted to const ref return type). Added test for Reaper of Souls to validate collection of souls and healing/cooldown effects work. Release Build 10703. 7 months ago
sigonasr2 300e9834c7 Add UpperZone and LowerZone TMXParser's Map Constructor. This way we don't have to specify the zones ourselves creating invalid maps during testing. Added Reaper of Souls enchant unit test. Add unit tests for equipping two rings of the same enchant. Unique and class enchants behavior modified to not double up. 128/128 unit tests passing. Release Build 10694. 7 months ago
sigonasr2 9a65b731e9 Implement Reaper of Souls Enchant. Fix text rendering bug with really small pixel values getting clamped by integer scaling causing the cached text to be cut off. Release Build 10683. 7 months ago
sigonasr2 cfd73ab036 Implemented Death Defiance enchant. Correct shadow render strings being square (not using proper Y sizes to create cached text). Move game configuration initialization out to global scope to allow config variables to be used when initializing the AiL class. Release Build 10670. 7 months ago
sigonasr2 62086cbd92 Implemented Emergency Recovery unique enchant. Added unit test. 126/126 unit tests passing. Release Build 10653. 7 months ago
sigonasr2 94bd702ee8 Implemented Second Wind unique enchant. Added unit test. Release Build 10646. 7 months ago
sigonasr2 f84787971d Lethal Tempo unique enchant implemented. Added helper functions for retrieving, writing, and reading buffs easily. Added unit tests for new enchant. 124/124 tests passing. Release Build 10635. 7 months ago
sigonasr2 cd986d16fa Variable enchant attributes show up in a different color in their descriptions. Release Build 10608. 7 months ago
sigonasr2 d208772107 Add Strip color unit tests. Remove unused displayCol member from the Item class. 123/123 unit tests passing. Release Build 10607. 7 months ago
sigonasr2 23673ec2af Added unit tests for new strip leading color and final render color engine rendering functions. Fixed rendering bug with text elements of different HTML color codes but with same content not having rendering with the correct color code. Fix potential memory leak errors occurring with rendering Font versions of shadow strings that would have different pulsating colors w/same text content. Added an admin console and added the ability to give the player items and accessories with enchants. Also added a help command. Change version number to appear red while in admin mode (as a visual). Unlock all button tied to admin mode. Release Build 10604. 7 months ago
sigonasr2 f6731463da Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 7 months ago
sigonasr2 2f244e21ff Added stat-based item enchant checks. Remove friend class dependencies from unit tests and added appropriate publicly exposed functions. Release Build 10538. 7 months ago
Quapsel 3038bf8652 Chapter 3 Presets done (for now). 7 months ago
sigonasr2 d4d325503e Move character level and XP bar in the Character menu to a location that is visible even when hovering over equipment (the default when playing with controller/keyboard). Release Build 10532. 7 months ago
sigonasr2 ee02900a3c Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 7 months ago
sigonasr2 c3c8be14f8 Include unequipping checks for enchant unit test. Removing items should update the player's active enchant list. 7 months ago
sigonasr2 8efb5a9f8f Add missing #define CALLSTYLE and missing #include <cassert> to PGE Header file. Fast-forward to Moro's updated PGE cmake template which removes SDL2 dependency for Apple devices. Currently still not compiling. 7 months ago
sigonasr2 77d77f3030 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
sigonasr2 68e7446053 Add Enchant API and EnchantItem() function that applies a random Enchant onto an item. Added HasEnchant() check to see if an equip has an enchant for future integration of enchants in the game. Fix money listeners structure not being reset between unit tests. Add unit tests to ensure items receive correct valid enchants and we can properly detect the player has enchants. Release Build 10527. 8 months ago
Quapsel 913b4ddef9 More Chapter 3 Presets. 8 months ago
Quapsel 1c36e1a0cd more Chapter 3 Presets. 8 months ago
sigonasr2 a295a8e309 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
sigonasr2 227c082a57 Add a clickable region to world map between chapter 1 and chapter 2 locations for mouse navigation. Release Build 10512. 8 months ago
Quapsel e364b5f4c9 more Chapter 3 Presets. 8 months ago
sigonasr2 1ca018aa5f Test Pack key push 2. 8 months ago
sigonasr2 4d24edb31b Test pack key push. 8 months ago
sigonasr2 7c763198b3 Add new pack key default source file. 8 months ago
sigonasr2 bdd491db93 Prepare full purging of packkey (one commit was protected) 8 months ago
Quapsel 2ba0dd4409 more Chapter 3 presets. 8 months ago
sigonasr2 1319a91d11 Switch to built-in uppercase hexadecimal format display when encoding URIs sending save files (emscripten version). Implement variable find and replace features when loading in Item Enchants. Grammar changes to item enchants config file. Convert numerical values to variable config values in item enchants config file. Release Build 10511. 8 months ago
sigonasr2 23fc505b12 Fix typos and minor descriptions in item enchants file. Enchantment data structures and configuration reading implemented. Release Build 10498. 8 months ago
sigonasr2 db81ac4fc8 Added sample Item Enchant configuration file. 8 months ago
sigonasr2 fcfbf6985e Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
sigonasr2 ee4d250370 Add helper function for removing an item from the player's inventory via item name. Add accessory refining check and refine functions. Release Build 10487. 8 months ago
Quapsel 5e511c4b48 More Chapter 3 Presets. 8 months ago
Quapsel 2f1199efa1 more Chapter 3 Presets. 8 months ago
sigonasr2 21dee760dd Fix scrollbars not changing color when the mouse is hovering over them. Release Build 10482. 8 months ago
sigonasr2 30b8760fd6 Add unit test to verify non-accessory items cannot be disassembled. 8 months ago
sigonasr2 97040ef051 Add automated script to apply all assets to the unit testing framework each run. Add new runtime warning for RowInventoryScrollableWindowComponent items that have item boxes larger than the actual component. Move testingMode flag for AiL class to be set before game configurations are read. Add branch for reading specific unit test game configuration files. Include unit test-specific images and configs committed to repository. Add Disassemble function to inventory class. Add Disassemble item test. Fix issues with extra stray shared pointers lingering everywhere when adding/removing items and grabbing their references. Make Stage Loot/Monster Loot have brand new shared pointers to items (copy instead of strong reference) so weak pointer references to existing items actually expire and behave as expected. Move Monster Loot and Stage Loot clear calls to the switch to Overworld Map trigger. Release Build 10476. 8 months ago
sigonasr2 9f4c7c7b0f Added operator< for Pixel class to allow sets of pixels to be used. Add fragment item default description. Generate fragment images that randomly sample from the source ring's colors. Add base fragment item icon. Release Build 10443. 8 months ago
sigonasr2 d7b9d2670e Setup Palm trees to be foreground objects. Prepare all Artificer window dialogs. Release Build 10424. 8 months ago
Quapsel f7984af2bc Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
Quapsel d775f7e3fe First Presets for Chapter 3. 8 months ago
sigonasr2 05af68f8d0 Fix crashes when forgetting to validate monster weak pointer references were actually valid (NPC targeting produces null pointer results.) Release Build 10421. 8 months ago
sigonasr2 57c5a2f579 Move Chapter 2 story to the correct story configuration file. Fix bug where closing a dialog didn't set the state to the previous state the game was in, but instead assumed it was Game Run. Which meant the player could attempt to leave the camp and be presented with the level complete window for no reason. Release Build 10415. 8 months ago
sigonasr2 fe42089080 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
sigonasr2 c01b8c9cf7 Extra Artificer notes. 8 months ago
sigonasr2 1230714be2 Artificer initial discussion window menu and setup. Add dialog finish callback function to perform actions once a dialog finishes. 8 months ago
sigonasr2 cf5c63847c Add dialog loading function to call and trigger Dialog game state. Add Artificer Introduction tutorial task. 8 months ago
sigonasr2 fdc02351bb Modify Visual Novel engine to include background alpha controls to allow the Visual Novel and game world drawing to both be rendered at the same time. Add the Dialog game state which represents a Visual Novel and game world to both be running at the same time. 8 months ago
sigonasr2 1747b1e5ef Adjusted audio events for earlier camp unlocks. Removed bass line for earlier versions of the camp song, reintroduce after chapter 2 is completed. Added placeholder Artificer sprite. Added Artificer dialog. Updated world map with a chapter 2 base location for convenience. Connect new connection points between map locations correctly. Adjust connection point unlock conditions and layer unlock conditions to their new chapter 2 levels. Release Build 10399. 8 months ago
sigonasr2 c36f783fc9 Tidied up monster drawing functions with portable lambdas to get rid of some copy-pasted code, making draw manipulation simpler. Implement Curse of Death ability. Release Build 10391. 8 months ago
sigonasr2 19f3483b44 Add sound effects for Witch auto attack, Curse of Pain, Throw Poison, and Curse of Death abilities. Release Build 10390. 8 months ago
sigonasr2 864b491c46 Adjust all spaces to tabs for consistent whitespace formatting on source files. Implement Throw Poison Ability. Added general Color Mod Buff so coloring targets a certain color based on applied buff is simpler. Release Build 10389. 8 months ago
sigonasr2 ede4bffa7a Trapper's Mark Target and Witch's Curse of Pain abilities should not go on cooldown if no target is found. Added purple glow/outline effect to monsters affected by Curse of Pain. Release Build 10363. 8 months ago
sigonasr2 a35ea5b772 Add cat mew sound effect for Witch Leap ability. Refactor Buffs to include a player/monster callback function when a buff expires. This is used specifically for the curse debuff. Clean up buff update code to use lambdas instead of macros. Release Build 10359. 8 months ago
sigonasr2 21579b7bdf Add cat transform sprite. Added Witch Transform ability parameters. Implemented Transform ability. Fix Trapper Mark unit test since lock on target delays were added. Release Build 10354. 8 months ago
sigonasr2 8df671dcab Add in Witch Transform ability setup. Afterimage setup with scanline removal animation effect for Transform ability. Release Build 10347. 8 months ago
sigonasr2 98208fb3e0 Refactored monster list to use shared pointers instead of unique pointers. Converted all raw monster pointers that needed to store monster data to use weak pointers instead in case a monster gets despawned while owning object is still alive. Implemented Witch's auto attack, added turn_towards_direction function for homing ability. Fixed player reflections being drawn without additive blending. Added 30s cooldown to Trapper's Explosive trap ability. Release Build 10345. 8 months ago
sigonasr2 4a7ad23196 Make knockups pause monster strategies. Fix Effect drawing code being invisible if their fadein timer was zero. Add explosion sound effect to Trapper's Explosive Trap ability. Add knockup and 100% temporary slowdown to Trapper's Bear Trap ability. Release Build 10324. 8 months ago
sigonasr2 9eaaf979f3 Fix display bug with Effects that used the original constructor. Refactor on 23 Jul was a big oversight leading to effects not displaying at all (scale set to 0). Release Build 10308. 8 months ago
sigonasr2 fad8f83554 Add in missing Beep sound effect for explosive traps. 8 months ago
NicoNicoNii a3495d6c16 [Compiler Error] Implement delayed lock-on targeting when multiple targets get marks at the same time (for more dramatic effect) 8 months ago
sigonasr2 92cc1c7b37 Setup Purple Energy Ball Attack. 8 months ago
sigonasr2 f5ecc20339 Prepare Witch class animations and spritesheet. 8 months ago
sigonasr2 3850cbaef9 Implemented Trapper's Explosive Trap ability. Release Build 10304. 8 months ago
sigonasr2 8903410848 Add sound effect for Mark Target ability. Release Build 10303. 8 months ago
sigonasr2 47319a99f8 Fix unit tests, item script tests weren't properly updated. Mark Tests were properly killing targets, removing their marks. 105/105 Tests passing. Release Build 10302. 8 months ago
sigonasr2 2420d02f24 Make player dot damage numbers fall instead of rise as well. Remove unused originalRiseSpd damage number member. Refactor buff repeat action system to instead use internal hard-coded restoration functions. Include the target of buffs inside the buff classes themselves so they know what to interact with. Updated Player and Monster AddBuff functions to represent new buff constructor requirements. Implemented Bear Trap ability. Refactored Monster Hit callback for bullets to send the amount of stacks a monster had before getting hit which is used as getting hurt removed a mark stack. Release Build 10300. 8 months ago
sigonasr2 b3c5894be7 Switch controller auto-targeting to use logic from Hurt function. This means the auto targeting tries to ensure it hits some target, but prefers a currently vulnerable target over an invulnerable / unhittable one. Cleanup the code to use std::optional. Add in a helper function to get nearest monster. Apply Mark to nearest selected target when Trapper Mark Target ability is used. Release Build 10275. 8 months ago
sigonasr2 60b45cf6b1 Add visual indicator/graphics for Trapper Mark on targets. Added TriggerMark and ApplyMark helper functions. Release Build 10264. 8 months ago
sigonasr2 540f0c39ae Added TriggerMark convenience function and appropriate unit test for it. 8 months ago
sigonasr2 f16d10a095 Add 0.1s delay on Hidden Dagger's throw for Thief. Allows the player to potentially jump through a target and being able to hit them with it. Fix Stone Elementals facing the incorrect direction while casting stone pillars. Move 2-8 end ring to more sensible area (in front of Chapter 2 boss arena). Reduce Chapter 2 Boss' and Stone Elemental's collision radius to better match the sprite. Add iframe time when hit by a breaking stone pillar's bullet ring to prevent getting obliterated by standing in the center of the bullet ring. Release Build 10261. 8 months ago
sigonasr2 ecfd7941b7 2-8 spawnpoint fix (oops) 8 months ago
sigonasr2 29b16bd7c8 Add iframe time on hit to Stone Elemental bullet ring so the player doesn't get instantly obliterated standing in the center of the bullet ring. Move Stone Elemental's bullet ring center point slightly downwards so it matches better visually. Fix staircases not working when the firstgid of the staircase tilesheet is not 0 in the tilesheet list. Release Build 10258. 8 months ago
sigonasr2 be8b4ab29e Remove all player buffs/debuffs at the start of a stage to prevent buff stacking exploit from using items in your loadout and dying/restarting the stage. Release Build 10256. 8 months ago
sigonasr2 1614022349 Add unit testing for DOT effects, true damage effects, and applying mark tests to both player tests and monster tests. Fixed bugs related to DOTs not triggering proper damage events and not resetting DOT damage number timers over time. 105/105 unit tests passing. Release Build 10254. 8 months ago
sigonasr2 3830ba8840 Adjust collision radius default based on monster's sheet frame size. Use defined collision radius of a monster instead of 12*SizeMult as that is the actual radius for HurtMonsterType() damage calls (fixes large stone hitting pillars a little too wide in the chapter 2 boss fight). Refactored Bullet check systems to include damage flags: DOT, PLAYER_ABILITY, and NONE. Player abilities flags assigned to all auto attack and abilities that players can launch, in preparation for marked target proccing. Mark Target buff detection added, Mark buff added. Reorganized bullet hierarchy, turning the default bullet into an interface and making the normal Bullet class a base child class all Bullets derive from. Added HurtDamageInfo structure, which is passed onto bullets to modify flags before being applied to the Hurt function when bullets hit targets. Changed all storage containers holding Bullet classes to now hold IBullet classes. Release Build 10248. 8 months ago
sigonasr2 c656b935e1 Added a non-foreground dark tile for bridges to place below areas that are above ground shadow tiles. Redesigned how reference tilesets were stored in TilesheetData and TilesetData structures such that they could be std::sort'd without causing reference bugs/glitches. Release Build 10217. 8 months ago
sigonasr2 6172ea3178 Fix layers in front of the bridge layer to check for their collision tiles first prior to using the bridge's collision tiles. Release Build 10205. 8 months ago
sigonasr2 efcc2ea8d9 Fix upper monster list not being cleared each frame. Release Build 10192. 8 months ago
sigonasr2 83afbb2720 Fix Stone Heart's item description, comma was treated as separate value. Make item descriptions be read in as full strings instead of as the first index in a OLC Datafile. Minor grammar edits to item descriptions. Add missing Upper Zone to II-V. Release Build 10191. 8 months ago
sigonasr2 717a66526c Fill in random hole in II-V. Apply Bridge Layer / Class to bridge in II-V to make it functional. Release Build 10190. 8 months ago
sigonasr2 7ae4fbea3a Goblin Bow users stop aiming 1/3rd of a second before firing, to allow the player to dodge and not cause them to suddenly turn. Added configurable parameter for Goblin Bow lock-in time. Release Build 10188. 8 months ago
sigonasr2 ddf761b6b2 Boars should not keep changing facing direction once locked in by scratching the ground. Removed the Upper flag from all Second Chapter end zones in the stage (accidentally copy-pasted). Added a check that crashes the game if these are detected. Release Build 10186. 8 months ago
sigonasr2 fa5031a50b Fix buff items so stat ups for zero intensity values are not applied. Add Move Spd % stat up buff to move speed multiplier calculation function. Boars now lock-on their position when they scratch the ground to prevent surprise turnarounds last second, and make them slightly easier to dodge. Fix bugged pathfinding for Thief Deadly Dash when pointing the cursor at a solid collision tile. Was just absolutely utterly stupidly broken. Fixed of course. Release Build 10183. 8 months ago
sigonasr2 8abb9e64af Fix bullet offsets. Division by two integers instead of a float and an integer causing incorrect rendering positions. Change damage number color to include a background color. Release Build 10176. 8 months ago
sigonasr2 2907fc9865 Add Trapper class new animation sheet. Change XP data size from holding 32 bit integers to 64 bit integers due to higher level EXP numbers overflowing the XP counter. Release Build 10172. 8 months ago
sigonasr2 7f4b949627 Add Adrenaline Rush unit test. 8 months ago
sigonasr2 0e03cb0f67 Add in edge case so movement targeting direction while standing still for Deadly Dash will still work for controllers. Fix bug with Releasing spell key required for precast spells with target indicators, now activate immediately on press. Release Build 10164. 8 months ago
sigonasr2 a763469fa5 Add in adrenaline rush buff. Make attack speed bonuses be applied via modifiers. Release Build 10153. 8 months ago
sigonasr2 0b4b1d6566 Fix Wizards and Rangers being able to override iframe timer when using Teleport and Retreat respectively. Remove last released key state. Seemed redundant when animation facing direction exists. Make player animation changes be reflected in the facing direction variable. Add pathfinding to Thief's Deadly Dash attack so it can't go through barriers. Added new class counterpoints as equippable classes for the prior weapons. Release Build 10146. 8 months ago
sigonasr2 bc27f18178 Add a distinct sound effect when Hidden Dagger hits a target. Deadly Dash's sound effect updated with a harder hitting sound. Add iframes at the end of Deadly Dash's attack. Footstep playing code now moved into its own function. Appropriate footstep sound plays while Roll is performed. Release Build 10116. 8 months ago
NicoNicoNii 00bb4fd22a Deadly Dash directional fix + Additive Blending toggle. 8 months ago
NicoNicoNii 873e25f927 Add deadly dash attack sound effects and basic behavior 8 months ago
sigonasr2 c62f24cdcd Add in Deadly Dash state. Change zoom out effect to zoom in when loading into a map. Dramatically decrease effect, too much motion sickness. Add deadly dash animation state. Release Build 10109. 8 months ago
sigonasr2 1f1d714dd1 Added shine effect to engine for upcoming Deadly Dash effect. Release Build 10097. 8 months ago
sigonasr2 6620dbe1b4 Add in zoom targeting and ease-in functions for the camera system. Release Build 10094. 8 months ago
sigonasr2 0927e57438 Add Thief Roll Ability. Release Build 10093. 8 months ago
sigonasr2 ee50d810bd Add new ability icons for new classes. Release Build 10068. 8 months ago
sigonasr2 87b4c390ec Change std::exception to std::runtime_error for gcc compatibility. 8 months ago
sigonasr2 c48ae07fcf Fix ScrollableWindowComponents buggy behavior when clicking the down arrow button causing the list to scroll all the way to the bottom. Release Build 10067. 8 months ago
sigonasr2 453be13395 Implement Thief Hidden Dagger attack. Add in missing icons for Elixir of the Wind and Recovery Potions. Release Build 10061. 8 months ago
sigonasr2 10f5521ec2 Adjust sword slash effect to accept config values. Add DrawPartialSquishedRotatedDecal to PGE. Performs rotation transform before scale transformation. Add thief animations to animation databases. Setup thief's auto attack ability. Release Build 10053. 8 months ago
sigonasr2 477c3ab086 Update spell descriptions to fit in class info boxes. Extend height of class info boxes. Add spell names to the class info spell boxes. Release Build 10050. 8 months ago
sigonasr2 ad1dafa0b4 Fix wind speed to check for bullet collisions via microsteps like regular bullets do. Fix bug with danger area color for second chapter boss' shockwave attack not matching config color. Add in new spell descriptions and config variables for the new classes. Release Build 10044. 8 months ago
sigonasr2 7086a8807b Add new item icons. Change safe area indicator colors. Make safe area indicator color configurable. Release Build 10039. 8 months ago
sigonasr2 f1b2aa59f9 Add extra reveal chunk tracker unordered map to prevent minimap updates every single frame the player character walked. Release Build 10027. 8 months ago
sigonasr2 0131620b8b Set background music of Chapter 2 stages to the foresty track. 8 months ago
sigonasr2 0a019d0f2c Add in Chapter 2 map spawn zones and end zones. 8 months ago
sigonasr2 0fbd30d682 Fix bug with multi-target bullets fading out upon hitting a single target. Reduce default fade time of bullets from 0.25 seconds to 0.1 seconds. Release Build 10024. 8 months ago
sigonasr2 7bb265e82a Add stone rain attack to second chapter boss. Second Chapter boss AI routine completed. Release Build 10015. 8 months ago
sigonasr2 001d5e1c79 Add ReverseOneShot animation type support to olcUTIL_Animate2D and config files. Stone Tosses into the air for second boss. Release Build 9994. 8 months ago
sigonasr2 08cdf26605 Fix alpha colors not being applied to Effects. Move the Bear slam attack pattern to a separate phase within the second boss' cycle to avoid constantly rerolling and choose the stone toss attack. Make target indicator for the stone toss a different color from pillar casts. Release Build 9979. 8 months ago
sigonasr2 8e3cdf9d14 Fix Stage Plates 2-6,2-7,2-8, and 2-B to point to their respective stages. Move the additional pillar spawning code outside of the regular boss' phase cycle since it's supposed to happen while the boss does other things according to the original spec. When new pillars appear/disappear, recalculate the safe areas dynamically. Release Build 9974. 8 months ago
sigonasr2 69305a2866 Fix missing braces in Weapons.txt. Remove Infinite map flag from Stage 2-B1. Add Stage 2-B1 stage plate. Add placeholder item icons for new items. Remove embedded tileset from Stage 2-B1. Release Build 9959. 8 months ago
sigonasr2 89db31ceea Add Pct-based pillar respawning attack for Stone Golem. Add "Breaking Stone Pillar" versions that will automatically shake and crumble. Release Build 9951. 8 months ago
sigonasr2 717f4551ea Stone pillars now use a separate rectangular collision to determine hiding zones, preventing the player from hiding along the outer edges of the pillar. Release Build 9948. 8 months ago
sigonasr2 b4fa870236 Item Loadout Usage Tests added. 101/101 unit tests passing. Release Build 9947. 8 months ago
sigonasr2 aaea75c6c6 Added Item Tests file. Added a check to make sure item loadout slot is not blank when attempting to use the item loadout slot. 95/95 tests passing. Release Build 9944. 8 months ago
sigonasr2 d008e4fefd Add Player HP Recovery tests. 90/90 tests passing. 8 months ago
sigonasr2 b2ae457feb Finish all player set effect equipment tests. 87/87 tests passing. 8 months ago
sigonasr2 e57f281ac4 Add tests for CDR checking. Move Game State initialize change to Main Menu into the actual initialization function instead of hiding inside the Game State's Initialize function. 78/78 tests passing. 8 months ago
sigonasr2 183b8ef29d Add in illegal stat buff checks and asserts when adding to an unsupported stat buff for both monsters and players. 77/77 tests passing. 8 months ago
sigonasr2 fecc001140 Fix bug where move spd % set bonus effect applied two times in a row. 56/56 tests passing. Release Build 9923. 8 months ago
sigonasr2 ce445c7d41 Monster tests to ensure Health % and Attack % modifiers work properly. 55/55 tests passing. 8 months ago
sigonasr2 65f1254d26 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
sigonasr2 d049676080 Convert monster stats to calculate using stat up bonuses instead of manual calculations. Release Build 9922. 8 months ago
Quapsel 2990578eab „Adventures in Lestoria/assets/config/items/Equipment.txt“ ändern 8 months ago
Quapsel 7e5457a36d „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 8 months ago
Quapsel 5648230084 „Adventures in Lestoria/assets/config/items/Weapons.txt“ ändern 8 months ago
Quapsel 0d0d7b49b1 „Adventures in Lestoria/assets/config/levels.txt“ ändern 8 months ago
Quapsel 2c8cd8e685 „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 8 months ago
Quapsel 9d053f044b „Adventures in Lestoria/assets/config/items/Weapons.txt“ ändern 8 months ago
sigonasr2 0ee780f1af Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
sigonasr2 4c72e2f3b4 Incorporate new class graphics (Sans Thief). Unlock new class buttons in character selection window. Release Build 9911. 8 months ago
Quapsel 2c77f00194 Chapter 2 Bonus stage - Spawn Zones and Monster placement. 9 months ago
Quapsel 086e1e3f9c Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 9 months ago
Quapsel 89b5ece5f1 Chapter 2 Bonus Stage added. 9 months ago
Quapsel c44780016a „Adventures in Lestoria/assets/config/Monsters.txt“ ändern 9 months ago
sigonasr2 77c908d52c Remove infinite flag and setup Map classes on new levels. Fix missing spawn zone settings for some monsters on 2-8. Add new levels to stage configuration list. Release Build 9906. 9 months ago
Quapsel 48484ca640 „Adventures in Lestoria/assets/config/Monsters.txt“ ändern 9 months ago
Quapsel ca1f91b463 Monster placement for 2_7 & 2_8 9 months ago
Quapsel ec8402aeca Monster placement for 2_3, 2_4, 2_5 & 2_6 9 months ago
Quapsel 75370b9299 Spawn Zones for Chapter 2 placed. 9 months ago
Quapsel 4c0010e92a Changes to Stage Plates on Worldmap and Monster Placement for 2_1 & 2_2. 9 months ago
sigonasr2 34515cec76 Add Heal function for monsters. Make player stat functions const return. Add Missing Health bonus stat from buffs. Refactor names of stat functions to be more explicit. Add more stat buff and pct buff checks. Fix up discrepancies with how stat up buffs work for the player. 53/53 passing tests. 9 months ago
sigonasr2 02523d5ebb Added tests for equipment stats, equipment set effects, and accessory random stat checks. 9 months ago
sigonasr2 6410d837c7 Player Equipment set effect tests added. 9 months ago
sigonasr2 5193f382a3 Fix bugs with static containers not being reset between unit test runs. Add in player test checks for seeded damage reduction test. 9 months ago
sigonasr2 0dc68d7f0c Fix unit test crashing when loading from GFX. 9 months ago
sigonasr2 dba8ee124d Add a proximity knockback overload function that handles hurt list target types instead. Make large stone toss knockback only apply to targets actually hit. Release Build 9837. 9 months ago
sigonasr2 c879cafe1e Targets already in the air should not be knocked back. Fix image loading dependencies internally within the engine so they work even during unit testing. Rearrange some item initializing functions to occur after the GFX map is reset so all graphics are loaded properly. Release Build 9835. 9 months ago
sigonasr2 6274dcefb1 Refactor ability use skills to be testable. Add in basic player damage check and ability use unit tests. 9 months ago
sigonasr2 6cb3cb4e6d Remove unnecessary dependencies from Test project. Segregate test types into separate source files. Fix bug with drop data not being reset for monster tests. 9 months ago
sigonasr2 2c7d69d1b5 Added Monster unit tests relating to the damage formula, crit rates, and special survival cases. 9 months ago
sigonasr2 f1fa126d1e Add in Monster Unit tests verifying move speed is adjusted properly. 9 months ago
sigonasr2 b617120867 Implement unit tests for the project. Fix Display Name bug (found in InternalNameCheck unit test). Add Monster Unit Tests. 9 months ago
sigonasr2 ce482d19b5 Accidental copying of triangles in collision checking loop. 9 months ago
sigonasr2 1c9641be0c Add in missing placeholder item images. Finish collision checking for shockwave attack. Release Build 9715. 9 months ago
sigonasr2 5f0f8259e8 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 9 months ago
sigonasr2 9b9ba195f1 Chapter 2 Boss shockwave safe spot location indicators added. 9 months ago
Quapsel d51603f75a „Adventures in Lestoria/assets/config/shops/Chapter 2 Merchants.txt“ ändern 9 months ago
Quapsel f511bc8900 „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 9 months ago
sigonasr2 d27009efa3 Implemented centroid and signed_area functions to the geom2d util. Just a bunch of maths, I don't know why it works. Release Build 9683. 9 months ago
sigonasr2 db515cca4c Fix a bug with stone elemental burrow rise bullet ring color not respecting the configuration value. Add bullet rings to chapter 2 boss pillars when they are broken. Release Build 9678. 9 months ago
sigonasr2 e56b0c1445 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 9 months ago
sigonasr2 053e2ba724 Only adjust DPS boss damage counter for monsters spawned and considered part of the boss fight. Add in ability for attacks to deal true damage. Change hurt function to handle a True Damage Flag. Stone Throw now breaks pillars it hits. Release Build 9675. 9 months ago
sigonasr2 6c4992ab96 More casts to make gcc happy. Change 2nd chapter bonus boss mob counter from using std::reduce to std::accumulate (Apparently gcc is ambiguous about argument order?? wtf) 9 months ago
sigonasr2 2ba0e1b9fe gcc does not like the promotion from float to double in std::max... 9 months ago
sigonasr2 4c93f5133c const in wrong spot for TMXParser::GetOptimizedMap. Release Build 9666. 9 months ago
sigonasr2 753ecf766d Forgot std::endl flush for logging on web build. Add deterministic keycode sum comparisons to keep proper ordering of input groups. Release Build 9665. 9 months ago
sigonasr2 b9d98741ea Checkbox decal now scales with the size of the menu component (Fixes the "Online Mode" checkbox, which was smaller than the rest). Release Build 9660. 9 months ago
sigonasr2 a66edefc19 Fix distance check mismatch with actual hurt range of the large stone toss. Knockback collisions now match the Hurt collisions. Add a universal knockback function. Release Build 9659. 9 months ago
sigonasr2 9f88460c0e Add RepeatingSoundEffect class to allow easy creation and handling of looping sound effects ingame. Add rock breaking, rock toss cast, pillar rise, dig sound effects. Fix typo on Hawk Feather item description key name. Placeholder item icons added. Add sound effects to Stone Elemental's attacks. Release Build 9656. 9 months ago
sigonasr2 3c48951bc4 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 9 months ago
sigonasr2 82faa111bb Finish stone throw phase/animation for 2nd chapter boss. Adjust animation frames slightly. Correct stone positioning of rock before throw. Rock throw no longer continues rerolling every game frame while Bear AI is running. Release Build 9648. 9 months ago
Quapsel 2e10ad85fe „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 9 months ago
Quapsel 86bbd378ca „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 9 months ago
Quapsel b221744850 „Adventures in Lestoria/assets/config/items/Accessories.txt“ ändern 9 months ago
sigonasr2 02b06424a9 Knockbacks are disabled on solid monsters. Add in small knockback effect on stone throw hit. Release Build 9641. 9 months ago
sigonasr2 daad7d82fb Monsters that ignore terrain collision should not be moved by other solid objects. Hide fade timers and the internal UpdateFadeTime function from inherited bullet types. Add large stone graphic. Apply fixed time step to large stone throw. Add in physics for large stone throw attack. Release Build 9630. 9 months ago
sigonasr2 c02d6cdb47 Create a SpellCircle effect to consolidate the two separate effects. Add a type identifier system for Effects. Finish spawn pillar phase of second chapter boss. Fix bug with the boss display info still appearing despite no longer being in a boss stage if the player leaves a boss level before the text has expired. Release Build 9622. 9 months ago
sigonasr2 480039d64c Fix bug with monsters being able to move solid objects. Make second chapter boss ignore terrain collision. Fix bug with bear slam attack causing knockback to the player when it would hit a monster. Release Build 9613. 9 months ago
sigonasr2 bcbe58eebd Revamp Player vs Monster collision code. Add in handling for solid objects causing the player to run against the object instead of bouncing off of it. Make solid monsters (pillars) have transparency like foreground terrain when looking behind them. Add terrain collision boxes for these as well. Release Build 9610. 9 months ago
sigonasr2 b06199efe0 Setup second chapter boss spawn group, template, and map. Fix issues with being able to click stage plates that were hidden behind the menu or off-screen. Fix crash when item drops spawn in a certain position. Release Build 9585. 9 months ago
sigonasr2 13b3e74591 Feather bullet attack speed increased. Clarify why a stage overlay mask is not automatically created if attempting to use one prematurely. Adjusted level tiles, moving bonus boss to its proper tile. Release Build 9578. 9 months ago
sigonasr2 f24cf38f85 Add 0.25s fade time to generic bullets. Add feather bullet type. Add feather spawning to tornado attack for second bonus boss. Addresses Issue #56. Release Build 9576. 9 months ago
sigonasr2 d9b8c2bc77 Remove test code for stage overlay mask implementation. Release Build 9574. 9 months ago
sigonasr2 e4fddc0c6d Merge pull request 'AreaHighlightTest' (#57) from AreaHighlightTest into master 9 months ago
sigonasr2 346f264c9d Add middle() function for polygons in geometry util. Restructure stage mask overlays to be part of the stage mask polygon class. Reproduce sample using new class. Release Build 9572. 9 months ago
sigonasr2 9c17b3b649 Split up implementation of Pixel from the header file so that the entire pixel game engine header isn't required for Pixels. [WIP] Stage Mask polygon and overlay structures setup. 9 months ago
sigonasr2 27d0e16a94 AreaHighlightTest branch created. Added test code for highlighting arbitrary areas via polygons within a map using optimization map rendering. Release Build 9561. 9 months ago
sigonasr2 efcd3f0bf8 Add Chapter 2 boss AI setup. Add Chapter 2 Boss Monster Entry. Add Boss Pillar Monster Entry. Setup Breaking Pillar Monster Strategy. Release Build 9554. 10 months ago
sigonasr2 ba7ccf72b0 Allow clicking to any unlocked locations on the Overworld Map rather than being forced to step through every adjacent stage to reach desired level. Release Build 9551. 10 months ago
sigonasr2 a1e04d38d9 Refactor redundant deactivation variable to now be tied to the fade out time. Make collision checks for bullets with radii of 0 no longer occur. Add mid phase tornado. Release Build 9546. 10 months ago
sigonasr2 6be3e5e83d Fix bug involving lingering tornado attack. Reduce size of boss arena so item drops and the end zone ring spawn within player reachable locations. Made item drop locations respect boss arenas. Added mid phase for 2nd chapter bonus boss. Fix boss indicator appearing when no boss is present. Release Build 9534. 10 months ago
sigonasr2 78d2234ccc Refactor manual typing of HP Ratios with an HP Ratio function for the monster and player classes. Release Build 9524. 10 months ago
sigonasr2 59dcde475a Fix a bug where hp recovery when at full health still applied passive hp recovery effects. Fix bug with volume transitions not respecting the user's set volume controls when adjusting audio events. Add debris spawning for second bonus boss. Release Build 9522. 10 months ago
sigonasr2 e4e1a42fc1 On death, Chapter 2 bonus boss clears the wind and overlay if it was in the middle of that attack. Wind speed gets reset when casting timer ends. Add an extra layer to bullet rendering so there is some consistency with rendering even when bullet Draws are overriden. Fix bug w/opening debug.log file once the AiL class has been initialized. Double the iframe time from getting hit by a tornado to prevent double-hits. Setup wind debris projectiles. Release Build 9515. 10 months ago
sigonasr2 62766ec928 Must have at least 20 messages to be considered a successful run. 10 months ago
sigonasr2 c0e813ca97 Escape the backticks so they aren't interpreted when outputting the runGame.sh file for Linux. 10 months ago
sigonasr2 0b84e69709 Flip argn and args in main function (to properly correspond to the main prototype). 10 months ago
sigonasr2 b46350ac78 Move get_Command_line_args function into WIN32 define macro (to allow building on Linux). 10 months ago
sigonasr2 764fdfb06d Fix redistribution scripts and Linux scripts. Linux scripts now detect if Steam is installed and if the game properly initializes with steam. If it does not, it will restart itself to run in nosteam mode. Added the ability to read command line arguments into the game. Release Build 9441. 10 months ago
sigonasr2 71466f2b97 Implement Rendering fixes (Infinite iterator bugs) from demo fixes. Release Build 9509. 10 months ago
sigonasr2 46ee54d7c5 Fix bug with double rendering. Not using iterator marker to determine what else to draw. Fix bug with maximum health not healing to maximum when health is affected by Health %. Refactored wind speed to be a global value within the game's engine. Included speed reduction properties for wind when warrior blocks. Include projectiles/player being affected by wind and casting to be allowed when pushed by wind. Release Build 9507. 10 months ago
sigonasr2 25a2879929 Add player projectile auto attack flag to identify bullets for wind affecting. Release Build 9497. 10 months ago
sigonasr2 36a7ceb26e Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 6016316503 Add player velocity adjustment function. Add Wind attack functionality for second bonus boss. Add overlay ease-in transparency. Release Build 9494. 10 months ago
Quapsel 9cf84b94cf Missing Chapter 2 stages added. 10 months ago
sigonasr2 831901cc08 Tuned wind streak spawns and settings. Good to go. Release Build 9491. 10 months ago
sigonasr2 e83515e6a0 Setup wind debris random variables. On the Overworld map selection, the currently selected stage now shows the selection cursor when the mouse cursor is not hovering over another stage to better indicate what stage you are looking at. Release Build 9486. 10 months ago
sigonasr2 563471f7e8 Fix accidental removal of knockback reduction for Warriors while blocking. Also add in knockup reduction for Warrior block. Release Build 9484. 10 months ago
sigonasr2 fb6e47fbfa Simplify fade in and fade out bullet code. Remove unnecessary variables. Release Build 9483. 10 months ago
sigonasr2 3c47cb908e Added an overlay control class. Optimize and rearrange rendering order of bullets and effects such that all bullets now appear above monsters. Fix a bug involving some objects that are supposed to appear behind the player end up in front when they are aligned on the same tile row. Preparations for wind attack. Release Build 9482. 10 months ago
sigonasr2 32c0052971 Move fadeouttimer for bullets to be private scope. Account for hits multiple flag for bullets that strike a player and adds them to the hit list. Coincidentally, this need also address Issue #17. Fix a bug with multi-hit bullets not applying additional hit effects to monsters. Tornado Attack Implemented. Release Build 9473. 10 months ago
sigonasr2 7cf8c0b138 Bullet fade in timer effects added. Added Tornado rings for second bonus boss. Release Build 9455. 10 months ago
sigonasr2 b7372ec283 Define tornado bullet type and attack. Release Build 9451. 10 months ago
sigonasr2 e53f7cb4cd Add rotation parameter and hit iframe time to bullets. Iframe timers now compares current iframes to the new duration and keeps only the larger amount. Fix bug allowing monsters with jump attacks to still cause knockback and iframes even when the hit does not land. Fly Across attack implemented for second bonus boss. Release Build 9447. 10 months ago
sigonasr2 bea3d5b6ee Setup Zephy AI phases. Added slight recoil when monsters take damage. Release Build 9434. 10 months ago
sigonasr2 1169630459 Indentation and fix includes for macros in Error.h Release Build 9427. 10 months ago
sigonasr2 e4215d108d Merge with master. 10 months ago
sigonasr2 81776a2c4e Cherry pick health % not being applied bug from commit 94c3125e73 into master branch. Release Build 9424. 10 months ago
sigonasr2 89c920d8e7 Fix bug with Health % item stats/set effects not being applied to the player. Release Build 9424. 10 months ago
sigonasr2 3ed876aa91 Add missing boss indicator to Ursule and fix Ursule's overlay sprite being mirrored to its main sprite. Fix Ursule facing direction not updating correctly before charging. Release Build 9416. 10 months ago
sigonasr2 e4317384af Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 2d2c123c33 Move damage numbers to be rendered on top of the HUD instead of below it. Add in boss indicators that appear while a boss is off-screen. Fix bugs with knockback buffs being applied in the wrong location, making them effectively useless. Fix bugs with player velocity being nan when standing directly on top of a monster spawn. Fix idle animation during stone elemental rock toss cast. Reduce enemy collision hitboxes to more sensible and playable numbers with new collision system. Release Build 9413. 10 months ago
Quapsel 8f346d724d early version for 2_8 added. 10 months ago
sigonasr2 9e9f46f461 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 3e4a1c53ac Make 4-way directional facing directions updating a bit more lenient. Framework for boss arrow indicators when bosses go off-screen. Release Build 9382. 10 months ago
Quapsel 7d6b7cbf4f slightly increased arena size (1 tile in height) for Chapter 2 Bonus Boss Stage. 10 months ago
sigonasr2 4fcc5db4af Fix Major Hawk AI transition not working properly when only one remains. Release Build 9381. 10 months ago
sigonasr2 440cc89c7b Update No XP/Drop Hawk labels in Tiled Editor. Added spawn controllers, setup multi-tiered spawns. Fixed collision radius bugs. Added Chapter 2 Bonus Boss and Major Hawks Tiled Templates. Fix Stone Elemental Casting circle radius. Release Build 9378. 10 months ago
sigonasr2 a39551c356 Change monster data image storing to use an unordered_map instead. Create an optional display name for monsters to use when monster data is being generated to allow for name overrides while retaining unique monster types. Added No XP variant of Hawks. Setup Chapter 2 Bonus Boss spawn. Release Build 9362. 10 months ago
sigonasr2 d6409f4604 Setup framework and stats for Major Hawk and Bonus Chapter 2 Boss. Add in override Hawk strategy behaviors for Major Hawk strategy. Release Build 9353. 10 months ago
sigonasr2 5361525b70 Fix new maps having embedded tilesets (unsupported feature). Add in new overworld stage connections to new maps. Slightly randomize stone elemental attack cycles to vary their attack timings. Release Build 9352. 10 months ago
sigonasr2 551cabf463 Implement Stone Elemental Burrowing attack, fixed Stone Elemental pathfinding towards target location, slightly increased visual indicator for stone pillar cast circle. Fixed sorted rendering for objects that appear before elements with collision. Refactored iteration loops for drawing so they don't cause multiple unnecessary draw iterations. Do not draw monsters that are marked for deletion anymore (prevent sudden pop-in when drawing on the last frame of fading out). Release Build 9350. 10 months ago
sigonasr2 1d5d4d8240 Implement rock toss and stone pillar spawning behaviors for stone elemental. Refactor HurtEnemies functions to instead hurt any target with targeting flags. Fix bug with player to monster collision not respecting new collision radii. Release Build 9318. 10 months ago
sigonasr2 9a9cbe765e Added casting spell circle graphic and AI for Stone Elemental Stone Pillar Attack. Added Stone Elemental configuration parameters. Release Build 9283. 10 months ago
sigonasr2 7ff1c60d94 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 ad04f89526 Change Bullet collision function name to make more sense. Implement collision radius property with a default value when unspecified, and allow setting to custom values individually from the size of the monster. Release Build 9272. 10 months ago
Quapsel 2f2d286fa4 2_4 added. 10 months ago
sigonasr2 dce39df412 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 3e68c9ac9e Update Stone Pillar graphic. Update Stone Pillar Monster data. Include a do nothing strategy. Implement immovable property for monsters. Implement Invulnerable property for monsters. Implement lifetime property for monsters. Include a fadeout timer for when the lifetime expires. Implement capability for monsters to be deleted from the map. Release Build 9271. 10 months ago
Quapsel b996a39905 2_5 early version added 10 months ago
sigonasr2 3c23cc774f Adjust fade effect so monsters are unaffected while on the title screen. Add in Stone Pillar graphic and setup stone pillar monster/sprite data. Release Build 9270. 10 months ago
sigonasr2 8fa1a72fad Setup Stone Elemental AI framework. Fix spritesheet alignment for certain animations. Setup animation data. Added casting animations. 10 months ago
sigonasr2 71f7363c4e Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 1dc1447fbf Add transparency effect for mismatching Z axis for bullets/monsters or if the player has iframes. Release Build 9266. 10 months ago
Quapsel f921e3951c Boss Stage - Chapter 2 - Bonus Boss 10 months ago
sigonasr2 f9ad9c4390 Add in Hawk attack sequence. Add wing flap sound effects. Fix bug with charging towards ground attack not quite reaching ground level. Release Build 9256. 10 months ago
sigonasr2 bd14a21793 Setup Hawk AI. Fix Hawk Animations. Added a property to ignore tile collisions for monsters. Add in monster artist name in credits. Release Build 9252. 10 months ago
sigonasr2 c3f2620d5d Add bomb explosion sound effect. Fix bug with the player getting knocked back whenever a monster's proximity knockback function is called. Release Build 9247. 10 months ago
sigonasr2 d508075bb6 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 e122de36db Make bomb explosion sizes be calculated independent of initial bomb size. Shrink down bomb size to be more reasonable. Bombs knock back players and monsters, regardless of the friendly flag. Only deals damage to opposing targets. Release Build 9237. 10 months ago
Quapsel aa8d6d92ce 2_3 added. 10 months ago
sigonasr2 b8e1006901 Added overrides for playing default animations in move towards and run away scripts. Implemented Bomb Goblin AI. Include convenience methods for proximity knockback functions. Prepped AI functions for the Hawk and Stone Elementals. Release Build 9230. 10 months ago
sigonasr2 a4e24b030d Fix bug with monsters not facing the player on spawn and incorrectly adjusting their directions initially with the previous facing direction not matching their starting facing direction. Fixed Issue #54 . Mounted animations can be specified for the encounter spawn labels so that their combined sprites display properly now. Release Build 9220. 11 months ago
sigonasr2 e67a1ba684 Resolve Issue #53. Flipped directional value so monsters face west when their target is west and east when their target is east. Add error checking to detect when a missing monster strategy value is found in MonsterStrategies.txt to prevent potential future game crashes. Fix Turret monster strategy not utilizing shooting animation, replace with new animation duration code. Release Build 9207. 11 months ago
sigonasr2 45060e9160 Make Goblin Boar Rider sprite a bit more sensible. Mounted monster animations now properly update. Release Build 9200. 11 months ago
sigonasr2 3fafcd39f3 Implement mounted monster behavior and animations that run separately from the main monster itself. Added Goblin Boar Rider AI and Goblin Bow (while on Boar Rider) AI. Added spawn of submonster on death of main mounted monster. Release Build 9199. 11 months ago
sigonasr2 2fed323770 Setup framework for the goblin boar rider. 11 months ago
sigonasr2 17581cfaf4 Change goblin dagger stab to match new sprite positions and modify animation speeds slightly. Release Build 9180. 11 months ago
sigonasr2 ef53d9852a Update knockback logic and formula again. Release Build 9177. 11 months ago
sigonasr2 8f02afb7bb Prevent monster facing directions from changing so rapidly and only allow directional changes when enough directional change has been reached for less choppy facing animations. Release Build 9176. 11 months ago
sigonasr2 6b64d6aaad Adjust all monster tilesets and presets with new sprites. 11 months ago
sigonasr2 264cf998b9 Fix bow goblin reload AI and changing facing direction while aiming at target. Release Build 9174. 11 months ago
sigonasr2 89bc17c0a2 Added arrow simulation for bow goblins. Added perception levels which increase the accuracy of the shooting monster over time. Refactor bullet update code to be inside the bullet class itself. Release Build 9169. 11 months ago
sigonasr2 aaaf10f6fc Fix Ranger's Rapid Fire and Multi Shot abilities not abiding by the new Arrow constructor. Arrows once again have the correct acceleration and can hit monsters. Fix bug with knockback being applied while the player has iframes. Fix bug with Slime King constantly applying knockback to the player due to no iframes. Release Build 9152. 11 months ago
sigonasr2 aa9c818773 Include animation gifs in repo. 11 months ago
sigonasr2 c4a3a6f915 Incorporated multi-directional sprites in-game. Included a method to change the current display animation sprite while retaining elapsed frame time information. Release Build 9146. 11 months ago
sigonasr2 4a76acfee9 olcUTIL_DataFile now properly parses out comments when using GetOrderedKeys(). Fixed monster animations not being read from the configuration file in the correct order. Release Build 9126. 11 months ago
sigonasr2 c4ac37af76 Fix crash when the game attempts to spawn item drops. Release Build 9125. 11 months ago
sigonasr2 b9a382855c Merge Quapsel's updates. 11 months ago
sigonasr2 33d81125df Change the radius of ranger's auto attack to use pixel units instead of tile units. Remove hardcoded player acceleration on shooting a player arrow. Refactor monster animation system to incorporate custom animations as part of the main set of animations and handle future 4-way directional animations easily. Release Build 9115. 11 months ago
Quapsel a9bb2c62eb changes at 2_1 & tilepresets. 11 months ago
sigonasr2 593180c730 Fix player spawn locations being a whole tile off. Change map background scrolling to not be fixed, but to utilize the entire image across the span of a map. Add a foreground background layer to provide 2 potential parallax backgrounds. Release Build 9091. 11 months ago
sigonasr2 96a8ba56c8 Update Dagger Goblin template to use a distinct icon in maps. Change all monster tileset paths from maps/Monsters to maps/ (game requires all tilesets placed in there). Added slash behaviors for dagger-wielding goblins. Release Build 9037. 11 months ago
sigonasr2 b694535812 Add slight knockback effect on goblin dagger stabs. Release Build 9035. 11 months ago
sigonasr2 270d70a387 Goblin (Dagger) dagger stab attack implemented. Release Build 9033. 11 months ago
sigonasr2 c03f87aefd Goblin Dagger AI basic behaviors implemented. Release Build 9027. 11 months ago
sigonasr2 074a53e34f Change trim function on Visual Novel. 11 months ago
sigonasr2 2f4ec4677a Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 11 months ago
sigonasr2 e40f7dccd5 Use smaller images for class character/visual novel sprites. Release Build 9020. 11 months ago
Quapsel 578011d394 2_2 added. 11 months ago
sigonasr2 5ff64da26a Tweak Boar behavior. Fix bug with knockback velocity not being saved when player comes into contact with another monster. Release Build 9019. 11 months ago
sigonasr2 64dc8bcf10 Enforce const-ness across tilesets to ensure copies and writes are explicitly allowed. Release Build 8990. 11 months ago
sigonasr2 e2101d5da0 Fix backpedal logic to use move run towards strategy instead. Attacked by Player trigger added. 11 months ago
sigonasr2 ed0d5e2507 Boar test behaviors and general AI implemented. Release Build 8958. 11 months ago
sigonasr2 c15fc769e1 Add animation utilities for getting current animation frame index and total animation duration. 11 months ago
sigonasr2 6a5dfa8e42 Merge upstream with demo branch. 11 months ago
sigonasr2 54c36d61fa Controller vibrations should all be halted during the death screen and reset (as a failsafe) when reloading a stage. Release Build 8947. 11 months ago
sigonasr2 a28f0706b3 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 11 months ago
sigonasr2 7330697806 First half of Boar AI completed. 11 months ago
Quapsel 4d01e983f1 added exp values for new Monsters. 11 months ago
Quapsel 2da1e111f5 added wall for start of 1_4. 11 months ago
sigonasr2 483fe6b32d Include monster info for Goblin Boar Riders. Add copies of sprite for basic 4-animation tilesheet setup. Add in test spawn zone for 2-1. Release Build 8947. 11 months ago
sigonasr2 05439de383 Add back in 2-1 and missing level entry and world map entry for 2-1 access. Added placeholder sprites and spawn templates for all chapter 2 enemies. 11 months ago
sigonasr2 171cc49d46 Add back in connections for Chapter 2. 11 months ago
sigonasr2 4557138662 Modified TMX Parser to read new map spawn format. Fix missing spawns. Added errors when spawns are missing from a monster. 11 months ago
sigonasr2 1d1c12004e Updated all maps to utilize monster template system. 11 months ago
sigonasr2 373ac8b94b Patch 1.2 11 months ago
sigonasr2 d98d127861 Implement visual novel Wizard player character. Change displayed character image based on class selection. Change image for display on Equip and class info window for the Wizard. Release Build 8925. 11 months ago
sigonasr2 31ccdee2f5 Character art is now handled differently in visual novels to incorporate character art of any width. Release Build 8923. 11 months ago
sigonasr2 7aa455a890 Add in breadcrumb re-exploration for minimaps. Release Build 8921. 11 months ago
sigonasr2 97030dd38f Upstream merge with master branch to update all chapter 1 levels to include boundaries on the stages. 11 months ago
sigonasr2 188f2f28b2 Implement map tile repeating factors and implement animated blocked off map region animation frames. Release Build 8917. 11 months ago
sigonasr2 debe7054b0 Minimap shadow ring implemented. Release Build 8904. 11 months ago
sigonasr2 5b35ca5c52 Remove Infinite Map setting from 1-B1 and 2-1 11 months ago
NicoNicoNii 4c1e4f18e5 Update CMakeLists to use imported libs for Discord and Steam APIs 11 months ago
sigonasr2 ae72a05fe0 Minimap can only be toggled when it's actually visible (e.g. not during boss fights) 11 months ago
sigonasr2 3215414e11 1-5. Fix a missed tile near waterfall area. 11 months ago
sigonasr2 7cbdd0df76 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 11 months ago
sigonasr2 b042e879d1 Downstream merge with demo branch into master branch. 11 months ago
sigonasr2 eb9114406e Change checkmark from being two line decals to being a single decal. (Note: There is a bug with ViewPort PGEX's line clipping.) Fix scrolling navigation for menus where the navigation doesn't reset the actual scrolling. Corrects Issue #37. Added exit ring indicator on minimap. Auto Pause game setting added. Auto Pause behavior changed to only pause on taking damage when the window is not focused. Release Build 8901. 11 months ago
sigonasr2 3c65db545d When the game loses focus, the game will autopause during exploration. Release Build 8879. 11 months ago
sigonasr2 77e2a654f4 Minimap follows camera instead of player. Minimap view setting is now saved in player configuration. Added controller option for minimap toggle input. Release Build 8875. 11 months ago
sigonasr2 1680b37671 Add toggle between small,large, and hidden minimap. Release Build 8868. 11 months ago
Quapsel f899995c49 added Walls at beginning and end of every Map. 11 months ago
sigonasr2 70f8de33d8 Downstream merge with demo branch. 11 months ago
sigonasr2 0b25bf1d1b Implemented minimap HUD overlay and included miniature player marker. Rearranged initialization of player animations in loading process. Release Build 8851. 11 months ago
sigonasr2 a37a7d41ce Minimap chunk revealing and updating is now stored in user save files. Fix minimap map alignment to match the tile size of the map (The camp has a different base tile size). Release Build 8825. 11 months ago
sigonasr2 581128002b Minimap is centered in frame, border is removed. Size is halved and positioned in upper-right corner of the HUD. Not visible during boss fights. Release Build 8819. 11 months ago
sigonasr2 2b2ce710e0 World map should connect to 2-1. Rebuilt executable with latest files. Release Build 8817. 11 months ago
sigonasr2 5494ab26e8 Downstream merge from demo branch. Fixes Steam crash when Steam is not installed. 11 months ago
sigonasr2 3871d9f085 Rebuild master branch build. Release Build 8667. 11 months ago
Quapsel 468bf9e4b6 small Tileset change for map start/end 11 months ago
sigonasr2 e59ace87f3 Implement a ViewPort for the minimap as a full circle. Fix a bug with ViewPort PGEX having hardcoded 4 sides for displaying decals. Release Build 8815. 11 months ago
sigonasr2 6dd032c6f6 Minimap chunk revealing implemented. Release Build 8781. 11 months ago
sigonasr2 205a974a7b Setup chunk data structure and move black minimap outline rendering to the sprite instead of doing it in the decal rendering. Release Build 8767. 11 months ago
sigonasr2 f9ffd026a9 Switch to storing unique pointers for the monsters list instead of objects. Added a source monster for frog tongues to remain attached to so they follow the monster that they originated from. Release Build 8734. 11 months ago
sigonasr2 d85946f034 Fixed out-of-bounds crash for pathfinding spline algorithm. If the game's window is out-of-bounds when setting the position, the game will try to move the window to an appropriate spot to be on-screen, or to a default location if it has trouble finding a spot. Release Build 8723. 11 months ago
sigonasr2 b5d5e547d7 Minimap shadowing. 12 months ago
sigonasr2 a81169cd7f Color mode sampling on tiles to determine minimap colors. Release Build 8703. 12 months ago
sigonasr2 914b55e4c1 Minimap generation implemented. Release Build 8675. 12 months ago
Nic0Nic0Nii 5773b5e95b Upstream Merge branch 'demo' 12 months ago
sigonasr2 b199208c9f Fix major accessory duplication bug. Release Build 8666. 12 months ago
sigonasr2 133dc1a2e0 Fix bug with leaving a stage via the pause menu while not having cleared a stage causing the unlock to still occur. Release Build 8656. 12 months ago
sigonasr2 84c8ceb898 Save file hash failing should log, not error. Release Build 8655. 12 months ago
sigonasr2 62f74bde4e Fixed bosses being able to leave their arenas. (Issue#41) Fix Slime King not actually moving during the Phase 4 retreat jump (division by zero when jump lockon timers were implemented). Release Build 8652. 12 months ago
sigonasr2 564ab7434c Fix a bug where save file hashes with trailing spaces were not read properly, locking players out of the character. Release Build 8647. 12 months ago
sigonasr2 5cc7139025 Address Issue#45. Pressing opposite movement keys now properly stops animation. Movement with analog stick now properly sets last moved direction key as well instead of facing last digital input direction. Velocity is now applied and footsteps factor in total velocity before playing. Fixed a bug where the game could be closed by the player during a file save, causing the hash of the file to potentially become corrupted and unplayable. Fix some tiles at the end of 1-1 to match the underlying color of the surrounding forest. Removed erroneous upwards path from base camp on the world map. Release Build 8630. 12 months ago
Nic0Nic0Nii 533c089485 Fix invalid play.html file reference in emscripten_run.sh script. Update scripts to make sure packkey source file is always assumed unchanged. Fix typos. 12 months ago
Nic0Nic0Nii c5f7cc1887 Issue#43 resolved. Steel weapons removed from blacksmith crafting list. 12 months ago
Nic0Nic0Nii 793815e9ce Move global update items to its own game loop update function. Damage numbers now update on the overworld map menu. 12 months ago
sigonasr2 6b2f9e80e8 Fix inventory and merchant windows displaying compact descriptions instead of non-compact descriptions. Make some more room for item descriptions in shop and inventory menus. Release Build 8619. 12 months ago
sigonasr2 b35d58f9bd Added noise to grass/ground tiles of maps. 12 months ago
sigonasr2 92da000b13 Added vignette effect when taking damage. Release Build 8614. 12 months ago
sigonasr2 962ee84da9 Changed Bandages item description to include casting info. 12 months ago
sigonasr2 b089ea9ed5 Fixed Issue#40. Mmory leak found and corrected, forgot to delete corresponding sprite attached to a decal when cleaning up text caches. Release Build 8610. 12 months ago
sigonasr2 e2abfd5c34 Upstream merge with released demo build. 12 months ago
sigonasr2 21b6334a2a Fix staircase tile settings being incorrect, added small collision lining on standalone staircase tiles. Implement Chapter 2 level setups. Release Build 8601. 12 months ago
Quapsel 0e9d1b391d Details added to Chapter 2 Presets & v1 of 2_1 finished. 12 months ago
Quapsel 2c29912a3a start of 2_1 & some more basic shapes for the chapter 2 presets. 12 months ago
sigonasr2 2a6f121938 gcc needs a float cast for std::min 12 months ago
sigonasr2 f47aa88e2e Auto-aim should lead slightly. Auto-retreat should attempt max distance retreat. Release Build 8598. 12 months ago
sigonasr2 ecde7d1833 Blame Sherman for unlocking areas early. Add more leniency for the far zone on analog sticks to reach max speed quicker. Make auto aim target right on closest instead of max aiming distance. Release Build 8595. 12 months ago
sigonasr2 a9b860c794 Fix static animation bug for controllers when using the DPad for character navigation. Fix bug allowing players to unlock the next areas by hitting the Return to Camp button even if they did not complete the stage. Release Build 8585. 12 months ago
sigonasr2 ab9729a0f2 Remove extraneous commenting. Release Build 8582. 12 months ago
sigonasr2 be15697fc8 Make sure file hash does not include the | character due to emscripten using it to split a save request. 12 months ago
sigonasr2 2a9e698e0a Create a compatible hash algorithm for both windows and linux cross-saving. Release Build 8579. 12 months ago
sigonasr2 fb96599945 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 12 months ago
sigonasr2 36775982a6 Emscripten compatibility fixes. 12 months ago
sigonasr2 ac307c0cfa distribute script needs proper linux syntax to include wildcard items 12 months ago
sigonasr2 cd8944db96 Finally figured out why "closest target" auto aim was not auto aiming at the actual closest target. Was aiming at the "max aiming point reach" of the closest target. Release Build 8578. 12 months ago
sigonasr2 b8790c16b6 Added analog controller precision movement for controllers. Fixed bugs with animation system involving movement velocities not being consistent nor updating correctly for up/down directions. New Steam Controller Configurations. Release Build 8577. 12 months ago
sigonasr2 047819d548 Switch monster kill count tracking to use steam stats progress tracking. Release Build 8551. 12 months ago
sigonasr2 e7c7504e6d Include Weapon Upgrade/Equipment related Achievements. Release Build 8548. 12 months ago
sigonasr2 5742c03c4f Add class-specific level up achievements. Fix bug with recalculating next levelXP when obtaining multiple levels at once upon stage completion. Release Build 8534. 12 months ago
sigonasr2 3840b28468 Implement monster-specific kill achievements. Release Build 8532. 12 months ago
sigonasr2 e3f79e6965 Fix equipment upgrade tests, left them in by accident. Added kill amount achievements. Release Build 8530. 12 months ago
sigonasr2 de52654380 Add achievement icons and setup achievement config file. Add in unlock area achievements. 12 months ago
sigonasr2 a997c5944a Add in some achievement icons and file hashing saving/loading of save files. Release Build 8515. 12 months ago
sigonasr2 dbe79f2fac Popup debugging log scripts and sessions included in dev environment. 12 months ago
sigonasr2 64a52d6910 Prep Time Trial system structure. Release Build 8492. 12 months ago
sigonasr2 8dbe98df48 Merge branch 'master' into demo 12 months ago
sigonasr2 0173b840f3 Upstream merge with main. Demo branch officially up-to-date. 12 months ago
sigonasr2 d17deb64ae Upstream demo branch merge with main. 12 months ago
sigonasr2 d5d54b250e Release script needs to deposit the static libs into the Adventures in Lestoria folder instead. 12 months ago
sigonasr2 c39e43aa31 Stupid Linux. 12 months ago
sigonasr2 820476756c Update linux distribution script. 12 months ago
sigonasr2 6258925932 Add in new base camp music and events. Added 3 unlock tiers for the base camp music progression. Release Build 8476. 12 months ago
sigonasr2 86242f9f87 Add tutorial button to use into the center of the display string instead of the beginning for clarity. Release Build 8472. 12 months ago
sigonasr2 d00fb5aa46 Add a tutorial task that requires the player to equip a crafted piece of gear. Release Build 8467. 12 months ago
sigonasr2 a63f5f9b6c Set VSync once the settings loads, since emscripten builds do not respect the vsync parameter at the very start of launching. 12 months ago
sigonasr2 05abf6c3e9 Added emscripten compatiblity fix for all Steam API changes. Fix bug where walking direction took priority over manual aim when using controller aiming. Release Build 8465. 12 months ago
sigonasr2 68ed4d2b5d Added VSync toggle to Settings windows. Release Build 8464. 12 months ago
sigonasr2 9fb5946369 Warrior auto attack swing now has a frontal sweep angle. Release Build 8457. 12 months ago
sigonasr2 3329d1a0e0 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 12 months ago
sigonasr2 fe52623a17 Warrior sword swing now lingers for the entirety of the animation instead of only applying damage immediately on use. Release Build 8451. 12 months ago
Quapsel b3e4f99754 Small progress on Chapter 2 presets. 12 months ago
sigonasr2 b8339ff9cc Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 12 months ago
sigonasr2 8e595546ab Fix tutorial task input prompts being too large. Release Build 8445. Rebuild with official pack key. 12 months ago
sigonasr2 d0366c21c7 Fix linux build and distribution release processes. 12 months ago
sigonasr2 0f2deb5f99 Open Binding panel when controller configuration is selected. Move steam API initialization to the beginning of the program's execution. Release Build 8442. 12 months ago
sigonasr2 66cb19abc5 Correct issues with not returning early when an invalid item slot is detected in the level complete window. Release Build 8427. 12 months ago
sigonasr2 19ceff257a Fix lightbar settings, add failsafe retry loading for icons and action button handles when the icons fail to load for reasons. Fix sin wave breathing for lightbar on death screen, misplaced division operation. Release Build 8418. 12 months ago
sigonasr2 412a88d294 Release builds of the game should hide the debug console by default and instead log to a debugging file. Release Build 8413. 12 months ago
sigonasr2 df0d7d680f Add controller lightbar response based on ingame events. Release Build 8399. 12 months ago
sigonasr2 07d4dbf1b4 Add in controller rumble for steam API controllers. Release Build 8398. 12 months ago
sigonasr2 dcc101296b Game pauses when a controller is disconnected. Release Build 8395. 12 months ago
sigonasr2 0c80c13449 Enable Keyboard input overlay when using Steam Big Picture. Enable Fullscreen automatically when using Steam Big Picture. Release Build 8394. 12 months ago
sigonasr2 97f6987028 Fixed icon sizes on Blacksmith crafting window and consumable crafting windows being too small. Added attribution for Animated Slime sprite. Release Build 8369. 12 months ago
sigonasr2 1742fa50eb Fix Ranger Charged Shot and Multi Shot animations being overwritten by cast completion state resetting. Release Build 8367. 12 months ago
sigonasr2 d442095fde Add Steam input binding groups to tutorial tasks. Release Build 8357. 12 months ago
sigonasr2 41228265b2 Add Shoulder2 button hack to get the other controller button scroll up/down pair to appear in input helpers. Release Build 8351. 12 months ago
sigonasr2 f18cba0b97 Version number updated to 1.0.0. Handle analog input hints for steam icons. Release Build 8348. 12 months ago
sigonasr2 78227b9c4c Implemented proper steam icon displays for all users of DrawInput (base camp NPCs). Release Build 8339. 12 months ago
sigonasr2 3e35645b09 Added proper icon return based on button style game setting and proper icon translation when a current graphic of that button exists by default. Release Build 8336. 12 months ago
sigonasr2 b9c89e5715 Fix steam icons from being loaded too early if the game starts up quickly. Release Build 8330. 12 months ago
sigonasr2 3194ade0d8 Added icon displays for Steam. Removed the menu/gameplay action set split and condensed everything into one simple configuration. Release Build 8324. 12 months ago
sigonasr2 681e13dd12 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 12 months ago
sigonasr2 3d621fa95b Hot swapping/plugging controllers now working. Refresh action sets of all active controllers. Release Build 8298. 12 months ago
Quapsel 4c85f8bd4c initial shape for some Chapter 2 Tilepresets. 12 months ago
sigonasr2 8f17c3b0fc Implement analog control detection. Invert Y axis for right analog stick because steam wants it to be inverted by default? Release Build 8293. 12 months ago
sigonasr2 40983204eb Retrieve Steam icons from steam platform files. Update official controller configs. Release Build 8290. 12 months ago
sigonasr2 6a7b40dd67 KEY_BACK was not referencing steam input's BACK enum but KEY's BACK enum. Release Build 8283. 12 months ago
sigonasr2 1d1929a1cc Press, Held, and Release events from Steam API controllers properly respond. Fix a bug involving the steam input API not returning handles properly on initialization. Release Build 8281. 12 months ago
sigonasr2 7b3c6adcc5 Include switch pro config because Steam is dumb and auto-switches inputs on it. 12 months ago
sigonasr2 9fa6e7e44f Get input detection from controllers configured through Steam Input. Release Build 8264. 12 months ago
sigonasr2 4520737042 Add in all steam input requests to input lists. Release Build 8238. 12 months ago
sigonasr2 2c799d5ef6 Add in Steam Rich Presence. Fix Display names of stage plates to be consistent with original naming conventions. Release Build 8226. 12 months ago
sigonasr2 954aa1ccf6 Make sure steam_appid.txt exists in working directory so testing will launch the app in the debugger. 12 months ago
sigonasr2 66f91578cf If the game can be run through Steam, the app is restarted through Steam directly. Make sure OnUserDestroy() gets called if the game is killed when OnUserCreate() returns false to free all resources. Release Build 8224. 12 months ago
sigonasr2 ae6bab6176 Add check to ensure steam is running before attempting to use the Steam API. Release Build 8221. 12 months ago
sigonasr2 2a98097336 Add steam API to build. Release Build 8219. 12 months ago
sigonasr2 5ca9690028 Fix crash when immediately exiting a new save file without unlocking anything. Change default conflicting keybind of Item 3 to Select by default. Release Build 8212. 12 months ago
sigonasr2 bf2a2982c9 Fix crash when hovering over a blank item loadout slot in the item loadout menu. Tutorial tasks now are only drawn while a stage is being actively played. Tutorial tasks get cleared upon a stage completion. Release Build 8210. 1 year ago
sigonasr2 3aaa79eda4 Fix right-hand side checkbox labels in Settings menu being too wide, going behind the scrollbar. Release Build 8208. 1 year ago
sigonasr2 e6043416d1 Show item loadout inputs on the item loadout menu. Fix bug causing item loadout menu item descriptions/names to not update when hovering over the item icons. Release Version 8207. 1 year ago
sigonasr2 bd4bb4d822 Fix outer boundary tile transitions in Intro setpieces. 1 year ago
sigonasr2 6ee3518c58 All Warrior abilities now cancel casts. Added in a small velocity threshold where the player can regain control via movement after getting bumped around. Release Version 8193. 1 year ago
sigonasr2 7608e9dfe2 Fix crash when a removed map unlock condition is loaded from a save file and saving to a save file. Move world rendering into each individual state draw method to prevent dividing by zero for invalid maps being loaded in. Loaded up new Overworld song. Added extra flower indicator under the bridge of 1-1. Fix 1-5 tree tiles. Make current map name / level name functinos return the proper story level name if we are in a story level to avoid programmer confusion / provide more flexible functions. Both Blacksmith story and chapter 1 bonus boss remove the visited flag from the camp, notifying the player visually on the world map that they need to revisit it. Move Blacksmith Tutorial trigger from requiring you to visit the camp via the world map to when you enter the camp regardless if you did it from completing a level or doing it from the world map. Release Build 8187. 1 year ago
Quapsel 83eabcae01 grass transition for 1_6 - 1_8 & 1_B1 1 year ago
Quapsel 0d210be144 Grass transition for 1_2 - 1_5 added. 1 year ago
Quapsel 97f2dddf45 changes on End_of_Map Tileset and grass transition at map end for 1_1. 1 year ago
sigonasr2 73268374df Tie PGE font setting to resource pack loading system. Release Build 8168. 1 year ago
sigonasr2 90a20d77af Modified pixel font to incorporate a copyright symbol. Removed copyright unicode character from credits file to avoid out-of-bounds check. Added copyright symbols to credits and splash screen. 1 year ago
sigonasr2 4498cc39eb Add artists to Credits screen. Modify Scrolling behavior of ScrollableWindowComponents so you can click in the scroll region to move the scrollbar, not just the scrollbar itself. Release Build 8155. 1 year ago
sigonasr2 49bd224202 Marketiiiiiiiiing 1 year ago
sigonasr2 d21352a633 Added low health warning visuals and sound effect. Release Build 8145. 1 year ago
sigonasr2 1d70685351 Add in showing of max health/mana display in settings menu. Release Build 8142. 1 year ago
sigonasr2 f5435e12db Change casting SFX volume when the SFX volume slider is changed. Release Build 8134. 1 year ago
sigonasr2 122e3ecffd Move Settings Window items into a Scrollable Window Component. Rearranged/Resized items to accomodate for it. Fix a bug with not specifying a menu navigation group meaning that the menu will always default to the default button instead of the actual component navigation groups defined. Release Build 8131. 1 year ago
sigonasr2 73819416cc Audio casting sound volume did not respect SFX option setting. 1 year ago
sigonasr2 c8b6273b48 Update to PGE v2.25. 1 year ago
sigonasr2 7dc8d19dca Fix text coloring issue for equip items in the blacksmith menu that are not valid for enhancement. Release Build 8113. 1 year ago
sigonasr2 e4e2a0c55e Lock icons implemented in equip menu. When a piece of gear is already equipped, prevent equip sounds/re-equipping of the same exact piece. Release Build 8105. 1 year ago
sigonasr2 67a971658e Merge pull request 'Add in lockin target time property to regular run towards monster strategy. Yellow Slime Jump target speed increased from 70 -> 90. Yellow Slime Jump lock-in target time increased from 0.0s -> 0.2s. Release Build 8087.' (#36) from YellowSlimeJumpChanges into master 1 year ago
sigonasr2 943996fd7d Add in lockin target time property to regular run towards monster strategy. Yellow Slime Jump target speed increased from 70 -> 90. Yellow Slime Jump lock-in target time increased from 0.0s -> 0.2s. Release Build 8087. 1 year ago
sigonasr2 6909515260 Merge pull request 'KingSlimeFix' (#35) from KingSlimeFix into master 1 year ago
sigonasr2 b9a2b77fdc Merge with master. 1 year ago
sigonasr2 931f1db470 Jump targeting lock-on time implemented for Slime King script. 1 year ago
sigonasr2 3616012457 When the application loses focus, all inputs are released properly so inputs aren't stuck on refocus. Release Build 8085. 1 year ago
sigonasr2 a020f42285 Proposed Slime King AI change will cause phase 3 to start on the bullet pattern instead of the jump pattern if the jump pattern from phase 2 was just executed. 1 year ago
sigonasr2 3a93a2ed5e Change lock/unlock key for keyboard users to not conflict with the menu key. Prevent crafting requirements of an item from being displayed when the max stage is reached. Navigating down from the decrease amount button in Sherman's consumable crafting menu should redirect the cursor to the craft button if available instead of the cancel button. Inventory menu properly displays changing color descriptions now. Remove ultra fast hyper switching in the level completion window when pressing down the left or right triggers on a controller. Release Build 8082. 1 year ago
sigonasr2 8917efbc51 Fix having the exact amount of XP not actually leveling you up. Fix strange shadow text clipping for text scaled less than 1.0 vertically. Release Build 8068. 1 year ago
sigonasr2 f588de19d0 Fix crash when hovering over lock/unlock buttons in the accessories menu on the Merchant menu. Fix size of icons in merchant menus. Enable/Disable increase/decrease buttons on shermans's consumable crafting menu as appropriate. Labels in merchant window and inventory window properly update their item descriptions with flashing / changing colors as needed. Text rendering system no longer eats away at memory for text strings that are equivalent but have different HTML color codes. Release Build 8066. 1 year ago
sigonasr2 cfdc2c9a8c Close consumables selection window automatically once a loadout item has been selected. Release Build 8038. 1 year ago
sigonasr2 2364176c79 Added Yellow Slime jump attack behavior. Release Build 8023. 1 year ago
sigonasr2, Sig, Sigo dc6d333e5b Add monster jumping inside run towards monster strategy. 1 year ago
Quapsel 3e7b7ed06e „Adventures in Lestoria/assets/config/Monsters.txt“ ändern 1 year ago
sigonasr2 3d87f1b241 Maxed out item rolls now show up in a different color. Release Build 8012. 1 year ago
sigonasr2 92ae65270e Prevent precast consumable items (Ex. Bandages) from still being activated when the loadout slot quantity reaches 0. Release Build 7998. 1 year ago
sigonasr2 e1088e9696 Warrior slash animation now extends based on actual attack range. Release Build 7997. 1 year ago
sigonasr2 b55dbcb63b Add in hp and atk growth rate displays on level ups. Make increase and decrease buttons disable when the appropriate sell/buy amounts have been reached. Sell inventory count now shows up in the selling window. Add Lock/Unlock input helper display on merchant accessory sell window. Fix missing collision tile in 1-1. Release Build 7992. Patch Version 0.5. 1 year ago
sigonasr2 2a39b9bda0 Returning to camp/map after dying now lets you obtain already collected resources/XP. Fix bug with XP bar display (visual error) in the level complete window. Release Build 7969. 1 year ago
sigonasr2 2a325810af Add in strange gamepad edge case where analog sticks may be falsely spazzing out between 0 and 1. Fix using software checks. Release Build 7967. 1 year ago
sigonasr2 2c81cf15f2 Implemented locking of accessories in the inventory and merchant window to prevent immediate selling of an item. Fixed a bug that prevented custom menu actions to be performed on the menu select button when a button was clicked. Release Build 7966. Patch Version 0.4.5. 1 year ago
sigonasr2 7ce1e37549 Add in support for subcomponents within subcomponents. Prioritize highest depth menu item when hovering over overlapping items. 1 year ago
sigonasr2 d45e0f6f04 Fix mob spawns in 1-1 and 1-7 for mobs that were spawning inside of map hitboxes. 1 year ago
sigonasr2 fba5274815 Warrior Battlecry Attack Range: 350->500, Battlecry Slowdown Duration: 5s -> 6s, Slowdown Amount: 30% -> 40% 1 year ago
sigonasr2 da969118bb Loading a save file with an old connection point location will place the player at the starting conection point. Blue Slime Movespd: 80 -> 70. Release Build 7910. 1 year ago
sigonasr2 0df14cf1e4 Ranger Backdash Iframe time 0.2s -> 0.22s. Ranger Backdash Distance 250 -> 300. 1 year ago
sigonasr2 a128dd5e3b Ranger hairstyle changed. 1 year ago
sigonasr2 091a4a20e1 Make Leave Area button work in the camp. Fix background music volume not being halved when entering/exiting the pause menu. Release Build 7909. 1 year ago
sigonasr2 dfa06b688d Change how blacksmith TutorialTask is handled with the complete condition actually fulfilled instead of a randomly variable (copy-paste error)? Release Version 7906. 1 year ago
sigonasr2 79d80e3e0d Fix menus being active during screen transitions. Release Build 7905. 1 year ago
sigonasr2 9d76814f9a Include new story art. 1 year ago
sigonasr2 d18104a080 Add collision to waterfall tiles. 1 year ago
sigonasr2 4a24b17b70 Added shadow underneath bridge for Stage I-I. Release Build 7902. 1 year ago
sigonasr2, Sig, Sigo d5625c165f Fix some story script and update TODO. Remove extra old testing code. 1 year ago
sigonasr2 73832cfa15 Fix equipment stats not correctly applying after enhancing gear. Release Build 7898. 1 year ago
sigonasr2 a26e13fbb6 Fix XP Gain tick rate (25% bar gain every 0.5 seconds). Release Build 7897. 1 year ago
sigonasr2 6a5e36b8da Fix DPad on controllers causing animations to lock up when pressing two or more directions at the same time. Release Build 7896. 1 year ago
sigonasr2 7cd91e96e1 Remove debug keys for changing class, spawning items, adding xp, and opening the consumables window. Fix XP progress bar so it doesn't loop the sound if the menu is closed early. Pause menu now pauses the game during gameplay. Release Build 7895. 1 year ago
sigonasr2 f7dad50ff5 Implement menu navigation for the new overworld button in the Level Complete window. Release Build 7889. 1 year ago
sigonasr2 2bf1c74460 Fix health display on equipment menu to show max health instead of current health. Fix health values being reduced due to temporary unequips when hovering over items. Release Build 7887. 1 year ago
sigonasr2 20a76315cc Fix player starting with no items if they first load another file and then return to the main menu to begin another character. Fix player stats not being reset proper when loading another file and then returning to the main menu and beginning another character. Fix potential infinite XP bar gain loop. Added Return to Map option on level completion screen. Equip Stat labels now show live stats (changes on application of buffs, etc.). Release Build 7886. Patch version 0.4.4. 1 year ago
sigonasr2 9afff3a8e5 Add slight knockback effect to Warrior's ground slam. Release Build 7885. 1 year ago
sigonasr2 030b16db3f Fix typos in credits input helper. Fix left analog stick not being active for scrolling the credits window. Release Build 7878. 1 year ago
sigonasr2 b1d98cebb2 Initial craft of an item now properly consumes the resources and money of the item. Start at Story I node instead of the Campaign I-I node. Include additional credits. Added additional story background. Release Build 7877. Patch version 0.4.3 1 year ago
sigonasr2 9b21d447da Fix game settings not properly loading for the emscripten version. 1 year ago
sigonasr2 ddddd966aa Credits navigation key actually pops up the credits for controllers. 1 year ago
sigonasr2 af0a628866 Story I-I no longer unlocks Campaign I-II map. 1 year ago
sigonasr2 a4bc56b457 Fix bug with emscripten involving bad references to other map items in InputHelper groupData? This makes zero sense and only occurs in the emscripten build, a clear fixes it. 1 year ago
sigonasr2 d33d5bb4f0 Add in #undef lines for Linux builds. Stupid Linux. 1 year ago
sigonasr2 e971e412e4 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2 d909c140cf Solve crash that occurs when selling items not in your loadout item slots. Release Build 7872. 1 year ago
Quapsel 6f342f7403 Changed Slime King jump Behaviour in Phase 2 and 3 of the Boss fight. Jumps are now way easier to dodge. It should be possible on every class to win this fight without getting hit. 1 year ago
sigonasr2 037e818f83 Updating the bgm volume also updates the environmental audio (important when unmuting sounds). Fix some deleted tiles in 1_7. Release Build 7871. 1 year ago
sigonasr2 a8ec34c12b Remove sold items from equipment slots and loadout slots if they exist there so they no longer linger. Relase Build 7870. Game Version 0.4.2 1 year ago
sigonasr2, Sig, Sigo 75fd63dbcd Restock loadout items from previous setup automatically. 1 year ago
sigonasr2, Sig, Sigo f8639d96f9 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2, Sig, Sigo 12db8ed839 Remove (most) stuttering from web build when loading stage sound. 1 year ago
Quapsel 36b527f489 „Adventures in Lestoria/assets/config/items/Weapons.txt“ ändern 1 year ago
Quapsel e15636a6c8 „Adventures in Lestoria/assets/config/items/Accessories.txt“ ändern 1 year ago
sigonasr2, Sig, Sigo eb069a0012 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2, Sig, Sigo cbf323da89 Story I unlocks from the beginning of the game. 1 year ago
Quapsel 55153c356d „Adventures in Lestoria/assets/config/Monsters.txt“ ändern 1 year ago
sigonasr2 2895defa43 Fix crash due to off-by-one error with randomizing the traveling merchant when reaching chapter 2+. Fix player inventory scrollbars not resizing when items are removed from the inventory because of crafting. Release Build 7857, Test Version 0.4.1 1 year ago
sigonasr2 f40d196637 Internally show censored text, but actual label must return the uncensored text of a TextEntryLabel/MenuLabel. Since emscripten doesn't have multi-threaded audio buffering, we immediately mute on stage load to prevent stuttering / bad noise. Fix HUB being a requirement when it's not a stage that can be unlocked for returning to camp conditions. Release Build 7852. 1 year ago
sigonasr2 6334bf3383 Close leftover open equip window if it was opened when leaving the character equip menu. Fix censored text input being uncensored for emscripten build. Release Build 7850. 1 year ago
sigonasr2 ce3529b976 Fix crash with emscripten version during story scenes. Fix inproper Blacksmith equipment displays for crafting menu (chapter check missing, wrong enhancement level checks). Fix Blacksmith trigger not unlocking on the right node. Fix Chapter 2 trigger. Fix emscripten build scripts. Release Version 7848. 1 year ago
sigonasr2 3145bef2cc Remove Resume input helper on the death menu. Add multi-line centering label support. Add credits configuration and credits menu to the game. Release Build 7841. Push game Version to 0.4. 1 year ago
sigonasr2 a803531a4d Mouse auto aim mode no longer occurs if a mouse button is held down but the mouse hasn't moved for 2 or more seconds. Fix bug with background sound not respecting BGM volume during fade-in / fade-out. Fix non-combat sound effects not playing while on the title screen. Combine unused foresty stems for now. Add in follow-directions for defensive abilities when using controller or keyboard inputs as well as an inversion direction flag. These are desirable for the retreat types since aiming defensives towards the target is not what the player prefers. Limited pathfinding frequency for monsters and wizard pathfinding ability. Fixed bug with shared pointer being passed into two separate inventories, causing double item duplication everytime a stackable item was added to the player's inventory. Release Build 7795. 1 year ago
sigonasr2 7d58ba843c Add saving indicator. Release Build 7761. 1 year ago
sigonasr2 3350fa8cb1 Fix story pause command to not actually get rid of the story dialog. Release Build 7759. 1 year ago
sigonasr2 2bd3e370e5 Added tutorial tooltips. Release Build 7758. 1 year ago
sigonasr2, Sig, Sigo 65587bed88 Adjustments to order of entering a stage 1 year ago
sigonasr2, Sig, Sigo 678f10ebb1 Setup tutorial and tutorial task features. 1 year ago
sigonasr2 37c4aca8bf Add in visual novel audio pitch and BGM change commands. Add in XP bonus when completing stages. Release Build 7706. 1 year ago
sigonasr2 750227a04d Change default controller keybinds for new characters. Player proceeds back to overworld if the camp has not been unlocked yet. Buttons adjusted accordingly. Rumble only occurs on the main menu while in the settings menu and only when toggling the rumble button. Cause iframes to continuously refresh on the main menu so the invisible player doesn't "die" if they view the main menu for too long. Release Build 7700. 1 year ago
sigonasr2 3e9817060e Add mosaic transitions from overworld to stages. Fix up Visual Novel placeholder text for player name. Release Build 7690. 1 year ago
sigonasr2 e205fe9806 Restore item loadout quantities on level restarts. Release Build 7674. 1 year ago
sigonasr2 78bc4585aa Implemented Death menu. Release Build 7668. 1 year ago
sigonasr2 9f1e6b69db Fix missing strategy draw call for monsters. Add in death state and basic transition effects. Release Build 7662. 1 year ago
sigonasr2 8fc5b530b4 Returning to camp no longer rewards you stage items. Changed CloseAllMenus() to actually call CloseMenu() multiple times instead to prevent lockouts while holding down menu buttons and having menus closed on (such as getting hit by enemy attacks while in the pause menu). Release Build 7646. 1 year ago
sigonasr2 60df8f0510 Add disabling of return to camp button when already at the camp or the camp has yet to be unlocked. Release Build 7644. 1 year ago
sigonasr2 305131d7db Redo rendering pipeline so all tile groups are now interweaved with game objects such that objects in front of foreground tiles are now properly displayed after stuff in the background. Release Build 7635. 1 year ago
sigonasr2, Sig, Sigo 09571da405 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2 9018e6418a Include `git-filter-repo` script. Rewrite history to exclude gamepack.pak file. Regenerate locked gamepack.pak file using unpublished key data. Release Build 7609. 1 year ago
sigonasr2 3221c3f2dd Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2 4a8514ffd6 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2 4cc14fb08f Lock down gamepack key so it's not included in repo. Release Build 7608. 1 year ago
sigonasr2 d439d91e5f Lock down gamepack key so it's not included in repo. Release Build 7608. 1 year ago
  1. 30
      Adventures in Lestoria Tests/Adventures in Lestoria Tests.vcxproj
  2. 17
      Adventures in Lestoria Tests/Adventures in Lestoria Tests.vcxproj.filters
  3. 173
      Adventures in Lestoria Tests/BuffTests.cpp
  4. 1262
      Adventures in Lestoria Tests/EnchantTests.cpp
  5. 178
      Adventures in Lestoria Tests/EngineTests.cpp
  6. 113
      Adventures in Lestoria Tests/FileTests.cpp
  7. 95
      Adventures in Lestoria Tests/GameHelper.h
  8. 156
      Adventures in Lestoria Tests/ItemTests.cpp
  9. 119
      Adventures in Lestoria Tests/MonsterTests.cpp
  10. 212
      Adventures in Lestoria Tests/PlayerTests.cpp
  11. 4
      Adventures in Lestoria/Ability.cpp
  12. 5
      Adventures in Lestoria/Ability.h
  13. 58
      Adventures in Lestoria/Adventures in Lestoria.tiled-project
  14. 173
      Adventures in Lestoria/Adventures in Lestoria.vcxproj
  15. 211
      Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
  16. 883
      Adventures in Lestoria/AdventuresInLestoria.cpp
  17. 86
      Adventures in Lestoria/AdventuresInLestoria.h
  18. 51
      Adventures in Lestoria/Animation.cpp
  19. 79
      Adventures in Lestoria/Arc.cpp
  20. 59
      Adventures in Lestoria/Arc.h
  21. 15
      Adventures in Lestoria/Arrow.cpp
  22. 150
      Adventures in Lestoria/ArtificerDisassembleWindow.cpp
  23. 73
      Adventures in Lestoria/ArtificerEnchantConfirmWindow.cpp
  24. 185
      Adventures in Lestoria/ArtificerEnchantWindow.cpp
  25. 75
      Adventures in Lestoria/ArtificerRefineResultWindow.cpp
  26. 164
      Adventures in Lestoria/ArtificerRefineWindow.cpp
  27. 28
      Adventures in Lestoria/ArtificerWindow.cpp
  28. 26
      Adventures in Lestoria/Attributable.h
  29. 3
      Adventures in Lestoria/AttributableStat.h
  30. 47
      Adventures in Lestoria/Audio.cpp
  31. 11
      Adventures in Lestoria/Audio.h
  32. 8
      Adventures in Lestoria/Bear.cpp
  33. 4
      Adventures in Lestoria/BearTrap.cpp
  34. 59
      Adventures in Lestoria/BlackHole.h
  35. 2
      Adventures in Lestoria/BlacksmithCraftingWindow.cpp
  36. 10
      Adventures in Lestoria/Boar.cpp
  37. 2
      Adventures in Lestoria/Bomb.cpp
  38. 4
      Adventures in Lestoria/BreakingPillar.cpp
  39. 75
      Adventures in Lestoria/Buff.cpp
  40. 41
      Adventures in Lestoria/Buff.h
  41. 2
      Adventures in Lestoria/Bullet.cpp
  42. 4
      Adventures in Lestoria/Bullet.h
  43. 178
      Adventures in Lestoria/BulletTypes.h
  44. 104
      Adventures in Lestoria/BurstBullet.cpp
  45. 49
      Adventures in Lestoria/Chapter_3_FinalBoss.txt
  46. 4
      Adventures in Lestoria/CharacterAbilityPreviewComponent.h
  47. 357
      Adventures in Lestoria/CharacterMenuWindow.cpp
  48. 15
      Adventures in Lestoria/ChargedArrow.cpp
  49. 7
      Adventures in Lestoria/ClassSelectionWindow.cpp
  50. 57
      Adventures in Lestoria/CollectedCoinEffect.cpp
  51. 2
      Adventures in Lestoria/ConnectionPoint.cpp
  52. 10
      Adventures in Lestoria/ConsoleCommands.txt
  53. 2
      Adventures in Lestoria/ConsumableCraftingWindow.cpp
  54. 118
      Adventures in Lestoria/Crab.cpp
  55. 2
      Adventures in Lestoria/CraftItemWindow.cpp
  56. 2
      Adventures in Lestoria/Crawler_Artificer.txt
  57. 54
      Adventures in Lestoria/DEFINES.h
  58. 4
      Adventures in Lestoria/DaggerSlash.cpp
  59. 4
      Adventures in Lestoria/DaggerStab.cpp
  60. 81
      Adventures in Lestoria/DamageNumber.cpp
  61. 21
      Adventures in Lestoria/DamageNumber.h
  62. 2
      Adventures in Lestoria/DeadlyDash.cpp
  63. 57
      Adventures in Lestoria/DynamicMenuLabel.h
  64. 59
      Adventures in Lestoria/Effect.cpp
  65. 128
      Adventures in Lestoria/Effect.h
  66. 7
      Adventures in Lestoria/EnergyBolt.cpp
  67. 8
      Adventures in Lestoria/EnhancementStatsLabel.h
  68. 60
      Adventures in Lestoria/Entity.cpp
  69. 56
      Adventures in Lestoria/Entity.h
  70. 3
      Adventures in Lestoria/EnvironmentalAudio.cpp
  71. 2
      Adventures in Lestoria/EquipSlotButton.h
  72. 21
      Adventures in Lestoria/Error.h
  73. 27
      Adventures in Lestoria/ExplosiveTrap.cpp
  74. 65
      Adventures in Lestoria/FadeInOutEffect.cpp
  75. 23
      Adventures in Lestoria/FallingBullet.cpp
  76. 7
      Adventures in Lestoria/FireBolt.cpp
  77. 58
      Adventures in Lestoria/FlipCoinEffect.cpp
  78. 10
      Adventures in Lestoria/ForegroundEffect.cpp
  79. 12
      Adventures in Lestoria/Frog.cpp
  80. 5
      Adventures in Lestoria/FrogTongue.cpp
  81. 298
      Adventures in Lestoria/GhostOfPirateCaptain.cpp
  82. 84
      Adventures in Lestoria/GhostSaber.cpp
  83. 84
      Adventures in Lestoria/GiantCrab.cpp
  84. 179
      Adventures in Lestoria/GiantOctopus.cpp
  85. 9
      Adventures in Lestoria/Goblin_Boar_Rider.cpp
  86. 6
      Adventures in Lestoria/Goblin_Bomb.cpp
  87. 10
      Adventures in Lestoria/Goblin_Bow.cpp
  88. 12
      Adventures in Lestoria/Goblin_Dagger.cpp
  89. 10
      Adventures in Lestoria/Hawk.cpp
  90. 54
      Adventures in Lestoria/HomingBullet.cpp
  91. 130
      Adventures in Lestoria/HubPauseMenu.cpp
  92. 7
      Adventures in Lestoria/HurtDamageInfo.h
  93. 35
      Adventures in Lestoria/IBullet.cpp
  94. 10
      Adventures in Lestoria/IBullet.h
  95. 5
      Adventures in Lestoria/IT.cpp
  96. 2
      Adventures in Lestoria/IT.h
  97. 52
      Adventures in Lestoria/Ink.cpp
  98. 66
      Adventures in Lestoria/InkBullet.cpp
  99. 2
      Adventures in Lestoria/InventoryWindow.cpp
  100. 232
      Adventures in Lestoria/Item.cpp
  101. Some files were not shown because too many files have changed in this diff Show More

@ -47,7 +47,8 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria;$(IncludePath)</IncludePath>
<IncludePath>J:\AdventuresInLestoria\Adventures in Lestoria;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria;$(IncludePath)</IncludePath>
<OutDir>..\x64\Unit Testing</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|Win32'">
<LinkIncremental>true</LinkIncremental>
@ -57,16 +58,17 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>J:\AdventuresInLestoria\Adventures in Lestoria\steam;J:\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\niconiconii\OneDrive\Documents\include;C:\Users\sigon\OneDrive\Documents\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard>stdcpplatest</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>discord_game_sdk.dll.lib;freetype.lib;steam_api64.lib;$(CoreLibraryDependencies);%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PreBuildEvent>
<Command>powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File ../unit-testing-prebuild.ps1</Command>
@ -101,6 +103,22 @@
<ClCompile Include="..\Adventures in Lestoria\discord-files\store_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\user_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\voice_manager.cpp" />
<ClCompile Include="BuffTests.cpp">
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'">$(ProjectDir)..\Adventures in Lestoria\discord-files;$(ProjectDir)..\Adventures in Lestoria\steam;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\niconiconii\OneDrive\Documents\include;C:\Users\sigon\OneDrive\Documents\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<ClCompile Include="EnchantTests.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="EngineTests.cpp">
<SubType>
</SubType>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'">$(ProjectDir)..\Adventures in Lestoria\discord-files;$(ProjectDir)..\Adventures in Lestoria\steam;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\niconiconii\OneDrive\Documents\include;C:\Users\sigon\OneDrive\Documents\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<ClCompile Include="FileTests.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="GeometryTests.cpp" />
<ClCompile Include="ItemTests.cpp">
<SubType>
@ -120,6 +138,12 @@
<Project>{8e3067af-cfe7-4b11-bc6b-b867c32753d7}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClInclude Include="GameHelper.h">
<SubType>
</SubType>
</ClInclude>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>

@ -69,5 +69,22 @@
<ClCompile Include="ItemTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="EnchantTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="EngineTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="BuffTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FileTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="GameHelper.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

@ -0,0 +1,173 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "CppUnitTest.h"
#include "AdventuresInLestoria.h"
#include "config.h"
#include "ItemDrop.h"
#include "Tutorial.h"
#include "DamageNumber.h"
#include "GameHelper.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
INCLUDE_MONSTER_DATA
INCLUDE_game
INCLUDE_GFX
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_MONSTER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
namespace BuffTests
{
TEST_CLASS(BuffTest)
{
public:
std::unique_ptr<AiL>testGame;
#pragma region Setup Functions
void SetupTestMonster(){
InitializeGameConfigurations();
testGame.reset(new AiL(true));
ItemAttribute::Initialize();
ItemInfo::InitializeItems();
testGame->InitializeGraphics();
testGame->InitializeClasses();
sig::Animation::InitializeAnimations();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA["CAMPAIGN_1_1"];
game->_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
#pragma endregion
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
}
void SetupMockMap(){
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
}
#pragma endregion
TEST_METHOD_INITIALIZE(BuffInitialize){
SetupTestMonster();
SetupMockMap();
}
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
TEST_METHOD(AddBuffMonsterCallbackExpireFunctionTest){
Monster&m{game->SpawnMonster({},MONSTER_DATA.at("TestName"))};
Game::Update(0.f);
Assert::AreEqual(size_t(0),m.GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size(),L"Monsters do not have any lightning bolt affected buffs when spawning.");
m.AddBuff(BuffType::AFFECTED_BY_LIGHTNING_BOLT,3.f,1,[](std::weak_ptr<Monster>attachedTarget,Buff&b){attachedTarget.lock()->Hurt(5,attachedTarget.lock()->OnUpperLevel(),attachedTarget.lock()->GetZ());});
Assert::AreEqual(size_t(1),m.GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size(),L"Monster now has Affected By Lightning Bolt buff. Should get hurt for 5 damage in 3 seconds...");
Game::Update(0.f);
Game::Update(3.f);
Assert::AreEqual(25,m.GetHealth(),L"Monster should have taken 5 health from the expired callback provided.");
}
TEST_METHOD(AddBuffPlayerCallbackExpireFunctionTest){
Assert::AreEqual(size_t(0),game->GetPlayer()->GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size(),L"Player does not have any lightning bolt affected buffs when spawning.");
game->GetPlayer()->AddBuff(BuffType::AFFECTED_BY_LIGHTNING_BOLT,3.f,1,[](Player*attachedTarget,Buff&b){attachedTarget->Hurt(5,attachedTarget->OnUpperLevel(),attachedTarget->GetZ());});
Game::Update(0.f);
Assert::AreEqual(size_t(1),game->GetPlayer()->GetBuffs(BuffType::AFFECTED_BY_LIGHTNING_BOLT).size(),L"Player is now affected By Lightning Bolt buff. Should get hurt for 5 damage in 3 seconds...");
Game::Update(0.f);
Game::Update(3.f);
Assert::AreEqual(95,game->GetPlayer()->GetHealth(),L"Player should have taken 5 health from the expired callback provided.");
}
TEST_METHOD(MonsterHasBuffFunctionTest){
Monster&m{game->SpawnMonster({},MONSTER_DATA.at("TestName"))};
Game::Update(0.f);
Assert::AreEqual(false,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should not have a speedboost buff before being given one.");
m.AddBuff(BuffType::ADRENALINE_RUSH,1.f,1.f);
Assert::AreEqual(false,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should not have a speedboost buff when given an unrelated buff.");
m.AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
Assert::AreEqual(true,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should now have a speedboost buff.");
m.AddBuff(BuffType::SPEEDBOOST,2.f,1.f);
Assert::AreEqual(true,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should still report having a speedboost buff.");
Game::Update(0.f);
Game::Update(1.f);
Assert::AreEqual(true,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should still have one speedboost buff.");
Game::Update(1.f);
Assert::AreEqual(false,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should no longer have a speedboost buff.");
m.AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
m.RemoveBuff(BuffType::SPEEDBOOST);
Assert::AreEqual(false,m.HasBuff(BuffType::SPEEDBOOST),L"Monster should no longer have a speedboost buff.");
}
TEST_METHOD(PlayerHasBuffFunctionTest){
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should not have a speedboost buff before being given one.");
Game::AddBuffToPlayer(BuffType::ADRENALINE_RUSH,1.f,1.f);
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should not have a speedboost buff when given an unrelated buff.");
Game::AddBuffToPlayer(BuffType::SPEEDBOOST,1.f,1.f);
Assert::AreEqual(true,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should now have a speedboost buff.");
Game::AddBuffToPlayer(BuffType::SPEEDBOOST,2.f,1.f);
Assert::AreEqual(true,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should still report having a speedboost buff.");
Game::Update(0.f);
Game::Update(1.f);
Assert::AreEqual(true,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should still have one speedboost buff.");
Game::Update(1.f);
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should no longer have a speedboost buff.");
Game::AddBuffToPlayer(BuffType::SPEEDBOOST,1.f,1.f);
game->GetPlayer()->RemoveBuff(BuffType::SPEEDBOOST);
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should no longer have a speedboost buff.");
}
TEST_METHOD(PlayerDoesNotImmediatelyReceiveBuffTest){
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should not have a speedboost buff before being given one.");
game->GetPlayer()->AddBuff(BuffType::SPEEDBOOST,1.f,1.f);
Assert::AreEqual(false,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should still not have a speedboost buff since it is in the add queue.");
Game::Update(0.f);
Assert::AreEqual(true,game->GetPlayer()->HasBuff(BuffType::SPEEDBOOST),L"Player should now have a speedboost buff.");
}
};
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,178 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "CppUnitTest.h"
#include "AdventuresInLestoria.h"
#include "Tutorial.h"
#include <random>
#include <format>
#include "ItemDrop.h"
#include "DamageNumber.h"
#include <ranges>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
extern std::mt19937 rng;
namespace EngineTests
{
TEST_CLASS(EngineTest)
{
public:
std::unique_ptr<AiL>testGame;
InputGroup testKeyboardInput;
Player*player;
HWButton*testKey;
TEST_METHOD_INITIALIZE(PlayerInitialize){
InitializeGameConfigurations();
rng=std::mt19937{57189U};//Establish a fixed random seed on setup so the exact same results are generated every test run.
testGame.reset(new AiL(true));
ItemAttribute::Initialize();
ItemInfo::InitializeItems();
testGame->InitializeGraphics();
testGame->InitializeClasses();
sig::Animation::InitializeAnimations();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
#pragma region Setup a fake test map and test monster
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
#pragma endregion
testGame->ResetLevelStates();
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,0});
testKey=testGame->GetKeyboardState(0);
testGame->olc_UpdateKeyFocus(true); //Force the game to be "focused" for tests. Required if we want keyboard inputs to work.
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
}
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
TEST_METHOD(StripColorTest){
std::string noColCode{"Hello World!"};
Assert::AreEqual("Hello World!"s,testGame->stripCol(noColCode),L"Function should not strip out any text when there's no color codes.");
std::string leadingColCode{"#FFFFFFHello World!"};
Assert::AreEqual("Hello World!"s,testGame->stripCol(leadingColCode),L"Function should strip out all color codes.");
std::string extraColCodes{"#FFFFFFHello #00FF00World!"};
Assert::AreEqual("Hello World!"s,testGame->stripCol(extraColCodes),L"Function should strip out all color codes.");
std::u32string u32noColCode{noColCode.begin(),noColCode.end()};
std::u32string u32noColCodeResult{testGame->stripCol(u32noColCode)};
Assert::AreEqual("Hello World!"s,std::string{u32noColCodeResult.begin(),u32noColCodeResult.end()},L"Function should not strip out any text when there's no color codes.");
std::u32string u32leadingColCode{noColCode.begin(),noColCode.end()};
std::u32string u32leadingColCodeResult{testGame->stripCol(u32leadingColCode)};
Assert::AreEqual("Hello World!"s,std::string{u32leadingColCodeResult.begin(),u32leadingColCodeResult.end()},L"Function should strip out all color codes.");
std::u32string u32extraColCodes{extraColCodes.begin(),extraColCodes.end()};
std::u32string u32extraColCodesResult{testGame->stripCol(u32extraColCodes)};
Assert::AreEqual("Hello World!"s,std::string{u32extraColCodesResult.begin(),u32extraColCodesResult.end()},L"Function should strip out all color codes.");
}
TEST_METHOD(StripLeadingColorTest){
std::string noColCode{"Hello World!"};
Assert::AreEqual("Hello World!"s,testGame->stripLeadingCol(noColCode),L"Function should not strip out any text when there's no color codes.");
std::string leadingColCode{"#FFFFFFHello World!"};
Assert::AreEqual("Hello World!"s,testGame->stripLeadingCol(leadingColCode),L"Function should strip out color code at beginning of text.");
std::string extraColCodes{"#FFFFFFHello #00FF00World!"};
Assert::AreEqual("Hello #00FF00World!"s,testGame->stripLeadingCol(extraColCodes),L"Function should only strip out color code at beginning of text.");
std::u32string u32noColCode{noColCode.begin(),noColCode.end()};
std::u32string u32noColCodeResult{testGame->stripLeadingCol(u32noColCode)};
Assert::AreEqual("Hello World!"s,std::string{u32noColCodeResult.begin(),u32noColCodeResult.end()},L"Function should not strip out any text when there's no color codes.");
std::u32string u32leadingColCode{noColCode.begin(),noColCode.end()};
std::u32string u32leadingColCodeResult{testGame->stripLeadingCol(u32leadingColCode)};
Assert::AreEqual("Hello World!"s,std::string{u32leadingColCodeResult.begin(),u32leadingColCodeResult.end()},L"Function should strip out color code at beginning of text.");
std::u32string u32extraColCodes{extraColCodes.begin(),extraColCodes.end()};
std::u32string u32extraColCodesResult{testGame->stripLeadingCol(u32extraColCodes)};
Assert::AreEqual("Hello #00FF00World!"s,std::string{u32extraColCodesResult.begin(),u32extraColCodesResult.end()},L"Function should only strip out color code at beginning of text.");
}
TEST_METHOD(GetFinalRenderColorTest){
Assert::AreEqual(WHITE.n,testGame->GetFinalRenderColor(WHITE,"Hello World!").n,L"Should use source color as there's no HTML color code.");
Assert::AreEqual(BLUE.n,testGame->GetFinalRenderColor(WHITE,"#0000FFHello World!").n,L"Should use color in string since it has a leading HTML color code.");
Assert::AreEqual(BLUE.n,testGame->GetFinalRenderColor(WHITE,"#0000FFHello #00FF00World!").n,L"Should use color in string since it has a leading HTML color code.");
Assert::AreEqual(WHITE.n,testGame->GetFinalRenderColor(WHITE,"Hello #00FF00World!").n,L"Should use source color since there's no leading HTML color code.");
std::string testStr{"Hello World!"};
std::u32string u32testStr{testStr.begin(),testStr.end()};
Assert::AreEqual(WHITE.n,testGame->GetFinalRenderColor(WHITE,testStr).n,L"Should use source color as there's no HTML color code.");
std::string colorCodeStr{"#0000FFHello World!"};
std::u32string u32colorCodeStr{colorCodeStr.begin(),colorCodeStr.end()};
Assert::AreEqual(BLUE.n,testGame->GetFinalRenderColor(WHITE,colorCodeStr).n,L"Should use color in string since it has a leading HTML color code.");
std::string extraColorCodeStr{"#0000FFHello #00FF00World!"};
std::u32string u32extraColorCodeStr{extraColorCodeStr.begin(),extraColorCodeStr.end()};
Assert::AreEqual(BLUE.n,testGame->GetFinalRenderColor(WHITE,extraColorCodeStr).n,L"Should use color in string since it has a leading HTML color code.");
std::string middleColorCodeStr{"Hello #00FF00World!"};
std::u32string u32middleColorCodeStr{middleColorCodeStr.begin(),middleColorCodeStr.end()};
Assert::AreEqual(WHITE.n,testGame->GetFinalRenderColor(WHITE,middleColorCodeStr).n,L"Should use source color since there's no leading HTML color code.");
}
TEST_METHOD(UtilMapRangeTest){
Assert::AreEqual(0.f,util::map_range<float>(0.f,0,100,0,100),L"0 in input range 0-100 output range 0-100 maps to 0");
Assert::AreEqual(100.f,util::map_range<float>(100.f,0,100,0,100),L"100 in input range 0-100 output range 0-100 maps to 100");
Assert::AreEqual(50.f,util::map_range<float>(50.f,0,100,0,100),L"50 in input range 0-100 output range 0-100 maps to 100");
Assert::AreEqual(0.f,util::map_range<float>(0.f,0,50,0,100),L"0 in input range 0-50 output range 0-100 maps to 0");
Assert::AreEqual(200.f,util::map_range<float>(100.f,0,50,0,100),L"100 in input range 0-50 output range 0-100 maps to 200");
Assert::AreEqual(100.f,util::map_range<float>(50.f,0,50,0,100),L"50 in input range 0-50 output range 0-100 maps to 100");
Assert::AreEqual(100.f,util::map_range<float>(0.f,0,100,100,200),L"0 in input range 0-100 output range 100-200 maps to 100");
Assert::AreEqual(200.f,util::map_range<float>(100.f,0,100,100,200),L"100 in input range 0-100 output range 100-200 maps to 200");
Assert::AreEqual(150.f,util::map_range<float>(50.f,0,100,100,200),L"50 in input range 0-100 output range 100-200 maps to 150");
Assert::AreEqual(0.f,util::map_range<float>(0.f,50,100,100,200),L"0 in input range 50-100 output range 100-200 maps to 0");
Assert::AreEqual(200.f,util::map_range<float>(100.f,50,100,100,200),L"100 in input range 50-100 output range 100-200 maps to 200");
Assert::AreEqual(100.f,util::map_range<float>(50.f,50,100,100,200),L"50 in input range 50-100 output range 100-200 maps to 100");
}
};
}

@ -0,0 +1,113 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "CppUnitTest.h"
#include "AdventuresInLestoria.h"
#include "config.h"
#include "ItemDrop.h"
#include "Tutorial.h"
#include "DamageNumber.h"
#include "GameHelper.h"
#include "SaveFile.h"
#include <ranges>
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
INCLUDE_MONSTER_DATA
INCLUDE_game
INCLUDE_GFX
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_MONSTER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
namespace FileTests
{
TEST_CLASS(FileTest)
{
public:
std::unique_ptr<AiL>testGame;
#pragma region Setup Functions
void SetupTest(){
InitializeGameConfigurations();
testGame.reset(new AiL(true));
ItemAttribute::Initialize();
ItemInfo::InitializeItems();
testGame->InitializeGraphics();
testGame->InitializeClasses();
sig::Animation::InitializeAnimations();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA["CAMPAIGN_1_1"];
game->_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
#pragma endregion
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
Menu::themes.SetInitialized();
GFX.SetInitialized();
DAMAGENUMBER_LIST.clear();
}
void SetupMockMap(){
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
}
#pragma endregion
TEST_METHOD_INITIALIZE(FileTestInitialize){
SetupTest();
SetupMockMap();
}
TEST_METHOD_CLEANUP(CleanupFileTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
};
}

@ -0,0 +1,95 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "CppUnitTest.h"
#include "AdventuresInLestoria.h"
INCLUDE_game
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace Game{
enum class CastWaitProperty{
WAIT_FOR_CAST_TIME,
NO_WAIT,
};
inline void Update(const float fElapsedTime){
game->SetElapsedTime(fElapsedTime);
game->OnUserUpdate(fElapsedTime);
}
inline void CastAbilityAtLocation(Ability&ability,const vf2d&worldLoc,const CastWaitProperty castWaitTime=CastWaitProperty::WAIT_FOR_CAST_TIME){ //NOTE: screenLoc is the actual screen coordinates, NOT the world coordinates! You are defining the mouse position essentially.
game->GetPlayer()->SetTestScreenAimingLocation(worldLoc);
game->GetPlayer()->PrepareCast(ability);
game->GetPlayer()->CastSpell(ability);
Game::Update(ability.precastInfo.castTime);
}
inline void ChangeClass(Player*&player_in,const Class&cl){
game->ChangePlayerClass(cl);
player_in=game->GetPlayer();
}
inline std::weak_ptr<Item>GiveAndEquipEnchantedRing(const std::string_view enchantName,const EquipSlot slot=EquipSlot::RING1){
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,slot);
nullRing.lock()->_EnchantItem(enchantName);
return nullRing;
}
//Adds the buff directly to the player instead of the buffs added list. (By calling an update tick.)
inline void AddBuffToPlayer(BuffType type,float duration,float intensity){
game->GetPlayer()->AddBuff(type,duration,intensity);
Update(0.f);
}
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
inline void AddBuffToPlayer(BuffType type,float duration,float intensity,std::set<ItemAttribute>attr){
game->GetPlayer()->AddBuff(type,duration,intensity,attr);
Update(0.f);
}
//NOTE: If adding a % increase stat, please use the percentage version! 100% = 1!!
inline void AddBuffToPlayer(BuffType type,float duration,float intensity,std::set<std::string>attr){
game->GetPlayer()->AddBuff(type,duration,intensity,attr);
Update(0.f);
}
}
namespace Test
{
template<class T>
inline static void InRange(T initialVal,std::pair<T,T>range,std::wstring_view assertMessage){
Assert::IsTrue(initialVal>=range.first&&initialVal<=range.second,std::format(L"Expected: {}~{} Actual: {} - {}",range.first,range.second,initialVal,assertMessage).c_str());
}
}

@ -41,12 +41,15 @@ All rights reserved.
#include <random>
#include <format>
#include "ItemDrop.h"
#include <ranges>
#include "GameHelper.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_INITIALIZEGAMECONFIGURATIONS
extern std::mt19937 rng;
@ -60,6 +63,7 @@ namespace ItemTests
Player*player;
HWButton*testKey;
TEST_METHOD_INITIALIZE(ItemInitialize){
InitializeGameConfigurations();
rng=std::mt19937{57189U};//Establish a fixed random seed on setup so the exact same results are generated every test run.
testGame.reset(new AiL(true));
ItemAttribute::Initialize();
@ -73,27 +77,29 @@ namespace ItemTests
Menu::InitializeMenus();
Tutorial::Initialize();
Stats::InitializeDamageReductionTable();
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
testGame->ResetLevelStates();
#pragma region Setup a fake test map and test monster
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::drops.clear();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
#pragma endregion
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,0});
testKey=&testGame->pKeyboardState[0];
testKey=testGame->GetKeyboardState(0);
testGame->olc_UpdateKeyFocus(true); //Force the game to be "focused" for tests. Required if we want keyboard inputs to work.
Menu::themes.SetInitialized();
GFX.SetInitialized();
}
TEST_METHOD_CLEANUP(CleanupTests){
TEST_METHOD_CLEANUP(ItemCleanupTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
TEST_METHOD(ItemGiveTest){
Inventory::AddItem("Health Potion"s,3);
@ -130,25 +136,26 @@ namespace ItemTests
Inventory::AddItem("Minor Health Potion"s,5U);
game->SetLoadoutItem(0,"Minor Health Potion");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->useItem1,testKeyboardInput);
player->CheckAndPerformAbility(player->GetItem1(),testKeyboardInput);
Assert::AreEqual(1,Inventory::loadoutItemsUsed[0].second,L"1 Health potion considered used in loadout inventory.");
Assert::AreEqual(4U,Inventory::GetItemCount("Minor Health Potion"s),L"4 Health potions remain in player's inventory.");
Assert::AreEqual(player->useItem1.GetCooldownTime(),player->useItem1.cooldown,L"Item 1 is now on cooldown.");
Assert::AreEqual(player->GetItem1().GetCooldownTime(),player->GetItem1().cooldown,L"Item 1 is now on cooldown.");
}
TEST_METHOD(ItemScriptBuffTest){
player->Hurt(1,player->OnUpperLevel(),player->GetZ());
Inventory::AddItem("Stat Up Everything Potion"s,5U);
game->SetLoadoutItem(0,"Stat Up Everything Potion");
testKey->bHeld=true; //Simulate key being pressed.
Assert::ExpectException<std::exception>([&](){player->CheckAndPerformAbility(player->useItem1,testKeyboardInput);},L"If all buffs are properly applied, then some of these stat up buffs are illegal and will catch an exception.");
Assert::ExpectException<std::exception>([&](){player->CheckAndPerformAbility(player->GetItem1(),testKeyboardInput);},L"If all buffs are properly applied, then some of these stat up buffs are illegal and will catch an exception.");
}
TEST_METHOD(FlatRestoreScriptTest){
player->Hurt(75,player->OnUpperLevel(),player->GetZ());
player->mana=24;
player->ConsumeMana(76);
Inventory::AddItem("Flat Recovery Potion"s,5U);
game->SetLoadoutItem(0,"Flat Recovery Potion");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->useItem1,testKeyboardInput);
player->CheckAndPerformAbility(player->GetItem1(),testKeyboardInput);
game->OnUserUpdate(0.f); //Wait an extra tick for the buff to begin going down.
game->SetElapsedTime(0.05f);
game->OnUserUpdate(0.05f);//Wait some time as the item applies a buff that heals us. We're also going to gain one mana during this tick.
Assert::AreEqual(75,player->GetHealth(),L"Player Health is 75 after using Flat Recovery Potion.");
@ -156,11 +163,12 @@ namespace ItemTests
}
TEST_METHOD(PctRestoreScriptTest){
player->Hurt(75,player->OnUpperLevel(),player->GetZ());
player->mana=24;
player->ConsumeMana(76);
Inventory::AddItem("Pct Recovery Potion"s,5U);
game->SetLoadoutItem(1,"Pct Recovery Potion");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->useItem2,testKeyboardInput);
player->CheckAndPerformAbility(player->GetItem2(),testKeyboardInput);
game->OnUserUpdate(0.f); //Wait an extra tick for the buff to begin going down.
game->SetElapsedTime(0.05f);
game->OnUserUpdate(0.05f);//Wait some time as the item applies a buff that heals us.
Assert::AreEqual(75,player->GetHealth(),L"Player Health is 75 after using Pct Recovery Potion.");
@ -171,7 +179,8 @@ namespace ItemTests
Inventory::AddItem("Bandages"s,5U);
game->SetLoadoutItem(2,"Bandages");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->useItem3,testKeyboardInput);
player->CheckAndPerformAbility(player->GetItem3(),testKeyboardInput);
game->OnUserUpdate(0.f); //Wait an extra tick for the buff to begin going down.
game->SetElapsedTime(0.05f);
game->OnUserUpdate(0.05f);//Wait some time as the item applies a buff that heals us.
Assert::AreEqual(30,player->GetHealth(),L"Player is immediately healed for 5 health points on Bandages use.");
@ -214,6 +223,7 @@ namespace ItemTests
std::weak_ptr<Item>testArmor{Inventory::AddItem("Test Armor"s)};
Assert::IsFalse(testArmor.lock()->CanBeRefined(),L"Test Armor should not be allowed to be refined since it's not an accessory.");
Inventory::AddItem(slimeKingRing.lock()->FragmentName(),50U);
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
Assert::IsTrue(slimeKingRing.lock()->CanBeRefined(),L"Ring of the Slime King should now be allowed to be refined since we meet all requirements.");
player->SetMoney(0);
Assert::IsFalse(slimeKingRing.lock()->CanBeRefined(),L"Ring of the Slime King should not be allowed to be refined since we do not have enough money.");
@ -225,6 +235,122 @@ namespace ItemTests
for(const auto&[attr,val]:slimeKingRing.lock()->RandomStats()){
Assert::AreEqual(ITEM_DATA[slimeKingRing.lock()->ActualName()].GetMaxStats().A_Read(attr),val,L"The current stats should be equal to the maximum stats when refinement is done.");
}
/*Ring of the Slime King has the following refining stats:
* StatValues = Health,Mana,Move Spd %
MinStats = 5,1,1
MaxStats = 20,4,3
*
Therefore, after this process is done the player should have 20 more health, 4 more mana, and 3% more move speed than normal.
*/
//These checks make sure that the refining call actually modifies already equipped items!
Assert::AreEqual(120,player->GetMaxHealth(),L"Player should now have 120 max health.");
Assert::AreEqual(104,player->GetMaxMana(),L"Player should now have 104 max mana.");
Assert::AreEqual(1.03f,player->GetMoveSpdMult(),L"Player should now have 103% move speed.");
}
TEST_METHOD(EnchantTestCheck){
std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)};
Assert::IsFalse(slimeKingRing.lock()->HasEnchant());
bool obtainedDuplicate{false};
for(int i:std::ranges::iota_view(0,1000)){
std::optional<ItemEnchant>previousEnchant{slimeKingRing.lock()->GetEnchant()};
std::string previousEnchantName{};
if(slimeKingRing.lock()->HasEnchant())previousEnchantName=slimeKingRing.lock()->GetEnchant().value().Name();
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant(previousEnchant));
Assert::IsTrue(slimeKingRing.lock()->HasEnchant());
if(previousEnchantName==slimeKingRing.lock()->GetEnchant().value().Name()){
bool improvementExists{false};
std::string statDowngrades{};
if(previousEnchant.value().HasAttributes()){
for(const auto&[attr,val]:previousEnchant.value()){
if(slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName())>val){
improvementExists=true;
break;
}else statDowngrades+=std::format("{} - Previous:{}, New:{}\n",attr.Name(),val,slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName()));
}
}else improvementExists=true;
Assert::IsTrue(improvementExists,util::wformat("Could not find a stat improvement for same name stat! {} THIS SHOULD NOT BE ALLOWED!",statDowngrades).c_str());
obtainedDuplicate=true;
}
if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())Assert::AreEqual(int(player->GetClass()),int(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().value())); //Validate enchant is only for this class if it's a class-based ability.
}
testGame->ChangePlayerClass(WIZARD);
player=testGame->GetPlayer(); //The player pointer has been reassigned...
for(int i:std::ranges::iota_view(0,1000)){
std::optional<ItemEnchant> previousEnchant{slimeKingRing.lock()->GetEnchant()};
std::string previousEnchantName{};
if(slimeKingRing.lock()->HasEnchant())previousEnchantName=slimeKingRing.lock()->GetEnchant().value().Name();
slimeKingRing.lock()->_EnchantItem(ItemEnchant::RollRandomEnchant(previousEnchant));
Assert::IsTrue(slimeKingRing.lock()->HasEnchant());
if(previousEnchantName==slimeKingRing.lock()->GetEnchant().value().Name()){
bool improvementExists{false};
std::string statDowngrades{};
if(previousEnchant.value().HasAttributes()){
for(const auto&[attr,val]:previousEnchant.value()){
if(slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName())>val){
improvementExists=true;
break;
}else statDowngrades+=std::format("{} - Previous:{}, New:{}\n",attr.Name(),val,slimeKingRing.lock()->GetEnchant().value().GetAttribute(attr.ActualName()));
}
}else improvementExists=true;
Assert::IsTrue(improvementExists,util::wformat("Could not find a stat improvement for same name stat! {} THIS SHOULD NOT BE ALLOWED!",statDowngrades).c_str());
obtainedDuplicate=true;
}
if(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().has_value())Assert::AreEqual(int(player->GetClass()),int(ItemEnchantInfo::ENCHANT_LIST.at(slimeKingRing.lock()->GetEnchant().value().Name()).GetClass().value())); //Validate enchant is only for this class if it's a class-based ability.
}
Assert::IsTrue(obtainedDuplicate,L"During this test a duplicate enchant was never obtained! THIS SHOULD BE ALLOWED!");
}
TEST_METHOD(AccessoryAntiCompatibilityCheck){
std::weak_ptr<Item>extraRing{Inventory::AddItem("Ring of the Slime King"s)};
std::weak_ptr<Item>extraRing2{Inventory::AddItem("Ring of the Slime King"s)};
Inventory::EquipItem(extraRing,EquipSlot::RING2);
Assert::AreEqual(true,Item::SelectedEquipIsDifferent(extraRing2,EquipSlot::RING1),L"The game should allow equipping of any two normal rings that are not the same ring.");
Assert::AreEqual(false,Item::SelectedEquipIsDifferent(extraRing,EquipSlot::RING1),L"The game should not allow equipping the same ring if it's already equipped.");
Inventory::UnequipItem(EquipSlot::RING2);
Assert::AreEqual(true,Item::SelectedEquipIsDifferent(extraRing,EquipSlot::RING1),L"The game should allow equipping a ring to either blank slot if they're open.");
}
TEST_METHOD(AccessoryRandomEnchantTest){
std::weak_ptr<Item>extraRing{Inventory::AddItem("Ring of the Slime King"s)};
std::unordered_map<ItemEnchantInfo::ItemEnchantCategory,uint32_t>enchantCounts;
for(int i:std::ranges::iota_view(0,1000)){
Inventory::AddItem(extraRing.lock()->FragmentName(),"Fragment Enchant Cost"_i[0]);
player->AddMoney("Fragment Enchant Cost"_i[1]);
const ItemEnchant&resultEnchant{extraRing.lock()->ApplyRandomEnchant()};
if(resultEnchant.GetClass().has_value())Assert::AreEqual(int(resultEnchant.GetClass().value()),int(player->GetClass()),L"Player's class matches the class of the enchant.");
enchantCounts[resultEnchant.Category()]++;
Assert::AreEqual(true,extraRing.lock()->GetEnchant().has_value(),L"Ring is expected to be enchanted.");
Assert::AreEqual(resultEnchant.Name(),extraRing.lock()->GetEnchant().value().Name(),L"Ring is expected to be enchanted with the same enchant that was selected.");
Assert::AreEqual(false,player->HasEnchant(resultEnchant.Name()),L"Player is not expected to have the same enchant that was selected while the ring is unequipped.");
Inventory::EquipItem(extraRing,EquipSlot::RING1);
Assert::AreEqual(true,player->HasEnchant(resultEnchant.Name()),L"Player is expected to have the same enchant that was selected.");
Inventory::UnequipItem(EquipSlot::RING1);
}
Test::InRange(enchantCounts[ItemEnchantInfo::ItemEnchantCategory::GENERAL],{450U,550U},util::wformat("General enchants % is approx 50%."));
Test::InRange(enchantCounts[ItemEnchantInfo::ItemEnchantCategory::CLASS],{350U,450U},util::wformat("Class enchants % is approx 40%."));
Test::InRange(enchantCounts[ItemEnchantInfo::ItemEnchantCategory::UNIQUE],{50U,150U},util::wformat("Unique enchants % is approx 40%."));
}
TEST_METHOD(EnchantColorTest){
Assert::AreEqual("Item Enchants.General Enchants.Enchant Display Color"_Pixel.n,ItemEnchantInfo::GetEnchant("Health Boost").DisplayCol().n,L"Expecting a general enchant to have the general enchant pixel display color.");
Assert::AreEqual("Item Enchants.Class Enchants.Enchant Display Color"_Pixel.n,ItemEnchantInfo::GetEnchant("Quickdraw").DisplayCol().n,L"Expecting a class enchant to have the class enchant pixel display color.");
Assert::AreEqual("Item Enchants.Unique Enchants.Enchant Display Color"_Pixel.n,ItemEnchantInfo::GetEnchant("Magical Protection").DisplayCol().n,L"Expecting a unique enchant to have the unique enchant pixel display color.");
}
TEST_METHOD(CanBeEnchantedTest){
std::weak_ptr<Item>extraRing{Inventory::AddItem("Ring of the Slime King"s)};
std::weak_ptr<Item>testArmor{Inventory::AddItem("Test Armor"s)};
Assert::AreEqual(false,extraRing.lock()->CanBeEnchanted(),L"We don't have the money nor required fragments to enchant this item.");
Assert::AreEqual(false,testArmor.lock()->CanBeEnchanted(),L"We can't enchant armor.");
player->SetMoney(2000U);
Assert::AreEqual(false,extraRing.lock()->CanBeEnchanted(),L"We don't have the required fragments to enchant this item.");
player->SetMoney(0U);
Inventory::AddItem(extraRing.lock()->FragmentName(),"Fragment Enchant Cost"_i[0]);
Assert::AreEqual(false,extraRing.lock()->CanBeEnchanted(),L"We don't have the required money to enchant this item.");
player->SetMoney(2000U);
Assert::AreEqual(true,extraRing.lock()->CanBeEnchanted(),L"We don't have the required money to enchant this item.");
extraRing.lock()->ApplyRandomEnchant();
Assert::AreEqual(false,extraRing.lock()->CanBeEnchanted(),L"Ring cannot be enchanted again due to consumption of fragments.");
Inventory::AddItem(extraRing.lock()->FragmentName(),"Fragment Enchant Cost"_i[0]);
Assert::AreEqual(true,extraRing.lock()->CanBeEnchanted(),L"Ring can be enchanted again with the right amount of fragments.");
Assert::AreEqual(uint32_t(2000-"Fragment Enchant Cost"_i[1]),player->GetMoney(),util::wformat("Lost {} money due to enchanting ring.","Fragment Enchant Cost"_i[1]).c_str());
}
};
}

@ -41,6 +41,7 @@ All rights reserved.
#include "ItemDrop.h"
#include "Tutorial.h"
#include "DamageNumber.h"
#include "GameHelper.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -50,6 +51,7 @@ INCLUDE_game
INCLUDE_GFX
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_MONSTER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
TEST_MODULE_INITIALIZE(AiLTestSuite)
{
@ -65,12 +67,15 @@ namespace MonsterTests
#pragma region Setup Functions
//Makes MONSTER_DATA["TestName"] available.
void SetupTestMonster(){
InitializeGameConfigurations();
testGame.reset(new AiL(true));
ItemAttribute::Initialize();
ItemInfo::InitializeItems();
testGame->InitializeGraphics();
testGame->InitializeClasses();
sig::Animation::InitializeAnimations();
Monster::InitializeStrategies();
MonsterData::InitializeMonsterData();
testGame->InitializeDefaultKeybinds();
testGame->InitializePlayer();
sig::Animation::SetupPlayerAnimations();
@ -80,13 +85,12 @@ namespace MonsterTests
GameState::Initialize();
GameState::STATE=GameState::states.at(States::State::GAME_RUN);
testGame->ResetLevelStates();
#pragma region Setup a fake test map
game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["UpperZone"];
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["LowerZone"];
game->currentLevel="CAMPAIGN_1_1";
ItemDrop::drops.clear();
game->_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
#pragma endregion
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
@ -98,7 +102,7 @@ namespace MonsterTests
}
void SetupMockMap(){
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::drops.clear();
ItemDrop::ClearDrops();
}
#pragma endregion
@ -106,8 +110,10 @@ namespace MonsterTests
SetupTestMonster();
SetupMockMap();
}
~MonsterTest(){
testGame.release();
TEST_METHOD_CLEANUP(MonsterTestCleanup){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
TEST_METHOD(DisplayNameCheck){
Assert::AreEqual("Test Monster",MONSTER_DATA["TestName"].GetDisplayName().c_str());
@ -396,7 +402,7 @@ namespace MonsterTests
testMonster.Hurt(10,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::DOT);
Assert::AreEqual(10,testMonster.GetHealth(),L"A DOT should go through all sources of iframes.");
Assert::AreEqual(2,std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr<DamageNumber>&damageNumber){
if(damageNumber->type==DamageNumberType::DOT)return count+1;
if(damageNumber->GetType()==DamageNumberType::DOT)return count+1;
else return count;
})
,L"There should be 2 damage numbers of type DOT.");
@ -404,8 +410,7 @@ namespace MonsterTests
TEST_METHOD(TrapperMarkTest){
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
game->SpawnMonster({},testMonsterData);
game->SetElapsedTime(0.1f);
game->OnUserUpdate(0.1f); //A monster that is spawned needs to be added to the monster list in the next tick.
Game::Update(0.1f); //A monster that is spawned needs to be added to the monster list in the next tick.
std::weak_ptr<Monster>testMonster{MONSTER_LIST.front()};
Assert::AreEqual(uint8_t(0),testMonster.lock()->GetMarkStacks(),L"Monster has 0 marks initially.");
@ -456,5 +461,99 @@ namespace MonsterTests
Assert::AreEqual(uint8_t(0),testMonster.lock()->GetMarkStacks(),L"The marks should have expired after 10 seconds.");
}
TEST_METHOD(HurtFailsWhenDead){
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
Monster&m{game->SpawnMonster({},testMonsterData)};
m.Hurt(50,m.OnUpperLevel(),m.GetZ());
Assert::IsTrue(m.IsDead(),L"Monster is considered dead.");
Assert::IsTrue(!m.IsAlive(),L"Monster is considered dead.");
Assert::IsFalse(m.Hurt(50,m.OnUpperLevel(),m.GetZ()),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m._DealTrueDamage(50),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m._DealTrueDamage(50,HurtFlag::DOT),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m._DealTrueDamage(50,HurtFlag::PLAYER_ABILITY),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
}
TEST_METHOD(HurtSucceedsWhenAlive){
MonsterData testMonsterData{"TestName","Test Monster",3000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
Monster&m{game->SpawnMonster({},testMonsterData)};
m.Hurt(50,m.OnUpperLevel(),m.GetZ());
Assert::IsTrue(!m.IsDead(),L"Monster is considered alive.");
Assert::IsTrue(m.IsAlive(),L"Monster is considered alive.");
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ()),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m._DealTrueDamage(50),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::DOT),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::PLAYER_ABILITY),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
}
TEST_METHOD(HurtSucceedsWhenDyingWithExactHealth){
MonsterData testMonsterData{"TestName","Test Monster",50,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
Monster&m{game->SpawnMonster({},testMonsterData)};
m.Hurt(50,m.OnUpperLevel(),m.GetZ());
Assert::IsTrue(m.IsDead(),L"Monster is considered dead.");
Assert::IsTrue(!m.IsAlive(),L"Monster is considered dead.");
m.Heal(50);
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ()),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m._DealTrueDamage(50),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::DOT),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::PLAYER_ABILITY),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
}
TEST_METHOD(UnconsciousMonsterTest){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
Game::Update(1.f);
Assert::IsFalse(parrot.IsUnconscious(),L"Parrot should not be immediately unconscious.");
parrot.Hurt(1,parrot.OnUpperLevel(),parrot.GetZ());
Assert::IsFalse(parrot.IsUnconscious(),L"Parrot should not be unconscious after taking 1 damage.");
parrot.Hurt(parrot.GetMaxHealth(),parrot.OnUpperLevel(),parrot.GetZ());
Assert::IsTrue(parrot.IsUnconscious(),L"Parrot should now be unconscious.");
Assert::IsTrue(parrot.IsDead(),L"Parrot is considered dead.");
Assert::IsTrue(parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()),L"Parrot should be in an undamageable state. Hopefully for obvious reasons.");
Game::Update(MONSTER_DATA.at("Parrot").UnconsciousTime()/2);
Assert::IsTrue(parrot.IsUnconscious(),L"Parrot should still be unconscious.");
Assert::IsTrue(parrot.IsDead(),L"Parrot should still be considered dead.");
Assert::IsTrue(parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()),L"Parrot should still be in an undamageable state.");
Game::Update(MONSTER_DATA.at("Parrot").UnconsciousTime()/2);
Assert::IsTrue(!parrot.IsUnconscious(),L"Parrot should no longer be unconscious.");
Assert::IsTrue(parrot.IsAlive(),L"Parrot should be alive.");
Assert::IsTrue(!parrot.InUndamageableState(parrot.OnUpperLevel(),parrot.GetZ()),L"Parrot should not be in an undamageable state.");
}
TEST_METHOD(UnconsciousMonsterHurtTest){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
Game::Update(1.f);
parrot.Hurt(parrot.GetMaxHealth(),parrot.OnUpperLevel(),parrot.GetZ());
Assert::IsTrue(parrot.IsUnconscious(),L"Parrot should now be unconscious.");
}
TEST_METHOD(MonsterCollisionRadiusTest){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
Game::Update(1.f);
Game::Update(1.f);
Assert::AreEqual(parrot.GetOriginalCollisionRadius(),parrot.GetCollisionRadius(),L"Parrot collision radius should be normal.");
Assert::AreEqual(int(game->GetPlayer()->GetMaxHealth()-parrot.GetCollisionDamage()),game->GetPlayer()->GetHealth(),L"Player should take collision damage from the parrot.");
parrot.SetCollisionRadius(0.f);
Assert::AreEqual(0.f,parrot.GetCollisionRadius(),L"Parrot collision radius should now be zero.");
game->GetPlayer()->Heal(game->GetPlayer()->GetMaxHealth());
game->GetPlayer()->_SetIframes(0.f);
parrot.SetPos({});
game->GetPlayer()->ForceSetPos({});
Game::Update(1.f);
Assert::AreEqual(game->GetPlayer()->GetMaxHealth(),game->GetPlayer()->GetHealth(),L"Player should be full health.");
parrot.SetCollisionRadius(parrot.GetOriginalCollisionRadius());
Game::Update(1.f);
Assert::AreEqual(int(game->GetPlayer()->GetMaxHealth()-parrot.GetCollisionDamage()),game->GetPlayer()->GetHealth(),L"Player should take collision damage from the parrot.");
}
TEST_METHOD(MonsterCollisionRadiusSizeTest){
Monster&parrot{game->SpawnMonster({},MONSTER_DATA.at("Parrot"))};
}
};
}

@ -42,6 +42,8 @@ All rights reserved.
#include <format>
#include "ItemDrop.h"
#include "DamageNumber.h"
#include <ranges>
#include "GameHelper.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -49,6 +51,7 @@ using namespace olc::utils;
INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
extern std::mt19937 rng;
@ -62,6 +65,7 @@ namespace PlayerTests
Player*player;
HWButton*testKey;
TEST_METHOD_INITIALIZE(PlayerInitialize){
InitializeGameConfigurations();
rng=std::mt19937{57189U};//Establish a fixed random seed on setup so the exact same results are generated every test run.
testGame.reset(new AiL(true));
ItemAttribute::Initialize();
@ -81,15 +85,16 @@ namespace PlayerTests
#pragma region Setup a fake test map and test monster
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::drops.clear();
ItemDrop::ClearDrops();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
#pragma endregion
testGame->ResetLevelStates();
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,0});
testKey=&testGame->pKeyboardState[0];
testKey=testGame->GetKeyboardState(0);
testGame->olc_UpdateKeyFocus(true); //Force the game to be "focused" for tests. Required if we want keyboard inputs to work.
Menu::themes.SetInitialized();
GFX.SetInitialized();
@ -98,6 +103,8 @@ namespace PlayerTests
}
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
TEST_METHOD(PlayerAlive){
Assert::IsTrue(player->IsAlive());
@ -315,11 +322,11 @@ namespace PlayerTests
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(HealthStatUpBuffCheck){
player->AddBuff(BuffType::STAT_UP,5.f,1000.f,{"Health"});
Game::AddBuffToPlayer(BuffType::STAT_UP,5.f,1000.f,{"Health"});
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(HealthPctStatUpBuffCheck){
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Health %"});
Game::AddBuffToPlayer(BuffType::STAT_UP,5.f,1000.0_Pct,{"Health %"});
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(IllegalCritRateStatUpBuffCheck){
@ -431,6 +438,18 @@ namespace PlayerTests
player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility4(),testKeyboardInput);
Inventory::AddItem("Health Potion"s);
Inventory::AddItem("Berries"s);
Inventory::AddItem("Mana Potion"s);
game->SetLoadoutItem(0,"Health Potion");
game->SetLoadoutItem(1,"Berries");
game->SetLoadoutItem(2,"Mana Potion");
player->CheckAndPerformAbility(player->GetItem1(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetItem2(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetItem3(),testKeyboardInput);
game->SetElapsedTime(0.01f); //Force elapsed time value.
game->OnUserUpdate(0.01f); //Let 0.01 seconds go by. All abilities should be off cooldown.
Assert::AreEqual(0.f,player->GetRightClickAbility().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown.");
@ -438,6 +457,9 @@ namespace PlayerTests
Assert::AreEqual(0.f,player->GetAbility2().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown.");
Assert::AreEqual(0.f,player->GetAbility3().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown.");
Assert::AreEqual(0.f,player->GetAbility4().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown.");
Assert::AreNotEqual(0.f,player->GetItem1().cooldown,L"Using an item ability with 100% CDR should not affect the cooldown.");
Assert::AreNotEqual(0.f,player->GetItem2().cooldown,L"Using an item ability with 100% CDR should not affect the cooldown.");
Assert::AreNotEqual(0.f,player->GetItem3().cooldown,L"Using an item ability with 100% CDR should not affect the cooldown.");
}
TEST_METHOD(CritRateStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
@ -483,10 +505,10 @@ namespace PlayerTests
}
TEST_METHOD(DamageReductionStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Damage Reduction is 0%");
Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Damage Reduction is 0%");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100.0_Pct,player->GetDamageReductionPct(),L"Damage Reduction is 100%");
Assert::AreEqual(75.0_Pct,player->GetDamageReductionFromBuffs(),L"Max Damage Reduction is 75%, even if a piece has 100% damage reduction.");
}
TEST_METHOD(HPRecoveryStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
@ -572,6 +594,7 @@ namespace PlayerTests
Assert::AreEqual(0.f,player->GetAttackRecoveryRateReduction(),L"Attack rate cooldown starts with being reduced by 0 seconds.");
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Game::Update(0.f);
Assert::IsTrue(player->GetBuffs(BuffType::ADRENALINE_RUSH).size()>0,L"After using Adrenaline Rush, player has the Adrenaline Rush buff.");
Assert::AreEqual(1.1f,player->GetMoveSpdMult(),L"Move Speed Multiplier increased by 10% to x1.1");
Assert::AreEqual(0.105f,player->GetAttackRecoveryRateReduction(),L"Attack Recovery Rate reduced by 30%, or 0.105s");
@ -591,7 +614,7 @@ namespace PlayerTests
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),HurtFlag::DOT);
Assert::AreEqual(80,player->GetHealth(),L"Player should take 20 damage through a DOT.");
Assert::AreEqual(1,std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr<DamageNumber>&damageNumber){
if(damageNumber->type==DamageNumberType::DOT)return count+1;
if(damageNumber->GetType()==DamageNumberType::DOT)return count+1;
else return count;
})
,L"There should be a damage number of type DOT.");
@ -600,10 +623,181 @@ namespace PlayerTests
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),HurtFlag::DOT);
Assert::AreEqual(80,player->GetHealth(),L"Player should take 20 damage through a DOT even when iframes are on."); //HP Recovery/4s set effect has restored the health of the player to 100. So it should go back down to 80.
Assert::AreEqual(2,std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr<DamageNumber>&damageNumber){
if(damageNumber->type==DamageNumberType::DOT)return count+1;
if(damageNumber->GetType()==DamageNumberType::DOT)return count+1;
else return count;
})
,L"There should be 2 damage numbers of type DOT.");
}
TEST_METHOD(HasEnchantCheck){
std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)};
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
slimeKingRing.lock()->_EnchantItem("Emergency Recovery"sv);
Assert::IsTrue(player->HasEnchant("Emergency Recovery"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Emergency Recovery"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
slimeKingRing.lock()->_EnchantItem("Reaper of Souls"sv);
Assert::IsTrue(player->HasEnchant("Reaper of Souls"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Reaper of Souls"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING1);
slimeKingRing.lock()->_EnchantItem("Attack Boost"sv);
Assert::IsTrue(player->HasEnchant("Attack Boost"));
Inventory::EquipItem(slimeKingRing,EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Attack Boost"));
std::weak_ptr<Item>leatherHelmet{Inventory::AddItem("Leather Helmet"s)};
std::weak_ptr<Item>leatherArmor{Inventory::AddItem("Leather Armor"s)};
std::weak_ptr<Item>leatherPants{Inventory::AddItem("Leather Pants"s)};
std::weak_ptr<Item>leatherGloves{Inventory::AddItem("Leather Gloves"s)};
std::weak_ptr<Item>leatherShoes{Inventory::AddItem("Leather Shoes"s)};
std::weak_ptr<Item>woodenSword{Inventory::AddItem("Wooden Sword"s)};
Inventory::EquipItem(leatherHelmet,EquipSlot::HELMET);
Inventory::EquipItem(leatherArmor,EquipSlot::ARMOR);
Inventory::EquipItem(leatherPants,EquipSlot::PANTS);
Inventory::EquipItem(leatherGloves,EquipSlot::GLOVES);
Inventory::EquipItem(leatherShoes,EquipSlot::SHOES);
Inventory::EquipItem(woodenSword,EquipSlot::WEAPON);
leatherHelmet.lock()->_EnchantItem("Wizard's Soul"sv);
Assert::IsTrue(player->HasEnchant("Wizard's Soul"));
leatherArmor.lock()->_EnchantItem("Ability Haste"sv);
Assert::IsTrue(player->HasEnchant("Ability Haste"));
leatherPants.lock()->_EnchantItem("Improved Ground Slam"sv);
Assert::IsTrue(player->HasEnchant("Improved Ground Slam"));
leatherGloves.lock()->_EnchantItem("Battle Shout"sv);
Assert::IsTrue(player->HasEnchant("Battle Shout"));
leatherShoes.lock()->_EnchantItem("Attack Boost"sv);
Inventory::UnequipItem(EquipSlot::RING2);
Assert::IsTrue(player->HasEnchant("Attack Boost"));
woodenSword.lock()->_EnchantItem("Mana Pool"sv);
Assert::IsTrue(player->HasEnchant("Mana Pool"));
Inventory::UnequipItem(EquipSlot::HELMET);
Inventory::UnequipItem(EquipSlot::ARMOR);
Inventory::UnequipItem(EquipSlot::PANTS);
Inventory::UnequipItem(EquipSlot::GLOVES);
Inventory::UnequipItem(EquipSlot::SHOES);
Inventory::UnequipItem(EquipSlot::WEAPON);
Inventory::UnequipItem(EquipSlot::RING1);
Inventory::UnequipItem(EquipSlot::RING2);
Assert::IsFalse(player->HasEnchant("Emergency Recovery"));
Assert::IsFalse(player->HasEnchant("Reaper of Souls"));
Assert::IsFalse(player->HasEnchant("Attack Boost"));
Assert::IsFalse(player->HasEnchant("Wizard's Soul"));
Assert::IsFalse(player->HasEnchant("Ability Haste"));
Assert::IsFalse(player->HasEnchant("Improved Ground Slam"));
Assert::IsFalse(player->HasEnchant("Battle Shout"));
Assert::IsFalse(player->HasEnchant("Attack Boost"));
Assert::IsFalse(player->HasEnchant("Mana Pool"));
}
TEST_METHOD(AutoAttackReductionFunctionCheck){
Assert::AreEqual(0.f,player->GetAutoAttackTimer(),L"Auto attack timer is not on cooldown.");
player->AutoAttack();
Assert::AreEqual("Warrior.Auto Attack.Cooldown"_F,player->GetAutoAttackTimer(),L"Auto attack timer should've went on cooldown.");
player->ReduceAutoAttackTimer(0.2f);
Assert::AreEqual("Warrior.Auto Attack.Cooldown"_F-0.2f,player->GetAutoAttackTimer(),L"Auto attack timer should've been reduced by 0.2 seconds.");
}
TEST_METHOD(CooldownChargesCheck){
player->GetAbility4()=Ranger::ability1;
for(const std::reference_wrapper<Ability>&a:player->GetAbilities()){
if(a.get().name=="???")continue;
Assert::AreEqual(uint8_t(1),a.get().charges,util::wformat("Ability {} start with 1 charge.",a.get().name).c_str());
Assert::AreEqual(0.f,a.get().cooldown,util::wformat("Ability {} start off cooldown.",a.get().name).c_str());
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(a.get(),testKeyboardInput);
Assert::AreEqual(uint8_t(0),a.get().charges,util::wformat("Ability {} should consume a charge when used.",a.get().name).c_str());
Assert::AreEqual(a.get().COOLDOWN_TIME,a.get().cooldown,util::wformat("Ability {} go on cooldown when used.",a.get().name).c_str());
player->RestoreMana(100);
player->SetState(State::NORMAL);
player->CheckAndPerformAbility(a.get(),testKeyboardInput);
a.get().MAX_CHARGES=5;
for(int i:std::ranges::iota_view(0,5)){
testGame->SetElapsedTime(a.get().COOLDOWN_TIME);
testGame->OnUserUpdate(a.get().COOLDOWN_TIME);
Assert::AreEqual(uint8_t(i+1),a.get().charges,util::wformat("Ability {} should increment their charge count when they are completely off cooldown.",a.get().name).c_str());
if(i!=4)Assert::AreEqual(a.get().COOLDOWN_TIME,a.get().cooldown,util::wformat("Ability {} should go on cooldown again when their stack count is not maxed out.",a.get().name).c_str());
else Assert::AreEqual(0.f,a.get().cooldown,util::wformat("Ability {} should not go on cooldown again when their stack count is maxed out.",a.get().name).c_str());
}
player->RestoreMana(100);
player->SetState(State::NORMAL);
}
}
TEST_METHOD(ChargesEquipBehaviorCheck){
testGame->ChangePlayerClass(RANGER);
player=testGame->GetPlayer();
player->GetAbility3().charges=3;
testKey->bHeld=true; //Force the key to be held down for testing purposes.
testGame->SetElapsedTime(0.25f);
testGame->OnUserUpdate(0.25f);
Assert::AreEqual(uint8_t(1),player->GetAbility3().charges,L"Back down to 1 charge without having Multi-Multishot enchant equipped.");
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Assert::AreEqual(uint8_t(0),player->GetAbility3().charges,L"Back down to 0 charges without having Multi-Multishot enchant equipped.");
}
TEST_METHOD(CooldownEquipBehaviorCheck){
testGame->ChangePlayerClass(RANGER);
player=testGame->GetPlayer();
float oldCooldownTime{player->GetAbility3().GetCooldownTime()};
player->GetAbility3().cooldown=player->GetAbility3().GetCooldownTime();
std::weak_ptr<Item>nullRing{Inventory::AddItem("Null Ring"s)};
Inventory::EquipItem(nullRing,EquipSlot::RING1);
nullRing.lock()->_EnchantItem("Multi-Multishot"sv);
testKey->bHeld=true; //Force the key to be held down for testing purposes.
Assert::AreEqual(player->GetAbility3().GetCooldownTime(),oldCooldownTime-oldCooldownTime*"Multi-Multishot"_ENC["COOLDOWN REDUCTION PCT"]/100.f,L"Old cooldown time with multishot cooldown reduction pct matches.");
testGame->SetElapsedTime(0.f);
testGame->OnUserUpdate(0.f);
Assert::AreEqual(player->GetAbility3().GetCooldownTime(),player->GetAbility3().cooldown,L"Cooldown now matches the new reduced amount.");
}
TEST_METHOD(PlayerGetShieldCheck){
player=testGame->GetPlayer();
Assert::AreEqual(0U,player->GetShield(),L"Player should have no shields at first.");
}
TEST_METHOD(PlayerAddShieldCheck){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
Assert::AreEqual(60U,player->GetShield(),L"Player has 60 shield points.");
testGame->SetElapsedTime(5.f);
testGame->OnUserUpdate(5.f);
Assert::AreEqual(0U,player->GetShield(),L"Player should lose the shield after 5 seconds.");
}
TEST_METHOD(PlayerMultiShieldCheck){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
player->AddShield(100U,2.f,PlayerTimerType::PLAYER_OUTLINE_TIMER);
Assert::AreEqual(160U,player->GetShield(),L"Player has 160 shield points.");
testGame->SetElapsedTime(2.f);
testGame->OnUserUpdate(2.f);
Assert::AreEqual(60U,player->GetShield(),L"Player has 60 shield points.");
testGame->SetElapsedTime(3.f);
testGame->OnUserUpdate(3.f);
Assert::AreEqual(0U,player->GetShield(),L"Player should lose the shield after 5 seconds.");
}
TEST_METHOD(PlayerSubtractShieldCheck){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
Assert::AreEqual(60U,player->GetShield(),L"Player has 60 shield points.");
player->SubtractShield(40U);
Assert::AreEqual(20U,player->GetShield(),L"Player has 20 shield points.");
player->SubtractShield(200U);
Assert::AreEqual(0U,player->GetShield(),L"Player should have 0 shield points.");
}
TEST_METHOD(PlayerDamageShieldCheck){
player=testGame->GetPlayer();
player->AddShield(60U,5.f,PlayerTimerType::ADVANCE_SHIELD_TIMER);
Assert::AreEqual(60U,player->GetShield(),L"Player has 60 shield points.");
player->Hurt(20U,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(40U,player->GetShield(),L"Player has 40 shield points.");
Assert::AreEqual(player->GetMaxHealth(),player->GetHealth(),L"Player should still be full health.");
player->Hurt(200U,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(0U,player->GetShield(),L"Player has 0 shield points.");
Assert::AreEqual(player->GetMaxHealth(),player->GetHealth(),L"Player should still be full health.");
player->Hurt(10U,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(player->GetMaxHealth()-10,player->GetHealth(),L"Player now takes damage with 0 shield.");
}
};
}
}

@ -56,4 +56,8 @@ Ability::Ability(std::string name,std::string shortName,std::string description,
const float Ability::GetCooldownTime()const{
return COOLDOWN_TIME;
}
const bool Ability::operator==(const Ability&a)const{
return name==a.name&&isOriginalAbility==a.isOriginalAbility;
}

@ -62,6 +62,8 @@ struct Ability{
std::string description="";
float cooldown=0;
float COOLDOWN_TIME=0;
uint8_t charges{1};
uint8_t MAX_CHARGES{1};
int manaCost=0;
Pixel barColor1,barColor2;
PrecastData precastInfo;
@ -73,12 +75,15 @@ struct Ability{
//If set to true, this ability instead activates immediately when a cast occurs. When the cast finishes, nothing happens instead.
bool actionPerformedDuringCast=false;
bool waitForRelease=false;
bool keyReleaseRequiredToReactivate{false}; //When the player presses an ability, they cannot use it again until they have released the key to press it down again. So this gets set to true everytime an ability activates, and set to false once the player releases that key.
bool isOriginalAbility{false};
//Ability action function, returns true if the ability can be casted, otherwise returns false.
// Argument 1: Player* - player pointer
// Argument 2: vf2d - The returned precast target position (if the ability needs to be aimed, otherwise {})
std::function<bool(Player*,vf2d)>action=[](Player*,vf2d){return false;};
static InputGroup DEFAULT;
const float GetCooldownTime()const;
const bool operator==(const Ability&a)const;
Ability();
//NOTE: icon expects the actual name relative to the "Ability Icons" directory for this constructor!
Ability(std::string name,std::string shortName,std::string description,float cooldownTime,int manaCost,InputGroup*input,std::string icon,Pixel barColor1=VERY_DARK_RED,Pixel barColor2=DARK_RED,PrecastData precastInfo={},bool canCancelCast=false);

@ -29,6 +29,24 @@
"object"
]
},
{
"id": 43,
"name": "AudioEvent",
"storageType": "string",
"type": "enum",
"values": [
"LowHealth",
"TitleScreenLoaded",
"BlacksmithUnlock",
"Chapter2Unlock",
"Chapter3Unlock",
"BossFanfare",
"BossDefeated",
"PreBossPhase",
"Default Volume"
],
"valuesAsFlags": false
},
{
"id": 30,
"name": "Backdrop",
@ -52,7 +70,11 @@
"foresty1_1",
"overworld",
"foresty_boss",
"base_camp"
"base_camp",
"mountain",
"mountain_boss",
"beach",
"beach_boss"
],
"valuesAsFlags": false
},
@ -175,7 +197,11 @@
"Birds2",
"Birds3",
"Birds4",
"Campfire"
"Campfire",
"Crashing Waves",
"Gull1",
"Gull2",
"Gull3"
],
"valuesAsFlags": false
},
@ -269,14 +295,28 @@
"BOSS_2",
"BOSS_2_B",
"STORY_2_1",
"STORY_2_2"
"STORY_2_2",
"NONE",
"CAMPAIGN_3_1",
"CAMPAIGN_3_2",
"CAMPAIGN_3_3",
"CAMPAIGN_3_4",
"CAMPAIGN_3_5",
"CAMPAIGN_3_6",
"CAMPAIGN_3_7",
"CAMPAIGN_3_8",
"CAMPAIGN_3_B1",
"BOSS_3",
"BOSS_3_B",
"STORY_3_1",
"STORY_3_2"
],
"valuesAsFlags": false
},
{
"id": 25,
"name": "LevelType",
"storageType": "string",
"storageType": "int",
"type": "enum",
"values": [
"Dungeon",
@ -319,6 +359,12 @@
"drawFill": true,
"id": 19,
"members": [
{
"name": "Audio Event",
"propertyType": "AudioEvent",
"type": "string",
"value": "Default Volume"
},
{
"name": "Backdrop",
"propertyType": "Backdrop",
@ -369,8 +415,8 @@
{
"name": "Level Type",
"propertyType": "LevelType",
"type": "string",
"value": "World Map"
"type": "int",
"value": 4
},
{
"name": "Optimize",

@ -130,6 +130,7 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">
<OutDir>$(SolutionDir)$(PlatformTarget)\Release</OutDir>
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IncludePath>$(IncludePath)</IncludePath>
@ -140,11 +141,19 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>$(VCInstallDir)Auxiliary\VS\UnitTest\include;$(IncludePath)</IncludePath>
<LibraryPath>$(VCInstallDir)Auxiliary\VS\UnitTest\lib;$(LibraryPath)</LibraryPath>
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'">
<IncludePath>$(VCInstallDir)Auxiliary\VS\UnitTest\include;$(IncludePath)</IncludePath>
<LibraryPath>$(VCInstallDir)Auxiliary\VS\UnitTest\lib;$(LibraryPath)</LibraryPath>
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Emscripten|x64'">
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Emscripten Debug|x64'">
<CodeAnalysisRuleSet>..\CodeAnalysisRuleset.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
@ -218,8 +227,7 @@
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\LabUser\Documents\include;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>/MP20 %(AdditionalOptions)</AdditionalOptions>
<AdditionalIncludeDirectories>C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;$(ProjectDir)steam;$(ProjectDir)discord-files;C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\LabUser\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\niconiconii\OneDrive\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<TreatSpecificWarningsAsErrors>4099;5030;4715;4172;4834</TreatSpecificWarningsAsErrors>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
@ -244,8 +252,7 @@
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include</AdditionalIncludeDirectories>
<AdditionalOptions>/MP20 %(AdditionalOptions)</AdditionalOptions>
<AdditionalIncludeDirectories>$(ProjectDir)steam;$(ProjectDir)discord-files;C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\LabUser\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\niconiconii\OneDrive\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<TreatSpecificWarningsAsErrors>4099;5030;4715;4172;4834</TreatSpecificWarningsAsErrors>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
@ -275,8 +282,7 @@
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/MP20 %(AdditionalOptions)</AdditionalOptions>
<AdditionalIncludeDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;$(ProjectDir)steam;$(ProjectDir)discord-files;C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\LabUser\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\niconiconii\OneDrive\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
@ -368,6 +374,10 @@
<ClInclude Include="Ability.h" />
<ClInclude Include="AccessoryRowItemDisplay.h" />
<ClInclude Include="Animation.h" />
<ClInclude Include="Arc.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Attributable.h" />
<ClInclude Include="AttributableStat.h">
<SubType>
@ -381,6 +391,10 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="BlackHole.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="BombBoom.h">
<SubType>
</SubType>
@ -390,6 +404,14 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="DynamicMenuLabel.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Entity.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="HurtDamageInfo.h">
<SubType>
</SubType>
@ -511,12 +533,20 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MenuDecal.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MenuDefinitions.h" />
<ClInclude Include="MenuItemLabel.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MenuItemLoadoutButton.h" />
<ClInclude Include="MenuRefineLabel.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MenuType.h">
<SubType>
</SubType>
@ -527,6 +557,10 @@
</ClInclude>
<ClInclude Include="MonsterData.h" />
<ClInclude Include="olcPGEX_SplashScreen.h" />
<ClInclude Include="Oscillator.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Overlay.h">
<SubType>
</SubType>
@ -585,6 +619,10 @@
<ClInclude Include="olcUTIL_Geometry2D.h" />
<ClInclude Include="Pathfinding.h" />
<ClInclude Include="Player.h" />
<ClInclude Include="PlayerTimerType.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="PopupMenuLabel.h">
<SubType>
</SubType>
@ -707,10 +745,15 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Timer.h" />
<ClInclude Include="TitleScreen.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="TrailEffect.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Tutorial.h" />
<ClInclude Include="VisualNovel.h" />
<ClInclude Include="Test.h" />
@ -729,11 +772,11 @@
<ItemGroup>
<ClCompile Include="Ability.cpp" />
<ClCompile Include="Animation.cpp" />
<ClCompile Include="Arrow.cpp" />
<ClCompile Include="ArtificerDisassembleConfirmWindow.cpp">
<ClCompile Include="Arc.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Arrow.cpp" />
<ClCompile Include="ArtificerDisassembleWindow.cpp">
<SubType>
</SubType>
@ -746,7 +789,7 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ArtificerRefineConfirmWindow.cpp">
<ClCompile Include="ArtificerRefineResultWindow.cpp">
<SubType>
</SubType>
</ClCompile>
@ -795,10 +838,49 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="BurstBullet.cpp" />
<ClCompile Include="CollectedCoinEffect.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Crab.cpp" />
<ClCompile Include="Entity.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ExplosiveTrap.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="FadeInOutEffect.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="FlipCoinEffect.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="GhostSaber.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="GhostOfPirateCaptain.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="GiantCrab.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="GiantOctopus.cpp" />
<ClCompile Include="HomingBullet.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="HubPauseMenu.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="IBullet.cpp" />
<ClCompile Include="BuyItemWindow.cpp">
<SubType>
@ -921,6 +1003,14 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Ink.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="InkBullet.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="InputHelper.cpp">
<SubType>
</SubType>
@ -952,7 +1042,15 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ItemHubLoadout.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ItemLoadoutWindow.cpp" />
<ClCompile Include="ItemScript.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Key.cpp" />
<ClCompile Include="LargeStone.cpp" />
<ClCompile Include="LargeTornado.cpp">
@ -1000,10 +1098,15 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="MonsterSoul.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="NPC.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="OctopusArm.cpp" />
<ClCompile Include="Overlay.cpp">
<SubType>
</SubType>
@ -1014,15 +1117,39 @@
</SubType>
</ClCompile>
<ClCompile Include="packkey.cpp" />
<ClCompile Include="Parrot.cpp" />
<ClCompile Include="PauseMenu.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Pirates_Treasure.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Pirates_Coin.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Pirate_Buccaneer.cpp" />
<ClCompile Include="Pirate_Captain.cpp" />
<ClCompile Include="Pirate_Marauder.cpp" />
<ClCompile Include="Pixel.cpp" />
<ClCompile Include="PoisonBottle.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="RotateBullet.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ThrownProjectile.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="PoisonPool.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="PurpleEnergyBall.cpp">
<SubType>
</SubType>
@ -1041,6 +1168,7 @@
<ClCompile Include="PulsatingFire.cpp" />
<ClCompile Include="Ranger.cpp" />
<ClCompile Include="RUN_STRATEGY.cpp" />
<ClCompile Include="Sandworm.cpp" />
<ClCompile Include="SaveFile.cpp">
<SubType>
</SubType>
@ -1049,6 +1177,7 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Seagull.cpp" />
<ClCompile Include="SellItemWindow.cpp">
<SubType>
</SubType>
@ -1072,7 +1201,7 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="FallingStone.cpp">
<ClCompile Include="FallingBullet.cpp">
<SubType>
</SubType>
</ClCompile>
@ -1127,6 +1256,10 @@
</ClCompile>
<ClCompile Include="Test.cpp" />
<ClCompile Include="Thief.cpp" />
<ClCompile Include="Timer.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="TitleScreen.cpp" />
<ClCompile Include="Tornado.cpp">
<SubType>
@ -1154,7 +1287,23 @@
<ClCompile Include="Zephy.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="..\commit.bat" />
<None Include="..\debug.sh" />
<None Include="..\debugGame.sh" />
<None Include="..\distribute.ps1" />
<None Include="..\distribute.sh" />
<None Include="..\emscripten_build.ps1" />
<None Include="..\emscripten_build.sh" />
<None Include="..\emscripten_debug_build.ps1" />
<None Include="..\emscripten_debug_build.sh" />
<None Include="..\emscripten_run.ps1" />
<None Include="..\emscripten_run.sh" />
<None Include="..\read_debug_log.ps1" />
<None Include="..\release.sh" />
<None Include="..\runGame - NO STEAM.bat" />
<None Include="..\runGame.bat" />
<None Include="..\runGame.sh" />
<None Include="..\unit-testing-prebuild.ps1" />
<None Include="ClassDiagram.cd" />
<None Include="ClassDiagram2.cd" />
<None Include="cpp.hint" />
@ -1162,6 +1311,7 @@
<None Include="steam\steam_api.json" />
</ItemGroup>
<ItemGroup>
<Text Include="..\x64\Unit Testing\debug.log" />
<Text Include="assets\config\Achievements.txt" />
<Text Include="assets\config\audio\audio.txt" />
<Text Include="assets\config\audio\bgm.txt" />
@ -1212,8 +1362,10 @@
<Text Include="Chapter_1_Creatures_Part_2.txt" />
<Text Include="Chapter_2_Boss.txt" />
<Text Include="Chapter_2_Monsters.txt" />
<Text Include="Chapter_3_FinalBoss.txt" />
<Text Include="Chapter_3_Monsters.txt" />
<Text Include="characters.txt" />
<Text Include="ConsoleCommands.txt" />
<Text Include="Crawler_2_Bonus_Boss.txt" />
<Text Include="Crawler_Artificer.txt" />
<Text Include="Crawler_System_Overview.txt" />
@ -1222,6 +1374,7 @@
<Text Include="Merchant%27s Items.txt" />
<Text Include="NewClasses.txt" />
<Text Include="InitialConcept.txt" />
<Text Include="Oktopus boss.txt" />
<Text Include="Slime_King_Encounter.txt" />
<Text Include="StatCalculations.txt" />
<Text Include="TODO.txt" />

@ -97,6 +97,15 @@
<Filter Include="Header Files\Engine">
<UniqueIdentifier>{aaa148fb-5e34-4c35-a5bf-65ee8f2c0fb1}</UniqueIdentifier>
</Filter>
<Filter Include="Documentation\Admin">
<UniqueIdentifier>{e565fb16-43e6-4d18-a450-af7474df70b9}</UniqueIdentifier>
</Filter>
<Filter Include="Scripts">
<UniqueIdentifier>{eba9dd86-1d5d-4c68-8dcb-760c759099c0}</UniqueIdentifier>
</Filter>
<Filter Include="Debug">
<UniqueIdentifier>{c4119802-3fc8-4555-9013-a7a3ac9b204d}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="olcPixelGameEngine.h">
@ -228,9 +237,6 @@
<ClInclude Include="MenuAnimatedIconToggleButton.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="Toggleable.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="ConnectionPoint.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -675,6 +681,39 @@
<ClInclude Include="ItemEnchant.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PlayerTimerType.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Toggleable.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Timer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="BlackHole.h">
<Filter>Source Files\Effects</Filter>
</ClInclude>
<ClInclude Include="TrailEffect.h">
<Filter>Source Files\Effects</Filter>
</ClInclude>
<ClInclude Include="Oscillator.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="MenuDecal.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="MenuRefineLabel.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="DynamicMenuLabel.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="Arc.h">
<Filter>Header Files\Utils</Filter>
</ClInclude>
<ClInclude Include="Entity.h">
<Filter>Header Files\Utils</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Player.cpp">
@ -1133,7 +1172,7 @@
<ClCompile Include="RockLaunch.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="FallingStone.cpp">
<ClCompile Include="FallingBullet.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="ShineEffect.cpp">
@ -1157,7 +1196,7 @@
<ClCompile Include="PurpleEnergyBall.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="PoisonBottle.cpp">
<ClCompile Include="ThrownProjectile.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="State_Dialog.cpp">
@ -1169,15 +1208,9 @@
<ClCompile Include="ArtificerRefineWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="ArtificerRefineConfirmWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="ArtificerDisassembleWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="ArtificerDisassembleConfirmWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="ArtificerEnchantWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
@ -1187,6 +1220,102 @@
<ClCompile Include="ItemEnchant.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MonsterSoul.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="Timer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FadeInOutEffect.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="PoisonPool.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="ArtificerRefineResultWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="Pirate_Marauder.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Crab.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Parrot.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Pirate_Buccaneer.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Pirate_Captain.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Sandworm.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Seagull.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="GiantCrab.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="ItemScript.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="PoisonBottle.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="GiantOctopus.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="OctopusArm.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Arc.cpp">
<Filter>Source Files\Utils</Filter>
</ClCompile>
<ClCompile Include="BurstBullet.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="Entity.cpp">
<Filter>Source Files\Utils</Filter>
</ClCompile>
<ClCompile Include="RotateBullet.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="InkBullet.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="Ink.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="HomingBullet.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="HubPauseMenu.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="ItemHubLoadout.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="GhostOfPirateCaptain.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="GhostSaber.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="Pirates_Treasure.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Pirates_Coin.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="FlipCoinEffect.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="CollectedCoinEffect.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />
@ -1199,6 +1328,54 @@
<Filter>Header Files\steam</Filter>
</None>
<None Include="..\read_debug_log.ps1" />
<None Include="..\commit.bat">
<Filter>Scripts</Filter>
</None>
<None Include="..\debug.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\debugGame.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\distribute.ps1">
<Filter>Scripts</Filter>
</None>
<None Include="..\distribute.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_build.ps1">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_build.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_debug_build.ps1">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_debug_build.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_run.ps1">
<Filter>Scripts</Filter>
</None>
<None Include="..\emscripten_run.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\release.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\runGame - NO STEAM.bat">
<Filter>Scripts</Filter>
</None>
<None Include="..\runGame.bat">
<Filter>Scripts</Filter>
</None>
<None Include="..\runGame.sh">
<Filter>Scripts</Filter>
</None>
<None Include="..\unit-testing-prebuild.ps1">
<Filter>Scripts</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Text Include="InitialConcept.txt">
@ -1388,6 +1565,18 @@
<Text Include="assets\config\items\ItemEnchants.txt">
<Filter>Configurations\Items</Filter>
</Text>
<Text Include="ConsoleCommands.txt">
<Filter>Documentation\Admin</Filter>
</Text>
<Text Include="..\x64\Unit Testing\debug.log">
<Filter>Debug</Filter>
</Text>
<Text Include="Oktopus boss.txt">
<Filter>Documentation\Mechanics</Filter>
</Text>
<Text Include="Chapter_3_FinalBoss.txt">
<Filter>Documentation\Mechanics</Filter>
</Text>
</ItemGroup>
<ItemGroup>
<Image Include="assets\heart.ico">

File diff suppressed because it is too large Load Diff

@ -60,22 +60,28 @@ All rights reserved.
#include "Overlay.h"
#include <variant>
#undef KEY_MENU
#undef KEY_SELECT
#undef KEY_SCROLLDOWN
#undef KEY_SCROLLUP
#undef KEY_ENTER
class SteamKeyboardCallbackHandler;
class SteamStatsReceivedHandler;
#define CreateBullet(type) INCLUDE_BULLET_LIST \
BULLET_LIST.push_back(std::make_unique<type>(type
#define EndBullet ));
INCLUDE_BULLET_LIST
#define CreateBullet(type) \
(*((type*const)BULLET_LIST.emplace_back(std::make_unique<type>(type
#define EndBullet )).get()))
using HurtReturnValue=bool;
using HurtList=std::vector<std::pair<std::variant<Monster*,Player*>,HurtReturnValue>>;
using HurtListItem=std::pair<std::variant<Monster*,Player*>,HurtReturnValue>;
using HurtList=std::vector<HurtListItem>;
using StackCount=uint8_t;
using MarkTime=float;
enum class HurtType{
PLAYER=0b01,
MONSTER=0b10,
};
using ForegroundWrapper=std::reference_wrapper<Effect>;
using BackgroundWrapper=std::reference_wrapper<Effect>;
enum class KnockbackCondition{
KNOCKBACK_HURT_TARGETS, //Knockback only targets that took damage.
@ -95,12 +101,23 @@ class AiL : public olc::PixelGameEngine
friend class sig::Animation;
friend class Audio;
friend class Minimap;
friend class MonsterTests::MonsterTest;
friend class PlayerTests::PlayerTest;
friend class ItemTests::ItemTest;
friend class MiniAudio;
friend class Arc;
std::unique_ptr<Player>player;
SplashScreen splash;
public:
class Notification{
std::string message;
float time;
Pixel col;
public:
#undef GetMessage
Notification(const std::string_view message,const float time,const Pixel col);
void Update(const float fElapsedTime);
const bool Expired()const;
const bool operator==(const Notification&n)const;
void Draw(PixelGameEngine&pge);
};
enum MusicChange{
NO_MUSIC_CHANGE,
PLAY_LEVEL_MUSIC,
@ -151,11 +168,11 @@ public:
double levelTime=0.;
Camera2D camera;
std::map<MapName,Map>MAP_DATA;
ResourcePack gamepack;
private:
std::vector<std::unique_ptr<Effect>>foregroundEffects,backgroundEffects,foregroundEffectsToBeInserted,backgroundEffectsToBeInserted;
std::vector<std::unique_ptr<Effect>>foregroundEffects,backgroundEffects,foregroundEffectsToBeInserted,backgroundEffectsToBeInserted;
std::vector<TileRenderData*>tilesWithCollision,tilesWithoutCollision;
std::vector<int>dropsBeforeLower,dropsAfterLower,dropsBeforeUpper,dropsAfterUpper;
std::vector<ZoneData>endZones,upperEndZones;
std::vector<vf2d>circleCooldownPoints;
std::vector<vf2d>squareCircleCooldownPoints;
std::map<std::string,TilesetData>MAP_TILESETS;
@ -170,11 +187,7 @@ private:
std::vector<TileGroup>upperForegroundTileGroups;
int bridgeLayerIndex=-1;
float bridgeFadeFactor=0.f;
void InitializeClasses();
int DEBUG_PATHFINDING=0;
std::vector<Monster*>monstersBeforeLower,monstersAfterLower,monstersBeforeUpper,monstersAfterUpper;
std::vector<IBullet*>bulletsLower,bulletsUpper;
std::vector<Effect*>backgroundEffectsLower,backgroundEffectsUpper,foregroundEffectsLower,foregroundEffectsUpper;
float reflectionUpdateTimer=0;
float reflectionStepTime=0;
std::set<vi2d>visibleTiles;
@ -189,7 +202,7 @@ private:
float fadeOutDuration=0;
States::State transitionState=States::State::GAME_RUN;
bool gameEnd=false;
std::vector<Monster>monstersToBeSpawned;
std::vector<std::shared_ptr<Monster>>monstersToBeSpawned;
bool aMonsterIsMarkedForDeletion=false; //DO NOT MODIFY DIRECTLY! Use AMonsterIsMarkedForDeletion() instead!
time_t gameStarted;
std::function<void(std::string_view)>responseCallback;
@ -198,13 +211,14 @@ private:
bool disableFadeIn=false;
DynamicCounter healthCounter;
DynamicCounter manaCounter;
DynamicCounter shieldCounter;
int playerShieldDisplayAmt{};
Pixel worldColor=WHITE;
std::function<Pixel(vi2d)>worldColorFunc=[](vi2d pos){return WHITE;};
std::map<std::string,std::vector<::ZoneData>>ZONE_LIST;
float lastMouseMovement=0.f; //Amount of time since the last time the cursor was moved or interacted with.
vi2d lastMousePos={};
bool gameInitialized=false;
ResourcePack gamepack;
uint8_t mosaicEffectTransition=1U;
float saveGameDisplayTime=0.f;
float loadingWaitTime=0.f;
@ -230,7 +244,13 @@ private:
float targetZoom{1.f};
float zoomAdjustSpeed{0.1f};
std::vector<std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>>lockOnTargets;
std::vector<std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>>lockOnSpecialTargets;
float lastLockOnTargetTime{};
float lastLockOnSpecialTargetTime{};
void AdminConsole();
virtual bool OnConsoleCommand(const std::string& sCommand)override final;
Pixel vignetteOverlayCol{"Interface.Vignette Color"_Pixel};
std::vector<Notification>notifications;
public:
AiL(bool testingMode=false);
bool OnUserCreate() override;
@ -255,9 +275,10 @@ public:
void RenderHud();
void RenderMenu();
bool MenuClicksDeactivated()const;
void AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect>background);
std::pair<ForegroundWrapper,BackgroundWrapper>AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect>background);
//If back is true, places the effect in the background
void AddEffect(std::unique_ptr<Effect>foreground,bool back=false);
Effect&AddEffect(std::unique_ptr<Effect>foreground,bool back=false);
const std::vector<Effect*>GetEffect(EffectType type);
const HurtList Hurt(vf2d pos,float radius,int damage,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE)const;
const HurtList HurtMonsterType(vf2d pos,float radius,int damage,bool upperLevel,float z,const std::string_view monsterName,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE)const;
//NOTE: This function will also add any enemies that were hit into the hit list!
@ -314,6 +335,7 @@ public:
bool IsReflectiveTile(TilesheetData tileSheet,int tileID);
Monster&SpawnMonster(vf2d pos,MonsterData&data,bool upperLevel=false,bool isBossSpawn=false); //Queues a monster for spawning on the next frame.
void DrawPie(vf2d center,float radius,float degreesCut,Pixel col);
void DrawPieArc(const std::string_view texture,vf2d center,float radius,float degreesCut,Pixel col); //Draws an arc that has UV coordinates going from (0,0) in the center out to (1,1)
void DrawSquarePie(vf2d center,float radius,float degreesCut,Pixel col);
void RenderCooldowns();
void InitializeDefaultKeybinds();
@ -337,7 +359,7 @@ public:
int GetLoadoutSize()const;
void RestockLoadoutItems();
//Returns true if the item can be used (we have >0 of it)
bool UseLoadoutItem(int slot);
bool UseLoadoutItem(int slot,const std::optional<vf2d>targetingPos={});
//Blanks out this loadout item.
void ClearLoadoutItem(int slot);
void RenderFadeout();
@ -364,7 +386,7 @@ public:
void UpdateMonsters();
void ActivateActionSetForAllControllers(InputActionSetHandle_t actionSetHandle);
const float GetEncounterDuration()const;
void ShowDamageVignetteOverlay();
void ShowDamageVignetteOverlay(const Pixel="Interface.Vignette Color"_Pixel);
void GlobalGameUpdates();
const bool QuitRequested()const;
void SetQuitAllowed(bool quittingAllowed); //Locks the game from quitting during sensitive operations such as file saving/loading.
@ -379,13 +401,23 @@ public:
Overlay&GetOverlay();
void SetWindSpeed(vf2d newWindSpd);
const vf2d&GetWindSpeed()const;
void InitializeGameConfigurations();
void InitializePlayer();
void SetWorldZoom(float newZoomScale);
//Plays the correct footstep sound based on player's current tile.
void PlayFootstepSound();
const std::map<std::string,TilesetData>&GetTilesets()const;
void AddToMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData);
void AddToSpecialMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData);
void InitializeClasses();
void _SetCurrentLevel(const MapName map); //NOTE: This will modify the currentLevel variable without triggering anything else in-game, this will normally mess up the state in the game. Ideally this is only used when initializing a test level.
void InitializeCamera();
void AddNotification(const Notification&n);
void ResetLevelStates();
std::vector<Effect*>GetForegroundEffects()const;
std::vector<Effect*>GetBackgroundEffects()const;
std::vector<Effect*>GetAllEffects()const; //Foreground and background effects in one vector.
struct TileGroupData{
vi2d tilePos;
@ -394,4 +426,4 @@ public:
return layer<rhs.layer||(layer==rhs.layer&&tilePos<rhs.tilePos);
}
};
};
};

@ -48,7 +48,6 @@ INCLUDE_GFX
void sig::Animation::InitializeAnimations(){
ANIMATION_DATA.Reset();
auto CreateStillAnimation=[&](std::string imgName,vf2d size,AnimationData data={}){
Animate2D::FrameSequence anim(data.frameDuration,data.style);
anim.AddFrame({&GFX[imgName],{{0,0},size}});
@ -347,6 +346,28 @@ void sig::Animation::InitializeAnimations(){
ANIMATION_DATA["WITCH_TRANSFORM_W"]=pl_witch_transform_w;
ANIMATION_DATA["WITCH_TRANSFORM_E"]=pl_witch_transform_e;
Animate2D::FrameSequence pl_witch_cat_walk_s(0.2f);
for(int i=0;i<2;i++){
pl_witch_cat_walk_s.AddFrame({&GFX["nico-witch.png"],{vi2d{0+i,4}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAT_WALK_S"]=pl_witch_cat_walk_s;
Animate2D::FrameSequence pl_witch_cat_walk_n(0.2f);
for(int i=0;i<2;i++){
pl_witch_cat_walk_n.AddFrame({&GFX["nico-witch.png"],{vi2d{0+i,5}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAT_WALK_N"]=pl_witch_cat_walk_n;
Animate2D::FrameSequence pl_witch_cat_walk_w(0.2f);
for(int i=0;i<2;i++){
pl_witch_cat_walk_w.AddFrame({&GFX["nico-witch.png"],{vi2d{0+i,6}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAT_WALK_W"]=pl_witch_cat_walk_w;
Animate2D::FrameSequence pl_witch_cat_walk_e(0.2f);
for(int i=0;i<2;i++){
pl_witch_cat_walk_e.AddFrame({&GFX["nico-witch.png"],{vi2d{0+i,7}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAT_WALK_E"]=pl_witch_cat_walk_e;
CreateHorizontalAnimationSequence("ground-slam-attack-back.png",5,{64,64},{0.02f,Animate2D::Style::OneShot});
CreateHorizontalAnimationSequence("ground-slam-attack-front.png",5,{64,64},{0.02f,Animate2D::Style::OneShot});
CreateHorizontalAnimationSequence("battlecry_effect.png",5,{84,84},{0.02f,Animate2D::Style::OneShot});
@ -371,6 +392,7 @@ void sig::Animation::InitializeAnimations(){
CreateStillAnimation("chain_lightning.png",{1,9});
CreateHorizontalAnimationSequence("monstersoul.png",3,{24,24},AnimationData{.frameDuration{0.25f},.style{Animate2D::Style::Repeat}});
CreateHorizontalAnimationSequence("lightning_splash_effect.png",5,{24,24});
CreateHorizontalAnimationSequence("dagger_stab.png",2,{24,24},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::PingPong}});
CreateHorizontalAnimationSequence("goblin_sword_slash.png",3,{24,24},{0.05f,Animate2D::Style::OneShot});
@ -379,6 +401,9 @@ void sig::Animation::InitializeAnimations(){
CreateHorizontalAnimationSequence("bomb_boom.png",5,{36,36},AnimationData{.frameDuration{0.2f},.style{Animate2D::Style::OneShot}});
CreateHorizontalAnimationSequence("tornado2.png",4,{24,48},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}});
CreateHorizontalAnimationSequence("large_tornado.png",4,{72,144},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}});
CreateHorizontalAnimationSequence("sand_suction.png",4,{72,72},AnimationData{.frameDuration{0.2f},.style{Animate2D::Style::Repeat}});
CreateHorizontalAnimationSequence("bomb.png",4,{24,24},AnimationData{.frameDuration{0.2f},.style{Animate2D::Style::OneShot}});
CreateHorizontalAnimationSequence("ghost_dagger.png",3,{24,24},{0.1f,Animate2D::Style::PingPong});
CreateStillAnimation("meteor.png",{192,192});
@ -411,6 +436,14 @@ void sig::Animation::InitializeAnimations(){
ANIMATION_DATA[std::format("GOBLIN_BOW_ATTACK_{}",animationIndex)]=mountShootAnimation;
}
Animate2D::FrameSequence parrot_sit_n,parrot_sit_e,parrot_sit_s,parrot_sit_w;
//Idle sequences for the sitting parrot.
for(int animationIndex=0;animationIndex<4;animationIndex++){
Animate2D::FrameSequence mountAnimation{0.6f};
mountAnimation.AddFrame(Animate2D::Frame{&GFX["monsters/commercial_assets/Parrot_foreground.png"],{{0,animationIndex*48},{48,48}}});
ANIMATION_DATA[std::format("PARROT_MOUNTED_{}",animationIndex)]=mountAnimation;
}
#pragma region Trapper Target Mark Debuff
AnimationData targetAnimData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}};
Animate2D::FrameSequence targetAnim(targetAnimData.frameDuration,targetAnimData.style);
@ -418,12 +451,28 @@ void sig::Animation::InitializeAnimations(){
targetAnim.AddFrame({&GFX["target.png"],{{int(0*24),0},{24,24}}});
targetAnim.AddFrame({&GFX["target.png"],{{int(1*24),0},{24,24}}});
ANIMATION_DATA["target.png"]=targetAnim;
AnimationData specialTargetAnimData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}};
Animate2D::FrameSequence specialTargetAnim(specialTargetAnimData.frameDuration,specialTargetAnimData.style);
specialTargetAnim.AddFrame({&GFX["special_target.png"],{{int(0*24),0},{24,24}}});
specialTargetAnim.AddFrame({&GFX["special_target.png"],{{int(0*24),0},{24,24}}});
specialTargetAnim.AddFrame({&GFX["special_target.png"],{{int(1*24),0},{24,24}}});
ANIMATION_DATA["special_target.png"]=specialTargetAnim;
#pragma endregion
CreateHorizontalAnimationSequence("bear_trap.png",3,{24,24},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::PingPong}});
CreateHorizontalAnimationSequence("explosive_trap.png",4,{48,48},AnimationData{.frameDuration{0.06f},.style{Animate2D::Style::PingPong}});
CreateHorizontalAnimationSequence("explosionframes.png",21,{24,24},AnimationData{.frameDuration{0.02f},.style{Animate2D::Style::OneShot}});
CreateHorizontalAnimationSequence("fire_ring.png",5,{60,60},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}});
CreateHorizontalAnimationSequence("portal.png",8,{24,24},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}});
CreateHorizontalAnimationSequence("burstbullet.png",4,{24,24},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::OneShot}});
CreateHorizontalAnimationSequence("inkbubble_explode.png",4,{24,24},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::OneShot}});
//!!!!!WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//DO NOT CREATE MORE ANIMATION SEQUENCES UNDERNEATH HERE AS THE NEXT BLOCK WILL CREATE DEFAULT ANIMATIONS
//FOR ALL NON-SPECIFIED ANIMATION SEQUENCES!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
for(auto&dat:GFX){
std::string imgFile=dat.first;
if(!ANIMATION_DATA.count(imgFile)){

@ -0,0 +1,79 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Arc.h"
#include "AdventuresInLestoria.h"
INCLUDE_game
Arc::Arc(const vf2d pos,const float radius,const float pointingAngle,const float sweepAngle)
:pos(pos),radius(radius),pointingAngle(pointingAngle),sweepAngle(sweepAngle){
if(sweepAngle<0.f)ERR(std::format("WARNING! Sweep angle must be greater than or equal to 0! Provided Sweep Angle: {}",sweepAngle));
GenerateArc();
}
void Arc::Draw(AiL*game,const Pixel col){
game->SetDecalStructure(DecalStructure::FAN);
game->view.DrawPolygonDecal(nullptr,poly.pos,poly.pos,col);
}
const bool Arc::overlaps(const vf2d checkPos)const{
return geom2d::overlaps(checkPos,poly);
}
void Arc::GrowRadius(const float growAmt){
radius+=growAmt;
GenerateArc();
}
void Arc::GenerateArc(){
poly.pos.clear();
//Use cut-off point between two angles
poly.pos.emplace_back(pos); //Always add 0,0
float smallestAng{util::radToDeg(pointingAngle-sweepAngle)+90};
float largestAng{util::radToDeg(pointingAngle+sweepAngle)+90};
while(smallestAng<0.f)smallestAng+=360;
while(largestAng<0.f)largestAng+=360;
int startInd{int(std::fmod(smallestAng/4+1,90))};
int endInd{int(std::fmod(largestAng/4+1,90))};
if(startInd>endInd){
startInd=(startInd+3)%90;
}else{
endInd=(endInd+3)%90;
}
for(int i=startInd;i!=endInd;i=(i+1)%game->circleCooldownPoints.size()){
poly.pos.emplace_back(pos+game->circleCooldownPoints[i]*radius);
}
poly.pos.emplace_back(pos); //Connect back to itself.
}

@ -0,0 +1,59 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "olcUTIL_Geometry2D.h"
#include "Pixel.h"
class AiL;
class Arc{
public:
//Define a sweep angle such that each direction will arc that way. Example: PI/2 means a sweep angle from -PI/2 to PI/2. MUST BE POSITIVE
Arc(const vf2d pos,const float radius,const float pointingAngle,const float sweepAngle);
void Draw(AiL*game,const Pixel col);
const bool overlaps(const vf2d checkPos)const;
void GrowRadius(const float growAmt);
const vf2d pos;
const float pointingAngle;
const float sweepAngle;
float radius;
geom2d::polygon<float>poly;
private:
void GenerateArc();
};

@ -43,6 +43,7 @@ All rights reserved.
#include "olcUTIL_Geometry2D.h"
INCLUDE_game
INCLUDE_GFX
Arrow::Arrow(vf2d pos,vf2d targetPos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:finalDistance(geom2d::line(pos,targetPos).length()*1.2f),acc(PI/2*250),targetPos(targetPos),
@ -94,6 +95,7 @@ BulletDestroyState Arrow::PlayerHit(Player*player)
{
fadeOutTime=0.2f;
game->AddEffect(std::make_unique<Effect>(player->GetPos(),0,"splash_effect.png",upperLevel,player->GetSizeMult(),0.25));
if(poisonArrow)player->AddBuff(BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,"Poisonous Arrow"_ENC["POISON DURATION"],damage*("Poisonous Arrow"_ENC["POISON TICK DAMAGE"]/100.f),1.f);
return BulletDestroyState::KEEP_ALIVE;
}
@ -101,9 +103,22 @@ BulletDestroyState Arrow::MonsterHit(Monster&monster,const uint8_t markStacksBef
{
fadeOutTime=0.2f;
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),0.25));
if(poisonArrow)monster.AddBuff(BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,"Poisonous Arrow"_ENC["POISON DURATION"],damage*("Poisonous Arrow"_ENC["POISON TICK DAMAGE"]/100.f),1.f);
if(game->GetPlayer()->HasEnchant("Burning Arrow")&&markStacksBeforeHit>0&&monster.GetBuffs(BuffType::BURNING_ARROW_BURN).size()<"Burning Arrow"_ENC["MAX BURN STACK"])monster.AddBuff(BuffType::BURNING_ARROW_BURN,BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,"Burning Arrow"_ENC["BURN DURATION"],game->GetPlayer()->GetAttack()*"Burning Arrow"_ENC["BURN DAMAGE"]/100.f,2.f);
return BulletDestroyState::KEEP_ALIVE;
}
void Arrow::ModifyOutgoingDamageData(HurtDamageInfo&data){
if(friendly)data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
}
void Arrow::Draw(const Pixel blendCol)const{
if(poisonArrow){
game->SetDecalMode(DecalMode::ADDITIVE);
game->view.DrawRotatedDecal(pos,GFX["glow.png"].Decal(),image_angle,GFX["glow.png"].Sprite()->Size()/2,scale,{255,255,0,120});
game->SetDecalMode(DecalMode::NORMAL);
Bullet::Draw({128,192,0,blendCol.a});
}else{
Bullet::Draw(blendCol);
}
}

@ -38,15 +38,112 @@ All rights reserved.
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "RowInventoryScrollableWindowComponent.h"
#include "InventoryCreator.h"
#include "MenuItemItemButton.h"
#include "RowItemDisplay.h"
#include "MenuDecal.h"
#include "SoundEffect.h"
INCLUDE_game
void Menu::InitializeArtificerDisassembleWindow(){
Menu*artificerDisassembleWindow=CreateMenu(ARTIFICER_DISASSEMBLE,CENTERED,vi2d{144,144});
Menu*artificerDisassembleWindow=CreateMenu(ARTIFICER_DISASSEMBLE,CENTERED,game->GetScreenSize()-vi2d{52,52});
const auto ResetDisassemblyDisplay=[artificerDisassembleWindow](){
MenuType menuType{artificerDisassembleWindow->GetType()};
Component<MenuDecal>(menuType,"Down Arrow")->Disable();
Component<MenuIconButton>(menuType,"Disassembly Result")->Disable();
Component<MenuLabel>(menuType,"Disassembly Result Title")->Disable();
Component<MenuLabel>(menuType,"Fragment Total Count")->Disable();
Component<MenuComponent>(menuType,"Disassemble Button")->Disable();
};
const auto EnableDisassemblyDisplay=[artificerDisassembleWindow](){
MenuType menuType{artificerDisassembleWindow->GetType()};
Component<MenuDecal>(menuType,"Down Arrow")->Enable();
Component<MenuIconButton>(menuType,"Disassembly Result")->Enable();
Component<MenuLabel>(menuType,"Disassembly Result Title")->Enable();
Component<MenuLabel>(menuType,"Fragment Total Count")->Enable();
Component<MenuComponent>(menuType,"Disassemble Button")->Enable();
};
auto disassemblyTitleLabel{artificerDisassembleWindow->ADD("Disassembly Title Label",MenuLabel)(geom2d::rect<float>{{},{artificerDisassembleWindow->size.x,24.f}},"Accessory Disassembly",2.f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END};
auto inventoryLabel=artificerDisassembleWindow->ADD("Accessory List Label",MenuLabel)(geom2d::rect<float>{{0.f,28.f},{180.f,12.f}},"Choose Accessory:",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END;
auto itemIcon{artificerDisassembleWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>({artificerDisassembleWindow->size.x/2+4.f,44.f},{48,48}),Item::BLANK,DO_NOTHING,"","Item Description",IconButtonAttr::NOT_SELECTABLE)END};
itemIcon->SetIconScale({2.f,2.f});
itemIcon->SetCompactDescriptions(true);
auto accessoryDescription{artificerDisassembleWindow->ADD("Item Description",MenuLabel)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+56.f,44.f},{artificerDisassembleWindow->size.x/2-56.f,72.f}},"",0.5f,ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
auto downArrow{artificerDisassembleWindow->ADD("Down Arrow",MenuDecal)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+4.f,92.f},{48,48}},GFX.at("downarrow.png"),DO_NOTHING,DO_NOTHING)END};
auto disassemblyResult{artificerDisassembleWindow->ADD("Disassembly Result",MenuIconButton)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+4.f,141.f},{48,48}},nullptr,DO_NOTHING,IconButtonAttr::NOT_SELECTABLE)END};
auto disassemblyResultTitle{artificerDisassembleWindow->ADD("Disassembly Result Title",MenuLabel)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+56.f,141.f},{artificerDisassembleWindow->size.x/2-56.f,24.f}},"",2.f,ComponentAttr::BACKGROUND|ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::FIT_TO_LABEL)END};
auto disassemblyTotalCount{artificerDisassembleWindow->ADD("Fragment Total Count",MenuLabel)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+56.f,127.f},{artificerDisassembleWindow->size.x/2-56.f,12.f}},"",0.85f,ComponentAttr::RIGHT_ALIGN|ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL)END};
auto disassembleButton{artificerDisassembleWindow->ADD("Disassemble Button",MenuComponent)(geom2d::rect<float>{{artificerDisassembleWindow->size.x/2+72.f,171.f},{artificerDisassembleWindow->size.x/2-72.f,16.f}},"Disassemble",[ResetDisassemblyDisplay](MenuFuncData data){
auto it{Component<MenuItemItemButton>(data.menu.type,"Item Icon")->GetItem()};
if(Inventory::Disassemble(it)==Inventory::DisassembleResult::SUCCESS){
SoundEffect::PlaySFX("Disassemble Item",SoundEffect::CENTERED);
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(Item::BLANK);
Component<MenuIconButton>(data.menu.type,"Disassembly Result")->SetIcon(nullptr);
Component<MenuLabel>(data.menu.type,"Disassembly Result Title")->SetLabel("");
Component<MenuLabel>(data.menu.type,"Fragment Total Count")->SetLabel("");
ResetDisassemblyDisplay();
}else{
SoundEffect::PlaySFX("Locked Item",SoundEffect::CENTERED);
game->AddNotification(AiL::Notification{"Cannot disassemble locked accessory!",5.f,RED});
}
return true;
})END};
auto inventoryDisplay=artificerDisassembleWindow->ADD("Accessory List",RowInventoryScrollableWindowComponent)(geom2d::rect<float>{{0.f,44.f},{artificerDisassembleWindow->size.x/2-4.f,artificerDisassembleWindow->size.y-60}},"","",[](MenuFuncData data){
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->SelectChild(DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component));
return true;
},[EnableDisassemblyDisplay](MenuFuncData data){
EnableDisassemblyDisplay();
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem().lock());
Component<MenuIconButton>(data.menu.type,"Disassembly Result")->SetIcon(item.GetItem().lock()->Icon().Decal());
Component<MenuLabel>(data.menu.type,"Disassembly Result Title")->SetLabel(item.GetItem().lock()->FragmentName());
Component<MenuLabel>(data.menu.type,"Fragment Total Count")->SetLabel(std::format("Currently Owned: {}",Inventory::GetItemCount(item.GetItem().lock()->FragmentName())));
return true;
},[ResetDisassemblyDisplay,EnableDisassemblyDisplay](MenuFuncData data){
ResetDisassemblyDisplay();
auto childComponent{DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->GetSelectedChild()};
if(childComponent){
RowItemDisplay&item{childComponent.value().get()};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem().lock());
Component<MenuIconButton>(data.menu.type,"Disassembly Result")->SetIcon(item.GetItem().lock()->Icon().Decal());
Component<MenuLabel>(data.menu.type,"Disassembly Result Title")->SetLabel(item.GetItem().lock()->FragmentName());
Component<MenuLabel>(data.menu.type,"Fragment Total Count")->SetLabel(std::format("Currently Owned: {}",Inventory::GetItemCount(item.GetItem().lock()->FragmentName())));
EnableDisassemblyDisplay();
}
return true;
},
InventoryCreator::RowPlayer_InventoryUpdate,
InventoryWindowOptions{.padding=1,.size={artificerDisassembleWindow->size.x/2-5.f-12.f,28}})END;
auto backButton=artificerDisassembleWindow->ADD("Back",MenuComponent)(geom2d::rect<float>{{0.f,artificerDisassembleWindow->size.y-12.f},{96.f,16.f}},"Back",[](MenuFuncData data){
Menu::CloseMenu();
return true;
})END;
Menu::AddInventoryListener(inventoryDisplay,"Accessories");
ResetDisassemblyDisplay();
artificerDisassembleWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="";
auto&items{Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents()};
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else if(items.size()>0)returnData=items[0];
else returnData="Back";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -54,12 +151,49 @@ void Menu::InitializeArtificerDisassembleWindow(){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
{{game->KEY_SHOULDER2,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_FASTSCROLLUP,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->DecreaseSelectionIndex(3.f);
}}},
{{game->KEY_FASTSCROLLDOWN,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->IncreaseSelectionIndex(3.f);
}}},
}
,{ //Button Navigation Rules
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
{"Accessory List",{
.up=[&](MenuType type,Data&returnData){
Menu::ScrollUp(type,"Accessory List",returnData,"Back");
},
.down=[&](MenuType type,Data&returnData){
Menu::ScrollDown(type,"Accessory List",returnData,"Back");
},
.left="Back",
.right=[&](MenuType type,Data&returnData){
Menu::menus[type]->GetSelection().lock()->Click();
returnData="Disassemble Button";
},}},
{"Back",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left="Disassemble Button",
.right="Disassemble Button",}},
{"Disassemble Button",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left=[&](MenuType type,Data&returnData){
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else returnData="Back";
},
.right="Back",}},
}
);
}

@ -38,28 +38,81 @@ All rights reserved.
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "MenuIconButton.h"
#include "MenuItemLabel.h"
#include "SoundEffect.h"
INCLUDE_game
void Menu::InitializeArtificerEnchantConfirmWindow(){
Menu*artificerEnchantConfirmWindow=CreateMenu(ARTIFICER_ENCHANT_CONFIRM,CENTERED,vi2d{144,144});
Menu*artificerEnchantConfirmWindow=CreateMenu(ARTIFICER_ENCHANT_CONFIRM,CENTERED,vi2d{240,144});
auto enchantSuccessLabel{artificerEnchantConfirmWindow->ADD("Enchant Success Label",MenuLabel)(geom2d::rect<float>{{0.f,-14.f},vf2d{artificerEnchantConfirmWindow->size.x,12.f}},"Enchantment Success!",1.f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END};
auto chooseResultLabel{artificerEnchantConfirmWindow->ADD("Choose Result Label",MenuLabel)(geom2d::rect<float>{vf2d{0.f,0.f},vf2d{artificerEnchantConfirmWindow->size.x,12.f}},"Choose a Result",1.f,ComponentAttr::SHADOW)END};
auto backgroundOld{artificerEnchantConfirmWindow->ADD("Old Background",MenuLabel)(geom2d::rect<float>{{-2.f,12.f},{artificerEnchantConfirmWindow->size.x/2.f-4.f,124.f}},"",1.f,ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END};
auto backgroundNew{artificerEnchantConfirmWindow->ADD("New Background",MenuLabel)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/2.f-2.f+8.f,12.f},{artificerEnchantConfirmWindow->size.x/2.f-4.f,124.f}},"",1.f,ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END};
const float oldLabelTextWidth{game->GetTextSize("OLD").x*2.f};
auto oldLabel{artificerEnchantConfirmWindow->ADD("Old Item Label",MenuLabel)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/4.f-oldLabelTextWidth/2-4.f,14.f},vf2d{oldLabelTextWidth+8.f,24.f}},"OLD",2.f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND)END};
const float newLabelTextWidth{game->GetTextSize("NEW").x*2.f};
auto newLabel{artificerEnchantConfirmWindow->ADD("New Item Label",MenuLabel)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/2.f+artificerEnchantConfirmWindow->size.x/4.f-oldLabelTextWidth/2+2.f,14.f},vf2d{oldLabelTextWidth+8.f,24.f}},"NEW",2.f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND)END};
auto oldIcon{artificerEnchantConfirmWindow->ADD("Old Item Icon",MenuIconButton)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/4.f-16.f,42.f},{24,24}},nullptr,DO_NOTHING,IconButtonAttr::NOT_SELECTABLE|IconButtonAttr::NO_OUTLINE)END};
auto newIcon{artificerEnchantConfirmWindow->ADD("New Item Icon",MenuIconButton)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/2.f+artificerEnchantConfirmWindow->size.x/4.f-16.f+4.f,42.f},{24,24}},nullptr,DO_NOTHING,IconButtonAttr::NOT_SELECTABLE|IconButtonAttr::NO_OUTLINE)END};
auto oldItemDescription{artificerEnchantConfirmWindow->ADD("Old Item Description",MenuItemLabel)(geom2d::rect<float>{{0.f,74.f},{artificerEnchantConfirmWindow->size.x/2.f-8.f,60.f}},Item::BLANK,ItemLabelDescriptionType::ITEM_DESCRIPTION,0.5f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::LEFT_ALIGN)END};
auto newItemDescription{artificerEnchantConfirmWindow->ADD("New Item Description",MenuItemLabel)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/2.f+8.f,74.f},{artificerEnchantConfirmWindow->size.x/2.f-8.f,60.f}},Item::BLANK,ItemLabelDescriptionType::ITEM_DESCRIPTION,0.5f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::LEFT_ALIGN)END};
const float takeOldTextWidth{float(game->GetTextSize("Take Old").x)*2.f};
auto takeOldButton{artificerEnchantConfirmWindow->ADD("Take Old Button",MenuComponent)(geom2d::rect<float>{{artificerEnchantConfirmWindow->size.x/4.f-takeOldTextWidth/2.f,138.f},{takeOldTextWidth-8.f,20.f}},"Take Old",[](MenuFuncData data){
onClick:
std::any&oldEnchant{Menu::menus[ARTIFICER_ENCHANT]->ANY(A::ENCHANT)};
std::weak_ptr<Item>newItem{std::get<std::weak_ptr<Item>>(Component<MenuItemLabel>(data.menu.GetType(),"New Item Description")->GetItem())}; //NOTE: We're making an assumption here that the new item description holds a weak pointer. This should be true because the only way to get here was to set it up through clicking the Enchant button in Artificer Enchant Window.
if(oldEnchant.has_value())newItem.lock()->_EnchantItem(std::any_cast<ItemEnchant&>(oldEnchant));
else newItem.lock()->RemoveEnchant();
Component<MenuComponent>(ARTIFICER_ENCHANT,"Fragment Enchant Button")->SetGrayedOut(!newItem.lock()->CanBeEnchanted());
const std::string_view fragmentName{newItem.lock()->FragmentName()};
const Pixel fragmentItemDisplayCol{Inventory::GetItemCount(fragmentName)>="Fragment Enchant Cost"_i[0]?WHITE:RED};
const Pixel moneyCostDisplayCol{game->GetPlayer()->GetMoney()>="Fragment Enchant Cost"_i[1]?WHITE:RED};
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Label")->SetLabel(std::format("{}{} x{} ({})",fragmentItemDisplayCol.toHTMLColorCode(),fragmentName,"Fragment Enchant Cost"_i[0],Inventory::GetItemCount(fragmentName)));
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Money Cost Label")->SetLabel(std::format("{}{} gold",moneyCostDisplayCol.toHTMLColorCode(),"Fragment Enchant Cost"_i[1]));
SoundEffect::PlaySFX("Take Enchant",SoundEffect::CENTERED);
Menu::CloseMenu();
return true;
},vf2d{2.f,2.f})END};
const float takeNewTextWidth{float(game->GetTextSize("Take New").x)*2.f};
auto takeNewButton{artificerEnchantConfirmWindow->ADD("Take New Button",MenuComponent)(geom2d::rect<float>{vf2d{artificerEnchantConfirmWindow->size.x/2.f+8.f+artificerEnchantConfirmWindow->size.x/4.f-takeNewTextWidth/2.f,138.f},{takeNewTextWidth-8.f,20.f}},"Take New",[](MenuFuncData data){
onClick:
std::weak_ptr<Item>newItem{std::get<std::weak_ptr<Item>>(Component<MenuItemLabel>(data.menu.GetType(),"New Item Description")->GetItem())};
Component<MenuComponent>(ARTIFICER_ENCHANT,"Fragment Enchant Button")->SetGrayedOut(!newItem.lock()->CanBeEnchanted());
const std::string_view fragmentName{newItem.lock()->FragmentName()};
const Pixel fragmentItemDisplayCol{Inventory::GetItemCount(fragmentName)>="Fragment Enchant Cost"_i[0]?WHITE:RED};
const Pixel moneyCostDisplayCol{game->GetPlayer()->GetMoney()>="Fragment Enchant Cost"_i[1]?WHITE:RED};
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Label")->SetLabel(std::format("{}{} x{} ({})",fragmentItemDisplayCol.toHTMLColorCode(),fragmentName,"Fragment Enchant Cost"_i[0],Inventory::GetItemCount(fragmentName)));
Component<MenuLabel>(ARTIFICER_ENCHANT,"Fragment Money Cost Label")->SetLabel(std::format("{}{} gold",moneyCostDisplayCol.toHTMLColorCode(),"Fragment Enchant Cost"_i[1]));
SoundEffect::PlaySFX("Take Enchant",SoundEffect::CENTERED);
Menu::CloseMenu();
return true;
},vf2d{2.f,2.f})END};
artificerEnchantConfirmWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="";
returnData="Take New Button";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
{game->KEY_BACK,{"Stay",[](MenuType type){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
,{ //Button Navigation Rules
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
{"Take Old Button",{
.left="Take New Button",
.right="Take New Button",}},
{"Take New Button",{
.left="Take Old Button",
.right="Take Old Button",}},
});
}

@ -38,15 +38,144 @@ All rights reserved.
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "RowInventoryScrollableWindowComponent.h"
#include "MenuItemItemButton.h"
#include "MenuItemLabel.h"
#include "MenuDecal.h"
#include "PlayerMoneyLabel.h"
#include "SoundEffect.h"
INCLUDE_game
void Menu::InitializeArtificerEnchantWindow(){
Menu*artificerEnchantWindow=CreateMenu(ARTIFICER_ENCHANT,CENTERED,vi2d{144,144});
Menu*artificerEnchantWindow=CreateMenu(ARTIFICER_ENCHANT,CENTERED,game->GetScreenSize()-vi2d{52,52});
auto enchantingTitleLabel{artificerEnchantWindow->ADD("Enchanting Title Label",MenuLabel)(geom2d::rect<float>{{0.f,-16.f},{artificerEnchantWindow->size.x,24.f}},"Accessory Enchanting",2.f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END};
auto inventoryLabel{artificerEnchantWindow->ADD("Accessory List Label",MenuLabel)(geom2d::rect<float>{{0.f,12.f},{180.f,12.f}},"Choose Accessory:",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
auto itemIcon{artificerEnchantWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>({artificerEnchantWindow->size.x/2+4.f,16.f},{48,48}),Item::BLANK,DO_NOTHING,"","Item Description",IconButtonAttr::NOT_SELECTABLE)END};
itemIcon->SetIconScale({2.f,2.f});
itemIcon->SetCompactDescriptions(true);
auto accessoryDescription{artificerEnchantWindow->ADD("Item Description",MenuLabel)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+56.f,16.f},{artificerEnchantWindow->size.x/2-40.f,72.f}},"",0.5f,ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
const auto ResetEnchantDisplay{[artificerEnchantWindow](){
MenuType menuType{artificerEnchantWindow->GetType()};
Component<MenuItemItemButton>(menuType,"Item Icon")->SetItem(Item::BLANK);
Component<ScrollableWindowComponent>(menuType,"Enchant Container")->Disable();
Component<MenuLabel>(menuType,"Enchant List Header")->Disable();
Component<MenuLabel>(menuType,"Enchant Cost Label")->Disable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->Disable();
Component<MenuLabel>(menuType,"Fragment Label")->Disable();
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->Disable();
Component<MenuComponent>(menuType,"Fragment Enchant Button")->Disable();
}};
const auto EnableEnchantDisplay{[artificerEnchantWindow](){
MenuType menuType{artificerEnchantWindow->GetType()};
const std::weak_ptr<Item>&selectedItem{Component<MenuItemItemButton>(menuType,"Item Icon")->GetItem()};
std::vector<ItemEnchantInfo>availableEnchants{ItemEnchant::GetAvailableEnchants()};
std::sort(availableEnchants.begin(),availableEnchants.end(),[](const ItemEnchantInfo&enchant1,const ItemEnchantInfo&enchant2){
return enchant1.Category()!=enchant2.Category()?int(enchant1.Category())<int(enchant2.Category()):enchant1.Name()<enchant2.Name();
});
std::string enchantList{std::accumulate(availableEnchants.begin(),availableEnchants.end(),""s,[](const std::string&acc,const ItemEnchantInfo&enchant){
return std::format("{}{}{}#FFFFFF\n",acc,enchant.DisplayCol().toHTMLColorCode(),enchant.Name());
})};
Component<ScrollableWindowComponent>(menuType,"Enchant Container")->Enable();
Component<MenuLabel>(menuType,"Enchant List")->SetLabel(enchantList);
Component<MenuLabel>(menuType,"Enchant List Header")->Enable();
Component<MenuLabel>(menuType,"Enchant Cost Label")->Enable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->Enable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->SetImage(GFX.at(selectedItem.lock()->FragmentIcon().value()));
Component<MenuLabel>(menuType,"Fragment Label")->Enable();
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->Enable();
Component<MenuComponent>(menuType,"Fragment Enchant Button")->Enable();
Component<MenuComponent>(menuType,"Fragment Enchant Button")->SetGrayedOut(!selectedItem.lock()->CanBeEnchanted());
const std::string_view fragmentName{selectedItem.lock()->FragmentName()};
const Pixel fragmentItemDisplayCol{Inventory::GetItemCount(fragmentName)>="Fragment Enchant Cost"_i[0]?WHITE:RED};
const Pixel moneyCostDisplayCol{game->GetPlayer()->GetMoney()>="Fragment Enchant Cost"_i[1]?WHITE:RED};
Component<MenuLabel>(menuType,"Fragment Label")->SetLabel(std::format("{}{} x{} ({})",fragmentItemDisplayCol.toHTMLColorCode(),fragmentName,"Fragment Enchant Cost"_i[0],Inventory::GetItemCount(fragmentName)));
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->SetLabel(std::format("{}{} gold",moneyCostDisplayCol.toHTMLColorCode(),"Fragment Enchant Cost"_i[1]));
}};
auto inventoryDisplay{artificerEnchantWindow->ADD("Accessory List",RowInventoryScrollableWindowComponent)(geom2d::rect<float>{{0.f,28.f},{artificerEnchantWindow->size.x/2-4.f,artificerEnchantWindow->size.y-44}},"","",[](MenuFuncData data){
OnClick:
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->SelectChild(DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component));
return true;
},[EnableEnchantDisplay](MenuFuncData data){OnHover:
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem());
EnableEnchantDisplay();
return true;
},[ResetEnchantDisplay,EnableEnchantDisplay](MenuFuncData data){OnMouseOut:
ResetEnchantDisplay();
auto childComponent{DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->GetSelectedChild()};
if(childComponent){
RowItemDisplay&item{childComponent.value().get()};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem());
EnableEnchantDisplay();
}
return true;
},
InventoryCreator::RowPlayer_InventoryUpdate,
InventoryWindowOptions{.padding=1,.size={artificerEnchantWindow->size.x/2-5.f-12.f,28}})END};
auto enchantListHeaderLabel{artificerEnchantWindow->ADD("Enchant List Header",MenuLabel)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+4.f,92.f},{artificerEnchantWindow->size.x/2+12.f,12.f}},"Possible Enchantments:",1.f,ComponentAttr::FIT_TO_LABEL|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::SHADOW)END};
auto enchantContainer{artificerEnchantWindow->ADD("Enchant Container",ScrollableWindowComponent)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+4.f,104.f},{artificerEnchantWindow->size.x/2+12.f,44.f}})END};
auto enchantList{enchantContainer->ADD("Enchant List",MenuLabel)(geom2d::rect<float>{{0.f,2.f},{enchantContainer->GetSize().x-12.f,0.f}},"",1.f,ComponentAttr::CENTER|ComponentAttr::SHADOW)END};
auto enchantCostLabel{artificerEnchantWindow->ADD("Enchant Cost Label",MenuLabel)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+4.f,152.f},{64.f,20.f}},"Enchant Cost:",1.f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END};
auto fragmentCostIcon{artificerEnchantWindow->ADD("Fragment Cost Icon",MenuDecal)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+68.f,152.f},{12.f,12.f}})END};
auto fragmentDisplayLabel{artificerEnchantWindow->ADD("Fragment Label",MenuLabel)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+80.f,152.f},{artificerEnchantWindow->size.x/2-60.f,12.f}},"",1.f,ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL|ComponentAttr::LEFT_ALIGN)END};
auto fragmentMoneyCostLabel{artificerEnchantWindow->ADD("Fragment Money Cost Label",MenuLabel)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+80.f,164.f},{artificerEnchantWindow->size.x/2-60.f,12.f}},"",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
auto fragmentEnchantButton{artificerEnchantWindow->ADD("Fragment Enchant Button",MenuComponent)(geom2d::rect<float>{{artificerEnchantWindow->size.x/2+96.f,180.f},{artificerEnchantWindow->size.x/2-80.f,12.f}},"Enchant",[EnableEnchantDisplay](MenuFuncData data){
onClick:
EnableEnchantDisplay(); //Refresh the current display contents.
SoundEffect::PlaySFX("Enchant Item",SoundEffect::CENTERED);
std::weak_ptr<Item>item{Component<MenuItemItemButton>(data.menu.type,"Item Icon")->GetItem()};
std::optional<ItemEnchant>previousEnchant{item.lock()->GetEnchant()};
data.menu.ANY(A::ENCHANT)={};
if(previousEnchant.has_value())data.menu.ANY(A::ENCHANT)=previousEnchant.value();
Component<MenuIconButton>(ARTIFICER_ENCHANT_CONFIRM,"Old Item Icon")->SetIcon(item.lock()->Icon().Decal());
Component<MenuItemLabel>(ARTIFICER_ENCHANT_CONFIRM,"Old Item Description")->SetItem(*item.lock());
item.lock()->ApplyRandomEnchant();
const ItemEnchant&newEnchant{item.lock()->GetEnchant().value()};
Component<MenuIconButton>(ARTIFICER_ENCHANT_CONFIRM,"New Item Icon")->SetIcon(item.lock()->Icon().Decal());
Component<MenuItemLabel>(ARTIFICER_ENCHANT_CONFIRM,"New Item Description")->SetItem(item.lock());
Menu::OpenMenu(ARTIFICER_ENCHANT_CONFIRM,true);
return true;
})END};
#pragma region Money Display
vf2d moneyIconPos{artificerEnchantWindow->size.x/2-28.f,artificerEnchantWindow->size.y-12.f};
auto moneyIcon=artificerEnchantWindow->ADD("Money Icon",MenuIconButton)(geom2d::rect<float>{moneyIconPos,{24,24}},GFX["money.png"].Decal(),DO_NOTHING,IconButtonAttr::NOT_SELECTABLE|IconButtonAttr::NO_OUTLINE|IconButtonAttr::NO_BACKGROUND)END;
std::string moneyText=std::to_string(game->GetPlayer()->GetMoney());
vf2d moneyTextSize=game->GetTextSizeProp(moneyText)*2;
auto moneyDisplay=artificerEnchantWindow->ADD("Money Label",PlayerMoneyLabel)(geom2d::rect<float>{moneyIconPos+vf2d{26.f,4.f},moneyTextSize},2,1.85f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN|ComponentAttr::FIT_TO_LABEL)END;
Player::AddMoneyListener(moneyDisplay);
#pragma endregion
auto backButton{artificerEnchantWindow->ADD("Back",MenuComponent)(geom2d::rect<float>{{0.f,artificerEnchantWindow->size.y-12.f},{96.f,16.f}},"Back",[](MenuFuncData data){
Menu::CloseMenu();
return true;
})END};
ResetEnchantDisplay();
Menu::AddInventoryListener(inventoryDisplay,"Accessories");
artificerEnchantWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="";
auto&items{Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents()};
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else if(items.size()>0)returnData=items[0];
else returnData="Back";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -54,12 +183,52 @@ void Menu::InitializeArtificerEnchantWindow(){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
{{game->KEY_SHOULDER2,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_FASTSCROLLUP,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->DecreaseSelectionIndex(3.f);
}}},
{{game->KEY_FASTSCROLLDOWN,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->IncreaseSelectionIndex(3.f);
}}},
{{game->KEY_SCROLLVERT_R,Analog,InputEngageGroup::NOT_VISIBLE},{"Scroll Enchants",[](MenuType type){
Component<ScrollableWindowComponent>(type,"Enchant Container")->Scroll(game->KEY_SCROLLVERT.Analog());
}}},
}
,{ //Button Navigation Rules
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
{"Accessory List",{
.up=[&](MenuType type,Data&returnData){
Menu::ScrollUp(type,"Accessory List",returnData,"Back");
},
.down=[&](MenuType type,Data&returnData){
Menu::ScrollDown(type,"Accessory List",returnData,"Back");
},
.left="Back",
.right=[&](MenuType type,Data&returnData){
Menu::menus[type]->GetSelection().lock()->Click();
returnData="Fragment Enchant Button";
},}},
{"Back",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left="Fragment Enchant Button",
.right="Fragment Enchant Button",}},
{"Fragment Enchant Button",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left=[&](MenuType type,Data&returnData){
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else returnData="Back";
},
.right="Back",}},
}
);
}

@ -0,0 +1,75 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "MenuItemItemButton.h"
#include "DynamicMenuLabel.h"
INCLUDE_game
void Menu::InitializeArtificerRefineResultWindow(){
Menu*artificerRefineResultWindow=CreateMenu(ARTIFICER_REFINE_RESULT,CENTERED,vi2d{144,144});
auto itemIcon{artificerRefineResultWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>{{artificerRefineResultWindow->size.x/2.f-24.f,0.f},{48.f,48.f}},Item::BLANK,DO_NOTHING,"","",IconButtonAttr::NOT_SELECTABLE)END};
itemIcon->SetIconScale({2.f,2.f});
auto refineItemTextdisplay{artificerRefineResultWindow->ADD("Refine Item Text Display",MenuLabel)(geom2d::rect<float>{vf2d{0.f,artificerRefineResultWindow->size.y/2.f},vf2d{artificerRefineResultWindow->size.x,12.f}},"",1.f,ComponentAttr::SHADOW)END};
auto refineResultDisplay{artificerRefineResultWindow->ADD("Refine Result",DynamicMenuLabel)(geom2d::rect<float>{vf2d{0.f,artificerRefineResultWindow->size.y/2.f+artificerRefineResultWindow->size.y*0.33f},vf2d{artificerRefineResultWindow->size.x,12.f}},[](){return "";},1.f,ComponentAttr::SHADOW)END};
auto continueButton{artificerRefineResultWindow->ADD("Continue Button",MenuComponent)(geom2d::rect<float>{vf2d{artificerRefineResultWindow->size.x/4.f,artificerRefineResultWindow->size.y-6.f},{artificerRefineResultWindow->size.x/2.f,12.f}},"Continue",[](MenuFuncData data){
onClick:
Menu::CloseMenu();
return true;
})END};
artificerRefineResultWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="Continue Button";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
{game->KEY_BACK,{"Stay",[](MenuType type){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
,{ //Button Navigation Rules
});
}

@ -39,31 +39,128 @@ All rights reserved.
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "RowInventoryScrollableWindowComponent.h"
#include "MenuLabel.h"
#include "MenuItemItemButton.h"
#include "MenuRefineLabel.h"
#include "PlayerMoneyLabel.h"
#include "MenuDecal.h"
#include "SoundEffect.h"
#include "DynamicMenuLabel.h"
INCLUDE_game
void Menu::InitializeArtificerRefineWindow(){
Menu*artificerRefineWindow=CreateMenu(ARTIFICER_REFINE,CENTERED,game->GetScreenSize()-vi2d{52,52});
Menu*const artificerRefineWindow{CreateMenu(ARTIFICER_REFINE,CENTERED,game->GetScreenSize()-vi2d{52,52})};
auto refiningTitleLabel{artificerRefineWindow->ADD("Refining Title Label",MenuLabel)(geom2d::rect<float>{{0.f,-16.f},{artificerRefineWindow->size.x,24.f}},"Accessory Refinement",2.f,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END};
auto inventoryLabel{artificerRefineWindow->ADD("Accessory List Label",MenuLabel)(geom2d::rect<float>{{0.f,12.f},{180.f,12.f}},"Choose Accessory:",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
auto itemIcon{artificerRefineWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>({artificerRefineWindow->size.x/2+4.f,28.f},{48,48}),Item::BLANK,DO_NOTHING,"","Item Description",IconButtonAttr::NOT_SELECTABLE)END};
itemIcon->SetIconScale({2.f,2.f});
itemIcon->SetCompactDescriptions(true);
auto accessoryDescription{artificerRefineWindow->ADD("Item Description",MenuLabel)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+56.f,28.f},{artificerRefineWindow->size.x/2-40.f,72.f}},"",0.5f,ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE|ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
auto inventoryLabel=artificerRefineWindow->ADD("Accessory List Label",MenuLabel)(geom2d::rect<float>{{},{180.f,12.f}},"Choose Accessory:",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END;
const auto ResetRefineDisplay{[artificerRefineWindow](){
MenuType menuType{artificerRefineWindow->GetType()};
Component<MenuItemItemButton>(menuType,"Item Icon")->SetItem(Item::BLANK);
Component<MenuRefineLabel>(menuType,"Stats Block")->Disable();
Component<MenuLabel>(menuType,"Refine Cost Label")->Disable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->Disable();
Component<MenuLabel>(menuType,"Fragment Label")->Disable();
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->Disable();
Component<MenuComponent>(menuType,"Fragment Refine Button")->Disable();
}};
const auto EnableRefineDisplay{[artificerRefineWindow](){
MenuType menuType{artificerRefineWindow->GetType()};
const std::weak_ptr<Item>&selectedItem{Component<MenuItemItemButton>(menuType,"Item Icon")->GetItem()};
Component<MenuRefineLabel>(menuType,"Stats Block")->SetItem(selectedItem);
Component<MenuRefineLabel>(menuType,"Stats Block")->Enable();
Component<MenuLabel>(menuType,"Refine Cost Label")->Enable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->Enable();
Component<MenuDecal>(menuType,"Fragment Cost Icon")->SetImage(GFX.at(selectedItem.lock()->FragmentIcon().value()));
Component<MenuLabel>(menuType,"Fragment Label")->Enable();
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->Enable();
Component<MenuComponent>(menuType,"Fragment Refine Button")->Enable();
Component<MenuComponent>(menuType,"Fragment Refine Button")->SetGrayedOut(!selectedItem.lock()->CanBeRefined());
const std::string_view fragmentName{selectedItem.lock()->FragmentName()};
const Pixel fragmentItemDisplayCol{Inventory::GetItemCount(fragmentName)>="Fragment Refine Cost"_i[0]?WHITE:RED};
const Pixel moneyCostDisplayCol{game->GetPlayer()->GetMoney()>="Fragment Refine Cost"_i[1]?WHITE:RED};
Component<MenuLabel>(menuType,"Fragment Label")->SetLabel(std::format("{}{} x{} ({})",fragmentItemDisplayCol.toHTMLColorCode(),fragmentName,"Fragment Refine Cost"_i[0],Inventory::GetItemCount(fragmentName)));
Component<MenuLabel>(menuType,"Fragment Money Cost Label")->SetLabel(std::format("{}{} gold",moneyCostDisplayCol.toHTMLColorCode(),"Fragment Refine Cost"_i[1]));
}};
auto inventoryDisplay=artificerRefineWindow->ADD("Accessory List",RowInventoryScrollableWindowComponent)(geom2d::rect<float>{{0.f,16.f},{artificerRefineWindow->size.x/2-4.f,artificerRefineWindow->size.y-32}},"","",[](MenuFuncData data){
auto inventoryDisplay{artificerRefineWindow->ADD("Accessory List",RowInventoryScrollableWindowComponent)(geom2d::rect<float>{{0.f,28.f},{artificerRefineWindow->size.x/2-4.f,artificerRefineWindow->size.y-44}},"","",[](MenuFuncData data){
OnClick:
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->SelectChild(DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component));
return true;
},DO_NOTHING,DO_NOTHING,
},[EnableRefineDisplay](MenuFuncData data){OnHover:
RowItemDisplay&item{*DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component)};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem());
EnableRefineDisplay();
return true;
},[ResetRefineDisplay,EnableRefineDisplay](MenuFuncData data){OnMouseOut:
ResetRefineDisplay();
auto childComponent{DYNAMIC_POINTER_CAST<RowInventoryScrollableWindowComponent>(data.parentComponent.lock())->GetSelectedChild()};
if(childComponent){
RowItemDisplay&item{childComponent.value().get()};
Component<MenuItemItemButton>(data.menu.type,"Item Icon")->SetItem(item.GetItem());
EnableRefineDisplay();
}
return true;
},
InventoryCreator::RowPlayer_InventoryUpdate,
InventoryWindowOptions{.padding=1,.size={artificerRefineWindow->size.x/2-5.f,28}})END;
InventoryWindowOptions{.padding=1,.size={artificerRefineWindow->size.x/2-5.f-12.f,28}})END};
auto statsBlock{artificerRefineWindow->ADD("Stats Block",MenuRefineLabel)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+4.f,104.f},{artificerRefineWindow->size.x/2+12.f,44.f}},Item::BLANK,1.f,ComponentAttr::BACKGROUND|ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::FIXED_WIDTH_FONT|ComponentAttr::FIT_TO_LABEL|ComponentAttr::LEFT_ALIGN)END};
auto refineCostLabel{artificerRefineWindow->ADD("Refine Cost Label",MenuLabel)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+4.f,152.f},{64.f,20.f}},"Refine Cost:",1.f,ComponentAttr::SHADOW|ComponentAttr::BACKGROUND|ComponentAttr::OUTLINE)END};
auto backButton=artificerRefineWindow->ADD("Back",MenuComponent)(geom2d::rect<float>{{0.f,artificerRefineWindow->size.y-12.f},{120.f,12.f}},"Back",[](MenuFuncData data){
auto fragmentCostIcon{artificerRefineWindow->ADD("Fragment Cost Icon",MenuDecal)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+68.f,152.f},{12.f,12.f}})END};
auto fragmentDisplayLabel{artificerRefineWindow->ADD("Fragment Label",MenuLabel)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+80.f,152.f},{artificerRefineWindow->size.x/2-60.f,12.f}},"",1.f,ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL|ComponentAttr::LEFT_ALIGN)END};
auto fragmentMoneyCostLabel{artificerRefineWindow->ADD("Fragment Money Cost Label",MenuLabel)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+80.f,164.f},{artificerRefineWindow->size.x/2-60.f,12.f}},"",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END};
auto fragmentRefineButton{artificerRefineWindow->ADD("Fragment Refine Button",MenuComponent)(geom2d::rect<float>{{artificerRefineWindow->size.x/2+96.f,180.f},{artificerRefineWindow->size.x/2-80.f,12.f}},"Refine",[EnableRefineDisplay](MenuFuncData data){
onClick:
SoundEffect::PlaySFX("Refine Item",SoundEffect::CENTERED);
std::weak_ptr<Item>item{Component<MenuItemItemButton>(data.menu.type,"Item Icon")->GetItem()};
RefineResult result{item.lock()->Refine()};
Component<MenuItemItemButton>(ARTIFICER_REFINE_RESULT,"Item Icon")->SetItem(item);
Component<DynamicMenuLabel>(ARTIFICER_REFINE_RESULT,"Refine Result")->SetLabelUpdateFunction([result](){
const Pixel shimmeringColor{PixelLerp(WHITE,{220,220,220},sin((70*game->GetRunTime())/2.f+0.5f))};
return std::format("{} -> {}+{}{} #FFFF00UP!",result.first.Name(),shimmeringColor.toHTMLColorCode(),result.second,result.first.DisplayAsPercent()?"%":"");
});
Component<MenuLabel>(ARTIFICER_REFINE_RESULT,"Refine Item Text Display")->SetLabel(std::format("#FFFF00{} has been refined!",item.lock()->DisplayName()));
EnableRefineDisplay(); //Refresh the current display contents.
Menu::OpenMenu(ARTIFICER_REFINE_RESULT,true);
return true;
})END};
#pragma region Money Display
vf2d moneyIconPos{artificerRefineWindow->size.x/2-28.f,artificerRefineWindow->size.y-12.f};
auto moneyIcon=artificerRefineWindow->ADD("Money Icon",MenuIconButton)(geom2d::rect<float>{moneyIconPos,{24,24}},GFX["money.png"].Decal(),DO_NOTHING,IconButtonAttr::NOT_SELECTABLE|IconButtonAttr::NO_OUTLINE|IconButtonAttr::NO_BACKGROUND)END;
std::string moneyText=std::to_string(game->GetPlayer()->GetMoney());
vf2d moneyTextSize=game->GetTextSizeProp(moneyText)*2;
auto moneyDisplay=artificerRefineWindow->ADD("Money Label",PlayerMoneyLabel)(geom2d::rect<float>{moneyIconPos+vf2d{26.f,4.f},moneyTextSize},2,1.85f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN|ComponentAttr::FIT_TO_LABEL)END;
Player::AddMoneyListener(moneyDisplay);
#pragma endregion
auto backButton{artificerRefineWindow->ADD("Back",MenuComponent)(geom2d::rect<float>{{0.f,artificerRefineWindow->size.y-12.f},{96.f,16.f}},"Back",[](MenuFuncData data){
Menu::CloseMenu();
return true;
})END;
})END};
Menu::AddInventoryListener(inventoryDisplay,"Accessories");
artificerRefineWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="";
auto&items{Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents()};
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else if(items.size()>0)returnData=items[0];
else returnData="Back";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -71,12 +168,49 @@ void Menu::InitializeArtificerRefineWindow(){
Menu::CloseMenu();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
{{game->KEY_SHOULDER2,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_FASTSCROLLUP,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->DecreaseSelectionIndex(3.f);
}}},
{{game->KEY_FASTSCROLLDOWN,PressedDAS},{"Scroll",[](MenuType type){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->IncreaseSelectionIndex(3.f);
}}},
}
,{ //Button Navigation Rules
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
{"Accessory List",{
.up=[&](MenuType type,Data&returnData){
Menu::ScrollUp(type,"Accessory List",returnData,"Back");
},
.down=[&](MenuType type,Data&returnData){
Menu::ScrollDown(type,"Accessory List",returnData,"Back");
},
.left="Back",
.right=[&](MenuType type,Data&returnData){
Menu::menus[type]->GetSelection().lock()->Click();
returnData="Fragment Refine Button";
},}},
{"Back",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left="Fragment Refine Button",
.right="Fragment Refine Button",}},
{"Fragment Refine Button",{
.up=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().back().lock()->GetName();
},
.down=[&](MenuType type,Data&returnData){
returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetComponents().front().lock()->GetName();
},
.left=[&](MenuType type,Data&returnData){
if(Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild())returnData=Component<RowInventoryScrollableWindowComponent>(type,"Accessory List")->GetSelectedChild().value().get().GetName();
else returnData="Back";
},
.right="Back",}},
}
);
}

@ -43,22 +43,26 @@ All rights reserved.
#include "Unlock.h"
#include "MenuLabel.h"
#include "VisualNovel.h"
#include "RowInventoryScrollableWindowComponent.h"
INCLUDE_game
void Menu::InitializeArtificerWindow(){
Menu*artificerWindow=CreateMenu(ARTIFICER,CENTERED,vi2d{144,144});
artificerWindow->ADD("Refine Button",MenuComponent)(geom2d::rect<float>{{0.f,4.f},{144.f,24.f}},"Refine",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_REFINE);
artificerWindow->ADD("Disassemble Button",MenuComponent)(geom2d::rect<float>{{0.f,4.f},{144.f,24.f}},"Disassemble",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_DISASSEMBLE);
Component<RowInventoryScrollableWindowComponent>(MenuType::ARTIFICER_DISASSEMBLE,"Accessory List")->ClearSelectedChild();
return true;
},vf2d{2.f,2.f},ButtonAttr::FIT_TO_LABEL)END;
artificerWindow->ADD("Disassemble Button",MenuComponent)(geom2d::rect<float>{{0.f,32.f},{144.f,24.f}},"Disassemble",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_DISASSEMBLE);
artificerWindow->ADD("Refine Button",MenuComponent)(geom2d::rect<float>{{0.f,32.f},{144.f,24.f}},"Refine",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_REFINE);
Component<RowInventoryScrollableWindowComponent>(MenuType::ARTIFICER_REFINE,"Accessory List")->ClearSelectedChild();
return true;
},vf2d{2.f,2.f},ButtonAttr::FIT_TO_LABEL)END;
artificerWindow->ADD("Enchant Button",MenuComponent)(geom2d::rect<float>{{0.f,60.f},{144.f,24.f}},"Enchant",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_ENCHANT);
Component<RowInventoryScrollableWindowComponent>(MenuType::ARTIFICER_ENCHANT,"Accessory List")->ClearSelectedChild();
return true;
},vf2d{2.f,2.f},ButtonAttr::FIT_TO_LABEL)END;
artificerWindow->ADD("Help Button",MenuComponent)(geom2d::rect<float>{{0.f,88.f},{144.f,24.f}},"Help",[](MenuFuncData data){
@ -72,7 +76,7 @@ void Menu::InitializeArtificerWindow(){
artificerWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="Refine Button";
returnData="Disassemble Button";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -82,20 +86,20 @@ void Menu::InitializeArtificerWindow(){
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
,{ //Button Navigation Rules
{"Refine Button",{
.up="Leave Button",
.down="Disassemble Button",}},
{"Disassemble Button",{
.up="Refine Button",
.up="Leave Button",
.down="Refine Button",}},
{"Refine Button",{
.up="Disassemble Button",
.down="Enchant Button",}},
{"Enchant Button",{
.up="Disassemble Button",
.up="Refine Button",
.down="Help Button",}},
{"Help Button",{
.up="Enchant Button",
.down="Leave Button",}},
{"Leave Button",{
.up="Help Button",
.down="Refine Button",}},
.down="Disassemble Button",}},
});
}

@ -40,50 +40,58 @@ All rights reserved.
#include "DEFINES.h"
#include <variant>
using namespace std::literals;
class IAttributable{
public:
inline virtual ~IAttributable(){};
std::map<Attribute,std::variant<VARIANTS>>attributes;
std::map<Attribute,std::any>attributes;
inline float&GetFloat(Attribute a){
if(attributes.count(a)==0){
attributes[a]=0.f;
}
return std::get<float>(attributes[a]);
return std::any_cast<float&>(attributes[a]);
};
inline int&GetInt(Attribute a){
if(attributes.count(a)==0){
attributes[a]=0;
}
return std::get<int>(attributes[a]);
return std::any_cast<int&>(attributes[a]);
};
inline std::string&GetString(Attribute a){
if(attributes.count(a)==0){
attributes[a]="";
attributes[a]=""s;
}
return std::get<std::string>(attributes[a]);
return std::any_cast<std::string&>(attributes[a]);
};
inline bool&GetBool(Attribute a){
if(attributes.count(a)==0){
attributes[a]=false;
}
return std::get<bool>(attributes[a]);
return std::any_cast<bool&>(attributes[a]);
};
inline vf2d&GetVf2d(Attribute a){
if(attributes.count(a)==0){
attributes[a]=vf2d{};
}
return std::get<vf2d>(attributes[a]);
return std::any_cast<vf2d&>(attributes[a]);
};
inline std::vector<std::any>&GetVec(Attribute a){
if(attributes.count(a)==0){
attributes[a]=std::vector<std::any>{};
}
return std::get<std::vector<std::any>>(attributes[a]);
return std::any_cast<std::vector<std::any>&>(attributes[a]);
};
inline std::any&GetAny(Attribute a){
if(attributes.count(a)==0){
attributes[a]=std::any{};
}
return attributes[a];
};
inline size_t&GetSizeT(Attribute a){
if(attributes.count(a)==0){
attributes[a]=size_t(0);
}
return std::get<size_t>(attributes[a]);
return std::any_cast<size_t&>(attributes[a]);
};
};

@ -113,6 +113,9 @@ public:
inline auto end()const{
return attributes.end();
}
inline auto size()const{
return attributes.size();
}
inline void clear(){
attributes.clear();
}

@ -41,6 +41,7 @@ All rights reserved.
#include "util.h"
#include "LoadingScreen.h"
#include "Menu.h"
#include "SoundEffect.h"
INCLUDE_game
INCLUDE_DATA
@ -70,7 +71,12 @@ void Audio::Initialize(){
while(data.HasProperty(std::format("channel[{}]",channelCounter))){
std::string channelName=data[std::format("channel[{}]",channelCounter)].GetString();
if(!std::filesystem::exists("bgm_directory"_S+channelName))ERR(std::format("WARNING! Could not load file {} for track {}",channelName,songFileName));
if(!game->gamepack.Loaded()){
if(!std::filesystem::exists("bgm_directory"_S+channelName))ERR(std::format("WARNING! Could not load file {} for track {}",channelName,songFileName));
if("GENERATE_GAMEPACK"_B){
game->gamepack.AddFile("bgm_directory"_S+channelName);
}
}
bgm.AddChannel(channelName);
channelCounter++;
}
@ -83,6 +89,9 @@ void Audio::Initialize(){
bgm.SetFadeTime(defaultFadeTime);
}
if(data.HasProperty("Loop Repeat Start Point")){
bgm.SetLoopStartTime(data["Loop Repeat Start Point"].GetReal());
}
if(data.HasProperty("Events")){
for(auto&eventName:Self().events){
@ -118,8 +127,8 @@ MiniAudio&Audio::Engine(){
void Audio::Play(const std::string_view sound){
Engine().Play(std::string(sound));
};
const size_t Audio::LoadAndPlay(const std::string_view sound,const bool loop){
size_t soundID=Engine().LoadSound(std::string(sound));
const size_t Audio::LoadAndPlaySFX(const std::string_view sound,const bool loop){
size_t soundID=Engine().LoadSound(std::string(sound),MiniAudio::SFX);
Engine().Play(soundID,loop);
return soundID;
};
@ -175,6 +184,7 @@ void Audio::BGM::Load(){
}
Self().currentBGM=songFileName;
Self().currentLoopIndex=0;
Self().lastTimestamp=0.f;
BGM&newBgm=Self().bgm[songFileName];
if(newBgm.channels.size()>0)ERR(std::format("WARNING! The size of the channels list is greater than zero! Size: {}",newBgm.channels.size()));
}else{
@ -323,6 +333,17 @@ void Audio::PlayBGM(const std::string_view sound,const bool loop){
}
void Audio::Update(){
if(Self().BGMFullyLoaded()&&Self().BGMIsPlaying()){
Audio::BGM&track=Self().bgm[Self().GetTrackName()];
float currentTimestamp{Engine().GetCursorMilliseconds(track.GetChannelIDs()[0])/1000.f};
if(Self().lastTimestamp>currentTimestamp){
for(int trackID:track.GetChannelIDs()){
Engine().Seek(trackID,(unsigned long long)(track.GetLoopStartTime())*1000);
}
currentTimestamp=Engine().GetCursorMilliseconds(track.GetChannelIDs()[0])/1000.f; //Update to new timestamp now that it's been shifted over.
}
Self().lastTimestamp=currentTimestamp;
}
if(Self().fadeToTargetVolumeTime==0.f&&Self().playBGMWaitTime>0.f){
Self().playBGMWaitTime=std::max(Self().playBGMWaitTime-game->GetElapsedTime(),0.f);
if(Self().playBGMWaitTime==0.f&&Self().immediatelyLoadAudio){
@ -330,7 +351,7 @@ void Audio::Update(){
UpdateLoop(); //We immediately load the file. In a loading screen setting we would defer UpdateLoop() such that we have extra time to update the screen, UpdateLoop() is divided into many parts of the music loading process.
}
//Start playing the tracks.
//Start playing the tracks and setup a callback to repeat at looped time.
Audio::BGM&track=Self().bgm[Self().GetTrackName()];
for(int trackID:track.GetChannelIDs()){
Engine().Play(trackID,true);
@ -355,11 +376,16 @@ const float&Audio::BGM::GetFadeTime()const{
return fadeTime;
}
const float&Audio::BGM::GetLoopStartTime()const{
return loopStartTime;
}
void Audio::SetBGMVolume(float vol){
bgmVol=vol;
UpdateBGMVolume();
}
void Audio::SetBGMPitch(float pitch){
if(game->TestingModeEnabled())return;
BGM&track=Self().bgm[Self().playParams.sound];
for(int channelListIndex=0;int trackID:track.GetChannelIDs()){
Engine().SetPitch(trackID,pitch);
@ -391,8 +417,11 @@ float Audio::GetCalculatedBGMVolume(const float channelVol){
}
return channelVol*GetBGMVolume()*GetMuteMult()*pauseMult;
}
float Audio::GetCalculatedSFXVolume(const float vol){
return vol*GetSFXVolume()*GetMuteMult();
float Audio::GetCalculatedSFXVolume(const SoundEffect&sfx){
return sfx.TreatAsBPM()?Audio::GetCalculatedBGMVolume(sfx.GetVolume()):sfx.GetVolume()*GetSFXVolume()*GetMuteMult();
}
float Audio::GetCalculatedSFXVolume(const float sfxVol){
return sfxVol*GetSFXVolume()*GetMuteMult();
}
float Audio::GetMuteMult(){
if(muted)return 0.f;
@ -402,4 +431,8 @@ float Audio::GetMuteMult(){
int Audio::GetPrepareBGMLoopIterations(std::string_view sound){
BGM&newBgm=Self().bgm[std::string(sound)];
return newBgm.GetChannels().size()*2+2; //The channels list gets populated by calling newBgm.Load(), which then provides the list of channels that need to be loaded and played. This is why we multiply by 2. Each of the loading phases also consist of an initialization phase, so we add 2 as well.
}
}
void Audio::BGM::SetLoopStartTime(const float loopStartTime){
this->loopStartTime=loopStartTime;
}

@ -49,6 +49,8 @@ using ChannelIDList=std::vector<ChannelID>;
using Volume=float;
using VolumeList=std::vector<Volume>;
class SoundEffect;
class Audio{
friend class AiL;
public:
@ -59,7 +61,7 @@ public:
static void UpdateLoop();
static void Play(const std::string_view sound);
[[nodiscard]]
static const size_t LoadAndPlay(const std::string_view sound,const bool loop=true);
static const size_t LoadAndPlaySFX(const std::string_view sound,const bool loop=true);
//Prepares a BGM for loading. This means we call UpdateLoop() repeatedly until the loading of the music is complete. Names are found in bgm.txt configuration file.
static void PrepareBGM(const std::string_view sound,const bool loop=true);
//Play immediately a BGM given a name found in bgm.txt configuration file.
@ -80,7 +82,8 @@ public:
//This will get a prepared BGM loop iteration count which is useful for loading stages.
static int GetPrepareBGMLoopIterations(std::string_view sound);
static float GetCalculatedBGMVolume(const float channelVol);
static float GetCalculatedSFXVolume(const float vol);
static float GetCalculatedSFXVolume(const SoundEffect&sfx);
static float GetCalculatedSFXVolume(const float sfxVol); //NOTE: This is a more manually invoked function! If you are trying to play a specific sound effect from an event, use the SoundEffect version instead!! This accounts for any additional flags related to volume.
private:
bool trackLoadStarted=false;
bool trackLoadComplete=false;
@ -118,12 +121,15 @@ private:
const ChannelID&GetChannelID(const int index);
const ChannelIDList&GetChannelIDs()const;
const float&GetFadeTime()const;
const float&GetLoopStartTime()const;
void SetLoopStartTime(const float loopStartTime);
private:
std::string songName; //Name of the track.
std::string songFileName; //Name of the key in bgm.
ChannelIDList channels;
std::vector<ChannelName>channelNames;
EventData eventVolumes;
float loopStartTime{0.f};
float fadeTime="BGM.Default Fade Time"_F;
void Unload();
};
@ -141,6 +147,7 @@ private:
float playBGMWaitTime=0.0f;
BGMPlayParams playParams;
static bool muted;
float lastTimestamp{0.f};
};
std::string operator""_SFX(const char*key,size_t length);

@ -52,11 +52,11 @@ INCLUDE_MONSTER_DATA
using A=Attribute;
void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
switch(m.I(A::PHASE)){
switch(PHASE()){
case 0:{
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
if(distToPlayer<operator""_Pixels(ConfigFloat("Attack Range"))){
m.I(A::PHASE)=1;
SETPHASE(1);
m.PerformShootAnimation();
m.F(A::CASTING_TIMER)=ConfigFloat("Chargeup Time");
//The bear slam attack indicator will move with the bear, and the LOCKON_POS variable will hold a polar coordinate indicating distance and angle for where it should be attacking relative to its position.
@ -75,7 +75,7 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
case 1:{
m.F(A::CASTING_TIMER)=std::max(0.f,m.F(A::CASTING_TIMER)-fElapsedTime);
if(m.F(A::CASTING_TIMER)==0.f){
m.I(A::PHASE)=2;
SETPHASE(2);
m.F(A::CASTING_TIMER)=ConfigFloat("Attack Animation Wait Time");
m.PerformAnimation("SLAM");
}
@ -85,7 +85,7 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
if(m.F(A::CASTING_TIMER)==0.f){
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
SoundEffect::PlaySFX("Bear Slam Attack",m.GetPos()+m.V(A::LOCKON_POS).cart());
m.I(A::PHASE)=0;
SETPHASE(0);
m.I(A::BEAR_STOMP_COUNT)++;
geom2d::circle<float>attackCircle={m.GetPos()+m.V(A::LOCKON_POS).cart(),float(operator""_Pixels(ConfigFloat("Smash Attack Diameter"))/2.f)};
if(geom2d::overlaps(attackCircle,game->GetPlayer()->Hitbox())){

@ -63,9 +63,11 @@ BulletDestroyState BearTrap::MonsterHit(Monster&monster,const uint8_t markStacks
animation.ChangeState(internal_animState,"bear_trap.png");
const float bleedDamage{"Trapper.Ability 2.Marked Target Bleed"_f[0]/100.f*game->GetPlayer()->GetAttack()};
const float bleedDuration{"Trapper.Ability 2.Marked Target Bleed"_f[1]};
float bleedDuration{"Trapper.Ability 2.Marked Target Bleed"_f[1]};
const float timeBetweenTicks{"Trapper.Ability 2.Marked Target Bleed"_f[2]};
if(game->GetPlayer()->HasEnchant("Lingering Scent"))bleedDuration+="Lingering Scent"_ENC["BLEED EXTRA DURATION"];
if(markStacksBeforeHit>0){
const uint8_t resetStackCount{uint8_t("Trapper.Ability 2.Marked Target Stack Count Reset"_I+1U)}; //Add an additional stack because we know the target hit is about to lose one stack.
const uint8_t numberOfStacksToReplenish{uint8_t(resetStackCount-monster.GetMarkStacks())};

@ -0,0 +1,59 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "AdventuresInLestoria.h"
#include "util.h"
#include "Effect.h"
INCLUDE_MONSTER_LIST
struct BlackHole:FadeInOutEffect{
inline BlackHole(Oscillator<vf2d>pos,const std::string&img,float lifetime,bool onUpperLevel,Oscillator<vf2d>size,vf2d spd,Oscillator<Pixel>col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::function<Effect(const Effect&self)>&particleGenerator={})
:FadeInOutEffect(pos,img,lifetime,onUpperLevel,size,spd,col,rotation,rotationSpd,additiveBlending,particleSpawnFreq,particleGenerator){}
inline bool Update(float fElapsedTime){
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
float distToMonster{util::distance(pos,m->GetPos())};
if(!m->IsSolid()&&m->OnUpperLevel()==OnUpperLevel()&&m->GetZ()<1.f&&distToMonster<="Black Hole"_ENC["PULL IN RADIUS"]/100.f*24){
float pullInForce{util::map_range<float>(distToMonster,0,"Black Hole"_ENC["PULL IN RADIUS"]/100.f*24,"Black Hole"_ENC["PULL IN FORCE MAX"],"Black Hole"_ENC["PULL IN FORCE MIN"])};
m->AddAddedVelocity(util::pointTo(m->GetPos(),pos)*pullInForce);
}
}
return FadeInOutEffect::Update(fElapsedTime);
}
};

@ -212,7 +212,7 @@ void Menu::InitializeBlacksmithCraftingWindow(){
auto moneyIcon=blacksmithWindow->ADD("Money Icon",MenuIconButton)(geom2d::rect<float>{moneyIconPos,{24,24}},GFX["money.png"].Decal(),DO_NOTHING,IconButtonAttr::NOT_SELECTABLE|IconButtonAttr::NO_OUTLINE|IconButtonAttr::NO_BACKGROUND)END;
std::string moneyText=std::to_string(game->GetPlayer()->GetMoney());
vf2d moneyTextSize=game->GetTextSizeProp(moneyText)*2;
auto moneyDisplay=blacksmithWindow->ADD("Money Label",PlayerMoneyLabel)(geom2d::rect<float>{moneyIconPos-vf2d{2+moneyTextSize.x,-2},moneyTextSize},2,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN|ComponentAttr::FIT_TO_LABEL)END;
auto moneyDisplay=blacksmithWindow->ADD("Money Label",PlayerMoneyLabel)(geom2d::rect<float>{moneyIconPos-vf2d{2+moneyTextSize.x,-2},moneyTextSize},2,1.85f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN|ComponentAttr::FIT_TO_LABEL)END;
moneyDisplay->SetRightAlignment(true);
Player::AddMoneyListener(moneyDisplay);
#pragma endregion

@ -57,7 +57,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
RECOVERY,
};
switch(m.phase){
switch(PHASE()){
case PhaseName::MOVE:{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
if(m.canMove&&distToPlayer>=ConfigInt("Closein Range")/100.f*24){
@ -75,7 +75,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
ScratchPhaseTransition:
m.PerformAnimation("SCRATCH");
m.F(A::CASTING_TIMER)=ConfigInt("Ground Scratch Count")*m.GetCurrentAnimation().GetTotalAnimationDuration();
m.phase=PhaseName::SCRATCH;
SETPHASE(PhaseName::SCRATCH);
vf2d chargeTargetPoint=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(ConfigFloat("Charge Distance")/100.f*24);
float distanceToChargePoint=geom2d::line<float>(m.GetPos(),chargeTargetPoint).length();
@ -89,7 +89,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
m.PerformShootAnimation();
m.phase=PhaseName::CHARGE;
SETPHASE(PhaseName::CHARGE);
m.AddBuff(BuffType::SPEEDBOOST,INFINITE,ConfigFloat("Charge Movespeed")/100.f-1);
@ -102,7 +102,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
float distToTarget=geom2d::line<float>(m.GetPos(),m.target).length();
auto TransitionToRecoveryPhase=[&](){
m.phase=PhaseName::RECOVERY;
SETPHASE(PhaseName::RECOVERY);
m.F(A::CHARGE_COOLDOWN)=ConfigFloat("Charge Recovery Time");
m.PerformIdleAnimation();
};
@ -120,7 +120,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::CHARGE_COOLDOWN)-=fElapsedTime;
m.targetAcquireTimer=0.f;
m.RemoveBuff(BuffType::SPEEDBOOST);
if(m.F(A::CHARGE_COOLDOWN)<=0)m.phase=PhaseName::MOVE;
if(m.F(A::CHARGE_COOLDOWN)<=0)SETPHASE(PhaseName::MOVE);
}break;
}
}

@ -103,7 +103,7 @@ BulletDestroyState Bomb::MonsterHit(Monster&monster,const uint8_t markStacksBefo
void Bomb::Draw(const Pixel blendCol)const{
Bullet::Draw(blendCol);
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},bomb_animation.GetFrame(animation).GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2:0,bomb_animation.GetFrame(animation).GetSourceRect().size/2,bomb_animation.GetFrame(animation).GetSourceRect().pos,bomb_animation.GetFrame(animation).GetSourceRect().size,scale,fadeOutTime==0?col:Pixel{col.r,col.g,col.b,uint8_t(util::lerp(col.a,0,1-((fadeOutTime-GetFadeoutTimer())/fadeOutTime)))});
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},bomb_animation.GetFrame(animation).GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2:0,bomb_animation.GetFrame(animation).GetSourceRect().size/2,bomb_animation.GetFrame(animation).GetSourceRect().pos,bomb_animation.GetFrame(animation).GetSourceRect().size,scale,fadeOutTime==0?col:Pixel{col.r,col.g,col.b,uint8_t(util::lerp(col.a,uint8_t(0),1-((fadeOutTime-GetFadeoutTimer())/fadeOutTime)))});
}
void Bomb::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -56,11 +56,11 @@ void Monster::STRATEGY::BREAKING_PILLAR(Monster&m,float fElapsedTime,std::string
m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Break Phase 1 Animation Name"));
}else m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Break Phase 2 Animation Name"));
switch(m.phase){
switch(PHASE()){
case INITIALIZE:{
m.F(A::BREAK_TIME)=ConfigFloat("Break Time");
m.F(A::SHAKE_TIMER)=0.2f;
m.phase=RUN;
SETPHASE(RUN);
m.SetStrategyDeathFunction([&](GameEvent&deathEvent,Monster&m,const std::string&strategy){
m.lifetime=0.01f;

@ -41,29 +41,57 @@ All rights reserved.
#include "Monster.h"
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity){}
:Buff(attachedTarget,type,duration,intensity,[](std::weak_ptr<Monster>m,Buff&b){}){}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,PlayerBuffExpireCallbackFunction expireCallbackFunc)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),originalDuration(this->duration),playerBuffCallbackFunc(expireCallbackFunc){}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,MonsterBuffExpireCallbackFunction expireCallbackFunc)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),originalDuration(this->duration),monsterBuffCallbackFunc(expireCallbackFunc){}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<ItemAttribute> attr)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),attr(attr){}
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),attr(attr),originalDuration(this->duration){}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<std::string> attr)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity){
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),originalDuration(this->duration){
for(const std::string&s:attr){
this->attr.insert(ItemAttribute::attributes.at(s));
}
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,type,overTimeType,duration,intensity,timeBetweenTicks){
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,ONE_OFF,restorationType,overTimeType,duration,intensity,timeBetweenTicks){
playerBuffCallbackFunc=expireCallbackFunc;
switch(restorationType){
case BuffRestorationType::ONE_OFF:{
this->type=ONE_OFF;
}break;
case BuffRestorationType::OVER_TIME:{
this->type=OVER_TIME;
}break;
case BuffRestorationType::OVER_TIME_DURING_CAST:{
this->type=OVER_TIME_DURING_CAST;
}break;
}
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,type,overTimeType,duration,intensity,timeBetweenTicks){
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,ONE_OFF,restorationType,overTimeType,duration,intensity,timeBetweenTicks){
monsterBuffCallbackFunc=expireCallbackFunc;
switch(restorationType){
case BuffRestorationType::ONE_OFF:{
this->type=ONE_OFF;
}break;
case BuffRestorationType::OVER_TIME:{
this->type=OVER_TIME;
}break;
case BuffRestorationType::OVER_TIME_DURING_CAST:{
this->type=OVER_TIME_DURING_CAST;
}break;
}
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks)
:attachedTarget(attachedTarget),duration(duration),intensity(intensity),nextTick(duration-timeBetweenTicks),timeBetweenTicks(timeBetweenTicks),overTimeType(overTimeType){
switch(type){
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks)
:Buff(attachedTarget,ONE_OFF,restorationType,overTimeType,duration,intensity,timeBetweenTicks){
switch(restorationType){
case BuffRestorationType::ONE_OFF:{
this->type=ONE_OFF;
}break;
@ -76,13 +104,30 @@ Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestor
}
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks){
playerBuffCallbackFunc=expireCallbackFunc;
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks){
monsterBuffCallbackFunc=expireCallbackFunc;
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks)
:attachedTarget(attachedTarget),type(type),restorationType(restorationType),duration(duration),intensity(intensity),nextTick(timeBetweenTicks),timeBetweenTicks(timeBetweenTicks),overTimeType(overTimeType),originalDuration(this->duration){}
void Buff::Update(AiL*game,float fElapsedTime){
duration-=fElapsedTime;
if(enabled&&overTimeType.has_value()&&nextTick>0&&duration<nextTick){
BuffTick(game,fElapsedTime);
nextTick-=timeBetweenTicks;
if(type==ONE_OFF)enabled=false;
if(!waitOneTick){
duration-=fElapsedTime;
lifetime+=fElapsedTime;
if(enabled&&overTimeType.has_value()&&lifetime>=nextTick){
BuffTick(game,fElapsedTime);
nextTick+=timeBetweenTicks;
if(restorationType==BuffRestorationType::ONE_OFF)enabled=false;
}
}
waitOneTick=false;
}
void Buff::BuffTick(AiL*game,float fElapsedTime){

@ -50,58 +50,77 @@ enum BuffType{
FIXED_COLLISION_DMG, //Does a fixed amount of collision damage based on intensity of this buff.
COLLISION_KNOCKBACK_STRENGTH, //Causes an amount of knockback based on intensity when hit via collision with this buff
SELF_INFLICTED_SLOWDOWN, //Used for monsters and can't be applied by any player abilities.
ADRENALINE_RUSH,
ADRENALINE_RUSH, //Intensity indicates the stack count (used by the Bloodlust enchant) this buff gives which in turn increases attack.
TRAPPER_MARK,
SPECIAL_MARK, //The mark applied by the Opportunity Shot.
OVER_TIME,
ONE_OFF,
ONE_OFF, //This is used as a hack fix for the RestoreDuringCast Item script since they require us to restore 1 tick immediately. Over time buffs do not apply a tick immediately.
OVER_TIME_DURING_CAST,
GLOW_PURPLE,
COLOR_MOD,
DAMAGE_AMPLIFICATION, //Multiplies all incoming damage by this amount.
LETHAL_TEMPO,
BURNING_ARROW_BURN,
SWORD_ENCHANTMENT,
CURSE_OF_PAIN,
CURSE_OF_DEATH,
AFFECTED_BY_LIGHTNING_BOLT, //Intensity indicates number of repeats remaining.
INK_SLOWDOWN, //Intensity indicates % movespd slowdown.
PIRATE_GHOST_CAPTAIN_PRECURSE, //A coin icon appears above the player's head.
PIRATE_GHOST_CAPTAIN_CURSE_COIN, //A coin icon appears above the player's head.
PIRATE_GHOST_CAPTAIN_CURSE_DOT, //The same as above, but now is a damage over time as well.
};
enum class BuffRestorationType{
ONE_OFF,
ONE_OFF, //This is used as a hack fix for the RestoreDuringCast Item script since they require us to restore 1 tick immediately. Over time buffs do not apply a tick immediately.
OVER_TIME,
OVER_TIME_DURING_CAST,
};
namespace BuffOverTimeType{
enum BuffOverTimeType{
HP_RESTORATION,
HP_PCT_RESTORATION,
HP_PCT_RESTORATION, //Percentage should be the raw percentage, NOT % form! (So 1 for 1%, not 0.01)
MP_RESTORATION,
MP_PCT_RESTORATION,
MP_PCT_RESTORATION, //Percentage should be the raw percentage, NOT % form! (So 1 for 1%, not 0.01)
HP_DAMAGE_OVER_TIME,
HP_PCT_DAMAGE_OVER_TIME,
HP_PCT_DAMAGE_OVER_TIME, //Percentage should be the raw percentage, NOT % form! (So 1 for 1%, not 0.01)
};
};
class AiL;
struct Buff{
using PlayerBuffExpireCallbackFunction=std::function<void(Player*attachedTarget,Buff&b)>;
using MonsterBuffExpireCallbackFunction=std::function<void(std::weak_ptr<Monster>attachedTarget,Buff&b)>;
BuffType type;
BuffRestorationType restorationType{BuffRestorationType::ONE_OFF};
float duration=1;
float timeBetweenTicks=1;
float intensity=1;
float nextTick=0;
float lifetime{};
std::set<ItemAttribute> attr;
std::variant<Player*,std::weak_ptr<Monster>>attachedTarget; //Who has this buff.
PlayerBuffExpireCallbackFunction playerBuffCallbackFunc=[](Player*p,Buff&b){};
MonsterBuffExpireCallbackFunction monsterBuffCallbackFunc=[](std::weak_ptr<Monster>m,Buff&b){};
float originalDuration{};
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<ItemAttribute> attr);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<std::string> attr);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,MonsterBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc);
void Update(AiL*game,float fElapsedTime);
private:
bool enabled{true};
bool waitOneTick{true};
bool enabled{true}; //This is only turned off because the ONE_OFF effect. See BuffType::ONE_OFF for more details.
std::optional<BuffOverTimeType::BuffOverTimeType>overTimeType;
void BuffTick(AiL*game,float fElapsedTime);
};

@ -41,6 +41,6 @@ All rights reserved.
Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale,float image_angle)
:IBullet(pos,vel,radius,damage,upperLevel,friendly,col,scale,image_angle){}
//Initializes a bullet with an animation.
Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float image_angle,std::string_view hitSound)
Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,const std::string&animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float image_angle,std::string_view hitSound)
:IBullet(pos,vel,radius,damage,animation,upperLevel,hitsMultiple,lifetime,rotatesWithAngle,friendly,col,scale,image_angle,hitSound){}
void Bullet::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -43,7 +43,7 @@ class Bullet:public IBullet{
public:
Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f);
//Initializes a bullet with an animation.
Bullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,std::string_view hitSound="");
Bullet(vf2d pos,vf2d vel,float radius,int damage,const std::string&animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,std::string_view hitSound="");
protected:
virtual void ModifyOutgoingDamageData(HurtDamageInfo&data);
virtual void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
};

@ -39,6 +39,10 @@ All rights reserved.
#include "Bullet.h"
#include "Direction.h"
#include "Effect.h"
#include "TrailEffect.h"
#include "Entity.h"
#include <variant>
#include <memory>
struct EnergyBolt:public Bullet{
float lastParticleSpawn=0;
@ -46,7 +50,7 @@ struct EnergyBolt:public Bullet{
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
};
struct FireBolt:public Bullet{
@ -55,16 +59,25 @@ struct FireBolt:public Bullet{
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
std::optional<std::reference_wrapper<TrailEffect>>flameTrail;
};
struct LightningBolt:public Bullet{
enum ChainLightningStatus{//Any other number represents something not defined here, the number of hits remaining.
ORIGINAL_BOLT=-1,
NO_MORE_HITS=0,
};
float lastParticleSpawn=0;
LightningBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
/*chainLightningRepeatCount - Set to higher amount to indicate when this monster instead affects monsters due to the enchantment, which changes which shock count variable we look at and will be used for counting down shock times.*/
static void ApplyLightningShock(const Monster&monster,const bool onUpperLevel,const ChainLightningStatus chainLightningRepeatCount=ORIGINAL_BOLT);
};
struct Arrow:public Bullet{
@ -72,6 +85,7 @@ struct Arrow:public Bullet{
float finalDistance=0;
float acc=PI/2*250;
vf2d targetPos;
bool poisonArrow{false};
Arrow(vf2d pos,vf2d targetPos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
Arrow(vf2d pos,vf2d targetPos,vf2d vel,const std::string_view gfx,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
@ -80,16 +94,20 @@ struct Arrow:public Bullet{
const vf2d PointToBestTargetPath(const uint8_t perceptionLevel);
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
void Draw(const Pixel blendCol)const override;
};
struct ChargedArrow:public Bullet{
vf2d lastLaserPos;
ChargedArrow(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
ChargedArrow(const std::string&shotArrowGraphic,const std::string&laserGraphic,vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
std::string laserGraphic;
};
struct FrogTongue:public Bullet{
@ -103,7 +121,7 @@ struct FrogTongue:public Bullet{
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void Draw(const Pixel blendCol)const override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
};
struct Wisp:public Bullet{
@ -111,7 +129,7 @@ struct Wisp:public Bullet{
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
};
enum class HorizontalFlip{
@ -136,11 +154,11 @@ struct DaggerStab:public Bullet{
float daggerStabDistance;
float knockbackAmt;
DirectionOffsets daggerPositionOffsets;
DaggerStab(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerStabDistance,const DirectionOffsets offsets,bool friendly=false,Pixel col=WHITE);
DaggerStab(Monster&sourceMonster,const std::string&image,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerStabDistance,const DirectionOffsets offsets,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
};
struct DaggerSlash:public Bullet{
@ -149,11 +167,11 @@ struct DaggerSlash:public Bullet{
float frameDuration;
float daggerSlashDistance;
float knockbackAmt;
DaggerSlash(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerSlashDistance,bool friendly=false,Pixel col=WHITE);
DaggerSlash(Monster&sourceMonster,const std::string&image,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerSlashDistance,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
};
struct Bomb:public Bullet{
@ -170,7 +188,7 @@ struct Bomb:public Bullet{
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
void Draw(const Pixel blendCol)const override;
};
@ -196,7 +214,7 @@ struct LevitatingRock:public Bullet{
void Draw(const Pixel blendCol)const override;
void AssignMaster(LevitatingRock*masterRock);
const bool IsMaster()const;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
};
struct Tornado:public Bullet{
@ -211,7 +229,7 @@ struct Tornado:public Bullet{
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
};
struct Debris:public Bullet{
@ -222,7 +240,7 @@ struct Debris:public Bullet{
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
void Draw(const Pixel blendCol)const override;
};
@ -235,12 +253,12 @@ struct LargeTornado:public Bullet{
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
};
struct Feather:public Bullet{
Feather(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f);
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
};
struct LargeStone:public Bullet{
@ -250,7 +268,7 @@ protected:
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
const float gravity;
const float fixedTimeStep{1/30.f};
@ -264,13 +282,13 @@ private:
const float knockbackAmt;
};
struct FallingStone:public Bullet{
//The position for this bullet represents where the falling stone should land.
FallingStone(vf2d targetPos,vf2d vel,float zVel,float indicatorDisplayTime,float radius,int damage,bool upperLevel,bool hitsMultiple=false,float knockbackAmt=0.f,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,float spellCircleRotation=0.f,float spellCircleRotationSpd=0.f,Pixel insigniaCol=WHITE,float insigniaRotation=0.f,float insigniaRotationSpd=0.f);
struct FallingBullet:public Bullet{
//The position for this bullet represents where the falling bullet should land.
FallingBullet(const std::string&imgName,vf2d targetPos,vf2d vel,float zVel,float indicatorDisplayTime,float radius,int damage,bool upperLevel,bool hitsMultiple=false,float knockbackAmt=0.f,float lifetime=INFINITE,bool friendly=false,Pixel spellCircleCol=WHITE,vf2d scale={1,1},float image_angle=0.f,float spellCircleRotation=0.f,float spellCircleRotationSpd=0.f,Pixel insigniaCol=WHITE,float insigniaRotation=0.f,float insigniaRotationSpd=0.f);
protected:
void Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
const vf2d targetPos;
const float zVel{};
@ -278,6 +296,7 @@ private:
SpellCircle indicator;
const float knockbackAmt;
float lastTrailEffect{};
const float collisionRadius{};
};
//While not a bullet directly, the DeadlyDash class generates a bunch of afterimages and collision checks.
@ -285,7 +304,7 @@ struct DeadlyDash:public Bullet{
DeadlyDash(vf2d startPos,vf2d endPos,float radius,float afterImagesLingeringTime,int damage,float knockbackAmt,bool upperLevel,bool friendly,float afterImagesSpreadDist,const std::string&animation,Pixel col);
protected:
void Draw(const Pixel blendCol)const override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
const vf2d startPos;
const vf2d endPos;
@ -300,7 +319,7 @@ struct BearTrap:public Bullet{
BearTrap(vf2d pos,float radius,int damage,float fadeinTime,float fadeoutTime,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1});
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
};
struct ExplosiveTrap:public Bullet{
@ -308,7 +327,7 @@ struct ExplosiveTrap:public Bullet{
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
void Detonate();
const float explosionRadius{};
@ -316,11 +335,13 @@ private:
float automaticDetonationTime{};
float activationWaitTime{};
float lastBeepTime{};
int explosionCount{1};
float rearmTime{};
uint8_t beepCount{1U};
};
struct PurpleEnergyBall:public Bullet{
PurpleEnergyBall(vf2d pos,float radius,float homingRadius,int damage,bool upperLevel,vf2d speed,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1});
PurpleEnergyBall(vf2d pos,float radius,float homingRadius,int damage,bool upperLevel,vf2d speed,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1});
void Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
@ -328,20 +349,115 @@ struct PurpleEnergyBall:public Bullet{
private:
const vf2d initialScale;
const float homingRadius;
int bounceCount{1};
float lastHitTimer{0.f};
std::optional<std::weak_ptr<Monster>>lastHitTarget;
std::optional<std::weak_ptr<Monster>>homingTarget;
};
struct PoisonBottle:public Bullet{
PoisonBottle(vf2d pos,vf2d targetPos,float explodeRadius,float z,float totalFallTime,float totalRiseZAmt,int damage,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle={0.f});
struct ThrownProjectile:public Bullet{
ThrownProjectile(vf2d pos,vf2d targetPos,const std::string&img,float explodeRadius,float z,float totalFallTime,float totalRiseZAmt,int damage,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle={0.f},const std::optional<Effect>explodeEffect={},const std::optional<std::string>explodeSoundEffect={},const std::optional<LingeringEffect>lingeringEffect={});
void Update(float fElapsedTime)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
private:
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
void _OnGroundLand();
virtual void OnGroundLand(); //This is called when the projectile lands, damage is not dealt yet, default behavior just deals damage in the area.
protected:
const vf2d targetPos;
const vf2d startingPos;
const float totalFallTime;
const float originalRisingTime,originalFallingTime;
float originalRisingTime;
float originalFallingTime;
float risingTime,fallingTime;
const float initialZ;
const float totalRiseZAmt;
const float explodeRadius;
const std::optional<Effect>explodeEffect;
const std::optional<std::string>explodeSoundEffect;
const std::optional<LingeringEffect>lingeringEffect;
const std::string img;
};
struct PoisonBottle:public ThrownProjectile{
PoisonBottle(vf2d pos,vf2d targetPos,const std::string&img,float explodeRadius,float bounceExplodeRadius,float z,float totalFallTime,float totalRiseZAmt,int damage,int additionalBounceCount,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle={0.f});
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
virtual void OnGroundLand()override final; //This is called when the projectile lands, damage is not dealt yet, default behavior just deals damage in the area.
private:
const float bounceExplodeRadius;
int additionalBounceCount;
};
struct BurstBullet:public Bullet{
BurstBullet(vf2d pos,vf2d vel,const Entity target,const float explodeDist,const int extraBulletCount,const float extraBulletHeadingAngleChange,const float extraBulletRadius,const vf2d extraBulletScale,const float extraBulletStartSpeed,const float extraBulletAcc,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
void Explode();
const Entity target;
const float explodeDist;
Oscillator<vf2d>scaleOscillator;
bool activated{false};
float explodeTimer{0.f};
const int extraBulletCount;
const float extraBulletHeadingAngleChange; //In radians.
const float extraBulletRadius;
const vf2d extraBulletScale;
const float extraBulletAcc;
const float extraBulletStartSpeed;
};
struct RotateBullet:public Bullet{
//headingAngleChange determines how the startingAngle adjusts over time (in radians).
RotateBullet(vf2d pos,float startingAng,float startSpeed,float acc,float headingAngleChange,float radius,int damage,bool upperLevel,bool friendly,Pixel col);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
const float headingAngleChange;
};
struct InkBullet:public Bullet{
InkBullet(const vf2d pos,const vf2d targetPos,const vf2d vel,const float inkExplosionRadius,const float inkPuddleLifetime,const float inkSlowdownDuration,const float inkSlowdownPct,const float inkPuddleRadius,const bool upperLevel,const bool friendly);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
void Splat();
const vf2d targetPos;
const float inkExplosionRadius;
const float inkSlowdownPct;
const float inkSlowdownDuration;
const float inkPuddleLifetime;
const float inkPuddleRadius;
};
struct HomingBullet:public Bullet{
HomingBullet(const vf2d pos,const Entity target,const float rotateTowardsSpeed,const float rotateTowardsSpeedCoveredInInk,const float lifetime,const float speed,const float radius,const int damage,const bool upperLevel,const bool friendly=false,const Pixel col=WHITE,const vf2d scale={1,1},const float image_angle=0.f);
void Update(float fElapsedTime)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
const Entity target;
const float rotateTowardsSpeed;
const float rotateTowardsSpeedCoveredInInk;
};
struct GhostSaber:public Bullet{
GhostSaber(const vf2d pos,const std::weak_ptr<Monster>target,const float lifetime,const float distFromTarget,const float knockbackAmt,const float initialRot,const float radius,const float expandSpd,const int damage,const bool upperLevel,const float rotSpd,const bool friendly=false,const Pixel col=WHITE,const vf2d scale={1,1},const float image_angle=0.f);
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
private:
const std::weak_ptr<Monster>attachedMonster;
const float rotSpd;
float distFromTarget;
float rot;
const float knockbackAmt;
float particleTimer{};
float aliveTime{};
float expandSpd{};
Oscillator<uint8_t>alphaOscillator{128U,255U,0.6f};
};

@ -0,0 +1,104 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "BulletTypes.h"
#include "Effect.h"
#include "AdventuresInLestoria.h"
#include "DEFINES.h"
#include "util.h"
#include "SoundEffect.h"
#include "TrailEffect.h"
#include <ranges>
INCLUDE_game
INCLUDE_MONSTER_LIST
INCLUDE_ANIMATION_DATA
BurstBullet::BurstBullet(vf2d pos,vf2d vel,const Entity target,const float explodeDist,const int extraBulletCount,const float extraBulletHeadingAngleChange,const float extraBulletRadius,const vf2d extraBulletScale,const float extraBulletStartSpeed,const float extraBulletAcc,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale)
:Bullet(pos,vel,radius,damage,"burstrotatebullet.png",upperLevel,false,INFINITE,true,friendly,col,scale),extraBulletStartSpeed(extraBulletStartSpeed),extraBulletAcc(extraBulletAcc),extraBulletRadius(extraBulletRadius),extraBulletScale(extraBulletScale),extraBulletHeadingAngleChange(extraBulletHeadingAngleChange),extraBulletCount(extraBulletCount),target(target),explodeDist(explodeDist),scaleOscillator({0.85f,0.85f},{1.f,1.f},0.3f){
animation.mult=0.f; //Prevent animating.
}
void BurstBullet::Explode(){
fadeOutTime=0.1f;
HurtType targets{friendly?HurtType::MONSTER:HurtType::PLAYER};
for(const auto&[target,isHurt]:game->Hurt(pos,radius,damage,OnUpperLevel(),GetZ(),targets)){
if(isHurt){
Entity ent{target};
ent.SetIframeTime(0.3f);
}
}
SoundEffect::PlaySFX("Burst Bullet Explode",pos);
for(int i:std::ranges::iota_view(0,extraBulletCount+1)){
const float angle{(360.f/extraBulletCount)*i};
const vf2d newPos{pos+vf2d{radius/2.f,angle}.cart()};
CreateBullet(RotateBullet)(newPos,angle,extraBulletStartSpeed,extraBulletAcc,extraBulletHeadingAngleChange,extraBulletRadius,damage,OnUpperLevel(),friendly,WHITE)EndBullet;
}
Deactivate();
}
void BurstBullet::Update(float fElapsedTime){
if(IsActivated()){
if(!activated){
scaleOscillator.Update(fElapsedTime);
if(util::distance(target.GetPos(),pos)<=explodeDist){
activated=true;
animation.mult=1.f;
explodeTimer=ANIMATION_DATA[animation.currentStateName].GetTotalAnimationDuration();
}
}else if(explodeTimer>0.f){
explodeTimer-=fElapsedTime;
if(explodeTimer<=0.f)Explode();
}
}
}
BulletDestroyState BurstBullet::PlayerHit(Player*player){
Explode();
player->ApplyIframes(0.2f);
return BulletDestroyState::KEEP_ALIVE;
}
BulletDestroyState BurstBullet::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
Explode();
monster.ApplyIframes(0.2f);
return BulletDestroyState::KEEP_ALIVE;
}
void BurstBullet::ModifyOutgoingDamageData(HurtDamageInfo&data){
data.damage=0; //Nullify the damage because proximity damage will occur instead.
}

@ -0,0 +1,49 @@
Ghost of Pirate Captain
60 000 HP
Size 600%
Movespeed 50%
Damage:
Canonshot 80 dmg
Shrapnel 35 dmg
Sabres 75 dmg
Collision 50 dmg
Curse nothing for first 15 seconds, after that 5 dmg per second.
----------------------
Skills:
- Command Canons (2 Kinds of Attack, Big Canon Balls and spreaded shrapnel shot)
- Spreading the curse (The Ghost Throws a Coin towards the Player, and the Player gets marked. starts getting dot damage after time if the coin doesnt get returned to the Pirate Treasure, which has a fixed position on the map)
- Hide and Seek (The Ghost hides Behind a Rock, and Canons Bombard the area until player hits the Captain once)
- Ghost Sabers (Spinning Ghost Swords)
- 70%, 40% & 10% Curse + Hide - permanent Bombardment + Shrapnel Shot every 5 seconds.
Once found on the 10% Hide and Seek Spawns a Cage around the Pirate Treasure, and Spreads the curse on the player again. (If player didnt cleanse the curse before finding the captain it just stays up.)
Both kind of Canon attacks happen at all time now, 10 Ghost Sabers spawn that circle around the boss in different directions
Boss stands still in the middle (maybe raising an arm animation wise or something to show that he is controlling the Sabers)
----------------------
Normal Fight
Canon Shots happen with a 0,4 second delay. between shooting an impact is a 2.5 second delay.
Canon have a 350 impact size
Shrapnel shoots similar to small bolders from chapter 2 boss. 25 hits, 50 radius. Delay between shooting and impact also 2.5 seconds.
its 8 canon shots, 2 second silence, 1 shrapnel shot, 2 second silence, repeat.
the Canon shots can happen in one of the following patterns:
- Bombardment: every shot is at a random location within 900 Range of the player. (This is also the move during hide and seek.)
- precise Bombardment: same as before but within 700 range of the player.
- Shooting in a line: the 4th or 5th hit would hit the player if the player doesnt move at all.
- Sharpshooter: aiming directly at the player, skipping every 2nd shot. (only 4 instead of 8 shots)
- prediction: shoots in the direction of the current or last players movement and predicts where the player would be in 2.8 seconds. (slightly further then where the player would be at impact. thats why 2.8 instead of 2.5 seconds)
Boss spawns after the first Canon rotation ends.
Boss constantly Slowly floats towards player, within 400 range it uses Ghost Sabers once every 4 seconds. (Throws them, Similar movement like Hammers of a Hammerdin in Diablo 2 - https://www.youtube.com/watch?v=jZfH4SY4Lsc - both the spinning animation and the flightpath are a good example. Sabre always spawn of oposite side towards player to give meeles time to dodge.)
On Collision damage trigger a slash animation with its saber.

@ -68,8 +68,8 @@ protected:
vi2d descriptionPos=iconPos+vi2d{int(rect.size.y)-2,9};
window.DrawShadowStringPropDecal(descriptionPos-vf2d{0,10},ability->name,{0xFF,0xAF,0x56},BLACK,{0.8f,1.f},int(rect.size.x-(descriptionPos.x-rect.pos.x))-4);
window.DrawShadowStringPropDecal(descriptionPos,ability->description,WHITE,BLACK,{0.8f,1.f},int(rect.size.x-(descriptionPos.x-rect.pos.x))-4);
window.DrawShadowStringPropDecal(descriptionPos-vf2d{0,10},ability->name,{0xFF,0xAF,0x56},BLACK,{0.8f,1.f},{0.8f,1.f},int(rect.size.x-(descriptionPos.x-rect.pos.x))-4);
window.DrawShadowStringPropDecal(descriptionPos,ability->description,WHITE,BLACK,{0.8f,1.f},{0.8f,1.f},int(rect.size.x-(descriptionPos.x-rect.pos.x))-4);
InputType controlType=KEY;
if(Input::UsingGamepad())controlType=CONTROLLER;

@ -53,6 +53,7 @@ All rights reserved.
#ifndef __EMSCRIPTEN__
#include "steam/isteamuserstats.h"
#endif
#include <bit>
INCLUDE_game
INCLUDE_GFX
@ -66,41 +67,19 @@ namespace CharacterMenuWindow{
AttributeData{"Health",[]()->int{return game->GetPlayer()->GetMaxHealth();}},
AttributeData{"Attack",[]()->int{return game->GetPlayer()->GetAttack();}},
AttributeData{"Defense",[]()->int{return game->GetPlayer()->GetDefense();}},
AttributeData{"Move Spd %",[]()->int{return ceil(game->GetPlayer()->GetMoveSpdMult()*100);}},
AttributeData{"CDR",[]()->int{return ceil(game->GetPlayer()->GetCooldownReductionPct()*100);}},
AttributeData{"Crit Rate",[]()->int{return ceil(game->GetPlayer()->GetCritRatePct()*100);}},
AttributeData{"Crit Dmg",[]()->int{return ceil(game->GetPlayer()->GetCritDmgPct()*100);}},
AttributeData{"Move Spd %",[]()->int{return round(game->GetPlayer()->GetMoveSpdMult()*100);}},
AttributeData{"CDR",[]()->int{return round(game->GetPlayer()->GetCooldownReductionPct()*100);}},
AttributeData{"Crit Rate",[]()->int{return round(game->GetPlayer()->GetCritRatePct()*100);}},
AttributeData{"Crit Dmg",[]()->int{return round(game->GetPlayer()->GetCritDmgPct()*100);}},
};
const static std::array<std::string,8>slotNames{"Helmet","Weapon","Armor","Gloves","Pants","Shoes","Ring 1","Ring 2"};
template<class T>
std::shared_ptr<RowItemDisplay>GenerateItemDisplay(std::shared_ptr<ScrollableWindowComponent>parent,int invIndex,const std::weak_ptr<Item>it){
auto component=parent->ADD("Equip Item "+std::to_string(invIndex),T)(geom2d::rect<float>{{2,2+invIndex*29.f},{120-15,28}},it,
[&](MenuFuncData data){
const auto SelectedEquipIsDifferent=[](std::weak_ptr<RowItemDisplay>comp){
EquipSlot slot=EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE));
std::weak_ptr<Item>currentItem=Inventory::GetEquip(slot);
switch(slot){
case EquipSlot::RING1:
case EquipSlot::RING2:{
std::weak_ptr<Item>otherItem;
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
return ISBLANK(otherItem)||
(&*comp.lock()->GetItem().lock()!=&*otherItem.lock()&&
&*comp.lock()->GetItem().lock()!=&*currentItem.lock());
}break;
default:{
return &*comp.lock()->GetItem().lock()!=&*currentItem.lock();
}
}
};
std::weak_ptr<T>comp=DYNAMIC_POINTER_CAST<T>(data.component.lock());
if(!comp.expired()){
if(SelectedEquipIsDifferent(comp)){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
if(Item::SelectedEquipIsDifferent(comp.lock()->GetItem(),EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE)))){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
Inventory::EquipItem(comp.lock()->GetItem(),EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE)));
#pragma region Fully Decked Out Achievement
@ -144,13 +123,14 @@ void Menu::InitializeCharacterMenuWindow(){
vf2d windowSize=game->GetScreenSize()-vf2d{52,52};
Menu*characterMenuWindow=CreateMenu(CHARACTER_MENU,CENTERED,windowSize);
characterMenuWindow->ADD("Character Label",MenuLabel)(geom2d::rect<float>{{0,-4},{float(windowSize.x)-1,24}},"Character",2,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
characterMenuWindow->ADD("Character Label",MenuLabel)(geom2d::rect<float>{{0,-4},{float(windowSize.x*0.5f)-1,24}},"Character",2,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
characterMenuWindow->ADD("Equip Slot Outline",MenuComponent)(geom2d::rect<float>{{0,28},{120,windowSize.y-37}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END;
//Don't load this graphical element if testing mode is enabled.
characterMenuWindow->ADD("Character Rotating Display",CharacterRotatingDisplay)(geom2d::rect<float>{{118,18},{130,windowSize.y-28}},GFX[classutils::GetClassInfo(game->GetPlayer()->GetClassName()).classFullImgName].Decal())END;
characterMenuWindow->ADD("Level Class Display",MenuLabel)(geom2d::rect<float>{vf2d{126.f,windowSize.y-28},{118.f,8.f}},std::format("Lv{} {}",game->GetPlayer()->Level(),game->GetPlayer()->GetClassName()),1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END;
characterMenuWindow->ADD("XP Bar",ProgressBar)(geom2d::rect<float>{vf2d{126.f,10.f+windowSize.y-28},{118.f,8.f}},Pixel{247,183,82},BLACK,game->GetPlayer()->CurrentXP(),game->GetPlayer()->NextLevelXPRequired(),"xp")END;
characterMenuWindow->ADD("Level Class Outline",MenuComponent)(geom2d::rect<float>{{windowSize.x*0.5f+4.f,-4},{float(windowSize.x*0.5f)-4,24}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END;
characterMenuWindow->ADD("Level Class Display",MenuLabel)(geom2d::rect<float>{vf2d{windowSize.x*0.5f+10.f,0.f},{118.f,8.f}},std::format("Lv{} {}",game->GetPlayer()->Level(),game->GetPlayer()->GetClassName()),1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)END;
characterMenuWindow->ADD("XP Bar",ProgressBar)(geom2d::rect<float>{vf2d{windowSize.x*0.5f+10.f,10.f},{118.f,8.f}},Pixel{247,183,82},BLACK,game->GetPlayer()->CurrentXP(),game->GetPlayer()->NextLevelXPRequired(),"xp")END;
characterMenuWindow->ADD("Equip Selection Outline",MenuComponent)(geom2d::rect<float>{{123,28},{120,windowSize.y-37}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END
->Disable();
@ -198,155 +178,138 @@ void Menu::InitializeCharacterMenuWindow(){
EquipSlot slot=EquipSlot(equipSlot);
auto equipmentSlot=characterMenuWindow->ADD("Equip Slot "+CharacterMenuWindow::slotNames[i],EquipSlotButton)(geom2d::rect<float>{{x,y+28},{24,24}},slot,
[&](MenuFuncData data){
EquipSlot slot=EquipSlot(data.component.lock()->I(Attribute::EQUIP_TYPE));
data.menu.I(A::EQUIP_TYPE)=int(slot);
const std::vector<std::shared_ptr<Item>>&equips=Inventory::get("Equipment");
const std::vector<std::shared_ptr<Item>>&accessories=Inventory::get("Accessories");
std::vector<std::weak_ptr<Item>>availableEquipment;
std::copy_if(equips.begin(),equips.end(),std::back_inserter(availableEquipment),[&](const std::shared_ptr<Item>it){
return it->GetEquipSlot()&slot;
});
std::copy_if(accessories.begin(),accessories.end(),std::back_inserter(availableEquipment),[&](const std::shared_ptr<Item>it){
return it->GetEquipSlot()&slot;
});
std::shared_ptr<ScrollableWindowComponent>equipList=Component<ScrollableWindowComponent>(data.component.lock()->parentMenu,"Equip List");
equipList->RemoveAllComponents();
for(int counter=0;const std::weak_ptr<Item>it:availableEquipment){
std::shared_ptr<RowItemDisplay>equip;
const bool isAccessorySlot=slot&(EquipSlot::RING1|EquipSlot::RING2);
if(isAccessorySlot){
equip=CharacterMenuWindow::GenerateItemDisplay<AccessoryRowItemDisplay>(equipList,counter,it);
}else{
equip=CharacterMenuWindow::GenerateItemDisplay<RowItemDisplay>(equipList,counter,it);
}
if(CanModifyEquipSlots()){
EquipSlot slot=EquipSlot(data.component.lock()->I(Attribute::EQUIP_TYPE));
data.menu.I(A::EQUIP_TYPE)=int(slot);
const std::vector<std::shared_ptr<Item>>&equips=Inventory::get("Equipment");
const std::vector<std::shared_ptr<Item>>&accessories=Inventory::get("Accessories");
std::vector<std::weak_ptr<Item>>availableEquipment;
std::copy_if(equips.begin(),equips.end(),std::back_inserter(availableEquipment),[&](const std::shared_ptr<Item>it){
return it->GetEquipSlot()&slot;
});
std::copy_if(accessories.begin(),accessories.end(),std::back_inserter(availableEquipment),[&](const std::shared_ptr<Item>it){
return it->GetEquipSlot()&slot;
});
std::shared_ptr<ScrollableWindowComponent>equipList=Component<ScrollableWindowComponent>(data.component.lock()->parentMenu,"Equip List");
equipList->RemoveAllComponents();
for(int counter=0;const std::weak_ptr<Item>it:availableEquipment){
std::shared_ptr<RowItemDisplay>equip;
const bool isAccessorySlot=slot&(EquipSlot::RING1|EquipSlot::RING2);
if(isAccessorySlot){
equip=CharacterMenuWindow::GenerateItemDisplay<AccessoryRowItemDisplay>(equipList,counter,it);
}else{
equip=CharacterMenuWindow::GenerateItemDisplay<RowItemDisplay>(equipList,counter,it);
}
equip->SetHoverFunc(
[&](MenuFuncData data){
const auto SelectedEquipIsDifferent=[](std::weak_ptr<RowItemDisplay>comp){
EquipSlot slot=EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE));
std::weak_ptr<Item>currentItem=Inventory::GetEquip(slot);
switch(slot){
case EquipSlot::RING1:
case EquipSlot::RING2:{
std::weak_ptr<Item>otherItem;
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
return ISBLANK(otherItem)||
(&*comp.lock()->GetItem().lock()!=&*otherItem.lock()&&
&*comp.lock()->GetItem().lock()!=&*currentItem.lock());
}break;
default:{
return &*comp.lock()->GetItem().lock()!=&*currentItem.lock();
equip->SetHoverFunc(
[&](MenuFuncData data){
if(!data.component.lock()->GetSubcomponentParent().expired())return true;
std::weak_ptr<RowItemDisplay>button=DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component.lock());
if(!button.expired()){
const std::weak_ptr<Item>buttonItem=button.lock()->GetItem();
std::vector<float>statsBeforeEquip;
EquipSlot slot=EquipSlot(button.lock()->I(Attribute::EQUIP_TYPE));
for(const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
statsBeforeEquip.push_back(attribute.calcFunc());
}
}
};
if(!data.component.lock()->GetSubcomponentParent().expired())return true;
std::weak_ptr<RowItemDisplay>button=DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component.lock());
if(!button.expired()){
const std::weak_ptr<Item>buttonItem=button.lock()->GetItem();
std::vector<float>statsBeforeEquip;
EquipSlot slot=EquipSlot(button.lock()->I(Attribute::EQUIP_TYPE));
for(const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
statsBeforeEquip.push_back(attribute.calcFunc());
}
int healthBeforeEquip=game->GetPlayer()->GetHealth();
int manaBeforeEquip=game->GetPlayer()->GetMana();
std::weak_ptr<Item>equippedItem=Inventory::GetEquip(slot);
std::weak_ptr<Item>otherItem;
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
if(SelectedEquipIsDifferent(button.lock())){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
Inventory::EquipItem(buttonItem,slot);
for(int counter=0;const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute.attrName).Name())+" Label");
int statChangeAmt=attribute.calcFunc()-statsBeforeEquip[counter];
statDisplayLabel.lock()->SetStatChangeAmt(statChangeAmt);
counter++;
}
Inventory::UnequipItem(slot);
if(!ISBLANK(equippedItem)){
Inventory::EquipItem(equippedItem,slot);
game->GetPlayer()->Heal(healthBeforeEquip-game->GetPlayer()->GetHealth(),true);
game->GetPlayer()->RestoreMana(manaBeforeEquip-game->GetPlayer()->GetMana(),true);
}
if(!ISBLANK(otherItem)){
if(slot&EquipSlot::RING1)Inventory::EquipItem(otherItem,EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)Inventory::EquipItem(otherItem,EquipSlot::RING1);
int healthBeforeEquip=game->GetPlayer()->GetHealth();
int manaBeforeEquip=game->GetPlayer()->GetMana();
std::weak_ptr<Item>equippedItem=Inventory::GetEquip(slot);
std::weak_ptr<Item>otherItem;
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
if(Item::SelectedEquipIsDifferent(button.lock()->GetItem(),slot)){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
Inventory::EquipItem(buttonItem,slot);
for(int counter=0;const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute.attrName).Name())+" Label");
int statChangeAmt=attribute.calcFunc()-statsBeforeEquip[counter];
statDisplayLabel.lock()->SetStatChangeAmt(statChangeAmt);
counter++;
}
Inventory::UnequipItem(slot);
if(!ISBLANK(equippedItem)){
Inventory::EquipItem(equippedItem,slot);
game->GetPlayer()->Heal(healthBeforeEquip-game->GetPlayer()->GetHealth(),true);
game->GetPlayer()->RestoreMana(manaBeforeEquip-game->GetPlayer()->GetMana(),true);
}
if(!ISBLANK(otherItem)){
if(slot&EquipSlot::RING1)Inventory::EquipItem(otherItem,EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)Inventory::EquipItem(otherItem,EquipSlot::RING1);
}
}
Component<MenuItemLabel>(data.menu.GetType(),"Item Name")->SetItem(buttonItem);
Component<MenuItemLabel>(data.menu.GetType(),"Item Description")->SetItem(buttonItem);
Component<MenuItemLabel>(data.menu.GetType(),"Item Name")->Enable();
Component<MenuItemLabel>(data.menu.GetType(),"Item Description")->Enable();
}else{
ERR("WARNING! Attempting to cast a button that isn't a RowItemDisplay!");
}
Component<MenuItemLabel>(data.menu.GetType(),"Item Name")->SetItem(buttonItem);
Component<MenuItemLabel>(data.menu.GetType(),"Item Description")->SetItem(buttonItem);
Component<MenuItemLabel>(data.menu.GetType(),"Item Name")->Enable();
Component<MenuItemLabel>(data.menu.GetType(),"Item Description")->Enable();
}else{
ERR("WARNING! Attempting to cast a button that isn't a RowItemDisplay!");
}
return true;
});
equip->SetMouseOutFunc(
[](MenuFuncData data){
for(int counter=0;const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute.attrName).Name())+" Label");
statDisplayLabel.lock()->SetStatChangeAmt(0);
counter++;
}
Component<MenuItemLabel>(data.menu.GetType(),"Item Name")->Disable();
Component<MenuItemLabel>(data.menu.GetType(),"Item Description")->Disable();
return true;
});
equip->SetShowQuantity(false);
equip->SetSelectionType(SelectionType::NONE);
equip->I(Attribute::EQUIP_TYPE)=int(slot);
if(Inventory::GetEquip(slot)==it){
equip->SetSelected(true);
}
equip->SetCompactDescriptions(NON_COMPACT);
return true;
});
equip->SetMouseOutFunc(
[](MenuFuncData data){
for(int counter=0;const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute.attrName).Name())+" Label");
statDisplayLabel.lock()->SetStatChangeAmt(0);
counter++;
}
Component<MenuItemLabel>(data.menu.GetType(),"Item Name")->Disable();
Component<MenuItemLabel>(data.menu.GetType(),"Item Description")->Disable();
return true;
});
counter++;
}
equip->SetShowQuantity(false);
equip->SetSelectionType(SelectionType::NONE);
equip->I(Attribute::EQUIP_TYPE)=int(slot);
if(Inventory::GetEquip(slot)==it){
equip->SetSelected(true);
}
equip->SetCompactDescriptions(NON_COMPACT);
equipList->I(Attribute::INDEXED_THEME)=data.component.lock()->I(Attribute::INDEXED_THEME);
Component<MenuComponent>(data.component.lock()->parentMenu,"Equip Selection Outline")->Enable();
equipList->Enable();
Component<MenuComponent>(data.component.lock()->parentMenu,"Equip Selection Bottom Outline")->Enable();
Component<MenuComponent>(data.component.lock()->parentMenu,"Equip Selection Select Button")->Enable();
Component<CharacterRotatingDisplay>(data.component.lock()->parentMenu,"Character Rotating Display")->Disable();
equipmentWindowOpened=true;
auto equipmentList=equipList->GetComponents();
auto itemEquipped=std::find_if(equipmentList.begin(),equipmentList.end(),[&](std::weak_ptr<MenuComponent>&component){
return !ISBLANK(Inventory::GetEquip(slot))&&component.lock()->GetSubcomponentParent().expired()&&&*DYNAMIC_POINTER_CAST<RowItemDisplay>(component)->GetItem().lock()==&*Inventory::GetEquip(slot).lock();
});
if(itemEquipped!=equipmentList.end()){
data.menu.SetSelection(*itemEquipped,true,true);
if(Menu::UsingMouseNavigation()){
equipList->HandleOutsideDisabledButtonSelection(*itemEquipped);
counter++;
}
data.menu.I(A::ITEM_SLOT)=equipList->GetComponentIndex(*itemEquipped);
}else
if(equipmentList.size()>0){
data.menu.SetSelection(equipmentList[0],true,true);
if(Menu::UsingMouseNavigation()){
equipList->HandleOutsideDisabledButtonSelection(equipmentList[0]);
equipList->I(Attribute::INDEXED_THEME)=data.component.lock()->I(Attribute::INDEXED_THEME);
Component<MenuComponent>(data.component.lock()->parentMenu,"Equip Selection Outline")->Enable();
equipList->Enable();
Component<MenuComponent>(data.component.lock()->parentMenu,"Equip Selection Bottom Outline")->Enable();
Component<MenuComponent>(data.component.lock()->parentMenu,"Equip Selection Select Button")->Enable();
Component<CharacterRotatingDisplay>(data.component.lock()->parentMenu,"Character Rotating Display")->Disable();
equipmentWindowOpened=true;
auto equipmentList=equipList->GetComponents();
auto itemEquipped=std::find_if(equipmentList.begin(),equipmentList.end(),[&](std::weak_ptr<MenuComponent>&component){
return !ISBLANK(Inventory::GetEquip(slot))&&component.lock()->GetSubcomponentParent().expired()&&&*DYNAMIC_POINTER_CAST<RowItemDisplay>(component)->GetItem().lock()==&*Inventory::GetEquip(slot).lock();
});
if(itemEquipped!=equipmentList.end()){
data.menu.SetSelection(*itemEquipped,true,true);
if(Menu::UsingMouseNavigation()){
equipList->HandleOutsideDisabledButtonSelection(*itemEquipped);
}
data.menu.I(A::ITEM_SLOT)=equipList->GetComponentIndex(*itemEquipped);
}else
if(equipmentList.size()>0){
data.menu.SetSelection(equipmentList[0],true,true);
if(Menu::UsingMouseNavigation()){
equipList->HandleOutsideDisabledButtonSelection(equipmentList[0]);
}
data.menu.I(A::ITEM_SLOT)=0;
}else{
data.menu.SetSelection("Equip Selection Select Button"sv);
}
data.menu.I(A::ITEM_SLOT)=0;
return true;
}else{
data.menu.SetSelection("Equip Selection Select Button"sv);
game->AddNotification(AiL::Notification{"Cannot change equipment in a stage!",5.f,YELLOW});
return false;
}
return true;
},[](MenuFuncData data){//On Mouse Hover
EquipSlot slot=DYNAMIC_POINTER_CAST<EquipSlotButton>(data.component.lock())->GetSlot();
const std::weak_ptr<Item>equip=Inventory::GetEquip(slot);
@ -414,15 +377,17 @@ void Menu::InitializeCharacterMenuWindow(){
},[](MenuType type){
if(!Menu::menus[type]->GetSelection().expired()&&
Menu::menus[type]->GetSelection().lock()->GetName().starts_with("Equip Slot ")){
EquipSlot slot=EquipSlot(Menu::menus[type]->GetSelection().lock()->I(Attribute::EQUIP_TYPE));
if(!ISBLANK(Inventory::GetEquip(slot))){
Inventory::UnequipItem(slot);
if(slot&EquipSlot::RING1||slot&EquipSlot::RING2){
SoundEffect::PlaySFX("Unequip Accessory",SoundEffect::CENTERED);
}else{
SoundEffect::PlaySFX("Unequip Armor",SoundEffect::CENTERED);
if(CanModifyEquipSlots()){
EquipSlot slot=EquipSlot(Menu::menus[type]->GetSelection().lock()->I(Attribute::EQUIP_TYPE));
if(!ISBLANK(Inventory::GetEquip(slot))){
Inventory::UnequipItem(slot);
if(slot&EquipSlot::RING1||slot&EquipSlot::RING2){
SoundEffect::PlaySFX("Unequip Accessory",SoundEffect::CENTERED);
}else{
SoundEffect::PlaySFX("Unequip Armor",SoundEffect::CENTERED);
}
}
}
}else game->AddNotification(AiL::Notification{"Cannot change equipment in a stage!",5.f,YELLOW});
}
}}},
{{game->KEY_FACELEFT,Pressed},{[](MenuFuncData data){
@ -437,15 +402,17 @@ void Menu::InitializeCharacterMenuWindow(){
},[](MenuType type){
if(!Menu::menus[type]->GetSelection().expired()&&
Menu::menus[type]->GetSelection().lock()->GetName().starts_with("Equip Slot ")){
EquipSlot slot=EquipSlot(Menu::menus[type]->GetSelection().lock()->I(Attribute::EQUIP_TYPE));
if(!ISBLANK(Inventory::GetEquip(slot))){
Inventory::UnequipItem(slot);
if(slot&EquipSlot::RING1||slot&EquipSlot::RING2){
SoundEffect::PlaySFX("Unequip Accessory",SoundEffect::CENTERED);
}else{
SoundEffect::PlaySFX("Unequip Armor",SoundEffect::CENTERED);
if(CanModifyEquipSlots()){
EquipSlot slot=EquipSlot(Menu::menus[type]->GetSelection().lock()->I(Attribute::EQUIP_TYPE));
if(!ISBLANK(Inventory::GetEquip(slot))){
Inventory::UnequipItem(slot);
if(slot&EquipSlot::RING1||slot&EquipSlot::RING2){
SoundEffect::PlaySFX("Unequip Accessory",SoundEffect::CENTERED);
}else{
SoundEffect::PlaySFX("Unequip Armor",SoundEffect::CENTERED);
}
}
}
}else game->AddNotification(AiL::Notification{"Cannot change equipment in a stage!",5.f,YELLOW});
}
}}},
{game->KEY_BACK,{"Back",[](MenuType type){
@ -515,28 +482,10 @@ void Menu::InitializeCharacterMenuWindow(){
,{ //Button Navigation Rules
{"Equip List",{
.up=[](MenuType type,Data&returnData){
if(!Menu::menus[type]->GetSelection().expired()){
auto selection=Menu::menus[type]->GetSelection().lock();
size_t index=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponentIndex(selection);
index--;
if(index>=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents().size()){
returnData="Equip Selection Select Button";
}else{
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[index];
}
}
Menu::ScrollUp(type,"Equip List",returnData,"Equip Selection Select Button");
},
.down=[](MenuType type,Data&returnData){
if(!Menu::menus[type]->GetSelection().expired()){
auto selection=Menu::menus[type]->GetSelection().lock();
size_t index=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponentIndex(selection);
index++;
if(index>=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents().size()){
returnData="Equip Selection Select Button";
}else{
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[index];
}
}
Menu::ScrollDown(type,"Equip List",returnData,"Equip Selection Select Button");
},
.left=[](MenuType type,Data&returnData){
auto equipList=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents();

@ -45,16 +45,23 @@ All rights reserved.
INCLUDE_game
ChargedArrow::ChargedArrow(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:lastLaserPos(pos),
Bullet(pos,vel,radius,damage,
"charged_shot_arrow.png",upperLevel,true,INFINITE,true,friendly,col){}
:lastLaserPos(pos),laserGraphic("laser.png"),
Bullet(pos,vel,radius,damage,"charged_shot_arrow.png",upperLevel,true,INFINITE,true,friendly,col){}
ChargedArrow::ChargedArrow(const std::string&shotArrowGraphic,const std::string&laserGraphic,vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:lastLaserPos(pos),laserGraphic(laserGraphic),
Bullet(pos,vel,radius,damage,shotArrowGraphic,upperLevel,true,INFINITE,true,friendly,col){}
void ChargedArrow::Update(float fElapsedTime){
geom2d::line lineToCurrentPos(geom2d::line(lastLaserPos,pos));
float dist=lineToCurrentPos.length();
if(dist>=1){
vf2d midpoint(lineToCurrentPos.rpoint(0.5));
game->AddEffect(std::make_unique<Effect>(midpoint,0.1f,"laser.png",upperLevel,vf2d{1,dist},0.3f,vf2d{},Pixel{192,128,238},atan2(pos.y-lastLaserPos.y,pos.x-lastLaserPos.x)+PI/2,0,true));
const float normalBeamRadius{12*"Ranger.Ability 2.Radius"_F/100/5};
float laserWidth{radius/normalBeamRadius};
game->AddEffect(std::make_unique<Effect>(midpoint,0.1f,laserGraphic,upperLevel,vf2d{laserWidth,dist*2},0.3f,vf2d{},Pixel{192,128,238},atan2(pos.y-lastLaserPos.y,pos.x-lastLaserPos.x)+PI/2,0,true));
lastLaserPos=pos;
}
}

@ -50,6 +50,7 @@ All rights reserved.
#endif
INCLUDE_game
INCLUDE_DEMO_BUILD
using A=Attribute;
void Menu::InitializeClassSelectionWindow(){
@ -168,6 +169,12 @@ void Menu::InitializeClassSelectionWindow(){
classSprite->S(A::CLASS_SELECTION)=className;
if(DEMO_BUILD&&i>2){ //Disable the three later classes in the demo build.
classLabel->SetGrayedOut(true);
classButton->SetGrayedOut(true);
classSprite->SetGrayedOut(true);
}
toggleGroup.push_back(classSprite);
}

@ -0,0 +1,57 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Effect.h"
#include "AdventuresInLestoria.h"
#include "DEFINES.h"
#include "util.h"
INCLUDE_game
CollectCoinEffect::CollectCoinEffect(const Entity attachedTarget,float coinArcRiseZAmt,float lifetime,const std::string&imgFile,bool upperLevel,float size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect(attachedTarget.GetPos(),lifetime,imgFile,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending),attachedTarget(attachedTarget),sizeFlipper(-1.f,1.f,lifetime*2.f),zRiser(0.f,coinArcRiseZAmt,1/lifetime){
}
bool CollectCoinEffect::Update(float fElapsedTime){
zRiser.Update(fElapsedTime);
pos=attachedTarget.GetPos()-vf2d{0,zRiser.get()}; //A hack, instead of using the built-in z (which would display a shadow, we instead manipulate the final position based on the zRiser output).
size.x=sizeFlipper.Update(fElapsedTime);
return Effect::Update(fElapsedTime);
}
void CollectCoinEffect::Draw(const Pixel blendCol)const{
Effect::Draw(blendCol);
}

@ -55,6 +55,6 @@ void ConnectionPoint::ResetVisitedFlag(){
visited=false;
}
const bool ConnectionPoint::Visited()const{
[[nodiscard]]const bool ConnectionPoint::Visited()const{
return visited;
}

@ -0,0 +1,10 @@
To open the admin console, press F12 in-game. The admin console is turned on via the ADMIN_MODE variable located in AdventuresInLestoria.cpp
Required arguments are in <>. Optional arguments are in [].
Items with spaces must be surrounded in quotation marks "".
Valid Commands are:
/help - Shows this help file.
/give <Item Name> [Amount] - Gives an item of type <Item Name> into the player's inventory. Optionally adds [Amount] amount of items to the player's inventory.
/accessory <Accessory Name> [Enchant Name] - Gives an accessory of type <Accessory Name> into the player's inventory. Optionally adds an enchant of type [Enchant Name].

@ -133,7 +133,7 @@ void Menu::InitializeConsumableCraftingWindow(){
auto moneyIcon=consumableCraftingWindow->ADD("Money Icon",MenuIconButton)(geom2d::rect<float>{moneyIconPos,{24,24}},GFX["money.png"].Decal(),DO_NOTHING,IconButtonAttr::NOT_SELECTABLE|IconButtonAttr::NO_OUTLINE|IconButtonAttr::NO_BACKGROUND)END;
std::string moneyText=std::to_string(game->GetPlayer()->GetMoney());
vf2d moneyTextSize=game->GetTextSizeProp(moneyText)*2;
auto moneyDisplay=consumableCraftingWindow->ADD("Money Label",PlayerMoneyLabel)(geom2d::rect<float>{moneyIconPos-vf2d{2+moneyTextSize.x,-2},moneyTextSize},2,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN|ComponentAttr::FIT_TO_LABEL)END;
auto moneyDisplay=consumableCraftingWindow->ADD("Money Label",PlayerMoneyLabel)(geom2d::rect<float>{moneyIconPos-vf2d{2+moneyTextSize.x,-2},moneyTextSize},2,1.85f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN|ComponentAttr::FIT_TO_LABEL)END;
moneyDisplay->SetRightAlignment(true);
Player::AddMoneyListener(moneyDisplay);
#pragma endregion

@ -0,0 +1,118 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "MonsterStrategyHelpers.h"
#include "util.h"
#include "AdventuresInLestoria.h"
#include "SoundEffect.h"
#include "BulletTypes.h"
using A=Attribute;
INCLUDE_game
void Monster::STRATEGY::CRAB(Monster&m,float fElapsedTime,std::string strategy){
enum PhaseName{
INIT,
MOVE,
PREPARE_CHARGE,
CHARGE,
};
switch(PHASE()){
case INIT:{
m.B(A::RANDOM_DIRECTION)=util::random()%2;
m.F(A::RANDOM_RANGE)=util::random_range(ConfigPixelsArr("Random Direction Range",0),ConfigPixelsArr("Random Direction Range",1));
SETPHASE(MOVE);
}break;
case MOVE:{
m.F(A::ATTACK_COOLDOWN)+=fElapsedTime;
m.F(A::LAST_JUMP_TIMER)+=fElapsedTime;
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
const bool outsideMaxShootingRange=distToPlayer>=ConfigPixelsArr("Stand Still and Shoot Range",1);
if(m.F(A::LAST_JUMP_TIMER)>=ConfigFloat("Stop Check Interval")){
if(util::random(100)<=ConfigFloat("Stop Percent")){
SETPHASE(PREPARE_CHARGE);
m.PerformAnimation("CHARGEUP",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
m.F(A::CASTING_TIMER)=ConfigFloat("Charge Wait Time");
m.target=game->GetPlayer()->GetPos()+util::distance(m.GetPos(),game->GetPlayer()->GetPos())*util::pointTo(m.GetPos(),game->GetPlayer()->GetPos());
}else
if(util::random(100)<=ConfigFloat("Change Direction Chance"))m.B(A::RANDOM_DIRECTION)=!m.B(A::RANDOM_DIRECTION);
m.F(A::LAST_JUMP_TIMER)=0.f;
}else
if(outsideMaxShootingRange){
m.target=game->GetPlayer()->GetPos();
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else
if(distToPlayer<ConfigPixels("Run Away Range")){
m.target=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).upoint(-1);
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else
if(distToPlayer>=ConfigPixelsArr("Random Direction Range",0)&&distToPlayer<ConfigPixelsArr("Random Direction Range",1)){
#define CW true
#define CCW false
//We are going to walk in a circular direction either CW or CCW (determined in windup phase)
float dirFromPlayer=util::angleTo(game->GetPlayer()->GetPos(),m.GetPos());
float targetDir=m.B(A::RANDOM_DIRECTION)==CW?dirFromPlayer+PI/4:dirFromPlayer-PI/4;
m.target=game->GetPlayer()->GetPos()+vf2d{m.F(A::RANDOM_RANGE),targetDir}.cart();
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
m.F(A::CHASE_TIMER)=1.f;
}
}break;
case PREPARE_CHARGE:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
SETPHASE(CHARGE);
m.F(A::CHASE_TIMER)=ConfigFloat("Charge Max Time");
m.PerformAnimation("PINCER");
}
}break;
case CHARGE:{
m.F(A::CHASE_TIMER)-=fElapsedTime;
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
if(m.F(A::CHASE_TIMER)<=0.f||m.ReachedTargetPos()){
SETPHASE(MOVE);
m.B(A::RANDOM_DIRECTION)=util::random()%2;
m.F(A::RANDOM_RANGE)=util::random_range(ConfigPixelsArr("Random Direction Range",0),ConfigPixelsArr("Random Direction Range",1));
}
}break;
}
}

@ -70,7 +70,7 @@ void Menu::InitializeCraftItemWindow(){
for(const auto&[name,amt]:consumedResources.GetItems()){
Inventory::RemoveItem(Inventory::GetItem(name)[0],amt);
}
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-consumedResources.GetCost());
game->GetPlayer()->RemoveMoney(consumedResources.GetCost());
Inventory::UpdateBlacksmithInventoryLists();
SoundEffect::PlaySFX("Craft Equip",SoundEffect::CENTERED);

@ -36,7 +36,7 @@ Artificer has 4 Dialog Options
Enhancing Gear
Refining Gear
- increases the stats of an item.
- Increases a random stat by a random amount that did not reach its cap. (cant go higher then 20% of its current amount in on Enhance attempt and cant go higher then the max stat value of the item)
- Costs 1 piece of the Ring you want to enhance + 20g

@ -61,6 +61,9 @@ using MonsterSpawnerID=int;
#define DO_NOTHING [](MenuFuncData data){return true;}
#define INCLUDE_WINDOW_SIZE extern vi2d WINDOW_SIZE;
#define INCLUDE_ITEM_CONVERSIONS extern safemap<std::string,IT>ITEM_CONVERSIONS;
#define INCLUDE_ADMIN_MODE extern bool ADMIN_MODE;
#define INCLUDE_DEMO_BUILD extern bool DEMO_BUILD;
#define INCLUDE_ITEM_SCRIPTS extern safemap<std::string,ItemScript>ITEM_SCRIPTS;
#define INCLUDE_PACK_KEY extern std::string PACK_KEY;
@ -72,22 +75,68 @@ using MonsterSpawnerID=int;
#define ACCESS_PLAYER Player*p=game->GetPlayer();
#define INCLUDE_INITIALIZEGAMECONFIGURATIONS extern void InitializeGameConfigurations();
#define VARIANTS float,int,std::string,bool,vf2d,std::vector<std::any>,size_t
#undef INFINITE
#define INFINITE 999999
#define MAX_BULLETS size_t(512)
#define SETUP_CLASS(class) \
class::class() \
:Player::Player(){} \
class::class(Player*player) \
:Player::Player(player){} \
Class class::GetClass(){return cl;} \
void class::CreateOriginalCopies(){ \
original_rightClickAbility=rightClickAbility; \
original_ability1=ability1; \
original_ability2=ability2; \
original_ability3=ability3; \
original_ability4=ability4; \
original_rightClickAbility.isOriginalAbility=true; \
original_ability1.isOriginalAbility=true; \
original_ability2.isOriginalAbility=true; \
original_ability3.isOriginalAbility=true; \
original_ability4.isOriginalAbility=true; \
} \
void class::ResetToOriginalAbilities(){ \
rightClickAbility.COOLDOWN_TIME=original_rightClickAbility.COOLDOWN_TIME; \
ability1.COOLDOWN_TIME=original_ability1.COOLDOWN_TIME; \
ability2.COOLDOWN_TIME=original_ability2.COOLDOWN_TIME; \
ability3.COOLDOWN_TIME=original_ability3.COOLDOWN_TIME; \
ability4.COOLDOWN_TIME=original_ability4.COOLDOWN_TIME; \
rightClickAbility.MAX_CHARGES=original_rightClickAbility.MAX_CHARGES; \
ability1.MAX_CHARGES=original_ability1.MAX_CHARGES; \
ability2.MAX_CHARGES=original_ability2.MAX_CHARGES; \
ability3.MAX_CHARGES=original_ability3.MAX_CHARGES; \
ability4.MAX_CHARGES=original_ability4.MAX_CHARGES; \
rightClickAbility.precastInfo=original_rightClickAbility.precastInfo; \
ability1.precastInfo=original_ability1.precastInfo; \
ability2.precastInfo=original_ability2.precastInfo; \
ability3.precastInfo=original_ability3.precastInfo; \
ability4.precastInfo=original_ability4.precastInfo; \
} \
const std::string&class::GetClassName(){return name;} \
Ability&class::GetRightClickAbility(){return rightClickAbility;}; \
Ability&class::GetAbility1(){return ability1;}; \
Ability&class::GetAbility2(){return ability2;}; \
Ability&class::GetAbility3(){return ability3;}; \
Ability&class::GetAbility4(){return ability4;}; \
Ability&class::_GetOriginalAbility1(){return GetOriginalAbility1();}; \
Ability&class::_GetOriginalAbility2(){return GetOriginalAbility2();}; \
Ability&class::_GetOriginalAbility3(){return GetOriginalAbility2();}; \
Ability&class::_GetOriginalRightClickAbility(){return GetOriginalRightClickAbility();}; \
Ability&class::GetOriginalAbility1(){return original_ability1;}; \
Ability&class::GetOriginalAbility2(){return original_ability2;}; \
Ability&class::GetOriginalAbility3(){return original_ability3;}; \
Ability&class::GetOriginalRightClickAbility(){return original_rightClickAbility;}; \
void class::SetAbility4(const Ability&originalAbility){ \
auto it=std::find(Player::ABILITY_LIST.begin(),Player::ABILITY_LIST.end(),originalAbility); \
if(it==Player::ABILITY_LIST.end())ERR(std::format("WARNING! Could not find ability {} in original ability list! This function MUST be supplied with an original ability. Was original ability: {}",originalAbility.name,originalAbility.isOriginalAbility)); \
class::original_ability4=class::ability4=originalAbility; \
} \
std::string&class::GetWalkNAnimation(){return walk_n;}; \
std::string&class::GetWalkEAnimation(){return walk_e;}; \
std::string&class::GetWalkSAnimation(){return walk_s;}; \
@ -103,6 +152,11 @@ Ability class::ability1=Ability(); \
Ability class::ability2=Ability(); \
Ability class::ability3=Ability(); \
Ability class::ability4=Ability(); \
Ability class::original_rightClickAbility=Ability(); \
Ability class::original_ability1=Ability(); \
Ability class::original_ability2=Ability(); \
Ability class::original_ability3=Ability(); \
Ability class::original_ability4=Ability(); \
std::string class::idle_n="WARRIOR_IDLE_N"; \
std::string class::idle_e="WARRIOR_IDLE_E"; \
std::string class::idle_s="WARRIOR_IDLE_S"; \

@ -45,8 +45,8 @@ All rights reserved.
INCLUDE_game
INCLUDE_ANIMATION_DATA
DaggerSlash::DaggerSlash(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerSlashDistance,bool friendly,Pixel col)
:Bullet(sourceMonster.GetPos(),{},radius,damage,"goblin_sword_slash.png",upperLevel,false,daggerFrameDuration*ANIMATION_DATA["goblin_sword_slash.png"].GetFrameCountBasedOnAnimationStyle(),true,friendly,col),
DaggerSlash::DaggerSlash(Monster&sourceMonster,const std::string&image,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerSlashDistance,bool friendly,Pixel col)
:Bullet(sourceMonster.GetPos(),{},radius,damage,image,upperLevel,false,daggerFrameDuration*ANIMATION_DATA["goblin_sword_slash.png"].GetFrameCountBasedOnAnimationStyle(),true,friendly,col),
sourceMonster(sourceMonster),frameDuration(daggerFrameDuration),daggerSlashDistance(daggerSlashDistance),facingDir(facingDir),knockbackAmt(knockbackAmt){}
void DaggerSlash::Update(float fElapsedTime){
ANIMATION_DATA["goblin_sword_slash.png"].ChangeFrameDuration(frameDuration);

@ -45,8 +45,8 @@ All rights reserved.
INCLUDE_game
INCLUDE_ANIMATION_DATA
DaggerStab::DaggerStab(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerStabDistance,const DirectionOffsets offsets,bool friendly,Pixel col)
:Bullet(sourceMonster.GetPos(),{},radius,damage,"dagger_stab.png",upperLevel,false,daggerFrameDuration*ANIMATION_DATA["dagger_stab.png"].GetFrameCountBasedOnAnimationStyle(),true,friendly,col),
DaggerStab::DaggerStab(Monster&sourceMonster,const std::string&image,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerStabDistance,const DirectionOffsets offsets,bool friendly,Pixel col)
:Bullet(sourceMonster.GetPos(),{},radius,damage,image,upperLevel,false,daggerFrameDuration*ANIMATION_DATA["dagger_stab.png"].GetFrameCountBasedOnAnimationStyle(),true,friendly,col),
sourceMonster(sourceMonster),frameDuration(daggerFrameDuration),daggerStabDistance(daggerStabDistance),facingDir(facingDir),daggerPositionOffsets(offsets),knockbackAmt(knockbackAmt){}
void DaggerStab::Update(float fElapsedTime){
ANIMATION_DATA["dagger_stab.png"].ChangeFrameDuration(frameDuration);

@ -44,13 +44,8 @@ const float DamageNumber::MOVE_UP_TIME=0.4f;
using enum DamageNumberType::DamageNumberType;
DamageNumber::DamageNumber()
:damage(0){
}
DamageNumber::DamageNumber(vf2d pos,int damage,bool friendly,DamageNumberType::DamageNumberType type):
pos(pos),damage(damage),friendly(friendly),type(type),invertedDirection(type==INTERRUPT),riseSpd(20.f){
if(type==INTERRUPT||type==MANA_GAIN)riseSpd=40.f;
pos(pos),damage(damage),friendly(friendly),type(type),invertedDirection(type==INTERRUPT),riseSpd(GetOriginalRiseSpd()){
RecalculateSize();
}
@ -59,17 +54,35 @@ void DamageNumber::RecalculateSize(){
if(!friendly){
float newSize=std::clamp(roundf(damageMultRatio),1.0f,4.0f);
if(type==HEALTH_LOSS||type==CRIT)riseSpd*=newSize;
float riseSpd{GetOriginalRiseSpd()};
if(type==HEALTH_LOSS||type==CRIT||type==DOT)riseSpd*=newSize;
size=vf2d{newSize,newSize};
}
}
void DamageNumber::Update(){
if(pauseTime>0){
pauseTime-=game->GetElapsedTime();
} else{
lifetime+=game->GetElapsedTime();
if(lifetime<=1){
if(lifetime<DamageNumber::MOVE_UP_TIME){
if(invertedDirection){
pos.y+=riseSpd*game->GetElapsedTime();
}else{
pos.y-=riseSpd*game->GetElapsedTime();
}
}
}
}
}
void DamageNumber::Draw(){
#define NumberScalesWithDamage true
#define NormalNumber false
auto DrawDamageNumber=[&](const bool ScaleWithNumber,std::string_view text,std::pair<Pixel,Pixel>colorsEnemy,std::pair<Pixel,Pixel>colorsFriendly,vf2d scaling={1.f,1.f}){
auto DrawDamageNumber=[&](const bool ScaleWithNumber,std::string_view text,std::pair<Pixel,Pixel>colorWhenAppliedToMonster,std::pair<Pixel,Pixel>colorWhenAppliedToPlayer,vf2d scaling={1.f,1.f}){
vf2d textSize=game->GetTextSizeProp(text)*scaling;
if(!friendly){
vf2d additionalScaling={1.f,1.f};
@ -81,13 +94,13 @@ void DamageNumber::Draw(){
drawPos.x=std::clamp(drawPos.x,game->view.GetWorldTL().x,game->view.GetWorldBR().x-textSize.x);
drawPos.y=std::clamp(drawPos.y,game->view.GetWorldTL().y,game->view.GetWorldBR().y-textSize.y);
game->view.DrawShadowStringPropDecal(drawPos,text,colorsEnemy.first,colorsEnemy.second,scaling*additionalScaling);
game->view.DrawShadowStringPropDecal(drawPos,text,colorWhenAppliedToMonster.first,colorWhenAppliedToMonster.second,scaling*additionalScaling);
}else{
vf2d drawPos=pos-textSize/2.f;
drawPos.x=std::clamp(drawPos.x,game->view.GetWorldTL().x,game->view.GetWorldBR().x-textSize.x);
drawPos.y=std::clamp(drawPos.y,game->view.GetWorldTL().y,game->view.GetWorldBR().y-textSize.y);
game->view.DrawShadowStringPropDecal(drawPos,text,colorsFriendly.first,colorsFriendly.second,scaling);
game->view.DrawShadowStringPropDecal(drawPos,text,colorWhenAppliedToPlayer.first,colorWhenAppliedToPlayer.second,scaling);
}
};
@ -114,8 +127,54 @@ void DamageNumber::Draw(){
}break;
case DOT:{
std::string text=std::to_string(damage);
DrawDamageNumber(NormalNumber,text,{0xE1BEE7,0x1F083A},{0xDCE775,0x37320A});
DrawDamageNumber(NormalNumber,text,std::pair<Pixel,Pixel>{0xE1BEE7,0x1F083A},std::pair<Pixel,Pixel>{0xDCE775,0x37320A});
}break;
case BACKSTAB:{
std::string text=std::to_string(damage);
DrawDamageNumber(NumberScalesWithDamage,text,std::pair<Pixel,Pixel>{0x888093,0x150035},{BLACK,{0,0,0,0}});
}break;
case SHIELD_LOSS:{
std::string text=std::to_string(damage);
DrawDamageNumber(NumberScalesWithDamage,text,std::pair<Pixel,Pixel>{DARK_BLUE,0x68d7ef},std::pair<Pixel,Pixel>{BLUE,0x4141be});
}break;
default:ERR(std::format("Damage Number Type {} is not implemented! THIS SHOULD NOT BE HAPPENING!",int(type)));
}
}
float DamageNumber::GetOriginalRiseSpd(){
float riseSpd{20.f};
switch(type){
case INTERRUPT:
case HEALTH_GAIN:{
riseSpd=40.f;
}break;
case MANA_GAIN:{
riseSpd=-20.f;
}break;
case DOT:{
riseSpd=-10.f;
}break;
}
return riseSpd;
}
void DamageNumber::AddDamage(int damageAmt){
if(damageAmt<0)ERR(std::format("WARNING! Trying to apply a negative damage amount to a damage counter: {}! THIS SHOULD NOT BE HAPPENING!",damageAmt));
damage+=damageAmt;
}
const bool DamageNumber::IsDead()const{
return lifetime>1.f;
}
void DamageNumber::SetPauseTimer(const float pauseTime){
this->pauseTime=pauseTime;
}
void DamageNumber::SetType(const DamageNumberType::DamageNumberType type){
this->type=type;
}
const DamageNumberType::DamageNumberType DamageNumber::GetType()const{
return type;
}

@ -46,23 +46,32 @@ namespace DamageNumberType{
INTERRUPT,
CRIT,
DOT,
BACKSTAB,
SHIELD_LOSS,
};
}
struct DamageNumber{
//The friendly flag indicates if the number was for a friendly/player target or if it's for a monster target (set to false)
DamageNumber(vf2d pos,int damage,bool friendly=false,DamageNumberType::DamageNumberType type=DamageNumberType::HEALTH_LOSS);
void Update();
void Draw();
void AddDamage(int damageAmt);
const bool IsDead()const;
void SetPauseTimer(const float pauseTime);
void SetType(const DamageNumberType::DamageNumberType type);
const DamageNumberType::DamageNumberType GetType()const;
private:
vf2d pos;
int damage;
float lifeTime=0;
float lifetime=0;
float pauseTime=0;
DamageNumberType::DamageNumberType type=DamageNumberType::HEALTH_LOSS;
float riseSpd=0.f;
vf2d size{1.f,1.f};
bool friendly=false;
bool invertedDirection=false;
DamageNumberType::DamageNumberType type=DamageNumberType::HEALTH_LOSS;
const static float MOVE_UP_TIME;
DamageNumber();
//The friendly flag indicates if the number was for a friendly/player target or if it's for a monster target (set to false)
DamageNumber(vf2d pos,int damage,bool friendly=false,DamageNumberType::DamageNumberType type=DamageNumberType::HEALTH_LOSS);
float GetOriginalRiseSpd();
void RecalculateSize();
void Draw();
};

@ -79,7 +79,7 @@ void DeadlyDash::Draw(const Pixel blendCol)const{
for(int i:std::ranges::iota_view(0,afterImageCount)){
const float fadeTimeBegins{(i+1)*afterImagesLingeringTime};
uint8_t alpha{255U};
if(GetAliveTime()>fadeTimeBegins)alpha=std::max(0.f,util::lerp(255,0,(GetAliveTime()-fadeTimeBegins)/afterImagesLingeringTime));
if(GetAliveTime()>fadeTimeBegins)alpha=std::max(0.f,float(util::lerp(255,0,(GetAliveTime()-fadeTimeBegins)/afterImagesLingeringTime)));
const Animate2D::FrameSequence&animation{ANIMATION_DATA[this->animation]};
const float animationFrameTimer{i*animation.m_fFrameDuration};

@ -0,0 +1,57 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "MenuLabel.h"
//A class holding a lambda function where you return an updated menu label that is declared on creation to reduce boilerplate for menu labels that just need to update text differently..
class DynamicMenuLabel:public MenuLabel{
public:
inline DynamicMenuLabel(geom2d::rect<float>rect,std::function<const std::string()>labelUpdateFunc,float scale=1,ComponentAttr attributes=ComponentAttr::NONE)
:labelUpdateFunc(labelUpdateFunc),MenuLabel(rect,"",scale,attributes){}
inline void SetLabelUpdateFunction(std::function<const std::string()>labelUpdateFunc){
this->labelUpdateFunc=labelUpdateFunc;
}
protected:
inline virtual void Update(AiL*game)override{
SetLabel(labelUpdateFunc());
MenuLabel::Update(game);
}
private:
std::function<const std::string()>labelUpdateFunc;
};

@ -42,18 +42,23 @@ All rights reserved.
INCLUDE_ANIMATION_DATA
INCLUDE_game
INCLUDE_GFX
Effect::Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect::Effect(pos,lifetime,imgFile,upperLevel,0.f,fadeout,vf2d{size,size},spd,col,rotation,rotationSpd,additiveBlending){}
//Note: The fadeout time activates when the original lifetime of the bullet expires! It doesn't begin immediately.
Effect::Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect(pos,lifetime,imgFile,upperLevel,0.f,fadeout,vf2d{size,size},spd,col,rotation,rotationSpd,additiveBlending){}
Effect::Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:pos(pos),lifetime(lifetime),upperLevel(upperLevel),size(size),fadeout(fadeout),original_fadeOutTime(fadeout),spd(spd),col(col),rotation(rotation),rotationSpd(rotationSpd),additiveBlending(additiveBlending){
this->animation.AddState(imgFile,ANIMATION_DATA.at(imgFile));
this->animation.ChangeState(internal_animState,imgFile);
}
//Note: The fadeout time activates when the original lifetime of the bullet expires! It doesn't begin immediately.
Effect::Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,vf2d size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect(pos,lifetime,imgFile,upperLevel,0.f,fadeout,size,spd,col,rotation,rotationSpd,additiveBlending){}
Effect::Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:pos(pos),lifetime(lifetime),upperLevel(upperLevel),size(size),fadein(fadein),original_fadeInTime(fadein),fadeout(fadeout),original_fadeOutTime(fadeout),spd(spd),col(col),rotation(rotation),rotationSpd(rotationSpd),additiveBlending(additiveBlending){
//Note: The fadeout time activates when the original lifetime of the bullet expires! It doesn't begin immediately.
Effect::Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect(pos,lifetime,imgFile,upperLevel,fadein,fadeout,size,spd,EffectType::NONE,col,rotation,rotationSpd,additiveBlending){}
//Note: The fadeout time activates when the original lifetime of the bullet expires! It doesn't begin immediately.
Effect::Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,EffectType type,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:pos(pos),lifetime(lifetime),upperLevel(upperLevel),size(size),fadein(fadein),original_fadeInTime(fadein),fadeout(fadeout),original_fadeOutTime(fadeout),spd(spd),col(col),rotation(rotation),rotationSpd(rotationSpd),additiveBlending(additiveBlending),type(type){
this->animation.AddState(imgFile,ANIMATION_DATA.at(imgFile));
this->animation.ChangeState(internal_animState,imgFile);
}
@ -79,22 +84,36 @@ bool Effect::Update(float fElapsedTime){
return true;
}
void Effect::Draw()const{
void Effect::_Draw()const{
if(additiveBlending)game->SetDecalMode(DecalMode::ADDITIVE);
const bool FadeInFinished{original_fadeInTime==0||fadein==original_fadeInTime};
const bool HasFadeout{fadeout>0};
if(GetZ()>0){
vf2d shadowScale=vf2d{8*size.x/3.f,1}/std::max(1.f,GetZ()/8);
game->view.DrawDecal(pos-vf2d{3,3}*shadowScale/2+vf2d{0,12*size.y},GFX["circle.png"].Decal(),shadowScale,BLACK);
}
Pixel blendCol{col};
[[unlikely]]if(!FadeInFinished){
game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadein/original_fadeInTime*col.a)});
blendCol={col.r,col.g,col.b,uint8_t(fadein/original_fadeInTime*col.a)};
}else
[[likely]]if(HasFadeout){
game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadeout/original_fadeOutTime*col.a)});
}else{
game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,col);
blendCol={col.r,col.g,col.b,uint8_t(fadeout/original_fadeOutTime*col.a)};
}
Draw(blendCol);
game->SetDecalMode(DecalMode::NORMAL);
}
void Effect::Draw(const Pixel blendCol)const{
if(GetZ()>0){
vf2d shadowScale=vf2d{8*size.x/3.f,1}/std::max(1.f,GetZ()/24);
game->view.DrawDecal(pos-vf2d{3,3}*shadowScale/2+vf2d{0,6*size.x},GFX["circle.png"].Decal(),shadowScale,BLACK);
}
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,blendCol);
}
Animate2D::Frame Effect::GetFrame()const{
return animation.GetFrame(internal_animState);
}
@ -105,4 +124,16 @@ bool Effect::OnUpperLevel(){
const EffectType Effect::GetType()const{
return type;
}
const float Effect::GetZ()const{
return z;
}
void Effect::SetType(const EffectType type){
this->type=type;
}
const bool&Effect::OnUpperLevel()const{
return upperLevel;
}

@ -37,8 +37,11 @@ All rights reserved.
#pragma endregion
#pragma once
#include "Animation.h"
#include "IBullet.h"
#include <unordered_set>
#include <variant>
#include "Oscillator.h"
#include "Entity.h"
class Monster;
class Player;
using HitList=std::unordered_set<std::variant<Monster*,Player*>>;
@ -46,11 +49,14 @@ using HitList=std::unordered_set<std::variant<Monster*,Player*>>;
enum class EffectType{
NONE,
SPELL_CIRCLE,
MONSTER_SOUL,
BLINK_PORTAL,
TRAIL_OF_FIRE,
};
struct Effect{
friend class AiL;
friend struct FallingStone;
friend struct FallingBullet;
vf2d pos={0,0};
float lifetime=0;
float fadeout=0;
@ -62,43 +68,66 @@ struct Effect{
float rotationSpd=0;
vf2d scaleSpd{};
bool additiveBlending=false;
float z{};
private:
bool dead=false;
public:
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,const std::string&imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,EffectType type,Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
virtual bool Update(float fElapsedTime);
Animate2D::Frame GetFrame()const;
virtual void Draw()const;
void _Draw()const;
virtual void Draw(const Pixel blendCol)const;
bool OnUpperLevel();
const EffectType GetType()const;
const float GetZ()const;
const bool&OnUpperLevel()const;
void SetType(const EffectType type);
protected:
float original_fadeOutTime;
float original_fadeInTime{};
EffectType type{EffectType::NONE};
private:
Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState;
private:
bool upperLevel=false;
double aliveTime{};
};
struct Meteor:Effect{
Meteor(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
enum MeteorSetting{
METEOR,
COMET,
SOLAR_FLARE,
COMET_FLARE,
};
Meteor(const vf2d pos,const float lifetime,const bool upperLevel,const MeteorSetting setting,const float fadeout=0.0f,const vf2d spd={},const Pixel col=WHITE,const float rotation=0,const float rotationSpd=0,const bool additiveBlending=false);
float startLifetime=0;
bool shakeField=false;
bool Update(float fElapsedTime)override;
void Draw()const override;
void Draw(const Pixel blendCol)const override;
private:
int meteorImpactParticles{};
float randomColorTintR{};
float randomColorTintG{};
float randomColorTintB{};
float meteorRadius{};
float fallSpdMult{1.f};
float damageMult{1.f};
float fireRingLifetime{};
std::string meteorCrashSFX{};
};
struct PulsatingFire:Effect{
PulsatingFire(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
PulsatingFire(vf2d pos,float lifetime,const float radius,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
std::vector<float>pulsatingFireValues;
float lastParticleTimer=0;
float lastDamageTimer=0;
float radius;
bool Update(float fElapsedTime)override;
void Draw()const override;
void Draw(const Pixel blendCol)const override;
};
struct SwordSlash:Effect{
@ -114,7 +143,7 @@ private:
struct ForegroundEffect:Effect{
ForegroundEffect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
ForegroundEffect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
virtual void Draw()const override final;
virtual void Draw(const Pixel blendCol)const override final;
};
struct SpellCircle:Effect{
@ -122,7 +151,7 @@ struct SpellCircle:Effect{
SpellCircle(vf2d pos,float lifetime,std::string imgFile,std::string spellInsigniaFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false,vf2d insigniaSize={1,1},float insigniaFadeout=0.0f,vf2d insigniaSpd={},Pixel insigniaCol=WHITE,float insigniaRotation=0,float insigniaRotationSpd=0,bool insigniaAdditiveBlending=false);
Effect spellInsignia{vf2d{},0.f,"spell_insignia.png",false,{}};
virtual bool Update(float fElapsedTime)override final;
virtual void Draw()const override final;
virtual void Draw(const Pixel blendCol)const override final;
};
struct RockLaunch:Effect{
@ -141,4 +170,79 @@ struct ShineEffect:Effect{
private:
float fadeinTime;
const Pixel originalCol;
};
//Spawns and moves towards the player after some amount of time.
struct MonsterSoul:Effect{
MonsterSoul(vf2d pos,float fadeoutTime,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false);
virtual bool Update(float fElapsedTime)override final;
virtual void Draw(const Pixel blendCol)const override final;
public:
enum Phase{
RISING,
TRACKING_PLAYER,
DEAD,
};
uint8_t alpha{0U};
Phase phase{RISING};
float moveSpd{24.f};
float fadeoutTime;
};
struct FadeInOutEffect:Effect{
//cycleSpd is how long it takes to get from fully opaque to fully transparent, and back to fully opaque
FadeInOutEffect(vf2d pos,const std::string&img,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::function<Effect(const Effect&self)>&particleGenerator={});
//A version with oscillators for position and colors, for extra animation effects!
FadeInOutEffect(Oscillator<vf2d>pos,const std::string&img,float lifetime,bool onUpperLevel,Oscillator<vf2d>size,vf2d spd,Oscillator<Pixel>col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::function<Effect(const Effect&self)>&particleGenerator={});
virtual bool Update(float fElapsedTime)override;
virtual void Draw(const Pixel blendCol)const override;
std::function<Effect(const Effect&self)>particleGenerator;
const float particleSpawnFreq;
Oscillator<vf2d>posOscillator;
Oscillator<vf2d>sizeOscillator;
Oscillator<Pixel>colOscillator;
float particleSpawnTimer{};
const float originalParticleSpawnTimer{};
};
struct LingeringEffect:FadeInOutEffect{
LingeringEffect(vf2d pos,const std::string&img,const std::string&soundEffect,float radius,int damage,float damageFreq,const HurtType friendly,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::function<Effect(const Effect&self)>&particleGenerator={});
virtual bool Update(float fElapsedTime)override final;
const int damage;
float damageTimer{};
const float originalDamageTimer{};
const float radius;
HurtType friendly;
const size_t sfxID{};
};
struct Ink:Effect{
//NOTE: The way ink works is that every update frame, each ink stack will check for distance to player, and if the player collides with the radius of the ink it will add one to the ink debuff for the player.
// At the end of AdventuresInLestoria::HandleUserInput(), the ink stack counter is reset after handling user input for moving the player.
Ink(vf2d pos,float radius,float lifetime,float inkSlowdownDuration,float inkSlowdownPct,float fadeinTime,float fadeoutTime,vf2d size,Pixel col,bool onUpperLevel,float rotation);
virtual bool Update(float fElapsedTime)override final;
private:
const float radius;
const float inkSlowdownDuration;
const float inkSlowdownAmount;
};
struct FlipCoinEffect:Effect{
//cycleSpd is how long it takes to get from fully opaque to fully transparent, and back to fully opaque
FlipCoinEffect(Oscillator<vf2d>startEndPos,float coinArcRiseZAmt,float lifetime,const std::string&imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
virtual bool Update(float fElapsedTime)override;
virtual void Draw(const Pixel blendCol)const override;
Oscillator<vf2d>startEndPos;
Oscillator<float>sizeFlipper;
Oscillator<float>zRiser;
};
struct CollectCoinEffect:Effect{
//cycleSpd is how long it takes to get from fully opaque to fully transparent, and back to fully opaque
CollectCoinEffect(const Entity attachedTarget,float coinArcRiseZAmt,float lifetime,const std::string&imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
virtual bool Update(float fElapsedTime)override;
virtual void Draw(const Pixel blendCol)const override;
const Entity attachedTarget;
Oscillator<float>sizeFlipper;
Oscillator<float>zRiser;
};

@ -45,7 +45,7 @@ INCLUDE_game
EnergyBolt::EnergyBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:Bullet(pos,vel,radius,damage,
"energy_bolt.png",upperLevel,false,INFINITE,true,friendly,col){}
"energy_bolt.png",upperLevel,game->GetPlayer()->HasEnchant("Piercing Bolt")?true:false,INFINITE,true,friendly,col){}
void EnergyBolt::Update(float fElapsedTime){
lastParticleSpawn=std::max(0.f,lastParticleSpawn-fElapsedTime);
@ -53,7 +53,7 @@ void EnergyBolt::Update(float fElapsedTime){
lastParticleSpawn="Wizard.Auto Attack.ParticleFrequency"_F;
game->AddEffect(std::make_unique<Effect>(pos,"Wizard.Auto Attack.ParticleLifetimeRange"_FRange,"energy_particle.png",upperLevel,"Wizard.Auto Attack.ParticleSizeRange"_FRange,"Wizard.Auto Attack.ParticleFadeoutTime"_F,vf2d{"Wizard.Auto Attack.ParticleSpeedRange"_FRange,"Wizard.Auto Attack.ParticleSpeedRange"_FRange}));
}
if(distanceTraveled>"Wizard.Auto Attack.Max Range"_F&&IsActivated()){
if(distanceTraveled>"Wizard.Auto Attack.Range"_F&&IsActivated()){
fadeOutTime="Wizard.Auto Attack.BulletHitFadeoutTime"_F;
}
}
@ -67,8 +67,9 @@ BulletDestroyState EnergyBolt::PlayerHit(Player*player)
BulletDestroyState EnergyBolt::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)
{
fadeOutTime="Wizard.Auto Attack.BulletHitFadeoutTime"_F;
if(!game->GetPlayer()->HasEnchant("Piercing Bolt"))fadeOutTime="Wizard.Auto Attack.BulletHitFadeoutTime"_F;
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Auto Attack.SplashEffectFadeoutTime"_F));
if(hitList.size()==1)damage=ceil(damage*"Piercing Bolt"_ENC["MULTI TARGET DAMAGE"]/100.f);
return BulletDestroyState::KEEP_ALIVE;
}

@ -80,7 +80,7 @@ protected:
for(float yOffset=0;const auto&[attr,value]:itemRef.lock()->GetEnhancementInfo()[itemRef.lock()->EnhancementLevel()].stats){
std::string attributeLabel=std::format("{}",attr.Name());
float attributeLabelWidth=game->GetTextSizeProp(attributeLabel).x;
window.DrawShadowStringPropDecal(drawPos+vf2d{maxAttributeLabelSize/2+24,yOffset}-vf2d{attributeLabelWidth/2,0},attributeLabel,WHITE,BLACK,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x,1.0f);
window.DrawShadowStringPropDecal(drawPos+vf2d{maxAttributeLabelSize/2+24,yOffset}-vf2d{attributeLabelWidth/2,0},attributeLabel,WHITE,BLACK,adjustedScale,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x);
uint8_t nextEnhanceLevel=itemRef.lock()->EnhancementLevel()+1;
float nextStageValue=0;
if(itemRef.lock()->GetEnhancementInfo().size()>nextEnhanceLevel){
@ -92,21 +92,21 @@ protected:
if(Inventory::GetItemCount(itemRef.lock()->ActualName())==0){ //This item hasn't been created yet, so just show that we are developing the item first.
window.DrawShadowStringDecal(drawPos+vf2d{maxAttributeLabelSize+4+24,yOffset},std::format("{:<5}",(
(attr.ShowAsDecimal()?std::format("{:>4.2f}{}",value,percentageSign):std::format("{}{}",value,percentageSign)))),
WHITE,BLACK,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x,1.0f);
WHITE,BLACK,adjustedScale,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x);
}else{ //This item is getting enhanced to the next level.
window.DrawShadowStringDecal(drawPos+vf2d{maxAttributeLabelSize+4+24,yOffset},std::format("{:>5} ->#00AA00 {:<5}",
attr.ShowAsDecimal()?std::format("{:>4.2f}{}",value,percentageSign):std::format("{}{}",value,percentageSign),
(nextEnhanceLevel<="Item.Item Max Enhancement Level"_I&&itemRef.lock()->GetEnhancementInfo()[nextEnhanceLevel].chapterAvailable<=game->GetCurrentChapter())?
attr.ShowAsDecimal()?std::format("{:<4.2f}{}",nextStageValue,percentageSign):std::format("{}{}",nextStageValue,percentageSign)
:"MAX"),
WHITE,BLACK,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x,1.0f);
WHITE,BLACK,adjustedScale,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x);
}
yOffset+=16;
}
}else{
std::string text="Cannot be enhanced.";
float textWidth=game->GetTextSizeProp(text).x;
window.DrawShadowStringPropDecal(rect.middle()-vf2d{textWidth,0}/2,text,RED,BLACK,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x,1.0f);
window.DrawShadowStringPropDecal(rect.middle()-vf2d{textWidth,0}/2,text,RED,BLACK,adjustedScale,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x);
}
}
};

@ -0,0 +1,60 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Entity.h"
#include "Player.h"
#define is(type) std::holds_alternative<type>(entity)
#define get(type) std::get<type>(entity)
Entity::Entity(Player*player):entity(player){}
Entity::Entity(Monster*monster):entity(monster){}
Entity::Entity(const std::variant<Monster*,Player*>ent):entity(ent){}
const vf2d Entity::GetPos()const{
if(is(Player*))return get(Player*)->GetPos();
if(is(Monster*))return get(Monster*)->GetPos();
ERR("Entity is not a valid type! THIS SHOULD NOT BE HAPPENING!");
return {}; //NOTE: Unreachable
}
void Entity::SetIframeTime(const float iframeTime){
if(is(Player*))get(Player*)->ApplyIframes(iframeTime);
else if(is(Monster*))get(Monster*)->ApplyIframes(iframeTime);
else ERR("Entity is not a valid type! THIS SHOULD NOT BE HAPPENING!");
}

@ -0,0 +1,56 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#pragma once
#include "olcUTIL_Geometry2D.h"
#include <variant>
class Player;
class Monster;
//A helper class to connect multiple entity types based on their commonalities without using inheritance shenanigans.
class Entity{
public:
Entity(Player*player);
Entity(Monster*monster);
Entity(const std::variant<Monster*,Player*>ent);
const vf2d GetPos()const;
void SetIframeTime(const float iframeTime);
private:
const std::variant<Monster*,Player*>entity;
};

@ -67,7 +67,8 @@ void EnvironmentalAudio::SetAudioName(const std::string_view audioName){
}
void EnvironmentalAudio::Activate(){
if(activated)return;
soundInstance=Audio::LoadAndPlay(operator""_SFX(SOUND_DATA[audioName].file.c_str(),SOUND_DATA[audioName].file.length()),true);
soundInstance=Audio::LoadAndPlaySFX(operator""_SFX(SOUND_DATA[audioName].file.c_str(),SOUND_DATA[audioName].file.length()),true);
Audio::Engine().SetPitch(soundInstance,util::random_range(0.9f,1.1f));
activated=true;
}
void EnvironmentalAudio::Deactivate(){

@ -52,7 +52,7 @@ public:
inline void OnEquipStatsUpdate()override{
const std::weak_ptr<Item>equip=Inventory::GetEquip(slot);
if(!ISBLANK(equip)){
icon=const_cast<Decal*>(equip.lock()->Decal());
icon=const_cast<Decal*>(equip.lock()->Icon().Decal());
itemRef=equip;
}else{
icon=nullptr;

@ -43,6 +43,9 @@ All rights reserved.
#include <memory>
#include <source_location>
#include <fstream>
#ifndef __EMSCRIPTEN__
#include <stacktrace>
#endif
inline std::ofstream debugLogger;
@ -80,7 +83,9 @@ inline std::ofstream debugLogger;
}
inline static void log(std::stringstream&str,std::source_location loc){
debugLogger<<loc.file_name()<<"("<<loc.line()<<":"<<loc.column()<<") "<<loc.function_name()<<": "<<str.str()<<std::endl;
debugLogger<<std::stacktrace::current()<<std::endl;
std::cout<<loc.file_name()<<"("<<loc.line()<<":"<<loc.column()<<") "<<loc.function_name()<<": "<<str.str()<<std::endl;
std::cout<<std::stacktrace::current()<<std::endl;
throw std::runtime_error{std::format("{}({}:{}) {}: {}",loc.file_name(),loc.line(),loc.column(),loc.function_name(),str.str()).c_str()};
}
};
@ -88,16 +93,16 @@ inline std::ofstream debugLogger;
#define ERR(err) { \
std::stringstream errStream; \
errStream<<err; \
Error::log(errStream,std::source_location::current());}
Error::log(errStream);}
#define LOG(err) { \
std::stringstream errStream; \
errStream<<err; \
Error::log(errStream,std::source_location::current());}
Error::log(errStream);}
class Error{
public:
inline static void log(std::stringstream&str,std::source_location loc){
std::cout<<loc.file_name()<<"("<<loc.line()<<":"<<loc.column()<<") "<<loc.function_name()<<": "<<str.str()<<std::endl;
inline static void log(std::stringstream&str){
std::cout<<__FILE__<<"("<<__LINE__<<":) "<<str.str()<<std::endl;
#ifdef __DEBUG__
throw;
#endif
@ -115,15 +120,15 @@ type DYNAMIC_CAST(auto variable){
}
template<typename T,typename U>
std::shared_ptr<T>DYNAMIC_POINTER_CAST(const std::shared_ptr<U>&variable){
std::shared_ptr<T> newVariable=dynamic_pointer_cast<T>(variable);
const std::shared_ptr<T>DYNAMIC_POINTER_CAST(const std::shared_ptr<U>&variable){
const std::shared_ptr<T> newVariable=dynamic_pointer_cast<T>(variable);
if(!newVariable)ERR("Could not dynamic cast to pointer type "<<typeid(newVariable).name()<<"!");
return newVariable;
}
template<typename T,typename U>
std::shared_ptr<T>DYNAMIC_POINTER_CAST(const std::weak_ptr<U>&variable){
std::shared_ptr<T> newVariable=dynamic_pointer_cast<T>(variable.lock());
const std::shared_ptr<T>DYNAMIC_POINTER_CAST(const std::weak_ptr<U>&variable){
const std::shared_ptr<T> newVariable=dynamic_pointer_cast<T>(variable.lock());
if(!newVariable)ERR("Could not dynamic cast to pointer type "<<typeid(newVariable).name()<<"!");
return newVariable;
}

@ -47,7 +47,7 @@ INCLUDE_ANIMATION_DATA
INCLUDE_game
ExplosiveTrap::ExplosiveTrap(vf2d pos,float radius,float explosionRadius,float automaticDetonationTime,int damage,float fadeinTime,float fadeoutTime,float activationWaitTime,bool upperLevel,bool hitsMultiple,float lifetime,bool friendly,Pixel col,vf2d scale)
:activationWaitTime(activationWaitTime),automaticDetonationTime(automaticDetonationTime),activationRadius(radius),explosionRadius(explosionRadius),Bullet(pos,{},0.f,damage,"Ability Icons/explosive_trap.png",upperLevel,hitsMultiple,lifetime,false,friendly,col,scale,0.f,"Trap Hit"){
:activationWaitTime(activationWaitTime),automaticDetonationTime(automaticDetonationTime),activationRadius(radius),explosionRadius(explosionRadius),explosionCount(game->GetPlayer()->HasEnchant("Concussive Trap")?"Concussive Trap"_ENC["TRAP EXPLODE COUNT"]:1),Bullet(pos,{},0.f,damage,"Ability Icons/explosive_trap.png",upperLevel,hitsMultiple,INFINITE,false,friendly,col,scale,0.f,"Trap Hit"){
fadeInTime=fadeinTime;
animation.AddState("explosive_trap.png",ANIMATION_DATA["explosive_trap.png"]);
if(!friendly)ERR("WARNING! Trying to use unimplemented enemy version of the Explosive Trap Bullet!");
@ -58,7 +58,10 @@ void ExplosiveTrap::Update(float fElapsedTime){
if(IsDeactivated())return;
if(!trapActivated){
if(rearmTime>0.f){
rearmTime-=fElapsedTime;
if(rearmTime<=0.f)Detonate();
}else if(!trapActivated){
activationWaitTime-=fElapsedTime;
if(activationWaitTime<=0.f){
radius=activationRadius;
@ -89,13 +92,14 @@ BulletDestroyState ExplosiveTrap::PlayerHit(Player*player){
}
void ExplosiveTrap::Detonate(){
fadeOutTime=0.5f;
const HurtList list{game->HurtNotHit(pos,"Trapper.Ability 3.Explosion Radius"_F/100.f*24,damage,hitList,OnUpperLevel(),GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)};
const HurtList list{game->Hurt(pos,"Trapper.Ability 3.Explosion Radius"_F/100.f*24,damage,OnUpperLevel(),GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)};
for(const auto&[targetPtr,wasHit]:list){
if(wasHit){
std::get<Monster*>(targetPtr)->ApplyMark("Trapper.Ability 3.Explosion Mark Stack Time"_F,"Trapper.Ability 3.Explosion Mark Stack Increase"_I);
std::get<Monster*>(targetPtr)->ProximityKnockback(pos,"Trapper.Ability 3.Explosion Knockback Amount"_F);
std::get<Monster*>(targetPtr)->Knockup("Trapper.Ability 3.Explosion Knockup Amount"_F);
if(explosionCount==1)std::get<Monster*>(targetPtr)->ProximityKnockback(pos,"Trapper.Ability 3.Explosion Knockback Amount"_F);
else std::get<Monster*>(targetPtr)->ProximityKnockback(pos,20.f);
if(explosionCount==1)std::get<Monster*>(targetPtr)->Knockup("Trapper.Ability 3.Explosion Knockup Amount"_F);
else std::get<Monster*>(targetPtr)->Knockup(0.1f);
}
}
@ -103,7 +107,13 @@ void ExplosiveTrap::Detonate(){
explodeEffect->scaleSpd={0.125f,0.125f};
game->AddEffect(std::move(explodeEffect));
SoundEffect::PlaySFX("Explosion",pos);
Deactivate();
explosionCount--;
if(explosionCount>0)rearmTime=0.6f;
else{
fadeOutTime=0.5f;
Deactivate();
lifetime=0.f;
}
}
BulletDestroyState ExplosiveTrap::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
@ -111,9 +121,6 @@ BulletDestroyState ExplosiveTrap::MonsterHit(Monster&monster,const uint8_t markS
monster.Hurt(0,monster.OnUpperLevel(),monster.GetZ(),HurtFlag::PLAYER_ABILITY);//Triggers mark multiple times after the first mark.
}
monster.ProximityKnockback(pos,"Trapper.Ability 3.Explosion Knockback Amount"_F);
monster.Knockup("Trapper.Ability 3.Explosion Knockup Amount"_F);
Detonate();
return BulletDestroyState::KEEP_ALIVE;

@ -0,0 +1,65 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Effect.h"
#include "AdventuresInLestoria.h"
#include "DEFINES.h"
#include "util.h"
INCLUDE_game
FadeInOutEffect::FadeInOutEffect(vf2d pos,const std::string&img,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending,float particleSpawnFreq,const std::function<Effect(const Effect&self)>&particleGenerator)
:FadeInOutEffect({pos,pos,cycleSpd},img,lifetime,onUpperLevel,{{size,size},{size,size},cycleSpd},spd,{col,{col.r,col.g,col.b,0},cycleSpd},rotation,rotationSpd,additiveBlending,particleSpawnFreq,particleGenerator){}
FadeInOutEffect::FadeInOutEffect(Oscillator<vf2d>pos,const std::string&img,float lifetime,bool onUpperLevel,Oscillator<vf2d>size,vf2d spd,Oscillator<Pixel>col,float rotation,float rotationSpd,bool additiveBlending,float particleSpawnFreq,const std::function<Effect(const Effect&self)>&particleGenerator)
:particleSpawnFreq(particleSpawnFreq),particleGenerator(particleGenerator),particleSpawnTimer(particleSpawnFreq),originalParticleSpawnTimer(particleSpawnTimer),posOscillator(pos),colOscillator(col),sizeOscillator(size),Effect(pos.first,lifetime,img,onUpperLevel,size.first,0.25f,spd,col.first,rotation,rotationSpd,additiveBlending){}
bool FadeInOutEffect::Update(float fElapsedTime){
if(particleGenerator){
particleSpawnTimer-=fElapsedTime;
if(particleSpawnTimer<=0.f){
particleSpawnTimer+=originalParticleSpawnTimer;
game->AddEffect(std::make_unique<Effect>(particleGenerator(*this)));
}
}
pos=posOscillator.Update(fElapsedTime);
size=sizeOscillator.Update(fElapsedTime);
col=colOscillator.Update(fElapsedTime);
return Effect::Update(fElapsedTime);
}
void FadeInOutEffect::Draw(const Pixel blendCol)const{
Effect::Draw(blendCol);
}

@ -45,14 +45,14 @@ All rights reserved.
INCLUDE_game
FallingStone::FallingStone(vf2d targetPos,vf2d vel,float zVel,float indicatorDisplayTime,float radius,int damage,bool upperLevel,bool hitsMultiple,float knockbackAmt,float lifetime,bool friendly,Pixel col,vf2d scale,float image_angle,float spellCircleRotation,float spellCircleRotationSpd,Pixel insigniaCol,float insigniaRotation,float insigniaRotationSpd)
:Bullet(targetPos,vel,radius,damage,"rock.png",upperLevel,false,lifetime+0.1f,false,friendly,col,scale,image_angle),targetPos(targetPos),zVel(zVel),indicatorDisplayTime(indicatorDisplayTime),knockbackAmt(knockbackAmt),
indicator(targetPos,lifetime+0.1f,"range_indicator.png","spell_insignia.png",upperLevel,radius/12.f,0.5f,{},col,spellCircleRotation,spellCircleRotationSpd,false,radius/12.f,0.f,{},insigniaCol,insigniaRotation,insigniaRotationSpd,false){
FallingBullet::FallingBullet(const std::string&imgName,vf2d targetPos,vf2d vel,float zVel,float indicatorDisplayTime,float radius,int damage,bool upperLevel,bool hitsMultiple,float knockbackAmt,float lifetime,bool friendly,Pixel spellCircleCol,vf2d scale,float image_angle,float spellCircleRotation,float spellCircleRotationSpd,Pixel insigniaCol,float insigniaRotation,float insigniaRotationSpd)
:Bullet(targetPos,vel,0.f,damage,imgName,upperLevel,false,lifetime+0.1f,false,friendly,WHITE,scale,image_angle),targetPos(targetPos),zVel(zVel),indicatorDisplayTime(indicatorDisplayTime),knockbackAmt(knockbackAmt),collisionRadius(radius),
indicator(targetPos,lifetime+0.1f,"range_indicator.png","spell_insignia.png",upperLevel,radius/12.f,0.5f,{},spellCircleCol,spellCircleRotation,spellCircleRotationSpd,false,radius/12.f,0.f,{},insigniaCol,insigniaRotation,insigniaRotationSpd,false){
pos+=-vel*lifetime;
z=-zVel*lifetime;
}
void FallingStone::Update(float fElapsedTime){
void FallingBullet::Update(float fElapsedTime){
z+=zVel*fElapsedTime;
lastTrailEffect-=fElapsedTime;
if(z<=0.f){
@ -62,16 +62,15 @@ void FallingStone::Update(float fElapsedTime){
fadeOutTime=0.5f;
SoundEffect::PlaySFX("Stone Land",pos);
if(friendly){
for(auto&[monsterPtr,hurt]:game->Hurt(targetPos,radius,damage,OnUpperLevel(),z,HurtType::MONSTER)){
for(auto&[monsterPtr,hurt]:game->Hurt(pos,collisionRadius,damage,OnUpperLevel(),z,HurtType::MONSTER)){
if(hurt)std::get<Monster*>(monsterPtr)->ApplyIframes(0.1f);
}
}
else{
for(auto&[playerPtr,hurt]:game->Hurt(targetPos,radius,damage,OnUpperLevel(),z,HurtType::PLAYER)){
}else{
for(auto&[playerPtr,hurt]:game->Hurt(pos,collisionRadius,damage,OnUpperLevel(),z,HurtType::PLAYER)){
if(hurt)std::get<Player*>(playerPtr)->ApplyIframes(0.1f);
}
}
game->ProximityKnockback(targetPos,radius,knockbackAmt,HurtType::PLAYER|HurtType::MONSTER);
game->ProximityKnockback(pos,collisionRadius,knockbackAmt,HurtType::PLAYER|HurtType::MONSTER);
for(int i:std::ranges::iota_view(0,30))game->AddEffect(std::make_unique<Effect>(pos-vf2d{0.f,GetZ()},util::random_range(0.05f,0.2f),"circle_outline.png",OnUpperLevel(),util::random_range(0.5f,1.f),0.2f,vf2d{util::random_range(-10.f,10.f),util::random_range(-3.f,0.f)},PixelLerp(BLACK,col,util::random(1.f)),0.f,0.f,true));
Deactivate();
}
@ -82,11 +81,11 @@ void FallingStone::Update(float fElapsedTime){
}
indicator.Update(fElapsedTime);
}
void FallingStone::Draw(const Pixel blendCol)const{
void FallingBullet::Draw(const Pixel blendCol)const{
if(lifetime<=indicatorDisplayTime){
indicator.Draw();
indicator._Draw();
}
Bullet::Draw(blendCol);
}
void FallingStone::ModifyOutgoingDamageData(HurtDamageInfo&data){}
void FallingBullet::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -41,13 +41,16 @@ All rights reserved.
#include "DEFINES.h"
#include "util.h"
#include "SoundEffect.h"
#include "TrailEffect.h"
INCLUDE_game
INCLUDE_MONSTER_LIST
FireBolt::FireBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:Bullet(pos,vel,radius,damage,
"energy_bolt.png",upperLevel,false,INFINITE,true,friendly,col){}
"energy_bolt.png",upperLevel,false,INFINITE,true,friendly,col){
if(game->GetPlayer()->HasEnchant("Trail of Fire"))flameTrail=dynamic_cast<TrailEffect&>(game->AddEffect(std::make_unique<TrailEffect>(pos,"Trail of Fire"_ENC["TRAIL DURATION"],"FlamesTexture.png","Trail of Fire"_ENC["TRAIL DAMAGE"]/100.f*game->GetPlayer()->GetAttack(),"Trail of Fire"_ENC["TRAIL TICK FREQUENCY"],upperLevel,1.f,vf2d{1.f,2.f},30000.f,Oscillator<Pixel>{{255,0,0,128},Pixel(0xE74F30),2.f},EffectType::TRAIL_OF_FIRE,true),true));
}
void FireBolt::Update(float fElapsedTime){
lastParticleSpawn=std::max(0.f,lastParticleSpawn-fElapsedTime);
@ -72,6 +75,8 @@ void FireBolt::Update(float fElapsedTime){
SoundEffect::PlaySFX("Wizard Fire Bolt Hit",pos);
}
if(flameTrail)flameTrail.value().get().SetEndPos(pos);
}
BulletDestroyState FireBolt::PlayerHit(Player*player)

@ -0,0 +1,58 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Effect.h"
#include "AdventuresInLestoria.h"
#include "DEFINES.h"
#include "util.h"
INCLUDE_game
FlipCoinEffect::FlipCoinEffect(Oscillator<vf2d>startEndPos,float coinArcRiseZAmt,float lifetime,const std::string&imgFile,bool upperLevel,float size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect(startEndPos.first,lifetime,imgFile,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending),startEndPos(startEndPos),sizeFlipper(-1.f,1.f,lifetime*2.f),zRiser(0.f,coinArcRiseZAmt,1/lifetime){
}
bool FlipCoinEffect::Update(float fElapsedTime){
zRiser.Update(fElapsedTime);
pos=startEndPos.Update(fElapsedTime);
size.y=sizeFlipper.Update(fElapsedTime);
z=zRiser.get();
return Effect::Update(fElapsedTime);
}
void FlipCoinEffect::Draw(const Pixel blendCol)const{
Effect::Draw(blendCol);
}

@ -47,12 +47,6 @@ ForegroundEffect::ForegroundEffect(vf2d pos,float lifetime,std::string imgFile,b
:Effect(pos,lifetime,imgFile,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending){}
ForegroundEffect::ForegroundEffect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:Effect(pos,lifetime,imgFile,upperLevel,size,fadeout,spd,col,rotation,rotationSpd,additiveBlending){}
void ForegroundEffect::Draw()const{
if(additiveBlending)game->SetDecalMode(DecalMode::ADDITIVE);
if(fadeout==0){
game->DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,col);
} else {
game->DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadeout/original_fadeOutTime*255)});
}
game->SetDecalMode(DecalMode::NORMAL);
void ForegroundEffect::Draw(const Pixel blendCol)const{
game->DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,blendCol);
}

@ -51,11 +51,11 @@ using A=Attribute;
void Monster::STRATEGY::FROG(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::LOCKON_WAITTIME)=std::max(0.0f,m.F(A::LOCKON_WAITTIME)-fElapsedTime);
phase:
switch(m.I(A::PHASE)){
switch(PHASE()){
case 0:{ //Move towards phase.
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
if(distToPlayer<24*ConfigInt("Range")/100.f){
m.I(A::PHASE)++;
SETPHASE(PHASE()+1);
m.F(A::LOCKON_WAITTIME)=ConfigFloat("Lockon Wait Time");
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
m.RotateTowardsPos(m.V(A::LOCKON_POS));
@ -72,23 +72,23 @@ void Monster::STRATEGY::FROG(Monster&m,float fElapsedTime,std::string strategy){
SoundEffect::PlaySFX("Slime Shoot",m.pos);
CreateBullet(FrogTongue)(m,tongueMaxRangePos,ConfigFloat("Attack Duration"),m.GetAttack(),m.OnUpperLevel(),ConfigFloat("Tongue Knockback Strength"),false,ConfigPixel("Tongue Color"))EndBullet;
m.PerformShootAnimation();
m.I(A::PHASE)=2;
SETPHASE(2);
}
m.PerformIdleAnimation();
}break;
case 2:{
if(m.F(A::LOCKON_WAITTIME)==0.0f){
m.F(A::LOCKON_WAITTIME)=ConfigFloat("Attack Recovery Time");
m.I(A::PHASE)=3;
SETPHASE(3);
}
}break;
case 3:{
if(m.F(A::LOCKON_WAITTIME)==0.0f){
m.I(A::PHASE)=0;
SETPHASE(0);
}
}break;
default:{
ERR(std::format("Unhandled phase {} for {} strategy!",m.I(A::PHASE),strategy));
ERR(std::format("Unhandled phase {} for {} strategy!",PHASE(),strategy));
}
}
}

@ -56,13 +56,14 @@ void FrogTongue::Update(float fElapsedTime){
geom2d::line<float>lineToTarget(pos,targetPos);
vf2d drawVec=lineToTarget.vector().norm()*3;
tongueLength=util::lerp(0,lineToTarget.length(),pow(sin((lifetime*PI)/duration),20.f));
tongueLength=util::lerp(0.f,lineToTarget.length(),pow(sin((lifetime*PI)/duration),20.f));
vf2d tongueEndPos=geom2d::line<float>(pos+drawVec,targetPos).upoint(pow(sin((lifetime*PI)/duration),20.f));
geom2d::line<float>tongueLine(pos+drawVec,tongueEndPos);
if(!friendly&&geom2d::overlaps(game->GetPlayer()->Hitbox(),tongueLine)){
if(!friendly&&hitList.find(game->GetPlayer())==hitList.end()&&geom2d::overlaps(game->GetPlayer()->Hitbox(),tongueLine)){
_PlayerHit(game->GetPlayer());
hitList.insert(game->GetPlayer());
}
if(friendly){
for(std::shared_ptr<Monster>&m:MONSTER_LIST){

@ -0,0 +1,298 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "MonsterStrategyHelpers.h"
#include "util.h"
#include "AdventuresInLestoria.h"
#include "SoundEffect.h"
#include "BulletTypes.h"
#include <ranges>
using A=Attribute;
INCLUDE_game
INCLUDE_MONSTER_LIST
void Monster::STRATEGY::GHOST_OF_PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std::string strategy){
enum PhaseName{
INIT,
NORMAL,
AFTERIMAGE_FADEIN,
GHOSTSABER_SLASH=999,
TOSS_COIN,
HIDING,
};
enum CannonShotType{
BOMBARDMENT,//every shot is at a random location within 900 Range of the player. (This is also the move during hide and seek.)
PRECISE_BOMBARDMENT,//same as before but within 700 range of the player.
LINE,//the 4th or 5th hit would hit the player if the player doesnt move at all.
SHARPSHOOTER,//aiming directly at the player, skipping every 2nd shot. (only 4 instead of 8 shots)
PREDICTION,//shoots in the direction of the current or last players movement and predicts where the player would be in 2.8 seconds. (slightly further then where the player would be at impact. thats why 2.8 instead of 2.5 seconds)
};
static const uint8_t PHASE_COUNT{uint8_t(DATA.GetProperty("MonsterStrategy.Ghost of Pirate Captain.Cannon Cycle").GetValueCount())};
static uint8_t TOTAL_CANNON_SHOTS{0};
const bool IsHiding{m.V(A::HIDING_POS)!=vf2d{}};
const auto AdvanceCannonPhase{[&m,&strategy](){
m.GetFloat(A::CANNON_TIMER)=0.f;
if(m.GetInt(A::CANNON_PHASE)+1>=PHASE_COUNT)m.GetInt(A::CANNON_SHOT_TYPE)=util::random()%5;
const int prevCannonPhase{m.I(A::CANNON_PHASE)};
m.I(A::CANNON_PHASE)=(m.I(A::CANNON_PHASE)+1)%PHASE_COUNT;
if(prevCannonPhase>m.I(A::CANNON_PHASE)){//Phase has wrapped around, reset cannon shot count.
m.I(A::CANNON_SHOT_COUNT)=0;
if(!m.B(A::FIRST_WAVE_COMPLETE)){
m.B(A::FIRST_WAVE_COMPLETE)=true;
m.ForceSetPos(m.spawnPos);
m.SetupAfterImage();
m.afterImagePos=m.GetPos();
m.SetCollisionRadius(0.f);
m.F(A::CASTING_TIMER)=1.f;
SETPHASE(AFTERIMAGE_FADEIN);
}
}
}};
if(m.F(A::SHRAPNEL_SHOT_FALL_TIMER)>0.f){
m.F(A::SHRAPNEL_SHOT_FALL_TIMER)-=fElapsedTime;
while(m.I(A::SHRAPNEL_SHOT_COUNT)&&m.F(A::SHRAPNEL_SHOT_FALL_TIMER)<=0.f){
const float randomAng{util::random_range(0,2*PI)};
const float range{util::random_range(0,ConfigPixels("Bombardment Max Distance"))};
const vf2d targetPos{game->GetPlayer()->GetPos()+vf2d{range,randomAng}.cart()};
m.F(A::SHRAPNEL_SHOT_FALL_TIMER)+=ConfigFloat("Shrapnel Shot Bullet Separation");
CreateBullet(FallingBullet)("cannonball.png",targetPos,ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Shrapnel Shot Bullet Radius"),ConfigInt("Shrapnel Shot Damage"),m.OnUpperLevel(),false,ConfigFloat("Shrapnel Knockback Amt"),ConfigFloat("Shrapnel Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Shrapnel Shot Bullet Radius")/100.f*1.75f,ConfigFloat("Shrapnel Shot Bullet Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
m.I(A::SHRAPNEL_SHOT_COUNT)--;
}
}
switch(PHASE()){
enum CannonPhaseType{
CANNON_SHOT,
SILENCE,
SHRAPNEL_SHOT,
};
case INIT:{
m.SetStrategyDeathFunction([](GameEvent&ev,Monster&m,const std::string&strategy){
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(m->IsAlive())m->_DealTrueDamage(m->GetHealth(),HurtFlag::NONE);
}
return false;
});
for(int i:std::ranges::iota_view(0U,PHASE_COUNT)){
const int cannonCycle{DATA.GetProperty("MonsterStrategy.Ghost of Pirate Captain.Cannon Cycle").GetInt(i)};
m.VEC(A::CANNON_PHASES).emplace_back(cannonCycle);
if(cannonCycle==CANNON_SHOT)TOTAL_CANNON_SHOTS++;
}
m.B(A::FIRST_WAVE_COMPLETE)=false;
m.I(A::CANNON_SHOT_TYPE)=BOMBARDMENT;
m.ForceSetPos({-400.f,-400.f});
SETPHASE(NORMAL);
}break;
case NORMAL:{
m.F(A::CANNON_TIMER)+=fElapsedTime;
m.F(A::SHRAPNEL_CANNON_TIMER)+=fElapsedTime;
const int phase{std::any_cast<int>(m.VEC(A::CANNON_PHASES)[m.I(A::CANNON_PHASE)])};
switch(phase){
case CANNON_SHOT:{//Normal Cannon Shot. Takes on one of five varieties.
if(m.F(A::CANNON_TIMER)>=ConfigFloat("Cannon Shot Delay")){
switch(m.I(A::CANNON_SHOT_TYPE)){
case BOMBARDMENT:{
const float randomAng{util::random_range(0,2*PI)};
const float range{util::random_range(0,ConfigPixels("Bombardment Max Distance"))};
const vf2d targetPos{game->GetPlayer()->GetPos()+vf2d{range,randomAng}.cart()};
CreateBullet(FallingBullet)("cannonball.png",targetPos,ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
}break;
case PRECISE_BOMBARDMENT:{
const float randomAng{util::random_range(0,2*PI)};
const float range{util::random_range(0,ConfigPixels("Precise Bombardment Max Distance"))};
const vf2d targetPos{game->GetPlayer()->GetPos()+vf2d{range,randomAng}.cart()};
CreateBullet(FallingBullet)("cannonball.png",targetPos,ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
}break;
case LINE:{
//Draw a line from one side of the screen to the other, drawing through the middle.
if(m.I(A::CANNON_SHOT_COUNT)==0)m.F(A::LINE_SHOT_ANG)=util::random_range(0,2*PI);
const vf2d targetPos{geom2d::line{game->GetPlayer()->GetPos()+vf2d{float(game->ScreenHeight()),m.F(A::LINE_SHOT_ANG)}.cart(),game->GetPlayer()->GetPos()+vf2d{float(game->ScreenHeight()),m.F(A::LINE_SHOT_ANG)+PI}.cart()}.upoint(float(m.I(A::CANNON_SHOT_COUNT))/TOTAL_CANNON_SHOTS)};
CreateBullet(FallingBullet)("cannonball.png",targetPos,ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
}break;
case SHARPSHOOTER:{
if(m.I(A::CANNON_SHOT_COUNT)%2==0)CreateBullet(FallingBullet)("cannonball.png",game->GetPlayer()->GetPos(),ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
}break;
case PREDICTION:{
LOG(std::format("Previous Pos: {} Current: {}",game->GetPlayer()->GetPreviousPos().str(),game->GetPlayer()->GetPos().str()));
const float angle{util::angleTo(game->GetPlayer()->GetPreviousPos(),game->GetPlayer()->GetPos())};
const float range{util::random_range(0,100.f*game->GetPlayer()->GetMoveSpdMult())*ConfigFloat("Cannon Shot Impact Time")};
LOG(std::format("Range/Angle: {}",vf2d{range,angle}.str()));
const vf2d targetPos{game->GetPlayer()->GetPos()+vf2d{range,angle}.cart()};
CreateBullet(FallingBullet)("cannonball.png",targetPos,ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
}break;
}
AdvanceCannonPhase();
m.I(A::CANNON_SHOT_COUNT)++;
}
}break;
case SILENCE:{
if(m.F(A::CANNON_TIMER)>=ConfigFloat("Silence Time"))AdvanceCannonPhase();
}break;
case SHRAPNEL_SHOT:{
if(m.F(A::CANNON_TIMER)>=ConfigFloat("Shrapnel Shot Delay")){
m.I(A::SHRAPNEL_SHOT_COUNT)=ConfigInt("Shrapnel Shot Bullet Count");
m.F(A::SHRAPNEL_SHOT_FALL_TIMER)=ConfigFloat("Shrapnel Shot Bullet Separation");
AdvanceCannonPhase();
}
}break;
}
if(Config("Curse Thresholds").GetValueCount()>m.I(A::CURSE_THRESHOLD_ARRAY_IND)&&
m.GetHealthRatio()<=ConfigFloatArr("Curse Thresholds",m.I(A::CURSE_THRESHOLD_ARRAY_IND))/100.f){
m.I(A::CURSE_THRESHOLD_ARRAY_IND)++;
m.F(A::TOSS_COIN_WAIT_TIMER)=ConfigFloat("Coin Toss Pause Time");
m.V(A::TOSS_COIN_TARGET)=game->GetPlayer()->GetPos();
game->AddEffect(std::make_unique<FlipCoinEffect>(Oscillator<vf2d>{m.GetPos(),m.V(A::TOSS_COIN_TARGET),1.f/m.F(A::TOSS_COIN_WAIT_TIMER)/2.f},ConfigFloat("Coin Toss Rise Amount"),m.F(A::TOSS_COIN_WAIT_TIMER),"coin.png",m.OnUpperLevel(),3.f));
#pragma region Determine a hiding spot
const auto&hidingSpots{game->GetZones().at("Hiding Spot")};
if(hidingSpots.size()==0)ERR("WARNING! Could not find any zones with the name \"Hiding Spot\" on the map!! THIS SHOULD NOT BE HAPPENING!")
m.V(A::HIDING_POS)=hidingSpots[util::random()%hidingSpots.size()].zone.middle();
#pragma endregion
SETPHASE(TOSS_COIN);
}
m.F(A::GHOST_SABER_TIMER)-=fElapsedTime;
m.F(A::LAST_COLLISION_TIMER)-=fElapsedTime;
if(m.B(A::FIRST_WAVE_COMPLETE)){
if(m.F(A::LAST_COLLISION_TIMER)<=0.f){
m.UpdateFacingDirection(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
m.SetVelocity(util::pointTo(m.GetPos(),game->GetPlayer()->GetPos())*100.f*m.GetMoveSpdMult());
const float distToPlayer{util::distance(m.GetPos(),game->GetPlayer()->GetPos())};
if(m.F(A::GHOST_SABER_TIMER)<=0.f&&distToPlayer<=ConfigPixels("Ghost Saber Activation Range")){
m.F(A::GHOST_SABER_TIMER)=ConfigFloat("Ghost Saber Cooldown");
const float playerToMonsterAngle{util::pointTo(game->GetPlayer()->GetPos(),m.GetPos()).polar().y};
CreateBullet(GhostSaber)(m.GetPos(),m.GetWeakPointer(),ConfigFloat("Ghost Saber Lifetime"),ConfigFloat("Ghost Saber Distance"),ConfigFloat("Ghost Saber Knockback Amt"),playerToMonsterAngle,ConfigFloat("Ghost Saber Radius"),ConfigFloat("Ghost Saber Expand Spd"),ConfigInt("Ghost Saber Damage"),m.OnUpperLevel(),util::degToRad(ConfigFloat("Ghost Saber Rotation Spd")))EndBullet;
}
}
if(m.B(A::COLLIDED_WITH_PLAYER)){
m.PerformAnimation("SLASHING");
m.F(A::GHOST_SABER_SLASH_ANIMATION_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration();
m.F(A::LAST_COLLISION_TIMER)=ConfigFloat("Collision Recovery Time");
m.I(A::PREVIOUS_PHASE)=PHASE();
SETPHASE(GHOSTSABER_SLASH);
}
}
}break;
case AFTERIMAGE_FADEIN:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
SETPHASE(NORMAL);
m.SetCollisionRadius(m.GetOriginalCollisionRadius());
}
}break;
case GHOSTSABER_SLASH:{ //This was triggered by GhostSaber when
m.F(A::GHOST_SABER_SLASH_ANIMATION_TIMER)-=fElapsedTime;
if(m.F(A::GHOST_SABER_SLASH_ANIMATION_TIMER)<=0.f){
m.PerformIdleAnimation();
SETPHASE(m.I(A::PREVIOUS_PHASE));
}
}break;
case TOSS_COIN:{
m.F(A::TOSS_COIN_WAIT_TIMER)-=fElapsedTime;
if(m.F(A::TOSS_COIN_WAIT_TIMER)<=0.f){
const float curseDmgPctOverTime{ConfigFloat("Curse Damage")};
game->GetPlayer()->AddBuff(BuffType::PIRATE_GHOST_CAPTAIN_PRECURSE,ConfigFloat("Curse Damage Wait Time"),ceil(game->GetPlayer()->GetMaxHealth()*ConfigFloat("Curse Damage")/100.f)+1,[curseDmgPctOverTime](Player*attachedTarget,Buff&b){
attachedTarget->AddBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_DOT,BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_PCT_DAMAGE_OVER_TIME,INFINITY,curseDmgPctOverTime,1.f);
});
game->SpawnMonster(m.V(A::TOSS_COIN_TARGET),MONSTER_DATA["Pirate's Coin"],m.OnUpperLevel());
m.SetupAfterImage();
m.afterImagePos=m.GetPos();
m.SetPos(m.V(A::HIDING_POS));
m.SetVelocity({});
m.arrowIndicator=false; //While the boss is hiding, the indicator will not show up.
m.SetStrategyOnHitFunction([&m](const HurtDamageInfo damageData,Monster&monster,const StrategyName&strategyName)->void{
m.SetPhase(strategyName,NORMAL);
m.arrowIndicator=true;
m.SetStrategyOnHitFunction({});
});
SETPHASE(HIDING);
}
}break;
case HIDING:{
m.F(A::CANNON_TIMER)+=fElapsedTime;
if(m.F(A::CANNON_TIMER)>=ConfigFloat("Cannon Shot Delay")){
switch(m.I(A::CANNON_SHOT_TYPE)){
case BOMBARDMENT:{
const float randomAng{util::random_range(0,2*PI)};
const float range{util::random_range(0,ConfigPixels("Bombardment Max Distance"))};
const vf2d targetPos{game->GetPlayer()->GetPos()+vf2d{range,randomAng}.cart()};
CreateBullet(FallingBullet)("cannonball.png",targetPos,ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
}break;
case PRECISE_BOMBARDMENT:{
const float randomAng{util::random_range(0,2*PI)};
const float range{util::random_range(0,ConfigPixels("Precise Bombardment Max Distance"))};
const vf2d targetPos{game->GetPlayer()->GetPos()+vf2d{range,randomAng}.cart()};
CreateBullet(FallingBullet)("cannonball.png",targetPos,ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
}break;
case LINE:{
//Draw a line from one side of the screen to the other, drawing through the middle.
if(m.I(A::CANNON_SHOT_COUNT)==0)m.F(A::LINE_SHOT_ANG)=util::random_range(0,2*PI);
const vf2d targetPos{geom2d::line{game->GetPlayer()->GetPos()+vf2d{float(game->ScreenHeight()),m.F(A::LINE_SHOT_ANG)}.cart(),game->GetPlayer()->GetPos()+vf2d{float(game->ScreenHeight()),m.F(A::LINE_SHOT_ANG)+PI}.cart()}.upoint(float(m.I(A::CANNON_SHOT_COUNT))/TOTAL_CANNON_SHOTS)};
CreateBullet(FallingBullet)("cannonball.png",targetPos,ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
}break;
case SHARPSHOOTER:{
if(m.I(A::CANNON_SHOT_COUNT)%2==0)CreateBullet(FallingBullet)("cannonball.png",game->GetPlayer()->GetPos(),ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
}break;
case PREDICTION:{
LOG(std::format("Previous Pos: {} Current: {}",game->GetPlayer()->GetPreviousPos().str(),game->GetPlayer()->GetPos().str()));
const float angle{util::angleTo(game->GetPlayer()->GetPreviousPos(),game->GetPlayer()->GetPos())};
const float range{util::random_range(0,100.f*game->GetPlayer()->GetMoveSpdMult())*ConfigFloat("Cannon Shot Impact Time")};
LOG(std::format("Range/Angle: {}",vf2d{range,angle}.str()));
const vf2d targetPos{game->GetPlayer()->GetPos()+vf2d{range,angle}.cart()};
CreateBullet(FallingBullet)("cannonball.png",targetPos,ConfigVec("Cannon Vel"),ConfigFloatArr("Cannon Vel",2),ConfigFloat("Indicator Time"),ConfigPixels("Cannon Radius"),ConfigInt("Cannon Damage"),m.OnUpperLevel(),false,ConfigFloat("Cannon Knockback Amt"),ConfigFloat("Cannon Shot Impact Time"),false,ConfigPixel("Cannon Spell Circle Color"),vf2d{ConfigFloat("Cannon Radius")/100.f*1.75f,ConfigFloat("Cannon Radius")/100.f*1.75f},util::random(2*PI),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Circle Rotation Spd")),ConfigPixel("Cannon Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Cannon Spell Insignia Rotation Spd")))EndBullet;
}break;
}
AdvanceCannonPhase();
m.I(A::CANNON_SHOT_COUNT)++;
}
if(m.F(A::SHRAPNEL_CANNON_TIMER)>=ConfigFloat("Shrapnel Hiding Shot Delay")){
m.I(A::SHRAPNEL_SHOT_COUNT)=ConfigInt("Shrapnel Shot Bullet Count");
m.F(A::SHRAPNEL_SHOT_FALL_TIMER)=ConfigFloat("Shrapnel Shot Bullet Separation");
m.F(A::SHRAPNEL_CANNON_TIMER)=0.f;
}
}break;
}
}

@ -0,0 +1,84 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "BulletTypes.h"
#include "Attributable.h"
GhostSaber::GhostSaber(const vf2d pos,const std::weak_ptr<Monster>target,const float lifetime,const float distFromTarget,const float knockbackAmt,const float initialRot,const float radius,const float expandSpd,const int damage,const bool upperLevel,const float rotSpd,const bool friendly,const Pixel col,const vf2d scale,const float image_angle)
:Bullet(target.lock()->GetPos()+vf2d{distFromTarget,initialRot}.cart(),{},radius,damage,"ghost_dagger.png",upperLevel,false,INFINITE,false,friendly,col,scale,image_angle),attachedMonster(target),rotSpd(rotSpd),distFromTarget(distFromTarget),rot(initialRot),aliveTime(lifetime),knockbackAmt(knockbackAmt),expandSpd(expandSpd){}
void GhostSaber::Update(float fElapsedTime){
alphaOscillator.Update(fElapsedTime);
particleTimer-=fElapsedTime;
aliveTime-=fElapsedTime;
distFromTarget+=expandSpd*fElapsedTime;
if(particleTimer<=0.f){
particleTimer+=0.05f;
game->AddEffect(std::make_unique<ShineEffect>(pos,0.1f,0.1f,"pixel.png",2.f,vf2d{},Pixel{239,215,98,192},util::random(2*PI),0.f,true));
}
rot+=rotSpd*fElapsedTime;
if(!attachedMonster.expired()){
pos=attachedMonster.lock()->GetPos()+vf2d{distFromTarget,rot}.cart();
}
if(aliveTime<=0.f&&!IsDeactivated()){
Deactivate();
fadeOutTime=0.5f;
}
col.a=alphaOscillator.get();
image_angle=-rot*2;
};
BulletDestroyState GhostSaber::PlayerHit(Player*player){
player->ApplyIframes(0.2f);
player->Knockback(vf2d{knockbackAmt,rot}.cart());
if(!attachedMonster.expired()){
const int GHOSTSABER_SLASH_PHASEID{999};
attachedMonster.lock()->GetInt(Attribute::PREVIOUS_PHASE)=attachedMonster.lock()->GetPhase(attachedMonster.lock()->GetStrategyName());
attachedMonster.lock()->SetPhase(attachedMonster.lock()->GetStrategyName(),GHOSTSABER_SLASH_PHASEID);
attachedMonster.lock()->PerformAnimation("SLASHING");
attachedMonster.lock()->GetFloat(Attribute::GHOST_SABER_SLASH_ANIMATION_TIMER)=attachedMonster.lock()->GetCurrentAnimation().GetTotalAnimationDuration();
}
return BulletDestroyState::KEEP_ALIVE;
}
BulletDestroyState GhostSaber::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
monster.ApplyIframes(0.2f);
monster.Knockback(vf2d{knockbackAmt,rot}.cart());
return BulletDestroyState::KEEP_ALIVE;
}
void GhostSaber::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -0,0 +1,84 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "MonsterStrategyHelpers.h"
#include "util.h"
#include "AdventuresInLestoria.h"
#include "SoundEffect.h"
#include "BulletTypes.h"
using A=Attribute;
INCLUDE_game
void Monster::STRATEGY::GIANT_CRAB(Monster&m,float fElapsedTime,std::string strategy){
enum PhaseName{
PREPARE_CHARGE,
CHARGE,
};
switch(PHASE()){
case PREPARE_CHARGE:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
m.AddBuff(BuffType::SPEEDBOOST,ConfigFloat("Charge Time"),0.f);
m.F(A::SPEED_RAMPUP_TIMER)=0.1f;
SETPHASE(CHARGE);
}
}break;
case CHARGE:{
m.F(A::CHASE_TIMER)+=fElapsedTime;
m.F(A::SPEED_RAMPUP_TIMER)-=fElapsedTime;
if(m.F(A::CHASE_TIMER)>=ConfigFloat("Charge Time")||m.ReachedTargetPos()){
m.F(A::CHASE_TIMER)=0.f;
m.target=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(util::distance(m.GetPos(),game->GetPlayer()->GetPos())+ConfigPixels("Charge Extend Distance"));
m.RemoveBuff(BuffType::SPEEDBOOST);
m.F(A::CASTING_TIMER)=ConfigFloat("Charge Wait Time");
SETPHASE(PREPARE_CHARGE);
}
if(m.F(A::SPEED_RAMPUP_TIMER)<=0.f){
m.F(A::SPEED_RAMPUP_TIMER)=0.1f;
if(m.HasBuff(BuffType::SPEEDBOOST)){
const float buffIntensity{m.GetBuffs(BuffType::SPEEDBOOST)[0].intensity};
m.EditBuff(BuffType::SPEEDBOOST,0).intensity=std::min(buffIntensity+ConfigFloat("Movespeed Rampup Final Amount")/100.f/(ConfigFloat("Movespeed Rampup Time")/0.1f),ConfigFloat("Movespeed Rampup Final Amount")/100.f);
}
}
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}break;
}
}

@ -0,0 +1,179 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "MonsterStrategyHelpers.h"
#include "util.h"
#include "AdventuresInLestoria.h"
#include "SoundEffect.h"
#include "BulletTypes.h"
using A=Attribute;
INCLUDE_game
INCLUDE_MONSTER_LIST
void Monster::STRATEGY::GIANT_OCTOPUS(Monster&m,float fElapsedTime,std::string strategy){
enum PhaseName{
INIT,
IDENTIFY_ARMS,
NORMAL,
HURT_ANIMATION,
DEAD,
};
if(!m.B(A::ARM_SPEEDS_INCREASED)&&m.GetHealth()<=ConfigInt("Arm Speedup Health Threshold")){
m.B(A::ARM_SPEEDS_INCREASED)=true;
for(std::shared_ptr<Monster>&arm:MONSTER_LIST){
const std::string OCTOPUS_ARM_NAME{"Octopus Arm"};
if(arm->GetName()==OCTOPUS_ARM_NAME){
std::weak_ptr<Monster>armPtr{arm};
if(!armPtr.expired())armPtr.lock()->GetFloat(A::ATTACK_ANIMATION_SPEED)=1.f+ConfigFloat("Arm Animation Speed Increase")/100.f;
}
}
}
switch(PHASE()){
case INIT:{
m.F(A::BREAK_TIME)=0.5f;
m.AddBuff(BuffType::DAMAGE_REDUCTION,INFINITE,ConfigFloat("Permanent Resistance Buff")/100.f);
m.SetStrategyDeathFunction([](GameEvent&event,Monster&m,const std::string&strategy){
std::string takoyakiImgDir{"item_img_directory"_S+"Takoyaki.png"};
if(!GFX.count(takoyakiImgDir))ERR(std::format("WARNING! Could not find item image {}",takoyakiImgDir));
game->AddEffect(std::make_unique<Effect>(m.GetPos(),INFINITE,"item_img_directory"_S+"Takoyaki.png",m.OnUpperLevel(),1.f,0.f,vf2d{4.f,4.f},vf2d{},WHITE));
m.SetLifetime(4.f);
SETPHASE(DEAD);
return false;
});
SETPHASE(IDENTIFY_ARMS);
}break;
case IDENTIFY_ARMS:{
m.F(A::BREAK_TIME)-=fElapsedTime;
if(m.F(A::BREAK_TIME)<=0.f){
m.F(A::CASTING_TIMER)=util::random_range(ConfigFloatArr("Arm Move Timer",0),ConfigFloatArr("Arm Move Timer",1));
for(std::shared_ptr<Monster>&arm:MONSTER_LIST){
const std::string OCTOPUS_ARM_NAME{"Octopus Arm"};
if(arm->GetName()==OCTOPUS_ARM_NAME){
std::weak_ptr<Monster>armPtr{arm};
m.VEC(A::ARM_LIST).emplace_back(armPtr);
m.VEC(A::ARM_LOCATIONS).emplace_back(armPtr.lock()->GetPos());
}
}
SETPHASE(NORMAL);
}
}break;
case NORMAL:{
const bool InSecondPhase{m.GetHealth()<=ConfigInt("Phase 2 Health Threshold")};
m.F(A::SHOOT_TIMER)-=fElapsedTime;
m.F(A::CASTING_TIMER)-=fElapsedTime;
m.F(A::LAST_SHOOT_TIMER)-=fElapsedTime;
m.F(A::LAST_INK_SHOOT_TIMER)-=fElapsedTime;
if(m.F(A::SHOOT_ANIMATION_TIME)>0.f){
m.F(A::SHOOT_ANIMATION_TIME)-=fElapsedTime;
if(m.F(A::SHOOT_ANIMATION_TIME)<=0.f)m.PerformIdleAnimation();
}
if(m.F(A::CASTING_TIMER)<=0.f){
int deadMonsterCount{0};
std::vector<vf2d>unoccupiedArmLocs;
AddAllUnoccupedArmLocations:
std::for_each(m.VEC(A::ARM_LOCATIONS).begin(),m.VEC(A::ARM_LOCATIONS).end(),[&unoccupiedArmLocs](const std::any&armLoc){unoccupiedArmLocs.emplace_back(std::any_cast<vf2d>(armLoc));});
std::vector<std::any>liveArms;
RemoveOccupiedArmLocationsAndDetectAliveArms:
std::copy_if(m.VEC(A::ARM_LIST).begin(),m.VEC(A::ARM_LIST).end(),std::back_inserter(liveArms),[&unoccupiedArmLocs,&deadMonsterCount](const std::any&arm){
const std::weak_ptr<Monster>&m{std::any_cast<std::weak_ptr<Monster>>(arm)};
const bool isLive{!m.expired()&&m.lock()->IsAlive()};
if(isLive)std::erase_if(unoccupiedArmLocs,[&m](const vf2d&armLoc){return m.lock()->GetPos()==armLoc;});
else deadMonsterCount++;
return isLive;
});
RemoveArmLocationsTooFarFromPlayer:
std::erase_if(unoccupiedArmLocs,[maxDist=ConfigPixels("Arm Max Move Distance From Player")](const vf2d&armLoc){return util::distance(game->GetPlayer()->GetPos(),armLoc)>maxDist;});
const bool AtLeastOneArmAlive{deadMonsterCount!=m.VEC(A::ARM_LIST).size()};
if(deadMonsterCount>0&&AtLeastOneArmAlive&&unoccupiedArmLocs.size()>0){
const std::weak_ptr<Monster>&randomArm{std::any_cast<std::weak_ptr<Monster>>(liveArms[util::random()%liveArms.size()])};
const vf2d&randomLoc{std::any_cast<vf2d>(unoccupiedArmLocs[util::random()%unoccupiedArmLocs.size()])};
randomArm.lock()->PerformAnimation("SUBMERGE");
randomArm.lock()->SetPhase("Octopus Arm",randomArm.lock()->I(A::SUBMERGE_STRAT_ID));
randomArm.lock()->GetFloat(A::RECOVERY_TIME)=randomArm.lock()->GetCurrentAnimation().GetTotalAnimationDuration();
randomArm.lock()->SetCollisionRadius(0.f);
randomArm.lock()->V(A::JUMP_TARGET_POS)=randomLoc;
randomArm.lock()->SetStrategyDrawFunction([](AiL*game,Monster&monster,const std::string&strategy){});
}
m.F(A::CASTING_TIMER)=util::random_range(ConfigFloatArr("Arm Move Timer",0),ConfigFloatArr("Arm Move Timer",1));
}
if(m.F(A::SHOOT_TIMER)<=0.f){
const auto CreateBurstBullet=[&](){
CreateBullet(BurstBullet)(m.GetPos(),util::pointTo(m.GetPos(),game->GetPlayer()->GetPos())*ConfigFloat("Big Bullet Speed"),game->GetPlayer(),ConfigPixels("Big Bullet Detection Radius"),ConfigInt("Big Bullet Extra Bullet Count"),util::degToRad(ConfigFloat("Big Bullet Extra Bullet Rotate Speed")),ConfigFloat("Big Bullet Extra Bullet Radius"),vf2d{ConfigFloatArr("Big Bullet Extra Bullet Image Scale",0),ConfigFloatArr("Big Bullet Extra Bullet Image Scale",1)},ConfigFloat("Big Bullet Extra Bullet Speed"),ConfigFloat("Big Bullet Extra Bullet Acceleration"),ConfigFloat("Big Bullet Radius"),ConfigInt("Big Bullet Damage"),m.OnUpperLevel(),false,ConfigPixel("Big Bullet Color"),vf2d{ConfigFloat("Big Bullet Image Scale"),ConfigFloat("Big Bullet Image Scale")})EndBullet;
m.F(A::SHOOT_TIMER)=ConfigFloat("Big Bullet Boss Rest Time");
m.I(A::ATTACK_COUNT)=-1;
};
if(InSecondPhase){
if(m.F(A::LAST_INK_SHOOT_TIMER)<=0.f){
CreateBullet(InkBullet)(m.GetPos(),game->GetPlayer()->GetPos(),vf2d{ConfigFloat("Phase 2.Ink Bullet Speed"),0.f},ConfigFloat("Phase 2.Ink Explosion Radius"),ConfigFloat("Phase 2.Ink Puddle Lifetime"),ConfigFloat("Phase 2.Ink Slowdown Time"),ConfigFloat("Phase 2.Ink Slowdown Amount")/100.f,ConfigFloat("Phase 2.Ink Puddle Collision Radius"),m.OnUpperLevel(),false)EndBullet;
m.F(A::LAST_INK_SHOOT_TIMER)=ConfigFloat("Phase 2.Ink Bullet Frequency");
goto BulletShot;
}else
if(m.I(A::BULLET_COUNT_AFTER_INK_ATTACK)>ConfigInt("Phase 2.Homing Bullet Starts After")){
if(m.I(A::ATTACK_COUNT)%ConfigInt("Phase 2.Homing Bullet Frequency")==0)CreateBullet(HomingBullet)(m.GetPos(),Entity{game->GetPlayer()},util::degToRad(ConfigFloat("Phase 2.Homing Bullet Rotation Speed")),util::degToRad(ConfigFloat("Phase 2.Homing Bullet Rotation Speed Player Covered In Ink")),ConfigFloat("Phase 2.Homing Bullet Lifetime"),ConfigFloat("Bullet Speed"),ConfigFloat("Bullet Radius"),ConfigFloat("Bullet Damage"),m.OnUpperLevel(),false,ConfigPixel("Phase 2.Homing Bullet Color"),{ConfigFloat("Bullet Radius"),ConfigFloat("Bullet Radius")})EndBullet;
else
if(m.I(A::ATTACK_COUNT)>=ConfigInt("Big Bullet Frequency")-1)CreateBurstBullet();
else CreateBullet(Bullet)(m.GetPos(),util::pointTo(m.GetPos(),game->GetPlayer()->GetPos())*ConfigFloat("Bullet Speed"),ConfigFloat("Bullet Radius"),ConfigFloat("Bullet Damage"),m.OnUpperLevel(),false,ConfigPixel("Bullet Color"),{ConfigFloat("Bullet Radius"),ConfigFloat("Bullet Radius")})EndBullet;
m.F(A::SHOOT_TIMER)=ConfigFloat("Shoot Frequency");
goto BulletShot;
}
m.I(A::BULLET_COUNT_AFTER_INK_ATTACK)++;
}
if(m.I(A::ATTACK_COUNT)>=ConfigInt("Big Bullet Frequency")-1)CreateBurstBullet();
else{
m.F(A::SHOOT_TIMER)=ConfigFloat("Shoot Frequency");
CreateBullet(Bullet)(m.GetPos(),util::pointTo(m.GetPos(),game->GetPlayer()->GetPos())*ConfigFloat("Bullet Speed"),ConfigFloat("Bullet Radius"),ConfigFloat("Bullet Damage"),m.OnUpperLevel(),false,ConfigPixel("Bullet Color"),{ConfigFloat("Bullet Radius"),ConfigFloat("Bullet Radius")})EndBullet;
}
BulletShot:
m.F(A::LAST_SHOOT_TIMER)=1.f;
m.PerformShootAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
m.I(A::ATTACK_COUNT)++;
}
if(m.F(A::LAST_SHOOT_TIMER)<=0.f){
m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
}
}break;
case HURT_ANIMATION:{
}break;
case DEAD:{}break;
}
}

@ -52,17 +52,16 @@ void Monster::STRATEGY::GOBLIN_BOAR_RIDER(Monster&m,float fElapsedTime,std::stri
if(!m.B(A::INITIALIZED_MOUNTED_MONSTER)){
m.B(A::INITIALIZED_MOUNTED_MONSTER)=true;
m.F(A::PERCEPTION_LEVEL)=ConfigFloat("Starting Perception Level");
m.internal_mounted_animState=Animate2D::AnimationState{};
m.mounted_animation=Animate2D::Animation<std::string>{};
for(bool firstAnimation=true;const std::string&animation:Config("Imposed Monster Animations").GetValues()){
m.mounted_animation.value().AddState(animation,ANIMATION_DATA.at(animation));
if(firstAnimation)m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),animation);
if(firstAnimation)m.mounted_animation.value().ChangeState(m.internal_mounted_animState,animation);
firstAnimation=false;
}
m.mountedSprOffset=ConfigVec("Imposed Monster Offset");
m.deathData.push_back(DeathSpawnInfo{ConfigString("Spawned Monster"),1U});
m.deathData.emplace_back(ConfigString("Spawned Monster"),1U);
}
BOAR(m,fElapsedTime,"Boar");
@ -79,11 +78,11 @@ void Monster::STRATEGY::GOBLIN_BOAR_RIDER(Monster&m,float fElapsedTime,std::stri
m.F(A::ATTACK_COOLDOWN)=0.f;
m.F(A::PERCEPTION_LEVEL)=std::min(ConfigFloat("Maximum Perception Level"),m.F(A::PERCEPTION_LEVEL)+ConfigFloat("Perception Level Increase"));
m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),std::format("GOBLIN_BOW_MOUNTED_{}",int(m.GetFacingDirection())));
m.mounted_animation.value().ChangeState(m.internal_mounted_animState,std::format("GOBLIN_BOW_MOUNTED_{}",int(m.GetFacingDirection())));
}
}else
if(m.F(A::ATTACK_COOLDOWN)>=ConfigFloat("Attack Reload Time")){
m.F(A::SHOOT_TIMER)=ConfigFloat("Attack Windup Time");
m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),std::format("GOBLIN_BOW_ATTACK_{}",int(m.GetFacingDirection())));
m.mounted_animation.value().ChangeState(m.internal_mounted_animState,std::format("GOBLIN_BOW_ATTACK_{}",int(m.GetFacingDirection())));
}
}

@ -60,12 +60,12 @@ void Monster::STRATEGY::GOBLIN_BOMB(Monster&m,float fElapsedTime,std::string str
RUN,
};
switch(m.phase){
switch(PHASE()){
case INITIALIZE:{
m.F(A::SHOOT_TIMER)=m.randomFrameOffset;
m.phase=RUN;
SETPHASE(RUN);
}break;
case RUN:{
case RUN:{
m.F(A::SHOOT_TIMER)+=fElapsedTime;
m.F(A::SHOOT_ANIMATION_TIME)-=fElapsedTime;
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();

@ -66,10 +66,10 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
};
#pragma endregion
switch(m.phase){
switch(PHASE()){
case INITIALIZE_PERCEPTION:{
m.F(A::PERCEPTION_LEVEL)=ConfigFloat("Starting Perception Level");
m.phase=MOVE;
SETPHASE(MOVE);
}break;
case MOVE:{
m.F(A::ATTACK_COOLDOWN)+=fElapsedTime;
@ -79,7 +79,7 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
const bool outsideMaxShootingRange=distToPlayer>=ConfigPixelsArr("Stand Still and Shoot Range",1);
auto PrepareToShoot=[&](){
m.phase=LOCKON;
SETPHASE(LOCKON);
m.F(A::SHOOT_TIMER)=ConfigFloat("Attack Windup Time");
m.PerformAnimation("SHOOT",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
@ -118,7 +118,7 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
m.V(A::EXTENDED_LINE)=extendedLine;
Arrow tempArrow{m.GetPos(),extendedLine,pointTowardsPlayer.vector().norm()*ConfigFloat("Arrow Spd"),"goblin_arrow.png",ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel()};
m.V(A::FIRE_VELOCITY)=tempArrow.PointToBestTargetPath(m.F(A::PERCEPTION_LEVEL));
m.phase=WINDUP;
SETPHASE(WINDUP);
}
}break;
case WINDUP:{
@ -128,7 +128,7 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
CreateBullet(Arrow)(m.GetPos(),m.V(A::EXTENDED_LINE),m.V(A::FIRE_VELOCITY),"goblin_arrow.png",ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel())EndBullet;
m.F(A::PERCEPTION_LEVEL)=std::min(ConfigFloat("Maximum Perception Level"),m.F(A::PERCEPTION_LEVEL)+ConfigFloat("Perception Level Increase"));
m.PerformAnimation("IDLE",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
m.phase=MOVE;
SETPHASE(MOVE);
}
m.B(A::RANDOM_DIRECTION)=util::random()%2;
m.F(A::RANDOM_RANGE)=util::random_range(ConfigPixelsArr("Random Direction Range",0),ConfigPixelsArr("Random Direction Range",1));

@ -66,13 +66,13 @@ void Monster::STRATEGY::GOBLIN_DAGGER(Monster&m,float fElapsedTime,std::string s
SLASH
};
switch(m.phase){
switch(PHASE()){
case MOVE:{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
if(distToPlayer>ConfigFloat("Attack Spacing")/100.f*24){
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else{
m.phase=WINDUP;
SETPHASE(WINDUP);
m.I(A::ATTACK_TYPE)=util::random()%2; //Choose randomly between stab or slash.
switch(m.I(A::ATTACK_TYPE)){
case STAB:{
@ -90,18 +90,18 @@ void Monster::STRATEGY::GOBLIN_DAGGER(Monster&m,float fElapsedTime,std::string s
case WINDUP:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){
m.phase=RECOVERY;
SETPHASE(RECOVERY);
switch(m.I(A::ATTACK_TYPE)){
case STAB:{
vf2d stabTarget=game->GetPlayer()->GetPos();
m.PerformAnimation("STABBING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
CreateBullet(DaggerStab)(m,ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Stab Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(stabTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Stab Distance"),
CreateBullet(DaggerStab)(m,ConfigString("Dagger Stab Image"),ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Stab Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(stabTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Stab Distance"),
DaggerStab::DirectionOffsets{ConfigVec("Dagger Up Offset"),ConfigVec("Dagger Down Offset"),ConfigVec("Dagger Right Offset"),ConfigVec("Dagger Left Offset")})EndBullet;
}break;
case SLASH:{
vf2d slashTarget=game->GetPlayer()->GetPos();
m.PerformAnimation("SLASHING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
CreateBullet(DaggerSlash)(m,ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Slash Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(slashTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Slash Distance"))EndBullet;
CreateBullet(DaggerSlash)(m,ConfigString("Dagger Slash Image"),ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Slash Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(slashTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Slash Distance"))EndBullet;
}break;
default:ERR(std::format("WARNING! Invalid Attack type {} provided. THIS SHOULD NOT BE HAPPENING!",m.I(A::ATTACK_TYPE)));
}
@ -113,7 +113,7 @@ void Monster::STRATEGY::GOBLIN_DAGGER(Monster&m,float fElapsedTime,std::string s
m.F(A::CASTING_TIMER)-=fElapsedTime;
m.F(A::RECOVERY_TIME)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));}
if(m.F(A::RECOVERY_TIME)<=0)m.phase=MOVE;
if(m.F(A::RECOVERY_TIME)<=0)SETPHASE(MOVE);
}break;
}
}

@ -63,11 +63,11 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
m.SetZ(std::max(float(m.F(A::FLYING_HEIGHT)+ConfigFloat("Flight Oscillation Amount")*sin((PI*m.TimeSpentAlive())/1.5f)),0.f));
#pragma endregion
switch(m.phase){
switch(PHASE()){
case INITIALIZE:{
m.F(A::TARGET_FLYING_HEIGHT)=m.F(A::FLYING_HEIGHT)=ConfigFloat("Flight Height")-ConfigFloat("Flight Height Variance")+util::random_range(0,ConfigFloat("Flight Height Variance")*2);
m.B(A::RANDOM_DIRECTION)=int(util::random_range(0,2));
m.phase=FLYING;
SETPHASE(FLYING);
m.AddBuff(BuffType::SELF_INFLICTED_SLOWDOWN,INFINITE,util::random_range(0,ConfigFloat("Flight Speed Variance")/100));
m.F(A::ATTACK_COOLDOWN)=util::random_range(1.f,ConfigFloat("Flight Charge Cooldown"));
}break;
@ -76,7 +76,7 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
if(m.F(A::FLYING_HEIGHT)<m.F(A::TARGET_FLYING_HEIGHT))m.F(A::FLYING_HEIGHT)=std::min(m.F(A::TARGET_FLYING_HEIGHT),m.F(A::FLYING_HEIGHT)+ConfigFloat("Attack Z Speed")*fElapsedTime);
if(m.F(A::ATTACK_COOLDOWN)<=0){
m.F(A::CASTING_TIMER)=ConfigFloat("Attack Wait Time");
m.phase=PREPARE_CHARGE;
SETPHASE(PREPARE_CHARGE);
}else{
float dirToPlayer{util::pointTo(game->GetPlayer()->GetPos(),m.GetPos()).polar().y};
dirToPlayer+=m.B(A::RANDOM_DIRECTION)?0.25*PI:-0.25*PI;
@ -89,7 +89,7 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
m.UpdateFacingDirection(game->GetPlayer()->GetPos());
m.PerformAnimation("ATTACK");
if(m.F(A::CASTING_TIMER)<=0){
m.phase=CHARGE;
SETPHASE(CHARGE);
m.PerformAnimation("ATTACKING");
m.target=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(ConfigFloat("Flight Distance")*2.f);
m.UpdateFacingDirection(m.target);
@ -103,7 +103,7 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
float distToTarget=geom2d::line<float>(m.GetPos(),m.target).length();
if(distToTarget<12.f){
m.F(A::TARGET_FLYING_HEIGHT)=ConfigFloat("Flight Height")-ConfigFloat("Flight Height Variance")+util::random_range(0,ConfigFloat("Flight Height Variance")*2);
m.phase=FLYING;
SETPHASE(FLYING);
m.F(A::ATTACK_COOLDOWN)=ConfigFloat("Flight Charge Cooldown");
m.PerformJumpAnimation();
}

@ -0,0 +1,54 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "BulletTypes.h"
HomingBullet::HomingBullet(const vf2d pos,const Entity target,const float rotateTowardsSpeed,const float rotateTowardsSpeedCoveredInInk,const float lifetime,const float speed,const float radius,const int damage,const bool upperLevel,const bool friendly,const Pixel col,const vf2d scale,const float image_angle)
:Bullet(pos,util::pointTo(pos,target.GetPos())*speed,radius,damage,upperLevel,friendly,col,scale,image_angle),target(target),rotateTowardsSpeed(rotateTowardsSpeed),rotateTowardsSpeedCoveredInInk(rotateTowardsSpeedCoveredInInk){
this->lifetime=lifetime;
}
void HomingBullet::Update(float fElapsedTime){
const float magnitude{vel.mag()};
float bulletAngle{vel.polar().y};
float bulletRotationSpeed{rotateTowardsSpeed};
if(game->GetPlayer()->CoveredInInk())bulletRotationSpeed=rotateTowardsSpeedCoveredInInk;
if(util::distance(pos,game->GetPlayer()->GetPos())>=7*24.f)bulletRotationSpeed=0.f; //Don't rotate if basically offscreen.
util::turn_towards_target(bulletAngle,pos,target.GetPos(),bulletRotationSpeed,fElapsedTime);
vel=vf2d{magnitude,bulletAngle}.cart();
}
void HomingBullet::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -0,0 +1,130 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Menu.h"
#include "MenuComponent.h"
#include "GameState.h"
#include "MenuLabel.h"
#include "AdventuresInLestoria.h"
#include "CharacterRotatingDisplay.h"
#include "ClassInfo.h"
#include "Unlock.h"
INCLUDE_game
INCLUDE_GFX
void Menu::InitializeHubPauseWindow(){
Menu*hubPauseWindow=CreateMenu(MenuType::HUB_PAUSE,CENTERED,vi2d{96,168});
hubPauseWindow->ADD("Resume Button",MenuComponent)(geom2d::rect<float>{{6.f,0.f},{84.f,24.f}},"Resume",[](MenuFuncData data){void ClearGarbage();
game->ClearGarbage();
Menu::CloseMenu();
return true;
},ButtonAttr::FIT_TO_LABEL)END;
hubPauseWindow->ADD("Character Button",MenuComponent)(geom2d::rect<float>{{6.f,28.f},{84.f,24.f}},"Character",[](MenuFuncData data){
Component<CharacterRotatingDisplay>(CHARACTER_MENU,"Character Rotating Display")->SetIcon(GFX[classutils::GetClassInfo(game->GetPlayer()->GetClassName()).classFullImgName].Decal());
Component<MenuComponent>(CHARACTER_MENU,"Equip Selection Select Button")->Click();
Menu::OpenMenu(CHARACTER_MENU);
return true;
},ButtonAttr::FIT_TO_LABEL)END;
hubPauseWindow->ADD("Change Loadout Button",MenuComponent)(geom2d::rect<float>{{6.f,56.f},{84.f,24.f}},"Change\nLoadout",[](MenuFuncData data){
Menu::OpenMenu(ITEM_HUB_LOADOUT);
return true;
},ButtonAttr::FIT_TO_LABEL)END;
hubPauseWindow->ADD("Inventory Button",MenuComponent)(geom2d::rect<float>{{6.f,84.f},{84.f,24.f}},"Inventory",[](MenuFuncData data){
Menu::OpenMenu(INVENTORY);
return true;
},ButtonAttr::FIT_TO_LABEL)END;
hubPauseWindow->ADD("Settings Button",MenuComponent)(geom2d::rect<float>{{6.f,112.f},{84.f,24.f}},"Settings",[](MenuFuncData data){
Menu::OpenMenu(SETTINGS);
return true;
},ButtonAttr::FIT_TO_LABEL)END;
hubPauseWindow->ADD("Return to Camp Button",MenuComponent)(geom2d::rect<float>{{6.f,140.f},{84.f,24.f}},"Leave Area",[](MenuFuncData data){
Component<MenuLabel>(LEVEL_COMPLETE,"Stage Complete Label")->SetLabel("Stage Summary");
Component<MenuComponent>(LEVEL_COMPLETE,"Level Details Outline")->SetLabel("");
if(game->GetCurrentMapName()=="HUB"&&game->PreviousStageCompleted())Unlock::UnlockCurrentMap(); //Special unlock for the hub area when leaving.
if(Unlock::IsUnlocked("STORY_1_1")){
Component<MenuComponent>(LEVEL_COMPLETE,"Next Button")->Enable();
}else{
Component<MenuComponent>(LEVEL_COMPLETE,"Next Button")->Disable();
}
if(GameState::STATE==GameState::states[States::GAME_HUB]){
GameState::ChangeState(States::OVERWORLD_MAP,0.4f);
}else{
GameState::ChangeState(States::LEVEL_COMPLETE,0.4f);
}
return true;
},ButtonAttr::FIT_TO_LABEL)END;
hubPauseWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="Resume Button";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
{game->KEY_BACK,{"Resume",[](MenuType type){
Component<MenuComponent>(type,"Resume Button")->Click();
}}},
{game->KEY_MENU,{"Resume",[](MenuType type){
Component<MenuComponent>(type,"Resume Button")->Click();
}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
,{ //Button Navigation Rules
{"Resume Button",{
.up="Return to Camp Button",
.down="Character Button",}},
{"Character Button",{
.up="Resume Button",
.down="Change Loadout Button",}},
{"Change Loadout Button",{
.up="Character Button",
.down="Inventory Button",}},
{"Inventory Button",{
.up="Change Loadout Button",
.down="Settings Button",}},
{"Settings Button",{
.up="Inventory Button",
.down="Return to Camp Button",}},
{"Return to Camp Button",{
.up="Settings Button",
.down="Resume Button",}},
});
}

@ -39,9 +39,10 @@ All rights reserved.
namespace HurtFlag{
enum HurtFlag{
NONE = 0b00,
PLAYER_ABILITY = 0b01, //Specifically use this flag for player auto attacks or Abilities only! (Identifies these attacks for the Trapper class)
DOT = 0b10, //Damage over time ability.
NONE = 0b000,
PLAYER_ABILITY = 0b001, //Specifically use this flag for player auto attacks or Abilities only! (Identifies these attacks for the Trapper class)
DOT = 0b010, //Damage over time ability.
NO_DAMAGE_NUMBER = 0b100, //This flag ensures no damage number appears.
};
}

@ -51,7 +51,7 @@ INCLUDE_WINDOW_SIZE
IBullet::IBullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale,float image_angle)
:pos(pos),vel(vel),radius(radius),damage(damage),col(col),friendly(friendly),upperLevel(upperLevel),scale(scale),image_angle(image_angle){};
IBullet::IBullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float image_angle,std::string_view hitSound)
IBullet::IBullet(vf2d pos,vf2d vel,float radius,int damage,const std::string&animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float image_angle,std::string_view hitSound)
:pos(pos),vel(vel),radius(radius),damage(damage),col(col),animated(true),rotates(rotatesWithAngle),lifetime(lifetime),hitsMultiple(hitsMultiple),friendly(friendly),upperLevel(upperLevel),scale(scale),image_angle(image_angle),hitSound(std::string(hitSound)){
this->animation.AddState(animation,ANIMATION_DATA.at(animation));
this->animation.ChangeState(internal_animState,animation);
@ -88,8 +88,8 @@ void IBullet::_Update(const float fElapsedTime){
UpdateFadeTime(fElapsedTime);
Update(fElapsedTime);
animation.UpdateState(internal_animState,fElapsedTime);
const bool CollisionCheckRequired=IsActivated()&&fadeInTimer==fadeInTime&&radius!=0.f;
if(CollisionCheckRequired){
const auto CollisionCheckRequired=[this](){return IsActivated()&&fadeInTimer==fadeInTime&&radius!=0.f;};
if(CollisionCheckRequired()){
float totalDistance=(vel*fElapsedTime).mag();
int iterations=int(std::max(1.f,(vel*fElapsedTime).mag()));
int totalIterations=iterations;
@ -106,31 +106,29 @@ void IBullet::_Update(const float fElapsedTime){
ModifyOutgoingDamageData(damageData);
uint8_t markStacksBeforeHit{m->GetMarkStacks()};
if(m->Hurt(damageData)){
hitList.insert(&*m);
if(!hitsMultiple){
if(_MonsterHit(*m,markStacksBeforeHit)==BulletDestroyState::DESTROY){
dead=true;
}
if(_MonsterHit(*m,markStacksBeforeHit)==BulletDestroyState::DESTROY)dead=true;
return false;
}else _MonsterHit(*m,markStacksBeforeHit);
hitList.insert(&*m);
if(!CollisionCheckRequired())return false;
}
}
}
}
} else {
}else{
if(geom2d::overlaps(game->GetPlayer()->Hitbox(),geom2d::circle(pos,radius))){
if(hitList.find(game->GetPlayer())==hitList.end()){
HurtDamageInfo damageData{game->GetPlayer(),damage,OnUpperLevel(),z,HurtFlag::NONE};
//NOTE: OnHurt() will potentially change the Damage Data, passing it along to bullet children that might need to modify the flags!
ModifyOutgoingDamageData(damageData);
if(game->GetPlayer()->Hurt(damageData)){
hitList.insert(game->GetPlayer());
if(!hitsMultiple){
if(_PlayerHit(&*game->GetPlayer())==BulletDestroyState::DESTROY){
dead=true;
}
if(_PlayerHit(&*game->GetPlayer())==BulletDestroyState::DESTROY)dead=true;
return false;
}else _PlayerHit(&*game->GetPlayer());
hitList.insert(game->GetPlayer());
if(!CollisionCheckRequired())return false;
}
}
}
@ -141,6 +139,7 @@ void IBullet::_Update(const float fElapsedTime){
while(iterations>0){
iterations--;
if(IsPlayerAutoAttackProjectile()){pos+=(game->GetWindSpeed()*game->GetElapsedTime())/float(totalIterations);}
vel=vf2d{vel.polar().x+(acc*fElapsedTime/float(totalIterations)),vel.polar().y}.cart();
pos+=(vel*fElapsedTime)/float(totalIterations);
if(!CollisionCheck()){
goto DeadBulletCheck;
@ -152,6 +151,7 @@ void IBullet::_Update(const float fElapsedTime){
}
}else{
if(IsPlayerAutoAttackProjectile()){pos+=game->GetWindSpeed()*game->GetElapsedTime();}
vel=vf2d{vel.polar().x+acc*fElapsedTime,vel.polar().y}.cart();
pos+=vel*fElapsedTime;
}
@ -168,11 +168,13 @@ void IBullet::_Update(const float fElapsedTime){
}
void IBullet::_Draw()const{
game->SetDecalMode(DecalMode::NORMAL);
if(additiveBlending)game->SetDecalMode(DecalMode::ADDITIVE);
Pixel blendCol=col;
if(fadeInTime==0&&fadeOutTime==0)blendCol.a=col.a;
else if(fadeOutTime>0)blendCol.a=uint8_t(util::lerp(col.a,0,1-((fadeOutTime-fadeOutTimer)/fadeOutTime)));
else if(fadeInTime>0)blendCol.a=uint8_t(util::lerp(col.a,0,((fadeInTime-fadeInTimer)/fadeInTime)));
else if(fadeOutTime>0)blendCol.a=uint8_t(util::lerp(col.a,uint8_t(0),1-((fadeOutTime-fadeOutTimer)/fadeOutTime)));
else if(fadeInTime>0)blendCol.a=uint8_t(util::lerp(col.a,uint8_t(0),((fadeInTime-fadeInTimer)/fadeInTime)));
if(GetZ()>0){
vf2d shadowScale=vf2d{8*scale.x/3.f,1}/std::max(1.f,GetZ()/8);
@ -186,6 +188,7 @@ void IBullet::_Draw()const{
blendCol.a/=1.59f; //Comes from 255 divided by 160 which is roughly what we want the alpha to be when the bullet has full transparency.
}
Draw(blendCol);
game->SetDecalMode(DecalMode::NORMAL);
}
void IBullet::Draw(const Pixel blendCol)const{
@ -274,6 +277,10 @@ void IBullet::Deactivate(){
deactivated=true;
}
void IBullet::Activate(){
deactivated=false;
}
const double IBullet::GetTimeAlive()const{
return aliveTime;
}

@ -42,6 +42,11 @@ All rights reserved.
#include "DEFINES.h"
using HitList=std::unordered_set<std::variant<Monster*,Player*>>;
enum class HurtType{
PLAYER=0b01,
MONSTER=0b10,
};
enum class BulletType{
UNDEFINED,
FEATHER,
@ -55,6 +60,7 @@ enum class BulletDestroyState{
struct IBullet{
friend class AiL;
float acc{};
vf2d vel;
vf2d pos;
float radius;
@ -76,6 +82,8 @@ protected:
float fadeInTime=0; //Setting the fade in time causes the bullet to be disabled and the bullet's alpha will fade in from zero to the actual alpha of the bullet. When the fade in timer reaches the fade in time automatically, the bullet will be enabled.
virtual void Update(float fElapsedTime);
void Deactivate();
void Activate();
bool additiveBlending{false};
private:
float fadeOutTimer=0;
float fadeInTimer=0;
@ -104,7 +112,7 @@ public:
virtual ~IBullet()=default;
IBullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f);
//Initializes a bullet with an animation.
IBullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,std::string_view hitSound="");
IBullet(vf2d pos,vf2d vel,float radius,int damage,const std::string&animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,std::string_view hitSound="");
public:
void SimulateUpdate(const float fElapsedTime);

@ -49,9 +49,14 @@ IT::IT(std::string name)
itemName=ITEM_CONVERSIONS.at(name); //Convert the item if it's using an old name.
}
}
IT::IT(std::string_view name)
:IT(std::string(name)){}
IT::operator std::string(){
return itemName;
}
IT::operator std::string_view(){
return itemName;
}
std::ostream&operator<<(std::ostream&rhs,IT&item){
rhs<<item.itemName;
return rhs;

@ -49,7 +49,9 @@ class IT{
public:
IT();
IT(std::string name);
IT(std::string_view name);
operator std::string();
operator std::string_view();
const bool operator==(const std::shared_ptr<Item>&rhs);
const bool operator==(const std::weak_ptr<Item>&rhs);
friend std::ostream&operator<<(std::ostream&rhs,IT&item);

@ -0,0 +1,52 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "AdventuresInLestoria.h"
INCLUDE_game
Ink::Ink(vf2d pos,float radius,float lifetime,float inkSlowdownDuration,float inkSlowdownPct,float fadeinTime,float fadeoutTime,vf2d size,Pixel col,bool onUpperLevel,float rotation)
:Effect(pos,lifetime,"ink.png",onUpperLevel,fadeinTime,fadeoutTime,size,{},EffectType::NONE,col,rotation,0.f,false),radius(radius),inkSlowdownDuration(inkSlowdownDuration),inkSlowdownAmount(inkSlowdownPct){}
bool Ink::Update(float fElapsedTime){
const float distToPlayer{util::distance(game->GetPlayer()->GetPos(),pos)};
if(distToPlayer<=radius){
Buff&currentInkSlowdown{game->GetPlayer()->GetOrAddBuff(BuffType::INK_SLOWDOWN,{inkSlowdownDuration,inkSlowdownAmount})};
currentInkSlowdown.duration=inkSlowdownDuration; //Reset the duration if needed.
}
return Effect::Update(fElapsedTime);
}

@ -0,0 +1,66 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "BulletTypes.h"
INCLUDE_ANIMATION_DATA
InkBullet::InkBullet(const vf2d pos,const vf2d targetPos,const vf2d vel,const float inkExplosionRadius,const float inkPuddleLifetime,const float inkSlowdownDuration,const float inkSlowdownPct,const float inkPuddleRadius,const bool upperLevel,const bool friendly)
:Bullet(pos,vel,inkExplosionRadius,0,"inkbullet.png",upperLevel,friendly),targetPos(targetPos),inkExplosionRadius(inkExplosionRadius),inkSlowdownPct(inkSlowdownPct),inkSlowdownDuration(inkSlowdownDuration),inkPuddleLifetime(inkPuddleLifetime),inkPuddleRadius(inkPuddleRadius){
additiveBlending=true;
}
void InkBullet::Update(float fElapsedTime){
const float magnitude{vel.mag()};
vel=util::pointTo(pos,targetPos)*magnitude;
if(util::distance(pos,targetPos)<=8.f){Splat();}
else if(GetAliveTime()>=30.f)ERR(std::format("WARNING! An Ink Bullet did not resolve to target position {} in time! Last reported pos: {}! THE DISTANCE THRESHOLD FOR INK EXPLOSIONS IS LIKELY TOO HIGH! THIS SHOULD NOT BE HAPPENING!",targetPos.str(),pos.str())); //NOTE: THIS SHOULD NOT BE HAPPENING! If this does, then we probably just set the distance threshold WAY TOO LOW! Let's crash for now.
}
void InkBullet::Splat(){
game->AddEffect(std::make_unique<Effect>(pos,ANIMATION_DATA["inkbubble_explode.png"].GetTotalAnimationDuration(),"inkbubble_explode.png",OnUpperLevel(),vf2d{1.f,1.f},0.f,vf2d{},WHITE,0.f,0.f,additiveBlending));
game->AddEffect(std::make_unique<Ink>(pos,inkPuddleRadius,inkPuddleLifetime,inkSlowdownDuration,inkSlowdownPct,0.4f,0.4f,vf2d{1.f,1.f},WHITE,OnUpperLevel(),int(util::random_range(0,4.f))*PI/2),true);
lifetime=0.f;
}
BulletDestroyState InkBullet::PlayerHit(Player*player){
Splat();
return BulletDestroyState::DESTROY;
}
BulletDestroyState InkBullet::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
Splat();
return BulletDestroyState::DESTROY;
}
void InkBullet::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -150,7 +150,7 @@ void Menu::InitializeInventoryWindow(){
auto moneyIcon=inventoryWindow->ADD("Money Icon",MenuIconButton)(geom2d::rect<float>{moneyIconPos,{24,24}},GFX["money.png"].Decal(),DO_NOTHING,IconButtonAttr::NOT_SELECTABLE|IconButtonAttr::NO_OUTLINE|IconButtonAttr::NO_BACKGROUND)END;
std::string moneyText=std::to_string(game->GetPlayer()->GetMoney());
vf2d moneyTextSize=game->GetTextSizeProp(moneyText)*2;
auto moneyDisplay=inventoryWindow->ADD("Money Label",PlayerMoneyLabel)(geom2d::rect<float>{moneyIconPos-vf2d{2+moneyTextSize.x,-2},moneyTextSize},2,SHADOW|LEFT_ALIGN|FIT_TO_LABEL)END;
auto moneyDisplay=inventoryWindow->ADD("Money Label",PlayerMoneyLabel)(geom2d::rect<float>{moneyIconPos-vf2d{2+moneyTextSize.x,-2},moneyTextSize},2,1.85f,SHADOW|LEFT_ALIGN|FIT_TO_LABEL)END;
moneyDisplay->SetRightAlignment(true);
Player::AddMoneyListener(moneyDisplay);
#pragma endregion

@ -80,7 +80,7 @@ std::vector<std::string>ItemSortRules::secondarySort;
Stats Stats::NO_MAX_HIGHLIGHT;
ItemInfo::ItemInfo()
:customProps({nullptr,nullptr}),img(nullptr){}
:customProps({nullptr,nullptr}){}
void ItemInfo::InitializeItems(){
Stats::NO_MAX_HIGHLIGHT={};
@ -142,6 +142,8 @@ void ItemInfo::InitializeItems(){
std::string scriptName="",description="",category="";
std::string setName="";
float castTime=0;
float castRange=0;
float castSize=0;
std::vector<std::string> slot;
float cooldownTime="Item.Item Cooldown Time"_F;
std::vector<ItemAttribute>statValueList;
@ -153,6 +155,7 @@ void ItemInfo::InitializeItems(){
std::unordered_set<std::string>equippableClass;
EventName useSound;
std::optional<std::string>fragmentName;
std::optional<std::string>fragmentIcon;
for(auto&[itemKey,itemValue]:data[key].GetKeys()){
std::string keyName=itemKey;
if(keyName=="Description"){
@ -166,6 +169,12 @@ void ItemInfo::InitializeItems(){
}else
if(keyName=="Cast Time"){
castTime=float(data[key][keyName].GetReal());
}
if(keyName=="Cast Range"){
castRange=float(data[key][keyName].GetReal());
}else
if(keyName=="Cast Size"){
castSize=float(data[key][keyName].GetReal())/100.f*24;
}else
if(keyName=="Cooldown Time"){
cooldownTime=float(data[key][keyName].GetReal());
@ -204,6 +213,9 @@ void ItemInfo::InitializeItems(){
}else
if(keyName.starts_with("Fragment Name")){
fragmentName=data[key][keyName].GetString();
}else
if(keyName.starts_with("Fragment Icon")){
fragmentIcon=data[key][keyName].GetString();
}else{ //THis is a custom override modifier for a script. NO-OP
}
}
@ -298,6 +310,8 @@ void ItemInfo::InitializeItems(){
it.description=description;
it.category=category;
it.castTime=castTime;
it.castRange=castRange;
it.castSize=castSize;
it.cooldownTime=cooldownTime;
it.slot=EquipSlot::NONE;
it.set=setName;
@ -316,7 +330,7 @@ void ItemInfo::InitializeItems(){
ERR("WARNING! Tried to add item "<<it.name<<" to category "<<it.category<<" which does not exist!")
}
ITEM_CATEGORIES.at(it.category).insert(it.name);
it.img=img.Decal();
it.img=img;
ItemProps&props=it.customProps;
if(scriptName!=""){
props.scriptProps=&DATA["ItemScript"][scriptName];
@ -326,6 +340,7 @@ void ItemInfo::InitializeItems(){
it.minStats=minStats;
it.maxStats=maxStats;
it.fragmentName=fragmentName;
it.fragmentIcon=fragmentIcon;
#pragma region Equipment Category Verification Tests
int equipmentCategories=0;
@ -366,19 +381,28 @@ void ItemInfo::InitializeItems(){
for(const auto&itemName:ITEM_CATEGORIES.at("Accessories")){
const std::string fragmentName{ITEM_DATA.at(itemName).fragmentName.value()};
ItemInfo&it{ITEM_DATA[fragmentName]};
GFX[fragmentName].Create(24,24);
GFX["item_img_directory"_S+fragmentName].Create(24,24);
it.img=GFX.at("item_img_directory"_S+fragmentName);
if(!game->TestingModeEnabled()){
#pragma region Collect colors from source ring image
if(it.FragmentIcon()){
game->SetDrawTarget(GFX.at("item_img_directory"_S+fragmentName).Sprite());
game->Clear(BLANK);
game->SetPixelMode(Pixel::ALPHA);
game->DrawSprite({},GFX.at(it.FragmentIcon().value()).Sprite());
game->SetDrawTarget(nullptr);
GFX.at("item_img_directory"_S+fragmentName).Decal()->Update();
}else{
#pragma region Collect colors from source ring image
std::set<Pixel>colors;
for(int y=0;y<24;y++){
for(int x=0;x<24;x++){
colors.insert(ITEM_DATA.at(itemName).img->sprite->GetPixel(x,y));
colors.insert(ITEM_DATA.at(itemName).Icon().Sprite()->GetPixel(x,y));
}
}
colors.erase(BLANK);
#pragma endregion
#pragma region Generate fragment with randomly sampled pixels from the source ring image
game->SetDrawTarget(GFX.at(fragmentName).Sprite());
#pragma endregion
#pragma region Generate fragment with randomly sampled pixels from the source ring image
game->SetDrawTarget(GFX.at("item_img_directory"_S+fragmentName).Sprite());
game->DrawSprite({},GFX.at("items/Fragment.png").Sprite(),1U,0U,[colors](const Pixel&in){
if(in==BLANK)return in;
for(const Pixel&p:colors){
@ -387,11 +411,12 @@ void ItemInfo::InitializeItems(){
return in;
});
game->SetDrawTarget(nullptr);
GFX.at(fragmentName).Decal()->Update();
GFX.at("item_img_directory"_S+fragmentName).Decal()->Update();
#pragma endregion
}
}
it.img=GFX.at(fragmentName).Decal();
it.name=fragmentName;
it.fragmentIcon="item_img_directory"_S+fragmentName;
it.description="Fragment Description"_S;
it.category="Materials";
LOG(std::format("Item Fragment {} generated...",fragmentName));
@ -412,15 +437,15 @@ void ItemInfo::InitializeItems(){
ItemProps::ItemProps(utils::datafile*scriptProps,utils::datafile*customProps)
:scriptProps(scriptProps),customProps(customProps){}
int ItemProps::GetIntProp(const std::string&prop,size_t index)const{
int ItemProps::GetInt(const std::string&prop,size_t index)const{
if(customProps->HasProperty(prop)) return (*customProps)[prop].GetInt(index);
else return (*scriptProps)[prop].GetInt(index);
};
float ItemProps::GetFloatProp(const std::string&prop,size_t index)const{
float ItemProps::GetFloat(const std::string&prop,size_t index)const{
if(customProps->HasProperty(prop)) return float((*customProps)[prop].GetReal(index));
else return float((*scriptProps)[prop].GetReal(index));
};
std::string ItemProps::GetStringProp(const std::string&prop,size_t index)const{
std::string ItemProps::GetString(const std::string&prop,size_t index)const{
if(customProps->HasProperty(prop)) return (*customProps)[prop].GetString(index);
else return (*scriptProps)[prop].GetString(index);
};
@ -437,51 +462,6 @@ const std::unordered_map<std::string,BuffOverTimeType::BuffOverTimeType>ItemInfo
{"MP % Restore",BuffOverTimeType::MP_PCT_RESTORATION},
};
void ItemInfo::InitializeScripts(){
ITEM_SCRIPTS["Restore"]=[](AiL*game,ItemProps props){
for(const auto&[propName,buffType]:NameToBuffType){
int restoreAmt=props.GetIntProp(propName);
game->GetPlayer()->AddBuff(BuffRestorationType::ONE_OFF,NameToBuffType.at(propName),0.01f,float(restoreAmt),0.0f);
if(restoreAmt>0&&props.PropCount(propName)==3){
game->GetPlayer()->AddBuff(BuffRestorationType::OVER_TIME,NameToBuffType.at(propName),props.GetFloatProp(propName,2),float(restoreAmt),props.GetFloatProp(propName,1));
}
}
return true;
};
for(auto&[key,value]:ItemAttribute::attributes){
if(!DATA.GetProperty("ItemScript.Buff").HasProperty(key)){
ERR("WARNING! Buff Item Script does not support Buff "<<std::quoted(value.Name())<<"!");
}
}
ITEM_SCRIPTS["Buff"]=[](AiL*game,ItemProps props){
for(auto&[key,value]:ItemAttribute::attributes){
float intensity=props.GetFloatProp(key,0);
if(intensity==0.f)continue;
if(ItemAttribute::Get(key).DisplayAsPercent())intensity/=100;
game->GetPlayer()->AddBuff(BuffType::STAT_UP,props.GetFloatProp(key,1),intensity,{ItemAttribute::Get(key)});
}
return true;
};
ITEM_SCRIPTS["RestoreDuringCast"]=[](AiL*game,ItemProps props){
for(const auto&[propName,buffType]:NameToBuffType){
int restoreAmt=props.GetIntProp(propName);
game->GetPlayer()->AddBuff(BuffRestorationType::ONE_OFF,NameToBuffType.at(propName),0.01f,float(restoreAmt),0.0f);
if(restoreAmt>0&&props.PropCount(propName)==3){
game->GetPlayer()->AddBuff(BuffRestorationType::OVER_TIME_DURING_CAST,NameToBuffType.at(propName),props.GetFloatProp(propName,2),float(restoreAmt),props.GetFloatProp(propName,1));
}
}
return true;
};
ITEM_SCRIPTS.SetInitialized();
LOG(ITEM_SCRIPTS.size()<<" item scripts have been loaded.");
}
Item::Item()
:amt(0),it(nullptr),enhancementLevel(0){}
@ -546,12 +526,14 @@ uint32_t Inventory::GetItemCount(IT it){
}
//Returns true if the item has been consumed completely and there are 0 remaining of that type in our inventory.
bool Inventory::UseItem(IT it,uint32_t amt){
bool Inventory::UseItem(IT it,uint32_t amt,const std::optional<vf2d>targetingPos){
if(!_inventory.count(it))return false;
//There are two places to manipulate items in (Both the sorted inventory and the actual inventory)
for(uint32_t i=0;i<amt;i++){
if(ExecuteAction(it)){
return RemoveItem(GetItem(it)[0]);
if(ExecuteAction(it,targetingPos)){
if(GameState::GetCurrentState()!=States::GAME_HUB){
return RemoveItem(GetItem(it)[0]);
}else return false;
}
}
return false;
@ -666,9 +648,9 @@ void Inventory::InsertIntoStageInventoryCategory(std::shared_ptr<Item>itemRef,co
Menu::InventorySlotsUpdated(stageInventoryCategory);
}
bool Inventory::ExecuteAction(IT item){
bool Inventory::ExecuteAction(IT item,const std::optional<vf2d>targetingPos){
if(ITEM_SCRIPTS.count(ITEM_DATA.at(item).useFunc)){
return ITEM_SCRIPTS.at(ITEM_DATA.at(item).useFunc)(game,ITEM_DATA[item].customProps);
return ITEM_SCRIPTS.at(ITEM_DATA.at(item).useFunc)(game,targetingPos,ITEM_DATA[item].customProps);
}else{
return false;
}
@ -700,7 +682,7 @@ const std::string Item::DisplayName()const{
if(IsEquippable()&&EnhancementLevel()>0){
name+=" [#00FF00+"+std::to_string(EnhancementLevel())+"#FFFFFF]";
}
return name;
return GetDisplayNameColor().toHTMLColorCode()+name;
}
const bool ItemInfo::IsEquippable()const{
return slot!=EquipSlot::NONE&&(category=="Equipment"||category=="Accessories");
@ -768,13 +750,16 @@ const std::string Item::Description(CompactText compact)const{
}
}
}
if(HasEnchant()){
description+=std::format("\n{}{}\n#E0E0E0{}#FFFFFF",GetEnchant().value().DisplayCol().toHTMLColorCode(),GetEnchant().value().Name(),GetEnchant().value().Description());
}
return description;
}
const ITCategory Item::Category()const{
return it->Category();
}
const::Decal*const Item::Decal()const{
return it->Decal();
const::Renderable&Item::Icon()const{
return it->Icon();
}
const ItemScript&Item::OnUseAction()const{
return it->OnUseAction();
@ -789,8 +774,8 @@ const std::string&ItemInfo::Description()const{
const ITCategory ItemInfo::Category()const{
return category;
}
const::Decal*const ItemInfo::Decal()const{
return img;
const::Renderable&ItemInfo::Icon()const{
return img.value().get();
}
const ItemScript&ItemInfo::OnUseAction()const{
return ITEM_SCRIPTS.at(useFunc);
@ -837,7 +822,7 @@ void ItemOverlay::Draw(){
Pixel lightCol=Menu::GetCurrentTheme().GetButtonCol()*1.2f;
game->GradientFillRectDecal(pos,{item.width,8},darkCol,darkCol,darkCol,lightCol);
game->DrawRectDecal(pos,{item.width,8},Menu::GetCurrentTheme().GetHighlightCol());
game->DrawDecal(pos,const_cast<Decal*>(item.it.Decal()),{itemScale,itemScale});
game->DrawDecal(pos,const_cast<Decal*>(item.it.Icon().Decal()),{itemScale,itemScale});
game->DrawShadowStringPropDecal(pos+vf2d{itemScale*24+2,2},item.it.Name(),WHITE,BLACK,{0.5f,0.7f});
counter++;
}
@ -1017,7 +1002,7 @@ void Item::EnhanceItem(uint8_t qty){
for(const auto&[name,amt]:consumedResources.GetItems()){
Inventory::RemoveItem(Inventory::GetItem(name)[0],amt);
}
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-consumedResources.GetCost());
game->GetPlayer()->RemoveMoney(consumedResources.GetCost());
SoundEffect::PlaySFX("Enhance Item",SoundEffect::CENTERED);
@ -1030,7 +1015,7 @@ void Item::EnhanceItem(uint8_t qty){
for(const auto&[name,amt]:consumedResources.GetItems()){
Inventory::RemoveItem(Inventory::GetItem(name)[0],amt);
}
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-consumedResources.GetCost());
game->GetPlayer()->RemoveMoney(consumedResources.GetCost());
}
}
SoundEffect::PlaySFX("Craft Item",SoundEffect::CENTERED);
@ -1070,8 +1055,7 @@ const std::string Stats::GetStatsString(const Stats&maxStats,CompactText compact
std::string col="";
if(maxStats.attributes.count(attr)&&int(val)>=int(maxStats.attributes.at(attr))){
Pixel shimmeringCol=PixelLerp({255,196,60},{254,217,133},sin((70*game->GetRunTime())/2.f+0.5f));
col=util::PixelToHTMLColorCode(shimmeringCol);
col=Stats::GetShimmeringColor().toHTMLColorCode();
}
description+=col+std::string(attr.Name())+": "+statNumber+(attr.DisplayAsPercent()?"%":"")+"#FFFFFF";
@ -1397,12 +1381,14 @@ const EquipSlot ItemInfo::StringToEquipSlot(std::string_view slotName){
return nameToEquipSlot[std::string(slotName)];
}
void Inventory::Disassemble(std::weak_ptr<Item>itemRef){
const Inventory::DisassembleResult Inventory::Disassemble(std::weak_ptr<Item>itemRef){
if(ISBLANK(itemRef))ERR(std::format("WARNING! Trying to feed a blank item into the Disassemble function! THIS SHOULD NOT BE HAPPENING!"));
const std::shared_ptr<Item>&currentItem{itemRef.lock()};
if(!currentItem->IsAccessory())ERR(std::format("WARNING! Trying to disassemble Item {} which is not an accessory! THIS SHOULD NOT BE HAPPENING!",currentItem->ActualName()));
if(currentItem->IsLocked())return DisassembleResult::FAILED;
Inventory::RemoveItem(itemRef);
Inventory::AddItem(currentItem->FragmentName(),"Fragment Disassemble Gain Amount"_I);
return DisassembleResult::SUCCESS;
}
const std::string&Item::FragmentName()const{
@ -1423,10 +1409,14 @@ const bool Item::CanBeRefined()const{
return false; //Maxed out, so cannot be refined.
}
const bool Item::CanBeEnchanted()const{
return IsAccessory()&&Inventory::GetItemCount(FragmentName())>="Fragment Enchant Cost"_i[0]&&game->GetPlayer()->GetMoney()>="Fragment Enchant Cost"_i[1];
}
RefineResult Item::Refine(){
if(!CanBeRefined())ERR("WARNING! Trying to refine an item that cannot be refined! Make sure you are checking with CanBeRefined() before calling this function!");
Inventory::RemoveItem(FragmentName(),"Fragment Refine Cost"_i[0]);
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-"Fragment Refine Cost"_i[1]);
game->GetPlayer()->RemoveMoney("Fragment Refine Cost"_i[1]);
std::vector<ItemAttribute>statsAvailableForRefining;
for(const auto&[attr,val]:randomizedStats){
float maxVal{ITEM_DATA[ActualName()].GetMaxStats().A_Read(attr)};
@ -1439,5 +1429,97 @@ RefineResult Item::Refine(){
const float oldAmt{randomizedStats.A_Read(chosenAttr)};
randomizedStats.A(chosenAttr)=std::min(maxStat,oldAmt+increaseAmt);
const float newAmt{randomizedStats.A_Read(chosenAttr)};
game->GetPlayer()->RecalculateEquipStats();
return RefineResult{chosenAttr,newAmt-oldAmt};
}
void Item::_EnchantItem(const ItemEnchant&newEnchant){
enchant=newEnchant;
game->GetPlayer()->RecalculateEquipStats();
}
const std::optional<ItemEnchant>&Item::GetEnchant()const{
return enchant;
}
const bool Item::HasEnchant()const{
return enchant.has_value();
}
const Pixel Item::GetDisplayNameColor()const{
Pixel col{WHITE};
if(HasEnchant())col=enchant.value().DisplayCol();
return col;
}
const bool Item::SelectedEquipIsDifferent(const std::weak_ptr<Item>equipAttemptingToEquip,const EquipSlot slotToEquipTo){
std::weak_ptr<Item>currentItem=Inventory::GetEquip(slotToEquipTo);
switch(slotToEquipTo){
case EquipSlot::RING1:
case EquipSlot::RING2:{
std::weak_ptr<Item>otherItem;
if(slotToEquipTo&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slotToEquipTo&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
const auto EquipIsNotSameAsOppositeRingSlot=[&equip=equipAttemptingToEquip,&otherItem](){
return &*equip.lock()!=&*otherItem.lock();
};
const auto EquipIsNotSameAsCurrentRingSlot=[&equip=equipAttemptingToEquip,&currentItem](){
return &*equip.lock()!=&*currentItem.lock();
};
const auto OneOfBothRingsDoesNotHaveEnchant=[&equip=equipAttemptingToEquip,&otherItem](){
return !equip.lock()->HasEnchant()||!otherItem.lock()->HasEnchant();
};
const auto BothRingsAreCompatible=[&equip=equipAttemptingToEquip,&otherItem](){ //Check if enchants are compatible... Currently unused.
return true;
};
return (ISBLANK(otherItem)&&(OneOfBothRingsDoesNotHaveEnchant()||BothRingsAreCompatible())||
EquipIsNotSameAsOppositeRingSlot()&&
EquipIsNotSameAsCurrentRingSlot()&&
(OneOfBothRingsDoesNotHaveEnchant()||BothRingsAreCompatible()));
}break;
default:{
return &*equipAttemptingToEquip.lock()!=&*currentItem.lock();
}
}
}
const std::optional<std::string>&ItemInfo::FragmentIcon()const{
return fragmentIcon;
}
const Pixel Stats::GetShimmeringColor(){
return PixelLerp({255,196,60},{254,217,133},sin((70*game->GetRunTime())/2.f+0.5f));
}
const std::optional<std::string>&Item::FragmentIcon()const{
return ITEM_DATA.at(FragmentName()).FragmentIcon();
}
const ItemEnchant&Item::ApplyRandomEnchant(){
if(!CanBeEnchanted())ERR("WARNING! Trying to enchant an item that cannot be enchanted! Make sure you are checking with CanBeEnchanted() before calling this function!");
ItemEnchant randomEnchant{ItemEnchant::RollRandomEnchant(GetEnchant())};
_EnchantItem(randomEnchant);
Inventory::RemoveItem(FragmentName(),"Fragment Enchant Cost"_i[0]);
game->GetPlayer()->RemoveMoney("Fragment Enchant Cost"_i[1]);
return GetEnchant().value();
}
void Item::RemoveEnchant(){
enchant.reset();
}
const float Item::CastRange()const{
return ITEM_DATA.at(ActualName()).CastRange();
}
const float ItemInfo::CastRange()const{
return castRange;
}
const float Item::CastSize()const{
return ITEM_DATA.at(ActualName()).CastSize();
}
const float ItemInfo::CastSize()const{
return castSize;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save