Compare commits

..

509 Commits

Author SHA1 Message Date
Quapsel c5769ad0a8 more Chapter 3 presets. 8 months ago
sigonasr2 1f9d54f0cb 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 b9f5b80300 Fix typos and minor descriptions in item enchants file. Enchantment data structures and configuration reading implemented. Release Build 10498. 8 months ago
sigonasr2 3c40b3ee41 Added sample Item Enchant configuration file. 8 months ago
sigonasr2 4774f94d9b Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
sigonasr2 eae5c2915d 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 6c7e7a311f More Chapter 3 Presets. 8 months ago
Quapsel 23654dec15 more Chapter 3 Presets. 8 months ago
sigonasr2 56f731db36 Fix scrollbars not changing color when the mouse is hovering over them. Release Build 10482. 8 months ago
sigonasr2 9640014442 Add unit test to verify non-accessory items cannot be disassembled. 8 months ago
sigonasr2 830b893b1c 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 13eeb46000 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 47530d4822 Setup Palm trees to be foreground objects. Prepare all Artificer window dialogs. Release Build 10424. 8 months ago
Quapsel 795c612f16 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
Quapsel b6638d3b97 First Presets for Chapter 3. 8 months ago
sigonasr2 1ad54ee167 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 b799a8ab4b 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 688514fd97 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
sigonasr2 0a73d825cf Extra Artificer notes. 8 months ago
sigonasr2 2ccb63a74d Artificer initial discussion window menu and setup. Add dialog finish callback function to perform actions once a dialog finishes. 8 months ago
sigonasr2 db50617371 Add dialog loading function to call and trigger Dialog game state. Add Artificer Introduction tutorial task. 8 months ago
sigonasr2 5a4aa61136 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 14bad7cf69 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 7100bec234 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 3fecf73b3c Add sound effects for Witch auto attack, Curse of Pain, Throw Poison, and Curse of Death abilities. Release Build 10390. 8 months ago
sigonasr2 a5f7973c4d 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 f27caf4382 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 d72113506e 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 223bf1b2fe 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 41aab6d8dd Add in Witch Transform ability setup. Afterimage setup with scanline removal animation effect for Transform ability. Release Build 10347. 8 months ago
sigonasr2 e748bff898 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 5ae21944d6 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 5c1144a3bf 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 ce043c19cb Add in missing Beep sound effect for explosive traps. 8 months ago
NicoNicoNii 5a7c60ac7c [Compiler Error] Implement delayed lock-on targeting when multiple targets get marks at the same time (for more dramatic effect) 8 months ago
sigonasr2 23c2bfb45c Setup Purple Energy Ball Attack. 8 months ago
sigonasr2 d7351bd872 Prepare Witch class animations and spritesheet. 8 months ago
sigonasr2 0df19d7b56 Implemented Trapper's Explosive Trap ability. Release Build 10304. 8 months ago
sigonasr2 05d81e4446 Add sound effect for Mark Target ability. Release Build 10303. 8 months ago
sigonasr2 0cb52d1c0f 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 8025680617 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 345a4abb48 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 c3e7c62bd3 Add visual indicator/graphics for Trapper Mark on targets. Added TriggerMark and ApplyMark helper functions. Release Build 10264. 8 months ago
sigonasr2 a1eb109e70 Added TriggerMark convenience function and appropriate unit test for it. 8 months ago
sigonasr2 ee79ef225b 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 efc976758b 2-8 spawnpoint fix (oops) 8 months ago
sigonasr2 b91b1a8362 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 aefa81d71f 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 2cb6fe9d87 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 a9b59b5eba 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 2a50695f51 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 11691e5cba 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 84e823aeb8 Fix upper monster list not being cleared each frame. Release Build 10192. 8 months ago
sigonasr2 3a40d44fd1 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 8c9d8cdcde 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 e0178fb08b 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 64f726b50d 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 55b5cb4738 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 14f9a71346 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 9d290aae5a 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 6ce8ff535a Add Adrenaline Rush unit test. 8 months ago
sigonasr2 5e6f46b85a 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 747c9bbffe Add in adrenaline rush buff. Make attack speed bonuses be applied via modifiers. Release Build 10153. 8 months ago
sigonasr2 b2fc642723 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 78804d666b 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 11b45499fd Deadly Dash directional fix + Additive Blending toggle. 8 months ago
NicoNicoNii 7266e5eb2e Add deadly dash attack sound effects and basic behavior 8 months ago
sigonasr2 1450d01edf 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 fe91e36b60 Added shine effect to engine for upcoming Deadly Dash effect. Release Build 10097. 8 months ago
sigonasr2 ed4a755d1c Add in zoom targeting and ease-in functions for the camera system. Release Build 10094. 8 months ago
sigonasr2 352847ab27 Add Thief Roll Ability. Release Build 10093. 8 months ago
sigonasr2 d2841e0e0c Add new ability icons for new classes. Release Build 10068. 8 months ago
sigonasr2 26af9def75 Change std::exception to std::runtime_error for gcc compatibility. 8 months ago
sigonasr2 6becdd4f6b 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 c80ec17999 Implement Thief Hidden Dagger attack. Add in missing icons for Elixir of the Wind and Recovery Potions. Release Build 10061. 8 months ago
sigonasr2 5f40ee8306 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 34510e732a 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 c307178d95 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 5b32eb268b Add new item icons. Change safe area indicator colors. Make safe area indicator color configurable. Release Build 10039. 8 months ago
sigonasr2 38710151c4 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 210dd3b9de Set background music of Chapter 2 stages to the foresty track. 8 months ago
sigonasr2 b63372230e Add in Chapter 2 map spawn zones and end zones. 8 months ago
sigonasr2 ed5ab319de 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 421ba4bc4a Add stone rain attack to second chapter boss. Second Chapter boss AI routine completed. Release Build 10015. 8 months ago
sigonasr2 191aa24dc2 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 ddcec79102 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 a308fcc4d8 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 9551d71f75 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 784aa022e2 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 3560ddc31e 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 9d95796062 Item Loadout Usage Tests added. 101/101 unit tests passing. Release Build 9947. 8 months ago
sigonasr2 01d61c95f4 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 29f97a901a Add Player HP Recovery tests. 90/90 tests passing. 8 months ago
sigonasr2 3e8c7ddfc0 Finish all player set effect equipment tests. 87/87 tests passing. 8 months ago
sigonasr2 1e4266e0cd 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 9dbc5b46f8 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 fab21ab693 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 ba2d568f77 Monster tests to ensure Health % and Attack % modifiers work properly. 55/55 tests passing. 8 months ago
sigonasr2 f7ec3ef630 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
sigonasr2 a8d04d258f Convert monster stats to calculate using stat up bonuses instead of manual calculations. Release Build 9922. 8 months ago
Quapsel 41e8384012 „Adventures in Lestoria/assets/config/items/Equipment.txt“ ändern 8 months ago
Quapsel 256e41599c „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 8 months ago
Quapsel 37f46c9d91 „Adventures in Lestoria/assets/config/items/Weapons.txt“ ändern 8 months ago
Quapsel 2f6c6f5c96 „Adventures in Lestoria/assets/config/levels.txt“ ändern 8 months ago
Quapsel 21d4cbcaae „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 8 months ago
Quapsel 7484c94708 „Adventures in Lestoria/assets/config/items/Weapons.txt“ ändern 8 months ago
sigonasr2 a7b9017fa7 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 8 months ago
sigonasr2 c7fca6a694 Incorporate new class graphics (Sans Thief). Unlock new class buttons in character selection window. Release Build 9911. 8 months ago
Quapsel efcaac792f Chapter 2 Bonus stage - Spawn Zones and Monster placement. 9 months ago
Quapsel a25545bd99 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 9 months ago
Quapsel 2ef2a9b1f9 Chapter 2 Bonus Stage added. 9 months ago
Quapsel d0d96c83ce „Adventures in Lestoria/assets/config/Monsters.txt“ ändern 9 months ago
sigonasr2 4828b95a8c 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 ad4549cc5d „Adventures in Lestoria/assets/config/Monsters.txt“ ändern 9 months ago
Quapsel 48718453df Monster placement for 2_7 & 2_8 9 months ago
Quapsel 3ab6ef678d Monster placement for 2_3, 2_4, 2_5 & 2_6 9 months ago
Quapsel d45e9e2dab Spawn Zones for Chapter 2 placed. 9 months ago
Quapsel 2cd2104914 Changes to Stage Plates on Worldmap and Monster Placement for 2_1 & 2_2. 9 months ago
sigonasr2 6ea8588feb 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 048f2cce74 Added tests for equipment stats, equipment set effects, and accessory random stat checks. 9 months ago
sigonasr2 ff358a7598 Player Equipment set effect tests added. 9 months ago
sigonasr2 fd62ab749c 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 6cb67ccaa6 Fix unit test crashing when loading from GFX. 9 months ago
sigonasr2 afff03e201 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 d8c2c615dc 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 d6d02cc986 Refactor ability use skills to be testable. Add in basic player damage check and ability use unit tests. 9 months ago
sigonasr2 f355b01171 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 e4a1cddd77 Added Monster unit tests relating to the damage formula, crit rates, and special survival cases. 9 months ago
sigonasr2 e33b3552f1 Add in Monster Unit tests verifying move speed is adjusted properly. 9 months ago
sigonasr2 dcea90fedb Implement unit tests for the project. Fix Display Name bug (found in InternalNameCheck unit test). Add Monster Unit Tests. 9 months ago
sigonasr2 abb18767d4 Accidental copying of triangles in collision checking loop. 9 months ago
sigonasr2 1f635a667d Add in missing placeholder item images. Finish collision checking for shockwave attack. Release Build 9715. 9 months ago
sigonasr2 ddeef9973a Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 9 months ago
sigonasr2 2a8bc4cdcb Chapter 2 Boss shockwave safe spot location indicators added. 9 months ago
Quapsel ef0bf13747 „Adventures in Lestoria/assets/config/shops/Chapter 2 Merchants.txt“ ändern 9 months ago
Quapsel 98f30c0ad4 „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 9 months ago
sigonasr2 a82d06940d 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 8699f1b808 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 64823fb2f3 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 9 months ago
sigonasr2 dc2394cd17 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 c83aff8962 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 c6c3e5cf72 gcc does not like the promotion from float to double in std::max... 9 months ago
sigonasr2 0eb28bd029 const in wrong spot for TMXParser::GetOptimizedMap. Release Build 9666. 9 months ago
sigonasr2 5f77321d3d 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 9dcfa55407 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 c8e74cb647 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 a740d6eb01 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 cd1a734b19 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 9 months ago
sigonasr2 0fd1d5ee8a 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 5cc1bedeac „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 9 months ago
Quapsel a11240c372 „Adventures in Lestoria/assets/config/items/ItemDatabase.txt“ ändern 9 months ago
Quapsel 77da812319 „Adventures in Lestoria/assets/config/items/Accessories.txt“ ändern 9 months ago
sigonasr2 eb3562eca3 Knockbacks are disabled on solid monsters. Add in small knockback effect on stone throw hit. Release Build 9641. 9 months ago
sigonasr2 c625b0767e 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 4a77391dec 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 09d3b2b233 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 1066a7a37d 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 01df50e8da 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 c738bdac0b 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 96790c3073 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 6dc986d424 Remove test code for stage overlay mask implementation. Release Build 9574. 9 months ago
sigonasr2 597e861677 Merge pull request 'AreaHighlightTest' (#57) from AreaHighlightTest into master 9 months ago
sigonasr2 5af5cac7e8 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 0dd8ca5ae9 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 cea4372737 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 8c5438b73b 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 82e8dca6ee 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 5f0a516be5 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 15cf736be0 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 5918846fbd Refactor manual typing of HP Ratios with an HP Ratio function for the monster and player classes. Release Build 9524. 10 months ago
sigonasr2 cfbd4dce97 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 c55a3a1e6b 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 f401138006 Must have at least 20 messages to be considered a successful run. 10 months ago
sigonasr2 4af39499e4 Escape the backticks so they aren't interpreted when outputting the runGame.sh file for Linux. 10 months ago
sigonasr2 b6c88a0caa Flip argn and args in main function (to properly correspond to the main prototype). 10 months ago
sigonasr2 cd10f12590 Move get_Command_line_args function into WIN32 define macro (to allow building on Linux). 10 months ago
sigonasr2 68778bac0a 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 8a12cc0c66 Implement Rendering fixes (Infinite iterator bugs) from demo fixes. Release Build 9509. 10 months ago
sigonasr2 a8bb30e12f 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 05caa062e1 Add player projectile auto attack flag to identify bullets for wind affecting. Release Build 9497. 10 months ago
sigonasr2 5150f4f218 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 92a3c463f7 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 f1ec24df66 Missing Chapter 2 stages added. 10 months ago
sigonasr2 d500e48e67 Tuned wind streak spawns and settings. Good to go. Release Build 9491. 10 months ago
sigonasr2 df56fec448 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 35bcdb3ec2 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 1f8c62188b Simplify fade in and fade out bullet code. Remove unnecessary variables. Release Build 9483. 10 months ago
sigonasr2 d3374062fe 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 3a9bd4afff 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 bbdef1cdfb Bullet fade in timer effects added. Added Tornado rings for second bonus boss. Release Build 9455. 10 months ago
sigonasr2 b956a103df Define tornado bullet type and attack. Release Build 9451. 10 months ago
sigonasr2 ad14544418 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 e1ed802fed Setup Zephy AI phases. Added slight recoil when monsters take damage. Release Build 9434. 10 months ago
sigonasr2 f115786584 Indentation and fix includes for macros in Error.h Release Build 9427. 10 months ago
sigonasr2 e648c31dd2 Merge with master. 10 months ago
sigonasr2 2b6201f641 Cherry pick health % not being applied bug from commit f0ea435562 into master branch. Release Build 9424. 10 months ago
sigonasr2 17ab92eeee Fix bug with Health % item stats/set effects not being applied to the player. Release Build 9424. 10 months ago
sigonasr2 c7b5660a70 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 01ea60b3a5 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 862d047b06 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 f3ca091124 early version for 2_8 added. 10 months ago
sigonasr2 626a9ee742 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 4720f59ba7 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 a075ed79f0 slightly increased arena size (1 tile in height) for Chapter 2 Bonus Boss Stage. 10 months ago
sigonasr2 6902852b51 Fix Major Hawk AI transition not working properly when only one remains. Release Build 9381. 10 months ago
sigonasr2 21b8a9c3cb 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 64ea11b4af 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 38efe127be 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 153589a232 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 45be6e80e6 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 fa0caa5fa9 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 177d62af58 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 ea2dc34bb6 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 29a36770aa 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 6bd76453f3 2_4 added. 10 months ago
sigonasr2 2903c45d16 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 c2b2652810 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 d8777cfff6 2_5 early version added 10 months ago
sigonasr2 39303e2438 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 42b8cf5da7 Setup Stone Elemental AI framework. Fix spritesheet alignment for certain animations. Setup animation data. Added casting animations. 10 months ago
sigonasr2 7292094358 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 5ef4b90b29 Add transparency effect for mismatching Z axis for bullets/monsters or if the player has iframes. Release Build 9266. 10 months ago
Quapsel 6107d98075 Boss Stage - Chapter 2 - Bonus Boss 10 months ago
sigonasr2 081bc71612 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 e9ed495d39 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 b2b1fa81cf 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 3f463bcf2f Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 10 months ago
sigonasr2 ee2a66f5fe 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 8c8e05c15e 2_3 added. 10 months ago
sigonasr2 79b595bbef 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 f5bfc0d34d 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 cb35d2c1fa 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 beb1bdfc79 Make Goblin Boar Rider sprite a bit more sensible. Mounted monster animations now properly update. Release Build 9200. 11 months ago
sigonasr2 5c7e5a3ab3 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 c83f84f29c Setup framework for the goblin boar rider. 11 months ago
sigonasr2 3051422383 Change goblin dagger stab to match new sprite positions and modify animation speeds slightly. Release Build 9180. 11 months ago
sigonasr2 cc3a95094a Update knockback logic and formula again. Release Build 9177. 11 months ago
sigonasr2 365d66098e 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 253bbc610e Adjust all monster tilesets and presets with new sprites. 11 months ago
sigonasr2 8827d1ca4e Fix bow goblin reload AI and changing facing direction while aiming at target. Release Build 9174. 11 months ago
sigonasr2 7375d37d99 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 280a321abb 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 8a18e0bbaf Include animation gifs in repo. 11 months ago
sigonasr2 dbe52a2a4a 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 16c52bf397 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 70906d5594 Fix crash when the game attempts to spawn item drops. Release Build 9125. 11 months ago
sigonasr2 f3f91f560e Merge Quapsel's updates. 11 months ago
sigonasr2 cdda8c9a99 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 b9f64982f8 changes at 2_1 & tilepresets. 11 months ago
sigonasr2 76c1396fec 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 00b18355c1 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 6b6bdf741b Add slight knockback effect on goblin dagger stabs. Release Build 9035. 11 months ago
sigonasr2 c37d52d186 Goblin (Dagger) dagger stab attack implemented. Release Build 9033. 11 months ago
sigonasr2 7320fe9348 Goblin Dagger AI basic behaviors implemented. Release Build 9027. 11 months ago
sigonasr2 e294c56c32 Change trim function on Visual Novel. 11 months ago
sigonasr2 69ee9ce5be Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 11 months ago
sigonasr2 d40ab48e78 Use smaller images for class character/visual novel sprites. Release Build 9020. 11 months ago
Quapsel 392afbb8be 2_2 added. 11 months ago
sigonasr2 f2c18a76a1 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 5912efeb4f Enforce const-ness across tilesets to ensure copies and writes are explicitly allowed. Release Build 8990. 11 months ago
sigonasr2 045e10b888 Fix backpedal logic to use move run towards strategy instead. Attacked by Player trigger added. 11 months ago
sigonasr2 adb35f2ce5 Boar test behaviors and general AI implemented. Release Build 8958. 11 months ago
sigonasr2 119efab573 Add animation utilities for getting current animation frame index and total animation duration. 11 months ago
sigonasr2 016251fde3 Merge upstream with demo branch. 11 months ago
sigonasr2 f5acd59b5b 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 42aab4b5e9 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 11 months ago
sigonasr2 4fb84cf365 First half of Boar AI completed. 11 months ago
Quapsel 9d0dfe9562 added exp values for new Monsters. 11 months ago
Quapsel f10f3a0fe4 added wall for start of 1_4. 11 months ago
sigonasr2 ec83c6351a 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 95c43b06b0 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 236b212eab Add back in connections for Chapter 2. 11 months ago
sigonasr2 a88b0b810b 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 d10127b721 Updated all maps to utilize monster template system. 11 months ago
sigonasr2 35acba879e Patch 1.2 11 months ago
sigonasr2 50515c515c 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 f769a77e42 Character art is now handled differently in visual novels to incorporate character art of any width. Release Build 8923. 11 months ago
sigonasr2 bdf126820a Add in breadcrumb re-exploration for minimaps. Release Build 8921. 11 months ago
sigonasr2 e0aea34a43 Upstream merge with master branch to update all chapter 1 levels to include boundaries on the stages. 11 months ago
sigonasr2 512c46ea8a Implement map tile repeating factors and implement animated blocked off map region animation frames. Release Build 8917. 11 months ago
sigonasr2 4c8a251e33 Minimap shadow ring implemented. Release Build 8904. 11 months ago
sigonasr2 cc8f79d34a Remove Infinite Map setting from 1-B1 and 2-1 11 months ago
NicoNicoNii 1a48066922 Update CMakeLists to use imported libs for Discord and Steam APIs 11 months ago
sigonasr2 ef7fc2c826 Minimap can only be toggled when it's actually visible (e.g. not during boss fights) 11 months ago
sigonasr2 5446da7ef8 1-5. Fix a missed tile near waterfall area. 11 months ago
sigonasr2 f91ddf542d Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 11 months ago
sigonasr2 3d897b1aba Downstream merge with demo branch into master branch. 11 months ago
sigonasr2 bba33d43d2 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 25075217dd When the game loses focus, the game will autopause during exploration. Release Build 8879. 11 months ago
sigonasr2 145ecdaa01 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 4a8ccd2e20 Add toggle between small,large, and hidden minimap. Release Build 8868. 11 months ago
Quapsel 00fc1b4a92 added Walls at beginning and end of every Map. 11 months ago
sigonasr2 317ea5a672 Downstream merge with demo branch. 11 months ago
sigonasr2 2e39c0a3f5 Implemented minimap HUD overlay and included miniature player marker. Rearranged initialization of player animations in loading process. Release Build 8851. 11 months ago
sigonasr2 64a25dc66c 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 f2b17e8c85 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 5d09e8d66a World map should connect to 2-1. Rebuilt executable with latest files. Release Build 8817. 11 months ago
sigonasr2 df28a12c63 Downstream merge from demo branch. Fixes Steam crash when Steam is not installed. 11 months ago
sigonasr2 91a660c5b4 Rebuild master branch build. Release Build 8667. 11 months ago
Quapsel 626cf84d99 small Tileset change for map start/end 11 months ago
sigonasr2 60696e9956 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 089e84134f Minimap chunk revealing implemented. Release Build 8781. 11 months ago
sigonasr2 c378939994 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 4306f4eab8 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 254b4b3ea0 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 8c759f0367 Minimap shadowing. 12 months ago
sigonasr2 90beda6807 Color mode sampling on tiles to determine minimap colors. Release Build 8703. 12 months ago
sigonasr2 d2079f2a90 Minimap generation implemented. Release Build 8675. 12 months ago
Nic0Nic0Nii 5abd80932e Upstream Merge branch 'demo' 12 months ago
sigonasr2 37a8847e4e Fix major accessory duplication bug. Release Build 8666. 12 months ago
sigonasr2 99d449400b 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 28003c88a0 Save file hash failing should log, not error. Release Build 8655. 12 months ago
sigonasr2 020f47f1f9 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 3f13c574c0 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 35c269bd8b 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 c864e0f155 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 24888f35db Issue#43 resolved. Steel weapons removed from blacksmith crafting list. 12 months ago
Nic0Nic0Nii 23f17ec459 Move global update items to its own game loop update function. Damage numbers now update on the overworld map menu. 12 months ago
sigonasr2 bcbe2150ee 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 c362721096 Added noise to grass/ground tiles of maps. 12 months ago
sigonasr2 02e7d5665a Added vignette effect when taking damage. Release Build 8614. 12 months ago
sigonasr2 d6e140cfb4 Changed Bandages item description to include casting info. 12 months ago
sigonasr2 65c3b2cdb6 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 8b3113fbde Upstream merge with released demo build. 12 months ago
sigonasr2 7cdcebc71e 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 ba5c187ec7 Details added to Chapter 2 Presets & v1 of 2_1 finished. 12 months ago
Quapsel cf6788a418 start of 2_1 & some more basic shapes for the chapter 2 presets. 12 months ago
sigonasr2 78082c6bd4 gcc needs a float cast for std::min 12 months ago
sigonasr2 0a42be844b Auto-aim should lead slightly. Auto-retreat should attempt max distance retreat. Release Build 8598. 12 months ago
sigonasr2 fcb557a2d1 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 ddfe0cf13d 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 71acd6ef7d Remove extraneous commenting. Release Build 8582. 12 months ago
sigonasr2 3c2c19f822 Make sure file hash does not include the | character due to emscripten using it to split a save request. 12 months ago
sigonasr2 ad79910848 Create a compatible hash algorithm for both windows and linux cross-saving. Release Build 8579. 12 months ago
sigonasr2 4c81506c64 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 12 months ago
sigonasr2 282f918d1d Emscripten compatibility fixes. 12 months ago
sigonasr2 4870e5952b distribute script needs proper linux syntax to include wildcard items 12 months ago
sigonasr2 826aae50f0 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 e052f6e089 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 2599cfd14b Switch monster kill count tracking to use steam stats progress tracking. Release Build 8551. 12 months ago
sigonasr2 dfff1762f3 Include Weapon Upgrade/Equipment related Achievements. Release Build 8548. 12 months ago
sigonasr2 e15f23bad1 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 1c72a86945 Implement monster-specific kill achievements. Release Build 8532. 12 months ago
sigonasr2 21e0cd5b65 Fix equipment upgrade tests, left them in by accident. Added kill amount achievements. Release Build 8530. 12 months ago
sigonasr2 68e4934355 Add achievement icons and setup achievement config file. Add in unlock area achievements. 12 months ago
sigonasr2 3256a686a3 Add in some achievement icons and file hashing saving/loading of save files. Release Build 8515. 12 months ago
sigonasr2 d1f95e03c9 Popup debugging log scripts and sessions included in dev environment. 12 months ago
sigonasr2 9a3cbc40f9 Prep Time Trial system structure. Release Build 8492. 12 months ago
sigonasr2 523050d3ab Merge branch 'master' into demo 12 months ago
sigonasr2 c3f8c81a0d Upstream merge with main. Demo branch officially up-to-date. 12 months ago
sigonasr2 c5e0df5f44 Upstream demo branch merge with main. 12 months ago
sigonasr2 d24df058b1 Release script needs to deposit the static libs into the Adventures in Lestoria folder instead. 12 months ago
sigonasr2 a3fe98f542 Stupid Linux. 12 months ago
sigonasr2 ae08ab316a Update linux distribution script. 12 months ago
sigonasr2 794d38cdb3 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 18bfa4a26b 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 daa90538ad Add a tutorial task that requires the player to equip a crafted piece of gear. Release Build 8467. 12 months ago
sigonasr2 ea74eec6f8 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 f2929d915d 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 32d681f130 Added VSync toggle to Settings windows. Release Build 8464. 12 months ago
sigonasr2 ee382488f9 Warrior auto attack swing now has a frontal sweep angle. Release Build 8457. 12 months ago
sigonasr2 02933fdc38 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 12 months ago
sigonasr2 9ca52cbead 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 78c591f64e Small progress on Chapter 2 presets. 12 months ago
sigonasr2 3c315da193 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 12 months ago
sigonasr2 d348d6505c Fix tutorial task input prompts being too large. Release Build 8445. Rebuild with official pack key. 12 months ago
sigonasr2 88572368b5 Fix linux build and distribution release processes. 12 months ago
sigonasr2 561f403cd2 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 b9f9d0ceb2 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 6e03f99543 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 758696d28b 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 cb39f0859b Add controller lightbar response based on ingame events. Release Build 8399. 12 months ago
sigonasr2 8a3a101d28 Add in controller rumble for steam API controllers. Release Build 8398. 12 months ago
sigonasr2 81d6e103cc Game pauses when a controller is disconnected. Release Build 8395. 12 months ago
sigonasr2 b4c9b29be4 Enable Keyboard input overlay when using Steam Big Picture. Enable Fullscreen automatically when using Steam Big Picture. Release Build 8394. 12 months ago
sigonasr2 c291aff00a 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 0830185829 Fix Ranger Charged Shot and Multi Shot animations being overwritten by cast completion state resetting. Release Build 8367. 12 months ago
sigonasr2 37d738fbf9 Add Steam input binding groups to tutorial tasks. Release Build 8357. 12 months ago
sigonasr2 2f8dee095f 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 bb86146a04 Version number updated to 1.0.0. Handle analog input hints for steam icons. Release Build 8348. 12 months ago
sigonasr2 5016dc9b66 Implemented proper steam icon displays for all users of DrawInput (base camp NPCs). Release Build 8339. 12 months ago
sigonasr2 db5b071689 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 cd1e989e5e Fix steam icons from being loaded too early if the game starts up quickly. Release Build 8330. 12 months ago
sigonasr2 ecc0f1f733 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 bf6c78c286 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 12 months ago
sigonasr2 2f6e3b1718 Hot swapping/plugging controllers now working. Refresh action sets of all active controllers. Release Build 8298. 12 months ago
Quapsel 5f56b14b4c initial shape for some Chapter 2 Tilepresets. 12 months ago
sigonasr2 02ef18826d 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 3be02315a5 Retrieve Steam icons from steam platform files. Update official controller configs. Release Build 8290. 12 months ago
sigonasr2 bdad7cf186 KEY_BACK was not referencing steam input's BACK enum but KEY's BACK enum. Release Build 8283. 12 months ago
sigonasr2 fa79df59de 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 195ec1a0f8 Include switch pro config because Steam is dumb and auto-switches inputs on it. 12 months ago
sigonasr2 8011310d96 Get input detection from controllers configured through Steam Input. Release Build 8264. 12 months ago
sigonasr2 06ee669034 Add in all steam input requests to input lists. Release Build 8238. 12 months ago
sigonasr2 6593f43b96 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 7aa65eae72 Make sure steam_appid.txt exists in working directory so testing will launch the app in the debugger. 12 months ago
sigonasr2 6cd34639ef 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 8768b89f38 Add check to ensure steam is running before attempting to use the Steam API. Release Build 8221. 12 months ago
sigonasr2 03ac0be3f3 Add steam API to build. Release Build 8219. 12 months ago
sigonasr2 b13fa843de 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 48464e3d8b 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 7a683fffa6 Fix right-hand side checkbox labels in Settings menu being too wide, going behind the scrollbar. Release Build 8208. 1 year ago
sigonasr2 f73cadcbff 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 51964406a7 Fix outer boundary tile transitions in Intro setpieces. 1 year ago
sigonasr2 a71978c819 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 f8e7144a02 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 b6e6cb58b1 grass transition for 1_6 - 1_8 & 1_B1 1 year ago
Quapsel d1c8b1d06b Grass transition for 1_2 - 1_5 added. 1 year ago
Quapsel c360898800 changes on End_of_Map Tileset and grass transition at map end for 1_1. 1 year ago
sigonasr2 15d5d2fb4a Tie PGE font setting to resource pack loading system. Release Build 8168. 1 year ago
sigonasr2 585cd209be 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 de82e3f913 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 cb0e6105f2 Marketiiiiiiiiing 1 year ago
sigonasr2 9133ab9e94 Added low health warning visuals and sound effect. Release Build 8145. 1 year ago
sigonasr2 0484908b37 Add in showing of max health/mana display in settings menu. Release Build 8142. 1 year ago
sigonasr2 4d912ab259 Change casting SFX volume when the SFX volume slider is changed. Release Build 8134. 1 year ago
sigonasr2 18699fb4ca 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 48ce0c6cd9 Audio casting sound volume did not respect SFX option setting. 1 year ago
sigonasr2 25654fc0da Update to PGE v2.25. 1 year ago
sigonasr2 454963c80e Fix text coloring issue for equip items in the blacksmith menu that are not valid for enhancement. Release Build 8113. 1 year ago
sigonasr2 d4d83a1e44 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 6c59787373 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 965029e224 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 ecab0ae259 Merge pull request 'KingSlimeFix' (#35) from KingSlimeFix into master 1 year ago
sigonasr2 751513135e Merge with master. 1 year ago
sigonasr2 1abbf05298 Jump targeting lock-on time implemented for Slime King script. 1 year ago
sigonasr2 5dc5a74ed6 When the application loses focus, all inputs are released properly so inputs aren't stuck on refocus. Release Build 8085. 1 year ago
sigonasr2 60e5ca2033 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 a687bbe455 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 2de0ba3810 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 5a8a3d64e9 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 13854574c9 Close consumables selection window automatically once a loadout item has been selected. Release Build 8038. 1 year ago
sigonasr2 72c5a7394b Added Yellow Slime jump attack behavior. Release Build 8023. 1 year ago
sigonasr2, Sig, Sigo 468302833f Add monster jumping inside run towards monster strategy. 1 year ago
Quapsel 63fc61cc09 „Adventures in Lestoria/assets/config/Monsters.txt“ ändern 1 year ago
sigonasr2 da1774bfdd Maxed out item rolls now show up in a different color. Release Build 8012. 1 year ago
sigonasr2 3f352eeeeb Prevent precast consumable items (Ex. Bandages) from still being activated when the loadout slot quantity reaches 0. Release Build 7998. 1 year ago
sigonasr2 6ff939e491 Warrior slash animation now extends based on actual attack range. Release Build 7997. 1 year ago
sigonasr2 588e184764 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 68bdb6bc26 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 dec1f0516f 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 6e4f206391 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 5fc5169ddd Add in support for subcomponents within subcomponents. Prioritize highest depth menu item when hovering over overlapping items. 1 year ago
sigonasr2 59eb24b65b Fix mob spawns in 1-1 and 1-7 for mobs that were spawning inside of map hitboxes. 1 year ago
sigonasr2 822bab3318 Warrior Battlecry Attack Range: 350->500, Battlecry Slowdown Duration: 5s -> 6s, Slowdown Amount: 30% -> 40% 1 year ago
sigonasr2 7ada663056 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 227d43bd77 Ranger Backdash Iframe time 0.2s -> 0.22s. Ranger Backdash Distance 250 -> 300. 1 year ago
sigonasr2 8ec8a5a263 Ranger hairstyle changed. 1 year ago
sigonasr2 0fd57311be 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 05e59f8ca4 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 3dcce251ef Fix menus being active during screen transitions. Release Build 7905. 1 year ago
sigonasr2 f272e774f5 Include new story art. 1 year ago
sigonasr2 a6bd3c0c43 Add collision to waterfall tiles. 1 year ago
sigonasr2 a8b5b3f84a Added shadow underneath bridge for Stage I-I. Release Build 7902. 1 year ago
sigonasr2, Sig, Sigo b3a632a375 Fix some story script and update TODO. Remove extra old testing code. 1 year ago
sigonasr2 592db62c9a Fix equipment stats not correctly applying after enhancing gear. Release Build 7898. 1 year ago
sigonasr2 09a0a16bdc Fix XP Gain tick rate (25% bar gain every 0.5 seconds). Release Build 7897. 1 year ago
sigonasr2 462710a44d 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 93081fe7f0 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 9a6f710201 Implement menu navigation for the new overworld button in the Level Complete window. Release Build 7889. 1 year ago
sigonasr2 72f4693cfc 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 5992adbc5f 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 2d3b506556 Add slight knockback effect to Warrior's ground slam. Release Build 7885. 1 year ago
sigonasr2 3aaa7d03e4 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 a9a640be82 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 79d50fdf96 Fix game settings not properly loading for the emscripten version. 1 year ago
sigonasr2 a506c47aea Credits navigation key actually pops up the credits for controllers. 1 year ago
sigonasr2 78cbad9297 Story I-I no longer unlocks Campaign I-II map. 1 year ago
sigonasr2 886eff7d7a 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 eb0477a787 Add in #undef lines for Linux builds. Stupid Linux. 1 year ago
sigonasr2 e9b2773f7f Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2 686fa7916c Solve crash that occurs when selling items not in your loadout item slots. Release Build 7872. 1 year ago
Quapsel ff01195c01 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 f8408dc530 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 613e2be21f 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 84e713b9a2 Restock loadout items from previous setup automatically. 1 year ago
sigonasr2, Sig, Sigo f1dc45ea95 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2, Sig, Sigo 519a373fa8 Remove (most) stuttering from web build when loading stage sound. 1 year ago
Quapsel ab01cc6acc „Adventures in Lestoria/assets/config/items/Weapons.txt“ ändern 1 year ago
Quapsel 1b2831bbb2 „Adventures in Lestoria/assets/config/items/Accessories.txt“ ändern 1 year ago
sigonasr2, Sig, Sigo 8be4f12212 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2, Sig, Sigo 8b7703f457 Story I unlocks from the beginning of the game. 1 year ago
Quapsel a53aa64b07 „Adventures in Lestoria/assets/config/Monsters.txt“ ändern 1 year ago
sigonasr2 94f314a7aa 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 469c360e51 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 a51d3bc898 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 aa4d35d754 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 93e5921e57 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 2d0702af2c 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 543a0b6792 Add saving indicator. Release Build 7761. 1 year ago
sigonasr2 f6c822f868 Fix story pause command to not actually get rid of the story dialog. Release Build 7759. 1 year ago
sigonasr2 4b3b36bdfd Added tutorial tooltips. Release Build 7758. 1 year ago
sigonasr2, Sig, Sigo fecabce054 Adjustments to order of entering a stage 1 year ago
sigonasr2, Sig, Sigo 85dcc64e2e Setup tutorial and tutorial task features. 1 year ago
sigonasr2 0c619aa4f6 Add in visual novel audio pitch and BGM change commands. Add in XP bonus when completing stages. Release Build 7706. 1 year ago
sigonasr2 fa8dfd3c6f 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 3261ddf6f5 Add mosaic transitions from overworld to stages. Fix up Visual Novel placeholder text for player name. Release Build 7690. 1 year ago
sigonasr2 3faff12015 Restore item loadout quantities on level restarts. Release Build 7674. 1 year ago
sigonasr2 b96b5735e9 Implemented Death menu. Release Build 7668. 1 year ago
sigonasr2 03e6d3bd80 Fix missing strategy draw call for monsters. Add in death state and basic transition effects. Release Build 7662. 1 year ago
sigonasr2 08aa7a309d 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 e8ac5a957f 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 53b5c1f967 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 b0f0e7dcdd Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2 2253c14f4c 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 efa4f6dc95 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2 eb1133c2e2 Merge branch 'master' of http://sig.projectdivar.com/sigonasr2/AdventuresInLestoria 1 year ago
sigonasr2 35e8a70059 Lock down gamepack key so it's not included in repo. Release Build 7608. 1 year ago
sigonasr2 ae26813f05 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. 150
      Adventures in Lestoria Tests/ItemTests.cpp
  9. 119
      Adventures in Lestoria Tests/MonsterTests.cpp
  10. 210
      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. 843
      Adventures in Lestoria/AdventuresInLestoria.cpp
  17. 82
      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. 30
      Adventures in Lestoria/ArtificerDisassembleConfirmWindow.cpp
  23. 150
      Adventures in Lestoria/ArtificerDisassembleWindow.cpp
  24. 73
      Adventures in Lestoria/ArtificerEnchantConfirmWindow.cpp
  25. 185
      Adventures in Lestoria/ArtificerEnchantWindow.cpp
  26. 32
      Adventures in Lestoria/ArtificerRefineConfirmWindow.cpp
  27. 75
      Adventures in Lestoria/ArtificerRefineResultWindow.cpp
  28. 164
      Adventures in Lestoria/ArtificerRefineWindow.cpp
  29. 26
      Adventures in Lestoria/ArtificerWindow.cpp
  30. 26
      Adventures in Lestoria/Attributable.h
  31. 3
      Adventures in Lestoria/AttributableStat.h
  32. 43
      Adventures in Lestoria/Audio.cpp
  33. 11
      Adventures in Lestoria/Audio.h
  34. 8
      Adventures in Lestoria/Bear.cpp
  35. 4
      Adventures in Lestoria/BearTrap.cpp
  36. 59
      Adventures in Lestoria/BlackHole.h
  37. 2
      Adventures in Lestoria/BlacksmithCraftingWindow.cpp
  38. 10
      Adventures in Lestoria/Boar.cpp
  39. 2
      Adventures in Lestoria/Bomb.cpp
  40. 4
      Adventures in Lestoria/BreakingPillar.cpp
  41. 71
      Adventures in Lestoria/Buff.cpp
  42. 41
      Adventures in Lestoria/Buff.h
  43. 2
      Adventures in Lestoria/Bullet.cpp
  44. 4
      Adventures in Lestoria/Bullet.h
  45. 178
      Adventures in Lestoria/BulletTypes.h
  46. 104
      Adventures in Lestoria/BurstBullet.cpp
  47. 49
      Adventures in Lestoria/Chapter_3_FinalBoss.txt
  48. 4
      Adventures in Lestoria/CharacterAbilityPreviewComponent.h
  49. 95
      Adventures in Lestoria/CharacterMenuWindow.cpp
  50. 15
      Adventures in Lestoria/ChargedArrow.cpp
  51. 7
      Adventures in Lestoria/ClassSelectionWindow.cpp
  52. 57
      Adventures in Lestoria/CollectedCoinEffect.cpp
  53. 2
      Adventures in Lestoria/ConnectionPoint.cpp
  54. 10
      Adventures in Lestoria/ConsoleCommands.txt
  55. 2
      Adventures in Lestoria/ConsumableCraftingWindow.cpp
  56. 118
      Adventures in Lestoria/Crab.cpp
  57. 2
      Adventures in Lestoria/CraftItemWindow.cpp
  58. 2
      Adventures in Lestoria/Crawler_Artificer.txt
  59. 54
      Adventures in Lestoria/DEFINES.h
  60. 4
      Adventures in Lestoria/DaggerSlash.cpp
  61. 4
      Adventures in Lestoria/DaggerStab.cpp
  62. 81
      Adventures in Lestoria/DamageNumber.cpp
  63. 21
      Adventures in Lestoria/DamageNumber.h
  64. 2
      Adventures in Lestoria/DeadlyDash.cpp
  65. 57
      Adventures in Lestoria/DynamicMenuLabel.h
  66. 59
      Adventures in Lestoria/Effect.cpp
  67. 128
      Adventures in Lestoria/Effect.h
  68. 7
      Adventures in Lestoria/EnergyBolt.cpp
  69. 8
      Adventures in Lestoria/EnhancementStatsLabel.h
  70. 60
      Adventures in Lestoria/Entity.cpp
  71. 56
      Adventures in Lestoria/Entity.h
  72. 3
      Adventures in Lestoria/EnvironmentalAudio.cpp
  73. 2
      Adventures in Lestoria/EquipSlotButton.h
  74. 21
      Adventures in Lestoria/Error.h
  75. 25
      Adventures in Lestoria/ExplosiveTrap.cpp
  76. 65
      Adventures in Lestoria/FadeInOutEffect.cpp
  77. 23
      Adventures in Lestoria/FallingStone.cpp
  78. 7
      Adventures in Lestoria/FireBolt.cpp
  79. 58
      Adventures in Lestoria/FlipCoinEffect.cpp
  80. 10
      Adventures in Lestoria/ForegroundEffect.cpp
  81. 12
      Adventures in Lestoria/Frog.cpp
  82. 5
      Adventures in Lestoria/FrogTongue.cpp
  83. 298
      Adventures in Lestoria/GhostOfPirateCaptain.cpp
  84. 84
      Adventures in Lestoria/GhostSaber.cpp
  85. 84
      Adventures in Lestoria/GiantCrab.cpp
  86. 179
      Adventures in Lestoria/GiantOctopus.cpp
  87. 9
      Adventures in Lestoria/Goblin_Boar_Rider.cpp
  88. 4
      Adventures in Lestoria/Goblin_Bomb.cpp
  89. 10
      Adventures in Lestoria/Goblin_Bow.cpp
  90. 12
      Adventures in Lestoria/Goblin_Dagger.cpp
  91. 10
      Adventures in Lestoria/Hawk.cpp
  92. 54
      Adventures in Lestoria/HomingBullet.cpp
  93. 130
      Adventures in Lestoria/HubPauseMenu.cpp
  94. 7
      Adventures in Lestoria/HurtDamageInfo.h
  95. 33
      Adventures in Lestoria/IBullet.cpp
  96. 10
      Adventures in Lestoria/IBullet.h
  97. 5
      Adventures in Lestoria/IT.cpp
  98. 2
      Adventures in Lestoria/IT.h
  99. 52
      Adventures in Lestoria/Ink.cpp
  100. 66
      Adventures in Lestoria/InkBullet.cpp
  101. Some files were not shown because too many files have changed in this diff Show More

@ -47,8 +47,7 @@
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'">
<LinkIncremental>true</LinkIncremental>
<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>
<IncludePath>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|Win32'">
<LinkIncremental>true</LinkIncremental>
@ -58,17 +57,16 @@
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<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>
<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>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<LanguageStandard>stdcpplatest</LanguageStandard>
<LanguageStandard>stdcpp20</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>
@ -103,22 +101,6 @@
<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>
@ -138,12 +120,6 @@
<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,22 +69,5 @@
<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>

@ -1,173 +0,0 @@
#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

@ -1,178 +0,0 @@
#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");
}
};
}

@ -1,113 +0,0 @@
#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();
}
};
}

@ -1,95 +0,0 @@
#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,15 +41,12 @@ 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;
@ -63,7 +60,6 @@ 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();
@ -77,13 +73,13 @@ 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::ClearDrops();
ItemDrop::drops.clear();
MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
#pragma endregion
@ -91,15 +87,13 @@ namespace ItemTests
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,0});
testKey=testGame->GetKeyboardState(0);
testKey=&testGame->pKeyboardState[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(ItemCleanupTests){
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
TEST_METHOD(ItemGiveTest){
Inventory::AddItem("Health Potion"s,3);
@ -136,26 +130,25 @@ 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->GetItem1(),testKeyboardInput);
player->CheckAndPerformAbility(player->useItem1,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->GetItem1().GetCooldownTime(),player->GetItem1().cooldown,L"Item 1 is now on cooldown.");
Assert::AreEqual(player->useItem1.GetCooldownTime(),player->useItem1.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->GetItem1(),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->useItem1,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->ConsumeMana(76);
player->mana=24;
Inventory::AddItem("Flat Recovery Potion"s,5U);
game->SetLoadoutItem(0,"Flat Recovery Potion");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->GetItem1(),testKeyboardInput);
game->OnUserUpdate(0.f); //Wait an extra tick for the buff to begin going down.
player->CheckAndPerformAbility(player->useItem1,testKeyboardInput);
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.");
@ -163,12 +156,11 @@ namespace ItemTests
}
TEST_METHOD(PctRestoreScriptTest){
player->Hurt(75,player->OnUpperLevel(),player->GetZ());
player->ConsumeMana(76);
player->mana=24;
Inventory::AddItem("Pct Recovery Potion"s,5U);
game->SetLoadoutItem(1,"Pct Recovery Potion");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->GetItem2(),testKeyboardInput);
game->OnUserUpdate(0.f); //Wait an extra tick for the buff to begin going down.
player->CheckAndPerformAbility(player->useItem2,testKeyboardInput);
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.");
@ -179,8 +171,7 @@ namespace ItemTests
Inventory::AddItem("Bandages"s,5U);
game->SetLoadoutItem(2,"Bandages");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->GetItem3(),testKeyboardInput);
game->OnUserUpdate(0.f); //Wait an extra tick for the buff to begin going down.
player->CheckAndPerformAbility(player->useItem3,testKeyboardInput);
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.");
@ -223,7 +214,6 @@ 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.");
@ -235,122 +225,6 @@ 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,7 +41,6 @@ All rights reserved.
#include "ItemDrop.h"
#include "Tutorial.h"
#include "DamageNumber.h"
#include "GameHelper.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -51,7 +50,6 @@ INCLUDE_game
INCLUDE_GFX
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_MONSTER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
TEST_MODULE_INITIALIZE(AiLTestSuite)
{
@ -67,15 +65,12 @@ 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();
@ -85,12 +80,13 @@ 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->_SetCurrentLevel("CAMPAIGN_1_1");
ItemDrop::ClearDrops();
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["UpperZone"];
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["LowerZone"];
game->currentLevel="CAMPAIGN_1_1";
ItemDrop::drops.clear();
#pragma endregion
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
@ -102,7 +98,7 @@ namespace MonsterTests
}
void SetupMockMap(){
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
ItemDrop::drops.clear();
}
#pragma endregion
@ -110,10 +106,8 @@ namespace MonsterTests
SetupTestMonster();
SetupMockMap();
}
TEST_METHOD_CLEANUP(MonsterTestCleanup){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
~MonsterTest(){
testGame.release();
}
TEST_METHOD(DisplayNameCheck){
Assert::AreEqual("Test Monster",MONSTER_DATA["TestName"].GetDisplayName().c_str());
@ -402,7 +396,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->GetType()==DamageNumberType::DOT)return count+1;
if(damageNumber->type==DamageNumberType::DOT)return count+1;
else return count;
})
,L"There should be 2 damage numbers of type DOT.");
@ -410,7 +404,8 @@ 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::Update(0.1f); //A monster that is spawned needs to be added to the monster list in the next tick.
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.
std::weak_ptr<Monster>testMonster{MONSTER_LIST.front()};
Assert::AreEqual(uint8_t(0),testMonster.lock()->GetMarkStacks(),L"Monster has 0 marks initially.");
@ -461,99 +456,5 @@ 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,8 +42,6 @@ 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;
@ -51,7 +49,6 @@ using namespace olc::utils;
INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_INITIALIZEGAMECONFIGURATIONS
extern std::mt19937 rng;
@ -65,7 +62,6 @@ 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();
@ -85,16 +81,15 @@ namespace PlayerTests
#pragma region Setup a fake test map and test monster
game->MAP_DATA["CAMPAIGN_1_1"];
ItemDrop::ClearDrops();
ItemDrop::drops.clear();
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);
testKey=&testGame->pKeyboardState[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();
@ -103,8 +98,6 @@ namespace PlayerTests
}
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();
testGame->OnUserUpdate(0.f);
testGame.reset();
}
TEST_METHOD(PlayerAlive){
Assert::IsTrue(player->IsAlive());
@ -322,11 +315,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){
Game::AddBuffToPlayer(BuffType::STAT_UP,5.f,1000.f,{"Health"});
player->AddBuff(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){
Game::AddBuffToPlayer(BuffType::STAT_UP,5.f,1000.0_Pct,{"Health %"});
player->AddBuff(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){
@ -438,18 +431,6 @@ 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.");
@ -457,9 +438,6 @@ 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)};
@ -505,10 +483,10 @@ namespace PlayerTests
}
TEST_METHOD(DamageReductionStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Damage Reduction is 0%");
Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Damage Reduction is 0%");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(75.0_Pct,player->GetDamageReductionFromBuffs(),L"Max Damage Reduction is 75%, even if a piece has 100% damage reduction.");
Assert::AreEqual(100.0_Pct,player->GetDamageReductionPct(),L"Damage Reduction is 100%");
}
TEST_METHOD(HPRecoveryStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
@ -594,7 +572,6 @@ 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");
@ -614,7 +591,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->GetType()==DamageNumberType::DOT)return count+1;
if(damageNumber->type==DamageNumberType::DOT)return count+1;
else return count;
})
,L"There should be a damage number of type DOT.");
@ -623,181 +600,10 @@ 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->GetType()==DamageNumberType::DOT)return count+1;
if(damageNumber->type==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.");
}
};
}

@ -57,7 +57,3 @@ 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,8 +62,6 @@ 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;
@ -75,15 +73,12 @@ 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,24 +29,6 @@
"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",
@ -70,11 +52,7 @@
"foresty1_1",
"overworld",
"foresty_boss",
"base_camp",
"mountain",
"mountain_boss",
"beach",
"beach_boss"
"base_camp"
],
"valuesAsFlags": false
},
@ -197,11 +175,7 @@
"Birds2",
"Birds3",
"Birds4",
"Campfire",
"Crashing Waves",
"Gull1",
"Gull2",
"Gull3"
"Campfire"
],
"valuesAsFlags": false
},
@ -295,28 +269,14 @@
"BOSS_2",
"BOSS_2_B",
"STORY_2_1",
"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"
"STORY_2_2"
],
"valuesAsFlags": false
},
{
"id": 25,
"name": "LevelType",
"storageType": "int",
"storageType": "string",
"type": "enum",
"values": [
"Dungeon",
@ -359,12 +319,6 @@
"drawFill": true,
"id": 19,
"members": [
{
"name": "Audio Event",
"propertyType": "AudioEvent",
"type": "string",
"value": "Default Volume"
},
{
"name": "Backdrop",
"propertyType": "Backdrop",
@ -415,8 +369,8 @@
{
"name": "Level Type",
"propertyType": "LevelType",
"type": "int",
"value": 4
"type": "string",
"value": "World Map"
},
{
"name": "Optimize",

@ -130,7 +130,6 @@
</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>
@ -141,19 +140,11 @@
<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>
@ -227,7 +218,8 @@
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<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>
<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>
<TreatSpecificWarningsAsErrors>4099;5030;4715;4172;4834</TreatSpecificWarningsAsErrors>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
@ -252,7 +244,8 @@
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<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>
<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>
<TreatSpecificWarningsAsErrors>4099;5030;4715;4172;4834</TreatSpecificWarningsAsErrors>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
@ -282,7 +275,8 @@
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<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>
<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>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
@ -374,10 +368,6 @@
<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>
@ -391,10 +381,6 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="BlackHole.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="BombBoom.h">
<SubType>
</SubType>
@ -404,14 +390,6 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="DynamicMenuLabel.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Entity.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="HurtDamageInfo.h">
<SubType>
</SubType>
@ -533,20 +511,12 @@
<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>
@ -557,10 +527,6 @@
</ClInclude>
<ClInclude Include="MonsterData.h" />
<ClInclude Include="olcPGEX_SplashScreen.h" />
<ClInclude Include="Oscillator.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Overlay.h">
<SubType>
</SubType>
@ -619,10 +585,6 @@
<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>
@ -745,15 +707,10 @@
<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" />
@ -772,11 +729,11 @@
<ItemGroup>
<ClCompile Include="Ability.cpp" />
<ClCompile Include="Animation.cpp" />
<ClCompile Include="Arc.cpp">
<ClCompile Include="Arrow.cpp" />
<ClCompile Include="ArtificerDisassembleConfirmWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Arrow.cpp" />
<ClCompile Include="ArtificerDisassembleWindow.cpp">
<SubType>
</SubType>
@ -789,7 +746,7 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ArtificerRefineResultWindow.cpp">
<ClCompile Include="ArtificerRefineConfirmWindow.cpp">
<SubType>
</SubType>
</ClCompile>
@ -838,49 +795,10 @@
<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>
@ -1003,14 +921,6 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Ink.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="InkBullet.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="InputHelper.cpp">
<SubType>
</SubType>
@ -1042,15 +952,7 @@
<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">
@ -1098,15 +1000,10 @@
<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>
@ -1117,39 +1014,15 @@
</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>
@ -1168,7 +1041,6 @@
<ClCompile Include="PulsatingFire.cpp" />
<ClCompile Include="Ranger.cpp" />
<ClCompile Include="RUN_STRATEGY.cpp" />
<ClCompile Include="Sandworm.cpp" />
<ClCompile Include="SaveFile.cpp">
<SubType>
</SubType>
@ -1177,7 +1049,6 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Seagull.cpp" />
<ClCompile Include="SellItemWindow.cpp">
<SubType>
</SubType>
@ -1201,7 +1072,7 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="FallingBullet.cpp">
<ClCompile Include="FallingStone.cpp">
<SubType>
</SubType>
</ClCompile>
@ -1256,10 +1127,6 @@
</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>
@ -1287,23 +1154,7 @@
<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" />
@ -1311,7 +1162,6 @@
<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" />
@ -1362,10 +1212,8 @@
<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" />
@ -1374,7 +1222,6 @@
<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,15 +97,6 @@
<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">
@ -237,6 +228,9 @@
<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>
@ -681,39 +675,6 @@
<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">
@ -1172,7 +1133,7 @@
<ClCompile Include="RockLaunch.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="FallingBullet.cpp">
<ClCompile Include="FallingStone.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="ShineEffect.cpp">
@ -1196,7 +1157,7 @@
<ClCompile Include="PurpleEnergyBall.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="ThrownProjectile.cpp">
<ClCompile Include="PoisonBottle.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="State_Dialog.cpp">
@ -1208,9 +1169,15 @@
<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>
@ -1220,102 +1187,6 @@
<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" />
@ -1328,54 +1199,6 @@
<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">
@ -1565,18 +1388,6 @@
<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,28 +60,22 @@ 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;
INCLUDE_BULLET_LIST
#define CreateBullet(type) \
(*((type*const)BULLET_LIST.emplace_back(std::make_unique<type>(type
#define EndBullet )).get()))
#define CreateBullet(type) INCLUDE_BULLET_LIST \
BULLET_LIST.push_back(std::make_unique<type>(type
#define EndBullet ));
using HurtReturnValue=bool;
using HurtListItem=std::pair<std::variant<Monster*,Player*>,HurtReturnValue>;
using HurtList=std::vector<HurtListItem>;
using HurtList=std::vector<std::pair<std::variant<Monster*,Player*>,HurtReturnValue>>;
using StackCount=uint8_t;
using MarkTime=float;
using ForegroundWrapper=std::reference_wrapper<Effect>;
using BackgroundWrapper=std::reference_wrapper<Effect>;
enum class HurtType{
PLAYER=0b01,
MONSTER=0b10,
};
enum class KnockbackCondition{
KNOCKBACK_HURT_TARGETS, //Knockback only targets that took damage.
@ -101,23 +95,12 @@ class AiL : public olc::PixelGameEngine
friend class sig::Animation;
friend class Audio;
friend class Minimap;
friend class MiniAudio;
friend class Arc;
friend class MonsterTests::MonsterTest;
friend class PlayerTests::PlayerTest;
friend class ItemTests::ItemTest;
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,
@ -168,11 +151,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<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;
@ -187,7 +170,11 @@ 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;
@ -202,7 +189,7 @@ private:
float fadeOutDuration=0;
States::State transitionState=States::State::GAME_RUN;
bool gameEnd=false;
std::vector<std::shared_ptr<Monster>>monstersToBeSpawned;
std::vector<Monster>monstersToBeSpawned;
bool aMonsterIsMarkedForDeletion=false; //DO NOT MODIFY DIRECTLY! Use AMonsterIsMarkedForDeletion() instead!
time_t gameStarted;
std::function<void(std::string_view)>responseCallback;
@ -211,14 +198,13 @@ 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;
@ -244,13 +230,7 @@ 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;
@ -275,10 +255,9 @@ public:
void RenderHud();
void RenderMenu();
bool MenuClicksDeactivated()const;
std::pair<ForegroundWrapper,BackgroundWrapper>AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect>background);
void AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect>background);
//If back is true, places the effect in the background
Effect&AddEffect(std::unique_ptr<Effect>foreground,bool back=false);
const std::vector<Effect*>GetEffect(EffectType type);
void AddEffect(std::unique_ptr<Effect>foreground,bool back=false);
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!
@ -335,7 +314,6 @@ 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();
@ -359,7 +337,7 @@ public:
int GetLoadoutSize()const;
void RestockLoadoutItems();
//Returns true if the item can be used (we have >0 of it)
bool UseLoadoutItem(int slot,const std::optional<vf2d>targetingPos={});
bool UseLoadoutItem(int slot);
//Blanks out this loadout item.
void ClearLoadoutItem(int slot);
void RenderFadeout();
@ -386,7 +364,7 @@ public:
void UpdateMonsters();
void ActivateActionSetForAllControllers(InputActionSetHandle_t actionSetHandle);
const float GetEncounterDuration()const;
void ShowDamageVignetteOverlay(const Pixel="Interface.Vignette Color"_Pixel);
void ShowDamageVignetteOverlay();
void GlobalGameUpdates();
const bool QuitRequested()const;
void SetQuitAllowed(bool quittingAllowed); //Locks the game from quitting during sensitive operations such as file saving/loading.
@ -401,23 +379,13 @@ 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;

@ -48,6 +48,7 @@ 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}});
@ -346,28 +347,6 @@ 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});
@ -392,7 +371,6 @@ 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});
@ -401,9 +379,6 @@ 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});
@ -436,14 +411,6 @@ 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);
@ -451,28 +418,12 @@ 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)){

@ -1,79 +0,0 @@
#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.
}

@ -1,59 +0,0 @@
#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,7 +43,6 @@ 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),
@ -95,7 +94,6 @@ 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;
}
@ -103,22 +101,9 @@ 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);
}
}

@ -36,18 +36,30 @@ All rights reserved.
*/
#pragma endregion
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "MonsterStrategyHelpers.h"
INCLUDE_game
void Monster::STRATEGY::PIRATES_TREASURE(Monster&m,float fElapsedTime,std::string strategy){
const float distToPlayer{util::distance(game->GetPlayer()->GetPos(),m.GetPos())};
if(distToPlayer<=ConfigFloat("Open Distance"))m.PerformAnimation("OPEN");
else m.PerformIdleAnimation();
if(m.B(Attribute::COLLIDED_WITH_PLAYER)&&game->GetPlayer()->HasBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_COIN)){
game->GetPlayer()->RemoveBuff(BuffType::PIRATE_GHOST_CAPTAIN_PRECURSE);
game->GetPlayer()->RemoveBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_COIN);
game->GetPlayer()->RemoveBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_DOT);
void Menu::InitializeArtificerDisassembleConfirmWindow(){
Menu*artificerDisassembleConfirmWindow=CreateMenu(ARTIFICER_DISASSEMBLE_CONFIRM,CENTERED,vi2d{144,144});
artificerDisassembleConfirmWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="";
},
{ //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="",}},
});
}

@ -38,112 +38,15 @@ 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,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();
Menu*artificerDisassembleWindow=CreateMenu(ARTIFICER_DISASSEMBLE,CENTERED,vi2d{144,144});
artificerDisassembleWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
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";
returnData="";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -151,49 +54,12 @@ 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
{"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",}},
}
);
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
}

@ -38,81 +38,28 @@ 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{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};
Menu*artificerEnchantConfirmWindow=CreateMenu(ARTIFICER_ENCHANT_CONFIRM,CENTERED,vi2d{144,144});
artificerEnchantConfirmWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="Take New Button";
returnData="";
},
{ //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
{"Take Old Button",{
.left="Take New Button",
.right="Take New Button",}},
{"Take New Button",{
.left="Take Old Button",
.right="Take Old Button",}},
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
}

@ -38,144 +38,15 @@ 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,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");
Menu*artificerEnchantWindow=CreateMenu(ARTIFICER_ENCHANT,CENTERED,vi2d{144,144});
artificerEnchantWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
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";
returnData="";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -183,52 +54,12 @@ 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
{"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",}},
}
);
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
}

@ -36,20 +36,30 @@ All rights reserved.
*/
#pragma endregion
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "MonsterStrategyHelpers.h"
#include "Entity.h"
using A=Attribute;
INCLUDE_game
void Monster::STRATEGY::PIRATES_COIN(Monster&m,float fElapsedTime,std::string strategy){
if(m.B(Attribute::COLLIDED_WITH_PLAYER)){
game->AddEffect(std::make_unique<CollectCoinEffect>(game->GetPlayer(),ConfigFloat("Coin Collect Rise Amount"),ConfigFloat("Coin Rise Timer"),"coin.png",m.OnUpperLevel()),true);
game->GetPlayer()->AddBuff(BuffType::PIRATE_GHOST_CAPTAIN_CURSE_COIN,INFINITY,1);
m._DealTrueDamage(m.GetHealth(),HurtFlag::NO_DAMAGE_NUMBER);
m.SetLifetime(0.f);
m.SetSize(0.f,false);
void Menu::InitializeArtificerRefineConfirmWindow(){
Menu*artificerRefineConfirmWindow=CreateMenu(ARTIFICER_REFINE_CONFIRM,CENTERED,vi2d{144,144});
artificerRefineConfirmWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="";
},
{ //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="",}},
});
}

@ -1,75 +0,0 @@
#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,128 +39,31 @@ All rights reserved.
#include "Menu.h"
#include "AdventuresInLestoria.h"
#include "RowInventoryScrollableWindowComponent.h"
#include "MenuItemItemButton.h"
#include "MenuRefineLabel.h"
#include "PlayerMoneyLabel.h"
#include "MenuDecal.h"
#include "SoundEffect.h"
#include "DynamicMenuLabel.h"
#include "MenuLabel.h"
INCLUDE_game
void Menu::InitializeArtificerRefineWindow(){
Menu*const artificerRefineWindow{CreateMenu(ARTIFICER_REFINE,CENTERED,game->GetScreenSize()-vi2d{52,52})};
Menu*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>{{},{180.f,12.f}},"Choose Accessory:",1.f,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN)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};
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,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;
},[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();
}
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){
return true;
},
},DO_NOTHING,DO_NOTHING,
InventoryCreator::RowPlayer_InventoryUpdate,
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 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};
InventoryWindowOptions{.padding=1,.size={artificerRefineWindow->size.x/2-5.f,28}})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){
auto backButton=artificerRefineWindow->ADD("Back",MenuComponent)(geom2d::rect<float>{{0.f,artificerRefineWindow->size.y-12.f},{120.f,12.f}},"Back",[](MenuFuncData data){
Menu::CloseMenu();
return true;
})END};
})END;
Menu::AddInventoryListener(inventoryDisplay,"Accessories");
artificerRefineWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
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";
returnData="";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -168,49 +71,12 @@ 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
{"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",}},
}
);
{"Sample Button",{
.up="",
.down="",
.left="",
.right="",}},
});
}

@ -43,26 +43,22 @@ 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("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();
artificerWindow->ADD("Refine Button",MenuComponent)(geom2d::rect<float>{{0.f,4.f},{144.f,24.f}},"Refine",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_REFINE);
return true;
},vf2d{2.f,2.f},ButtonAttr::FIT_TO_LABEL)END;
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();
artificerWindow->ADD("Disassemble Button",MenuComponent)(geom2d::rect<float>{{0.f,32.f},{144.f,24.f}},"Disassemble",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_DISASSEMBLE);
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){
@ -76,7 +72,7 @@ void Menu::InitializeArtificerWindow(){
artificerWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="Disassemble Button";
returnData="Refine Button";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
@ -86,20 +82,20 @@ void Menu::InitializeArtificerWindow(){
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
,{ //Button Navigation Rules
{"Disassemble Button",{
.up="Leave Button",
.down="Refine Button",}},
{"Refine Button",{
.up="Disassemble Button",
.up="Leave Button",
.down="Disassemble Button",}},
{"Disassemble Button",{
.up="Refine Button",
.down="Enchant Button",}},
{"Enchant Button",{
.up="Refine Button",
.up="Disassemble Button",
.down="Help Button",}},
{"Help Button",{
.up="Enchant Button",
.down="Leave Button",}},
{"Leave Button",{
.up="Help Button",
.down="Disassemble Button",}},
.down="Refine Button",}},
});
}

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

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

@ -41,7 +41,6 @@ All rights reserved.
#include "util.h"
#include "LoadingScreen.h"
#include "Menu.h"
#include "SoundEffect.h"
INCLUDE_game
INCLUDE_DATA
@ -71,12 +70,7 @@ void Audio::Initialize(){
while(data.HasProperty(std::format("channel[{}]",channelCounter))){
std::string channelName=data[std::format("channel[{}]",channelCounter)].GetString();
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++;
}
@ -89,9 +83,6 @@ 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){
@ -127,8 +118,8 @@ MiniAudio&Audio::Engine(){
void Audio::Play(const std::string_view sound){
Engine().Play(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);
const size_t Audio::LoadAndPlay(const std::string_view sound,const bool loop){
size_t soundID=Engine().LoadSound(std::string(sound));
Engine().Play(soundID,loop);
return soundID;
};
@ -184,7 +175,6 @@ 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{
@ -333,17 +323,6 @@ 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){
@ -351,7 +330,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 and setup a callback to repeat at looped time.
//Start playing the tracks.
Audio::BGM&track=Self().bgm[Self().GetTrackName()];
for(int trackID:track.GetChannelIDs()){
Engine().Play(trackID,true);
@ -376,16 +355,11 @@ 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);
@ -417,11 +391,8 @@ float Audio::GetCalculatedBGMVolume(const float channelVol){
}
return channelVol*GetBGMVolume()*GetMuteMult()*pauseMult;
}
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::GetCalculatedSFXVolume(const float vol){
return vol*GetSFXVolume()*GetMuteMult();
}
float Audio::GetMuteMult(){
if(muted)return 0.f;
@ -432,7 +403,3 @@ 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,8 +49,6 @@ using ChannelIDList=std::vector<ChannelID>;
using Volume=float;
using VolumeList=std::vector<Volume>;
class SoundEffect;
class Audio{
friend class AiL;
public:
@ -61,7 +59,7 @@ public:
static void UpdateLoop();
static void Play(const std::string_view sound);
[[nodiscard]]
static const size_t LoadAndPlaySFX(const std::string_view sound,const bool loop=true);
static const size_t LoadAndPlay(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.
@ -82,8 +80,7 @@ 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 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.
static float GetCalculatedSFXVolume(const float vol);
private:
bool trackLoadStarted=false;
bool trackLoadComplete=false;
@ -121,15 +118,12 @@ 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();
};
@ -147,7 +141,6 @@ 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(PHASE()){
switch(m.I(A::PHASE)){
case 0:{
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
if(distToPlayer<operator""_Pixels(ConfigFloat("Attack Range"))){
SETPHASE(1);
m.I(A::PHASE)=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){
SETPHASE(2);
m.I(A::PHASE)=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());
SETPHASE(0);
m.I(A::PHASE)=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,11 +63,9 @@ 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()};
float bleedDuration{"Trapper.Ability 2.Marked Target Bleed"_f[1]};
const 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())};

@ -1,59 +0,0 @@
#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,1.85f,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,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(PHASE()){
switch(m.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();
SETPHASE(PhaseName::SCRATCH);
m.phase=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();
SETPHASE(PhaseName::CHARGE);
m.phase=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=[&](){
SETPHASE(PhaseName::RECOVERY);
m.phase=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)SETPHASE(PhaseName::MOVE);
if(m.F(A::CHARGE_COOLDOWN)<=0)m.phase=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,uint8_t(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,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(PHASE()){
switch(m.phase){
case INITIALIZE:{
m.F(A::BREAK_TIME)=ConfigFloat("Break Time");
m.F(A::SHAKE_TIMER)=0.2f;
SETPHASE(RUN);
m.phase=RUN;
m.SetStrategyDeathFunction([&](GameEvent&deathEvent,Monster&m,const std::string&strategy){
m.lifetime=0.01f;

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

@ -50,77 +50,58 @@ 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, //Intensity indicates the stack count (used by the Bloodlust enchant) this buff gives which in turn increases attack.
ADRENALINE_RUSH,
TRAPPER_MARK,
SPECIAL_MARK, //The mark applied by the Opportunity Shot.
OVER_TIME,
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.
ONE_OFF,
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, //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.
ONE_OFF,
OVER_TIME,
OVER_TIME_DURING_CAST,
};
namespace BuffOverTimeType{
enum BuffOverTimeType{
HP_RESTORATION,
HP_PCT_RESTORATION, //Percentage should be the raw percentage, NOT % form! (So 1 for 1%, not 0.01)
HP_PCT_RESTORATION,
MP_RESTORATION,
MP_PCT_RESTORATION, //Percentage should be the raw percentage, NOT % form! (So 1 for 1%, not 0.01)
MP_PCT_RESTORATION,
HP_DAMAGE_OVER_TIME,
HP_PCT_DAMAGE_OVER_TIME, //Percentage should be the raw percentage, NOT % form! (So 1 for 1%, not 0.01)
HP_PCT_DAMAGE_OVER_TIME,
};
};
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,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);
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);
void Update(AiL*game,float fElapsedTime);
private:
bool waitOneTick{true};
bool enabled{true}; //This is only turned off because the ONE_OFF effect. See BuffType::ONE_OFF for more details.
bool enabled{true};
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,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)
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)
: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,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="");
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="");
protected:
virtual void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
virtual void ModifyOutgoingDamageData(HurtDamageInfo&data);
};

@ -39,10 +39,6 @@ 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;
@ -50,7 +46,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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct FireBolt:public Bullet{
@ -59,25 +55,16 @@ 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)override;
private:
std::optional<std::reference_wrapper<TrailEffect>>flameTrail;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
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)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);
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct Arrow:public Bullet{
@ -85,7 +72,6 @@ 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;
@ -94,20 +80,16 @@ 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)override;
void Draw(const Pixel blendCol)const override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
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)override;
private:
std::string laserGraphic;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct FrogTongue:public Bullet{
@ -121,7 +103,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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct Wisp:public Bullet{
@ -129,7 +111,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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
enum class HorizontalFlip{
@ -154,11 +136,11 @@ struct DaggerStab:public Bullet{
float daggerStabDistance;
float knockbackAmt;
DirectionOffsets daggerPositionOffsets;
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);
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);
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;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct DaggerSlash:public Bullet{
@ -167,11 +149,11 @@ struct DaggerSlash:public Bullet{
float frameDuration;
float daggerSlashDistance;
float knockbackAmt;
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);
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);
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;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct Bomb:public Bullet{
@ -188,7 +170,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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void Draw(const Pixel blendCol)const override;
};
@ -214,7 +196,7 @@ struct LevitatingRock:public Bullet{
void Draw(const Pixel blendCol)const override;
void AssignMaster(LevitatingRock*masterRock);
const bool IsMaster()const;
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct Tornado:public Bullet{
@ -229,7 +211,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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct Debris:public Bullet{
@ -240,7 +222,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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void Draw(const Pixel blendCol)const override;
};
@ -253,12 +235,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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct LargeStone:public Bullet{
@ -268,7 +250,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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
private:
const float gravity;
const float fixedTimeStep{1/30.f};
@ -282,13 +264,13 @@ private:
const float knockbackAmt;
};
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);
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);
protected:
void Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override;
void ModifyOutgoingDamageData(HurtDamageInfo&data)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
private:
const vf2d targetPos;
const float zVel{};
@ -296,7 +278,6 @@ 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.
@ -304,7 +285,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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
private:
const vf2d startPos;
const vf2d endPos;
@ -319,7 +300,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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct ExplosiveTrap:public Bullet{
@ -327,7 +308,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)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
private:
void Detonate();
const float explosionRadius{};
@ -335,13 +316,11 @@ 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,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,bool hitsMultiple=false,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()!!
@ -349,115 +328,20 @@ 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 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={});
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});
void Update(float fElapsedTime)override;
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:
void ModifyOutgoingDamageData(HurtDamageInfo&data);
private:
const vf2d targetPos;
const vf2d startingPos;
const float totalFallTime;
float originalRisingTime;
float originalFallingTime;
const float originalRisingTime,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};
};

@ -1,104 +0,0 @@
#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.
}

@ -1,49 +0,0 @@
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},{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);
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);
InputType controlType=KEY;
if(Input::UsingGamepad())controlType=CONTROLLER;

@ -53,7 +53,6 @@ All rights reserved.
#ifndef __EMSCRIPTEN__
#include "steam/isteamuserstats.h"
#endif
#include <bit>
INCLUDE_game
INCLUDE_GFX
@ -67,19 +66,41 @@ 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 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);}},
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);}},
};
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(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.
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.
Inventory::EquipItem(comp.lock()->GetItem(),EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE)));
#pragma region Fully Decked Out Achievement
@ -123,14 +144,13 @@ 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*0.5f)-1,24}},"Character",2,ComponentAttr::SHADOW|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
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("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 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("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("Equip Selection Outline",MenuComponent)(geom2d::rect<float>{{123,28},{120,windowSize.y-37}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END
->Disable();
@ -178,7 +198,6 @@ 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){
if(CanModifyEquipSlots()){
EquipSlot slot=EquipSlot(data.component.lock()->I(Attribute::EQUIP_TYPE));
data.menu.I(A::EQUIP_TYPE)=int(slot);
@ -205,6 +224,28 @@ void Menu::InitializeCharacterMenuWindow(){
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();
}
}
};
if(!data.component.lock()->GetSubcomponentParent().expired())return true;
std::weak_ptr<RowItemDisplay>button=DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component.lock());
if(!button.expired()){
@ -223,7 +264,7 @@ void Menu::InitializeCharacterMenuWindow(){
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.
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");
@ -306,10 +347,6 @@ void Menu::InitializeCharacterMenuWindow(){
}
return true;
}else{
game->AddNotification(AiL::Notification{"Cannot change equipment in a stage!",5.f,YELLOW});
return false;
}
},[](MenuFuncData data){//On Mouse Hover
EquipSlot slot=DYNAMIC_POINTER_CAST<EquipSlotButton>(data.component.lock())->GetSlot();
const std::weak_ptr<Item>equip=Inventory::GetEquip(slot);
@ -377,7 +414,6 @@ void Menu::InitializeCharacterMenuWindow(){
},[](MenuType type){
if(!Menu::menus[type]->GetSelection().expired()&&
Menu::menus[type]->GetSelection().lock()->GetName().starts_with("Equip Slot ")){
if(CanModifyEquipSlots()){
EquipSlot slot=EquipSlot(Menu::menus[type]->GetSelection().lock()->I(Attribute::EQUIP_TYPE));
if(!ISBLANK(Inventory::GetEquip(slot))){
Inventory::UnequipItem(slot);
@ -387,7 +423,6 @@ void Menu::InitializeCharacterMenuWindow(){
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){
@ -402,7 +437,6 @@ void Menu::InitializeCharacterMenuWindow(){
},[](MenuType type){
if(!Menu::menus[type]->GetSelection().expired()&&
Menu::menus[type]->GetSelection().lock()->GetName().starts_with("Equip Slot ")){
if(CanModifyEquipSlots()){
EquipSlot slot=EquipSlot(Menu::menus[type]->GetSelection().lock()->I(Attribute::EQUIP_TYPE));
if(!ISBLANK(Inventory::GetEquip(slot))){
Inventory::UnequipItem(slot);
@ -412,7 +446,6 @@ void Menu::InitializeCharacterMenuWindow(){
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){
@ -482,10 +515,28 @@ void Menu::InitializeCharacterMenuWindow(){
,{ //Button Navigation Rules
{"Equip List",{
.up=[](MenuType type,Data&returnData){
Menu::ScrollUp(type,"Equip List",returnData,"Equip Selection Select Button");
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];
}
}
},
.down=[](MenuType type,Data&returnData){
Menu::ScrollDown(type,"Equip List",returnData,"Equip Selection Select Button");
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];
}
}
},
.left=[](MenuType type,Data&returnData){
auto equipList=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents();

@ -45,23 +45,16 @@ All rights reserved.
INCLUDE_game
ChargedArrow::ChargedArrow(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel 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){}
:lastLaserPos(pos),
Bullet(pos,vel,radius,damage,
"charged_shot_arrow.png",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));
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));
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));
lastLaserPos=pos;
}
}

@ -50,7 +50,6 @@ All rights reserved.
#endif
INCLUDE_game
INCLUDE_DEMO_BUILD
using A=Attribute;
void Menu::InitializeClassSelectionWindow(){
@ -169,12 +168,6 @@ 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);
}

@ -1,57 +0,0 @@
#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;
}
[[nodiscard]]const bool ConnectionPoint::Visited()const{
const bool ConnectionPoint::Visited()const{
return visited;
}

@ -1,10 +0,0 @@
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,1.85f,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,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN|ComponentAttr::FIT_TO_LABEL)END;
moneyDisplay->SetRightAlignment(true);
Player::AddMoneyListener(moneyDisplay);
#pragma endregion

@ -1,118 +0,0 @@
#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()->RemoveMoney(consumedResources.GetCost());
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-consumedResources.GetCost());
Inventory::UpdateBlacksmithInventoryLists();
SoundEffect::PlaySFX("Craft Equip",SoundEffect::CENTERED);

@ -36,7 +36,7 @@ Artificer has 4 Dialog Options
Refining Gear
Enhancing 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,9 +61,6 @@ 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;
@ -75,68 +72,22 @@ 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;}; \
@ -152,11 +103,6 @@ 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,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),
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),
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,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),
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),
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,8 +44,13 @@ 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(GetOriginalRiseSpd()){
pos(pos),damage(damage),friendly(friendly),type(type),invertedDirection(type==INTERRUPT),riseSpd(20.f){
if(type==INTERRUPT||type==MANA_GAIN)riseSpd=40.f;
RecalculateSize();
}
@ -54,35 +59,17 @@ void DamageNumber::RecalculateSize(){
if(!friendly){
float newSize=std::clamp(roundf(damageMultRatio),1.0f,4.0f);
float riseSpd{GetOriginalRiseSpd()};
if(type==HEALTH_LOSS||type==CRIT||type==DOT)riseSpd*=newSize;
if(type==HEALTH_LOSS||type==CRIT)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>colorWhenAppliedToMonster,std::pair<Pixel,Pixel>colorWhenAppliedToPlayer,vf2d scaling={1.f,1.f}){
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}){
vf2d textSize=game->GetTextSizeProp(text)*scaling;
if(!friendly){
vf2d additionalScaling={1.f,1.f};
@ -94,13 +81,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,colorWhenAppliedToMonster.first,colorWhenAppliedToMonster.second,scaling*additionalScaling);
game->view.DrawShadowStringPropDecal(drawPos,text,colorsEnemy.first,colorsEnemy.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,colorWhenAppliedToPlayer.first,colorWhenAppliedToPlayer.second,scaling);
game->view.DrawShadowStringPropDecal(drawPos,text,colorsFriendly.first,colorsFriendly.second,scaling);
}
};
@ -127,54 +114,8 @@ void DamageNumber::Draw(){
}break;
case DOT:{
std::string text=std::to_string(damage);
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});
DrawDamageNumber(NormalNumber,text,{0xE1BEE7,0x1F083A},{0xDCE775,0x37320A});
}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,32 +46,23 @@ 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;
float GetOriginalRiseSpd();
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 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,float(util::lerp(255,0,(GetAliveTime()-fadeTimeBegins)/afterImagesLingeringTime)));
if(GetAliveTime()>fadeTimeBegins)alpha=std::max(0.f,util::lerp(255,0,(GetAliveTime()-fadeTimeBegins)/afterImagesLingeringTime));
const Animate2D::FrameSequence&animation{ANIMATION_DATA[this->animation]};
const float animationFrameTimer{i*animation.m_fFrameDuration};

@ -1,57 +0,0 @@
#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,23 +42,18 @@ All rights reserved.
INCLUDE_ANIMATION_DATA
INCLUDE_game
INCLUDE_GFX
//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,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,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){}
//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){}
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,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){
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){
this->animation.AddState(imgFile,ANIMATION_DATA.at(imgFile));
this->animation.ChangeState(internal_animState,imgFile);
}
@ -84,36 +79,22 @@ 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){
blendCol={col.r,col.g,col.b,uint8_t(fadein/original_fadeInTime*col.a)};
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)});
}else
[[likely]]if(HasFadeout){
blendCol={col.r,col.g,col.b,uint8_t(fadeout/original_fadeOutTime*col.a)};
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);
}
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);
}
@ -125,15 +106,3 @@ 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,11 +37,8 @@ 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*>>;
@ -49,14 +46,11 @@ 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 FallingBullet;
friend struct FallingStone;
vf2d pos={0,0};
float lifetime=0;
float fadeout=0;
@ -68,66 +62,43 @@ struct Effect{
float rotationSpd=0;
vf2d scaleSpd{};
bool additiveBlending=false;
float z{};
private:
bool dead=false;
public:
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);
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);
virtual bool Update(float fElapsedTime);
Animate2D::Frame GetFrame()const;
void _Draw()const;
virtual void Draw(const Pixel blendCol)const;
virtual void Draw()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{
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);
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);
float startLifetime=0;
bool shakeField=false;
bool Update(float fElapsedTime)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{};
void Draw()const override;
};
struct PulsatingFire:Effect{
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);
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);
std::vector<float>pulsatingFireValues;
float lastParticleTimer=0;
float lastDamageTimer=0;
float radius;
bool Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override;
void Draw()const override;
};
struct SwordSlash:Effect{
@ -143,7 +114,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 Pixel blendCol)const override final;
virtual void Draw()const override final;
};
struct SpellCircle:Effect{
@ -151,7 +122,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 Pixel blendCol)const override final;
virtual void Draw()const override final;
};
struct RockLaunch:Effect{
@ -171,78 +142,3 @@ 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,game->GetPlayer()->HasEnchant("Piercing Bolt")?true:false,INFINITE,true,friendly,col){}
"energy_bolt.png",upperLevel,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.Range"_F&&IsActivated()){
if(distanceTraveled>"Wizard.Auto Attack.Max Range"_F&&IsActivated()){
fadeOutTime="Wizard.Auto Attack.BulletHitFadeoutTime"_F;
}
}
@ -67,9 +67,8 @@ BulletDestroyState EnergyBolt::PlayerHit(Player*player)
BulletDestroyState EnergyBolt::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)
{
if(!game->GetPlayer()->HasEnchant("Piercing Bolt"))fadeOutTime="Wizard.Auto Attack.BulletHitFadeoutTime"_F;
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,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.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);
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,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x);
WHITE,BLACK,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x,1.0f);
}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,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x);
WHITE,BLACK,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x,1.0f);
}
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,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x);
window.DrawShadowStringPropDecal(rect.middle()-vf2d{textWidth,0}/2,text,RED,BLACK,adjustedScale,fitToLabel?std::numeric_limits<float>::max():rect.size.x,1.0f);
}
}
};

@ -1,60 +0,0 @@
#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!");
}

@ -1,56 +0,0 @@
#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,8 +67,7 @@ void EnvironmentalAudio::SetAudioName(const std::string_view audioName){
}
void EnvironmentalAudio::Activate(){
if(activated)return;
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));
soundInstance=Audio::LoadAndPlay(operator""_SFX(SOUND_DATA[audioName].file.c_str(),SOUND_DATA[audioName].file.length()),true);
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()->Icon().Decal());
icon=const_cast<Decal*>(equip.lock()->Decal());
itemRef=equip;
}else{
icon=nullptr;

@ -43,9 +43,6 @@ All rights reserved.
#include <memory>
#include <source_location>
#include <fstream>
#ifndef __EMSCRIPTEN__
#include <stacktrace>
#endif
inline std::ofstream debugLogger;
@ -83,9 +80,7 @@ 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()};
}
};
@ -93,16 +88,16 @@ inline std::ofstream debugLogger;
#define ERR(err) { \
std::stringstream errStream; \
errStream<<err; \
Error::log(errStream);}
Error::log(errStream,std::source_location::current());}
#define LOG(err) { \
std::stringstream errStream; \
errStream<<err; \
Error::log(errStream);}
Error::log(errStream,std::source_location::current());}
class Error{
public:
inline static void log(std::stringstream&str){
std::cout<<__FILE__<<"("<<__LINE__<<":) "<<str.str()<<std::endl;
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;
#ifdef __DEBUG__
throw;
#endif
@ -120,15 +115,15 @@ type DYNAMIC_CAST(auto variable){
}
template<typename T,typename U>
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);
std::shared_ptr<T>DYNAMIC_POINTER_CAST(const std::shared_ptr<U>&variable){
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>
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());
std::shared_ptr<T>DYNAMIC_POINTER_CAST(const std::weak_ptr<U>&variable){
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),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"){
: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"){
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,10 +58,7 @@ void ExplosiveTrap::Update(float fElapsedTime){
if(IsDeactivated())return;
if(rearmTime>0.f){
rearmTime-=fElapsedTime;
if(rearmTime<=0.f)Detonate();
}else if(!trapActivated){
if(!trapActivated){
activationWaitTime-=fElapsedTime;
if(activationWaitTime<=0.f){
radius=activationRadius;
@ -92,14 +89,13 @@ BulletDestroyState ExplosiveTrap::PlayerHit(Player*player){
}
void ExplosiveTrap::Detonate(){
const HurtList list{game->Hurt(pos,"Trapper.Ability 3.Explosion Radius"_F/100.f*24,damage,OnUpperLevel(),GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)};
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)};
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);
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);
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);
}
}
@ -107,13 +103,7 @@ void ExplosiveTrap::Detonate(){
explodeEffect->scaleSpd={0.125f,0.125f};
game->AddEffect(std::move(explodeEffect));
SoundEffect::PlaySFX("Explosion",pos);
explosionCount--;
if(explosionCount>0)rearmTime=0.6f;
else{
fadeOutTime=0.5f;
Deactivate();
lifetime=0.f;
}
}
BulletDestroyState ExplosiveTrap::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
@ -121,6 +111,9 @@ 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;

@ -1,65 +0,0 @@
#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
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){
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){
pos+=-vel*lifetime;
z=-zVel*lifetime;
}
void FallingBullet::Update(float fElapsedTime){
void FallingStone::Update(float fElapsedTime){
z+=zVel*fElapsedTime;
lastTrailEffect-=fElapsedTime;
if(z<=0.f){
@ -62,15 +62,16 @@ void FallingBullet::Update(float fElapsedTime){
fadeOutTime=0.5f;
SoundEffect::PlaySFX("Stone Land",pos);
if(friendly){
for(auto&[monsterPtr,hurt]:game->Hurt(pos,collisionRadius,damage,OnUpperLevel(),z,HurtType::MONSTER)){
for(auto&[monsterPtr,hurt]:game->Hurt(targetPos,radius,damage,OnUpperLevel(),z,HurtType::MONSTER)){
if(hurt)std::get<Monster*>(monsterPtr)->ApplyIframes(0.1f);
}
}else{
for(auto&[playerPtr,hurt]:game->Hurt(pos,collisionRadius,damage,OnUpperLevel(),z,HurtType::PLAYER)){
}
else{
for(auto&[playerPtr,hurt]:game->Hurt(targetPos,radius,damage,OnUpperLevel(),z,HurtType::PLAYER)){
if(hurt)std::get<Player*>(playerPtr)->ApplyIframes(0.1f);
}
}
game->ProximityKnockback(pos,collisionRadius,knockbackAmt,HurtType::PLAYER|HurtType::MONSTER);
game->ProximityKnockback(targetPos,radius,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();
}
@ -81,11 +82,11 @@ void FallingBullet::Update(float fElapsedTime){
}
indicator.Update(fElapsedTime);
}
void FallingBullet::Draw(const Pixel blendCol)const{
void FallingStone::Draw(const Pixel blendCol)const{
if(lifetime<=indicatorDisplayTime){
indicator._Draw();
indicator.Draw();
}
Bullet::Draw(blendCol);
}
void FallingBullet::ModifyOutgoingDamageData(HurtDamageInfo&data){}
void FallingStone::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -41,16 +41,13 @@ 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){
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));
}
"energy_bolt.png",upperLevel,false,INFINITE,true,friendly,col){}
void FireBolt::Update(float fElapsedTime){
lastParticleSpawn=std::max(0.f,lastParticleSpawn-fElapsedTime);
@ -75,8 +72,6 @@ void FireBolt::Update(float fElapsedTime){
SoundEffect::PlaySFX("Wizard Fire Bolt Hit",pos);
}
if(flameTrail)flameTrail.value().get().SetEndPos(pos);
}
BulletDestroyState FireBolt::PlayerHit(Player*player)

@ -1,58 +0,0 @@
#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,6 +47,12 @@ 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 Pixel blendCol)const{
game->DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,blendCol);
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);
}

@ -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(PHASE()){
switch(m.I(A::PHASE)){
case 0:{ //Move towards phase.
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
if(distToPlayer<24*ConfigInt("Range")/100.f){
SETPHASE(PHASE()+1);
m.I(A::PHASE)++;
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();
SETPHASE(2);
m.I(A::PHASE)=2;
}
m.PerformIdleAnimation();
}break;
case 2:{
if(m.F(A::LOCKON_WAITTIME)==0.0f){
m.F(A::LOCKON_WAITTIME)=ConfigFloat("Attack Recovery Time");
SETPHASE(3);
m.I(A::PHASE)=3;
}
}break;
case 3:{
if(m.F(A::LOCKON_WAITTIME)==0.0f){
SETPHASE(0);
m.I(A::PHASE)=0;
}
}break;
default:{
ERR(std::format("Unhandled phase {} for {} strategy!",PHASE(),strategy));
ERR(std::format("Unhandled phase {} for {} strategy!",m.I(A::PHASE),strategy));
}
}
}

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

@ -1,298 +0,0 @@
#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;
}
}

@ -1,84 +0,0 @@
#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){}

@ -1,84 +0,0 @@
#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;
}
}

@ -1,179 +0,0 @@
#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,16 +52,17 @@ 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,animation);
if(firstAnimation)m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),animation);
firstAnimation=false;
}
m.mountedSprOffset=ConfigVec("Imposed Monster Offset");
m.deathData.emplace_back(ConfigString("Spawned Monster"),1U);
m.deathData.push_back(DeathSpawnInfo{ConfigString("Spawned Monster"),1U});
}
BOAR(m,fElapsedTime,"Boar");
@ -78,11 +79,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,std::format("GOBLIN_BOW_MOUNTED_{}",int(m.GetFacingDirection())));
m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),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,std::format("GOBLIN_BOW_ATTACK_{}",int(m.GetFacingDirection())));
m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),std::format("GOBLIN_BOW_ATTACK_{}",int(m.GetFacingDirection())));
}
}

@ -60,10 +60,10 @@ void Monster::STRATEGY::GOBLIN_BOMB(Monster&m,float fElapsedTime,std::string str
RUN,
};
switch(PHASE()){
switch(m.phase){
case INITIALIZE:{
m.F(A::SHOOT_TIMER)=m.randomFrameOffset;
SETPHASE(RUN);
m.phase=RUN;
}break;
case RUN:{
m.F(A::SHOOT_TIMER)+=fElapsedTime;

@ -66,10 +66,10 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
};
#pragma endregion
switch(PHASE()){
switch(m.phase){
case INITIALIZE_PERCEPTION:{
m.F(A::PERCEPTION_LEVEL)=ConfigFloat("Starting Perception Level");
SETPHASE(MOVE);
m.phase=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=[&](){
SETPHASE(LOCKON);
m.phase=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));
SETPHASE(WINDUP);
m.phase=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()));
SETPHASE(MOVE);
m.phase=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(PHASE()){
switch(m.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{
SETPHASE(WINDUP);
m.phase=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){
SETPHASE(RECOVERY);
m.phase=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,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"),
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"),
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,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;
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;
}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)SETPHASE(MOVE);
if(m.F(A::RECOVERY_TIME)<=0)m.phase=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(PHASE()){
switch(m.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));
SETPHASE(FLYING);
m.phase=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");
SETPHASE(PREPARE_CHARGE);
m.phase=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){
SETPHASE(CHARGE);
m.phase=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);
SETPHASE(FLYING);
m.phase=FLYING;
m.F(A::ATTACK_COOLDOWN)=ConfigFloat("Flight Charge Cooldown");
m.PerformJumpAnimation();
}

@ -1,54 +0,0 @@
#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){}

@ -1,130 +0,0 @@
#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,10 +39,9 @@ All rights reserved.
namespace HurtFlag{
enum HurtFlag{
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.
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.
};
}

@ -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,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::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)
: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 auto CollisionCheckRequired=[this](){return IsActivated()&&fadeInTimer==fadeInTime&&radius!=0.f;};
if(CollisionCheckRequired()){
const bool CollisionCheckRequired=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,12 +106,13 @@ 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);
if(!CollisionCheckRequired())return false;
hitList.insert(&*m);
}
}
}
@ -123,12 +124,13 @@ void IBullet::_Update(const float fElapsedTime){
//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());
if(!CollisionCheckRequired())return false;
hitList.insert(game->GetPlayer());
}
}
}
@ -139,7 +141,6 @@ 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;
@ -151,7 +152,6 @@ 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,13 +168,11 @@ 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,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)));
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)));
if(GetZ()>0){
vf2d shadowScale=vf2d{8*scale.x/3.f,1}/std::max(1.f,GetZ()/8);
@ -188,7 +186,6 @@ 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{
@ -277,10 +274,6 @@ void IBullet::Deactivate(){
deactivated=true;
}
void IBullet::Activate(){
deactivated=false;
}
const double IBullet::GetTimeAlive()const{
return aliveTime;
}

@ -42,11 +42,6 @@ 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,
@ -60,7 +55,6 @@ enum class BulletDestroyState{
struct IBullet{
friend class AiL;
float acc{};
vf2d vel;
vf2d pos;
float radius;
@ -82,8 +76,6 @@ 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;
@ -112,7 +104,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,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="");
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="");
public:
void SimulateUpdate(const float fElapsedTime);

@ -49,14 +49,9 @@ 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,9 +49,7 @@ 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);

@ -1,52 +0,0 @@
#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);
}

@ -1,66 +0,0 @@
#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){}

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

Loading…
Cancel
Save