Merge branch 'release/1.11.0'

This commit is contained in:
ntr 2020-01-03 18:38:03 +10:00
commit 9cf81735f1
129 changed files with 13894 additions and 8807 deletions

View File

@ -1,3 +1,15 @@
## [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 ## [1.9.1] - 2019-11-21
### Fixed ### Fixed
- Fixed Item+ Purchasing Bug - Fixed Item+ Purchasing Bug

266
COMBOS.md
View File

@ -1,142 +1,200 @@
# item_info -> # Spec / Skill hybrid specs #
combos [strike, [R R Attack]] Create skills specs by combining an upgraded skills with corresponding colour specs:
specs [spec [bonus amount, [r g b]] 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 ## Why Skill Specs
stats randomised
initial stash drops - Give tools to players to make cool unique builds
6 skills - Passive utility that you work towards
6 colours - Specialise in a type of skill
6 specs - Repurpose skills that you aren't using much for active use
- More layers of complexity
play first round ## Skill specs philosphy
basically duke it out
# 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 ### ## Brainstorming on what kind of things skill specs could do
Real world concepts
Aggressive
Apply Buffs
Fast & Chaotic
### Green ### - Passive
Healing Specialisation - (Abosrb Spec) when you take damage you permanently gain X Power/Speed/Life
Defensive - Grant you Amplify with (X% multiplier) when you KO a target
Purge buffs & debuffs - Convert (X% of your red / blue power -> green power)
- Reduce / Increase damage taken of a certain type (Block type upgrades maybe?)
### Blue ### - Active
Fantasy concepts (magical) - Make a skill cast another skill (strike another guy)
Aggressive & Defensive - Apply an effect when you cast a skill
Apply Debuffs - Change damage / healing / speed of a skill
Slow & Reliable
# Classes # ## The BIG LIST
Class names to be changed ### Attack Base
==================== Strike
Pure Red `Nature` Power
Pure Green `Non-Violence` Life
Pure Blue `Destruction` Speed (Repeat skill at X% multiplier)
Hybrid Red / Blue `Chaos`
Hybrid Red / Green `Purity`
Hybrid Blue / Green `Technology`
Chaos
Power
Life
Speed
Skills Heal
========== Power (Convert X% Red/Blue Power to GreenPower)
Life
Speed
Basic Type Blast
------------------------------------------------------------------------- Power
Attack `Basic offensive skill - deal damage` Life
Buff `Base ally targetted skill - increase ally speed` 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 # Slay
Power
Life
Speed
RR - Strike Siphon
GG - Heal Power
BB - Blast Life
RG - Purify Speed
GB - Decay
RB - Blast
# Stun Base # ### Stun Base
Bash
Power
Life
Speed
RR - Strangle Sleep
GG - Break Power
BB - Ruin Life
RG - Banish Speed
GB - Silence
RB - Hex
# Buff Base # Ruin
Power
Life
Speed
RR - Empower Link
GR - Triage Power
BB - Absorb Life
RG - Sustain Speed
GB - Amplify
RB - Haste
# Debuff Base # Banish
Power
Life
Speed
RR - Restrict Break
GG - Purge Power
BB - Curse Life
RG - Slow Speed
GB - Siphon
RB - Invert
# Block Base # ### Block Base
RR - Counter Counter
GG - Reflect Power
BB - Electrify Life
RG - Intercept Speed
GB - Life `rename?`
RB - Recharge
Reflect
Power
Life
Speed
## Advanced combos ## Purify
Power
Life
Speed
Two ways of upgrading Sustain
#1 -> combine more of the same for a stronger version of the same skill / spec (T2 / T3 Combos) Power
#2 -> combine skill with two matching colour specs to change the way the skill works (Spec / Skill hybrid) 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 # ### Buff Base
`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 Intercept
Upgraded skills use the same speed formula as previously Power
Life
Speed
### Spec / Skill hybrid specs ### Triage
Power
Life
Speed
# Strike # Absorb
2 x Red Damage + Strike => Strike damage bonus (crit?) Power (Gain X Power when you take damage)
2 x Red Speed + Strike => Strike reduces enemy speed Life (Gain X Life when you take damage)
2 x Red Life + Strike => Strike reduces enemy healing (% reduction) Speed (Gain X Speed when you take damage)
# Heal # Amplify
2 x Green Damage + Heal => Heal target for additional 20% of caster's maximum life Power
2 x Green Speed + Heal => Heal target gets bonus speed Life
2 x Green Life + Heal => Heal increases target's max hp for 2 turns 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

View File

@ -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

View File

@ -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 #

View File

@ -34,8 +34,12 @@ Player Events e.g. chatwheel
Matchmaking + ELO / Leaderboard Matchmaking + ELO / Leaderboard
Game skill private fields Game skill private fields
# Phase 4 (Release -> Full Shill mode)
Refine artwork, icons, scaling etc Refine artwork, icons, scaling etc
Music Music
Skill Specs
Some sort viewable combat log
Marketing materials Marketing materials
Videos Videos

224
SPECS.md
View File

@ -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

View File

@ -1 +1 @@
1.10.0 1.11.0

View File

@ -3,7 +3,6 @@
_ntr_ _ntr_
* can't reset password without knowing password =\ * can't reset password without knowing password =\
* skip faceoff on server side
* change cooldowns to delay & recharge * change cooldowns to delay & recharge
- delay is cooldown before skill can first be used - delay is cooldown before skill can first be used
- recharge is cooldown after using skill - recharge is cooldown after using skill
@ -30,14 +29,11 @@ Hexagon Set
- Increase intensity for each visit - Increase intensity for each visit
_mashy_ _mashy_
* floating combat text combat (start opposite hp and float towards it) to speed up animations * rebalance
* speed specs
* life specs
* represent construct colours during game phase (try %bar or dots) * 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)
_external_ _external_
* Graphics * Graphics
@ -57,29 +53,19 @@ _tba_
## SOON ## SOON
* combo rework * Skill / Spec hybrids - SEE COMBOS.md
- 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
* elo + leaderboards * elo + leaderboards
## LATER ## LATER
* Graphical status effects instead of text * 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 * theme toasts
* rework vecs into sets * rework vecs into sets

View File

@ -1,6 +1,6 @@
{ {
"name": "mnml-client", "name": "mnml-client",
"version": "1.10.0", "version": "1.11.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -10,6 +10,7 @@ echo "Setting version to $VERSION"
echo $VERSION | tr -d '\n' > VERSION echo $VERSION | tr -d '\n' > VERSION
cd $MNML_PATH/server && sed -i "/^version/c\version = \"$VERSION\"" Cargo.toml cd $MNML_PATH/server && sed -i "/^version/c\version = \"$VERSION\"" Cargo.toml
cd $MNML_PATH/core && sed -i "/^version/c\version = \"$VERSION\"" Cargo.toml
cd $MNML_PATH/ops && npm --allow-same-version --no-git-tag-version version "$VERSION" cd $MNML_PATH/ops && npm --allow-same-version --no-git-tag-version version "$VERSION"
cd $MNML_PATH/client && npm --allow-same-version --no-git-tag-version version "$VERSION" cd $MNML_PATH/client && npm --allow-same-version --no-git-tag-version version "$VERSION"
cd $MNML_PATH/acp && npm --allow-same-version --no-git-tag-version version "$VERSION" cd $MNML_PATH/acp && npm --allow-same-version --no-git-tag-version version "$VERSION"

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 106 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -3,16 +3,25 @@
div { div {
padding-right: 1em; padding-right: 1em;
// display: flex;
// flex-flow: column;
line-height: 2em;
}
h3 {
// text-transform: uppercase;
margin-bottom: 0.5em;
} }
button { button {
width: 100%; width: 100%;
height: 2.5em;
display: block; display: block;
} }
input { input {
width: 100%; width: 100%;
height: 3em; height: 2.5em;
display: block; display: block;
} }

View File

@ -5,7 +5,7 @@
@white: #f5f5f5; // whitesmoke @white: #f5f5f5; // whitesmoke
@purple: #9355b5; // 6lack - that far cover @purple: #9355b5; // 6lack - that far cover
@yellow: #ffa100; @yellow: #ffa100;
@silver: #c0c0c0; @silver: #2c2c2c;
@black: black; @black: black;
@gray: #222; @gray: #222;

View File

@ -54,11 +54,7 @@
button { button {
&.highlight { &.highlight {
color: black;
background: @silver; background: @silver;
// border: 1px solid @white; (this bangs around the vbox)
// overwrite the classes on white svg elements
svg { svg {
stroke-width: 0.75em; stroke-width: 0.75em;
} }

View File

@ -75,25 +75,11 @@
flex: 1; flex: 1;
border-top: 0; border-top: 0;
border: 0.1em solid #222; border: 0.1em solid #222;
&:not(:last-child) {
border-right: 0;
}
&:last-child { &:last-child {
float: right; float: right;
} }
} }
} }
.login {
display: flex;
flex-flow: column;
.terms {
display: inline;
margin: 0 1em;
}
}
} }
section { section {
@ -108,62 +94,14 @@ section {
padding-right: 1em; padding-right: 1em;
} }
.list {
letter-spacing: 0.25em;
text-transform: uppercase;
display: grid;
// grid-template-columns: repeat(4, 1fr);
grid-template-columns: 1fr 1fr;
grid-gap: 1em;
flex-flow: row wrap;
align-items: flex-end;
button {
border-radius: 0.25em;
// height: 3em;
}
&.sub {
grid-template-columns: 1fr;
}
&.play {
grid-template-columns: repeat(2, 1fr);
align-items: flex-start;
&.rejoin {
grid-template-columns: 1fr;
}
button.ready:enabled {
color: forestgreen;
border-color: forestgreen;
&:hover {
background: forestgreen;
color: black;
border-color: forestgreen;
}
}
// // all green
// button.ready:enabled {
// background: forestgreen;
// color: black;
// border-color: forestgreen;
// &:hover {
// color: forestgreen;
// border-color: forestgreen;
// background: 0;
// }
// }
}
}
.panes { .panes {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
} }
}
.list {
margin-bottom: 2em;
figure { figure {
letter-spacing: 0.25em; letter-spacing: 0.25em;
@ -172,61 +110,92 @@ section {
display: flex; display: flex;
flex-flow: column; flex-flow: column;
} }
letter-spacing: 0.25em;
text-transform: uppercase;
display: grid;
// grid-template-columns: repeat(4, 1fr);
grid-template-columns: 1fr 1fr;
grid-gap: 1em;
flex-flow: row wrap;
align-items: flex-end;
&.sub {
grid-template-columns: 1fr;
}
&.play {
grid-template-columns: repeat(2, 1fr);
align-items: flex-start;
&.rejoin {
grid-template-columns: 1fr;
}
button.ready:enabled {
color: forestgreen;
border-color: forestgreen;
&:hover {
background: forestgreen;
color: black;
border-color: forestgreen;
}
}
// // all green
// button.ready:enabled {
// background: forestgreen;
// color: black;
// border-color: forestgreen;
// &:hover {
// color: forestgreen;
// border-color: forestgreen;
// background: 0;
// }
// }
}
} }
.demo {
margin-top: 1em;
display: block; .login {
display: flex;
flex-flow: column;
.terms {
display: inline;
margin: 0 1em;
}
button { button {
pointer-events: none; padding: 0 0.5em;
margin-top: 1em;
}
}
.options {
grid-area: hdr;
display: flex;
.logo {
flex: 0 1 10%;
margin-right: 1em;
border: none;
} }
section { button {
margin-bottom: 0.5em; flex: 1;
border-top: 0;
div:first-child { border: 0.1em solid #222;
padding-right: 1em; &:last-child {
} float: right;
}
.construct-section {
.construct-list {
height: 25em;
grid-area: unset;
.instance-construct {
// border: 0;
}
}
}
.colour-info {
grid-area: vinfo;
display: flex;
align-items: center;
div {
display: flex;
}
svg {
flex: 1;
height: 1em;
}
}
.game-demo {
.game {
height: 25em;
display: flex;
flex-flow: column;
.game-construct {
flex: 1;
}
} }
} }
} }
.intro {
text-align: center;
font-size: 0.8em;
}

View File

@ -27,23 +27,6 @@ html body {
overflow-y: hidden; overflow-y: hidden;
} }
#mnml {
/* this is the sweet nectar to keep it full page*/
height: 100vh;
max-height: 100vh;
min-height: 100vh;
/* stops inspector going skitz*/
overflow-x: hidden;
// overflow-y: hidden;
}
// @media (min-width: 1921px) {
// html, body, #mnml {
// font-size: 16pt;
// }
// }
html { html {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
@ -108,11 +91,37 @@ dl {
padding: 0.5em 1em; padding: 0.5em 1em;
/* this is the sweet nectar to keep it full page*/
height: 100vh;
max-height: 100vh;
min-height: 100vh;
/* stops inspector going skitz*/
overflow-x: hidden;
// overflow-y: hidden;
&.animations-test { &.animations-test {
aside button { aside button {
font-size: 50%; font-size: 50%;
} }
} }
&.front-page {
display: block;
main {
padding: 0 25%;
}
.logo {
margin: 2em 0;
}
.list {
margin-bottom: 0;
}
}
} }
main { main {
@ -129,7 +138,7 @@ button, input {
box-sizing: border-box; box-sizing: border-box;
font-size: 1em; font-size: 1em;
flex: 1; flex: 1;
border-radius: 0.5em; border-radius: 0;
line-height: 2em; line-height: 2em;
padding-right: 0.1em; padding-right: 0.1em;
padding-left: 0.1em; padding-left: 0.1em;
@ -150,9 +159,12 @@ button, input {
&:focus { &:focus {
/*colour necesary to bash skellington*/ /*colour necesary to bash skellington*/
outline: 0; outline: 0;
} }
// &:active {
// filter: url("#noiseFilter");
// }
} }
a { a {
@ -261,28 +273,12 @@ figure.gray {
display: none; display: none;
} }
header {
.options {
font-size: 200%;
}
button {
height: 2em;
// border-radius: 0.1em;
border: none;
border-radius: 0;
}
}
.options { .options {
button { button {
&.highlight { &.highlight {
color: @white; color: @white;
box-shadow: inset 0px 5px 0px 0px @white; box-shadow: inset 0px 5px 0px 0px @white;
border: 0;
} }
border: none;
} }
} }
@ -300,11 +296,20 @@ li {
} }
.logo { .logo {
height: 2em; height: 4em;
background-image: url("../../assets/mnml.logo.trim.svg"); filter: url("#noiseFilter");
background-image: url("../../assets/mnml.logo.text.svg");
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: left; background-position: center;
}
.awards {
height: 100%;
background-image: url("../../assets/mnml.awards.svg");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
} }
.discord-btn { .discord-btn {
@ -316,8 +321,13 @@ li {
.mnni { .mnni {
background-image: url("./../mnni.svg"); background-image: url("./../mnni.svg");
filter: url("#noiseFilter");
} }
// .highlight {
// filter: url("#noiseFilter");
// }
.avatar { .avatar {
grid-area: avatar; grid-area: avatar;
object-fit: contain; object-fit: contain;
@ -328,6 +338,10 @@ li {
// pointer-events: none; // pointer-events: none;
} }
header {
// font-size: 1.2em;
}
#clipboard { #clipboard {
width: 1px; width: 1px;
height: 1px; height: 1px;
@ -359,4 +373,8 @@ li {
} }
} }
#noise {
height: 0;
}
@import 'styles.mobile.less'; @import 'styles.mobile.less';

View File

@ -7,6 +7,12 @@
font-size: 8pt; font-size: 8pt;
padding: 0; padding: 0;
&.front-page {
main {
padding: 0 0.5em;
}
}
.instance { .instance {
grid-template-areas: grid-template-areas:
"vbox vbox" "vbox vbox"
@ -164,12 +170,21 @@
// portrait menu or small size vertical in landscape // portrait menu or small size vertical in landscape
@media (max-width: 550px) and (max-height: 800px) { @media (max-width: 550px) and (max-height: 800px) and (orientation: portrait) {
#mnml { #mnml {
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: 1fr; grid-template-rows: 1fr;
grid-template-areas: grid-template-areas:
"main" "main";
&.front-page {
display: block;
main {
padding: 0 0.5em;
}
}
} }
section { section {
@ -264,6 +279,9 @@
} }
.info-combiner { .info-combiner {
max-height: 7em;
overflow-y: scroll;
.info { .info {
display: none; display: none;
} }

View File

@ -147,16 +147,11 @@
} }
&.highlight { &.highlight {
color: black;
background: @silver; background: @silver;
// overwrite the classes on white svg elements // overwrite the classes on white svg elements
svg { svg {
stroke-width: 0.75em; stroke-width: 0.75em;
} }
.white {
stroke: black;
}
} }
} }

View File

@ -1,6 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><style>@font-face {
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><style>@font-face {
font-family: octicons-anchor; font-family: octicons-anchor;
src: url(https://cdnjs.cloudflare.com/ajax/libs/octicons/4.4.0/font/octicons.woff) format('woff'); src: url(https://cdnjs.cloudflare.com/ajax/libs/octicons/4.4.0/font/octicons.woff) format('woff');
} }
@ -733,6 +731,24 @@ pre {
} }
} }
</style><title>CHANGELOG</title></head><body><article class="markdown-body"><h2> </style><title>CHANGELOG</title></head><body><article class="markdown-body"><h2>
<a id="user-content-1101---2019-12-04" class="anchor" href="#1101---2019-12-04" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.10.1] - 2019-12-04</h2>
<h3>
<a id="user-content-changed" class="anchor" href="#changed" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>Reduced the number of items to create + and ++ versions from 3 to 2 (need 4 items total to make a ++)</li>
<li>Reduced the power of ++ skills to be closer to the power of previous + versions</li>
<li>Tweaked all values of power specs to reduce their power bonuses</li>
</ul>
<h2>
<a id="user-content-1100---2019-11-29" class="anchor" href="#1100---2019-11-29" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.10.0] - 2019-11-29</h2>
<h3>
<a id="user-content-changed-1" class="anchor" href="#changed-1" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>Reworked the vbox layout</li>
<li>Floating combat text and faster animations</li>
<li>Mobile UI fixes</li>
</ul>
<h2>
<a id="user-content-191---2019-11-21" class="anchor" href="#191---2019-11-21" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.9.1] - 2019-11-21</h2> <a id="user-content-191---2019-11-21" class="anchor" href="#191---2019-11-21" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.9.1] - 2019-11-21</h2>
<h3> <h3>
<a id="user-content-fixed" class="anchor" href="#fixed" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3> <a id="user-content-fixed" class="anchor" href="#fixed" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3>
@ -742,7 +758,7 @@ pre {
<h2> <h2>
<a id="user-content-190---2019-11-21" class="anchor" href="#190---2019-11-21" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.9.0] - 2019-11-21</h2> <a id="user-content-190---2019-11-21" class="anchor" href="#190---2019-11-21" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.9.0] - 2019-11-21</h2>
<h3> <h3>
<a id="user-content-changed" class="anchor" href="#changed" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3> <a id="user-content-changed-2" class="anchor" href="#changed-2" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul> <ul>
<li>VBOX <li>VBOX
<ul> <ul>
@ -794,7 +810,7 @@ pre {
<li>Resizing of vbox when you buy / create certain items</li> <li>Resizing of vbox when you buy / create certain items</li>
</ul> </ul>
<h3> <h3>
<a id="user-content-changed-1" class="anchor" href="#changed-1" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3> <a id="user-content-changed-3" class="anchor" href="#changed-3" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul> <ul>
<li>Automatically shows a preview of combo item when you have 3 items selected for combining</li> <li>Automatically shows a preview of combo item when you have 3 items selected for combining</li>
<li>Only highlight the first available item slot when equipping</li> <li>Only highlight the first available item slot when equipping</li>
@ -826,7 +842,7 @@ pre {
<li>An issue where skills would not be put on cooldown after being used.</li> <li>An issue where skills would not be put on cooldown after being used.</li>
</ul> </ul>
<h3> <h3>
<a id="user-content-changed-2" class="anchor" href="#changed-2" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3> <a id="user-content-changed-4" class="anchor" href="#changed-4" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul> <ul>
<li> <li>
<p>Game phase</p> <p>Game phase</p>
@ -893,7 +909,7 @@ pre {
</li> </li>
</ul> </ul>
<h3> <h3>
<a id="user-content-changed-3" class="anchor" href="#changed-3" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3> <a id="user-content-changed-5" class="anchor" href="#changed-5" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul> <ul>
<li> <li>
<p>Construct life changed</p> <p>Construct life changed</p>
@ -979,7 +995,7 @@ pre {
</li> </li>
</ul> </ul>
<h3> <h3>
<a id="user-content-changed-4" class="anchor" href="#changed-4" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3> <a id="user-content-changed-6" class="anchor" href="#changed-6" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul> <ul>
<li> <li>
<p>Vbox phase</p> <p>Vbox phase</p>
@ -1125,7 +1141,7 @@ pre {
<li>Player width styling</li> <li>Player width styling</li>
</ul> </ul>
<h3> <h3>
<a id="user-content-changed-5" class="anchor" href="#changed-5" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3> <a id="user-content-changed-7" class="anchor" href="#changed-7" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul> <ul>
<li>Improved wiggle animation</li> <li>Improved wiggle animation</li>
<li>Intercept is now considered defensive by bots</li> <li>Intercept is now considered defensive by bots</li>
@ -1134,7 +1150,7 @@ pre {
<h2> <h2>
<a id="user-content-164---2019-10-24" class="anchor" href="#164---2019-10-24" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.6.4] - 2019-10-24</h2> <a id="user-content-164---2019-10-24" class="anchor" href="#164---2019-10-24" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.6.4] - 2019-10-24</h2>
<h3> <h3>
<a id="user-content-changed-6" class="anchor" href="#changed-6" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3> <a id="user-content-changed-8" class="anchor" href="#changed-8" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul> <ul>
<li>Animations processing on client side reduced.</li> <li>Animations processing on client side reduced.</li>
</ul> </ul>
@ -1176,7 +1192,7 @@ pre {
<li>Subscriber chat!</li> <li>Subscriber chat!</li>
</ul> </ul>
<h3> <h3>
<a id="user-content-changed-7" class="anchor" href="#changed-7" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3> <a id="user-content-changed-9" class="anchor" href="#changed-9" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul> <ul>
<li> <li>
<p>Made available skill / effect information during the combat phase.</p> <p>Made available skill / effect information during the combat phase.</p>
@ -1208,7 +1224,7 @@ pre {
<a id="user-content-156---2019-10-17" class="anchor" href="#156---2019-10-17" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.5.6] - 2019-10-17</h2> <a id="user-content-156---2019-10-17" class="anchor" href="#156---2019-10-17" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.5.6] - 2019-10-17</h2>
<p>We've updated the UI during the vbox / buy phase to give a better indication of valid actions.</p> <p>We've updated the UI during the vbox / buy phase to give a better indication of valid actions.</p>
<h3> <h3>
<a id="user-content-changed-8" class="anchor" href="#changed-8" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3> <a id="user-content-changed-10" class="anchor" href="#changed-10" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul> <ul>
<li> <li>
<p>Borders for skill combo's represent the base colours.</p> <p>Borders for skill combo's represent the base colours.</p>
@ -1234,7 +1250,7 @@ pre {
<h2> <h2>
<a id="user-content-155---2019-10-15" class="anchor" href="#155---2019-10-15" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.5.5] - 2019-10-15</h2> <a id="user-content-155---2019-10-15" class="anchor" href="#155---2019-10-15" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.5.5] - 2019-10-15</h2>
<h3> <h3>
<a id="user-content-changed-9" class="anchor" href="#changed-9" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3> <a id="user-content-changed-11" class="anchor" href="#changed-11" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul> <ul>
<li> <li>
<p>Purge</p> <p>Purge</p>

View File

@ -1,6 +1,6 @@
{ {
"name": "mnml-client", "name": "mnml-client",
"version": "1.10.0", "version": "1.11.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -1,14 +1,12 @@
export const setAccount = value => ({ type: 'SET_ACCOUNT', value }); export const setAccount = value => ({ type: 'SET_ACCOUNT', value });
export const setAuthenticated = value => ({ type: 'SET_AUTHENTICATED', value });
export const setAnimating = value => ({ type: 'SET_ANIMATING', value }); export const setAnimating = value => ({ type: 'SET_ANIMATING', value });
export const setAnimCb = value => ({ type: 'SET_ANIM_CB', value });
export const setAnimFocus = value => ({ type: 'SET_ANIM_FOCUS', value }); export const setAnimFocus = value => ({ type: 'SET_ANIM_FOCUS', value });
export const setAnimSkill = value => ({ type: 'SET_ANIM_SKILL', value }); export const setAnimSkill = value => ({ type: 'SET_ANIM_SKILL', value });
export const setAnimSource = value => ({ type: 'SET_ANIM_SOURCE', value }); export const setAnimSource = value => ({ type: 'SET_ANIM_SOURCE', value });
export const setAnimTarget = value => ({ type: 'SET_ANIM_TARGET', value }); export const setAnimTarget = value => ({ type: 'SET_ANIM_TARGET', value });
export const setAnimText = value => ({ type: 'SET_ANIM_TEXT', value }); export const setResolution = value => ({ type: 'SET_RESOLUTION', value });
export const setDemo = value => ({ type: 'SET_DEMO', value });
export const setChatShow = value => ({ type: 'SET_CHAT_SHOW', value }); export const setChatShow = value => ({ type: 'SET_CHAT_SHOW', value });
export const setChatWheel = value => ({ type: 'SET_CHAT_WHEEL', value }); export const setChatWheel = value => ({ type: 'SET_CHAT_WHEEL', value });

View File

@ -4,9 +4,7 @@ const toast = require('izitoast');
const eachSeries = require('async/eachSeries'); const eachSeries = require('async/eachSeries');
const actions = require('./actions'); const actions = require('./actions');
const { TIMES } = require('./constants'); const { setAnimations, clearAnimations } = require('./animations.utils');
const animations = require('./animations.utils');
const { removeTier } = require('./utils');
const SOCKET_URL = process.env.NODE_ENV === 'production' ? 'wss://mnml.gg/api/ws' : 'ws://localhost/api/ws'; const SOCKET_URL = process.env.NODE_ENV === 'production' ? 'wss://mnml.gg/api/ws' : 'ws://localhost/api/ws';
@ -20,73 +18,32 @@ function createSocket(store) {
ws.send(cbor.encode(msg)); ws.send(cbor.encode(msg));
} }
function sendDevResolve(a, b, skill) { function sendDevResolve(skill) {
send(['DevResolve', { a, b, skill }]); send(['DevResolve', { skill }]);
} }
function onDevResolutions(newRes) { function setGame(game) {
const { game, account, animating } = store.getState(); store.dispatch(actions.setGame(game));
if (animating) return false;
store.dispatch(actions.setAnimating(true)); store.dispatch(actions.setAnimating(true));
store.dispatch(actions.setGameSkillInfo(null));
// stop fetching the game state til animations are done // stop fetching the game state til animations are done
const newRes = game.resolutions[game.resolutions.length - 1];
return eachSeries(newRes, (r, cb) => { return eachSeries(newRes, (r, cb) => {
if (!r.event) return cb(); // if (r.delay === 0) return cb(); // TargetKo etc
const timeout = animations.getTime(r.stages); setAnimations(r, store);
const anims = animations.getObjects(r, game, account); return setTimeout(cb, r.delay);
const text = animations.getText(r);
store.dispatch(actions.setAnimFocus(animations.getFocusTargets(r, game)));
if (anims.animSkill) store.dispatch(actions.setAnimSkill(anims.animSkill));
if (r.stages.includes('START_SKILL') && anims.animSource) {
store.dispatch(actions.setAnimSource(anims.animSource));
}
if (r.stages.includes('END_SKILL') && anims.animTarget) {
store.dispatch(actions.setAnimTarget(anims.animTarget));
if (animations.isCbAnim(anims.animSkill)) store.dispatch(actions.setAnimCb(cb));
}
if (r.stages.includes('POST_SKILL') && text) {
// timeout to prevent text classes from being added too soon
if (timeout === TIMES.POST_SKILL_DURATION_MS) {
store.dispatch(actions.setAnimText(text));
} else {
setTimeout(
() => store.dispatch(actions.setAnimText(text)),
timeout - TIMES.POST_SKILL_DURATION_MS
);
}
}
return setTimeout(() => {
store.dispatch(actions.setAnimSkill(null));
store.dispatch(actions.setAnimSource(null));
store.dispatch(actions.setAnimTarget(null));
store.dispatch(actions.setAnimText(null));
store.dispatch(actions.setAnimFocus([]));
if (r.stages.includes('END_SKILL') && animations.isCbAnim(anims.animSkill)) return true;
return cb();
}, timeout);
}, err => { }, err => {
if (err) return console.error(err); if (err) return console.error(err);
// clear animation state clearAnimations(store);
store.dispatch(actions.setAnimSkill(null));
store.dispatch(actions.setAnimSource(null));
store.dispatch(actions.setAnimTarget(null));
store.dispatch(actions.setAnimText(null));
store.dispatch(actions.setAnimating(false));
store.dispatch(actions.setGameEffectInfo(null));
// set the game state so resolutions don't fire twice // set the game state so resolutions don't fire twice
store.dispatch(actions.setGame(game)); store.dispatch(actions.setGame(game));
// ws.sendGameState(game.id);
return true; return true;
}); });
} }
const handlers = { const handlers = {
DevResolutions: onDevResolutions, GameState: setGame,
}; };
// decodes the cbor and // decodes the cbor and

View File

@ -5,18 +5,7 @@ const { createStore, combineReducers } = require('redux');
const reducers = require('./reducers'); const reducers = require('./reducers');
const actions = require('./actions'); const actions = require('./actions');
const createSocket = require('./animations.socket'); const createSocket = require('./animations.socket');
// const TrippyTriangle = require('./components/svgs/trippy.triangle');
// const Amplify = require('./components/svgs/amplify');
// const Hex = require('./components/svgs/hex');
const Game = require('./components/game'); const Game = require('./components/game');
const testGameBuilder = require('./test.game');
const testGame = testGameBuilder('8552e0bf-340d-4fc8-b6fc-cccccccccccc');
const testAccount = {
id: '8552e0bf-340d-4fc8-b6fc-cccccccccccc',
name: 'ntr',
};
// Redux Store // Redux Store
const store = createStore( const store = createStore(
@ -24,20 +13,15 @@ const store = createStore(
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
); );
const testAccount = {
id: '8552e0bf-340d-4fc8-b6fc-cccccccccccc',
name: 'ntr',
};
store.dispatch(actions.setAccount(testAccount)); store.dispatch(actions.setAccount(testAccount));
store.dispatch(actions.setGame(testGame));
function animationsNav(ws) { function animationsNav(ws) {
function useSkill(skill) { function useSkill(skill) {
const ateam = Math.round(Math.random()); return ws.sendDevResolve(skill);
const bteam = Math.round(Math.random());
const acon = Math.floor(Math.random() * 3);
const bcon = Math.floor(Math.random() * 3);
const a = testGame.players[ateam].constructs[acon].id;
const b = testGame.players[bteam].constructs[bcon].id;
return ws.sendDevResolve(a, b, skill);
} }
return SKILLS.map((s, i) => ( return SKILLS.map((s, i) => (

View File

@ -1,207 +1,49 @@
const actions = require('./actions');
const { TIMES } = require('./constants'); const { TIMES } = require('./constants');
const { removeTier } = require('./utils');
function none() { function setAnimations(r, store) {
return { const { focus, event: [type, variant] } = r;
animSource: null,
animTarget: null,
};
}
function getObjects(resolution, game, account) { if (type === 'HitAoe') {
if (!resolution) return none(); const { construct } = variant;
if (!resolution.target) return none(); const aoeFocus = focus.concat(construct);
const [type, event] = resolution.event; store.dispatch(actions.setResolution(null));
if (!event || !event.skill) return none(); store.dispatch(actions.setAnimFocus(aoeFocus));
store.dispatch(actions.setAnimTarget(r));
const playerTeam = game.players.find(t => t.id === account.id); return setTimeout(() => store.dispatch(actions.setAnimSource(null)), TIMES.SOURCE_DURATION_MS);
const playerTeamIds = playerTeam.constructs.map(c => c.id);
const otherTeam = game.players.find(t => t.id !== account.id);
const otherTeamIds = otherTeam.constructs.map(c => c.id);
const sourceIsPlayer = playerTeamIds.includes(resolution.source.id);
const targetIsPlayer = playerTeamIds.includes(resolution.target.id);
const targetting = () => {
if (type === 'AoeSkill') {
if (targetIsPlayer) return playerTeamIds;
return otherTeamIds;
}
return [resolution.target.id];
};
const sameTeam = (sourceIsPlayer && targetIsPlayer) || (!sourceIsPlayer && !targetIsPlayer);
let y = 0;
if (!sameTeam) y = targetIsPlayer ? 1 : -1;
const i = sourceIsPlayer
? playerTeamIds.findIndex(c => c === resolution.source.id)
: otherTeamIds.findIndex(c => c === resolution.source.id);
const j = targetIsPlayer
? playerTeamIds.findIndex(c => c === resolution.target.id)
: otherTeamIds.findIndex(c => c === resolution.target.id);
const x = j - i;
const direction = { x, y };
// const targetTeam = targetIsPlayer ? playerTeamIds : otherTeamIds;
const createSourceAnim = () => {
return {
animation: 'sourceCast',
constructId: resolution.source.id,
direction,
};
};
const skipSource = !resolution.stages.includes('START_SKILL')
|| resolution.source.id === resolution.target.id;
const animSource = skipSource
? null
: createSourceAnim();
const animTarget = {
skill: event.skill,
constructId: targetting(),
player: playerTeamIds.includes(resolution.target.id),
direction,
};
return {
animSource,
animTarget,
animSkill: event.skill,
};
}
function getTime(stages) {
let time = 0;
if (stages.includes('START_SKILL') && stages.includes('END_SKILL')) {
time += TIMES.SOURCE_AND_TARGET_TOTAL_DURATION;
} else {
if (stages.includes('START_SKILL')) time += TIMES.SOURCE_DURATION_MS;
if (stages.includes('END_SKILL')) time += TIMES.TARGET_DURATION_MS;
}
if (stages.includes('POST_SKILL')) time += TIMES.POST_SKILL_DURATION_MS;
return time;
}
function getFocusTargets(resolution, game) {
if (!resolution) return [];
if (!resolution.event) return [];
const [type] = resolution.event;
const source = resolution.source.id;
const target = resolution.target.id;
if (type === 'AoeSkill') {
const targetTeam = game.players.find(t => t.constructs.find(c => c.id === target));
const targetTeamIds = targetTeam.constructs.map(c => c.id);
if (source !== target) targetTeamIds.push(source);
return targetTeamIds;
}
if (source !== target) return [source, target];
return [target];
}
function getText(resolution) {
const nullText = { text: null, constructId: null, life: null };
if (!resolution) return nullText;
if (!resolution.stages.includes('POST_SKILL')) return nullText;
function generatePostSkill() {
const [type, event] = resolution.event;
if (type === 'Ko') {
return { text: 'KO!', css: 'ko-transition' };
}
if (type === 'Disable') {
const { disable } = event;
return { text: `${disable}`, css: '' };
}
if (type === 'Immunity') {
return { text: 'IMMUNE', css: '' };
}
if (type === 'Damage') {
const { mitigation, colour } = event;
let { amount } = event;
let css = '';
if (colour === 'Green') {
css = 'green';
amount *= -1;
}
if (colour === 'Red') css = 'red';
if (colour === 'Blue') css = 'blue';
const mitigationText = mitigation
? `(${mitigation})`
: '';
return { text: `${amount} ${mitigationText}`, css };
}
if (type === 'Healing') {
const { amount, overhealing } = event;
return { text: `${amount} (${overhealing} OH)`, css: 'green' };
}
if (type === 'Inversion') {
return { text: 'INVERT', css: '' };
}
if (type === 'Reflection') {
return { text: 'REFLECT', css: '' };
}
if (type === 'Effect') {
const { effect, duration, construct_effects: effects } = event;
return { text: `+ ${effect} ${duration}T`, css: '', effects };
}
if (type === 'Recharge') {
const { red, blue } = event;
if (red > 0 && blue > 0) return { text: `+${red}R +${blue}B`, css: 'rb' };
if (red > 0) return { text: `+${red}R`, css: 'red' };
if (blue > 0) return { text: `+${blue}B`, css: 'blue' };
return nullText;
}
if (type === 'Removal') {
const { effect, construct_effects: effects } = event;
if (!effect) {
return { text: 'Effect Removal', css: '', effects };
}
return { text: `-${effect}`, css: '', effects };
}
return false;
} }
const { green, red, blue } = resolution.target; store.dispatch(actions.setAnimFocus(focus));
const { text, css, effects } = generatePostSkill();
const skill = resolution.event[1] ? resolution.event[1].skill : null; if (type === 'Cast') {
return { store.dispatch(actions.setResolution(null));
css, store.dispatch(actions.setAnimSource(r));
text, return setTimeout(() => store.dispatch(actions.setAnimSource(null)), TIMES.SOURCE_DURATION_MS);
effects, }
life: { green, red, blue },
constructId: resolution.target.id, if (type === 'Hit') {
skill, store.dispatch(actions.setResolution(null));
}; store.dispatch(actions.setAnimTarget(r));
return setTimeout(() => store.dispatch(actions.setAnimTarget(null)), TIMES.TARGET_DURATION_MS);
}
return store.dispatch(actions.setResolution(r));
} }
function isCbAnim(skill) { function clearAnimations(store) {
return ['Attack', 'Blast', 'Siphon', 'SiphonTick', 'Strike', 'Chaos', 'Slay', 'Heal', store.dispatch(actions.setAnimSkill(null));
'Buff', 'Amplify', 'Haste', 'Triage', 'TriageTick', 'Link', 'Hybrid', 'Intercept', store.dispatch(actions.setAnimSource(null));
'Debuff', 'Curse', 'Decay', 'DecayTick', 'Purge', 'Silence', 'Restrict', store.dispatch(actions.setAnimTarget(null));
'Stun', 'Bash', 'Absorb', 'Sleep', 'Break', 'Ruin', store.dispatch(actions.setResolution(null));
'Block', 'Sustain', 'Electrify', 'Electrocute', 'ElectrocuteTick', store.dispatch(actions.setAnimating(false));
'Counter', 'CounterAttack', 'Purify', 'Recharge', 'Reflect'].includes(removeTier(skill)); store.dispatch(actions.setGameEffectInfo(null));
store.dispatch(actions.setAnimFocus(null));
} }
module.exports = { module.exports = {
getFocusTargets, setAnimations,
getObjects, clearAnimations,
getTime,
getText,
isCbAnim,
}; };

View File

@ -33,6 +33,10 @@ const ws = createSocket(events);
ws.connect(); ws.connect();
events.setWs(ws); events.setWs(ws);
if (process.env.NODE_ENV !== 'development') {
LogRocket.init('yh0dy3/mnml');
}
const App = () => ( const App = () => (
<Provider store={store}> <Provider store={store}>
{window.Stripe {window.Stripe

View File

@ -152,11 +152,9 @@ class AccountStatus extends Component {
return ( return (
<section class='account top' onClick={tlClick}> <section class='account top' onClick={tlClick}>
{subInfo()}
<div> <div>
{subInfo()} <h3>Email</h3>
</div>
<div>
<label for="email">Email Settings:</label>
<dl> <dl>
<dt>Recovery Email</dt> <dt>Recovery Email</dt>
<dd>{email ? email.email : 'No email set'}</dd> <dd>{email ? email.email : 'No email set'}</dd>
@ -174,6 +172,7 @@ class AccountStatus extends Component {
<button onClick={() => sendSetEmail(emailState)}>Update</button> <button onClick={() => sendSetEmail(emailState)}>Update</button>
</div> </div>
<div> <div>
<h3>Password</h3>
<label for="current">Password:</label> <label for="current">Password:</label>
<input <input
class="login-input" class="login-input"
@ -208,6 +207,7 @@ class AccountStatus extends Component {
</button> </button>
</div> </div>
<div> <div>
<h3>Other</h3>
<figure> <figure>
<figcaption>spawn new construct</figcaption> <figcaption>spawn new construct</figcaption>
<button onClick={() => sendConstructSpawn()} type="submit"> <button onClick={() => sendConstructSpawn()} type="submit">

View File

@ -5,6 +5,7 @@ const { connect } = require('preact-redux');
const Amplify = require('./anims/amplify'); const Amplify = require('./anims/amplify');
const Attack = require('./anims/attack'); const Attack = require('./anims/attack');
const Absorb = require('./anims/absorb'); const Absorb = require('./anims/absorb');
const Absorption = require('./anims/absorption');
const Bash = require('./anims/bash'); const Bash = require('./anims/bash');
const Blast = require('./anims/blast'); const Blast = require('./anims/blast');
const Block = require('./anims/block'); const Block = require('./anims/block');
@ -44,8 +45,8 @@ const { removeTier } = require('../utils');
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
const { animTarget } = state; const { animTarget, account } = state;
return { animTarget }; return { animTarget, account };
} }
); );
@ -60,20 +61,21 @@ class ConstructAnimation extends Component {
const { const {
animTarget, animTarget,
construct, construct,
account,
} = props; } = props;
if (!animTarget) return false; if (!animTarget) return false;
const { const { skill, event: [, variant] } = animTarget;
skill, const { construct: constructId, player, direction: [x, y] } = variant;
player,
direction, const animY = y && player === account.id ? -1 : y;
constructId, const isPlayer = player === account.id;
} = animTarget; const direction = { x, y: animY };
const animSkill = removeTier(skill); const animSkill = removeTier(skill);
if (!constructId.includes(construct.id)) return false; if (!constructId.includes(construct.id)) return false;
// find target animation // find target animation
const chooseAnim = () => { const chooseAnim = () => {
switch (animSkill) { switch (animSkill) {
@ -93,9 +95,9 @@ class ConstructAnimation extends Component {
case 'Haste': return <Haste />; case 'Haste': return <Haste />;
case 'Triage': return <Triage />; case 'Triage': return <Triage />;
case 'TriageTick': return <TriageTick />; case 'TriageTick': return <TriageTick />;
case 'Link': return <Link player={player} />; case 'Link': return <Link player={isPlayer} />;
case 'Hybrid': return <Hybrid />; case 'Hybrid': return <Hybrid />;
case 'Intercept': return <Intercept player={player} />; case 'Intercept': return <Intercept player={isPlayer} />;
// Debuff base // Debuff base
case 'Debuff': return <Debuff />; case 'Debuff': return <Debuff />;
@ -112,21 +114,22 @@ class ConstructAnimation extends Component {
// case 'Banish': return setAvatarAnimation(true, true, resolution.id, construct.id, 'banish', null); // case 'Banish': return setAvatarAnimation(true, true, resolution.id, construct.id, 'banish', null);
case 'Bash': return <Bash />; case 'Bash': return <Bash />;
case 'Absorb': return <Absorb />; case 'Absorb': return <Absorb />;
case 'Absorption': return <Absorption />;
case 'Sleep': return <Sleep />; case 'Sleep': return <Sleep />;
case 'Break': return <Break />; case 'Break': return <Break />;
case 'Ruin': return <Ruin />; case 'Ruin': return <Ruin />;
// Block Base // Block Base
case 'Block': return <Block />; case 'Block': return <Block />;
case 'Sustain': return <Sustain player={player} />; case 'Sustain': return <Sustain player={isPlayer} />;
case 'Electrify': return <Electrify />; case 'Electrify': return <Electrify />;
case 'Electrocute': return <Electrocute />; case 'Electrocute': return <Electrocute />;
case 'ElectrocuteTick': return <Electrocute />; case 'ElectrocuteTick': return <Electrocute />;
case 'Counter': return <Counter player={player} />; case 'Counter': return <Counter player={isPlayer} />;
case 'CounterAttack': return <Attack direction={direction} />; case 'CounterAttack': return <Attack direction={direction} />;
case 'Purify': return <Purify player={player} />; case 'Purify': return <Purify player={isPlayer} />;
case 'Recharge': return <Recharge player={player} />; case 'Recharge': return <Recharge player={isPlayer} />;
case 'Reflect': return <Refl player={player} />; case 'Reflect': return <Refl player={isPlayer} />;
default: return false; default: return false;
} }

View File

@ -1,21 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default; const anime = require('animejs').default;
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
// shamelessly lifted from teh anime docs
// https://animejs.com/documentation/#svgAttr
class Absorb extends Component { class Absorb extends Component {
constructor() { constructor() {
super(); super();
@ -56,7 +44,7 @@ class Absorb extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#absorb'], targets: ['#absorb'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -66,8 +54,6 @@ class Absorb extends Component {
targets: ['#absorb polygon'], targets: ['#absorb polygon'],
points: '64 124.94732621931382 8.574 96.00354944613788 8.62269130211947 32.03166105954991 64 4 119.37730869788052 32.03166105954991 119.426 96.00354944613788', points: '64 124.94732621931382 8.574 96.00354944613788 8.62269130211947 32.03166105954991 64 4 119.37730869788052 32.03166105954991 119.426 96.00354944613788',
easing: 'easeOutExpo', easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -76,8 +62,6 @@ class Absorb extends Component {
strokeWidth: [2, 1], strokeWidth: [2, 1],
easing: 'easeInOutSine', easing: 'easeInOutSine',
direction: 'alternate', direction: 'alternate',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -90,8 +74,7 @@ class Absorb extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Absorb); module.exports = Absorb;

View File

@ -0,0 +1,80 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { TIMES } = require('../../constants');
class Absorb extends Component {
constructor() {
super();
this.animations = [];
}
render() {
return (
<svg
class='skill-animation blue'
version="1.1"
id="absorb"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 128 128">
<defs>
<filter id="absorbFilter">
<feGaussianBlur in="SourceGraphic" stdDeviation="5" />
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<polygon
class='blue'
points="64 124.94732621931382 8.574 96.00354944613788 8.62269130211947 32.03166105954991 64 4 119.37730869788052 32.03166105954991 119.426 96.00354944613788"
filter="url(#absorbFilter)">
</polygon>
<polygon
class='white'
points="64 124.94732621931382 8.574 96.00354944613788 8.62269130211947 32.03166105954991 64 4 119.37730869788052 32.03166105954991 119.426 96.00354944613788">
</polygon>
</svg>
);
}
componentDidMount() {
this.animations.push(anime({
targets: ['#absorb'],
opacity: [
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
}));
this.animations.push(anime({
targets: ['#absorb polygon'],
points: '64 68.64 8.574 100 63.446 67.68 64 4 64.554 67.68 119.426 100',
easing: 'easeOutExpo',
duration: TIMES.TARGET_DURATION_MS,
}));
this.animations.push(anime({
targets: ['#absorb polygon.white'],
strokeWidth: [2, 1],
easing: 'easeInOutSine',
direction: 'alternate',
duration: TIMES.TARGET_DURATION_MS,
}));
}
// this is necessary because
// skipping / timing / unmounting race conditions
// can cause the animations to cut short, this will ensure the values are reset
// because preact will recycle all these components
componentWillUnmount() {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
}
}
module.exports = Absorb;

View File

@ -5,13 +5,6 @@ const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Amplify extends Component { class Amplify extends Component {
constructor() { constructor() {
super(); super();
@ -43,7 +36,7 @@ class Amplify extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#amplify'], targets: ['#amplify'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -54,8 +47,6 @@ class Amplify extends Component {
d: [{ value: altPath }], d: [{ value: altPath }],
stroke: ['#3050f8', '#a52a2a'], stroke: ['#3050f8', '#a52a2a'],
easing: 'easeInOutSine', easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -64,8 +55,6 @@ class Amplify extends Component {
baseFrequency: 0.15, baseFrequency: 0.15,
scale: 4, scale: 4,
easing: 'easeInOutExpo', easing: 'easeInOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -78,8 +67,7 @@ class Amplify extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Amplify); module.exports = Amplify;

View File

@ -5,13 +5,6 @@ const anime = require('animejs').default;
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Attack extends Component { class Attack extends Component {
constructor(props) { constructor(props) {
super(); super();
@ -46,8 +39,8 @@ class Attack extends Component {
y: [400, 200], y: [400, 200],
height: [100, 10, 0], height: [100, 10, 0],
width: [12, 5, 0], width: [12, 5, 0],
delay: () => anime.random(TIMES.TARGET_DELAY_MS, TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS / 2), delay: () => anime.random(0, TIMES.TARGET_DURATION_MS / 4),
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS * 5 / 4,
})); }));
} }
@ -59,9 +52,7 @@ class Attack extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Attack); module.exports = Attack;

View File

@ -8,7 +8,6 @@ function Banish(id, idle) {
scaleY: 0, scaleY: 0,
fill: '#fff', fill: '#fff',
easing: 'easeOutElastic', easing: 'easeOutElastic',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.45, duration: TIMES.TARGET_DURATION_MS * 0.45,
direction: 'alternate', direction: 'alternate',
begin: idle.pause, begin: idle.pause,

View File

@ -5,13 +5,6 @@ const anime = require('animejs').default;
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Bash extends Component { class Bash extends Component {
constructor() { constructor() {
super(); super();
@ -54,7 +47,7 @@ class Bash extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#bash'], targets: ['#bash'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -64,7 +57,6 @@ class Bash extends Component {
targets: ['#bash'], targets: ['#bash'],
scale: { scale: {
value: 1, value: 1,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.2, duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeInExpo', easing: 'easeInExpo',
}, },
@ -72,10 +64,9 @@ class Bash extends Component {
value: 180, value: 180,
easing: 'linear', easing: 'linear',
loop: true, loop: true,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
}, },
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.1, delay: TIMES.TARGET_DURATION_MS * 0.1,
duration: TIMES.TARGET_DURATION_MS * 0.2, duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeOutSine', easing: 'easeOutSine',
})); }));
@ -86,8 +77,6 @@ class Bash extends Component {
scale: 10, scale: 10,
numOctaves: 5, numOctaves: 5,
easing: 'easeOutSine', easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -100,8 +89,7 @@ class Bash extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Bash); module.exports = Bash;

View File

@ -1,18 +1,10 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default; const anime = require('animejs').default;
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Bash extends Component { class Bash extends Component {
constructor() { constructor() {
super(); super();
@ -47,7 +39,7 @@ class Bash extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#bash'], targets: ['#bash'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -57,7 +49,6 @@ class Bash extends Component {
targets: ['#bash'], targets: ['#bash'],
scale: { scale: {
value: 1, value: 1,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.2, duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeInExpo', easing: 'easeInExpo',
}, },
@ -74,7 +65,7 @@ class Bash extends Component {
{ translateX: 0, translateY: 2 }, { translateX: 0, translateY: 2 },
], ],
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.1, delay: TIMES.TARGET_DURATION_MS * 0.1,
duration: TIMES.TARGET_DURATION_MS * 0.2, duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeOutSine', easing: 'easeOutSine',
})); }));
@ -90,7 +81,6 @@ class Bash extends Component {
], ],
easing: 'easeOutSine', easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -103,8 +93,7 @@ class Bash extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Bash); module.exports = Bash;

View File

@ -1,34 +1,12 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default; const anime = require('animejs').default;
const times = require('lodash/times'); const times = require('lodash/times');
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) {
return (
<circle
cx={x}
cy={y}
r={radius}
fill="url(#grad1)"
stroke-width="2"
stroke={colour}
filter="url(#explosion)"
/>
);
}
class Blast extends Component { class Blast extends Component {
constructor(props) { constructor() {
super(); super();
this.animations = []; this.animations = [];
} }
@ -62,7 +40,7 @@ class Blast extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#blast'], targets: ['#blast'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.5, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.5, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -75,7 +53,6 @@ class Blast extends Component {
`, `,
style: { rotate: anime.random(-180, 180) }, style: { rotate: anime.random(-180, 180) },
easing: 'easeOutCubic', easing: 'easeOutCubic',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -88,8 +65,7 @@ class Blast extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Blast); module.exports = Blast;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Block extends Component { class Block extends Component {
constructor() { constructor() {
super(); super();
@ -38,7 +30,7 @@ class Block extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#block'], targets: ['#block'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -53,8 +45,7 @@ class Block extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Block); module.exports = Block;

View File

@ -1,20 +1,12 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
// logarithmic spiral lifted from // logarithmic spiral lifted from
// https://upload.wikimedia.org/wikipedia/commons/5/5b/Logarithmic_spiral_(1).svg // https://upload.wikimedia.org/wikipedia/commons/5/5b/Logarithmic_spiral_(1).svg
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Break extends Component { class Break extends Component {
constructor() { constructor() {
super(); super();
@ -63,7 +55,7 @@ class Break extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#break'], targets: ['#break'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -74,7 +66,6 @@ class Break extends Component {
rotate: 180, rotate: 180,
easing: 'linear', easing: 'linear',
loop: true, loop: true,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -82,7 +73,6 @@ class Break extends Component {
targets: ['#break circle'], targets: ['#break circle'],
easing: 'easeInSine', easing: 'easeInSine',
r: 300, r: 300,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -93,10 +83,8 @@ class Break extends Component {
numOctaves: 5, numOctaves: 5,
easing: 'easeOutSine', easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
// this is necessary because // this is necessary because
@ -107,8 +95,7 @@ class Break extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Break); module.exports = Break;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Buff extends Component { class Buff extends Component {
constructor() { constructor() {
super(); super();
@ -39,7 +31,7 @@ class Buff extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#buff'], targets: ['#buff'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -50,7 +42,6 @@ class Buff extends Component {
points: '0,190 100,10 190,190', points: '0,190 100,10 190,190',
easing: 'easeOutExpo', easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -59,7 +50,6 @@ class Buff extends Component {
points: '40,170 100,50 160,170', points: '40,170 100,50 160,170',
easing: 'easeOutExpo', easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -68,7 +58,6 @@ class Buff extends Component {
points: '70,150 100,90 130,150', points: '70,150 100,90 130,150',
easing: 'easeOutExpo', easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -81,8 +70,7 @@ class Buff extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Buff); module.exports = Buff;

View File

@ -1,26 +1,19 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const { randomPoints } = require('../../utils'); const { randomPoints } = require('../../utils');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) { function projectile(x, y, radius, colour) {
return ( return (
<circle <circle
cx={x} cx={anime.random(0, 400)}
cy={y} cy={anime.random(0, 400)}
r={radius} r={radius}
fill={colour} fill={colour}
stroke="none" stroke={colour === '#a52a2a' ? 'none' : '#f5f5f5'}
stroke-width={colour === '#a52a2a' ? '0' : '0.05em'}
filter={colour === '#a52a2a' ? 'url(#chaosRedFilter)' : 'url(#chaosBlueFilter)'} filter={colour === '#a52a2a' ? 'url(#chaosRedFilter)' : 'url(#chaosBlueFilter)'}
/> />
); );
@ -30,10 +23,10 @@ class Chaos extends Component {
constructor() { constructor() {
super(); super();
this.animations = []; this.animations = [];
const points = randomPoints(20, 30, { x: 0, y: 0, width: 300, height: 100 }); const points = randomPoints(20, 30, { x: 0, y: 0, width: 1000, height: 1000 });
this.charges = points.map(coord => { this.charges = points.map(coord => {
const colour = Math.random() >= 0.5 ? '#a52a2a' : '#3050f8'; const colour = Math.random() >= 0.5 ? '#a52a2a' : '#3050f8';
return projectile(coord[0], coord[1], 14, colour); return projectile(coord[0], coord[1], '0.5em', colour);
}); });
} }
@ -65,39 +58,12 @@ class Chaos extends Component {
componentDidMount() { componentDidMount() {
const projectiles = document.querySelectorAll('.skill-anim circle'); const projectiles = document.querySelectorAll('.skill-anim circle');
anime.set('.skill-anim', {
translateY: -(window.innerHeight) * 0.35 * this.props.direction.y,
translateX: -(window.innerWidth) * 0.15 * this.props.direction.x,
opacity: 0,
});
this.animations.push(anime({
targets: '.skill-anim',
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.3 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.7, duration: TIMES.POST_SKILL_DURATION_MS },
],
}));
this.animations.push(anime({
targets: '.skill-anim',
translateY: 0,
translateX: 0,
loop: false,
delay: TIMES.TARGET_DELAY_MS,
duration: (TIMES.TARGET_DURATION_MS * 1 / 2),
easing: 'easeInQuad',
}));
this.animations.push(anime({ this.animations.push(anime({
targets: ['#chaosRedFilter feTurbulence', '#chaosRedFilter feDisplacementMap'], targets: ['#chaosRedFilter feTurbulence', '#chaosRedFilter feDisplacementMap'],
baseFrequency: 2, baseFrequency: 2,
scale: 20, scale: 5,
numOctaves: 5, numOctaves: 3,
easing: 'easeOutSine', easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -105,13 +71,9 @@ class Chaos extends Component {
targets: proj, targets: proj,
cx: 150 + (Math.random() * 50 * (Math.random() < 0.5 ? -1 : 1)), cx: 150 + (Math.random() * 50 * (Math.random() < 0.5 ? -1 : 1)),
cy: 200 + (Math.random() * 50 * (Math.random() < 0.5 ? -1 : 1)), cy: 200 + (Math.random() * 50 * (Math.random() < 0.5 ? -1 : 1)),
// cx: 150, duration: anime.random(TIMES.TARGET_DURATION_MS * 2 / 3, TIMES.TARGET_DURATION_MS),
// cy: 200, opacity: 0,
// opacity: 0, easing: 'easeInExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: (TIMES.TARGET_DURATION_MS * 2 / 3),
easing: 'easeInQuad',
}))); })));
} }
@ -119,8 +81,7 @@ class Chaos extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Chaos); module.exports = Chaos;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Counter extends Component { class Counter extends Component {
constructor() { constructor() {
super(); super();
@ -51,7 +43,7 @@ class Counter extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#counter'], targets: ['#counter'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -60,7 +52,7 @@ class Counter extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#counter'], targets: ['#counter'],
rotateX: 180, rotateX: 180,
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS / 3, delay: TIMES.TARGET_DURATION_MS / 3,
duration: TIMES.TARGET_DURATION_MS / 2, duration: TIMES.TARGET_DURATION_MS / 2,
easing: 'easeOutSine', easing: 'easeOutSine',
})); }));
@ -71,8 +63,6 @@ class Counter extends Component {
scale: 10, scale: 10,
numOctaves: 5, numOctaves: 5,
easing: 'easeOutSine', easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -85,8 +75,7 @@ class Counter extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Counter); module.exports = Counter;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Curse extends Component { class Curse extends Component {
constructor() { constructor() {
super(); super();
@ -54,7 +46,7 @@ class Curse extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#curse'], targets: ['#curse'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -64,7 +56,6 @@ class Curse extends Component {
targets: ['#curseCircleOne', '#curseFilterOne'], targets: ['#curseCircleOne', '#curseFilterOne'],
r: 30, r: 30,
easing: 'easeInOutSine', easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -72,7 +63,6 @@ class Curse extends Component {
targets: ['#curseCircleTwo', '#curseFilterTwo'], targets: ['#curseCircleTwo', '#curseFilterTwo'],
r: 60, r: 60,
easing: 'easeInOutSine', easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -80,7 +70,6 @@ class Curse extends Component {
targets: ['#curseCircleThree', '#curseFilterThree'], targets: ['#curseCircleThree', '#curseFilterThree'],
r: 90, r: 90,
easing: 'easeInOutSine', easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -93,8 +82,7 @@ class Curse extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Curse); module.exports = Curse;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Debuff extends Component { class Debuff extends Component {
constructor() { constructor() {
super(); super();
@ -41,7 +33,7 @@ class Debuff extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#debuff'], targets: ['#debuff'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -52,7 +44,6 @@ class Debuff extends Component {
points: '0,190 100,10 190,190', points: '0,190 100,10 190,190',
easing: 'easeOutExpo', easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -61,7 +52,6 @@ class Debuff extends Component {
points: '40,170 100,50 160,170', points: '40,170 100,50 160,170',
easing: 'easeOutExpo', easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -70,7 +60,6 @@ class Debuff extends Component {
points: '70,150 100,90 130,150', points: '70,150 100,90 130,150',
easing: 'easeOutExpo', easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -83,8 +72,7 @@ class Debuff extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Debuff); module.exports = Debuff;

View File

@ -2,17 +2,9 @@ const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const times = require('lodash/times'); const times = require('lodash/times');
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Decay extends Component { class Decay extends Component {
constructor() { constructor() {
super(); super();
@ -40,7 +32,7 @@ class Decay extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#decay'], targets: ['#decay'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -55,7 +47,6 @@ class Decay extends Component {
rotate(${anime.random(-15, 15)}) rotate(${anime.random(-15, 15)})
`, `,
easing: 'easeOutSine', easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -68,8 +59,7 @@ class Decay extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Decay); module.exports = Decay;

View File

@ -1,19 +1,10 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const times = require('lodash/times'); const times = require('lodash/times');
const { connect } = require('preact-redux');
const anime = require('animejs').default; const anime = require('animejs').default;
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Electrify extends Component { class Electrify extends Component {
constructor() { constructor() {
super(); super();
@ -57,7 +48,7 @@ class Electrify extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#electrify'], targets: ['#electrify'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -109,8 +100,7 @@ class Electrify extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Electrify); module.exports = Electrify;

View File

@ -1,19 +1,11 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const times = require('lodash/times') const times = require('lodash/times')
const { connect } = require('preact-redux');
const anime = require('animejs').default; const anime = require('animejs').default;
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Electrocute extends Component { class Electrocute extends Component {
constructor() { constructor() {
super(); super();
@ -55,7 +47,7 @@ class Electrocute extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#electrify'], targets: ['#electrify'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -101,8 +93,7 @@ class Electrocute extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Electrocute); module.exports = Electrocute;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Haste extends Component { class Haste extends Component {
constructor() { constructor() {
super(); super();
@ -49,7 +41,7 @@ class Haste extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#haste'], targets: ['#haste'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -59,7 +51,6 @@ class Haste extends Component {
targets: ['#haste g'], targets: ['#haste g'],
stroke: [COLOURS.GREEN, COLOURS.RED], stroke: [COLOURS.GREEN, COLOURS.RED],
easing: 'easeInOutSine', easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.75, duration: TIMES.TARGET_DURATION_MS * 0.75,
})); }));
@ -69,8 +60,6 @@ class Haste extends Component {
scale: 10, scale: 10,
numOctaves: 5, numOctaves: 5,
easing: 'easeInOutSine', easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -83,8 +72,7 @@ class Haste extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Haste); module.exports = Haste;

View File

@ -1,18 +1,10 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const { randomPoints } = require('../../utils'); const { randomPoints } = require('../../utils');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) { function projectile(x, y, radius, colour) {
return ( return (
<circle <circle
@ -60,7 +52,7 @@ class Heal extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#heal'], targets: ['#heal'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS / 4, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS / 4, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -70,8 +62,7 @@ class Heal extends Component {
targets: ['#heal circle'], targets: ['#heal circle'],
cx: 150, cx: 150,
cy: 200, cy: 200,
delay: TIMES.TARGET_DELAY_MS * 4, duration: TIMES.TARGET_DURATION_MS * 0.6,
duration: TIMES.TARGET_DURATION_MS * 0.9,
easing: 'easeOutCirc', easing: 'easeOutCirc',
direction: 'reverse', direction: 'reverse',
})); }));
@ -81,8 +72,7 @@ class Heal extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Heal); module.exports = Heal;

View File

@ -1,16 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default; const anime = require('animejs').default;
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
// shamelessly lifted from teh anime docs // shamelessly lifted from teh anime docs
// https://animejs.com/documentation/#svgAttr // https://animejs.com/documentation/#svgAttr
@ -54,7 +47,7 @@ class Hex extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#hex'], targets: ['#hex'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -65,7 +58,6 @@ class Hex extends Component {
points: '64 124.94732621931382 8.574 96.00354944613788 8.62269130211947 32.03166105954991 64 4 119.37730869788052 32.03166105954991 119.426 96.00354944613788', points: '64 124.94732621931382 8.574 96.00354944613788 8.62269130211947 32.03166105954991 64 4 119.37730869788052 32.03166105954991 119.426 96.00354944613788',
easing: 'easeOutExpo', easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -75,7 +67,6 @@ class Hex extends Component {
easing: 'easeInOutSine', easing: 'easeInOutSine',
direction: 'alternate', direction: 'alternate',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -88,8 +79,7 @@ class Hex extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Hex); module.exports = Hex;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Hybrid extends Component { class Hybrid extends Component {
constructor() { constructor() {
super(); super();
@ -94,12 +86,11 @@ class Hybrid extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#hybrid'], targets: ['#hybrid'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
transform: { transform: {
value: ['rotate(0)', 'rotate(360)'], value: ['rotate(0)', 'rotate(360)'],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
direction: 'alternate', direction: 'alternate',
}, },
@ -110,7 +101,6 @@ class Hybrid extends Component {
r: [10, anime.random(10, 30)], r: [10, anime.random(10, 30)],
targets: ['#hybrid circle.green-one'], targets: ['#hybrid circle.green-one'],
cx: [50, 250, 50, 250], cx: [50, 250, 50, 250],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInOutSine', easing: 'easeInOutSine',
loop: true, loop: true,
@ -120,7 +110,6 @@ class Hybrid extends Component {
r: [10, anime.random(10, 30)], r: [10, anime.random(10, 30)],
targets: ['#hybrid circle.green-two'], targets: ['#hybrid circle.green-two'],
cy: [250, 50, 250, 50], cy: [250, 50, 250, 50],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInOutSine', easing: 'easeInOutSine',
loop: true, loop: true,
@ -131,7 +120,6 @@ class Hybrid extends Component {
targets: ['#hybrid circle.bluewhite-one'], targets: ['#hybrid circle.bluewhite-one'],
fill: [COLOURS.WHITE, COLOURS.BLUE], fill: [COLOURS.WHITE, COLOURS.BLUE],
cy: [50, 250, 50, 250], cy: [50, 250, 50, 250],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInOutSine', easing: 'easeInOutSine',
loop: true, loop: true,
@ -142,7 +130,6 @@ class Hybrid extends Component {
targets: ['#hybrid circle.bluewhite-two'], targets: ['#hybrid circle.bluewhite-two'],
cx: [250, 50, 250, 50], cx: [250, 50, 250, 50],
fill: [COLOURS.WHITE, COLOURS.BLUE], fill: [COLOURS.WHITE, COLOURS.BLUE],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInOutSine', easing: 'easeInOutSine',
loop: true, loop: true,
@ -153,8 +140,7 @@ class Hybrid extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Hybrid); module.exports = Hybrid;

View File

@ -1,7 +1,5 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default; const anime = require('animejs').default;
const { const {
@ -9,13 +7,6 @@ const {
COLOURS, COLOURS,
} = require('../../constants'); } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Intercept extends Component { class Intercept extends Component {
constructor() { constructor() {
super(); super();
@ -47,7 +38,7 @@ class Intercept extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#intercept'], targets: ['#intercept'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -61,7 +52,6 @@ class Intercept extends Component {
], ],
strokeWidth: 0, strokeWidth: 0,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInCubic', easing: 'easeInCubic',
// direction: 'reverse', // direction: 'reverse',
@ -70,7 +60,6 @@ class Intercept extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#intercept rect'], targets: ['#intercept rect'],
y: 96, y: 96,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInCubic', easing: 'easeInCubic',
// direction: 'reverse', // direction: 'reverse',
@ -82,8 +71,6 @@ class Intercept extends Component {
scale: 10, scale: 10,
numOctaves: 5, numOctaves: 5,
easing: 'easeOutSine', easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -96,8 +83,7 @@ class Intercept extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Intercept); module.exports = Intercept;

View File

@ -6,7 +6,6 @@ function Invert(id, idle) {
return anime({ return anime({
targets: [document.getElementById(id)], targets: [document.getElementById(id)],
rotate: 180, rotate: 180,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.45, duration: TIMES.TARGET_DURATION_MS * 0.45,
easing: 'easeInOutElastic', easing: 'easeInOutElastic',
direction: 'alternate', direction: 'alternate',

View File

@ -1,18 +1,10 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default; const anime = require('animejs').default;
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Link extends Component { class Link extends Component {
constructor() { constructor() {
super(); super();
@ -68,7 +60,7 @@ class Link extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#link'], targets: ['#link'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: 1000 }, { value: 1, duration: 1000 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.7, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.7, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -78,7 +70,6 @@ class Link extends Component {
targets: ['#link path'], targets: ['#link path'],
strokeDashoffset: [anime.setDashoffset, 0], strokeDashoffset: [anime.setDashoffset, 0],
duration: TIMES.TARGET_DURATION_MS * 0.8, duration: TIMES.TARGET_DURATION_MS * 0.8,
delay: TIMES.TARGET_DELAY_MS,
easing: 'easeInOutSine', easing: 'easeInOutSine',
}); });
} }
@ -91,8 +82,7 @@ class Link extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Link); module.exports = Link;

View File

@ -1,17 +1,8 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Purge extends Component { class Purge extends Component {
constructor() { constructor() {
super(); super();
@ -51,7 +42,7 @@ class Purge extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#purge'], targets: ['#purge'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -60,7 +51,6 @@ class Purge extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#purge g'], targets: ['#purge g'],
strokeWidth: [15, 0], strokeWidth: [15, 0],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInSine', easing: 'easeInSine',
})); }));
@ -81,8 +71,7 @@ class Purge extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Purge); module.exports = Purge;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) { function projectile(x, y, radius, colour) {
return ( return (
<circle <circle
@ -80,7 +72,7 @@ class Purify extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#purify'], targets: ['#purify'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -89,7 +81,7 @@ class Purify extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#block'], targets: ['#block'],
opacity: [ opacity: [
{ value: 0, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS }, { value: 0, duration: TIMES.TARGET_DURATION_MS },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
})); }));
@ -102,7 +94,6 @@ class Purify extends Component {
easing: 'easeInOutSine', easing: 'easeInOutSine',
cx: 128, cx: 128,
cy: 24, cy: 24,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -115,8 +106,7 @@ class Purify extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Purify); module.exports = Purify;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Recharge extends Component { class Recharge extends Component {
constructor() { constructor() {
super(); super();
@ -59,7 +51,7 @@ class Recharge extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#recharge'], targets: ['#recharge'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -71,7 +63,6 @@ class Recharge extends Component {
easing: 'easeInOutSine', easing: 'easeInOutSine',
direction: 'alternate', direction: 'alternate',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -82,7 +73,6 @@ class Recharge extends Component {
numOctaves: 5, numOctaves: 5,
easing: 'easeOutSine', easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -95,8 +85,7 @@ class Recharge extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Recharge); module.exports = Recharge;

View File

@ -1,83 +0,0 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Block extends Component {
constructor() {
super();
this.animations = [];
}
render() {
return (
<svg
class="skill-animation red"
version="1.1"
id="block"
xmlns="http://www.w3.org/2000/svg"
style={{ transform: 'rotate(180deg)' }}
viewBox="0 0 256 256">
<filter id='blockFilter'>
<feTurbulence type="turbulence" baseFrequency="0.05" numOctaves="2" result="turbulence"></feTurbulence>
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="15" xChannelSelector="R" yChannelSelector="G"></feDisplacementMap>
</filter>
<polygon
points='128,168 80,240 176,240'
style={{ filter: 'url("#blockFilter")' }}
id="charge"/>
<polyline
points='176,240 212,216 128,96 44,216 80,240'
style={{ filter: 'url("#blockFilter")' }}
id="charge"/>
<polyline
points='212,216 248,192 128,24 8,192 44,216'
style={{ filter: 'url("#blockFilter")' }}
id="charge"/>
</svg>
);
}
componentDidMount() {
this.animations.push(anime({
targets: ['#block'],
opacity: [
{ value: 1, delay: TIMES.TARGET_FADE_IN_DELAY, duration: TIMES.TARGET_FADE_IN_DURATION },
{ value: 0, delay: TIMES.TARGET_FADE_OUT_DELAY, duration: TIMES.FADE_OUT_DURATION },
],
easing: 'easeInOutSine',
}));
this.animations.push(anime({
targets: ['#blockFilter feTurbulence', ' #blockFilter feDisplacementMap'],
baseFrequency: 0,
scale: 1,
easing: 'easeOutSine',
delay: TIMES.TARGET_FADE_IN_DELAY,
duration: TIMES.TARGET_DURATION_MS,
}));
}
// this is necessary because
// skipping / timing / unmounting race conditions
// can cause the animations to cut short, this will ensure the values are reset
// because preact will recycle all these components
componentWillUnmount() {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Block);

View File

@ -1,89 +0,0 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Intercept extends Component {
constructor() {
super();
this.animations = [];
}
render({ player }) {
return (
<svg
class="skill-animation red"
version="1.1"
id="intercept"
xmlns="http://www.w3.org/2000/svg"
style={{
transform: player ? 'rotate3d(1, 0, 0, 180deg)' : '',
}}
viewBox="0 0 128 128">
<filter id='interceptFilter'>
<feTurbulence type="turbulence" baseFrequency="0" numOctaves="1" result="turbulence"></feTurbulence>
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="1" xChannelSelector="R" yChannelSelector="G"></feDisplacementMap>
</filter>
<g filter="url(#interceptFilter)">
<circle cx="64" cy="128" r="48" />
<circle cx="64" cy="128" r="32" />
<circle cx="64" cy="128" r="16" />
</g>
</svg>
);
}
componentDidMount() {
this.animations.push(anime({
targets: ['#intercept'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
}));
this.animations.push(anime({
targets: ['#intercept'],
scale: 3,
strokeWidth: 0,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInOutCubic',
}));
this.animations.push(anime({
targets: ['#interceptFilter feTurbulence', '#interceptFilter feDisplacementMap'],
baseFrequency: 2,
scale: 10,
numOctaves: 5,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
// this is necessary because
// skipping / timing / unmounting race conditions
// can cause the animations to cut short, this will ensure the values are reset
// because preact will recycle all these components
componentWillUnmount() {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Intercept);

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Refl extends Component { class Refl extends Component {
constructor() { constructor() {
super(); super();
@ -54,7 +46,7 @@ class Refl extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#reflect'], targets: ['#reflect'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -77,8 +69,7 @@ class Refl extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Refl); module.exports = Refl;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Restrict extends Component { class Restrict extends Component {
constructor() { constructor() {
super(); super();
@ -42,7 +34,7 @@ class Restrict extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#restrict'], targets: ['#restrict'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -52,12 +44,11 @@ class Restrict extends Component {
targets: ['#restrict'], targets: ['#restrict'],
scale: { scale: {
value: 1, value: 1,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.2, duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeInExpo', easing: 'easeInExpo',
}, },
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.1, delay: TIMES.TARGET_DURATION_MS * 0.1,
duration: TIMES.TARGET_DURATION_MS * 0.2, duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeOutSine', easing: 'easeOutSine',
})); }));
@ -69,7 +60,6 @@ class Restrict extends Component {
numOctaves: 5, numOctaves: 5,
easing: 'easeOutSine', easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -78,7 +68,7 @@ class Restrict extends Component {
d: 'M 124 8 L 8 124', d: 'M 124 8 L 8 124',
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.4, delay: TIMES.TARGET_DURATION_MS * 0.4,
duration: TIMES.TARGET_DURATION_MS * 0.4, duration: TIMES.TARGET_DURATION_MS * 0.4,
easing: 'easeOutSine', easing: 'easeOutSine',
})); }));
@ -88,7 +78,7 @@ class Restrict extends Component {
d: 'M 124 124 L 8 8', d: 'M 124 124 L 8 8',
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.4, delay: TIMES.TARGET_DURATION_MS * 0.4,
duration: TIMES.TARGET_DURATION_MS * 0.4, duration: TIMES.TARGET_DURATION_MS * 0.4,
easing: 'easeOutSine', easing: 'easeOutSine',
})); }));
@ -102,8 +92,7 @@ class Restrict extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Restrict); module.exports = Restrict;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Ruin extends Component { class Ruin extends Component {
constructor() { constructor() {
super(); super();
@ -75,7 +67,7 @@ class Ruin extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#ruin'], targets: ['#ruin'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -86,7 +78,6 @@ class Ruin extends Component {
rotate: 180, rotate: 180,
easing: 'linear', easing: 'linear',
loop: true, loop: true,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -99,12 +90,7 @@ class Ruin extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
try {
this.props.animCb && this.props.animCb();
} catch (e) {
console.log(e);
}
} }
} }
module.exports = addState(Ruin); module.exports = Ruin;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default; const anime = require('animejs').default;
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Silence extends Component { class Silence extends Component {
constructor() { constructor() {
super(); super();
@ -47,7 +39,7 @@ class Silence extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#silence'], targets: ['#silence'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -58,12 +50,11 @@ class Silence extends Component {
rotate: [90, 90], rotate: [90, 90],
scale: { scale: {
value: 1, value: 1,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.2, duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeInExpo', easing: 'easeInExpo',
}, },
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.1, delay: TIMES.TARGET_DURATION_MS * 0.1,
duration: TIMES.TARGET_DURATION_MS * 0.2, duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeOutSine', easing: 'easeOutSine',
})); }));
@ -73,7 +64,7 @@ class Silence extends Component {
d: 'M 124 8 L 8 124', d: 'M 124 8 L 8 124',
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.4, delay: TIMES.TARGET_DURATION_MS * 0.4,
duration: TIMES.TARGET_DURATION_MS * 0.4, duration: TIMES.TARGET_DURATION_MS * 0.4,
easing: 'easeOutSine', easing: 'easeOutSine',
})); }));
@ -83,7 +74,7 @@ class Silence extends Component {
d: 'M 124 124 L 8 8', d: 'M 124 124 L 8 8',
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.4, delay: TIMES.TARGET_DURATION_MS * 0.4,
duration: TIMES.TARGET_DURATION_MS * 0.4, duration: TIMES.TARGET_DURATION_MS * 0.4,
easing: 'easeOutSine', easing: 'easeOutSine',
})); }));
@ -97,8 +88,7 @@ class Silence extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Silence); module.exports = Silence;

View File

@ -1,19 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const duration = TIMES.TARGET_DURATION_MS;
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Siphon extends Component { class Siphon extends Component {
constructor() { constructor() {
super(); super();
@ -43,7 +33,7 @@ class Siphon extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: '.skill-anim', targets: '.skill-anim',
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.3 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.3 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.7, duration: TIMES.POST_SKILL_DURATION_MS }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.7, duration: TIMES.POST_SKILL_DURATION_MS },
], ],
})); }));
@ -51,8 +41,7 @@ class Siphon extends Component {
anime({ anime({
targets: '#siphon', targets: '#siphon',
r: 0, r: 0,
delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS,
duration,
easing: 'easeInSine', easing: 'easeInSine',
}); });
} }
@ -61,8 +50,7 @@ class Siphon extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Siphon); module.exports = Siphon;

View File

@ -1,18 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const duration = TIMES.TARGET_DURATION_MS;
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) { function projectile(x, y, radius, colour) {
return ( return (
@ -82,7 +73,7 @@ class SiphonTick extends Component {
anime({ anime({
targets: '#siphon', targets: '#siphon',
r: 600, r: 600,
duration: duration * 2 / 3, duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInSine', easing: 'easeInSine',
}); });
@ -92,8 +83,8 @@ class SiphonTick extends Component {
targets: proj, targets: proj,
cx: 150 + (Math.random() * 300 * (Math.random() < 0.5 ? -1 : 1)), cx: 150 + (Math.random() * 300 * (Math.random() < 0.5 ? -1 : 1)),
cy: 150 + (Math.random() * 300 * (Math.random() < 0.5 ? -1 : 1)), cy: 150 + (Math.random() * 300 * (Math.random() < 0.5 ? -1 : 1)),
delay: (Math.random() * duration * 1 / 2), delay: (Math.random() * TIMES.TARGET_DURATION_MS * 1 / 2),
duration, duration: TIMES.TARGET_DURATION_MS,
easing: 'easeOutQuad', easing: 'easeOutQuad',
}); });
}); });
@ -103,8 +94,7 @@ class SiphonTick extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(SiphonTick); module.exports = SiphonTick;

View File

@ -1,42 +1,17 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux'); const times = require('lodash/times');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
const addState = connect( const GREEN = '#1FF01F';
function receiveState(state) { const RED = '#a52a2a';
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) {
return (
<circle
cx={x}
cy={y}
stroke="none"
r={radius}
fill={colour}
/>
);
}
function sword(colour) {
return (
<polygon points='150,150 100,75, 150,300, 200,75' stroke="none" fill={colour} id="sword" filter="url(#slayFilter)"></polygon>
);
}
class Slay extends Component { class Slay extends Component {
constructor() { constructor() {
super(); super();
this.animations = []; this.animations = [];
this.colour = '#a52a2a';
const points = new Array(30).fill(0);
this.charges = points.map(() => projectile(150, 420, 7, '#1FF01F'));
} }
render() { render() {
@ -47,13 +22,16 @@ class Slay extends Component {
id="slay" id="slay"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 300 300"> viewBox="0 0 300 300">
<filter id="slayFilter"> {times(10, () => (
<feGaussianBlur stdDeviation="4"/> <ellipse
<feTurbulence type="turbulence" baseFrequency="0.001" numOctaves="3" result="turbulence"/> cx={anime.random(100, 200)}
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="1" xChannelSelector="A" yChannelSelector="A"/> cy={anime.random(-60, -30)}
</filter> stroke="none"
{sword(this.colour)} rx={anime.random(5, 10)}
{this.charges} ry={10}
fill={RED}
/>
))}
</svg> </svg>
); );
} }
@ -73,73 +51,33 @@ class Slay extends Component {
anime.set('#slay', { anime.set('#slay', {
rotate, rotate,
});
anime.set('#slay', {
translateY: -1 * (window.innerHeight) * 0.35,
translateX: 0,
});
anime.set('#slayFilter feDisplacementMap', {
scale: 0,
});
anime.set('#sword', {
fill: this.colour,
opacity: 1, opacity: 1,
}); });
this.animations.push(anime({ anime.set('#slay ellipse',{
targets: '#slay', fill: RED,
opacity: [ })
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{
value: 0,
delay: TIMES.TARGET_DURATION_MS + TIMES.POST_SKILL_DURATION_MS * 0.2,
duration: TIMES.POST_SKILL_DURATION_MS * 0.3,
}],
translateY: 0,
translateX: 0,
loop: false,
delay: TIMES.TARGET_DELAY_MS,
duration: (TIMES.TARGET_DURATION_MS * 0.5),
easing: 'easeInQuad',
}));
this.animations.push(anime({ this.animations.push(anime({
targets: ['#slayFilter feTurbulence', '#slayFilter feDisplacementMap'], targets: ['#slay ellipse'],
baseFrequency: 10, cx: 150,
scale: 100, cy: 325,
delay: (TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.5), duration: TIMES.TARGET_DURATION_MS * 0.2,
duration: (TIMES.TARGET_DURATION_MS * 0.5), duration: TIMES.TARGET_DURATION_MS * 0.4,
easing: 'easeInQuad', easing: 'easeOutQuad',
direction: 'alternate',
})); }));
this.animations.push(anime({ setTimeout(() => anime.set('#slay ellipse',{
targets: '#sword', fill: GREEN,
opacity: 0, }), TIMES.TARGET_DURATION_MS * 0.5);
delay: (TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS),
}));
const projectiles = document.querySelectorAll('#slay circle');
projectiles.forEach(proj => {
this.animations.push(anime({
targets: proj,
cx: Math.random() * 250 + 25,
cy: Math.random() * 200 - 100,
delay: (TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS + TIMES.POST_SKILL_DURATION_MS * 0.2),
duration: (TIMES.POST_SKILL_DURATION_MS * 0.3),
easing: 'easeInQuad',
}));
});
} }
componentWillUnmount() { componentWillUnmount() {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Slay); module.exports = Slay;

View File

@ -1,7 +1,6 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const { randomPoints } = require('../../utils'); const { randomPoints } = require('../../utils');
@ -9,13 +8,6 @@ const { randomPoints } = require('../../utils');
// logarithmic spiral lifted from // logarithmic spiral lifted from
// https://upload.wikimedia.org/wikipedia/commons/5/5b/Logarithmic_spiral_(1).svg // https://upload.wikimedia.org/wikipedia/commons/5/5b/Logarithmic_spiral_(1).svg
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) { function projectile(x, y, radius, colour) {
return ( return (
<circle <circle
@ -81,7 +73,7 @@ class Sleep extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#sleep'], targets: ['#sleep'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
})); }));
@ -91,32 +83,21 @@ class Sleep extends Component {
rotate: 180, rotate: 180,
easing: 'linear', easing: 'linear',
loop: true, loop: true,
delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS,
duration: TIMES.TARGET_DURATION_MS + TIMES.POST_SKILL_DURATION_MS,
})); }));
this.animations.push(anime({ this.animations.push(anime({
targets: ['#stun'], targets: ['#stun'],
opacity: [ opacity: [
{ value: 0, delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.8, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.8, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
})); }));
/* this.animations.push(anime({
targets: ['#heal'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.7, duration: TIMES.TARGET_DURATION_MS * 0.3 },
{ value: 0, delay: TIMES.POST_SKILL_DURATION_MS * 0.6, duration: TIMES.POST_SKILL_DURATION_MS * 0.4 },
],
easing: 'easeInOutSine',
}));
*/
this.animations.push(anime({ this.animations.push(anime({
targets: ['#charges'], targets: ['#charges'],
opacity: 1, opacity: 1,
delay: anime.stagger(TIMES.TARGET_DURATION_MS * 0.015, { start: TIMES.TARGET_DELAY_MS }), delay: anime.stagger(TIMES.TARGET_DURATION_MS * 0.01),
easing: 'easeInOutSine', easing: 'easeInOutSine',
})); }));
@ -125,11 +106,10 @@ class Sleep extends Component {
targets: ['#charges'], targets: ['#charges'],
cx: 0, cx: 0,
cy: 0, cy: 0,
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS + TIMES.POST_SKILL_DURATION_MS * 0.75, delay: TIMES.TARGET_DURATION_MS + TIMES.POST_SKILL_DURATION_MS * 0.75,
duration: TIMES.POST_SKILL_DURATION_MS * 0.25, duration: TIMES.POST_SKILL_DURATION_MS * 0.25,
easing: 'easeInOutSine', easing: 'easeInOutSine',
})); }));
} }
// this is necessary because // this is necessary because
@ -140,8 +120,7 @@ class Sleep extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Sleep); module.exports = Sleep;

View File

@ -1,31 +1,16 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default; const anime = require('animejs').default;
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Strike extends Component { class Strike extends Component {
constructor(props) { constructor() {
super(); super();
this.props = props;
this.animations = []; this.animations = [];
} }
render() { render() {
// const { x, y } = (this.props && this.props.direction) || { x: 0, y: 0 };
// const angle = (Math.atan(y / x) * (180 / Math.PI)) + 90;
// console.log(x, -y);
// console.log(angle);
// can't get this shit to work
return ( return (
<svg <svg
class="strike-anim" class="strike-anim"
@ -54,8 +39,8 @@ class Strike extends Component {
x: [200, 0, 200], x: [200, 0, 200],
height: [200, 10, 0], height: [200, 10, 0],
width: [20, 400, 0], width: [20, 400, 0],
delay: TIMES.TARGET_DELAY_MS * 0.5,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
delay: TIMES.TARGET_DURATION_MS * 0.2,
})); }));
this.animations.push(anime({ this.animations.push(anime({
@ -64,7 +49,7 @@ class Strike extends Component {
scale: 50, scale: 50,
numOctaves: 5, numOctaves: 5,
easing: 'easeOutSine', easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS + (TIMES.TARGET_DURATION_MS / 3), delay: TIMES.TARGET_DURATION_MS / 3,
duration: TIMES.TARGET_DURATION_MS / 2, duration: TIMES.TARGET_DURATION_MS / 2,
})); }));
} }
@ -77,9 +62,7 @@ class Strike extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Strike); module.exports = Strike;

View File

@ -1,20 +1,11 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants'); const { TIMES } = require('../../constants');
// logarithmic spiral lifted from
// https://upload.wikimedia.org/wikipedia/commons/5/5b/Logarithmic_spiral_(1).svg // https://upload.wikimedia.org/wikipedia/commons/5/5b/Logarithmic_spiral_(1).svg
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Stun extends Component { class Stun extends Component {
constructor() { constructor() {
super(); super();
@ -54,7 +45,7 @@ class Stun extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#stun'], targets: ['#stun'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -65,7 +56,6 @@ class Stun extends Component {
rotate: 180, rotate: 180,
easing: 'linear', easing: 'linear',
loop: true, loop: true,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
} }
@ -78,8 +68,7 @@ class Stun extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Stun); module.exports = Stun;

View File

@ -1,17 +1,9 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Sustain extends Component { class Sustain extends Component {
constructor() { constructor() {
super(); super();
@ -33,17 +25,17 @@ class Sustain extends Component {
</filter> </filter>
<polyline <polyline
id="stageOne" id="stageOne"
points='0 0' points='128,168 80,240 176,240 128,168'
style={{ filter: 'url("#sustainFilter")' }} style={{ filter: 'url("#sustainFilter")' }}
/> />
<polyline <polyline
id="stageTwo" id="stageTwo"
points='0 0' points='176,240 212,216 128,96 44,216 80,240'
style={{ filter: 'url("#sustainFilter")' }} style={{ filter: 'url("#sustainFilter")' }}
/> />
<polyline <polyline
id="stageThree" id="stageThree"
points='0 0' points='212,216 248,192 128,24 8,192 44,216'
style={{ filter: 'url("#sustainFilter")' }} style={{ filter: 'url("#sustainFilter")' }}
/> />
</svg> </svg>
@ -54,7 +46,7 @@ class Sustain extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#sustain'], targets: ['#sustain'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.8, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.8, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -62,17 +54,15 @@ class Sustain extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#stageOne'], targets: ['#stageOne'],
points: '128,168 80,240 176,240 128,168',
keyframes: [ keyframes: [
{ {
stroke: [COLOURS.GREEN, COLOURS.RED], stroke: [COLOURS.GREEN, COLOURS.RED],
fill: [null, COLOURS.RED], fill: [null, COLOURS.RED],
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.2, delay: TIMES.TARGET_DURATION_MS * 0.2,
duration: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.15,
easing: 'easeInOutSine', easing: 'easeInOutSine',
}, },
], ],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -82,13 +72,11 @@ class Sustain extends Component {
{ {
stroke: [COLOURS.GREEN, COLOURS.RED], stroke: [COLOURS.GREEN, COLOURS.RED],
fill: [null, COLOURS.RED], fill: [null, COLOURS.RED],
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.5, delay: TIMES.TARGET_DURATION_MS * 0.35,
duration: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.15,
easing: 'easeInOutSine', easing: 'easeInOutSine',
}, },
], ],
points: '176,240 212,216 128,96 44,216 80,240',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -98,13 +86,11 @@ class Sustain extends Component {
{ {
stroke: [COLOURS.GREEN, COLOURS.RED], stroke: [COLOURS.GREEN, COLOURS.RED],
fill: [null, COLOURS.RED], fill: [null, COLOURS.RED],
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.8, delay: TIMES.TARGET_DURATION_MS * 0.5,
duration: TIMES.TARGET_DURATION_MS * 0.4, duration: TIMES.TARGET_DURATION_MS * 0.15,
easing: 'easeInOutSine', easing: 'easeInOutSine',
}, },
], ],
points: '212,216 248,192 128,24 8,192 44,216',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS, duration: TIMES.TARGET_DURATION_MS,
})); }));
@ -114,7 +100,7 @@ class Sustain extends Component {
scale: 10, scale: 10,
numOctaves: 5, numOctaves: 5,
easing: 'easeOutSine', easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.4, delay: TIMES.TARGET_DURATION_MS * 0.55,
duration: TIMES.TARGET_DURATION_MS * 0.3 + TIMES.POST_SKILL_DURATION_MS, duration: TIMES.TARGET_DURATION_MS * 0.3 + TIMES.POST_SKILL_DURATION_MS,
})); }));
} }
@ -127,8 +113,7 @@ class Sustain extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Sustain); module.exports = Sustain;

View File

@ -6,13 +6,6 @@ const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const { randomPoints } = require('../../utils'); const { randomPoints } = require('../../utils');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) { function projectile(x, y, radius, colour) {
return ( return (
<circle <circle
@ -60,7 +53,7 @@ class Triage extends Component {
this.animations.push(anime({ this.animations.push(anime({
targets: ['#triage'], targets: ['#triage'],
opacity: [ opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 }, { value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
], ],
easing: 'easeInOutSine', easing: 'easeInOutSine',
@ -80,8 +73,7 @@ class Triage extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(Triage); module.exports = Triage;

View File

@ -1,7 +1,6 @@
const preact = require('preact'); const preact = require('preact');
const { Component } = require('preact'); const { Component } = require('preact');
const anime = require('animejs').default; const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants'); const { TIMES, COLOURS } = require('../../constants');
const { randomPoints } = require('../../utils'); const { randomPoints } = require('../../utils');
@ -18,17 +17,9 @@ function projectile(x, y, radius, colour) {
); );
} }
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class TriageTick extends Component { class TriageTick extends Component {
constructor(props) { constructor(props) {
super(); super();
this.team = props.team;
this.animations = []; this.animations = [];
const points = randomPoints(15, 10, { x: 0, y: 0, width: 300, height: 400 }); const points = randomPoints(15, 10, { x: 0, y: 0, width: 300, height: 400 });
this.charges = points.map(coord => projectile(coord[0], coord[1], 15, COLOURS.GREEN)); this.charges = points.map(coord => projectile(coord[0], coord[1], 15, COLOURS.GREEN));
@ -79,8 +70,7 @@ class TriageTick extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) { for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset(); this.animations[i].reset();
} }
this.props.animCb && this.props.animCb();
} }
} }
module.exports = addState(TriageTick); module.exports = TriageTick;

View File

@ -13,8 +13,8 @@ const { ConstructAnimation } = require('./animations');
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
const { animSource, animTarget, animText } = state; const { animating, animSource, animTarget, resolution, account } = state;
return { animSource, animTarget, animText }; return { animating, animSource, animTarget, resolution, account };
} }
); );
@ -43,6 +43,7 @@ class ConstructAvatar extends Component {
} }
onClick() { onClick() {
if (this.props.animating) return false;
return this.animations.push(wiggle(this.props.construct.id, this.idle)); return this.animations.push(wiggle(this.props.construct.id, this.idle));
} }
@ -62,19 +63,24 @@ class ConstructAvatar extends Component {
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { animSource, animTarget, animText, construct } = this.props; const { animSource, animTarget, resolution, construct, account } = this.props;
// a different text object and text construct // a different text object and text construct
if (animText && animText !== prevProps.animText && animText.constructId === construct.id) { if (resolution && resolution !== prevProps.resolution && resolution.event[1].construct === construct.id) {
return wiggle(construct.id, this.idle); const type = resolution.event[0];
// only trigger the wiggle on damage and ko events rather than spam it on everything
// also stops wiggle triggering when invert effect is applied
if (['Damage', 'Ko'].includes(type)) return wiggle(construct.id, this.idle);
} }
// different source object and source construct // different source object and source construct
if (animSource && animSource !== prevProps.animSource && animSource.constructId === construct.id) { if (animSource && animSource !== prevProps.animSource && animSource.event[1].construct === construct.id) {
return sourceCast(animSource.constructId, animSource.direction, this.idle); const { construct: constructId, player, direction: [x, y] } = animSource.event[1];
const animY = y && player === account.id ? -1 : y;
return sourceCast(constructId, { x, y: animY }, this.idle);
} }
// different target object and target construct // different target object and target construct
if (animTarget && animTarget !== prevProps.animTarget && animTarget.constructId.includes(construct.id)) { if (animTarget && animTarget !== prevProps.animTarget && animTarget.event[1].construct.includes(construct.id)) {
switch (animTarget.skill) { switch (animTarget.skill) {
case 'Banish': return banish(construct.id, this.idle); case 'Banish': return banish(construct.id, this.idle);
case 'Invert': return invert(construct.id, this.idle); case 'Invert': return invert(construct.id, this.idle);
@ -86,26 +92,23 @@ class ConstructAvatar extends Component {
shouldComponentUpdate(newProps) { shouldComponentUpdate(newProps) {
const { animSource, animTarget, animText, construct, mouseOver } = newProps; const { animSource, animTarget, resolution, construct, mouseOver } = newProps;
if (animSource !== this.props.animSource) return true; if (animSource !== this.props.animSource) return true;
if (animTarget !== this.props.animTarget) return true; if (animTarget !== this.props.animTarget) return true;
if (animText !== this.props.animText) return true; if (resolution !== this.props.resolution) return true;
if (construct !== this.props.construct) return true; if (construct !== this.props.construct) return true;
if (mouseOver !== this.props.mouseOver) return true; if (mouseOver !== this.props.mouseOver) return true;
return false; return false;
} }
} }
function ConstructText(props) { function ConstructName(props) {
const { construct } = props; const { construct } = props;
if (!construct) return false; if (!construct) return false;
return <h3 class={'name'}><span>{construct.name}</span></h3>;
const text = construct.name;
return <h3 class={'name'}><span>{text}</span></h3>;
} }
module.exports = { module.exports = {
ConstructAvatar: addState(ConstructAvatar), ConstructAvatar: addState(ConstructAvatar),
ConstructText, ConstructName,
}; };

View File

@ -10,6 +10,7 @@ const addState = connect(
function receiveState(state) { function receiveState(state) {
const { const {
ws, ws,
authenticated,
account, account,
game, game,
instance, instance,
@ -17,6 +18,7 @@ const addState = connect(
} = state; } = state;
return { return {
authenticated,
account, account,
game, game,
instance, instance,
@ -28,6 +30,7 @@ const addState = connect(
function Controls(args) { function Controls(args) {
const { const {
game, game,
authenticated,
account, account,
instance, instance,
nav, nav,
@ -38,6 +41,7 @@ function Controls(args) {
if (game) return <GameCtrl />; if (game) return <GameCtrl />;
if (instance) return <InstanceCtrl />; if (instance) return <InstanceCtrl />;
if (!authenticated) return false;
if (nav === 'play' || nav === 'shop' || nav === 'reshape' || !nav) return <PlayCtrl /> if (nav === 'play' || nav === 'shop' || nav === 'reshape' || !nav) return <PlayCtrl />
if (nav === 'team' || nav === 'account') return <TeamCtrl /> if (nav === 'team' || nav === 'account') return <TeamCtrl />

View File

@ -1,190 +0,0 @@
const { connect } = require('preact-redux');
const preact = require('preact');
// const actions = require('../actions');
const shapes = require('./shapes');
const { ConstructAvatar } = require('./construct');
// const { ConstructAnimation } = require('./animations');
const addState = connect(
function receiveState(state) {
const {
account,
itemInfo,
demo,
} = state;
return {
account,
itemInfo,
demo,
};
}
/* function receiveDispatch(dispatch) {
function setAnimTarget(anim) {
dispatch(actions.setAnimTarget(anim));
}
return { setAnimTarget };
} */
);
function Demo(args) {
const {
demo,
itemInfo,
account,
// setAnimTarget,
} = args;
if (!demo || !itemInfo.items.length || account) return false;
const { combiner, items, equipping, equipped, players, combo } = demo;
const vboxDemo = () => {
function stashBtn(i, j) {
if (!i) return <button disabled class='empty' >&nbsp;</button>;
const highlighted = combiner.indexOf(j) > -1;
const classes = `${highlighted ? 'highlight' : ''}`;
if (shapes[i]) {
return <button class={classes} key={j}>{shapes[i]()}</button>;
}
return <button class={classes}>{i}</button>;
}
function combinerBtn() {
let text = '';
if (combiner.length < 3) {
for (let i = 0; i < 3; i++) {
if (combiner.length > i) {
text += '■ ';
} else {
text += '▫ ';
}
}
} else {
text = 'combine';
}
return (
<button
class='vbox-btn'
disabled={combiner.length !== 3}>
{text}
</button>
);
}
function stashElement() {
return (
<div class="vbox">
<div class='vbox-section'>
<h2 class='colour-info'>
VBOX PHASE {shapes.Red()} {shapes.Green()} {shapes.Blue()}
</h2>
<p>
Combine colours with base skills and specialisations to build an array of powerful variants.
</p>
</div>
<div>&nbsp;</div>
<div class='vbox-section'>
<div class='vbox-items'>
{items.map((i, j) => stashBtn(i, j))}
</div>
{combinerBtn()}
</div>
</div>
);
}
return (
<div class="news vbox-demo">
{stashElement()}
</div>
);
};
const vboxConstructs = () => {
const btnClass = equipping
? 'equipping empty gray'
: 'empty gray';
const constructEl = c => (
<div class="instance-construct">
<h2 class="name" >{c.name}</h2>
<ConstructAvatar construct={c} />
<div class="skills">
{equipped
? <button>{combo}</button>
: <button disabled={!equipping} class={btnClass}>SKILL</button>
}
<button disabled={!equipping} class={btnClass}>SKILL</button>
<button disabled={!equipping} class={btnClass}>SKILL</button>
</div>
<div class="specs">
</div>
<div class="stats">
</div>
</div>
);
return (
<section class="construct-section">
<div>
<h2>CONSTRUCTS</h2>
<p><b>Constructs</b> are the units you control. They are reset every game and their initial appearance is randomly generated.</p>
<p><b>Skills</b> and <b>Specs</b> you create in the <b>VBOX Phase</b> are equipped to your constructs to create a build.</p>
</div>
<div class='construct-list'>
{constructEl(players[0].constructs[0])}
</div>
</section>
);
};
const gameDemo = () => {
return (
<section class="game-demo">
<div>
<h2>COMBAT PHASE</h2>
<p>Battle your opponent using dynamic team builds from the VBOX phase.</p>
<p>The skills crafted can be used to damage the opponent or support your team.</p>
<p>Simultaneous turn based combat: each team picks targets for their skills during this phase.</p>
<p>The damage dealt by skills, cast order and construct life depend on your decisions in the VBOX phase.</p>
</div>
<div class="game">
<div class="game-construct">
<div class="left"></div>
<div class="right">
<ConstructAvatar construct={players[1].constructs[0]} />
</div>
</div>
<div></div>
<div class="game-construct">
<div class="left"></div>
<div class="right">
<ConstructAvatar construct={players[1].constructs[1]} />
</div>
</div>
</div>
</section>
);
};
return (
<section class='demo news top'>
{gameDemo()}
{vboxDemo()}
{vboxConstructs()}
</section>
);
}
module.exports = addState(Demo);

View File

@ -0,0 +1,85 @@
// const { connect } = require('preact-redux');
const preact = require('preact');
const { connect } = require('preact-redux');
const { errorToast, infoToast } = require('../utils');
const actions = require('./../actions');
const VERSION = process.env.npm_package_version;
const Welcome = require('./welcome');
const addState = connect(
function receiveState(state) {
const {
ws,
account,
} = state;
function sendInstancePractice() {
ws.sendInstancePractice();
}
return {
account,
sendInstancePractice,
};
},
);
function Play(args) {
const {
account,
sendInstancePractice,
} = args;
const news = (
<div class="list">
<div class="intro">
<p> MNML is a turn-based 1v1 strategy game in an abstract setting. </p>
<p>
Build a unique team of 3 constructs from a range of skills and specialisations.<br />
Outplay your opponent across multiple rounds by adapting to an always shifting meta. <br />
</p>
</div>
<div class="awards"></div>
</div>
);
const list = () => {
return (
<div class='list play'>
<figure>
<button
class="ready"
onClick={() => sendInstancePractice()}>
Play
</button>
<figcaption>Learn MNML</figcaption>
</figure>
<figure>
<button
class='discord-btn'
onClick={() => window.open('https://discord.gg/YJJgurM') }>
&nbsp;
</button>
<figcaption>Join the Community</figcaption>
</figure>
</div>
);
};
return (
<main>
<div class="logo"/>
<hr />
{list()}
<hr />
<Welcome />
<hr />
{news}
</main>
);
}
module.exports = addState(Play);

View File

@ -0,0 +1,89 @@
const preact = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const reactStringReplace = require('react-string-replace');
const shapes = require('./shapes');
const { removeTier } = require('../utils');
const { TIMES } = require('./../constants');
const addState = connect(({ resolution, itemInfo }) => ({ resolution, itemInfo }));
class AnimText extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.resolution !== this.props.resolution) return true;
return false;
}
componentDidUpdate(prevProps) {
const { resolution, construct } = this.props;
if (resolution && resolution !== prevProps.resolution && resolution.event[1].construct === construct.id) {
anime({
targets: '.combat-text',
top: '40%',
duration: TIMES.POST_SKILL_DURATION_MS - 500,
easing: 'easeOutQuad',
});
}
}
render() {
const { construct, resolution, itemInfo } = this.props;
if (resolution && resolution.event[1].construct === construct.id) {
const itemSourceDescription = () => {
const itemSource = itemInfo.combos.filter(c => c.item === removeTier(resolution.skill));
const itemSourceInfo = itemSource.length
? `${itemSource[0].components[0]} ${itemSource[0].components[1]} ${itemSource[0].components[2]}`
: false;
const itemRegEx = /(Red|Blue|Green)/;
return reactStringReplace(itemSourceInfo, itemRegEx, match => shapes[match]());
};
const generateAnimText = () => {
const [type, event] = resolution.event;
switch (type) {
case 'Damage': {
const { amount, mitigation, colour } = event;
const mitigationText = mitigation ? `(${mitigation})` : '';
return <h1><span class={colour.toLowerCase()}>-{amount} {mitigationText} </span></h1>;
}
case 'Healing': {
const { amount, overhealing, colour } = event;
const overHealingText = overhealing ? `(${overhealing} OH)` : '';
return <h1><span class={colour.toLowerCase()}>+{amount} {overHealingText}</span></h1>;
}
case 'Effect': {
const { effect, duration } = event;
return <h1><span>+{effect} {duration}T</span></h1>;
}
case 'Removal': {
const { effect } = event;
if (!effect) return <h1><span>Effect Removal</span></h1>;
return <h1><span>-{effect}</span></h1>;
}
case 'Ko': return <h1><span>KO!</span></h1>;
case 'Reflection': return <h1><span>REFLECT</span></h1>;
default: return false;
}
};
// We don't send inversion / disable / immune event text
/* case 'Inversion': return <h1><span>INVERT</span></h1>;
case 'Disable': {
const { disable } = event;
return <h1><span>{disable}</span></h1>;
}
case 'Immunity': return <h1><span>IMMUNE</span></h1>; */
return (
<div class="combat-text">
<h2><span>{resolution.skill}</span></h2>
<span>{itemSourceDescription()}</span>
{generateAnimText()}
</div>
);
}
return null;
}
}
module.exports = addState(AnimText);

View File

@ -0,0 +1,73 @@
const preact = require('preact');
const { connect } = require('preact-redux');
const reactStringReplace = require('react-string-replace');
const actions = require('../actions');
const shapes = require('./shapes');
const { INFO } = require('./../constants');
const addState = connect(
({ resolution, itemInfo, gameSkillInfo }) => ({ resolution, itemInfo, gameSkillInfo }),
function receiveDispatch(dispatch) {
function setGameEffectInfo(info) {
dispatch(actions.setGameEffectInfo(info));
}
return { setGameEffectInfo };
}
);
class GameConstructEffects extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.resolution && newProps.resolution !== this.props.resolution) {
const [type, info] = newProps.resolution.event;
if (info.construct === this.props.construct.id
&& (type === 'Effect' || type === 'Removal')) return true;
}
if (newProps.construct !== this.props.construct) return true;
return false;
}
render() {
const {
resolution,
construct,
gameSkillInfo,
setGameEffectInfo,
itemInfo,
} = this.props;
function hoverInfo(e, info) {
e.stopPropagation();
return setGameEffectInfo(info);
}
if (gameSkillInfo && gameSkillInfo.constructId === construct.id) {
const fullInfo = itemInfo.items.find(k => k.item === gameSkillInfo.skill) || INFO[gameSkillInfo.skill];
const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat)/;
const infoDescription = reactStringReplace(fullInfo.description, regEx, match => shapes[match]());
const speed = <span> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </span>;
return (
<div class="skill-description">
<h2><span> {gameSkillInfo.skill} </span></h2>
<span>{infoDescription} </span> <br />
{speed}
</div>);
}
const effects = resolution ? resolution.event[1].display.effects : construct.effects;
const renderEffects = effects.length
? effects.map(c =>
<div key={c.effect}>
<span key={c.effect} onMouseOver={e => hoverInfo(e, c)}
onMouseOut={e => hoverInfo(e, null)}> {c.effect} - {c.duration}T
</span>
</div>)
: null;
return (<div class="effects"> {renderEffects} </div>);
}
}
module.exports = addState(GameConstructEffects);

View File

@ -1,95 +1,23 @@
const preact = require('preact'); const preact = require('preact');
const { connect } = require('preact-redux'); const { connect } = require('preact-redux');
const anime = require('animejs').default;
const range = require('lodash/range'); const range = require('lodash/range');
const reactStringReplace = require('react-string-replace');
const { STATS, removeTier } = require('../utils'); const { ConstructAvatar, ConstructName } = require('./construct');
const { ConstructAvatar, ConstructText } = require('./construct');
const shapes = require('./shapes');
const { INFO, TIMES } = require('./../constants');
const actions = require('../actions');
const SkillBtn = require('./skill.btn'); const SkillBtn = require('./game.construct.skill.btn');
const ConstructAnimationText = require('./game.construct.anim.text');
const addStateText = connect(({ animText, itemInfo }) => ({ animText, itemInfo })); const ConstructLife = require('./game.construct.life');
const ConstructEffectBox = require('./game.construct.effect.box');
class combatText extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.animText !== this.props.animText) return true;
return false;
}
componentDidUpdate(prevProps) {
const { animText, construct } = this.props;
if (animText && animText !== prevProps.animText && animText.constructId === construct.id) {
anime({
targets: '.combat-text',
top: '40%',
duration: TIMES.POST_SKILL_DURATION_MS - 500,
easing: 'easeOutQuad',
});
}
}
render(props) {
const { construct, animText, itemInfo } = props;
if (animText && animText.constructId === construct.id) {
const itemSourceDescription = () => {
const itemSource = itemInfo.combos.filter(c => c.item === removeTier(animText.skill));
const itemSourceInfo = itemSource.length
? `${itemSource[0].components[0]} ${itemSource[0].components[1]} ${itemSource[0].components[2]}`
: false;
const itemRegEx = /(Red|Blue|Green)/;
return reactStringReplace(itemSourceInfo, itemRegEx, match => shapes[match]());
};
const animationTextHtml = () => {
// monkaW hack to make red / blue recharge work nicely
if (animText.css === 'rb') {
const text = animText.text.split(' ');
return (
<h1>
<span class="red">{text[0]}</span>&nbsp;
<span class="blue">{text[1]}</span>
</h1>
);
}
return (
<h1 class={animText.css}>
<span>{animText.text}</span>
</h1>
);
};
return (
<div class="combat-text">
<h2><span>{animText.skill}</span></h2>
<span>{itemSourceDescription()}</span>
{animationTextHtml()}
</div>
);
}
return null;
}
}
const ConstructAnimationText = addStateText(combatText);
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
const { const {
ws, ws,
game, game,
account,
activeSkill, activeSkill,
animFocus, animFocus,
animating, resolution,
animText,
gameSkillInfo,
itemInfo,
tutorialGame,
} = state; } = state;
function selectSkillTarget(targetConstructId) { function selectSkillTarget(targetConstructId) {
@ -100,65 +28,23 @@ const addState = connect(
} }
return { return {
game,
account,
animating,
animFocus,
animText,
activeSkill, activeSkill,
animFocus,
resolution,
selectSkillTarget, selectSkillTarget,
gameSkillInfo,
itemInfo,
tutorialGame,
}; };
},
function receiveDispatch(dispatch) {
function setGameEffectInfo(info) {
dispatch(actions.setGameEffectInfo(info));
}
function setTutorialGameClear(activeSkill, tutorialGame) {
if (activeSkill && tutorialGame) dispatch(actions.setTutorialGame(null));
}
return { setGameEffectInfo, setTutorialGameClear };
} }
); );
const eventClasses = (animating, animFocus, construct, postSkill) => {
if (!animating) return '';
if (!postSkill) {
if (animFocus.includes(construct.id)) return '';
return 'unfocus';
}
if (postSkill.constructId !== construct.id) {
if (animFocus.includes(construct.id)) return '';
return 'unfocus';
}
if (postSkill.effects) construct.effects = postSkill.effects;
construct.green_life.value = postSkill.life.green;
construct.red_life.value = postSkill.life.red;
construct.blue_life.value = postSkill.life.blue;
return postSkill.css;
};
class GameConstruct extends preact.Component { class GameConstruct extends preact.Component {
constructor() {
super();
this.resolvedLength = 0;
}
shouldComponentUpdate(newProps) { shouldComponentUpdate(newProps) {
if (newProps.activeSkill !== this.props.activeSkill) return true; if (newProps.activeSkill !== this.props.activeSkill) return true;
if (newProps.animFocus !== this.props.animFocus) return true; if (newProps.animFocus !== this.props.animFocus) return true;
if (newProps.animText !== this.props.animText) return true;
if (newProps.animating !== this.props.animating) return true;
if (newProps.construct !== this.props.construct) return true; if (newProps.construct !== this.props.construct) return true;
if (newProps.player !== this.props.player) return true; if (newProps.resolution && newProps.resolution !== this.props.resolution) {
if (newProps.tutorialGame !== this.props.tutorialGame) return true; const [type, variant] = newProps.resolution.event;
if (newProps.gameSkillInfo !== this.props.gameSkillInfo) return true; if (variant.construct === this.props.construct.id && type === 'Ko') return true;
}
return false; return false;
} }
@ -167,84 +53,46 @@ class GameConstruct extends preact.Component {
// Changing state variables // Changing state variables
activeSkill, activeSkill,
animFocus, animFocus,
animText, resolution,
animating, selectSkillTarget,
construct, construct,
player, player,
tutorialGame,
gameSkillInfo,
// Constants
i,
itemInfo,
// Functions
selectSkillTarget,
setTutorialGameClear,
setGameEffectInfo,
} = this.props; } = this.props;
const koEvent = animText ? animText.text === 'KO!' && animText.constructId === construct.id : false; // construct green_life comes from game state and won't update during animations
const ko = construct.green_life.value === 0 && !koEvent ? 'ko' : ''; // treat the construct as ko for the remainder of the anims if ko event occurs
const classes = eventClasses(animating, animFocus, construct, animText); const ko = construct.green_life.value === 0 || this.ko ? 'ko' : '';
const cssClass = ['ko-transition', 'unfocus'].includes(classes) ? classes : null; const koEvent = () => {
if (resolution) {
const stats = ['RedLife', 'GreenLife', 'BlueLife'].map((s, j) => ( const [type, variant] = resolution.event;
<div key={j} alt={STATS[s].stat}> if (variant.construct === construct.id && type === 'Ko') {
{shapes[s]()} this.ko = true;
<div class="max" >{construct[STATS[s].stat].value} / {construct[STATS[s].stat].max}</div> return 'ko-transition';
<div class="value" >{construct[STATS[s].stat].value}</div> }
</div>
));
const skills = range(0, 3)
.map(j => <SkillBtn key={j} construct={construct} i={j} j={i} animating={animating} />);
let crypSkills = <div></div>;
if (player) crypSkills = (<div class="skills"> {skills} </div>);
function hoverInfo(e, info) {
e.stopPropagation();
return setGameEffectInfo(info);
}
const effectBox = () => {
if (gameSkillInfo && gameSkillInfo.constructId === construct.id) {
const fullInfo = itemInfo.items.find(k => k.item === gameSkillInfo.skill) || INFO[gameSkillInfo.skill];
const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat)/;
const infoDescription = reactStringReplace(fullInfo.description, regEx, match => shapes[match]());
const speed = <span> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </span>;
return (
<div class="skill-description">
<h2><span> {gameSkillInfo.skill} </span></h2>
<span>{infoDescription} </span> <br />
{speed}
</div>);
} }
const effects = construct.effects.length return '';
? construct.effects.map(c =>
<div key={c.effect}>
<span key={c.effect} onMouseOver={e => hoverInfo(e, c)}
onMouseOut={e => hoverInfo(e, null)}> {c.effect} - {c.duration}T
</span>
</div>)
: null;
return (<div class="effects"> {effects} </div>);
}; };
const unfocus = animFocus && !animFocus.includes(construct.id) ? 'unfocus' : '';
const crypSkills = player
? <div class="skills"> {range(0, 3).map(j => <SkillBtn key={j} construct={construct} i={j} />)} </div>
: <div></div>;
return ( return (
<div <div
onClick={() => { onClick={() => selectSkillTarget(construct.id)}
selectSkillTarget(construct.id);
setTutorialGameClear(activeSkill, tutorialGame);
}}
style={ activeSkill ? { cursor: 'pointer' } : {}} style={ activeSkill ? { cursor: 'pointer' } : {}}
class={`game-construct ${ko} ${cssClass}`}> class={`game-construct ${ko} ${koEvent()} ${unfocus}`}>
<div class="left"> <div class="left">
{crypSkills} {crypSkills}
{effectBox()} <ConstructEffectBox construct={construct} />
</div> </div>
<div class="right"> <div class="right">
<div class="stats"> {stats} </div> <ConstructLife construct={construct} />
<ConstructAvatar construct={construct} /> <ConstructAvatar construct={construct} />
<ConstructText construct={construct} /> <ConstructName construct={construct} />
<ConstructAnimationText construct={construct} /> <ConstructAnimationText construct={construct} />
</div> </div>
</div> </div>

View File

@ -0,0 +1,49 @@
const preact = require('preact');
const { connect } = require('preact-redux');
const shapes = require('./shapes');
const addState = connect(({ resolution }) => ({ resolution }));
class GameConstructLife extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.resolution && newProps.resolution !== this.props.resolution) {
const [type, variant] = newProps.resolution.event;
if (variant.construct === this.props.construct.id
&& (type === 'Damage' || type === 'Healing')) return true;
}
if (newProps.construct !== this.props.construct) return true;
return false;
}
render() {
const { construct, resolution } = this.props;
const lifeBars = (redLife, greenLife, blueLife) => {
return (
<div class="stats">
<div>
{shapes.RedLife()}
<div class="max" >{redLife} / {construct.red_life.max}</div>
</div>
<div>
{shapes.GreenLife()}
<div class="max" >{greenLife} / {construct.green_life.max}</div>
</div>
<div>
{shapes.BlueLife()}
<div class="max" >{blueLife} / {construct.blue_life.max}</div>
</div>
</div>
);
};
if (resolution) {
const { red, blue, green } = resolution.event[1].display;
return lifeBars(red, green, blue);
}
return lifeBars(construct.red_life.value, construct.green_life.value, construct.blue_life.value);
}
}
module.exports = addState(GameConstructLife);

View File

@ -10,11 +10,13 @@ const addState = connect(
const { const {
activeSkill, activeSkill,
game, game,
animating,
} = state; } = state;
return { return {
activeSkill, activeSkill,
game, game,
animating,
}; };
}, },

View File

@ -8,6 +8,7 @@ const addState = connect(
const { const {
ws, ws,
account, account,
authenticated,
nav, nav,
} = state; } = state;
@ -22,6 +23,7 @@ const addState = connect(
return { return {
account, account,
authenticated,
nav, nav,
sendInstanceState, sendInstanceState,
@ -48,6 +50,7 @@ const addState = connect(
function Header(args) { function Header(args) {
const { const {
account, account,
authenticated,
nav, nav,
sendAccountStates, sendAccountStates,
@ -56,6 +59,8 @@ function Header(args) {
if (!account) return false; if (!account) return false;
if (!authenticated) return false;
function navTo(p) { function navTo(p) {
return setNav(p); return setNav(p);
} }
@ -68,11 +73,6 @@ function Header(args) {
return ( return (
<header> <header>
<div class="options"> <div class="options">
<button
onClick={() => navTo('play')}
class='logo login-btn'>
&nbsp;
</button>
<button <button
onClick={() => navTo('play')} onClick={() => navTo('play')}
class={`login-btn ${nav === 'play' ? 'highlight' : ''}`}> class={`login-btn ${nav === 'play' ? 'highlight' : ''}`}>

View File

@ -23,6 +23,7 @@ const addState = connect(
function Top(args) { function Top(args) {
const { const {
nav, nav,
authenticated,
} = args; } = args;
if (nav === 'account') return <AccountTop />; if (nav === 'account') return <AccountTop />;

View File

@ -4,27 +4,38 @@ const { connect } = require('preact-redux');
const Main = require('./main'); const Main = require('./main');
// const Nav = require('./nav'); // const Nav = require('./nav');
const Controls = require('./controls'); const Controls = require('./controls');
const FrontPage = require('./front.page');
const Noise = require('./noise');
const addState = connect( const addState = connect(
({ game, instance }) => ({ game, instance }) ({ game, instance, authenticated }) => ({ game, instance, authenticated })
); );
function Mnml(args) { function Mnml(args) {
const { const {
game, game,
instance, instance,
authenticated,
} = args; } = args;
const rotateClass = (game || instance) && window.innerHeight < 900 && window.innerWidth < window.innerHeight const rotateClass = (game || instance) && window.innerHeight < 900 && window.innerWidth < window.innerHeight
? 'show' ? 'show'
: ''; : '';
if (!authenticated && !instance && !game) return (
<div id="mnml" class='front-page'>
<Noise />
<FrontPage />
<div id="rotate" class={rotateClass} ></div>
</div>
);
return ( return (
<div id="mnml"> <div id="mnml">
<Main /> <Main />
<Controls /> <Controls />
<div id="rotate" class={rotateClass} > <Noise />
</div> <div id="rotate" class={rotateClass} ></div>
</div> </div>
); );
} }

View File

@ -0,0 +1,75 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
class Noise extends Component {
constructor() {
super();
this.animations = [];
}
render() {
return (
<svg
version="1.1"
id="noise"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 400 400">
<filter id='noiseFilter'>
<feTurbulence type="turbulence" baseFrequency="0.2" numOctaves="2" result="turbulence"></feTurbulence>
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="2" xChannelSelector="R" yChannelSelector="G"></feDisplacementMap>
</filter>
</svg>
);
}
componentDidMount() {
this.animations.push(anime({
targets: ['#noiseFilter feTurbulence', '#noiseFilter feDisplacementMap'],
easing: 'linear',
loop: true,
keyframes: [
{
baseFrequency: 0.5,
duration: () => anime.random(1000, 2000),
},
],
}));
this.animations.push(anime({
targets: ['#noiseFilter feDisplacementMap'],
easing: 'linear',
loop: true,
keyframes: [
{
scale: 2,
duration: () => anime.random(2000, 5000),
},
{
scale: 4,
duration: () => anime.random(150, 250),
},
{
scale: 2,
duration: () => anime.random(100, 150),
},
{
scale: 4,
duration: () => anime.random(150, 250),
},
],
}));
}
// this is necessary because
// skipping / timing / unmounting race conditions
// can cause the animations to cut short, this will ensure the values are reset
// because preact will recycle all these components
componentWillUnmount() {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
}
}
module.exports = Noise;

View File

@ -6,11 +6,11 @@ const reactStringReplace = require('react-string-replace');
const throttle = require('lodash/throttle'); const throttle = require('lodash/throttle');
const shapes = require('./shapes'); const shapes = require('./shapes');
const { effectInfo, removeTier } = require('../utils'); const { effectInfo } = require('../utils');
const addState = connect( const addState = connect(
({ game, account, animSkill, animating, itemInfo, gameEffectInfo, tutorialGame }) => ({ game, account, animating, itemInfo, gameEffectInfo, tutorialGame }) =>
({ game, account, animSkill, animating, itemInfo, gameEffectInfo, tutorialGame }) ({ game, account, animating, itemInfo, gameEffectInfo, tutorialGame })
); );
class TargetSvg extends Component { class TargetSvg extends Component {
@ -31,7 +31,6 @@ class TargetSvg extends Component {
if (newProps.game !== this.props.game) return true; if (newProps.game !== this.props.game) return true;
if (newProps.account !== this.props.account) return true; if (newProps.account !== this.props.account) return true;
if (newProps.animating !== this.props.animating) return true; if (newProps.animating !== this.props.animating) return true;
if (newProps.animSkill !== this.props.animSkill) return true;
if (newProps.gameEffectInfo !== this.props.gameEffectInfo) return true; if (newProps.gameEffectInfo !== this.props.gameEffectInfo) return true;
if (newProps.tutorialGame !== this.props.tutorialGame) return true; if (newProps.tutorialGame !== this.props.tutorialGame) return true;
if (newState.width !== this.state.width) return true; if (newState.width !== this.state.width) return true;
@ -44,12 +43,9 @@ class TargetSvg extends Component {
// Changing State Variables // Changing State Variables
account, account,
animating, animating,
animSkill,
game, game,
gameEffectInfo, gameEffectInfo,
tutorialGame, tutorialGame,
// Static
itemInfo,
} = props; } = props;
const { width, height } = state; const { width, height } = state;
@ -87,14 +83,14 @@ class TargetSvg extends Component {
const otherTeam = game.players.find(t => t.id !== account.id); const otherTeam = game.players.find(t => t.id !== account.id);
const playerTeamIds = playerTeam.constructs.map(c => c.id); const playerTeamIds = playerTeam.constructs.map(c => c.id);
const outgoing = game.stack.filter(stack => playerTeamIds.includes(stack.source_construct_id)); const outgoing = game.stack.filter(stack => stack.player === account.id);
function getPath(cast) { function getPath(cast) {
const source = playerTeam.constructs.findIndex(c => c.id === cast.source_construct_id); const source = playerTeam.constructs.findIndex(c => c.id === cast.source);
const defensive = playerTeamIds.includes(cast.target_construct_id); const defensive = playerTeamIds.includes(cast.target);
const target = defensive const target = defensive
? playerTeam.constructs.findIndex(c => c.id === cast.target_construct_id) ? playerTeam.constructs.findIndex(c => c.id === cast.target)
: otherTeam.constructs.findIndex(c => c.id === cast.target_construct_id); : otherTeam.constructs.findIndex(c => c.id === cast.target);
const skillNumber = window.innerWidth <= 800 // mobile styling trigger const skillNumber = window.innerWidth <= 800 // mobile styling trigger
? playerTeam.constructs[source].skills.findIndex(s => s.skill === cast.skill) ? playerTeam.constructs[source].skills.findIndex(s => s.skill === cast.skill)

View File

@ -71,7 +71,6 @@ class Combos extends preact.Component {
<div class="combos"> <div class="combos">
<div class="combo-header"> <div class="combo-header">
<h2>COMBOS</h2> <h2>COMBOS</h2>
Combine colours and items.
</div> </div>
<div class="combo-list" <div class="combo-list"
onMouseOver={e => e.stopPropagation()} onMouseOver={e => e.stopPropagation()}

View File

@ -5,10 +5,9 @@ const Login = require('./welcome.login');
const Register = require('./welcome.register'); const Register = require('./welcome.register');
const Help = require('./welcome.help'); const Help = require('./welcome.help');
// const About = require('./welcome.about'); // const About = require('./welcome.about');
const Demo = require('./demo');
function Welcome() { function Welcome() {
const page = this.state.page || 'register'; const page = this.state.page || 'login';
const pageEl = () => { const pageEl = () => {
if (page === 'login') return <Login />; if (page === 'login') return <Login />;
@ -17,65 +16,32 @@ function Welcome() {
return false; return false;
}; };
const news = ( const form = <div>{pageEl()}</div>;
<div class="news">
<p> Welcome to mnml.</p>
<p> MNML is a turn-based 1v1 strategy game in an abstract setting. </p>
<p>
Build a unique team of 3 constructs from a range of skills and specialisations.<br />
Outplay your opponent in multiple rounds by adapting to an always shifting meta. <br />
Simple rules, complex interactions and unique mechanics.<br />
</p>
<p> Free to play, no pay to win. Register to start playing.<br /></p>
<a href='https://www.youtube.com/watch?v=VtZLlkpJuS8'>Tutorial Playthrough on YouTube</a>
</div>
);
const main = (['login', 'register', 'help'].includes(page))
? <section>{news}{pageEl()}</section>
: <Demo />;
return ( return (
<main class="menu welcome"> <header>
<header> <div class="options">
<div class="options"> <button
<button class={`login-btn ${page === 'login' ? 'highlight' : ''}`}
onClick={() => this.setState({ page: 'login' })} disabled={page === 'login'}
class='logo login-btn'> onClick={() => this.setState({ page: 'login' })}>
&nbsp; Login
</button> </button>
<button <button
class={`login-btn ${page === 'login' ? 'highlight' : ''}`} class={`login-btn ${page === 'register' ? 'highlight' : ''}`}
disabled={page === 'login'} disabled={page === 'register'}
onClick={() => this.setState({ page: 'login' })}> onClick={() => this.setState({ page: 'register' })}>
Login Register
</button> </button>
<button <button
class={`login-btn ${page === 'register' ? 'highlight' : ''}`} class={`login-btn ${page === 'help' ? 'highlight' : ''}`}
disabled={page === 'register'} disabled={page === 'help'}
onClick={() => this.setState({ page: 'register' })}> onClick={() => this.setState({ page: 'help' })}>
Register Help
</button> </button>
<button
class={`login-btn ${page === 'info' ? 'highlight' : ''}`}
disabled={page === 'info'}
onClick={() => this.setState({ page: 'info' })}>
Info
</button>
<button
class={`login-btn ${page === 'help' ? 'highlight' : ''}`}
disabled={page === 'help'}
onClick={() => this.setState({ page: 'help' })}>
Help
</button>
</div>
</header>
<div class="top">
{main}
</div> </div>
</main> {form}
</header>
); );
} }

View File

@ -5,8 +5,7 @@ const eachSeries = require('async/eachSeries');
const sample = require('lodash/sample'); const sample = require('lodash/sample');
const actions = require('./actions'); const actions = require('./actions');
const { TIMES } = require('./constants'); const { setAnimations, clearAnimations } = require('./animations.utils');
const animations = require('./animations.utils');
const { infoToast, errorToast } = require('./utils'); const { infoToast, errorToast } = require('./utils');
const { tutorialVbox } = require('./tutorial.utils'); const { tutorialVbox } = require('./tutorial.utils');
@ -28,10 +27,13 @@ function registerEvents(store) {
function clearTutorial() { function clearTutorial() {
store.dispatch(actions.setTutorial(null)); store.dispatch(actions.setTutorial(null));
localStorage.setItem('tutorial-complete', true);
} }
function clearTutorialGame() {
store.dispatch(actions.setTutorialGame(null));
}
function setPing(ping) { function setPing(ping) {
store.dispatch(actions.setPing(ping)); store.dispatch(actions.setPing(ping));
} }
@ -73,67 +75,23 @@ function registerEvents(store) {
} }
function setGame(game) { function setGame(game) {
const { game: currentGame, account, ws, animating } = store.getState(); const { game: currentGame, ws, animating } = store.getState();
if (animating) return false; if (animating) return false;
if (game && currentGame) { if (game && currentGame) {
if (game.resolved.length !== currentGame.resolved.length) { if (game.resolutions.length !== currentGame.resolutions.length) {
store.dispatch(actions.setAnimating(true)); store.dispatch(actions.setAnimating(true));
store.dispatch(actions.setGameSkillInfo(null)); store.dispatch(actions.setGameSkillInfo(null));
// stop fetching the game state til animations are done // stop fetching the game state til animations are done
const newRes = game.resolved.slice(currentGame.resolved.length); const newRes = game.resolutions[game.resolutions.length - 1];
return eachSeries(newRes, (r, cb) => { return eachSeries(newRes, (r, cb) => {
if (!r.event) return cb(); if (r.delay === 0) return cb(); // TargetKo etc
let timeout = animations.getTime(r.stages); setAnimations(r, store);
const anims = animations.getObjects(r, game, account); return setTimeout(cb, r.delay);
const text = animations.getText(r);
store.dispatch(actions.setAnimFocus(animations.getFocusTargets(r, game)));
if (anims.animSkill) store.dispatch(actions.setAnimSkill(anims.animSkill));
if (r.stages.includes('START_SKILL') && anims.animSource) {
store.dispatch(actions.setAnimSource(anims.animSource));
store.dispatch(actions.setAnimText(null));
}
if (r.stages.includes('END_SKILL') && anims.animTarget) {
store.dispatch(actions.setAnimTarget(anims.animTarget));
store.dispatch(actions.setAnimText(null));
if (animations.isCbAnim(anims.animSkill)) store.dispatch(actions.setAnimCb(cb));
}
if (r.stages.includes('POST_SKILL') && text) {
// timeout to prevent text classes from being added too soon
if (timeout === TIMES.POST_SKILL_DURATION_MS) {
store.dispatch(actions.setAnimText(text));
} else {
setTimeout(
() => store.dispatch(actions.setAnimText(text)),
timeout - TIMES.POST_SKILL_DURATION_MS - 700
);
timeout -= 700;
}
}
return setTimeout(() => {
store.dispatch(actions.setAnimSkill(null));
store.dispatch(actions.setAnimSource(null));
store.dispatch(actions.setAnimTarget(null));
// store.dispatch(actions.setAnimText(null));
store.dispatch(actions.setAnimFocus([]));
if (r.stages.includes('END_SKILL') && animations.isCbAnim(anims.animSkill)) return true;
return cb();
}, timeout);
}, err => { }, err => {
if (err) return console.error(err); if (err) return console.error(err);
// clear animation state clearAnimations(store);
store.dispatch(actions.setAnimSkill(null));
store.dispatch(actions.setAnimSource(null));
store.dispatch(actions.setAnimTarget(null));
store.dispatch(actions.setAnimText(null));
store.dispatch(actions.setAnimating(false));
store.dispatch(actions.setGameEffectInfo(null));
// set the game state so resolutions don't fire twice // set the game state so resolutions don't fire twice
store.dispatch(actions.setGame(game)); store.dispatch(actions.setGame(game));
ws.sendGameState(game.id); ws.sendGameState(game.id);
@ -146,16 +104,21 @@ function registerEvents(store) {
} }
function setAccount(account) { function setAccount(account) {
if (account && process.env.NODE_ENV !== 'development') { store.dispatch(actions.setAccount(account));
LogRocket.init('yh0dy3/mnml'); }
LogRocket.identify(account.id, account);
if (window.Notification) { function setAuthenticated(account) {
window.Notification.requestPermission(); if (account && window.Notification) {
} window.Notification.requestPermission();
}
if (process.env.NODE_ENV !== 'development') {
LogRocket.identify(account.id, account);
} }
store.dispatch(actions.setAccount(account)); store.dispatch(actions.setAccount(account));
store.dispatch(actions.setTutorial(null));
store.dispatch(actions.setAuthenticated(true));
} }
function setEmail(email) { function setEmail(email) {
@ -220,23 +183,14 @@ function registerEvents(store) {
const player = v.players.find(p => p.id === account.id); const player = v.players.find(p => p.id === account.id);
store.dispatch(actions.setPlayer(player)); store.dispatch(actions.setPlayer(player));
const skip = v.time_control === 'Practice' && v.phase === 'Lobby'; if (tutorial) tutorialVbox(player, store, tutorial);
if (skip) {
ws.sendInstanceReady(v.id);
}
if (v.phase === 'Finished') { if (v.phase === 'Finished') {
ws.sendAccountInstances(); ws.sendAccountInstances();
} }
// instance.mobile.less hides info at @media 1000
if (localStorage.getItem('tutorial-complete') || window.innerWidth <= 1100) {
store.dispatch(actions.setTutorial(null));
} else if (v.time_control === 'Practice' && v.rounds.length === 1 && tutorial) {
tutorialVbox(player, store, tutorial);
}
} }
return store.dispatch(actions.setInstance(v)); return store.dispatch(actions.setInstance(v));
} }
@ -252,94 +206,6 @@ function registerEvents(store) {
return store.dispatch(actions.setItemInfo(v)); return store.dispatch(actions.setItemInfo(v));
} }
function setDemo(d) {
const vboxDemo = {
players: d,
combiner: [],
equipped: false,
equipping: false,
};
const startDemo = () => {
const { account, itemInfo } = store.getState();
if (account) return false;
if (!itemInfo || itemInfo.items.length === 0) return setTimeout(startDemo, 500);
store.dispatch(actions.setAnimTarget(null));
const bases = ['Attack', 'Stun', 'Buff', 'Debuff', 'Block'];
const combo = sample(itemInfo.combos.filter(i => bases.some(b => i.components.includes(b))));
vboxDemo.combo = combo.item;
vboxDemo.items = combo.components;
store.dispatch(actions.setDemo(vboxDemo));
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0] }))), 500);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0, 1] }))), 1000);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0, 1, 2] }))), 1500);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [], items: [vboxDemo.combo, '', ''] }))), 2500);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0], items: [vboxDemo.combo, '', ''], equipping: true }))), 3000);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [], items: ['', '', ''], equipped: true, equipping: false }))), 4000);
setTimeout(() => {
return store.dispatch(actions.setAnimTarget({
skill: sample(itemInfo.items.filter(i => i.skill)).item,
constructId: d[1].constructs[0].id,
player: false,
direction: 0,
}));
}, 500);
setTimeout(() => {
return store.dispatch(actions.setAnimTarget({
skill: sample(itemInfo.items.filter(i => i.skill)).item,
constructId: d[1].constructs[1].id,
player: true,
direction: 0,
}));
}, 3000);
return setTimeout(startDemo, 5000);
};
startDemo();
}
// store.subscribe(setInfo);
// store.on('SET_INFO', setInfo);
// events.on('SET_PLAYER', setInstance);
// events.on('SEND_SKILL', function skillActive(gameId, constructId, targetConstructId, skill) {
// ws.sendGameSkill(gameId, constructId, targetConstructId, skill);
// setConstructStatusUpdate(constructId, skill, targetConstructId);
// });
// events.on('CONSTRUCT_ACTIVE', function constructActiveCb(construct) {
// for (let i = 0; i < constructs.length; i += 1) {
// if (constructs[i].id === construct.id) constructs[i].active = !constructs[i].active;
// }
// return setConstructs(constructs);
// });
/* function errorPrompt(type) {
const message = errMessages[type];
const OK_BUTTON = '<button type="submit">OK</button>';
toast.error({
theme: 'dark',
color: 'black',
timeout: false,
drag: false,
position: 'center',
maxWidth: window.innerWidth / 2,
close: false,
buttons: [
[OK_BUTTON, (instance, thisToast) => instance.hide({ transitionOut: 'fadeOut' }, thisToast)],
],
message,
});
} */
// setup / localstorage
function urlHashChange() { function urlHashChange() {
const { ws } = store.getState(); const { ws } = store.getState();
const cmds = querystring.parse(location.hash); const cmds = querystring.parse(location.hash);
@ -348,6 +214,11 @@ function registerEvents(store) {
return true; return true;
} }
function startTutorial() {
store.dispatch(actions.setTutorial(1));
}
window.addEventListener('hashchange', urlHashChange, false); window.addEventListener('hashchange', urlHashChange, false);
return { return {
@ -356,12 +227,13 @@ function registerEvents(store) {
clearInstance, clearInstance,
clearMtxActive, clearMtxActive,
clearTutorial, clearTutorial,
clearTutorialGame,
setAccount, setAccount,
setAuthenticated,
setAccountInstances, setAccountInstances,
setActiveItem, setActiveItem,
setActiveSkill, setActiveSkill,
setChatWheel, setChatWheel,
setDemo,
setConstructList, setConstructList,
setNewConstruct, setNewConstruct,
setGame, setGame,
@ -377,6 +249,8 @@ function registerEvents(store) {
setSubscription, setSubscription,
setWs, setWs,
startTutorial,
urlHashChange, urlHashChange,
notify, notify,

View File

@ -10,18 +10,16 @@ function createReducer(defaultState, actionType) {
/* eslint-disable key-spacing */ /* eslint-disable key-spacing */
module.exports = { module.exports = {
account: createReducer(null, 'SET_ACCOUNT'), account: createReducer(null, 'SET_ACCOUNT'),
authenticated: createReducer(null, 'SET_AUTHENTICATED'),
activeItem: createReducer(null, 'SET_ACTIVE_VAR'), activeItem: createReducer(null, 'SET_ACTIVE_VAR'),
activeSkill: createReducer(null, 'SET_ACTIVE_SKILL'), activeSkill: createReducer(null, 'SET_ACTIVE_SKILL'),
animating: createReducer(false, 'SET_ANIMATING'), animating: createReducer(false, 'SET_ANIMATING'),
animCb: createReducer(null, 'SET_ANIM_CB'),
animSkill: createReducer(null, 'SET_ANIM_SKILL'),
animSource: createReducer(null, 'SET_ANIM_SOURCE'), animSource: createReducer(null, 'SET_ANIM_SOURCE'),
animFocus: createReducer(null, 'SET_ANIM_FOCUS'), animFocus: createReducer(null, 'SET_ANIM_FOCUS'),
animTarget: createReducer(null, 'SET_ANIM_TARGET'), animTarget: createReducer(null, 'SET_ANIM_TARGET'),
animText: createReducer(null, 'SET_ANIM_TEXT'),
demo: createReducer(null, 'SET_DEMO'), resolution: createReducer(null, 'SET_RESOLUTION'),
chatShow: createReducer(null, 'SET_CHAT_SHOW'), chatShow: createReducer(null, 'SET_CHAT_SHOW'),
chatWheel: createReducer([], 'SET_CHAT_WHEEL'), chatWheel: createReducer([], 'SET_CHAT_WHEEL'),

View File

@ -129,6 +129,7 @@ function createSocket(events) {
{ game_id: gameId, construct_id: constructId, target_construct_id: targetConstructId, skill }, { game_id: gameId, construct_id: constructId, target_construct_id: targetConstructId, skill },
]); ]);
events.setActiveSkill(null); events.setActiveSkill(null);
events.clearTutorialGame();
} }
function sendGameSkillClear(gameId) { function sendGameSkillClear(gameId) {
@ -255,10 +256,6 @@ function createSocket(events) {
events.setItemInfo(info); events.setItemInfo(info);
} }
function onDemo(v) {
events.setDemo(v);
}
let pongTimeout; let pongTimeout;
function onPong() { function onPong() {
events.setPing(Date.now() - ping); events.setPing(Date.now() - ping);
@ -273,6 +270,7 @@ function createSocket(events) {
// this object wraps the reply types to a function // this object wraps the reply types to a function
const handlers = { const handlers = {
AccountState: onAccount, AccountState: onAccount,
AccountAuthenticated: account => events.setAuthenticated(account),
AccountConstructs: onAccountConstructs, AccountConstructs: onAccountConstructs,
AccountTeam: onAccountTeam, AccountTeam: onAccountTeam,
AccountInstances: onAccountInstances, AccountInstances: onAccountInstances,
@ -284,7 +282,6 @@ function createSocket(events) {
InstanceState: onInstanceState, InstanceState: onInstanceState,
ItemInfo: onItemInfo, ItemInfo: onItemInfo,
Pong: onPong, Pong: onPong,
Demo: onDemo,
// QueueRequested: () => events.notify('PVP queue request received.'), // QueueRequested: () => events.notify('PVP queue request received.'),
QueueRequested: () => true, QueueRequested: () => true,
@ -303,6 +300,8 @@ function createSocket(events) {
ChatWheel: wheel => events.setChatWheel(wheel), ChatWheel: wheel => events.setChatWheel(wheel),
// Joining: () => events.notify('Searching for instance...'), // Joining: () => events.notify('Searching for instance...'),
StartTutorial: () => events.startTutorial(),
Processing: () => true, Processing: () => true,
Error: errHandler, Error: errHandler,
}; };

View File

@ -115,8 +115,9 @@ function tutorialStage(tutorial, clearTutorial, instance) {
if (tutorial === 1) { if (tutorial === 1) {
return ( return (
<div class='info-item'> <div class='info-item'>
<h2>Tutorial</h2> <h1>Welcome to MNML</h1>
<p> Welcome to the vbox phase tutorial.</p> <p> This is the <b>VBOX Phase</b> tutorial.</p>
<p> In the <b>VBOX Phase</b> you customise your constructs' skills and specialisations. </p>
<p> Colours are used to create powerful combinations with base items. </p> <p> Colours are used to create powerful combinations with base items. </p>
<p> Buy the two colours from the store to continue. </p> <p> Buy the two colours from the store to continue. </p>
</div> </div>
@ -126,9 +127,9 @@ function tutorialStage(tutorial, clearTutorial, instance) {
if (tutorial === 2) { if (tutorial === 2) {
return ( return (
<div class='info-item'> <div class='info-item'>
<h2>Tutorial</h2> <h2>Combining Items</h2>
<p> You start the game with the base <b>Attack</b> skill item. </p> <p> You start the game with the base <b>Attack</b> skill item. </p>
<p> Highlight all three items then click combine.</p> <p> Highlight the <b>Attack</b> and the two <b> colours</b> then click <b> combine</b> </p>
</div> </div>
); );
} }
@ -137,11 +138,11 @@ function tutorialStage(tutorial, clearTutorial, instance) {
const constructOne = instance.players[0].constructs[0].name; const constructOne = instance.players[0].constructs[0].name;
return ( return (
<div class='info-item'> <div class='info-item'>
<h2>Tutorial</h2> <h2>Equipping Items</h2>
<p> The first construct on your team is <b>{constructOne}</b>. </p> <p> The first construct on your team is <b>{constructOne}</b>. </p>
<p> Skill items can be equipped to your constructs to be used in the combat phase. </p> <p> Skill items can be equipped to your constructs to be used in the combat phase. </p>
<p> Click your new skill from the stash. <br /> <p> Click your new skill from the stash. <br />
Once selected click the flashing <b>SKILL</b> slot to equip the skill. </p> Once selected click the flashing <b>SKILL</b> slot or the construct img to equip the skill. </p>
</div> </div>
); );
} }
@ -149,7 +150,7 @@ function tutorialStage(tutorial, clearTutorial, instance) {
if (tutorial === 4) { if (tutorial === 4) {
return ( return (
<div class='info-item'> <div class='info-item'>
<h2>Tutorial</h2> <h2>Specialisations</h2>
<p> You can also buy specialisation items for your constructs. <br /> <p> You can also buy specialisation items for your constructs. <br />
Specialisation items increase stats including power, speed and life. </p> Specialisation items increase stats including power, speed and life. </p>
<p> Buy the specialisation item from the store to continue. </p> <p> Buy the specialisation item from the store to continue. </p>
@ -160,11 +161,12 @@ function tutorialStage(tutorial, clearTutorial, instance) {
if (tutorial === 5) { if (tutorial === 5) {
return ( return (
<div class='info-item'> <div class='info-item'>
<h2>Tutorial</h2> <h2>Specialisations</h2>
<p> Equipping specialisation items will increase the stats of your constructs.</p> <p> Equipping specialisation items will increase the stats of your constructs.</p>
<p> These can also be combined with colours for further specialisation. </p> <p> These can also be combined with colours for further specialisation. </p>
<p> Click the specialisation item in the stash.<br /> <p> Click the specialisation item in the stash.<br />
Once selected click the flashing <b>SPEC</b> slot to equip the specialisation. </p> Once selected click the flashing <b>SPEC</b> slot to equip the specialisation. </p>
<p> <b>PRO TIP:</b> while selecting an item in the shop, click on your construct to buy and equip in one step. </p>
</div> </div>
); );
} }
@ -174,11 +176,12 @@ function tutorialStage(tutorial, clearTutorial, instance) {
const constructThree = instance.players[0].constructs[2].name; const constructThree = instance.players[0].constructs[2].name;
return ( return (
<div class='info-item'> <div class='info-item'>
<h2>Tutorial</h2> <h2>Skills</h2>
<p> You have now created a construct with an upgraded skill and base spec. </p> <p> You have now created a construct with an upgraded skill and base spec. </p>
<p> The goal is to create three powerful constructs for combat. </p> <p> The goal is to create three powerful constructs for combat. </p>
<p> Equip your other constructs <b>{constructTwo}</b> and <b>{constructThree}</b> with the Attack skill. <br /> <p> Equip your other constructs <b>{constructTwo}</b> and <b>{constructThree}</b> with the Attack skill. <br />
Ensure each construct has a single skill to continue. </p> Ensure each construct has a single skill to continue. </p>
<p> <b>PRO TIP:</b> Select a skill or spec on a construct and click another construct to swap it. </p>
</div> </div>
); );
} }
@ -186,7 +189,7 @@ function tutorialStage(tutorial, clearTutorial, instance) {
if (tutorial === 7) { if (tutorial === 7) {
return ( return (
<div class='info-item'> <div class='info-item'>
<h2>Tutorial</h2> <h2>Economy</h2>
<p> Each round you start with 30 bits and a store full of different skills, specs and colours. </p> <p> Each round you start with 30 bits and a store full of different skills, specs and colours. </p>
<p> Bits are your currency for buying items. <br /> <p> Bits are your currency for buying items. <br />
You can refill the store by pressing the refill button for 2b. <br /> You can refill the store by pressing the refill button for 2b. <br />
@ -203,22 +206,22 @@ function tutorialStage(tutorial, clearTutorial, instance) {
return ( return (
<div class='info-item'> <div class='info-item'>
<h2>Tutorial</h2> <h2>GLHF</h2>
<p>That completes the VBOX Tutorial.</p> <p>That completes the VBOX Tutorial.</p>
<p>Press <b>READY</b> to progress to the <b>GAME PHASE</b> <br /> <p>Press the green <b>READY</b> button in the bottom right to progress to the <b>GAME PHASE</b> <br />
You can continue creating new items to upgrade your constructs further. </p> or continue creating new items to upgrade your constructs further. </p>
</div> </div>
); );
} }
return false; return false;
}; };
const classes = tutorial === 8 ? 'focus' : ''; const exitTutorial = tutorial === 8 ?
const text = tutorial === 8 ? 'Continue' : 'Skip Tutorial' <button
const exitTutorial = <button class='focus'
class={classes} onClick={e => e.stopPropagation()}
onClick={e => e.stopPropagation()} onMouseDown={exit}> Continue </button>
onMouseDown={exit}> {text} </button>; : null;
return ( return (
<div class='tutorial'> <div class='tutorial'>

View File

@ -240,28 +240,16 @@ function convertItem(v) {
} }
function effectInfo(i) { function effectInfo(i) {
// FIX ME
const hybridBlast = 25; const hybridBlast = 25;
const hasteStrike = 30; const hasteStrike = 30;
function multiplier(s) { // Update later to use server info in future function multiplier(s) { // Update later to use server info in future
if (s === 'CounterAttack') return 120; if (s === 'CounterAttack') return 115;
if (s === 'CounterAttack+') return 160; if (s === 'CounterAttack+') return 130;
if (s === 'CounterAttack++') return 230; if (s === 'CounterAttack++') return 160;
if (s === 'Electrocute') return 80;
if (s === 'DecayTick') return 33; if (s === 'Electrocute+') return 90;
if (s === 'DecayTick+') return 45; if (s === 'Electrocute++') return 100;
if (s === 'DecayTick++') return 70;
if (s === 'SiphonTick') return 25;
if (s === 'SiphonTick+') return 30;
if (s === 'SiphonTick++') return 40;
if (s === 'TriageTick') return 75;
if (s === 'TriageTick+') return 110;
if (s === 'TriageTick++') return 140;
if (s === 'Electrocute' || s === 'ElectrocuteTick') return 80;
if (s === 'Electrocute+' || s === 'ElectrocuteTick+') return 100;
if (s === 'Electrocute++' || s === 'ElectrocuteTick++') return 130;
return 0; return 0;
} }
@ -286,13 +274,13 @@ function effectInfo(i) {
case 'Vulnerable': return `Construct will take ${i.meta[1] - 100}% increased red damage`; case 'Vulnerable': return `Construct will take ${i.meta[1] - 100}% increased red damage`;
case 'Silence': return 'Disable construct from casting any blue skills'; case 'Silence': return 'Disable construct from casting any blue skills';
case 'Wither': return `Construct will take ${100 - i.meta[1]}% reduced healing`; // case 'Wither': return `Construct will take ${100 - i.meta[1]}% reduced healing`; //
case 'Decay': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower as blue damage each turn.`; // case 'Decay': return `Construct will take ${i.meta[1].amount} blue damage each turn.`; //
case 'Electric': return `Attacks against this construct will apply Electrocute dealing ${multiplier(i.meta[1])}% of construct BluePower as blue damage each turn.`; case 'Electric': return `Attacks against this construct will apply Electrocute dealing ${multiplier(i.meta[1])}% of construct BluePower as blue damage each turn.`;
case 'Electrocute': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower as blue damage each turn.`; case 'Electrocute': return `Construct will take ${i.meta[1].amount} blue damage each turn.`;
case 'Absorb': return 'If construct takes damage, Absorption will be applied increasing RedPower and BluePower based on damage taken.'; case 'Absorb': return 'If construct takes damage, Absorption will be applied increasing RedPower and BluePower based on damage taken.';
case 'Absorption': return `Increasing construct RedPower and BluePower by ${i.meta[1]}`; case 'Absorption': return `Increasing construct RedPower and BluePower by ${i.meta[1]}`;
case 'Triage': return `Construct will be healed for ${multiplier(i.tick.skill)}% of caster's GreenPower each turn.`; case 'Triage': return `Construct will be healed for ${i.meta[1].amount} green life each turn.`;
case 'Siphon': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower + GreenPower as blue damage each turn, healing the caster.`; case 'Siphon': return `Construct will take ${i.meta[1].amount} blue damage each turn, healing the caster.`;
default: return 'Missing Effect Text'; default: return 'Missing Effect Text';
} }

3
core/.cargo/config Executable file
View File

@ -0,0 +1,3 @@
[target.x86_64-pc-windows-msvc.gnu]
rustc-link-search = ["C:\\Program Files\\PostgreSQL\\pg96\\lib"]

4
core/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
target/
Cargo.lock
log/
.env

13
core/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "mnml_core"
version = "1.11.0"
authors = ["ntr <ntr@smokestack.io>", "mashy <mashy@mnml.gg>"]
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
failure = "0.1"
log = "0.4"
rand = "0.6"
serde = "1"
serde_derive = "1"
uuid = { version = "0.5", features = ["serde", "v4"] }

5
core/fixme.md Normal file
View File

@ -0,0 +1,5 @@
# FIXME
game ready not auto starting resolve phase
remove big header and move to rhs of news pane
add big logo w/ noise when you mouseover stuff etc

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,9 @@
use construct::{Stat, EffectMeta}; use construct::{Stat, EffectMeta};
use game::{Colour};
use skill::{Skill}; use skill::{Skill};
use util::{IntPct}; use util::{IntPct};
pub type Cooldown = Option<u8>; pub type Cooldown = Option<usize>;
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Effect { pub enum Effect {
@ -41,10 +42,17 @@ pub enum Effect {
// effects over time // effects over time
Triage, Triage,
Decay, Triaged, // immunity
Regen,
Siphon,
Decay,
Decayed, // immunity
Siphon,
Siphoned, // immunity
Countered,
// Regen,
// Airborne, // Airborne,
// Boost // Boost
// Bleed, // Bleed,
@ -66,18 +74,42 @@ impl Effect {
pub fn immune(&self, skill: Skill) -> bool { pub fn immune(&self, skill: Skill) -> bool {
match self { match self {
Effect::Banish => true, Effect::Banish => true,
Effect::Sustain => [
Skill::Stun, // these provide immunity for the ticks
Skill::Silence, // the base skills will still resolve
Skill::SilencePlus, // but they have early return checks
Skill::SilencePlusPlus, // to ensure the effect is reapplied but damage is not
Skill::Ruin, Effect::Siphoned => [
Skill::RuinPlus, Skill::SiphonTick,
Skill::RuinPlusPlus,
Skill::Restrict,
Skill::RestrictPlus,
Skill::RestrictPlusPlus
].contains(&skill), ].contains(&skill),
Effect::Decayed => [
Skill::DecayTick,
].contains(&skill),
Effect::Triaged => [
Skill::TriageTick,
].contains(&skill),
Effect::Countered => [
Skill::CounterAttack,
Skill::CounterAttackPlus,
Skill::CounterAttackPlusPlus,
].contains(&skill),
_ => false,
}
}
// hidden effects are used generally for immunities
// they are not displayed on client
// and not included in counts
pub fn hidden(&self) -> bool {
match self {
Effect::Siphoned => true,
Effect::Decayed => true,
Effect::Triaged => true,
Effect::Countered => true,
_ => false, _ => false,
} }
} }
@ -101,7 +133,7 @@ impl Effect {
pub fn modifications(&self) -> Vec<Stat> { pub fn modifications(&self) -> Vec<Stat> {
match self { match self {
// Bases // Bases
Effect::Block => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken], Effect::Block => vec![Stat::RedDamageReceived, Stat::BlueDamageReceived],
Effect::Buff => vec![Stat::BluePower, Stat::RedPower, Stat::Speed], Effect::Buff => vec![Stat::BluePower, Stat::RedPower, Stat::Speed],
Effect::Slow => vec![Stat::Speed], Effect::Slow => vec![Stat::Speed],
@ -111,10 +143,10 @@ impl Effect {
Effect::Hybrid => vec![Stat::GreenPower], Effect::Hybrid => vec![Stat::GreenPower],
// Damage taken changes // Damage taken changes
Effect::Curse => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken], Effect::Curse => vec![Stat::RedDamageReceived, Stat::BlueDamageReceived],
Effect::Pure => vec![Stat::GreenDamageTaken], // increased green taken Effect::Pure => vec![Stat::GreenHealingReceived], // increased green taken
Effect::Vulnerable => vec![Stat::RedDamageTaken], Effect::Vulnerable => vec![Stat::RedDamageReceived],
Effect::Wither => vec![Stat::GreenDamageTaken], // reduced green taken Effect::Wither => vec![Stat::GreenHealingReceived], // reduced green taken
// Speed // Speed
Effect::Haste => vec![Stat::Speed], Effect::Haste => vec![Stat::Speed],
@ -123,7 +155,7 @@ impl Effect {
} }
} }
pub fn apply(&self, value: u64, meta: Option<EffectMeta>) -> u64 { pub fn apply(&self, value: usize, meta: Option<EffectMeta>) -> usize {
match self { match self {
Effect::Amplify | Effect::Amplify |
Effect::Vulnerable | Effect::Vulnerable |
@ -154,7 +186,10 @@ impl Effect {
} }
} }
pub fn colour(&self) -> Option<Colour> { // Old colour matching system for buffs / debuffs
// Had issues as some effects will be considered as both a buff and debuff e.g. invert,
// Ended up being confusing with mismatch skills that have red / blue e.g. amplify, haste, hybrid
/* pub fn colour(&self) -> Option<Colour> {
match self { match self {
// physical // physical
Effect::Stun => Some(Colour::Red), Effect::Stun => Some(Colour::Red),
@ -192,18 +227,14 @@ impl Effect {
// effects over time // effects over time
Effect::Triage => Some(Colour::Green), Effect::Triage => Some(Colour::Green),
Effect::Decay => Some(Colour::Blue), Effect::Decay => Some(Colour::Blue),
Effect::Regen => Some(Colour::Green),
Effect::Siphon => Some(Colour::Blue), Effect::Siphon => Some(Colour::Blue),
Effect::Pure => Some(Colour::Green), Effect::Pure => Some(Colour::Green),
Effect::Triaged => None,
Effect::Decayed => None,
Effect::Siphoned => None,
Effect::Countered => None,
Effect::Ko => None, Effect::Ko => None,
} }
} }*/
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Colour {
Red,
Blue,
Green,
} }

2514
core/src/game.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,8 @@
use std::fs::File;
use std::collections::{HashMap}; use std::collections::{HashMap};
use uuid::Uuid; use uuid::Uuid;
use serde_cbor::{from_slice, to_vec};
use postgres::transaction::Transaction;
use failure::Error; use failure::Error;
use failure::err_msg; use failure::err_msg;
@ -14,16 +10,11 @@ use failure::err_msg;
use chrono::prelude::*; use chrono::prelude::*;
use chrono::Duration; use chrono::Duration;
use account::Account; use player::{Player, Score};
use game::{Game};
use item::{Item};
use vbox; use vbox;
use player::{Player, Score, player_create};
use mob::{bot_player, instance_mobs};
use game::{Game, Phase, game_get, game_write, game_update};
use item::{Item};
use rpc::{RpcMessage};
use img;
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
enum InstancePhase { enum InstancePhase {
@ -107,14 +98,14 @@ pub struct Instance {
time_control: TimeControl, time_control: TimeControl,
phase: InstancePhase, phase: InstancePhase,
phase_end: Option<DateTime<Utc>>, pub phase_end: Option<DateTime<Utc>>,
phase_start: DateTime<Utc>, pub phase_start: DateTime<Utc>,
winner: Option<Uuid>, winner: Option<Uuid>,
} }
impl Instance { impl Instance {
fn new() -> Instance { pub fn new() -> Instance {
Instance { Instance {
id: Uuid::new_v4(), id: Uuid::new_v4(),
players: vec![], players: vec![],
@ -152,8 +143,8 @@ impl Instance {
.collect::<Vec<Uuid>>() .collect::<Vec<Uuid>>()
} }
// time out lobbies that have been open too long
pub fn upkeep(mut self) -> (Instance, Option<Game>) { pub fn upkeep(mut self) -> (Instance, Option<Game>) {
// time out lobbies that have been open too long
if self.phase == InstancePhase::Lobby && self.phase_timed_out() { if self.phase == InstancePhase::Lobby && self.phase_timed_out() {
self.finish(); self.finish();
return (self, None); return (self, None);
@ -178,7 +169,7 @@ impl Instance {
(self, new_game) (self, new_game)
} }
fn set_name(mut self, name: String) -> Result<Instance, Error> { pub fn set_name(mut self, name: String) -> Result<Instance, Error> {
if name.len() == 0 { if name.len() == 0 {
return Err(err_msg("name must have a length")); return Err(err_msg("name must have a length"));
} }
@ -187,12 +178,12 @@ impl Instance {
Ok(self) Ok(self)
} }
fn set_time_control(mut self, tc: TimeControl) -> Instance { pub fn set_time_control(mut self, tc: TimeControl) -> Instance {
self.time_control = tc; self.time_control = tc;
self self
} }
fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> { pub fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> {
if self.players.len() >= self.max_players { if self.players.len() >= self.max_players {
return Err(err_msg("game full")) return Err(err_msg("game full"))
} }
@ -206,7 +197,7 @@ impl Instance {
Ok(self) Ok(self)
} }
fn player_ready(&mut self, player_id: Uuid) -> Result<Option<Game>, Error> { pub fn player_ready(&mut self, player_id: Uuid) -> Result<Option<Game>, Error> {
if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) { if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) {
return Err(err_msg("instance not in start or vbox phase")); return Err(err_msg("instance not in start or vbox phase"));
} }
@ -308,7 +299,7 @@ impl Instance {
self.next_round() self.next_round()
} }
fn next_round(&mut self) -> &mut Instance { pub fn next_round(&mut self) -> &mut Instance {
if self.finish_condition() { if self.finish_condition() {
return self.finish(); return self.finish();
} }
@ -350,7 +341,7 @@ impl Instance {
self self
} }
fn finished(&self) -> bool { pub fn finished(&self) -> bool {
self.phase == InstancePhase::Finished self.phase == InstancePhase::Finished
} }
@ -378,7 +369,7 @@ impl Instance {
self self
} }
fn current_game_id(&self) -> Option<Uuid> { pub fn current_game_id(&self) -> Option<Uuid> {
if self.phase != InstancePhase::InProgress { if self.phase != InstancePhase::InProgress {
return None; return None;
} }
@ -394,7 +385,7 @@ impl Instance {
return current_round.game_id; return current_round.game_id;
} }
fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> { pub fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> {
{ {
let current_round = self.rounds let current_round = self.rounds
.iter_mut() .iter_mut()
@ -436,14 +427,14 @@ impl Instance {
} }
// PLAYER ACTIONS // PLAYER ACTIONS
fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> { pub fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> {
self.players self.players
.iter_mut() .iter_mut()
.find(|p| p.id == account) .find(|p| p.id == account)
.ok_or(err_msg("account not in instance")) .ok_or(err_msg("account not in instance"))
} }
fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> { pub fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> {
self.players self.players
.iter_mut() .iter_mut()
.find(|p| p.id != account) .find(|p| p.id != account)
@ -501,334 +492,18 @@ impl Instance {
Ok(self) Ok(self)
} }
pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct_id: Option<Uuid>) -> Result<Instance, Error> { pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct: Option<Uuid>) -> Result<Instance, Error> {
self.vbox_action_allowed(account)?; self.vbox_action_allowed(account)?;
self.account_player(account)? self.account_player(account)?
.vbox_unequip(target, construct_id, target_construct_id)?; .vbox_unequip(target, construct_id, target_construct)?;
Ok(self) Ok(self)
} }
} }
pub fn instance_create(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
let instance_bytes = to_vec(&instance)?;
let query = "
INSERT INTO instances (id, data, upkeep)
VALUES ($1, $2, $3)
RETURNING id;
";
let result = tx
.query(query, &[&instance.id, &instance_bytes, &instance.phase_end])?;
result.iter().next().ok_or(format_err!("no instances written"))?;
return Ok(instance);
}
pub fn instance_update(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
let instance_bytes = to_vec(&instance)?;
let query = "
UPDATE instances
SET data = $1, finished = $2, upkeep = $3, updated_at = now()
WHERE id = $4
RETURNING id, data;
";
let result = tx
.query(query, &[&instance_bytes, &instance.finished(), &instance.phase_end, &instance.id])?;
result.iter().next().ok_or(err_msg("no instance row returned"))?;
trace!("{:?} wrote instance", instance.id);
if instance.finished() {
info!("finished id={:?}", instance.id);
match instance_json_file_write(&instance) {
Ok(dest) => info!("wrote dest={:?}", dest),
Err(e) => error!("json write error={:?}", e),
};
}
return Ok(instance);
}
fn instance_json_file_write(g: &Instance) -> Result<String, Error> {
let dest = format!("/var/lib/mnml/data/instances/{}.mnml.instance.json", g.id);
serde_json::to_writer(File::create(&dest)?, g)?;
Ok(dest)
}
pub fn instance_get(tx: &mut Transaction, instance_id: Uuid) -> Result<Instance, Error> {
let query = "
SELECT *
FROM instances
WHERE id = $1
FOR UPDATE;
";
let result = tx
.query(query, &[&instance_id])?;
let returned = match result.iter().next() {
Some(row) => row,
None => return Err(err_msg("instance not found")),
};
let instance_bytes: Vec<u8> = returned.get("data");
let instance = from_slice::<Instance>(&instance_bytes)?;
return Ok(instance);
}
pub fn instance_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
let query = "
DELETE
FROM instances
WHERE id = $1;
";
let result = tx
.execute(query, &[&id])?;
if result != 1 {
return Err(format_err!("unable to delete instance {:?}", id));
}
info!("instance deleted {:?}", id);
return Ok(());
}
pub fn _instance_list(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
let query = "
SELECT data, id
FROM instances
AND finished = false;
";
let result = tx
.query(query, &[])?;
let mut list = vec![];
for row in result.into_iter() {
let bytes: Vec<u8> = row.get(0);
let id = row.get(1);
match from_slice::<Instance>(&bytes) {
Ok(i) => list.push(i),
Err(_e) => {
instance_delete(tx, id)?;
}
};
}
return Ok(list);
}
pub fn instances_need_upkeep(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
let query = "
SELECT data, id
FROM instances
WHERE finished = false
AND upkeep < now()
FOR UPDATE;
";
let result = tx
.query(query, &[])?;
let mut list = vec![];
for row in result.into_iter() {
let bytes: Vec<u8> = row.get(0);
let id = row.get(1);
match from_slice::<Instance>(&bytes) {
Ok(i) => list.push(i),
Err(_e) => {
instance_delete(tx, id)?;
}
};
}
return Ok(list);
}
// timed out instances with no time control
pub fn instances_idle(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
let query = "
SELECT data, id
FROM instances
WHERE finished = false
AND updated_at < now() - interval '1 hour'
FOR UPDATE;
";
let result = tx
.query(query, &[])?;
let mut list = vec![];
for row in result.into_iter() {
let bytes: Vec<u8> = row.get(0);
let id = row.get(1);
match from_slice::<Instance>(&bytes) {
Ok(i) => list.push(i),
Err(_e) => {
instance_delete(tx, id)?;
}
};
}
return Ok(list);
}
pub fn instance_practice(tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
let bot = bot_player();
let bot_id = bot.id;
// generate bot imgs for the client to see
for c in bot.constructs.iter() {
img::shapes_write(c.img)?;
}
let mut instance = Instance::new()
.set_time_control(TimeControl::Practice)
.set_name(bot.name.clone())?;
let player = Player::from_account(tx, account)?;
instance.add_player(player.clone())?;
instance.add_player(bot)?;
instance.player_ready(bot_id)?;
instance = instance_create(tx, instance)?;
player_create(tx, player, instance.id, account)?;
Ok(instance)
}
pub fn pvp(tx: &mut Transaction, a: &Account, b: &Account) -> Result<Instance, Error> {
let mut instance = Instance::new()
// TODO generate nice game names
.set_name("PVP".to_string())?;
instance = instance_create(tx, instance)?;
for account in [a, b].iter() {
let acc_p = Player::from_account(tx, &account)?;
let player = player_create(tx, acc_p, instance.id, account)?;
instance.add_player(player)?;
}
instance_update(tx, instance)
}
pub fn instance_abandon(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<RpcMessage, Error> {
let mut instance = instance_get(tx, instance_id)?;
if let Some(game_id) = instance.current_game_id() {
let mut game = game_get(tx, game_id)?;
game.player_by_id(account.id)?.forfeit();
game = game.start(); // actually finishes it...
game_update(tx, &game)?;
}
instance.account_player(account.id)?.set_lose();
instance.account_opponent(account.id)?.set_win();
instance.next_round();
Ok(RpcMessage::InstanceState(instance_update(tx, instance)?))
}
pub fn instance_ready(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<RpcMessage, Error> {
let mut instance = instance_get(tx, instance_id)?;
let player_id = instance.account_player(account.id)?.id;
if let Some(game) = instance.player_ready(player_id)? {
game_write(tx, &game)?;
// ensures cleanup for warden etc is done
game_update(tx, &game)?;
instance_update(tx, instance)?;
return Ok(RpcMessage::GameState(game));
}
Ok(RpcMessage::InstanceState(instance_update(tx, instance)?))
}
pub fn instance_state(tx: &mut Transaction, instance_id: Uuid) -> Result<RpcMessage, Error> {
let instance = instance_get(tx, instance_id)?;
if let Some(game_id) = instance.current_game_id() {
let game = game_get(tx, game_id)?;
// return the game until it's finished
if game.phase != Phase::Finished {
return Ok(RpcMessage::GameState(game))
}
}
Ok(RpcMessage::InstanceState(instance))
}
pub fn instance_game_finished(tx: &mut Transaction, game: &Game, instance_id: Uuid) -> Result<(), Error> {
let mut instance = instance_get(tx, instance_id)?;
instance.game_finished(game)?;
// info!("{:?}", instance_get(tx, instance_id)?);
instance_update(tx, instance)?;
Ok(())
}
pub fn bot_instance() -> Instance {
let mut instance = Instance::new();
let bot_player = bot_player();
let bot = bot_player.id;
instance.add_player(bot_player).unwrap();
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);
instance.add_player(player).expect("could not add player");
instance.player_ready(player_account).unwrap();
instance.player_ready(bot).unwrap();
return instance;
}
pub fn demo() -> Result<Vec<Player>, Error> {
let bot = bot_player();
// generate bot imgs for the client to see
for c in bot.constructs.iter() {
img::shapes_write(c.img)?;
};
let bot2 = bot_player();
// generate bot imgs for the client to see
for c in bot2.constructs.iter() {
img::shapes_write(c.img)?;
};
Ok(vec![bot, bot2])
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use mob::{bot_player, instance_mobs};
#[test] #[test]
fn instance_pve_test() { fn instance_pve_test() {
@ -854,7 +529,7 @@ mod tests {
let _instance = Instance::new(); let _instance = Instance::new();
let player_account = Uuid::new_v4(); let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account); let constructs = instance_mobs(player_account);
let _player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); let _player = Player::new(player_account, None, &"test".to_string(), constructs).set_bot(true);
} }
#[test] #[test]
@ -865,7 +540,7 @@ mod tests {
let player_account = Uuid::new_v4(); let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account); let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"a".to_string(), constructs); let player = Player::new(player_account, None, &"a".to_string(), constructs);
let a_id = player.id; let a_id = player.id;
instance.add_player(player).expect("could not add player"); instance.add_player(player).expect("could not add player");
@ -873,7 +548,7 @@ mod tests {
let player_account = Uuid::new_v4(); let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account); let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"b".to_string(), constructs); let player = Player::new(player_account, None, &"b".to_string(), constructs);
let b_id = player.id; let b_id = player.id;
instance.add_player(player).expect("could not add player"); instance.add_player(player).expect("could not add player");
@ -902,7 +577,7 @@ mod tests {
let player_account = Uuid::new_v4(); let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account); let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"a".to_string(), constructs); let player = Player::new(player_account, None, &"a".to_string(), constructs);
let a_id = player.id; let a_id = player.id;
instance.add_player(player).expect("could not add player"); instance.add_player(player).expect("could not add player");
@ -910,7 +585,7 @@ mod tests {
let player_account = Uuid::new_v4(); let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account); let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"b".to_string(), constructs); let player = Player::new(player_account, None, &"b".to_string(), constructs);
let b_id = player.id; let b_id = player.id;
instance.add_player(player).expect("could not add player"); instance.add_player(player).expect("could not add player");
@ -942,7 +617,7 @@ mod tests {
let player_account = Uuid::new_v4(); let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account); let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"a".to_string(), constructs); let player = Player::new(player_account, None, &"a".to_string(), constructs);
let _a_id = player.id; let _a_id = player.id;
instance.add_player(player).expect("could not add player"); instance.add_player(player).expect("could not add player");

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