Compare commits

...

517 Commits

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

30
.gitignore vendored

@ -11,6 +11,7 @@
*.sln.docstates
emsdk
emscripten
release
commercial_assets
*.o
*.h.gch
@ -396,3 +397,32 @@ build/CMakeFiles/3.16.3/CompilerIdC/CMakeCCompilerId.c
build/CMakeFiles/3.16.3/CompilerIdCXX/CMakeCXXCompilerId.cpp
test.cpp
/Adventures in Lestoria/Adventures in Lestoria
/x64/Release/Adventures in Lestoria.zip
/x64/Release/Adventures in Lestoria_web.zip
/x64/Release/AdventuresInLestoria_web.zip
packkey.cpp
desktop.ini
.tmp.driveupload
/Adventures in Lestoria Tests/Adventures in Lestoria.lib
/Adventures in Lestoria Tests/discord-files
/Adventures in Lestoria Tests/discord_game_sdk.dll
/Adventures in Lestoria Tests/discord_game_sdk.dll.lib
/Adventures in Lestoria Tests/freetype
/Adventures in Lestoria Tests/freetype.dll
/Adventures in Lestoria Tests/freetype.lib
/Adventures in Lestoria Tests/ft2build.h
/Adventures in Lestoria Tests/pch.h
/Adventures in Lestoria Tests/steam
/Adventures in Lestoria Tests/steam_api64.dll
/Adventures in Lestoria Tests/steam_api64.lib
/Adventures in Lestoria Tests/x64/Library (Debug)
/x64/Library (Debug)
/x64/Library
/x64/Adventures in Lestoria.idb
/x64/Adventures in Lestoria.lib
/Adventures in Lestoria Tests/x64/Library (Release)
/x64/Unit Testing/assets
/Adventures in Lestoria Tests/x64/Unit Testing
/x64/Unit Testing
/Adventures in Lestoria/assets/2.10
/Adventures in Lestoria/assets/items/Bird_s Treasure.png

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Unit Testing|Win32">
<Configuration>Unit Testing</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Unit Testing|x64">
<Configuration>Unit Testing</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<ProjectGuid>{11969B7B-3D50-4825-9584-AF01D15B88E0}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>AdventuresinLestoriaTests</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectSubType>NativeUnitTestProject</ProjectSubType>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<UseOfMfc>false</UseOfMfc>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<UseOfMfc>false</UseOfMfc>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File ../unit-testing-prebuild.ps1</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<UseFullPaths>true</UseFullPaths>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\Adventures in Lestoria\discord-files\achievement_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\activity_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\application_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\core.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\image_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\lobby_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\network_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\overlay_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\relationship_manager.cpp" />
<ClCompile Include="..\Adventures in Lestoria\discord-files\storage_manager.cpp" />
<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="GeometryTests.cpp" />
<ClCompile Include="ItemTests.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="MonsterTests.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="PlayerTests.cpp">
<SubType>
</SubType>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Adventures in Lestoria\Adventures in Lestoria.vcxproj">
<Project>{8e3067af-cfe7-4b11-bc6b-b867c32753d7}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Source Files\discord-files">
<UniqueIdentifier>{a858da34-e037-467f-9f95-8066221a9657}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="GeometryTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MonsterTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\achievement_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\activity_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\application_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\core.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\image_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\lobby_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\network_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\overlay_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\relationship_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\storage_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\store_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\user_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="..\Adventures in Lestoria\discord-files\voice_manager.cpp">
<Filter>Source Files\discord-files</Filter>
</ClCompile>
<ClCompile Include="PlayerTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ItemTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

@ -0,0 +1,55 @@
#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 "olcUTIL_Geometry2D.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
namespace GeometryTests
{
TEST_CLASS(GeometryTest)
{
public:
TEST_METHOD(CircleOverlapTest)
{
Assert::IsTrue(geom2d::overlaps(geom2d::circle<float>{vf2d{},10},vf2d{5,5}));
}
};
}

@ -0,0 +1,230 @@
#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"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
INCLUDE_GFX
INCLUDE_ITEM_DATA
extern std::mt19937 rng;
namespace ItemTests
{
TEST_CLASS(ItemTest)
{
public:
std::unique_ptr<AiL>testGame;
InputGroup testKeyboardInput;
Player*player;
HWButton*testKey;
TEST_METHOD_INITIALIZE(ItemInitialize){
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::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
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,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(CleanupTests){
testGame->EndGame();
}
TEST_METHOD(ItemGiveTest){
Inventory::AddItem("Health Potion"s,3);
Assert::AreEqual(3U,Inventory::GetItemCount("Health Potion"s),L"Player has 3 Health Potions.");
}
TEST_METHOD(ItemRemoveTest){
Inventory::AddItem("Health Potion"s,3);
for(std::weak_ptr<Item>&item:Inventory::GetItem("Health Potion"s))Inventory::RemoveItem(item,3);
Assert::AreEqual(0U,Inventory::GetItemCount("Health Potion"s),L"Player has no Health Potions.");
}
TEST_METHOD(ItemQuantityStackTest){
Inventory::AddItem("Health Potion"s,3);
Inventory::AddItem("Health Potion"s,3);
Assert::AreEqual(6U,Inventory::GetItemCount("Health Potion"s),L"Player has 6 Health Potions.");
}
TEST_METHOD(EquipmentNoStackTest){
Inventory::AddItem("Ring of the Slime King"s);
Inventory::AddItem("Ring of the Slime King"s);
Inventory::AddItem("Ring of the Slime King"s);
int itemCounter{};
for(std::weak_ptr<Item>&item:Inventory::GetItem("Ring of the Slime King"s))itemCounter++;
Assert::AreEqual(3,itemCounter,L"3 separate item entries for Ring of the Slime King.");
}
TEST_METHOD(UsingBlankLoadoutItemDoesNothing){
Assert::IsFalse(game->UseLoadoutItem(0),L"Using a blank loadout item slot (0) does not produce a result.");
Assert::IsFalse(game->UseLoadoutItem(1),L"Using a blank loadout item slot (1) does not produce a result.");
Assert::IsFalse(game->UseLoadoutItem(2),L"Using a blank loadout item slot (2) does not produce a result.");
}
TEST_METHOD(UsingLoadoutItemOfQuantityZeroDoesNothing){
Assert::ExpectException<std::exception>([](){game->SetLoadoutItem(0,"Minor Health Potion");},L"Applying an item that doesn't exist to a loadout slot should not be allowed.");
}
TEST_METHOD(UsingLoadoutItemConsumesIt){
player->Hurt(1,player->OnUpperLevel(),player->GetZ());
Inventory::AddItem("Minor Health Potion"s,5U);
game->SetLoadoutItem(0,"Minor Health Potion");
testKey->bHeld=true; //Simulate key being pressed.
player->CheckAndPerformAbility(player->useItem1,testKeyboardInput);
Assert::AreEqual(1,Inventory::loadoutItemsUsed[0].second,L"1 Health potion considered used in loadout inventory.");
Assert::AreEqual(4U,Inventory::GetItemCount("Minor Health Potion"s),L"4 Health potions remain in player's inventory.");
Assert::AreEqual(player->useItem1.GetCooldownTime(),player->useItem1.cooldown,L"Item 1 is now on cooldown.");
}
TEST_METHOD(ItemScriptBuffTest){
player->Hurt(1,player->OnUpperLevel(),player->GetZ());
Inventory::AddItem("Stat Up Everything Potion"s,5U);
game->SetLoadoutItem(0,"Stat Up Everything Potion");
testKey->bHeld=true; //Simulate key being pressed.
Assert::ExpectException<std::exception>([&](){player->CheckAndPerformAbility(player->useItem1,testKeyboardInput);},L"If all buffs are properly applied, then some of these stat up buffs are illegal and will catch an exception.");
}
TEST_METHOD(FlatRestoreScriptTest){
player->Hurt(75,player->OnUpperLevel(),player->GetZ());
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->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.");
Assert::AreEqual(75,player->GetMana(),L"Player Mana is 75 after using Flat Recovery Potion.");
}
TEST_METHOD(PctRestoreScriptTest){
player->Hurt(75,player->OnUpperLevel(),player->GetZ());
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->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.");
Assert::AreEqual(75,player->GetMana(),L"Player Mana is 75 after using Pct Recovery Potion.");
}
TEST_METHOD(HealOverTimeTest){
player->Hurt(75,player->OnUpperLevel(),player->GetZ());
Inventory::AddItem("Bandages"s,5U);
game->SetLoadoutItem(2,"Bandages");
testKey->bHeld=true; //Simulate key being pressed.
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.");
game->SetElapsedTime(1.f);
game->OnUserUpdate(1.f);
for(int seconds{1};seconds<=6;seconds++){
Assert::AreEqual(30+seconds*5,player->GetHealth(),L"Player is healed again for 5 health points.");
game->OnUserUpdate(1.f);
}
Assert::AreEqual(60,player->GetHealth(),L"Player should not be healed now that the Bandages effect is over.");
}
TEST_METHOD(DisassembleAccessoryTest){
Inventory::AddItem("Ring of the Slime King"s);
std::weak_ptr<Item>disassembleRingTest{Inventory::AddItem("Ring of the Slime King"s)};
Inventory::AddItem("Ring of the Slime King"s);
Inventory::Disassemble(disassembleRingTest);
Assert::AreEqual(2U,Inventory::GetItemCount("Ring of the Slime King"s),L"Disassembly has removed one of the Slime King rings from our inventory.");
Assert::IsTrue(disassembleRingTest.expired(),L"Original reference to disassembled ring should now be invalid.");
Assert::AreEqual(1U,Inventory::GetItemCount(ITEM_DATA["Ring of the Slime King"].FragmentName()),L"Disassembly has given us a Slime King Ring Fragment.");
}
TEST_METHOD(DisassembleNonAccessoryTest){
Inventory::AddItem("Ring of the Slime King"s);
try{
Inventory::Disassemble(Inventory::AddItem("Test Armor"s));
Assert::Fail(L"Disassembling Test Armor succeeded! This should NOT be allowed!");
}catch(std::runtime_error&e){}
try{
Inventory::Disassemble(Inventory::AddItem("Green Slime Remains"s));
Assert::Fail(L"Disassembling Green Slime Remains succeeded! This should NOT be allowed!");
}catch(std::runtime_error&e){}
try{
Inventory::Disassemble(Inventory::AddItem("Health Potion"s));
Assert::Fail(L"Disassembling a Health Potion succeeded! This should NOT be allowed!");
}catch(std::runtime_error&e){}
}
TEST_METHOD(RefiningTest){
std::weak_ptr<Item>slimeKingRing{Inventory::AddItem("Ring of the Slime King"s)};
Assert::IsFalse(slimeKingRing.lock()->CanBeRefined(),L"Ring of the Slime King should not be allowed to be refined since we have no fragments.");
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);
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.");
player->SetMoney(10000);
while(slimeKingRing.lock()->CanBeRefined()){
RefineResult result{slimeKingRing.lock()->Refine()};
LOG(std::format("Enhanced {} by {}",result.first.Name(),result.second));
}
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.");
}
}
};
}

@ -0,0 +1,460 @@
#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"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
INCLUDE_MONSTER_DATA
INCLUDE_game
INCLUDE_GFX
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_MONSTER_LIST
TEST_MODULE_INITIALIZE(AiLTestSuite)
{
debugLogger.open("debug.log");
}
namespace MonsterTests
{
TEST_CLASS(MonsterTest)
{
public:
std::unique_ptr<AiL>testGame;
#pragma region Setup Functions
//Makes MONSTER_DATA["TestName"] available.
void SetupTestMonster(){
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
game->MAP_DATA["CAMPAIGN_1_1"];
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["UpperZone"];
game->MAP_DATA["CAMPAIGN_1_1"].ZoneData["LowerZone"];
game->currentLevel="CAMPAIGN_1_1";
ItemDrop::drops.clear();
#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::drops.clear();
}
#pragma endregion
TEST_METHOD_INITIALIZE(MonsterInitialize){
SetupTestMonster();
SetupMockMap();
}
~MonsterTest(){
testGame.release();
}
TEST_METHOD(DisplayNameCheck){
Assert::AreEqual("Test Monster",MONSTER_DATA["TestName"].GetDisplayName().c_str());
}
TEST_METHOD(InternalNameCheck){
Monster testMonster{{},MONSTER_DATA["TestName"]};
Assert::AreEqual("TestName",testMonster.GetName().c_str());
}
TEST_METHOD(HealthCheck){
Monster testMonster{{},MONSTER_DATA["TestName"]};
Assert::AreEqual(testMonster.GetHealth(),testMonster.GetMaxHealth());
Assert::AreEqual(testMonster.GetHealth(),30);
}
TEST_METHOD(Deal5Damage)
{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ());
Assert::AreEqual(testMonster.GetHealth(),testMonster.GetMaxHealth()-5);
}
TEST_METHOD(IFrameShouldResultInNoDamage){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.ApplyIframes(0.5f);
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ());
Assert::AreEqual(testMonster.GetHealth(),testMonster.GetMaxHealth());
}
TEST_METHOD(BeingInTheAirShouldAvoidAttacksFromTheGround){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.SetZ(2.f);
testMonster.Hurt(5,testMonster.OnUpperLevel(),0.f);
Assert::AreEqual(testMonster.GetHealth(),testMonster.GetMaxHealth());
}
TEST_METHOD(MonstersDeal10Damage_NoDamageReduction)
{
Monster testMonster{{},MONSTER_DATA["TestName"]};
Monster testMonster2{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
testMonster2.Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
Assert::AreEqual(game->GetPlayer()->GetMaxHealth()-10,game->GetPlayer()->GetHealth());
Assert::AreEqual(testMonster2.GetMaxHealth()-10,testMonster2.GetHealth());
}
TEST_METHOD(DoubleAttackPctModifierWorks){
Monster buffMonster{{},MONSTER_DATA["TestName"]};
buffMonster.AddBuff(BuffType::STAT_UP,5,100._Pct,{ItemAttribute::Get("Attack %")});
Monster testMonster2{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
testMonster2.Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
Assert::AreEqual(game->GetPlayer()->GetMaxHealth()-20,game->GetPlayer()->GetHealth());
Assert::AreEqual(testMonster2.GetMaxHealth()-20,testMonster2.GetHealth());
}
TEST_METHOD(AttackUpModifierWorks){
Monster buffMonster{{},MONSTER_DATA["TestName"]};
buffMonster.AddBuff(BuffType::STAT_UP,5,5.f,{"Attack"});
Monster testMonster2{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
testMonster2.Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
Assert::AreEqual(game->GetPlayer()->GetMaxHealth()-15,game->GetPlayer()->GetHealth(),L"Player Health is now 85.");
Assert::AreEqual(testMonster2.GetMaxHealth()-15,testMonster2.GetHealth(),L"Monster Health is now 15.");
}
TEST_METHOD(HealthUpModifierWorks){
Monster buffMonster{{},MONSTER_DATA["TestName"]};
buffMonster.AddBuff(BuffType::STAT_UP,5,5.f,{"Health"});
Assert::AreEqual(35,buffMonster.GetMaxHealth(),L"Monster Max Health is now 35.");
Assert::AreEqual(30,buffMonster.GetHealth(),L"Monster Current Health is still 30.");
}
TEST_METHOD(AttackUpPctModifierWorks){
Monster buffMonster{{},MONSTER_DATA["TestName"]};
buffMonster.AddBuff(BuffType::STAT_UP,5,100.0_Pct,{"Attack %"});
Monster testMonster2{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
testMonster2.Hurt(buffMonster.GetAttack(),buffMonster.OnUpperLevel(),buffMonster.GetZ());
Assert::AreEqual(game->GetPlayer()->GetMaxHealth()-20,game->GetPlayer()->GetHealth(),L"Player Health is now 80.");
Assert::AreEqual(testMonster2.GetMaxHealth()-20,testMonster2.GetHealth(),L"Monster Health is now 10.");
}
TEST_METHOD(MonsterIsConsideredDeadAt0Health){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.Hurt(testMonster.GetMaxHealth(),testMonster.OnUpperLevel(),testMonster.GetZ());
Assert::AreEqual(true,testMonster.IsDead());
}
TEST_METHOD(ItemDropSpawnsWhenMonsterIsKilled){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.Hurt(testMonster.GetMaxHealth(),testMonster.OnUpperLevel(),testMonster.GetZ());
Assert::AreEqual(size_t(1),ItemDrop::GetDrops().size());
}
TEST_METHOD(MoveSpdSetCorrectly){
Monster testMonster{{},MONSTER_DATA["TestName"]};
Assert::AreEqual(2.f,testMonster.GetMoveSpdMult());
}
TEST_METHOD(SlowdownBuffTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::SLOWDOWN,5.f,0.5f);
Assert::AreEqual(1.f,testMonster.GetMoveSpdMult());
}
TEST_METHOD(SelfInflictedSlowdownTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::SELF_INFLICTED_SLOWDOWN,5.f,0.5f);
Assert::AreEqual(1.f,testMonster.GetMoveSpdMult());
}
TEST_METHOD(SpeedBoostTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::SPEEDBOOST,5.f,0.5f);
Assert::AreEqual(3.f,testMonster.GetMoveSpdMult());
}
TEST_METHOD(LockOnSpeedBoostTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::LOCKON_SPEEDBOOST,5.f,0.5f);
Assert::AreEqual(3.f,testMonster.GetMoveSpdMult());
}
TEST_METHOD(AdditiveMoveSpdBuffsTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::SLOWDOWN,5.f,0.5f);
testMonster.AddBuff(BuffType::SPEEDBOOST,5.f,0.75f);
Assert::AreEqual(2.5f,testMonster.GetMoveSpdMult());
}
TEST_METHOD(DamageReductionBuffTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::DAMAGE_REDUCTION,5.f,0.25f);
testMonster.Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
//25% damage reduction should result in 7.5 health taken, When ceiling'd results in 8 health taken.
Assert::AreEqual(22,testMonster.GetHealth());
}
TEST_METHOD(BarrierBuffTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::BARRIER_DAMAGE_REDUCTION,5.f,0.5f);
testMonster.Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
//50% damage reduction should result in 5 health taken
Assert::AreEqual(25,testMonster.GetHealth());
}
TEST_METHOD(AdditiveDamageReductionTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::DAMAGE_REDUCTION,5.f,0.25f);
testMonster.AddBuff(BuffType::BARRIER_DAMAGE_REDUCTION,5.f,0.5f);
testMonster.Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
//25+50% damage reduction should result in 2.5 health taken. When ceiling'd, results in 3 health taken.
Assert::AreEqual(27,testMonster.GetHealth());
}
TEST_METHOD(MaximumDamageReductionTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::DAMAGE_REDUCTION,5.f,INFINITY);
testMonster.Hurt(testMonster.GetAttack(),testMonster.OnUpperLevel(),testMonster.GetZ());
//At 100% or more damage reduction, the monster should take 0 damage.
Assert::AreEqual(30,testMonster.GetHealth());
}
TEST_METHOD(TrueDamageTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster._DealTrueDamage(testMonster.GetAttack()*3);
Assert::AreEqual(0,testMonster.GetHealth());
}
TEST_METHOD(TrueDamageTestWith100PctDamageReduction){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::DAMAGE_REDUCTION,5.f,INFINITY);
testMonster._DealTrueDamage(testMonster.GetAttack()*3);
//Damage reduction should not affect true damage at all.
Assert::AreEqual(0,testMonster.GetHealth());
}
TEST_METHOD(CriticalRateTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->SetBaseStat(ItemAttribute::Get("Crit Rate"),100.f);
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ());
//If crit rate works 100% of the time, getting hurt should deal crit dmg % more damage (which defaults to 50%). Ceiling 7.5 damage to 8.
Assert::AreEqual(22,testMonster.GetHealth());
}
TEST_METHOD(CriticalDamageTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
game->GetPlayer()->SetBaseStat(ItemAttribute::Get("Crit Rate"),100.f);
game->GetPlayer()->SetBaseStat(ItemAttribute::Get("Crit Dmg"),150.f);
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ());
//If crit rate works 100% of the time, getting hurt will deal 150% more damage. Ceiling 12.5 damage to 13.
Assert::AreEqual(17,testMonster.GetHealth());
}
TEST_METHOD(ShouldNotDieNormallyTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.diesNormally=false;
testMonster.Hurt(1000,testMonster.OnUpperLevel(),testMonster.GetZ());
testMonster.Hurt(1000,testMonster.OnUpperLevel(),testMonster.GetZ());
testMonster.Hurt(1000,testMonster.OnUpperLevel(),testMonster.GetZ());
//Health should continue to remain at 1 and the monster should remain alive if the dies normally flag is false.
Assert::AreEqual(1,testMonster.GetHealth());
Assert::IsTrue(testMonster.IsAlive());
}
TEST_METHOD(IllegalDefenseStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Defense"});
Assert::Fail(L"Adding a Defense buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalMoveSpdStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Move Spd %"});
Assert::Fail(L"Adding a Move Spd % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalDefensePctStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Defense %"});
Assert::Fail(L"Adding a Defense % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalCDRStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"CDR"});
Assert::Fail(L"Adding a CDR buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalCritRateStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Crit Rate"});
Assert::Fail(L"Adding a Crit Rate buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalCritDmgStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Crit Damage"});
Assert::Fail(L"Adding a Crit Damage buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalHPRecoveryPctStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"HP Recovery %"});
Assert::Fail(L"Adding a HP Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalHP6RecoveryPctStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"HP6 Recovery %"});
Assert::Fail(L"Adding a HP6 Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalHP4RecoveryPctStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"HP4 Recovery %"});
Assert::Fail(L"Adding a HP4 Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalDamageReductionStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Damage Reduction"});
Assert::Fail(L"Adding a Damage Reduction buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalAttackSpdStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Attack Spd"});
Assert::Fail(L"Adding a Attack Spd buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){};
}
TEST_METHOD(IllegalManaStatUpBuffCheck){
try{
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::STAT_UP,5.f,5.f,{"Mana"});
Assert::Fail(L"Adding a Mana buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(DOTTest){
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.AddBuff(BuffType::DAMAGE_REDUCTION,10.f,100._Pct);
testMonster.Hurt(10,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::DOT);
Assert::AreEqual(20,testMonster.GetHealth(),L"A DOT should go through all sources of damage reduction.");
testMonster.Update(1.f); //Let time pass so two DOT numbers don't stack up.
testMonster.ApplyIframes(0.5f);
testMonster.Hurt(10,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::DOT);
Assert::AreEqual(10,testMonster.GetHealth(),L"A DOT should go through all sources of iframes.");
Assert::AreEqual(2,std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr<DamageNumber>&damageNumber){
if(damageNumber->type==DamageNumberType::DOT)return count+1;
else return count;
})
,L"There should be 2 damage numbers of type DOT.");
}
TEST_METHOD(TrapperMarkTest){
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
game->SpawnMonster({},testMonsterData);
game->SetElapsedTime(0.1f);
game->OnUserUpdate(0.1f); //A monster that is spawned needs to be added to the monster list in the next tick.
std::weak_ptr<Monster>testMonster{MONSTER_LIST.front()};
Assert::AreEqual(uint8_t(0),testMonster.lock()->GetMarkStacks(),L"Monster has 0 marks initially.");
testMonster.lock()->ApplyMark(7.f,5U);
game->SetElapsedTime(0.3f);
game->OnUserUpdate(0.3f); //A monster that had a mark applied needs to be added as a lock on target in the next tick.
Assert::AreEqual(uint8_t(5),testMonster.lock()->GetMarkStacks(),L"Monster has 5 marks after receiving a buff.");
testMonster.lock()->Hurt(1,testMonster.lock()->OnUpperLevel(),testMonster.lock()->GetZ());
Assert::AreEqual(uint8_t(5),testMonster.lock()->GetMarkStacks(),L"Monster should still have 5 marks after taking damage from a normal attack.");
testMonster.lock()->Hurt(1,testMonster.lock()->OnUpperLevel(),testMonster.lock()->GetZ(),HurtFlag::PLAYER_ABILITY);
Assert::AreEqual(uint8_t(4),testMonster.lock()->GetMarkStacks(),L"Monster should have 4 marks remaining.");
Assert::AreEqual(22,testMonster.lock()->GetHealth(),L"Mark deals 60% of the player's attack. And 2 damage already taken from earlier.");
testMonster.lock()->Hurt(1,testMonster.lock()->OnUpperLevel(),testMonster.lock()->GetZ(),HurtFlag::DOT);
Assert::AreEqual(uint8_t(4),testMonster.lock()->GetMarkStacks(),L"Monster should still have 4 marks remaining (DOTs don't remove a mark).");
testMonster.lock()->_DealTrueDamage(1);
Assert::AreEqual(uint8_t(4),testMonster.lock()->GetMarkStacks(),L"Monster should still have 4 marks remaining after taking true damage from something not marked as a player ability.");
testMonster.lock()->Heal(testMonster.lock()->GetMaxHealth()); //Heal the monster so it doesn't die.
testMonster.lock()->_DealTrueDamage(1,HurtFlag::PLAYER_ABILITY);
Assert::AreEqual(uint8_t(3),testMonster.lock()->GetMarkStacks(),L"Monster should have 3 marks remaining after taking true damage.");
testMonster.lock()->_DealTrueDamage(10,HurtFlag::DOT|HurtFlag::PLAYER_ABILITY);
Assert::AreEqual(uint8_t(2),testMonster.lock()->GetMarkStacks(),L"Monster should have 2 marks remaining after taking true damage, even though it's classified as a DOT. This is an edge case, but it's really meaningless here...");
testMonster.lock()->Heal(testMonster.lock()->GetMaxHealth());
testMonster.lock()->TriggerMark();
Assert::AreEqual(uint8_t(1),testMonster.lock()->GetMarkStacks(),L"Monster should have 1 mark remaining after using TriggerMark function");
Assert::AreEqual(24,testMonster.lock()->GetHealth(),L"Monster should not take damage from the TriggerMark function (Mark deals 6 damage though).");
game->SetElapsedTime(10.f);
testMonster.lock()->Update(10.f);
Assert::AreEqual(uint8_t(0),testMonster.lock()->GetMarkStacks(),L"The marks should have expired after 10 seconds.");
}
};
}

@ -0,0 +1,609 @@
#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"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
INCLUDE_GFX
INCLUDE_ITEM_DATA
INCLUDE_DAMAGENUMBER_LIST
extern std::mt19937 rng;
namespace PlayerTests
{
TEST_CLASS(PlayerTest)
{
public:
std::unique_ptr<AiL>testGame;
InputGroup testKeyboardInput;
Player*player;
HWButton*testKey;
TEST_METHOD_INITIALIZE(PlayerInitialize){
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::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
player=testGame->GetPlayer();
//Setup key "0" as a test input
testKeyboardInput.AddKeybind(Input{InputType::KEY,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();
DAMAGENUMBER_LIST.clear();
}
TEST_METHOD_CLEANUP(CleanupTests){
testGame->EndGame();
}
TEST_METHOD(PlayerAlive){
Assert::IsTrue(player->IsAlive());
}
TEST_METHOD(PlayerTakesDamage){
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(70,player->GetHealth());
}
TEST_METHOD(PlayerBlockingTakesNoDamage){
player->SetState(State::BLOCK);
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(100,player->GetHealth());
}
TEST_METHOD(PlayerIframesPreventsDamage){
player->ApplyIframes(0.5f);
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(100,player->GetHealth());
}
TEST_METHOD(PlayerConsumesManaOnRightClickAbilityUse){
int abilityManaCost{player->GetRightClickAbility().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana()-abilityManaCost,player->GetMana());
}
TEST_METHOD(PlayerConsumesManaOnAbility1Use){
int abilityManaCost{player->GetAbility1().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility1(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana()-abilityManaCost,player->GetMana());
}
TEST_METHOD(PlayerConsumesManaOnAbility2Use){
int abilityManaCost{player->GetAbility2().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana()-abilityManaCost,player->GetMana());
}
TEST_METHOD(PlayerConsumesManaOnAbility3Use){
int abilityManaCost{player->GetAbility3().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana()-abilityManaCost,player->GetMana());
}
TEST_METHOD(PlayerConsumesManaOnAbility4Use){
int abilityManaCost{player->GetAbility4().manaCost};
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility4(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana()-abilityManaCost,player->GetMana());
}
TEST_METHOD(WarriorBlockActivatesBlock){
player->GetRightClickAbility().action(player,player->GetPos());
Assert::AreEqual(int(State::BLOCK),int(player->GetState()));
}
TEST_METHOD(WizardCastDoesNotConsumeManaImmediately){
testGame->ChangePlayerClass(WIZARD);
player=testGame->GetPlayer(); //The player pointer has been reassigned...
//Ability 3 is Meteor, which is a cast. Mana should not be consumed for a spell that begins as a cast.
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana(),player->GetMana());
}
TEST_METHOD(RangerCastDoesNotConsumeManaImmediately){
testGame->ChangePlayerClass(RANGER);
player=testGame->GetPlayer(); //The player pointer has been reassigned...
//Ability 2 is Charged Shot, which is a cast. Mana should not be consumed for a spell that begins as a cast.
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
Assert::AreEqual(player->GetMaxMana(),player->GetMana());
}
TEST_METHOD(PlayerTakesDamageWithDamageReductionApplied){
std::weak_ptr<Item>testArmor{Inventory::AddItem("Bone Armor"s)};
testArmor.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(testArmor,EquipSlot::ARMOR);
//If any of these values change, the armor piece may return a different value and these need to be updated for
//BONE ARMOR +10!
Assert::AreEqual(72.f,testArmor.lock()->GetStats().A_Read("Defense"),L"Defense increase is not 72");
Assert::AreEqual(14.f,testArmor.lock()->GetStats().A_Read("Health"),L"Health increase is not 14");
Assert::AreEqual(7.f,testArmor.lock()->GetStats().A_Read("Attack"),L"Attack increase is not 7");
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(74,player->GetHealth()); //Even though we are supposed to take 30 damage, damage reduction reduces it to 26 instead. (Random variance is controlled during testing. If this number changes at some point, either the damage formula or the item's stats have changed!)
}
TEST_METHOD(PlayerSetEffectsAcknowledged){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor"s)};
setArmor.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100 defense, 1000 health, and 100 attack.
Assert::AreEqual(100.f+player->GetBaseStat("Defense"),player->GetEquipStat("Defense"),L"Defense stat was not increased by 100.");
Assert::AreEqual(100.f+player->GetBaseStat("Attack"),player->GetEquipStat("Attack"),L"Attack stat was not increased by 100.");
Assert::AreEqual(1000.f+player->GetBaseStat("Health"),player->GetEquipStat("Health"),L"Health Points was not increased by 1000.");
}
TEST_METHOD(PlayerSetEffectsAcknowledged2){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
setArmor.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100% defense, 100% Attack, 100% Move Spd, 100% CDR, 100% Crit Rate, 100% Crit Dmg, 100% Health %, 100% HP/6 Recovery
Assert::AreEqual(100.f+player->GetBaseStat("Defense %"),player->GetEquipStat("Defense %"),L"Defense % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Attack %"),player->GetEquipStat("Attack %"),L"Attack % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Move Spd % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("CDR"),player->GetEquipStat("CDR"),L"CDR stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate % stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Crit Dmg"),player->GetEquipStat("Crit Dmg"),L"Critical Damage stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Health %"),player->GetEquipStat("Health %"),L"Health Points was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("HP6 Recovery %"),player->GetEquipStat("HP6 Recovery %"),L"HP/6s Recovery stat was not increased by 100%.");
}
TEST_METHOD(PlayerSetEffectsAcknowledged3){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
setArmor.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100% HP/4 Recovery, 100% Damage Reduction
Assert::AreEqual(100.f+player->GetBaseStat("HP4 Recovery %"),player->GetEquipStat("HP4 Recovery %"),L"HP/4s Recovery stat was not increased by 100%.");
Assert::AreEqual(100.f+player->GetBaseStat("Damage Reduction"),player->GetEquipStat("Damage Reduction"),L"Damage Reduction stat was not increased by 100%.");
}
TEST_METHOD(PlayerSetEffectsAcknowledged4){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
setArmor.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
//This gear is supposed to be provide an additional 100% HP/s Recovery, 50% Attack Spd, and 100 More Base Mana.
Assert::AreEqual(100.f+player->GetBaseStat("HP Recovery %"),player->GetEquipStat("HP Recovery %"),L"HP Recovery % stat was not increased by 100%.");
Assert::AreEqual(50.f+player->GetBaseStat("Attack Spd"),player->GetEquipStat("Attack Spd"),L"Attack Speed stat was not increased by 50%.");
Assert::AreEqual(100.f+player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Mana stat was not increased by 100.");
}
TEST_METHOD(MultiPieceSetEffectsWork){
std::weak_ptr<Item>testArmor{Inventory::AddItem("Bone Armor"s)};
std::weak_ptr<Item>testHeadpiece{Inventory::AddItem("Bone Helmet"s)};
std::weak_ptr<Item>testLeggings{Inventory::AddItem("Bone Pants"s)};
std::weak_ptr<Item>testGloves{Inventory::AddItem("Bone Gloves"s)};
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Base Crit Rate does not match initial Crit Rate");
Assert::AreEqual(player->GetBaseStat("Crit Dmg"),player->GetEquipStat("Crit Dmg"),L"Base Crit Dmg does not match initial Crit Dmg");
Inventory::EquipItem(testArmor,EquipSlot::ARMOR);
Inventory::EquipItem(testHeadpiece,EquipSlot::HELMET);
Assert::AreEqual(5+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate does not increase by 5% with 2 Bone Equips (Set Bonus)");
Assert::AreEqual(player->GetBaseStat("Crit Dmg"),player->GetEquipStat("Crit Dmg"),L"Crit Dmg does not remain the same with 2 Bone Equips (Set Bonus)");
Inventory::UnequipItem(EquipSlot::ARMOR);
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Removing an armor piece does not remove the crit rate set effect");
Assert::AreEqual(player->GetBaseStat("Crit Dmg"),player->GetEquipStat("Crit Dmg"),L"Removing an armor piece affects crit dmg incorrectly");
Inventory::EquipItem(testArmor,EquipSlot::ARMOR);
Inventory::EquipItem(testLeggings,EquipSlot::PANTS);
Inventory::EquipItem(testGloves,EquipSlot::GLOVES);
Assert::AreEqual(5+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate does not increase by 5% with 4 Bone Equips (Set Bonus)");
Assert::AreEqual(7+player->GetBaseStat("Crit Dmg"),player->GetEquipStat("Crit Dmg"),L"Crit Dmg does not increase by 5% with 4 Bone Equips (Set Bonus)");
}
TEST_METHOD(AccessoriesWork){
std::weak_ptr<Item>testRing{Inventory::AddItem("Bird's Treasure"s)};
std::weak_ptr<Item>testRing2{Inventory::AddItem("Bird's Treasure"s)};
Assert::AreEqual(3.f,testRing.lock()->RandomStats().A_Read("Move Spd %"),L"Generated Ring 1 Move Spd % is incorrect.");
Assert::AreEqual(2.f,testRing.lock()->RandomStats().A_Read("Crit Rate"),L"Generated Ring 1 Crit Rate is incorrect.");
Assert::AreEqual(5.f,testRing.lock()->RandomStats().A_Read("Mana"),L"Generated Ring 1 Mana is incorrect.");
Assert::AreEqual(2.f,testRing2.lock()->RandomStats().A_Read("Move Spd %"),L"Generated Ring 2 Move Spd % is incorrect.");
Assert::AreEqual(2.f,testRing2.lock()->RandomStats().A_Read("Crit Rate"),L"Generated Ring 2 Crit Rate is incorrect.");
Assert::AreEqual(2.f,testRing2.lock()->RandomStats().A_Read("Mana"),L"Generated Ring 2 Mana is incorrect.");
Assert::IsTrue(testRing.lock()->RandomStats().A_Read("Move Spd %")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Move Spd %")&&
testRing.lock()->RandomStats().A_Read("Move Spd %")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Move Spd %"),std::format(L"Generated Move Spd % {} is out of bounds: Min:{} Max:{}",testRing.lock()->RandomStats().A_Read("Move Spd %"),ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Move Spd %"),ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Move Spd %")).c_str());
Assert::IsTrue(testRing.lock()->RandomStats().A_Read("Crit Rate")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Crit Rate")&&
testRing.lock()->RandomStats().A_Read("Crit Rate")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Crit Rate"),std::format(L"Generated Crit Rate {} is out of bounds: Min:{} Max:{}",testRing.lock()->RandomStats().A_Read("Crit Rate"),ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Move Spd %"),ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Crit Rate")).c_str());
Assert::IsTrue(testRing.lock()->RandomStats().A_Read("Mana")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Mana")&&
testRing.lock()->RandomStats().A_Read("Mana")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Mana"),std::format(L"Generated Mana {} is out of bounds: Min:{} Max:{}",testRing.lock()->RandomStats().A_Read("Mana"),ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Mana"),ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Mana")).c_str());
Assert::IsTrue(testRing2.lock()->RandomStats().A_Read("Move Spd %")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Move Spd %")&&
testRing2.lock()->RandomStats().A_Read("Move Spd %")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Move Spd %"),std::format(L"Generated Move Spd % {} is out of bounds: Min:{} Max:{}",testRing2.lock()->RandomStats().A_Read("Move Spd %"),ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Move Spd %"),ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Move Spd %")).c_str());
Assert::IsTrue(testRing2.lock()->RandomStats().A_Read("Crit Rate")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Crit Rate")&&
testRing2.lock()->RandomStats().A_Read("Crit Rate")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Crit Rate"),std::format(L"Generated Crit Rate {} is out of bounds: Min:{} Max:{}",testRing2.lock()->RandomStats().A_Read("Crit Rate"),ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Move Spd %"),ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Crit Rate")).c_str());
Assert::IsTrue(testRing2.lock()->RandomStats().A_Read("Mana")>=ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Mana")&&
testRing2.lock()->RandomStats().A_Read("Mana")<=ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Mana"),std::format(L"Generated Mana {} is out of bounds: Min:{} Max:{}",testRing2.lock()->RandomStats().A_Read("Mana"),ITEM_DATA.at("Bird's Treasure"s).GetMinStats().A_Read("Mana"),ITEM_DATA.at("Bird's Treasure"s).GetMaxStats().A_Read("Mana")).c_str());
//Ensure stats are default.
Assert::AreEqual(player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Base Move Spd %does not match initial Move Spd %");
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Base Crit Rate does not match initial Crit Rate");
Assert::AreEqual(player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Base Mana does not match initial Mana");
Inventory::EquipItem(testRing,EquipSlot::RING1);
Assert::AreEqual(3+player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Move Spd % increased incorrectly when ring 1 equipped!");
Assert::AreEqual(2+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate increased incorrectly when ring 1 equipped!");
Assert::AreEqual(5+player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Mana increased incorrectly when ring 1 equipped!");
//Equipping to same slot should undo the previous stats and apply the new ring's stats.
Inventory::EquipItem(testRing2,EquipSlot::RING1);
Assert::AreEqual(2+player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Move Spd % increased incorrectly when ring 1 equipped!");
Assert::AreEqual(2+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate increased incorrectly when ring 1 equipped!");
Assert::AreEqual(2+player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Mana increased incorrectly when ring 1 equipped!");
Inventory::UnequipItem(EquipSlot::RING1);
//Ensure stats are default again.
Assert::AreEqual(player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Base Move Spd %does not match initial Move Spd % after unequipping the ring.");
Assert::AreEqual(player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Base Crit Rate does not match initial Crit Rate after unequipping the ring.");
Assert::AreEqual(player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Base Mana does not match initial Mana after unequipping the ring.");
Inventory::EquipItem(testRing,EquipSlot::RING1);
Inventory::EquipItem(testRing2,EquipSlot::RING2);
Assert::AreEqual(5+player->GetBaseStat("Move Spd %"),player->GetEquipStat("Move Spd %"),L"Move Spd % increased incorrectly when both rings equipped!");
Assert::AreEqual(4+player->GetBaseStat("Crit Rate"),player->GetEquipStat("Crit Rate"),L"Crit Rate increased incorrectly when both rings equipped!");
Assert::AreEqual(7+player->GetBaseStat("Mana"),player->GetEquipStat("Mana"),L"Mana increased incorrectly when both rings equipped!");
}
TEST_METHOD(FlatDefenseStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
player->Hurt(100,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(17,player->GetHealth(),L"100 Defense prevents 17 damage, resulting in 83 damage points dealt.");
}
TEST_METHOD(HealthStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(HealthStatUpBuffCheck){
player->AddBuff(BuffType::STAT_UP,5.f,1000.f,{"Health"});
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(HealthPctStatUpBuffCheck){
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Health %"});
Assert::AreEqual(1000+player->GetBaseStat("Health"),float(player->GetMaxHealth()),L"Max Health stat should be increased from 100 to 1100.");
}
TEST_METHOD(IllegalCritRateStatUpBuffCheck){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Crit Rate"});
Assert::Fail(L"Adding a Crit Rate buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalCritDmgStatUpBuffCheck){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Crit Damage"});
Assert::Fail(L"Adding a Crit Damage buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalHPRecoveryPctStatUpBuffCheck){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"HP Recovery %"});
Assert::Fail(L"Adding a HP Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalHP6RecoveryPctStatUpBuffCheck){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"HP6 Recovery %"});
Assert::Fail(L"Adding a HP6 Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalHP4RecoveryPctStatUpBuffCheck){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"HP4 Recovery %"});
Assert::Fail(L"Adding a HP4 Recovery % buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalDamageReductionStatUpBuffCheck){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Damage Reduction"});
Assert::Fail(L"Adding a Damage Reduction buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(IllegalAttackSpdStatUpBuffCheck){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Attack Spd"});
Assert::Fail(L"Adding a Attack Spd buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){};
}
TEST_METHOD(IllegalManaStatUpBuffCheck){
try{
player->AddBuff(BuffType::STAT_UP,5.f,1000.0_Pct,{"Mana"});
Assert::Fail(L"Adding a Mana buff succeeded! This should NOT be allowed!");
}catch(std::exception&e){}
}
TEST_METHOD(AttackStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100+player->GetBaseStat("Attack"),float(player->GetAttack()),L"Attack stat should be increased from 10 to 110.");
}
TEST_METHOD(DefensePctStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(player->GetBaseStat("Defense"),float(player->GetDefense()),L"100% Defense % causes a 0 increase in Defense when it's default zero.");
std::weak_ptr<Item>bonePants{Inventory::AddItem("Bone Pants"s)};
bonePants.lock()->enhancementLevel="Item.Item Max Enhancement Level"_I; //Force enhance the item to the max enhancement level.
Inventory::EquipItem(bonePants,EquipSlot::PANTS);
Assert::AreEqual(122.f+player->GetBaseStat("Defense"),float(player->GetDefense()),L"100% Defense % causes double increase in Defense. 61 -> 122");
Inventory::UnequipItem(EquipSlot::ARMOR);
player->Hurt(100,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(13,player->GetHealth(),L"Took 87 damage with 61 defense.");
player->Heal(87); //Back to 100 Health.
LOG(std::format("Player Health is now {}",player->GetHealth()));
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
player->Hurt(100,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(36,player->GetHealth(),L"Took 54 damage with 122 defense.");
}
TEST_METHOD(AttackPctStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Monster testMonster{{},MONSTER_DATA["TestName"]};
testMonster.Hurt(player->GetAttack(),player->OnUpperLevel(),player->GetZ());
const int damageDealt{testMonster.GetMaxHealth()-testMonster.GetHealth()};
testMonster.Heal(testMonster.GetMaxHealth());
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(player->GetBaseStat("Attack")*2,float(player->GetAttack()),L"100% Attack should double the attack of the player.");
testMonster.Hurt(player->GetAttack(),player->OnUpperLevel(),player->GetZ());
const int enhancedDamageDealt{testMonster.GetMaxHealth()-testMonster.GetHealth()};
Assert::AreEqual(enhancedDamageDealt,damageDealt*5,L"Original damage dealt should be half of the new attack. Factor in 100% crit rate and a bonus 100% crit dmg (making it 250%) 200%*2.5=500% damage.");
}
TEST_METHOD(MoveSpdPctStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Assert::AreEqual(1.f,player->GetMoveSpdMult(),L"Move Spd is 100%");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(2.f,player->GetMoveSpdMult(),L"100% Move Spd should double the move speed from 100% to 200%");
}
TEST_METHOD(CDRStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Assert::AreEqual(0.f,player->GetCooldownReductionPct(),L"CDR is 0%");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(1.f,player->GetCooldownReductionPct(),L"Player should have 100% CDR");
testKey->bHeld=true; //Force the key to be held down for testing purposes.
player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility1(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility3(),testKeyboardInput);
player->CheckAndPerformAbility(player->GetAbility4(),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.");
Assert::AreEqual(0.f,player->GetAbility1().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown.");
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.");
}
TEST_METHOD(CritRateStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Assert::AreEqual(0.f,player->GetCritRatePct(),L"Crit Rate is 0%");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(1.f,player->GetCritRatePct(),L"Player should have 100% Crit Rate");
}
TEST_METHOD(CritDmgStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Assert::AreEqual(0.5f,player->GetCritDmgPct(),L"Crit Damage is 50%");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(1.5f,player->GetCritDmgPct(),L"Player should have 150% Crit Damage");
}
TEST_METHOD(HealthPctStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Assert::AreEqual(100,player->GetHealth(),L"Current Health is 100");
Assert::AreEqual(100,player->GetMaxHealth(),L"Max Health is 100");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100,player->GetHealth(),L"Current Health is still 100");
Assert::AreEqual(200,player->GetMaxHealth(),L"Max Health is now 200");
}
TEST_METHOD(HP6RecoveryStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
Assert::AreEqual(0.0_Pct,player->GetHP6RecoveryPct(),L"HP6 Recovery % is 0");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100.0_Pct,player->GetHP6RecoveryPct(),L"HP6 Recovery % is 100%");
}
TEST_METHOD(HP4RecoveryStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
Assert::AreEqual(0.0_Pct,player->GetHP4RecoveryPct(),L"HP4 Recovery % is 0");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100.0_Pct,player->GetHP4RecoveryPct(),L"HP4 Recovery % is 100%");
}
TEST_METHOD(DamageReductionStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Damage Reduction is 0%");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
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)};
Assert::AreEqual(0.0_Pct,player->GetHPRecoveryPct(),L"HP Recovery % is 0");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100.0_Pct,player->GetHPRecoveryPct(),L"HP Recovery % is 100%");
}
TEST_METHOD(AttackSpeedStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
Assert::AreEqual(0.f,player->GetAttackRecoveryRateReduction(),L"Attack Speed Reduction is 0 seconds");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(50.f,player->GetAttackRecoveryRateReduction(),L"Attack Speed Reduction is 50 seconds");
}
TEST_METHOD(ManaStatEquipCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
Assert::AreEqual(100,player->GetMana(),L"Mana is 100");
Assert::AreEqual(100,player->GetMaxMana(),L"Max Mana is 100");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100,player->GetMana(),L"Mana is still 100");
Assert::AreEqual(200,player->GetMaxMana(),L"Max Mana is now 200");
}
TEST_METHOD(HP6RecoveryTest){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor2"s)};
game->SetElapsedTime(0.1f);
game->OnUserUpdate(0.1f); //Advance the game by 0.1s so that all healing ticks occur at least once.
//Hurt the player for most of their health.
player->Hurt(player->GetMaxHealth()-1,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(1,player->GetHealth(),L"Player is at 1 Health.");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100.0_Pct,player->GetHP6RecoveryPct(),L"HP6 Recovery % is 100%");
game->SetElapsedTime(3.f);
game->OnUserUpdate(3.f); //Wait 3 seconds. The player shouldn't be healed yet.
Assert::AreEqual(1,player->GetHealth(),L"After waiting for 3 seconds, the player should still be at 1 health.");
game->SetElapsedTime(3.f);
game->OnUserUpdate(3.f); //Wait 3 more seconds. The player should be healed now.
Assert::AreEqual(player->GetMaxHealth(),player->GetHealth(),L"After waiting for 6 seconds, the player should be at full health.");
}
TEST_METHOD(HP4RecoveryTest){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
game->SetElapsedTime(0.1f);
game->OnUserUpdate(0.1f); //Advance the game by 0.1s so that all healing ticks occur at least once.
//Hurt the player for most of their health.
player->Hurt(player->GetMaxHealth()-1,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(1,player->GetHealth(),L"Player is at 1 Health.");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100.0_Pct,player->GetHP4RecoveryPct(),L"HP4 Recovery % is 100%");
game->SetElapsedTime(2.f);
game->OnUserUpdate(2.f); //Wait 2 seconds. The player shouldn't be healed yet.
Assert::AreEqual(1,player->GetHealth(),L"After waiting for 2 seconds, the player should still be at 1 health.");
game->SetElapsedTime(2.f);
game->OnUserUpdate(2.f); //Wait 2 more seconds. The player should be healed now.
Assert::AreEqual(player->GetMaxHealth(),player->GetHealth(),L"After waiting for 4 seconds, the player should be at full health.");
}
TEST_METHOD(HPRecoveryTest){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor4"s)};
game->SetElapsedTime(0.1f);
game->OnUserUpdate(0.1f); //Advance the game by 0.1s so that all healing ticks occur at least once.
//Hurt the player for most of their health.
player->Hurt(player->GetMaxHealth()-1,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(1,player->GetHealth(),L"Player is at 1 Health.");
Inventory::EquipItem(setArmor,EquipSlot::ARMOR);
Assert::AreEqual(100.0_Pct,player->GetHPRecoveryPct(),L"HP Recovery % is 100%");
game->SetElapsedTime(0.5f);
game->OnUserUpdate(0.5f); //Wait 0.5 seconds. The player shouldn't be healed yet.
Assert::AreEqual(1,player->GetHealth(),L"After waiting for 0.5 seconds, the player should still be at 1 health.");
game->SetElapsedTime(0.5f);
game->OnUserUpdate(0.5f); //Wait 0.5 more seconds. The player should be healed now.
Assert::AreEqual(player->GetMaxHealth(),player->GetHealth(),L"After waiting for 1 second, the player should be at full health.");
}
TEST_METHOD(AdrenalineRushSkillBuffTest){
testGame->ChangePlayerClass(THIEF);
player=testGame->GetPlayer(); //The player pointer has been reassigned...
Assert::AreEqual(1.f,player->GetMoveSpdMult(),L"Move Speed Multiplier is set to x1.0 by default.");
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);
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");
}
TEST_METHOD(TrueDamageCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR); //Equip an item with 100% damage reduction.
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),TrueDamageFlag::IGNORE_DAMAGE_RULES);
Assert::AreEqual(80,player->GetHealth(),L"Player should take 20 damage through true damage.");
player->ApplyIframes(0.5f);
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),TrueDamageFlag::IGNORE_DAMAGE_RULES);
Assert::AreEqual(60,player->GetHealth(),L"Player should take 20 damage through true damage even when iframes are on.");
}
TEST_METHOD(DOTDamageCheck){
std::weak_ptr<Item>setArmor{Inventory::AddItem("Test Armor3"s)};
Inventory::EquipItem(setArmor,EquipSlot::ARMOR); //Equip an item with 100% damage reduction.
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),HurtFlag::DOT);
Assert::AreEqual(80,player->GetHealth(),L"Player should take 20 damage through a DOT.");
Assert::AreEqual(1,std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr<DamageNumber>&damageNumber){
if(damageNumber->type==DamageNumberType::DOT)return count+1;
else return count;
})
,L"There should be a damage number of type DOT.");
player->Update(1.0f);//Let some time pass so two DOT numbers don't stack up.
player->ApplyIframes(0.5f);
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),HurtFlag::DOT);
Assert::AreEqual(80,player->GetHealth(),L"Player should take 20 damage through a DOT even when iframes are on."); //HP Recovery/4s set effect has restored the health of the player to 100. So it should go back down to 80.
Assert::AreEqual(2,std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr<DamageNumber>&damageNumber){
if(damageNumber->type==DamageNumberType::DOT)return count+1;
else return count;
})
,L"There should be 2 damage numbers of type DOT.");
}
};
}

@ -5,6 +5,8 @@ VisualStudioVersion = 17.5.33516.290
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Adventures in Lestoria", "Adventures in Lestoria\Adventures in Lestoria.vcxproj", "{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Adventures in Lestoria Tests", "Adventures in Lestoria Tests\Adventures in Lestoria Tests.vcxproj", "{11969B7B-3D50-4825-9584-AF01D15B88E0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -15,6 +17,10 @@ Global
Emscripten|x86 = Emscripten|x86
Release Desktop|x64 = Release Desktop|x64
Release Desktop|x86 = Release Desktop|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
Unit Testing|x64 = Unit Testing|x64
Unit Testing|x86 = Unit Testing|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Debug|x64.ActiveCfg = Debug|x64
@ -33,6 +39,28 @@ Global
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Release Desktop|x64.Build.0 = Release Desktop|x64
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Release Desktop|x86.ActiveCfg = Release Desktop|Win32
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Release Desktop|x86.Build.0 = Release Desktop|Win32
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Release|x64.ActiveCfg = Release Desktop|x64
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Release|x64.Build.0 = Release Desktop|x64
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Release|x86.ActiveCfg = Release Desktop|Win32
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Release|x86.Build.0 = Release Desktop|Win32
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Unit Testing|x64.ActiveCfg = Unit Testing|x64
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Unit Testing|x64.Build.0 = Unit Testing|x64
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Unit Testing|x86.ActiveCfg = Unit Testing|Win32
{8E3067AF-CFE7-4B11-BC6B-B867C32753D7}.Unit Testing|x86.Build.0 = Unit Testing|Win32
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Debug|x64.ActiveCfg = Unit Testing|x64
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Debug|x86.ActiveCfg = Unit Testing|Win32
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Emscripten Debug|x64.ActiveCfg = Unit Testing|x64
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Emscripten Debug|x86.ActiveCfg = Unit Testing|Win32
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Emscripten|x64.ActiveCfg = Unit Testing|x64
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Emscripten|x86.ActiveCfg = Unit Testing|Win32
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Release Desktop|x64.ActiveCfg = Unit Testing|x64
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Release Desktop|x86.ActiveCfg = Unit Testing|Win32
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Release|x64.ActiveCfg = Unit Testing|x64
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Release|x86.ActiveCfg = Unit Testing|Win32
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Unit Testing|x64.ActiveCfg = Unit Testing|x64
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Unit Testing|x64.Build.0 = Unit Testing|x64
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Unit Testing|x86.ActiveCfg = Unit Testing|Win32
{11969B7B-3D50-4825-9584-AF01D15B88E0}.Unit Testing|x86.Build.0 = Unit Testing|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

@ -43,11 +43,9 @@ INCLUDE_game
PrecastData::PrecastData()
:castTime(0),range(0),size(0){};
PrecastData::PrecastData(float castTime)
:castTime(castTime),range(0),size(0){precastTargetingRequired=true;};
:castTime(castTime),range(0),size(0),precastTargetingRequired(castTime>0){};
PrecastData::PrecastData(float castTime,float range,float size)
:castTime(castTime),range(range),size(size){
if(castTime>0)precastTargetingRequired=true;
};
:castTime(castTime),range(range),size(size),precastTargetingRequired(range>0||castTime>0){};
InputGroup Ability::DEFAULT;

@ -0,0 +1,93 @@
#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 "RowItemDisplay.h"
#include "SoundEffect.h"
#include "MenuIconButton.h"
INCLUDE_game
INCLUDE_GFX
class AccessoryRowItemDisplay:public RowItemDisplay{
std::weak_ptr<MenuIconButton>lockButton;
public:
inline AccessoryRowItemDisplay(geom2d::rect<float>rect,const std::weak_ptr<Item>itemRef,MenuFunc onClick,std::string itemNameLabelName,std::string itemDescriptionLabelName,ButtonAttr attributes=ButtonAttr::NONE)
:RowItemDisplay(rect,itemRef,onClick,itemNameLabelName,itemDescriptionLabelName,attributes){}
inline ~AccessoryRowItemDisplay(){
if(!lockButton.expired()){
size_t subcomponentCount=parentComponent.lock()->GetSubcomponentCount();
parentComponent.lock()->RemoveButton(lockButton);
if(parentComponent.lock()->GetSubcomponentCount()>=subcomponentCount)ERR(std::format("WARNING! The subcomponent count went from {} -> {} when trying to remove the lock button for item {}! THIS SHOULD NOT BE HAPPENING!",subcomponentCount,parentComponent.lock()->GetSubcomponentCount(),name));
lockButton.reset();
}
}
inline void AfterCreate()override final{
lockButton=parentComponent.lock()->ADDSUB(std::format("{}_lock",name),MenuIconButton)(geom2d::rect<float>{rect.pos+vf2d{rect.size.x-10.f,rect.size.y-10.f},vf2d{10.f,10.f}},GFX["unlock.png"].Decal(),[](MenuFuncData data){
std::weak_ptr<AccessoryRowItemDisplay>accessoryParent=DYNAMIC_POINTER_CAST<AccessoryRowItemDisplay>(data.component.lock()->GetSubcomponentParent());
accessoryParent.lock()->SetLocked(!accessoryParent.lock()->IsLocked());
if(accessoryParent.lock()->IsLocked()){
SoundEffect::PlaySFX("Lock Accessory",SoundEffect::CENTERED);
}else{
SoundEffect::PlaySFX("Unlock Accessory",SoundEffect::CENTERED);
}
std::weak_ptr<MenuIconButton>lockButton=DYNAMIC_POINTER_CAST<MenuIconButton>(data.component.lock());
return true;
})END;
}
inline void Update(AiL*game)override final{
MenuComponent::Update(game);
if(itemRef.lock()->IsLocked()){
lockButton.lock()->SetIcon(GFX["lock.png"].Decal());
}else{
lockButton.lock()->SetIcon(GFX["unlock.png"].Decal());
}
}
inline const bool IsLocked()const{
return itemRef.lock()->IsLocked();
}
inline void SetLocked(const bool locked){
if(locked){
itemRef.lock()->Lock();
}else{
itemRef.lock()->Unlock();
}
}
inline const std::weak_ptr<MenuIconButton>GetLockButton()const{
return lockButton;
}
};

@ -35,7 +35,7 @@ var Module = {
})(),
};
</script>
<script async type="text/javascript" src="Adventures in Lestoria.js"></script>
<script async type="text/javascript" src="AdventuresInLestoria.js"></script>
<script type="text/javascript">
Module.canvas.addEventListener("resize", (e) => {

@ -7,6 +7,8 @@
"folders": [
"."
],
"properties": [
],
"propertyTypes": [
{
"color": "#ff3af8eb",
@ -34,7 +36,9 @@
"type": "enum",
"values": [
"None",
"forest"
"forest",
"mountain_day",
"mountain_night"
],
"valuesAsFlags": false
},
@ -48,7 +52,7 @@
"foresty1_1",
"overworld",
"foresty_boss",
"foresty_loop1"
"base_camp"
],
"valuesAsFlags": false
},
@ -90,6 +94,30 @@
"layer"
]
},
{
"color": "#ffa0a0a4",
"drawFill": true,
"id": 41,
"members": [
{
"name": "TileRepeatFactor X",
"type": "int",
"value": 1
},
{
"name": "TileRepeatFactor Y",
"type": "int",
"value": 1
}
],
"name": "CustomTile",
"type": "class",
"useAs": [
"property",
"tile",
"project"
]
},
{
"color": "#ffbdc34c",
"drawFill": true,
@ -170,6 +198,25 @@
"layer"
]
},
{
"color": "#ff4f4f51",
"drawFill": true,
"id": 37,
"members": [
{
"name": "Scroll Direction",
"propertyType": "ScrollDirection",
"type": "string",
"value": "NORTH"
}
],
"name": "Focus Area",
"type": "class",
"useAs": [
"property",
"object"
]
},
{
"color": "#ffd9d929",
"drawFill": true,
@ -210,7 +257,19 @@
"HUB",
"CAMPAIGN_1_B1",
"BOSS_1_B",
"STORY_2_1"
"CAMPAIGN_2_1",
"CAMPAIGN_2_2",
"CAMPAIGN_2_3",
"CAMPAIGN_2_4",
"CAMPAIGN_2_5",
"CAMPAIGN_2_6",
"CAMPAIGN_2_7",
"CAMPAIGN_2_8",
"CAMPAIGN_2_B1",
"BOSS_2",
"BOSS_2_B",
"STORY_2_1",
"STORY_2_2"
],
"valuesAsFlags": false
},
@ -272,6 +331,41 @@
"type": "string",
"value": "None"
},
{
"name": "Create Optimization Map (Override)",
"type": "bool",
"value": false
},
{
"name": "Dev Completion Time - Ranger (s)",
"type": "float",
"value": 0
},
{
"name": "Dev Completion Time - Thief (s)",
"type": "float",
"value": 0
},
{
"name": "Dev Completion Time - Trapper (s)",
"type": "float",
"value": 0
},
{
"name": "Dev Completion Time - Warrior (s)",
"type": "float",
"value": 0
},
{
"name": "Dev Completion Time - Witch (s)",
"type": "float",
"value": 0
},
{
"name": "Dev Completion Time - Wizard (s)",
"type": "float",
"value": 0
},
{
"name": "Level Type",
"propertyType": "LevelType",
@ -296,12 +390,6 @@
"drawFill": false,
"id": 1,
"members": [
{
"name": "Type",
"propertyType": "MonsterName",
"type": "string",
"value": "None"
},
{
"name": "spawner",
"type": "object",
@ -331,7 +419,8 @@
"Windhound",
"Bear",
"Frog",
"Ursule, Mother of Bears"
"Ursule, Mother of Bears",
"Bun"
],
"valuesAsFlags": false
},
@ -372,13 +461,7 @@
"type": "class",
"useAs": [
"property",
"map",
"layer",
"object",
"tile",
"tileset",
"wangcolor",
"wangset"
"object"
]
},
{
@ -390,10 +473,23 @@
"None",
"Blacksmith",
"PotionCrafting",
"TravelingMerchant"
"TravelingMerchant",
"Artificer"
],
"valuesAsFlags": false
},
{
"color": "#ffa0a0a4",
"drawFill": true,
"id": 39,
"members": [
],
"name": "Overlay",
"type": "class",
"useAs": [
"layer"
]
},
{
"color": "#fffa00f6",
"drawFill": true,
@ -420,6 +516,62 @@
"tile"
]
},
{
"id": 38,
"name": "ScrollDirection",
"storageType": "string",
"type": "enum",
"values": [
"NORTH",
"NORTHEAST",
"EAST",
"SOUTHEAST",
"SOUTH",
"SOUTHWEST",
"WEST",
"NORTHWEST"
],
"valuesAsFlags": false
},
{
"color": "#ffa0a0a4",
"drawFill": true,
"id": 42,
"members": [
{
"name": "Spawn1",
"type": "object",
"value": 0
},
{
"name": "Spawn2",
"type": "object",
"value": 0
},
{
"name": "Spawn3",
"type": "object",
"value": 0
},
{
"name": "Spawn4",
"type": "object",
"value": 0
},
{
"name": "Spawn5",
"type": "object",
"value": 0
}
],
"name": "SpawnController",
"type": "class",
"useAs": [
"property",
"object",
"project"
]
},
{
"color": "#ffe67300",
"drawFill": true,
@ -564,6 +716,19 @@
"tileset"
]
},
{
"color": "#fff7ff5d",
"drawFill": true,
"id": 40,
"members": [
],
"name": "TrialClock",
"type": "class",
"useAs": [
"property",
"object"
]
},
{
"color": "#ffa40aa4",
"drawFill": true,

@ -21,6 +21,14 @@
<Configuration>Emscripten</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Unit Testing|Win32">
<Configuration>Unit Testing</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Unit Testing|x64">
<Configuration>Unit Testing</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release Desktop|Win32">
<Configuration>Release Desktop</Configuration>
<Platform>Win32</Platform>
@ -49,6 +57,12 @@
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Desktop|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
@ -62,6 +76,12 @@
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
@ -89,12 +109,18 @@
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release Desktop|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
@ -108,9 +134,18 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IncludePath>$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|Win32'">
<IncludePath>$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IncludePath>$(VCInstallDir)Auxiliary\VS\UnitTest\include;$(IncludePath)</IncludePath>
<LibraryPath>$(VCInstallDir)Auxiliary\VS\UnitTest\lib;$(LibraryPath)</LibraryPath>
</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>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
@ -132,6 +167,27 @@
<Command>powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File update_version.ps1 "./version.h"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalIncludeDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\Documents\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>/MP8 %(AdditionalOptions)</AdditionalOptions>
<TreatSpecificWarningsAsErrors>4099;5030;4715;4172;4834</TreatSpecificWarningsAsErrors>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>discord_game_sdk.dll.lib;freetype.lib;$(CoreLibraryDependencies);%(AdditionalDependencies);</AdditionalDependencies>
</Link>
<PreBuildEvent>
<Command>powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File update_version.ps1 "./version.h"</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release Desktop|Win32'">
<ClCompile>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
@ -161,16 +217,43 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<AdditionalIncludeDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\LabUser\Documents\include;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>/MP20 %(AdditionalOptions)</AdditionalOptions>
<TreatSpecificWarningsAsErrors>4099;5030;4715;4172;4834</TreatSpecificWarningsAsErrors>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria;%(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 update_version.ps1 "./version.h"</Command>
</PreBuildEvent>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Unit Testing|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include</AdditionalIncludeDirectories>
<AdditionalOptions>/MP20 %(AdditionalOptions)</AdditionalOptions>
<TreatSpecificWarningsAsErrors>4099;5030;4715;4172;4834</TreatSpecificWarningsAsErrors>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>discord_game_sdk.dll.lib;freetype.lib;$(CoreLibraryDependencies);%(AdditionalDependencies);</AdditionalDependencies>
<AdditionalDependencies>discord_game_sdk.dll.lib;freetype.lib;steam_api64.lib;$(CoreLibraryDependencies);%(AdditionalDependencies);</AdditionalDependencies>
</Link>
<PreBuildEvent>
<Command>powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File update_version.ps1 "./version.h"</Command>
@ -179,6 +262,9 @@
<Command>
</Command>
</PostBuildEvent>
<Lib>
<AdditionalDependencies>discord_game_sdk.dll.lib;freetype.lib;steam_api64.lib;$(CoreLibraryDependencies);%(AdditionalDependencies);</AdditionalDependencies>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release Desktop|x64'">
<ClCompile>
@ -188,16 +274,17 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalOptions>/MP20 %(AdditionalOptions)</AdditionalOptions>
<AdditionalIncludeDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include</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>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>discord_game_sdk.dll.lib;freetype.lib;$(CoreLibraryDependencies);%(AdditionalDependencies);discord_game_sdk.dll.lib</AdditionalDependencies>
<AdditionalDependencies>discord_game_sdk.dll.lib;freetype.lib;steam_api64.lib;$(CoreLibraryDependencies);%(AdditionalDependencies);discord_game_sdk.dll.lib</AdditionalDependencies>
</Link>
<PreBuildEvent>
<Command>powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File update_version.ps1 "./version.h"</Command>
@ -206,6 +293,10 @@
<Command>
</Command>
</PostBuildEvent>
<CustomBuildStep>
<Command>
</Command>
</CustomBuildStep>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Emscripten|Win32'">
<Link>
@ -245,8 +336,9 @@
<AdditionalDependencies>$(CoreLibraryDependencies);%(AdditionalDependencies);discord_game_sdk.dll.lib</AdditionalDependencies>
</Link>
<ClCompile>
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<PostBuildEvent>
<Command>powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File ../emscripten_build.ps1</Command>
@ -261,8 +353,9 @@
<AdditionalDependencies>$(CoreLibraryDependencies);%(AdditionalDependencies);discord_game_sdk.dll.lib</AdditionalDependencies>
</Link>
<ClCompile>
<LanguageStandard>stdcpp20</LanguageStandard>
<LanguageStandard>stdcpplatest</LanguageStandard>
<AdditionalIncludeDirectories>C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<PostBuildEvent>
<Command>powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File ../emscripten_debug_build.ps1</Command>
@ -273,6 +366,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="Ability.h" />
<ClInclude Include="AccessoryRowItemDisplay.h" />
<ClInclude Include="Animation.h" />
<ClInclude Include="Attributable.h" />
<ClInclude Include="AttributableStat.h">
@ -287,8 +381,20 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="BombBoom.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Buff.h" />
<ClInclude Include="Bullet.h" />
<ClInclude Include="Bullet.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="HurtDamageInfo.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="IBullet.h" />
<ClInclude Include="BulletTypes.h" />
<ClInclude Include="CharacterAbilityPreviewComponent.h" />
<ClInclude Include="CharacterRotatingDisplay.h" />
@ -304,6 +410,10 @@
<ClInclude Include="AdventuresInLestoria.h" />
<ClInclude Include="DamageNumber.h" />
<ClInclude Include="DEFINES.h" />
<ClInclude Include="Direction.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="discord-files\achievement_manager.h" />
<ClInclude Include="discord-files\activity_manager.h" />
<ClInclude Include="discord-files\application_manager.h" />
@ -328,6 +438,7 @@
</ClInclude>
<ClInclude Include="Effect.h" />
<ClInclude Include="Emitter.h" />
<ClInclude Include="emscripten_compat.h" />
<ClInclude Include="EncountersSpawnListScrollableWindowComponent.h" />
<ClInclude Include="EnhancementStatsLabel.h">
<SubType>
@ -342,6 +453,11 @@
</SubType>
</ClInclude>
<ClInclude Include="Error.h" />
<ClInclude Include="ExpandingRing.h" />
<ClInclude Include="FloatingMenuComponent.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="GameEvent.h">
<SubType>
</SubType>
@ -375,6 +491,10 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="ItemEnchant.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="ItemMapData.h">
<SubType>
</SubType>
@ -387,12 +507,34 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="LoadingScreen.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MenuDefinitions.h" />
<ClInclude Include="MenuItemLabel.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MenuItemLoadoutButton.h" />
<ClInclude Include="MenuType.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Minimap.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="MonsterData.h" />
<ClInclude Include="olcPGEX_SplashScreen.h" />
<ClInclude Include="Overlay.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Pixel.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="PlayerMoneyLabel.h">
<SubType>
</SubType>
@ -475,7 +617,16 @@
<ClInclude Include="Slider.h" />
<ClInclude Include="SoundEffect.h" />
<ClInclude Include="SpawnEncounterLabel.h" />
<ClInclude Include="StageMaskPolygon.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="State.h" />
<ClInclude Include="State_Death.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="State_Dialog.h" />
<ClInclude Include="State_GameHub.h">
<SubType>
</SubType>
@ -496,6 +647,62 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="SteamKeyboardCallbackHandler.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="SteamStatsReceivedHandler.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="steam\isteamapps.h" />
<ClInclude Include="steam\isteamappticket.h" />
<ClInclude Include="steam\isteamclient.h" />
<ClInclude Include="steam\isteamcontroller.h" />
<ClInclude Include="steam\isteamdualsense.h" />
<ClInclude Include="steam\isteamfriends.h" />
<ClInclude Include="steam\isteamgamecoordinator.h" />
<ClInclude Include="steam\isteamgameserver.h" />
<ClInclude Include="steam\isteamgameserverstats.h" />
<ClInclude Include="steam\isteamhtmlsurface.h" />
<ClInclude Include="steam\isteamhttp.h" />
<ClInclude Include="steam\isteaminput.h" />
<ClInclude Include="steam\isteaminventory.h" />
<ClInclude Include="steam\isteammatchmaking.h" />
<ClInclude Include="steam\isteammusic.h" />
<ClInclude Include="steam\isteammusicremote.h" />
<ClInclude Include="steam\isteamnetworking.h" />
<ClInclude Include="steam\isteamnetworkingmessages.h" />
<ClInclude Include="steam\isteamnetworkingsockets.h" />
<ClInclude Include="steam\isteamnetworkingutils.h" />
<ClInclude Include="steam\isteamparentalsettings.h" />
<ClInclude Include="steam\isteamps3overlayrenderer.h" />
<ClInclude Include="steam\isteamremoteplay.h" />
<ClInclude Include="steam\isteamremotestorage.h" />
<ClInclude Include="steam\isteamscreenshots.h" />
<ClInclude Include="steam\isteamugc.h" />
<ClInclude Include="steam\isteamuser.h" />
<ClInclude Include="steam\isteamuserstats.h" />
<ClInclude Include="steam\isteamutils.h" />
<ClInclude Include="steam\isteamvideo.h" />
<ClInclude Include="steam\matchmakingtypes.h" />
<ClInclude Include="steam\steamclientpublic.h" />
<ClInclude Include="steam\steamencryptedappticket.h" />
<ClInclude Include="steam\steamhttpenums.h" />
<ClInclude Include="steam\steamnetworkingfakeip.h" />
<ClInclude Include="steam\steamnetworkingtypes.h" />
<ClInclude Include="steam\steamps3params.h" />
<ClInclude Include="steam\steamtypes.h" />
<ClInclude Include="steam\steamuniverse.h" />
<ClInclude Include="steam\steam_api.h" />
<ClInclude Include="steam\steam_api_common.h" />
<ClInclude Include="steam\steam_api_flat.h" />
<ClInclude Include="steam\steam_api_internal.h" />
<ClInclude Include="steam\steam_gameserver.h" />
<ClInclude Include="TEST_DEFINES.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="TextEntryLabel.h">
<SubType>
</SubType>
@ -504,6 +711,7 @@
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="Tutorial.h" />
<ClInclude Include="VisualNovel.h" />
<ClInclude Include="Test.h" />
<ClInclude Include="Theme.h" />
@ -522,6 +730,31 @@
<ClCompile Include="Ability.cpp" />
<ClCompile Include="Animation.cpp" />
<ClCompile Include="Arrow.cpp" />
<ClCompile Include="ArtificerDisassembleConfirmWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ArtificerDisassembleWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ArtificerEnchantConfirmWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ArtificerEnchantWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ArtificerRefineConfirmWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ArtificerRefineWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ArtificerWindow.cpp" />
<ClCompile Include="AttributableStat.cpp">
<SubType>
</SubType>
@ -534,16 +767,44 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="BearTrap.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="BlacksmithCraftingWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Bullet.cpp" />
<ClCompile Include="Boar.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Bomb.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="BreakingPillar.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Buff.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Bullet.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ExplosiveTrap.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="IBullet.cpp" />
<ClCompile Include="BuyItemWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="CharacterInfoWindow.cpp" />
<ClCompile Include="ClassInfoWindow.cpp" />
<ClCompile Include="CharacterMenuWindow.cpp">
<SubType>
</SubType>
@ -565,7 +826,31 @@
</SubType>
</ClCompile>
<ClCompile Include="AdventuresInLestoria.cpp" />
<ClCompile Include="CreditsWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="DaggerSlash.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="DaggerStab.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="DamageNumber.cpp" />
<ClCompile Include="DeadlyDash.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="DeathMenu.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Debris.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="discord-files\achievement_manager.cpp" />
<ClCompile Include="discord-files\activity_manager.cpp" />
<ClCompile Include="discord-files\application_manager.cpp" />
@ -580,6 +865,10 @@
<ClCompile Include="discord-files\types.cpp" />
<ClCompile Include="discord-files\user_manager.cpp" />
<ClCompile Include="discord-files\voice_manager.cpp" />
<ClCompile Include="Do_Nothing.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="drawutil.cpp" />
<ClCompile Include="DynamicCounter.cpp">
<SubType>
@ -593,7 +882,15 @@
</SubType>
</ClCompile>
<ClCompile Include="FallingDebris.h" />
<ClCompile Include="Feather.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="FireBolt.cpp" />
<ClCompile Include="ForegroundEffect.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Frog.cpp">
<SubType>
</SubType>
@ -607,6 +904,23 @@
</SubType>
</ClCompile>
<ClCompile Include="GameState.cpp" />
<ClCompile Include="Goblin_Boar_Rider.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Goblin_Bomb.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Goblin_Bow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Goblin_Dagger.cpp" />
<ClCompile Include="Hawk.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="InputHelper.cpp">
<SubType>
</SubType>
@ -634,16 +948,37 @@
</ClCompile>
<ClCompile Include="Item.cpp" />
<ClCompile Include="ItemDrop.cpp" />
<ClCompile Include="ItemEnchant.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ItemLoadoutWindow.cpp" />
<ClCompile Include="Key.cpp" />
<ClCompile Include="LargeStone.cpp" />
<ClCompile Include="LargeTornado.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="LevelCompleteWindow.cpp" />
<ClCompile Include="LevitatingRock.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="LightningBolt.cpp" />
<ClCompile Include="LightningBoltEmitter.cpp" />
<ClCompile Include="LoadGameWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="LoadingScreen.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="MainMenuWindow.cpp" />
<ClCompile Include="MajorHawk.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Map.cpp" />
<ClCompile Include="Menu.cpp" />
<ClCompile Include="MenuAnimatedIconButton.h" />
@ -661,19 +996,41 @@
</SubType>
</ClCompile>
<ClCompile Include="Meteor.cpp" />
<ClCompile Include="Minimap.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="NPC.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Overlay.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="OverworldMapLevelWindow.cpp" />
<ClCompile Include="OverworldMenuWindow.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="packkey.cpp" />
<ClCompile Include="PauseMenu.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Pixel.cpp" />
<ClCompile Include="PoisonBottle.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="PurpleEnergyBall.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="RockLaunch.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="RunAway.cpp" />
<ClCompile Include="RunTowards.cpp" />
<ClCompile Include="Pathfinding.cpp" />
@ -701,6 +1058,10 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ShineEffect.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="ShootAfar.cpp" />
<ClCompile Include="SlimeKing.cpp" />
<ClCompile Include="SoundEffect.cpp">
@ -711,6 +1072,23 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="FallingStone.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="SpellCircle.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="StageMaskPolygon.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="State_Death.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="State_Dialog.cpp" />
<ClCompile Include="State_GameHub.cpp">
<SubType>
</SubType>
@ -727,11 +1105,36 @@
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="SteamKeyboardCallbackHandler.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="SteamStatsReceivedHandler.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="StoneGolem.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Stone_Elemental.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="SwordSlash.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Test.cpp" />
<ClCompile Include="Thief.cpp" />
<ClCompile Include="TitleScreen.cpp" />
<ClCompile Include="Tornado.cpp">
<SubType>
</SubType>
</ClCompile>
<ClCompile Include="Trapper.cpp" />
<ClCompile Include="Turret.cpp" />
<ClCompile Include="Tutorial.cpp" />
<ClCompile Include="Unlock.cpp" />
<ClCompile Include="Ursule.cpp" />
<ClCompile Include="UserIDMenu.cpp">
@ -748,19 +1151,22 @@
<ClCompile Include="Witch.cpp" />
<ClCompile Include="Wizard.cpp" />
<ClCompile Include="Wolf.cpp" />
<ClCompile Include="Zephy.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="..\read_debug_log.ps1" />
<None Include="ClassDiagram.cd" />
<None Include="ClassDiagram2.cd" />
<None Include="cpp.hint" />
<None Include="CREDITS" />
<None Include="steam\steam_api.json" />
</ItemGroup>
<ItemGroup>
<Text Include="assets\config\Achievements.txt" />
<Text Include="assets\config\audio\audio.txt" />
<Text Include="assets\config\audio\bgm.txt" />
<Text Include="assets\config\audio\environmentalaudio.txt" />
<Text Include="assets\config\audio\events.txt" />
<Text Include="assets\config\bgm\bgm.txt" />
<Text Include="assets\config\bgm\events.txt" />
<Text Include="assets\config\classes\Ranger.txt" />
<Text Include="assets\config\classes\Thief.txt" />
<Text Include="assets\config\classes\Trapper.txt" />
@ -768,6 +1174,7 @@
<Text Include="assets\config\classes\Witch.txt" />
<Text Include="assets\config\classes\Wizard.txt" />
<Text Include="assets\config\configuration.txt" />
<Text Include="assets\config\credits.txt" />
<Text Include="assets\config\gfx\backdrops.txt" />
<Text Include="assets\config\gfx\gfx.txt" />
<Text Include="assets\config\gfx\themes.txt" />
@ -776,12 +1183,14 @@
<Text Include="assets\config\items\Equipment.txt" />
<Text Include="assets\config\items\ItemCategory.txt" />
<Text Include="assets\config\items\ItemDatabase.txt" />
<Text Include="assets\config\items\ItemEnchants.txt" />
<Text Include="assets\config\items\items.txt" />
<Text Include="assets\config\items\ItemScript.txt" />
<Text Include="assets\config\items\ItemSets.txt" />
<Text Include="assets\config\items\ItemStats.txt" />
<Text Include="assets\config\items\Weapons.txt" />
<Text Include="assets\config\levels.txt" />
<Text Include="assets\config\minimap.txt" />
<Text Include="assets\config\Monsters.txt" />
<Text Include="assets\config\MonsterStrategies.txt" />
<Text Include="assets\config\NPCs.txt" />
@ -794,12 +1203,22 @@
<Text Include="assets\config\shops\Chapter 5 Merchants.txt" />
<Text Include="assets\config\shops\Chapter 6 Merchants.txt" />
<Text Include="assets\config\story\Chapter 1.txt" />
<Text Include="Adventures in Lestoria_Story_Chapter_1 (2).txt" />
<Text Include="Adventures in Lestoria_System_Overview.txt" />
<Text Include="assets\config\story\Chapter 2.txt" />
<Text Include="assets\config\story\Chapter 3.txt" />
<Text Include="assets\config\story\Chapter 4.txt" />
<Text Include="assets\config\story\Chapter 5.txt" />
<Text Include="assets\config\story\Chapter 6.txt" />
<Text Include="Chapter_1_2nd_Boss.txt" />
<Text Include="Chapter_1_Creatures_Part_2.txt" />
<Text Include="Chapter_2_Boss.txt" />
<Text Include="Chapter_2_Monsters.txt" />
<Text Include="Chapter_3_Monsters.txt" />
<Text Include="characters.txt" />
<Text Include="Crawler_2_Bonus_Boss.txt" />
<Text Include="Crawler_Artificer.txt" />
<Text Include="Crawler_System_Overview.txt" />
<Text Include="Crawler_Trapper_Witch_thief.txt" />
<Text Include="debug.log" />
<Text Include="Merchant%27s Items.txt" />
<Text Include="NewClasses.txt" />
<Text Include="InitialConcept.txt" />

@ -88,6 +88,15 @@
<Filter Include="Configurations\Setttings">
<UniqueIdentifier>{c90a78bc-c74d-4609-b758-69320d7741e5}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\steam">
<UniqueIdentifier>{ac44510a-638e-4ae5-8529-2c68dddad459}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Engine">
<UniqueIdentifier>{1b310925-7ec7-4584-8c9c-2154a6e0df80}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Engine">
<UniqueIdentifier>{aaa148fb-5e34-4c35-a5bf-65ee8f2c0fb1}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="olcPixelGameEngine.h">
@ -129,7 +138,7 @@
<ClInclude Include="DEFINES.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Bullet.h">
<ClInclude Include="IBullet.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Class.h">
@ -465,6 +474,207 @@
<ClInclude Include="IconType.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="LoadingScreen.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="State_Death.h">
<Filter>Header Files\State</Filter>
</ClInclude>
<ClInclude Include="Tutorial.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FloatingMenuComponent.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="AccessoryRowItemDisplay.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="MenuItemLabel.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="MenuItemLoadoutButton.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="steam\isteamapps.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamappticket.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamclient.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamcontroller.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamdualsense.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamfriends.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamgamecoordinator.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamgameserver.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamgameserverstats.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamhtmlsurface.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamhttp.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteaminput.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteaminventory.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteammatchmaking.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteammusic.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteammusicremote.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamnetworking.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamnetworkingmessages.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamnetworkingsockets.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamnetworkingutils.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamparentalsettings.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamps3overlayrenderer.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamremoteplay.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamremotestorage.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamscreenshots.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamugc.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamuser.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamuserstats.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamutils.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\isteamvideo.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\matchmakingtypes.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steam_api.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steam_api_common.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steam_api_flat.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steam_api_internal.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steam_gameserver.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steamclientpublic.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steamencryptedappticket.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steamhttpenums.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steamnetworkingfakeip.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steamnetworkingtypes.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steamps3params.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steamtypes.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="steam\steamuniverse.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="SteamKeyboardCallbackHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="emscripten_compat.h">
<Filter>Header Files\steam</Filter>
</ClInclude>
<ClInclude Include="SteamStatsReceivedHandler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Minimap.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="MonsterData.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="BombBoom.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Direction.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Overlay.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="StageMaskPolygon.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Pixel.h">
<Filter>Header Files\Engine</Filter>
</ClInclude>
<ClInclude Include="ExpandingRing.h">
<Filter>Source Files\Effects</Filter>
</ClInclude>
<ClInclude Include="TEST_DEFINES.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Bullet.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="HurtDamageInfo.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="State_Dialog.h">
<Filter>Header Files\State</Filter>
</ClInclude>
<ClInclude Include="ItemEnchant.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Player.cpp">
@ -488,7 +698,7 @@
<ClCompile Include="Effect.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Bullet.cpp">
<ClCompile Include="IBullet.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Ability.cpp">
@ -593,7 +803,7 @@
<ClCompile Include="InventoryConsumableWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="CharacterInfoWindow.cpp">
<ClCompile Include="ClassInfoWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="ClassSelectionWindow.cpp">
@ -812,6 +1022,171 @@
<ClCompile Include="GameSettings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LoadingScreen.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="packkey.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="State_Death.cpp">
<Filter>Source Files\Game States</Filter>
</ClCompile>
<ClCompile Include="DeathMenu.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="Tutorial.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CreditsWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="SwordSlash.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="SteamKeyboardCallbackHandler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SteamStatsReceivedHandler.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Minimap.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Boar.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DaggerSlash.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="DaggerStab.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Do_Nothing.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LevitatingRock.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Stone_Elemental.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Goblin_Dagger.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Goblin_Boar_Rider.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Goblin_Bomb.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Goblin_Bow.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Hawk.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Zephy.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="MajorHawk.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="Tornado.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Overlay.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Debris.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ForegroundEffect.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LargeTornado.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="StoneGolem.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="BreakingPillar.cpp">
<Filter>Source Files\Monster Strategies</Filter>
</ClCompile>
<ClCompile Include="StageMaskPolygon.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Pixel.cpp">
<Filter>Source Files\Engine</Filter>
</ClCompile>
<ClCompile Include="Feather.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SpellCircle.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="LargeStone.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="Bomb.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="RockLaunch.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="FallingStone.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="ShineEffect.cpp">
<Filter>Source Files\Effects</Filter>
</ClCompile>
<ClCompile Include="DeadlyDash.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="Bullet.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="BearTrap.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="Buff.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ExplosiveTrap.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="PurpleEnergyBall.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="PoisonBottle.cpp">
<Filter>Source Files\Bullet Types</Filter>
</ClCompile>
<ClCompile Include="State_Dialog.cpp">
<Filter>Source Files\Game States</Filter>
</ClCompile>
<ClCompile Include="ArtificerWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<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>
<ClCompile Include="ArtificerEnchantConfirmWindow.cpp">
<Filter>Source Files\Interface</Filter>
</ClCompile>
<ClCompile Include="ItemEnchant.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="cpp.hint" />
@ -819,6 +1194,11 @@
<None Include="CREDITS">
<Filter>Documentation</Filter>
</None>
<None Include="ClassDiagram.cd" />
<None Include="steam\steam_api.json">
<Filter>Header Files\steam</Filter>
</None>
<None Include="..\read_debug_log.ps1" />
</ItemGroup>
<ItemGroup>
<Text Include="InitialConcept.txt">
@ -926,10 +1306,6 @@
<Text Include="assets\config\items\Accessories.txt">
<Filter>Configurations\Items</Filter>
</Text>
<Text Include="Adventures in Lestoria_Story_Chapter_1 (2).txt" />
<Text Include="Adventures in Lestoria_System_Overview.txt" />
<Text Include="assets\config\bgm\bgm.txt" />
<Text Include="assets\config\bgm\events.txt" />
<Text Include="assets\config\audio\audio.txt">
<Filter>Configurations\Audio</Filter>
</Text>
@ -966,6 +1342,52 @@
<Text Include="assets\config\settings\input.txt">
<Filter>Configurations\Setttings</Filter>
</Text>
<Text Include="assets\config\credits.txt">
<Filter>Configurations</Filter>
</Text>
<Text Include="assets\config\Achievements.txt">
<Filter>Configurations</Filter>
</Text>
<Text Include="assets\config\minimap.txt">
<Filter>Configurations</Filter>
</Text>
<Text Include="Chapter_2_Monsters.txt" />
<Text Include="Crawler_2_Bonus_Boss.txt">
<Filter>Documentation\Mechanics</Filter>
</Text>
<Text Include="Chapter_2_Boss.txt">
<Filter>Documentation\Mechanics</Filter>
</Text>
<Text Include="debug.log">
<Filter>Documentation</Filter>
</Text>
<Text Include="Chapter_3_Monsters.txt">
<Filter>Documentation\Mechanics</Filter>
</Text>
<Text Include="Crawler_Trapper_Witch_thief.txt">
<Filter>Documentation</Filter>
</Text>
<Text Include="Crawler_Artificer.txt">
<Filter>Documentation\Mechanics</Filter>
</Text>
<Text Include="assets\config\story\Chapter 2.txt">
<Filter>Configurations\Story</Filter>
</Text>
<Text Include="assets\config\story\Chapter 3.txt">
<Filter>Configurations\Story</Filter>
</Text>
<Text Include="assets\config\story\Chapter 4.txt">
<Filter>Configurations\Story</Filter>
</Text>
<Text Include="assets\config\story\Chapter 5.txt">
<Filter>Configurations\Story</Filter>
</Text>
<Text Include="assets\config\story\Chapter 6.txt">
<Filter>Configurations\Story</Filter>
</Text>
<Text Include="assets\config\items\ItemEnchants.txt">
<Filter>Configurations\Items</Filter>
</Text>
</ItemGroup>
<ItemGroup>
<Image Include="assets\heart.ico">

File diff suppressed because it is too large Load Diff

@ -55,12 +55,37 @@ All rights reserved.
#include "olcPGEX_SplashScreen.h"
#include "olcPixelGameEngine.h"
#include "DynamicCounter.h"
#include "UndefKeys.h"
#include "Minimap.h"
#include "Overlay.h"
#include <variant>
#define CreateBullet(type) BULLET_LIST.push_back(std::make_unique<type>(type
class SteamKeyboardCallbackHandler;
class SteamStatsReceivedHandler;
#define CreateBullet(type) INCLUDE_BULLET_LIST \
BULLET_LIST.push_back(std::make_unique<type>(type
#define EndBullet ));
using HurtReturnValue=bool;
using MonsterHurtList=std::vector<std::pair<Monster*,HurtReturnValue>>;
using HurtList=std::vector<std::pair<std::variant<Monster*,Player*>,HurtReturnValue>>;
using StackCount=uint8_t;
using MarkTime=float;
enum class HurtType{
PLAYER=0b01,
MONSTER=0b10,
};
enum class KnockbackCondition{
KNOCKBACK_HURT_TARGETS, //Knockback only targets that took damage.
KNOCKBACK_ALL_TARGETS, //Knockback all targets, even if they were invulnerable or immovable.
KNOCKBACK_UNHURT_TARGETS, //Knockback only targets that did not get hit.
};
namespace PlayerTests{
class PlayerTest;
}
class AiL : public olc::PixelGameEngine
{
@ -69,9 +94,17 @@ class AiL : public olc::PixelGameEngine
friend class SaveFile;
friend class sig::Animation;
friend class Audio;
friend class Minimap;
friend class MonsterTests::MonsterTest;
friend class PlayerTests::PlayerTest;
friend class ItemTests::ItemTest;
std::unique_ptr<Player>player;
SplashScreen splash;
public:
enum MusicChange{
NO_MUSIC_CHANGE,
PLAY_LEVEL_MUSIC,
};
Pathfinding pathfinder;
static InputGroup KEY_BACK;
static InputGroup KEY_CONFIRM;
@ -100,23 +133,27 @@ public:
static InputGroup KEY_SCROLLRIGHT;
static InputGroup KEY_SCROLLHORZ;
static InputGroup KEY_SCROLLHORZ_L;
static InputGroup KEY_SCROLLHORZ_R;
static InputGroup KEY_SCROLLVERT;
static InputGroup KEY_SCROLLVERT_R;
static InputGroup KEY_SCROLLVERT_L;
static InputGroup KEY_SCROLL;
static InputGroup KEY_SHOULDER;
static InputGroup KEY_SHOULDER2;
static InputGroup KEY_CHANGE_LOADOUT;
static InputGroup KEY_ENTER;
static InputGroup KEY_MOUSE_RIGHT;
static InputGroup KEY_TOGGLE_MAP;
static float SIZE_CHANGE_SPEED;
double levelTime;
double levelTime=0.;
Camera2D camera;
std::map<MapName,Map>MAP_DATA;
private:
std::vector<std::unique_ptr<Effect>>foregroundEffects,backgroundEffects,foregroundEffectsToBeInserted,backgroundEffectsToBeInserted;
std::vector<TileRenderData*>tilePreparationList,tileForegroundList;
std::vector<TileRenderData*>tilesWithCollision,tilesWithoutCollision;
std::vector<int>dropsBeforeLower,dropsAfterLower,dropsBeforeUpper,dropsAfterUpper;
std::vector<ZoneData>endZones,upperEndZones;
std::vector<vf2d>circleCooldownPoints;
@ -127,6 +164,7 @@ private:
float lastWorldShakeAdjust=0;
vf2d worldShakeVel={};
const float WORLD_SHAKE_ADJUST_MAX_TIME=0.4f;
MapName previousLevel="CAMPAIGN_1_1";
MapName currentLevel="CAMPAIGN_1_1";
std::vector<TileGroup>foregroundTileGroups;
std::vector<TileGroup>upperForegroundTileGroups;
@ -135,7 +173,7 @@ private:
void InitializeClasses();
int DEBUG_PATHFINDING=0;
std::vector<Monster*>monstersBeforeLower,monstersAfterLower,monstersBeforeUpper,monstersAfterUpper;
std::vector<Bullet*>bulletsLower,bulletsUpper;
std::vector<IBullet*>bulletsLower,bulletsUpper;
std::vector<Effect*>backgroundEffectsLower,backgroundEffectsUpper,foregroundEffectsLower,foregroundEffectsUpper;
float reflectionUpdateTimer=0;
float reflectionStepTime=0;
@ -152,6 +190,7 @@ private:
States::State transitionState=States::State::GAME_RUN;
bool gameEnd=false;
std::vector<Monster>monstersToBeSpawned;
bool aMonsterIsMarkedForDeletion=false; //DO NOT MODIFY DIRECTLY! Use AMonsterIsMarkedForDeletion() instead!
time_t gameStarted;
std::function<void(std::string_view)>responseCallback;
float fadeInDuration=0;
@ -164,29 +203,50 @@ private:
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 paused=false;
bool gameInitialized=false;
ResourcePack gamepack;
uint8_t mosaicEffectTransition=1U;
float saveGameDisplayTime=0.f;
float loadingWaitTime=0.f;
bool displayHud=true;
float vignetteDisplayTime=0.f;
bool savingFile=false;
bool prevStageCompleted=false;
vf2d windSpd{};
void ValidateGameStatus();
void _PrepareLevel(MapName map,MusicChange changeMusic);
//This function assigns the mode tile colors of each loaded tileset.
void ComputeModeColors(TilesetData&tileset);
#ifndef __EMSCRIPTEN__
::discord::Result SetupDiscord();
#endif
Audio audioEngine;
SteamKeyboardCallbackHandler*steamKeyboardCallbackListener=nullptr;
SteamStatsReceivedHandler*steamStatsReceivedHandlerListener=nullptr;
std::optional<vf2d>bossIndicatorPos{};
bool steamAPIEnabled{true};
Overlay hudOverlay{"pixel.png",BLANK};
float targetZoom{1.f};
float zoomAdjustSpeed{0.1f};
std::vector<std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>>lockOnTargets;
float lastLockOnTargetTime{};
public:
AiL();
AiL(bool testingMode=false);
bool OnUserCreate() override;
bool OnUserUpdate(float fElapsedTime) override;
bool OnUserDestroy() override;
void GetAnyKeyPress(Key key)override final;
void GetAnyKeyRelease(Key key)override final;
void GetAnyMousePress(int32_t mouseButton)override final;
void GetAnyMouseHeld(int32_t mouseButton)override final;
void GetAnyMouseRelease(int32_t mouseButton)override final;
void UsingSteamAPI(const bool usingSteam);
public:
geom2d::rect<float>NO_COLLISION={{0.f,0.f,},{0.f,0.f}};
TileTransformedView view;
void InitializeLevel(std::string mapFile,MapName map);
void LoadLevel(MapName map);
void LoadLevel(MapName map,MusicChange changeMusic=PLAY_LEVEL_MUSIC);
void HandleUserInput(float fElapsedTime);
void UpdateCamera(float fElapsedTime);
void UpdateEffects(float fElapsedTime);
@ -194,10 +254,20 @@ public:
void RenderWorld(float fElapsedTime);
void RenderHud();
void RenderMenu();
bool MenuClicksDeactivated()const;
void AddEffect(std::unique_ptr<Effect>foreground,std::unique_ptr<Effect>background);
//If back is true, places the effect in the background
void AddEffect(std::unique_ptr<Effect>foreground,bool back=false);
const MonsterHurtList HurtEnemies(vf2d pos,float radius,int damage,bool upperLevel,float z);
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!
const HurtList HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE)const;
// angle: The central angle where the arc will extend from.
// sweepAngle: The amount of radians to extend in both directions from the central angle.
// NOTE: This function will also add any enemies that were hit into the hit list!
const HurtList HurtConeNotHit(vf2d pos,float radius,float angle,float sweepAngle,int damage,HitList&hitList,bool upperLevel,float z,const HurtType hurtTargets,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE)const;
void ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtType knockbackTargets)const;
void ProximityKnockback(const vf2d pos,const float radius,const float knockbackAmt,const HurtList&knockbackTargets,const KnockbackCondition condition=KnockbackCondition::KNOCKBACK_HURT_TARGETS)const;
vf2d GetWorldMousePos();
bool LeftHeld();
bool RightHeld();
@ -211,21 +281,22 @@ public:
bool RightReleased();
bool UpReleased();
bool DownReleased();
Player*GetPlayer();
Player*const GetPlayer()const;
void SetupWorldShake(float duration);
//tileID is the tile number from the tilesets.
bool IsForegroundTile(TilesheetData sheet,int tileID);
//tileID is the tile number from the tilesets.
bool IsUpperForegroundTile(int tileID);
//tileID is the tile number from the tilesets.
TilesheetData GetTileSheet(MapName map,int tileID);
const TilesheetData GetTileSheet(MapName map,int tileID)const;
//Gets the rectangle of the tile collision at this tile. If upperLevel is set to true, the collision tile must be in a Bridge class layer for the tile to hit. Also, zones containing LowerBridgeCollision will apply when upperLevel is set to false.
geom2d::rect<float>GetTileCollision(MapName map,vf2d pos,bool upperLevel=false);
const geom2d::rect<float>GetTileCollision(MapName map,vf2d pos,bool upperLevel=false)const;
Pixel GetTileColor(MapName map,vf2d pos,bool upperLevel=false);
//Checks if the point resides inside of a collision tile.
bool HasTileCollision(MapName map,vf2d pos,bool upperLevel=false);
const MapName&GetCurrentLevel()const;
bool IsBridgeLayer(LayerTag&layer);
std::map<std::string,std::vector<ZoneData>>&GetZoneData(MapName map);
bool IsOverlayLayer(LayerTag&layer);
void PopulateRenderLists();
void ChangePlayerClass(Class cl);
std::string GetString(std::string key);
@ -241,7 +312,7 @@ public:
void RenderTile(vi2d pos,TilesheetData tileSheet,int tileSheetIndex,vi2d tileSheetPos);
void RenderTile(TileRenderData&tileSheet,Pixel col);
bool IsReflectiveTile(TilesheetData tileSheet,int tileID);
void SpawnMonster(vf2d pos,MonsterData&data,bool upperLevel=false,bool isBossSpawn=false); //Queues a monster for spawning on the next frame.
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 DrawSquarePie(vf2d center,float radius,float degreesCut,Pixel col);
void RenderCooldowns();
@ -261,8 +332,10 @@ public:
const std::string_view GetCurrentMapDisplayName()const;
int GetCurrentChapter();
void SetChapter(int chapter);
const std::weak_ptr<Item>GetLoadoutItem(int slot);
const std::weak_ptr<Item>GetLoadoutItem(int slot); //Slot range is 0-2.
void SetLoadoutItem(int slot,std::string itemName);
int GetLoadoutSize()const;
void RestockLoadoutItems();
//Returns true if the item can be used (we have >0 of it)
bool UseLoadoutItem(int slot);
//Blanks out this loadout item.
@ -272,7 +345,7 @@ public:
void EndGame();
void UpdateDiscordStatus(std::string levelName,std::string className);
void InitializePlayerLevelCap();
void ResetGame();
void ResetGame(bool changeToMainMenu=true);
void OnRequestCompleted(const std::string_view receivedData)const override;
void DisableFadeIn(const bool disable);
//vi2d provides a tile in world coords.
@ -286,10 +359,33 @@ public:
void AddZone(const std::string_view zoneName,const ZoneData&zone);
//Returns the last time the mouse was moved or interacted with.
const float LastMouseMovement()const;
void PauseGame();
void ResumeGame();
const bool GameInitialized()const;
rcode LoadResource(Renderable&renderable,std::string_view imgPath,bool filter=false,bool clamp=true);
void UpdateMonsters();
void ActivateActionSetForAllControllers(InputActionSetHandle_t actionSetHandle);
const float GetEncounterDuration()const;
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.
const bool PreviousStageCompleted()const;
void SetCompletedStageFlag();
void ResetCompletedStageFlag();
void UpdateEntities();
Minimap minimap;
void AMonsterIsMarkedForDeletion(); //The way this is implemented is that monsters marked for deletion will cause the monster update loop to detect there's at least one or more monsters that must be deleted and will call erase_if on the list at the end of the iteration loop.
void SetBossIndicatorPos(const vf2d pos);
void SetOverlay(std::string animationName,Pixel overlayCol);
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);
struct TileGroupData{
vi2d tilePos;

@ -39,6 +39,7 @@ All rights reserved.
#include "AdventuresInLestoria.h"
#include "DEFINES.h"
#include "safemap.h"
#include <ranges>
INCLUDE_game
INCLUDE_ANIMATION_DATA
@ -46,6 +47,8 @@ INCLUDE_DATA
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}});
@ -61,25 +64,25 @@ void sig::Animation::InitializeAnimations(){
};
auto SetupClassWalkIdleAnimations=[&](Renderable&sheet,std::string className){
Animate2D::FrameSequence pl_walk_s{0.2f};
Animate2D::FrameSequence pl_walk_s{"Player.WalkingFrameSpd"_F};
pl_walk_s.AddFrame({&sheet,{vi2d{0,0}*24,{24,24}}});
pl_walk_s.AddFrame({&sheet,{vi2d{1,0}*24,{24,24}}});
pl_walk_s.AddFrame({&sheet,{vi2d{0,0}*24,{24,24}}});
pl_walk_s.AddFrame({&sheet,{vi2d{2,0}*24,{24,24}}});
ANIMATION_DATA[className+"_WALK_S"]=pl_walk_s;
Animate2D::FrameSequence pl_walk_e{0.2f};
Animate2D::FrameSequence pl_walk_e{"Player.WalkingFrameSpd"_F};
pl_walk_e.AddFrame({&sheet,{vi2d{0,3}*24,{24,24}}});
pl_walk_e.AddFrame({&sheet,{vi2d{1,3}*24,{24,24}}});
pl_walk_e.AddFrame({&sheet,{vi2d{0,3}*24,{24,24}}});
pl_walk_e.AddFrame({&sheet,{vi2d{2,3}*24,{24,24}}});
ANIMATION_DATA[className+"_WALK_E"]=pl_walk_e;
Animate2D::FrameSequence pl_walk_w{0.2f};
Animate2D::FrameSequence pl_walk_w{"Player.WalkingFrameSpd"_F};
pl_walk_w.AddFrame({&sheet,{vi2d{0,2}*24,{24,24}}});
pl_walk_w.AddFrame({&sheet,{vi2d{1,2}*24,{24,24}}});
pl_walk_w.AddFrame({&sheet,{vi2d{0,2}*24,{24,24}}});
pl_walk_w.AddFrame({&sheet,{vi2d{2,2}*24,{24,24}}});
ANIMATION_DATA[className+"_WALK_W"]=pl_walk_w;
Animate2D::FrameSequence pl_walk_n{0.2f};
Animate2D::FrameSequence pl_walk_n{"Player.WalkingFrameSpd"_F};
pl_walk_n.AddFrame({&sheet,{vi2d{0,1}*24,{24,24}}});
pl_walk_n.AddFrame({&sheet,{vi2d{1,1}*24,{24,24}}});
pl_walk_n.AddFrame({&sheet,{vi2d{0,1}*24,{24,24}}});
@ -209,10 +212,147 @@ void sig::Animation::InitializeAnimations(){
}
ANIMATION_DATA["WIZARD_CAST_W"]=pl_wizard_cast_w;
//Thief animations.
SetupClassWalkIdleAnimations(GFX["nico-thief.png"],"THIEF");
Animate2D::FrameSequence pl_thief_swing_s(0.05f),pl_thief_swing_n(0.05f),pl_thief_swing_e(0.05f),pl_thief_swing_w(0.05f);
Animate2D::FrameSequence pl_thief_deadlydash_s(0.1f,Animate2D::Style::Repeat),pl_thief_deadlydash_n(0.1f,Animate2D::Style::Repeat),pl_thief_deadlydash_e(0.1f,Animate2D::Style::Repeat),pl_thief_deadlydash_w(0.1f,Animate2D::Style::Repeat);
for (int i=0;i<4;i++){
pl_thief_swing_s.AddFrame({&GFX["nico-thief.png"],{vi2d{4+i,0}*24,{24,24}}});
pl_thief_deadlydash_s.AddFrame({&GFX["nico-thief.png"],{vi2d{i,4}*24,{24,24}}});
}
for (int i=0;i<4;i++){
pl_thief_swing_n.AddFrame({&GFX["nico-thief.png"],{vi2d{4+i,1}*24,{24,24}}});
pl_thief_deadlydash_n.AddFrame({&GFX["nico-thief.png"],{vi2d{i,5}*24,{24,24}}});
}
for (int i=0;i<4;i++){
pl_thief_swing_w.AddFrame({&GFX["nico-thief.png"],{vi2d{4+i,2}*24,{24,24}}});
pl_thief_deadlydash_w.AddFrame({&GFX["nico-thief.png"],{vi2d{i,6}*24,{24,24}}});
}
for (int i=0;i<4;i++){
pl_thief_swing_e.AddFrame({&GFX["nico-thief.png"],{vi2d{4+i,3}*24,{24,24}}});
pl_thief_deadlydash_e.AddFrame({&GFX["nico-thief.png"],{vi2d{i,7}*24,{24,24}}});
}
ANIMATION_DATA["THIEF_SWINGSWORD_N"]=pl_thief_swing_n;
ANIMATION_DATA["THIEF_SWINGSWORD_E"]=pl_thief_swing_e;
ANIMATION_DATA["THIEF_SWINGSWORD_S"]=pl_thief_swing_s;
ANIMATION_DATA["THIEF_SWINGSWORD_W"]=pl_thief_swing_w;
ANIMATION_DATA["THIEF_DEADLYDASH_N"]=pl_thief_deadlydash_n;
ANIMATION_DATA["THIEF_DEADLYDASH_E"]=pl_thief_deadlydash_e;
ANIMATION_DATA["THIEF_DEADLYDASH_S"]=pl_thief_deadlydash_s;
ANIMATION_DATA["THIEF_DEADLYDASH_W"]=pl_thief_deadlydash_w;
//Trapper animations
SetupClassWalkIdleAnimations(GFX["nico-trapper.png"],"TRAPPER");
Animate2D::FrameSequence pl_trapper_shoot_s,pl_trapper_shoot_n,pl_trapper_shoot_e,pl_trapper_shoot_w;
Animate2D::FrameSequence pl_trapper_setTrap_s,pl_trapper_setTrap_n,pl_trapper_setTrap_e,pl_trapper_setTrap_w;
for(int i=0;i<3;i++){
pl_trapper_shoot_s.AddFrame({&GFX["nico-trapper.png"],{vi2d{3+i,0}*24,{24,24}}});
pl_trapper_shoot_n.AddFrame({&GFX["nico-trapper.png"],{vi2d{3+i,1}*24,{24,24}}});
pl_trapper_shoot_e.AddFrame({&GFX["nico-trapper.png"],{vi2d{3+i,3}*24,{24,24}}});
pl_trapper_shoot_w.AddFrame({&GFX["nico-trapper.png"],{vi2d{3+i,2}*24,{24,24}}});
}
for(int i:std::ranges::iota_view(0,5)){
pl_trapper_setTrap_s.AddFrame({&GFX["nico-trapper.png"],{vi2d{1+i,4}*24,{24,24}}});
pl_trapper_setTrap_n.AddFrame({&GFX["nico-trapper.png"],{vi2d{1+i,5}*24,{24,24}}});
int frameInd{i};
if(i==4)frameInd--; //One less frame for East and West facing sprites.
pl_trapper_setTrap_e.AddFrame({&GFX["nico-trapper.png"],{vi2d{1+frameInd,6}*24,{24,24}}});
pl_trapper_setTrap_w.AddFrame({&GFX["nico-trapper.png"],{vi2d{1+frameInd,7}*24,{24,24}}});
}
ANIMATION_DATA["TRAPPER_SHOOT_S"]=pl_trapper_shoot_s;
ANIMATION_DATA["TRAPPER_SHOOT_N"]=pl_trapper_shoot_n;
ANIMATION_DATA["TRAPPER_SHOOT_E"]=pl_trapper_shoot_e;
ANIMATION_DATA["TRAPPER_SHOOT_W"]=pl_trapper_shoot_w;
ANIMATION_DATA["TRAPPER_SETTRAP_S"]=pl_trapper_setTrap_s;
ANIMATION_DATA["TRAPPER_SETTRAP_N"]=pl_trapper_setTrap_n;
ANIMATION_DATA["TRAPPER_SETTRAP_E"]=pl_trapper_setTrap_e;
ANIMATION_DATA["TRAPPER_SETTRAP_W"]=pl_trapper_setTrap_w;
//Witch animations
SetupClassWalkIdleAnimations(GFX["nico-witch.png"],"WITCH");
Animate2D::FrameSequence pl_witch_idle_attack_s;
pl_witch_idle_attack_s.AddFrame({&GFX["nico-witch.png"],{vi2d{4,0}*24,{24,24}}});
ANIMATION_DATA["WITCH_IDLE_ATTACK_S"]=pl_witch_idle_attack_s;
Animate2D::FrameSequence pl_witch_idle_attack_e;
pl_witch_idle_attack_e.AddFrame({&GFX["nico-witch.png"],{vi2d{4,3}*24,{24,24}}});
ANIMATION_DATA["WITCH_IDLE_ATTACK_E"]=pl_witch_idle_attack_e;
Animate2D::FrameSequence pl_witch_idle_attack_w;
pl_witch_idle_attack_w.AddFrame({&GFX["nico-witch.png"],{vi2d{4,2}*24,{24,24}}});
ANIMATION_DATA["WITCH_IDLE_ATTACK_W"]=pl_witch_idle_attack_w;
Animate2D::FrameSequence pl_witch_idle_attack_n;
pl_witch_idle_attack_n.AddFrame({&GFX["nico-witch.png"],{vi2d{4,1}*24,{24,24}}});
ANIMATION_DATA["WITCH_IDLE_ATTACK_N"]=pl_witch_idle_attack_n;
Animate2D::FrameSequence pl_witch_attack_s(0.2f);
for(int i=0;i<3;i++){
pl_witch_attack_s.AddFrame({&GFX["nico-witch.png"],{vi2d{4+i,0}*24,{24,24}}});
if(i==1){
pl_witch_attack_s.AddFrame({&GFX["nico-witch.png"],{vi2d{4,0}*24,{24,24}}});
}
}
ANIMATION_DATA["WITCH_ATTACK_S"]=pl_witch_attack_s;
Animate2D::FrameSequence pl_witch_attack_e(0.2f);
for(int i=0;i<3;i++){
pl_witch_attack_e.AddFrame({&GFX["nico-witch.png"],{vi2d{4+i,3}*24,{24,24}}});
if(i==1){
pl_witch_attack_e.AddFrame({&GFX["nico-witch.png"],{vi2d{4,3}*24,{24,24}}});
}
}
ANIMATION_DATA["WITCH_ATTACK_E"]=pl_witch_attack_e;
Animate2D::FrameSequence pl_witch_attack_w(0.2f);
for(int i=0;i<3;i++){
pl_witch_attack_w.AddFrame({&GFX["nico-witch.png"],{vi2d{4+i,2}*24,{24,24}}});
if(i==1){
pl_witch_attack_w.AddFrame({&GFX["nico-witch.png"],{vi2d{4,2}*24,{24,24}}});
}
}
ANIMATION_DATA["WITCH_ATTACK_W"]=pl_witch_attack_w;
Animate2D::FrameSequence pl_witch_attack_n(0.2f);
for(int i=0;i<3;i++){
pl_witch_attack_n.AddFrame({&GFX["nico-witch.png"],{vi2d{4+i,1}*24,{24,24}}});
if(i==1){
pl_witch_attack_n.AddFrame({&GFX["nico-witch.png"],{vi2d{4,1}*24,{24,24}}});
}
}
ANIMATION_DATA["WITCH_ATTACK_N"]=pl_witch_attack_n;
Animate2D::FrameSequence pl_witch_cast_s(0.1f);
for(int i=0;i<2;i++){
pl_witch_cast_s.AddFrame({&GFX["nico-witch.png"],{vi2d{7+i,0}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAST_S"]=pl_witch_cast_s;
Animate2D::FrameSequence pl_witch_cast_e(0.1f);
for(int i=0;i<2;i++){
pl_witch_cast_e.AddFrame({&GFX["nico-witch.png"],{vi2d{7+i,3}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAST_E"]=pl_witch_cast_e;
Animate2D::FrameSequence pl_witch_cast_n(0.1f);
for(int i=0;i<2;i++){
pl_witch_cast_n.AddFrame({&GFX["nico-witch.png"],{vi2d{7+i,1}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAST_N"]=pl_witch_cast_n;
Animate2D::FrameSequence pl_witch_cast_w(0.1f);
for(int i=0;i<2;i++){
pl_witch_cast_w.AddFrame({&GFX["nico-witch.png"],{vi2d{7+i,2}*24,{24,24}}});
}
ANIMATION_DATA["WITCH_CAST_W"]=pl_witch_cast_w;
Animate2D::FrameSequence pl_witch_transform_s(0.1f);
pl_witch_transform_s.AddFrame({&GFX["nico-witch.png"],{vi2d{0,4}*24,{24,24}}});
Animate2D::FrameSequence pl_witch_transform_n(0.1f);
pl_witch_transform_n.AddFrame({&GFX["nico-witch.png"],{vi2d{0,5}*24,{24,24}}});
Animate2D::FrameSequence pl_witch_transform_w(0.1f);
pl_witch_transform_w.AddFrame({&GFX["nico-witch.png"],{vi2d{0,6}*24,{24,24}}});
Animate2D::FrameSequence pl_witch_transform_e(0.1f);
pl_witch_transform_e.AddFrame({&GFX["nico-witch.png"],{vi2d{0,7}*24,{24,24}}});
ANIMATION_DATA["WITCH_TRANSFORM_S"]=pl_witch_transform_s;
ANIMATION_DATA["WITCH_TRANSFORM_N"]=pl_witch_transform_n;
ANIMATION_DATA["WITCH_TRANSFORM_W"]=pl_witch_transform_w;
ANIMATION_DATA["WITCH_TRANSFORM_E"]=pl_witch_transform_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});
CreateHorizontalAnimationSequence("sonicslash.png",4,{60,60},{0.04f,Animate2D::Style::OneShot});
CreateHorizontalAnimationSequence("swordslash.png",3,{24,24},{0.05f,Animate2D::Style::OneShot});
CreateStillAnimation("energy_bolt.png",{24,24});
@ -232,6 +372,13 @@ void sig::Animation::InitializeAnimations(){
CreateStillAnimation("chain_lightning.png",{1,9});
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});
CreateHorizontalAnimationSequence("goblin_bomb.png",4,{24,24},AnimationData{.frameDuration{0.2f},.style{Animate2D::Style::PingPong}});
CreateHorizontalAnimationSequence("goblin_bomb_fuse.png",4,{24,24},AnimationData{.frameDuration{1.f},.style{Animate2D::Style::OneShot}});
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}});
CreateStillAnimation("meteor.png",{192,192});
@ -245,10 +392,42 @@ void sig::Animation::InitializeAnimations(){
CreateStillAnimation("laser.png",{5,1});
CreateStillAnimation("range_indicator.png",{24,24});
Animate2D::FrameSequence goblin_bow_mount_n,goblin_bow_mount_e,goblin_bow_mount_s,goblin_bow_mount_w;
Animate2D::FrameSequence goblin_bow_attack_n,goblin_bow_attack_e,goblin_bow_attack_s,goblin_bow_attack_w;
//Idle sequences for the mounted boar bow goblin.
for(int animationIndex=0;animationIndex<4;animationIndex++){
Animate2D::FrameSequence mountAnimation{0.6f};
for(int i=0;i<2;i++){
mountAnimation.AddFrame(Animate2D::Frame{&GFX["monsters/commercial_assets/Goblin (Bow)_foreground.png"],{{i*32,animationIndex*32},{32,32}}});
}
ANIMATION_DATA[std::format("GOBLIN_BOW_MOUNTED_{}",animationIndex)]=mountAnimation;
}
//Shooting sequences for the mounted boar bow goblin.
for(int animationIndex=0;animationIndex<4;animationIndex++){
Animate2D::FrameSequence mountShootAnimation{0.06f,Animate2D::Style::OneShot};
for(int i=0;i<4;i++){
mountShootAnimation.AddFrame(Animate2D::Frame{&GFX["monsters/commercial_assets/Goblin (Bow)_foreground.png"],{{i*32,animationIndex*32+4*32},{32,32}}});
}
ANIMATION_DATA[std::format("GOBLIN_BOW_ATTACK_{}",animationIndex)]=mountShootAnimation;
}
#pragma region Trapper Target Mark Debuff
AnimationData targetAnimData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}};
Animate2D::FrameSequence targetAnim(targetAnimData.frameDuration,targetAnimData.style);
targetAnim.AddFrame({&GFX["target.png"],{{int(0*24),0},{24,24}}});
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;
#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}});
for(auto&dat:GFX){
std::string imgFile=dat.first;
if(!ANIMATION_DATA.count(imgFile)){
std::cout<<"WARNING! Animation data for "<<imgFile<<" not found! Auto-generating..."<<std::endl;
LOG("WARNING! Animation data for "<<imgFile<<" not found! Auto-generating...");
CreateStillAnimation(imgFile,GFX[imgFile].Sprite()->Size());
}
}

@ -45,32 +45,65 @@ All rights reserved.
INCLUDE_game
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*"Ranger.Auto Attack.ArrowSpd"_F),
:finalDistance(geom2d::line(pos,targetPos).length()*1.2f),acc(PI/2*250),targetPos(targetPos),
Bullet(pos,vel,radius,damage,
"arrow.png",upperLevel,false,INFINITE,true,friendly,col){}
Arrow::Arrow(vf2d pos,vf2d targetPos,vf2d vel,const std::string_view gfx,float radius,int damage,bool upperLevel,bool friendly,Pixel col)
:finalDistance(geom2d::line(pos,targetPos).length()*1.2f),acc(PI/2*250),targetPos(targetPos),
Bullet(pos,vel,radius,damage,std::string(gfx),upperLevel,false,INFINITE,true,friendly,col){}
void Arrow::Update(float fElapsedTime){
float speed=vel.mag();
travelDistance+=speed*fElapsedTime;
vel.y+=acc*fElapsedTime;
if(!deactivated&&travelDistance>=finalDistance){
deactivated=true;
if(IsActivated()&&travelDistance>=finalDistance){
fadeOutTime=0.2f;
}
}
bool Arrow::PlayerHit(Player*player)
const vf2d Arrow::PointToBestTargetPath(const uint8_t perceptionLevel){
if(perceptionLevel>90)ERR(std::format("WARNING! Perception level {} provided. Acceptable range is 0-90.",perceptionLevel));
Arrow copiedArrow{*this};
float closestDist=std::numeric_limits<float>::max();
vf2d closestVel{};
for(float angle=util::degToRad(-perceptionLevel);angle<=util::degToRad(perceptionLevel);angle+=util::degToRad(1.f)){
Arrow simulatedArrow{copiedArrow};
vf2d simulatedAimingDir=simulatedArrow.vel.polar();
simulatedAimingDir.y+=angle;
simulatedArrow.vel=simulatedAimingDir.cart();
vf2d originalSimulatedShootingAngle=simulatedArrow.vel;
while(simulatedArrow.IsActivated()){
simulatedArrow.SimulateUpdate(1/30.f);
float distToPlayer=geom2d::line<float>(simulatedArrow.pos,game->GetPlayer()->GetPos()).length();
if(distToPlayer<closestDist){
closestDist=distToPlayer;
closestVel=originalSimulatedShootingAngle;
}
}
}
if(closestVel==vf2d{0,0})ERR("WARNING! We didn't find a valid path of flight for the Arrow! THIS SHOULD NOT BE HAPPENING!");
vel=closestVel;
return vel;
}
BulletDestroyState Arrow::PlayerHit(Player*player)
{
deactivated=true;
fadeOutTime=0.2f;
game->AddEffect(std::make_unique<Effect>(player->GetPos(),0,"splash_effect.png",upperLevel,player->GetSizeMult(),0.25));
return false;
return BulletDestroyState::KEEP_ALIVE;
}
bool Arrow::MonsterHit(Monster& monster)
BulletDestroyState Arrow::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)
{
deactivated=true;
fadeOutTime=0.2f;
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),0.25));
return false;
return BulletDestroyState::KEEP_ALIVE;
}
void Arrow::ModifyOutgoingDamageData(HurtDamageInfo&data){
if(friendly)data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
}

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

@ -0,0 +1,65 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Menu.h"
#include "AdventuresInLestoria.h"
INCLUDE_game
void Menu::InitializeArtificerDisassembleWindow(){
Menu*artificerDisassembleWindow=CreateMenu(ARTIFICER_DISASSEMBLE,CENTERED,vi2d{144,144});
artificerDisassembleWindow->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="",}},
});
}

@ -0,0 +1,65 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Menu.h"
#include "AdventuresInLestoria.h"
INCLUDE_game
void Menu::InitializeArtificerEnchantConfirmWindow(){
Menu*artificerEnchantConfirmWindow=CreateMenu(ARTIFICER_ENCHANT_CONFIRM,CENTERED,vi2d{144,144});
artificerEnchantConfirmWindow->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="",}},
});
}

@ -0,0 +1,65 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Menu.h"
#include "AdventuresInLestoria.h"
INCLUDE_game
void Menu::InitializeArtificerEnchantWindow(){
Menu*artificerEnchantWindow=CreateMenu(ARTIFICER_ENCHANT,CENTERED,vi2d{144,144});
artificerEnchantWindow->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="",}},
});
}

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

@ -0,0 +1,82 @@
#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 "RowInventoryScrollableWindowComponent.h"
#include "MenuLabel.h"
INCLUDE_game
void Menu::InitializeArtificerRefineWindow(){
Menu*artificerRefineWindow=CreateMenu(ARTIFICER_REFINE,CENTERED,game->GetScreenSize()-vi2d{52,52});
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 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,28}})END;
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;
Menu::AddInventoryListener(inventoryDisplay,"Accessories");
artificerRefineWindow->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="",}},
});
}

@ -0,0 +1,101 @@
#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 "AdventuresInLestoria.h"
#include "Unlock.h"
#include "MenuLabel.h"
#include "VisualNovel.h"
INCLUDE_game
void Menu::InitializeArtificerWindow(){
Menu*artificerWindow=CreateMenu(ARTIFICER,CENTERED,vi2d{144,144});
artificerWindow->ADD("Refine Button",MenuComponent)(geom2d::rect<float>{{0.f,4.f},{144.f,24.f}},"Refine",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_REFINE);
return true;
},vf2d{2.f,2.f},ButtonAttr::FIT_TO_LABEL)END;
artificerWindow->ADD("Disassemble Button",MenuComponent)(geom2d::rect<float>{{0.f,32.f},{144.f,24.f}},"Disassemble",[](MenuFuncData data){
Menu::OpenMenu(MenuType::ARTIFICER_DISASSEMBLE);
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);
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){
VisualNovel::LoadDialog("ARTIFICER_HELP",[](){Menu::OpenMenu(MenuType::ARTIFICER);});
return true;
},vf2d{2.f,2.f},ButtonAttr::FIT_TO_LABEL)END;
artificerWindow->ADD("Leave Button",MenuComponent)(geom2d::rect<float>{{0.f,116.f},{144.f,24.f}},"Leave",[](MenuFuncData data){
Menu::CloseMenu();
return true;
},vf2d{2.f,2.f},ButtonAttr::FIT_TO_LABEL)END;
artificerWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="Refine 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
{"Refine Button",{
.up="Leave Button",
.down="Disassemble Button",}},
{"Disassemble Button",{
.up="Refine Button",
.down="Enchant Button",}},
{"Enchant Button",{
.up="Disassemble Button",
.down="Help Button",}},
{"Help Button",{
.up="Enchant Button",
.down="Leave Button",}},
{"Leave Button",{
.up="Help Button",
.down="Refine Button",}},
});
}

@ -80,4 +80,10 @@ public:
}
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::get<size_t>(attributes[a]);
};
};

@ -94,6 +94,12 @@ const bool ItemAttribute::operator<(const ItemAttribute&rhs)const{
return originalName<rhs.originalName;
};
float operator+(const float&lhs,const ItemAttribute&rhs){
float result{lhs};
result+=rhs;
return result;
};
const std::string_view ItemAttribute::Modifies()const{
return modifies;
};
@ -118,10 +124,11 @@ float&operator+=(float&lhs,const ItemAttribute&rhs){
}
#pragma endregion
if(rhs.DisplayAsPercent()){ //This is a percentage-based stat modifier.
statModifierTotal+=stats->A_Read(rhs)/100.f;
if(rhs.Modifies().length()>0){
lhs+=stats->A_Read(rhs.Modifies())*statModifierTotal;
}else{
lhs+=stats->A_Read(rhs)*statModifierTotal;
lhs*=statModifierTotal;
}
}else{ //This is a flat stat modifier.
lhs+=statModifierTotal;
@ -152,10 +159,11 @@ float&operator-=(float&lhs,const ItemAttribute&rhs){
}
#pragma endregion
if(rhs.DisplayAsPercent()){ //This is a percentage-based stat modifier.
statModifierTotal+=stats->A_Read(rhs)/100.f;
if(rhs.Modifies().length()>0){
lhs-=stats->A_Read(rhs.Modifies())*statModifierTotal;
}else{
lhs-=stats->A_Read(rhs)*statModifierTotal;
lhs/=statModifierTotal;
}
}else{ //This is a flat stat modifier.
lhs-=statModifierTotal;

@ -56,7 +56,6 @@ class ItemAttribute{
bool showDecimal=false;
std::string modifies="";
std::optional<std::variant<Player*,Monster*>>target;
static void Initialize();
public:
inline static safemap<std::string,ItemAttribute>attributes;
ItemAttribute(std::string_view originalName,std::string_view name,bool isPct,bool showDecimal,std::string_view modifies=""sv);
@ -70,12 +69,15 @@ public:
friend float&operator+=(float&lhs,const ItemAttribute&rhs);
friend float&operator-=(float&lhs,const ItemAttribute&rhs);
const bool operator==(const ItemAttribute&rhs)const;
friend float operator+(const float&lhs,const ItemAttribute&rhs);
static void Initialize();
};
struct Stats;
class ItemAttributable{
friend class ItemInfo;
friend class EntityStats;
protected:
std::map<ItemAttribute,float>attributes;
public:

@ -39,6 +39,8 @@ All rights reserved.
#include "AdventuresInLestoria.h"
#include "DEFINES.h"
#include "util.h"
#include "LoadingScreen.h"
#include "Menu.h"
INCLUDE_game
INCLUDE_DATA
@ -121,12 +123,20 @@ const size_t Audio::LoadAndPlay(const std::string_view sound,const bool loop){
Engine().Play(soundID,loop);
return soundID;
};
void Audio::PlayBGM(const std::string_view sound,const bool loop){
void Audio::PrepareBGM(const std::string_view sound,const bool loop){
BGM&track=Self().bgm[std::string(sound)];
Self().fullyLoaded=false;
StopBGM(); //Stop any currently playing track.
Self().playParams={std::string(sound),loop};
Self().playBGMWaitTime=0.7f;
#pragma region Internal Loading Loop Setup
Self().trackLoadStarted=false;
Self().trackLoadComplete=false;
Self().channelPlayingStarted=false;
Self().channelPlayingComplete=false;
int currentLoopIndex=0;
#pragma endregion
Self().immediatelyLoadAudio=false;
};
void Audio::StopBGM(){
@ -145,24 +155,39 @@ const bool Audio::BGMIsPlaying(){
return Self().currentBGM.length()>0;
}
const Volume&Audio::BGM::GetVolume(const Event&eventName,const ChannelID&id)const{
return eventVolumes.GetVolumes(eventName).at(id);
const Volume&Audio::BGM::GetVolume(const Event&eventName,const int&index)const{
return eventVolumes.GetVolumes(eventName).at(index);
}
void Audio::BGM::Load(){
if(Self().BGMIsPlaying()){
if(Self().GetTrackName()==songFileName)return; //We are already playing the current track.
BGM&bgm=Self().bgm[Self().GetTrackName()];
if(!Self().trackLoadStarted){
Self().trackLoadStarted=true;
if(Self().BGMIsPlaying()){
bgm.Unload();
if(Self().GetTrackName()==songFileName){ //We are already playing the current track.
Self().trackLoadComplete=Self().channelPlayingComplete=Self().fullyLoaded=true;
return;
}else{
BGM&bgm=Self().bgm[Self().GetTrackName()];
if(Self().BGMIsPlaying()){
bgm.Unload();
}
}
}
}
Self().currentBGM=songFileName;
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()));
for(const ChannelName&channel:newBgm.GetChannels()){
Self().currentBGM=songFileName;
Self().currentLoopIndex=0;
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{
BGM&newBgm=Self().bgm[songFileName];
const ChannelName&channel=newBgm.GetChannels()[Self().currentLoopIndex];
ChannelID soundID=Engine().LoadSound("bgm_directory"_S+channel);
newBgm.channels.push_back(soundID);
#pragma region Handle threaded loop indexing
Self().currentLoopIndex++;
if(Self().currentLoopIndex>=newBgm.GetChannels().size()){
Self().trackLoadComplete=true;
}
#pragma endregion
}
}
@ -258,32 +283,65 @@ const SongName&Audio::GetTrackName(){
return Self().currentBGM;
}
void Audio::Update(){
if(Self().playBGMWaitTime>0.f){
Self().playBGMWaitTime=std::max(Self().playBGMWaitTime-game->GetElapsedTime(),0.f);
if(Self().playBGMWaitTime==0.f){
BGM&track=Self().bgm[Self().playParams.sound];
void Audio::UpdateLoop(){
if(Self().playBGMWaitTime==0.f){
BGM&track=Self().bgm[Self().playParams.sound];
if(!Self().trackLoadComplete){
track.Load();
Self().prevVolumes.clear();
Self().targetVolumes.clear();
Self().fadeToTargetVolumeTime=0.f;
for(int channelListIndex=0;int trackID:track.GetChannelIDs()){
float channelVol=track.GetVolume(Self().currentAudioEvent,channelListIndex);
}else
if(!Self().channelPlayingComplete){
if(!Self().channelPlayingStarted){
Self().prevVolumes.clear();
Self().targetVolumes.clear();
Self().fadeToTargetVolumeTime=0.f;
Self().currentLoopIndex=0;
Self().channelPlayingStarted=true;
}else{
int trackID=track.GetChannelIDs()[Self().currentLoopIndex];
float channelVol=track.GetVolume(Self().currentAudioEvent,Self().currentLoopIndex);
Self().prevVolumes.push_back(channelVol);
Self().targetVolumes.push_back(channelVol);
Engine().SetVolume(trackID,channelVol*GetBGMVolume());
Engine().Play(trackID,Self().playParams.loop);
channelListIndex++;
Engine().SetVolume(trackID,Self().GetCalculatedBGMVolume(channelVol));
#pragma region Handle threaded loop indexing
Self().currentLoopIndex++;
if(Self().currentLoopIndex>=track.GetChannelIDs().size()){
Self().channelPlayingComplete=true;
Self().fullyLoaded=true;
}
#pragma endregion
}
Self().fullyLoaded=true;
}else
if(!Self().fullyLoaded){
ERR("Invalid loading state or too many calls to initialize audio loop! The audio is still not fully loaded!");
}
}
}
void Audio::PlayBGM(const std::string_view sound,const bool loop){
PrepareBGM(sound,loop);
Self().immediatelyLoadAudio=true;
}
void Audio::Update(){
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){
while(!Self().BGMFullyLoaded()){
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.
Audio::BGM&track=Self().bgm[Self().GetTrackName()];
for(int trackID:track.GetChannelIDs()){
Engine().Play(trackID,true);
}
}
}
if(Self().fadeToTargetVolumeTime>0.f){
Self().fadeToTargetVolumeTime=std::max(0.f,Self().fadeToTargetVolumeTime-game->GetElapsedTime());
for(int counter=0;float&vol:Self().prevVolumes){
const BGM&currentBgm=Self().bgm[Self().currentBGM];
Engine().SetVolume(currentBgm.GetChannelIDs()[counter],util::lerp(vol,Self().targetVolumes[counter],1-(Self().fadeToTargetVolumeTime/currentBgm.GetFadeTime()))*GetBGMVolume());
Engine().SetVolume(currentBgm.GetChannelIDs()[counter],util::lerp(Self().GetCalculatedBGMVolume(vol),Self().GetCalculatedBGMVolume(Self().targetVolumes[counter]),1-(Self().fadeToTargetVolumeTime/currentBgm.GetFadeTime())));
counter++;
}
}
@ -299,10 +357,12 @@ const float&Audio::BGM::GetFadeTime()const{
void Audio::SetBGMVolume(float vol){
bgmVol=vol;
UpdateBGMVolume();
}
void Audio::SetBGMPitch(float pitch){
BGM&track=Self().bgm[Self().playParams.sound];
for(int channelListIndex=0;int trackID:track.GetChannelIDs()){
float channelVol=track.GetVolume(Self().currentAudioEvent,channelListIndex);
Engine().SetVolume(trackID,channelVol*GetBGMVolume()*GetMuteMult());
Engine().SetPitch(trackID,pitch);
channelListIndex++;
}
}
@ -315,7 +375,31 @@ float&Audio::GetBGMVolume(){
float&Audio::GetSFXVolume(){
return sfxVol;
}
void Audio::UpdateBGMVolume(){
BGM&track=Self().bgm[Self().playParams.sound];
for(int channelListIndex=0;int trackID:track.GetChannelIDs()){
float channelVol=track.GetVolume(Self().currentAudioEvent,channelListIndex);
Engine().SetVolume(trackID,Self().GetCalculatedBGMVolume(channelVol));
channelListIndex++;
}
EnvironmentalAudio::UpdateEnvironmentalAudio();
}
float Audio::GetCalculatedBGMVolume(const float channelVol){
float pauseMult=1.f;
if(Menu::IsMenuOpen()&&Menu::stack.front()==Menu::menus[MenuType::PAUSE]){
pauseMult=0.5f;
}
return channelVol*GetBGMVolume()*GetMuteMult()*pauseMult;
}
float Audio::GetCalculatedSFXVolume(const float vol){
return vol*GetSFXVolume()*GetMuteMult();
}
float Audio::GetMuteMult(){
if(muted)return 0.f;
return 1.f;
}
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.
}

@ -56,10 +56,13 @@ public:
static MiniAudio&Engine();
static void Initialize();
static void Update();
static void UpdateLoop();
static void Play(const std::string_view sound);
[[nodiscard]]
static const size_t LoadAndPlay(const std::string_view sound,const bool loop=true);
//Play a BGM given a name found in bgm.txt configuration file.
//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.
static void PlayBGM(const std::string_view sound,const bool loop=true);
static void StopBGM();
static const Event&GetAudioEvent();
@ -67,15 +70,29 @@ public:
static void SetAudioEvent(const Event&eventName);
static const bool BGMIsPlaying();
static const bool BGMFullyLoaded(); //Fully loaded means when the audio buffer has finished filling up, which means sound is now playing.
static void SetBGMPitch(float pitch);
static void SetBGMVolume(float vol);
static void UpdateBGMVolume();
static void SetSFXVolume(float vol);
static float&GetBGMVolume();
static float&GetSFXVolume();
static float GetMuteMult();
//This will get a prepared BGM loop iteration count which is useful for loading stages.
static int GetPrepareBGMLoopIterations(std::string_view sound);
static float GetCalculatedBGMVolume(const float channelVol);
static float GetCalculatedSFXVolume(const float vol);
private:
bool trackLoadStarted=false;
bool trackLoadComplete=false;
bool channelPlayingStarted=false;
bool channelPlayingComplete=false;
int currentLoopIndex=0;
//Set to false by PrepareBGM(). If PlayBGM() is called instead, it will set the state of this variable to true, such that the loading is performed in Audio::Update()!
bool immediatelyLoadAudio=false;
struct BGMPlayParams{
std::string sound;
bool loop;
bool loop=false;
};
class EventData{
public:
@ -92,7 +109,7 @@ private:
const size_t GetChannelCount()const;
const std::vector<ChannelName>&GetChannels()const;
const SongName&GetName()const;
const Volume&GetVolume(const Event&eventName,const ChannelID&id)const;
const Volume&GetVolume(const Event&eventName,const int&index)const;
void SetName(std::string_view name);
void SetFileName(std::string_view name);
void AddChannel(const ChannelName&name);

@ -59,13 +59,14 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
m.I(A::PHASE)=1;
m.PerformShootAnimation();
m.F(A::CASTING_TIMER)=ConfigFloat("Chargeup Time");
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
//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.
m.V(A::LOCKON_POS)=vf2d{distToPlayer,util::angleTo(m.GetPos(),game->GetPlayer()->GetPos())};
m.SetStrategyDrawFunction([](AiL*game,Monster&m,const std::string&strategy){
if(m.IsAlive()){
game->view.DrawRotatedDecal(m.V(A::LOCKON_POS),GFX["range_indicator.png"].Decal(),0.f,{12.f,12.f},vf2d{ConfigFloat("Smash Attack Diameter"),ConfigFloat("Smash Attack Diameter")}/100.f,{255,255,0,160});
game->view.DrawRotatedDecal(m.GetPos()+m.V(A::LOCKON_POS).cart(),GFX["range_indicator.png"].Decal(),0.f,{12.f,12.f},vf2d{ConfigFloat("Smash Attack Diameter"),ConfigFloat("Smash Attack Diameter")}/100.f,{255,255,0,160});
}
});
m.RotateTowardsPos(m.V(A::LOCKON_POS));
m.RotateTowardsPos(m.GetPos()+m.V(A::LOCKON_POS).cart());
}else{
m.target=game->GetPlayer()->GetPos();
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
@ -76,16 +77,17 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
if(m.F(A::CASTING_TIMER)==0.f){
m.I(A::PHASE)=2;
m.F(A::CASTING_TIMER)=ConfigFloat("Attack Animation Wait Time");
m.PerformOtherAnimation(0);
m.PerformAnimation("SLAM");
}
}break;
case 2:{
m.F(A::CASTING_TIMER)=std::max(0.f,m.F(A::CASTING_TIMER)-fElapsedTime);
if(m.F(A::CASTING_TIMER)==0.f){
SoundEffect::PlaySFX("Bear Slam Attack",m.V(A::LOCKON_POS));
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
SoundEffect::PlaySFX("Bear Slam Attack",m.GetPos()+m.V(A::LOCKON_POS).cart());
m.I(A::PHASE)=0;
m.I(A::BEAR_STOMP_COUNT)++;
geom2d::circle<float>attackCircle={m.V(A::LOCKON_POS),float(operator""_Pixels(ConfigFloat("Smash Attack Diameter"))/2.f)};
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())){
if(game->GetPlayer()->Hurt(m.GetAttack(),m.OnUpperLevel(),0.f)){
game->GetPlayer()->Knockup(ConfigFloat("Attack Knockup Duration"));
@ -93,11 +95,11 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
game->GetPlayer()->Knockback(playerDirVecNorm*ConfigFloat("Attack Knockback Amount"));
}
}
for(Monster&otherM:MONSTER_LIST){
if(!otherM.AttackAvoided(m.GetZ())&&&m!=&otherM&&geom2d::overlaps(attackCircle,otherM.Hitbox())){
otherM.Knockup(ConfigFloat("Attack Knockup Duration"));
vf2d monsterDirVecNorm=geom2d::line<float>(m.GetPos(),otherM.GetPos()).vector().norm();
game->GetPlayer()->Knockback(monsterDirVecNorm*ConfigFloat("Attack Knockback Amount"));
for(std::shared_ptr<Monster>&otherM:MONSTER_LIST){
if(!otherM->AttackAvoided(m.GetZ())&&&m!=otherM.get()&&geom2d::overlaps(attackCircle,otherM->BulletCollisionHitbox())){
otherM->Knockup(ConfigFloat("Attack Knockup Duration"));
vf2d monsterDirVecNorm=geom2d::line<float>(m.GetPos(),otherM->GetPos()).vector().norm();
otherM->Knockback(monsterDirVecNorm*ConfigFloat("Attack Knockback Amount"));
}
}
m.spriteRot=0.f;

@ -0,0 +1,80 @@
#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 "AdventuresInLestoria.h"
INCLUDE_ANIMATION_DATA
INCLUDE_game
BearTrap::BearTrap(vf2d pos,float radius,int damage,float fadeinTime,float fadeoutTime,bool upperLevel,bool hitsMultiple,float lifetime,bool friendly,Pixel col,vf2d scale)
:Bullet(pos,{},radius,damage,"Ability Icons/bear_trap.png",upperLevel,hitsMultiple,lifetime,false,friendly,col,scale,0.f,"Trap Hit"){
fadeInTime=fadeinTime;
animation.AddState("bear_trap.png",ANIMATION_DATA["bear_trap.png"]);
if(!friendly)ERR("WARNING! Trying to use unimplemented enemy version of the Bear Trap Bullet!");
}
void BearTrap::ModifyOutgoingDamageData(HurtDamageInfo&data){
data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
}
BulletDestroyState BearTrap::PlayerHit(Player*player){
fadeOutTime=0.5f;
animation.ChangeState(internal_animState,"bear_trap.png");
return BulletDestroyState::KEEP_ALIVE;
}
BulletDestroyState BearTrap::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
fadeOutTime=0.5f;
animation.ChangeState(internal_animState,"bear_trap.png");
const float bleedDamage{"Trapper.Ability 2.Marked Target Bleed"_f[0]/100.f*game->GetPlayer()->GetAttack()};
const float bleedDuration{"Trapper.Ability 2.Marked Target Bleed"_f[1]};
const float timeBetweenTicks{"Trapper.Ability 2.Marked Target Bleed"_f[2]};
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())};
monster.ApplyMark("Trapper.Ability 2.Marked Target Reset Time"_F,numberOfStacksToReplenish);
monster.ApplyDot(bleedDuration,bleedDamage,timeBetweenTicks);
}
monster.AddBuff(BuffType::SLOWDOWN,"Trapper.Ability 2.Slowdown Time"_F,"Trapper.Ability 2.Slowdown Amount"_F/100.f);
monster.Knockup("Trapper.Ability 2.Knockup Amount"_F);
return BulletDestroyState::KEEP_ALIVE;
}

@ -201,7 +201,8 @@ void Menu::InitializeBlacksmithCraftingWindow(){
#pragma region Inventory Description
float inventoryDescriptionWidth=blacksmithWindow->pos.x+blacksmithWindow->size.x-26-224;
blacksmithWindow->ADD("Item Description Outline",MenuLabel)(geom2d::rect<float>{{224,28},{inventoryDescriptionWidth,blacksmithWindow->size.y-44}},"",1,ComponentAttr::LEFT_ALIGN|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
blacksmithWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>{{226+inventoryDescriptionWidth/2-24,30},{48,48}},Item::BLANK,DO_NOTHING,"","",IconButtonAttr::NOT_SELECTABLE)END;
blacksmithWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>{{226+inventoryDescriptionWidth/2-24,30},{48,48}},Item::BLANK,DO_NOTHING,"","",IconButtonAttr::NOT_SELECTABLE)END
->SetIconScale({2.f,2.f});
blacksmithWindow->ADD("Item Name Label",MenuLabel)(geom2d::rect<float>{{226,84},{inventoryDescriptionWidth-6,12}},"",0.75f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
blacksmithWindow->ADD("Item Description Label",MenuLabel)(geom2d::rect<float>{{226,94},{inventoryDescriptionWidth-6,blacksmithWindow->size.y-44-66}},"",0.5f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
#pragma endregion
@ -271,6 +272,7 @@ void Menu::InitializeBlacksmithCraftingWindow(){
Component<MenuComponent>(type,"Weapon Tab")->Click();
}
}}},
{{game->KEY_SHOULDER2,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
}
,{ //Button Navigation Rules

@ -0,0 +1,126 @@
#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 "DEFINES.h"
#include "Monster.h"
#include "MonsterStrategyHelpers.h"
/*
* Attack Strategie: If range to player >700 move closer.
if range 400-700 Scratch the ground twice and then charge with 30% Move-Spd bonus in the players direction for a distance of 900.
If range to player<400 backpaddle with 50% move-spd. if getting hit while backpaddling, start charge sequence.
*/
INCLUDE_game
INCLUDE_ANIMATION_DATA
using A=Attribute;
void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
enum PhaseName{
MOVE,
SCRATCH,
CHARGE,
RECOVERY,
};
switch(m.phase){
case PhaseName::MOVE:{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
if(m.canMove&&distToPlayer>=ConfigInt("Closein Range")/100.f*24){
m.RemoveBuff(BuffType::SELF_INFLICTED_SLOWDOWN);
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else
if(m.HasLineOfSight(game->GetPlayer()->GetPos())&&m.canMove&&distToPlayer<=ConfigInt("Backpedal Range")/100.f*24){
m.RemoveBuff(BuffType::SELF_INFLICTED_SLOWDOWN);
m.AddBuff(BuffType::SELF_INFLICTED_SLOWDOWN,INFINITE,(100-ConfigInt("Backpedal Movespeed"))/100.f);
m.target=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(-100);
if(m.attackedByPlayer)goto ScratchPhaseTransition;
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
m.UpdateFacingDirection(game->GetPlayer()->GetPos());
}else{
ScratchPhaseTransition:
m.PerformAnimation("SCRATCH");
m.F(A::CASTING_TIMER)=ConfigInt("Ground Scratch Count")*m.GetCurrentAnimation().GetTotalAnimationDuration();
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();
m.F(A::TARGET_TIMER)=distanceToChargePoint/(100.f*m.GetMoveSpdMult()); //This should be how long a charge takes.
m.target=chargeTargetPoint;
m.UpdateFacingDirection(game->GetPlayer()->GetPos());
}
}break;
case PhaseName::SCRATCH:{
m.RemoveBuff(BuffType::SELF_INFLICTED_SLOWDOWN);
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
m.PerformShootAnimation();
m.phase=PhaseName::CHARGE;
m.AddBuff(BuffType::SPEEDBOOST,INFINITE,ConfigFloat("Charge Movespeed")/100.f-1);
m.AddBuff(BuffType::COLLISION_KNOCKBACK_STRENGTH,15,ConfigFloat("Charge Knockback Amount"));
}
}break;
case PhaseName::CHARGE:{
m.F(A::TARGET_TIMER)-=fElapsedTime;
float distToTarget=geom2d::line<float>(m.GetPos(),m.target).length();
auto TransitionToRecoveryPhase=[&](){
m.phase=PhaseName::RECOVERY;
m.F(A::CHARGE_COOLDOWN)=ConfigFloat("Charge Recovery Time");
m.PerformIdleAnimation();
};
if(m.F(A::TARGET_TIMER)<=0||m.bumpedIntoTerrain||distToTarget<12.f){
TransitionToRecoveryPhase();
}else{
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
//m.PerformShootAnimation();
if(!m.canMove)TransitionToRecoveryPhase();
}
}break;
case PhaseName::RECOVERY:{
m.RemoveBuff(BuffType::COLLISION_KNOCKBACK_STRENGTH);
m.F(A::CHARGE_COOLDOWN)-=fElapsedTime;
m.targetAcquireTimer=0.f;
m.RemoveBuff(BuffType::SPEEDBOOST);
if(m.F(A::CHARGE_COOLDOWN)<=0)m.phase=PhaseName::MOVE;
}break;
}
}

@ -0,0 +1,109 @@
#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 "util.h"
#include "AdventuresInLestoria.h"
#include "BombBoom.h"
#include "SoundEffect.h"
INCLUDE_DATA
INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_LIST
INCLUDE_game
Bomb::Bomb(const vf2d pos,const float z,const float gravity,const float detonationTime,const float bombFadeoutTime,const float bombKnockbackFactor,const vf2d targetPos,const float explosionRadius,const int damage,const bool upperLevel,const bool friendly,const Pixel col,const vf2d scale)
:Bullet(pos,{},0.f,damage,"goblin_bomb.png",upperLevel,true,INFINITY,false,friendly,col,scale)
,explosionRadius(explosionRadius),gravity(gravity),detonationTime(detonationTime),bombFadeoutTime(bombFadeoutTime),bombKnockbackFactor(bombKnockbackFactor),targetPos(targetPos){
this->z=z;
bomb_animation.AddState("Fuse",ANIMATION_DATA.at("goblin_bomb_fuse.png"));
bomb_animation.ChangeState(animation,"Fuse");
}
void Bomb::Update(float fElapsedTime){
vel=geom2d::line<float>(pos,targetPos).vector();
detonationTime-=fElapsedTime;
bomb_animation.UpdateState(animation,fElapsedTime);
if(detonationTime>0.f){
zVel+=gravity*fElapsedTime;
z=std::max(0.f,z+zVel*fElapsedTime);
if(z==0.f)zVel*=-1.f;
}else
if(fadeOutTime==0.f){
z=0; //Force the bomb to be grounded.
fadeOutTime=bombFadeoutTime;
game->AddEffect(std::make_unique<BombBoom>(pos,0.f,OnUpperLevel(),vf2d{explosionRadius,explosionRadius}/12.f/1.5f/*Upscale 24x24 to 36x36*/,1.f,vf2d{},WHITE,0.f,0.f,true));
SoundEffect::PlaySFX("Bomb Explode",pos);
float distToPlayer=geom2d::line<float>(pos,game->GetPlayer()->GetPos()).length();
if(friendly){
const HurtList hurtEnemies=game->Hurt(pos,explosionRadius,damage,OnUpperLevel(),z,HurtType::MONSTER);
for(auto&[targetPtr,wasHit]:hurtEnemies){
if(!std::holds_alternative<Monster*>(targetPtr))ERR("WARNING! Hurt enemies list returned a non-monster pointer!? THIS SHOULD NOT BE HAPPENING!");
Monster*monsterPtr=std::get<Monster*>(targetPtr);
if(wasHit)monsterPtr->ProximityKnockback(pos,bombKnockbackFactor);
}
if(distToPlayer<=explosionRadius){
game->GetPlayer()->ProximityKnockback(pos,bombKnockbackFactor);
}
}else{
if(distToPlayer<=explosionRadius){
if(game->GetPlayer()->Hurt(damage,OnUpperLevel(),z)){
game->GetPlayer()->ProximityKnockback(pos,bombKnockbackFactor);
}
}
for(auto&monsterPtr:MONSTER_LIST){
float distToMonster=geom2d::line<float>(pos,monsterPtr->GetPos()).length();
if(distToMonster<=explosionRadius){
monsterPtr->ProximityKnockback(pos,bombKnockbackFactor);
}
}
}
}
}
BulletDestroyState Bomb::PlayerHit(Player*player){
return BulletDestroyState::KEEP_ALIVE;
}
BulletDestroyState Bomb::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
return BulletDestroyState::KEEP_ALIVE;
}
void Bomb::Draw(const Pixel blendCol)const{
Bullet::Draw(blendCol);
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},bomb_animation.GetFrame(animation).GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2:0,bomb_animation.GetFrame(animation).GetSourceRect().size/2,bomb_animation.GetFrame(animation).GetSourceRect().pos,bomb_animation.GetFrame(animation).GetSourceRect().size,scale,fadeOutTime==0?col:Pixel{col.r,col.g,col.b,uint8_t(util::lerp(col.a,0,1-((fadeOutTime-GetFadeoutTimer())/fadeOutTime)))});
}
void Bomb::ModifyOutgoingDamageData(HurtDamageInfo&data){}

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

@ -0,0 +1,95 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "MonsterStrategyHelpers.h"
#include "util.h"
#include "AdventuresInLestoria.h"
#include "SoundEffect.h"
using A=Attribute;
void Monster::STRATEGY::BREAKING_PILLAR(Monster&m,float fElapsedTime,std::string strategy){
enum Phase{
INITIALIZE,
RUN,
};
if(m.GetHealthRatio()>=ConfigFloat("Break Phase 1 HP % Threshold")/100.f){
m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Unbroken Animation Name"));
}else
if(m.GetHealthRatio()>=ConfigFloat("Break Phase 2 HP % Threshold")/100.f){
m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Break Phase 1 Animation Name"));
}else m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Break Phase 2 Animation Name"));
switch(m.phase){
case INITIALIZE:{
m.F(A::BREAK_TIME)=ConfigFloat("Break Time");
m.F(A::SHAKE_TIMER)=0.2f;
m.phase=RUN;
m.SetStrategyDeathFunction([&](GameEvent&deathEvent,Monster&m,const std::string&strategy){
m.lifetime=0.01f;
m.B(A::MARKED_DEAD)=true;
const float bulletAngRandomOffset{util::random(PI/2)};
for(int i=0;i<ConfigInt("Death Ring Bullet Count");i++){
const float bulletAngle=((2*PI)/ConfigInt("Death Ring Bullet Count"))*i+bulletAngRandomOffset;
CreateBullet(Bullet)(m.GetPos(),vf2d{ConfigFloat("Death Ring Bullet Speed"),bulletAngle}.cart(),ConfigFloat("Death Ring Bullet Size"),ConfigInt("Death Ring Bullet Damage"),m.OnUpperLevel(),false,ConfigPixel("Death Ring Bullet Color"),vf2d{ConfigFloat("Death Ring Bullet Size")/3,ConfigFloat("Death Ring Bullet Size")/3})EndBullet;
BULLET_LIST.back()->SetIframeTimeOnHit(0.15f);
}
return false;
});
}break;
case RUN:{
m.F(A::BREAK_TIME)-=fElapsedTime;
if(m.F(A::BREAK_TIME)<=2.f){
if(m.F(A::BREAK_TIME)<=0.f){
m._DealTrueDamage(m.GetHealth()); //Kill the pillar, making it crumble.
break;
}
m.F(A::SHAKE_TIMER)-=fElapsedTime;
if(m.F(A::SHAKE_TIMER)<=0.f){
m.F(A::SHAKE_TIMER)+=0.2f;
if(m.B(A::SHAKE_DIR))m.SetX(m.GetPos().x-2);
else m.SetX(m.GetPos().x+2);
m.B(A::SHAKE_DIR)=!m.B(A::SHAKE_DIR);
SoundEffect::PlaySFX("Pillar Shake",m.GetPos());
}
}
}break;
}
}

@ -0,0 +1,130 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "Buff.h"
#include "Player.h"
#include "Monster.h"
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity){}
Buff::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){}
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){
for(const std::string&s:attr){
this->attr.insert(ItemAttribute::attributes.at(s));
}
}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc)
:Buff(attachedTarget,type,overTimeType,duration,intensity,timeBetweenTicks){
playerBuffCallbackFunc=expireCallbackFunc;
}
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;
}
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;
case BuffRestorationType::OVER_TIME:{
this->type=OVER_TIME;
}break;
case BuffRestorationType::OVER_TIME_DURING_CAST:{
this->type=OVER_TIME_DURING_CAST;
}break;
}
}
void Buff::Update(AiL*game,float fElapsedTime){
duration-=fElapsedTime;
if(enabled&&overTimeType.has_value()&&nextTick>0&&duration<nextTick){
BuffTick(game,fElapsedTime);
nextTick-=timeBetweenTicks;
if(type==ONE_OFF)enabled=false;
}
}
void Buff::BuffTick(AiL*game,float fElapsedTime){
if(!overTimeType.has_value())ERR("WARNING! Trying to run BuffTick without a valid over time type provided! THIS SHOULD NOT BE HAPPENING!");
using enum BuffOverTimeType::BuffOverTimeType;
const auto OwnerIsPlayer=[&](){return std::holds_alternative<Player*>(attachedTarget);};
const auto OwnerIsMonster=[&](){return std::holds_alternative<std::weak_ptr<Monster>>(attachedTarget);};
const auto MonsterIsValid=[&](){return !std::get<std::weak_ptr<Monster>>(attachedTarget).expired();};
const auto GetPlayer=[&](){return std::get<Player*>(attachedTarget);};
const auto GetMonster=[&](){return std::get<std::weak_ptr<Monster>>(attachedTarget).lock();};
switch(int(overTimeType.value())){
case HP_RESTORATION:{
if(OwnerIsPlayer())GetPlayer()->Heal(intensity);
else if(OwnerIsMonster()){if(MonsterIsValid())GetMonster()->Heal(intensity);}
else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!")
}break;
case MP_RESTORATION:{
if(OwnerIsPlayer())GetPlayer()->RestoreMana(intensity);
else if(OwnerIsMonster())ERR("WARNING! Monsters don't have mana, this functionality is not supported!")
else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!")
}break;
case HP_PCT_RESTORATION:{
if(OwnerIsPlayer())GetPlayer()->Heal(GetPlayer()->GetMaxHealth()*intensity/100.f);
else if(OwnerIsMonster()){if(MonsterIsValid())GetMonster()->Heal(GetMonster()->GetMaxHealth()*intensity/100.f);}
else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!")
}break;
case MP_PCT_RESTORATION:{
if(OwnerIsPlayer())GetPlayer()->RestoreMana(GetPlayer()->GetMaxMana()*intensity/100.f);
else if(OwnerIsMonster()){ERR("WARNING! Monsters don't have mana, this functionality is not supported!")}
else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!")
}break;
case HP_DAMAGE_OVER_TIME:{
if(OwnerIsPlayer())GetPlayer()->Hurt(intensity,GetPlayer()->OnUpperLevel(),GetPlayer()->GetZ(),HurtFlag::DOT);
else if(OwnerIsMonster()){if(MonsterIsValid())GetMonster()->Hurt(intensity,GetMonster()->OnUpperLevel(),GetMonster()->GetZ(),HurtFlag::DOT);}
else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!")
}break;
case HP_PCT_DAMAGE_OVER_TIME:{
if(OwnerIsPlayer())GetPlayer()->Hurt(GetPlayer()->GetMaxHealth()*intensity/100.f,GetPlayer()->OnUpperLevel(),GetPlayer()->GetZ(),HurtFlag::DOT);
else if(OwnerIsMonster()){if(MonsterIsValid())GetMonster()->Hurt(GetMonster()->GetMaxHealth()*intensity/100.f,GetMonster()->OnUpperLevel(),GetMonster()->GetZ(),HurtFlag::DOT);}
else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!")
}break;
}
}

@ -44,35 +44,64 @@ enum BuffType{
DAMAGE_REDUCTION,
SLOWDOWN,
BLOCK_SLOWDOWN,
RESTORATION,
RESTORATION_DURING_CAST,
LOCKON_SPEEDBOOST, //Specifically used for wolves.
SPEEDBOOST,
BARRIER_DAMAGE_REDUCTION, //Creates a visual barrier around the target
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,
TRAPPER_MARK,
OVER_TIME,
ONE_OFF,
OVER_TIME_DURING_CAST,
GLOW_PURPLE,
COLOR_MOD,
DAMAGE_AMPLIFICATION, //Multiplies all incoming damage by this amount.
};
enum class BuffRestorationType{
ONE_OFF,
OVER_TIME,
OVER_TIME_DURING_CAST,
};
namespace BuffOverTimeType{
enum BuffOverTimeType{
HP_RESTORATION,
HP_PCT_RESTORATION,
MP_RESTORATION,
MP_PCT_RESTORATION,
HP_DAMAGE_OVER_TIME,
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;
float duration=1;
float timeBetweenTicks=1;
float intensity=1;
float nextTick=0;
std::set<ItemAttribute> attr;
std::function<void(AiL*,int)>repeatAction;
inline Buff(BuffType type,float duration,float intensity)
:type(type),duration(duration),intensity(intensity){}
inline Buff(BuffType type,float duration,float intensity,std::set<ItemAttribute> attr)
:type(type),duration(duration),intensity(intensity),attr(attr){}
inline Buff(BuffType type,float duration,float intensity,std::set<std::string> attr)
:type(type),duration(duration),intensity(intensity){
for(const std::string&s:attr){
this->attr.insert(ItemAttribute::attributes.at(s));
}
}
inline Buff(BuffType type,float duration,float intensity,float timeBetweenTicks,std::function<void(AiL*,int)>repeatAction)
:type(type),duration(duration),intensity(intensity),nextTick(duration-timeBetweenTicks),timeBetweenTicks(timeBetweenTicks),repeatAction(repeatAction){}
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){};
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<ItemAttribute> attr);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<std::string> attr);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc);
void Update(AiL*game,float fElapsedTime);
private:
bool enabled{true};
std::optional<BuffOverTimeType::BuffOverTimeType>overTimeType;
void BuffTick(AiL*game,float fElapsedTime);
};

@ -30,55 +30,17 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
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
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 "Bullet.h"
#include "AdventuresInLestoria.h"
#include "DEFINES.h"
#include "safemap.h"
INCLUDE_ANIMATION_DATA
INCLUDE_game
INCLUDE_GFX
Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale)
:pos(pos),vel(vel),radius(radius),damage(damage),col(col),friendly(friendly),upperLevel(upperLevel),scale(scale){};
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)
:pos(pos),vel(vel),radius(radius),damage(damage),col(col),animated(true),rotates(rotatesWithAngle),lifetime(lifetime),hitsMultiple(hitsMultiple),friendly(friendly),upperLevel(upperLevel),scale(scale){
this->animation.AddState(animation,ANIMATION_DATA[animation]);
this->animation.ChangeState(internal_animState,animation);
};
Animate2D::Frame Bullet::GetFrame(){
return animation.GetFrame(internal_animState);
}
void Bullet::UpdateFadeTime(float fElapsedTime)
{
if(fadeOutTime>0){
if(fadeOutTimer==0){
lifetime=fadeOutTime;
}
fadeOutTimer+=fElapsedTime;
}
}
void Bullet::Update(float fElapsedTime){}
void Bullet::Draw(){
auto lerp=[](uint8_t f1,uint8_t f2,float t){return uint8_t((float(f2)*t)+f1*(1-t));};
if(animated){
game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2:0,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,fadeOutTime==0?col:Pixel{col.r,col.g,col.b,lerp(col.a,0,1-((fadeOutTime-fadeOutTimer)/fadeOutTime))});
} else {
game->view.DrawDecal(pos-GFX["circle.png"].Sprite()->Size()*scale/2,GFX["circle.png"].Decal(),scale,fadeOutTime==0?col:Pixel{col.r,col.g,col.b,lerp(col.a,0,1-((fadeOutTime-fadeOutTimer)/fadeOutTime))});
game->view.DrawDecal(pos-GFX["circle.png"].Sprite()->Size()*scale/2,GFX["circle_outline.png"].Decal(),scale,fadeOutTime==0?WHITE:Pixel{WHITE.r,WHITE.g,WHITE.b,lerp(WHITE.a,0,1-((fadeOutTime-fadeOutTimer)/fadeOutTime))});
}
}
#include "Bullet.h"
bool Bullet::PlayerHit(Player*player){return true;}
bool Bullet::MonsterHit(Monster&monster){return true;}
bool Bullet::OnUpperLevel(){return upperLevel;}
Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale,float image_angle)
:IBullet(pos,vel,radius,damage,upperLevel,friendly,col,scale,image_angle){}
//Initializes a bullet with an animation.
Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float image_angle,std::string_view hitSound)
:IBullet(pos,vel,radius,damage,animation,upperLevel,hitsMultiple,lifetime,rotatesWithAngle,friendly,col,scale,image_angle,hitSound){}
void Bullet::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -30,56 +30,20 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
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
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 "Animation.h"
#include "olcUTIL_Animate2D.h"
#include "Monster.h"
#include "DEFINES.h"
struct Bullet{
friend class AiL;
vf2d vel;
vf2d pos;
float radius;
int damage;
Pixel col;
float lifetime=float(INFINITE);
bool hitsMultiple=false;
bool rotates=false;
bool animated=false;
bool deactivated=false; //A deactivated bullet no longer interacts with the world. It's just a visual.
float fadeOutTime=0; //Setting the fade out time causes the bullet's lifetime to be set to the fadeout time as well, as that's when the bullet's alpha will reach 0, so it dies.
bool friendly=false; //Whether or not it's a player bullet or enemy bullet.
bool upperLevel=false;
bool rendered=false;
bool alwaysOnTop=false;
protected:
float fadeOutTimer=0;
float distanceTraveled=0.f;
private:
void UpdateFadeTime(float fElapsedTime);
vf2d scale={1,1};
bool dead=false; //When marked as dead it wil be removed by the next frame.
#include "IBullet.h"
class Bullet:public IBullet{
public:
Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState;
std::set<Monster*>hitList;
virtual ~Bullet()=default;
Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1});
Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f);
//Initializes a bullet with an animation.
Bullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1});
public:
virtual void Update(float fElapsedTime);
//Used by special bullets to control custom despawning behavior! Return true when the bullet should be destroyed. Return false to handle it otherwise (like deactivating it instead). You become responsible for getting rid of the bullet.
virtual bool PlayerHit(Player*player);
//Used by special bullets to control custom despawning behavior! Return true when the bullet should be destroyed. Return false to handle it otherwise (like deactivating it instead). You become responsible for getting rid of the bullet.
virtual bool MonsterHit(Monster&monster);
Animate2D::Frame GetFrame();
virtual void Draw();
bool OnUpperLevel();
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);
};

@ -37,47 +37,59 @@ All rights reserved.
#pragma endregion
#pragma once
#include "Bullet.h"
#include "Direction.h"
#include "Effect.h"
struct EnergyBolt:public Bullet{
float lastParticleSpawn=0;
EnergyBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
bool PlayerHit(Player*player)override;
bool MonsterHit(Monster&monster)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);
};
struct FireBolt:public Bullet{
float lastParticleSpawn=0;
FireBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
bool PlayerHit(Player*player)override;
bool MonsterHit(Monster&monster)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);
};
struct LightningBolt:public Bullet{
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;
bool PlayerHit(Player*player)override;
bool MonsterHit(Monster&monster)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);
};
struct Arrow:public Bullet{
float travelDistance=0;
float finalDistance=0;
float acc=PI/2*250;
vf2d targetPos;
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;
bool PlayerHit(Player*player)override;
bool MonsterHit(Monster&monster)override;
// Change the arrow's heading by predicting a path somewhere in the future and aiming at the closest possible spot to its targetPos.
// The perception level can be a value from 0-90 indicating the sweep angle to check beyond the initial aiming angle.
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);
};
struct ChargedArrow:public Bullet{
vf2d lastLaserPos;
ChargedArrow(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
bool PlayerHit(Player*player)override;
bool MonsterHit(Monster&monster)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);
};
struct FrogTongue:public Bullet{
@ -85,16 +97,251 @@ struct FrogTongue:public Bullet{
float tongueLength;
float duration;
float knockbackStrength;
FrogTongue(vf2d pos,vf2d targetPos,float lifetime,int damage,bool upperLevel,float knockbackStrength=1.0f,bool friendly=false,Pixel col=WHITE);
Monster&sourceMonster;
FrogTongue(Monster&sourceMonster,vf2d targetPos,float lifetime,int damage,bool upperLevel,float knockbackStrength=1.0f,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
bool PlayerHit(Player*player)override;
bool MonsterHit(Monster&monster)override;
void Draw()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 Draw(const Pixel blendCol)const override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct Wisp:public Bullet{
Wisp(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE);
void Update(float fElapsedTime)override;
bool PlayerHit(Player*player)override;
bool MonsterHit(Monster&monster)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);
};
enum class HorizontalFlip{
NONE,
FLIPPED,
};
struct DaggerStab:public Bullet{
struct DirectionOffsets{
std::unordered_map<Direction,vf2d>offsets;
DirectionOffsets(const vf2d upOffset,const vf2d downOffset,const vf2d rightOffset,const vf2d leftOffset){
offsets[Direction::NORTH]=upOffset;
offsets[Direction::SOUTH]=downOffset;
offsets[Direction::EAST]=rightOffset;
offsets[Direction::WEST]=leftOffset;
}
};
Monster&sourceMonster;
Direction facingDir;
float frameDuration;
float daggerStabDistance;
float knockbackAmt;
DirectionOffsets daggerPositionOffsets;
DaggerStab(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerStabDistance,const DirectionOffsets offsets,bool friendly=false,Pixel col=WHITE);
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);
};
struct DaggerSlash:public Bullet{
Monster&sourceMonster;
Direction facingDir;
float frameDuration;
float daggerSlashDistance;
float knockbackAmt;
DaggerSlash(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerSlashDistance,bool friendly=false,Pixel col=WHITE);
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);
};
struct Bomb:public Bullet{
float gravity;
float zVel{0.f};
float detonationTime{0.f};
float bombFadeoutTime{0.f};
const float explosionRadius{};
const vf2d targetPos{};
Animate2D::AnimationState animation;
Animate2D::Animation<std::string>bomb_animation;
float bombKnockbackFactor{0.f};
Bomb(const vf2d pos,const float z,const float gravity,const float detonationTime,const float bombFadeoutTime,float bombKnockbackFactor,const vf2d targetPos,const float explosionRadius,const int damage,const bool upperLevel,const bool friendly=false,const Pixel col=WHITE,const vf2d scale={1,1});
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void Draw(const Pixel blendCol)const override;
};
struct LevitatingRock:public Bullet{
const Monster&attachedTarget;
const vf2d&attackingTarget;
float facingRotOffset;
float dist;
float lockOnTime;
float initialWaitTime;
float flyingSpd;
float fadeInTime;
const float fadeInOriginalTime;
const float collisionRadius;
std::optional<vf2d>targetVel;
std::vector<LevitatingRock*>slaveRocks;
//The rock will rotate around the attachedTarget towards the attackingTarget. The facingRotOffset determines the angle relative to the direction pointed towards this rock will be positioned at. Once the wait time expires, the rock is fired at target speed in that given direction.
//The lock on time is how long the rocks will follow the player. The wait time is how long to wait until firing.
LevitatingRock(const Monster&attachedTarget,const vf2d&attackingTarget,const float fadeInTime,const float facingRotOffset,const float distance,const float lockOnTime,const float waitTime,const float targetSpd,const float radius,const int damage,const bool upperLevel,const bool friendly=false,const Pixel col=WHITE,const vf2d scale={1,1});
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 Draw(const Pixel blendCol)const override;
void AssignMaster(LevitatingRock*masterRock);
const bool IsMaster()const;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
};
struct Tornado:public Bullet{
float rotatingSpd{};
vf2d polarAngle{};
vf2d centerPoint{};
float knockupDuration{};
float knockbackAmt{};
float fadeInDuration{};
float rot{0.f};
Tornado(vf2d centerPoint,float distance,float initialRot,float rotSpd,int damage,const float knockupAmt,const float knockbackAmt,const float lifetime,bool upperLevel,bool friendly=false,Pixel col=WHITE,const vf2d scale={1,1});
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);
};
struct Debris:public Bullet{
const int randomFrame{};
const float rotatingSpd{};
float knockbackAmt{};
Debris(const vf2d pos,const vf2d vel,const int damage,const float radius,const float knockbackAmt,const float rotSpd,const float lifetime,bool upperLevel,bool friendly=false,Pixel col=WHITE,const vf2d scale={1,1});
void Update(float fElapsedTime)override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
void ModifyOutgoingDamageData(HurtDamageInfo&data);
void Draw(const Pixel blendCol)const override;
};
struct LargeTornado:public Bullet{
float suctionAmt{}; //Suction amount in pixels/sec
float knockupDuration{};
float knockbackAmt{};
//Suction amount in pixels/sec
LargeTornado(vf2d pos,const float suctionAmt,const float knockupAmt,const float knockbackAmt,const int damage,const float radius,const float lifetime,bool upperLevel,bool friendly=false,Pixel col=WHITE,const vf2d scale={1,1});
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);
};
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);
};
struct LargeStone:public Bullet{
public:
LargeStone(vf2d pos,const float stoneThrowTime,const vf2d landingPos,float moveVelWaitTimer,float radius,float z,float gravity,int damage,const float knockbackAmt,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f);
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);
private:
const float gravity;
const float fixedTimeStep{1/30.f};
const float stoneThrowTime;
const vf2d landingPos;
const vf2d startingPos;
float fixedTimeStepAcc{};
float zVel{};
float moveTimer; //Counts down to 0, when it hits zero sets the velocity to futurevel.
const float initialMoveWaitTime;
const float knockbackAmt;
};
struct FallingStone:public Bullet{
//The position for this bullet represents where the falling stone should land.
FallingStone(vf2d targetPos,vf2d vel,float zVel,float indicatorDisplayTime,float radius,int damage,bool upperLevel,bool hitsMultiple=false,float knockbackAmt=0.f,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=0.f,float spellCircleRotation=0.f,float spellCircleRotationSpd=0.f,Pixel insigniaCol=WHITE,float insigniaRotation=0.f,float insigniaRotationSpd=0.f);
protected:
void Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
private:
const vf2d targetPos;
const float zVel{};
const float indicatorDisplayTime;
SpellCircle indicator;
const float knockbackAmt;
float lastTrailEffect{};
};
//While not a bullet directly, the DeadlyDash class generates a bunch of afterimages and collision checks.
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);
private:
const vf2d startPos;
const vf2d endPos;
const float checkRadius;
const float afterImagesSpreadDist;
const float afterImagesLingeringTime;
const float knockbackAmt;
std::string animation;
};
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);
};
struct ExplosiveTrap:public Bullet{
ExplosiveTrap(vf2d pos,float radius,float explosionRadius,float automaticDetonationTime,int damage,float fadeinTime,float fadeoutTime,float activationWaitTime,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1});
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);
private:
void Detonate();
const float explosionRadius{};
const float activationRadius{};
float automaticDetonationTime{};
float activationWaitTime{};
float lastBeepTime{};
uint8_t beepCount{1U};
};
struct PurpleEnergyBall:public Bullet{
PurpleEnergyBall(vf2d pos,float radius,float homingRadius,int damage,bool upperLevel,vf2d speed,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1});
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()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
private:
const vf2d initialScale;
const float homingRadius;
std::optional<std::weak_ptr<Monster>>homingTarget;
};
struct PoisonBottle:public Bullet{
PoisonBottle(vf2d pos,vf2d targetPos,float explodeRadius,float z,float totalFallTime,float totalRiseZAmt,int damage,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle={0.f});
void Update(float fElapsedTime)override;
void ModifyOutgoingDamageData(HurtDamageInfo&data);
private:
const vf2d targetPos;
const vf2d startingPos;
const float totalFallTime;
const float originalRisingTime,originalFallingTime;
float risingTime,fallingTime;
const float initialZ;
const float totalRiseZAmt;
const float explodeRadius;
};

@ -61,6 +61,20 @@ void Menu::InitializeBuyItemWindow(){
if(!canPurchase)colorCode="#FF0000";
Component<MenuLabel>(BUY_ITEM,"Total Price Amount Label")->SetLabel(colorCode+std::to_string(qty*pricePerItem));
Component<MenuComponent>(BUY_ITEM,"Purchase Button")->SetGrayedOut(!canPurchase);
if(qty==99||pricePerItem*(qty+1)>game->GetPlayer()->GetMoney()){
Component<MenuComponent>(BUY_ITEM,"Increase buy amount Button")->SetGrayedOut(true);
Menu::menus[BUY_ITEM]->SetSelection("Decrease buy amount Button"sv);
}else{
Component<MenuComponent>(BUY_ITEM,"Increase buy amount Button")->SetGrayedOut(false);
}
if(qty<=1){
Component<MenuComponent>(BUY_ITEM,"Decrease buy amount Button")->SetGrayedOut(true);
Menu::menus[BUY_ITEM]->SetSelection(static_cast<std::weak_ptr<MenuComponent>>(Component<MenuComponent>(BUY_ITEM,"Increase buy amount Button")));
}else{
Component<MenuComponent>(BUY_ITEM,"Decrease buy amount Button")->SetGrayedOut(false);
}
};
buyItemWindow->ADD("Item Purchase Header",MenuLabel)(geom2d::rect<float>{{2,2},{188,12}},"Buying ",1.f,ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND|ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL)END;
@ -120,6 +134,7 @@ void Menu::InitializeBuyItemWindow(){
},[](MenuType type){
Component<MenuComponent>(type,"Purchase Button")->Click();
}}},
{{game->KEY_SHOULDER2,Pressed},{"Qty Up/Down",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Qty Up/Down",[](MenuType type){}}},
{{game->KEY_FASTSCROLLDOWN,PressedDAS},{"",[](MenuType type){
Component<MenuComponent>(type,"Increase buy amount Button")->Click();
@ -175,8 +190,8 @@ void Menu::InitializeBuyItemWindow(){
.left="Decrease buy amount Button",
.right="Decrease buy amount Button",}},
{"Decrease buy amount Button",{
.up="Cancel Button",
.down="Cancel Button",
.up="Purchase Button",
.down="Purchase Button",
.left="Increase buy amount Button",
.right="Increase buy amount Button",}},
});

@ -1,3 +1,6 @@
Quapsel
A2Z
The inspiration that started it all, Pokemon-based Nico Yazawa sprite:
https://www.deviantart.com/kirbysmith/art/Nico-Yazawa-Love-Live-Pokemon-Sprites-548007023

@ -0,0 +1,69 @@
Chapter 2 Boss
Stonegolem
HP: 30 000
Size: 800%
>The boss casts 3 Pillars at the beginning of the fight, targeting the player.
>1 at a time.
>2 sec cast.
>0.5 sec delay until next cast starts.
>40 dmg
>Pillar-radius: 350
>every 10% (first time at 90%) use a shockwave where you need to hide behind one of the pillar to avoid the damage. (Shockwave has 3 seconds cast, 60 dmg)
>The pillars get damaged with every shockwave. after the 3rd the pillar break.
>starting 75% every 10% (75%, 65%, 55% ...) The Stone golem targetst the Player with 3 Pillar attacks like the one at the beginning of the fight.
>2 of the 3 Pillars spawn damaged and will break 3 seconds later again. one is solid and can tank up to 3 shockwaves.
>Like previously every pillar appears with 2,5 seconds delay from each other. (2 sec with an indicator, 0,5 seconds delay after a pillar got created)
>The Boss continues with its normal behaviour while the pillars are getting casted.
>every Pillar that breaks Casts a ring of projectiles on death (15 dmg)
>The golem attacks similar like bear boss but with half the radius on the attack. (35 dmg)
>after every attack there is a 25% chance The Stone golem targets the Player. the targeting gets Indicated around the player.
>after 2.5 seconds the golems throws a giant rock with 250 radius on the players position. Damaging the player and destroying Pillars hit. (55 dmg)
>Stone Throw cant overlap with shockwave. when a 10% mark is reached while a rock is targeting the player, Shockwave cast starts after the rock was thrown.
if the golem wasnt able to attack the player for 7 seconds it throws 2 stones in the air.
Afterwards Screenwide ~25 indicators appear where small rocks will rain down with a delay of 2,5 - 4 seonds (30 dmg each, 50 radius)
(The trigger condition for this ability may changes in the future, depending how it actually plays out.)

@ -0,0 +1,87 @@
Chapter 2 Monsters:
Boar:
Hp: 260
Attack: 24
Size: 110%
Move-Spd: 100%
Attack Strategie: If range to player >700 move closer.
if range 400-700 Scratch the ground twice and then charge with 30% Move-Spd bonus in the players direction for a distance of 900.
If range to player<400 backpaddle with 50% move-spd. if getting hit while backpaddling, start charge sequence.
Before a charge occurs, the boar will make sure to have full line-of-sight of the player before beginning a charge.
Once a charge begins, the direction cannot be modified.
Goblin (Dagger):
Hp: 120
Attack: 18
Size: 90%
Move-Spd: 90%
Attack Strategie: Runs infront of player, short preparation for attack (~0.3 - 0.5 secs depending what looks better animation wise) and either slashes with the dagger or stabs with it. 1 sec of idle before loop repeats
Goblin (Bow):
Hp: 120
Attack: 18
Size: 90%
Move-Spd: 70%
Attack Strategie:
if range > 1000 move to a 850 range.
if range 1000-700. Stand still and shoot. (reload twice as fast)
if range between 500 and 700. Shoot and run in random direction while reloading without getting out of the 500 - 700 range
range < 500 while reloading try to get distance. still shoot if reload finishes
After every attack: reload takes 2 seconds. before shooting takes 1 second.
Goblin (Bombs):
Hp: 120
Attack: 38
Size: 90%
Move-Spd: 70%
Attack Strategie: Throws every 4 seconds a bomb on top of the player that detonates after 3.5 seconds in a 300 radius. Tries to keeps his distance on a 600 radius. Throws bombs even while moving.
Goblin Boar Rider:
Same stats as Boar.
On death leaves a Bow Goblin on location of death
Same attack strategie as normal Boar.
+ the Goblin on Top also shoots every 4 seconds
Hawks
Hp: 10
Attack: 22
Size: 50%
Move-Spd: 180%
Attack Strategie: Fly circles at the edge of the Players Screen. every 8 seconds charge on players location to the other corner of the screen.
Stone Elemental
Hp: 990
Attack: 52
Move-Spd: 50%
Size: 220%
Attack Strategie:
Every 2 seconds after completing an attack do one of the following attacks:
Casts a Stone Pillar on the location of the Player: radius 350. Hits with 3 seconds delay. Becomes an solid object (Player colision) and decays after 5 seconds again.
If the Stone pillar would hit the Stone Elemental it will move out of its range.
Shoot a Stone (bullet) in the direction of the target.
The Stone gets created in form of a Cone and tracks the player movement without moving for the first second.
Once it locks it doesnt move for another second. then it starts flying towards the player with 3x the normal Bullet speed.
Dive under earth and show up 1 second later in a random location 400-700 away from player. while emerging shoot a ring of bullets (0.5x Attack)
(maybe emerge with 200 away from anything with colision to avoid the Elemental getting stuck? Other solutions for that are welcome aswell)
if range to player > 1200 always use 3rd attack

@ -0,0 +1,190 @@
Pirate
-Wields a Saber and wears an eye patch, pirate hat, the typical Pirate stereotype.
Animations (5):
Idle
Walk
Slash
Stab
Dead
Pirate Marauder
-Equipped with 2 Swords.
Animations (5):
Idle
Walk
Jump Attack (Plunge)
Whirlwind Attack (Spinning around frantically)
Dead
Pirate Buccaneer
-Equipped with a musket.
Animations (5):
Idle
Walk
Aim
Firing
Dead
Pirate Captain
-Holds a Saber in one hand and a Hand Gun in the other.
Animations (7):
Idle
Walk
Slash using Saber
Stab using Saber
Raise + Shoot Handgun
Drink a bottle of Rum.
Dead
Parrot
======
(Does not require a death animation)
Animations (3):
Idle
Fly
Charge
Crab
====
Animations (4):
Idle
Sidestep / Crab Walk
Lifts up pincers and charges
Dead
Sandworm
========
Starts underground (can see silhouette)
Animations (6):
Idle (Head above ground)
Dive underground
Swim in sand
Rise up from ground
Shoot/Spit Sand
Dead
Seagull
=======
Animations (3):
Idle
Flying
Dead
=================
Pirate
Size: 100%
Hp: 350
Exp: 33
Attack: 31
Move-Spd: 110%
Equipped with a Saber.
Same behaviour like Chapter 2 Dagger Goblins.
Pirate Marauder
Size: 100%
Hp: 500
Exp: 39
Attack: 43
Move-Spd: 125%
Equipped with 2 Swords.
Base behaviour like Pirate.
every 8 seconds chance to do an ability:
25% & insight of players vision & further then 400 range away: Jump attack on the player 150 radius impact area
25% & within 700 range of player & further then 400 range away: whirlwind attack, spinning to the players position +60% Move-spd during this attack
50% continue base behaviour.
Pirate Buccaneer
Size: 100%
Hp: 430
Exp: 37
Attack: 45
Move-Spd: 100%
Equipped with a Muskeet.
Aim (1.0 Sec) - Lock position (0.3 sec) - Shoot - reload (2 sec) - move if to far away. (trying to stay in 1000 Range)
Pirate Captain (+Parrot)
Size: 120%
Hp: 1050
Exp: 59
Attack: 55
Move-Spd: 95%
1 hand Saber. 1 Hand Gun
Also Base Pirate behaviour.
every 6 seconds if range <400 60% chance to stand still for 0.5 seconds - shoot his gun and continue base movement.
Once the Captain dropps below 300 health he pulls out a bottle of Rum and starts drinking. takes 2 seconds and recovers 150 Health. Only triggers once.
When the Pirate Enters the Screen the Parrot is still sitting on its shoulder and starts flying 2 seconds after the enemy is visible.
The parrot has the same stats and behaviour like chapter 2 Hawks.
The Parrot doesnt die, instead it gets stunned for 5 seconds on death and then recovers.
When the Captain dies the Parrot flies away and despawns out of screen.
Crap
Size: 60%
Hp: 600
Exp: 34
Attack: 23
Move-Spd: 120%
Side step circles around the player in a 400 range radius.
every second running in a circle 20% chance to stop for 0.5 seconds, turn his side towards the player and charges with a pincer attack and runs a bit further to get to 400 range on the other side of the player.
starts circling the player again.
Giant Crap
Size: 180%
Hp: 1800
Exp: 61
Attack: 49
Move-Spd: 90%
Charges player sideways. charges 600 range further then players position.
After charge 1 second no moving. then starts next charge building up movespeed over first 2 seconds.
Sandworm
Size: 90%
Hp: 400
Exp: 24
Attack: 33
Move-Spd: 200% // 0% (stationary on air)
The sandworm spawns underground.
The sandworm ist completly invisible when its underground.
It moves to a random point within a 400 range around the player. (can be exactly under the player aswell)
once it reaches it position there is an 300 radisu animation and the player gets sucked in with a 30% movespd effect (
Examples:
if playing doesnt move it moves with 30% speed into the middle,
if moving to the center normal speed +30%
if moving out normal speed -30%
Effect lasts for 3 seconds. If player is in the middle player receives collision damage.
Worm behaves like Chapter 1 Flower for up to 6 Attacks or if range to player is higher then 1500.
Then it burrows down again and repeats.
Seagul
Size: 80%
Hp: 40
Exp: 6
Attack: 0
Move-Spd: 120%
When Player gets within 1000 Range Seagul starts flying away. takes 2 seconds to leave the ground. (time needs to be extended if 2 seconds arent enough for a warrior to get in warcry range)
Despawns once it reaches 4000 Range.
Bosses
Giant Octopus
Pirate Captain Ghost

@ -64,16 +64,16 @@ protected:
float boxWidth=rect.size.y-4; //No, the y is not a typo. It's a square, we use the y to determine the x.
window.DrawRectDecal(rect.pos+vi2d{2,2},vi2d{int(rect.size.y)-4,int(rect.size.y)-4});
window.DrawDecal(iconPos,GFX[ability->icon].Decal());
window.DrawDecal(iconPos,GFX[ability->icon].Decal(),{1.33f,1.33f});
vi2d descriptionPos=iconPos+vi2d{int(rect.size.y)-2,-1};
vi2d descriptionPos=iconPos+vi2d{int(rect.size.y)-2,9};
window.DrawShadowStringPropDecal(descriptionPos-vf2d{0,10},ability->name,{0xFF,0xAF,0x56},BLACK,{0.8f,1.f},int(rect.size.x-(descriptionPos.x-rect.pos.x))-4);
window.DrawShadowStringPropDecal(descriptionPos,ability->description,WHITE,BLACK,{0.8f,1.f},int(rect.size.x-(descriptionPos.x-rect.pos.x))-4);
InputType controlType=KEY;
if(Input::UsingGamepad())controlType=CONTROLLER;
if(textWidth>boxWidth){
window.DrawShadowStringPropDecal(textPos,ability->input->GetDisplayName(),WHITE,BLACK,{boxWidth/textWidth*0.5f,0.5});
}else{
window.DrawShadowStringPropDecal(textPos,ability->input->GetDisplayName(),WHITE,BLACK,{0.5,0.5});
}
ability->input->DrawPrimaryInput(&window,textPos+vf2d{boxWidth/2,7},"",255,controlType,{0.5f,0.5f});
}
};

@ -46,14 +46,97 @@ All rights reserved.
#include "EquipSlotButton.h"
#include "Item.h"
#include "ScrollableWindowComponent.h"
#include "RowItemDisplay.h"
#include "AccessoryRowItemDisplay.h"
#include "SoundEffect.h"
#include "ProgressBar.h"
#include <bit>
#include "MenuItemLabel.h"
#ifndef __EMSCRIPTEN__
#include "steam/isteamuserstats.h"
#endif
INCLUDE_game
INCLUDE_GFX
namespace CharacterMenuWindow{
struct AttributeData{
std::string attrName;
const std::function<int()>calcFunc;
};
const static std::array<AttributeData,7>displayAttrs{
AttributeData{"Health",[]()->int{return game->GetPlayer()->GetMaxHealth();}},
AttributeData{"Attack",[]()->int{return game->GetPlayer()->GetAttack();}},
AttributeData{"Defense",[]()->int{return game->GetPlayer()->GetDefense();}},
AttributeData{"Move Spd %",[]()->int{return ceil(game->GetPlayer()->GetMoveSpdMult()*100);}},
AttributeData{"CDR",[]()->int{return ceil(game->GetPlayer()->GetCooldownReductionPct()*100);}},
AttributeData{"Crit Rate",[]()->int{return ceil(game->GetPlayer()->GetCritRatePct()*100);}},
AttributeData{"Crit Dmg",[]()->int{return ceil(game->GetPlayer()->GetCritDmgPct()*100);}},
};
const static std::array<std::string,8>slotNames{"Helmet","Weapon","Armor","Gloves","Pants","Shoes","Ring 1","Ring 2"};
template<class T>
std::shared_ptr<RowItemDisplay>GenerateItemDisplay(std::shared_ptr<ScrollableWindowComponent>parent,int invIndex,const std::weak_ptr<Item>it){
auto component=parent->ADD("Equip Item "+std::to_string(invIndex),T)(geom2d::rect<float>{{2,2+invIndex*29.f},{120-15,28}},it,
[&](MenuFuncData data){
const auto SelectedEquipIsDifferent=[](std::weak_ptr<RowItemDisplay>comp){
EquipSlot slot=EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE));
std::weak_ptr<Item>currentItem=Inventory::GetEquip(slot);
switch(slot){
case EquipSlot::RING1:
case EquipSlot::RING2:{
std::weak_ptr<Item>otherItem;
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
return ISBLANK(otherItem)||
(&*comp.lock()->GetItem().lock()!=&*otherItem.lock()&&
&*comp.lock()->GetItem().lock()!=&*currentItem.lock());
}break;
default:{
return &*comp.lock()->GetItem().lock()!=&*currentItem.lock();
}
}
};
std::weak_ptr<T>comp=DYNAMIC_POINTER_CAST<T>(data.component.lock());
if(!comp.expired()){
if(SelectedEquipIsDifferent(comp)){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
Inventory::EquipItem(comp.lock()->GetItem(),EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE)));
#pragma region Fully Decked Out Achievement
STEAMUSERSTATS(
datafile&unlock=DATA.GetProperty("Achievement.Equip Unlocks.Fully Decked Out");
if(Inventory::EquipsFullyMaxedOut(unlock["Weapon Max Level"].GetInt(),unlock["Armor Max Level"].GetInt())){
SteamUserStats()->SetAchievement(unlock["API Name"].GetString().c_str());
SteamUserStats()->StoreStats();
}
)
#pragma endregion
if(Menu::IsCurrentlyActive(data.menu.GetType())){
SoundEffect::PlaySFX(comp.lock()->GetItem().lock()->UseSound(),SoundEffect::CENTERED);
}
for(std::weak_ptr<MenuComponent>button:DYNAMIC_POINTER_CAST<ScrollableWindowComponent>(data.parentComponent.lock())->GetComponents()){
std::weak_ptr<T>comp=DYNAMIC_POINTER_CAST<T>(button.lock());
comp.lock()->SetSelected(false);
}
comp.lock()->SetSelected(true);
for(int counter=0;const AttributeData&attribute:displayAttrs){
std::shared_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute.attrName).Name())+" Label");
statDisplayLabel->SetStatChangeAmt(0);
}
std::shared_ptr<MenuItemItemButton>equipButton=Component<MenuItemItemButton>(CHARACTER_MENU,"Equip Slot "+slotNames[data.parentComponent.lock()->I(A::INDEXED_THEME)]);
equipButton->SetItem(comp.lock()->GetItem(),false);
}
}else{
ERR("WARNING! Attempting to cast a button that isn't a RowItemDisplay!");
}
return true;
},"","")END;
return component;
}
};
void Menu::InitializeCharacterMenuWindow(){
static bool equipmentWindowOpened=false;
static int lastEquipButtonOpened=0;
@ -63,23 +146,12 @@ void Menu::InitializeCharacterMenuWindow(){
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 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;
const static std::array<std::string,7>displayAttrs{
"Health",
"Attack",
"Defense",
"Move Spd %",
"CDR",
"Crit Rate",
"Crit Dmg",
};
const static std::array<std::string,8>slotNames{"Helmet","Weapon","Armor","Gloves","Pants","Shoes","Ring 1","Ring 2"};
characterMenuWindow->ADD("Equip Selection Outline",MenuComponent)(geom2d::rect<float>{{123,28},{120,windowSize.y-37}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END
->Disable();
characterMenuWindow->ADD("Equip List",ScrollableWindowComponent)(geom2d::rect<float>{{123,28},{120,windowSize.y-37-24}})DEPTH -1 END
@ -93,11 +165,11 @@ void Menu::InitializeCharacterMenuWindow(){
Component<MenuComponent>(data.component.lock()->parentMenu,"Equip Selection Bottom Outline")->Disable();
Component<MenuComponent>(data.component.lock()->parentMenu,"Equip Selection Select Button")->Disable();
Component<CharacterRotatingDisplay>(data.component.lock()->parentMenu,"Character Rotating Display")->Enable();
for(int counter=0;const std::string&attribute:displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
for(int counter=0;const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute.attrName).Name())+" Label");
statDisplayLabel.lock()->SetStatChangeAmt(0);
}
data.menu.SetSelection(std::string_view(std::format("Equip Slot {}",slotNames[std::bit_width(unsigned(data.menu.I(A::EQUIP_TYPE)))-1])));
data.menu.SetSelection(std::string_view(std::format("Equip Slot {}",CharacterMenuWindow::slotNames[std::bit_width(unsigned(data.menu.I(A::EQUIP_TYPE)))-1])));
equipmentWindowOpened=false;
return true;
})DEPTH 0 END;
@ -106,7 +178,7 @@ void Menu::InitializeCharacterMenuWindow(){
const static auto GetLabelText=[](ItemAttribute attribute){
std::string attrStr=std::string(attribute.Name())+"\n ";
attrStr+=std::to_string(game->GetPlayer()->GetStat(attribute));
attrStr+=std::to_string(game->GetPlayer()->GetEquipStat(attribute));
if(attribute.DisplayAsPercent()){
attrStr+="%";
}
@ -124,7 +196,7 @@ void Menu::InitializeCharacterMenuWindow(){
labelY-=8;
}
EquipSlot slot=EquipSlot(equipSlot);
auto equipmentSlot=characterMenuWindow->ADD("Equip Slot "+slotNames[i],EquipSlotButton)(geom2d::rect<float>{{x,y+28},{24,24}},slot,
auto equipmentSlot=characterMenuWindow->ADD("Equip Slot "+CharacterMenuWindow::slotNames[i],EquipSlotButton)(geom2d::rect<float>{{x,y+28},{24,24}},slot,
[&](MenuFuncData data){
EquipSlot slot=EquipSlot(data.component.lock()->I(Attribute::EQUIP_TYPE));
data.menu.I(A::EQUIP_TYPE)=int(slot);
@ -142,76 +214,80 @@ void Menu::InitializeCharacterMenuWindow(){
std::shared_ptr<ScrollableWindowComponent>equipList=Component<ScrollableWindowComponent>(data.component.lock()->parentMenu,"Equip List");
equipList->RemoveAllComponents();
for(int counter=0;const std::weak_ptr<Item>it:availableEquipment){
const static auto OppositeRingSlotDoesNotMatchCurrentEquip=[](std::weak_ptr<RowItemDisplay>comp){
EquipSlot slot=EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE));
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());
};
auto equip=equipList->ADD("Equip Item "+std::to_string(counter),RowItemDisplay)(geom2d::rect<float>{{2,2+counter*29.f},{120-15,28}},it,
[](MenuFuncData data){
std::weak_ptr<RowItemDisplay>comp=DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component.lock());
if(!comp.expired()){
if(OppositeRingSlotDoesNotMatchCurrentEquip(comp.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(comp.lock()->GetItem(),EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE)));
if(Menu::IsCurrentlyActive(data.menu.GetType())){
SoundEffect::PlaySFX(comp.lock()->GetItem().lock()->UseSound(),SoundEffect::CENTERED);
}
for(std::weak_ptr<MenuComponent>button:DYNAMIC_POINTER_CAST<ScrollableWindowComponent>(data.parentComponent.lock())->GetComponents()){
std::weak_ptr<RowItemDisplay>comp=DYNAMIC_POINTER_CAST<RowItemDisplay>(button.lock());
comp.lock()->SetSelected(false);
}
comp.lock()->SetSelected(true);
for(int counter=0;const std::string&attribute:displayAttrs){
std::shared_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
statDisplayLabel->SetStatChangeAmt(0);
}
std::shared_ptr<MenuItemItemButton>equipButton=Component<MenuItemItemButton>(CHARACTER_MENU,"Equip Slot "+slotNames[data.parentComponent.lock()->I(A::INDEXED_THEME)]);
equipButton->SetItem(comp.lock()->GetItem(),false);
}
}else{
ERR("WARNING! Attempting to cast a button that isn't a RowItemDisplay!");
}
return true;
},"Item Name","Item Description")END;
std::shared_ptr<RowItemDisplay>equip;
const bool isAccessorySlot=slot&(EquipSlot::RING1|EquipSlot::RING2);
if(isAccessorySlot){
equip=CharacterMenuWindow::GenerateItemDisplay<AccessoryRowItemDisplay>(equipList,counter,it);
}else{
equip=CharacterMenuWindow::GenerateItemDisplay<RowItemDisplay>(equipList,counter,it);
}
equip->SetHoverFunc(
[&](MenuFuncData data){
const auto SelectedEquipIsDifferent=[](std::weak_ptr<RowItemDisplay>comp){
EquipSlot slot=EquipSlot(comp.lock()->I(Attribute::EQUIP_TYPE));
std::weak_ptr<Item>currentItem=Inventory::GetEquip(slot);
switch(slot){
case EquipSlot::RING1:
case EquipSlot::RING2:{
std::weak_ptr<Item>otherItem;
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
return ISBLANK(otherItem)||
(&*comp.lock()->GetItem().lock()!=&*otherItem.lock()&&
&*comp.lock()->GetItem().lock()!=&*currentItem.lock());
}break;
default:{
return &*comp.lock()->GetItem().lock()!=&*currentItem.lock();
}
}
};
if(!data.component.lock()->GetSubcomponentParent().expired())return true;
std::weak_ptr<RowItemDisplay>button=DYNAMIC_POINTER_CAST<RowItemDisplay>(data.component.lock());
if(!button.expired()){
const std::weak_ptr<Item>buttonItem=button.lock()->GetItem();
std::vector<float>statsBeforeEquip;
EquipSlot slot=EquipSlot(button.lock()->I(Attribute::EQUIP_TYPE));
for(const std::string&attribute:displayAttrs){
statsBeforeEquip.push_back(game->GetPlayer()->GetStat(attribute));
for(const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
statsBeforeEquip.push_back(attribute.calcFunc());
}
int healthBeforeEquip=game->GetPlayer()->GetHealth();
int manaBeforeEquip=game->GetPlayer()->GetMana();
std::weak_ptr<Item>equippedItem=Inventory::GetEquip(slot);
std::weak_ptr<Item>otherItem;
if(slot==EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
if(slot&EquipSlot::RING1)otherItem=Inventory::GetEquip(EquipSlot::RING2);
else
if(slot==EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
if(OppositeRingSlotDoesNotMatchCurrentEquip(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.
if(slot&EquipSlot::RING2)otherItem=Inventory::GetEquip(EquipSlot::RING1);
if(SelectedEquipIsDifferent(button.lock())){ //If we find that the opposite ring slot is equipped to us, this would be an item swap or the exact same ring, therefore no stat calculations apply.
Inventory::EquipItem(buttonItem,slot);
for(int counter=0;const std::string&attribute:displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
int statChangeAmt=game->GetPlayer()->GetStat(attribute)-statsBeforeEquip[counter];
for(int counter=0;const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute.attrName).Name())+" Label");
int statChangeAmt=attribute.calcFunc()-statsBeforeEquip[counter];
statDisplayLabel.lock()->SetStatChangeAmt(statChangeAmt);
counter++;
}
Inventory::UnequipItem(slot);
if(!ISBLANK(equippedItem)){
Inventory::EquipItem(equippedItem,slot);
game->GetPlayer()->Heal(healthBeforeEquip-game->GetPlayer()->GetHealth(),true);
game->GetPlayer()->RestoreMana(manaBeforeEquip-game->GetPlayer()->GetMana(),true);
}
if(!ISBLANK(otherItem)){
if(slot==EquipSlot::RING1)Inventory::EquipItem(otherItem,EquipSlot::RING2);
if(slot&EquipSlot::RING1)Inventory::EquipItem(otherItem,EquipSlot::RING2);
else
if(slot==EquipSlot::RING2)Inventory::EquipItem(otherItem,EquipSlot::RING1);
if(slot&EquipSlot::RING2)Inventory::EquipItem(otherItem,EquipSlot::RING1);
}
}
Component<MenuItemLabel>(data.menu.GetType(),"Item Name")->SetItem(buttonItem);
Component<MenuItemLabel>(data.menu.GetType(),"Item Description")->SetItem(buttonItem);
Component<MenuItemLabel>(data.menu.GetType(),"Item Name")->Enable();
Component<MenuItemLabel>(data.menu.GetType(),"Item Description")->Enable();
}else{
ERR("WARNING! Attempting to cast a button that isn't a RowItemDisplay!");
}
@ -219,11 +295,13 @@ void Menu::InitializeCharacterMenuWindow(){
});
equip->SetMouseOutFunc(
[](MenuFuncData data){
for(int counter=0;const std::string&attribute:displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label");
for(int counter=0;const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
std::weak_ptr<StatLabel>statDisplayLabel=Component<StatLabel>(CHARACTER_MENU,"Attribute "+std::string(ItemAttribute::Get(attribute.attrName).Name())+" Label");
statDisplayLabel.lock()->SetStatChangeAmt(0);
counter++;
}
Component<MenuItemLabel>(data.menu.GetType(),"Item Name")->Disable();
Component<MenuItemLabel>(data.menu.GetType(),"Item Description")->Disable();
return true;
});
@ -249,7 +327,7 @@ void Menu::InitializeCharacterMenuWindow(){
auto equipmentList=equipList->GetComponents();
auto itemEquipped=std::find_if(equipmentList.begin(),equipmentList.end(),[&](std::weak_ptr<MenuComponent>&component){
return !ISBLANK(Inventory::GetEquip(slot))&&&*DYNAMIC_POINTER_CAST<RowItemDisplay>(component)->GetItem().lock()==&*Inventory::GetEquip(slot).lock();
return !ISBLANK(Inventory::GetEquip(slot))&&component.lock()->GetSubcomponentParent().expired()&&&*DYNAMIC_POINTER_CAST<RowItemDisplay>(component)->GetItem().lock()==&*Inventory::GetEquip(slot).lock();
});
if(itemEquipped!=equipmentList.end()){
data.menu.SetSelection(*itemEquipped,true,true);
@ -291,24 +369,23 @@ void Menu::InitializeCharacterMenuWindow(){
equipmentSlot->SetShowQuantity(false);
equipmentSlot->SetCompactDescriptions(false);
equipSlot<<=1;
characterMenuWindow->ADD("Equip Label "+slotNames[i],PopupMenuLabel)(geom2d::rect<float>{{labelX,labelY},{24,16}},slotNames[i],vf2d{0.5,1.f},ComponentAttr::SHADOW)END;
characterMenuWindow->ADD("Equip Label "+CharacterMenuWindow::slotNames[i],PopupMenuLabel)(geom2d::rect<float>{{labelX,labelY},{24,16}},CharacterMenuWindow::slotNames[i],vf2d{0.5,1.f},ComponentAttr::SHADOW)END;
Menu::AddEquipStatListener(equipmentSlot);
}
characterMenuWindow->ADD("Stat Display Outline",MenuComponent)(geom2d::rect<float>{{245,28},{62,windowSize.y-37}},"",DO_NOTHING,ButtonAttr::UNSELECTABLE)END;
int yOffset=0;
for(const std::string&attribute:displayAttrs){
std::string attrStr=GetLabelText(ItemAttribute::Get(attribute));
auto attrLabel=characterMenuWindow->ADD("Attribute "+std::string(ItemAttribute::Get(attribute).Name())+" Label",StatLabel)(geom2d::rect<float>{{245,28+2+float(yOffset)},{62,18}},ItemAttribute::Get(attribute),1,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN|ComponentAttr::FIT_TO_LABEL)END;
Menu::AddEquipStatListener(attrLabel);
for(const CharacterMenuWindow::AttributeData&attribute:CharacterMenuWindow::displayAttrs){
std::string attrStr=GetLabelText(ItemAttribute::Get(attribute.attrName));
auto attrLabel=characterMenuWindow->ADD("Attribute "+std::string(ItemAttribute::Get(attribute.attrName).Name())+" Label",StatLabel)(geom2d::rect<float>{{245,28+2+float(yOffset)},{62,18}},ItemAttribute::Get(attribute.attrName),attribute.calcFunc,1,ComponentAttr::SHADOW|ComponentAttr::LEFT_ALIGN|ComponentAttr::FIT_TO_LABEL)END;
yOffset+=20;
}
characterMenuWindow->ADD("Back button",MenuComponent)(geom2d::rect<float>{{windowSize.x/2-64,windowSize.y-4.f},{128,12}},"Back",[](MenuFuncData data){Menu::stack.pop_back();return true;})END;
auto itemNameDisplay=characterMenuWindow->ADD("Item Name",MenuLabel)(geom2d::rect<float>{{0,28},{120,12}},"",1,ComponentAttr::BACKGROUND|ComponentAttr::LEFT_ALIGN|ComponentAttr::OUTLINE|ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL)END;
auto itemDescriptionDisplay=characterMenuWindow->ADD("Item Description",MenuLabel)(geom2d::rect<float>{{0,40},{120,windowSize.y-49}},"",1,ComponentAttr::BACKGROUND|ComponentAttr::LEFT_ALIGN|ComponentAttr::OUTLINE|ComponentAttr::SHADOW)END;
auto itemNameDisplay=characterMenuWindow->ADD("Item Name",MenuItemLabel)(geom2d::rect<float>{{0,28},{120,12}},std::weak_ptr<Item>{},ITEM_NAME,1.f,ComponentAttr::BACKGROUND|ComponentAttr::LEFT_ALIGN|ComponentAttr::OUTLINE|ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL)END;
auto itemDescriptionDisplay=characterMenuWindow->ADD("Item Description",MenuItemLabel)(geom2d::rect<float>{{0,40},{120,windowSize.y-49}},std::weak_ptr<Item>{},ITEM_DESCRIPTION,1.f,ComponentAttr::BACKGROUND|ComponentAttr::LEFT_ALIGN|ComponentAttr::OUTLINE|ComponentAttr::SHADOW)END;
auto itemEquipNameDisplay=characterMenuWindow->ADD("Item Equip Name",MenuLabel)(geom2d::rect<float>{{123,28},{120,12}},"",1,ComponentAttr::BACKGROUND|ComponentAttr::LEFT_ALIGN|ComponentAttr::OUTLINE|ComponentAttr::SHADOW|ComponentAttr::FIT_TO_LABEL)END;
auto itemEquipDescriptionDisplay=characterMenuWindow->ADD("Item Equip Description",MenuLabel)(geom2d::rect<float>{{123,40},{120,windowSize.y-49}},"",1,ComponentAttr::BACKGROUND|ComponentAttr::LEFT_ALIGN|ComponentAttr::OUTLINE|ComponentAttr::SHADOW)END;
@ -319,9 +396,10 @@ void Menu::InitializeCharacterMenuWindow(){
characterMenuWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData=std::format("Equip Slot {}",slotNames[0]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[0]);
},
{ //Button Key
{{game->KEY_SHOULDER2,Pressed},{"Scroll",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Scroll",[](MenuType type){}}},
{{game->KEY_SCROLL,Analog},{"Scroll",[](MenuType type){}}},
{{game->KEY_MOUSE_RIGHT,Pressed},{[](MenuFuncData data){
@ -372,8 +450,9 @@ void Menu::InitializeCharacterMenuWindow(){
}}},
{game->KEY_BACK,{"Back",[](MenuType type){
if(!Menu::menus[type]->GetSelection().expired()&&
!Menu::menus[type]->GetSelection().lock()->parentComponent.expired()&&
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->GetName()=="Equip List"){
(!Menu::menus[type]->GetSelection().lock()->parentComponent.expired()&&
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->GetName()=="Equip List")||
Menu::menus[type]->GetSelection().lock()->GetName()=="Equip Selection Select Button"){
Component<MenuComponent>(type,"Equip Selection Select Button")->Click();
}else{
Component<MenuComponent>(type,"Back button")->Click();
@ -411,6 +490,27 @@ void Menu::InitializeCharacterMenuWindow(){
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->IncreaseSelectionIndex(-3.f);
}
}}},
{game->KEY_SELECT,{[](MenuFuncData data){
if(!data.menu.GetSelection().expired()&&
!data.menu.GetSelection().lock()->parentComponent.expired()&&
data.menu.GetSelection().lock()->parentComponent.lock()->GetName()=="Equip List"
&&data.component.lock()->GetSubcomponentParent().expired()
&&EquipSlot(data.menu.I(A::EQUIP_TYPE))&(EquipSlot::RING1|EquipSlot::RING2)){
if(DYNAMIC_POINTER_CAST<AccessoryRowItemDisplay>(data.menu.GetSelection().lock())->GetItem().lock()->IsLocked()){
return "Unlock";
}else{
return "Lock";
}
}
return "";
},[](MenuType type){
if(!Menu::menus[type]->GetSelection().expired()&&
!Menu::menus[type]->GetSelection().lock()->parentComponent.expired()&&
Menu::menus[type]->GetSelection().lock()->parentComponent.lock()->GetName()=="Equip List"
&&Menu::menus[type]->GetSelection().lock()->GetSubcomponentParent().expired()){
DYNAMIC_POINTER_CAST<AccessoryRowItemDisplay>(Menu::menus[type]->GetSelection().lock())->GetLockButton().lock()->Click();
}
}}},
}
,{ //Button Navigation Rules
{"Equip List",{
@ -440,11 +540,11 @@ void Menu::InitializeCharacterMenuWindow(){
},
.left=[](MenuType type,Data&returnData){
auto equipList=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents();
returnData=std::format("Equip Slot {}",slotNames[std::bit_width(unsigned(Menu::menus[type]->I(A::EQUIP_TYPE)))-1]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[std::bit_width(unsigned(Menu::menus[type]->I(A::EQUIP_TYPE)))-1]);
},
.right=[](MenuType type,Data&returnData){
auto equipList=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents();
returnData=std::format("Equip Slot {}",slotNames[std::bit_width(unsigned(Menu::menus[type]->I(A::EQUIP_TYPE)))-1]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[std::bit_width(unsigned(Menu::menus[type]->I(A::EQUIP_TYPE)))-1]);
}
}},
{"Equip Selection Select Button",{
@ -464,108 +564,108 @@ void Menu::InitializeCharacterMenuWindow(){
},
.left=[](MenuType type,Data&returnData){
auto equipList=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents();
returnData=std::format("Equip Slot {}",slotNames[std::bit_width(unsigned(Menu::menus[type]->I(A::EQUIP_TYPE)))-1]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[std::bit_width(unsigned(Menu::menus[type]->I(A::EQUIP_TYPE)))-1]);
},
.right=[](MenuType type,Data&returnData){
auto equipList=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents();
returnData=std::format("Equip Slot {}",slotNames[std::bit_width(unsigned(Menu::menus[type]->I(A::EQUIP_TYPE)))-1]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[std::bit_width(unsigned(Menu::menus[type]->I(A::EQUIP_TYPE)))-1]);
}
}},
{std::format("Equip Slot {}", slotNames[0]),{
{std::format("Equip Slot {}", CharacterMenuWindow::slotNames[0]),{
.up="Back button",
.down=std::format("Equip Slot {}", slotNames[2]),
.down=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[2]),
.left=[&](MenuType type,Data&returnData){
if(equipmentWindowOpened){
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[unsigned(Menu::menus[type]->I(A::ITEM_SLOT))];
}else{
returnData=std::format("Equip Slot {}",slotNames[1]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[1]);
}
},
.right=std::format("Equip Slot {}", slotNames[1]),}},
{std::format("Equip Slot {}", slotNames[1]),{
.right=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[1]),}},
{std::format("Equip Slot {}", CharacterMenuWindow::slotNames[1]),{
.up="Back button",
.down=std::format("Equip Slot {}", slotNames[3]),
.left=std::format("Equip Slot {}", slotNames[0]),
.down=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[3]),
.left=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[0]),
.right=[](MenuType type,Data&returnData){
if(equipmentWindowOpened){
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[unsigned(Menu::menus[type]->I(A::ITEM_SLOT))];
}else{
returnData=std::format("Equip Slot {}",slotNames[0]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[0]);
}
},}},
{std::format("Equip Slot {}", slotNames[2]),{
.up=std::format("Equip Slot {}", slotNames[0]),
.down=std::format("Equip Slot {}", slotNames[4]),
{std::format("Equip Slot {}", CharacterMenuWindow::slotNames[2]),{
.up=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[0]),
.down=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[4]),
.left=[](MenuType type,Data&returnData){
if(equipmentWindowOpened){
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[unsigned(Menu::menus[type]->I(A::ITEM_SLOT))];
}else{
returnData=std::format("Equip Slot {}",slotNames[3]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[3]);
}
},
.right=std::format("Equip Slot {}", slotNames[3]),}},
{std::format("Equip Slot {}", slotNames[3]),{
.up=std::format("Equip Slot {}", slotNames[1]),
.down=std::format("Equip Slot {}", slotNames[5]),
.left=std::format("Equip Slot {}", slotNames[2]),
.right=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[3]),}},
{std::format("Equip Slot {}", CharacterMenuWindow::slotNames[3]),{
.up=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[1]),
.down=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[5]),
.left=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[2]),
.right=[](MenuType type,Data&returnData){
if(equipmentWindowOpened){
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[unsigned(Menu::menus[type]->I(A::ITEM_SLOT))];
}else{
returnData=std::format("Equip Slot {}",slotNames[2]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[2]);
}
},}},
{std::format("Equip Slot {}", slotNames[4]),{
.up=std::format("Equip Slot {}", slotNames[2]),
.down=std::format("Equip Slot {}", slotNames[6]),
{std::format("Equip Slot {}", CharacterMenuWindow::slotNames[4]),{
.up=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[2]),
.down=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[6]),
.left=[](MenuType type,Data&returnData){
if(equipmentWindowOpened){
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[unsigned(Menu::menus[type]->I(A::ITEM_SLOT))];
}else{
returnData=std::format("Equip Slot {}",slotNames[5]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[5]);
}
},
.right=std::format("Equip Slot {}", slotNames[5]),
.right=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[5]),
}},
{std::format("Equip Slot {}", slotNames[5]),{
.up=std::format("Equip Slot {}", slotNames[3]),
.down=std::format("Equip Slot {}", slotNames[7]),
.left=std::format("Equip Slot {}", slotNames[4]),
{std::format("Equip Slot {}", CharacterMenuWindow::slotNames[5]),{
.up=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[3]),
.down=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[7]),
.left=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[4]),
.right=[](MenuType type,Data&returnData){
if(equipmentWindowOpened){
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[unsigned(Menu::menus[type]->I(A::ITEM_SLOT))];
}else{
returnData=std::format("Equip Slot {}",slotNames[4]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[4]);
}
},}},
{std::format("Equip Slot {}", slotNames[6]),{
.up=std::format("Equip Slot {}", slotNames[4]),
{std::format("Equip Slot {}", CharacterMenuWindow::slotNames[6]),{
.up=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[4]),
.down="Back button",
.left=[](MenuType type,Data&returnData){
if(equipmentWindowOpened){
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[unsigned(Menu::menus[type]->I(A::ITEM_SLOT))];
}else{
returnData=std::format("Equip Slot {}",slotNames[7]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[7]);
}
},
.right=std::format("Equip Slot {}",slotNames[7]),
.right=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[7]),
}},
{std::format("Equip Slot {}", slotNames[7]),{
.up=std::format("Equip Slot {}", slotNames[5]),
{std::format("Equip Slot {}", CharacterMenuWindow::slotNames[7]),{
.up=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[5]),
.down="Back button",
.left=std::format("Equip Slot {}",slotNames[6]),
.left=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[6]),
.right=[](MenuType type,Data&returnData){
if(equipmentWindowOpened){
returnData=Component<ScrollableWindowComponent>(type,"Equip List")->GetComponents()[unsigned(Menu::menus[type]->I(A::ITEM_SLOT))];
}else{
returnData=std::format("Equip Slot {}",slotNames[6]);
returnData=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[6]);
}
},}},
{"Back button",{
.up=std::format("Equip Slot {}", slotNames[7]),
.down=std::format("Equip Slot {}", slotNames[0]),
.left=std::format("Equip Slot {}", slotNames[7]),
.right=std::format("Equip Slot {}",slotNames[6]),
.up=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[7]),
.down=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[0]),
.left=std::format("Equip Slot {}", CharacterMenuWindow::slotNames[7]),
.right=std::format("Equip Slot {}",CharacterMenuWindow::slotNames[6]),
}},
});
}

@ -50,7 +50,7 @@ protected:
float perspectiveFactor=6;
public:
inline CharacterRotatingDisplay(geom2d::rect<float>rect,Decal*icon)
:MenuComponent(rect,"",DO_NOTHING),icon(icon){}
:MenuComponent(rect,"",DO_NOTHING,ButtonAttr::UNSELECTABLE),icon(icon){}
inline void SetIcon(Decal*icon){
this->icon=icon;
}

@ -59,12 +59,16 @@ void ChargedArrow::Update(float fElapsedTime){
}
}
bool ChargedArrow::PlayerHit(Player*player)
BulletDestroyState ChargedArrow::PlayerHit(Player*player)
{
return false;
return BulletDestroyState::KEEP_ALIVE;
}
bool ChargedArrow::MonsterHit(Monster& monster)
BulletDestroyState ChargedArrow::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)
{
return false;
return BulletDestroyState::KEEP_ALIVE;
}
void ChargedArrow::ModifyOutgoingDamageData(HurtDamageInfo&data){
if(friendly)data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
}

@ -42,6 +42,7 @@ All rights reserved.
#include "SoundEffect.h"
INCLUDE_game
INCLUDE_GFX
class Checkbox:public MenuComponent{
protected:
@ -76,17 +77,11 @@ public:
}
inline void DrawDecal(ViewPort&window,bool focused)override{
geom2d::line<float>checkmarkLine1=geom2d::line<float>({rect.left().start.x+rect.size.x*0.125f,rect.left().start.y+rect.size.y*0.5f},{rect.top().start.x+rect.size.x*0.375f,rect.top().start.y+rect.size.y*0.875f});
geom2d::line<float>checkmarkLine2=geom2d::line<float>(checkmarkLine1.end,{rect.left().start.x+rect.size.x*0.875f,rect.top().start.y+rect.size.y*0.25f});
MenuComponent::DrawDecal(window,focused);
if(checked){
for(int y=-1;y<=1;y++){
for(int x=-1;x<=1;x++){
window.DrawLineDecal(vf2d{float(x),float(y)}/game->GetScreenPixelSize()+checkmarkLine1.start,vf2d{float(x),float(y)}/game->GetScreenPixelSize()+checkmarkLine1.end);
window.DrawLineDecal(vf2d{float(x),float(y)}/game->GetScreenPixelSize()+checkmarkLine2.start,vf2d{float(x),float(y)}/game->GetScreenPixelSize()+checkmarkLine2.end);
}
}
const vf2d scale{GetSize()/GFX["checkmark.png"].Sprite()->Size()};
window.DrawDecal(rect.pos,GFX["checkmark.png"].Decal(),scale);
}
}
};

@ -36,12 +36,15 @@ All rights reserved.
*/
#pragma endregion
#pragma once
#include "Ability.h"
#include "Animation.h"
#include <vector>
#include <string>
#include "DEFINES.h"
#include "olcUTIL_DataFile.h"
#undef GetClassInfo
//Classes have bit-wise operator capabilities.
INCLUDE_DATA
enum Class{
ANY=0,
WARRIOR=1,
@ -49,5 +52,15 @@ enum Class{
RANGER=4,
TRAPPER=8,
WIZARD=16,
WITCH=32
WITCH=32,
};
namespace classutils{//Classes have bit-wise operator capabilities.
static inline Class StringToClass(std::string className){
const std::vector<std::string>&classList=DATA["class_list"].GetValues();
auto it=std::find(classList.begin(),classList.end(),className);
if(it==classList.end())ERR(std::format("WARNING! Class {} does not exist!",className));
int element=int(std::distance(classList.begin(),it));
return Class(1<<element); //Yes...It's bitwise flags, who in god's name knows why I did this.
};
};

File diff suppressed because it is too large Load Diff

@ -54,15 +54,7 @@ struct ClassInfo{
Ability*rightClickAbility;
};
class classutils{
public:
static inline Class StringToClass(std::string className){
const std::vector<std::string>&classList=DATA["class_list"].GetValues();
auto it=std::find(classList.begin(),classList.end(),className);
if(it==classList.end())ERR(std::format("WARNING! Class {} does not exist!",className));
int element=int(std::distance(classList.begin(),it));
return Class(1<<element); //Yes...It's bitwise flags, who in god's name knows why I did this.
};
namespace classutils{
static inline ClassInfo GetClassInfo(std::string className){
ClassInfo data{
DATA.GetProperty(className+".ClassName").GetString(),

@ -48,7 +48,7 @@ INCLUDE_DATA
using A=Attribute;
void Menu::InitializeClassInfoWindow(){
Menu*classInfoWindow=CreateMenu(CLASS_INFO,CENTERED,game->GetScreenSize()-vi2d{24,24});
Menu*classInfoWindow=CreateMenu(CLASS_INFO,CENTERED,game->GetScreenSize()-vi2d{24,0});
Menu*classSelectionWindow=Menu::menus[CLASS_SELECTION];
ClassInfo data=classutils::GetClassInfo(classSelectionWindow->S(A::CLASS_SELECTION));
@ -66,12 +66,12 @@ void Menu::InitializeClassInfoWindow(){
vf2d abilityIconOffsets = {0,32};
classInfoWindow->ADD("Ability 1 Display",CharacterAbilityPreviewComponent)(geom2d::rect<float>{healthDisplayLabelPos+vf2d{0,32*0}+abilityIconOffsets,labelSize*vf2d{1,2}},data.ability1)END;
classInfoWindow->ADD("Ability 2 Display",CharacterAbilityPreviewComponent)(geom2d::rect<float>{healthDisplayLabelPos+vf2d{0,32*1}+abilityIconOffsets,labelSize*vf2d{1,2}},data.ability2)END;
classInfoWindow->ADD("Ability 3 Display",CharacterAbilityPreviewComponent)(geom2d::rect<float>{healthDisplayLabelPos+vf2d{0,32*2}+abilityIconOffsets,labelSize*vf2d{1,2}},data.ability3)END;
classInfoWindow->ADD("Right Click Ability Display",CharacterAbilityPreviewComponent)(geom2d::rect<float>{healthDisplayLabelPos+vf2d{0,32*3}+abilityIconOffsets,labelSize*vf2d{1,2}},data.rightClickAbility)END;
classInfoWindow->ADD("Ability 1 Display",CharacterAbilityPreviewComponent)(geom2d::rect<float>{healthDisplayLabelPos+vf2d{0,40*0}+abilityIconOffsets,labelSize*vf2d{1,2}+vf2d{0,8}},data.ability1)END;
classInfoWindow->ADD("Ability 2 Display",CharacterAbilityPreviewComponent)(geom2d::rect<float>{healthDisplayLabelPos+vf2d{0,40*1}+abilityIconOffsets,labelSize*vf2d{1,2}+vf2d{0,8}},data.ability2)END;
classInfoWindow->ADD("Ability 3 Display",CharacterAbilityPreviewComponent)(geom2d::rect<float>{healthDisplayLabelPos+vf2d{0,40*2}+abilityIconOffsets,labelSize*vf2d{1,2}+vf2d{0,8}},data.ability3)END;
classInfoWindow->ADD("Right Click Ability Display",CharacterAbilityPreviewComponent)(geom2d::rect<float>{healthDisplayLabelPos+vf2d{0,40*3}+abilityIconOffsets,labelSize*vf2d{1,2}+vf2d{0,8}},data.rightClickAbility)END;
classInfoWindow->ADD("Back Button",MenuComponent)(geom2d::rect<float>{{classInfoWindow->center().x/2,healthDisplayLabelPos.y+32*4+abilityIconOffsets.y+12},{classInfoWindow->size.x/2,16}},"Back",[](MenuFuncData data){Menu::CloseMenu();return true;})END;
classInfoWindow->ADD("Back Button",MenuComponent)(geom2d::rect<float>{{0.f,healthDisplayLabelPos.y+32*4+abilityIconOffsets.y+16},{classInfoWindow->size.x/3-6.f,12}},"Back",[](MenuFuncData data){Menu::CloseMenu();return true;})END;
classInfoWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open

@ -45,6 +45,9 @@ All rights reserved.
#include "SaveFileNameButton.h"
#include "TextEntryLabel.h"
#include "Checkbox.h"
#ifndef __EMSCRIPTEN__
#include "steam/isteamutils.h"
#endif
INCLUDE_game
using A=Attribute;
@ -68,6 +71,12 @@ void Menu::InitializeClassSelectionWindow(){
Component<TextEntryLabel>(SAVE_FILE_NAME,"Save File Name Text Entry")->SetInput(std::string(SaveFile::GetSaveFileName()));
game->TextEntryEnable(true);
Menu::OpenMenu(SAVE_FILE_NAME);
vf2d textboxWindowPos=data.component.lock()->rect.pos*game->GetPixelSize();
vf2d textboxWindowSize=data.component.lock()->rect.size*game->GetPixelSize();
STEAMUTILS(
SteamUtils()->ShowGamepadTextInput(k_EGamepadTextInputModeNormal,k_EGamepadTextInputLineModeSingleLine,"Character Name:",16U,std::string(SaveFile::GetSaveFileName()).c_str());
)
return true;
},vf2d{2.f,2.f})END;
@ -77,6 +86,11 @@ void Menu::InitializeClassSelectionWindow(){
classSelectionWindow->ADD("Confirm",MenuComponent)(geom2d::rect<float>{{outlineSize.x+4-navigationButtonSize.x-2,outlineSize.y+29-navigationButtonSize.y-14},navigationButtonSize},"Confirm",[](MenuFuncData data){
std::string selectedClass=data.component.lock()->S(A::CLASS_SELECTION);
//Player starting items.
Inventory::AddItem("Minor Health Potion"s,3);
Inventory::AddItem("Bandages"s,10);
data.game->ChangePlayerClass(classutils::StringToClass(selectedClass));
#ifdef __EMSCRIPTEN__
if(SaveFile::IsOnline()){
@ -151,12 +165,6 @@ void Menu::InitializeClassSelectionWindow(){
data.menu.components["Confirm"]->S(A::CLASS_SELECTION)=data.component.lock()->S(A::CLASS_SELECTION);
return true;
})END;
if(i>=3){
classLabel->SetGrayedOut(true);
classButton->SetGrayedOut(true);
classSprite->SetGrayedOut(true);
}
classSprite->S(A::CLASS_SELECTION)=className;

@ -51,6 +51,10 @@ void ConnectionPoint::SetVisited(){
visited=true;
}
void ConnectionPoint::ResetVisitedFlag(){
visited=false;
}
const bool ConnectionPoint::Visited()const{
return visited;
}

@ -63,6 +63,7 @@ struct ConnectionPoint{
bool IsNeighbor(ConnectionPoint&testPoint);
//Sets the visited flag to true.
void SetVisited();
void ResetVisitedFlag();
//Whether or not this connection point has been visited yet.
const bool Visited()const;
};

@ -59,9 +59,23 @@ void Menu::InitializeConsumableCraftItemWindow(){
const std::string&item=Component<MenuLabel>(CONSUMABLE_CRAFT_ITEM,"Item Name Header")->GetString(A::ITEM_NAME);
bool canCraft=Item(qty,item).CanEnhanceItem(qty);
bool canCraftOneHigher=Item(qty+1,item).CanEnhanceItem(qty+1);
std::string colorCode="";
if(!canCraft)colorCode="#FF0000";
if(qty==99||!canCraftOneHigher){
Component<MenuComponent>(CONSUMABLE_CRAFT_ITEM,"Increase Craft amount Button")->SetGrayedOut(true);
Menu::menus[CONSUMABLE_CRAFT_ITEM]->SetSelection("Decrease Craft amount Button"sv);
}else{
Component<MenuComponent>(CONSUMABLE_CRAFT_ITEM,"Increase Craft amount Button")->SetGrayedOut(false);
}
if(qty<=1){
Component<MenuComponent>(CONSUMABLE_CRAFT_ITEM,"Decrease Craft amount Button")->SetGrayedOut(true);
Menu::menus[CONSUMABLE_CRAFT_ITEM]->SetSelection("Increase Craft amount Button"sv);
}else{
Component<MenuComponent>(CONSUMABLE_CRAFT_ITEM,"Decrease Craft amount Button")->SetGrayedOut(false);
}
Component<RequiredMaterialsList>(CONSUMABLE_CRAFT_ITEM,"Required Materials List")->SetQuantity(qty);
Component<MenuComponent>(CONSUMABLE_CRAFT_ITEM,"Craft Button")->SetGrayedOut(!canCraft);
};
@ -125,6 +139,7 @@ void Menu::InitializeConsumableCraftItemWindow(){
},[](MenuType type){
Component<MenuComponent>(type,"Craft Button")->Click();
}}},
{{game->KEY_SHOULDER2,Pressed},{"Qty Up/Down",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Qty Up/Down",[](MenuType type){}}},
{{game->KEY_FASTSCROLLDOWN,PressedDAS},{"",[](MenuType type){
Component<MenuComponent>(type,"Increase Craft amount Button")->Click();
@ -180,8 +195,8 @@ void Menu::InitializeConsumableCraftItemWindow(){
.left="Decrease Craft amount Button",
.right="Decrease Craft amount Button",}},
{"Decrease Craft amount Button",{
.up="Back Button",
.down="Back Button",
.up="Craft Button",
.down="Craft Button",
.left="Increase Craft amount Button",
.right="Increase Craft amount Button",}},
});

@ -69,6 +69,7 @@ void Menu::InitializeConsumableCraftingWindow(){
Component<RequiredMaterialsList>(CONSUMABLE_CRAFT_ITEM,"Required Materials List")->SetItem(item);
Component<MenuComponent>(CONSUMABLE_CRAFT_ITEM,"Craft Button")->SetGrayedOut(!item.lock()->CanEnhanceItem());
if(item.lock()->GetEnhancementInfo()[0].chapterAvailable<=game->GetCurrentChapter()){
Component<MenuComponent>(CONSUMABLE_CRAFT_ITEM,"Decrease Craft amount Button")->SetGrayedOut(true);
Menu::OpenMenu(CONSUMABLE_CRAFT_ITEM);
}else{
SoundEffect::PlaySFX("Locked Item",SoundEffect::CENTERED);
@ -120,10 +121,11 @@ void Menu::InitializeConsumableCraftingWindow(){
#pragma region Inventory Description
float inventoryDescriptionWidth=consumableCraftingWindow->pos.x+consumableCraftingWindow->size.x-26-224;
consumableCraftingWindow->ADD("Item Description Outline",MenuLabel)(geom2d::rect<float>{{224,28},{inventoryDescriptionWidth,consumableCraftingWindow->size.y-44}},"",1,ComponentAttr::LEFT_ALIGN|ComponentAttr::OUTLINE|ComponentAttr::BACKGROUND)END;
consumableCraftingWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>{{226+inventoryDescriptionWidth/2-24,30},{48,48}},Item::BLANK,DO_NOTHING,"","",IconButtonAttr::NOT_SELECTABLE)END
->SetShowQuantity(false);
consumableCraftingWindow->ADD("Item Name Label",MenuLabel)(geom2d::rect<float>{{226,84},{inventoryDescriptionWidth-6,12}},"",0.75f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
consumableCraftingWindow->ADD("Item Description Label",MenuLabel)(geom2d::rect<float>{{226,94},{inventoryDescriptionWidth-6,consumableCraftingWindow->size.y-44-66}},"",0.5f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
auto itemIconButton=consumableCraftingWindow->ADD("Item Icon",MenuItemItemButton)(geom2d::rect<float>{{226+inventoryDescriptionWidth/2-18,30},{36,36}},Item::BLANK,DO_NOTHING,"","",IconButtonAttr::NOT_SELECTABLE)END;
itemIconButton->SetShowQuantity(false);
itemIconButton->SetIconScale({1.5f,1.5f});
consumableCraftingWindow->ADD("Item Name Label",MenuLabel)(geom2d::rect<float>{{226,72},{inventoryDescriptionWidth-6,12}},"",0.75f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
consumableCraftingWindow->ADD("Item Description Label",MenuLabel)(geom2d::rect<float>{{226,82},{inventoryDescriptionWidth-6,consumableCraftingWindow->size.y-44-66}},"",0.5f,ComponentAttr::LEFT_ALIGN|ComponentAttr::SHADOW)END;
#pragma endregion
#pragma region Money Display
@ -152,6 +154,7 @@ void Menu::InitializeConsumableCraftingWindow(){
}
},
{ //Button Key
{{game->KEY_SHOULDER2,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Scroll Up/Down",[](MenuType type){}}},
{{game->KEY_FASTSCROLLDOWN,PressedDAS},{"",[&](MenuType type){
if(!Menu::menus[type]->GetSelection().expired()&&

@ -40,6 +40,7 @@ All rights reserved.
#include "EnhancementStatsLabel.h"
#include "RequiredMaterialsList.h"
#include "SoundEffect.h"
#include "Tutorial.h"
void Menu::InitializeCraftItemWindow(){
Menu*craftItemWindow=CreateMenu(CRAFT_ITEM,CENTERED,{240,120});
@ -63,11 +64,22 @@ void Menu::InitializeCraftItemWindow(){
item=Inventory::AddItem(item.lock()->ActualName());
Component<EnhancementStatsLabel>(CRAFT_ITEM,"Enhancement Stats Label")->SetItem(item);
Component<RequiredMaterialsList>(CRAFT_ITEM,"Required Materials List")->SetItem(item); //Update the item refs in the enhancement level screen to now display the correct item.
const CraftingRequirement&consumedResources=item.lock()->GetEnhancementInfo()[item.lock()->EnhancementLevel()].craftingRequirement;
for(const auto&[name,amt]:consumedResources.GetItems()){
Inventory::RemoveItem(Inventory::GetItem(name)[0],amt);
}
game->GetPlayer()->SetMoney(game->GetPlayer()->GetMoney()-consumedResources.GetCost());
Inventory::UpdateBlacksmithInventoryLists();
SoundEffect::PlaySFX("Craft Equip",SoundEffect::CENTERED);
}else{ //Since we already own this equipment, just enhance it.
item.lock()->EnhanceItem();
}
if(!Tutorial::TaskIsComplete(TutorialTaskName::EQUIP_GEAR)){
Tutorial::SetNextTask(TutorialTaskName::EQUIP_GEAR);
}
}
std::string label="";
if(item.lock()->EnhancementIsPossible()&&item.lock()->GetEnhancementInfo().size()>item.lock()->EnhancementLevel()+1

@ -37,10 +37,10 @@ All rights reserved.
#pragma endregion
#include "CraftingRequirement.h"
CraftingRequirement::CraftingRequirement(const std::vector<std::pair<IT,int>>&craftingItems,const uint32_t cost,const uint8_t availableChapter)
CraftingRequirement::CraftingRequirement(const std::vector<ItemPair>&craftingItems,const uint32_t cost,const uint8_t availableChapter)
:craftingItems(craftingItems),cost(cost),availableChapter(availableChapter){}
const std::vector<std::pair<IT,int>>&CraftingRequirement::GetItems()const{
const std::vector<ItemPair>&CraftingRequirement::GetItems()const{
return craftingItems;
}
const uint32_t CraftingRequirement::GetCost()const{

@ -39,12 +39,12 @@ All rights reserved.
#include "Item.h"
class CraftingRequirement{
std::vector<std::pair<IT,int>>craftingItems;
std::vector<ItemPair>craftingItems;
uint32_t cost;
uint8_t availableChapter;
public:
CraftingRequirement(const std::vector<std::pair<IT,int>>&craftingItems,const uint32_t cost,const uint8_t availableChapter);
const std::vector<std::pair<IT,int>>&GetItems()const;
CraftingRequirement(const std::vector<ItemPair>&craftingItems,const uint32_t cost,const uint8_t availableChapter);
const std::vector<ItemPair>&GetItems()const;
const uint32_t GetCost()const;
const uint8_t GetAvailableChapter()const;
};

@ -0,0 +1,56 @@
Eagle Boss - King of Birds, Zephy
On entering boss arena:
8 basic Hawks appear, no exp/dropp
after defeat:
2 Major Hawks appear.
Major Hawks
Hp: 520
Attack: 31
Size: 120%
Move-Spd: 220%
no exp or dropps
As long both are active normal Hawk Strategie:
Attack Strategie: Fly circles at the edge of the Players Screen. every 8 seconds charge on players location to the other corner of the screen.
If only 1 is active instead of every 8 seconds charge every 3.
Once those are defeated the boss spawns.
Boss
Hp: 7000
Attack: 48
Size: 400%
Move-Spd: 180%
From now on every 10 seconds a basic Hawk spawns.
It uses the following attack moves in random order:
- Flys from left to right or right to left in the top of the Screen and shits down. (25 dmg on hit)
- Lands in the middle of the boss arena (in the area the player is at) and create small Tornados around himself which move in a circle (size: ~80% spd: 90%, )
- Flys to the Central top of the arena and does a Fan of feathers. Feathers fly until the end of the arena. you cant outrange it. there are a few gaps inbetween to dodge though.
(8 dmg each, can be hit from multiple at once)
Repeats this move once.
- lands on the left or right side of the arena and starts to create alot of wind with his wings. moving towards the boss slows the movespeed running away increases the move speed.
starts with +/- 10% and increases by another 10% every second until it reaches 60%.
On full strength: Projectiles of auto attacks get blown away. Objects appear that deal damage on collision. (Bushes & stones from forest tileset. if possible spinning)
Collision dmg 40.
Full strength lasts for 12 Seconds.
50%-Phase
- Boss lands on the Pillar in the top Middle of the arena and creates and tornado. Player gets constantly slightly sucked towards it.
- The Tornado shoots feather projectiles in random directions.
(10 dmg, amount of feathers needs a bit of testing.
it needs to be enough that it is some kind of threat but not to much
that you are just overwhelmed by them while fighting the other birds.)
- the 10 second Hawk spawner is disabled until boss reappears.
- 2 Major hawks spawn + 4 Basic Hawks
- after every Hawk is defeated the fight goes back to normal mode. (which are the 6 spawned + any bird that still spawned in the fight before and didnt got killed yet)

@ -0,0 +1,52 @@
Artificer
- Unlocks after Chapter 2 - Bonus Boss
- Unlocks an additional feature after Chapter 3 - Bonus Boss
- Is available in the hub area
Artificer unlocks after beating chapter 2 bonus boss.
Player heads to camp.
Camera pans over to the Artificer's station, and a new NPC appears there.
Then camera pans back over to the player
The leave buttons are disabled and a tutorial prompt to visit the artificer appears.
You go to the artificer and talk to them.
Artificer explains that your accessories' stats may not have reached their full potential yet and can be refined.
You can disassemble previously obtained rings to get ring fragments, then refine your rings to increase its power further.
Artificer: Hello adventurer! Welcome to my artificing station! May I have the pleasure of knowing your name?
[YOU]: Hello, my name is ____.
Artificer: Excelent! Nice to meet you, [YOU]. Here you have the ability to refine and fix up imperfect jewelry discovered along your journey!
Artificer: Break down rings that are undesirable to you to retrieve fragments, then use the Refining station to empower your equipment further!
Artificer: This will let you get use out of all those extra rings you may be carrying around, eh?
(Auto-generate fragment item types for each ring)
Confirmation dialog for disassembly
Refining: Popup a dialog showing current stats of selected ring, show possible upgraded stats as (+ ??)
Hit Refine button to consume fragment to upgrade some stat. Play an animation next to stat that was upgraded.
(Whichever is greater): Always go up by 20%/Always go up by 1
Artificer has 4 Dialog Options
- Repair/Enhance/(Refine?) (Name may change in the future)
- Disassemble
- Enchant (Requieres Chapter 3 - Bonus boss)
- Help (Runs through the explanation again.)
- Leave
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
Disassemble
- Destroys a ring to get 1 piece of the ring
- doesnt cost anything but destroys the item
- pieces have the same vendor value a ring itself has.
Enchant
- Gives a ring 1 additional Bonus (there will be 3 different type of bonuses: Base attributes, Class specifics spell modifiers, Unique effects)
- If a ring is already enchanted you override its current enchant. (Maybe give an "Keep old enchant" option?)
- Requieres 3 Pieces + 35g

@ -0,0 +1,171 @@
Thief
Autohit: Sword Slash (0.35 sec cd)
Rightclick - Roll
Dodgeroll that gives an iframe and 2 sec of move spd buff afterwards.
Range: 250
Movespd buff: 30%
cd: 8 sec
1 - Hidden Dagger
Backwards Jump while Throwing a Dagger infront
Jump-Range: 200
Damage: 2x Atk
cd: 8 sec.
20 Mana
2 - deadly Dash
Dashes to Target location
Damages everything the Thief goes through.
iframe lasting additional 0.5 sec after skill ends.
Damage: 4x Atk
Range: 450
cd: 15 sec.
Mana 45
3 - Adrenaline Rush
30% Attack Speed and 10% Movementspeed buff
Duration: 8 seconds
Cd: 37 sec.
Mana 75
Trapper
Autohit: Same as Ranger
Rightclick - Sprint
3 sec / 60% movespeed buff
cd: 7 sec.
1 - Mark Target
Range: Screenwide
Cd: 12 sec.
25 Mana
Target receives addition 0.6x Atk dmg every time the target get hit by any auto attack or ability.
7 sec Duration or 5 triggers
NOTE: Ranger abilities can proc this too, due to 4th ability sharing.
2 - Bear Trap
Lays down a trap on Players position.
Damage: 2.5x Attack
Cd: 15 sec
If Target is marked the mark gets reset to 7 seconds with 5 stacks.
If Target was marked the enemy gets a bleed debuff dealing 0.1x Attack every second for 10 seconds.
3 - Explosive Trap
Trap takes 1.5 sec until its active until it gets thrown somewhere.
Trap explodes either if an enemy touches it or with a 5 second delay.
Damage: 5.5x Attack
If anyone inside the explosion is marget, Target Triggers 2 Marks at once.
Anyone who is not marked inside the explosion gets 1 Stack of mark with 7 sec duration.
Witch
Autohit: Hooming Dark/Purple Energy Bolt. only starts hooming if it gets really close to an enemy. (0.85 sec cd)
Righclick - Transform
Transforms to a Cat Leaps for 400 Range and transforms back (iFrame during move)
cd: 8 sec.
1 Curse of Pain
Single Target dot Target on Mouse location. 6 seconds
Range: 700
35 Mana
1x attack every 2 seconds for 8 seconds. Last Tick does double damage.
8 sec Cd
2 Throw Poison
Throws a poison bottle like a grenate. Massive aoe dmg and low dmg ticking dot over long time.
Range: 700
AoE: 350 Range
Damage: 5x Atk Poisons over 30 seconds (does every 3 secs 0.5x Atk dmg)
40 Mana
16 sec cd
3 Curse of Death
Curses Target to receive double dmg for next 7 seconds. Target with Mouse location
Range: 700
cd: 35 sec
35 Mana

@ -0,0 +1,93 @@
#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 "Menu.h"
#include "ScrollableWindowComponent.h"
#include "MenuLabel.h"
INCLUDE_WINDOW_SIZE
void Menu::InitializeCreditsWindow(){
vf2d windowSize=WINDOW_SIZE-vf2d{78,78};
Menu*creditsWindow=CreateMenu(MenuType::CREDITS,CENTERED,windowSize);
auto displayScrollWindow=creditsWindow->ADD("Display Text Scroll Window",ScrollableWindowComponent)(geom2d::rect<float>{{4,4},windowSize-vf2d{8,8}})END;
displayScrollWindow->ADD("Display Text",MenuLabel)(geom2d::rect<float>{{2,2},windowSize-vf2d{24,12}},"Test Text\nEven more testing text that we could use for this game.",1.f,ComponentAttr::CENTER)END;
creditsWindow->ADD("Go Back Button",MenuComponent)(geom2d::rect<float>{{windowSize.x/2.f-48.f,windowSize.y},{96.f,12.f}},"Back",[](MenuFuncData data){
Menu::CloseMenu();
return true;
})END;
creditsWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="Go Back Button";
},
{ //Button Key
{{game->KEY_SCROLLUP,Held},{"",[](MenuType type){
Component<ScrollableWindowComponent>(type,"Display Text Scroll Window")->Scroll(1.f);
}}},
{{game->KEY_SCROLLDOWN,Held},{"",[](MenuType type){
Component<ScrollableWindowComponent>(type,"Display Text Scroll Window")->Scroll(-1.f);
}}},
{{game->KEY_SHOULDER2,Pressed},{"Scroll",[](MenuType type){}}},
{{game->KEY_SHOULDER,Pressed},{"Scroll",[](MenuType type){
Component<ScrollableWindowComponent>(type,"Display Text Scroll Window")->Scroll(-1.0f);
}}},
{{game->KEY_FASTSCROLLUP,Held,InputEngageGroup::NOT_VISIBLE},{"Scroll",[](MenuType type){
Component<ScrollableWindowComponent>(type,"Display Text Scroll Window")->Scroll(-1.0f);
}}},
{{game->KEY_FASTSCROLLDOWN,Held,InputEngageGroup::NOT_VISIBLE},{"Scroll",[](MenuType type){
Component<ScrollableWindowComponent>(type,"Display Text Scroll Window")->Scroll(1.0f);
}}},
{{game->KEY_SCROLLVERT_R,Analog},{"Scroll",[](MenuType type){
Component<ScrollableWindowComponent>(type,"Display Text Scroll Window")->Scroll(game->KEY_SCROLLVERT.Analog());
}}},
{{game->KEY_SCROLLVERT_L,Analog,InputEngageGroup::NOT_VISIBLE},{"Scroll",[](MenuType type){
Component<ScrollableWindowComponent>(type,"Display Text Scroll Window")->Scroll(game->KEY_SCROLLVERT.Analog());
}}},
{game->KEY_BACK,{"Back",[](MenuType type){
Component<MenuComponent>(type,"Go Back Button")->Click();
}}},
{game->KEY_CONFIRM,{"Back",[](MenuType type){}}},
}
,{ //Button Navigation Rules
});
}

@ -41,13 +41,16 @@ All rights reserved.
using BackdropName=std::string;
using MonsterSpawnerID=int;
#define INCLUDE_ANIMATION_DATA extern safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA;
#define INCLUDE_MONSTER_LIST extern std::vector<Monster>MONSTER_LIST;
#define INCLUDE_SPAWNER_LIST extern std::vector<MonsterSpawner>SPAWNER_LIST;
#define INCLUDE_MONSTER_LIST extern std::vector<std::shared_ptr<Monster>>MONSTER_LIST;
#define INCLUDE_SPAWNER_LIST extern std::unordered_map<MonsterSpawnerID,MonsterSpawner>SPAWNER_LIST;
#define INCLUDE_SPAWNER_CONTROLLER extern std::optional<std::queue<MonsterSpawnerID>>SPAWNER_CONTROLLER;
#define INCLUDE_DAMAGENUMBER_LIST extern std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST;
#define INCLUDE_game extern AiL*game;
#define INCLUDE_MONSTER_DATA extern std::map<std::string,MonsterData>MONSTER_DATA;
#define INCLUDE_BULLET_LIST extern std::vector<std::unique_ptr<Bullet>>BULLET_LIST;
#define INCLUDE_BULLET_LIST extern std::vector<std::unique_ptr<IBullet>>BULLET_LIST;
#define INCLUDE_EMITTER_LIST extern std::vector<std::unique_ptr<Emitter>>EMITTER_LIST;
#define INCLUDE_DATA extern utils::datafile DATA;
#define INCLUDE_STRATEGY_DATA extern safemap<std::string,std::function<void(Monster&,float,std::string)>>STRATEGY_DATA;
@ -59,14 +62,17 @@ using BackdropName=std::string;
#define INCLUDE_WINDOW_SIZE extern vi2d WINDOW_SIZE;
#define INCLUDE_ITEM_CONVERSIONS extern safemap<std::string,IT>ITEM_CONVERSIONS;
#define INCLUDE_PACK_KEY extern std::string PACK_KEY;
#define INCLUDE_BACKDROP_DATA extern std::map<BackdropName,Renderable>BACKDROP_DATA;
#define INCLUDE_FOREDROP_DATA extern std::map<BackdropName,Renderable>FOREDROP_DATA;
#define INCLUDE_CENTERED extern const vf2d Menu::CENTERED;
#define ACCESS_PLAYER Player*p=game->GetPlayer();
#define VARIANTS float,int,std::string,bool,vf2d,std::vector<std::any>
#define VARIANTS float,int,std::string,bool,vf2d,std::vector<std::any>,size_t
#undef INFINITE
#define INFINITE 999999

@ -0,0 +1,102 @@
#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 "SoundEffect.h"
#include "AdventuresInLestoria.h"
#include "DEFINES.h"
#include "util.h"
INCLUDE_game
INCLUDE_ANIMATION_DATA
DaggerSlash::DaggerSlash(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerSlashDistance,bool friendly,Pixel col)
:Bullet(sourceMonster.GetPos(),{},radius,damage,"goblin_sword_slash.png",upperLevel,false,daggerFrameDuration*ANIMATION_DATA["goblin_sword_slash.png"].GetFrameCountBasedOnAnimationStyle(),true,friendly,col),
sourceMonster(sourceMonster),frameDuration(daggerFrameDuration),daggerSlashDistance(daggerSlashDistance),facingDir(facingDir),knockbackAmt(knockbackAmt){}
void DaggerSlash::Update(float fElapsedTime){
ANIMATION_DATA["goblin_sword_slash.png"].ChangeFrameDuration(frameDuration);
pos=sourceMonster.GetPos();
#pragma region Dagger slash offset
switch(facingDir){
case Direction::NORTH:{
pos+=vf2d{0,-daggerSlashDistance};
}break;
case Direction::EAST:{
pos+=vf2d{daggerSlashDistance,0};
}break;
case Direction::SOUTH:{
pos+=vf2d{0,daggerSlashDistance};
}break;
case Direction::WEST:{
pos+=vf2d{-daggerSlashDistance,0};
}break;
default:ERR(std::format("WARNING! Unknown direction value {} was supplied! THIS SHOULD NOT BE HAPPENING!",int(facingDir)));
}
#pragma endregion
#pragma region Dagger rotation handling
switch(facingDir){
case Direction::NORTH:{
vel={0,-0.001f};
}break;
case Direction::EAST:{
vel={0.001f,0};
}break;
case Direction::SOUTH:{
vel={0,0.001f};
}break;
case Direction::WEST:{
vel={-0.001f,0};
}break;
default:ERR(std::format("WARNING! Unknown direction value {} was supplied! THIS SHOULD NOT BE HAPPENING!",int(facingDir)));
}
#pragma endregion
}
BulletDestroyState DaggerSlash::PlayerHit(Player*player){
game->AddEffect(std::make_unique<Effect>(pos,0,"lightning_splash_effect.png",upperLevel,player->GetSizeMult()*0.25f,0.25,vf2d{}));
player->Knockback(util::pointTo(sourceMonster.GetPos(),player->GetPos())*knockbackAmt);
Deactivate();
return BulletDestroyState::KEEP_ALIVE;
}
BulletDestroyState DaggerSlash::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
game->AddEffect(std::make_unique<Effect>(pos,0,"lightning_splash_effect.png",upperLevel,monster.GetSizeMult()*0.25f,0.25,vf2d{}));
monster.Knockback(util::pointTo(sourceMonster.GetPos(),monster.GetPos())*knockbackAmt);
Deactivate();
return BulletDestroyState::KEEP_ALIVE;
}
void DaggerSlash::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -0,0 +1,108 @@
#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 "SoundEffect.h"
#include "AdventuresInLestoria.h"
#include "DEFINES.h"
#include "util.h"
INCLUDE_game
INCLUDE_ANIMATION_DATA
DaggerStab::DaggerStab(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerStabDistance,const DirectionOffsets offsets,bool friendly,Pixel col)
:Bullet(sourceMonster.GetPos(),{},radius,damage,"dagger_stab.png",upperLevel,false,daggerFrameDuration*ANIMATION_DATA["dagger_stab.png"].GetFrameCountBasedOnAnimationStyle(),true,friendly,col),
sourceMonster(sourceMonster),frameDuration(daggerFrameDuration),daggerStabDistance(daggerStabDistance),facingDir(facingDir),daggerPositionOffsets(offsets),knockbackAmt(knockbackAmt){}
void DaggerStab::Update(float fElapsedTime){
ANIMATION_DATA["dagger_stab.png"].ChangeFrameDuration(frameDuration);
#pragma region Dagger Position Offset
vf2d daggerOffset=daggerPositionOffsets.offsets[facingDir];
pos=sourceMonster.GetPos()+daggerOffset;
#pragma endregion
#pragma region Dagger stabbing
if(lifetime<=ANIMATION_DATA["dagger_stab.png"].GetTotalAnimationDuration()*0.6667f&&
lifetime>=ANIMATION_DATA["dagger_stab.png"].GetTotalAnimationDuration()*0.3333f){
switch(facingDir){
case Direction::NORTH:{
pos+=vf2d{0,-daggerStabDistance};
}break;
case Direction::EAST:{
pos+=vf2d{daggerStabDistance,0};
}break;
case Direction::SOUTH:{
pos+=vf2d{0,daggerStabDistance};
}break;
case Direction::WEST:{
pos+=vf2d{-daggerStabDistance,0};
}break;
default:ERR(std::format("WARNING! Unknown direction value {} was supplied! THIS SHOULD NOT BE HAPPENING!",int(facingDir)));
}
}
#pragma endregion
#pragma region Dagger rotation handling
switch(facingDir){
case Direction::NORTH:{
vel={0,-0.001f};
}break;
case Direction::EAST:{
vel={0.001f,0};
}break;
case Direction::SOUTH:{
vel={0,0.001f};
}break;
case Direction::WEST:{
vel={-0.001f,0};
}break;
default:ERR(std::format("WARNING! Unknown direction value {} was supplied! THIS SHOULD NOT BE HAPPENING!",int(facingDir)));
}
#pragma endregion
}
BulletDestroyState DaggerStab::PlayerHit(Player*player){
game->AddEffect(std::make_unique<Effect>(pos,0,"lightning_splash_effect.png",upperLevel,player->GetSizeMult()*0.25f,0.25,vf2d{}));
player->Knockback(util::pointTo(sourceMonster.GetPos(),player->GetPos())*knockbackAmt);
Deactivate();
return BulletDestroyState::KEEP_ALIVE;
}
BulletDestroyState DaggerStab::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
game->AddEffect(std::make_unique<Effect>(pos,0,"lightning_splash_effect.png",upperLevel,monster.GetSizeMult()*0.25f,0.25,vf2d{}));
monster.Knockback(util::pointTo(sourceMonster.GetPos(),monster.GetPos())*knockbackAmt);
Deactivate();
return BulletDestroyState::KEEP_ALIVE;
}
void DaggerStab::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -42,20 +42,20 @@ INCLUDE_game
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 type):
DamageNumber::DamageNumber(vf2d pos,int damage,bool friendly,DamageNumberType::DamageNumberType type):
pos(pos),damage(damage),friendly(friendly),type(type),invertedDirection(type==INTERRUPT),riseSpd(20.f){
if(type==INTERRUPT||type==MANA_GAIN)riseSpd=40.f;
originalRiseSpd=riseSpd;
RecalculateSize();
}
void DamageNumber::RecalculateSize(){
float damageMultRatio=damage/game->GetPlayer()->GetStat("Attack")/2.f;
riseSpd=originalRiseSpd;
float damageMultRatio=damage/game->GetPlayer()->GetEquipStat("Attack")/2.f;
if(!friendly){
float newSize=std::clamp(roundf(damageMultRatio),1.0f,4.0f);
@ -63,4 +63,59 @@ void DamageNumber::RecalculateSize(){
size=vf2d{newSize,newSize};
}
}
void DamageNumber::Draw(){
#define NumberScalesWithDamage true
#define NormalNumber false
auto DrawDamageNumber=[&](const bool ScaleWithNumber,std::string_view text,std::pair<Pixel,Pixel>colorsEnemy,std::pair<Pixel,Pixel>colorsFriendly,vf2d scaling={1.f,1.f}){
vf2d textSize=game->GetTextSizeProp(text)*scaling;
if(!friendly){
vf2d additionalScaling={1.f,1.f};
if(ScaleWithNumber)additionalScaling=size;
textSize*=additionalScaling;
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,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,colorsFriendly.first,colorsFriendly.second,scaling);
}
};
switch(type){
case HEALTH_LOSS:{
std::string text=std::to_string(damage);
DrawDamageNumber(NumberScalesWithDamage,text,{DARK_RED,{255,160,160}},{RED,VERY_DARK_GREY});
}break;
case HEALTH_GAIN:{
std::string text="+"+std::to_string(damage);
DrawDamageNumber(NormalNumber,text,{DARK_GREEN,BLACK},{GREEN,VERY_DARK_GREY});
}break;
case MANA_GAIN:{
std::string text="+"+std::to_string(damage);
DrawDamageNumber(NormalNumber,text,{BLUE,VERY_DARK_GREY},{BLUE,VERY_DARK_GREY});
}break;
case INTERRUPT:{
std::string text="Interrupted!";
DrawDamageNumber(NormalNumber,text,{BLACK,VERY_DARK_GREY},{BLACK,VERY_DARK_GREY},{0.5f,1});
}break;
case CRIT:{
std::string text=std::to_string(damage);
DrawDamageNumber(NumberScalesWithDamage,text,{YELLOW,DARK_YELLOW},{BLACK,{0,0,0,0}});
}break;
case DOT:{
std::string text=std::to_string(damage);
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)));
}
}

@ -38,13 +38,16 @@ All rights reserved.
#pragma once
#include "olcUTIL_Geometry2D.h"
enum DamageNumberType{
HEALTH_LOSS,
HEALTH_GAIN,
MANA_GAIN,
INTERRUPT,
CRIT,
};
namespace DamageNumberType{
enum DamageNumberType{
HEALTH_LOSS,
HEALTH_GAIN,
MANA_GAIN,
INTERRUPT,
CRIT,
DOT,
};
}
struct DamageNumber{
vf2d pos;
@ -55,10 +58,11 @@ struct DamageNumber{
vf2d size{1.f,1.f};
bool friendly=false;
bool invertedDirection=false;
DamageNumberType type=HEALTH_LOSS;
DamageNumberType::DamageNumberType type=DamageNumberType::HEALTH_LOSS;
const static float MOVE_UP_TIME;
float originalRiseSpd=0.f;
DamageNumber();
DamageNumber(vf2d pos,int damage,bool friendly=false,DamageNumberType type=HEALTH_LOSS);
//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();
};

@ -0,0 +1,98 @@
#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 <ranges>
#include "util.h"
#include "AdventuresInLestoria.h"
INCLUDE_ANIMATION_DATA
INCLUDE_game
DeadlyDash::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)
:animation(animation),startPos(startPos),endPos(endPos),checkRadius(radius),afterImagesSpreadDist(afterImagesSpreadDist),afterImagesLingeringTime(afterImagesLingeringTime),knockbackAmt(knockbackAmt),Bullet({},{},0.f,damage,"circle.png",upperLevel,true,INFINITE,false,friendly,col,{1.f,1.f},0.f){
const geom2d::line<float>afterImageLine{startPos,endPos};
const int afterImageCount{int(afterImageLine.length()/afterImagesSpreadDist)+1};
lifetime=afterImageCount*afterImagesLingeringTime;
for(int i:std::ranges::iota_view(0,afterImageCount)){
const float offsetDist{afterImagesSpreadDist*(i+1)};
const vf2d afterImagePos{geom2d::line<float>{startPos,endPos}.rpoint(offsetDist)};
if(friendly){
const HurtList&hitList{game->HurtNotHit(afterImagePos,radius,damage,this->hitList,OnUpperLevel(),GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)};
for(auto&[entityPtr,wasHit]:hitList){
if(wasHit){
Monster*monster{std::get<Monster*>(entityPtr)};
monster->ProximityKnockback(afterImagePos,knockbackAmt);
this->hitList.insert(monster);
}
}
}else{
const HurtList&hitList{game->HurtNotHit(afterImagePos,radius,damage,this->hitList,OnUpperLevel(),GetZ(),HurtType::PLAYER)};
for(auto&[entityPtr,wasHit]:hitList){
if(wasHit){
Player*player{std::get<Player*>(entityPtr)};
player->ProximityKnockback(afterImagePos,knockbackAmt);
}
}
}
}
}
void DeadlyDash::Draw(const Pixel blendCol)const{
const geom2d::line<float>afterImageLine{startPos,endPos};
const int afterImageCount{int(afterImageLine.length()/afterImagesSpreadDist)+1};
for(int i:std::ranges::iota_view(0,afterImageCount)){
const float fadeTimeBegins{(i+1)*afterImagesLingeringTime};
uint8_t alpha{255U};
if(GetAliveTime()>fadeTimeBegins)alpha=std::max(0.f,util::lerp(255,0,(GetAliveTime()-fadeTimeBegins)/afterImagesLingeringTime));
const Animate2D::FrameSequence&animation{ANIMATION_DATA[this->animation]};
const float animationFrameTimer{i*animation.m_fFrameDuration};
const float offsetDist{afterImagesSpreadDist*(i+1)};
const vf2d drawPos{geom2d::line<float>{startPos,endPos}.rpoint(offsetDist)};
const Animate2D::Frame&frame{animation.GetFrame(animationFrameTimer)};
game->SetDecalMode(DecalMode::ADDITIVE);
game->view.DrawPartialRotatedDecal(drawPos,frame.GetSourceImage()->Decal(),0.f,frame.GetSourceRect().size/2,frame.GetSourceRect().pos,frame.GetSourceRect().size,{game->GetPlayer()->GetSizeMult(),game->GetPlayer()->GetSizeMult()},{col.r,col.g,col.b,alpha});
game->SetDecalMode(DecalMode::NORMAL);
}
}
void DeadlyDash::ModifyOutgoingDamageData(HurtDamageInfo&data){
//NOTE: Despite it looking like we modify the damage here, the radius passed to Bullet is 0, so this shouldn't matter anyways, the damage happens in the check in Update()
if(friendly)data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
}

@ -0,0 +1,78 @@
#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 "AdventuresInLestoria.h"
#include "Unlock.h"
INCLUDE_game
INCLUDE_GFX
void Menu::InitializeDeathWindow(){
Menu*deathWindow=CreateMenu(MenuType::DEATH,CENTERED,vi2d{96,52});
deathWindow->ADD("Retry Button",MenuComponent)(geom2d::rect<float>{{6.f,0.f},{84.f,24.f}},"Retry",[](MenuFuncData data){
Menu::CloseAllMenus();
Inventory::GivePlayerLoadoutItemsUsed();
GameState::ChangeState(States::GAME_RUN);
return true;
},ButtonAttr::FIT_TO_LABEL)END;
deathWindow->ADD("Return to Camp Button",MenuComponent)(geom2d::rect<float>{{6.f,28.f},{84.f,24.f}},"Return to Camp",[](MenuFuncData data){
Component<MenuComponent>(MenuType::PAUSE,"Return to Camp Button")->Click();
return true;
},ButtonAttr::FIT_TO_LABEL)END;
deathWindow->SetupKeyboardNavigation(
[](MenuType type,Data&returnData){ //On Open
returnData="Retry Button";
},
{ //Button Key
{game->KEY_SCROLL,{"Navigate",[](MenuType type){}}},
{game->KEY_CONFIRM,{"Select",[](MenuType type){}}},
}
,{ //Button Navigation Rules
{"Retry Button",{
.up="Return to Camp Button",
.down="Return to Camp Button",}},
{"Return to Camp Button",{
.up="Retry Button",
.down="Retry Button",}},
});
}

@ -0,0 +1,65 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions or derivations of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice. This list of conditions and the following disclaimer must be
reproduced in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Portions of this software are copyright © 2024 The FreeType
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "BulletTypes.h"
#include "AdventuresInLestoria.h"
#include "util.h"
INCLUDE_GFX
INCLUDE_game
Debris::Debris(const vf2d pos,const vf2d vel,const int damage,const float radius,const float knockbackAmt,const float rotSpd,const float lifetime,bool upperLevel,bool friendly,Pixel col,const vf2d scale)
:Bullet(pos,vel,radius,damage,"commercial_assets/wind_solid_objects.png",upperLevel,false,lifetime,false,friendly,col,scale),knockbackAmt(knockbackAmt),rotatingSpd(rotSpd),randomFrame(util::random(GFX["commercial_assets/wind_solid_objects.png"].Sprite()->Size().x/24)){}
void Debris::Update(float fElapsedTime){
image_angle+=rotatingSpd*fElapsedTime;
}
BulletDestroyState Debris::PlayerHit(Player*player){
player->Knockback(vel.norm()*knockbackAmt);
fadeOutTime=0.5f;
return BulletDestroyState::KEEP_ALIVE;
}
BulletDestroyState Debris::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
monster.Knockback(vel.norm()*knockbackAmt);
fadeOutTime=0.5f;
return BulletDestroyState::KEEP_ALIVE;
}
void Debris::Draw(const Pixel blendCol)const{
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY,GetFrame().GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2+image_angle:image_angle,{12,12},vf2d{randomFrame*24.f,0.f},{24,24},scale,blendCol);
}
void Debris::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -0,0 +1,45 @@
#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
enum class Direction{
NORTH,
EAST,
SOUTH,
WEST
};

@ -0,0 +1,41 @@
#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 "Monster.h"
void Monster::STRATEGY::DONOTHING(Monster&m,float fElapsedTime,std::string strategy){}

@ -44,44 +44,65 @@ INCLUDE_ANIMATION_DATA
INCLUDE_game
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,vf2d{size,size},fadeout,spd,col,rotation,rotationSpd,additiveBlending){
this->animation.AddState(imgFile,ANIMATION_DATA[imgFile]);
}
:Effect::Effect(pos,lifetime,imgFile,upperLevel,0.f,fadeout,vf2d{size,size},spd,col,rotation,rotationSpd,additiveBlending){}
Effect::Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending)
:pos(pos),lifetime(lifetime),upperLevel(upperLevel),size(size),fadeout(fadeout),original_fadeoutTime(fadeout),spd(spd),col(col),rotation(rotation),rotationSpd(rotationSpd),additiveBlending(additiveBlending){
this->animation.AddState(imgFile,ANIMATION_DATA[imgFile]);
: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);
}
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);
}
bool Effect::Update(float fElapsedTime){
lifetime-=fElapsedTime;
if(lifetime<=0){
fadeout-=fElapsedTime;
if(fadeout<=0){
dead=true;
return false;
aliveTime+=fElapsedTime;
if(fadein<original_fadeInTime){
fadein=std::min(original_fadeInTime,fadein+fElapsedTime);
}else{
lifetime-=fElapsedTime;
if(lifetime<=0){
fadeout-=fElapsedTime;
if(fadeout<=0){
dead=true;
return false;
}
}
}
rotation+=rotationSpd*fElapsedTime;
size+=scaleSpd*fElapsedTime;
pos+=spd*fElapsedTime;
animation.UpdateState(internal_animState,fElapsedTime);
return true;
}
void Effect::Draw(){
void Effect::Draw()const{
if(additiveBlending)game->SetDecalMode(DecalMode::ADDITIVE);
if(fadeout==0){
const bool FadeInFinished{original_fadeInTime==0||fadein==original_fadeInTime};
const bool HasFadeout{fadeout>0};
[[unlikely]]if(!FadeInFinished){
game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadein/original_fadeInTime*col.a)});
}else
[[likely]]if(HasFadeout){
game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,{col.r,col.g,col.b,uint8_t(fadeout/original_fadeOutTime*col.a)});
}else{
game->view.DrawPartialRotatedDecal(pos,GetFrame().GetSourceImage()->Decal(),rotation,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,size,col);
} else {
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*255)});
}
game->SetDecalMode(DecalMode::NORMAL);
}
Animate2D::Frame Effect::GetFrame(){
Animate2D::Frame Effect::GetFrame()const{
return animation.GetFrame(internal_animState);
}
bool Effect::OnUpperLevel(){
return upperLevel;
}
const EffectType Effect::GetType()const{
return type;
}

@ -37,34 +37,51 @@ All rights reserved.
#pragma endregion
#pragma once
#include "Animation.h"
#include <unordered_set>
#include <variant>
class Monster;
class Player;
using HitList=std::unordered_set<std::variant<Monster*,Player*>>;
enum class EffectType{
NONE,
SPELL_CIRCLE,
};
struct Effect{
friend class AiL;
friend struct FallingStone;
vf2d pos={0,0};
float lifetime=0;
float fadeout=0;
float fadein{0.f};
vf2d size={1,1};
Pixel col=WHITE;
vf2d spd={};
float rotation=0;
float rotationSpd=0;
vf2d scaleSpd{};
bool additiveBlending=false;
bool rendered=false;
private:
bool dead=false;
public:
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
Effect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float fadein,float fadeout,vf2d size,vf2d spd,Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
virtual bool Update(float fElapsedTime);
Animate2D::Frame GetFrame();
virtual void Draw();
Animate2D::Frame GetFrame()const;
virtual void Draw()const;
bool OnUpperLevel();
const EffectType GetType()const;
protected:
float original_fadeoutTime;
float original_fadeOutTime;
float original_fadeInTime{};
EffectType type{EffectType::NONE};
private:
Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState;
bool upperLevel=false;
double aliveTime{};
};
struct Meteor:Effect{
@ -72,7 +89,7 @@ struct Meteor:Effect{
float startLifetime=0;
bool shakeField=false;
bool Update(float fElapsedTime)override;
void Draw()override;
void Draw()const override;
};
struct PulsatingFire:Effect{
@ -81,5 +98,47 @@ struct PulsatingFire:Effect{
float lastParticleTimer=0;
float lastDamageTimer=0;
bool Update(float fElapsedTime)override;
void Draw()override;
void Draw()const override;
};
struct SwordSlash:Effect{
SwordSlash(float lifetime,std::string imgFile,float damageMult, float swordSweepAngle,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
bool Update(float fElapsedTime)override;
private:
HitList hitList;
const float damageMult;
const float swordSweepAngle;
};
//This draws effects using screen coordinates instead of world coordinates, useful for applying effects directly on the screen.
struct ForegroundEffect:Effect{
ForegroundEffect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
ForegroundEffect(vf2d pos,float lifetime,std::string imgFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false);
virtual void Draw()const override final;
};
struct SpellCircle:Effect{
SpellCircle(vf2d pos,float lifetime,std::string imgFile,std::string spellInsigniaFile,bool upperLevel,float size=1.0f,float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false,float insigniaSize=1.0f,float insigniaFadeout=0.0f,vf2d insigniaSpd={},Pixel insigniaCol=WHITE,float insigniaRotation=0,float insigniaRotationSpd=0,bool insigniaAdditiveBlending=false);
SpellCircle(vf2d pos,float lifetime,std::string imgFile,std::string spellInsigniaFile,bool upperLevel,vf2d size={1,1},float fadeout=0.0f,vf2d spd={},Pixel col=WHITE,float rotation=0,float rotationSpd=0,bool additiveBlending=false,vf2d insigniaSize={1,1},float insigniaFadeout=0.0f,vf2d insigniaSpd={},Pixel insigniaCol=WHITE,float insigniaRotation=0,float insigniaRotationSpd=0,bool insigniaAdditiveBlending=false);
Effect spellInsignia{vf2d{},0.f,"spell_insignia.png",false,{}};
virtual bool Update(float fElapsedTime)override final;
virtual void Draw()const override final;
};
struct RockLaunch:Effect{
//The lifetime is for how long the effect lasts after it's launched. You don't have to calculate extra lifetime to compensate for the delayTime, it is done for you.
RockLaunch(vf2d pos,float lifetime,std::string imgFile,float delayTime,float size,float fadeout,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false);
virtual bool Update(float fElapsedTime)override final;
private:
float delayTime;
vf2d futureSpd;
};
struct ShineEffect:Effect{
//An effect that starts invisible, becomes visible, then fades out again.
ShineEffect(vf2d pos,float fadeinTime,float fadeoutTime,std::string imgFile,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false);
virtual bool Update(float fElapsedTime)override final;
private:
float fadeinTime;
const Pixel originalCol;
};

@ -53,24 +53,25 @@ void EnergyBolt::Update(float fElapsedTime){
lastParticleSpawn="Wizard.Auto Attack.ParticleFrequency"_F;
game->AddEffect(std::make_unique<Effect>(pos,"Wizard.Auto Attack.ParticleLifetimeRange"_FRange,"energy_particle.png",upperLevel,"Wizard.Auto Attack.ParticleSizeRange"_FRange,"Wizard.Auto Attack.ParticleFadeoutTime"_F,vf2d{"Wizard.Auto Attack.ParticleSpeedRange"_FRange,"Wizard.Auto Attack.ParticleSpeedRange"_FRange}));
}
if(distanceTraveled>"Wizard.Auto Attack.Max Range"_F&&!deactivated){
deactivated=true;
if(distanceTraveled>"Wizard.Auto Attack.Max Range"_F&&IsActivated()){
fadeOutTime="Wizard.Auto Attack.BulletHitFadeoutTime"_F;
}
}
bool EnergyBolt::PlayerHit(Player*player)
BulletDestroyState EnergyBolt::PlayerHit(Player*player)
{
deactivated=true;
fadeOutTime="Wizard.Auto Attack.BulletHitFadeoutTime"_F;
game->AddEffect(std::make_unique<Effect>(player->GetPos(),0,"splash_effect.png",upperLevel,player->GetSizeMult(),"Wizard.Auto Attack.SplashEffectFadeoutTime"_F));
return false;
return BulletDestroyState::KEEP_ALIVE;
}
bool EnergyBolt::MonsterHit(Monster& monster)
BulletDestroyState EnergyBolt::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)
{
deactivated=true;
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));
return false;
return BulletDestroyState::KEEP_ALIVE;
}
void EnergyBolt::ModifyOutgoingDamageData(HurtDamageInfo&data){
if(friendly)data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
}

@ -94,6 +94,6 @@ void EnvironmentalAudio::Update(){
float distRatio=1-distanceFromPlayer/ACTIVATION_RANGE; //0-1 where 1 is full volume.
float xDistRatio=(pos.x-game->GetPlayer()->GetX())/ACTIVATION_RANGE; //0-1 where 1 is full volume.
Audio::Engine().SetVolume(soundInstance,distRatio*SOUND_DATA[audioName].volume);
Audio::Engine().SetVolume(soundInstance,distRatio*SOUND_DATA[audioName].volume*Audio::GetSFXVolume()*Audio::GetMuteMult());
Audio::Engine().SetPan(soundInstance,xDistRatio);
}

@ -59,7 +59,27 @@ public:
itemRef=Item::BLANK;
}
}
virtual inline void Update(AiL*game)override{
MenuIconButton::Update(game);
valid=!ISBLANK(itemRef);
if(!valid){
icon=nullptr;
}
if(hovered){
UpdateLabel();
}
}
inline const EquipSlot GetSlot()const{
return slot;
}
inline virtual void DrawDecal(ViewPort&window,bool focused)override final{
MenuItemItemButton::DrawDecal(window,focused);
if(!itemRef.expired()&&itemRef.lock()->EnhancementLevel()>0){
const std::string enhanceLevelStr="+"+std::to_string(itemRef.lock()->EnhancementLevel());
vi2d enhanceLevelStrSize=game->GetTextSize(enhanceLevelStr)*0.75f;
window.DrawShadowStringDecal(rect.pos+rect.size-enhanceLevelStrSize,enhanceLevelStr,GREEN,BLACK,vf2d{0.75f,0.75f});
}
}
};

@ -42,22 +42,23 @@ All rights reserved.
#include <any>
#include <memory>
#include <source_location>
#include <fstream>
inline std::ofstream debugLogger;
#ifdef _DEBUG
#ifndef __EMSCRIPTEN__
#ifndef __linux__
#define NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
// Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
// allocations to be of _CLIENT_BLOCK type
#else
#define NEW new
#ifndef __EMSCRIPTEN__
#ifndef __linux__
#define NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
// Replace _NORMAL_BLOCK with _CLIENT_BLOCK if you want the
// allocations to be of _CLIENT_BLOCK type
#endif
#endif
#endif
#else
#ifndef NEW //For everything else.
#define NEW new
#endif
#else
#define NEW new
#endif
#undef ERR //Stupid Windows
@ -67,12 +68,20 @@ All rights reserved.
std::stringstream errStream; \
errStream<<err; \
Error::log(errStream,std::source_location::current());}
#define LOG(msg) { \
std::stringstream debugStream; \
debugStream<<msg; \
Error::debug(debugStream,std::source_location::current());}
class Error{
public:
inline static void debug(std::stringstream&str,std::source_location loc){
debugLogger<<loc.file_name()<<"("<<loc.line()<<":"<<loc.column()<<") "<<loc.function_name()<<": "<<str.str()<<std::endl;
std::cout<<loc.file_name()<<"("<<loc.line()<<":"<<loc.column()<<") "<<loc.function_name()<<": "<<str.str()<<std::endl;
}
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;
std::cout<<loc.file_name()<<"("<<loc.line()<<":"<<loc.column()<<") "<<loc.function_name()<<": "<<str.str()<<std::endl;
throw;
throw std::runtime_error{std::format("{}({}:{}) {}: {}",loc.file_name(),loc.line(),loc.column(),loc.function_name(),str.str()).c_str()};
}
};
#else
@ -80,6 +89,10 @@ All rights reserved.
std::stringstream errStream; \
errStream<<err; \
Error::log(errStream,std::source_location::current());}
#define LOG(err) { \
std::stringstream errStream; \
errStream<<err; \
Error::log(errStream,std::source_location::current());}
class Error{
public:
@ -113,4 +126,4 @@ 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;
}
}

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

@ -0,0 +1,120 @@
#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 "AdventuresInLestoria.h"
#include <ranges>
#include "config.h"
#include "util.h"
#include "SoundEffect.h"
INCLUDE_ANIMATION_DATA
INCLUDE_game
ExplosiveTrap::ExplosiveTrap(vf2d pos,float radius,float explosionRadius,float automaticDetonationTime,int damage,float fadeinTime,float fadeoutTime,float activationWaitTime,bool upperLevel,bool hitsMultiple,float lifetime,bool friendly,Pixel col,vf2d scale)
:activationWaitTime(activationWaitTime),automaticDetonationTime(automaticDetonationTime),activationRadius(radius),explosionRadius(explosionRadius),Bullet(pos,{},0.f,damage,"Ability Icons/explosive_trap.png",upperLevel,hitsMultiple,lifetime,false,friendly,col,scale,0.f,"Trap Hit"){
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!");
}
void ExplosiveTrap::Update(float fElapsedTime){
const bool trapActivated{radius==activationRadius};
if(IsDeactivated())return;
if(!trapActivated){
activationWaitTime-=fElapsedTime;
if(activationWaitTime<=0.f){
radius=activationRadius;
animation.ChangeState(internal_animState,"explosive_trap.png");
beepCount=std::min(4U,beepCount+1U);
SoundEffect::PlaySFX(std::format("Beep {}",beepCount),pos);
lastBeepTime=1.f;
}
}else{
automaticDetonationTime-=fElapsedTime;
lastBeepTime-=fElapsedTime;
if(lastBeepTime<=0.f){
beepCount=std::min(4U,beepCount+1U);
SoundEffect::PlaySFX(std::format("Beep {}",beepCount),pos);
lastBeepTime=1.f;
}
if(IsActivated()&&automaticDetonationTime<=0.f)Detonate();
}
}
void ExplosiveTrap::ModifyOutgoingDamageData(HurtDamageInfo&data){
data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
}
BulletDestroyState ExplosiveTrap::PlayerHit(Player*player){
fadeOutTime=0.5f;
return BulletDestroyState::KEEP_ALIVE;
}
void ExplosiveTrap::Detonate(){
fadeOutTime=0.5f;
const HurtList list{game->HurtNotHit(pos,"Trapper.Ability 3.Explosion Radius"_F/100.f*24,damage,hitList,OnUpperLevel(),GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)};
for(const auto&[targetPtr,wasHit]:list){
if(wasHit){
std::get<Monster*>(targetPtr)->ApplyMark("Trapper.Ability 3.Explosion Mark Stack Time"_F,"Trapper.Ability 3.Explosion Mark Stack Increase"_I);
std::get<Monster*>(targetPtr)->ProximityKnockback(pos,"Trapper.Ability 3.Explosion Knockback Amount"_F);
std::get<Monster*>(targetPtr)->Knockup("Trapper.Ability 3.Explosion Knockup Amount"_F);
}
}
std::unique_ptr<Effect>explodeEffect{std::make_unique<Effect>(pos,ANIMATION_DATA["explosionframes.png"].GetTotalAnimationDuration()-0.2f,"explosionframes.png",OnUpperLevel(),scale*2.f,0.2f,vf2d{0,-6.f},WHITE,util::random(2*PI),0.f)};
explodeEffect->scaleSpd={0.125f,0.125f};
game->AddEffect(std::move(explodeEffect));
SoundEffect::PlaySFX("Explosion",pos);
Deactivate();
}
BulletDestroyState ExplosiveTrap::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
for(int i:std::ranges::iota_view(0,"Trapper.Ability 3.Trap Mark Trigger Count"_I-1)){
monster.Hurt(0,monster.OnUpperLevel(),monster.GetZ(),HurtFlag::PLAYER_ABILITY);//Triggers mark multiple times after the first mark.
}
monster.ProximityKnockback(pos,"Trapper.Ability 3.Explosion Knockback Amount"_F);
monster.Knockup("Trapper.Ability 3.Explosion Knockup Amount"_F);
Detonate();
return BulletDestroyState::KEEP_ALIVE;
}

@ -0,0 +1,92 @@
#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 "DEFINES.h"
#include "AdventuresInLestoria.h"
#include "util.h"
#include <ranges>
#include "SoundEffect.h"
INCLUDE_game
FallingStone::FallingStone(vf2d targetPos,vf2d vel,float zVel,float indicatorDisplayTime,float radius,int damage,bool upperLevel,bool hitsMultiple,float knockbackAmt,float lifetime,bool friendly,Pixel col,vf2d scale,float image_angle,float spellCircleRotation,float spellCircleRotationSpd,Pixel insigniaCol,float insigniaRotation,float insigniaRotationSpd)
:Bullet(targetPos,vel,radius,damage,"rock.png",upperLevel,false,lifetime+0.1f,false,friendly,col,scale,image_angle),targetPos(targetPos),zVel(zVel),indicatorDisplayTime(indicatorDisplayTime),knockbackAmt(knockbackAmt),
indicator(targetPos,lifetime+0.1f,"range_indicator.png","spell_insignia.png",upperLevel,radius/12.f,0.5f,{},col,spellCircleRotation,spellCircleRotationSpd,false,radius/12.f,0.f,{},insigniaCol,insigniaRotation,insigniaRotationSpd,false){
pos+=-vel*lifetime;
z=-zVel*lifetime;
}
void FallingStone::Update(float fElapsedTime){
z+=zVel*fElapsedTime;
lastTrailEffect-=fElapsedTime;
if(z<=0.f){
z=0.f;
vel={};
if(IsActivated()){
fadeOutTime=0.5f;
SoundEffect::PlaySFX("Stone Land",pos);
if(friendly){
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(targetPos,radius,damage,OnUpperLevel(),z,HurtType::PLAYER)){
if(hurt)std::get<Player*>(playerPtr)->ApplyIframes(0.1f);
}
}
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();
}
}else{
if(lastTrailEffect<=0.f){
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(-3.f,3.f),util::random_range(-3.f,3.f)},PixelLerp(BLACK,col,util::random(1.f)),0.f,0.f,true));
}
}
indicator.Update(fElapsedTime);
}
void FallingStone::Draw(const Pixel blendCol)const{
if(lifetime<=indicatorDisplayTime){
indicator.Draw();
}
Bullet::Draw(blendCol);
}
void FallingStone::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -0,0 +1,47 @@
#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 "Player.h"
Feather::Feather(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool hitsMultiple,float lifetime,bool friendly,Pixel col,vf2d scale,float image_angle)
:Bullet(pos,vel,radius,damage,"feather.png",upperLevel,hitsMultiple,lifetime,true,friendly,col){
SetBulletType(BulletType::FEATHER);
}
void Feather::ModifyOutgoingDamageData(HurtDamageInfo&data){}

@ -55,15 +55,14 @@ void FireBolt::Update(float fElapsedTime){
lastParticleSpawn="Wizard.Ability 1.ParticleFrequency"_F;
game->AddEffect(std::make_unique<Effect>(pos,"Wizard.Ability 1.ParticleLifetimeRange"_FRange,"energy_particle.png",upperLevel,"Wizard.Ability 1.ParticleSizeRange"_FRange,"Wizard.Ability 1.ParticleFadeoutTime"_F,vf2d{"Wizard.Ability 1.ParticleXSpeedRange"_FRange,"Wizard.Ability 1.ParticleYSpeedRange"_FRange},Pixel{uint8_t("Wizard.Ability 1.ParticleRedRange"_FRange),uint8_t("Wizard.Ability 1.ParticleGreenRange"_FRange),uint8_t("Wizard.Ability 1.ParticleBlueRange"_FRange),uint8_t("Wizard.Ability 1.ParticleAlphaRange"_FRange)}));
}
if(distanceTraveled>"Wizard.Ability 1.Max Range"_F&&!deactivated){
deactivated=true;
if(distanceTraveled>"Wizard.Ability 1.Max Range"_F&&IsActivated()){
fadeOutTime="Wizard.Ability 1.BulletHitFadeoutTime"_F;
for(int i=0;i<"Wizard.Ability 1.BulletHitExplosionParticleCount"_I;i++){
game->AddEffect(std::make_unique<Effect>(pos,"Wizard.Ability 1.BulletHitExplosionParticleLifetimeRange"_FRange,"circle.png",upperLevel,"Wizard.Ability 1.BulletHitExplosionParticleSizeRange"_FRange,"Wizard.Ability 1.BulletHitExplosionParticleFadeoutTimeRange"_FRange,vf2d{"Wizard.Ability 1.BulletHitExplosionParticleSpeedRange"_FRange,"Wizard.Ability 1.BulletHitExplosionParticleSpeedRange"_FRange},Pixel{uint8_t("Wizard.Ability 1.BulletHitExplosionParticleRedRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleGreenRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleBlueRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleAlphaRange"_FRange)}));
}
game->SetupWorldShake("Wizard.Ability 1.WorldShakeTime"_F);
if(friendly){
game->HurtEnemies(pos,"Wizard.Ability 1.BulletHitExplosionRange"_F/100*12,int("Wizard.Ability 1.BulletHitExplosionDamageMult"_F*game->GetPlayer()->GetAttack()),OnUpperLevel(),0);
game->Hurt(pos,"Wizard.Ability 1.BulletHitExplosionRange"_F/100*12,int("Wizard.Ability 1.BulletHitExplosionDamageMult"_F*game->GetPlayer()->GetAttack()),OnUpperLevel(),0,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY);
}else{
if(geom2d::overlaps(geom2d::circle<float>{pos,"Wizard.Ability 1.BulletHitExplosionRange"_F/100*12},geom2d::circle<float>{game->GetPlayer()->GetPos(),12.f})){
game->GetPlayer()->Hurt(damage,OnUpperLevel(),0.f);
@ -75,9 +74,8 @@ void FireBolt::Update(float fElapsedTime){
}
}
bool FireBolt::PlayerHit(Player*player)
BulletDestroyState FireBolt::PlayerHit(Player*player)
{
deactivated=true;
fadeOutTime="Wizard.Ability 1.BulletHitFadeoutTime"_F;
for(int i=0;i<"Wizard.Ability 1.BulletHitExplosionParticleCount"_I;i++){
game->AddEffect(std::make_unique<Effect>(player->GetPos(),"Wizard.Ability 1.BulletHitExplosionParticleLifetimeRange"_FRange,"circle.png",upperLevel,"Wizard.Ability 1.BulletHitExplosionParticleSizeRange"_FRange,"Wizard.Ability 1.BulletHitExplosionParticleFadeoutTimeRange"_FRange,vf2d{"Wizard.Ability 1.BulletHitExplosionParticleSpeedRange"_FRange,"Wizard.Ability 1.BulletHitExplosionParticleSpeedRange"_FRange},Pixel{uint8_t("Wizard.Ability 1.BulletHitExplosionParticleRedRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleGreenRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleBlueRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleAlphaRange"_FRange)}));
@ -86,20 +84,23 @@ bool FireBolt::PlayerHit(Player*player)
game->AddEffect(std::make_unique<Effect>(player->GetPos(),0,"splash_effect.png",upperLevel,5,0.25,vf2d{},Pixel{240,120,60}));
SoundEffect::PlaySFX("Wizard Fire Bolt Hit",pos);
return false;
return BulletDestroyState::KEEP_ALIVE;
}
bool FireBolt::MonsterHit(Monster& monster)
BulletDestroyState FireBolt::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)
{
deactivated=true;
fadeOutTime="Wizard.Ability 1.BulletHitFadeoutTime"_F;
for(int i=0;i<"Wizard.Ability 1.BulletHitExplosionParticleCount"_I;i++){
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),"Wizard.Ability 1.BulletHitExplosionParticleLifetimeRange"_FRange,"circle.png",upperLevel,"Wizard.Ability 1.BulletHitExplosionParticleSizeRange"_FRange,"Wizard.Ability 1.BulletHitExplosionParticleFadeoutTimeRange"_FRange,vf2d{"Wizard.Ability 1.BulletHitExplosionParticleSpeedRange"_FRange,"Wizard.Ability 1.BulletHitExplosionParticleSpeedRange"_FRange},Pixel{uint8_t("Wizard.Ability 1.BulletHitExplosionParticleRedRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleGreenRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleBlueRange"_FRange),uint8_t("Wizard.Ability 1.BulletHitExplosionParticleAlphaRange"_FRange)}));
}
game->SetupWorldShake("Wizard.Ability 1.WorldShakeTime"_F);
game->HurtEnemies(monster.GetPos(),"Wizard.Ability 1.BulletHitExplosionRange"_F/100*12,int("Wizard.Ability 1.BulletHitExplosionDamageMult"_F*game->GetPlayer()->GetAttack()),OnUpperLevel(),0);
game->Hurt(monster.GetPos(),"Wizard.Ability 1.BulletHitExplosionRange"_F/100*12,int("Wizard.Ability 1.BulletHitExplosionDamageMult"_F*game->GetPlayer()->GetAttack()),OnUpperLevel(),0,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY);
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),0,"splash_effect.png",upperLevel,"Wizard.Ability 1.BulletHitExplosionRange"_F/100*2,"Wizard.Ability 1.BulletHitExplosionFadeoutTime"_F,vf2d{},"Wizard.Ability 1.BulletHitExplosionColor"_Pixel));
SoundEffect::PlaySFX("Wizard Fire Bolt Hit",pos);
return false;
return BulletDestroyState::KEEP_ALIVE;
}
void FireBolt::ModifyOutgoingDamageData(HurtDamageInfo&data){
if(friendly)data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
}

@ -0,0 +1,107 @@
#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 "MenuComponent.h"
#include "AdventuresInLestoria.h"
#include "drawutil.h"
#include "util.h"
#include "ScrollableWindowComponent.h"
using A=Attribute;
INCLUDE_game
//A button that directly draws in a location in the game.
class FloatingMenuComponent:public MenuComponent{
public:
inline FloatingMenuComponent(geom2d::rect<float>rect,std::string label,MenuFunc onClick,ButtonAttr attributes=ButtonAttr::NONE)
:MenuComponent(rect,label,onClick,attributes){};
inline virtual bool HandleOutsideDisabledButtonSelection(std::weak_ptr<MenuComponent>disabledButton)override final{
return true;
};
inline virtual bool GetHoverState(AiL*game){
if(!parentComponent.expired()){
return parentComponent.lock()->GetHoverState(game,this);
}else{
return geom2d::overlaps(geom2d::rect<float>{rect.pos,rect.size},game->GetMousePos());
}
}
inline virtual void DrawDecal(ViewPort&window,bool focused)override final{
if(background){
Pixel backCol=PixelLerp(Menu::themes[Menu::themeSelection].GetButtonCol(),Menu::themes[Menu::themeSelection].GetHighlightCol(),hoverEffect/"ThemeGlobal.HighlightTime"_F);
if(grayedOut){
backCol=DARK_GREY;
}
game->FillRectDecal(rect.pos+V(A::DRAW_OFFSET),rect.size,backCol);
}
if(selected&&selectionType==HIGHLIGHT){
game->FillRectDecal(rect.pos+V(A::DRAW_OFFSET),rect.size,PixelLerp(Menu::themes[Menu::themeSelection].GetButtonCol(),Menu::themes[Menu::themeSelection].GetHighlightCol(),util::lerp(0.75f,1.0f,hoverEffect/"ThemeGlobal.HighlightTime"_F)));
}
if(border){
game->FillRectDecal(rect.pos+V(A::DRAW_OFFSET),{rect.size.x+1,1},borderCol);
game->FillRectDecal(rect.pos+V(A::DRAW_OFFSET),{1,rect.size.y+1},borderCol);
game->FillRectDecal(rect.pos+V(A::DRAW_OFFSET)+vf2d{rect.size.x-1+1,0},{1,rect.size.y+1},borderCol);
game->FillRectDecal(rect.pos+V(A::DRAW_OFFSET)+vf2d{0,rect.size.y-1+1},{rect.size.x+1,1},borderCol);
}
if(showDefaultLabel){
vf2d adjustedScale=labelScaling;
vi2d labelTextSize=vf2d(game->GetTextSizeProp(label))*adjustedScale;
if(fitToLabel){
float sizeRatio=(vf2d(labelTextSize).x)/(rect.size.x-2);
if(sizeRatio>1){
adjustedScale.x/=sizeRatio;
labelTextSize.x/=sizeRatio;
}
}
game->DrawStringPropDecal(rect.pos+V(A::DRAW_OFFSET)+rect.size/2-vf2d(labelTextSize)/2.f,label,WHITE,adjustedScale);
}
if(selected){
switch(selectionType){
case CROSSHAIR:drawutil::DrawCrosshairDecal(game,{rect.pos+V(A::DRAW_OFFSET),rect.size},0);break;
case INNER_BOX:game->DrawRectDecal(rect.pos+V(A::DRAW_OFFSET)+vi2d{1,1},rect.size-vi2d{2,2});break;
case HIGHLIGHT:break;//Not used.
case SelectionType::NONE:break;//Displays nothing.
default:ERR("Undefined selection type selected: "<<int(selectionType));
}
}
}
};

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

@ -70,7 +70,7 @@ void Monster::STRATEGY::FROG(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::LOCKON_WAITTIME)=ConfigFloat("Attack Duration");
vf2d tongueMaxRangePos=geom2d::line<float>(m.GetPos(),m.V(A::LOCKON_POS)).upoint(ConfigFloat("Tongue Max Range")/ConfigFloat("Range"));
SoundEffect::PlaySFX("Slime Shoot",m.pos);
CreateBullet(FrogTongue)(m.pos,tongueMaxRangePos,ConfigFloat("Attack Duration"),m.GetAttack(),m.OnUpperLevel(),ConfigFloat("Tongue Knockback Strength"),false,ConfigPixel("Tongue Color"))EndBullet;
CreateBullet(FrogTongue)(m,tongueMaxRangePos,ConfigFloat("Attack Duration"),m.GetAttack(),m.OnUpperLevel(),ConfigFloat("Tongue Knockback Strength"),false,ConfigPixel("Tongue Color"))EndBullet;
m.PerformShootAnimation();
m.I(A::PHASE)=2;
}

@ -44,50 +44,53 @@ INCLUDE_game
INCLUDE_MONSTER_LIST
INCLUDE_GFX
FrogTongue::FrogTongue(vf2d pos,vf2d targetPos,float lifetime,int damage,bool upperLevel,float knockbackStrength,bool friendly,Pixel col)
:Bullet(pos,{},0,damage,upperLevel,friendly,col),targetPos(targetPos),tongueLength(0.f),knockbackStrength(knockbackStrength){
FrogTongue::FrogTongue(Monster&sourceMonster,vf2d targetPos,float lifetime,int damage,bool upperLevel,float knockbackStrength,bool friendly,Pixel col)
:Bullet(sourceMonster.GetPos(),{},0,damage,upperLevel,friendly,col),targetPos(targetPos),tongueLength(0.f),knockbackStrength(knockbackStrength),sourceMonster(sourceMonster){
this->lifetime=lifetime;
duration=lifetime;
}
void FrogTongue::Update(float fElapsedTime){
geom2d::line<float>lineToTarget(pos,targetPos);
vf2d drawVec=lineToTarget.vector().norm()*3;
pos=sourceMonster.GetPos();
tongueLength=util::lerp(0,lineToTarget.length(),pow(sin((lifetime*PI)/duration),20.f));
if(sourceMonster.IsAlive()){
geom2d::line<float>lineToTarget(pos,targetPos);
vf2d drawVec=lineToTarget.vector().norm()*3;
vf2d tongueEndPos=geom2d::line<float>(pos+drawVec,targetPos).upoint(pow(sin((lifetime*PI)/duration),20.f));
geom2d::line<float>tongueLine(pos+drawVec,tongueEndPos);
tongueLength=util::lerp(0,lineToTarget.length(),pow(sin((lifetime*PI)/duration),20.f));
if(!friendly&&geom2d::overlaps(game->GetPlayer()->Hitbox(),tongueLine)){
PlayerHit(game->GetPlayer());
}
if(friendly){
for(Monster&m:MONSTER_LIST){
if(hitList.find(&m)==hitList.end()&&geom2d::overlaps(m.Hitbox(),tongueLine)){
MonsterHit(m);
hitList.insert(&m);
vf2d tongueEndPos=geom2d::line<float>(pos+drawVec,targetPos).upoint(pow(sin((lifetime*PI)/duration),20.f));
geom2d::line<float>tongueLine(pos+drawVec,tongueEndPos);
if(!friendly&&geom2d::overlaps(game->GetPlayer()->Hitbox(),tongueLine)){
_PlayerHit(game->GetPlayer());
}
if(friendly){
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(hitList.find(&*m)==hitList.end()&&geom2d::overlaps(m->BulletCollisionHitbox(),tongueLine)){
_MonsterHit(*m,m->GetMarkStacks());
hitList.insert(&*m);
}
}
}
}
}
bool FrogTongue::PlayerHit(Player*player){
if(!deactivated){
BulletDestroyState FrogTongue::PlayerHit(Player*player){
if(IsActivated()){
player->Hurt(damage,OnUpperLevel(),0);
geom2d::line<float>lineToTarget(pos,targetPos);
vf2d drawVec=lineToTarget.vector().norm()*3;
player->Knockback(geom2d::line<float>(pos+drawVec,targetPos).vector()*knockbackStrength);
deactivated=true;
}
return false;
return BulletDestroyState::KEEP_ALIVE;
}
bool FrogTongue::MonsterHit(Monster&monster){
BulletDestroyState FrogTongue::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit){
monster.Hurt(damage,OnUpperLevel(),0);
geom2d::line<float>lineToTarget(pos,targetPos);
vf2d drawVec=lineToTarget.vector().norm()*3;
monster.Knockback(geom2d::line<float>(pos+drawVec,targetPos).vector()*knockbackStrength);
return false;
return BulletDestroyState::KEEP_ALIVE;
}
void FrogTongue::Draw(){
void FrogTongue::Draw(const Pixel blendCol)const{
geom2d::line<float>lineToTarget(pos,targetPos);
vf2d drawVec=lineToTarget.vector().norm()*3;
@ -95,4 +98,8 @@ void FrogTongue::Draw(){
game->view.DrawRotatedDecal(pos+drawVec,GFX["tongue.png"].Decal(),drawVec.polar().y,{0.f,1.f},{tongueLength,1.0f},col);
game->view.DrawRotatedDecal(tongueEndPos,GFX["tongue_end.png"].Decal(),drawVec.polar().y,{2.f,2.f},{1.f,1.f},col);
}
void FrogTongue::ModifyOutgoingDamageData(HurtDamageInfo&data){
if(friendly)data.hurtFlags|=HurtFlag::PLAYER_ABILITY;
}

@ -37,7 +37,6 @@ All rights reserved.
#pragma endregion
#include "GameEvent.h"
#include "Monster.h"
#include <map>
std::vector<std::unique_ptr<GameEvent>>GameEvent::events;
@ -68,6 +67,5 @@ MonsterStrategyGameEvent::MonsterStrategyGameEvent(std::function<bool(GameEvent&
:runFunc(func),m(m),strategy(strategy){}
void MonsterStrategyGameEvent::Update(){
INCLUDE_MONSTER_DATA
isActive=runFunc(*this,this->m,this->strategy);
}

@ -46,17 +46,28 @@ All rights reserved.
INCLUDE_DATA
bool GameSettings::screenShake=true;
bool GameSettings::showMaxHealth=false;
bool GameSettings::showMaxMana=false;
bool GameSettings::rumble=true;
bool GameSettings::terrainCollisionBoxes=true;
bool GameSettings::keyboardAutoAim=false;
bool GameSettings::vsync=true;
bool GameSettings::autopause=true;
const bool GameSettings::OVERRIDE=true;
vi2d GameSettings::windowPos{30,30};
IconType GameSettings::iconType=IconType::XB;
const bool GameSettings::ShowMaxHealth(){
return showMaxHealth;
}
const bool GameSettings::ShowMaxMana(){
return showMaxMana;
}
const bool GameSettings::ScreenShakeEnabled(){
return screenShake;
}
const bool GameSettings::RumbleEnabled(){
return rumble;
const bool GameSettings::RumbleEnabled(const bool override){
return rumble&&(override||GameState::STATE!=GameState::states[States::MAIN_MENU]);
}
const bool GameSettings::TerrainCollisionBoxesEnabled(){
return terrainCollisionBoxes;
@ -70,7 +81,19 @@ const vi2d GameSettings::GetWindowPos(){
const IconType GameSettings::GetIconType(){
return iconType;
}
const bool GameSettings::VSyncEnabled(){
return vsync;
}
const bool GameSettings::AutoPauseEnabled(){
return !game->TestingModeEnabled()&&autopause;
}
void GameSettings::SetMaxHealthDisplay(bool maxHealthDisplayed){
showMaxHealth=maxHealthDisplayed;
}
void GameSettings::SetMaxManaDisplay(bool maxManaDisplayed){
showMaxMana=maxManaDisplayed;
}
void GameSettings::SetScreenShake(bool screenShakeEnabled){
screenShake=screenShakeEnabled;
}
@ -90,13 +113,28 @@ void GameSettings::SetIconType(IconType type){
iconType=type;
}
void GameSettings::SetVSync(const bool vSyncEnabled){
vsync=vSyncEnabled;
}
void GameSettings::Initialize(){
utils::datafile loadSystemFile;
std::string loadSystemFilename="save_file_path"_S+"system.conf";
if(std::filesystem::exists(loadSystemFilename))utils::datafile::Read(loadSystemFile,loadSystemFilename);
if(std::filesystem::exists(loadSystemFilename)){
LOG("Reading system data file...");
utils::datafile::Read(loadSystemFile,loadSystemFilename);
}
if(loadSystemFile.HasProperty("Show Max Health")){
GameSettings::SetMaxHealthDisplay(loadSystemFile["Show Max Health"].GetBool());
Component<Checkbox>(SETTINGS,"Show Max HP Checkbox")->SetChecked(loadSystemFile["Show Max Health"].GetBool());
}
if(loadSystemFile.HasProperty("Show Max Mana")){
GameSettings::SetMaxManaDisplay(loadSystemFile["Show Max Mana"].GetBool());
Component<Checkbox>(SETTINGS,"Show Max Mana Checkbox")->SetChecked(loadSystemFile["Show Max Mana"].GetBool());
}
if(loadSystemFile.HasProperty("Screen Shake")){
GameSettings::SetScreenShake(loadSystemFile["Screen Shake"].GetBool());
Component<Checkbox>(SETTINGS,"Screen Shake Checkbox")->SetChecked(loadSystemFile["Screen Shake"].GetBool());
@ -113,6 +151,15 @@ void GameSettings::Initialize(){
GameSettings::SetKeyboardAutoAim(loadSystemFile["Keyboard Auto-Aim"].GetBool());
Component<Checkbox>(SETTINGS,"Keyboard Play Auto-Aim Checkbox")->SetChecked(loadSystemFile["Keyboard Auto-Aim"].GetBool());
}
if(loadSystemFile.HasProperty("VSync")){
GameSettings::SetVSync(loadSystemFile["VSync"].GetBool());
Component<Checkbox>(SETTINGS,"VSync Checkbox")->SetChecked(loadSystemFile["VSync"].GetBool());
game->SetVSync(GameSettings::VSyncEnabled());
}
if(loadSystemFile.HasProperty("Auto Pause")){
GameSettings::SetAutoPause(loadSystemFile["Auto Pause"].GetBool());
Component<Checkbox>(SETTINGS,"Auto Pause Checkbox")->SetChecked(loadSystemFile["Auto Pause"].GetBool());
}
if(loadSystemFile.HasProperty("Controller Icons")){
const int maxIterations=10;
int iterationCount=0;
@ -128,23 +175,33 @@ void GameSettings::Initialize(){
if(loadSystemFile.HasProperty("SFX Level"))Audio::SetSFXVolume(loadSystemFile["SFX Level"].GetReal());
#pragma region Load up Menu Keybinds
//NOTE: We are shadowing code from InputKeyboardWindow! If at some point the retrival method for getting input displays changes, we likely will be changing the code here as well!
//ALSO NOTE: The menu inputs are saved to the system file while gameplay inputs are per-character and saved to the character settings file!
const int menuRowCount=DATA.GetProperty("Inputs.Menu Input Names").GetValueCount()%2==0?DATA.GetProperty("Inputs.Menu Input Names").GetValueCount()/2:DATA.GetProperty("Inputs.Menu Input Names").GetValueCount()/2+1;
const int menuColCount=2;
for(int row=0;row<menuRowCount;row++){
for(int col=0;col<menuColCount;col++){
int inputID=row*menuColCount+col;
if(DATA.GetProperty("Inputs.Menu Input Names").GetValueCount()%2==1&&col==1&&row==menuRowCount-1)continue; //We only continue on a blank space when we have an odd number of elements.
std::string keyName="Menu Keyboard Input_"+"Inputs.Menu Input Names"_s[inputID];
if(loadSystemFile.HasProperty(keyName)){
InputGroup&group=Component<InputDisplayComponent>(INPUT_KEY_DISPLAY,std::format("Input_{}_{} Input Displayer",row,col))->GetInput();
group.SetNewPrimaryKeybind({KEY,loadSystemFile[keyName].GetInt()});
if(loadSystemFile.HasProperty("Menu Keyboard Input_"+"Inputs.Menu Input Names"_s[inputID])){
std::string keyName="Menu Keyboard Input_"+"Inputs.Menu Input Names"_s[inputID];
if(loadSystemFile.HasProperty(keyName)){
InputGroup&group=Component<InputDisplayComponent>(INPUT_KEY_DISPLAY,std::format("Input_{}_{} Input Displayer",row,col))->GetInput();
group.SetNewPrimaryKeybind({KEY,loadSystemFile[keyName].GetInt()});
}
}
keyName="Menu Controller Input_"+"Inputs.Menu Input Names"_s[inputID];
if(loadSystemFile.HasProperty(keyName)){
InputGroup&group=Component<InputDisplayComponent>(INPUT_KEY_DISPLAY,std::format("Input_{}_{} Input Displayer",row,col))->GetInput();
group.SetNewPrimaryKeybind({CONTROLLER,loadSystemFile[keyName].GetInt()});
if(loadSystemFile.HasProperty("Menu Controller Input_"+"Inputs.Menu Input Names"_s[inputID])){
std::string keyName="Menu Controller Input_"+"Inputs.Menu Input Names"_s[inputID];
if(loadSystemFile.HasProperty(keyName)){
InputGroup&group=Component<InputDisplayComponent>(INPUT_KEY_DISPLAY,std::format("Input_{}_{} Input Displayer",row,col))->GetInput();
group.SetNewPrimaryKeybind({CONTROLLER,loadSystemFile[keyName].GetInt()});
}
}
}
}
#pragma endregion
}
void GameSettings::SetAutoPause(const bool autoPauseEnabled){
autopause=autoPauseEnabled;
}

@ -41,24 +41,38 @@ All rights reserved.
#include "IconType.h"
class GameSettings{
static bool showMaxHealth;
static bool showMaxMana;
static bool screenShake;
static bool rumble;
static bool terrainCollisionBoxes;
static bool keyboardAutoAim;
static bool vsync;
static bool autopause;
static vi2d windowPos;
static IconType iconType;
public:
static const bool OVERRIDE;
static const bool ShowMaxHealth();
static const bool ShowMaxMana();
static const bool ScreenShakeEnabled();
static const bool RumbleEnabled();
static const bool RumbleEnabled(const bool override=false);
static const bool TerrainCollisionBoxesEnabled();
static const bool KeyboardAutoAimEnabled();
static const bool VSyncEnabled();
static const bool AutoPauseEnabled();
static const vi2d GetWindowPos();
static const IconType GetIconType();
static void SetMaxHealthDisplay(bool maxHealthDisplayed);
static void SetMaxManaDisplay(bool maxManaDisplayed);
static void SetScreenShake(bool screenShakeEnabled);
static void SetRumble(bool rumbleEnabled);
static void SetTerrainCollisionBoxes(bool terrainCollisionBoxesEnabled);
static void SetKeyboardAutoAim(bool autoAimEnabled);
static void SetWindowPos(vi2d windowPos);
static void SetIconType(IconType type);
static void SetVSync(const bool vSyncEnabled);
static void SetAutoPause(const bool autoPauseEnabled);
static void Initialize();
};

@ -42,11 +42,15 @@ All rights reserved.
#include "State_LevelComplete.h"
#include "State_Story.h"
#include "State_GameHub.h"
#include "State_Death.h"
#include "State_Dialog.h"
INCLUDE_game
#define NEW_STATE(state,class) GameState::states[state]=NEW class();
States::State GameState::currentState;
void GameState::Initialize(){
NEW_STATE(States::GAME_RUN,State_GameRun);
NEW_STATE(States::OVERWORLD_MAP,State_OverworldMap);
@ -54,12 +58,13 @@ void GameState::Initialize(){
NEW_STATE(States::LEVEL_COMPLETE,State_LevelComplete);
NEW_STATE(States::STORY,State_Story);
NEW_STATE(States::GAME_HUB,State_GameHub);
GameState::ChangeState(States::MAIN_MENU);
NEW_STATE(States::DEATH,State_Death);
NEW_STATE(States::DIALOG,State_Dialog);
}
void GameState::_ChangeState(States::State newState){
GameState*prevState=STATE;
currentState=newState;
if(!states.count(newState)){
ERR("WARNING! State not defined for state "<<newState<<"!")
}
@ -68,9 +73,10 @@ void GameState::_ChangeState(States::State newState){
STATE->OnStateChange(prevState);
}
void GameState::ChangeState(States::State newState,float fadeOutDuration){
void GameState::ChangeState(States::State newState,float fadeOutDuration,uint8_t mosaicEffect){
if(fadeOutDuration>0){
game->fadeOutDuration=game->fadeOutTotalTime=game->fadeInDuration=fadeOutDuration;
game->mosaicEffectTransition=mosaicEffect;
game->transitionState=newState;
}else{
_ChangeState(newState);
@ -84,4 +90,8 @@ void GameState::GetAnyKeyRelease(Key k){}
void GameState::GetAnyMousePress(int32_t mouseButton){}
void GameState::GetAnyMouseRelease(int32_t mouseButton){}
void GameState::DrawOverlay(AiL*game){};
void GameState::DrawOverlay(AiL*game){};
States::State GameState::GetCurrentState(){
return currentState;
}

@ -51,14 +51,18 @@ namespace States{
MAIN_MENU,
LEVEL_COMPLETE,
STORY,
DIALOG,
KEYBIND,
DEATH,
};
};
class GameState{
friend class AiL;
friend class VisualNovel;
private:
static void _ChangeState(States::State newState);
static States::State currentState;
public:
inline static GameState*STATE=nullptr;
inline static std::map<States::State,GameState*>states;
@ -72,5 +76,7 @@ public:
virtual void GetAnyKeyRelease(Key k);
virtual void GetAnyMousePress(int32_t mouseButton);
virtual void GetAnyMouseRelease(int32_t mouseButton);
static void ChangeState(States::State newState,float fadeOutDuration=0);
static void ChangeState(States::State newState,float fadeOutDuration=0,uint8_t mosaicEffect=1U);
virtual void OnLevelLoad()=0;
static States::State GetCurrentState();
};

@ -0,0 +1,89 @@
#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 "DEFINES.h"
#include "Monster.h"
#include "MonsterStrategyHelpers.h"
#include "BulletTypes.h"
INCLUDE_ANIMATION_DATA
INCLUDE_BULLET_LIST
INCLUDE_game
using A=Attribute;
void Monster::STRATEGY::GOBLIN_BOAR_RIDER(Monster&m,float fElapsedTime,std::string strategy){
//We have access to GOBLIN_BOW_MOUNTED_X and GOBLIN_BOW_ATTACK_X
if(!m.B(A::INITIALIZED_MOUNTED_MONSTER)){
m.B(A::INITIALIZED_MOUNTED_MONSTER)=true;
m.F(A::PERCEPTION_LEVEL)=ConfigFloat("Starting Perception Level");
m.internal_mounted_animState=Animate2D::AnimationState{};
m.mounted_animation=Animate2D::Animation<std::string>{};
for(bool firstAnimation=true;const std::string&animation:Config("Imposed Monster Animations").GetValues()){
m.mounted_animation.value().AddState(animation,ANIMATION_DATA.at(animation));
if(firstAnimation)m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),animation);
firstAnimation=false;
}
m.mountedSprOffset=ConfigVec("Imposed Monster Offset");
m.deathData.push_back(DeathSpawnInfo{ConfigString("Spawned Monster"),1U});
}
BOAR(m,fElapsedTime,"Boar");
m.F(A::ATTACK_COOLDOWN)+=fElapsedTime;
if(m.F(A::SHOOT_TIMER)>0.f){
m.F(A::SHOOT_TIMER)-=fElapsedTime;
if(m.F(A::SHOOT_TIMER)<=0.f){
geom2d::line pointTowardsPlayer(m.GetPos(),game->GetPlayer()->GetPos());
vf2d extendedLine=pointTowardsPlayer.upoint(1.1f);
CreateBullet(Arrow)(m.GetPos(),extendedLine,pointTowardsPlayer.vector().norm()*ConfigFloat("Arrow Spd"),"goblin_arrow.png",ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel())EndBullet;
Arrow&arrow=static_cast<Arrow&>(*BULLET_LIST.back());
arrow.PointToBestTargetPath(m.F(A::PERCEPTION_LEVEL));
m.F(A::ATTACK_COOLDOWN)=0.f;
m.F(A::PERCEPTION_LEVEL)=std::min(ConfigFloat("Maximum Perception Level"),m.F(A::PERCEPTION_LEVEL)+ConfigFloat("Perception Level Increase"));
m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),std::format("GOBLIN_BOW_MOUNTED_{}",int(m.GetFacingDirection())));
}
}else
if(m.F(A::ATTACK_COOLDOWN)>=ConfigFloat("Attack Reload Time")){
m.F(A::SHOOT_TIMER)=ConfigFloat("Attack Windup Time");
m.mounted_animation.value().ChangeState(m.internal_mounted_animState.value(),std::format("GOBLIN_BOW_ATTACK_{}",int(m.GetFacingDirection())));
}
}

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

Loading…
Cancel
Save