Skip to content

Commit

Permalink
Merge pull request #72 from dan-atack/Dan-04-28-Hype-attack-chain-logic
Browse files Browse the repository at this point in the history
Dan 04 28 hype attack chain logic
  • Loading branch information
dan-atack authored May 6, 2021
2 parents 0f786cc + 20bda31 commit badd12c
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,14 @@ const CombatEnvironment = () => {
});
// If none of the player's moves is in range of the baddie, skip to the next phase:
if (!(baddieInXRange && baddieInYRange)) {
// This code down here is from the combat ui for the attack button handlers: IT SHOULD BE IN A HELPER FUNCTION!
// This code down here is the same as that in the combat ui for the attack button handlers:
setEnemyAttackRadius([]);
setPlayerMoveOptions([]);
dispatch(setCombatPhase('specialEvent'));
} else {
console.log(baddieInXRange, baddieInYRange, 'Baddie should be in range.')
}
break; // Await input from the attack selection inputs and no more.
case 'specialEvent':
setPlayerAttacksInQueue(0);
setPlayerAttacksInQueue([]);
specialEventLogic(dispatch, setCombatPhase, level);
break;
case 'baddieMove':
Expand Down Expand Up @@ -195,6 +193,7 @@ const CombatEnvironment = () => {
baddieCoords={baddieCoords}
baddieOrientation={baddieOrientation}
baddieDecision={baddieDecision}
playerAttacksInQueue={playerAttacksInQueue}
/>}
{/* <Versus/> */}
<CombatUi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ const CombatUi = ({turn, setEnemyAttackRadius}) => {
const encounter = baddieData.find(obj => obj.level === level);

// This handles the clicking of any skill button, generated by the map function.
const handleClick = async (skill) => {
const handleClick = async (skill, cost) => {
if (!playerIsDead && combatPhase === 'playerAction') { // Skill can only be used if player is alive and it's their action phase
dispatch(setReflexCheck(skill.id))
setSelectedSkill(skill);
setEnemyAttackRadius([])
setPlayerMoveOptions([])
const range = await attackRange(skill, playerCoords, seed.width, seed.height, seed.obstructions);
setPlayerAttackRadius(range);
setPlayerAttacksInQueue(playerAttacksInQueue + 1);
setPlayerAttacksInQueue([...playerAttacksInQueue, skill]);
setPlayerHype(playerHype - cost);
}
}

Expand All @@ -66,9 +67,7 @@ const CombatUi = ({turn, setEnemyAttackRadius}) => {
{combatPhase === 'victory' && <VictoryButton />}
{doReflexCheck &&
<ReflexCheck
move={playerMoves.find((move) => move.id === reflexCheckId)}
combo={randomCombo}
numPrevMoves={playerAttacksInQueue}
style={{ position: 'absolute', top: '0px', right: '50px' }}
/>}
<HealthHud src={healthbar}/>
Expand All @@ -83,7 +82,7 @@ const CombatUi = ({turn, setEnemyAttackRadius}) => {
key={`player-move-${skill.id}`}
skill={skill}
handleClick={handleClick}
numPrevMoves={playerAttacksInQueue}
numPrevMoves={playerAttacksInQueue.length}
playerHype={playerHype}
/>
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const ResetButton = () => {
const handleClick = () => {
// This is ugly AF and terribly not dry... but it *does* work!
// TODO: Make default values object for greater flexibility if you ever wanna reset these hard-coded bastards.
setPlayerAttacksInQueue(0);
setPlayerAttacksInQueue([]);
setPlayerHealth(100);
setPlayerAttackRadius([]);
setPlayerHype(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const SkillButton = (props) => {
// Hype cost is equal to the baseline cost times the amount of attacks already selected (the first one is free though)
// E.G. 1st attack = 0 hype, 2nd attack = 10 hype, 3rd attack = 30 hype, etc.
<Wrapper
onClick={() => handleClick(skill)}
onClick={() => handleClick(skill, currentHypeCost)}
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
>
Expand All @@ -27,8 +27,13 @@ const SkillButton = (props) => {
)
} else {
return (
<Wrapper>
Too expensive!
<Wrapper
style={{ backgroundColor: 'gray' }}
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
>
{skill.icon && <img src={require(`../../../../assets/actionIcons/${skill.icon}`)} alt={skill.name}/> || skill.name}
{open && <Tooltip skill={skill} costModifier={costModifier}/>}
</Wrapper>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const DevDisplay = ({
baddieHP,
baddieCoords,
baddieOrientation,
baddieDecision
baddieDecision,
playerAttacksInQueue,
}) => {
return (
<Wrapper>
Expand All @@ -23,6 +24,7 @@ const DevDisplay = ({
<p>Baddie Coords: ({baddieCoords.x}, {baddieCoords.y})</p>
<p>Baddie Orientation: {baddieOrientation}</p>
<p>Baddie Decision: {baddieDecision.name}</p>
<p>playerAttacksInQueue: {playerAttacksInQueue.length}</p>
</Wrapper>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const LevelVisualGenerator = ({row, baddieCoords, playerMove, playerAttack, enem
seed.obstructions.find((obs) => sq.x === obs.x && sq.y === obs.y)
) {
// This renders an obstacle if the map file contains an obstacle at the specified coords:
console.log(sq.x, sq.y);
// console.log(sq.x, sq.y);
return (
<ObstructionTile
key={Math.random() * 100000}
Expand Down
89 changes: 59 additions & 30 deletions client/src/components/Game/ReflexCheck/ReflexCheck.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,86 @@ import ugh from '../../../assets/sounds/ugh-01.mp3';
import { useTime } from '../../../hooks/useTime';
import { useDispatch } from 'react-redux';
import combatState from '../../../state';
import { useRecoilState } from 'recoil';
import { useRecoilState, useRecoilValue } from 'recoil';
import { setCombatPhase, stopReflexCheck } from '../../../actions';

// Args: move = the data object for the move, combo = integer for which of the 3 possible key combos, numPrevMoves = integer.
function ReflexCheck({ move, combo, numPrevMoves }) {
// Combo = integer for which of the 3 possible key combos to use for the reflex check:
function ReflexCheck({ combo }) {
const dispatch = useDispatch();
// Update time at very short interval to 'sample' for updates at high frequency:
let now = useTime(20);
const [then, setThen] = React.useState(0);
// Reduce time by ten percent per number of moves previously executed:
const timeToPerform = move.time * (1 - numPrevMoves * 0.1);
// Then keep track of the time remaining in state:
const [timeLeft, setTimeLeft] = React.useState(timeToPerform);
const [timeString, setTimeString] = React.useState('00:00.00');
// Keep track of current key index and number of keys pressed - reflex check ends as soon as a wrong key is pressed.:
const [currentKey, setCurrentKey] = React.useState(0);
const [keystrokes, setKeystrokes] = React.useState(0);
// Replace this with a useSelector?
// Local state determines when the Reflex Check is over:
const [failStatus, setFailStatus] = React.useState(false);
const [successStatus, setSuccessStatus] = React.useState(false);
// Baddie HP, to be set (reduced) by a successful completion of the reflex check:
const [baddieHP, setBaddieHP] = useRecoilState(combatState.baddieHP);
// Player Hype is also affected by reflex check outcomes:
const [playerHype, setPlayerHype] = useRecoilState(combatState.playerHype);
// Now using state to pass all move data:
const playerMovesInQueue = useRecoilValue(combatState.playerAttacksInQueue);
// Keep track of where we're at in the attack queue:
const [attackQueueIndexPosition, setAttackQueueIndexPosition] = React.useState(0);
// Set initial time to execute the first combo:
const timeToPerform = playerMovesInQueue[0].time * (1 - playerMovesInQueue.length * 0.1);
// Then keep track of the time remaining in state (these can be reset in the event of a multi-attack):
const [timeLeft, setTimeLeft] = React.useState(timeToPerform);
const [timeString, setTimeString] = React.useState('00:00.00');
// Introducing sound effects:
const [playUghSound] = useSound(ugh);
// This function calculates the damage you inflict if you succeed:
const determineDamage = () => {
const remainder = timeLeft / move.time * 100;
const remainder = timeLeft / playerMovesInQueue[attackQueueIndexPosition].time * 100;
if (remainder > 75) {
return move.baseDmg * 2; // Double damage if you have more than 75% time left
return playerMovesInQueue[attackQueueIndexPosition].baseDmg * 2; // Double damage if you have more than 75% time left
} else if (remainder > 50) {
return Math.ceil(move.baseDmg * 1.5); // 50% bonus damage if you have more than 50% time left
return Math.ceil(playerMovesInQueue[attackQueueIndexPosition].baseDmg * 1.5); // 50% bonus damage if you have more than 50% time left
} else {
return move.baseDmg; // Otherwise you just do the base dmg.
return playerMovesInQueue[attackQueueIndexPosition].baseDmg; // Otherwise you just do the base dmg.
}
}
// If you hit, Hype earned = (max hype plus combo length in letters) times time remaining.
const determineHype = () => {
const remainder = timeLeft / move.time; // Just the fraction this time
const stringLength = move.combos[0].length // Find how many keys were needed to complete the move
return Math.floor((move.maxHype + stringLength) * remainder);
const remainder = timeLeft / playerMovesInQueue[attackQueueIndexPosition].time; // Just the fraction this time
const stringLength = playerMovesInQueue[attackQueueIndexPosition].combos[combo].length // Find how many keys were needed to complete the move
return Math.floor((playerMovesInQueue[attackQueueIndexPosition].maxHype + stringLength) * remainder);
}

// What happens if you complete the LAST combo in the queue:
const finalAttackSuccess = () => {
setSuccessStatus(true);
dispatch(setCombatPhase('specialEvent'));
dispatch(stopReflexCheck());
}

// What happens if you are NOT on the last combo in the queue:
const setupNextAttack = () => {
// Reset timer to the time value of the next move, minus 10 percent per previously executed move:
setTimeLeft(playerMovesInQueue[attackQueueIndexPosition].time * (1 - playerMovesInQueue.length * 0.1));
// Reset keystroke tracker:
setCurrentKey(0);
setKeystrokes(0);
}

// What happens when you complete a combo (regardless of how many attacks are queued):
const attackSuccess = () => {
playUghSound();
console.log(`Player hits baddie with ${playerMovesInQueue[attackQueueIndexPosition].name} for ${determineDamage()} damage!`);
console.log(`Player gains ${determineHype()} hype points!`);
setBaddieHP(baddieHP - determineDamage());
setPlayerHype(playerHype + determineHype());
// If there are multiple attacks queued and you didn't just do the last one, setup the next move:
if (playerMovesInQueue.length > 1 && attackQueueIndexPosition < playerMovesInQueue.length - 1) {
setAttackQueueIndexPosition(attackQueueIndexPosition + 1);
setupNextAttack();
} else {
finalAttackSuccess();
}
}

// One Effect to handle all the keys:
React.useEffect(() => {
// If you've failed, ensure the string is exactly zero, and dispatch to advance state AND declare an end to the reflex check):
Expand Down Expand Up @@ -76,7 +113,7 @@ function ReflexCheck({ move, combo, numPrevMoves }) {
if (!failStatus) {
setFailStatus(true);
// Check each key in the combo against the keydown event:
move.combos[combo].forEach((key, idx) => {
playerMovesInQueue[attackQueueIndexPosition].combos[combo].forEach((key, idx) => {
if (ev.code === `Key${key}`) {
// If the index of the event key is the current key, advance the sequence:
if (idx === currentKey) {
Expand All @@ -87,32 +124,24 @@ function ReflexCheck({ move, combo, numPrevMoves }) {
})
setKeystrokes(keystrokes + 1);
}
}; // IF SUCCESS:
if (move.combos[combo].length === currentKey && !failStatus) {
setSuccessStatus(true);
playUghSound();
console.log(`Player hits baddie with ${move.name} for ${determineDamage()} damage!`);
console.log(`Player gains ${determineHype()} hype points!`);
setBaddieHP(baddieHP - determineDamage());
setPlayerHype(playerHype + determineHype());
dispatch(setCombatPhase('specialEvent'));
dispatch(stopReflexCheck());
}; // IF COMBO IS EXECUTED SUCCESSFULLY:
if (playerMovesInQueue[attackQueueIndexPosition].combos[combo].length === currentKey && !failStatus) {
attackSuccess();
}
window.addEventListener('keydown', handleKeydown);

return () => {

window.removeEventListener('keydown', handleKeydown);
};
}
}, [now]);
return (
<Wrapper failure={failStatus} success={successStatus}>
<Ticker progress={timeLeft/timeToPerform * 100}/>
<MoveHeader><i>MOVE: {move.name.toUpperCase()}</i></MoveHeader>
<MoveHeader><i>MOVE: {playerMovesInQueue[attackQueueIndexPosition].name.toUpperCase()}</i></MoveHeader>
<FlexDiv>
<h4>KEYS:</h4>
{move.combos[combo].map((key, idx) => {
{playerMovesInQueue[attackQueueIndexPosition].combos[combo].map((key, idx) => {
return(
<KeyPad key={Math.random() * 10000000} done={idx < currentKey}>{key}</KeyPad>
)
Expand Down
2 changes: 1 addition & 1 deletion client/src/state/combatState.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const playerAP = atom({

export const playerAttacksInQueue = atom({
key: 'playerMovesInQueue',
default: 0, // this just counts how many moves you've made in a given combat round.
default: [], // every move in the queue increases hype cost; list is reset each combat round.
})

export const playerHype = atom({
Expand Down

0 comments on commit badd12c

Please sign in to comment.