From 9fe9020ac92c531d164dcaa7ff2d7c75e0129198 Mon Sep 17 00:00:00 2001 From: ntr Date: Fri, 29 Nov 2019 16:08:09 +1000 Subject: [PATCH 001/206] skip faceoff serverside --- WORKLOG.md | 1 - client/src/events.jsx | 5 ----- server/src/instance.rs | 3 +++ 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/WORKLOG.md b/WORKLOG.md index d254b298..7b8101ea 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -3,7 +3,6 @@ _ntr_ * can't reset password without knowing password =\ -* skip faceoff on server side * change cooldowns to delay & recharge - delay is cooldown before skill can first be used - recharge is cooldown after using skill diff --git a/client/src/events.jsx b/client/src/events.jsx index f1b7e158..d6ca701a 100644 --- a/client/src/events.jsx +++ b/client/src/events.jsx @@ -220,11 +220,6 @@ function registerEvents(store) { const player = v.players.find(p => p.id === account.id); store.dispatch(actions.setPlayer(player)); - const skip = v.time_control === 'Practice' && v.phase === 'Lobby'; - if (skip) { - ws.sendInstanceReady(v.id); - } - if (v.phase === 'Finished') { ws.sendAccountInstances(); } diff --git a/server/src/instance.rs b/server/src/instance.rs index 65c17b65..912b89c6 100644 --- a/server/src/instance.rs +++ b/server/src/instance.rs @@ -706,7 +706,10 @@ pub fn instance_practice(tx: &mut Transaction, account: &Account) -> Result Date: Fri, 29 Nov 2019 16:55:52 +1000 Subject: [PATCH 002/206] wl --- WORKLOG.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/WORKLOG.md b/WORKLOG.md index 7b8101ea..cdd37a00 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -29,14 +29,8 @@ Hexagon Set - Increase intensity for each visit _mashy_ -* floating combat text combat (start opposite hp and float towards it) to speed up animations * represent construct colours during game phase (try %bar or dots) -* buy from preview if you have the required bases in vbox / inventory - - a "buy" becomes available under the current info / preview section - - clicking the buy automatically purchases / combine items - - could also be used to upgrade already equipped skills / specs - - e.g. an equipped white power spec could be upgraded by clicking under preview - - if this was added we could reduce inventory size to 3 and rearrange vbox (see mockup img) +* reduce combos from 3->2 and rebalance _external_ * Graphics @@ -79,6 +73,12 @@ _tba_ * Graphical status effects instead of text +* buy from preview if you have the required bases in vbox / inventory + - a "buy" becomes available under the current info / preview section + - clicking the buy automatically purchases / combine items + - could also be used to upgrade already equipped skills / specs + - e.g. an equipped white power spec could be upgraded by clicking under preview + * theme toasts * rework vecs into sets From 44758874104dc3fe85badde6f3cbf444dbab75f0 Mon Sep 17 00:00:00 2001 From: ntr Date: Mon, 2 Dec 2019 16:24:31 +1000 Subject: [PATCH 003/206] core init --- COMBOS.md | 142 --- ECONOMY.md | 75 -- NODES.md | 49 - ROADMAP.md | 63 -- SPECS.md | 224 ----- WORKLOG.md | 43 +- core/.cargo/config | 3 + core/.gitignore | 4 + core/Cargo.toml | 17 + core/src/construct.rs | 1009 +++++++++++++++++++ core/src/effect.rs | 209 ++++ core/src/game.rs | 1315 +++++++++++++++++++++++++ core/src/instance.rs | 631 ++++++++++++ core/src/item.rs | 1581 +++++++++++++++++++++++++++++ core/src/lib.rs | 23 + core/src/mob.rs | 30 + core/src/names.rs | 138 +++ core/src/player.rs | 442 +++++++++ core/src/skill.rs | 2188 +++++++++++++++++++++++++++++++++++++++++ core/src/spec.rs | 734 ++++++++++++++ core/src/util.rs | 40 + core/src/vbox.rs | 305 ++++++ server/src/game.rs | 98 +- server/src/skill.rs | 56 +- 24 files changed, 8716 insertions(+), 703 deletions(-) delete mode 100644 COMBOS.md delete mode 100644 ECONOMY.md delete mode 100644 NODES.md delete mode 100644 ROADMAP.md delete mode 100644 SPECS.md create mode 100755 core/.cargo/config create mode 100644 core/.gitignore create mode 100644 core/Cargo.toml create mode 100644 core/src/construct.rs create mode 100644 core/src/effect.rs create mode 100644 core/src/game.rs create mode 100644 core/src/instance.rs create mode 100644 core/src/item.rs create mode 100644 core/src/lib.rs create mode 100644 core/src/mob.rs create mode 100644 core/src/names.rs create mode 100644 core/src/player.rs create mode 100644 core/src/skill.rs create mode 100644 core/src/spec.rs create mode 100644 core/src/util.rs create mode 100644 core/src/vbox.rs diff --git a/COMBOS.md b/COMBOS.md deleted file mode 100644 index cf563a8c..00000000 --- a/COMBOS.md +++ /dev/null @@ -1,142 +0,0 @@ -# item_info -> - -combos [strike, [R R Attack]] -specs [spec [bonus amount, [r g b]] - -# Playthrough - -constructs join game - stats randomised - -initial stash drops - 6 skills - 6 colours - 6 specs - -play first round - basically duke it out - -# Colours # - -### Red ### -Real world concepts -Aggressive -Apply Buffs -Fast & Chaotic - -### Green ### -Healing Specialisation -Defensive -Purge buffs & debuffs - -### Blue ### -Fantasy concepts (magical) -Aggressive & Defensive -Apply Debuffs -Slow & Reliable - -# Classes # - -Class names to be changed -==================== -Pure Red `Nature` -Pure Green `Non-Violence` -Pure Blue `Destruction` -Hybrid Red / Blue `Chaos` -Hybrid Red / Green `Purity` -Hybrid Blue / Green `Technology` - - -Skills -========== - -Basic Type -------------------------------------------------------------------------- -Attack `Basic offensive skill - deal damage` -Buff `Base ally targetted skill - increase ally speed` -Stun `Base enemy disable - disable enemy for 2 rounds` -Block `Base self targetted defensive - reduced damage taken for 2 rounds` -Debuff `Base enemy debuff - reduce enemy speed` - -# Attack Base # - -RR - Strike -GG - Heal -BB - Blast -RG - Purify -GB - Decay -RB - Blast - -# Stun Base # - -RR - Strangle -GG - Break -BB - Ruin -RG - Banish -GB - Silence -RB - Hex - -# Buff Base # - -RR - Empower -GR - Triage -BB - Absorb -RG - Sustain -GB - Amplify -RB - Haste - -# Debuff Base # - -RR - Restrict -GG - Purge -BB - Curse -RG - Slow -GB - Siphon -RB - Invert - -# Block Base # - -RR - Counter -GG - Reflect -BB - Electrify -RG - Intercept -GB - Life `rename?` -RB - Recharge - - -## Advanced combos ## - -Two ways of upgrading - #1 -> combine more of the same for a stronger version of the same skill / spec (T2 / T3 Combos) - #2 -> combine skill with two matching colour specs to change the way the skill works (Spec / Skill hybrid) - -### T2 / T3 Combos ### - -All current specs / items can be further combo'd into T2 and T3 versions - -# 3 of same base => 1 upgraded tier # -`3 x T1 Red Damage Spec => T2 Red Damage Spec` -`3 x T2 Red Damage Spec => T3 Red Damage Spec` -`3 x T1 Strike => T2 Strike` -`3 x T2 Strike => T3 Strike` - -Upgraded skills will have a combination of higher damage / longer duration / reduced cooldown -Upgraded skills use the same speed formula as previously - -### Spec / Skill hybrid specs ### - -# Strike # -2 x Red Damage + Strike => Strike damage bonus (crit?) -2 x Red Speed + Strike => Strike reduces enemy speed -2 x Red Life + Strike => Strike reduces enemy healing (% reduction) - -# Heal # -2 x Green Damage + Heal => Heal target for additional 20% of caster's maximum life -2 x Green Speed + Heal => Heal target gets bonus speed -2 x Green Life + Heal => Heal increases target's max hp for 2 turns - -etc etc - -30 skills * 3 specs => 90 spec / skill hybrid specs -> might be overcomplicated - - diff --git a/ECONOMY.md b/ECONOMY.md deleted file mode 100644 index e40a894c..00000000 --- a/ECONOMY.md +++ /dev/null @@ -1,75 +0,0 @@ -# Everything costs money (gold?) - -Items - Base colours / skills / specs and associated upgrades - -### Sources of money -- Start with money and gain income after each battle -- Higher income from winning - -- Selling items in inventory or equipped on character refunds - - Selling from inventory full refund - - Selling from charcter 50% refund - -### Uses for money - -- Buying items -- Rerolling vbox - -### Base Costs - -Base colours have a base 1 cost -Base skills have a base 2 cost -Base specs have a base 3 cost - -### Actual Costs - -- Costs increase as more of an item is used on constructs in the game -- The cost increases by the base cost for every 6 allocations of base item -- Allocation is based on all constructs in the game - -### Example ### - -Round #1 - -All costs are base costs -# Player #1 and Player #2 (They both bought the same things) -Construct #1 Strike (Attack + RR), (2 + 1 + 1) = (4) cost -Construct #1 Empower (Buff + RR), (2 + 1 + 1) = (4) cost -Construct #3 Attack, 2 cost - -Total cost - 10 - -Round #2 - -Items used on constructs include: - -Red x 8 -Attack x 4 -Buff x 2 - -The costs of red for round #2 are now (1 + 1) = 2 - -If they were to buy the same skill setup it would be as follows: - -# Player #1 and Player #2 (They both bought the same things) -Construct #1 Strike (Attack + RR), (2 + 2 + 2) = (6) cost -Construct #1 Empower (Buff + RR), (2 + 2 + 2) = (6) cost -Construct #3 Attack, 2 cost - -Total cost - 14 - -### Philosophy of increasing item costs - -- Two games will never feel exactly the same -- Costs change over rounds to diversify skill choice and gameplay -- As optimal builds emerge the paths to reach them will change every game -- Rewarded for going (hipster) builds nobody else is trying -- Some reward for hoarding items in your inventory while they cheaper (hodl red) - -### Income values - -Could try with 9 base income -Income increases by 3 each round and winning bonus of 6 - - - diff --git a/NODES.md b/NODES.md deleted file mode 100644 index 52dd0e80..00000000 --- a/NODES.md +++ /dev/null @@ -1,49 +0,0 @@ - -# Stat Multipliers # - -### Defenses ### - -Rare `Increased GreenLife` - -Common `Increased Evasion rating` -Common `Increased Blue Life rating` -Common `Increased RedLife rating` -Common `Increased Healing done` -Common `Increased Healing received` -Common `Increased Blue Damage` -Common `Increased Red Damage` - -Uncommon `Reduced hp loss penalty to evade chance` -Uncommon `Increased base evasion chance per X evasion rating` -Uncommon `Increased % mitigation from red_life` -Uncommon `Increased % mitigation from spell shield` -Uncommon `Increased damage over time` - -Rare `gain empower on KO` -Rare `cannot be restrictd` -Rare `cannot be silenced` -Rare `cannot be intercepted` - -Rare `25% stun for attack` -Rare `25% hex for blast` - -Rare `cooldown reduction` -Rare `effect duration` - -Rare `increased phys damage, 0 spell damage` -Rare `increased spell damage, 0 phys damage` - -Rare `increased phys damage, silenced` -Rare `increased spell damage, restrictd` - -Rare `increased speed, increased durations` -Rare `increased speed, increased cooldowns` - -# Nature - Technology - Nonviolence - Destruction - Purity - Chaos # - - - Increased power - - Increased speed - - Increased stat - - ??? Related Notables - -# ??? Constructs need to have a minimum of X of the construct stat to learn a skill # diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index 7f9ff085..00000000 --- a/ROADMAP.md +++ /dev/null @@ -1,63 +0,0 @@ - -### Road Map ### - -# NOW Phase 1 (Dev -> Alpha) - -Form company structure - Brainstorm Names? - Finalise documents - -Game - Server T2 / T3 / Custom Specs - Any other outstanding "major" features ??? - -# Phase 2 (Alpha -> Beta) - -Friends / Word of mouth testing - Server balance adjustments based on data - Client improvements based on feedback - -Combat animations - -Make in game shop - Payment processors / CC etc - Handler for game purchases - MTX - Construct Avatars - MTX - Skill anims - -Setup company bank accounts - Accounting system - Xero etc - -# Phase 3 (Beta -> Release) - -Player Events e.g. chatwheel -Matchmaking + ELO / Leaderboard -Game skill private fields - -Refine artwork, icons, scaling etc -Music - -Marketing materials - Videos - Twitch - Advertisments? - Information - - - -# china shit -You need to read the details more carefully. Playsaurus messed up: - -1. They launched in China without registering a trademark - -2. A competitor registered the trademark after 3 months of their launch - -3. They continued to sell for 4 years under a name trademarked by another company, making $73,000+ yearly from that one country - -Now, they complain about it on Reddit, even though China is a 'First to File' company. - -The fault lies entirely with Playsaurus, nothing illegal occurred here. - -https://www.trademarknow.com/blog/first-to-file-versus-first... - -This situation could have occurred in many other countries - the difference is probably that the competitor is content to just sell in China, under a Chinese name, whereas products sold anywhere else would need to use the English name. \ No newline at end of file diff --git a/SPECS.md b/SPECS.md deleted file mode 100644 index e1c4cc64..00000000 --- a/SPECS.md +++ /dev/null @@ -1,224 +0,0 @@ -### Specs ### - -Numbers are placeholder -`Specs get a bonus dependent on the total of Red / Green / Blue in player skills & specs` - -# Example to meet 5 red gem bonus from skills only -In your player Construct #1 has `Strike`, Construct #2 has `Slay` and `Heal`, Construct #3 has `Restrict` -- RR skill `Strike` contributes 2 red gems to the total red gems (2 total) -- RG skill `Slay` contributes 1 red gem to the total red gems (3 total) -- GG skill `Heal` contirubtes 0 red gems to the total red gems (3 total) -- RR skill `Restrict` contirubtes 2 red gems to the total red gems (5 total) - -# Advanced specs also require a minimum number of Red / Green / Blue gems on the construct to take effect - - Tier 1 Basic specs (Damage / Health / Defense) will have no requirements - - Advanced specs will require a certain threshold of red / green / blue gems to be enabled - - Provided spec requirements are met, all specs will add gems to the construct - -# Starting from scratch with a vbox - -### Round 1 - - - Buy 4 reds (items) - - Buy two 'Attack' Skills & 1 Stun skill (items) - - Buy 1 Basic Damage Spec (item) - - Combine 2 Red + 'Attack' -> Strike - Combine 2 Red + 'Basic Damage Spec' -> Red Damage - - Construct #1 -> Give Strike & Red Damage Spec -> Strike + 1 x Red Damage Spec - Construct #2 -> Give Attack -> Attack - Construct #3 -> Give Stun -> Stun - - Player Total (4 Red + 2 Basic gems) - -### Round 2 - - - Buy 2 reds & 2 green & 2 blue (all available colour items) - - Buy 2 Basic Damage Spec (item) - - - Construct #2 Unequip Attack - - Combine 2 Green + 'Attack' -> Heal - - - Construct #3 Unequip Stun - - Combine 2 Blue + 'Stun' -> Ruin - - - Combine 2 Red + 'Basic Damage Spec' -> Red Damage - - Construct #1 -> Give Red Damage items -> Strike + 2 x Red Damage Spec (6R) - Construct #2 -> Give Heal item -> Heal (2G) - Construct #3 -> Give Ruin item -> Ruin (2B) - -## Round 3 - - - Buy 4 reds - - Buy 1 Attack, 1 Stun, 1 Block (item) - - Buy 2 Basic Damage Spec (item) - - - Combine 2 Red + 'Stun' -> Strangle - - Combine 2 Red + 'Block' -> Counter - - Construct #1 -> Give 'Stun' & 'Strangle' -> Strike, Stun, Strangle + 2 x Red Damage Spec (10R) - Construct #2 -> 'No change' -> Heal (2G) - Construct #3 -> Give Attack item & 2 Basic Damage Spec -> Attack + Ruin + 2 x Basic Damage Spec (2B) - -## Round 4 - - - Buy 4 reds (getting lucky with reds!) - - Buy 1 Attack, 1 Buff - - - Combine 2 Red + 'Attack' -> Strike - - Combine 2 Red + 'Buff' -> Empower - - - Construct #1 Unequip 2 x Red Damage spec, Equip Empower -> Strike, Stun, Strangle, Empower (8R) - - Combine 'Strike' + 2 x Red Damage spec -> 'Increased Strike Damage spec' - - ### Note 'Increased Strike Damage spec' requires 8R on the construct - - Construct #1 Equip Increased Strike Damage spec -> Strike, Stun, Strangle, Empower + Increased Strike Damage Spec (14R) - Construct #2 -> 'No change' -> Heal - Construct #3 -> 'No change' -> Attack + Ruin + 2 x Basic Damage Spec - -## Round 5 - - We already lost cause we went all in on 1 red construct like a noob - -### Generic Specs - -# Basic % GreenLife -`Base` -> 5% inc hp -`Player Bonus` -> 3 basic gems -> +5% // 6 basic gems -> +10% // 12 basic gems -> +15% -Maximum 35% inc hp - -# Basic Speed -`Base` -> 5% inc speed -`Player Bonus` -> 3 basic gems -> +10% // 6 basic gems -> +15% // 12 basic gems -> +20% -Maximum 50% inc speed - -# Basic Class Spec -`Base` -> +2 red, +2 green +2 blue gems on construct -# Basic Duration - -### Increased Damage Combos ### - -Generate by combining `Generic Spec (Basic Damage)` with respective RGB - -# Red Damage (Dmg + RR) -Add 2 `red gems` -`Base` -> 10% inc red dmg -`Player Bonus` 5 red gems -> +10% // 10 red gems -> +15% // 20 red gems -> +25% -Maximum +60% red damage - -# Blue Damage (Dmg + BB) # -Add 2 `blue gems` -`Base` -> 10% inc blue dmg -`Player Bonus` 5 blue gems -> +10% // 10 blue gems -> +15% // 20 blue gems -> +25% -Maximum +60% blue damage - -# Healing (Dmg + GG) # -Add 2 `green gems` -`Base` -> 10% inc healing -`Player Bonus` 5 green gems -> +10% // 10 green gems -> +15% // 20 green gems -> +25% -Maximum +60% inc healing - -# Red damage and healing (Dmg + RG) -Add 1 red 1 green gem -`Base` -> 5% inc red damage and 5% inc healing -`Player Bonus` (2R + 2G gems) -> +5% + 5% // (5R + 5G gems) -> +10% + 10% % // (10R + 10G) gems -> +15% + 15% -Maximum +35% inc red damage and 35% inc healing - -# Red and blue damage (Dmg + RB) -Add 1 red and 1 blue gem -`Base` -> 5% inc red damage and 5% inc healing -`Player Bonus` (2 red + 2 green gems) -> +5% + 5% // (5 red + 5 green gems) -> +10% + 10% % // 20 green gems -> +15% + 15% -Maximum +35% inc damage and 35% inc healing - -# Blue damage and healing (Dmg + BG) -Add 1 blue and 1 green gem -`Base` -> 5% inc blue damage and 5% inc healing -`Player Bonus` (2B + 2G gems) -> +5% + 5% // (5B + 5G gems) -> +10% + 10% % // (10B + 10G) gems -> +15% + 15% -Maximum +35% inc blue damage and 35% inc healing - -### Increased GreenLife Combos ### - -Generate by combining `Generic Spec (Basic GreenLife)` with respective RGB - -# Increased % Red Life (Basic %HP + 2R) -Add 2 `red gems` -`Base` -> 10% inc red shield -`Player Bonus` 5 red gems -> +10% // 10 red gems -> +15% // 20 red gems -> +20% -Maximum +55% inc red shield - -# Increased % Red Life and GreenLife (Basic %HP + 1R1G) -Add 1 red 1 green gem -`Base` -> 5% inc red shield and 5% inc hp -`Player Bonus` (2R + 2G gems) -> +5% + 5% // (5R + 5G gems) -> +10% + 10% % // (10R + 10G) gems -> +15% + 15% -Maximum +35% inc red shield and 35% inc hp - -# Increased % Blue Life (Basic %HP + 2B) -Add 2 `blue gems` -`Base` -> 10% inc red shield -`Player Bonus` 5 blue gems -> +10% // 10 blue gems -> +15% // 20 blue gems -> +20% -Maximum +55% inc blue shield - -# Increased % Blue Life and GreenLife (Basic %HP + 1B1G) -Add `1 blue and 1 green gems` -`Base` -> 5% inc red shield and 5% inc hp -`Player Bonus` (2B + 2G gems) -> +5% + 5% // (5B + 5G gems) -> +10% + 10% % // (10B + 10G) gems -> +15% + 15% -Maximum +35% inc blue shield and 35% inc hp - -# Increased % GreenLife (Basic %HP + 2G) -Add `2 green gems` -`Base` -> 10% inc hp -`Player Bonus` 5 green gems -> +10% // 10 green gems -> +15% // 20 green gems -> +20% -Maximum +55% inc hp - -# Increased % Blue and Red Life (Basic %HP + 1B1R) -Add `1 blue and 1 red gem` -`Base` -> 5% inc red shield and 5% inc hp -`Player Bonus` (2B + 2R gems) -> +5% + 5% // (5B + 5R gems) -> +10% + 10% % // (10B + 10R) gems -> +15% + 15% -Maximum +35% inc blue shield and 35% inc red shield - -## Upgraded Attack Spec Combos - -# Increased Strike Damage (Combine Strike + Red Damage Spec x 2) -Construct Requires `8 red gems` -Adds `6 red gems` -`Base` -> 15% increased strike damage -`Player Bonus` 15 red gems -> +15% // 20 red gems -> +20% // 30 red gems -> +30% -Maximum 80% increased strike damage - -# Improved Heal (Combine Heal + Healing Spec x 2) -Construct Requires `8 green gems` -`Base` -> 15% increased heal healing -`Player Bonus` 15 green gems -> +15% // 20 green gems -> +20% // 30 green gems -> +30% -Maximum 80% increased heal healing - -# Increased Blast Damage (Combine Blast + Blue Spec x 2) -Construct Requires `8 blue gems` -`Base` -> 15% increased blast damage -`Player Bonus` 15 blue gems -> +15% // 20 blue gems -> +20% // 30 blue gems -> +30% -Maximum 80% increased blast damage - -# Increased Slay Damage (Combine Slay + Red Damage Spec + Healing Spec) -Construct Requires `4 red 4 green gems` -`Base` -> 15% increased slay damage -`Player Bonus` (8R + 8G) gems -> +15% // (10R + 10G) gems -> +20% // (15R + 15G) gems -> +30% -Maximum 80% increased slay damage - -# Increased Banish Damage (Combine Slay + Red Damage Spec + Blue Damage Spec) -Construct Requires `4 red 4 blue gems` -`Base` -> 15% increased slay damage -`Player Bonus` (8R + 8B) gems -> +15% // (10R + 10B) gems -> +20% // (15R + 15B) gems -> +30% -Maximum 80% increased banish damage - -## Other Combos - -# Increased % Red Speed (Basic Speed + 2R) -Add 2 red gems -`Base` -> 15% inc red speed -`Player Bonus` 5 red gems -> +15% // 10 red gems -> +20% // 20 red gems -> +25% -Maximum 80% inc red speed - -# Nature Affinity (Basic Class spec + 2R) -`Base` -> Add 10 red gems diff --git a/WORKLOG.md b/WORKLOG.md index cdd37a00..0dfdeccb 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -2,31 +2,19 @@ ## NOW _ntr_ -* can't reset password without knowing password =\ +* effects rework + +Siphon = [ + Apply(Siphon(2T), target) + Apply(Siphoning(2T), source) + Skill(SiphonTick, source, target) + DamageBlue(50% BluePower, target), +] + * change cooldowns to delay & recharge - delay is cooldown before skill can first be used - recharge is cooldown after using skill - every x speed reduces delay of skills -* audio - * animation effects - * vbox combine / buy / equip etc - * background music -* effects rework - -Siphon = -[ - DamageBlue(50%), - Apply( - Siphon(2T) - - Siphoning(2T) - ), -] - -Hexagon Set -- Pick Colour -- Random Walk -- Draw hex -- Increase intensity for each visit _mashy_ * represent construct colours during game phase (try %bar or dots) @@ -50,6 +38,19 @@ _tba_ ## SOON +* can't reset password without knowing password =\ + +* audio + * animation effects + * vbox combine / buy / equip etc + * background music + +Hexagon Set +- Pick Colour +- Random Walk +- Draw hex +- Increase intensity for each visit + * combo rework - reduce number of items for creating t2/t3 items from 3 -> 2 - add lost complexity by adding skill spec items diff --git a/core/.cargo/config b/core/.cargo/config new file mode 100755 index 00000000..3c3311fc --- /dev/null +++ b/core/.cargo/config @@ -0,0 +1,3 @@ +[target.x86_64-pc-windows-msvc.gnu] +rustc-link-search = ["C:\\Program Files\\PostgreSQL\\pg96\\lib"] + diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 00000000..9e853f6c --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,4 @@ +target/ +Cargo.lock +log/ +.env \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 00000000..66e37389 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mnml-core" +version = "1.10.0" +authors = ["ntr ", "mashy "] + +[dependencies] +serde = "1" +serde_derive = "1" + +rand = "0.6" +uuid = { version = "0.5", features = ["serde", "v4"] } +chrono = { version = "0.4", features = ["serde"] } +bcrypt = "0.2" + +failure = "0.1" + +log = "0.4" diff --git a/core/src/construct.rs b/core/src/construct.rs new file mode 100644 index 00000000..725387b6 --- /dev/null +++ b/core/src/construct.rs @@ -0,0 +1,1009 @@ +use uuid::Uuid; +use rand::prelude::*; + +use failure::Error; +use failure::err_msg; + +use skill::{Skill, Cast, Immunity, Disable, Event}; +use effect::{Cooldown, Effect, Colour}; +use spec::{Spec}; +use item::{Item}; + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct Colours { + pub red: u8, + pub green: u8, + pub blue: u8, +} + +impl Colours { + pub fn new() -> Colours { + Colours { red: 0, green: 0, blue: 0 } + } + + pub fn from_construct(construct: &Construct) -> Colours { + let mut count = Colours::new(); + + for spec in construct.specs.iter() { + let v = Item::from(*spec); + v.colours(&mut count); + } + + for cs in construct.skills.iter() { + let v = Item::from(cs.skill); + v.colours(&mut count); + } + + count + } +} + + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub struct ConstructSkill { + pub skill: Skill, + pub cd: Cooldown, + // used for Uon client + pub disabled: bool, +} + +impl ConstructSkill { + pub fn new(skill: Skill) -> ConstructSkill { + ConstructSkill { + skill, + cd: skill.base_cd(), + disabled: false, + } + } +} + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum EffectMeta { + Skill(Skill), + TickAmount(u64), + AddedDamage(u64), + LinkTarget(Uuid), + Multiplier(u64), +} + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub struct ConstructEffect { + pub effect: Effect, + pub duration: u8, + pub meta: Option, + pub tick: Option, +} + +impl ConstructEffect { + pub fn new(effect: Effect, duration: u8) -> ConstructEffect { + ConstructEffect { effect, duration, meta: None, tick: None } + } + + pub fn set_tick(mut self, tick: Cast) -> ConstructEffect { + self.tick = Some(tick); + self + } + + pub fn set_meta(mut self, meta: EffectMeta) -> ConstructEffect { + self.meta = Some(meta); + self + } + + pub fn get_duration(&self) -> u8 { + self.duration + } + + pub fn get_multiplier(&self) -> u64 { + match self.meta { + Some(EffectMeta::Multiplier(s)) => s, + _ => 0 + } + } + + pub fn get_skill(&self) -> Option { + match self.meta { + Some(EffectMeta::Skill(s)) => Some(s), + _ => None, + } + + } +} + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum Stat { + Str, + Agi, + Int, + GreenLife, + Speed, + RedPower, + BluePower, + GreenPower, + RedDamageTaken, + BlueDamageTaken, + GreenDamageTaken, + RedLife, + BlueLife, + Evasion, +} + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub struct ConstructStat { + base: u64, + value: u64, + max: u64, + pub stat: Stat, +} + +impl ConstructStat { + // pub fn set(&mut self, v: u64, specs: &Vec) -> &mut ConstructStat { + // self.base = v; + // self.recalculate(specs) + // } + + pub fn recalculate(&mut self, specs: &Vec, player_colours: &Colours) -> &mut ConstructStat { + let specs = specs + .iter() + .filter(|s| s.affects().contains(&self.stat)) + .map(|s| *s) + .collect::>(); + + // applied with fold because it can be zeroed or multiplied + // but still needs access to the base amount + let value = specs.iter().fold(self.base, |acc, s| s.apply(acc, self.base, player_colours)); + self.value = value; + self.max = value; + + self + } + + pub fn reduce(&mut self, amt: u64) -> &mut ConstructStat { + self.value = self.value.saturating_sub(amt); + self + } + + pub fn increase(&mut self, amt: u64) -> &mut ConstructStat { + self.value = *[ + self.value.saturating_add(amt), + self.max + ].iter().min().unwrap(); + + self + } + + pub fn force(&mut self, v: u64) -> &mut ConstructStat { + self.base = v; + self.value = v; + self.max = v; + + self + } +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct ConstructSkeleton { + pub id: Uuid, + pub account: Uuid, + pub img: Uuid, + pub name: String, +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct Construct { + pub id: Uuid, + pub account: Uuid, + pub img: Uuid, + pub red_power: ConstructStat, + pub red_life: ConstructStat, + pub blue_life: ConstructStat, + pub blue_power: ConstructStat, + pub green_power: ConstructStat, + pub speed: ConstructStat, + pub green_life: ConstructStat, + // pub evasion: ConstructStat, + pub skills: Vec, + pub effects: Vec, + pub specs: Vec, + pub colours: Colours, + pub name: String, +} + +impl Construct { + pub fn new() -> Construct { + let id = Uuid::new_v4(); + return Construct { + id, + account: id, + img: Uuid::new_v4(), + red_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::RedPower }, + red_life: ConstructStat { base: 125, value: 125, max: 125, stat: Stat::RedLife }, + blue_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::BluePower }, + blue_life: ConstructStat { base: 125, value: 125, max: 125, stat: Stat::BlueLife }, + green_power: ConstructStat { base: 300, value: 300, max: 300, stat: Stat::GreenPower }, + green_life: ConstructStat { base: 800, value: 800, max: 800, stat: Stat::GreenLife }, + speed: ConstructStat { base: 100, value: 100, max: 100, stat: Stat::Speed }, + // evasion: ConstructStat { base: 0, value: 0, max: 0, stat: Stat::Evasion }, + skills: vec![], + effects: vec![], + specs: vec![], + colours: Colours::new(), + name: String::new(), + }; + } + + pub fn from_skeleton(skeleton: &ConstructSkeleton) -> Construct { + return Construct { + id: skeleton.id, + account: skeleton.account, + img: skeleton.img, + name: skeleton.name.clone(), + + .. Construct::new() + }; + } + + pub fn to_skeleton(&self) -> ConstructSkeleton { + ConstructSkeleton { + id: self.id, + account: self.account, + img: self.img, + name: self.name.clone(), + } + } + + + pub fn named(mut self, name: &String) -> Construct { + self.name = name.clone(); + self + } + + pub fn set_account(mut self, account: Uuid) -> Construct { + self.account = account; + self + } + + pub fn new_img(mut self) -> Construct { + self.img = Uuid::new_v4(); + self + } + + pub fn new_name(self, name: String) -> Result { + if name.len() > 20 { + return Err(err_msg("20 character name maximum")); + } + Ok(self.named(&name)) + } + + pub fn learn(mut self, s: Skill) -> Construct { + self.skills.push(ConstructSkill::new(s)); + self.colours = Colours::from_construct(&self); + self + } + + pub fn learn_mut(&mut self, s: Skill) -> &mut Construct { + self.skills.push(ConstructSkill::new(s)); + self.calculate_colours() + } + + pub fn forget(&mut self, skill: Skill) -> Result<&mut Construct, Error> { + match self.skills.iter().position(|s| s.skill == skill) { + Some(i) => { + self.skills.remove(i); + return Ok(self.calculate_colours()); + }, + None => Err(format_err!("{:?} does not know {:?}", self.name, skill)), + } + } + + pub fn spec_add(&mut self, spec: Spec) -> Result<&mut Construct, Error> { + if self.specs.len() >= 3 { + return Err(err_msg("maximum specs equipped")); + } + + self.specs.push(spec); + return Ok(self.calculate_colours()); + } + + pub fn spec_remove(&mut self, spec: Spec) -> Result<&mut Construct, Error> { + match self.specs.iter().position(|s| *s == spec) { + Some(p) => self.specs.remove(p), + None => return Err(err_msg("spec not found")), + }; + + Ok(self.calculate_colours()) + } + + fn calculate_colours(&mut self) -> &mut Construct { + self.colours = Colours::from_construct(&self); + self + } + + pub fn apply_modifiers(&mut self, player_colours: &Colours) -> &mut Construct { + self.red_power.recalculate(&self.specs, player_colours); + self.red_life.recalculate(&self.specs, player_colours); + self.blue_power.recalculate(&self.specs, player_colours); + self.blue_life.recalculate(&self.specs, player_colours); + // self.evasion.recalculate(&self.specs, &self.colours, player_colours); + self.speed.recalculate(&self.specs, player_colours); + self.green_power.recalculate(&self.specs, player_colours); + self.green_life.recalculate(&self.specs, player_colours); + + self + } + + pub fn is_ko(&self) -> bool { + self.green_life.value == 0 + } + + pub fn force_ko(&mut self) -> &mut Construct { + self.green_life.value = 0; + self + } + + pub fn immune(&self, skill: Skill) -> Option { + // also checked in resolve stage so shouldn't happen really + if self.is_ko() { + return Some(vec![Effect::Ko]); + } + + let immunities = self.effects.iter() + .filter(|e| e.effect.immune(skill)) + .map(|e| e.effect) + .collect::>(); + + if immunities.len() > 0 { + return Some(immunities); + } + + None + } + + pub fn disabled(&self, skill: Skill) -> Option { + if self.is_ko() && !skill.ko_castable() { + return Some(vec![Effect::Ko]); + } + + let disables = self.effects.iter() + .filter(|e| e.effect.disables_skill(skill)) + .map(|e| e.effect) + .collect::>(); + + if disables.len() > 0 { + return Some(disables); + } + + None + } + + pub fn is_stunned(&self) -> bool { + self.available_skills().len() == 0 + } + + pub fn affected(&self, effect: Effect) -> bool { + self.effects.iter().any(|s| s.effect == effect) + } + + pub fn available_skills(&self) -> Vec<&ConstructSkill> { + self.skills.iter() + .filter(|s| s.cd.is_none()) + .filter(|s| self.disabled(s.skill).is_none()) + .collect() + } + + pub fn mob_select_skill(&self) -> Option { + let available = self.available_skills(); + + if available.len() == 0 { + return None; + } + + let mut rng = thread_rng(); + + let i = match available.len() { + 1 => 0, + _ => rng.gen_range(0, available.len()), + }; + + return Some(available[i].skill); + + // let highest_cd = available.iter() + // .filter(|s| s.skill.base_cd().is_some()) + // .max_by_key(|s| s.skill.base_cd().unwrap()); + + // return match highest_cd { + // Some(s) => Some(s.skill), + // None => Some(available[0].skill), + // }; + } + + pub fn knows(&self, skill: Skill) -> bool { + self.skills.iter().any(|s| s.skill == skill) + } + + pub fn skill_on_cd(&self, skill: Skill) -> Option<&ConstructSkill> { + self.skills.iter().find(|s| s.skill == skill && s.cd.is_some()) + } + + pub fn skill_set_cd(&mut self, skill: Skill) -> &mut Construct { + let i = self.skills.iter().position(|s| s.skill == skill).unwrap(); + self.skills.remove(i); + self.skills.push(ConstructSkill::new(skill)); + + self + } + + pub fn reduce_cooldowns(&mut self) -> &mut Construct { + for skill in self.skills.iter_mut() { + // if used cooldown + if skill.skill.base_cd().is_some() { + // what is the current cd + if let Some(current_cd) = skill.cd { + + // if it's 1 set it to none + if current_cd == 1 { + skill.cd = None; + continue; + } + + // otherwise decrement it + skill.cd = Some(current_cd.saturating_sub(1)); + } + + } + } + + self + } + + pub fn reduce_effect_durations(&mut self) -> &mut Construct { + self.effects = self.effects.clone().into_iter().filter_map(|mut effect| { + effect.duration = effect.duration.saturating_sub(1); + + if effect.duration == 0 { + return None; + } + + // info!("reduced effect {:?}", effect); + return Some(effect); + }).collect::>(); + + self + } + + // Stats + pub fn red_power(&self) -> u64 { + let red_power_mods = self.effects.iter() + .filter(|e| e.effect.modifications().contains(&Stat::RedPower)) + .map(|e| (e.effect, e.meta)) + .collect::)>>(); + + let modified_red_power = red_power_mods.iter() + .fold(self.red_power.value, |acc, fx| fx.0.apply(acc, fx.1)); + return modified_red_power; + } + + pub fn blue_power(&self) -> u64 { + let blue_power_mods = self.effects.iter() + .filter(|e| e.effect.modifications().contains(&Stat::BluePower)) + .map(|e| (e.effect, e.meta)) + .collect::)>>(); + + let modified_blue_power = blue_power_mods.iter() + .fold(self.blue_power.value, |acc, fx| fx.0.apply(acc, fx.1)); + return modified_blue_power; + } + + pub fn green_power(&self) -> u64 { + let green_power_mods = self.effects.iter() + .filter(|e| e.effect.modifications().contains(&Stat::GreenPower)) + .map(|e| (e.effect, e.meta)) + .collect::)>>(); + + let modified_green_power = green_power_mods.iter() + .fold(self.green_power.value, |acc, fx| fx.0.apply(acc, fx.1)); + return modified_green_power; + } + + pub fn skill_speed(&self, s: Skill) -> u64 { + self.speed().saturating_mul(s.speed() as u64) + } + + // todo complete with specs + pub fn skill_is_aoe(&self, s: Skill) -> bool { + s.aoe() + } + + pub fn speed(&self) -> u64 { + let speed_mods = self.effects.iter() + .filter(|e| e.effect.modifications().contains(&Stat::Speed)) + .map(|e| (e.effect, e.meta)) + .collect::)>>(); + + let modified_speed = speed_mods.iter() + .fold(self.speed.value, |acc, fx| fx.0.apply(acc, fx.1)); + return modified_speed; + } + + pub fn red_life(&self) -> u64 { + self.red_life.value + } + + pub fn blue_life(&self) -> u64 { + self.blue_life.value + } + + pub fn green_life(&self) -> u64 { + self.green_life.value + } + + fn reduce_green_life(&mut self, amount: u64) { + self.green_life.reduce(amount); + if self.affected(Effect::Sustain) && self.green_life() == 0 { + self.green_life.value = 1; + } + } + + pub fn recharge(&mut self, skill: Skill, red_amount: u64, blue_amount: u64) -> Vec { + let mut events = vec![]; + + // Should red type immunity block recharge??? + if let Some(immunity) = self.immune(skill) { + if !self.is_ko() { + events.push(Event::Immunity { skill, immunity }); + } + return events; + } + + match self.affected(Effect::Invert) { + false => { + // Do we need inversion? + let current_red_life = self.red_life(); + self.red_life.increase(red_amount); + let new_red_life = self.red_life.value; + let red = new_red_life - current_red_life; + + let current_blue_life = self.blue_life(); + self.blue_life.increase(blue_amount); + let new_blue_life = self.blue_life.value; + let blue = new_blue_life - current_blue_life; + + if red != 0 || blue != 0 { + events.push(Event::Recharge { red, blue, skill }); + } + }, + true => { + // Recharge takes a red and blue amount so check for them + if red_amount != 0 { + let red_mods = self.effects.iter() + .filter(|e| e.effect.modifications().contains(&Stat::RedDamageTaken)) + .map(|e| (e.effect, e.meta)) + .collect::)>>(); + + let red_modified_power = red_mods.iter() + .fold(red_amount, |acc, fx| fx.0.apply(acc, fx.1)); + + + let red_remainder = red_modified_power.saturating_sub(self.red_life.value); + let red_mitigation = red_modified_power.saturating_sub(red_remainder); + + // reduce red_life by mitigation amount + self.red_life.reduce(red_mitigation); + + // deal remainder to green_life + let red_current_green_life = self.green_life(); + self.reduce_green_life(red_remainder); + let red_damage_amount = red_current_green_life - self.green_life(); + + events.push(Event::Damage { + skill, + amount: red_damage_amount, + mitigation: red_mitigation, + colour: Colour::Red + }); + } + + if blue_amount != 0 { + let blue_mods = self.effects.iter() + .filter(|e| e.effect.modifications().contains(&Stat::BlueDamageTaken)) + .map(|e| (e.effect, e.meta)) + .collect::)>>(); + + let blue_modified_power = blue_mods.iter() + .fold(blue_amount, |acc, fx| fx.0.apply(acc, fx.1)); + + + let blue_remainder = blue_modified_power.saturating_sub(self.blue_life.value); + let blue_mitigation = blue_modified_power.saturating_sub(blue_remainder); + + // reduce blue_life by mitigation amount + self.blue_life.reduce(blue_mitigation); + + // deal remainder to green_life + let blue_current_green_life = self.green_life(); + self.reduce_green_life(blue_remainder); + let blue_damage_amount = blue_current_green_life - self.green_life(); + + events.push(Event::Damage { + skill, + amount: blue_damage_amount, + mitigation: blue_mitigation, + colour: Colour::Blue + }); + } + } + } + return events; + } + + pub fn deal_green_damage(&mut self, skill: Skill, amount: u64) -> Vec { + let mut events = vec![]; + if let Some(immunity) = self.immune(skill) { + if !self.is_ko() { + events.push(Event::Immunity { skill, immunity }); + } + return events; + } + + let mods = self.effects.iter() + .filter(|e| e.effect.modifications().contains(&Stat::GreenDamageTaken)) + .map(|e| (e.effect, e.meta)) + .collect::)>>(); + + let modified_power = mods.iter() + .fold(amount, |acc, fx| fx.0.apply(acc, fx.1)); + + match self.affected(Effect::Invert) { + false => { + let current_green_life = self.green_life(); + self.green_life.increase(modified_power); + let new_green_life = self.green_life.value; + + let healing = new_green_life - current_green_life; + let overhealing = modified_power - healing; + + events.push(Event::Healing { + skill, + amount: healing, + overhealing, + }); + }, + true => { + // events.push(Event::Inversion { skill }); + + // there is no green shield (yet) + let current_green_life = self.green_life(); + self.reduce_green_life(modified_power); + let delta = current_green_life - self.green_life(); + + events.push(Event::Damage { + skill, + amount: delta, + mitigation: 0, + colour: Colour::Green, + }); + } + } + + return events; + } + + pub fn deal_red_damage(&mut self, skill: Skill, amount: u64) -> Vec { + let mut events = vec![]; + + if let Some(immunity) = self.immune(skill) { + if !self.is_ko() { + events.push(Event::Immunity { skill, immunity }); + } + return events; + } + + let mods = self.effects.iter() + .filter(|e| e.effect.modifications().contains(&Stat::RedDamageTaken)) + .map(|e| (e.effect, e.meta)) + .collect::)>>(); + + let modified_power = mods.iter() + .fold(amount, |acc, fx| fx.0.apply(acc, fx.1)); + + match self.affected(Effect::Invert) { + false => { + // calculate amount of damage red_life will not absorb + // eg 50 red_life 25 damage -> 0 remainder 25 mitigation + // 50 red_life 100 damage -> 50 remainder 50 mitigation + // 50 red_life 5 damage -> 0 remainder 5 mitigation + let remainder = modified_power.saturating_sub(self.red_life.value); + let mitigation = modified_power.saturating_sub(remainder); + + // reduce red_life by mitigation amount + self.red_life.reduce(mitigation); + + // deal remainder to green_life + let current_green_life = self.green_life(); + self.reduce_green_life(remainder); + let delta = current_green_life - self.green_life(); + + events.push(Event::Damage { + skill, + amount: delta, + mitigation, + colour: Colour::Red, + }); + }, + true => { + // events.push(Event::Inversion { skill }); + + let current_green_life = self.green_life(); + self.green_life.increase(modified_power); + let new_green_life = self.green_life.value; + let healing = new_green_life - current_green_life; + let overhealing = modified_power - healing; + + let current_life = self.red_life.value; + self.red_life.increase(overhealing); + let recharge = self.red_life.value - current_life; + + if healing > 0 { + events.push(Event::Healing { + skill, + amount: healing, + overhealing: overhealing - recharge, + }); + } + + if recharge > 0 { + events.push(Event::Recharge { red: recharge, blue: 0, skill }); + } + } + }; + + return events; + } + + pub fn deal_blue_damage(&mut self, skill: Skill, amount: u64) -> Vec { + let mut events = vec![]; + + if let Some(immunity) = self.immune(skill) { + if !self.is_ko() { + events.push(Event::Immunity { skill, immunity }); + } + return events; + } + + let mods = self.effects.iter() + .filter(|e| e.effect.modifications().contains(&Stat::BlueDamageTaken)) + .map(|e| (e.effect, e.meta)) + .collect::)>>(); + + let modified_power = mods.iter() + .fold(amount, |acc, fx| fx.0.apply(acc, fx.1)); + + match self.affected(Effect::Invert) { + false => { + let remainder = modified_power.saturating_sub(self.blue_life.value); + let mitigation = modified_power.saturating_sub(remainder); + + // reduce blue_life by mitigation amount + self.blue_life.reduce(mitigation); + + // deal remainder to green_life + let current_green_life = self.green_life(); + self.reduce_green_life(remainder); + let delta = current_green_life - self.green_life(); + + events.push(Event::Damage { + skill, + amount: delta, + mitigation, + colour: Colour::Blue, + }); + }, + true => { + // events.push(Event::Inversion { skill }); + + let current_green_life = self.green_life(); + self.green_life.increase(modified_power); + let new_green_life = self.green_life.value; + let healing = new_green_life - current_green_life; + let overhealing = modified_power - healing; + + let current_life = self.blue_life.value; + self.blue_life.increase(overhealing); + let recharge = self.blue_life.value - current_life; + + if healing > 0 { + events.push(Event::Healing { + skill, + amount: healing, + overhealing, + }); + } + + if recharge > 0 { + events.push(Event::Recharge { red: 0, blue: recharge, skill }); + } + } + }; + + return events; + } + + pub fn add_effect(&mut self, skill: Skill, effect: ConstructEffect) -> Event { + if let Some(immunity) = self.immune(skill) { + return Event::Immunity { + skill, + immunity, + }; + } + + if let Some(p) = self.effects.iter().position(|ce| ce.effect == effect.effect) { + // duplicate effect + // replace existing + + self.effects[p] = effect; + } else { + // new effect + // info!("{:?} {:?} adding effect", self.name, effect.effect); + self.effects.push(effect); + } + + // todo modified durations cause of buffs + let result = Event::Effect { + effect: effect.effect, + duration: effect.duration, + construct_effects: self.effects.clone(), + skill, + }; + return result; + } + + // pub fn evade(&self, skill: Skill) -> Option { + // if self.evasion.value == 0 { + // return None; + // } + + // let mut rng = thread_rng(); + // let green_life_pct = (self.green_life.value * 100) / self.green_life.value; + // let evasion_rating = (self.evasion.value * green_life_pct) / 100; + // let roll = rng.gen_range(0, 100); + // info!("{:} < {:?}", roll, evasion_rating); + + // match roll < evasion_rating { + // true => Some(Event::Evasion { + // skill, + // evasion_rating: evasion_rating, + // }), + // false => None, + // } + // } +} + + +#[cfg(test)] +mod tests { + use construct::*; + use util::IntPct; + + #[test] + fn create_construct_test() { + let construct = Construct::new() + .named(&"hatchling".to_string()); + + assert_eq!(construct.name, "hatchling".to_string()); + return; + } + + #[test] + fn construct_colours_test() { + let mut construct = Construct::new() + .named(&"redboi".to_string()); + + construct.learn_mut(Skill::Strike); + construct.spec_add(Spec::LifeGG).unwrap(); + construct.spec_add(Spec::PowerRR).unwrap(); + construct.spec_add(Spec::LifeBB).unwrap(); + + assert_eq!(construct.colours.red, 4); + assert_eq!(construct.colours.green, 2); + assert_eq!(construct.colours.blue, 2); + + return; + } + + #[test] + fn construct_player_modifiers_test() { + let mut construct = Construct::new() + .named(&"player player".to_string()); + + construct.spec_add(Spec::PowerRR).unwrap(); + construct.spec_add(Spec::PowerGG).unwrap(); + construct.spec_add(Spec::PowerBB).unwrap(); + construct.learn_mut(Skill::StrikePlusPlus); // 18 reds (24 total) + + let player_colours = Colours { + red: 5, + green: 15, + blue: 25, + }; + + construct.apply_modifiers(&player_colours); + + assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(35)); + assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(50)); + assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(70)); + + return; + } + + #[test] + fn construct_player_modifiers_base_test() { + let mut construct = Construct::new() + .named(&"player player".to_string()); + + construct.spec_add(Spec::Power).unwrap(); + construct.spec_add(Spec::Life).unwrap(); + + let player_colours = Colours { + red: 5, + green: 15, + blue: 25, + }; + + construct.apply_modifiers(&player_colours); + + assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(10)); + assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(10)); + assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(10)); + assert!(construct.green_life.value == construct.green_life.base + 125); + + return; + } + + #[test] + fn construct_colour_calc_test() { + let mut construct = Construct::new() + .named(&"player player".to_string()); + + construct.spec_add(Spec::PowerRR).unwrap(); + construct.spec_add(Spec::PowerGG).unwrap(); + construct.spec_add(Spec::PowerBB).unwrap(); + + let colours = Colours::from_construct(&construct); + assert!(colours.red == 2); + assert!(colours.blue == 2); + assert!(colours.green == 2); + + let construct = construct + .learn(Skill::Strike) + .learn(Skill::BlastPlusPlus); + + let colours = Colours::from_construct(&construct); + assert!(colours.red == 4); + assert!(colours.blue == 20); + assert!(colours.green == 2); + } + + #[test] + fn construct_player_modifiers_spec_bonus_test() { + let mut construct = Construct::new() + .named(&"player player".to_string()); + + construct.spec_add(Spec::PowerRR).unwrap(); + construct.spec_add(Spec::PowerGG).unwrap(); + construct.spec_add(Spec::PowerBB).unwrap(); + + let player_colours = Colours { + red: 5, + green: 0, + blue: 0, + }; + + construct.apply_modifiers(&player_colours); + + assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(35)); + assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(25)); + assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(25)); + + return; + } + +} diff --git a/core/src/effect.rs b/core/src/effect.rs new file mode 100644 index 00000000..e1a87604 --- /dev/null +++ b/core/src/effect.rs @@ -0,0 +1,209 @@ +use construct::{Stat, EffectMeta}; +use skill::{Skill}; +use util::{IntPct}; + +pub type Cooldown = Option; + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum Effect { + Amplify, + Banish, + Block, + Buff, + Counter, + Curse, + Haste, + Hybrid, + Intercept, + Invert, + Pure, + Purge, + Reflect, + Restrict, + Silence, + Slow, + Stun, + Sustain, + Vulnerable, + Wither, // Reduce green dmg (healing) taken + + // electric is the buff that applies + // electrocute the dmg debuff + Electric, + Electrocute, + + // absorbtion is the buff + // absorb is the increased damage + Absorb, + Absorption, + + // magic immunity + + // effects over time + Triage, + Decay, + Regen, + Siphon, + + // Airborne, + // Boost + // Bleed, + // Blind, + // Deadly, + // Enslave, + // Fury, + // Injured, + // Leech, + // Mesmerise, + // Untouchable, + // SpeedSiphon, + // SpeedIncrease, + + Ko, +} + +impl Effect { + pub fn immune(&self, skill: Skill) -> bool { + match self { + Effect::Banish => true, + Effect::Sustain => [ + Skill::Stun, + Skill::Silence, + Skill::SilencePlus, + Skill::SilencePlusPlus, + Skill::Ruin, + Skill::RuinPlus, + Skill::RuinPlusPlus, + Skill::Restrict, + Skill::RestrictPlus, + Skill::RestrictPlusPlus + ].contains(&skill), + _ => false, + } + } + + pub fn disables_skill(&self, skill: Skill) -> bool { + if skill.is_tick() { + return false; + } + + match self { + Effect::Stun => true, + Effect::Banish => true, + Effect::Silence => skill.colours().contains(&Colour::Blue), + Effect::Restrict => skill.colours().contains(&Colour::Red), + Effect::Purge => skill.colours().contains(&Colour::Green), + Effect::Ko => skill.ko_castable(), + _ => false, + } + } + + pub fn modifications(&self) -> Vec { + match self { + // Bases + Effect::Block => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken], + Effect::Buff => vec![Stat::BluePower, Stat::RedPower, Stat::Speed], + Effect::Slow => vec![Stat::Speed], + + // Power changes + Effect::Absorption => vec![Stat::RedPower, Stat::BluePower], + Effect::Amplify => vec![Stat::RedPower, Stat::BluePower], + Effect::Hybrid => vec![Stat::GreenPower], + + // Damage taken changes + Effect::Curse => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken], + Effect::Pure => vec![Stat::GreenDamageTaken], // increased green taken + Effect::Vulnerable => vec![Stat::RedDamageTaken], + Effect::Wither => vec![Stat::GreenDamageTaken], // reduced green taken + + // Speed + Effect::Haste => vec![Stat::Speed], + + _ => vec![], + } + } + + pub fn apply(&self, value: u64, meta: Option) -> u64 { + match self { + Effect::Amplify | + Effect::Vulnerable | + Effect::Block | + Effect::Buff | + Effect::Curse | + Effect::Haste | + Effect::Slow | + Effect::Hybrid | + Effect::Pure | + Effect::Wither => value.pct(match meta { + Some(EffectMeta::Multiplier(d)) => d, + _ => 100, + }), + + Effect::Absorption => value + match meta { + Some(EffectMeta::AddedDamage(d)) => d, + _ => { + warn!("absorb meta not damage"); + return 0; + } + }, + + _ => { + warn!("{:?} does not have a mod effect", self); + return value; + }, + } + } + + pub fn colour(&self) -> Option { + match self { + // physical + Effect::Stun => Some(Colour::Red), + Effect::Block => Some(Colour::Green), + Effect::Buff => Some(Colour::Green), + Effect::Counter => Some(Colour::Green), + Effect::Vulnerable => Some(Colour::Red), + Effect::Restrict => Some(Colour::Red), + Effect::Sustain => Some(Colour::Green), + Effect::Intercept => Some(Colour::Green), + + // magic + Effect::Curse => Some(Colour::Blue), + Effect::Banish => None, + // Effect::Banish => rng.gen_bool(0.5), + + Effect::Slow => Some(Colour::Blue), + Effect::Haste => Some(Colour::Green), + Effect::Absorption => Some(Colour::Green), + Effect::Reflect => Some(Colour::Green), + Effect::Amplify => Some(Colour::Green), + Effect::Silence => Some(Colour::Blue), + Effect::Wither => Some(Colour::Blue), + Effect::Purge => Some(Colour::Blue), + + Effect::Electric => Some(Colour::Green), + Effect::Electrocute => Some(Colour::Blue), + + Effect::Absorb => Some(Colour::Green), + + // magic + Effect::Hybrid => Some(Colour::Green), + Effect::Invert => Some(Colour::Green), + + // effects over time + Effect::Triage => Some(Colour::Green), + Effect::Decay => Some(Colour::Blue), + Effect::Regen => Some(Colour::Green), + Effect::Siphon => Some(Colour::Blue), + Effect::Pure => Some(Colour::Green), + + Effect::Ko => None, + } + } +} + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum Colour { + Red, + Blue, + Green, +} diff --git a/core/src/game.rs b/core/src/game.rs new file mode 100644 index 00000000..818e5643 --- /dev/null +++ b/core/src/game.rs @@ -0,0 +1,1315 @@ +use rand::prelude::*; +use uuid::Uuid; + +// timekeeping +use chrono::prelude::*; +use chrono::Duration; + +// Db Commons +use failure::Error; +use failure::err_msg; + +use construct::{Construct}; +use skill::{Skill, Cast, Resolution, Event, resolve}; +use effect::{Effect}; +use player::{Player}; +use instance::{TimeControl}; + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum Phase { + Start, + Skill, + Resolve, + Finished, +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct Game { + pub id: Uuid, + pub player_constructs: usize, + pub player_num: usize, + pub players: Vec, + pub phase: Phase, + pub stack: Vec, + pub resolved: Vec, + pub instance: Option, + time_control: TimeControl, + phase_start: DateTime, + phase_end: Option>, +} + +impl Game { + pub fn new() -> Game { + return Game { + id: Uuid::new_v4(), + player_constructs: 0, + player_num: 0, + players: vec![], + phase: Phase::Start, + stack: vec![], + resolved: vec![], + instance: None, + time_control: TimeControl::Standard, + phase_end: None, + phase_start: Utc::now(), + }; + } + + pub fn redact(mut self, account: Uuid) -> Game { + self.players = self.players.into_iter() + .map(|p| p.redact(account)) + .collect(); + + self.stack + .retain(|s| s.source_player_id == account); + + self + } + + pub fn set_time_control(&mut self, tc: TimeControl) -> &mut Game { + self.time_control = tc; + self.phase_end = Some(tc.lobby_timeout()); + self + } + + pub fn set_player_num(&mut self, size: usize) -> &mut Game { + self.player_num = size; + self + } + + pub fn set_player_constructs(&mut self, size: usize) -> &mut Game { + self.player_constructs = size; + self + } + + pub fn set_instance(&mut self, id: Uuid) -> &mut Game { + self.instance = Some(id); + self + } + + pub fn player_add(&mut self, mut player: Player) -> Result<&mut Game, Error> { + if self.players.len() == self.player_num { + return Err(err_msg("maximum number of players")); + } + + if self.players.iter().any(|t| t.id == player.id) { + return Err(err_msg("player already in game")); + } + + if player.constructs.iter().all(|c| c.skills.len() == 0) { + info!("WARNING: {:?} has no skills and has forfeited {:?}", player.name, self.id); + // self.log.push(format!("{:} has forfeited the game", player.name)); + player.forfeit(); + } + + // let player_description = player.constructs.iter().map(|c| c.name.clone()).collect::>().join(", "); + // self.log.push(format!("{:} has joined the game. [{:}]", player.name, player_description)); + + player.constructs.sort_unstable_by_key(|c| c.id); + self.players.push(player); + + Ok(self) + } + + // handle missing player properly + pub fn player_by_id(&mut self, id: Uuid) -> Result<&mut Player, Error> { + self.players + .iter_mut() + .find(|t| t.id == id) + .ok_or(format_err!("{:?} not in game", id)) + } + + pub fn construct_by_id(&mut self, id: Uuid) -> Option<&mut Construct> { + match self.players.iter_mut().find(|t| t.constructs.iter().any(|c| c.id == id)) { + Some(player) => player.constructs.iter_mut().find(|c| c.id == id), + None => None, + } + } + + fn all_constructs(&self) -> Vec { + self.players.clone() + .into_iter() + .flat_map( + |t| t.constructs + .into_iter()) + .collect::>() + } + + pub fn update_construct(&mut self, construct: &mut Construct) -> &mut Game { + match self.players.iter_mut().find(|t| t.constructs.iter().any(|c| c.id == construct.id)) { + Some(player) => { + let index = player.constructs.iter().position(|t| t.id == construct.id).unwrap(); + player.constructs.remove(index); + player.constructs.push(construct.clone()); + player.constructs.sort_unstable_by_key(|c| c.id); + }, + None => panic!("construct not in game"), + }; + + self + } + + pub fn can_start(&self) -> bool { + return self.players.len() == self.player_num + && self.players.iter().all(|t| t.constructs.len() == self.player_constructs) + } + + pub fn start(self) -> Game { + // self.log.push("Game starting...".to_string()); + + // both forfeit ddue to no skills + if self.finished() { + return self.finish(); + } + + self.skill_phase_start(0) + } + + fn skill_phase_start(mut self, resolution_animation_ms: i64) -> Game { + self.phase_start = Utc::now() + .checked_add_signed(Duration::milliseconds(resolution_animation_ms)) + .expect("could not set phase start"); + + self.phase_end = self.time_control.game_phase_end(resolution_animation_ms); + + for player in self.players.iter_mut() { + if player.skills_required() == 0 { + continue; + } + + player.set_ready(false); + + for construct in player.constructs.iter_mut() { + for i in 0..construct.skills.len() { + if let Some(_d) = construct.disabled(construct.skills[i].skill) { + // info!("{:?} disabled {:?}", construct.skills[i].skill, d); + construct.skills[i].disabled = true; + } else { + construct.skills[i].disabled = false; + } + } + } + } + + // self.log.push("".to_string()); + + if ![Phase::Start, Phase::Resolve].contains(&self.phase) { + panic!("game not in Resolve or start phase"); + } + + self.phase = Phase::Skill; + + self.pve_add_skills(); + + if self.skill_phase_finished() { + return self.resolve_phase_start() + } + + self + } + + fn pve_add_skills(&mut self) -> &mut Game { + let mut pve_skills = vec![]; + let mut rng = thread_rng(); + + for mobs in self.players + .iter() + .filter(|t| t.bot) { + let player_player = self.players.iter().find(|t| t.id != mobs.id).unwrap(); + + for mob in mobs.constructs.iter() { + let skill = mob.mob_select_skill(); + // info!("{:?} {:?}", mob.name, skill); + match skill { + Some(s) => { + // the mut marks it as being able to be called + // more than once + let mut find_target = || { + match s.defensive() { + true => &mobs.constructs[rng.gen_range(0, mobs.constructs.len())], + false => &player_player.constructs[rng.gen_range(0, player_player.constructs.len())], + } + }; + + let mut target = find_target(); + + while target.is_ko() { + target = find_target(); + } + + pve_skills.push((mobs.id, mob.id, target.id, s)); + }, + None => continue, + }; + } + } + + for (player_id, mob_id, target_id, s) in pve_skills { + match self.add_skill(player_id, mob_id, target_id, s) { + Ok(_) => (), + Err(e) => { + info!("{:?}", self.construct_by_id(mob_id)); + panic!("{:?} unable to add pve mob skill {:?}", e, s); + }, + } + + self.player_ready(player_id).unwrap(); + } + + self + } + + fn add_skill(&mut self, player_id: Uuid, source_construct_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Result<&mut Game, Error> { + // check player in game + self.player_by_id(player_id)?; + + if self.phase != Phase::Skill { + return Err(err_msg("game not in skill phase")); + } + + // target checks + { + let target = match self.construct_by_id(target_construct_id) { + Some(c) => c, + None => return Err(err_msg("target construct not in game")), + }; + + // fixme for rez + if target.is_ko() { + return Err(err_msg("target construct is ko")); + } + } + + // construct checks + { + let construct = match self.construct_by_id(source_construct_id) { + Some(c) => c, + None => return Err(err_msg("construct not in game")), + }; + + if construct.is_ko() { + return Err(err_msg("construct is ko")); + } + + // check the construct has the skill + if !construct.knows(skill) { + return Err(err_msg("construct does not have that skill")); + } + + if construct.skill_on_cd(skill).is_some() { + return Err(err_msg("abiltity on cooldown")); + } + + // check here as well so uncastable spells don't go on the stack + if let Some(disable) = construct.disabled(skill) { + return Err(format_err!("skill disabled {:?}", disable)); + } + } + + // replace construct skill + if let Some(s) = self.stack.iter_mut().position(|s| s.source_construct_id == source_construct_id) { + self.stack.remove(s); + } + + let skill = Cast::new(source_construct_id, player_id, target_construct_id, skill); + self.stack.push(skill); + + return Ok(self); + } + + fn offer_draw(mut self, player_id: Uuid) -> Result { + if self.phase != Phase::Skill { + return Err(err_msg("game not in skill phase")); + } + + { + let player = self.player_by_id(player_id)?; + player.draw_offered = true; + } + + // bots automatically accept draws + for player in self.players.iter_mut() { + if player.bot { + player.draw_offered = true; + } + } + + if self.players.iter().all(|p| p.draw_offered) { + return Ok(self.finish()); + } + + return Ok(self); + } + + fn concede(mut self, player_id: Uuid) -> Result { + if self.phase != Phase::Skill { + return Err(err_msg("game not in skill phase")); + } + + self.player_by_id(player_id)? + .forfeit(); + + return Ok(self.finish()); + } + + + fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> { + self.player_by_id(player_id)?; + if self.phase != Phase::Skill { + return Err(err_msg("game not in skill phase")); + } + let mut game_state = self.clone(); + self.stack.retain(|s| game_state.construct_by_id(s.source_construct_id).unwrap().account != player_id); + + return Ok(self); + } + + fn player_ready(&mut self, player_id: Uuid) -> Result<&mut Game, Error> { + if self.phase != Phase::Skill { + return Err(err_msg("game not in skill phase")); + } + + self.player_by_id(player_id)? + .set_ready(true); + + Ok(self) + } + + fn skill_phase_finished(&self) -> bool { + self.players.iter().all(|t| t.ready) + // self.players.iter() + // // for every player + // .all(|t| self.stack.iter() + // // the number of skills they have cast + // .filter(|s| s.source_player_id == t.id).collect::>() + // // should equal the number required this turn + // .len() == t.skills_required() + // ) + } + + // requires no input + // just do it + fn resolve_phase_start(mut self) -> Game { + if self.phase != Phase::Skill { + panic!("game not in skill phase"); + } + assert!(self.skill_phase_finished()); + + self.phase = Phase::Resolve; + // self.log.push("".to_string()); + + self.resolve_skills() + } + + fn stack_sort_speed(&mut self) -> &mut Game { + let mut sorted = self.stack.clone(); + sorted.iter_mut() + .for_each(|s| { + if !s.skill.is_tick() { + let caster = self.construct_by_id(s.source_construct_id).unwrap(); + let speed = caster.skill_speed(s.skill); + s.speed = speed; + } + }); + + sorted.sort_unstable_by_key(|s| s.speed); + + self.stack = sorted; + + self + } + + fn construct_aoe_targets(&self, construct_id: Uuid) -> Vec { + self.players.iter() + .find(|t| t.constructs.iter().any(|c| c.id == construct_id)) + .unwrap() + .constructs + .iter() + .map(|c| c.id) + .collect() + } + + pub fn get_targets(&self, skill: Skill, source: &Construct, target_construct_id: Uuid) -> Vec { + let target_player = self.players.iter() + .find(|t| t.constructs.iter().any(|c| c.id == target_construct_id)) + .unwrap(); + + if let Some(t) = target_player.intercepting() { + return vec![t.id]; + } + + match source.skill_is_aoe(skill) { + true => self.construct_aoe_targets(target_construct_id), + false => vec![target_construct_id], + } + } + + fn resolve_skills(mut self) -> Game { + if self.phase != Phase::Resolve { + panic!("game not in Resolve phase"); + } + + // find their statuses with ticks + let mut ticks = self.all_constructs() + .iter() + .flat_map( + |c| c.effects + .iter() + .cloned() + .filter_map(|e| e.tick)) + .collect::>(); + + // add them to the stack + self.stack.append(&mut ticks); + + self.stack_sort_speed(); + + // temp vec of this round's resolving skills + // because need to check cooldown use before pushing them into the complete list + let mut casts = vec![]; + let mut r_animation_ms = 0; + while let Some(cast) = self.stack.pop() { + // info!("{:} casts ", cast); + + let mut resolutions = resolve(&cast, &mut self); + r_animation_ms = resolutions.iter().fold(r_animation_ms, |acc, r| acc + r.clone().get_delay()); + + + // the cast itself goes into this temp vec to handle cooldowns + // if theres no resolution events, the skill didn't trigger (disable etc) + if resolutions.len() > 0 { + casts.push(cast); + } + + self.resolved.append(&mut resolutions); + + // while let Some(resolution) = resolutions.pop() { + // self.log_resolution(cast.speed, &resolution); + // // the results go into the resolutions + // self.resolved.push(resolution); + // } + + // sort the stack again in case speeds have changed + self.stack_sort_speed(); + }; + + // info!("{:#?}", self.casts); + + // handle cooldowns and statuses + self.progress_durations(&casts); + + if self.finished() { + return self.finish() + } + + self.skill_phase_start(r_animation_ms) + } + + fn progress_durations(&mut self, resolved: &Vec) -> &mut Game { + for mut construct in self.all_constructs() { + // info!("progressing durations for {:}", construct.name); + + if construct.is_ko() { + continue; + } + + // only reduce cooldowns if no cd was used + { + if let Some(skill) = resolved.iter() + .filter(|s| s.source_construct_id == construct.id) + .find(|s| s.used_cooldown()) { + construct.skill_set_cd(skill.skill); + } else { + construct.reduce_cooldowns(); + } + } + + // always reduce durations + construct.reduce_effect_durations(); + self.update_construct(&mut construct); + } + + self + } + + // fn log_resolution(&mut self, speed: u64, resolution: &Resolution) -> &mut Game { + // let Resolution { source, target, event, stages: _ } = resolution; + // match event { + // Event::Ko { skill: _ }=> + // self.log.push(format!("{:} KO!", target.name)), + + // Event::Disable { skill, disable } => + // self.log.push(format!("{:} {:?} {:} disabled {:?}", + // source.name, skill, target.name, disable)), + + // Event::Immunity { skill, immunity } => + // self.log.push(format!("[{:}] {:} {:?} {:} immune {:?}", + // speed, source.name, skill, target.name, immunity)), + + // Event::TargetKo { skill } => + // self.log.push(format!("[{:}] {:} {:?} {:} - target is KO", + // speed, source.name, skill, target.name)), + + // Event::Damage { skill, amount, mitigation, colour: _ } => + // self.log.push(format!("[{:}] {:} {:?} {:} {:} ({:} mitigated)", + // speed, source.name, skill, target.name, amount, mitigation)), + + // Event::Healing { skill, amount, overhealing } => + // self.log.push(format!("[{:}] {:} {:?} {:} {:} healing ({:}OH)", + // speed, source.name, skill, target.name, amount, overhealing)), + + // Event::Inversion { skill } => + // self.log.push(format!("[{:}] {:} {:?} {:} INVERTED", + // speed, source.name, skill, target.name)), + + // Event::Reflection { skill } => + // self.log.push(format!("[{:}] {:} {:?} {:} REFLECTED", + // speed, source.name, skill, target.name)), + + // Event::Effect { skill, effect, duration, construct_effects: _ } => + // self.log.push(format!("[{:}] {:} {:?} {:} {:?} {:}T", + // speed, source.name, skill, target.name, effect, duration)), + + // Event::Skill { skill } => + // self.log.push(format!("[{:}] {:} {:?} {:}", + // speed, source.name, skill, target.name)), + + // Event::Removal { effect, construct_effects: _ } => + // self.log.push(format!("[{:}] {:?} removed {:} {:?}", + // speed, source.name, target.name, effect)), + + // Event::Recharge { skill, red, blue } => + // self.log.push(format!("[{:}] {:} {:?} {:} {:}R {:}B", + // speed, source.name, skill, target.name, red, blue)), + + // Event::Evasion { skill, evasion_rating } => + // self.log.push(format!("[{:}] {:} {:?} {:} evaded ({:}%)", + // speed, source.name, skill, target.name, evasion_rating)), + + // Event::Incomplete => panic!("incomplete resolution {:?}", resolution), + // } + + // self + // } + + pub fn finished(&self) -> bool { + self.phase == Phase::Finished || self.players.iter().any(|t| t.constructs.iter().all(|c| c.is_ko())) + } + + pub fn winner(&self) -> Option<&Player> { + match self.players.iter().any(|t| t.constructs.iter().all(|c| c.is_ko())) { + true => self.players.iter().find(|t| t.constructs.iter().any(|c| !c.is_ko())), + false => None, + } + } + + fn finish(mut self) -> Game { + self.phase = Phase::Finished; + // self.log.push(format!("Game finished.")); + + // { + // let winner = self.players.iter().find(|t| t.constructs.iter().any(|c| !c.is_ko())); + // match winner { + // Some(w) => self.log.push(format!("Winner: {:}", w.name)), + // None => self.log.push(format!("Game was drawn.")), + // }; + // } + + self + } + + fn phase_timed_out(&self) -> bool { + match self.phase_end { + Some(t) => Utc::now().signed_duration_since(t).num_milliseconds() > 0, + None => false, + } + } + + pub fn upkeep(mut self) -> Game { + if self.phase == Phase::Finished { + return self; + } + + if !self.phase_timed_out() { + return self; + } + + info!("upkeep game: {:} vs {:}", self.players[0].name, self.players[1].name); + + for player in self.players.iter_mut() { + if !player.ready { + player.set_ready(true); + // player.add_warning(); + // info!("upkeep: {:} warned", player.name); + // if player.warnings >= 3 { + // player.forfeit(); + // info!("upkeep: {:} forfeited", player.name); + // //todo + // // self.resolved.push(forfeit) + // // self.log.push(format!("{:} forfeited.", player.name)); + // } + } + } + + self = self.resolve_phase_start(); + self + } +} + +#[cfg(test)] +mod tests { + use game::*; + use construct::*; + use util::IntPct; + + fn create_test_game() -> Game { + let mut x = Construct::new() + .named(&"pronounced \"creeep\"".to_string()) + .learn(Skill::Attack) + .learn(Skill::Stun) + .learn(Skill::Attack) + .learn(Skill::Block) + .learn(Skill::Counter) + .learn(Skill::Siphon) + .learn(Skill::Amplify) + .learn(Skill::Stun) + .learn(Skill::Block) + .learn(Skill::Sleep) + .learn(Skill::Decay); + + let mut y = Construct::new() + .named(&"lemongrass tea".to_string()) + .learn(Skill::Attack) + .learn(Skill::Stun) + .learn(Skill::Attack) + .learn(Skill::Block) + .learn(Skill::Counter) + .learn(Skill::Siphon) + .learn(Skill::Amplify) + .learn(Skill::Stun) + .learn(Skill::Block); + + let mut game = Game::new(); + + game + .set_player_num(2) + .set_player_constructs(1); + + let x_player_id = Uuid::new_v4(); + x.account = x_player_id; + let x_player = Player::new(x_player_id, &"ntr".to_string(), vec![x]); + + let y_player_id = Uuid::new_v4(); + y.account = y_player_id; + let y_player = Player::new(y_player_id, &"mash".to_string(), vec![y]); + + game + .player_add(x_player).unwrap() + .player_add(y_player).unwrap(); + + assert!(game.can_start()); + + return game.start(); + } + + fn create_2v2_test_game() -> Game { + let mut i = Construct::new() + .named(&"pretaliate".to_string()) + .learn(Skill::Attack) + .learn(Skill::Attack); + + let mut j = Construct::new() + .named(&"poy sian".to_string()) + .learn(Skill::Attack) + .learn(Skill::Attack); + + let mut x = Construct::new() + .named(&"pronounced \"creeep\"".to_string()) + .learn(Skill::Attack) + .learn(Skill::Attack); + + let mut y = Construct::new() + .named(&"lemongrass tea".to_string()) + .learn(Skill::Attack) + .learn(Skill::Attack); + + let mut game = Game::new(); + + game + .set_player_num(2) + .set_player_constructs(2); + + let i_player_id = Uuid::new_v4(); + i.account = i_player_id; + j.account = i_player_id; + let i_player = Player::new(i_player_id, &"ntr".to_string(), vec![i, j]); + + let x_player_id = Uuid::new_v4(); + x.account = x_player_id; + y.account = x_player_id; + let x_player = Player::new(x_player_id, &"mashy".to_string(), vec![x, y]); + + game + .player_add(i_player).unwrap() + .player_add(x_player).unwrap(); + + assert!(game.can_start()); + + return game.start(); + } + + #[test] + fn phase_test() { + let mut game = create_test_game(); + + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); + + let x_construct = x_player.constructs[0].clone(); + let y_construct = y_player.constructs[0].clone(); + + game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); + game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + + assert!(game.skill_phase_finished()); + + game = game.resolve_phase_start(); + + assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); + + return; + } + + #[test] + fn stun_test() { + let mut game = create_test_game(); + + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); + + let x_construct = x_player.constructs[0].clone(); + let y_construct = y_player.constructs[0].clone(); + + while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { + game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + } + + game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); + game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + + assert!(game.skill_phase_finished()); + game = game.resolve_phase_start(); + + // should auto progress back to skill phase + assert!(game.phase == Phase::Skill); + + // assert!(game.player_by_id(y_player.id).constructs[0].is_stunned()); + // assert!(game.player_by_id(y_player.id).skills_required() == 0); + } + + #[test] + fn ko_resolution_test() { + let mut game = create_test_game(); + + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); + + let x_construct = x_player.constructs[0].clone(); + let y_construct = y_player.constructs[0].clone(); + + game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().red_power.force(1000000000); + game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().speed.force(1000000000); + + while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { + game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + } + + // just in case + // remove all mitigation + game.player_by_id(x_player.id).unwrap().construct_by_id(x_construct.id).unwrap().red_life.force(0); + + game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); + game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + + assert!(game.skill_phase_finished()); + game = game.resolve_phase_start(); + + assert!(!game.player_by_id(y_player.id).unwrap().constructs[0].is_stunned()); + assert!(game.phase == Phase::Finished); + } + + #[test] + fn cooldown_test() { + let mut game = create_test_game(); + + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); + + let x_construct = x_player.constructs[0].clone(); + let y_construct = y_player.constructs[0].clone(); + + // should auto progress back to skill phase + assert!(game.phase == Phase::Skill); + + assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); + assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_some()); + assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); + + game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); + game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + + game = game.resolve_phase_start(); + + // should auto progress back to skill phase + assert!(game.phase == Phase::Skill); + assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_some()); + + // second round + // now we block and it should go back on cd + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); + game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + + game = game.resolve_phase_start(); + + assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_none()); + assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); + } + + #[test] + fn sleep_cooldown_test() { + let mut game = create_test_game(); + + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); + + let x_construct = x_player.constructs[0].clone(); + let y_construct = y_player.constructs[0].clone(); + + + for _n in 1..10 { + // should auto progress back to skill phase + assert!(game.phase == Phase::Skill); + + // Sleep 2T CD + assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); + + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + + // Sleep 1T CD + assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); + + game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap(); + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + + // Sleep 0T CD (we use it here) + assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_none()); + + game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Sleep).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + + // Sleep back to 2T CD + assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); + } + + } + + #[test] + fn counter_test() { + let mut game = create_test_game(); + + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); + + let x_construct = x_player.constructs[0].clone(); + let y_construct = y_player.constructs[0].clone(); + + while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { + game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns(); + } + + while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Counter).is_some() { + game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + } + + game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Counter).unwrap(); + game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + + game = game.resolve_phase_start(); + + // don't get stunned but not really stunning ¯\_(ツ)_/¯ + assert!(game.player_by_id(x_player.id).unwrap().constructs[0].is_stunned() == false); + // riposte + assert_eq!(game.player_by_id(y_player.id).unwrap().constructs[0].green_life(), ( + y_construct.green_life() + y_construct.red_life() - x_construct.red_power().pct(Skill::CounterAttack.multiplier()))); + } + + #[test] + fn electrify_test() { + let mut game = create_test_game(); + + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); + + let x_construct = x_player.constructs[0].clone(); + let y_construct = y_player.constructs[0].clone(); + + // one shot the target construct (should still get debuffed) + game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().red_power.force(1000000000); + + game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Electrify); + + while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Electrify).is_some() { + game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + } + + // apply buff + game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Electrify).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Electric)); + + // attack and receive debuff + game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + + assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Electrocute)); + } + + // #[test] + // fn link_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Link); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Link).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // // apply buff + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Link).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Link)); + + // let Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); + // match event { + // Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Link), + // _ => panic!("not siphon"), + // }; + + // let Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); + // match event { + // Event::Recharge { red: _, blue: _, skill: _ } => (), + // _ => panic!("link result was not recharge"), + // } + + // // attack and receive link hit + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // let Resolution { source: _, target, event, stages: _ } = game.resolved.pop().unwrap(); + // assert_eq!(target.id, y_construct.id); + // match event { + // Event::Damage { amount, skill: _, mitigation: _, colour: _} => + // assert_eq!(amount, x_construct.red_power().pct(Skill::Attack.multiplier()) >> 1), + // _ => panic!("not damage link"), + // }; + // } + + // #[test] + // fn absorb_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Absorb); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Absorb).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // // apply buff + // game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Absorb).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Absorb)); + + // // attack and receive debuff + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::TestAttack).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // info!("{:#?}", game); + // assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Absorption)); + // } + + #[test] + fn aoe_test() { + let mut game = create_2v2_test_game(); + + let i_player = game.players[0].clone(); + let x_player = game.players[1].clone(); + + let i_construct = i_player.constructs[0].clone(); + let j_construct = i_player.constructs[1].clone(); + let x_construct = x_player.constructs[0].clone(); + let y_construct = x_player.constructs[1].clone(); + + game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Ruin); + + while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Ruin).is_some() { + game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + } + + game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap(); + game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); + game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Ruin).unwrap(); + game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap(); + + game.player_ready(i_player.id).unwrap(); + game.player_ready(x_player.id).unwrap(); + + assert!(game.skill_phase_finished()); + game = game.resolve_phase_start(); + let ruins = game.resolved + .into_iter() + .filter(|r| { + let Resolution { source, target: _, event, stages: _ } = r; + match source.id == x_construct.id { + true => match event { + Event::Effect { effect, duration, skill: _, construct_effects: _ } => { + assert!(*effect == Effect::Stun); + assert!(*duration == 1); + true + }, + Event::AoeSkill { skill: _ } => false, + Event::Damage { amount: _, mitigation: _, colour: _, skill: _ } => false, + _ => panic!("ruin result not effect {:?}", event), + } + false => false, + } + }) + .count(); + + assert!(ruins == 2); + } + + #[test] + fn intercept_test() { + let mut game = create_2v2_test_game(); + + let i_player = game.players[0].clone(); + let x_player = game.players[1].clone(); + + let i_construct = i_player.constructs[0].clone(); + let j_construct = i_player.constructs[1].clone(); + let x_construct = x_player.constructs[0].clone(); + let y_construct = x_player.constructs[1].clone(); + + game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Intercept); + + while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Intercept).is_some() { + game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + } + + game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap(); + game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); + game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Intercept).unwrap(); + game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap(); + + game.player_ready(i_player.id).unwrap(); + game.player_ready(x_player.id).unwrap(); + + game = game.resolve_phase_start(); + + assert!(game.resolved.len() == 4); + while let Some(r) = game.resolved.pop() { + let Resolution { source , target, event: _, stages: _ } = r; + if [i_construct.id, j_construct.id].contains(&source.id) { + assert!(target.id == x_construct.id); + } + } + } + + #[test] + fn ko_pve_test() { + let mut game = create_2v2_test_game(); + + let i_player = game.players[0].clone(); + let x_player = game.players[1].clone(); + + let i_construct = i_player.constructs[0].clone(); + let j_construct = i_player.constructs[1].clone(); + let x_construct = x_player.constructs[0].clone(); + let y_construct = x_player.constructs[1].clone(); + + game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap() + .add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap() + .add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).unwrap() + .add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap() + .player_ready(i_player.id).unwrap() + .player_ready(x_player.id).unwrap(); + + assert!(game.skill_phase_finished()); + game = game.resolve_phase_start(); + + assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); + + // kill a construct + game.player_by_id(i_player.id).unwrap().construct_by_id(i_construct.id).unwrap().green_life.reduce(u64::max_value()); + + assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); + assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); + + // add some more skills + game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); + game.add_skill(x_player.id, x_construct.id, j_construct.id, Skill::Attack).unwrap(); + game.add_skill(x_player.id, y_construct.id, j_construct.id, Skill::Attack).unwrap(); + assert!(game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).is_err()); + + game.player_ready(i_player.id).unwrap(); + game.player_ready(x_player.id).unwrap(); + + assert!(game.skill_phase_finished()); + game = game.resolve_phase_start(); + + assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); + assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); + return; + } + + #[test] + fn tick_removal_test() { + let mut game = create_test_game(); + + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); + + let x_construct = x_player.constructs[0].clone(); + let y_construct = y_player.constructs[0].clone(); + + // make the purify construct super fast so it beats out decay + game.construct_by_id(y_construct.id).unwrap().speed.force(10000000); + + game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Decay); + while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Decay).is_some() { + game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + } + + game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Siphon); + while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Siphon).is_some() { + game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + } + + game.construct_by_id(y_construct.id).unwrap().learn_mut(Skill::Purify); + while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Purify).is_some() { + game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns(); + } + + // apply buff + game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Decay)); + + let Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); + match event { + Event::Damage { amount: _, skill, mitigation: _, colour: _ } => assert_eq!(skill, Skill::DecayTick), + _ => panic!("not decay"), + }; + + game.resolved.clear(); + + // remove + game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + + while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolved.pop() { + match event { + Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => + panic!("{:?} damage event", event), + _ => (), + } + }; + + game.add_skill(y_player.id, x_construct.id, y_construct.id, Skill::Siphon).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + + game.resolved.clear(); + + game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); + game = game.resolve_phase_start(); + + while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolved.pop() { + match event { + Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => + panic!("{:#?} {:#?} damage event", game.resolved, event), + _ => (), + } + }; + + } + + #[test] + fn upkeep_test() { + let mut game = create_2v2_test_game(); + game.players[0].set_ready(true); + game.phase_end = Some(Utc::now().checked_sub_signed(Duration::seconds(500)).unwrap()); + game = game.upkeep(); + // assert!(game.players[1].warnings == 1); + } +} diff --git a/core/src/instance.rs b/core/src/instance.rs new file mode 100644 index 00000000..1923172f --- /dev/null +++ b/core/src/instance.rs @@ -0,0 +1,631 @@ + +use std::collections::{HashMap}; + +use uuid::Uuid; + +use failure::Error; +use failure::err_msg; + +// timekeeping +use chrono::prelude::*; +use chrono::Duration; + +use player::{Player, Score}; +use mob::{bot_player, instance_mobs}; +use game::{Game}; +use item::{Item}; +use vbox; + + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +enum InstancePhase { + Lobby, + InProgress, + Finished, +} + +pub type ChatState = HashMap; + +#[derive(Debug,Clone,Serialize,Deserialize)] +struct Round { + game_id: Option, + finished: bool, +} + +impl Round { + fn new() -> Round { + Round { game_id: None, finished: false } + } +} + +#[derive(Debug,Clone,Copy,Serialize,Deserialize)] +pub enum TimeControl { + Standard, + Slow, + Practice, +} + +impl TimeControl { + fn vbox_time_seconds(&self) -> i64 { + match self { + TimeControl::Standard => 180, + TimeControl::Slow => 240, + TimeControl::Practice => panic!("practice vbox seconds called"), + } + } + + fn game_time_seconds(&self) -> i64 { + match self { + TimeControl::Standard => 60, + TimeControl::Slow => 120, + TimeControl::Practice => panic!("practice game seconds called"), + } + } + + pub fn vbox_phase_end(&self) -> Option> { + match self { + TimeControl::Practice => None, + _ => Some(Utc::now() + .checked_add_signed(Duration::seconds(self.vbox_time_seconds())) + .expect("could not set vbox phase end")), + } + } + + pub fn lobby_timeout(&self) -> DateTime { + Utc::now() + .checked_add_signed(Duration::seconds(15)) + .expect("could not set phase end") + } + + pub fn game_phase_end(&self, resolution_time_ms: i64) -> Option> { + match self { + TimeControl::Practice => None, + _ => Some(Utc::now() + .checked_add_signed(Duration::milliseconds(self.game_time_seconds() * 1000 + resolution_time_ms)) + .expect("could not set game phase end")), + } + } +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct Instance { + pub id: Uuid, + pub name: String, + + players: Vec, + rounds: Vec, + + max_players: usize, + time_control: TimeControl, + + phase: InstancePhase, + phase_end: Option>, + phase_start: DateTime, + + winner: Option, +} + +impl Instance { + fn new() -> Instance { + Instance { + id: Uuid::new_v4(), + players: vec![], + rounds: vec![], + phase: InstancePhase::Lobby, + max_players: 2, + name: String::new(), + time_control: TimeControl::Standard, + phase_start: Utc::now(), + phase_end: Some(TimeControl::Standard.lobby_timeout()), + winner: None, + } + } + + pub fn redact(mut self, account: Uuid) -> Instance { + self.players = self.players.into_iter() + .map(|p| p.redact(account)) + .collect(); + + self + } + + fn phase_timed_out(&self) -> bool { + match self.phase_end { + Some(t) => Utc::now().signed_duration_since(t).num_milliseconds() > 0, + None => false, + } + } + + fn timed_out_players(&self) -> Vec { + self.players + .iter() + .filter(|p| !p.ready) + .map(|p| p.id) + .collect::>() + } + + pub fn upkeep(mut self) -> (Instance, Option) { + // time out lobbies that have been open too long + if self.phase == InstancePhase::Lobby && self.phase_timed_out() { + self.finish(); + return (self, None); + } + + if self.phase != InstancePhase::InProgress { + return (self, None); + } + + if !self.phase_timed_out() { + return (self, None); + } + + let new_game = self + .timed_out_players() + .iter() + .filter_map(|p| self.player_ready(*p).unwrap()) + .collect::>() + .into_iter() + .next(); + + (self, new_game) + } + + fn set_name(mut self, name: String) -> Result { + if name.len() == 0 { + return Err(err_msg("name must have a length")); + } + + self.name = name; + Ok(self) + } + + fn set_time_control(mut self, tc: TimeControl) -> Instance { + self.time_control = tc; + self + } + + fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> { + if self.players.len() >= self.max_players { + return Err(err_msg("game full")) + } + + match self.players.iter().find(|p| p.id == player.id) { + Some(_p) => return Err(err_msg("already joined")), + None => (), + }; + + self.players.push(player); + Ok(self) + } + + fn player_ready(&mut self, player_id: Uuid) -> Result, Error> { + if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) { + return Err(err_msg("instance not in start or vbox phase")); + } + + // LOBBY CHECKS + if self.phase == InstancePhase::Lobby { + let i = self.players + .iter_mut() + .position(|p| p.id == player_id) + .ok_or(err_msg("player_id not found"))?; + + let v = !self.players[i].ready; + self.players[i].set_ready(v); + + match self.can_start() { + true => { + self.start(); + return Ok(None); + } + false => return Ok(None), + }; + } + + // GAME PHASE READY + let i = self.players + .iter_mut() + .position(|p| p.id == player_id) + .ok_or(err_msg("player_id not found"))?; + + let v = !self.players[i].ready; + self.players[i].set_ready(v); + + // start the game even if afk noobs have no skills + if !self.phase_timed_out() && self.players[i].constructs.iter().all(|c| c.skills.len() == 0) { + return Err(err_msg("your constructs have no skills")); + } + + // create a game object if both players are ready + // this should only happen once + + let all_ready = self.round_ready_check(); + + if !all_ready { + return Ok(None); + } + + let game = self.create_round_game(); + + let current_round = self.rounds + .last_mut() + .expect("instance does not have any rounds"); + + current_round.game_id = Some(game.id); + + return Ok(Some(game)); + + } + + fn round_ready_check(&mut self) -> bool { + self.players + .iter() + .all(|p| p.ready) + } + + // maybe just embed the games in the instance + // but seems hella inefficient + fn create_round_game(&mut self) -> Game { + let current_round = self.rounds + .last_mut() + .expect("instance does not have any rounds"); + + + let mut game = Game::new(); + current_round.game_id = Some(game.id); + + // disable upkeep until players finish their game + self.phase_end = None; + + game + .set_player_num(2) + .set_player_constructs(3) + .set_time_control(self.time_control) + .set_instance(self.id); + + for player in self.players.clone().into_iter() { + game.player_add(player).unwrap(); + } + + assert!(game.can_start()); + return game.start(); + } + + fn can_start(&self) -> bool { + self.players.len() == self.max_players && self.all_ready() + } + + fn start(&mut self) -> &mut Instance { + // self.players.sort_unstable_by_key(|p| p.id); + self.next_round() + } + + fn next_round(&mut self) -> &mut Instance { + if self.finish_condition() { + return self.finish(); + } + + self.phase = InstancePhase::InProgress; + self.phase_start = Utc::now(); + self.phase_end = self.time_control.vbox_phase_end(); + + let bits = match self.rounds.len() > 0 { + true => 30, + false => 0, + }; + + self.players.iter_mut().for_each(|p| { + p.vbox.balance_add(bits); + p.set_ready(false); + p.vbox.fill(); + }); + + self.rounds.push(Round::new()); + self.bot_round_actions(); + + self + } + + fn finish_condition(&mut self) -> bool { + self.players.iter().any(|p| p.score == Score::Win) + } + + pub fn finish(&mut self) -> &mut Instance { + self.phase = InstancePhase::Finished; + + for player in self.players.iter() { + if player.score == Score::Win { + self.winner = Some(player.id); + } + } + + self + } + + fn finished(&self) -> bool { + self.phase == InstancePhase::Finished + } + + fn bot_round_actions(&mut self) -> &mut Instance { + for bot in self.players.iter_mut().filter(|p| p.bot) { + bot.vbox.fill(); + bot.autobuy(); + } + + let games = self.players + .clone() + .iter() + .filter(|b| b.bot) + .filter_map(|b| self.player_ready(b.id).unwrap()) + .collect::>(); + + for game in games { + if game.finished() { + self.game_finished(&game).unwrap(); + } else { + info!("{:?} unfishededes", game); + } + } + + self + } + + fn current_game_id(&self) -> Option { + if self.phase != InstancePhase::InProgress { + return None; + } + + let current_round = self.rounds + .last() + .expect("instance does not have any rounds"); + + if current_round.finished || current_round.game_id.is_none() { + return None; + } + + return current_round.game_id; + } + + fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> { + { + let current_round = self.rounds + .iter_mut() + .filter(|r| r.game_id.is_some()) + .find(|r| r.game_id.unwrap() == game.id); + + match current_round { + Some(c) => c.finished = true, + None => return Err(err_msg("instance does not have a round for this game")), + }; + } + + // if you don't win, you lose + // ties can happen if both players agree to a draw + // or ticks fire and knock everybody out + if let Some(winner) = game.winner() { + let winner = self.players.iter_mut() + .find(|p| p.id == winner.id) + .unwrap(); + winner.score = winner.score.add_win(&Score::Zero); + }; + + if self.all_games_finished() { + self.next_round(); + } + + Ok(self) + } + + fn all_ready(&self) -> bool { + self.players.iter().all(|p| p.ready) + } + + fn all_games_finished(&self) -> bool { + match self.rounds.last() { + Some(r) => r.finished, + None => true, + } + } + + // PLAYER ACTIONS + fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> { + self.players + .iter_mut() + .find(|p| p.id == account) + .ok_or(err_msg("account not in instance")) + } + + fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> { + self.players + .iter_mut() + .find(|p| p.id != account) + .ok_or(err_msg("opponent not in instance")) + } + + pub fn vbox_action_allowed(&self, account: Uuid) -> Result<(), Error> { + if self.players.iter().find(|p| p.id == account).is_none() { + return Err(err_msg("player not in this instance")); + } + + if self.phase == InstancePhase::Lobby { + return Err(err_msg("game not yet started")); + } + + if self.current_game_id().is_some() { + return Err(err_msg("you cannot perform vbox actions while in a game")); + } + + Ok(()) + } + + pub fn vbox_refill(mut self, account: Uuid) -> Result { + self.vbox_action_allowed(account)?; + self.account_player(account)? + .vbox_refill()?; + Ok(self) + } + + pub fn vbox_buy(mut self, account: Uuid, group: vbox::ItemType, index: String, construct_id: Option) -> Result { + self.vbox_action_allowed(account)?; + self.account_player(account)? + .vbox_buy(group, index, construct_id)?; + Ok(self) + } + + pub fn vbox_combine(mut self, account: Uuid, inv_indices: Vec, vbox_indices: vbox::VboxIndices) -> Result { + self.vbox_action_allowed(account)?; + self.account_player(account)? + .vbox_combine(inv_indices, vbox_indices)?; + Ok(self) + } + + pub fn vbox_refund(mut self, account: Uuid, index: String) -> Result { + self.vbox_action_allowed(account)?; + self.account_player(account)? + .vbox_refund(index)?; + Ok(self) + } + + pub fn vbox_apply(mut self, account: Uuid, index: String, construct_id: Uuid) -> Result { + self.vbox_action_allowed(account)?; + self.account_player(account)? + .vbox_equip(index, construct_id)?; + Ok(self) + } + + pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct_id: Option) -> Result { + self.vbox_action_allowed(account)?; + self.account_player(account)? + .vbox_unequip(target, construct_id, target_construct_id)?; + Ok(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn instance_pve_test() { + let mut instance = Instance::new(); + + let bot = bot_player(); + let bot_one = bot.id; + instance.add_player(bot).unwrap(); + + let bot = bot_player(); + let bot_two = bot.id; + instance.add_player(bot).unwrap(); + + assert_eq!(instance.phase, InstancePhase::Lobby); + instance.player_ready(bot_one).unwrap(); + instance.player_ready(bot_two).unwrap(); + + assert_eq!(instance.phase, InstancePhase::Finished); + } + + #[test] + fn instance_bot_vbox_test() { + let _instance = Instance::new(); + let player_account = Uuid::new_v4(); + let constructs = instance_mobs(player_account); + let _player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); + } + + #[test] + fn instance_start_test() { + let mut instance = Instance::new(); + + assert_eq!(instance.max_players, 2); + + let player_account = Uuid::new_v4(); + let constructs = instance_mobs(player_account); + let player = Player::new(player_account, &"a".to_string(), constructs); + let a_id = player.id; + + instance.add_player(player).expect("could not add player"); + assert!(!instance.can_start()); + + let player_account = Uuid::new_v4(); + let constructs = instance_mobs(player_account); + let player = Player::new(player_account, &"b".to_string(), constructs); + let b_id = player.id; + + instance.add_player(player).expect("could not add player"); + + assert_eq!(instance.phase, InstancePhase::Lobby); + instance.player_ready(a_id).expect("a ready"); + assert!(!instance.can_start()); + + instance.player_ready(b_id).expect("b ready"); + assert_eq!(instance.phase, InstancePhase::InProgress); + + assert!(!instance.can_start()); + + instance.players[0].autobuy(); + instance.players[1].autobuy(); + + instance.player_ready(a_id).expect("a ready"); + let game = instance.player_ready(b_id).expect("b ready"); + + assert!(game.is_some()); + } + + #[test] + fn instance_upkeep_test() { + let mut instance = Instance::new(); + + let player_account = Uuid::new_v4(); + let constructs = instance_mobs(player_account); + let player = Player::new(player_account, &"a".to_string(), constructs); + let a_id = player.id; + + instance.add_player(player).expect("could not add player"); + assert!(!instance.can_start()); + + let player_account = Uuid::new_v4(); + let constructs = instance_mobs(player_account); + let player = Player::new(player_account, &"b".to_string(), constructs); + let b_id = player.id; + instance.add_player(player).expect("could not add player"); + + instance.players[0].autobuy(); + + instance.player_ready(a_id).expect("a ready"); + instance.player_ready(b_id).expect("b ready"); + + instance.phase_end = Some(Utc::now().checked_sub_signed(Duration::seconds(500)).unwrap()); + + let (mut instance, new_games) = instance.upkeep(); + + assert!(new_games.is_some()); + + let game = new_games.unwrap(); + assert!(game.finished()); + + instance.game_finished(&game).unwrap(); + + assert_eq!(instance.rounds.len(), 2); + assert!(instance.players.iter().all(|p| !p.ready)); + + // info!("{:#?}", instance); + } + + #[test] + fn instance_upkeep_idle_lobby_test() { + let mut instance = Instance::new(); + + let player_account = Uuid::new_v4(); + let constructs = instance_mobs(player_account); + let player = Player::new(player_account, &"a".to_string(), constructs); + let _a_id = player.id; + + instance.add_player(player).expect("could not add player"); + assert!(!instance.can_start()); + + instance.phase_end = Some(Utc::now().checked_sub_signed(Duration::minutes(61)).unwrap()); + let (instance, _new_games) = instance.upkeep(); + + assert!(instance.finished()); + } +} diff --git a/core/src/item.rs b/core/src/item.rs new file mode 100644 index 00000000..72639073 --- /dev/null +++ b/core/src/item.rs @@ -0,0 +1,1581 @@ +use skill::{Skill}; +use spec::{Spec, SpecValues}; +use construct::{Colours}; +use effect::{Colour, Cooldown}; + +#[derive(Debug,Copy,Clone,Serialize,Deserialize,PartialEq,PartialOrd,Ord,Eq)] +pub enum Item { + // colours + Blue, + Green, + Red, + + // base skills + Attack, + Block, + Stun, + Buff, + Debuff, + + // specs + // Base + Power, + Life, + Speed, + + // Lifes Upgrades + LifeGG, + LifeRR, + LifeBB, + LifeRG, + LifeGB, + LifeRB, + LifeGGPlus, + LifeRRPlus, + LifeBBPlus, + LifeRGPlus, + LifeGBPlus, + LifeRBPlus, + LifeGGPlusPlus, + LifeRRPlusPlus, + LifeBBPlusPlus, + LifeRGPlusPlus, + LifeGBPlusPlus, + LifeRBPlusPlus, + + // Power Upgrades + PowerGG, + PowerRR, + PowerBB, + PowerRG, + PowerGB, + PowerRB, + PowerGGPlus, + PowerRRPlus, + PowerBBPlus, + PowerRGPlus, + PowerGBPlus, + PowerRBPlus, + PowerGGPlusPlus, + PowerRRPlusPlus, + PowerBBPlusPlus, + PowerRGPlusPlus, + PowerGBPlusPlus, + PowerRBPlusPlus, + + // Speed Upgrades + SpeedGG, + SpeedRR, + SpeedBB, + SpeedRG, + SpeedGB, + SpeedRB, + SpeedGGPlus, + SpeedRRPlus, + SpeedBBPlus, + SpeedRGPlus, + SpeedGBPlus, + SpeedRBPlus, + SpeedGGPlusPlus, + SpeedRRPlusPlus, + SpeedBBPlusPlus, + SpeedRGPlusPlus, + SpeedGBPlusPlus, + SpeedRBPlusPlus, + + Amplify, + #[serde(rename = "Amplify+")] + AmplifyPlus, + #[serde(rename = "Amplify++")] + AmplifyPlusPlus, + + Absorb, + #[serde(rename = "Absorb+")] + AbsorbPlus, + #[serde(rename = "Absorb++")] + AbsorbPlusPlus, + + Banish, + #[serde(rename = "Banish+")] + BanishPlus, + #[serde(rename = "Banish++")] + BanishPlusPlus, + + Bash, + #[serde(rename = "Bash+")] + BashPlus, + #[serde(rename = "Bash++")] + BashPlusPlus, + + Blast, + #[serde(rename = "Blast+")] + BlastPlus, + #[serde(rename = "Blast++")] + BlastPlusPlus, + + Chaos, + #[serde(rename = "Chaos+")] + ChaosPlus, + #[serde(rename = "Chaos++")] + ChaosPlusPlus, + + Sustain, + #[serde(rename = "Sustain+")] + SustainPlus, + #[serde(rename = "Sustain++")] + SustainPlusPlus, + + Electrify, + #[serde(rename = "Electrify+")] + ElectrifyPlus, + #[serde(rename = "Electrify++")] + ElectrifyPlusPlus, + + Curse, + #[serde(rename = "Curse+")] + CursePlus, + #[serde(rename = "Curse++")] + CursePlusPlus, + + Decay, + #[serde(rename = "Decay+")] + DecayPlus, + #[serde(rename = "Decay++")] + DecayPlusPlus, + + Haste, + #[serde(rename = "Haste+")] + HastePlus, + #[serde(rename = "Haste++")] + HastePlusPlus, + + Heal, + #[serde(rename = "Heal+")] + HealPlus, + #[serde(rename = "Heal++")] + HealPlusPlus, + + Hybrid, + #[serde(rename = "Hybrid+")] + HybridPlus, + #[serde(rename = "Hybrid++")] + HybridPlusPlus, + + Invert, + #[serde(rename = "Invert+")] + InvertPlus, + #[serde(rename = "Invert++")] + InvertPlusPlus, + + Counter, + #[serde(rename = "Counter+")] + CounterPlus, + #[serde(rename = "Counter++")] + CounterPlusPlus, + + Purge, + #[serde(rename = "Purge+")] + PurgePlus, + #[serde(rename = "Purge++")] + PurgePlusPlus, + + Purify, + #[serde(rename = "Purify+")] + PurifyPlus, + #[serde(rename = "Purify++")] + PurifyPlusPlus, + + Reflect, + #[serde(rename = "Reflect+")] + ReflectPlus, + #[serde(rename = "Reflect++")] + ReflectPlusPlus, + + Recharge, + #[serde(rename = "Recharge+")] + RechargePlus, + #[serde(rename = "Recharge++")] + RechargePlusPlus, + + Ruin, + #[serde(rename = "Ruin+")] + RuinPlus, + #[serde(rename = "Ruin++")] + RuinPlusPlus, + + Link, + #[serde(rename = "Link+")] + LinkPlus, + #[serde(rename = "Link++")] + LinkPlusPlus, + + Silence, + #[serde(rename = "Silence+")] + SilencePlus, + #[serde(rename = "Silence++")] + SilencePlusPlus, + + Slay, + #[serde(rename = "Slay+")] + SlayPlus, + #[serde(rename = "Slay++")] + SlayPlusPlus, + + Sleep, + #[serde(rename = "Sleep+")] + SleepPlus, + #[serde(rename = "Sleep++")] + SleepPlusPlus, + + Restrict, + #[serde(rename = "Restrict+")] + RestrictPlus, + #[serde(rename = "Restrict++")] + RestrictPlusPlus, + + Strike, + #[serde(rename = "Strike+")] + StrikePlus, + #[serde(rename = "Strike++")] + StrikePlusPlus, + + Siphon, + #[serde(rename = "Siphon+")] + SiphonPlus, + #[serde(rename = "Siphon++")] + SiphonPlusPlus, + + Intercept, + #[serde(rename = "Intercept+")] + InterceptPlus, + #[serde(rename = "Intercept++")] + InterceptPlusPlus, + + Break, + #[serde(rename = "Break+")] + BreakPlus, + #[serde(rename = "Break++")] + BreakPlusPlus, + + Triage, + #[serde(rename = "Triage+")] + TriagePlus, + #[serde(rename = "Triage++")] + TriagePlusPlus, +} + +pub enum ItemEffect { + Skill, + Spec, +} + +impl Item { + pub fn colours(&self, count: &mut Colours) { + let combos = get_combos(); + let combo = combos.iter().find(|c| c.item == *self); + match combo { + Some(c) => c.components.iter().for_each(|unit| match unit { + Item::Red => count.red += 1, + Item::Blue => count.blue += 1, + Item::Green => count.green += 1, + _ => { + let mut combo_count = Colours::new(); + unit.colours(&mut combo_count); + count.red += combo_count.red; + count.blue += combo_count.blue; + count.green += combo_count.green; + } + }), + None => (), + } + } + + pub fn components(&self) -> Vec { + let combos = get_combos(); + let combo = combos.iter().find(|c| c.item == *self); + + match combo { + Some(c) => c.components.iter().flat_map(|c| c.components()).collect::>(), + None => vec![*self], + } + } + + pub fn cost(&self) -> usize { + match self { + Item::Red => 1, + Item::Green => 1, + Item::Blue => 1, + + Item::Attack => 2, + Item::Block => 2, + Item::Buff => 2, + Item::Debuff => 2, + Item::Stun => 2, + + Item::Power => 3, + Item::Life => 3, + Item::Speed => 3, + + _ => { + let combos = get_combos(); + let combo = combos.iter().find(|c| c.item == *self) + .unwrap_or_else(|| panic!("unable to find components for {:?}", self)); + return combo.components.iter().fold(0, |acc, c| { + match c { + Item::Attack | + Item::Block | + Item::Buff | + Item::Debuff | + Item::Stun => acc, + Item::Power | + Item::Life | + Item::Speed => acc + 1, + _ => acc + c.cost(), + } + }); + }, + } + } + + pub fn base_speed(&self) -> u64 { + match self { + Item::Attack => 1, + Item::Stun => 2, + Item::Block => 3, + Item::Buff | + Item::Debuff => 4, + Item::Blue => 1, + Item::Green => 2, + Item::Red => 3, + _ => 0, + } + } + + pub fn speed(&self) -> u64 { + match self { + Item::Attack | + Item::Stun | + Item::Block | + Item::Buff | + Item::Debuff => 24 + self.base_speed(), + _ => { + let combos = get_combos(); + let combo = combos.iter().find(|c| c.item == *self) + .unwrap_or_else(|| panic!("unable to find components for {:?}", self)); + + let mut colour_speed = 0; + let mut skill_speed = 0; + let mut component_speed = 0; + + combo.components.iter().for_each(|unit| { + colour_speed += match unit { + Item::Red | + Item::Green | + Item::Blue => unit.base_speed(), + _ => 0, + }; + skill_speed += match unit { + Item::Attack | + Item::Stun | + Item::Block | + Item::Buff | + Item::Debuff => unit.base_speed(), + _ => 0, + }; + if colour_speed == 0 && skill_speed == 0 { + component_speed = unit.speed(); + } + }); + if component_speed > 0 { return component_speed }; + return 24 + colour_speed * skill_speed + } + } + + } + + pub fn effect(&self) -> Option { + if let Some(_skill) = self.into_skill() { + return Some(ItemEffect::Skill); + } + if let Some(_spec) = self.into_spec() { + return Some(ItemEffect::Spec); + } + return None; + } + + pub fn into_skill(&self) -> Option { + match self { + Item::Absorb => Some(Skill::Absorb), + Item::AbsorbPlus => Some(Skill::AbsorbPlus), + Item::AbsorbPlusPlus => Some(Skill::AbsorbPlusPlus), + Item::Amplify => Some(Skill::Amplify), + Item::AmplifyPlus => Some(Skill::AmplifyPlus), + Item::AmplifyPlusPlus => Some(Skill::AmplifyPlusPlus), + Item::Attack => Some(Skill::Attack), + Item::Banish => Some(Skill::Banish), + Item::BanishPlus => Some(Skill::BanishPlus), + Item::BanishPlusPlus => Some(Skill::BanishPlusPlus), + Item::Bash => Some(Skill::Bash), + Item::BashPlus => Some(Skill::BashPlus), + Item::BashPlusPlus => Some(Skill::BashPlusPlus), + Item::Blast => Some(Skill::Blast), + Item::BlastPlus => Some(Skill::BlastPlus), + Item::BlastPlusPlus => Some(Skill::BlastPlusPlus), + Item::Block => Some(Skill::Block), + Item::Buff => Some(Skill::Buff), + Item::Chaos => Some(Skill::Chaos), + Item::ChaosPlus => Some(Skill::ChaosPlus), + Item::ChaosPlusPlus => Some(Skill::ChaosPlusPlus), + Item::Counter => Some(Skill::Counter), + Item::CounterPlus => Some(Skill::CounterPlus), + Item::CounterPlusPlus => Some(Skill::CounterPlusPlus), + Item::Curse => Some(Skill::Curse), + Item::CursePlus => Some(Skill::CursePlus), + Item::CursePlusPlus => Some(Skill::CursePlusPlus), + Item::Debuff => Some(Skill::Debuff), + Item::Decay => Some(Skill::Decay), + Item::DecayPlus => Some(Skill::DecayPlus), + Item::DecayPlusPlus => Some(Skill::DecayPlusPlus), + Item::Electrify => Some(Skill::Electrify), + Item::ElectrifyPlus => Some(Skill::ElectrifyPlus), + Item::ElectrifyPlusPlus => Some(Skill::ElectrifyPlusPlus), + Item::Haste => Some(Skill::Haste), + Item::HastePlus => Some(Skill::HastePlus), + Item::HastePlusPlus => Some(Skill::HastePlusPlus), + Item::Heal => Some(Skill::Heal), + Item::HealPlus => Some(Skill::HealPlus), + Item::HealPlusPlus => Some(Skill::HealPlusPlus), + Item::Hybrid => Some(Skill::Hybrid), + Item::HybridPlus => Some(Skill::HybridPlus), + Item::HybridPlusPlus => Some(Skill::HybridPlusPlus), + Item::Intercept => Some(Skill::Intercept), + Item::InterceptPlus => Some(Skill::InterceptPlus), + Item::InterceptPlusPlus => Some(Skill::InterceptPlusPlus), + Item::Invert => Some(Skill::Invert), + Item::InvertPlus => Some(Skill::InvertPlus), + Item::InvertPlusPlus => Some(Skill::InvertPlusPlus), + Item::Purge => Some(Skill::Purge), + Item::PurgePlus => Some(Skill::PurgePlus), + Item::PurgePlusPlus => Some(Skill::PurgePlusPlus), + Item::Purify => Some(Skill::Purify), + Item::PurifyPlus => Some(Skill::PurifyPlus), + Item::PurifyPlusPlus => Some(Skill::PurifyPlusPlus), + Item::Recharge => Some(Skill::Recharge), + Item::RechargePlus => Some(Skill::RechargePlus), + Item::RechargePlusPlus => Some(Skill::RechargePlusPlus), + Item::Reflect => Some(Skill::Reflect), + Item::ReflectPlus => Some(Skill::ReflectPlus), + Item::ReflectPlusPlus => Some(Skill::ReflectPlusPlus), + Item::Restrict => Some(Skill::Restrict), + Item::RestrictPlus => Some(Skill::RestrictPlus), + Item::RestrictPlusPlus => Some(Skill::RestrictPlusPlus), + Item::Ruin => Some(Skill::Ruin), + Item::RuinPlus => Some(Skill::RuinPlus), + Item::RuinPlusPlus => Some(Skill::RuinPlusPlus), + Item::Link => Some(Skill::Link), + Item::LinkPlus => Some(Skill::LinkPlus), + Item::LinkPlusPlus => Some(Skill::LinkPlusPlus), + Item::Silence => Some(Skill::Silence), + Item::SilencePlus => Some(Skill::SilencePlus), + Item::SilencePlusPlus => Some(Skill::SilencePlusPlus), + Item::Siphon => Some(Skill::Siphon), + Item::SiphonPlus => Some(Skill::SiphonPlus), + Item::SiphonPlusPlus => Some(Skill::SiphonPlusPlus), + Item::Slay => Some(Skill::Slay), + Item::SlayPlus => Some(Skill::SlayPlus), + Item::SlayPlusPlus => Some(Skill::SlayPlusPlus), + Item::Sleep => Some(Skill::Sleep), + Item::SleepPlus => Some(Skill::SleepPlus), + Item::SleepPlusPlus => Some(Skill::SleepPlusPlus), + Item::Strike => Some(Skill::Strike), + Item::StrikePlus => Some(Skill::StrikePlus), + Item::StrikePlusPlus => Some(Skill::StrikePlusPlus), + Item::Stun => Some(Skill::Stun), + Item::Sustain => Some(Skill::Sustain), + Item::SustainPlus => Some(Skill::SustainPlus), + Item::SustainPlusPlus => Some(Skill::SustainPlusPlus), + Item::Break => Some(Skill::Break), + Item::BreakPlus => Some(Skill::BreakPlus), + Item::BreakPlusPlus => Some(Skill::BreakPlusPlus), + Item::Triage => Some(Skill::Triage), + Item::TriagePlus => Some(Skill::TriagePlus), + Item::TriagePlusPlus => Some(Skill::TriagePlusPlus), + _ => None, + } + } + + pub fn into_spec(&self) -> Option { + match *self { + Item::Speed => Some(Spec::Speed), + Item::SpeedRR => Some(Spec::SpeedRR), + Item::SpeedBB => Some(Spec::SpeedBB), + Item::SpeedGG => Some(Spec::SpeedGG), + Item::SpeedRG => Some(Spec::SpeedRG), + Item::SpeedGB => Some(Spec::SpeedGB), + Item::SpeedRB => Some(Spec::SpeedRB), + + Item::SpeedRRPlus => Some(Spec::SpeedRRPlus), + Item::SpeedBBPlus => Some(Spec::SpeedBBPlus), + Item::SpeedGGPlus => Some(Spec::SpeedGGPlus), + Item::SpeedRGPlus => Some(Spec::SpeedRGPlus), + Item::SpeedGBPlus => Some(Spec::SpeedGBPlus), + Item::SpeedRBPlus => Some(Spec::SpeedRBPlus), + + Item::SpeedRRPlusPlus => Some(Spec::SpeedRRPlusPlus), + Item::SpeedBBPlusPlus => Some(Spec::SpeedBBPlusPlus), + Item::SpeedGGPlusPlus => Some(Spec::SpeedGGPlusPlus), + Item::SpeedRGPlusPlus => Some(Spec::SpeedRGPlusPlus), + Item::SpeedGBPlusPlus => Some(Spec::SpeedGBPlusPlus), + Item::SpeedRBPlusPlus => Some(Spec::SpeedRBPlusPlus), + + Item::Power => Some(Spec::Power), + Item::PowerRR => Some(Spec::PowerRR), + Item::PowerBB => Some(Spec::PowerBB), + Item::PowerGG => Some(Spec::PowerGG), + Item::PowerRG => Some(Spec::PowerRG), + Item::PowerGB => Some(Spec::PowerGB), + Item::PowerRB => Some(Spec::PowerRB), + Item::PowerRRPlus => Some(Spec::PowerRRPlus), + Item::PowerBBPlus => Some(Spec::PowerBBPlus), + Item::PowerGGPlus => Some(Spec::PowerGGPlus), + Item::PowerRGPlus => Some(Spec::PowerRGPlus), + Item::PowerGBPlus => Some(Spec::PowerGBPlus), + Item::PowerRBPlus => Some(Spec::PowerRBPlus), + Item::PowerRRPlusPlus => Some(Spec::PowerRRPlusPlus), + Item::PowerBBPlusPlus => Some(Spec::PowerBBPlusPlus), + Item::PowerGGPlusPlus => Some(Spec::PowerGGPlusPlus), + Item::PowerRGPlusPlus => Some(Spec::PowerRGPlusPlus), + Item::PowerGBPlusPlus => Some(Spec::PowerGBPlusPlus), + Item::PowerRBPlusPlus => Some(Spec::PowerRBPlusPlus), + + Item::Life => Some(Spec::Life), + Item::LifeRG => Some(Spec::LifeRG), + Item::LifeGB => Some(Spec::LifeGB), + Item::LifeRB => Some(Spec::LifeRB), + Item::LifeGG => Some(Spec::LifeGG), + Item::LifeRR => Some(Spec::LifeRR), + Item::LifeBB => Some(Spec::LifeBB), + Item::LifeRGPlus => Some(Spec::LifeRGPlus), + Item::LifeGBPlus => Some(Spec::LifeGBPlus), + Item::LifeRBPlus => Some(Spec::LifeRBPlus), + Item::LifeGGPlus => Some(Spec::LifeGGPlus), + Item::LifeRRPlus => Some(Spec::LifeRRPlus), + Item::LifeBBPlus => Some(Spec::LifeBBPlus), + Item::LifeRGPlusPlus => Some(Spec::LifeRGPlusPlus), + Item::LifeGBPlusPlus => Some(Spec::LifeGBPlusPlus), + Item::LifeRBPlusPlus => Some(Spec::LifeRBPlusPlus), + Item::LifeGGPlusPlus => Some(Spec::LifeGGPlusPlus), + Item::LifeRRPlusPlus => Some(Spec::LifeRRPlusPlus), + Item::LifeBBPlusPlus => Some(Spec::LifeBBPlusPlus), + + _ => None, + } + } + + pub fn into_colour(&self) -> Colour { + match *self { + Item::Red => Colour::Red, + Item::Green => Colour::Green, + Item::Blue => Colour::Blue, + _ => panic!("{:?} is not a colour", self), + } + } + + pub fn into_description(&self) -> String { + match self { + // colours + Item::Blue => format!("Combine two colours with a white base item to create a new combo. \n Slow speed, magical type. Deterrents and destruction."), + Item::Green => format!("Combine two colours with a white base item to create a new combo.\n Normal speed, healing type. Protection and trickery."), + Item::Red => format!("Combine two colours with a white base item to create a new combo. \n Fast speed, physical type. Chaos and momentum."), + + // base skills + Item::Attack => format!("Deal {:?}% RedPower as red damage.", + self.into_skill().unwrap().multiplier()), + Item::Block => format!("Reduce red damage and blue damage taken by {:?}%. Block lasts {:?}T", + 100 - self.into_skill().unwrap().effect()[0].get_multiplier(), + self.into_skill().unwrap().effect()[0].get_duration()), + + + Item::Stun => format!("Stun target construct for {:?}T.", + self.into_skill().unwrap().effect()[0].get_duration()), + Item::Buff => format!("Increase target construct RedPower BluePower SpeedStat by {:?}%. Buff lasts {:?}T", + self.into_skill().unwrap().effect()[0].get_multiplier() - 100, + self.into_skill().unwrap().effect()[0].get_duration()), + + Item::Debuff => format!("Slows the target reducing SpeedStat by {:?}%. Debuff lasts {:?}T", + 100 - self.into_skill().unwrap().effect()[0].get_multiplier(), + self.into_skill().unwrap().effect()[0].get_duration()), + // specs + // Base + Item::Power => format!("Increases all power stats by {:?}%. + Power determines the base damage and healing of your construct skills.", + self.into_spec().unwrap().values().base()), + Item::Life => format!("Increases construct GreenLife by {:?}. + When your construct reaches 0 GreenLife it is knocked out and cannot cast skills.", + self.into_spec().unwrap().values().base()), + Item::Speed => format!("Increases construct speed by {:?}%. + Speed SpeedStat determines the order in which skills resolve.", + self.into_spec().unwrap().values().base()), + + // Lifes Upgrades + Item::LifeGG | + Item::LifeGGPlus | + Item::LifeGGPlusPlus => format!("Increases construct GreenLife by {:?}. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + + Item::LifeRR | + Item::LifeRRPlus | + Item::LifeRRPlusPlus => format!("Increases construct RedLife by {:?}. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + + Item::LifeBB | + Item::LifeBBPlus | + Item::LifeBBPlusPlus => format!("Increases construct BlueLife by {:?}. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + + Item::LifeRG | + Item::LifeRGPlus | + Item::LifeRGPlusPlus => format!("Increases construct RedLife and GreenLife by {:?}. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + + Item::LifeGB | + Item::LifeGBPlus | + Item::LifeGBPlusPlus => format!("Increases construct GreenLife and BlueLife by {:?}. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + + Item::LifeRB | + Item::LifeRBPlus | + Item::LifeRBPlusPlus => format!("Increases construct RedLife and BlueLife by {:?}. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + + // Power Upgrades + Item::PowerRR | + Item::PowerRRPlus | + Item::PowerRRPlusPlus => format!("Increases construct RedPower by {:?}%. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + Item::PowerBB | + Item::PowerBBPlus | + Item::PowerBBPlusPlus => format!("Increases construct BluePower by {:?}%. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + + Item::PowerGG | + Item::PowerGGPlus | + Item::PowerGGPlusPlus => format!("Increases construct GreenPower by {:?}%. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + Item::PowerRG | + Item::PowerRGPlus | + Item::PowerRGPlusPlus => format!("Increases construct GreenPower and RedPower by {:?}%. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + Item::PowerGB | + Item::PowerGBPlus | + Item::PowerGBPlusPlus => format!("Increases construct GreenPower and BluePower by {:?}%. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + Item::PowerRB | + Item::PowerRBPlus | + Item::PowerRBPlusPlus => format!("Increases construct RedPower and BluePower by {:?}%. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + + // Speed Upgrades + Item::SpeedRR | + Item::SpeedBB | + Item::SpeedGG | + Item::SpeedRG | + Item::SpeedGB | + Item::SpeedRB | + Item::SpeedRRPlus | + Item::SpeedBBPlus | + Item::SpeedGGPlus | + Item::SpeedRGPlus | + Item::SpeedGBPlus | + Item::SpeedRBPlus | + Item::SpeedRRPlusPlus | + Item::SpeedBBPlusPlus | + Item::SpeedGGPlusPlus | + Item::SpeedRGPlusPlus | + Item::SpeedGBPlusPlus | + Item::SpeedRBPlusPlus => format!("Increases construct SpeedStat by {:?}%. + If your team meets total colour thresholds the spec provides additional bonuses.", + self.into_spec().unwrap().values().base()), + + // Skills <- need to move effect mulltipliers into skills + Item::Amplify| + Item::AmplifyPlus | + Item::AmplifyPlusPlus => format!("Increase RedPower BluePower by {:?}%. Lasts {:?}T.", + self.into_skill().unwrap().effect()[0].get_multiplier() - 100, + self.into_skill().unwrap().effect()[0].get_duration()), + + Item::Banish| + Item::BanishPlus | + Item::BanishPlusPlus => format!("Banish target for {:?}T. + Deal {:?}% target RedLife and BlueLife as red and blue damage respectively. + Banished constructs are immune to all skills and effects.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().multiplier()), + + Item::Blast| + Item::BlastPlus | + Item::BlastPlusPlus => format!("Deals {:?}% BluePower as blue damage.", self.into_skill().unwrap().multiplier()), + + Item::Chaos| + Item::ChaosPlus | + Item::ChaosPlusPlus => format!( + "Hits twice for red and blue damage. Damage {:?}% RedPower and BluePower. + Randomly deals 0 to 30% more damage.", + self.into_skill().unwrap().multiplier()), + + Item::Sustain| + Item::SustainPlus | + Item::SustainPlusPlus => format!( + "Construct cannot be KO'd while active and provides immunity to disables. Lasts {:?}T. + Recharges target RedLife based on {:?}% RedPower.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().multiplier()), + + Item::Electrify| + Item::ElectrifyPlus | + Item::ElectrifyPlusPlus => format!( + "Applies electrify for {:?}T. + If a construct with electrify takes direct damage they will apply an electrocute debuff to the caster. + Electrocute deals {:?}% BluePower as BlueDamage per turn for {:?}T.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), + self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_duration()), + + Item::Curse| + Item::CursePlus | + Item::CursePlusPlus => format!( + "Increases red and blue damage taken by {:?}%. Lasts {:?}T.", + self.into_skill().unwrap().effect()[0].get_multiplier() - 100, + self.into_skill().unwrap().effect()[0].get_duration()), + + Item::Decay| + Item::DecayPlus | + Item::DecayPlusPlus => format!( + "Reduces healing taken by {:?}% for {:?}T. + Deals blue damage {:?}% BluePower each turn for {:?}T.", + 100 - self.into_skill().unwrap().effect()[0].get_multiplier(), + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().effect()[1].get_skill().unwrap().multiplier(), + self.into_skill().unwrap().effect()[1].get_duration()), + + Item::Absorb| + Item::AbsorbPlus | + Item::AbsorbPlusPlus => format!( + "Gain Absorb for {:?}T. Taking damage replaces Absorb with Absorption. + Absorption increases RedPower and BluePower based on damage taken. + Absorption lasts {:?}T. Recharges BlueLife based on {:?}% BluePower.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().multiplier()), + + Item::Haste| + Item::HastePlus | + Item::HastePlusPlus => format!( + "Haste increases SpeedStat by {:?}%. + Red Attack based skills will strike again dealing {:?}% SpeedStat as red damage. + Lasts {:?}T", + self.into_skill().unwrap().effect()[0].get_multiplier() - 100, + Skill::HasteStrike.multiplier(), + self.into_skill().unwrap().effect()[0].get_duration()), + + Item::Heal| + Item::HealPlus | + Item::HealPlusPlus => format!("Heals target for {:?}% GreenPower.", self.into_skill().unwrap().multiplier()), + + Item::Hybrid| + Item::HybridPlus | + Item::HybridPlusPlus => format!( + "Hybrid increases GreenPower by {:?}%. + Blue based Attack skills will blast again dealing {:?}% GreenPower as blue damage. + Lasts {:?}T.", + self.into_skill().unwrap().effect()[0].get_multiplier() - 100, + Skill::HybridBlast.multiplier(), + self.into_skill().unwrap().effect()[0].get_duration()), + + Item::Invert| + Item::InvertPlus | + Item::InvertPlusPlus => format!( + "Reverse healing/recharge into damage and damage into healing/recharge. + Any excess red or blue damage is converted into shield recharge after healing. + Lasts {:?}T.", + self.into_skill().unwrap().effect()[0].get_duration()), + + Item::Counter| + Item::CounterPlus | + Item::CounterPlusPlus => format!( + "Applies counter for {:?}T. + Red damage taken during counter will trigger a counter attack. + Counter attack deals {:?}% RedPower as red damage.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier()), + + Item::Purge| + Item::PurgePlus | + Item::PurgePlusPlus => format!( + "Remove all effects from target construct. + Applies purge disabling target green skills for {:?}T.", + self.into_skill().unwrap().effect()[0].get_duration()), + + Item::Purify| + Item::PurifyPlus | + Item::PurifyPlusPlus => format!( + "Remove all effects and heals for {:?}% GreenPower per effect removed. + Applies Pure increasing healing taken by {:?}%.", + self.into_skill().unwrap().multiplier(), + self.into_skill().unwrap().effect()[0].get_multiplier() - 100), + + Item::Reflect| + Item::ReflectPlus | + Item::ReflectPlusPlus => format!( + "Reflect incoming blue skills to source. Lasts {:?}T. + Recharges target BlueLife based on {:?}% BluePower.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().multiplier()), + + Item::Recharge| + Item::RechargePlus | + Item::RechargePlusPlus => format!( + "Recharge RedLife and BlueLife based on {:?}% RedPower and BluePower.", + self.into_skill().unwrap().multiplier()), + + Item::Ruin| + Item::RuinPlus | + Item::RuinPlusPlus => format!( + "Team wide skill. Stun each construct for {:?}T. + Deal {:?}% BluePower as blue damage to each construct.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().multiplier()), + + Item::Link| + Item::LinkPlus | + Item::LinkPlusPlus => format!( + "Stun target for {:?}T. + Deal blue damage of {:?}% BluePower multiplied by number of effects on target.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().multiplier()), + + Item::Silence| + Item::SilencePlus | + Item::SilencePlusPlus => format!( + "Disable the target from using blue skills for {:?}T and deals {:?}% BluePower as blue damage. + Deals 45% more Damage per blue skill on target.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().multiplier()), + + Item::Slay| + Item::SlayPlus | + Item::SlayPlusPlus => format!( + "Deals {:?}% RedPower + {:?}% GreenPower as red damage. + Construct heals self for 50% of damage dealt to target construct GreenLife.", + self.into_skill().unwrap().multiplier(), + self.into_skill().unwrap().multiplier()), + + Item::Sleep| + Item::SleepPlus | + Item::SleepPlusPlus => format!( + "Stun for {:?}T and heal target for {:?}% GreenPower.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().multiplier()), + + Item::Restrict| + Item::RestrictPlus | + Item::RestrictPlusPlus => format!( + "Disable the target from using red skills for {:?}T and deals {:?}% RedPower as red damage. + Deals 35% more damage per red skill on target.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().multiplier()), + + Item::Bash| + Item::BashPlus | + Item::BashPlusPlus => format!( + "Bash the target increasing the cooldowns of target skills by 1T. Stuns target for {:?}T. + Deals {:?}% RedPower as red damage and 45% more damage per cooldown increased.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier()), + + Item::Strike| + Item::StrikePlus | + Item::StrikePlusPlus => format!( + "Strike the target with speed dealing {:?}% RedPower as red damage.", + self.into_skill().unwrap().multiplier()), + + Item::Siphon| + Item::SiphonPlus | + Item::SiphonPlusPlus => format!( + "Deals {:?}% BluePower + {:?}% GreenPower as blue damage each turn. + Construct heals self for 100% of damage dealt to target construct GreenLife. + Lasts {:?}T.", + self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), + self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), + self.into_skill().unwrap().effect()[0].get_duration()), + + Item::Intercept| + Item::InterceptPlus | + Item::InterceptPlusPlus => format!( + "Intercept redirects skills against the team to target, lasts {:?}T. + Recharges RedLife for {:?} RedPower.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().multiplier()), + + Item::Break| + Item::BreakPlus | + Item::BreakPlusPlus => format!( + "Stun the target for {:?}T and applies Vulnerable increasing red damage taken by {:?}% for {:?}T.", + self.into_skill().unwrap().effect()[0].get_duration(), + self.into_skill().unwrap().effect()[1].get_multiplier() - 100, + self.into_skill().unwrap().effect()[1].get_duration()), + + Item::Triage| + Item::TriagePlus | + Item::TriagePlusPlus => format!( + "Heals target for {:?}% GreenPower each turn. Lasts {:?}T.", + self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), + self.into_skill().unwrap().effect()[0].get_duration()), + } + } + + // !!!!!! + // IF YOU CHANGE A COMBO + // BE SURE TO EDIT BUTTONS.JSX TOO + // !!!!!! + + fn combo(&self) -> Vec { + match self { + Item::Intercept => vec![Item::Buff, Item::Red, Item::Red], + Item::Triage => vec![Item::Buff, Item::Green, Item::Green], + Item::Absorb => vec![Item::Buff, Item::Blue, Item::Blue], + Item::Amplify => vec![Item::Buff, Item::Red, Item::Blue], + Item::Haste => vec![Item::Buff, Item::Red, Item::Green], + Item::Hybrid => vec![Item::Buff, Item::Green, Item::Blue], + Item::InterceptPlus => vec![Item::Intercept, Item::Intercept, Item::Intercept], + Item::InterceptPlusPlus => vec![Item::InterceptPlus, Item::InterceptPlus, Item::InterceptPlus], + Item::TriagePlus => vec![Item::Triage, Item::Triage, Item::Triage], + Item::TriagePlusPlus => vec![Item::TriagePlus, Item::TriagePlus, Item::TriagePlus], + Item::HastePlus => vec![Item::Haste, Item::Haste, Item::Haste], + Item::HastePlusPlus => vec![Item::HastePlus, Item::HastePlus, Item::HastePlus], + Item::HybridPlus => vec![Item::Hybrid, Item::Hybrid, Item::Hybrid], + Item::HybridPlusPlus => vec![Item::HybridPlus, Item::HybridPlus, Item::HybridPlus], + Item::AbsorbPlus => vec![Item::Absorb, Item::Absorb, Item::Absorb], + Item::AbsorbPlusPlus => vec![Item::AbsorbPlus, Item::AbsorbPlus, Item::AbsorbPlus], + Item::AmplifyPlus => vec![Item::Amplify, Item::Amplify, Item::Amplify], + Item::AmplifyPlusPlus => vec![Item::AmplifyPlus, Item::AmplifyPlus, Item::AmplifyPlus], + + Item::Purge => vec![Item::Debuff, Item::Green, Item::Green], // Needs flavour + Item::Invert => vec![Item::Debuff, Item::Red, Item::Green], + Item::Restrict => vec![Item::Debuff, Item::Red, Item::Red], + Item::Silence => vec![Item::Debuff, Item::Blue, Item::Blue], + Item::Curse => vec![Item::Debuff, Item::Red, Item::Blue], + Item::Decay => vec![Item::Debuff, Item::Green, Item::Blue], + Item::RestrictPlus => vec![Item::Restrict, Item::Restrict, Item::Restrict], + Item::RestrictPlusPlus => vec![Item::RestrictPlus, Item::RestrictPlus, Item::RestrictPlus], + Item::PurgePlus => vec![Item::Purge, Item::Purge, Item::Purge], // Needs flavour + Item::PurgePlusPlus => vec![Item::PurgePlus, Item::PurgePlus, Item::PurgePlus], // Needs flavour + Item::SilencePlus => vec![Item::Silence, Item::Silence, Item::Silence], + Item::SilencePlusPlus => vec![Item::SilencePlus, Item::SilencePlus, Item::SilencePlus], + Item::CursePlus => vec![Item::Curse, Item::Curse, Item::Curse], + Item::CursePlusPlus => vec![Item::CursePlus, Item::CursePlus, Item::CursePlus], + Item::DecayPlus => vec![Item::Decay, Item::Decay, Item::Decay], + Item::DecayPlusPlus => vec![Item::DecayPlus, Item::DecayPlus, Item::DecayPlus], + Item::InvertPlus => vec![Item::Invert, Item::Invert, Item::Invert], + Item::InvertPlusPlus => vec![Item::InvertPlus, Item::InvertPlus, Item::InvertPlus], + + Item::Counter => vec![Item::Block, Item::Red, Item::Red], + Item::Reflect => vec![Item::Block, Item::Green, Item::Blue], + Item::Purify => vec![Item::Block, Item::Green, Item::Green], + Item::Sustain => vec![Item::Block, Item::Red, Item::Green], + Item::Electrify => vec![Item::Block, Item::Blue, Item::Blue], + Item::Recharge => vec![Item::Block, Item::Red, Item::Blue], + Item::CounterPlus => vec![Item::Counter, Item::Counter, Item::Counter], + Item::CounterPlusPlus => vec![Item::CounterPlus, Item::CounterPlus, Item::CounterPlus], // Add red recharge + Item::PurifyPlus => vec![Item::Purify, Item::Purify, Item::Purify], + Item::PurifyPlusPlus => vec![Item::PurifyPlus, Item::PurifyPlus, Item::PurifyPlus], + Item::ElectrifyPlus => vec![Item::Electrify, Item::Electrify, Item::Electrify], + Item::ElectrifyPlusPlus => vec![Item::ElectrifyPlus, Item::ElectrifyPlus, Item::ElectrifyPlus], + Item::SustainPlus => vec![Item::Sustain, Item::Sustain, Item::Sustain], + Item::SustainPlusPlus => vec![Item::SustainPlus, Item::SustainPlus, Item::SustainPlus], + Item::ReflectPlus => vec![Item::Reflect, Item::Reflect, Item::Reflect], + Item::ReflectPlusPlus => vec![Item::ReflectPlus, Item::ReflectPlus, Item::ReflectPlus], + Item::RechargePlus => vec![Item::Recharge, Item::Recharge, Item::Recharge], + Item::RechargePlusPlus => vec![Item::RechargePlus, Item::RechargePlus, Item::RechargePlus], + + Item::Bash => vec![Item::Stun, Item::Red, Item::Red], + Item::Sleep => vec![Item::Stun, Item::Green, Item::Green], + Item::Ruin => vec![Item::Stun, Item::Blue, Item::Blue], + Item::Link => vec![Item::Stun, Item::Blue, Item::Green], + Item::Banish => vec![Item::Stun, Item::Red, Item::Blue], + Item::Break => vec![Item::Stun, Item::Red, Item::Green], + Item::BashPlus => vec![Item::Bash, Item::Bash, Item::Bash], + Item::BashPlusPlus => vec![Item::BashPlus, Item::BashPlus, Item::BashPlus], + Item::SleepPlus => vec![Item::Sleep, Item::Sleep, Item::Sleep], + Item::SleepPlusPlus => vec![Item::SleepPlus, Item::SleepPlus, Item::SleepPlus], + Item::RuinPlus => vec![Item::Ruin, Item::Ruin, Item::Ruin], + Item::RuinPlusPlus => vec![Item::RuinPlus, Item::RuinPlus, Item::RuinPlus], + Item::BreakPlus => vec![Item::Break, Item::Break, Item::Break], + Item::BreakPlusPlus => vec![Item::BreakPlus, Item::BreakPlus, Item::BreakPlus], + Item::LinkPlus => vec![Item::Link, Item::Link, Item::Link], + Item::LinkPlusPlus => vec![Item::LinkPlus, Item::LinkPlus, Item::LinkPlus], + Item::BanishPlus => vec![Item::Banish, Item::Banish, Item::Banish], + Item::BanishPlusPlus => vec![Item::BanishPlus, Item::BanishPlus, Item::BanishPlus], + + Item::Strike => vec![Item::Attack, Item::Red, Item::Red], + Item::Chaos => vec![Item::Attack, Item::Red, Item::Blue], + Item::Heal => vec![Item::Attack, Item::Green, Item::Green], + Item::Blast => vec![Item::Attack, Item::Blue, Item::Blue], + Item::Slay => vec![Item::Attack, Item::Red, Item::Green], + Item::Siphon => vec![Item::Attack, Item::Green, Item::Blue], + Item::StrikePlus => vec![Item::Strike, Item::Strike, Item::Strike], + Item::StrikePlusPlus => vec![Item::StrikePlus, Item::StrikePlus, Item::StrikePlus], + Item::HealPlus => vec![Item::Heal, Item::Heal, Item::Heal], + Item::HealPlusPlus => vec![Item::HealPlus, Item::HealPlus, Item::HealPlus], + Item::BlastPlus => vec![Item::Blast, Item::Blast, Item::Blast], + Item::BlastPlusPlus => vec![Item::BlastPlus, Item::BlastPlus, Item::BlastPlus], + Item::SlayPlus => vec![Item::Slay, Item::Slay, Item::Slay], + Item::SlayPlusPlus => vec![Item::SlayPlus, Item::SlayPlus, Item::SlayPlus], + Item::SiphonPlus => vec![Item::Siphon, Item::Siphon, Item::Siphon], + Item::SiphonPlusPlus => vec![Item::SiphonPlus, Item::SiphonPlus, Item::SiphonPlus], + Item::ChaosPlus => vec![Item::Chaos, Item::Chaos, Item::Chaos], + Item::ChaosPlusPlus => vec![Item::ChaosPlus, Item::ChaosPlus, Item::ChaosPlus], + + Item::PowerRR => vec![Item::Power, Item::Red, Item::Red], + Item::PowerGG => vec![Item::Power, Item::Green, Item::Green], + Item::PowerBB => vec![Item::Power, Item::Blue, Item::Blue], + Item::PowerRG => vec![Item::Power, Item::Red, Item::Green], + Item::PowerGB => vec![Item::Power, Item::Green, Item::Blue], + Item::PowerRB => vec![Item::Power, Item::Red, Item::Blue], + Item::PowerRRPlus => vec![Item::PowerRR, Item::PowerRR, Item::PowerRR], + Item::PowerGGPlus => vec![Item::PowerGG, Item::PowerGG, Item::PowerGG], + Item::PowerBBPlus => vec![Item::PowerBB, Item::PowerBB, Item::PowerBB], + Item::PowerRGPlus => vec![Item::PowerRG, Item::PowerRG, Item::PowerRG], + Item::PowerGBPlus => vec![Item::PowerGB, Item::PowerGB, Item::PowerGB], + Item::PowerRBPlus => vec![Item::PowerRB, Item::PowerRB, Item::PowerRB], + Item::PowerRRPlusPlus => vec![Item::PowerRRPlus, Item::PowerRRPlus, Item::PowerRRPlus], + Item::PowerGGPlusPlus => vec![Item::PowerGGPlus, Item::PowerGGPlus, Item::PowerGGPlus], + Item::PowerBBPlusPlus => vec![Item::PowerBBPlus, Item::PowerBBPlus, Item::PowerBBPlus], + Item::PowerRGPlusPlus => vec![Item::PowerRGPlus, Item::PowerRGPlus, Item::PowerRGPlus], + Item::PowerGBPlusPlus => vec![Item::PowerGBPlus, Item::PowerGBPlus, Item::PowerGBPlus], + Item::PowerRBPlusPlus => vec![Item::PowerRBPlus, Item::PowerRBPlus, Item::PowerRBPlus], + + Item::LifeRR => vec![Item::Life, Item::Red, Item::Red], + Item::LifeGG => vec![Item::Life, Item::Green, Item::Green], + Item::LifeBB => vec![Item::Life, Item::Blue, Item::Blue], + Item::LifeRG => vec![Item::Life, Item::Red, Item::Green], + Item::LifeGB => vec![Item::Life, Item::Green, Item::Blue], + Item::LifeRB => vec![Item::Life, Item::Red, Item::Blue], + Item::LifeRRPlus => vec![Item::LifeRR, Item::LifeRR, Item::LifeRR], + Item::LifeGGPlus => vec![Item::LifeGG, Item::LifeGG, Item::LifeGG], + Item::LifeBBPlus => vec![Item::LifeBB, Item::LifeBB, Item::LifeBB], + Item::LifeRGPlus => vec![Item::LifeRG, Item::LifeRG, Item::LifeRG], + Item::LifeGBPlus => vec![Item::LifeGB, Item::LifeGB, Item::LifeGB], + Item::LifeRBPlus => vec![Item::LifeRB, Item::LifeRB, Item::LifeRB], + Item::LifeRRPlusPlus => vec![Item::LifeRRPlus, Item::LifeRRPlus, Item::LifeRRPlus], + Item::LifeGGPlusPlus => vec![Item::LifeGGPlus, Item::LifeGGPlus, Item::LifeGGPlus], + Item::LifeBBPlusPlus => vec![Item::LifeBBPlus, Item::LifeBBPlus, Item::LifeBBPlus], + Item::LifeRGPlusPlus => vec![Item::LifeRGPlus, Item::LifeRGPlus, Item::LifeRGPlus], + Item::LifeGBPlusPlus => vec![Item::LifeGBPlus, Item::LifeGBPlus, Item::LifeGBPlus], + Item::LifeRBPlusPlus => vec![Item::LifeRBPlus, Item::LifeRBPlus, Item::LifeRBPlus], + + Item::SpeedRR => vec![Item::Speed, Item::Red, Item::Red], + Item::SpeedGG => vec![Item::Speed, Item::Green, Item::Green], + Item::SpeedBB => vec![Item::Speed, Item::Blue, Item::Blue], + Item::SpeedRG => vec![Item::Speed, Item::Red, Item::Green], + Item::SpeedGB => vec![Item::Speed, Item::Green, Item::Blue], + Item::SpeedRB => vec![Item::Speed, Item::Red, Item::Blue], + Item::SpeedRRPlus => vec![Item::SpeedRR, Item::SpeedRR, Item::SpeedRR], + Item::SpeedGGPlus => vec![Item::SpeedGG, Item::SpeedGG, Item::SpeedGG], + Item::SpeedBBPlus => vec![Item::SpeedBB, Item::SpeedBB, Item::SpeedBB], + Item::SpeedRGPlus => vec![Item::SpeedRG, Item::SpeedRG, Item::SpeedRG], + Item::SpeedGBPlus => vec![Item::SpeedGB, Item::SpeedGB, Item::SpeedGB], + Item::SpeedRBPlus => vec![Item::SpeedRB, Item::SpeedRB, Item::SpeedRB], + Item::SpeedRRPlusPlus => vec![Item::SpeedRRPlus, Item::SpeedRRPlus, Item::SpeedRRPlus], + Item::SpeedGGPlusPlus => vec![Item::SpeedGGPlus, Item::SpeedGGPlus, Item::SpeedGGPlus], + Item::SpeedBBPlusPlus => vec![Item::SpeedBBPlus, Item::SpeedBBPlus, Item::SpeedBBPlus], + Item::SpeedRGPlusPlus => vec![Item::SpeedRGPlus, Item::SpeedRGPlus, Item::SpeedRGPlus], + Item::SpeedGBPlusPlus => vec![Item::SpeedGBPlus, Item::SpeedGBPlus, Item::SpeedGBPlus], + Item::SpeedRBPlusPlus => vec![Item::SpeedRBPlus, Item::SpeedRBPlus, Item::SpeedRBPlus], + + _ => vec![*self], + } + } +} + +impl From for Item { + fn from(skill: Skill) -> Item { + match skill { + Skill::Absorb => Item::Absorb, + Skill::AbsorbPlus => Item::AbsorbPlus, + Skill::AbsorbPlusPlus => Item::AbsorbPlusPlus, + Skill::Amplify => Item::Amplify, + Skill::AmplifyPlus => Item::AmplifyPlus, + Skill::AmplifyPlusPlus => Item::AmplifyPlusPlus, + Skill::Attack => Item::Attack, + Skill::Banish => Item::Banish, + Skill::BanishPlus => Item::BanishPlus, + Skill::BanishPlusPlus => Item::BanishPlusPlus, + Skill::Bash => Item::Bash, + Skill::BashPlus => Item::BashPlus, + Skill::BashPlusPlus => Item::BashPlusPlus, + Skill::Blast => Item::Blast, + Skill::BlastPlus => Item::BlastPlus, + Skill::BlastPlusPlus => Item::BlastPlusPlus, + Skill::Block => Item::Block, + Skill::Buff => Item::Buff, + Skill::Chaos => Item::Chaos, + Skill::ChaosPlus => Item::ChaosPlus, + Skill::ChaosPlusPlus => Item::ChaosPlusPlus, + Skill::Counter => Item::Counter, + Skill::CounterPlus => Item::CounterPlus, + Skill::CounterPlusPlus => Item::CounterPlusPlus, + Skill::Curse => Item::Curse, + Skill::CursePlus => Item::CursePlus, + Skill::CursePlusPlus => Item::CursePlusPlus, + Skill::Debuff => Item::Debuff, + Skill::Decay => Item::Decay, + Skill::DecayPlus => Item::DecayPlus, + Skill::DecayPlusPlus => Item::DecayPlusPlus, + Skill::Electrify => Item::Electrify, + Skill::ElectrifyPlus => Item::ElectrifyPlus, + Skill::ElectrifyPlusPlus=> Item::ElectrifyPlusPlus, + Skill::Haste => Item::Haste, + Skill::HastePlus => Item::HastePlus, + Skill::HastePlusPlus => Item::HastePlusPlus, + Skill::Heal => Item::Heal, + Skill::HealPlus => Item::HealPlus, + Skill::HealPlusPlus => Item::HealPlusPlus, + Skill::Hybrid => Item::Hybrid, + Skill::HybridPlus => Item::HybridPlus, + Skill::HybridPlusPlus => Item::HybridPlusPlus, + Skill::Intercept => Item::Intercept, + Skill::InterceptPlus => Item::InterceptPlus, + Skill::InterceptPlusPlus=> Item::InterceptPlusPlus, + Skill::Invert => Item::Invert, + Skill::InvertPlus => Item::InvertPlus, + Skill::InvertPlusPlus => Item::InvertPlusPlus, + Skill::Purge => Item::Purge, + Skill::PurgePlus => Item::PurgePlus, + Skill::PurgePlusPlus => Item::PurgePlusPlus, + Skill::Purify => Item::Purify, + Skill::PurifyPlus => Item::PurifyPlus, + Skill::PurifyPlusPlus => Item::PurifyPlusPlus, + Skill::Recharge => Item::Recharge, + Skill::RechargePlus => Item::RechargePlus, + Skill::RechargePlusPlus => Item::RechargePlusPlus, + Skill::Reflect => Item::Reflect, + Skill::ReflectPlus => Item::ReflectPlus, + Skill::ReflectPlusPlus => Item::ReflectPlusPlus, + Skill::Restrict => Item::Restrict, + Skill::RestrictPlus => Item::RestrictPlus, + Skill::RestrictPlusPlus => Item::RestrictPlusPlus, + Skill::Ruin => Item::Ruin, + Skill::RuinPlus => Item::RuinPlus, + Skill::RuinPlusPlus => Item::RuinPlusPlus, + Skill::Link => Item::Link, + Skill::LinkPlus => Item::LinkPlus, + Skill::LinkPlusPlus => Item::LinkPlusPlus, + Skill::Silence => Item::Silence, + Skill::SilencePlus => Item::SilencePlus, + Skill::SilencePlusPlus => Item::SilencePlusPlus, + Skill::Siphon => Item::Siphon, + Skill::SiphonPlus => Item::SiphonPlus, + Skill::SiphonPlusPlus => Item::SiphonPlusPlus, + Skill::Slay => Item::Slay, + Skill::SlayPlus => Item::SlayPlus, + Skill::SlayPlusPlus => Item::SlayPlusPlus, + Skill::Sleep => Item::Sleep, + Skill::SleepPlus => Item::SleepPlus, + Skill::SleepPlusPlus => Item::SleepPlusPlus, + Skill::Strike => Item::Strike, + Skill::StrikePlus => Item::StrikePlus, + Skill::StrikePlusPlus => Item::StrikePlusPlus, + Skill::Stun => Item::Stun, + Skill::Sustain => Item::Sustain, + Skill::SustainPlus => Item::SustainPlus, + Skill::SustainPlusPlus => Item::SustainPlusPlus, + Skill::Break => Item::Break, + Skill::BreakPlus => Item::BreakPlus, + Skill::BreakPlusPlus => Item::BreakPlusPlus, + Skill::Triage => Item::Triage, + Skill::TriagePlus => Item::TriagePlus, + Skill::TriagePlusPlus => Item::TriagePlusPlus, + + // Convert subskills into parent skills + Skill::Electrocute => Item::Electrify, + Skill::ElectrocutePlus => Item::ElectrifyPlus, + Skill::ElectrocutePlusPlus => Item::ElectrifyPlusPlus, + Skill::ElectrocuteTick => Item::Electrify, + Skill::ElectrocuteTickPlus => Item::ElectrifyPlus, + Skill::ElectrocuteTickPlusPlus => Item::ElectrifyPlus, + Skill::DecayTick => Item::Decay, + Skill::DecayTickPlus => Item::DecayPlus, + Skill::DecayTickPlusPlus => Item::DecayPlusPlus, + Skill::Absorption => Item::Absorb, + Skill::AbsorptionPlus => Item::AbsorbPlus, + Skill::AbsorptionPlusPlus => Item::AbsorbPlusPlus, + Skill::HasteStrike => Item::Haste, + Skill::HybridBlast => Item::Hybrid, + Skill::CounterAttack => Item::Counter, + Skill::CounterAttackPlus => Item::CounterPlus, + Skill::CounterAttackPlusPlus => Item::CounterPlusPlus, + Skill::SiphonTick => Item::Siphon, + Skill::SiphonTickPlus => Item::SiphonPlus, + Skill::SiphonTickPlusPlus => Item::SiphonPlusPlus, + Skill::TriageTick => Item::Triage, + Skill::TriageTickPlus => Item::TriagePlus, + Skill::TriageTickPlusPlus => Item::TriagePlusPlus, + } + } +} + +impl From for Item { + fn from(spec: Spec) -> Item { + match spec { + Spec::Speed => Item::Speed, + Spec::SpeedRR => Item::SpeedRR, + Spec::SpeedBB => Item::SpeedBB, + Spec::SpeedGG => Item::SpeedGG, + Spec::SpeedRG => Item::SpeedRG, + Spec::SpeedGB => Item::SpeedGB, + Spec::SpeedRB => Item::SpeedRB, + + Spec::SpeedRRPlus => Item::SpeedRRPlus, + Spec::SpeedBBPlus => Item::SpeedBBPlus, + Spec::SpeedGGPlus => Item::SpeedGGPlus, + Spec::SpeedRGPlus => Item::SpeedRGPlus, + Spec::SpeedGBPlus => Item::SpeedGBPlus, + Spec::SpeedRBPlus => Item::SpeedRBPlus, + + Spec::SpeedRRPlusPlus => Item::SpeedRRPlusPlus, + Spec::SpeedBBPlusPlus => Item::SpeedBBPlusPlus, + Spec::SpeedGGPlusPlus => Item::SpeedGGPlusPlus, + Spec::SpeedRGPlusPlus => Item::SpeedRGPlusPlus, + Spec::SpeedGBPlusPlus => Item::SpeedGBPlusPlus, + Spec::SpeedRBPlusPlus => Item::SpeedRBPlusPlus, + + Spec::Power => Item::Power, + Spec::PowerRR => Item::PowerRR, + Spec::PowerBB => Item::PowerBB, + Spec::PowerGG => Item::PowerGG, + Spec::PowerRG => Item::PowerRG, + Spec::PowerGB => Item::PowerGB, + Spec::PowerRB => Item::PowerRB, + Spec::PowerRRPlus => Item::PowerRRPlus, + Spec::PowerBBPlus => Item::PowerBBPlus, + Spec::PowerGGPlus => Item::PowerGGPlus, + Spec::PowerRGPlus => Item::PowerRGPlus, + Spec::PowerGBPlus => Item::PowerGBPlus, + Spec::PowerRBPlus => Item::PowerRBPlus, + Spec::PowerRRPlusPlus => Item::PowerRRPlusPlus, + Spec::PowerBBPlusPlus => Item::PowerBBPlusPlus, + Spec::PowerGGPlusPlus => Item::PowerGGPlusPlus, + Spec::PowerRGPlusPlus => Item::PowerRGPlusPlus, + Spec::PowerGBPlusPlus => Item::PowerGBPlusPlus, + Spec::PowerRBPlusPlus => Item::PowerRBPlusPlus, + + Spec::Life => Item::Life, + Spec::LifeRG => Item::LifeRG, + Spec::LifeGB => Item::LifeGB, + Spec::LifeRB => Item::LifeRB, + Spec::LifeGG => Item::LifeGG, + Spec::LifeRR => Item::LifeRR, + Spec::LifeBB => Item::LifeBB, + Spec::LifeRGPlus => Item::LifeRGPlus, + Spec::LifeGBPlus => Item::LifeGBPlus, + Spec::LifeRBPlus => Item::LifeRBPlus, + Spec::LifeGGPlus => Item::LifeGGPlus, + Spec::LifeRRPlus => Item::LifeRRPlus, + Spec::LifeBBPlus => Item::LifeBBPlus, + Spec::LifeRGPlusPlus => Item::LifeRGPlusPlus, + Spec::LifeGBPlusPlus => Item::LifeGBPlusPlus, + Spec::LifeRBPlusPlus => Item::LifeRBPlusPlus, + Spec::LifeGGPlusPlus => Item::LifeGGPlusPlus, + Spec::LifeRRPlusPlus => Item::LifeRRPlusPlus, + Spec::LifeBBPlusPlus => Item::LifeBBPlusPlus, + + // _ => panic!("{:?} not implemented as a item", spec), + } + } +} + + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct Combo { + pub item: Item, + pub components: Vec, +} + +pub fn get_combos() -> Vec { + let mut combinations = vec![ + Combo { components: Item::Intercept.combo(), item: Item::Intercept }, + Combo { components: Item::InterceptPlus.combo(), item: Item::InterceptPlus }, + Combo { components: Item::InterceptPlusPlus.combo(), item: Item::InterceptPlusPlus }, + Combo { components: Item::Triage.combo(), item: Item::Triage }, + Combo { components: Item::TriagePlus.combo(), item: Item::TriagePlus }, + Combo { components: Item::TriagePlusPlus.combo(), item: Item::TriagePlusPlus }, + Combo { components: Item::Absorb.combo(), item: Item::Absorb }, + Combo { components: Item::AbsorbPlus.combo(), item: Item::AbsorbPlus }, + Combo { components: Item::AbsorbPlusPlus.combo(), item: Item::AbsorbPlusPlus }, + Combo { components: Item::Haste.combo(), item: Item::Haste }, + Combo { components: Item::HastePlus.combo(), item: Item::HastePlus }, + Combo { components: Item::HastePlusPlus.combo(), item: Item::HastePlusPlus }, + Combo { components: Item::Hybrid.combo(), item: Item::Hybrid }, + Combo { components: Item::HybridPlus.combo(), item: Item::HybridPlus }, + Combo { components: Item::HybridPlusPlus.combo(), item: Item::HybridPlusPlus }, + Combo { components: Item::Amplify.combo(), item: Item::Amplify }, + Combo { components: Item::AmplifyPlus.combo(), item: Item::AmplifyPlus }, + Combo { components: Item::AmplifyPlusPlus.combo(), item: Item::AmplifyPlusPlus }, + + Combo { components: Item::Restrict.combo(), item: Item::Restrict }, + Combo { components: Item::RestrictPlus.combo(), item: Item::RestrictPlus }, + Combo { components: Item::RestrictPlusPlus.combo(), item: Item::RestrictPlusPlus }, + Combo { components: Item::Purge.combo(), item: Item::Purge }, // Needs flavour + Combo { components: Item::PurgePlus.combo(), item: Item::PurgePlus }, + Combo { components: Item::PurgePlusPlus.combo(), item: Item::PurgePlusPlus }, + + Combo { components: Item::Silence.combo(), item: Item::Silence }, + Combo { components: Item::SilencePlus.combo(), item: Item::SilencePlus }, + Combo { components: Item::SilencePlusPlus.combo(), item: Item::SilencePlusPlus }, + + + Combo { components: Item::Invert.combo(), item: Item::Invert }, + Combo { components: Item::InvertPlus.combo(), item: Item::InvertPlus }, + Combo { components: Item::InvertPlusPlus.combo(), item: Item::InvertPlusPlus }, + Combo { components: Item::Curse.combo(), item: Item::Curse }, + Combo { components: Item::CursePlus.combo(), item: Item::CursePlus }, + Combo { components: Item::CursePlusPlus.combo(), item: Item::CursePlusPlus }, + Combo { components: Item::Decay.combo(), item: Item::Decay }, + Combo { components: Item::DecayPlus.combo(), item: Item::DecayPlus }, + Combo { components: Item::DecayPlusPlus.combo(), item: Item::DecayPlusPlus }, + + Combo { components: Item::Counter.combo(), item: Item::Counter }, + Combo { components: Item::CounterPlus.combo(), item: Item::CounterPlus }, + Combo { components: Item::CounterPlusPlus.combo(), item: Item::CounterPlusPlus }, + Combo { components: Item::Purify.combo(), item: Item::Purify }, + Combo { components: Item::PurifyPlus.combo(), item: Item::PurifyPlus }, + Combo { components: Item::PurifyPlusPlus.combo(), item: Item::PurifyPlusPlus }, + + Combo { components: Item::Electrify.combo(), item: Item::Electrify }, + Combo { components: Item::ElectrifyPlus.combo(), item: Item::ElectrifyPlus }, + Combo { components: Item::ElectrifyPlusPlus.combo(), item: Item::ElectrifyPlusPlus }, + + Combo { components: Item::Sustain.combo(), item: Item::Sustain }, + Combo { components: Item::SustainPlus.combo(), item: Item::SustainPlus }, + Combo { components: Item::SustainPlusPlus.combo(), item: Item::SustainPlusPlus }, + Combo { components: Item::Reflect.combo(), item: Item::Reflect }, + Combo { components: Item::ReflectPlus.combo(), item: Item::ReflectPlus }, + Combo { components: Item::ReflectPlusPlus.combo(), item: Item::ReflectPlusPlus }, + + + Combo { components: Item::Recharge.combo(), item: Item::Recharge }, + Combo { components: Item::RechargePlus.combo(), item: Item::RechargePlus }, + Combo { components: Item::RechargePlusPlus.combo(), item: Item::RechargePlusPlus }, + + Combo { components: Item::Bash.combo(), item: Item::Bash }, + Combo { components: Item::BashPlus.combo(), item: Item::BashPlus }, + Combo { components: Item::BashPlusPlus.combo(), item: Item::BashPlusPlus }, + Combo { components: Item::Sleep.combo(), item: Item::Sleep }, + Combo { components: Item::SleepPlus.combo(), item: Item::SleepPlus }, + Combo { components: Item::SleepPlusPlus.combo(), item: Item::SleepPlusPlus }, + Combo { components: Item::Ruin.combo(), item: Item::Ruin }, + Combo { components: Item::RuinPlus.combo(), item: Item::RuinPlus }, + Combo { components: Item::RuinPlusPlus.combo(), item: Item::RuinPlusPlus }, + + Combo { components: Item::Break.combo(), item: Item::Break }, + Combo { components: Item::BreakPlus.combo(), item: Item::BreakPlus }, + Combo { components: Item::BreakPlusPlus.combo(), item: Item::BreakPlusPlus }, + Combo { components: Item::Link.combo(), item: Item::Link }, + Combo { components: Item::LinkPlus.combo(), item: Item::LinkPlus }, + Combo { components: Item::LinkPlusPlus.combo(), item: Item::LinkPlusPlus }, + Combo { components: Item::Banish.combo(), item: Item::Banish }, + Combo { components: Item::BanishPlus.combo(), item: Item::BanishPlus }, + Combo { components: Item::BanishPlusPlus.combo(), item: Item::BanishPlusPlus }, + + Combo { components: Item::Strike.combo(), item: Item::Strike }, + Combo { components: Item::StrikePlus.combo(), item: Item::StrikePlus }, + Combo { components: Item::StrikePlusPlus.combo(), item: Item::StrikePlusPlus }, + + Combo { components: Item::Heal.combo(), item: Item::Heal }, + Combo { components: Item::HealPlus.combo(), item: Item::HealPlus }, + Combo { components: Item::HealPlusPlus.combo(), item: Item::HealPlusPlus }, + Combo { components: Item::Blast.combo(), item: Item::Blast }, + Combo { components: Item::BlastPlus.combo(), item: Item::BlastPlus }, + Combo { components: Item::BlastPlusPlus.combo(), item: Item::BlastPlusPlus }, + Combo { components: Item::Slay.combo(), item: Item::Slay }, + Combo { components: Item::SlayPlus.combo(), item: Item::SlayPlus }, + Combo { components: Item::SlayPlusPlus.combo(), item: Item::SlayPlusPlus }, + Combo { components: Item::Siphon.combo(), item: Item::Siphon }, + Combo { components: Item::SiphonPlus.combo(), item: Item::SiphonPlus }, + Combo { components: Item::SiphonPlusPlus.combo(), item: Item::SiphonPlusPlus }, + Combo { components: Item::Chaos.combo(), item: Item::Chaos }, + Combo { components: Item::ChaosPlus.combo(), item: Item::ChaosPlus }, + Combo { components: Item::ChaosPlusPlus.combo(), item: Item::ChaosPlusPlus }, + + Combo { components: Item::PowerRR.combo(), item: Item::PowerRR }, + Combo { components: Item::PowerGG.combo(), item: Item::PowerGG }, + Combo { components: Item::PowerBB.combo(), item: Item::PowerBB }, + Combo { components: Item::PowerRG.combo(), item: Item::PowerRG }, + Combo { components: Item::PowerGB.combo(), item: Item::PowerGB }, + Combo { components: Item::PowerRB.combo(), item: Item::PowerRB }, + Combo { components: Item::PowerRRPlus.combo(), item: Item::PowerRRPlus }, + Combo { components: Item::PowerGGPlus.combo(), item: Item::PowerGGPlus }, + Combo { components: Item::PowerBBPlus.combo(), item: Item::PowerBBPlus }, + Combo { components: Item::PowerRGPlus.combo(), item: Item::PowerRGPlus }, + Combo { components: Item::PowerGBPlus.combo(), item: Item::PowerGBPlus }, + Combo { components: Item::PowerRBPlus.combo(), item: Item::PowerRBPlus }, + Combo { components: Item::PowerRRPlusPlus.combo(), item: Item::PowerRRPlusPlus }, + Combo { components: Item::PowerGGPlusPlus.combo(), item: Item::PowerGGPlusPlus }, + Combo { components: Item::PowerBBPlusPlus.combo(), item: Item::PowerBBPlusPlus }, + Combo { components: Item::PowerRGPlusPlus.combo(), item: Item::PowerRGPlusPlus }, + Combo { components: Item::PowerGBPlusPlus.combo(), item: Item::PowerGBPlusPlus }, + Combo { components: Item::PowerRBPlusPlus.combo(), item: Item::PowerRBPlusPlus }, + + Combo { components: Item::LifeRR.combo(), item: Item::LifeRR}, + Combo { components: Item::LifeGG.combo(), item: Item::LifeGG}, + Combo { components: Item::LifeBB.combo(), item: Item::LifeBB}, + Combo { components: Item::LifeRG.combo(), item: Item::LifeRG}, + Combo { components: Item::LifeGB.combo(), item: Item::LifeGB}, + Combo { components: Item::LifeRB.combo(), item: Item::LifeRB}, + Combo { components: Item::LifeRRPlus.combo(), item: Item::LifeRRPlus }, + Combo { components: Item::LifeGGPlus.combo(), item: Item::LifeGGPlus }, + Combo { components: Item::LifeBBPlus.combo(), item: Item::LifeBBPlus }, + Combo { components: Item::LifeRGPlus.combo(), item: Item::LifeRGPlus }, + Combo { components: Item::LifeGBPlus.combo(), item: Item::LifeGBPlus }, + Combo { components: Item::LifeRBPlus.combo(), item: Item::LifeRBPlus }, + Combo { components: Item::LifeRRPlusPlus.combo(), item: Item::LifeRRPlusPlus }, + Combo { components: Item::LifeGGPlusPlus.combo(), item: Item::LifeGGPlusPlus }, + Combo { components: Item::LifeBBPlusPlus.combo(), item: Item::LifeBBPlusPlus }, + Combo { components: Item::LifeRGPlusPlus.combo(), item: Item::LifeRGPlusPlus }, + Combo { components: Item::LifeGBPlusPlus.combo(), item: Item::LifeGBPlusPlus }, + Combo { components: Item::LifeRBPlusPlus.combo(), item: Item::LifeRBPlusPlus }, + + Combo { components: Item::SpeedRR.combo(), item: Item::SpeedRR}, + Combo { components: Item::SpeedGG.combo(), item: Item::SpeedGG}, + Combo { components: Item::SpeedBB.combo(), item: Item::SpeedBB}, + Combo { components: Item::SpeedRG.combo(), item: Item::SpeedRG}, + Combo { components: Item::SpeedGB.combo(), item: Item::SpeedGB}, + Combo { components: Item::SpeedRB.combo(), item: Item::SpeedRB}, + Combo { components: Item::SpeedRRPlus.combo(), item: Item::SpeedRRPlus }, + Combo { components: Item::SpeedGGPlus.combo(), item: Item::SpeedGGPlus }, + Combo { components: Item::SpeedBBPlus.combo(), item: Item::SpeedBBPlus }, + Combo { components: Item::SpeedRGPlus.combo(), item: Item::SpeedRGPlus }, + Combo { components: Item::SpeedGBPlus.combo(), item: Item::SpeedGBPlus }, + Combo { components: Item::SpeedRBPlus.combo(), item: Item::SpeedRBPlus }, + Combo { components: Item::SpeedRRPlusPlus.combo(), item: Item::SpeedRRPlusPlus }, + Combo { components: Item::SpeedGGPlusPlus.combo(), item: Item::SpeedGGPlusPlus }, + Combo { components: Item::SpeedBBPlusPlus.combo(), item: Item::SpeedBBPlusPlus }, + Combo { components: Item::SpeedRGPlusPlus.combo(), item: Item::SpeedRGPlusPlus }, + Combo { components: Item::SpeedGBPlusPlus.combo(), item: Item::SpeedGBPlusPlus }, + Combo { components: Item::SpeedRBPlusPlus.combo(), item: Item::SpeedRBPlusPlus }, + ]; + + combinations.iter_mut().for_each(|set| set.components.sort_unstable()); + + return combinations; +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct ItemInfo { + pub item: Item, + pub cost: usize, + pub spec: bool, + pub values: Option, + pub skill: bool, + pub speed: Option, + pub cooldown: Cooldown, + pub description: String, +} + + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct ItemInfoCtr { + pub combos: Vec, + pub items: Vec, +} + +pub fn item_info() -> ItemInfoCtr { + let combos = get_combos(); + let mut items = combos + .into_iter() + .flat_map(|mut c| { + c.components.push(c.item); + c.components + }) + .collect::>(); + + items.sort_unstable(); + items.dedup(); + + let items = items + .into_iter() + .map(|v| ItemInfo { + item: v, + cost: v.cost(), + spec: v.into_spec().is_some(), + values: match v.into_spec() { + Some(s) => Some(s.values()), + None => None + }, + skill: v.into_skill().is_some(), + description: v.into_description(), + speed: match v.into_skill() { + Some(s) => Some(s.speed()), + None => None + }, + cooldown: match v.into_skill() { + Some(s) => s.base_cd(), + None => None + }, + }) + .collect::>(); + + let combos = get_combos(); + + return ItemInfoCtr { + combos, + items, + }; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn item_components_test() { + assert_eq!(Item::Strike.components(), vec![Item::Red, Item::Red, Item::Attack]); + assert_eq!(Item::StrikePlus.components(), vec![ + Item::Red, Item::Red, Item::Attack, + Item::Red, Item::Red, Item::Attack, + Item::Red, Item::Red, Item::Attack, + ]); + assert_eq!(Item::StrikePlusPlus.components(), vec![ + Item::Red, Item::Red, Item::Attack, + Item::Red, Item::Red, Item::Attack, + Item::Red, Item::Red, Item::Attack, + + Item::Red, Item::Red, Item::Attack, + Item::Red, Item::Red, Item::Attack, + Item::Red, Item::Red, Item::Attack, + + Item::Red, Item::Red, Item::Attack, + Item::Red, Item::Red, Item::Attack, + Item::Red, Item::Red, Item::Attack, + ]); + + } + + #[test] + fn item_info_test() { + item_info(); + } +} \ No newline at end of file diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 00000000..2fb53670 --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,23 @@ +extern crate rand; +extern crate uuid; +extern crate bcrypt; +extern crate chrono; + +extern crate serde; +#[macro_use] extern crate serde_derive; +#[macro_use] extern crate failure; + +#[macro_use] extern crate log; + +pub mod construct; +pub mod effect; +pub mod game; +pub mod instance; +pub mod item; +pub mod mob; +pub mod names; +pub mod player; +pub mod skill; +pub mod spec; +pub mod util; +pub mod vbox; diff --git a/core/src/mob.rs b/core/src/mob.rs new file mode 100644 index 00000000..2cab954b --- /dev/null +++ b/core/src/mob.rs @@ -0,0 +1,30 @@ +use uuid::Uuid; + +use std::iter; + +use construct::{Construct}; +use names::{name}; +use player::{Player}; + +pub fn generate_mob() -> Construct { + let mob = Construct::new() + .named(&name()); + + return mob; +} + +pub fn instance_mobs(player_id: Uuid) -> Vec { + iter::repeat_with(|| + generate_mob() + .set_account(player_id)) + // .learn(Skill::Attack)) + .take(3) + .collect::>() +} + +pub fn bot_player() -> Player { + let bot_id = Uuid::new_v4(); + let constructs = instance_mobs(bot_id); + Player::new(bot_id, &name(), constructs).set_bot(true) +} + diff --git a/core/src/names.rs b/core/src/names.rs new file mode 100644 index 00000000..07cc6ff0 --- /dev/null +++ b/core/src/names.rs @@ -0,0 +1,138 @@ +use rand::prelude::*; +use rand::{thread_rng}; + +const FIRSTS: [&'static str; 53] = [ + "artificial", + "ambient", + "borean", + "brewing", + "bristling", + "compressed", + "ceramic", + "chromatic", + "concave", + "convex", + "distorted", + "deserted", + "emotive", + "emotionless", + "elliptical", + "extrasolar", + "fierce", + "fossilised", + "frozen", + "gravitational", + "jovian", + "inverted", + "leafy", + "lurking", + "limitless", + "magnetic", + "metallic", + "mossy", + "mighty", + "modulated", + "nocturnal", + "noisy", + "nutritious", + "powerful", + "obscure", + "organic", + "oxygenated", + "oscillating", + "ossified", + "orbiting", + "piscine", + "polar", + "pure", + "recalcitrant", + "rogue", + "sealed", + "subversive", + "subterranean", + "supercooled", + "subsonic", + "synthetic", + "terrestrial", + "weary", +]; + +const LASTS: [&'static str; 63] = [ + "artifact", + "assembly", + "antenna", + "alloy", + "carrier", + "carbon", + "console", + "construct", + "coordinates", + "craft", + "core", + "design", + "drone", + "distortion", + "detector", + "energy", + "entropy", + "exoplanet", + "foilage", + "forest", + "form", + "fossil", + "frequency", + "function", + "fusion", + "fission", + "information", + "insulator", + "layout", + "lifeform", + "liquid", + "landmass", + "lens", + "mass", + "mantle", + "magnetism", + "mechanism", + "mountain", + "nectar", + "nebula", + "oxide", + "orbit", + "pattern", + "plant", + "planet", + "poseidon", + "problem", + "receiver", + "replicant", + "river", + "satellite", + "scaffold", + "structure", + "shape", + "signal", + "synthesiser", + "system", + "tower", + "transmitter", + "traveller", + "vibration", + "warning", + "wildlife", +]; + +pub fn name() -> String { + let mut rng = thread_rng(); + + let first = rng.gen_range(0, FIRSTS.len() - 1); + let last = rng.gen_range(0, LASTS.len() - 1); + + let mut s = String::new(); + s.push_str(FIRSTS[first]); + s.push(' '); + s.push_str(LASTS[last]); + + s +} diff --git a/core/src/player.rs b/core/src/player.rs new file mode 100644 index 00000000..c87b8a97 --- /dev/null +++ b/core/src/player.rs @@ -0,0 +1,442 @@ +use std::collections::{HashMap}; + +use uuid::Uuid; +use rand::prelude::*; + +use failure::Error; +use failure::err_msg; + +use construct::{Construct, Colours}; +use vbox::{Vbox, ItemType, VboxIndices}; +use item::{Item, ItemEffect}; +use effect::{Effect}; + +const DISCARD_COST: usize = 2; + +#[derive(Debug,Copy,Clone,Serialize,Deserialize,Eq,PartialEq)] +pub enum Score { + Zero, + One, + Two, + Three, + Adv, + + Win, + Lose, +} + +impl Score { + pub fn add_win(self, _opp: &Score) -> Score { + match self { + Score::Zero => Score::One, + Score::One => Score::Two, + Score::Two => Score::Win, + // Tennis scoring + // Score::Three => match opp { + // Score::Adv => Score::Three, + // Score::Three => Score::Adv, + // _ => Score::Win, + // } + // Score::Adv => Score::Win, + + _ => panic!("faulty score increment {:?}", self), + } + } + + pub fn add_loss(self) -> Score { + match self { + // Score::Adv => Score::Three, + _ => self, + } + } + +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct Player { + pub id: Uuid, + pub img: Option, + pub name: String, + pub vbox: Vbox, + pub constructs: Vec, + pub bot: bool, + pub ready: bool, + pub draw_offered: bool, + pub score: Score, +} + +impl Player { + pub fn new(account: Uuid, name: &String, constructs: Vec) -> Player { + Player { + id: account, + img: Some(account), + name: name.clone(), + vbox: Vbox::new(), + constructs, + bot: false, + ready: false, + draw_offered: false, + score: Score::Zero, + } + } + + pub fn redact(mut self, account: Uuid) -> Player { + // all g + if account == self.id { + return self; + } + + // remove vbox + self.vbox = Vbox::new(); + + // hide skills + for construct in self.constructs.iter_mut() { + construct.skills = vec![]; + construct.specs = vec![]; + } + + self + } + + pub fn set_bot(mut self, bot: bool) -> Player { + self.bot = bot; + self + } + + pub fn set_ready(&mut self, ready: bool) -> &mut Player { + self.ready = ready; + self + } + + pub fn forfeit(&mut self) -> &mut Player { + for construct in self.constructs.iter_mut() { + construct.force_ko(); + } + self + } + + pub fn set_win(&mut self) -> &mut Player { + self.score = Score::Win; + self + } + + pub fn set_lose(&mut self) -> &mut Player { + self.score = Score::Lose; + self + } + + pub fn construct_get(&mut self, id: Uuid) -> Result<&mut Construct, Error> { + self.constructs.iter_mut().find(|c| c.id == id).ok_or(err_msg("construct not found")) + } + + pub fn autobuy(&mut self) -> &mut Player { + let mut rng = thread_rng(); + + // skill buying phase + while self.constructs.iter().any(|c| c.skills.len() < 3) { + // find the construct with the smallest number of skills + let construct_id = match self.constructs.iter().min_by_key(|c| c.skills.len()) { + None => panic!("no constructs in autobuy"), + Some(c) => c.id, + }; + + let i = self.vbox.stash.iter() + .find(|(_i, v)| v.into_skill().is_some()) + .map(|(i, _v)| i.clone()); + + // got a skill in stash + if let Some(i) = i { + // AAAAAAAAAAAAAAAAAAAA + // there's a bad bug here where if this apply fails + // the item in question will be silently dropped + let item = self.vbox.stash.remove(&i).unwrap(); + self.vbox_apply(item, construct_id).ok(); + continue; + } + // need to buy one + else { + + // do we have any colours in store? + let colours = self.vbox.store[&ItemType::Colours].keys() + .cloned() + .collect::>(); + + // how about a base skill? + let base = match self.vbox.store[&ItemType::Skills].iter().next() { + Some(b) => Some(b.0.clone()), + None => None, + }; + + // if no: try to refill and start again + match colours.len() < 2 || base.is_none() { + true => match self.vbox_refill() { + Ok(_) => continue, + Err(_) => break, // give up + }, + false => { + let mut vbox_items = HashMap::new(); + vbox_items.insert(ItemType::Colours, colours); + vbox_items.insert(ItemType::Skills, vec![base.unwrap()]); + + match self.vbox_combine(vec![], Some(vbox_items)) { + Ok(_) => continue, + Err(_) => break, // give up + } + } + } + } + } + + // spec buying phase + while self.constructs.iter().any(|c| c.specs.len() < 3) { + // find the construct with the smallest number of skills + let construct_id = match self.constructs.iter().min_by_key(|c| c.specs.len()) { + None => panic!("no constructs in autobuy"), + Some(c) => c.id, + }; + + let i = self.vbox.stash.iter() + .find(|(_i, v)| v.into_spec().is_some()) + .map(|(i, _v)| i.clone()); + + // got a skill in stash + if let Some(i) = i { + // AAAAAAAAAAAAAAAAAAAA + // there's a bad bug here where if this apply fails + // the item in question will be silently dropped + let item = self.vbox.stash.remove(&i).unwrap(); + self.vbox_apply(item, construct_id).ok(); + continue; + } + // need to buy one + else { + // do we have any colours in store? + let colours = self.vbox.store[&ItemType::Colours].keys() + .cloned() + .collect::>(); + + // how about a base spec? + let base = match self.vbox.store[&ItemType::Specs].iter().next() { + Some(b) => Some(b.0.clone()), + None => None, + }; + + // if no: try to refill and start again + match colours.len() < 2 || base.is_none() { + true => match self.vbox_refill() { + Ok(_) => continue, + Err(_) => break, // give up + }, + false => { + let mut vbox_items = HashMap::new(); + vbox_items.insert(ItemType::Colours, colours); + vbox_items.insert(ItemType::Specs, vec![base.unwrap()]); + + match self.vbox_combine(vec![], Some(vbox_items)) { + Ok(_) => continue, + Err(_) => break, // give up + } + } + } + } + } + + // upgrading phase + // NYI + + return self; + } + + pub fn vbox_refill(&mut self) -> Result<&mut Player, Error> { + self.vbox.balance_sub(DISCARD_COST)?; + self.vbox.fill(); + Ok(self) + } + + pub fn bot_vbox_accept(&mut self, group: ItemType) -> Result<&mut Player, Error> { + let item = self.vbox.bot_buy(group)?; + self.vbox.stash_add(item, None)?; + Ok(self) + } + + pub fn vbox_buy(&mut self, group: ItemType, index: String, construct_id: Option) -> Result<&mut Player, Error> { + let item = self.vbox.buy(group, &index)?; + + match construct_id { + Some(id) => { self.vbox_apply(item, id)?; }, + None => { self.vbox.stash_add(item, None)?; }, + }; + + Ok(self) + } + + pub fn vbox_combine(&mut self, inv_indices: Vec, vbox_indices: VboxIndices) -> Result<&mut Player, Error> { + self.vbox.combine(inv_indices, vbox_indices)?; + Ok(self) + } + + pub fn vbox_refund(&mut self, index: String) -> Result<&mut Player, Error> { + self.vbox.refund(index)?; + Ok(self) + } + + pub fn vbox_equip(&mut self, index: String, construct_id: Uuid) -> Result<&mut Player, Error> { + let item = self.vbox.stash.remove(&index) + .ok_or(format_err!("no item at index {:?} {:}", self, &index))?; + + self.vbox_apply(item, construct_id) + } + + pub fn vbox_apply(&mut self, item: Item, construct_id: Uuid) -> Result<&mut Player, Error> { + match item.effect() { + Some(ItemEffect::Skill) => { + let skill = item.into_skill().ok_or(format_err!("item {:?} has no associated skill", item))?; + let construct = self.construct_get(construct_id)?; + // done here because i teach them a tonne of skills for tests + let max_skills = 3; + if construct.skills.len() >= max_skills { + return Err(format_err!("construct at max skills ({:?})", max_skills)); + } + + if construct.knows(skill) { + return Err(format_err!("construct already knows skill ({:?})" , skill)); + } + + construct.learn_mut(skill); + }, + Some(ItemEffect::Spec) => { + let spec = item.into_spec().ok_or(format_err!("item {:?} has no associated spec", item))?; + let construct = self.construct_get(construct_id)?; + construct.spec_add(spec)?; + + }, + None => return Err(err_msg("item has no effect on constructs")), + } + + // now the item has been applied + // recalculate the stats of the whole player + let player_colours = self.constructs.iter().fold(Colours::new(), |tc, c| { + Colours { + red: tc.red + c.colours.red, + green: tc.green + c.colours.green, + blue: tc.blue + c.colours.blue + } + }); + + for construct in self.constructs.iter_mut() { + construct.apply_modifiers(&player_colours); + } + + Ok(self) + } + + pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid, target_construct_id: Option) -> Result<&mut Player, Error> { + if self.vbox.stash.len() >= 9 && !target_construct_id.is_some() { + return Err(err_msg("too many items stash")); + } + + match target.effect() { + Some(ItemEffect::Skill) => { + let skill = target.into_skill().ok_or(format_err!("item {:?} has no associated skill", target))?; + let construct = self.construct_get(construct_id)?; + construct.forget(skill)?; + }, + Some(ItemEffect::Spec) => { + let spec = target.into_spec().ok_or(format_err!("item {:?} has no associated spec", target))?; + let construct = self.construct_get(construct_id)?; + construct.spec_remove(spec)?; + }, + None => return Err(err_msg("item has no effect on constructs")), + } + + // now the item has been applied + // recalculate the stats of the whole player + let player_colours = self.constructs.iter().fold(Colours::new(), |tc, c| { + Colours { + red: tc.red + c.colours.red, + green: tc.green + c.colours.green, + blue: tc.blue + c.colours.blue + } + }); + + for construct in self.constructs.iter_mut() { + construct.apply_modifiers(&player_colours); + } + + match target_construct_id { + Some(cid) => { self.vbox_apply(target, cid)?; }, + None => { self.vbox.stash_add(target, None)?; }, + }; + + Ok(self) + } + + // GAME METHODS + pub fn skills_required(&self) -> usize { + let required = self.constructs.iter() + .filter(|c| !c.is_ko()) + .filter(|c| c.available_skills().len() > 0) + .collect::>().len(); + // info!("{:} requires {:} skills this turn", self.id, required); + return required; + } + + pub fn intercepting(&self) -> Option<&Construct> { + self.constructs.iter() + .find(|c| c.affected(Effect::Intercept)) + } + + pub fn construct_by_id(&mut self, id: Uuid) -> Option<&mut Construct> { + self.constructs.iter_mut().find(|c| c.id == id) + } + +} + +#[cfg(test)] +mod tests { + use mob::instance_mobs; + use super::*; + + #[test] + fn player_bot_vbox_test() { + let player_account = Uuid::new_v4(); + let constructs = instance_mobs(player_account); + let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); + + player.vbox.fill(); + player.autobuy(); + + assert!(player.constructs.iter().all(|c| c.skills.len() >= 1)); + } + + #[test] + fn player_score_test() { + let player_account = Uuid::new_v4(); + let constructs = instance_mobs(player_account); + let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); + + player.score = player.score.add_win(&Score::Zero); + player.score = player.score.add_win(&Score::Zero); + player.score = player.score.add_win(&Score::Zero); + assert_eq!(player.score, Score::Win); // 40 / 0 + + // Bo7 tennis scoring + /*assert_eq!(player.score, Score::Three); // 40 / 0 + + player.score = player.score.add_loss(); // adv -> deuce + assert_eq!(player.score, Score::Three); + + player.score = player.score.add_loss(); // adv -> deuce + assert_eq!(player.score, Score::Three); + + player.score = player.score.add_win(&Score::Adv); // opp adv -> stays deuce + assert_eq!(player.score, Score::Three); + + player.score = player.score.add_win(&Score::Three); + assert_eq!(player.score, Score::Adv); + + player.score = player.score.add_win(&Score::Three); + assert_eq!(player.score, Score::Win);*/ + } + +} \ No newline at end of file diff --git a/core/src/skill.rs b/core/src/skill.rs new file mode 100644 index 00000000..13fdcf82 --- /dev/null +++ b/core/src/skill.rs @@ -0,0 +1,2188 @@ +use rand::{thread_rng, Rng}; +use uuid::Uuid; + +use util::{IntPct}; +use construct::{Construct, ConstructEffect, EffectMeta}; +use item::{Item}; + +use game::{Game}; +use effect::{Effect, Colour, Cooldown}; + +pub fn dev_resolve(a_id: Uuid, b_id: Uuid, skill: Skill) -> Resolutions { + let mut resolutions = vec![]; + + let mut a = Construct::new(); + a.id = a_id; + let mut b = Construct::new(); + b.id = b_id; + if skill.aoe() { // Send an aoe skill event for anims + resolutions.push(Resolution::new(&a, &b).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd)); + } + return resolve_skill(skill, &mut a, &mut b, resolutions); +} + +pub fn resolve(cast: &Cast, game: &mut Game) -> Resolutions { + let mut resolutions = vec![]; + + let skill = cast.skill; + let source = game.construct_by_id(cast.source_construct_id).unwrap().clone(); + let targets = game.get_targets(cast.skill, &source, cast.target_construct_id); + + if skill.aoe() { // Send an aoe skill event for anims + resolutions.push(Resolution::new(&source, + &game.construct_by_id(cast.target_construct_id).unwrap().clone()).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd)); + } + + for target_id in targets { + // we clone the current state of the target and source + // so we can modify them during the resolution + // no more than 1 mutable ref allowed on game + let mut source = game.construct_by_id(cast.source_construct_id).unwrap().clone(); + let mut target = game.construct_by_id(target_id).unwrap().clone(); + + // bail out on ticks that have been removed + if skill.is_tick() && target.effects.iter().find(|ce| match ce.tick { + Some(t) => t.id == cast.id, + None => false, + }).is_none() { + continue; + } + + resolutions = resolve_skill(cast.skill, &mut source, &mut target, resolutions); + + // save the changes to the game + game.update_construct(&mut source); + game.update_construct(&mut target); + + // do additional steps + resolutions = post_resolve(cast.skill, game, resolutions); + } + + return resolutions; +} + +pub fn resolve_skill(skill: Skill, source: &mut Construct, target: &mut Construct, mut resolutions: Vec) -> Resolutions { + if let Some(_disable) = source.disabled(skill) { + // resolutions.push(Resolution::new(source, target).event(Event::Disable { disable, skill }).stages(EventStages::PostOnly)); + return resolutions; + } + + if target.is_ko() { + // resolutions.push(Resolution::new(source, target).event(Event::TargetKo { skill }).stages(EventStages::PostOnly)); + return resolutions; + } + + if target.affected(Effect::Reflect) && skill.colours().contains(&Colour::Blue) && !skill.is_tick() { + // guard against overflow + if source.affected(Effect::Reflect) { + return resolutions; + } + resolutions.push(Resolution::new(source, target).event(Event::Reflection { skill })); + return resolve_skill(skill, &mut source.clone(), source, resolutions); + } + + if source.affected(Effect::Haste) { + match skill { + Skill::Slay | + Skill::SlayPlus | + Skill::SlayPlusPlus | + Skill::Chaos | + Skill::ChaosPlus | + Skill::ChaosPlusPlus | + Skill::Strike | + Skill::StrikePlus | + Skill::StrikePlusPlus => { + let amount = source.speed().pct(Skill::HasteStrike.multiplier()); + target.deal_red_damage(Skill::HasteStrike, amount) + .into_iter() + .for_each(|e| resolutions.push(Resolution::new(source, target).event(e))); + }, + _ => (), + } + } + + if source.affected(Effect::Hybrid) { + match skill { + Skill::Blast| + Skill::BlastPlus | + Skill::BlastPlusPlus | + Skill::Chaos | + Skill::ChaosPlus | + Skill::ChaosPlusPlus | + Skill::Siphon | + Skill::SiphonPlus | + Skill::SiphonPlusPlus => { + let amount = source.green_power().pct(Skill::HybridBlast.multiplier()); + target.deal_blue_damage(Skill::HybridBlast, amount) + .into_iter() + .for_each(|e| resolutions.push(Resolution::new(source, target).event(e))); + }, + _ => (), + } + } + + // match self.category() == EffectCategory::Red { + // true => { + // if let Some(evasion) = target.evade(*self) { + // resolutions.push(evasion); + // return Event; + // } + // }, + // false => (), + // } + + resolutions = match skill { + Skill::Amplify| + Skill::AmplifyPlus | + Skill::AmplifyPlusPlus => amplify(source, target, resolutions, skill), + + Skill::Banish| + Skill::BanishPlus | + Skill::BanishPlusPlus => banish(source, target, resolutions, skill), + + Skill::Bash| + Skill::BashPlus | + Skill::BashPlusPlus => bash(source, target, resolutions, skill), + + Skill::Blast| + Skill::BlastPlus | + Skill::BlastPlusPlus => blast(source, target, resolutions, skill), + + Skill::Chaos| + Skill::ChaosPlus | + Skill::ChaosPlusPlus => chaos(source, target, resolutions, skill), + + Skill::Sustain| + Skill::SustainPlus | + Skill::SustainPlusPlus => sustain(source, target, resolutions, skill), + + Skill::Electrify| + Skill::ElectrifyPlus | + Skill::ElectrifyPlusPlus => electrify(source, target, resolutions, skill), + Skill::ElectrocuteTick| + Skill::ElectrocuteTickPlus | + Skill::ElectrocuteTickPlusPlus => electrocute_tick(source, target, resolutions, skill), + + Skill::Curse| + Skill::CursePlus | + Skill::CursePlusPlus => curse(source, target, resolutions, skill), + + Skill::Decay| + Skill::DecayPlus | + Skill::DecayPlusPlus => decay(source, target, resolutions, skill), + Skill::DecayTick| + Skill::DecayTickPlus | + Skill::DecayTickPlusPlus => decay_tick(source, target, resolutions, skill), + + Skill::Haste| + Skill::HastePlus | + Skill::HastePlusPlus => haste(source, target, resolutions, skill), + + Skill::Heal| + Skill::HealPlus | + Skill::HealPlusPlus => heal(source, target, resolutions, skill), + + Skill::Absorb| + Skill::AbsorbPlus | + Skill::AbsorbPlusPlus => absorb(source, target, resolutions, skill), + + Skill::Hybrid| + Skill::HybridPlus | + Skill::HybridPlusPlus => hybrid(source, target, resolutions, skill), + + Skill::Invert| + Skill::InvertPlus | + Skill::InvertPlusPlus => invert(source, target, resolutions, skill), + + Skill::Counter| + Skill::CounterPlus | + Skill::CounterPlusPlus => counter(source, target, resolutions, skill), + + Skill::Purge| + Skill::PurgePlus | + Skill::PurgePlusPlus => purge(source, target, resolutions, skill), + + Skill::Purify| + Skill::PurifyPlus | + Skill::PurifyPlusPlus => purify(source, target, resolutions, skill), + + Skill::Recharge| + Skill::RechargePlus | + Skill::RechargePlusPlus => recharge(source, target, resolutions, skill), + + Skill::Reflect| + Skill::ReflectPlus | + Skill::ReflectPlusPlus => reflect(source, target, resolutions, skill), + + Skill::Ruin| + Skill::RuinPlus | + Skill::RuinPlusPlus => ruin(source, target, resolutions, skill), + + Skill::Link| + Skill::LinkPlus | + Skill::LinkPlusPlus => link(source, target, resolutions, skill), + + Skill::Silence| + Skill::SilencePlus | + Skill::SilencePlusPlus => silence(source, target, resolutions, skill), + + Skill::Siphon| + Skill::SiphonPlus | + Skill::SiphonPlusPlus => siphon(source, target, resolutions, skill), + Skill::SiphonTick| + Skill::SiphonTickPlus | + Skill::SiphonTickPlusPlus => siphon_tick(source, target, resolutions, skill), + + Skill::Slay| + Skill::SlayPlus | + Skill::SlayPlusPlus => slay(source, target, resolutions, skill), + + Skill::Sleep| + Skill::SleepPlus | + Skill::SleepPlusPlus => sleep(source, target, resolutions, skill), + + Skill::Restrict| + Skill::RestrictPlus | + Skill::RestrictPlusPlus => restrict(source, target, resolutions, skill), + + Skill::Strike| + Skill::StrikePlus | + Skill::StrikePlusPlus => strike(source, target, resolutions, skill), + + Skill::Intercept| + Skill::InterceptPlus | + Skill::InterceptPlusPlus => intercept(source, target, resolutions, skill), + + Skill::Break| + Skill::BreakPlus | + Skill::BreakPlusPlus => break_(source, target, resolutions, skill), + + Skill::Triage| + Skill::TriagePlus | + Skill::TriagePlusPlus => triage(source, target, resolutions, skill), + + Skill::TriageTick| + Skill::TriageTickPlus | + Skill::TriageTickPlusPlus => triage_tick(source, target, resolutions, skill), + + // Base Skills + Skill::Attack => attack(source, target, resolutions, skill), + Skill::Block => block(source, target, resolutions, skill), + Skill::Buff => buff(source, target, resolutions, skill), + Skill::Debuff => debuff(source, target, resolutions, skill), + Skill::Stun => stun(source, target, resolutions, skill), + + // Triggered + Skill::Electrocute | + Skill::ElectrocutePlus | + Skill::ElectrocutePlusPlus => panic!("should only trigger from electrify hit"), + Skill::HasteStrike => panic!("should only trigger from haste"), + Skill::Absorption| + Skill::AbsorptionPlus | + Skill::AbsorptionPlusPlus => panic!("should only trigger from absorb"), + Skill::HybridBlast => panic!("should only trigger from hybrid"), + Skill::CounterAttack| + Skill::CounterAttackPlus | + Skill::CounterAttackPlusPlus => panic!("should only trigger from counter"), + + + // Not used + }; + + return resolutions; +} + +fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) -> Resolutions { + for Resolution { source: event_source, target: event_target, event, stages: _ } in resolutions.clone() { + let mut source = game.construct_by_id(event_source.id).unwrap().clone(); + let mut target = game.construct_by_id(event_target.id).unwrap().clone(); + + match event { + Event::Damage { amount, skill, mitigation, colour: c } => { + if target.affected(Effect::Electric) && !skill.is_tick() { + let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() + .find(|e| e.effect == Effect::Electric).unwrap().clone(); + match meta { + Some(EffectMeta::Skill(s)) => { + // Gurad against reflect overflow + if !(source.affected(Effect::Reflect) && target.affected(Effect::Reflect)) { + // Check reflect don't bother if electrocute is procing on death + if source.affected(Effect::Reflect) && !target.is_ko() { + resolutions.push(Resolution::new(&target, &source) + .event(Event::Reflection { skill: s }).stages(EventStages::EndPost)); + resolutions = electrocute(&mut source, &mut target, resolutions, s); + } else { + resolutions = electrocute(&mut target, &mut source, resolutions, s); + } + } + }, + _ => panic!("no electrify skill"), + }; + } + + if target.affected(Effect::Absorb) && !target.is_ko() { + let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() + .find(|e| e.effect == Effect::Absorb).unwrap().clone(); + match meta { + Some(EffectMeta::Skill(s)) => { + resolutions = absorption(&mut source, &mut target, resolutions, skill, amount + mitigation, s); + }, + _ => panic!("no absorb skill"), + }; + } + if c == Colour::Red { + if target.affected(Effect::Counter) && !target.is_ko() { + let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() + .find(|e| e.effect == Effect::Counter).unwrap().clone(); + match meta { + Some(EffectMeta::Skill(s)) => { + resolutions = counter_attack(&mut target, &mut source, resolutions, s); + }, + _ => panic!("no counter skill"), + }; + } + } + + if target.is_ko() && event_target.green == 0 { + // Make sure target ko is from this event + target.effects.clear(); + resolutions.push(Resolution::new(&source, &target).event(Event::Ko()).stages(EventStages::PostOnly)); + } + }, + _ => (), + }; + + + game.update_construct(&mut source); + game.update_construct(&mut target); + }; + + return resolutions; +} + + + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub struct Cast { + pub id: Uuid, + pub source_player_id: Uuid, + pub source_construct_id: Uuid, + pub target_construct_id: Uuid, + pub skill: Skill, + pub speed: u64, +} + +impl Cast { + pub fn new(source_construct_id: Uuid, source_player_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Cast { + return Cast { + id: Uuid::new_v4(), + source_construct_id, + source_player_id, + target_construct_id, + skill, + speed: 0, + }; + } + + pub fn new_tick(source: &mut Construct, target: &mut Construct, skill: Skill) -> Cast { + Cast { + id: Uuid::new_v4(), + source_construct_id: source.id, + source_player_id: source.account, + target_construct_id: target.id, + skill, + speed: source.skill_speed(skill), + } + } + + pub fn used_cooldown(&self) -> bool { + return self.skill.base_cd().is_some(); + } +} + +pub type Disable = Vec; +pub type Immunity = Vec; + +// used to show the progress of a construct +// while the resolutions are animating +#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +pub struct EventConstruct { + pub id: Uuid, + pub red: u64, + pub green: u64, + pub blue: u64, +} + +#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +pub enum EventStages { + #[serde(rename = "START_SKILL END_SKILL POST_SKILL")] + AllStages, // Anim Anim Anim + #[serde(rename = "START_SKILL END_SKILL")] + StartEnd, // Anim Anim Skip + #[serde(rename = "START_SKILL POST_SKILL")] + StartPost, // Anim Skip Anim + #[serde(rename = "START_SKILL")] + StartOnly, // Anim Skip Skip + #[serde(rename = "END_SKILL POST_SKILL")] + EndPost, // Skip Anim Anim + #[serde(rename = "END_SKILL")] + EndOnly, // Skip Anim Skip + #[serde(rename = "POST_SKILL")] + PostOnly, // Skip Skip Anim +} + +#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +pub struct Resolution { + pub source: EventConstruct, + pub target: EventConstruct, + pub event: Event, + pub stages: EventStages, +} + +impl Resolution { + fn new(source: &Construct, target: &Construct) -> Resolution { + Resolution { + source: EventConstruct { + id: source.id, + red: source.red_life(), + green: source.green_life(), + blue: source.blue_life(), + }, + target: EventConstruct { + id: target.id, + red: target.red_life(), + green: target.green_life(), + blue: target.blue_life(), + }, + event: Event::Incomplete, + stages: EventStages::AllStages, + } + } + + fn event(mut self, e: Event) -> Resolution { + self.event = e; + self + } + + fn stages(mut self, s: EventStages) -> Resolution { + self.stages = s; + self + } + + pub fn get_delay(self) -> i64 { + let source_duration = 1000; // Time for SOURCE ONLY + let target_delay = 500; // Used for Source + Target + let target_duration = 1500; // Time for TARGET ONLY + let post_skill = 1000; // Time for all POST + let source_and_target_total = target_delay + target_duration; // SOURCE + TARGET time + + match self.stages { + EventStages::AllStages => source_and_target_total + post_skill, // Anim Anim Anim + EventStages::StartEnd => source_and_target_total, // Anim Anim Skip + EventStages::StartPost => source_duration + post_skill, // Anim Skip Anim + EventStages::StartOnly => source_duration, // Anim Skip Skip + EventStages::EndPost => target_duration + post_skill, // Skip Anim Anim + EventStages::EndOnly => target_duration, // Skip Anim Skip + EventStages::PostOnly => post_skill, // Skip Skip Anim + } + } +} + + +#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +pub enum Event { + Disable { skill: Skill, disable: Disable }, + Immunity { skill: Skill, immunity: Immunity }, + Damage { skill: Skill, amount: u64, mitigation: u64, colour: Colour }, + Healing { skill: Skill, amount: u64, overhealing: u64 }, + Recharge { skill: Skill, red: u64, blue: u64 }, + Inversion { skill: Skill }, + Reflection { skill: Skill }, + AoeSkill { skill: Skill }, + Skill { skill: Skill }, + Effect { skill: Skill, effect: Effect, duration: u8, construct_effects: Vec }, + Removal { skill: Skill, effect: Option, construct_effects: Vec }, + TargetKo { skill: Skill }, + // skill not necessary but makes it neater as all events are arrays in js + Ko (), + Forfeit (), + Incomplete, + // not used + Evasion { skill: Skill, evasion_rating: u64 }, +} + +pub type Resolutions = Vec; + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum Skill { + Attack, + Debuff, + Buff, + Block, // reduce damage + Stun, + + // Boost -- sounds nice + // Evade, // actively evade + // Nightmare, + // Sleep, + + Amplify, + #[serde(rename = "Amplify+")] + AmplifyPlus, + #[serde(rename = "Amplify++")] + AmplifyPlusPlus, + + Absorb, + #[serde(rename = "Absorb+")] + AbsorbPlus, + #[serde(rename = "Absorb++")] + AbsorbPlusPlus, + + Banish, + #[serde(rename = "Banish+")] + BanishPlus, + #[serde(rename = "Banish++")] + BanishPlusPlus, + + Bash, + #[serde(rename = "Bash+")] + BashPlus, + #[serde(rename = "Bash++")] + BashPlusPlus, + + Blast, + #[serde(rename = "Blast+")] + BlastPlus, + #[serde(rename = "Blast++")] + BlastPlusPlus, + + Chaos, + #[serde(rename = "Chaos+")] + ChaosPlus, + #[serde(rename = "Chaos++")] + ChaosPlusPlus, + + Sustain, + #[serde(rename = "Sustain+")] + SustainPlus, + #[serde(rename = "Sustain++")] + SustainPlusPlus, + + Electrify, + #[serde(rename = "Electrify+")] + ElectrifyPlus, + #[serde(rename = "Electrify++")] + ElectrifyPlusPlus, + + Curse, + #[serde(rename = "Curse+")] + CursePlus, + #[serde(rename = "Curse++")] + CursePlusPlus, + + Decay, + #[serde(rename = "Decay+")] + DecayPlus, + #[serde(rename = "Decay++")] + DecayPlusPlus, + + Haste, + #[serde(rename = "Haste+")] + HastePlus, + #[serde(rename = "Haste++")] + HastePlusPlus, + + Heal, + #[serde(rename = "Heal+")] + HealPlus, + #[serde(rename = "Heal++")] + HealPlusPlus, + + Hybrid, + #[serde(rename = "Hybrid+")] + HybridPlus, + #[serde(rename = "Hybrid++")] + HybridPlusPlus, + + Invert, + #[serde(rename = "Invert+")] + InvertPlus, + #[serde(rename = "Invert++")] + InvertPlusPlus, + + Counter, + #[serde(rename = "Counter+")] + CounterPlus, + #[serde(rename = "Counter++")] + CounterPlusPlus, + + Purge, + #[serde(rename = "Purge+")] + PurgePlus, + #[serde(rename = "Purge++")] + PurgePlusPlus, + + Purify, + #[serde(rename = "Purify+")] + PurifyPlus, + #[serde(rename = "Purify++")] + PurifyPlusPlus, + + Reflect, + #[serde(rename = "Reflect+")] + ReflectPlus, + #[serde(rename = "Reflect++")] + ReflectPlusPlus, + + Recharge, + #[serde(rename = "Recharge+")] + RechargePlus, + #[serde(rename = "Recharge++")] + RechargePlusPlus, + + Ruin, + #[serde(rename = "Ruin+")] + RuinPlus, + #[serde(rename = "Ruin++")] + RuinPlusPlus, + + Link, + #[serde(rename = "Link+")] + LinkPlus, + #[serde(rename = "Link++")] + LinkPlusPlus, + + Silence, + #[serde(rename = "Silence+")] + SilencePlus, + #[serde(rename = "Silence++")] + SilencePlusPlus, + + Slay, + #[serde(rename = "Slay+")] + SlayPlus, + #[serde(rename = "Slay++")] + SlayPlusPlus, + + Sleep, + #[serde(rename = "Sleep+")] + SleepPlus, + #[serde(rename = "Sleep++")] + SleepPlusPlus, + + Restrict, + #[serde(rename = "Restrict+")] + RestrictPlus, + #[serde(rename = "Restrict++")] + RestrictPlusPlus, + + Strike, + #[serde(rename = "Strike+")] + StrikePlus, + #[serde(rename = "Strike++")] + StrikePlusPlus, + + Siphon, + #[serde(rename = "Siphon+")] + SiphonPlus, + #[serde(rename = "Siphon++")] + SiphonPlusPlus, + + Intercept, + #[serde(rename = "Intercept+")] + InterceptPlus, + #[serde(rename = "Intercept++")] + InterceptPlusPlus, + + Break, + #[serde(rename = "Break+")] + BreakPlus, + #[serde(rename = "Break++")] + BreakPlusPlus, + + Triage, + #[serde(rename = "Triage+")] + TriagePlus, + #[serde(rename = "Triage++")] + TriagePlusPlus, + + Absorption, + #[serde(rename = "Absorption+")] + AbsorptionPlus, + #[serde(rename = "Absorption++")] + AbsorptionPlusPlus, + + CounterAttack, + #[serde(rename = "CounterAttack+")] + CounterAttackPlus, + #[serde(rename = "CounterAttack++")] + CounterAttackPlusPlus, + + Electrocute, + #[serde(rename = "Electrocute+")] + ElectrocutePlus, + #[serde(rename = "Electrocute++")] + ElectrocutePlusPlus, + ElectrocuteTick, + #[serde(rename = "ElectrocuteTick+")] + ElectrocuteTickPlus, + #[serde(rename = "ElectrocuteTick++")] + ElectrocuteTickPlusPlus, + + DecayTick, // dot + #[serde(rename = "DecayTick+")] + DecayTickPlus, + #[serde(rename = "DecayTick++")] + DecayTickPlusPlus, + + HasteStrike, + HybridBlast, + + SiphonTick, + #[serde(rename = "SiphonTick+")] + SiphonTickPlus, + #[serde(rename = "SiphonTick++")] + SiphonTickPlusPlus, + + TriageTick, + #[serde(rename = "TriageTick+")] + TriageTickPlus, + #[serde(rename = "TriageTick++")] + TriageTickPlusPlus, +} + +impl Skill { + pub fn multiplier(&self) -> u64 { + match self { + // Attack Base + Skill::Attack => 80, // Base + + Skill::Blast => 105, // BB + Skill::BlastPlus => 140, // BB + Skill::BlastPlusPlus => 200, // BB + + Skill::Chaos => 40, // BR + Skill::ChaosPlus => 65, // BR + Skill::ChaosPlusPlus => 90, // BR + + Skill::Heal => 125, //GG + Skill::HealPlus => 185, //GG + Skill::HealPlusPlus => 270, //GG + + Skill::SiphonTick => 25, // GB + Skill::SiphonTickPlus => 30, + Skill::SiphonTickPlusPlus => 40, + + Skill::Slay => 45, // RG + Skill::SlayPlus => 65, + Skill::SlayPlusPlus => 100, + + Skill::Strike => 90, //RR + Skill::StrikePlus => 140, + Skill::StrikePlusPlus => 200, + + // Block Base + Skill::ElectrocuteTick => 80, + Skill::ElectrocuteTickPlus => 100, + Skill::ElectrocuteTickPlusPlus => 130, + + Skill::CounterAttack => 120, + Skill::CounterAttackPlus => 160, + Skill::CounterAttackPlusPlus => 230, + + Skill::Purify => 45, //Green dmg (heal) + Skill::PurifyPlus => 70, + Skill::PurifyPlusPlus => 105, + + Skill::Reflect => 45, //Recharge blue life (heal) + Skill::ReflectPlus => 70, + Skill::ReflectPlusPlus => 100, + + Skill::Recharge => 70, //Recharge red and blue life (heal) + Skill::RechargePlus => 110, + Skill::RechargePlusPlus => 170, + + Skill::Sustain => 120, // Recharge red life (heal) + Skill::SustainPlus => 150, + Skill::SustainPlusPlus => 230, + + // Stun Base + Skill::Sleep => 200, //Green dmg (heal) + Skill::SleepPlus => 290, + Skill::SleepPlusPlus => 400, + + Skill::Banish => 40, //Green dmg (heal) + Skill::BanishPlus => 75, + Skill::BanishPlusPlus => 125, + + Skill::Bash => 45, + Skill::BashPlus => 65, + Skill::BashPlusPlus => 100, + + Skill::Link => 25, + Skill::LinkPlus => 40, + Skill::LinkPlusPlus => 70, + + Skill::Ruin => 40, + Skill::RuinPlus => 70, + Skill::RuinPlusPlus => 100, + + // Debuff Base + Skill::DecayTick => 33, + Skill::DecayTickPlus => 45, + Skill::DecayTickPlusPlus => 70, + + Skill::Silence => 55, // Deals more per blue skill on target + Skill::SilencePlus => 80, + Skill::SilencePlusPlus => 110, + + Skill::Restrict => 40, // Deals more per red skill on target + Skill::RestrictPlus => 65, + Skill::RestrictPlusPlus => 100, + + // Buff base + Skill::HybridBlast => 50, + + Skill::HasteStrike => 60, + + Skill::Absorb=> 95, + Skill::AbsorbPlus => 120, + Skill::AbsorbPlusPlus => 155, + + Skill::Intercept => 80, + Skill::InterceptPlus => 110, + Skill::InterceptPlusPlus => 150, + + Skill::TriageTick => 75, + Skill::TriageTickPlus => 110, + Skill::TriageTickPlusPlus => 140, + + _ => 100, + } + } + + pub fn effect(&self) -> Vec { + match self { + // Modifiers + Skill::Amplify => vec![ConstructEffect {effect: Effect::Amplify, duration: 2, + meta: Some(EffectMeta::Multiplier(150)), tick: None}], + Skill::AmplifyPlus => vec![ConstructEffect {effect: Effect::Amplify, duration: 3, + meta: Some(EffectMeta::Multiplier(175)), tick: None}], + Skill::AmplifyPlusPlus => vec![ConstructEffect {effect: Effect::Amplify, duration: 4, + meta: Some(EffectMeta::Multiplier(200)), tick: None}], + + Skill::Banish => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}], + Skill::BanishPlus => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}], + Skill::BanishPlusPlus => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}], + Skill::Block => vec![ConstructEffect {effect: Effect::Block, duration: 1, + meta: Some(EffectMeta::Multiplier(35)), tick: None}], + Skill::Buff => vec![ConstructEffect {effect: Effect::Buff, duration: 3, + meta: Some(EffectMeta::Multiplier(130)), tick: None }], + + Skill::Electrify => vec![ConstructEffect {effect: Effect::Electric, duration: 1, + meta: Some(EffectMeta::Skill(Skill::Electrocute)), tick: None}], + Skill::ElectrifyPlus => vec![ConstructEffect {effect: Effect::Electric, duration: 1, + meta: Some(EffectMeta::Skill(Skill::ElectrocutePlus)), tick: None}], + Skill::ElectrifyPlusPlus => vec![ConstructEffect {effect: Effect::Electric, duration: 1, + meta: Some(EffectMeta::Skill(Skill::ElectrocutePlusPlus)), tick: None}], + Skill::Electrocute => vec![ConstructEffect {effect: Effect::Electrocute, duration: 2, + meta: Some(EffectMeta::Skill(Skill::ElectrocuteTick)), tick: None}], + Skill::ElectrocutePlus => vec![ConstructEffect {effect: Effect::Electrocute, duration: 3, + meta: Some(EffectMeta::Skill(Skill::ElectrocuteTickPlus)), tick: None}], + Skill::ElectrocutePlusPlus => vec![ConstructEffect {effect: Effect::Electrocute, duration: 4, + meta: Some(EffectMeta::Skill(Skill::ElectrocuteTickPlusPlus)), tick: None}], + + Skill::Sustain => vec![ConstructEffect {effect: Effect::Sustain, duration: 1, meta: None, tick: None }], + Skill::SustainPlus => vec![ConstructEffect {effect: Effect::Sustain, duration: 1, meta: None, tick: None }], + Skill::SustainPlusPlus => vec![ConstructEffect {effect: Effect::Sustain, duration: 1, meta: None, tick: None }], + + Skill::Curse => vec![ConstructEffect {effect: Effect::Curse, duration: 2, + meta: Some(EffectMeta::Multiplier(150)), tick: None}], + Skill::CursePlus => vec![ConstructEffect {effect: Effect::Curse, duration: 2, + meta: Some(EffectMeta::Multiplier(200)), tick: None}], + Skill::CursePlusPlus => vec![ConstructEffect {effect: Effect::Curse, duration: 3, + meta: Some(EffectMeta::Multiplier(250)), tick: None}], + + Skill::Debuff => vec![ConstructEffect {effect: Effect::Slow, duration: 3, + meta: Some(EffectMeta::Multiplier(50)), tick: None }], + + Skill::Decay => vec![ConstructEffect {effect: Effect::Wither, duration: 3, + meta: Some(EffectMeta::Multiplier(50)), tick: None }, + ConstructEffect {effect: Effect::Decay, duration: 3, + meta: Some(EffectMeta::Skill(Skill::DecayTick)), tick: None}], + Skill::DecayPlus => vec![ConstructEffect {effect: Effect::Wither, duration: 3, + meta: Some(EffectMeta::Multiplier(35)), tick: None }, + ConstructEffect {effect: Effect::Decay, duration: 3, + meta: Some(EffectMeta::Skill(Skill::DecayTickPlus)), tick: None}], + Skill::DecayPlusPlus => vec![ConstructEffect {effect: Effect::Wither, duration: 4, + meta: Some(EffectMeta::Multiplier(20)), tick: None }, + ConstructEffect {effect: Effect::Decay, duration: 4, + meta: Some(EffectMeta::Skill(Skill::DecayTickPlusPlus)), tick: None}], + + Skill::Haste => vec![ConstructEffect {effect: Effect::Haste, duration: 3, + meta: Some(EffectMeta::Multiplier(150)), tick: None }], + Skill::HastePlus => vec![ConstructEffect {effect: Effect::Haste, duration: 4, + meta: Some(EffectMeta::Multiplier(175)), tick: None }], + Skill::HastePlusPlus => vec![ConstructEffect {effect: Effect::Haste, duration: 5, + meta: Some(EffectMeta::Multiplier(225)), tick: None }], + + Skill::Absorb => vec![ConstructEffect {effect: Effect::Absorb, duration: 1, + meta: Some(EffectMeta::Skill(Skill::Absorption)), tick: None}], + Skill::AbsorbPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 1, + meta: Some(EffectMeta::Skill(Skill::AbsorptionPlus)), tick: None}], + Skill::AbsorbPlusPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 1, + meta: Some(EffectMeta::Skill(Skill::AbsorptionPlusPlus)), tick: None}], + + Skill::Absorption => vec![ConstructEffect {effect: Effect::Absorption, duration: 3, meta: None, tick: None}], + Skill::AbsorptionPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 5, meta: None, tick: None}], + Skill::AbsorptionPlusPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 7, meta: None, tick: None}], + + Skill::Hybrid => vec![ConstructEffect {effect: Effect::Hybrid, duration: 3, + meta: Some(EffectMeta::Multiplier(150)), tick: None }], + Skill::HybridPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 4, + meta: Some(EffectMeta::Multiplier(175)), tick: None }], + Skill::HybridPlusPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 5, + meta: Some(EffectMeta::Multiplier(225)), tick: None }], + + Skill::Invert => vec![ConstructEffect {effect: Effect::Invert, duration: 2, meta: None, tick: None}], + Skill::InvertPlus => vec![ConstructEffect {effect: Effect::Invert, duration: 3, meta: None, tick: None}], + Skill::InvertPlusPlus => vec![ConstructEffect {effect: Effect::Invert, duration: 4, meta: None, tick: None}], + + Skill::Counter => vec![ConstructEffect {effect: Effect::Counter, duration: 1, + meta: Some(EffectMeta::Skill(Skill::CounterAttack)), tick: None}], + Skill::CounterPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1, + meta: Some(EffectMeta::Skill(Skill::CounterAttackPlus)), tick: None}], + Skill::CounterPlusPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1, + meta: Some(EffectMeta::Skill(Skill::CounterAttackPlusPlus)), tick: None}], + + Skill::Reflect => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }], + Skill::ReflectPlus => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }], + Skill::ReflectPlusPlus => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }], + + Skill::Break => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}, + ConstructEffect {effect: Effect::Vulnerable, duration: 3, + meta: Some(EffectMeta::Multiplier(150)), tick: None}], + Skill::BreakPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}, + ConstructEffect {effect: Effect::Vulnerable, duration: 4, + meta: Some(EffectMeta::Multiplier(200)), tick: None}], + Skill::BreakPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}, + ConstructEffect {effect: Effect::Vulnerable, duration: 4, + meta: Some(EffectMeta::Multiplier(250)), tick: None}], + + Skill::Ruin => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], + Skill::RuinPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], + Skill::RuinPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], + + Skill::Purge => vec![ConstructEffect {effect: Effect::Purge, duration: 2, meta: None, tick: None}], + Skill::PurgePlus => vec![ConstructEffect {effect: Effect::Purge, duration: 3, meta: None, tick: None}], + Skill::PurgePlusPlus => vec![ConstructEffect {effect: Effect::Purge, duration: 4, meta: None, tick: None}], + + Skill::Link => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], + Skill::LinkPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], + Skill::LinkPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], + + Skill::Silence => vec![ConstructEffect {effect: Effect::Silence, duration: 2, meta: None, tick: None}], + Skill::SilencePlus => vec![ConstructEffect {effect: Effect::Silence, duration: 2, meta: None, tick: None}], + Skill::SilencePlusPlus => vec![ConstructEffect {effect: Effect::Silence, duration: 2, meta: None, tick: None}], + + Skill::Siphon => vec![ConstructEffect {effect: Effect::Siphon, duration: 2, + meta: Some(EffectMeta::Skill(Skill::SiphonTick)), tick: None}], + Skill::SiphonPlus => vec![ConstructEffect {effect: Effect::Siphon, duration: 3, + meta: Some(EffectMeta::Skill(Skill::SiphonTickPlus)), tick: None}], + Skill::SiphonPlusPlus => vec![ConstructEffect {effect: Effect::Siphon, duration: 4, + meta: Some(EffectMeta::Skill(Skill::SiphonTickPlusPlus)), tick: None}], + + Skill::Sleep => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}], + Skill::SleepPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 3, meta: None, tick: None}], + Skill::SleepPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 4, meta: None, tick: None}], + + Skill::Restrict => vec![ConstructEffect {effect: Effect::Restrict, duration: 2, meta: None, tick: None}], + Skill::RestrictPlus => vec![ConstructEffect {effect: Effect::Restrict, duration: 2, meta: None, tick: None}], + Skill::RestrictPlusPlus => vec![ConstructEffect {effect: Effect::Restrict, duration: 2, meta: None, tick: None}], + + Skill::Bash => vec![ConstructEffect {effect: Effect::Stun, duration: 2, + meta: Some(EffectMeta::Skill(Skill::Bash)), tick: None}], + Skill::BashPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2, + meta: Some(EffectMeta::Skill(Skill::BashPlus)), tick: None}], + Skill::BashPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2, + meta: Some(EffectMeta::Skill(Skill::BashPlusPlus)), tick: None}], + Skill::Stun => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}], + + Skill::Intercept => vec![ConstructEffect {effect: Effect::Intercept, duration: 1, meta: None, tick: None}], + Skill::InterceptPlus => vec![ConstructEffect {effect: Effect::Intercept, duration: 1, meta: None, tick: None}], + Skill::InterceptPlusPlus => vec![ConstructEffect {effect: Effect::Intercept, duration: 1, meta: None, tick: None}], + + Skill::Triage => vec![ConstructEffect {effect: Effect::Triage, duration: 2, + meta: Some(EffectMeta::Skill(Skill::TriageTick)), tick: None}], + Skill::TriagePlus => vec![ConstructEffect {effect: Effect::Triage, duration: 3, + meta: Some(EffectMeta::Skill(Skill::TriageTickPlus)), tick: None}], + Skill::TriagePlusPlus => vec![ConstructEffect {effect: Effect::Triage, duration: 4, + meta: Some(EffectMeta::Skill(Skill::TriageTickPlusPlus)), tick: None}], + + Skill::Purify => vec![ConstructEffect { effect: Effect::Pure, duration: 2, + meta: Some(EffectMeta::Multiplier(150)), tick: None}], + Skill::PurifyPlus => vec![ConstructEffect { effect: Effect::Pure, duration: 2, + meta: Some(EffectMeta::Multiplier(175)), tick: None}], + Skill::PurifyPlusPlus => vec![ConstructEffect { effect: Effect::Pure, duration: 2, + meta: Some(EffectMeta::Multiplier(200)), tick: None}], + + _ => { + panic!("{:?} no skill effect", self); + }, + } + } + + pub fn base_cd(&self) -> Cooldown { + match self { + Skill::Attack => None, + Skill::Block => None, // reduce damage + Skill::Buff => None, + Skill::Debuff => Some(1), + Skill::Stun => Some(2), + + Skill::Strike=> None, + Skill::StrikePlus => None, + Skill::StrikePlusPlus => None, + + Skill::Counter| + Skill::CounterPlus | + Skill::CounterPlusPlus => None, // avoid all damage + + Skill::Restrict | + Skill::RestrictPlus | + Skill::RestrictPlusPlus => Some(2), + + Skill::Bash | + Skill::BashPlus | + Skill::BashPlusPlus => Some(2), + + Skill::Heal=> None, + Skill::HealPlus => None, + Skill::HealPlusPlus => None, + + Skill::Triage=> None, // hot + Skill::TriagePlus => None, // hot + Skill::TriagePlusPlus => None, // hot + + Skill::Break | // no damage stun, adds vulnerable + Skill::BreakPlus | + Skill::BreakPlusPlus => Some(1), + + Skill::Blast | + Skill::BlastPlus | + Skill::BlastPlusPlus => None, + + Skill::Chaos | + Skill::ChaosPlus | + Skill::ChaosPlusPlus => None, + + Skill::Amplify | + Skill::AmplifyPlus | + Skill::AmplifyPlusPlus => Some(1), + + Skill::Hybrid | + Skill::HybridPlus | + Skill::HybridPlusPlus => Some(1), + + Skill::Invert | + Skill::InvertPlus | + Skill::InvertPlusPlus => Some(2), + + Skill::Decay => None, // dot + Skill::DecayPlus => None, + Skill::DecayPlusPlus => None, + + Skill::Siphon| + Skill::SiphonPlus | + Skill::SiphonPlusPlus => None, + + Skill::Curse | + Skill::CursePlus | + Skill::CursePlusPlus => Some(1), + + Skill::Link | + Skill::LinkPlus | + Skill::LinkPlusPlus => Some(1), + + Skill::Silence | + Skill::SilencePlus | + Skill::SilencePlusPlus => Some(2), + + Skill::Purify | + Skill::PurifyPlus | + Skill::PurifyPlusPlus => None, + + Skill::Purge | + Skill::PurgePlus | + Skill::PurgePlusPlus => Some(1), + + Skill::Banish | + Skill::BanishPlus | + Skill::BanishPlusPlus => Some(1), + + Skill::Haste | + Skill::HastePlus | + Skill::HastePlusPlus => Some(1), + + Skill::Reflect | + Skill::ReflectPlus | + Skill::ReflectPlusPlus => None, + + Skill::Recharge | + Skill::RechargePlus | + Skill::RechargePlusPlus => None, + + Skill::Ruin | + Skill::RuinPlus | + Skill::RuinPlusPlus => Some(2), + + Skill::Slay=> None, + Skill::SlayPlus => None, + Skill::SlayPlusPlus => None, + + Skill::Sleep | + Skill::SleepPlus | + Skill::SleepPlusPlus => Some(2), + + Skill::Sustain | + Skill::SustainPlus | + Skill::SustainPlusPlus => Some(1), + + Skill::Intercept => Some(1), + Skill::InterceptPlus => Some(1), + Skill::InterceptPlusPlus => Some(1), + + Skill::Electrify | + Skill::ElectrifyPlus | + Skill::ElectrifyPlusPlus => None, + + Skill::Absorb | + Skill::AbsorbPlus | + Skill::AbsorbPlusPlus => Some(1), + + //----------- + // Never cast directly + //--------- + // Trigger + Skill::HybridBlast | + Skill::HasteStrike | + Skill::CounterAttack| + Skill::CounterAttackPlus | + Skill::CounterAttackPlusPlus | // counter + Skill::Electrocute| + Skill::ElectrocutePlus | + Skill::ElectrocutePlusPlus | + Skill::Absorption| + Skill::AbsorptionPlus | + Skill::AbsorptionPlusPlus | + // Ticks + Skill::ElectrocuteTick| + Skill::ElectrocuteTickPlus | + Skill::ElectrocuteTickPlusPlus | + Skill::DecayTick| + Skill::DecayTickPlus | + Skill::DecayTickPlusPlus | + Skill::SiphonTick| + Skill::SiphonTickPlus | + Skill::SiphonTickPlusPlus | + Skill::TriageTick| + Skill::TriageTickPlus | + Skill::TriageTickPlusPlus => None, + } + } + + pub fn ko_castable(&self) -> bool { + match self { + Skill::ElectrocuteTick | + Skill::ElectrocuteTickPlus | + Skill::ElectrocuteTickPlusPlus | + Skill::DecayTick | + Skill::DecayTickPlus | + Skill::DecayTickPlusPlus | + Skill::SiphonTick | + Skill::SiphonTickPlus | + Skill::SiphonTickPlusPlus | + + Skill::TriageTick | + Skill::TriageTickPlus | + Skill::TriageTickPlusPlus => true, + _ => false, + } + } + + pub fn is_tick(&self) -> bool { + match self { + Skill::ElectrocuteTick | + Skill::ElectrocuteTickPlus | + Skill::ElectrocuteTickPlusPlus | + Skill::DecayTick | + Skill::DecayTickPlus | + Skill::DecayTickPlusPlus | + Skill::SiphonTick | + Skill::SiphonTickPlus | + Skill::SiphonTickPlusPlus | + Skill::TriageTick | + Skill::TriageTickPlus | + Skill::TriageTickPlusPlus => true, + + _ => false, + } + } + + pub fn speed(&self) -> u64 { + match self { + Skill::SiphonTick | + Skill::SiphonTickPlus | + Skill::SiphonTickPlusPlus => Skill::Siphon.speed(), + + Skill::DecayTick | + Skill::DecayTickPlus | + Skill::DecayTickPlusPlus => Skill::Decay.speed(), + + Skill::TriageTick | + Skill::TriageTickPlus | + Skill::TriageTickPlusPlus => Skill::Triage.speed(), + + Skill::ElectrocuteTick | + Skill::ElectrocuteTickPlus | + Skill::ElectrocuteTickPlusPlus => Skill::Electrify.speed(), + + _ => Item::from(*self).speed(), + } + } + + pub fn aoe(&self) -> bool { + match self { + Skill::Ruin | + Skill::RuinPlus | + Skill::RuinPlusPlus => true, + _ => false, + } + } + + pub fn defensive(&self) -> bool { + match self { + Skill::Amplify| + Skill::AmplifyPlus | + Skill::AmplifyPlusPlus | + Skill::Block | + Skill::Sustain | + Skill::SustainPlus | + Skill::SustainPlusPlus | + Skill::Electrify | + Skill::ElectrifyPlus | + Skill::ElectrifyPlusPlus | + Skill::Haste | + Skill::HastePlus | + Skill::HastePlusPlus | + Skill::Heal | + Skill::HealPlus | + Skill::HealPlusPlus | + Skill::Absorb | + Skill::AbsorbPlus | + Skill::AbsorbPlusPlus | + Skill::Invert | + Skill::InvertPlus | + Skill::InvertPlusPlus | + Skill::Intercept | + Skill::InterceptPlus | + Skill::InterceptPlusPlus | + Skill::Counter | + Skill::CounterPlus | + Skill::CounterPlusPlus | + Skill::Purify | + Skill::PurifyPlus | + Skill::PurifyPlusPlus | + Skill::Recharge | + Skill::RechargePlus | + Skill::RechargePlusPlus | + Skill::Reflect | + Skill::ReflectPlus | + Skill::ReflectPlusPlus | + Skill::Triage | + Skill::TriagePlus | + Skill::TriagePlusPlus => true, + + _ => false, + } + } + + fn components(&self) -> Vec { + let mut components = Item::from(*self).components(); + components.sort_unstable(); + return components; + } + + pub fn colours(&self) -> Vec { + let mut components = self.components(); + let colour_items = [Item::Red, Item::Green, Item::Blue]; + components.dedup(); + return components.iter() + .filter(|i| colour_items.contains(i)) + .map(|i| i.into_colour()) + .collect::>(); + } + + fn base(&self) -> Skill { + let bases = [Item::Attack, Item::Stun, Item::Buff, Item::Debuff, Item::Block]; + match self.components() + .iter() + .find(|i| bases.contains(i)) { + Some(i) => i.into_skill().unwrap(), + None => panic!("{:?} has no base item", self), + } + } +} + +fn attack(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let amount = source.red_power().pct(skill.multiplier()); + target.deal_red_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e))); + + return results; +} + +fn strike(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let amount = source.red_power().pct(skill.multiplier()); + target.deal_red_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e))); + + return results; +} + +fn stun(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + skill.effect().into_iter() + .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); + + return results; +} + +fn bash(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + skill.effect().into_iter() + .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); + + if results.iter().any(|r| match r.event { + Event::Effect { effect, skill: effect_skill, duration: _, construct_effects: _ } + => effect == Effect::Stun && skill == effect_skill, + _ => false, + }) { + let mut cds = 0; + for cs in target.skills.iter_mut() { + if cs.skill.base_cd().is_some() { + cs.cd = match cs.cd { + None => Some(1), + Some(i) => Some(i + 1), + }; + + cds += 1; + } + } + + let amount = source.red_power().pct(skill.multiplier().pct(100 + 45u64.saturating_mul(cds))); + target.deal_red_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + } + + return results; +} + + +fn sleep(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + skill.effect().into_iter() + .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); + + let amount = source.green_power().pct(skill.multiplier()); + target.deal_green_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + + return results; +} + +fn sustain(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + + let red_amount = source.red_power().pct(skill.multiplier()); + target.recharge(skill, red_amount, 0) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + + return results; +} + +fn intercept(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let intercept = skill.effect()[0]; + results.push(Resolution::new(source, target).event(target.add_effect(skill, intercept))); + + let red_amount = source.red_power().pct(skill.multiplier()); + target.recharge(skill, red_amount, 0) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + + return results; +} + +fn break_(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let stun = skill.effect()[0]; + results.push(Resolution::new(source, target).event(target.add_effect(skill, stun))); + let vuln = skill.effect()[1]; + results.push(Resolution::new(source, target).event(target.add_effect(skill, vuln)).stages(EventStages::PostOnly)); + + return results; +} + +fn block(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + return results; +} + +fn buff(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target) + .event(target.add_effect(skill, skill.effect()[0]))); + return results; +} + +fn counter(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target) + .event(target.add_effect(skill, skill.effect()[0]))); + + return results; +} + +fn counter_attack(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let amount = source.red_power().pct(skill.multiplier()); + target.deal_red_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e))); + + return results; +} + +fn restrict(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + skill.effect().into_iter() + .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); + + let s_multi = target.skills + .iter() + .fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Red) { + true => acc + 35, + false => acc, + }); + + let amount = source.red_power().pct(skill.multiplier()).pct(s_multi); + target.deal_red_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + + + return results; +} + +fn slay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let amount = source.red_power().pct(skill.multiplier()) + source.green_power().pct(skill.multiplier()); + let slay_events = target.deal_red_damage(skill, amount); + + for e in slay_events { + match e { + Event::Damage { amount, mitigation: _, colour: _, skill: _ } => { + results.push(Resolution::new(source, target).event(e)); + let heal = source.deal_green_damage(skill, amount.pct(50)); + for h in heal { + results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); + }; + }, + _ => results.push(Resolution::new(source, target).event(e)), + } + } + + return results; +} + +fn heal(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let amount = source.green_power().pct(skill.multiplier()); + target.deal_green_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e))); + return results; +} + +fn triage(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let skip_tick = target.effects.iter().any(|e| { + match e.effect { + Effect::Triage => source.skill_speed(skill) <= e.tick.unwrap().speed, + _ => false, + } + }); + + let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; + let tick_skill = match meta { + Some(EffectMeta::Skill(s)) => s, + _ => panic!("no triage tick skill"), + }; + let triage = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); + results.push(Resolution::new(source, target).event(target.add_effect(skill, triage))); + + match skip_tick { + true => return results, + false => return triage_tick(source, target, results, tick_skill) + } +} + +fn triage_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let amount = source.green_power().pct(skill.multiplier()); + target.deal_green_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); + return results; +} + +fn chaos(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let mut rng = thread_rng(); + let b_rng: u64 = rng.gen_range(100, 130); + let amount = source.blue_power().pct(skill.multiplier()).pct(b_rng); + target.deal_blue_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e))); + let r_rng: u64 = rng.gen_range(100, 130); + let amount = source.red_power().pct(skill.multiplier()).pct(r_rng); + target.deal_red_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + return results; +} + +fn blast(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let amount = source.blue_power().pct(skill.multiplier()); + target.deal_blue_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e))); + return results; +} + +fn amplify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + return results;; +} + +fn haste(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + return results;; +} + +fn debuff(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + return results;; +} + +fn decay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + + let wither = skill.effect()[0]; + results.push(Resolution::new(source, target).event(target.add_effect(skill, wither))); + + let skip_tick = target.effects.iter().any(|e| { + match e.effect { + Effect::Decay => source.skill_speed(skill) <= e.tick.unwrap().speed, + _ => false, + } + }); + let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[1]; + let tick_skill = match meta { + Some(EffectMeta::Skill(s)) => s, + _ => panic!("no decay tick skill"), + }; + let decay = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); + results.push(Resolution::new(source, target) + .event(target.add_effect(skill, decay)) + .stages(EventStages::PostOnly)); + + match skip_tick { + true => return results, + false => return decay_tick(source, target, results, tick_skill) + } +} + +fn decay_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let amount = source.blue_power().pct(skill.multiplier()); + target.deal_blue_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); + return results; +} + +// electrify is the buff effect +// when attacked it runs electrocute and applies a debuff +fn electrify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let electrify = skill.effect()[0]; + results.push(Resolution::new(source, target).event(target.add_effect(skill, electrify))); + return results;; +} + +fn electrocute(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + // Remove electric buff, no need to display if construct is dead + if !source.is_ko() { + let electric = source.effects.iter().position(|e| e.effect == Effect::Electric); + match electric { + Some(eff) => { + let ce = source.effects.remove(eff); + results.push(Resolution::new(source, source) + .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: source.effects.clone() }) + .stages(EventStages::PostOnly)); + } + None => () + } + } + + let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; + let tick_skill = match meta { + Some(EffectMeta::Skill(s)) => s, + _ => panic!("no electrocute tick skill"), + }; + + let skip_tick = target.effects.iter().any(|e| { + match e.effect { + Effect::Electrocute => source.skill_speed(skill) <= e.tick.unwrap().speed, + _ => false, + } + }); + let electrocute = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); + results.push(Resolution::new(source, target) + .event(target.add_effect(skill, electrocute)) + .stages(EventStages::PostOnly)); + + + match skip_tick { + true => return results, + false => return electrocute_tick(source, target, results, tick_skill) + } +} + +fn electrocute_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let amount = source.blue_power().pct(skill.multiplier()); + target.deal_blue_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); + return results; +} + +fn ruin(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let amount = source.blue_power().pct(skill.multiplier()); + target.deal_blue_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + + results.push(Resolution::new(source, target) + .event(target.add_effect(skill, skill.effect()[0])) + .stages(EventStages::PostOnly)); + return results;; +} + +fn absorb(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + let blue_amount = source.blue_power().pct(skill.multiplier()); + target.recharge(skill, 0, blue_amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + return results;; +} + +fn absorption(source: &mut Construct, target: &mut Construct, mut results: Resolutions, reflect_skill: Skill, amount: u64, skill: Skill) -> Resolutions { + let absorb = skill.effect()[0].set_meta(EffectMeta::AddedDamage(amount)); + + results.push(Resolution::new(source, target) + .event(target.add_effect(reflect_skill, absorb)) + .stages(EventStages::PostOnly)); + + let absorb_index = target.effects.iter().position(|e| e.effect == Effect::Absorb).expect("No absorb"); + let ce = target.effects.remove(absorb_index); + + results.push(Resolution::new(source, target) + .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: target.effects.clone() }) + .stages(EventStages::PostOnly)); + return results;; +} + +fn curse(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + return results;; +} + +fn hybrid(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + return results;; +} + +fn invert(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + return results;; +} + +fn reflect(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + + let blue_amount = source.blue_power().pct(skill.multiplier()); + target.recharge(skill, 0, blue_amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + + return results;; +} + +fn recharge(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); + let red_amount = source.red_power().pct(skill.multiplier()); + let blue_amount = source.blue_power().pct(skill.multiplier()); + target.recharge(skill, red_amount, blue_amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + + return results; +} + +fn siphon(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + + let skip_tick = target.effects.iter().any(|e| { + match e.effect { + Effect::Siphon => source.skill_speed(skill) <= e.tick.unwrap().speed, + _ => false, + } + }); + let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; + let tick_skill = match meta { + Some(EffectMeta::Skill(s)) => s, + _ => panic!("no siphon tick skill"), + }; + let siphon = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); + results.push(Resolution::new(source, target).event(target.add_effect(skill, siphon))); + + match skip_tick { + true => return results, + false => return siphon_tick(source, target, results, tick_skill) + } +} + +fn siphon_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + let amount = source.blue_power().pct(skill.multiplier()) + source.green_power().pct(skill.multiplier()); + let siphon_events = target.deal_blue_damage(skill, amount); + + for e in siphon_events { + match e { + Event::Damage { amount, mitigation: _, colour: _, skill: _ } => { + results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)); + let heal = source.deal_green_damage(skill, amount); + for h in heal { + results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); + }; + }, + _ => results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)), + } + } + + return results; +} + +fn link(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + + let amount = source.blue_power().pct(skill.multiplier().saturating_mul(target.effects.len() as u64)); + target.deal_blue_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + + return results; +} + +fn silence(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + + let s_multi = target.skills + .iter() + .fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Blue) { + true => acc + 45, + false => acc, + }); + + let amount = source.blue_power().pct(skill.multiplier()).pct(s_multi); + target.deal_blue_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + + return results; +} + +fn purge(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); + if target.effects.len() > 0 { + target.effects.clear(); + results.push(Resolution::new(source, target) + .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) + .stages(EventStages::PostOnly)); + } + + let effect = skill.effect()[0]; + results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); + + return results; +} + +fn purify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); + if target.effects.len() > 0 { + let amount = source.green_power().pct(skill.multiplier().saturating_mul(target.effects.len() as u64)); + target.effects.clear(); + results.push(Resolution::new(source, target) + .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) + .stages(EventStages::PostOnly)); + target.deal_green_damage(skill, amount) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + } + let effect = skill.effect()[0]; + results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); + + return results; +} + +fn banish(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { + results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); + + let red_damage = target.red_life().pct(skill.multiplier()); + let blue_damage = target.blue_life().pct(skill.multiplier()); + + if red_damage > 0 { + target.deal_red_damage(skill, red_damage) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + } + + if blue_damage > 0 { + target.deal_blue_damage(skill, blue_damage) + .into_iter() + .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + } + + results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])).stages(EventStages::PostOnly)); + return results; +} + +#[cfg(test)] +mod tests { + use skill::*; + + #[test] + fn heal_test() { + let mut x = Construct::new() + .named(&"muji".to_string()) + .learn(Skill::Heal); + + let mut y = Construct::new() + .named(&"camel".to_string()) + .learn(Skill::Heal); + + x.deal_red_damage(Skill::Attack, 5); + + heal(&mut y, &mut x, vec![], Skill::Heal); + } + + #[test] + fn decay_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + let mut y = Construct::new() + .named(&"camel".to_string()); + + decay(&mut x, &mut y, vec![], Skill::Decay); + + assert!(y.effects.iter().any(|e| e.effect == Effect::Decay)); + + y.reduce_effect_durations(); + let _decay = y.effects.iter().find(|e| e.effect == Effect::Decay); + // assert!(y.green_life() == y.green_life().saturating_sub(decay.unwrap().tick.unwrap().amount)); + } + + #[test] + fn block_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + let mut y = Construct::new() + .named(&"camel".to_string()); + + // ensure it doesn't have 0 pd + x.red_power.force(100); + y.green_life.force(500); + + block(&mut y.clone(), &mut y, vec![], Skill::Block); + assert!(y.effects.iter().any(|e| e.effect == Effect::Block)); + + let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); + + let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); + match event { + Event::Damage { amount, mitigation: _, colour: _, skill: _ } => + assert!(amount < x.red_power().pct(Skill::Attack.multiplier())), + _ => panic!("not damage"), + }; + } + + #[test] + fn sustain_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + let mut y = Construct::new() + .named(&"camel".to_string()); + + x.red_power.force(10000000000000); // multiplication of int max will cause overflow + y.green_life.force(1024); // make tests more flexible if we change stats + + sustain(&mut y.clone(), &mut y, vec![], Skill::Sustain); + assert!(y.affected(Effect::Sustain)); + + let mut results = ruin(&mut x, &mut y, vec![], Skill::Ruin); + let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); + match event { + Event::Immunity { skill: _, immunity } => assert!(immunity.contains(&Effect::Sustain)), + _ => panic!("not immune cluthc"), + }; + + let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); + assert!(y.green_life() == 1); + + let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); + match event { + Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 1023), + _ => panic!("not damage"), + }; + } + + #[test] + fn invert_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + let mut y = Construct::new() + .named(&"camel".to_string()); + + // give red shield but reduce to 0 + y.red_life.force(64); + y.red_life.reduce(64); + x.red_power.force(512); + invert(&mut y.clone(), &mut y, vec![], Skill::Invert); + assert!(y.affected(Effect::Invert)); + + // heal should deal green damage + heal(&mut x, &mut y, vec![], Skill::Heal); + assert!(y.green_life() < 1024); + + // attack should heal and recharge red shield + let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); + + // match results.remove(0).event { + // Event::Inversion { skill } => assert_eq!(skill, Skill::Attack), + // _ => panic!("not inversion"), + //}; + + match results.remove(0).event { + Event::Healing { skill: _, overhealing: _, amount } => assert!(amount > 0), + _ => panic!("not healing from inversion"), + }; + + match results.remove(0).event { + Event::Recharge { skill: _, red, blue: _ } => assert!(red > 0), + _ => panic!("not recharge from inversion"), + }; + } + + #[test] + fn reflect_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + let mut y = Construct::new() + .named(&"camel".to_string()); + + reflect(&mut y.clone(), &mut y, vec![], Skill::Reflect); + assert!(y.affected(Effect::Reflect)); + + let mut results = vec![]; + results = resolve_skill(Skill::Blast, &mut x, &mut y, results); + + assert!(x.green_life() < 1024); + + let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); + match event { + Event::Reflection { skill } => assert_eq!(skill, Skill::Blast), + _ => panic!("not reflection"), + }; + + let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); + match event { + Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert!(amount > 0), + _ => panic!("not damage"), + }; + } + + #[test] + fn siphon_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + let mut y = Construct::new() + .named(&"camel".to_string()); + + x.blue_power.force(256); + x.green_power.force(220); + x.green_life.force(1024); + y.blue_life.force(0); + x.green_life.reduce(512); + + let mut results = resolve_skill(Skill::Siphon, &mut x, &mut y, vec![]); + + assert!(y.affected(Effect::Siphon)); + assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier()))); + + let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); + match event { + Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Siphon), + _ => panic!("not siphon"), + }; + + let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); + match event { + Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) + + 220.pct(Skill::SiphonTick.multiplier())), + _ => panic!("not damage siphon"), + }; + + let Resolution { source: _, target, event, stages: _ } = results.remove(0); + match event { + Event::Healing { amount, skill: _, overhealing: _ } => { + assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier())); + assert_eq!(target.id, x.id); + }, + _ => panic!("not healing"), + }; + } + + #[test] + fn triage_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + let mut y = Construct::new() + .named(&"pretaliation".to_string()); + + // ensure it doesn't have 0 sd + x.blue_power.force(50); + + // remove all mitigation + y.red_life.force(0); + y.blue_life.force(0); + + y.deal_red_damage(Skill::Attack, 5); + let prev_hp = y.green_life(); + + triage(&mut x, &mut y, vec![], Skill::Triage); + + assert!(y.effects.iter().any(|e| e.effect == Effect::Triage)); + assert!(y.green_life() > prev_hp); + } + + #[test] + fn recharge_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + let mut y = Construct::new() + .named(&"pretaliation".to_string()); + + y.red_life.force(50); + y.blue_life.force(50); + + y.deal_red_damage(Skill::Attack, 5); + y.deal_blue_damage(Skill::Blast, 5); + + let mut results = recharge(&mut x, &mut y, vec![], Skill::Recharge); + + results.remove(0); + let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); + match event { + Event::Recharge { red, blue, skill: _ } => { + assert!(red == 5); + assert!(blue == 5); + } + _ => panic!("result was not recharge"), + } + } + + + #[test] + fn silence_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + silence(&mut x.clone(), &mut x, vec![], Skill::Silence); + assert!(x.effects.iter().any(|e| e.effect == Effect::Silence)); + assert!(x.disabled(Skill::Silence).is_some()); + } + + #[test] + fn amplify_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + x.blue_power.force(50); + + amplify(&mut x.clone(), &mut x, vec![], Skill::Amplify); + assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify)); + assert_eq!(x.blue_power(), 75); + } + + #[test] + fn purify_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + decay(&mut x.clone(), &mut x, vec![], Skill::Decay); + assert!(x.effects.iter().any(|e| e.effect == Effect::Decay)); + + purify(&mut x.clone(), &mut x, vec![], Skill::Purify); + assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay)); + } + + #[test] + fn bash_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + let mut y = Construct::new() + .named(&"pretaliation".to_string()) + .learn(Skill::Stun); + + let stun_cd = y.skills.iter().find(|cs| cs.skill == Skill::Stun).unwrap().cd.unwrap(); + + bash(&mut x, &mut y, vec![], Skill::Bash); + assert!(!x.effects.iter().any(|e| e.effect == Effect::Stun)); + assert!(y.skills.iter().any(|cs| cs.skill == Skill::Stun && cs.cd.unwrap() == stun_cd + 1)); + } + + #[test] + fn purge_test() { + let mut x = Construct::new() + .named(&"muji".to_string()); + + let mut y = Construct::new() + .named(&"pretaliation".to_string()) + .learn(Skill::Heal) + .learn(Skill::HealPlus); + + purge(&mut x, &mut y, vec![], Skill::Purge); + // 2 turns at lvl 1 + assert!(y.effects.iter().any(|e| e.effect == Effect::Purge && e.duration == 2)); + assert!(y.disabled(Skill::Heal).is_some()); + } +} diff --git a/core/src/spec.rs b/core/src/spec.rs new file mode 100644 index 00000000..736eaba1 --- /dev/null +++ b/core/src/spec.rs @@ -0,0 +1,734 @@ +use construct::{Stat, Colours}; +use util::{IntPct}; + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct SpecBonus { + pub req: Colours, + pub bonus: u64, +} + +impl SpecBonus { + pub fn get_bonus(&self, c: &Colours) -> u64 { + if c.red >= self.req.red && c.blue >= self.req.blue && c.green >= self.req.green { + return self.bonus; + } + return 0; + } +} + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct SpecValues { + pub base: u64, + pub bonuses: Vec, +} + +impl SpecValues { + pub fn max_value (&self, c: &Colours) -> u64 { + self.bonuses.iter().fold(self.base, |acc, s| acc + s.get_bonus(c)) + } + + pub fn base (self) -> u64 { + self.base + } +} + + +#[derive(Debug,Copy,Clone,Serialize,Deserialize,PartialEq,PartialOrd,Ord,Eq)] +pub enum Spec { + Speed, + SpeedRR, + SpeedBB, + SpeedGG, + SpeedRG, + SpeedGB, + SpeedRB, + + SpeedRRPlus, + SpeedBBPlus, + SpeedGGPlus, + SpeedRGPlus, + SpeedGBPlus, + SpeedRBPlus, + + SpeedRRPlusPlus, + SpeedBBPlusPlus, + SpeedGGPlusPlus, + SpeedRGPlusPlus, + SpeedGBPlusPlus, + SpeedRBPlusPlus, + + Life, + LifeGG, + LifeRR, + LifeBB, + LifeRG, + LifeGB, + LifeRB, + LifeGGPlus, + LifeRRPlus, + LifeBBPlus, + LifeRGPlus, + LifeGBPlus, + LifeRBPlus, + LifeGGPlusPlus, + LifeRRPlusPlus, + LifeBBPlusPlus, + LifeRGPlusPlus, + LifeGBPlusPlus, + LifeRBPlusPlus, + + Power, + PowerRR, + PowerGG, + PowerBB, + PowerRG, + PowerGB, + PowerRB, + PowerRRPlus, + PowerGGPlus, + PowerBBPlus, + PowerRGPlus, + PowerGBPlus, + PowerRBPlus, + PowerRRPlusPlus, + PowerGGPlusPlus, + PowerBBPlusPlus, + PowerRGPlusPlus, + PowerGBPlusPlus, + PowerRBPlusPlus, + +} + +impl Spec { + pub fn affects(&self) -> Vec { + match *self { + Spec::Power => vec![Stat::BluePower, Stat::RedPower, Stat::GreenPower], + Spec::PowerRR => vec![Stat::RedPower], + Spec::PowerGG => vec![Stat::GreenPower], + Spec::PowerBB => vec![Stat::BluePower], + Spec::PowerRG => vec![Stat::GreenPower, Stat::RedPower], + Spec::PowerGB => vec![Stat::GreenPower, Stat::BluePower], + Spec::PowerRB => vec![Stat::RedPower, Stat::BluePower], + Spec::PowerRRPlus => vec![Stat::RedPower], + Spec::PowerGGPlus => vec![Stat::GreenPower], + Spec::PowerBBPlus => vec![Stat::BluePower], + Spec::PowerRGPlus => vec![Stat::GreenPower, Stat::RedPower], + Spec::PowerGBPlus => vec![Stat::GreenPower, Stat::BluePower], + Spec::PowerRBPlus => vec![Stat::RedPower, Stat::BluePower], + Spec::PowerRRPlusPlus => vec![Stat::RedPower], + Spec::PowerGGPlusPlus => vec![Stat::GreenPower], + Spec::PowerBBPlusPlus => vec![Stat::BluePower], + Spec::PowerRGPlusPlus => vec![Stat::GreenPower, Stat::RedPower], + Spec::PowerGBPlusPlus => vec![Stat::GreenPower, Stat::BluePower], + Spec::PowerRBPlusPlus => vec![Stat::RedPower, Stat::BluePower], + + Spec::Speed => vec![Stat::Speed], + Spec::SpeedRR => vec![Stat::Speed], + Spec::SpeedBB => vec![Stat::Speed], + Spec::SpeedGG => vec![Stat::Speed], + Spec::SpeedRG => vec![Stat::Speed], + Spec::SpeedGB => vec![Stat::Speed], + Spec::SpeedRB => vec![Stat::Speed], + Spec::SpeedRRPlus => vec![Stat::Speed], + Spec::SpeedBBPlus => vec![Stat::Speed], + Spec::SpeedGGPlus => vec![Stat::Speed], + Spec::SpeedRGPlus => vec![Stat::Speed], + Spec::SpeedGBPlus => vec![Stat::Speed], + Spec::SpeedRBPlus => vec![Stat::Speed], + Spec::SpeedRRPlusPlus => vec![Stat::Speed], + Spec::SpeedBBPlusPlus => vec![Stat::Speed], + Spec::SpeedGGPlusPlus => vec![Stat::Speed], + Spec::SpeedRGPlusPlus => vec![Stat::Speed], + Spec::SpeedGBPlusPlus => vec![Stat::Speed], + Spec::SpeedRBPlusPlus => vec![Stat::Speed], + + Spec::Life => vec![Stat::GreenLife], + Spec::LifeRR => vec![Stat::RedLife], + Spec::LifeBB => vec![Stat::BlueLife], + Spec::LifeGG => vec![Stat::GreenLife], + Spec::LifeRG => vec![Stat::GreenLife, Stat::RedLife], + Spec::LifeGB => vec![Stat::GreenLife, Stat::BlueLife], + Spec::LifeRB => vec![Stat::BlueLife, Stat::RedLife], + Spec::LifeRRPlus => vec![Stat::RedLife], + Spec::LifeBBPlus => vec![Stat::BlueLife], + Spec::LifeGGPlus => vec![Stat::GreenLife], + Spec::LifeRGPlus => vec![Stat::GreenLife, Stat::RedLife], + Spec::LifeGBPlus => vec![Stat::GreenLife, Stat::BlueLife], + Spec::LifeRBPlus => vec![Stat::BlueLife, Stat::RedLife], + Spec::LifeRRPlusPlus => vec![Stat::RedLife], + Spec::LifeBBPlusPlus => vec![Stat::BlueLife], + Spec::LifeGGPlusPlus => vec![Stat::GreenLife], + Spec::LifeRGPlusPlus => vec![Stat::GreenLife, Stat::RedLife], + Spec::LifeGBPlusPlus => vec![Stat::GreenLife, Stat::BlueLife], + Spec::LifeRBPlusPlus => vec![Stat::BlueLife, Stat::RedLife], + } + } + + pub fn values(&self) -> SpecValues { + match *self { + Spec::Power => SpecValues { + base: 10, + bonuses: vec![] + }, + + Spec::PowerRR=> SpecValues { + base: 25, + bonuses: vec![ + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 10 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 15 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 20 } + ], + }, + + Spec::PowerGG=> SpecValues { + base: 25, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 10 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 15 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 20 } + ], + }, + + Spec::PowerBB=> SpecValues { + base: 25, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 10 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 15 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 20 } + ], + }, + + Spec::PowerRG=> SpecValues { + base: 20, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 5 }, + SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 10 }, + SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 15 } + ], + }, + + Spec::PowerGB=> SpecValues { + base: 20, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 5 }, + SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 10 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 15 } + ], + }, + + Spec::PowerRB=> SpecValues { + base: 20, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 5 }, + SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 10 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 15 } + ], + }, + + Spec::PowerRRPlus => SpecValues { + base: 45, + bonuses: vec![ + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 15 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 25 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 35 } + ], + }, + + Spec::PowerGGPlus => SpecValues { + base: 45, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 15 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 25 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 35 } + ], + }, + + Spec::PowerBBPlus => SpecValues { + base: 45, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 15 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 25 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 35 } + ], + }, + + Spec::PowerRGPlus => SpecValues { + base: 35, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 10 }, + SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 20 }, + SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 25 } + ], + }, + + Spec::PowerGBPlus => SpecValues { + base: 35, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 10 }, + SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 20 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 25 } + ], + }, + + Spec::PowerRBPlus => SpecValues { + base: 35, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 10 }, + SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 20 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 25 } + ], + }, + Spec::PowerRRPlusPlus => SpecValues { + base: 80, + bonuses: vec![ + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 25 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 45 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 60 } + ], + }, + + Spec::PowerGGPlusPlus => SpecValues { + base: 80, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 25 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 45 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 60 } + ], + }, + + Spec::PowerBBPlusPlus => SpecValues { + base: 80, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 25 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 45 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 60 } + ], + }, + + Spec::PowerRGPlusPlus => SpecValues { + base: 60, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 20 }, + SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 30 }, + SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 45 } + ], + }, + + Spec::PowerGBPlusPlus => SpecValues { + base: 60, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 20 }, + SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 30 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 45 } + ], + }, + + Spec::PowerRBPlusPlus => SpecValues { + base: 60, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 20 }, + SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 30 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 45 } + ], + }, + + Spec::Speed => SpecValues { + base: 40, + bonuses: vec![] + }, + + Spec::SpeedRR=> SpecValues { + base: 80, + bonuses: vec![ + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 80 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 80 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 80 } + ], + }, + + Spec::SpeedGG=> SpecValues { + base: 80, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 80 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 80 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 80 } + ], + }, + + Spec::SpeedBB=> SpecValues { + base: 80, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 80 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 80 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 80 } + ], + }, + + Spec::SpeedRG=> SpecValues { + base: 60, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 60 }, + SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 60 }, + SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 60 } + ], + }, + + Spec::SpeedGB=> SpecValues { + base: 60, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 60 }, + SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 60 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 60 } + ], + }, + + Spec::SpeedRB=> SpecValues { + base: 60, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 60 }, + SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 60 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 60 } + ], + }, + + Spec::SpeedRRPlus => SpecValues { + base: 120, + bonuses: vec![ + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 120 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 120 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 120 } + ], + }, + + Spec::SpeedGGPlus => SpecValues { + base: 120, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 120 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 120 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 120 } + ], + }, + + Spec::SpeedBBPlus => SpecValues { + base: 120, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 120 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 120 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 120 } + ], + }, + + Spec::SpeedRGPlus => SpecValues { + base: 80, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 80 }, + SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 80 }, + SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 80 } + ], + }, + + Spec::SpeedGBPlus => SpecValues { + base: 80, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 80 }, + SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 80 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 80 } + ], + }, + + Spec::SpeedRBPlus => SpecValues { + base: 80, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 80 }, + SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 80 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 80 } + ], + }, + + Spec::SpeedRRPlusPlus => SpecValues { + base: 160, + bonuses: vec![ + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 160 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 160 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 160 } + ], + }, + + Spec::SpeedGGPlusPlus => SpecValues { + base: 160, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 160 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 160 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 160 } + ], + }, + + Spec::SpeedBBPlusPlus => SpecValues { + base: 160, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 160 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 160 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 160 } + ], + }, + + Spec::SpeedRGPlusPlus => SpecValues { + base: 120, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 120 }, + SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 120 }, + SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 120 } + ], + }, + + Spec::SpeedGBPlusPlus => SpecValues { + base: 120, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 120 }, + SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 120 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 120 } + ], + }, + + Spec::SpeedRBPlusPlus => SpecValues { + base: 120, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 120 }, + SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 120 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 120 } + ], + }, + + Spec::Life => SpecValues { + base: 125, + bonuses: vec![]}, + + Spec::LifeRR=> SpecValues { + base: 275, + bonuses: vec![ + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 75 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 125 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 175 } + ], + }, + + Spec::LifeGG=> SpecValues { + base: 225, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 50 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 75 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 125 } + ], + }, + + Spec::LifeBB=> SpecValues { + base: 275, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 75 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 125 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 175 } + ], + }, + + Spec::LifeRG=> SpecValues { + base: 125, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 50 }, + SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 75 }, + SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 125 } + ], + }, + + Spec::LifeGB=> SpecValues { + base: 125, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 50 }, + SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 75 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 125 } + ], + }, + + Spec::LifeRB=> SpecValues { + base: 175, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 50 }, + SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 75 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 125 } + ], + }, + + Spec::LifeRRPlus => SpecValues { + base: 500, + bonuses: vec![ + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 125 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 225 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 300 } + ], + }, + + Spec::LifeGGPlus => SpecValues { + base: 400, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 90 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 130 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 225 } + ], + }, + + Spec::LifeBBPlus => SpecValues { + base: 500, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 125 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 225 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 300 } + ], + }, + + Spec::LifeRGPlus => SpecValues { + base: 225, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 100 }, + SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 150 }, + SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 225 } + ], + }, + + Spec::LifeGBPlus => SpecValues { + base: 225, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 100 }, + SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 150 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 225 } + ], + }, + + Spec::LifeRBPlus => SpecValues { + base: 350, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 100 }, + SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 150 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 225 } + ], + }, + Spec::LifeRRPlusPlus => SpecValues { + base: 875, + bonuses: vec![ + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 225 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 400 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 525 } + ], + }, + + Spec::LifeGGPlusPlus => SpecValues { + base: 475, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 130 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 225 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 300 } + ], + }, + + Spec::LifeBBPlusPlus => SpecValues { + base: 875, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 225 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 400 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 525 } + ], + }, + + Spec::LifeRGPlusPlus => SpecValues { + base: 400, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 175 }, + SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 275 }, + SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 400 } + ], + }, + + Spec::LifeGBPlusPlus => SpecValues { + base: 625, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 175 }, + SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 275 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 400 } + ], + }, + + Spec::LifeRBPlusPlus => SpecValues { + base: 400, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 175 }, + SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 275 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 400 } + ], + }, + } + } + + pub fn apply(&self, modified: u64, base: u64, player_colours: &Colours) -> u64 { + match *self { + // Percentage multipliers based on base value + Spec::Power | + Spec::Speed => modified + base.pct(self.values().base), + Spec::PowerRR| + Spec::PowerGG| + Spec::PowerBB| + Spec::PowerRG| + Spec::PowerGB| + Spec::PowerRB| + Spec::PowerRRPlus | + Spec::PowerGGPlus | + Spec::PowerBBPlus | + Spec::PowerRGPlus | + Spec::PowerGBPlus | + Spec::PowerRBPlus | + Spec::PowerRRPlusPlus | + Spec::PowerGGPlusPlus | + Spec::PowerBBPlusPlus | + Spec::PowerRGPlusPlus | + Spec::PowerGBPlusPlus | + Spec::PowerRBPlusPlus | + + Spec::SpeedRR| + Spec::SpeedGG| + Spec::SpeedBB| + Spec::SpeedRG| + Spec::SpeedGB| + Spec::SpeedRB| + Spec::SpeedRRPlus | + Spec::SpeedGGPlus | + Spec::SpeedBBPlus | + Spec::SpeedRGPlus | + Spec::SpeedGBPlus | + Spec::SpeedRBPlus | + Spec::SpeedRRPlusPlus | + Spec::SpeedGGPlusPlus | + Spec::SpeedBBPlusPlus | + Spec::SpeedRGPlusPlus | + Spec::SpeedGBPlusPlus | + Spec::SpeedRBPlusPlus => modified + base.pct(self.values().max_value(player_colours)), + + // Flat bonus + Spec::Life => modified + self.values().base, + Spec::LifeRR| + Spec::LifeGG| + Spec::LifeBB| + Spec::LifeRG| + Spec::LifeGB| + Spec::LifeRB| + Spec::LifeRRPlus | + Spec::LifeGGPlus | + Spec::LifeBBPlus | + Spec::LifeRGPlus | + Spec::LifeGBPlus | + Spec::LifeRBPlus | + Spec::LifeRRPlusPlus | + Spec::LifeGGPlusPlus | + Spec::LifeBBPlusPlus | + Spec::LifeRGPlusPlus | + Spec::LifeGBPlusPlus | + Spec::LifeRBPlusPlus => modified + self.values().max_value(player_colours), + } + } +} diff --git a/core/src/util.rs b/core/src/util.rs new file mode 100644 index 00000000..bf4f3032 --- /dev/null +++ b/core/src/util.rs @@ -0,0 +1,40 @@ +// use net::Db; +// Db Commons +// use failure::Error; + +// pub fn startup(db: Db) -> Result<(), Error> { +// let tx = db.transaction()?; + +// info!("running startup fns"); + +// match tx.commit() { +// Ok(_) => { +// info!("startup processes completed"); +// Ok(()) +// }, +// Err(e) => Err(format_err!("failed to commit startup tx {:?}", e)), +// } +// } + +pub trait IntPct { + fn pct(self, pct: u64) -> u64; +} + +impl IntPct for u64 { + fn pct(self, pct: u64) -> u64 { + self * pct / 100 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn int_pct_test() { + assert_eq!(100.pct(110), 110); + assert_eq!(100.pct(50), 50); + assert_eq!(1.pct(200), 2); + assert_eq!(1.pct(50), 0); + } +} \ No newline at end of file diff --git a/core/src/vbox.rs b/core/src/vbox.rs new file mode 100644 index 00000000..71a37ce2 --- /dev/null +++ b/core/src/vbox.rs @@ -0,0 +1,305 @@ +use uuid::Uuid; + +use std::iter; +use std::collections::HashMap; + +// refunds +use rand::prelude::*; +use rand::{thread_rng}; +use rand::distributions::{WeightedIndex}; + +use failure::Error; +use failure::err_msg; + +use instance::{Instance}; +use construct::{Colours}; + +use item::*; + +pub type VboxIndices = Option>>; + +#[derive(Debug,Clone,Serialize,Deserialize)] +pub struct Vbox { + pub bits: usize, + pub store: HashMap>, + pub stash: HashMap, +} + +#[derive(Debug,Copy,Clone,Serialize,Deserialize,Hash,PartialEq,Eq)] +pub enum ItemType { + Colours, + Skills, + Specs, +} + +const STORE_COLOURS_CAPACITY: usize = 6; +const STORE_SKILLS_CAPACITY: usize = 3; +const STORE_SPECS_CAPACITY: usize = 3; +const STASH_CAPACITY: usize = 6; +const STARTING_ATTACK_COUNT: usize = 3; + +impl Vbox { + pub fn new() -> Vbox { + let mut colours: HashMap = HashMap::new(); + let mut skills: HashMap = HashMap::new(); + let mut specs: HashMap = HashMap::new(); + + let store = [ + (ItemType::Colours, colours), + (ItemType::Skills, skills), + (ItemType::Colours, specs), + ].iter().cloned().collect(); + + let mut stash = HashMap::new(); + for i in 0..STARTING_ATTACK_COUNT { + stash.insert(i.to_string(), Item::Attack); + } + + Vbox { + store, + stash, + bits: 30, + } + } + + pub fn balance_sub(&mut self, amount: usize) -> Result<&mut Vbox, Error> { + let new_balance = self.bits + .checked_sub(amount) + .ok_or(format_err!("insufficient balance: {:?}", self.bits))?; + + self.bits = new_balance; + + Ok(self) + } + + pub fn balance_add(&mut self, amount: usize) -> &mut Vbox { + self.bits = self.bits.saturating_add(amount); + self + } + + pub fn fill(&mut self) -> &mut Vbox { + let mut rng = thread_rng(); + + let colours = vec![ + (Item::Red, 1), + (Item::Green, 1), + (Item::Blue, 1), + ]; + let colour_dist = WeightedIndex::new(colours.iter().map(|item| item.1)).unwrap(); + + let skills = vec![ + (Item::Attack, 1), + (Item::Block, 1), + (Item::Buff, 1), + (Item::Debuff, 1), + (Item::Stun, 1), + ]; + let skill_dist = WeightedIndex::new(skills.iter().map(|item| item.1)).unwrap(); + + let specs = vec![ + (Item::Power, 1), + (Item::Life, 1), + (Item::Speed, 1), + ]; + let spec_dist = WeightedIndex::new(specs.iter().map(|item| item.1)).unwrap(); + + for item_type in [ItemType::Colours, ItemType::Skills, ItemType::Specs].iter() { + let (items, num, dist) = match item_type { + ItemType::Colours => (&colours, STORE_COLOURS_CAPACITY, &colour_dist), + ItemType::Skills => (&skills, STORE_SKILLS_CAPACITY, &skill_dist), + ItemType::Specs => (&specs, STORE_SPECS_CAPACITY, &spec_dist), + }; + + let drops = iter::repeat_with(|| items[dist.sample(&mut rng)].0) + .take(num) + .enumerate() + .map(|(i, item)| (i.to_string(), item)) + .collect::>(); + + self.store.insert(*item_type, drops); + } + + self + } + + pub fn buy(&mut self, item: ItemType, i: &String) -> Result { + // check item exists + let selection = self.store + .get_mut(&item).ok_or(format_err!("no item group {:?}", item))? + .remove(i).ok_or(format_err!("no item at index {:?} {:}", self, i))?; + + self.balance_sub(selection.cost())?; + + Ok(selection) + } + + pub fn stash_add(&mut self, item: Item, index: Option<&String>) -> Result { + if self.stash.len() >= STASH_CAPACITY { + return Err(err_msg("stash full")); + } + + if let Some(index) = index { + if self.stash.contains_key(index) { + return Err(format_err!("slot occupied {:?}", index)); + } + self.stash.insert(index.clone(), item); + return Ok(index.to_string()); + } + + for i in (0..STASH_CAPACITY).map(|i| i.to_string()) { + if !self.stash.contains_key(&i) { + self.stash.insert(i.clone(), item); + return Ok(i); + } + } + + return Err(err_msg("stash full")); + } + + pub fn bot_buy(&mut self, item: ItemType) -> Result { + let buy_index = self.store[&item] + .keys() + .next() + .ok_or(format_err!("no item in group {:?}", item))? + .clone(); + + self.buy(item, &buy_index) + } + + pub fn refund(&mut self, i: String) -> Result<&mut Vbox, Error> { + let refunded = self.stash.remove(&i) + .ok_or(format_err!("no item at index {:?} {:?}", self.stash, i))?; + + let refund = refunded.cost(); + // info!("refunding {:?} for {:?}", refund, refunded); + self.balance_add(refund); + Ok(self) + } + + pub fn combine(&mut self, stash_indices: Vec, store_indices: Option>>) -> Result<&mut Vbox, Error> { + // find base item for index to insert into + let base_index = stash_indices.iter() + .find(|i| match self.stash.get(i.clone()) { + Some(item) => item.into_skill().is_some(), + None => false, + }); + + let mut input = stash_indices + .iter() + .map(|i| self.stash.remove(i) + .ok_or(format_err!("no item at index {:?} {:?}", self.stash, i))) + .collect::, Error>>()?; + + if let Some(store_indices) = store_indices { + let mut purchased = store_indices.iter() + .map(|(g, list)| + list.iter() + .map(|i| self.buy(*g, i)) + .collect::, Error>>() + ) + .collect::>, Error>>()? + .into_iter() + .flatten() + .collect(); + + input.append(&mut purchased); + } + + // sort the input to align with the combinations + // combos are sorted when created + input.sort_unstable(); + let combos = get_combos(); + let combo = combos.iter().find(|c| c.components == input).ok_or(err_msg("not a combo"))?; + + self.stash_add(combo.item, base_index)?; + + Ok(self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn combine_test() { + let mut vbox = Vbox::new(); + vbox.stash.insert(0.to_string(), Item::Attack); + vbox.stash.insert(1.to_string(), Item::Green); + vbox.stash.insert(2.to_string(), Item::Green); + vbox.combine(vec![0.to_string(), 1.to_string(), 2.to_string()], None).unwrap(); + assert_eq!(vbox.stash["0"], Item::Heal); + } + + #[test] + fn buy_test() { + let mut vbox = Vbox::new(); + vbox.fill(); + + // cannot rebuy same + vbox.buy(ItemType::Skills, &0.to_string()).unwrap(); + assert!(vbox.store[&ItemType::Skills].get(&0.to_string()).is_none()); + assert!(vbox.buy(ItemType::Skills, &0.to_string()).is_err()); + } + + #[test] + fn capacity_test() { + let mut vbox = Vbox::new(); + vbox.fill(); + vbox.stash_add(Item::Red, None).unwrap(); + vbox.stash_add(Item::Red, None).unwrap(); + vbox.stash_add(Item::Red, None).unwrap(); + assert!(vbox.stash_add(Item::Red, None).is_err()); + } + + #[test] + fn store_and_stash_combine_test() { + let mut vbox = Vbox::new(); + vbox.fill(); + + let mut skill_combine_args = HashMap::new(); + skill_combine_args.insert(ItemType::Colours, vec![0.to_string(), 1.to_string()]); + skill_combine_args.insert(ItemType::Skills, vec![0.to_string()]); + + let mut spec_combine_args = HashMap::new(); + spec_combine_args.insert(ItemType::Colours, vec![2.to_string(), 3.to_string()]); + spec_combine_args.insert(ItemType::Specs, vec![0.to_string()]); + + vbox.combine(vec![], Some(skill_combine_args)).unwrap(); + vbox.combine(vec![], Some(spec_combine_args)).unwrap(); + } + + #[test] + fn combos_test() { + let mut input = vec![Item::Green, Item::Attack, Item::Green]; + let combos = get_combos(); + + // sort input so they align + input.sort_unstable(); + + let combo = combos.iter().find(|c| c.components == input); + assert!(combo.is_some()); + } + + #[test] + fn refund_test() { + let mut vbox = Vbox::new(); + vbox.stash.insert(0.to_string(), Item::Strike); + vbox.refund(0.to_string()).unwrap(); + assert_eq!(vbox.bits, 32); + } + + #[test] + fn colours_count_test() { + let strike = Item::Strike; + + let mut count = Colours::new(); + strike.colours(&mut count); + assert_eq!(count.red, 2); + } + + // #[test] + // fn item_info_test() { + // info!("{:#?}", item_info()); + // } +} \ No newline at end of file diff --git a/server/src/game.rs b/server/src/game.rs index ddc701e4..8d3ed833 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -17,7 +17,7 @@ use account::Account; use pg::Db; use construct::{Construct}; -use skill::{Skill, Cast, Resolution, Event, resolution_steps}; +use skill::{Skill, Cast, Resolution, Event, resolve}; use effect::{Effect}; use player::{Player}; use instance::{TimeControl, instance_game_finished}; @@ -217,6 +217,7 @@ impl Game { fn pve_add_skills(&mut self) -> &mut Game { let mut pve_skills = vec![]; + let mut rng = thread_rng(); for mobs in self.players .iter() @@ -228,8 +229,6 @@ impl Game { // info!("{:?} {:?}", mob.name, skill); match skill { Some(s) => { - let mut rng = thread_rng(); - // the mut marks it as being able to be called // more than once let mut find_target = || { @@ -479,7 +478,7 @@ impl Game { while let Some(cast) = self.stack.pop() { // info!("{:} casts ", cast); - let mut resolutions = resolution_steps(&cast, &mut self); + let mut resolutions = resolve(&cast, &mut self); r_animation_ms = resolutions.iter().fold(r_animation_ms, |acc, r| acc + r.clone().get_delay()); @@ -807,91 +806,6 @@ pub fn game_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { return Ok(()); } -// pub fn game_global_startup(tx: &mut Transaction) -> Result<(), Error> { -// if game_global_get(tx).is_ok() { -// info!("global mm game exists"); -// return Ok(()); -// } - -// let mut game = Game::new(); - -// game -// .set_player_num(2) -// .set_player_constructs(3) -// .set_mode(GameMode::Pvp); - -// game_write(tx, &game)?; - -// let query = " -// INSERT INTO matchmaking (id, game) -// VALUES ($1, $2) -// RETURNING id; -// "; - -// let result = tx -// .query(query, &[&Uuid::nil(), &game.id])?; - -// result.iter().next().ok_or(format_err!("no game written"))?; - -// info!("{:} wrote global mm startup", game.id); - -// return Ok(()); -// } - -// pub fn game_global_set(tx: &mut Transaction, game: &Game) -> Result<(), Error> { -// let query = " -// UPDATE matchmaking -// SET game = $1 -// WHERE id = $2 -// RETURNING id, game; -// "; - -// let result = tx -// .query(query, &[&game.id, &Uuid::nil()])?; - -// result.iter() -// .next() -// .ok_or(err_msg("could not set global game mm"))?; - -// return Ok(()); -// } - -// pub fn game_global_get(tx: &mut Transaction) -> Result { -// let query = " -// SELECT * from games -// WHERE id = ( -// SELECT game -// FROM matchmaking -// WHERE id = $1 -// ); -// "; - -// let delete_query = " -// DELETE from matchmaking; -// "; - -// let result = tx -// .query(query, &[&Uuid::nil()])?; - -// let returned = match result.iter().next() { -// Some(row) => row, -// None => return Err(err_msg("game not found")), -// }; - -// // tells from_slice to cast into a construct -// let game_bytes: Vec = returned.get("data"); -// let game = match from_slice::(&game_bytes) { -// Ok(g) => g, -// Err(_) => { -// tx.query(delete_query, &[])?; -// return Err(err_msg("matchmaking game was invalid")) -// } -// }; - -// return Ok(game); -// } - - pub fn game_update(tx: &mut Transaction, game: &Game) -> Result<(), Error> { let game_bytes = to_vec(&game)?; @@ -964,13 +878,7 @@ pub fn game_concede(tx: &mut Transaction, account: &Account, game_id: Uuid) -> R pub fn game_skill_clear(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result { let mut game = game_get(tx, game_id)?; - game.clear_skill(account.id)?; - - if game.skill_phase_finished() { - game = game.resolve_phase_start(); - } - game_update(tx, &game)?; Ok(game) diff --git a/server/src/skill.rs b/server/src/skill.rs index d94cdc96..13fdcf82 100644 --- a/server/src/skill.rs +++ b/server/src/skill.rs @@ -18,18 +18,12 @@ pub fn dev_resolve(a_id: Uuid, b_id: Uuid, skill: Skill) -> Resolutions { if skill.aoe() { // Send an aoe skill event for anims resolutions.push(Resolution::new(&a, &b).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd)); } - return resolve(skill, &mut a, &mut b, resolutions); + return resolve_skill(skill, &mut a, &mut b, resolutions); } -pub fn resolution_steps(cast: &Cast, game: &mut Game) -> Resolutions { +pub fn resolve(cast: &Cast, game: &mut Game) -> Resolutions { let mut resolutions = vec![]; - resolutions = pre_resolve(cast, game, resolutions); - - return resolutions; -} - -pub fn pre_resolve(cast: &Cast, game: &mut Game, mut resolutions: Resolutions) -> Resolutions { let skill = cast.skill; let source = game.construct_by_id(cast.source_construct_id).unwrap().clone(); let targets = game.get_targets(cast.skill, &source, cast.target_construct_id); @@ -54,7 +48,7 @@ pub fn pre_resolve(cast: &Cast, game: &mut Game, mut resolutions: Resolutions) - continue; } - resolutions = resolve(cast.skill, &mut source, &mut target, resolutions); + resolutions = resolve_skill(cast.skill, &mut source, &mut target, resolutions); // save the changes to the game game.update_construct(&mut source); @@ -67,7 +61,7 @@ pub fn pre_resolve(cast: &Cast, game: &mut Game, mut resolutions: Resolutions) - return resolutions; } -pub fn resolve(skill: Skill, source: &mut Construct, target: &mut Construct, mut resolutions: Vec) -> Resolutions { +pub fn resolve_skill(skill: Skill, source: &mut Construct, target: &mut Construct, mut resolutions: Vec) -> Resolutions { if let Some(_disable) = source.disabled(skill) { // resolutions.push(Resolution::new(source, target).event(Event::Disable { disable, skill }).stages(EventStages::PostOnly)); return resolutions; @@ -84,7 +78,7 @@ pub fn resolve(skill: Skill, source: &mut Construct, target: &mut Construct, mut return resolutions; } resolutions.push(Resolution::new(source, target).event(Event::Reflection { skill })); - return resolve(skill, &mut source.clone(), source, resolutions); + return resolve_skill(skill, &mut source.clone(), source, resolutions); } if source.affected(Effect::Haste) { @@ -175,14 +169,14 @@ pub fn resolve(skill: Skill, source: &mut Construct, target: &mut Construct, mut Skill::Decay| Skill::DecayPlus | - Skill::DecayPlusPlus => decay(source, target, resolutions, skill), // dot + Skill::DecayPlusPlus => decay(source, target, resolutions, skill), Skill::DecayTick| Skill::DecayTickPlus | - Skill::DecayTickPlusPlus => decay_tick(source, target, resolutions, skill), // dot + Skill::DecayTickPlusPlus => decay_tick(source, target, resolutions, skill), Skill::Haste| Skill::HastePlus | - Skill::HastePlusPlus => haste(source, target, resolutions, skill), // speed slow + Skill::HastePlusPlus => haste(source, target, resolutions, skill), Skill::Heal| Skill::HealPlus | @@ -206,7 +200,7 @@ pub fn resolve(skill: Skill, source: &mut Construct, target: &mut Construct, mut Skill::Purge| Skill::PurgePlus | - Skill::PurgePlusPlus => purge(source, target, resolutions, skill), // dispel all buffs + Skill::PurgePlusPlus => purge(source, target, resolutions, skill), Skill::Purify| Skill::PurifyPlus | @@ -226,26 +220,26 @@ pub fn resolve(skill: Skill, source: &mut Construct, target: &mut Construct, mut Skill::Link| Skill::LinkPlus | - Skill::LinkPlusPlus => link(source, target, resolutions, skill), // target is immune to magic damage and fx + Skill::LinkPlusPlus => link(source, target, resolutions, skill), Skill::Silence| Skill::SilencePlus | - Skill::SilencePlusPlus => silence(source, target, resolutions, skill), // target cannot cast spells + Skill::SilencePlusPlus => silence(source, target, resolutions, skill), Skill::Siphon| Skill::SiphonPlus | - Skill::SiphonPlusPlus => siphon(source, target, resolutions, skill), // dot + Skill::SiphonPlusPlus => siphon(source, target, resolutions, skill), Skill::SiphonTick| Skill::SiphonTickPlus | - Skill::SiphonTickPlusPlus => siphon_tick(source, target, resolutions, skill), // dot + Skill::SiphonTickPlusPlus => siphon_tick(source, target, resolutions, skill), Skill::Slay| Skill::SlayPlus | - Skill::SlayPlusPlus => slay(source, target, resolutions, skill), // hybrid dmg self heal + Skill::SlayPlusPlus => slay(source, target, resolutions, skill), Skill::Sleep| Skill::SleepPlus | - Skill::SleepPlusPlus => sleep(source, target, resolutions, skill), // heal stun + Skill::SleepPlusPlus => sleep(source, target, resolutions, skill), Skill::Restrict| Skill::RestrictPlus | @@ -261,24 +255,24 @@ pub fn resolve(skill: Skill, source: &mut Construct, target: &mut Construct, mut Skill::Break| Skill::BreakPlus | - Skill::BreakPlusPlus => break_(source, target, resolutions, skill), // no damage stun, adds vulnerable + Skill::BreakPlusPlus => break_(source, target, resolutions, skill), Skill::Triage| Skill::TriagePlus | - Skill::TriagePlusPlus => triage(source, target, resolutions, skill), // hot + Skill::TriagePlusPlus => triage(source, target, resolutions, skill), Skill::TriageTick| Skill::TriageTickPlus | - Skill::TriageTickPlusPlus => triage_tick(source, target, resolutions, skill), // hot + Skill::TriageTickPlusPlus => triage_tick(source, target, resolutions, skill), // Base Skills Skill::Attack => attack(source, target, resolutions, skill), Skill::Block => block(source, target, resolutions, skill), Skill::Buff => buff(source, target, resolutions, skill), - Skill::Debuff => debuff(source, target, resolutions, skill), // speed slow + Skill::Debuff => debuff(source, target, resolutions, skill), Skill::Stun => stun(source, target, resolutions, skill), - //Triggered + // Triggered Skill::Electrocute | Skill::ElectrocutePlus | Skill::ElectrocutePlusPlus => panic!("should only trigger from electrify hit"), @@ -1267,8 +1261,6 @@ impl Skill { } pub fn defensive(&self) -> bool { - let mut rng = thread_rng(); - match self { Skill::Amplify| Skill::AmplifyPlus | @@ -1311,10 +1303,6 @@ impl Skill { Skill::TriagePlus | Skill::TriagePlusPlus => true, - Skill::Banish | - Skill::BanishPlus | - Skill::BanishPlusPlus => rng.gen_bool(0.5), - _ => false, } } @@ -2021,7 +2009,7 @@ mod tests { assert!(y.affected(Effect::Reflect)); let mut results = vec![]; - results = resolve(Skill::Blast, &mut x, &mut y, results); + results = resolve_skill(Skill::Blast, &mut x, &mut y, results); assert!(x.green_life() < 1024); @@ -2052,7 +2040,7 @@ mod tests { y.blue_life.force(0); x.green_life.reduce(512); - let mut results = resolve(Skill::Siphon, &mut x, &mut y, vec![]); + let mut results = resolve_skill(Skill::Siphon, &mut x, &mut y, vec![]); assert!(y.affected(Effect::Siphon)); assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier()))); From e661c2501f880f2edf38cc2754fc54aee36d8e11 Mon Sep 17 00:00:00 2001 From: Mashy Date: Mon, 2 Dec 2019 16:28:17 +1000 Subject: [PATCH 004/206] combos.md --- COMBOS.md | 266 ++++++++++++++++++++++++++++++++--------------------- ROADMAP.md | 4 + WORKLOG.md | 18 +--- 3 files changed, 167 insertions(+), 121 deletions(-) diff --git a/COMBOS.md b/COMBOS.md index cf563a8c..a9c01749 100644 --- a/COMBOS.md +++ b/COMBOS.md @@ -1,142 +1,200 @@ -# item_info -> +# Spec / Skill hybrid specs # -combos [strike, [R R Attack]] -specs [spec [bonus amount, [r g b]] +Create skills specs by combining an upgraded skills with corresponding colour specs: + Strike (RR + A) can be combined with (PowerRR, SpeedRR, LifeRR) + - Strike + PowerRR -> StrikePower + - Strike + SpeedRR -> StrikeSpeed + - Strike + LifeRR -> StrikeLife -# Playthrough +Could also create SkillSpec+ by combining two together. -constructs join game - stats randomised +## Why Skill Specs -initial stash drops - 6 skills - 6 colours - 6 specs +- Give tools to players to make cool unique builds +- Passive utility that you work towards +- Specialise in a type of skill +- Repurpose skills that you aren't using much for active use +- More layers of complexity -play first round - basically duke it out +## Skill specs philosphy -# Colours # +- Should be more interesting than just another stat "multiplier" we have that already (could be placeholder though) +- Passives should help build a theme / identity to the skills +- Specs should be numerically scalable so they can be `balanced` (also for upgraded versions think continuous vs discrete) +- While specialised should be useful for multiple skills + e.g. StrikeSpeed -> Causes your red attack skills to cast on another target on the same team for (X% damage) + A StrikeSpeed should do something for `some` other active skill that isn't strike +- They don't have to be `competely game changing` to be viable / fun / interesting -### Red ### -Real world concepts -Aggressive -Apply Buffs -Fast & Chaotic +## Brainstorming on what kind of things skill specs could do -### Green ### -Healing Specialisation -Defensive -Purge buffs & debuffs +- Passive + - (Abosrb Spec) when you take damage you permanently gain X Power/Speed/Life + - Grant you Amplify with (X% multiplier) when you KO a target + - Convert (X% of your red / blue power -> green power) + - Reduce / Increase damage taken of a certain type (Block type upgrades maybe?) -### Blue ### -Fantasy concepts (magical) -Aggressive & Defensive -Apply Debuffs -Slow & Reliable +- Active + - Make a skill cast another skill (strike another guy) + - Apply an effect when you cast a skill + - Change damage / healing / speed of a skill -# Classes # +## The BIG LIST -Class names to be changed -==================== -Pure Red `Nature` -Pure Green `Non-Violence` -Pure Blue `Destruction` -Hybrid Red / Blue `Chaos` -Hybrid Red / Green `Purity` -Hybrid Blue / Green `Technology` +### Attack Base +Strike + Power + Life + Speed (Repeat skill at X% multiplier) +Chaos + Power + Life + Speed -Skills -========== +Heal + Power (Convert X% Red/Blue Power to GreenPower) + Life + Speed -Basic Type -------------------------------------------------------------------------- -Attack `Basic offensive skill - deal damage` -Buff `Base ally targetted skill - increase ally speed` -Stun `Base enemy disable - disable enemy for 2 rounds` -Block `Base self targetted defensive - reduced damage taken for 2 rounds` -Debuff `Base enemy debuff - reduce enemy speed` +Blast + Power + Life + Speed -# Attack Base # +Slay + Power + Life + Speed -RR - Strike -GG - Heal -BB - Blast -RG - Purify -GB - Decay -RB - Blast +Siphon + Power + Life + Speed -# Stun Base # +### Stun Base +Bash + Power + Life + Speed -RR - Strangle -GG - Break -BB - Ruin -RG - Banish -GB - Silence -RB - Hex +Sleep + Power + Life + Speed -# Buff Base # +Ruin + Power + Life + Speed -RR - Empower -GR - Triage -BB - Absorb -RG - Sustain -GB - Amplify -RB - Haste +Link + Power + Life + Speed -# Debuff Base # +Banish + Power + Life + Speed -RR - Restrict -GG - Purge -BB - Curse -RG - Slow -GB - Siphon -RB - Invert +Break + Power + Life + Speed -# Block Base # +### Block Base -RR - Counter -GG - Reflect -BB - Electrify -RG - Intercept -GB - Life `rename?` -RB - Recharge +Counter + Power + Life + Speed +Reflect + Power + Life + Speed -## Advanced combos ## +Purify + Power + Life + Speed -Two ways of upgrading - #1 -> combine more of the same for a stronger version of the same skill / spec (T2 / T3 Combos) - #2 -> combine skill with two matching colour specs to change the way the skill works (Spec / Skill hybrid) +Sustain + Power + Life + Speed -### T2 / T3 Combos ### +Electrify + Power + Life + Speed -All current specs / items can be further combo'd into T2 and T3 versions +Recharge + Power + Life + Speed -# 3 of same base => 1 upgraded tier # -`3 x T1 Red Damage Spec => T2 Red Damage Spec` -`3 x T2 Red Damage Spec => T3 Red Damage Spec` -`3 x T1 Strike => T2 Strike` -`3 x T2 Strike => T3 Strike` +### Buff Base -Upgraded skills will have a combination of higher damage / longer duration / reduced cooldown -Upgraded skills use the same speed formula as previously +Intercept + Power + Life + Speed -### Spec / Skill hybrid specs ### +Triage + Power + Life + Speed -# Strike # -2 x Red Damage + Strike => Strike damage bonus (crit?) -2 x Red Speed + Strike => Strike reduces enemy speed -2 x Red Life + Strike => Strike reduces enemy healing (% reduction) +Absorb + Power (Gain X Power when you take damage) + Life (Gain X Life when you take damage) + Speed (Gain X Speed when you take damage) -# Heal # -2 x Green Damage + Heal => Heal target for additional 20% of caster's maximum life -2 x Green Speed + Heal => Heal target gets bonus speed -2 x Green Life + Heal => Heal increases target's max hp for 2 turns +Amplify + Power + Life + Speed -etc etc +Haste + Power + Life + Speed -30 skills * 3 specs => 90 spec / skill hybrid specs -> might be overcomplicated +Hybrid + Power + Life + Speed +### Debuff Base +Purge + Power + Life + Speed + +Invert + Power + Life + Speed + +Restrict + Power + Life + Speed + +Silence + Power + Life + Speed + +Curse + Power + Life + Speed + +Decay + Power + Life + Speed \ No newline at end of file diff --git a/ROADMAP.md b/ROADMAP.md index 7f9ff085..07215724 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -34,8 +34,12 @@ Player Events e.g. chatwheel Matchmaking + ELO / Leaderboard Game skill private fields +# Phase 4 (Release -> Full Shill mode) + Refine artwork, icons, scaling etc Music +Skill Specs +Some sort viewable combat log Marketing materials Videos diff --git a/WORKLOG.md b/WORKLOG.md index cdd37a00..3eab2abc 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -50,23 +50,7 @@ _tba_ ## SOON -* combo rework - - reduce number of items for creating t2/t3 items from 3 -> 2 - - add lost complexity by adding skill spec items - - Created by combining a skill with corresponding spec - e.g. - - Strike + PowerRR -> StrikePower (Will be the power symbol with strike text under) - - Construct does Y% more damage with Strike - - Strike + SpeedRR -> StrikeSpeed (strike has Y% more speed) - - Strike + LifeRR -> StrikeLife (Strike recharges X% of damage as red life) - - - Can also work as module style passive keystones - * troll life -> dmg -> Invert life spec? - * prince of peace - * bonus healing / no damage -> Heal power spec? - * fuck magic -> Some sort of reflect spec? - * empower on ko -> Amplify + Power spec - +* Skill / Spec hybrids - SEE COMBOS.md * elo + leaderboards ## LATER From 900a9089b1ac6b6a51f95cf923269f4ecd241d1d Mon Sep 17 00:00:00 2001 From: Mashy Date: Mon, 2 Dec 2019 16:46:02 +1000 Subject: [PATCH 005/206] yolo 3 -> 2 combos --- server/src/construct.rs | 2 +- server/src/item.rs | 234 ++++++++++++++++++++++------------------ 2 files changed, 131 insertions(+), 105 deletions(-) diff --git a/server/src/construct.rs b/server/src/construct.rs index 3c41ffd1..9f957d00 100644 --- a/server/src/construct.rs +++ b/server/src/construct.rs @@ -1082,7 +1082,7 @@ mod tests { let colours = Colours::from_construct(&construct); assert!(colours.red == 4); - assert!(colours.blue == 20); + assert!(colours.blue == 10); assert!(colours.green == 2); } diff --git a/server/src/item.rs b/server/src/item.rs index 72639073..fa228549 100644 --- a/server/src/item.rs +++ b/server/src/item.rs @@ -959,37 +959,51 @@ impl Item { Item::Amplify => vec![Item::Buff, Item::Red, Item::Blue], Item::Haste => vec![Item::Buff, Item::Red, Item::Green], Item::Hybrid => vec![Item::Buff, Item::Green, Item::Blue], - Item::InterceptPlus => vec![Item::Intercept, Item::Intercept, Item::Intercept], - Item::InterceptPlusPlus => vec![Item::InterceptPlus, Item::InterceptPlus, Item::InterceptPlus], - Item::TriagePlus => vec![Item::Triage, Item::Triage, Item::Triage], - Item::TriagePlusPlus => vec![Item::TriagePlus, Item::TriagePlus, Item::TriagePlus], - Item::HastePlus => vec![Item::Haste, Item::Haste, Item::Haste], - Item::HastePlusPlus => vec![Item::HastePlus, Item::HastePlus, Item::HastePlus], - Item::HybridPlus => vec![Item::Hybrid, Item::Hybrid, Item::Hybrid], - Item::HybridPlusPlus => vec![Item::HybridPlus, Item::HybridPlus, Item::HybridPlus], - Item::AbsorbPlus => vec![Item::Absorb, Item::Absorb, Item::Absorb], - Item::AbsorbPlusPlus => vec![Item::AbsorbPlus, Item::AbsorbPlus, Item::AbsorbPlus], - Item::AmplifyPlus => vec![Item::Amplify, Item::Amplify, Item::Amplify], - Item::AmplifyPlusPlus => vec![Item::AmplifyPlus, Item::AmplifyPlus, Item::AmplifyPlus], - Item::Purge => vec![Item::Debuff, Item::Green, Item::Green], // Needs flavour + Item::InterceptPlus => vec![Item::Intercept, Item::Intercept], + Item::InterceptPlusPlus => vec![Item::InterceptPlus, Item::InterceptPlus], + + Item::TriagePlus => vec![Item::Triage, Item::Triage], + Item::TriagePlusPlus => vec![Item::TriagePlus, Item::TriagePlus], + + Item::AbsorbPlus => vec![Item::Absorb, Item::Absorb], + Item::AbsorbPlusPlus => vec![Item::AbsorbPlus, Item::AbsorbPlus], + + Item::AmplifyPlus => vec![Item::Amplify, Item::Amplify], + Item::AmplifyPlusPlus => vec![Item::AmplifyPlus, Item::AmplifyPlus], + + Item::HastePlus => vec![Item::Haste, Item::Haste], + Item::HastePlusPlus => vec![Item::HastePlus, Item::HastePlus], + + Item::HybridPlus => vec![Item::Hybrid, Item::Hybrid], + Item::HybridPlusPlus => vec![Item::HybridPlus, Item::HybridPlus], + + + Item::Purge => vec![Item::Debuff, Item::Green, Item::Green], Item::Invert => vec![Item::Debuff, Item::Red, Item::Green], Item::Restrict => vec![Item::Debuff, Item::Red, Item::Red], Item::Silence => vec![Item::Debuff, Item::Blue, Item::Blue], Item::Curse => vec![Item::Debuff, Item::Red, Item::Blue], Item::Decay => vec![Item::Debuff, Item::Green, Item::Blue], - Item::RestrictPlus => vec![Item::Restrict, Item::Restrict, Item::Restrict], - Item::RestrictPlusPlus => vec![Item::RestrictPlus, Item::RestrictPlus, Item::RestrictPlus], - Item::PurgePlus => vec![Item::Purge, Item::Purge, Item::Purge], // Needs flavour - Item::PurgePlusPlus => vec![Item::PurgePlus, Item::PurgePlus, Item::PurgePlus], // Needs flavour - Item::SilencePlus => vec![Item::Silence, Item::Silence, Item::Silence], - Item::SilencePlusPlus => vec![Item::SilencePlus, Item::SilencePlus, Item::SilencePlus], - Item::CursePlus => vec![Item::Curse, Item::Curse, Item::Curse], - Item::CursePlusPlus => vec![Item::CursePlus, Item::CursePlus, Item::CursePlus], - Item::DecayPlus => vec![Item::Decay, Item::Decay, Item::Decay], - Item::DecayPlusPlus => vec![Item::DecayPlus, Item::DecayPlus, Item::DecayPlus], - Item::InvertPlus => vec![Item::Invert, Item::Invert, Item::Invert], - Item::InvertPlusPlus => vec![Item::InvertPlus, Item::InvertPlus, Item::InvertPlus], + + Item::PurgePlus => vec![Item::Purge, Item::Purge, Item::Purge], + Item::PurgePlusPlus => vec![Item::PurgePlus, Item::PurgePlus], + + Item::InvertPlus => vec![Item::Invert, Item::Invert], + Item::InvertPlusPlus => vec![Item::InvertPlus, Item::InvertPlus], + + Item::RestrictPlus => vec![Item::Restrict, Item::Restrict], + Item::RestrictPlusPlus => vec![Item::RestrictPlus, Item::RestrictPlus], + + Item::SilencePlus => vec![Item::Silence, Item::Silence], + Item::SilencePlusPlus => vec![Item::SilencePlus, Item::SilencePlus], + + Item::CursePlus => vec![Item::Curse, Item::Curse], + Item::CursePlusPlus => vec![Item::CursePlus, Item::CursePlus], + + Item::DecayPlus => vec![Item::Decay, Item::Decay], + Item::DecayPlusPlus => vec![Item::DecayPlus, Item::DecayPlus], + Item::Counter => vec![Item::Block, Item::Red, Item::Red], Item::Reflect => vec![Item::Block, Item::Green, Item::Blue], @@ -997,18 +1011,24 @@ impl Item { Item::Sustain => vec![Item::Block, Item::Red, Item::Green], Item::Electrify => vec![Item::Block, Item::Blue, Item::Blue], Item::Recharge => vec![Item::Block, Item::Red, Item::Blue], - Item::CounterPlus => vec![Item::Counter, Item::Counter, Item::Counter], - Item::CounterPlusPlus => vec![Item::CounterPlus, Item::CounterPlus, Item::CounterPlus], // Add red recharge - Item::PurifyPlus => vec![Item::Purify, Item::Purify, Item::Purify], - Item::PurifyPlusPlus => vec![Item::PurifyPlus, Item::PurifyPlus, Item::PurifyPlus], - Item::ElectrifyPlus => vec![Item::Electrify, Item::Electrify, Item::Electrify], - Item::ElectrifyPlusPlus => vec![Item::ElectrifyPlus, Item::ElectrifyPlus, Item::ElectrifyPlus], - Item::SustainPlus => vec![Item::Sustain, Item::Sustain, Item::Sustain], - Item::SustainPlusPlus => vec![Item::SustainPlus, Item::SustainPlus, Item::SustainPlus], - Item::ReflectPlus => vec![Item::Reflect, Item::Reflect, Item::Reflect], - Item::ReflectPlusPlus => vec![Item::ReflectPlus, Item::ReflectPlus, Item::ReflectPlus], - Item::RechargePlus => vec![Item::Recharge, Item::Recharge, Item::Recharge], - Item::RechargePlusPlus => vec![Item::RechargePlus, Item::RechargePlus, Item::RechargePlus], + + Item::CounterPlus => vec![Item::Counter, Item::Counter], + Item::CounterPlusPlus => vec![Item::CounterPlus, Item::CounterPlus], + + Item::PurifyPlus => vec![Item::Purify, Item::Purify], + Item::PurifyPlusPlus => vec![Item::PurifyPlus, Item::PurifyPlus], + + Item::ElectrifyPlus => vec![Item::Electrify, Item::Electrify], + Item::ElectrifyPlusPlus => vec![Item::ElectrifyPlus, Item::ElectrifyPlus], + + Item::SustainPlus => vec![Item::Sustain, Item::Sustain], + Item::SustainPlusPlus => vec![Item::SustainPlus, Item::SustainPlus], + + Item::ReflectPlus => vec![Item::Reflect, Item::Reflect], + Item::ReflectPlusPlus => vec![Item::ReflectPlus, Item::ReflectPlus, ], + + Item::RechargePlus => vec![Item::Recharge, Item::Recharge], + Item::RechargePlusPlus => vec![Item::RechargePlus, Item::RechargePlus], Item::Bash => vec![Item::Stun, Item::Red, Item::Red], Item::Sleep => vec![Item::Stun, Item::Green, Item::Green], @@ -1016,18 +1036,25 @@ impl Item { Item::Link => vec![Item::Stun, Item::Blue, Item::Green], Item::Banish => vec![Item::Stun, Item::Red, Item::Blue], Item::Break => vec![Item::Stun, Item::Red, Item::Green], + Item::BashPlus => vec![Item::Bash, Item::Bash, Item::Bash], - Item::BashPlusPlus => vec![Item::BashPlus, Item::BashPlus, Item::BashPlus], - Item::SleepPlus => vec![Item::Sleep, Item::Sleep, Item::Sleep], - Item::SleepPlusPlus => vec![Item::SleepPlus, Item::SleepPlus, Item::SleepPlus], - Item::RuinPlus => vec![Item::Ruin, Item::Ruin, Item::Ruin], - Item::RuinPlusPlus => vec![Item::RuinPlus, Item::RuinPlus, Item::RuinPlus], - Item::BreakPlus => vec![Item::Break, Item::Break, Item::Break], - Item::BreakPlusPlus => vec![Item::BreakPlus, Item::BreakPlus, Item::BreakPlus], - Item::LinkPlus => vec![Item::Link, Item::Link, Item::Link], - Item::LinkPlusPlus => vec![Item::LinkPlus, Item::LinkPlus, Item::LinkPlus], - Item::BanishPlus => vec![Item::Banish, Item::Banish, Item::Banish], - Item::BanishPlusPlus => vec![Item::BanishPlus, Item::BanishPlus, Item::BanishPlus], + Item::BashPlusPlus => vec![Item::BashPlus, Item::BashPlus], + + Item::SleepPlus => vec![Item::Sleep, Item::Sleep], + Item::SleepPlusPlus => vec![Item::SleepPlus, Item::SleepPlus,], + + Item::RuinPlus => vec![Item::Ruin, Item::Ruin], + Item::RuinPlusPlus => vec![Item::RuinPlus, Item::RuinPlus], + + Item::LinkPlus => vec![Item::Link, Item::Link], + Item::LinkPlusPlus => vec![Item::LinkPlus, Item::LinkPlus], + + Item::BanishPlus => vec![Item::Banish, Item::Banish], + Item::BanishPlusPlus => vec![Item::BanishPlus, Item::BanishPlus], + + Item::BreakPlus => vec![Item::Break, Item::Break], + Item::BreakPlusPlus => vec![Item::BreakPlus, Item::BreakPlus], + Item::Strike => vec![Item::Attack, Item::Red, Item::Red], Item::Chaos => vec![Item::Attack, Item::Red, Item::Blue], @@ -1035,18 +1062,24 @@ impl Item { Item::Blast => vec![Item::Attack, Item::Blue, Item::Blue], Item::Slay => vec![Item::Attack, Item::Red, Item::Green], Item::Siphon => vec![Item::Attack, Item::Green, Item::Blue], - Item::StrikePlus => vec![Item::Strike, Item::Strike, Item::Strike], - Item::StrikePlusPlus => vec![Item::StrikePlus, Item::StrikePlus, Item::StrikePlus], - Item::HealPlus => vec![Item::Heal, Item::Heal, Item::Heal], - Item::HealPlusPlus => vec![Item::HealPlus, Item::HealPlus, Item::HealPlus], - Item::BlastPlus => vec![Item::Blast, Item::Blast, Item::Blast], - Item::BlastPlusPlus => vec![Item::BlastPlus, Item::BlastPlus, Item::BlastPlus], - Item::SlayPlus => vec![Item::Slay, Item::Slay, Item::Slay], - Item::SlayPlusPlus => vec![Item::SlayPlus, Item::SlayPlus, Item::SlayPlus], - Item::SiphonPlus => vec![Item::Siphon, Item::Siphon, Item::Siphon], - Item::SiphonPlusPlus => vec![Item::SiphonPlus, Item::SiphonPlus, Item::SiphonPlus], - Item::ChaosPlus => vec![Item::Chaos, Item::Chaos, Item::Chaos], - Item::ChaosPlusPlus => vec![Item::ChaosPlus, Item::ChaosPlus, Item::ChaosPlus], + + Item::StrikePlus => vec![Item::Strike, Item::Strike], + Item::StrikePlusPlus => vec![Item::StrikePlus, Item::StrikePlus], + + Item::ChaosPlus => vec![Item::Chaos, Item::Chaos], + Item::ChaosPlusPlus => vec![Item::ChaosPlus, Item::ChaosPlus], + + Item::HealPlus => vec![Item::Heal, Item::Heal], + Item::HealPlusPlus => vec![Item::HealPlus, Item::HealPlus], + + Item::BlastPlus => vec![Item::Blast, Item::Blast], + Item::BlastPlusPlus => vec![Item::BlastPlus, Item::BlastPlus], + + Item::SlayPlus => vec![Item::Slay, Item::Slay], + Item::SlayPlusPlus => vec![Item::SlayPlus, Item::SlayPlus], + + Item::SiphonPlus => vec![Item::Siphon, Item::Siphon], + Item::SiphonPlusPlus => vec![Item::SiphonPlus, Item::SiphonPlus], Item::PowerRR => vec![Item::Power, Item::Red, Item::Red], Item::PowerGG => vec![Item::Power, Item::Green, Item::Green], @@ -1054,18 +1087,18 @@ impl Item { Item::PowerRG => vec![Item::Power, Item::Red, Item::Green], Item::PowerGB => vec![Item::Power, Item::Green, Item::Blue], Item::PowerRB => vec![Item::Power, Item::Red, Item::Blue], - Item::PowerRRPlus => vec![Item::PowerRR, Item::PowerRR, Item::PowerRR], - Item::PowerGGPlus => vec![Item::PowerGG, Item::PowerGG, Item::PowerGG], - Item::PowerBBPlus => vec![Item::PowerBB, Item::PowerBB, Item::PowerBB], - Item::PowerRGPlus => vec![Item::PowerRG, Item::PowerRG, Item::PowerRG], - Item::PowerGBPlus => vec![Item::PowerGB, Item::PowerGB, Item::PowerGB], - Item::PowerRBPlus => vec![Item::PowerRB, Item::PowerRB, Item::PowerRB], - Item::PowerRRPlusPlus => vec![Item::PowerRRPlus, Item::PowerRRPlus, Item::PowerRRPlus], - Item::PowerGGPlusPlus => vec![Item::PowerGGPlus, Item::PowerGGPlus, Item::PowerGGPlus], - Item::PowerBBPlusPlus => vec![Item::PowerBBPlus, Item::PowerBBPlus, Item::PowerBBPlus], - Item::PowerRGPlusPlus => vec![Item::PowerRGPlus, Item::PowerRGPlus, Item::PowerRGPlus], - Item::PowerGBPlusPlus => vec![Item::PowerGBPlus, Item::PowerGBPlus, Item::PowerGBPlus], - Item::PowerRBPlusPlus => vec![Item::PowerRBPlus, Item::PowerRBPlus, Item::PowerRBPlus], + Item::PowerRRPlus => vec![Item::PowerRR, Item::PowerRR], + Item::PowerGGPlus => vec![Item::PowerGG, Item::PowerGG], + Item::PowerBBPlus => vec![Item::PowerBB, Item::PowerBB], + Item::PowerRGPlus => vec![Item::PowerRG, Item::PowerRG], + Item::PowerGBPlus => vec![Item::PowerGB, Item::PowerGB], + Item::PowerRBPlus => vec![Item::PowerRB, Item::PowerRB], + Item::PowerRRPlusPlus => vec![Item::PowerRRPlus, Item::PowerRRPlus], + Item::PowerGGPlusPlus => vec![Item::PowerGGPlus, Item::PowerGGPlus], + Item::PowerBBPlusPlus => vec![Item::PowerBBPlus, Item::PowerBBPlus], + Item::PowerRGPlusPlus => vec![Item::PowerRGPlus, Item::PowerRGPlus], + Item::PowerGBPlusPlus => vec![Item::PowerGBPlus, Item::PowerGBPlus], + Item::PowerRBPlusPlus => vec![Item::PowerRBPlus, Item::PowerRBPlus], Item::LifeRR => vec![Item::Life, Item::Red, Item::Red], Item::LifeGG => vec![Item::Life, Item::Green, Item::Green], @@ -1073,18 +1106,18 @@ impl Item { Item::LifeRG => vec![Item::Life, Item::Red, Item::Green], Item::LifeGB => vec![Item::Life, Item::Green, Item::Blue], Item::LifeRB => vec![Item::Life, Item::Red, Item::Blue], - Item::LifeRRPlus => vec![Item::LifeRR, Item::LifeRR, Item::LifeRR], - Item::LifeGGPlus => vec![Item::LifeGG, Item::LifeGG, Item::LifeGG], - Item::LifeBBPlus => vec![Item::LifeBB, Item::LifeBB, Item::LifeBB], - Item::LifeRGPlus => vec![Item::LifeRG, Item::LifeRG, Item::LifeRG], - Item::LifeGBPlus => vec![Item::LifeGB, Item::LifeGB, Item::LifeGB], - Item::LifeRBPlus => vec![Item::LifeRB, Item::LifeRB, Item::LifeRB], - Item::LifeRRPlusPlus => vec![Item::LifeRRPlus, Item::LifeRRPlus, Item::LifeRRPlus], - Item::LifeGGPlusPlus => vec![Item::LifeGGPlus, Item::LifeGGPlus, Item::LifeGGPlus], - Item::LifeBBPlusPlus => vec![Item::LifeBBPlus, Item::LifeBBPlus, Item::LifeBBPlus], - Item::LifeRGPlusPlus => vec![Item::LifeRGPlus, Item::LifeRGPlus, Item::LifeRGPlus], - Item::LifeGBPlusPlus => vec![Item::LifeGBPlus, Item::LifeGBPlus, Item::LifeGBPlus], - Item::LifeRBPlusPlus => vec![Item::LifeRBPlus, Item::LifeRBPlus, Item::LifeRBPlus], + Item::LifeRRPlus => vec![Item::LifeRR, Item::LifeRR], + Item::LifeGGPlus => vec![Item::LifeGG, Item::LifeGG], + Item::LifeBBPlus => vec![Item::LifeBB, Item::LifeBB], + Item::LifeRGPlus => vec![Item::LifeRG, Item::LifeRG], + Item::LifeGBPlus => vec![Item::LifeGB, Item::LifeGB], + Item::LifeRBPlus => vec![Item::LifeRB, Item::LifeRB], + Item::LifeRRPlusPlus => vec![Item::LifeRRPlus, Item::LifeRRPlus], + Item::LifeGGPlusPlus => vec![Item::LifeGGPlus, Item::LifeGGPlus], + Item::LifeBBPlusPlus => vec![Item::LifeBBPlus, Item::LifeBBPlus], + Item::LifeRGPlusPlus => vec![Item::LifeRGPlus, Item::LifeRGPlus], + Item::LifeGBPlusPlus => vec![Item::LifeGBPlus, Item::LifeGBPlus], + Item::LifeRBPlusPlus => vec![Item::LifeRBPlus, Item::LifeRBPlus], Item::SpeedRR => vec![Item::Speed, Item::Red, Item::Red], Item::SpeedGG => vec![Item::Speed, Item::Green, Item::Green], @@ -1092,18 +1125,18 @@ impl Item { Item::SpeedRG => vec![Item::Speed, Item::Red, Item::Green], Item::SpeedGB => vec![Item::Speed, Item::Green, Item::Blue], Item::SpeedRB => vec![Item::Speed, Item::Red, Item::Blue], - Item::SpeedRRPlus => vec![Item::SpeedRR, Item::SpeedRR, Item::SpeedRR], - Item::SpeedGGPlus => vec![Item::SpeedGG, Item::SpeedGG, Item::SpeedGG], - Item::SpeedBBPlus => vec![Item::SpeedBB, Item::SpeedBB, Item::SpeedBB], - Item::SpeedRGPlus => vec![Item::SpeedRG, Item::SpeedRG, Item::SpeedRG], - Item::SpeedGBPlus => vec![Item::SpeedGB, Item::SpeedGB, Item::SpeedGB], - Item::SpeedRBPlus => vec![Item::SpeedRB, Item::SpeedRB, Item::SpeedRB], - Item::SpeedRRPlusPlus => vec![Item::SpeedRRPlus, Item::SpeedRRPlus, Item::SpeedRRPlus], - Item::SpeedGGPlusPlus => vec![Item::SpeedGGPlus, Item::SpeedGGPlus, Item::SpeedGGPlus], - Item::SpeedBBPlusPlus => vec![Item::SpeedBBPlus, Item::SpeedBBPlus, Item::SpeedBBPlus], - Item::SpeedRGPlusPlus => vec![Item::SpeedRGPlus, Item::SpeedRGPlus, Item::SpeedRGPlus], - Item::SpeedGBPlusPlus => vec![Item::SpeedGBPlus, Item::SpeedGBPlus, Item::SpeedGBPlus], - Item::SpeedRBPlusPlus => vec![Item::SpeedRBPlus, Item::SpeedRBPlus, Item::SpeedRBPlus], + Item::SpeedRRPlus => vec![Item::SpeedRR, Item::SpeedRR], + Item::SpeedGGPlus => vec![Item::SpeedGG, Item::SpeedGG], + Item::SpeedBBPlus => vec![Item::SpeedBB, Item::SpeedBB], + Item::SpeedRGPlus => vec![Item::SpeedRG, Item::SpeedRG], + Item::SpeedGBPlus => vec![Item::SpeedGB, Item::SpeedGB], + Item::SpeedRBPlus => vec![Item::SpeedRB, Item::SpeedRB], + Item::SpeedRRPlusPlus => vec![Item::SpeedRRPlus, Item::SpeedRRPlus], + Item::SpeedGGPlusPlus => vec![Item::SpeedGGPlus, Item::SpeedGGPlus], + Item::SpeedBBPlusPlus => vec![Item::SpeedBBPlus, Item::SpeedBBPlus], + Item::SpeedRGPlusPlus => vec![Item::SpeedRGPlus, Item::SpeedRGPlus], + Item::SpeedGBPlusPlus => vec![Item::SpeedGBPlus, Item::SpeedGBPlus], + Item::SpeedRBPlusPlus => vec![Item::SpeedRBPlus, Item::SpeedRBPlus], _ => vec![*self], } @@ -1338,7 +1371,7 @@ pub fn get_combos() -> Vec { Combo { components: Item::Restrict.combo(), item: Item::Restrict }, Combo { components: Item::RestrictPlus.combo(), item: Item::RestrictPlus }, Combo { components: Item::RestrictPlusPlus.combo(), item: Item::RestrictPlusPlus }, - Combo { components: Item::Purge.combo(), item: Item::Purge }, // Needs flavour + Combo { components: Item::Purge.combo(), item: Item::Purge }, Combo { components: Item::PurgePlus.combo(), item: Item::PurgePlus }, Combo { components: Item::PurgePlusPlus.combo(), item: Item::PurgePlusPlus }, @@ -1556,20 +1589,13 @@ mod tests { assert_eq!(Item::StrikePlus.components(), vec![ Item::Red, Item::Red, Item::Attack, Item::Red, Item::Red, Item::Attack, - Item::Red, Item::Red, Item::Attack, ]); assert_eq!(Item::StrikePlusPlus.components(), vec![ Item::Red, Item::Red, Item::Attack, Item::Red, Item::Red, Item::Attack, - Item::Red, Item::Red, Item::Attack, Item::Red, Item::Red, Item::Attack, Item::Red, Item::Red, Item::Attack, - Item::Red, Item::Red, Item::Attack, - - Item::Red, Item::Red, Item::Attack, - Item::Red, Item::Red, Item::Attack, - Item::Red, Item::Red, Item::Attack, ]); } From 5446ee19ae7bf2a2229781c9116fefa0e4b7fb25 Mon Sep 17 00:00:00 2001 From: ntr Date: Mon, 2 Dec 2019 17:40:37 +1000 Subject: [PATCH 006/206] cooldown stuff --- core/Cargo.toml | 14 +++++--------- core/src/construct.rs | 17 ++++++++++++++++- core/src/game.rs | 37 +++++++++++++------------------------ core/src/lib.rs | 1 - core/src/skill.rs | 4 +--- 5 files changed, 35 insertions(+), 38 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 66e37389..57a24193 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,17 +1,13 @@ [package] -name = "mnml-core" +name = "mnml_core" version = "1.10.0" authors = ["ntr ", "mashy "] [dependencies] +chrono = { version = "0.4", features = ["serde"] } +failure = "0.1" +log = "0.4" +rand = "0.6" serde = "1" serde_derive = "1" - -rand = "0.6" uuid = { version = "0.5", features = ["serde", "v4"] } -chrono = { version = "0.4", features = ["serde"] } -bcrypt = "0.2" - -failure = "0.1" - -log = "0.4" diff --git a/core/src/construct.rs b/core/src/construct.rs index 725387b6..7deb7abb 100644 --- a/core/src/construct.rs +++ b/core/src/construct.rs @@ -438,7 +438,6 @@ impl Construct { if skill.skill.base_cd().is_some() { // what is the current cd if let Some(current_cd) = skill.cd { - // if it's 1 set it to none if current_cd == 1 { skill.cd = None; @@ -1006,4 +1005,20 @@ mod tests { return; } + #[test] + fn construct_reduce_cooldown_test() { + let mut construct = Construct::new() + .named(&"sleepygirl".to_string()); + + construct.learn_mut(Skill::Sleep); + + let mut i = 0; + while construct.skill_on_cd(Skill::Sleep).is_some() { + construct.reduce_cooldowns(); + i += 1; + } + + assert_eq!(i, Skill::Sleep.base_cd().unwrap()); + } + } diff --git a/core/src/game.rs b/core/src/game.rs index 818e5643..2f9274a4 100644 --- a/core/src/game.rs +++ b/core/src/game.rs @@ -398,7 +398,7 @@ impl Game { self.phase = Phase::Resolve; // self.log.push("".to_string()); - self.resolve_skills() + self.resolve_stack() } fn stack_sort_speed(&mut self) -> &mut Game { @@ -444,7 +444,7 @@ impl Game { } } - fn resolve_skills(mut self) -> Game { + fn resolve_stack(mut self) -> Game { if self.phase != Phase::Resolve { panic!("game not in Resolve phase"); } @@ -466,28 +466,23 @@ impl Game { // temp vec of this round's resolving skills // because need to check cooldown use before pushing them into the complete list - let mut casts = vec![]; + let mut casters = vec![]; let mut r_animation_ms = 0; while let Some(cast) = self.stack.pop() { // info!("{:} casts ", cast); - let mut resolutions = resolve(&cast, &mut self); + let mut resolutions = vec![]; + resolutions = resolve(&cast, &mut self, resolutions); r_animation_ms = resolutions.iter().fold(r_animation_ms, |acc, r| acc + r.clone().get_delay()); - - // the cast itself goes into this temp vec to handle cooldowns // if theres no resolution events, the skill didn't trigger (disable etc) - if resolutions.len() > 0 { - casts.push(cast); + if resolutions.len() > 0 && cast.used_cooldown() { + casters.push(cast); } self.resolved.append(&mut resolutions); - // while let Some(resolution) = resolutions.pop() { - // self.log_resolution(cast.speed, &resolution); - // // the results go into the resolutions - // self.resolved.push(resolution); - // } + // println!("{:?}", self.resolved); // sort the stack again in case speeds have changed self.stack_sort_speed(); @@ -496,7 +491,7 @@ impl Game { // info!("{:#?}", self.casts); // handle cooldowns and statuses - self.progress_durations(&casts); + self.progress_durations(&casters); if self.finished() { return self.finish() @@ -513,16 +508,10 @@ impl Game { continue; } - // only reduce cooldowns if no cd was used - { - if let Some(skill) = resolved.iter() - .filter(|s| s.source_construct_id == construct.id) - .find(|s| s.used_cooldown()) { - construct.skill_set_cd(skill.skill); - } else { - construct.reduce_cooldowns(); - } - } + match resolved.iter().find(|s| s.source_construct_id == construct.id) { + Some(skill) => { construct.skill_set_cd(skill.skill); }, + None => { construct.reduce_cooldowns(); }, + }; // always reduce durations construct.reduce_effect_durations(); diff --git a/core/src/lib.rs b/core/src/lib.rs index 2fb53670..dface4fe 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,6 +1,5 @@ extern crate rand; extern crate uuid; -extern crate bcrypt; extern crate chrono; extern crate serde; diff --git a/core/src/skill.rs b/core/src/skill.rs index 13fdcf82..b370840b 100644 --- a/core/src/skill.rs +++ b/core/src/skill.rs @@ -21,9 +21,7 @@ pub fn dev_resolve(a_id: Uuid, b_id: Uuid, skill: Skill) -> Resolutions { return resolve_skill(skill, &mut a, &mut b, resolutions); } -pub fn resolve(cast: &Cast, game: &mut Game) -> Resolutions { - let mut resolutions = vec![]; - +pub fn resolve(cast: &Cast, game: &mut Game, mut resolutions: Resolutions) -> Resolutions { let skill = cast.skill; let source = game.construct_by_id(cast.source_construct_id).unwrap().clone(); let targets = game.get_targets(cast.skill, &source, cast.target_construct_id); From 0b5d23c2bf304202c57b19bc43cada982e070cce Mon Sep 17 00:00:00 2001 From: Mashy Date: Tue, 3 Dec 2019 10:48:40 +1000 Subject: [PATCH 007/206] bash purge fix --- server/src/item.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/item.rs b/server/src/item.rs index fa228549..0b50dca4 100644 --- a/server/src/item.rs +++ b/server/src/item.rs @@ -986,7 +986,7 @@ impl Item { Item::Curse => vec![Item::Debuff, Item::Red, Item::Blue], Item::Decay => vec![Item::Debuff, Item::Green, Item::Blue], - Item::PurgePlus => vec![Item::Purge, Item::Purge, Item::Purge], + Item::PurgePlus => vec![Item::Purge, Item::Purge], Item::PurgePlusPlus => vec![Item::PurgePlus, Item::PurgePlus], Item::InvertPlus => vec![Item::Invert, Item::Invert], @@ -1037,7 +1037,7 @@ impl Item { Item::Banish => vec![Item::Stun, Item::Red, Item::Blue], Item::Break => vec![Item::Stun, Item::Red, Item::Green], - Item::BashPlus => vec![Item::Bash, Item::Bash, Item::Bash], + Item::BashPlus => vec![Item::Bash, Item::Bash], Item::BashPlusPlus => vec![Item::BashPlus, Item::BashPlus], Item::SleepPlus => vec![Item::Sleep, Item::Sleep], From 8cb335fcc7a4b668a74cb78abbe0b19366d29c8b Mon Sep 17 00:00:00 2001 From: ntr Date: Tue, 3 Dec 2019 10:21:22 +1000 Subject: [PATCH 008/206] break stuff --- core/fixme.md | 4 + core/src/construct.rs | 97 +- core/src/effect.rs | 10 +- core/src/game.rs | 1136 +++++++++---------- core/src/instance.rs | 4 +- core/src/item.rs | 9 +- core/src/player.rs | 6 +- core/src/skill.rs | 2462 +++++++++++++++++++++-------------------- core/src/spec.rs | 12 +- core/src/util.rs | 6 +- 10 files changed, 1906 insertions(+), 1840 deletions(-) create mode 100644 core/fixme.md diff --git a/core/fixme.md b/core/fixme.md new file mode 100644 index 00000000..3f8b070b --- /dev/null +++ b/core/fixme.md @@ -0,0 +1,4 @@ +# FIXME + +aoe event +cooldown checking -> go through round and find all casters \ No newline at end of file diff --git a/core/src/construct.rs b/core/src/construct.rs index 7deb7abb..02beb0e9 100644 --- a/core/src/construct.rs +++ b/core/src/construct.rs @@ -4,8 +4,9 @@ use rand::prelude::*; use failure::Error; use failure::err_msg; -use skill::{Skill, Cast, Immunity, Disable, Event}; -use effect::{Cooldown, Effect, Colour}; +use skill::{Skill, Cast, Immunity, Disable, EventVariant}; +use game::{Colour}; +use effect::{Cooldown, Effect}; use spec::{Spec}; use item::{Item}; @@ -60,10 +61,10 @@ impl ConstructSkill { #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum EffectMeta { Skill(Skill), - TickAmount(u64), - AddedDamage(u64), + TickAmount(usize), + AddedDamage(usize), LinkTarget(Uuid), - Multiplier(u64), + Multiplier(usize), } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] @@ -93,7 +94,7 @@ impl ConstructEffect { self.duration } - pub fn get_multiplier(&self) -> u64 { + pub fn get_multiplier(&self) -> usize { match self.meta { Some(EffectMeta::Multiplier(s)) => s, _ => 0 @@ -129,14 +130,14 @@ pub enum Stat { #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct ConstructStat { - base: u64, - value: u64, - max: u64, + base: usize, + value: usize, + max: usize, pub stat: Stat, } impl ConstructStat { - // pub fn set(&mut self, v: u64, specs: &Vec) -> &mut ConstructStat { + // pub fn set(&mut self, v: usize, specs: &Vec) -> &mut ConstructStat { // self.base = v; // self.recalculate(specs) // } @@ -157,12 +158,12 @@ impl ConstructStat { self } - pub fn reduce(&mut self, amt: u64) -> &mut ConstructStat { + pub fn reduce(&mut self, amt: usize) -> &mut ConstructStat { self.value = self.value.saturating_sub(amt); self } - pub fn increase(&mut self, amt: u64) -> &mut ConstructStat { + pub fn increase(&mut self, amt: usize) -> &mut ConstructStat { self.value = *[ self.value.saturating_add(amt), self.max @@ -171,7 +172,7 @@ impl ConstructStat { self } - pub fn force(&mut self, v: u64) -> &mut ConstructStat { + pub fn force(&mut self, v: usize) -> &mut ConstructStat { self.base = v; self.value = v; self.max = v; @@ -470,7 +471,7 @@ impl Construct { } // Stats - pub fn red_power(&self) -> u64 { + pub fn red_power(&self) -> usize { let red_power_mods = self.effects.iter() .filter(|e| e.effect.modifications().contains(&Stat::RedPower)) .map(|e| (e.effect, e.meta)) @@ -481,7 +482,7 @@ impl Construct { return modified_red_power; } - pub fn blue_power(&self) -> u64 { + pub fn blue_power(&self) -> usize { let blue_power_mods = self.effects.iter() .filter(|e| e.effect.modifications().contains(&Stat::BluePower)) .map(|e| (e.effect, e.meta)) @@ -492,7 +493,7 @@ impl Construct { return modified_blue_power; } - pub fn green_power(&self) -> u64 { + pub fn green_power(&self) -> usize { let green_power_mods = self.effects.iter() .filter(|e| e.effect.modifications().contains(&Stat::GreenPower)) .map(|e| (e.effect, e.meta)) @@ -503,8 +504,8 @@ impl Construct { return modified_green_power; } - pub fn skill_speed(&self, s: Skill) -> u64 { - self.speed().saturating_mul(s.speed() as u64) + pub fn skill_speed(&self, s: Skill) -> usize { + self.speed().saturating_mul(s.speed() as usize) } // todo complete with specs @@ -512,7 +513,7 @@ impl Construct { s.aoe() } - pub fn speed(&self) -> u64 { + pub fn speed(&self) -> usize { let speed_mods = self.effects.iter() .filter(|e| e.effect.modifications().contains(&Stat::Speed)) .map(|e| (e.effect, e.meta)) @@ -523,32 +524,32 @@ impl Construct { return modified_speed; } - pub fn red_life(&self) -> u64 { + pub fn red_life(&self) -> usize { self.red_life.value } - pub fn blue_life(&self) -> u64 { + pub fn blue_life(&self) -> usize { self.blue_life.value } - pub fn green_life(&self) -> u64 { + pub fn green_life(&self) -> usize { self.green_life.value } - fn reduce_green_life(&mut self, amount: u64) { + fn reduce_green_life(&mut self, amount: usize) { self.green_life.reduce(amount); if self.affected(Effect::Sustain) && self.green_life() == 0 { self.green_life.value = 1; } } - pub fn recharge(&mut self, skill: Skill, red_amount: u64, blue_amount: u64) -> Vec { + pub fn recharge(&mut self, skill: Skill, red_amount: usize, blue_amount: usize) -> Vec { let mut events = vec![]; // Should red type immunity block recharge??? if let Some(immunity) = self.immune(skill) { if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); + events.push(EventVariant::Immunity { skill, immunity }); } return events; } @@ -567,7 +568,7 @@ impl Construct { let blue = new_blue_life - current_blue_life; if red != 0 || blue != 0 { - events.push(Event::Recharge { red, blue, skill }); + events.push(EventVariant::Recharge { red, blue, skill }); } }, true => { @@ -593,7 +594,7 @@ impl Construct { self.reduce_green_life(red_remainder); let red_damage_amount = red_current_green_life - self.green_life(); - events.push(Event::Damage { + events.push(EventVariant::Damage { skill, amount: red_damage_amount, mitigation: red_mitigation, @@ -622,7 +623,7 @@ impl Construct { self.reduce_green_life(blue_remainder); let blue_damage_amount = blue_current_green_life - self.green_life(); - events.push(Event::Damage { + events.push(EventVariant::Damage { skill, amount: blue_damage_amount, mitigation: blue_mitigation, @@ -634,11 +635,11 @@ impl Construct { return events; } - pub fn deal_green_damage(&mut self, skill: Skill, amount: u64) -> Vec { + pub fn deal_green_damage(&mut self, skill: Skill, amount: usize) -> Vec { let mut events = vec![]; if let Some(immunity) = self.immune(skill) { if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); + events.push(EventVariant::Immunity { skill, immunity }); } return events; } @@ -660,21 +661,21 @@ impl Construct { let healing = new_green_life - current_green_life; let overhealing = modified_power - healing; - events.push(Event::Healing { + events.push(EventVariant::Healing { skill, amount: healing, overhealing, }); }, true => { - // events.push(Event::Inversion { skill }); + // events.push(EventVariant::Inversion { skill }); // there is no green shield (yet) let current_green_life = self.green_life(); self.reduce_green_life(modified_power); let delta = current_green_life - self.green_life(); - events.push(Event::Damage { + events.push(EventVariant::Damage { skill, amount: delta, mitigation: 0, @@ -686,12 +687,12 @@ impl Construct { return events; } - pub fn deal_red_damage(&mut self, skill: Skill, amount: u64) -> Vec { + pub fn deal_red_damage(&mut self, skill: Skill, amount: usize) -> Vec { let mut events = vec![]; if let Some(immunity) = self.immune(skill) { if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); + events.push(EventVariant::Immunity { skill, immunity }); } return events; } @@ -721,7 +722,7 @@ impl Construct { self.reduce_green_life(remainder); let delta = current_green_life - self.green_life(); - events.push(Event::Damage { + events.push(EventVariant::Damage { skill, amount: delta, mitigation, @@ -729,7 +730,7 @@ impl Construct { }); }, true => { - // events.push(Event::Inversion { skill }); + // events.push(EventVariant::Inversion { skill }); let current_green_life = self.green_life(); self.green_life.increase(modified_power); @@ -742,7 +743,7 @@ impl Construct { let recharge = self.red_life.value - current_life; if healing > 0 { - events.push(Event::Healing { + events.push(EventVariant::Healing { skill, amount: healing, overhealing: overhealing - recharge, @@ -750,7 +751,7 @@ impl Construct { } if recharge > 0 { - events.push(Event::Recharge { red: recharge, blue: 0, skill }); + events.push(EventVariant::Recharge { red: recharge, blue: 0, skill }); } } }; @@ -758,12 +759,12 @@ impl Construct { return events; } - pub fn deal_blue_damage(&mut self, skill: Skill, amount: u64) -> Vec { + pub fn deal_blue_damage(&mut self, skill: Skill, amount: usize) -> Vec { let mut events = vec![]; if let Some(immunity) = self.immune(skill) { if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); + events.push(EventVariant::Immunity { skill, immunity }); } return events; } @@ -789,7 +790,7 @@ impl Construct { self.reduce_green_life(remainder); let delta = current_green_life - self.green_life(); - events.push(Event::Damage { + events.push(EventVariant::Damage { skill, amount: delta, mitigation, @@ -797,7 +798,7 @@ impl Construct { }); }, true => { - // events.push(Event::Inversion { skill }); + // events.push(EventVariant::Inversion { skill }); let current_green_life = self.green_life(); self.green_life.increase(modified_power); @@ -810,7 +811,7 @@ impl Construct { let recharge = self.blue_life.value - current_life; if healing > 0 { - events.push(Event::Healing { + events.push(EventVariant::Healing { skill, amount: healing, overhealing, @@ -818,7 +819,7 @@ impl Construct { } if recharge > 0 { - events.push(Event::Recharge { red: 0, blue: recharge, skill }); + events.push(EventVariant::Recharge { red: 0, blue: recharge, skill }); } } }; @@ -828,7 +829,7 @@ impl Construct { pub fn add_effect(&mut self, skill: Skill, effect: ConstructEffect) -> Event { if let Some(immunity) = self.immune(skill) { - return Event::Immunity { + return EventVariant::Immunity { skill, immunity, }; @@ -846,7 +847,7 @@ impl Construct { } // todo modified durations cause of buffs - let result = Event::Effect { + let result = EventVariant::Effect { effect: effect.effect, duration: effect.duration, construct_effects: self.effects.clone(), @@ -867,7 +868,7 @@ impl Construct { // info!("{:} < {:?}", roll, evasion_rating); // match roll < evasion_rating { - // true => Some(Event::Evasion { + // true => Some(EventVariant::Evasion { // skill, // evasion_rating: evasion_rating, // }), diff --git a/core/src/effect.rs b/core/src/effect.rs index e1a87604..b1cb1ca4 100644 --- a/core/src/effect.rs +++ b/core/src/effect.rs @@ -1,4 +1,5 @@ use construct::{Stat, EffectMeta}; +use game::{Colour}; use skill::{Skill}; use util::{IntPct}; @@ -123,7 +124,7 @@ impl Effect { } } - pub fn apply(&self, value: u64, meta: Option) -> u64 { + pub fn apply(&self, value: usize, meta: Option) -> usize { match self { Effect::Amplify | Effect::Vulnerable | @@ -200,10 +201,3 @@ impl Effect { } } } - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub enum Colour { - Red, - Blue, - Green, -} diff --git a/core/src/game.rs b/core/src/game.rs index 2f9274a4..6ed1673a 100644 --- a/core/src/game.rs +++ b/core/src/game.rs @@ -9,7 +9,7 @@ use chrono::Duration; use failure::Error; use failure::err_msg; -use construct::{Construct}; +use construct::{Construct, Stat}; use skill::{Skill, Cast, Resolution, Event, resolve}; use effect::{Effect}; use player::{Player}; @@ -23,6 +23,28 @@ pub enum Phase { Finished, } +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum Value { + Stat { construct: Uuid, stat: Stat, mult: usize }, + Fixed { amount: usize }, + Cooldowns { construct: Uuid }, + // Skills { construct: Uuid, colour: Colour }, +} + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum Colour { + Red, + Blue, + Green, +} + +#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +pub enum Action { + Damage { construct: Uuid, skill: Skill, values: Vec, colour: Colour }, + // Recharge { skill: Skill, red: usize, blue: usize }, + // Effect { skill: Skill, effect: Effect, duration: u8, construct_effects: Vec }, +} + #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Game { pub id: Uuid, @@ -31,7 +53,7 @@ pub struct Game { pub players: Vec, pub phase: Phase, pub stack: Vec, - pub resolved: Vec, + pub resolutions: Vec>, pub instance: Option, time_control: TimeControl, phase_start: DateTime, @@ -47,7 +69,7 @@ impl Game { players: vec![], phase: Phase::Start, stack: vec![], - resolved: vec![], + resolutions: vec![], instance: None, time_control: TimeControl::Standard, phase_end: None, @@ -61,7 +83,7 @@ impl Game { .collect(); self.stack - .retain(|s| s.source_player_id == account); + .retain(|s| s.player == account); self } @@ -259,7 +281,7 @@ impl Game { self } - fn add_skill(&mut self, player_id: Uuid, source_construct_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Result<&mut Game, Error> { + fn add_skill(&mut self, player_id: Uuid, source: Uuid, target: Uuid, skill: Skill) -> Result<&mut Game, Error> { // check player in game self.player_by_id(player_id)?; @@ -269,7 +291,7 @@ impl Game { // target checks { - let target = match self.construct_by_id(target_construct_id) { + let target = match self.construct_by_id(target) { Some(c) => c, None => return Err(err_msg("target construct not in game")), }; @@ -282,7 +304,7 @@ impl Game { // construct checks { - let construct = match self.construct_by_id(source_construct_id) { + let construct = match self.construct_by_id(source) { Some(c) => c, None => return Err(err_msg("construct not in game")), }; @@ -307,11 +329,11 @@ impl Game { } // replace construct skill - if let Some(s) = self.stack.iter_mut().position(|s| s.source_construct_id == source_construct_id) { + if let Some(s) = self.stack.iter_mut().position(|s| s.source == source) { self.stack.remove(s); } - let skill = Cast::new(source_construct_id, player_id, target_construct_id, skill); + let skill = Cast::new(source, player_id, target, skill); self.stack.push(skill); return Ok(self); @@ -359,7 +381,7 @@ impl Game { return Err(err_msg("game not in skill phase")); } let mut game_state = self.clone(); - self.stack.retain(|s| game_state.construct_by_id(s.source_construct_id).unwrap().account != player_id); + self.stack.retain(|s| game_state.construct_by_id(s.source).unwrap().account != player_id); return Ok(self); } @@ -381,21 +403,18 @@ impl Game { // // for every player // .all(|t| self.stack.iter() // // the number of skills they have cast - // .filter(|s| s.source_player_id == t.id).collect::>() + // .filter(|s| s.player == t.id).collect::>() // // should equal the number required this turn // .len() == t.skills_required() // ) } - // requires no input - // just do it fn resolve_phase_start(mut self) -> Game { if self.phase != Phase::Skill { panic!("game not in skill phase"); } - assert!(self.skill_phase_finished()); - self.phase = Phase::Resolve; + self.resolutions.push(vec![]); // self.log.push("".to_string()); self.resolve_stack() @@ -406,7 +425,7 @@ impl Game { sorted.iter_mut() .for_each(|s| { if !s.skill.is_tick() { - let caster = self.construct_by_id(s.source_construct_id).unwrap(); + let caster = self.construct_by_id(s.source).unwrap(); let speed = caster.skill_speed(s.skill); s.speed = speed; } @@ -419,31 +438,6 @@ impl Game { self } - fn construct_aoe_targets(&self, construct_id: Uuid) -> Vec { - self.players.iter() - .find(|t| t.constructs.iter().any(|c| c.id == construct_id)) - .unwrap() - .constructs - .iter() - .map(|c| c.id) - .collect() - } - - pub fn get_targets(&self, skill: Skill, source: &Construct, target_construct_id: Uuid) -> Vec { - let target_player = self.players.iter() - .find(|t| t.constructs.iter().any(|c| c.id == target_construct_id)) - .unwrap(); - - if let Some(t) = target_player.intercepting() { - return vec![t.id]; - } - - match source.skill_is_aoe(skill) { - true => self.construct_aoe_targets(target_construct_id), - false => vec![target_construct_id], - } - } - fn resolve_stack(mut self) -> Game { if self.phase != Phase::Resolve { panic!("game not in Resolve phase"); @@ -471,18 +465,13 @@ impl Game { while let Some(cast) = self.stack.pop() { // info!("{:} casts ", cast); - let mut resolutions = vec![]; - resolutions = resolve(&cast, &mut self, resolutions); - r_animation_ms = resolutions.iter().fold(r_animation_ms, |acc, r| acc + r.clone().get_delay()); + resolve(&mut self, cast); + // r_animation_ms = resolutions.iter().fold(r_animation_ms, |acc, r| acc + r.clone().get_delay()); - // if theres no resolution events, the skill didn't trigger (disable etc) - if resolutions.len() > 0 && cast.used_cooldown() { - casters.push(cast); - } - - self.resolved.append(&mut resolutions); - - // println!("{:?}", self.resolved); + // if theres no resolution resolutions, the skill didn't trigger (disable etc) + // if resolutions.len() > 0 && cast.used_cooldown() { + // casters.push(cast); + // } // sort the stack again in case speeds have changed self.stack_sort_speed(); @@ -500,7 +489,32 @@ impl Game { self.skill_phase_start(r_animation_ms) } - fn progress_durations(&mut self, resolved: &Vec) -> &mut Game { + fn resolution_add(&mut self, mut resolutions: Vec) -> &mut Game { + self.resolutions.last_mut().unwrap().append(&mut resolutions); + self + } + + pub fn actions(&mut self, actions: Vec) -> &mut Game { + for action in actions { + match action { + Action::Damage { construct, skill, values, colour } => self.damage(construct, skill, values, colour), + }; + } + + self + } + + fn damage(&mut self, construct: Uuid, skill: Skill, values: Vec, colour: Colour) -> &mut Game { + let target = self.construct_by_id(construct).unwrap(); + let resolutions = match colour { + _ => target.deal_red_damage(skill, 128) // fixme unwrap + }; + + self.resolution_add(resolutions); + self + } + + fn progress_durations(&mut self, resolutions: &Vec) -> &mut Game { for mut construct in self.all_constructs() { // info!("progressing durations for {:}", construct.name); @@ -508,7 +522,7 @@ impl Game { continue; } - match resolved.iter().find(|s| s.source_construct_id == construct.id) { + match resolutions.iter().find(|s| s.source == construct.id) { Some(skill) => { construct.skill_set_cd(skill.skill); }, None => { construct.reduce_cooldowns(); }, }; @@ -521,7 +535,7 @@ impl Game { self } - // fn log_resolution(&mut self, speed: u64, resolution: &Resolution) -> &mut Game { + // fn log_resolution(&mut self, speed: usize, resolution: &Resolution) -> &mut Game { // let Resolution { source, target, event, stages: _ } = resolution; // match event { // Event::Ko { skill: _ }=> @@ -634,7 +648,7 @@ impl Game { // player.forfeit(); // info!("upkeep: {:} forfeited", player.name); // //todo - // // self.resolved.push(forfeit) + // // self.resolutions.push(forfeit) // // self.log.push(format!("{:} forfeited.", player.name)); // } } @@ -747,257 +761,8 @@ mod tests { return game.start(); } - #[test] - fn phase_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - - game = game.resolve_phase_start(); - - assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); - - return; - } - - #[test] - fn stun_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - - // should auto progress back to skill phase - assert!(game.phase == Phase::Skill); - - // assert!(game.player_by_id(y_player.id).constructs[0].is_stunned()); - // assert!(game.player_by_id(y_player.id).skills_required() == 0); - } - - #[test] - fn ko_resolution_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().red_power.force(1000000000); - game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().speed.force(1000000000); - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - // just in case - // remove all mitigation - game.player_by_id(x_player.id).unwrap().construct_by_id(x_construct.id).unwrap().red_life.force(0); - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - - assert!(!game.player_by_id(y_player.id).unwrap().constructs[0].is_stunned()); - assert!(game.phase == Phase::Finished); - } - - #[test] - fn cooldown_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - // should auto progress back to skill phase - assert!(game.phase == Phase::Skill); - - assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); - assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_some()); - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - game = game.resolve_phase_start(); - - // should auto progress back to skill phase - assert!(game.phase == Phase::Skill); - assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_some()); - - // second round - // now we block and it should go back on cd - // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - game = game.resolve_phase_start(); - - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_none()); - assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); - } - - #[test] - fn sleep_cooldown_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - - for _n in 1..10 { - // should auto progress back to skill phase - assert!(game.phase == Phase::Skill); - - // Sleep 2T CD - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - // Sleep 1T CD - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap(); - // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - // Sleep 0T CD (we use it here) - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_none()); - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Sleep).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - // Sleep back to 2T CD - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); - } - - } - - #[test] - fn counter_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { - game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns(); - } - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Counter).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Counter).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - game = game.resolve_phase_start(); - - // don't get stunned but not really stunning ¯\_(ツ)_/¯ - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].is_stunned() == false); - // riposte - assert_eq!(game.player_by_id(y_player.id).unwrap().constructs[0].green_life(), ( - y_construct.green_life() + y_construct.red_life() - x_construct.red_power().pct(Skill::CounterAttack.multiplier()))); - } - - #[test] - fn electrify_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - // one shot the target construct (should still get debuffed) - game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().red_power.force(1000000000); - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Electrify); - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Electrify).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - // apply buff - game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Electrify).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - // game = game.resolve_phase_start(); - // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Electric)); - - // attack and receive debuff - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Electrocute)); - } - // #[test] - // fn link_test() { + // fn phase_test() { // let mut game = create_test_game(); // let x_player = game.players[0].clone(); @@ -1006,48 +771,466 @@ mod tests { // let x_construct = x_player.constructs[0].clone(); // let y_construct = y_player.constructs[0].clone(); - // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Link); + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Link).is_some() { + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + + // game = game.resolve_phase_start(); + + // assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); + + // return; + // } + + // #[test] + // fn stun_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + // game = game.resolve_phase_start(); + + // // should auto progress back to skill phase + // assert!(game.phase == Phase::Skill); + + // // assert!(game.player_by_id(y_player.id).constructs[0].is_stunned()); + // // assert!(game.player_by_id(y_player.id).skills_required() == 0); + // } + + // #[test] + // fn ko_resolution_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().red_power.force(1000000000); + // game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().speed.force(1000000000); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // // just in case + // // remove all mitigation + // game.player_by_id(x_player.id).unwrap().construct_by_id(x_construct.id).unwrap().red_life.force(0); + + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + // game = game.resolve_phase_start(); + + // assert!(!game.player_by_id(y_player.id).unwrap().constructs[0].is_stunned()); + // assert!(game.phase == Phase::Finished); + // } + + // #[test] + // fn cooldown_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // // should auto progress back to skill phase + // assert!(game.phase == Phase::Skill); + + // assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); + // assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_some()); + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); + + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // game = game.resolve_phase_start(); + + // // should auto progress back to skill phase + // assert!(game.phase == Phase::Skill); + // assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_some()); + + // // second round + // // now we block and it should go back on cd + // // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // game = game.resolve_phase_start(); + + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_none()); + // assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); + // } + + // #[test] + // fn sleep_cooldown_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + + // for _n in 1..10 { + // // should auto progress back to skill phase + // assert!(game.phase == Phase::Skill); + + // // Sleep 2T CD + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // // Sleep 1T CD + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); + + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap(); + // // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // // Sleep 0T CD (we use it here) + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_none()); + + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Sleep).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // // Sleep back to 2T CD + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); + // } + + // } + + // #[test] + // fn counter_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { + // game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns(); + // } + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Counter).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Counter).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // game = game.resolve_phase_start(); + + // // don't get stunned but not really stunning ¯\_(ツ)_/¯ + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].is_stunned() == false); + // // riposte + // assert_eq!(game.player_by_id(y_player.id).unwrap().constructs[0].green_life(), ( + // y_construct.green_life() + y_construct.red_life() - x_construct.red_power().pct(Skill::CounterAttack.multiplier()))); + // } + + // #[test] + // fn electrify_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // // one shot the target construct (should still get debuffed) + // game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().red_power.force(1000000000); + + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Electrify); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Electrify).is_some() { // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); // } // // apply buff - // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Link).unwrap(); + // game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Electrify).unwrap(); // game.player_ready(x_player.id).unwrap(); // game.player_ready(y_player.id).unwrap(); - // game = game.resolve_phase_start(); - // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Link)); + // // game = game.resolve_phase_start(); + // // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Electric)); - // let Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); - // match event { - // Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Link), - // _ => panic!("not siphon"), - // }; - - // let Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); - // match event { - // Event::Recharge { red: _, blue: _, skill: _ } => (), - // _ => panic!("link result was not recharge"), - // } - - // // attack and receive link hit + // // attack and receive debuff // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); // game.player_ready(x_player.id).unwrap(); // game.player_ready(y_player.id).unwrap(); // game = game.resolve_phase_start(); - // let Resolution { source: _, target, event, stages: _ } = game.resolved.pop().unwrap(); - // assert_eq!(target.id, y_construct.id); - // match event { - // Event::Damage { amount, skill: _, mitigation: _, colour: _} => - // assert_eq!(amount, x_construct.red_power().pct(Skill::Attack.multiplier()) >> 1), - // _ => panic!("not damage link"), - // }; + // assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Electrocute)); + // } + + // // #[test] + // // fn link_test() { + // // let mut game = create_test_game(); + + // // let x_player = game.players[0].clone(); + // // let y_player = game.players[1].clone(); + + // // let x_construct = x_player.constructs[0].clone(); + // // let y_construct = y_player.constructs[0].clone(); + + // // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Link); + + // // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Link).is_some() { + // // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // // } + + // // // apply buff + // // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Link).unwrap(); + // // game.player_ready(x_player.id).unwrap(); + // // game.player_ready(y_player.id).unwrap(); + // // game = game.resolve_phase_start(); + // // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Link)); + + // // let Resolution { source: _, target: _, event, stages: _ } = game.resolutions.last.unwrap().pop().unwrap(); + // // match event { + // // Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Link), + // // _ => panic!("not siphon"), + // // }; + + // // let Resolution { source: _, target: _, event, stages: _ } = game.resolutions.last.unwrap().pop().unwrap(); + // // match event { + // // Event::Recharge { red: _, blue: _, skill: _ } => (), + // // _ => panic!("link result was not recharge"), + // // } + + // // // attack and receive link hit + // // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + // // game.player_ready(x_player.id).unwrap(); + // // game.player_ready(y_player.id).unwrap(); + // // game = game.resolve_phase_start(); + + // // let Resolution { source: _, target, event, stages: _ } = game.resolutions.last.unwrap().pop().unwrap(); + // // assert_eq!(target.id, y_construct.id); + // // match event { + // // Event::Damage { amount, skill: _, mitigation: _, colour: _} => + // // assert_eq!(amount, x_construct.red_power().pct(Skill::Attack.multiplier()) >> 1), + // // _ => panic!("not damage link"), + // // }; + // // } + + // // #[test] + // // fn absorb_test() { + // // let mut game = create_test_game(); + + // // let x_player = game.players[0].clone(); + // // let y_player = game.players[1].clone(); + + // // let x_construct = x_player.constructs[0].clone(); + // // let y_construct = y_player.constructs[0].clone(); + + // // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Absorb); + + // // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Absorb).is_some() { + // // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // // } + + // // // apply buff + // // game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Absorb).unwrap(); + // // game.player_ready(x_player.id).unwrap(); + // // game.player_ready(y_player.id).unwrap(); + // // game = game.resolve_phase_start(); + // // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Absorb)); + + // // // attack and receive debuff + // // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::TestAttack).unwrap(); + // // game.player_ready(x_player.id).unwrap(); + // // game.player_ready(y_player.id).unwrap(); + // // game = game.resolve_phase_start(); + + // // info!("{:#?}", game); + // // assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Absorption)); + // // } + + // #[test] + // fn aoe_test() { + // let mut game = create_2v2_test_game(); + + // let i_player = game.players[0].clone(); + // let x_player = game.players[1].clone(); + + // let i_construct = i_player.constructs[0].clone(); + // let j_construct = i_player.constructs[1].clone(); + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = x_player.constructs[1].clone(); + + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Ruin); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Ruin).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap(); + // game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); + // game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Ruin).unwrap(); + // game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(i_player.id).unwrap(); + // game.player_ready(x_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + // game = game.resolve_phase_start(); + // let ruins = game.resolutions + // .last().unwrap() + // .into_iter() + // .filter(|r| { + // let Resolution { source, target: _, event, stages: _ } = r; + // match source.id == x_construct.id { + // true => match event { + // Event::Effect { effect, duration, skill: _, construct_effects: _ } => { + // assert!(*effect == Effect::Stun); + // assert!(*duration == 1); + // true + // }, + // Event::AoeSkill { skill: _ } => false, + // Event::Damage { amount: _, mitigation: _, colour: _, skill: _ } => false, + // _ => panic!("ruin result not effect {:?}", event), + // } + // false => false, + // } + // }) + // .count(); + + // assert!(ruins == 2); // } // #[test] - // fn absorb_test() { + // fn intercept_test() { + // let mut game = create_2v2_test_game(); + + // let i_player = game.players[0].clone(); + // let x_player = game.players[1].clone(); + + // let i_construct = i_player.constructs[0].clone(); + // let j_construct = i_player.constructs[1].clone(); + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = x_player.constructs[1].clone(); + + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Intercept); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Intercept).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap(); + // game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); + // game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Intercept).unwrap(); + // game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(i_player.id).unwrap(); + // game.player_ready(x_player.id).unwrap(); + + // game = game.resolve_phase_start(); + + // assert!(game.resolutions.len() == 4); + // while let Some(r) = game.resolutions.last().unwrap().pop() { + // let Resolution { source , target, event: _, stages: _ } = r; + // if [i_construct.id, j_construct.id].contains(&source.id) { + // assert!(target.id == x_construct.id); + // } + // } + // } + + // #[test] + // fn ko_pve_test() { + // let mut game = create_2v2_test_game(); + + // let i_player = game.players[0].clone(); + // let x_player = game.players[1].clone(); + + // let i_construct = i_player.constructs[0].clone(); + // let j_construct = i_player.constructs[1].clone(); + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = x_player.constructs[1].clone(); + + // game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap() + // .add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap() + // .add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).unwrap() + // .add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap() + // .player_ready(i_player.id).unwrap() + // .player_ready(x_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + // game = game.resolve_phase_start(); + + // assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); + + // // kill a construct + // game.player_by_id(i_player.id).unwrap().construct_by_id(i_construct.id).unwrap().green_life.reduce(usize::max_value()); + + // assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); + // assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); + + // // add some more skills + // game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); + // game.add_skill(x_player.id, x_construct.id, j_construct.id, Skill::Attack).unwrap(); + // game.add_skill(x_player.id, y_construct.id, j_construct.id, Skill::Attack).unwrap(); + // assert!(game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).is_err()); + + // game.player_ready(i_player.id).unwrap(); + // game.player_ready(x_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + // game = game.resolve_phase_start(); + + // assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); + // assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); + // return; + // } + + // #[test] + // fn tick_removal_test() { // let mut game = create_test_game(); // let x_player = game.players[0].clone(); @@ -1056,243 +1239,75 @@ mod tests { // let x_construct = x_player.constructs[0].clone(); // let y_construct = y_player.constructs[0].clone(); - // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Absorb); + // // make the purify construct super fast so it beats out decay + // game.construct_by_id(y_construct.id).unwrap().speed.force(10000000); - // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Absorb).is_some() { + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Decay); + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Decay).is_some() { // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); // } + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Siphon); + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Siphon).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // game.construct_by_id(y_construct.id).unwrap().learn_mut(Skill::Purify); + // while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Purify).is_some() { + // game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns(); + // } + // // apply buff - // game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Absorb).unwrap(); + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap(); // game.player_ready(x_player.id).unwrap(); // game.player_ready(y_player.id).unwrap(); // game = game.resolve_phase_start(); - // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Absorb)); + // assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Decay)); - // // attack and receive debuff - // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::TestAttack).unwrap(); + // let Resolution { source: _, target: _, event, stages: _ } = game.resolutions.last().unwrap().pop().unwrap(); + // match event { + // Event::Damage { amount: _, skill, mitigation: _, colour: _ } => assert_eq!(skill, Skill::DecayTick), + // _ => panic!("not decay"), + // }; + + // game.resolutions.clear(); + + // // remove + // game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); // game.player_ready(x_player.id).unwrap(); // game.player_ready(y_player.id).unwrap(); // game = game.resolve_phase_start(); - // info!("{:#?}", game); - // assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Absorption)); + // while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolutions.last().unwrap().pop() { + // match event { + // Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => + // panic!("{:?} damage event", event), + // _ => (), + // } + // }; + + // game.add_skill(y_player.id, x_construct.id, y_construct.id, Skill::Siphon).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // game.resolutions.clear(); + + // game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolutions.last().unwrap().pop() { + // match event { + // Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => + // panic!("{:#?} {:#?} damage event", game.resolutions, event), + // _ => (), + // } + // }; + // } - #[test] - fn aoe_test() { - let mut game = create_2v2_test_game(); - - let i_player = game.players[0].clone(); - let x_player = game.players[1].clone(); - - let i_construct = i_player.constructs[0].clone(); - let j_construct = i_player.constructs[1].clone(); - let x_construct = x_player.constructs[0].clone(); - let y_construct = x_player.constructs[1].clone(); - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Ruin); - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Ruin).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Ruin).unwrap(); - game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap(); - - game.player_ready(i_player.id).unwrap(); - game.player_ready(x_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - let ruins = game.resolved - .into_iter() - .filter(|r| { - let Resolution { source, target: _, event, stages: _ } = r; - match source.id == x_construct.id { - true => match event { - Event::Effect { effect, duration, skill: _, construct_effects: _ } => { - assert!(*effect == Effect::Stun); - assert!(*duration == 1); - true - }, - Event::AoeSkill { skill: _ } => false, - Event::Damage { amount: _, mitigation: _, colour: _, skill: _ } => false, - _ => panic!("ruin result not effect {:?}", event), - } - false => false, - } - }) - .count(); - - assert!(ruins == 2); - } - - #[test] - fn intercept_test() { - let mut game = create_2v2_test_game(); - - let i_player = game.players[0].clone(); - let x_player = game.players[1].clone(); - - let i_construct = i_player.constructs[0].clone(); - let j_construct = i_player.constructs[1].clone(); - let x_construct = x_player.constructs[0].clone(); - let y_construct = x_player.constructs[1].clone(); - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Intercept); - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Intercept).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Intercept).unwrap(); - game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap(); - - game.player_ready(i_player.id).unwrap(); - game.player_ready(x_player.id).unwrap(); - - game = game.resolve_phase_start(); - - assert!(game.resolved.len() == 4); - while let Some(r) = game.resolved.pop() { - let Resolution { source , target, event: _, stages: _ } = r; - if [i_construct.id, j_construct.id].contains(&source.id) { - assert!(target.id == x_construct.id); - } - } - } - - #[test] - fn ko_pve_test() { - let mut game = create_2v2_test_game(); - - let i_player = game.players[0].clone(); - let x_player = game.players[1].clone(); - - let i_construct = i_player.constructs[0].clone(); - let j_construct = i_player.constructs[1].clone(); - let x_construct = x_player.constructs[0].clone(); - let y_construct = x_player.constructs[1].clone(); - - game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap() - .add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap() - .add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).unwrap() - .add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap() - .player_ready(i_player.id).unwrap() - .player_ready(x_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - - assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); - - // kill a construct - game.player_by_id(i_player.id).unwrap().construct_by_id(i_construct.id).unwrap().green_life.reduce(u64::max_value()); - - assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); - assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); - - // add some more skills - game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(x_player.id, x_construct.id, j_construct.id, Skill::Attack).unwrap(); - game.add_skill(x_player.id, y_construct.id, j_construct.id, Skill::Attack).unwrap(); - assert!(game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).is_err()); - - game.player_ready(i_player.id).unwrap(); - game.player_ready(x_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - - assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); - assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); - return; - } - - #[test] - fn tick_removal_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - // make the purify construct super fast so it beats out decay - game.construct_by_id(y_construct.id).unwrap().speed.force(10000000); - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Decay); - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Decay).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Siphon); - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Siphon).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.construct_by_id(y_construct.id).unwrap().learn_mut(Skill::Purify); - while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Purify).is_some() { - game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns(); - } - - // apply buff - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Decay)); - - let Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); - match event { - Event::Damage { amount: _, skill, mitigation: _, colour: _ } => assert_eq!(skill, Skill::DecayTick), - _ => panic!("not decay"), - }; - - game.resolved.clear(); - - // remove - game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolved.pop() { - match event { - Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => - panic!("{:?} damage event", event), - _ => (), - } - }; - - game.add_skill(y_player.id, x_construct.id, y_construct.id, Skill::Siphon).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - game.resolved.clear(); - - game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolved.pop() { - match event { - Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => - panic!("{:#?} {:#?} damage event", game.resolved, event), - _ => (), - } - }; - - } - #[test] fn upkeep_test() { let mut game = create_2v2_test_game(); @@ -1301,4 +1316,17 @@ mod tests { game = game.upkeep(); // assert!(game.players[1].warnings == 1); } + + #[test] + fn attack_test() { + let mut game = create_2v2_test_game(); + let player_id = game.players[0].id; + let source = game.players[0].constructs[0].id; + let target = game.players[1].constructs[0].id; + println!("{:?}", game); + game.add_skill(player_id, source, target, Skill::Attack).unwrap(); + game = game.resolve_phase_start(); + + println!("{:?}", game.resolutions.last().unwrap()); + } } diff --git a/core/src/instance.rs b/core/src/instance.rs index 1923172f..83d7875e 100644 --- a/core/src/instance.rs +++ b/core/src/instance.rs @@ -493,10 +493,10 @@ impl Instance { Ok(self) } - pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct_id: Option) -> Result { + pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct: Option) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? - .vbox_unequip(target, construct_id, target_construct_id)?; + .vbox_unequip(target, construct_id, target_construct)?; Ok(self) } } diff --git a/core/src/item.rs b/core/src/item.rs index 72639073..12121ff3 100644 --- a/core/src/item.rs +++ b/core/src/item.rs @@ -1,7 +1,8 @@ use skill::{Skill}; use spec::{Spec, SpecValues}; use construct::{Colours}; -use effect::{Colour, Cooldown}; +use effect::{Cooldown}; +use game::{Colour}; #[derive(Debug,Copy,Clone,Serialize,Deserialize,PartialEq,PartialOrd,Ord,Eq)] pub enum Item { @@ -337,7 +338,7 @@ impl Item { } } - pub fn base_speed(&self) -> u64 { + pub fn base_speed(&self) -> usize { match self { Item::Attack => 1, Item::Stun => 2, @@ -351,7 +352,7 @@ impl Item { } } - pub fn speed(&self) -> u64 { + pub fn speed(&self) -> usize { match self { Item::Attack | Item::Stun | @@ -1490,7 +1491,7 @@ pub struct ItemInfo { pub spec: bool, pub values: Option, pub skill: bool, - pub speed: Option, + pub speed: Option, pub cooldown: Cooldown, pub description: String, } diff --git a/core/src/player.rs b/core/src/player.rs index c87b8a97..0c1bad1d 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -330,8 +330,8 @@ impl Player { Ok(self) } - pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid, target_construct_id: Option) -> Result<&mut Player, Error> { - if self.vbox.stash.len() >= 9 && !target_construct_id.is_some() { + pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid, construct: Option) -> Result<&mut Player, Error> { + if self.vbox.stash.len() >= 9 && !construct.is_some() { return Err(err_msg("too many items stash")); } @@ -363,7 +363,7 @@ impl Player { construct.apply_modifiers(&player_colours); } - match target_construct_id { + match construct { Some(cid) => { self.vbox_apply(target, cid)?; }, None => { self.vbox.stash_add(target, None)?; }, }; diff --git a/core/src/skill.rs b/core/src/skill.rs index b370840b..7c96a778 100644 --- a/core/src/skill.rs +++ b/core/src/skill.rs @@ -2,360 +2,413 @@ use rand::{thread_rng, Rng}; use uuid::Uuid; use util::{IntPct}; -use construct::{Construct, ConstructEffect, EffectMeta}; use item::{Item}; -use game::{Game}; -use effect::{Effect, Colour, Cooldown}; +use game::{Game, Colour, Value, Action}; +use construct::{Construct, ConstructEffect, EffectMeta, Stat}; +use effect::{Effect, Cooldown}; -pub fn dev_resolve(a_id: Uuid, b_id: Uuid, skill: Skill) -> Resolutions { - let mut resolutions = vec![]; +// pub fn dev_resolve(a_id: Uuid, b_id: Uuid, skill: Skill) { +// let mut resolutions =vec![]; - let mut a = Construct::new(); - a.id = a_id; - let mut b = Construct::new(); - b.id = b_id; - if skill.aoe() { // Send an aoe skill event for anims - resolutions.push(Resolution::new(&a, &b).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd)); +// let mut a = Construct::new(); +// a.id = a_id; +// let mut b = Construct::new(); +// b.id = b_id; +// if skill.aoe() { // Send an aoe skill event for anims +// game.event(Resolution::new(&a, &b).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd)); +// } +// return cast_actions(skill, &mut a, &mut b, resolutions); +// } + +fn modify_cast(game: &Game, cast: Cast) -> Vec { + let target_player = game.players.iter() + .find(|t| t.constructs.iter().any(|c| c.id == cast.target)) + .unwrap(); + + if let Some(t) = target_player.intercepting() { + return vec![Cast { target: t.id, ..cast }]; } - return resolve_skill(skill, &mut a, &mut b, resolutions); + + // if game.construct[source].multistrike() { + // return vec![ + // Cast { target: t.id, ..cast }, + // Cast { target: t.id, ..cast }, + // ]; + // } + + let targets = match cast.skill.aoe() { + true => game.players.iter() + .find(|t| t.constructs.iter().any(|c| c.id == cast.target)) + .unwrap() + .constructs + .iter() + .map(|c| Cast { target: c.id, ..cast }) + .collect(), + false => vec![cast], + }; + + return targets; } -pub fn resolve(cast: &Cast, game: &mut Game, mut resolutions: Resolutions) -> Resolutions { - let skill = cast.skill; - let source = game.construct_by_id(cast.source_construct_id).unwrap().clone(); - let targets = game.get_targets(cast.skill, &source, cast.target_construct_id); - if skill.aoe() { // Send an aoe skill event for anims - resolutions.push(Resolution::new(&source, - &game.construct_by_id(cast.target_construct_id).unwrap().clone()).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd)); +pub fn resolve(game: &mut Game, cast: Cast) { + let casts = modify_cast(game, cast); + + // let source = game.construct_by_id(cast.source).unwrap().clone(); + // if skill.aoe() { // Send an aoe skill event for anims + // game.event(Resolution::new(&source, + // &game.construct_by_id(cast.target).unwrap().clone()).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd)); + // } + + for cast in casts { + game.actions(cast_actions(cast)); } - for target_id in targets { - // we clone the current state of the target and source - // so we can modify them during the resolution - // no more than 1 mutable ref allowed on game - let mut source = game.construct_by_id(cast.source_construct_id).unwrap().clone(); - let mut target = game.construct_by_id(target_id).unwrap().clone(); - - // bail out on ticks that have been removed - if skill.is_tick() && target.effects.iter().find(|ce| match ce.tick { - Some(t) => t.id == cast.id, - None => false, - }).is_none() { - continue; - } - - resolutions = resolve_skill(cast.skill, &mut source, &mut target, resolutions); - - // save the changes to the game - game.update_construct(&mut source); - game.update_construct(&mut target); - - // do additional steps - resolutions = post_resolve(cast.skill, game, resolutions); - } - - return resolutions; } -pub fn resolve_skill(skill: Skill, source: &mut Construct, target: &mut Construct, mut resolutions: Vec) -> Resolutions { - if let Some(_disable) = source.disabled(skill) { - // resolutions.push(Resolution::new(source, target).event(Event::Disable { disable, skill }).stages(EventStages::PostOnly)); - return resolutions; +pub fn cast_actions(cast: Cast) -> Vec { + match cast.skill { + _ => vec![ + Action::Damage { + construct: cast.target, + skill: Skill::Attack, + colour: Colour::Red, + values: vec![Value::Stat { construct: cast.source, stat: Stat::RedPower, mult: Skill::Attack.multiplier() }], + }], } - if target.is_ko() { - // resolutions.push(Resolution::new(source, target).event(Event::TargetKo { skill }).stages(EventStages::PostOnly)); - return resolutions; - } + // Skill::Strike => game.event( + // Event::Damage { + // colour: Colour::Red, + // amount: SkillPower { construct: cast.source, skill: Skill::Strike } + // }, + // Event::LifeSteal { + // amount: CastDamage { id: cast.id, colour: Colour::Red, target: cast.source }, + // }, + // ), - if target.affected(Effect::Reflect) && skill.colours().contains(&Colour::Blue) && !skill.is_tick() { - // guard against overflow - if source.affected(Effect::Reflect) { - return resolutions; - } - resolutions.push(Resolution::new(source, target).event(Event::Reflection { skill })); - return resolve_skill(skill, &mut source.clone(), source, resolutions); - } + // Skill::Attack => game.event(Event::Damage { + // colour: Colour::Red, + // amount: Stat { construct: cast.source, stat: Stat::RedPower, pct: ATTACK_RED_POWER_PCT } + // }), - if source.affected(Effect::Haste) { - match skill { - Skill::Slay | - Skill::SlayPlus | - Skill::SlayPlusPlus | - Skill::Chaos | - Skill::ChaosPlus | - Skill::ChaosPlusPlus | - Skill::Strike | - Skill::StrikePlus | - Skill::StrikePlusPlus => { - let amount = source.speed().pct(Skill::HasteStrike.multiplier()); - target.deal_red_damage(Skill::HasteStrike, amount) - .into_iter() - .for_each(|e| resolutions.push(Resolution::new(source, target).event(e))); - }, - _ => (), - } - } - if source.affected(Effect::Hybrid) { - match skill { - Skill::Blast| - Skill::BlastPlus | - Skill::BlastPlusPlus | - Skill::Chaos | - Skill::ChaosPlus | - Skill::ChaosPlusPlus | - Skill::Siphon | - Skill::SiphonPlus | - Skill::SiphonPlusPlus => { - let amount = source.green_power().pct(Skill::HybridBlast.multiplier()); - target.deal_blue_damage(Skill::HybridBlast, amount) - .into_iter() - .for_each(|e| resolutions.push(Resolution::new(source, target).event(e))); - }, - _ => (), - } - } + // Skill::Bash => game.event(Event::Damage { + // colour: Colour::Red, + // amounts: vec![ + // Stat { construct: cast.source, stat: Stat::RedPower, pct: ATTACK_RED_POWER_PCT }, + // Cooldowns { construct: cast.source }, + // ], + // }) + + // // we clone the current state of the target and source + // // so we can modify them during the resolution + // // no more than 1 mutable ref allowed on game + + // let mut source = game.construct_by_id(cast.source).unwrap().clone(); + // let mut target = game.construct_by_id(target_id).unwrap().clone(); + + // // bail out on ticks that have been removed + // if skill.is_tick() && target.effects.iter().find(|ce| match ce.tick { + // Some(t) => t.id == cast.id, + // None => false, + // }).is_none() { + // return; + // } + + // if let Some(_disable) = source.disabled(skill) { + // game.event(Resolution::new(source, target).event(Event::Disable { disable, skill }).stages(EventStages::PostOnly)); + // return; + // } + + // if target.is_ko() { + // game.event(Resolution::new(source, target).event(Event::TargetKo { skill }).stages(EventStages::PostOnly)); + // return; + // } + + // if target.affected(Effect::Reflect) && skill.colours().contains(&Colour::Blue) && !skill.is_tick() { + // // guard against overflow + // if source.affected(Effect::Reflect) { + // } + // game.event(Resolution::new(source, target).event(Event::Reflection { skill })); + // return cast_actions(skill, &mut source.clone(), source, resolutions); + // } + + + // // haste_strike_check(game) + + // if source.affected(Effect::Haste) { + // match skill { + // Skill::Slay | + // Skill::SlayPlus | + // Skill::SlayPlusPlus | + // Skill::Chaos | + // Skill::ChaosPlus | + // Skill::ChaosPlusPlus | + // Skill::Strike | + // Skill::StrikePlus | + // Skill::StrikePlusPlus => { + // let amount = source.speed().pct(Skill::HasteStrike.multiplier()); + // target.deal_red_damage(Skill::HasteStrike, amount) + // .into_iter() + // .for_each(|e| game.event(Resolution::new(source, target).event(e))); + // }, + // _ => (), + // } + // } + + // if source.affected(Effect::Hybrid) { + // match skill { + // Skill::Blast| + // Skill::BlastPlus | + // Skill::BlastPlusPlus | + // Skill::Chaos | + // Skill::ChaosPlus | + // Skill::ChaosPlusPlus | + // Skill::Siphon | + // Skill::SiphonPlus | + // Skill::SiphonPlusPlus => { + // let amount = source.green_power().pct(Skill::HybridBlast.multiplier()); + // target.deal_blue_damage(Skill::HybridBlast, amount) + // .into_iter() + // .for_each(|e| game.event(Resolution::new(source, target).event(e))); + // }, + // _ => (), + // } + // } // match self.category() == EffectCategory::Red { // true => { // if let Some(evasion) = target.evade(*self) { - // resolutions.push(evasion); + // game.event(evasion); // return Event; // } // }, // false => (), // } - resolutions = match skill { - Skill::Amplify| - Skill::AmplifyPlus | - Skill::AmplifyPlusPlus => amplify(source, target, resolutions, skill), + // match skill { + // Skill::Amplify| + // Skill::AmplifyPlus | + // Skill::AmplifyPlusPlus => amplify(game, skill), - Skill::Banish| - Skill::BanishPlus | - Skill::BanishPlusPlus => banish(source, target, resolutions, skill), + // Skill::Banish| + // Skill::BanishPlus | + // Skill::BanishPlusPlus => banish(game, skill), - Skill::Bash| - Skill::BashPlus | - Skill::BashPlusPlus => bash(source, target, resolutions, skill), + // Skill::Bash| + // Skill::BashPlus | + // Skill::BashPlusPlus => bash(game, skill), - Skill::Blast| - Skill::BlastPlus | - Skill::BlastPlusPlus => blast(source, target, resolutions, skill), + // Skill::Blast| + // Skill::BlastPlus | + // Skill::BlastPlusPlus => blast(game, skill), - Skill::Chaos| - Skill::ChaosPlus | - Skill::ChaosPlusPlus => chaos(source, target, resolutions, skill), + // Skill::Chaos| + // Skill::ChaosPlus | + // Skill::ChaosPlusPlus => chaos(game, skill), - Skill::Sustain| - Skill::SustainPlus | - Skill::SustainPlusPlus => sustain(source, target, resolutions, skill), + // Skill::Sustain| + // Skill::SustainPlus | + // Skill::SustainPlusPlus => sustain(game, skill), - Skill::Electrify| - Skill::ElectrifyPlus | - Skill::ElectrifyPlusPlus => electrify(source, target, resolutions, skill), - Skill::ElectrocuteTick| - Skill::ElectrocuteTickPlus | - Skill::ElectrocuteTickPlusPlus => electrocute_tick(source, target, resolutions, skill), + // Skill::Electrify| + // Skill::ElectrifyPlus | + // Skill::ElectrifyPlusPlus => electrify(game, skill), + // Skill::ElectrocuteTick| + // Skill::ElectrocuteTickPlus | + // Skill::ElectrocuteTickPlusPlus => electrocute_tick(game, skill), - Skill::Curse| - Skill::CursePlus | - Skill::CursePlusPlus => curse(source, target, resolutions, skill), + // Skill::Curse| + // Skill::CursePlus | + // Skill::CursePlusPlus => curse(game, skill), - Skill::Decay| - Skill::DecayPlus | - Skill::DecayPlusPlus => decay(source, target, resolutions, skill), - Skill::DecayTick| - Skill::DecayTickPlus | - Skill::DecayTickPlusPlus => decay_tick(source, target, resolutions, skill), + // Skill::Decay| + // Skill::DecayPlus | + // Skill::DecayPlusPlus => decay(game, skill), + // Skill::DecayTick| + // Skill::DecayTickPlus | + // Skill::DecayTickPlusPlus => decay_tick(game, skill), - Skill::Haste| - Skill::HastePlus | - Skill::HastePlusPlus => haste(source, target, resolutions, skill), + // Skill::Haste| + // Skill::HastePlus | + // Skill::HastePlusPlus => haste(game, skill), - Skill::Heal| - Skill::HealPlus | - Skill::HealPlusPlus => heal(source, target, resolutions, skill), + // Skill::Heal| + // Skill::HealPlus | + // Skill::HealPlusPlus => heal(game, skill), - Skill::Absorb| - Skill::AbsorbPlus | - Skill::AbsorbPlusPlus => absorb(source, target, resolutions, skill), + // Skill::Absorb| + // Skill::AbsorbPlus | + // Skill::AbsorbPlusPlus => absorb(game, skill), - Skill::Hybrid| - Skill::HybridPlus | - Skill::HybridPlusPlus => hybrid(source, target, resolutions, skill), + // Skill::Hybrid| + // Skill::HybridPlus | + // Skill::HybridPlusPlus => hybrid(game, skill), - Skill::Invert| - Skill::InvertPlus | - Skill::InvertPlusPlus => invert(source, target, resolutions, skill), + // Skill::Invert| + // Skill::InvertPlus | + // Skill::InvertPlusPlus => invert(game, skill), - Skill::Counter| - Skill::CounterPlus | - Skill::CounterPlusPlus => counter(source, target, resolutions, skill), + // Skill::Counter| + // Skill::CounterPlus | + // Skill::CounterPlusPlus => counter(game, skill), - Skill::Purge| - Skill::PurgePlus | - Skill::PurgePlusPlus => purge(source, target, resolutions, skill), + // Skill::Purge| + // Skill::PurgePlus | + // Skill::PurgePlusPlus => purge(game, skill), - Skill::Purify| - Skill::PurifyPlus | - Skill::PurifyPlusPlus => purify(source, target, resolutions, skill), + // Skill::Purify| + // Skill::PurifyPlus | + // Skill::PurifyPlusPlus => purify(game, skill), - Skill::Recharge| - Skill::RechargePlus | - Skill::RechargePlusPlus => recharge(source, target, resolutions, skill), + // Skill::Recharge| + // Skill::RechargePlus | + // Skill::RechargePlusPlus => recharge(game, skill), - Skill::Reflect| - Skill::ReflectPlus | - Skill::ReflectPlusPlus => reflect(source, target, resolutions, skill), + // Skill::Reflect| + // Skill::ReflectPlus | + // Skill::ReflectPlusPlus => reflect(game, skill), - Skill::Ruin| - Skill::RuinPlus | - Skill::RuinPlusPlus => ruin(source, target, resolutions, skill), + // Skill::Ruin| + // Skill::RuinPlus | + // Skill::RuinPlusPlus => ruin(game, skill), - Skill::Link| - Skill::LinkPlus | - Skill::LinkPlusPlus => link(source, target, resolutions, skill), + // Skill::Link| + // Skill::LinkPlus | + // Skill::LinkPlusPlus => link(game, skill), - Skill::Silence| - Skill::SilencePlus | - Skill::SilencePlusPlus => silence(source, target, resolutions, skill), + // Skill::Silence| + // Skill::SilencePlus | + // Skill::SilencePlusPlus => silence(game, skill), - Skill::Siphon| - Skill::SiphonPlus | - Skill::SiphonPlusPlus => siphon(source, target, resolutions, skill), - Skill::SiphonTick| - Skill::SiphonTickPlus | - Skill::SiphonTickPlusPlus => siphon_tick(source, target, resolutions, skill), + // Skill::Siphon| + // Skill::SiphonPlus | + // Skill::SiphonPlusPlus => siphon(game, skill), + // Skill::SiphonTick| + // Skill::SiphonTickPlus | + // Skill::SiphonTickPlusPlus => siphon_tick(game, skill), - Skill::Slay| - Skill::SlayPlus | - Skill::SlayPlusPlus => slay(source, target, resolutions, skill), + // Skill::Slay| + // Skill::SlayPlus | + // Skill::SlayPlusPlus => slay(game, skill), - Skill::Sleep| - Skill::SleepPlus | - Skill::SleepPlusPlus => sleep(source, target, resolutions, skill), + // Skill::Sleep| + // Skill::SleepPlus | + // Skill::SleepPlusPlus => sleep(game, skill), - Skill::Restrict| - Skill::RestrictPlus | - Skill::RestrictPlusPlus => restrict(source, target, resolutions, skill), + // Skill::Restrict| + // Skill::RestrictPlus | + // Skill::RestrictPlusPlus => restrict(game, skill), - Skill::Strike| - Skill::StrikePlus | - Skill::StrikePlusPlus => strike(source, target, resolutions, skill), + // Skill::Strike| + // Skill::StrikePlus | + // Skill::StrikePlusPlus => strike(game, skill), - Skill::Intercept| - Skill::InterceptPlus | - Skill::InterceptPlusPlus => intercept(source, target, resolutions, skill), + // Skill::Intercept| + // Skill::InterceptPlus | + // Skill::InterceptPlusPlus => intercept(game, skill), - Skill::Break| - Skill::BreakPlus | - Skill::BreakPlusPlus => break_(source, target, resolutions, skill), + // Skill::Break| + // Skill::BreakPlus | + // Skill::BreakPlusPlus => break_(game, skill), - Skill::Triage| - Skill::TriagePlus | - Skill::TriagePlusPlus => triage(source, target, resolutions, skill), + // Skill::Triage| + // Skill::TriagePlus | + // Skill::TriagePlusPlus => triage(game, skill), - Skill::TriageTick| - Skill::TriageTickPlus | - Skill::TriageTickPlusPlus => triage_tick(source, target, resolutions, skill), + // Skill::TriageTick| + // Skill::TriageTickPlus | + // Skill::TriageTickPlusPlus => triage_tick(game, skill), - // Base Skills - Skill::Attack => attack(source, target, resolutions, skill), - Skill::Block => block(source, target, resolutions, skill), - Skill::Buff => buff(source, target, resolutions, skill), - Skill::Debuff => debuff(source, target, resolutions, skill), - Skill::Stun => stun(source, target, resolutions, skill), + // // Base Skills + // Skill::Attack => attack(game, skill), + // Skill::Block => block(game, skill), + // Skill::Buff => buff(game, skill), + // Skill::Debuff => debuff(game, skill), + // Skill::Stun => stun(game, skill), - // Triggered - Skill::Electrocute | - Skill::ElectrocutePlus | - Skill::ElectrocutePlusPlus => panic!("should only trigger from electrify hit"), - Skill::HasteStrike => panic!("should only trigger from haste"), - Skill::Absorption| - Skill::AbsorptionPlus | - Skill::AbsorptionPlusPlus => panic!("should only trigger from absorb"), - Skill::HybridBlast => panic!("should only trigger from hybrid"), - Skill::CounterAttack| - Skill::CounterAttackPlus | - Skill::CounterAttackPlusPlus => panic!("should only trigger from counter"), + // // Triggered + // Skill::Electrocute | + // Skill::ElectrocutePlus | + // Skill::ElectrocutePlusPlus => panic!("should only trigger from electrify hit"), + // Skill::HasteStrike => panic!("should only trigger from haste"), + // Skill::Absorption| + // Skill::AbsorptionPlus | + // Skill::AbsorptionPlusPlus => panic!("should only trigger from absorb"), + // Skill::HybridBlast => panic!("should only trigger from hybrid"), + // Skill::CounterAttack| + // Skill::CounterAttackPlus | + // Skill::CounterAttackPlusPlus => panic!("should only trigger from counter"), - // Not used - }; + // // Not used + // }; - return resolutions; -} + // for Resolution { source: event_source, target: event_target, event, stages: _ } in resolutions.clone() { + // let mut source = game.construct_by_id(event_source.id).unwrap().clone(); + // let mut target = game.construct_by_id(event_target.id).unwrap().clone(); -fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) -> Resolutions { - for Resolution { source: event_source, target: event_target, event, stages: _ } in resolutions.clone() { - let mut source = game.construct_by_id(event_source.id).unwrap().clone(); - let mut target = game.construct_by_id(event_target.id).unwrap().clone(); + // match event { + // Event::Damage { amount, skill, mitigation, colour: c } => { + // if target.affected(Effect::Electric) && !skill.is_tick() { + // let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() + // .find(|e| e.effect == Effect::Electric).unwrap().clone(); + // match meta { + // Some(EffectMeta::Skill(s)) => { + // // Gurad against reflect overflow + // if !(source.affected(Effect::Reflect) && target.affected(Effect::Reflect)) { + // // Check reflect don't bother if electrocute is procing on death + // if source.affected(Effect::Reflect) && !target.is_ko() { + // game.event(Resolution::new(&target, &source) + // .event(Event::Reflection { skill: s }).stages(EventStages::EndPost)); + // electrocute(&mut source, &mut target, resolutions, s); + // } else { + // electrocute(&mut target, &mut source, resolutions, s); + // } + // } + // }, + // _ => panic!("no electrify skill"), + // }; + // } - match event { - Event::Damage { amount, skill, mitigation, colour: c } => { - if target.affected(Effect::Electric) && !skill.is_tick() { - let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() - .find(|e| e.effect == Effect::Electric).unwrap().clone(); - match meta { - Some(EffectMeta::Skill(s)) => { - // Gurad against reflect overflow - if !(source.affected(Effect::Reflect) && target.affected(Effect::Reflect)) { - // Check reflect don't bother if electrocute is procing on death - if source.affected(Effect::Reflect) && !target.is_ko() { - resolutions.push(Resolution::new(&target, &source) - .event(Event::Reflection { skill: s }).stages(EventStages::EndPost)); - resolutions = electrocute(&mut source, &mut target, resolutions, s); - } else { - resolutions = electrocute(&mut target, &mut source, resolutions, s); - } - } - }, - _ => panic!("no electrify skill"), - }; - } + // if target.affected(Effect::Absorb) && !target.is_ko() { + // let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() + // .find(|e| e.effect == Effect::Absorb).unwrap().clone(); + // match meta { + // Some(EffectMeta::Skill(s)) => { + // absorption(&mut source, &mut target, resolutions, skill, amount + mitigation, s); + // }, + // _ => panic!("no absorb skill"), + // }; + // } + // if c == Colour::Red { + // if target.affected(Effect::Counter) && !target.is_ko() { + // let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() + // .find(|e| e.effect == Effect::Counter).unwrap().clone(); + // match meta { + // Some(EffectMeta::Skill(s)) => { + // counter_attack(&mut target, &mut source, resolutions, s); + // }, + // _ => panic!("no counter skill"), + // }; + // } + // } - if target.affected(Effect::Absorb) && !target.is_ko() { - let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() - .find(|e| e.effect == Effect::Absorb).unwrap().clone(); - match meta { - Some(EffectMeta::Skill(s)) => { - resolutions = absorption(&mut source, &mut target, resolutions, skill, amount + mitigation, s); - }, - _ => panic!("no absorb skill"), - }; - } - if c == Colour::Red { - if target.affected(Effect::Counter) && !target.is_ko() { - let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() - .find(|e| e.effect == Effect::Counter).unwrap().clone(); - match meta { - Some(EffectMeta::Skill(s)) => { - resolutions = counter_attack(&mut target, &mut source, resolutions, s); - }, - _ => panic!("no counter skill"), - }; - } - } - - if target.is_ko() && event_target.green == 0 { - // Make sure target ko is from this event - target.effects.clear(); - resolutions.push(Resolution::new(&source, &target).event(Event::Ko()).stages(EventStages::PostOnly)); - } - }, - _ => (), - }; + // if target.is_ko() && event_target.green == 0 { + // // Make sure target ko is from this event + // target.effects.clear(); + // game.event(Resolution::new(&source, &target).event(Event::Ko()).stages(EventStages::PostOnly)); + // } + // }, + // _ => (), + // }; - game.update_construct(&mut source); - game.update_construct(&mut target); - }; - - return resolutions; + // game.update_construct(&mut source); + // game.update_construct(&mut target); + // }; } @@ -363,20 +416,20 @@ fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) -> #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct Cast { pub id: Uuid, - pub source_player_id: Uuid, - pub source_construct_id: Uuid, - pub target_construct_id: Uuid, + pub player: Uuid, + pub source: Uuid, + pub target: Uuid, pub skill: Skill, - pub speed: u64, + pub speed: usize, } impl Cast { - pub fn new(source_construct_id: Uuid, source_player_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Cast { + pub fn new(source: Uuid, player: Uuid, target: Uuid, skill: Skill) -> Cast { return Cast { id: Uuid::new_v4(), - source_construct_id, - source_player_id, - target_construct_id, + source, + player, + target, skill, speed: 0, }; @@ -385,9 +438,9 @@ impl Cast { pub fn new_tick(source: &mut Construct, target: &mut Construct, skill: Skill) -> Cast { Cast { id: Uuid::new_v4(), - source_construct_id: source.id, - source_player_id: source.account, - target_construct_id: target.id, + source: source.id, + player: source.account, + target: target.id, skill, speed: source.skill_speed(skill), } @@ -401,14 +454,101 @@ impl Cast { pub type Disable = Vec; pub type Immunity = Vec; +#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +pub struct Resolution { + pub target: EventConstruct, + pub variant: EventVariant, + pub stages: EventStages, + pub delay: i64, +} + +impl Resolution { + pub fn new(target: &Construct, variant: EventVariant) -> Resolution { + let stages = variant.stages(); + + Resolution { + target: EventConstruct { + id: target.id, + red: target.red_life(), + green: target.green_life(), + blue: target.blue_life(), + }, + variant, + stages: stages, + delay: stages.delay(), + } + } +} + +#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +pub enum EventVariant { + Disable { skill: Skill, disable: Disable }, + Immunity { skill: Skill, immunity: Immunity }, + Damage { skill: Skill, amount: usize, mitigation: usize, colour: Colour }, + Healing { skill: Skill, amount: usize, overhealing: usize }, + Recharge { skill: Skill, red: usize, blue: usize }, + Inversion { skill: Skill }, + Reflection { skill: Skill }, + AoeSkill { skill: Skill }, + Skill { skill: Skill }, + Effect { skill: Skill, effect: Effect, duration: u8, construct_effects: Vec }, + Removal { skill: Skill, effect: Option, construct_effects: Vec }, + TargetKo { skill: Skill }, + // skill not necessary but makes it neater as all events are arrays in js + Ko (), + Forfeit (), + Incomplete (), + // not used + Evasion { skill: Skill, evasion_rating: usize }, +} + +impl EventVariant { + fn stages(&self) -> EventStages { + match self { + EventVariant::Disable { skill, disable} + => EventStages::PostOnly, + EventVariant::Immunity { skill, immunity} + => EventStages::PostOnly, + EventVariant::Damage { skill, amount, mitigation, colour} + => EventStages::PostOnly, + EventVariant::Healing { skill, amount, overhealing} + => EventStages::PostOnly, + EventVariant::Recharge { skill, red, blue} + => EventStages::PostOnly, + EventVariant::Inversion { skill } + => EventStages::PostOnly, + EventVariant::Reflection { skill } + => EventStages::PostOnly, + EventVariant::AoeSkill { skill } + => EventStages::PostOnly, + EventVariant::Skill { skill } + => EventStages::PostOnly, + EventVariant::Effect { skill, effect, duration, construct_effects } + => EventStages::PostOnly, + EventVariant::Removal { skill, effect, construct_effects } + => EventStages::PostOnly, + EventVariant::TargetKo { skill } + => EventStages::PostOnly, + EventVariant::Ko () + => EventStages::PostOnly, + EventVariant::Forfeit () + => EventStages::PostOnly, + EventVariant::Incomplete () + => EventStages::PostOnly, + EventVariant::Evasion { skill, evasion_rating } + => EventStages::PostOnly, + } + } +} + // used to show the progress of a construct // while the resolutions are animating #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] pub struct EventConstruct { pub id: Uuid, - pub red: u64, - pub green: u64, - pub blue: u64, + pub red: usize, + pub green: usize, + pub blue: usize, } #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] @@ -429,52 +569,15 @@ pub enum EventStages { PostOnly, // Skip Skip Anim } -#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] -pub struct Resolution { - pub source: EventConstruct, - pub target: EventConstruct, - pub event: Event, - pub stages: EventStages, -} - -impl Resolution { - fn new(source: &Construct, target: &Construct) -> Resolution { - Resolution { - source: EventConstruct { - id: source.id, - red: source.red_life(), - green: source.green_life(), - blue: source.blue_life(), - }, - target: EventConstruct { - id: target.id, - red: target.red_life(), - green: target.green_life(), - blue: target.blue_life(), - }, - event: Event::Incomplete, - stages: EventStages::AllStages, - } - } - - fn event(mut self, e: Event) -> Resolution { - self.event = e; - self - } - - fn stages(mut self, s: EventStages) -> Resolution { - self.stages = s; - self - } - - pub fn get_delay(self) -> i64 { +impl EventStages { + fn delay(self) -> i64 { let source_duration = 1000; // Time for SOURCE ONLY let target_delay = 500; // Used for Source + Target let target_duration = 1500; // Time for TARGET ONLY let post_skill = 1000; // Time for all POST let source_and_target_total = target_delay + target_duration; // SOURCE + TARGET time - match self.stages { + match self { EventStages::AllStages => source_and_target_total + post_skill, // Anim Anim Anim EventStages::StartEnd => source_and_target_total, // Anim Anim Skip EventStages::StartPost => source_duration + post_skill, // Anim Skip Anim @@ -486,31 +589,6 @@ impl Resolution { } } - -#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] -pub enum Event { - Disable { skill: Skill, disable: Disable }, - Immunity { skill: Skill, immunity: Immunity }, - Damage { skill: Skill, amount: u64, mitigation: u64, colour: Colour }, - Healing { skill: Skill, amount: u64, overhealing: u64 }, - Recharge { skill: Skill, red: u64, blue: u64 }, - Inversion { skill: Skill }, - Reflection { skill: Skill }, - AoeSkill { skill: Skill }, - Skill { skill: Skill }, - Effect { skill: Skill, effect: Effect, duration: u8, construct_effects: Vec }, - Removal { skill: Skill, effect: Option, construct_effects: Vec }, - TargetKo { skill: Skill }, - // skill not necessary but makes it neater as all events are arrays in js - Ko (), - Forfeit (), - Incomplete, - // not used - Evasion { skill: Skill, evasion_rating: u64 }, -} - -pub type Resolutions = Vec; - #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Skill { Attack, @@ -750,7 +828,7 @@ pub enum Skill { } impl Skill { - pub fn multiplier(&self) -> u64 { + pub fn multiplier(&self) -> usize { match self { // Attack Base Skill::Attack => 80, // Base @@ -1227,7 +1305,7 @@ impl Skill { } } - pub fn speed(&self) -> u64 { + pub fn speed(&self) -> usize { match self { Skill::SiphonTick | Skill::SiphonTickPlus | @@ -1332,855 +1410,815 @@ impl Skill { } } -fn attack(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.red_power().pct(skill.multiplier()); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - - return results; -} - -fn strike(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.red_power().pct(skill.multiplier()); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - - return results; -} - -fn stun(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - return results; -} - -fn bash(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - if results.iter().any(|r| match r.event { - Event::Effect { effect, skill: effect_skill, duration: _, construct_effects: _ } - => effect == Effect::Stun && skill == effect_skill, - _ => false, - }) { - let mut cds = 0; - for cs in target.skills.iter_mut() { - if cs.skill.base_cd().is_some() { - cs.cd = match cs.cd { - None => Some(1), - Some(i) => Some(i + 1), - }; - - cds += 1; - } - } - - let amount = source.red_power().pct(skill.multiplier().pct(100 + 45u64.saturating_mul(cds))); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - - return results; -} - - -fn sleep(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - let amount = source.green_power().pct(skill.multiplier()); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn sustain(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let red_amount = source.red_power().pct(skill.multiplier()); - target.recharge(skill, red_amount, 0) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn intercept(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let intercept = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, intercept))); - - let red_amount = source.red_power().pct(skill.multiplier()); - target.recharge(skill, red_amount, 0) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn break_(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let stun = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, stun))); - let vuln = skill.effect()[1]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, vuln)).stages(EventStages::PostOnly)); - - return results; -} - -fn block(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results; -} - -fn buff(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, skill.effect()[0]))); - return results; -} - -fn counter(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, skill.effect()[0]))); - - return results; -} - -fn counter_attack(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.red_power().pct(skill.multiplier()); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - - return results; -} - -fn restrict(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - let s_multi = target.skills - .iter() - .fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Red) { - true => acc + 35, - false => acc, - }); - - let amount = source.red_power().pct(skill.multiplier()).pct(s_multi); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - - return results; -} - -fn slay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.red_power().pct(skill.multiplier()) + source.green_power().pct(skill.multiplier()); - let slay_events = target.deal_red_damage(skill, amount); - - for e in slay_events { - match e { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => { - results.push(Resolution::new(source, target).event(e)); - let heal = source.deal_green_damage(skill, amount.pct(50)); - for h in heal { - results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); - }; - }, - _ => results.push(Resolution::new(source, target).event(e)), - } - } - - return results; -} - -fn heal(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.green_power().pct(skill.multiplier()); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - return results; -} - -fn triage(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Triage => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - - let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; - let tick_skill = match meta { - Some(EffectMeta::Skill(s)) => s, - _ => panic!("no triage tick skill"), - }; - let triage = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); - results.push(Resolution::new(source, target).event(target.add_effect(skill, triage))); - - match skip_tick { - true => return results, - false => return triage_tick(source, target, results, tick_skill) - } -} - -fn triage_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.green_power().pct(skill.multiplier()); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); - return results; -} - -fn chaos(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let mut rng = thread_rng(); - let b_rng: u64 = rng.gen_range(100, 130); - let amount = source.blue_power().pct(skill.multiplier()).pct(b_rng); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - let r_rng: u64 = rng.gen_range(100, 130); - let amount = source.red_power().pct(skill.multiplier()).pct(r_rng); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - return results; -} - -fn blast(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - return results; -} - -fn amplify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn haste(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn debuff(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn decay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - - let wither = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, wither))); - - let skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Decay => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[1]; - let tick_skill = match meta { - Some(EffectMeta::Skill(s)) => s, - _ => panic!("no decay tick skill"), - }; - let decay = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, decay)) - .stages(EventStages::PostOnly)); - - match skip_tick { - true => return results, - false => return decay_tick(source, target, results, tick_skill) - } -} - -fn decay_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); - return results; -} - -// electrify is the buff effect -// when attacked it runs electrocute and applies a debuff -fn electrify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let electrify = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, electrify))); - return results;; -} - -fn electrocute(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - // Remove electric buff, no need to display if construct is dead - if !source.is_ko() { - let electric = source.effects.iter().position(|e| e.effect == Effect::Electric); - match electric { - Some(eff) => { - let ce = source.effects.remove(eff); - results.push(Resolution::new(source, source) - .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: source.effects.clone() }) - .stages(EventStages::PostOnly)); - } - None => () - } - } - - let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; - let tick_skill = match meta { - Some(EffectMeta::Skill(s)) => s, - _ => panic!("no electrocute tick skill"), - }; - - let skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Electrocute => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - let electrocute = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, electrocute)) - .stages(EventStages::PostOnly)); - - - match skip_tick { - true => return results, - false => return electrocute_tick(source, target, results, tick_skill) - } -} - -fn electrocute_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); - return results; -} - -fn ruin(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, skill.effect()[0])) - .stages(EventStages::PostOnly)); - return results;; -} - -fn absorb(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - let blue_amount = source.blue_power().pct(skill.multiplier()); - target.recharge(skill, 0, blue_amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - return results;; -} - -fn absorption(source: &mut Construct, target: &mut Construct, mut results: Resolutions, reflect_skill: Skill, amount: u64, skill: Skill) -> Resolutions { - let absorb = skill.effect()[0].set_meta(EffectMeta::AddedDamage(amount)); - - results.push(Resolution::new(source, target) - .event(target.add_effect(reflect_skill, absorb)) - .stages(EventStages::PostOnly)); - - let absorb_index = target.effects.iter().position(|e| e.effect == Effect::Absorb).expect("No absorb"); - let ce = target.effects.remove(absorb_index); - - results.push(Resolution::new(source, target) - .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: target.effects.clone() }) - .stages(EventStages::PostOnly)); - return results;; -} - -fn curse(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn hybrid(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn invert(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn reflect(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let blue_amount = source.blue_power().pct(skill.multiplier()); - target.recharge(skill, 0, blue_amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results;; -} - -fn recharge(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - let red_amount = source.red_power().pct(skill.multiplier()); - let blue_amount = source.blue_power().pct(skill.multiplier()); - target.recharge(skill, red_amount, blue_amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn siphon(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - - let skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Siphon => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; - let tick_skill = match meta { - Some(EffectMeta::Skill(s)) => s, - _ => panic!("no siphon tick skill"), - }; - let siphon = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); - results.push(Resolution::new(source, target).event(target.add_effect(skill, siphon))); - - match skip_tick { - true => return results, - false => return siphon_tick(source, target, results, tick_skill) - } -} - -fn siphon_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()) + source.green_power().pct(skill.multiplier()); - let siphon_events = target.deal_blue_damage(skill, amount); - - for e in siphon_events { - match e { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => { - results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)); - let heal = source.deal_green_damage(skill, amount); - for h in heal { - results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); - }; - }, - _ => results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)), - } - } - - return results; -} - -fn link(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let amount = source.blue_power().pct(skill.multiplier().saturating_mul(target.effects.len() as u64)); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn silence(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let s_multi = target.skills - .iter() - .fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Blue) { - true => acc + 45, - false => acc, - }); - - let amount = source.blue_power().pct(skill.multiplier()).pct(s_multi); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn purge(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - if target.effects.len() > 0 { - target.effects.clear(); - results.push(Resolution::new(source, target) - .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) - .stages(EventStages::PostOnly)); - } - - let effect = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); - - return results; -} - -fn purify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - if target.effects.len() > 0 { - let amount = source.green_power().pct(skill.multiplier().saturating_mul(target.effects.len() as u64)); - target.effects.clear(); - results.push(Resolution::new(source, target) - .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) - .stages(EventStages::PostOnly)); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - let effect = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); - - return results; -} - -fn banish(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - - let red_damage = target.red_life().pct(skill.multiplier()); - let blue_damage = target.blue_life().pct(skill.multiplier()); - - if red_damage > 0 { - target.deal_red_damage(skill, red_damage) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - - if blue_damage > 0 { - target.deal_blue_damage(skill, blue_damage) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])).stages(EventStages::PostOnly)); - return results; -} - -#[cfg(test)] + +// fn strike(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.red_power().pct(skill.multiplier()); +// target.deal_red_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e))); + +// } + +// fn stun(source: &mut Construct, target: &mut Construct, skill: Skill) { +// skill.effect().into_iter() +// .for_each(|e| (game.event(Resolution::new(source, target).event(target.add_effect(skill, e))))); + +// } + +// fn bash(source: &mut Construct, target: &mut Construct, skill: Skill) { +// skill.effect().into_iter() +// .for_each(|e| (game.event(Resolution::new(source, target).event(target.add_effect(skill, e))))); + +// if resolutions.iter().any(|r| match r.event { +// Event::Effect { effect, skill: effect_skill, duration: _, construct_effects: _ } +// => effect == Effect::Stun && skill == effect_skill, +// _ => false, +// }) { +// let mut cds = 0; +// for cs in target.skills.iter_mut() { +// if cs.skill.base_cd().is_some() { +// cs.cd = match cs.cd { +// None => Some(1), +// Some(i) => Some(i + 1), +// }; + +// cds += 1; +// } +// } + +// let amount = source.red_power().pct(skill.multiplier().pct(100 + 45usize.saturating_mul(cds))); +// target.deal_red_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } + +// } + + +// fn sleep(source: &mut Construct, target: &mut Construct, skill: Skill) { +// skill.effect().into_iter() +// .for_each(|e| (game.event(Resolution::new(source, target).event(target.add_effect(skill, e))))); + +// let amount = source.green_power().pct(skill.multiplier()); +// target.deal_green_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn sustain(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + +// let red_amount = source.red_power().pct(skill.multiplier()); +// target.recharge(skill, red_amount, 0) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn intercept(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let intercept = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, intercept))); + +// let red_amount = source.red_power().pct(skill.multiplier()); +// target.recharge(skill, red_amount, 0) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn break_(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let stun = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, stun))); +// let vuln = skill.effect()[1]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, vuln)).stages(EventStages::PostOnly)); + +// } + +// fn block(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn buff(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target) +// .event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn counter(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target) +// .event(target.add_effect(skill, skill.effect()[0]))); + +// } + +// fn counter_attack(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.red_power().pct(skill.multiplier()); +// target.deal_red_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e))); + +// } + +// fn restrict(source: &mut Construct, target: &mut Construct, skill: Skill) { +// skill.effect().into_iter() +// .for_each(|e| (game.event(Resolution::new(source, target).event(target.add_effect(skill, e))))); + +// let s_multi = target.skills +// .iter() +// .fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Red) { +// true => acc + 35, +// false => acc, +// }); + +// let amount = source.red_power().pct(skill.multiplier()).pct(s_multi); +// target.deal_red_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + + +// } + +// fn slay(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.red_power().pct(skill.multiplier()) + source.green_power().pct(skill.multiplier()); +// let slay_events = target.deal_red_damage(skill, amount); + +// for e in slay_events { +// match e { +// Event::Damage { amount, mitigation: _, colour: _, skill: _ } => { +// game.event(Resolution::new(source, target).event(e)); +// let heal = source.deal_green_damage(skill, amount.pct(50)); +// for h in heal { +// game.event(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); +// }; +// }, +// _ => game.event(Resolution::new(source, target).event(e)), +// } +// } + +// } + +// fn heal(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.green_power().pct(skill.multiplier()); +// target.deal_green_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e))); +// } + +// fn triage(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let skip_tick = target.effects.iter().any(|e| { +// match e.effect { +// Effect::Triage => source.skill_speed(skill) <= e.tick.unwrap().speed, +// _ => false, +// } +// }); + +// let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; +// let tick_skill = match meta { +// Some(EffectMeta::Skill(s)) => s, +// _ => panic!("no triage tick skill"), +// }; +// let triage = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); +// game.event(Resolution::new(source, target).event(target.add_effect(skill, triage))); + +// match skip_tick { +// false => return triage_tick(source, target, resolutions, tick_skill) +// } +// } + +// fn triage_tick(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.green_power().pct(skill.multiplier()); +// target.deal_green_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); +// } + +// fn chaos(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let mut rng = thread_rng(); +// let b_rng: usize = rng.gen_range(100, 130); +// let amount = source.blue_power().pct(skill.multiplier()).pct(b_rng); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e))); +// let r_rng: usize = rng.gen_range(100, 130); +// let amount = source.red_power().pct(skill.multiplier()).pct(r_rng); +// target.deal_red_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } + +// fn blast(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.blue_power().pct(skill.multiplier()); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e))); +// } + +// fn amplify(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn haste(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn debuff(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn decay(source: &mut Construct, target: &mut Construct, skill: Skill) { + +// let wither = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, wither))); + +// let skip_tick = target.effects.iter().any(|e| { +// match e.effect { +// Effect::Decay => source.skill_speed(skill) <= e.tick.unwrap().speed, +// _ => false, +// } +// }); +// let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[1]; +// let tick_skill = match meta { +// Some(EffectMeta::Skill(s)) => s, +// _ => panic!("no decay tick skill"), +// }; +// let decay = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); +// game.event(Resolution::new(source, target) +// .event(target.add_effect(skill, decay)) +// .stages(EventStages::PostOnly)); + +// match skip_tick { +// false => return decay_tick(source, target, resolutions, tick_skill) +// } +// } + +// fn decay_tick(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.blue_power().pct(skill.multiplier()); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); +// } + +// // electrify is the buff effect +// // when attacked it runs electrocute and applies a debuff +// fn electrify(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let electrify = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, electrify))); +// } + +// fn electrocute(source: &mut Construct, target: &mut Construct, skill: Skill) { +// // Remove electric buff, no need to display if construct is dead +// if !source.is_ko() { +// let electric = source.effects.iter().position(|e| e.effect == Effect::Electric); +// match electric { +// Some(eff) => { +// let ce = source.effects.remove(eff); +// game.event(Resolution::new(source, source) +// .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: source.effects.clone() }) +// .stages(EventStages::PostOnly)); +// } +// None => () +// } +// } + +// let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; +// let tick_skill = match meta { +// Some(EffectMeta::Skill(s)) => s, +// _ => panic!("no electrocute tick skill"), +// }; + +// let skip_tick = target.effects.iter().any(|e| { +// match e.effect { +// Effect::Electrocute => source.skill_speed(skill) <= e.tick.unwrap().speed, +// _ => false, +// } +// }); +// let electrocute = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); +// game.event(Resolution::new(source, target) +// .event(target.add_effect(skill, electrocute)) +// .stages(EventStages::PostOnly)); + + +// match skip_tick { +// false => return electrocute_tick(source, target, resolutions, tick_skill) +// } +// } + +// fn electrocute_tick(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.blue_power().pct(skill.multiplier()); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); +// } + +// fn ruin(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.blue_power().pct(skill.multiplier()); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// game.event(Resolution::new(source, target) +// .event(target.add_effect(skill, skill.effect()[0])) +// .stages(EventStages::PostOnly)); +// } + +// fn absorb(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// let blue_amount = source.blue_power().pct(skill.multiplier()); +// target.recharge(skill, 0, blue_amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } + +// fn absorption(source: &mut Construct, target: &mut Construct, reflect_skill: Skill, amount: usize, skill: Skill) { +// let absorb = skill.effect()[0].set_meta(EffectMeta::AddedDamage(amount)); + +// game.event(Resolution::new(source, target) +// .event(target.add_effect(reflect_skill, absorb)) +// .stages(EventStages::PostOnly)); + +// let absorb_index = target.effects.iter().position(|e| e.effect == Effect::Absorb).expect("No absorb"); +// let ce = target.effects.remove(absorb_index); + +// game.event(Resolution::new(source, target) +// .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: target.effects.clone() }) +// .stages(EventStages::PostOnly)); +// } + +// fn curse(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn hybrid(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn invert(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn reflect(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + +// let blue_amount = source.blue_power().pct(skill.multiplier()); +// target.recharge(skill, 0, blue_amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn recharge(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); +// let red_amount = source.red_power().pct(skill.multiplier()); +// let blue_amount = source.blue_power().pct(skill.multiplier()); +// target.recharge(skill, red_amount, blue_amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn siphon(source: &mut Construct, target: &mut Construct, skill: Skill) { + +// let skip_tick = target.effects.iter().any(|e| { +// match e.effect { +// Effect::Siphon => source.skill_speed(skill) <= e.tick.unwrap().speed, +// _ => false, +// } +// }); +// let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; +// let tick_skill = match meta { +// Some(EffectMeta::Skill(s)) => s, +// _ => panic!("no siphon tick skill"), +// }; +// let siphon = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); +// game.event(Resolution::new(source, target).event(target.add_effect(skill, siphon))); + +// match skip_tick { +// false => return siphon_tick(source, target, resolutions, tick_skill) +// } +// } + +// fn siphon_tick(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.blue_power().pct(skill.multiplier()) + source.green_power().pct(skill.multiplier()); +// let siphon_events = target.deal_blue_damage(skill, amount); + +// for e in siphon_events { +// match e { +// Event::Damage { amount, mitigation: _, colour: _, skill: _ } => { +// game.event(Resolution::new(source, target).event(e).stages(EventStages::EndPost)); +// let heal = source.deal_green_damage(skill, amount); +// for h in heal { +// game.event(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); +// }; +// }, +// _ => game.event(Resolution::new(source, target).event(e).stages(EventStages::EndPost)), +// } +// } + +// } + +// fn link(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + +// let amount = source.blue_power().pct(skill.multiplier().saturating_mul(target.effects.len() as usize)); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn silence(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + +// let s_multi = target.skills +// .iter() +// .fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Blue) { +// true => acc + 45, +// false => acc, +// }); + +// let amount = source.blue_power().pct(skill.multiplier()).pct(s_multi); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn purge(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); +// if target.effects.len() > 0 { +// target.effects.clear(); +// game.event(Resolution::new(source, target) +// .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) +// .stages(EventStages::PostOnly)); +// } + +// let effect = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); + +// } + +// fn purify(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); +// if target.effects.len() > 0 { +// let amount = source.green_power().pct(skill.multiplier().saturating_mul(target.effects.len() as usize)); +// target.effects.clear(); +// game.event(Resolution::new(source, target) +// .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) +// .stages(EventStages::PostOnly)); +// target.deal_green_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } +// let effect = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); + +// } + +// fn banish(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); + +// let red_damage = target.red_life().pct(skill.multiplier()); +// let blue_damage = target.blue_life().pct(skill.multiplier()); + +// if red_damage > 0 { +// target.deal_red_damage(skill, red_damage) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } + +// if blue_damage > 0 { +// target.deal_blue_damage(skill, blue_damage) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } + +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])).stages(EventStages::PostOnly)); +// } + +// #[cfg(test)] mod tests { use skill::*; #[test] - fn heal_test() { - let mut x = Construct::new() - .named(&"muji".to_string()) - .learn(Skill::Heal); - - let mut y = Construct::new() - .named(&"camel".to_string()) - .learn(Skill::Heal); - - x.deal_red_damage(Skill::Attack, 5); - - heal(&mut y, &mut x, vec![], Skill::Heal); + fn attack_actions_test() { + let cast = Cast::new(Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4(), Skill::Attack); + let mut actions = cast_actions(cast); + let Action::Damage { construct, skill, values, colour } = actions.remove(0); + assert_eq!(skill, Skill::Attack); + assert_eq!(colour, Colour::Red); } - #[test] - fn decay_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - decay(&mut x, &mut y, vec![], Skill::Decay); - - assert!(y.effects.iter().any(|e| e.effect == Effect::Decay)); - - y.reduce_effect_durations(); - let _decay = y.effects.iter().find(|e| e.effect == Effect::Decay); - // assert!(y.green_life() == y.green_life().saturating_sub(decay.unwrap().tick.unwrap().amount)); - } - - #[test] - fn block_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - // ensure it doesn't have 0 pd - x.red_power.force(100); - y.green_life.force(500); - - block(&mut y.clone(), &mut y, vec![], Skill::Block); - assert!(y.effects.iter().any(|e| e.effect == Effect::Block)); - - let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => - assert!(amount < x.red_power().pct(Skill::Attack.multiplier())), - _ => panic!("not damage"), - }; - } - - #[test] - fn sustain_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - x.red_power.force(10000000000000); // multiplication of int max will cause overflow - y.green_life.force(1024); // make tests more flexible if we change stats - - sustain(&mut y.clone(), &mut y, vec![], Skill::Sustain); - assert!(y.affected(Effect::Sustain)); - - let mut results = ruin(&mut x, &mut y, vec![], Skill::Ruin); - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Immunity { skill: _, immunity } => assert!(immunity.contains(&Effect::Sustain)), - _ => panic!("not immune cluthc"), - }; - - let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); - assert!(y.green_life() == 1); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 1023), - _ => panic!("not damage"), - }; - } - - #[test] - fn invert_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - // give red shield but reduce to 0 - y.red_life.force(64); - y.red_life.reduce(64); - x.red_power.force(512); - invert(&mut y.clone(), &mut y, vec![], Skill::Invert); - assert!(y.affected(Effect::Invert)); - - // heal should deal green damage - heal(&mut x, &mut y, vec![], Skill::Heal); - assert!(y.green_life() < 1024); - - // attack should heal and recharge red shield - let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); - - // match results.remove(0).event { - // Event::Inversion { skill } => assert_eq!(skill, Skill::Attack), - // _ => panic!("not inversion"), - //}; - - match results.remove(0).event { - Event::Healing { skill: _, overhealing: _, amount } => assert!(amount > 0), - _ => panic!("not healing from inversion"), - }; - - match results.remove(0).event { - Event::Recharge { skill: _, red, blue: _ } => assert!(red > 0), - _ => panic!("not recharge from inversion"), - }; - } - - #[test] - fn reflect_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - reflect(&mut y.clone(), &mut y, vec![], Skill::Reflect); - assert!(y.affected(Effect::Reflect)); - - let mut results = vec![]; - results = resolve_skill(Skill::Blast, &mut x, &mut y, results); - - assert!(x.green_life() < 1024); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Reflection { skill } => assert_eq!(skill, Skill::Blast), - _ => panic!("not reflection"), - }; - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert!(amount > 0), - _ => panic!("not damage"), - }; - } - - #[test] - fn siphon_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - x.blue_power.force(256); - x.green_power.force(220); - x.green_life.force(1024); - y.blue_life.force(0); - x.green_life.reduce(512); - - let mut results = resolve_skill(Skill::Siphon, &mut x, &mut y, vec![]); - - assert!(y.affected(Effect::Siphon)); - assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier()))); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Siphon), - _ => panic!("not siphon"), - }; - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) - + 220.pct(Skill::SiphonTick.multiplier())), - _ => panic!("not damage siphon"), - }; - - let Resolution { source: _, target, event, stages: _ } = results.remove(0); - match event { - Event::Healing { amount, skill: _, overhealing: _ } => { - assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier())); - assert_eq!(target.id, x.id); - }, - _ => panic!("not healing"), - }; - } - - #[test] - fn triage_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"pretaliation".to_string()); - - // ensure it doesn't have 0 sd - x.blue_power.force(50); - - // remove all mitigation - y.red_life.force(0); - y.blue_life.force(0); - - y.deal_red_damage(Skill::Attack, 5); - let prev_hp = y.green_life(); - - triage(&mut x, &mut y, vec![], Skill::Triage); - - assert!(y.effects.iter().any(|e| e.effect == Effect::Triage)); - assert!(y.green_life() > prev_hp); - } - - #[test] - fn recharge_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"pretaliation".to_string()); - - y.red_life.force(50); - y.blue_life.force(50); - - y.deal_red_damage(Skill::Attack, 5); - y.deal_blue_damage(Skill::Blast, 5); - - let mut results = recharge(&mut x, &mut y, vec![], Skill::Recharge); - - results.remove(0); - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Recharge { red, blue, skill: _ } => { - assert!(red == 5); - assert!(blue == 5); - } - _ => panic!("result was not recharge"), - } - } - - - #[test] - fn silence_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - silence(&mut x.clone(), &mut x, vec![], Skill::Silence); - assert!(x.effects.iter().any(|e| e.effect == Effect::Silence)); - assert!(x.disabled(Skill::Silence).is_some()); - } - - #[test] - fn amplify_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - x.blue_power.force(50); - - amplify(&mut x.clone(), &mut x, vec![], Skill::Amplify); - assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify)); - assert_eq!(x.blue_power(), 75); - } - - #[test] - fn purify_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - decay(&mut x.clone(), &mut x, vec![], Skill::Decay); - assert!(x.effects.iter().any(|e| e.effect == Effect::Decay)); - - purify(&mut x.clone(), &mut x, vec![], Skill::Purify); - assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay)); - } - - #[test] - fn bash_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"pretaliation".to_string()) - .learn(Skill::Stun); - - let stun_cd = y.skills.iter().find(|cs| cs.skill == Skill::Stun).unwrap().cd.unwrap(); - - bash(&mut x, &mut y, vec![], Skill::Bash); - assert!(!x.effects.iter().any(|e| e.effect == Effect::Stun)); - assert!(y.skills.iter().any(|cs| cs.skill == Skill::Stun && cs.cd.unwrap() == stun_cd + 1)); - } - - #[test] - fn purge_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); + // #[test] + // fn heal_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()) + // .learn(Skill::Heal); + + // let mut y = Construct::new() + // .named(&"camel".to_string()) + // .learn(Skill::Heal); - let mut y = Construct::new() - .named(&"pretaliation".to_string()) - .learn(Skill::Heal) - .learn(Skill::HealPlus); - - purge(&mut x, &mut y, vec![], Skill::Purge); - // 2 turns at lvl 1 - assert!(y.effects.iter().any(|e| e.effect == Effect::Purge && e.duration == 2)); - assert!(y.disabled(Skill::Heal).is_some()); - } + // x.deal_red_damage(Skill::Attack, 5); + + // heal(&mut y, &mut x, vec![], Skill::Heal); + // } + + // #[test] + // fn decay_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"camel".to_string()); + + // decay(&mut x, &mut y, vec![], Skill::Decay); + + // assert!(y.effects.iter().any(|e| e.effect == Effect::Decay)); + + // y.reduce_effect_durations(); + // let _decay = y.effects.iter().find(|e| e.effect == Effect::Decay); + // // assert!(y.green_life() == y.green_life().saturating_sub(decay.unwrap().tick.unwrap().amount)); + // } + + // #[test] + // fn block_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"camel".to_string()); + + // // ensure it doesn't have 0 pd + // x.red_power.force(100); + // y.green_life.force(500); + + // block(&mut y.clone(), &mut y, vec![], Skill::Block); + // assert!(y.effects.iter().any(|e| e.effect == Effect::Block)); + + // attack(&mut x, &mut y, vec![], Skill::Attack); + + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Damage { amount, mitigation: _, colour: _, skill: _ } => + // assert!(amount < x.red_power().pct(Skill::Attack.multiplier())), + // _ => panic!("not damage"), + // }; + // } + + // #[test] + // fn sustain_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"camel".to_string()); + + // x.red_power.force(10000000000000); // multiplication of int max will cause overflow + // y.green_life.force(1024); // make tests more flexible if we change stats + + // sustain(&mut y.clone(), &mut y, vec![], Skill::Sustain); + // assert!(y.affected(Effect::Sustain)); + + // ruin(&mut x, &mut y, vec![], Skill::Ruin); + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Immunity { skill: _, immunity } => assert!(immunity.contains(&Effect::Sustain)), + // _ => panic!("not immune cluthc"), + // }; + + // attack(&mut x, &mut y, vec![], Skill::Attack); + // assert!(y.green_life() == 1); + + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 1023), + // _ => panic!("not damage"), + // }; + // } + + // #[test] + // fn invert_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"camel".to_string()); + + // // give red shield but reduce to 0 + // y.red_life.force(64); + // y.red_life.reduce(64); + // x.red_power.force(512); + // invert(&mut y.clone(), &mut y, vec![], Skill::Invert); + // assert!(y.affected(Effect::Invert)); + + // // heal should deal green damage + // heal(&mut x, &mut y, vec![], Skill::Heal); + // assert!(y.green_life() < 1024); + + // // attack should heal and recharge red shield + // attack(&mut x, &mut y, vec![], Skill::Attack); + + // // match resolutions.remove(0).event { + // // Event::Inversion { skill } => assert_eq!(skill, Skill::Attack), + // // _ => panic!("not inversion"), + // //}; + + // match resolutions.remove(0).event { + // Event::Healing { skill: _, overhealing: _, amount } => assert!(amount > 0), + // _ => panic!("not healing from inversion"), + // }; + + // match resolutions.remove(0).event { + // Event::Recharge { skill: _, red, blue: _ } => assert!(red > 0), + // _ => panic!("not recharge from inversion"), + // }; + // } + + // #[test] + // fn reflect_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"camel".to_string()); + + // reflect(&mut y.clone(), &mut y, vec![], Skill::Reflect); + // assert!(y.affected(Effect::Reflect)); + + // let mut vec![]; + // cast_actions(Skill::Blast, &mut x, &mut y, resolutions); + + // assert!(x.green_life() < 1024); + + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Reflection { skill } => assert_eq!(skill, Skill::Blast), + // _ => panic!("not reflection"), + // }; + + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert!(amount > 0), + // _ => panic!("not damage"), + // }; + // } + + // #[test] + // fn siphon_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"camel".to_string()); + + // x.blue_power.force(256); + // x.green_power.force(220); + // x.green_life.force(1024); + // y.blue_life.force(0); + // x.green_life.reduce(512); + + // cast_actions(Skill::Siphon, &mut x, &mut y, vec![]); + + // assert!(y.affected(Effect::Siphon)); + // assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier()))); + + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Siphon), + // _ => panic!("not siphon"), + // }; + + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) + // + 220.pct(Skill::SiphonTick.multiplier())), + // _ => panic!("not damage siphon"), + // }; + + // let Resolution { source: _, target, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Healing { amount, skill: _, overhealing: _ } => { + // assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier())); + // assert_eq!(target.id, x.id); + // }, + // _ => panic!("not healing"), + // }; + // } + + // #[test] + // fn triage_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"pretaliation".to_string()); + + // // ensure it doesn't have 0 sd + // x.blue_power.force(50); + + // // remove all mitigation + // y.red_life.force(0); + // y.blue_life.force(0); + + // y.deal_red_damage(Skill::Attack, 5); + // let prev_hp = y.green_life(); + + // triage(&mut x, &mut y, vec![], Skill::Triage); + + // assert!(y.effects.iter().any(|e| e.effect == Effect::Triage)); + // assert!(y.green_life() > prev_hp); + // } + + // #[test] + // fn recharge_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"pretaliation".to_string()); + + // y.red_life.force(50); + // y.blue_life.force(50); + + // y.deal_red_damage(Skill::Attack, 5); + // y.deal_blue_damage(Skill::Blast, 5); + + // recharge(&mut x, &mut y, vec![], Skill::Recharge); + + // resolutions.remove(0); + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Recharge { red, blue, skill: _ } => { + // assert!(red == 5); + // assert!(blue == 5); + // } + // _ => panic!("result was not recharge"), + // } + // } + + + // #[test] + // fn silence_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // silence(&mut x.clone(), &mut x, vec![], Skill::Silence); + // assert!(x.effects.iter().any(|e| e.effect == Effect::Silence)); + // assert!(x.disabled(Skill::Silence).is_some()); + // } + + // #[test] + // fn amplify_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // x.blue_power.force(50); + + // amplify(&mut x.clone(), &mut x, vec![], Skill::Amplify); + // assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify)); + // assert_eq!(x.blue_power(), 75); + // } + + // #[test] + // fn purify_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // decay(&mut x.clone(), &mut x, vec![], Skill::Decay); + // assert!(x.effects.iter().any(|e| e.effect == Effect::Decay)); + + // purify(&mut x.clone(), &mut x, vec![], Skill::Purify); + // assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay)); + // } + + // #[test] + // fn bash_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"pretaliation".to_string()) + // .learn(Skill::Stun); + + // let stun_cd = y.skills.iter().find(|cs| cs.skill == Skill::Stun).unwrap().cd.unwrap(); + + // bash(&mut x, &mut y, vec![], Skill::Bash); + // assert!(!x.effects.iter().any(|e| e.effect == Effect::Stun)); + // assert!(y.skills.iter().any(|cs| cs.skill == Skill::Stun && cs.cd.unwrap() == stun_cd + 1)); + // } + + // #[test] + // fn purge_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"pretaliation".to_string()) + // .learn(Skill::Heal) + // .learn(Skill::HealPlus); + + // purge(&mut x, &mut y, vec![], Skill::Purge); + // // 2 turns at lvl 1 + // assert!(y.effects.iter().any(|e| e.effect == Effect::Purge && e.duration == 2)); + // assert!(y.disabled(Skill::Heal).is_some()); + // } } diff --git a/core/src/spec.rs b/core/src/spec.rs index 736eaba1..cbb8de99 100644 --- a/core/src/spec.rs +++ b/core/src/spec.rs @@ -4,11 +4,11 @@ use util::{IntPct}; #[derive(Debug,Clone,Serialize,Deserialize)] pub struct SpecBonus { pub req: Colours, - pub bonus: u64, + pub bonus: usize, } impl SpecBonus { - pub fn get_bonus(&self, c: &Colours) -> u64 { + pub fn get_bonus(&self, c: &Colours) -> usize { if c.red >= self.req.red && c.blue >= self.req.blue && c.green >= self.req.green { return self.bonus; } @@ -18,16 +18,16 @@ impl SpecBonus { #[derive(Debug,Clone,Serialize,Deserialize)] pub struct SpecValues { - pub base: u64, + pub base: usize, pub bonuses: Vec, } impl SpecValues { - pub fn max_value (&self, c: &Colours) -> u64 { + pub fn max_value (&self, c: &Colours) -> usize { self.bonuses.iter().fold(self.base, |acc, s| acc + s.get_bonus(c)) } - pub fn base (self) -> u64 { + pub fn base (self) -> usize { self.base } } @@ -666,7 +666,7 @@ impl Spec { } } - pub fn apply(&self, modified: u64, base: u64, player_colours: &Colours) -> u64 { + pub fn apply(&self, modified: usize, base: usize, player_colours: &Colours) -> usize { match *self { // Percentage multipliers based on base value Spec::Power | diff --git a/core/src/util.rs b/core/src/util.rs index bf4f3032..f5ab8770 100644 --- a/core/src/util.rs +++ b/core/src/util.rs @@ -17,11 +17,11 @@ // } pub trait IntPct { - fn pct(self, pct: u64) -> u64; + fn pct(self, pct: usize) -> usize; } -impl IntPct for u64 { - fn pct(self, pct: u64) -> u64 { +impl IntPct for usize { + fn pct(self, pct: usize) -> usize { self * pct / 100 } } From b4e5421fd77d9d0797ec1f9e921a3134ca14bba0 Mon Sep 17 00:00:00 2001 From: ntr Date: Wed, 4 Dec 2019 00:18:53 +1000 Subject: [PATCH 009/206] fix autobuy --- server/src/player.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/server/src/player.rs b/server/src/player.rs index b0988a3a..612e1ce8 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -184,6 +184,7 @@ impl Player { // do we have any colours in store? let colours = self.vbox.store[&ItemType::Colours].keys() .cloned() + .take(2) .collect::>(); // how about a base skill? @@ -193,11 +194,13 @@ impl Player { }; // if no: try to refill and start again - match colours.len() < 2 || base.is_none() { - true => match self.vbox_refill() { - Ok(_) => continue, - Err(_) => break, // give up - }, + match colours.len() != 2 || base.is_none() { + true => { + match self.vbox_refill() { + Ok(_) => continue, + Err(_) => break, // give up + }; + } false => { let mut vbox_items = HashMap::new(); vbox_items.insert(ItemType::Colours, colours); @@ -230,7 +233,7 @@ impl Player { // there's a bad bug here where if this apply fails // the item in question will be silently dropped let item = self.vbox.stash.remove(&i).unwrap(); - self.vbox_apply(item, construct_id).ok(); + self.vbox_apply(item, construct_id).unwrap(); continue; } // need to buy one @@ -238,6 +241,7 @@ impl Player { // do we have any colours in store? let colours = self.vbox.store[&ItemType::Colours].keys() .cloned() + .take(2) .collect::>(); // how about a base spec? @@ -247,7 +251,7 @@ impl Player { }; // if no: try to refill and start again - match colours.len() < 2 || base.is_none() { + match colours.len() != 2 || base.is_none() { true => match self.vbox_refill() { Ok(_) => continue, Err(_) => break, // give up @@ -446,9 +450,11 @@ mod tests { let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); player.vbox.fill(); + player.vbox.bits = 100; player.autobuy(); - assert!(player.constructs.iter().all(|c| c.skills.len() >= 1)); + assert!(player.constructs.iter().all(|c| c.skills.len() > 1)); + assert!(player.constructs.iter().all(|c| c.specs.len() >= 1)); } #[test] From 84ac6db353eda3cab2f973b467dccf31acd55286 Mon Sep 17 00:00:00 2001 From: Mashy Date: Wed, 4 Dec 2019 11:56:50 +1000 Subject: [PATCH 010/206] power spec rebalance --- CHANGELOG.md | 11 ++ server/src/construct.rs | 2 +- server/src/spec.rs | 248 ++++++++++++++++++++-------------------- 3 files changed, 136 insertions(+), 125 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd1d26bb..f290763c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## [1.11.0] - 2019-12-XX +### Changed +- Reduced the number of items to create + and ++ versions from 3 to 2 (need 4 items total to make a ++) +- Reduced the power of ++ skills to be closer to the power of previous + versions + +## [1.10.0] - 2019-11-29 +### Changed +- Reworked the vbox layout +- Floating combat text and faster animations +- Mobile UI fixes + ## [1.9.1] - 2019-11-21 ### Fixed - Fixed Item+ Purchasing Bug diff --git a/server/src/construct.rs b/server/src/construct.rs index 9f957d00..15e405d7 100644 --- a/server/src/construct.rs +++ b/server/src/construct.rs @@ -223,7 +223,7 @@ impl Construct { red_life: ConstructStat { base: 125, value: 125, max: 125, stat: Stat::RedLife }, blue_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::BluePower }, blue_life: ConstructStat { base: 125, value: 125, max: 125, stat: Stat::BlueLife }, - green_power: ConstructStat { base: 300, value: 300, max: 300, stat: Stat::GreenPower }, + green_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::GreenPower }, green_life: ConstructStat { base: 800, value: 800, max: 800, stat: Stat::GreenLife }, speed: ConstructStat { base: 100, value: 100, max: 100, stat: Stat::Speed }, // evasion: ConstructStat { base: 0, value: 0, max: 0, stat: Stat::Evasion }, diff --git a/server/src/spec.rs b/server/src/spec.rs index 736eaba1..c09193a1 100644 --- a/server/src/spec.rs +++ b/server/src/spec.rs @@ -172,34 +172,141 @@ impl Spec { }, Spec::PowerRR=> SpecValues { - base: 25, + base: 10, bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 10 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 15 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 20 } + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 5 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 10 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 15 } ], }, Spec::PowerGG=> SpecValues { - base: 25, + base: 10, bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 10 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 15 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 20 } + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 5 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 10 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 15 } ], }, Spec::PowerBB=> SpecValues { - base: 25, + base: 10, bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 10 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 15 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 20 } + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 5 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 10 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 15 } ], }, Spec::PowerRG=> SpecValues { - base: 20, + base: 10, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 3 }, + SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 6 }, + SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 9 } + ], + }, + + Spec::PowerGB=> SpecValues { + base: 10, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 3 }, + SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 6 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 9 } + ], + }, + + Spec::PowerRB=> SpecValues { + base: 10, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 3 }, + SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 6 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 9 } + ], + }, + + Spec::PowerRRPlus => SpecValues { + base: 15, + bonuses: vec![ + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 6 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 12 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 18 } + ], + }, + + Spec::PowerGGPlus => SpecValues { + base: 15, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 6 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 12 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 18 } + ], + }, + + Spec::PowerBBPlus => SpecValues { + base: 15, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 6 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 12 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 18 } + ], + }, + + Spec::PowerRGPlus => SpecValues { + base: 15, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 4 }, + SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 8 }, + SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 12 } + ], + }, + + Spec::PowerGBPlus => SpecValues { + base: 15, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 4 }, + SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 8 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 12 } + ], + }, + + Spec::PowerRBPlus => SpecValues { + base: 15, + bonuses: vec![ + SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 4 }, + SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 8 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 12 } + ], + }, + Spec::PowerRRPlusPlus => SpecValues { + base: 25, + bonuses: vec![ + SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 8 }, + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 16 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 24 } + ], + }, + + Spec::PowerGGPlusPlus => SpecValues { + base: 25, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 8 }, + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 16 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 24 } + ], + }, + + Spec::PowerBBPlusPlus => SpecValues { + base: 25, + bonuses: vec![ + SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 8 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 16 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 24 } + ], + }, + + Spec::PowerRGPlusPlus => SpecValues { + base: 25, bonuses: vec![ SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 5 }, SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 10 }, @@ -207,8 +314,8 @@ impl Spec { ], }, - Spec::PowerGB=> SpecValues { - base: 20, + Spec::PowerGBPlusPlus => SpecValues { + base: 25, bonuses: vec![ SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 5 }, SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 10 }, @@ -216,8 +323,8 @@ impl Spec { ], }, - Spec::PowerRB=> SpecValues { - base: 20, + Spec::PowerRBPlusPlus => SpecValues { + base: 25, bonuses: vec![ SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 5 }, SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 10 }, @@ -225,113 +332,6 @@ impl Spec { ], }, - Spec::PowerRRPlus => SpecValues { - base: 45, - bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 15 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 25 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 35 } - ], - }, - - Spec::PowerGGPlus => SpecValues { - base: 45, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 15 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 25 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 35 } - ], - }, - - Spec::PowerBBPlus => SpecValues { - base: 45, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 15 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 25 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 35 } - ], - }, - - Spec::PowerRGPlus => SpecValues { - base: 35, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 10 }, - SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 20 }, - SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 25 } - ], - }, - - Spec::PowerGBPlus => SpecValues { - base: 35, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 10 }, - SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 20 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 25 } - ], - }, - - Spec::PowerRBPlus => SpecValues { - base: 35, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 10 }, - SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 20 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 25 } - ], - }, - Spec::PowerRRPlusPlus => SpecValues { - base: 80, - bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 25 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 45 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 60 } - ], - }, - - Spec::PowerGGPlusPlus => SpecValues { - base: 80, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 25 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 45 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 60 } - ], - }, - - Spec::PowerBBPlusPlus => SpecValues { - base: 80, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 25 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 45 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 60 } - ], - }, - - Spec::PowerRGPlusPlus => SpecValues { - base: 60, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 20 }, - SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 30 }, - SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 45 } - ], - }, - - Spec::PowerGBPlusPlus => SpecValues { - base: 60, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 20 }, - SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 30 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 45 } - ], - }, - - Spec::PowerRBPlusPlus => SpecValues { - base: 60, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 20 }, - SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 30 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 45 } - ], - }, - Spec::Speed => SpecValues { base: 40, bonuses: vec![] From e1f400726b5713368d0bb897b2e77326a1bfcd1f Mon Sep 17 00:00:00 2001 From: Mashy Date: Wed, 4 Dec 2019 12:01:38 +1000 Subject: [PATCH 011/206] another tweak --- server/src/spec.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/spec.rs b/server/src/spec.rs index c09193a1..9e084842 100644 --- a/server/src/spec.rs +++ b/server/src/spec.rs @@ -175,8 +175,8 @@ impl Spec { base: 10, bonuses: vec![ SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 5 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 10 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 15 } + SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 9 }, + SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 13 } ], }, @@ -184,8 +184,8 @@ impl Spec { base: 10, bonuses: vec![ SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 5 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 10 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 15 } + SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 9 }, + SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 13 } ], }, @@ -193,8 +193,8 @@ impl Spec { base: 10, bonuses: vec![ SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 5 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 10 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 15 } + SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 9 }, + SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 13 } ], }, From 66ec4ff5afa808b1132d5305e4dc5190b9fe5932 Mon Sep 17 00:00:00 2001 From: Mashy Date: Wed, 4 Dec 2019 12:17:17 +1000 Subject: [PATCH 012/206] test fix --- server/src/construct.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/src/construct.rs b/server/src/construct.rs index 15e405d7..454b9aa0 100644 --- a/server/src/construct.rs +++ b/server/src/construct.rs @@ -1031,9 +1031,9 @@ mod tests { construct.apply_modifiers(&player_colours); - assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(35)); - assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(50)); - assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(70)); + assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(15)); + assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(24)); + assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(37)); return; } @@ -1103,9 +1103,9 @@ mod tests { construct.apply_modifiers(&player_colours); - assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(35)); - assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(25)); - assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(25)); + assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(15)); + assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(10)); + assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(10)); return; } From 38eda7afac429283fc737f717600b8e7d3bc17fa Mon Sep 17 00:00:00 2001 From: Mashy Date: Wed, 4 Dec 2019 12:59:45 +1000 Subject: [PATCH 013/206] skill value rebalance --- WORKLOG.md | 5 +- server/src/skill.rs | 116 ++++++++++++++++++++++---------------------- 2 files changed, 62 insertions(+), 59 deletions(-) diff --git a/WORKLOG.md b/WORKLOG.md index 3eab2abc..3ab85ef3 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -29,8 +29,11 @@ Hexagon Set - Increase intensity for each visit _mashy_ +* rebalance + * speed specs + * life specs + * represent construct colours during game phase (try %bar or dots) -* reduce combos from 3->2 and rebalance _external_ * Graphics diff --git a/server/src/skill.rs b/server/src/skill.rs index d94cdc96..ea1f3131 100644 --- a/server/src/skill.rs +++ b/server/src/skill.rs @@ -764,87 +764,87 @@ impl Skill { Skill::Attack => 80, // Base Skill::Blast => 105, // BB - Skill::BlastPlus => 140, // BB - Skill::BlastPlusPlus => 200, // BB + Skill::BlastPlus => 125, // BB + Skill::BlastPlusPlus => 145, // BB Skill::Chaos => 40, // BR - Skill::ChaosPlus => 65, // BR - Skill::ChaosPlusPlus => 90, // BR + Skill::ChaosPlus => 50, // BR + Skill::ChaosPlusPlus => 65, // BR - Skill::Heal => 125, //GG - Skill::HealPlus => 185, //GG - Skill::HealPlusPlus => 270, //GG + Skill::Heal => 115, //GG + Skill::HealPlus => 135, //GG + Skill::HealPlusPlus => 160, //GG Skill::SiphonTick => 25, // GB - Skill::SiphonTickPlus => 30, - Skill::SiphonTickPlusPlus => 40, + Skill::SiphonTickPlus => 27, + Skill::SiphonTickPlusPlus => 30, - Skill::Slay => 45, // RG - Skill::SlayPlus => 65, - Skill::SlayPlusPlus => 100, + Skill::Slay => 40, // RG + Skill::SlayPlus => 50, + Skill::SlayPlusPlus => 65, Skill::Strike => 90, //RR - Skill::StrikePlus => 140, - Skill::StrikePlusPlus => 200, + Skill::StrikePlus => 110, + Skill::StrikePlusPlus => 140, // Block Base Skill::ElectrocuteTick => 80, - Skill::ElectrocuteTickPlus => 100, - Skill::ElectrocuteTickPlusPlus => 130, + Skill::ElectrocuteTickPlus => 90, + Skill::ElectrocuteTickPlusPlus => 100, - Skill::CounterAttack => 120, - Skill::CounterAttackPlus => 160, - Skill::CounterAttackPlusPlus => 230, + Skill::CounterAttack => 115, + Skill::CounterAttackPlus => 130, + Skill::CounterAttackPlusPlus => 160, Skill::Purify => 45, //Green dmg (heal) - Skill::PurifyPlus => 70, - Skill::PurifyPlusPlus => 105, + Skill::PurifyPlus => 60, + Skill::PurifyPlusPlus => 85, Skill::Reflect => 45, //Recharge blue life (heal) Skill::ReflectPlus => 70, Skill::ReflectPlusPlus => 100, Skill::Recharge => 70, //Recharge red and blue life (heal) - Skill::RechargePlus => 110, - Skill::RechargePlusPlus => 170, + Skill::RechargePlus => 90, + Skill::RechargePlusPlus => 110, - Skill::Sustain => 120, // Recharge red life (heal) - Skill::SustainPlus => 150, - Skill::SustainPlusPlus => 230, + Skill::Sustain => 110, // Recharge red life (heal) + Skill::SustainPlus => 130, + Skill::SustainPlusPlus => 150, // Stun Base - Skill::Sleep => 200, //Green dmg (heal) - Skill::SleepPlus => 290, - Skill::SleepPlusPlus => 400, + Skill::Sleep => 160, //Green dmg (heal) + Skill::SleepPlus => 200, + Skill::SleepPlusPlus => 240, - Skill::Banish => 40, //Green dmg (heal) - Skill::BanishPlus => 75, - Skill::BanishPlusPlus => 125, + Skill::Banish => 50, //Green dmg (heal) + Skill::BanishPlus => 65, + Skill::BanishPlusPlus => 80, Skill::Bash => 45, - Skill::BashPlus => 65, - Skill::BashPlusPlus => 100, + Skill::BashPlus => 55, + Skill::BashPlusPlus => 70, Skill::Link => 25, - Skill::LinkPlus => 40, - Skill::LinkPlusPlus => 70, + Skill::LinkPlus => 35, + Skill::LinkPlusPlus => 45, Skill::Ruin => 40, - Skill::RuinPlus => 70, - Skill::RuinPlusPlus => 100, + Skill::RuinPlus => 55, + Skill::RuinPlusPlus => 70, // Debuff Base Skill::DecayTick => 33, - Skill::DecayTickPlus => 45, - Skill::DecayTickPlusPlus => 70, + Skill::DecayTickPlus => 37, + Skill::DecayTickPlusPlus => 45, Skill::Silence => 55, // Deals more per blue skill on target - Skill::SilencePlus => 80, - Skill::SilencePlusPlus => 110, + Skill::SilencePlus => 65, + Skill::SilencePlusPlus => 80, Skill::Restrict => 40, // Deals more per red skill on target - Skill::RestrictPlus => 65, - Skill::RestrictPlusPlus => 100, + Skill::RestrictPlus => 55, + Skill::RestrictPlusPlus => 70, // Buff base Skill::HybridBlast => 50, @@ -852,16 +852,16 @@ impl Skill { Skill::HasteStrike => 60, Skill::Absorb=> 95, - Skill::AbsorbPlus => 120, - Skill::AbsorbPlusPlus => 155, + Skill::AbsorbPlus => 110, + Skill::AbsorbPlusPlus => 120, - Skill::Intercept => 80, - Skill::InterceptPlus => 110, - Skill::InterceptPlusPlus => 150, + Skill::Intercept => 85, + Skill::InterceptPlus => 100, + Skill::InterceptPlusPlus => 125, Skill::TriageTick => 75, - Skill::TriageTickPlus => 110, - Skill::TriageTickPlusPlus => 140, + Skill::TriageTickPlus => 90, + Skill::TriageTickPlusPlus => 110, _ => 100, } @@ -905,9 +905,9 @@ impl Skill { Skill::Curse => vec![ConstructEffect {effect: Effect::Curse, duration: 2, meta: Some(EffectMeta::Multiplier(150)), tick: None}], Skill::CursePlus => vec![ConstructEffect {effect: Effect::Curse, duration: 2, - meta: Some(EffectMeta::Multiplier(200)), tick: None}], + meta: Some(EffectMeta::Multiplier(175)), tick: None}], Skill::CursePlusPlus => vec![ConstructEffect {effect: Effect::Curse, duration: 3, - meta: Some(EffectMeta::Multiplier(250)), tick: None}], + meta: Some(EffectMeta::Multiplier(200)), tick: None}], Skill::Debuff => vec![ConstructEffect {effect: Effect::Slow, duration: 3, meta: Some(EffectMeta::Multiplier(50)), tick: None }], @@ -940,15 +940,15 @@ impl Skill { meta: Some(EffectMeta::Skill(Skill::AbsorptionPlusPlus)), tick: None}], Skill::Absorption => vec![ConstructEffect {effect: Effect::Absorption, duration: 3, meta: None, tick: None}], - Skill::AbsorptionPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 5, meta: None, tick: None}], - Skill::AbsorptionPlusPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 7, meta: None, tick: None}], + Skill::AbsorptionPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 4, meta: None, tick: None}], + Skill::AbsorptionPlusPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 5, meta: None, tick: None}], Skill::Hybrid => vec![ConstructEffect {effect: Effect::Hybrid, duration: 3, meta: Some(EffectMeta::Multiplier(150)), tick: None }], Skill::HybridPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 4, meta: Some(EffectMeta::Multiplier(175)), tick: None }], Skill::HybridPlusPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 5, - meta: Some(EffectMeta::Multiplier(225)), tick: None }], + meta: Some(EffectMeta::Multiplier(200)), tick: None }], Skill::Invert => vec![ConstructEffect {effect: Effect::Invert, duration: 2, meta: None, tick: None}], Skill::InvertPlus => vec![ConstructEffect {effect: Effect::Invert, duration: 3, meta: None, tick: None}], @@ -970,10 +970,10 @@ impl Skill { meta: Some(EffectMeta::Multiplier(150)), tick: None}], Skill::BreakPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}, ConstructEffect {effect: Effect::Vulnerable, duration: 4, - meta: Some(EffectMeta::Multiplier(200)), tick: None}], + meta: Some(EffectMeta::Multiplier(175)), tick: None}], Skill::BreakPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}, ConstructEffect {effect: Effect::Vulnerable, duration: 4, - meta: Some(EffectMeta::Multiplier(250)), tick: None}], + meta: Some(EffectMeta::Multiplier(200)), tick: None}], Skill::Ruin => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], Skill::RuinPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], From 0ea1d4b05ec64243644369b5797faac79e1e4e20 Mon Sep 17 00:00:00 2001 From: Mashy Date: Wed, 4 Dec 2019 13:01:54 +1000 Subject: [PATCH 014/206] 1.10.1 --- VERSION | 2 +- acp/package.json | 2 +- client/package.json | 2 +- ops/package.json | 2 +- server/Cargo.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index ed21137e..e33692ab 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.10.0 \ No newline at end of file +1.10.1 \ No newline at end of file diff --git a/acp/package.json b/acp/package.json index 7a6a5902..af19ae15 100644 --- a/acp/package.json +++ b/acp/package.json @@ -1,6 +1,6 @@ { "name": "mnml-client", - "version": "1.10.0", + "version": "1.10.1", "description": "", "main": "index.js", "scripts": { diff --git a/client/package.json b/client/package.json index 4cbd135c..c840b666 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "mnml-client", - "version": "1.10.0", + "version": "1.10.1", "description": "", "main": "index.js", "scripts": { diff --git a/ops/package.json b/ops/package.json index 63b2a82e..d4b90bb7 100644 --- a/ops/package.json +++ b/ops/package.json @@ -1,6 +1,6 @@ { "name": "mnml-ops", - "version": "1.10.0", + "version": "1.10.1", "description": "", "main": "index.js", "scripts": { diff --git a/server/Cargo.toml b/server/Cargo.toml index fb7f73c9..39e11d55 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mnml" -version = "1.10.0" +version = "1.10.1" authors = ["ntr "] [dependencies] From 1b1e3d782d0e18e35828866cbd5d89a3128cc357 Mon Sep 17 00:00:00 2001 From: Mashy Date: Wed, 4 Dec 2019 13:03:48 +1000 Subject: [PATCH 015/206] changelog --- CHANGELOG.md | 3 ++- client/changelog.html | 42 +++++++++++++++++++++++++++++------------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f290763c..8204531d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ -## [1.11.0] - 2019-12-XX +## [1.10.1] - 2019-12-04 ### Changed - Reduced the number of items to create + and ++ versions from 3 to 2 (need 4 items total to make a ++) - Reduced the power of ++ skills to be closer to the power of previous + versions +- Tweaked all values of power specs to reduce their power bonuses ## [1.10.0] - 2019-11-29 ### Changed diff --git a/client/changelog.html b/client/changelog.html index 6c4f1a03..dc2b3291 100644 --- a/client/changelog.html +++ b/client/changelog.html @@ -1,6 +1,4 @@ - - -CHANGELOG

+[1.10.1] - 2019-12-04

+

+Changed

+
    +
  • Reduced the number of items to create + and ++ versions from 3 to 2 (need 4 items total to make a ++)
  • +
  • Reduced the power of ++ skills to be closer to the power of previous + versions
  • +
  • Tweaked all values of power specs to reduce their power bonuses
  • +
+

+[1.10.0] - 2019-11-29

+

+Changed

+
    +
  • Reworked the vbox layout
  • +
  • Floating combat text and faster animations
  • +
  • Mobile UI fixes
  • +
+

[1.9.1] - 2019-11-21

Fixed

@@ -742,7 +758,7 @@ pre {

[1.9.0] - 2019-11-21

-Changed

+Changed