mirror of
https://github.com/CraftyBoss/SuperMarioOdysseyOnline.git
synced 2024-11-25 04:35:17 +00:00
new: Freeze-Tag game mode
Similar to Hide & Seek with two teams: chasers (seekers) and runners (hiders). The chasers try to catch all runners in a given round time. When all runners are catched the chasers win and the round ends immediately (wipe out). When the round time runs out, the runners win the round. Instead of joining the chasers runners are frozen when touching a chaser or dying. (Frozen at the last safe position when falling off the map.) In the frozen state they can spectate other players. Frozen runners are unfrozen when they are touched by other runners. Players gain points instead of accumulating time based on their performance: - Chasers receive points by catching runners or if they freeze themselves. - Runners receive points over time by not being frozen or by unfreezing other runners. - The team that wins gets extra points. A round is limited to the current stage, so checkpoint warps and stage changes are disabled. To be able to start a new round (with R + Up) a player first needs to become a "host". The host status can be activated in the freeze-tag config menu. --- Changes compared to the original cherry-picked version: - fix: try to not mix legacy update types between freeze-tag and other game modes - (see GameModeInf.h) - fix: change the player we spectate if they disconnect even if they aren't at the end of the list - fix: don't add players to an already started round (they missed the round start) - fix: don't start rounds w/ survival events when the round length decreases - fix: a round can end w/ wipeout if there is only one runner (cancelled for legacy clients) - fix: don't score a rescue event after a wipeout - fix: cancel the round when deactivating freeze-tag - change: combined freeze-tag config menu buttons to single toggle buttons - change: not closing the freeze-tag config menu after changing an option - change: split host mode & round length into two distinct options - change: possibility to always change settings in the FT config menu - change: send gamemode NONE when Freeze-Tag is selected but not active - change: improved distance calculations in squared distance space - change: short invulnerability at round start - add: toggle options to control mario/cappy collision/bounciness - add: show freeze-tag specific controls in the debug menu - depending on the current state: host, frozen, debug mode - add: can only start a round with at least one runner and one chaser - add: automatically cancel a round after 5s if there are not enough chasers/runners - add: show who manually started/cancelled a round - add: UI for players in other game modes outside of rounds - remove: custom boot screen, because it directly loads into the game - not seeing the main menu is an issue for empty save files - this is added again separately w/ the next commit (cherry picked from commit 782d0bd0831348c5ae327d50d0ab98f16f6111e3) (cherry picked from commit 6525060e108a604636bd8f952ce1910a8050ee9a) (cherry picked from commit 918f61fbfd619d781d88dc74878d392c48cfa480) (cherry picked from commit db8331b158d3277388dcb8d0df2da43d78988c7d) (cherry picked from commit 991aadb0f22ed0c745c218fdaae6ec43bb243e15) (cherry picked from commit fe17cff52b4241418b37cd30ac3fa0af40d3b9f6) (cherry picked from commit c5d6d428c6b25a5cdb0ff76c474b584a65b4bc60) (cherry picked from commit b00088f08fa489b35c439987780bff718bb21417) (cherry picked from commit 42c89927db611c662d9c01ff00c0e657e999e583) (cherry picked from commit 4818a24d12caa2af64ca6378a5a5f33b53c0b59a) (cherry picked from commit d707f2cf7dbeeb96602f9e45fa431fdd0ed476c3) (cherry picked from commit c1791080f1aa3ef2e49bee7703cc4b365d122922) (cherry picked from commit 96aff7dd7167d8244acef7884fa3503d4c3f868a) (cherry picked from commit 1d774cc6ad884de571148ca77d212b55140699a5) (cherry picked from commit bafca58e3906a6cd1046d7e7aed9512ab648c95d) (cherry picked from commit 5cd4319c20417f317f75338ad61222fd7bee3ec7) (cherry picked from commit a1575998c0835180a795a2a909a0b2c0942902d5) (cherry picked from commit a14e813da77c2d6d4c4b6f82fd52d22c4a9ffb55) (cherry picked from commit 1cd6c1f9443621ca0cd85fb277763cf5a6f10244) (cherry picked from commit 7e5f16a3b8b4696c934a4acf3ccc544ac93af9ca) (cherry picked from commit d022dde1de568b50e36ec809ff37d8ef42c81afa) (cherry picked from commit c09456bd1d6efa8ba2471c507654bedbbe062b0d) (cherry picked from commit 0fb6413ec91bbf679e9f8cea1aa512159101fa46) (cherry picked from commit dcac5dfe120177df0cfe7bf9a6ef1bfac5d67afc) (cherry picked from commit 6a412903aa34cd8fd740e0f898c75f2e01fe39c8) (cherry picked from commit 794b06c50a004dd258344592a4521f5d9a85db60) (cherry picked from commit e76e669074a671130ef453402033c65fdd004066) (cherry picked from commit bfc14c15bb4ad5a517783c13d8f61a7cb5c40d4f) (cherry picked from commit d888e3b71b519b214a693df92ced9fe5ce05101b) (cherry picked from commit 8db6e28cf42499e95f2924c22acb026926c09be5) (cherry picked from commit 784d9e6de300d13f761a4f3b21e1ec803f762b72) (cherry picked from commit 43ee8bfecf5abb090aa98089730e79e93713ebfd) (cherry picked from commit ab78a812fd18781655ccf38e803f619ea427d1ac) (cherry picked from commit d2d37d0879a4e4959862d9dbc2256456c45df7cf) (cherry picked from commit 8cb299bbaeb3c35498558a23f2a5c4925b87e7d7) (cherry picked from commit d3622c7c53077da0858e617d13d51588ba529164) (cherry picked from commit e46f73576380569c200fad5248f88cc845c3ee3b) (cherry picked from commit 8364cd18bc9acd4ffa554c8cc4df9e5c9432ff0c) (cherry picked from commit e383c7700eb68345773eafbed3fd1cd86cfb37f2) (cherry picked from commit 32895eaa17d8f58f8f838f143ec204eb39b69509) (cherry picked from commit dd9a98c02f4b8761d30d1bbb5a6604324ee721df) (cherry picked from commit 63937fa7c5e316859e0a57188a803dbf57fcb202) (cherry picked from commit eac79954958a591a0deb244d9b564f9c01a7fe41) (cherry picked from commit f46e6a1e426a4c14708ee48f91348294877e7f8e) (cherry picked from commit 8b15d09d2f093e7c9960f4395bf54478122e0273) (cherry picked from commit b56e0b5bcb2cde4f0723252ee6f00b55af47e6b2) Co-authored-by: Robin C. Ladiges <rcl.git@blackpinguin.de>
This commit is contained in:
parent
0c9d6b2159
commit
6af21f1b8f
63 changed files with 3304 additions and 14 deletions
|
@ -38,6 +38,7 @@ SOURCES := \
|
|||
source/puppets \
|
||||
source/server/hns \
|
||||
source/server/sardines \
|
||||
source/server/freeze-tag \
|
||||
source/server/gamemode \
|
||||
source/server/gamemode/modifiers \
|
||||
source/server \
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include "helpers.hpp"
|
||||
#include "algorithms/CaptureTypes.h"
|
||||
|
||||
#include "server/freeze-tag/FreezePlayerBlock.h"
|
||||
|
||||
class PuppetActor : public al::LiveActor {
|
||||
public:
|
||||
PuppetActor(const char* name);
|
||||
|
@ -86,6 +88,8 @@ class PuppetActor : public al::LiveActor {
|
|||
bool mIsCaptureModel = false;
|
||||
|
||||
float mClosingSpeed = 0;
|
||||
|
||||
FreezePlayerBlock* mFreezeTagIceBlock = nullptr;
|
||||
};
|
||||
|
||||
PlayerCostumeInfo* initMarioModelPuppet(
|
||||
|
|
28
include/al/alCollisionUtil.h
Normal file
28
include/al/alCollisionUtil.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/collision/Collider.h"
|
||||
|
||||
namespace al {
|
||||
struct Triangle;
|
||||
};
|
||||
|
||||
namespace alCollisionUtil {
|
||||
bool getFirstPolyOnArrow(
|
||||
al::IUseCollision const*,
|
||||
sead::Vector3f* result,
|
||||
al::Triangle* null,
|
||||
sead::Vector3f const& origin,
|
||||
sead::Vector3f const& ray,
|
||||
al::CollisionPartsFilterBase const* nope,
|
||||
al::TriangleFilterBase const* nothanks
|
||||
);
|
||||
|
||||
bool getHitPosOnArrow(
|
||||
al::IUseCollision const*,
|
||||
sead::Vector3f*,
|
||||
sead::Vector3f const&,
|
||||
sead::Vector3f const&,
|
||||
al::CollisionPartsFilterBase const*,
|
||||
al::TriangleFilterBase const*
|
||||
);
|
||||
};
|
15
include/al/camera/CameraOffsetCtrlPreset.h
Normal file
15
include/al/camera/CameraOffsetCtrlPreset.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/byaml/ByamlIter.h"
|
||||
#include "CameraOffsetPreset.h"
|
||||
|
||||
namespace al {
|
||||
class CameraOffsetCtrlPreset {
|
||||
public:
|
||||
float getOffset() const;
|
||||
void load(al::ByamlIter const&);
|
||||
|
||||
const char* mUnkVTable;
|
||||
CameraOffsetPreset* mOffsetPreset;
|
||||
};
|
||||
}
|
12
include/al/camera/CameraOffsetPreset.h
Normal file
12
include/al/camera/CameraOffsetPreset.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
namespace al {
|
||||
struct CameraOffsetPresetData;
|
||||
|
||||
class CameraOffsetPreset {
|
||||
public:
|
||||
CameraOffsetPresetData* mOffsetPresetData;
|
||||
int mUnkInt;
|
||||
int mUnk;
|
||||
};
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/camera/CameraTicket.h"
|
||||
|
||||
namespace al
|
||||
{
|
||||
class CameraPoseUpdater {
|
||||
public:
|
||||
unsigned char padding_A0[0xA0];
|
||||
al::CameraTicket* mTicket; // 0xA0
|
||||
};
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "CameraPoserFlag.h"
|
||||
#include "CameraStartInfo.h"
|
||||
#include "CameraOffsetCtrlPreset.h"
|
||||
#include "CameraTurnInfo.h"
|
||||
#include "CameraObjectRequestInfo.h"
|
||||
#include "al/rail/RailRider.h"
|
||||
|
@ -28,7 +29,6 @@ namespace al {
|
|||
class CameraAngleCtrlInfo;
|
||||
class CameraAngleSwingInfo;
|
||||
class CameraArrowCollider;
|
||||
class CameraOffsetCtrlPreset;
|
||||
class CameraParamMoveLimit;
|
||||
class GyroCameraCtrl;
|
||||
class SnapShotCameraCtrl;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "actors/PuppetActor.h"
|
||||
#include "actors/PuppetHackActor.h"
|
||||
#include "server/freeze-tag/FreezePlayerBlock.h"
|
||||
|
||||
namespace al {
|
||||
class AllDeadWatcher;
|
||||
|
@ -68,6 +69,7 @@ namespace al {
|
|||
|
||||
__attribute((used)) static al::NameToCreator<al::createActor> actorEntries[] = {
|
||||
// CUSTOM ACTOR ENTRIES HERE
|
||||
{"FreezePlayerBlock", &al::createCustomActor<FreezePlayerBlock>},
|
||||
{"PuppetActor", &al::createCustomActor<PuppetActor>},
|
||||
{"PuppetHackActor", &al::createCustomActor<PuppetHackActor>},
|
||||
// VANILLA ACTOR ENTRIES
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "CameraPoserFactory.h"
|
||||
#include "al/factory/Factory.h"
|
||||
|
||||
#include "cameras/CameraPoserActorSpectate.h"
|
||||
#include "cameras/CameraPoserCustom.h"
|
||||
|
||||
class CameraPoserFollowLimit;
|
||||
|
@ -53,6 +54,7 @@ static al::NameToCreator<al::createCameraPoser> poserEntries[] = {
|
|||
{"映像撮影レール", &al::createCameraPoserFunction<al::CameraPoserRailMoveMovie>},
|
||||
// Custom Posers
|
||||
{"CameraPoserCustom", &cc::createCustomCameraPoser<cc::CameraPoserCustom>}, // al::CameraPoserFollowSimple
|
||||
{"CameraPoserActorSpectate", &cc::createCustomCameraPoser<cc::CameraPoserActorSpectate>}, // al::CameraPoserFollowSimple
|
||||
};
|
||||
|
||||
// 0xB in size
|
||||
|
|
|
@ -341,6 +341,12 @@ namespace al
|
|||
|
||||
// misc
|
||||
|
||||
uint32_t getPostProcessingFilterPresetId(const Scene*);
|
||||
void incrementPostProcessingFilterPreset(Scene const*);
|
||||
void decrementPostProcessingFilterPreset(Scene const*);
|
||||
void validatePostProcessingFilter(al::Scene const*);
|
||||
void invalidatePostProcessingFilter(al::Scene const*);
|
||||
|
||||
void readSaveDataSync(const char* dataFile, uint, uint);
|
||||
|
||||
bool isSuccessSaveDataSequence();
|
||||
|
|
34
include/al/util/RandomUtil.h
Normal file
34
include/al/util/RandomUtil.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include <sead/math/seadVector.h>
|
||||
|
||||
namespace al {
|
||||
float getRandom();
|
||||
float getRandom(float);
|
||||
float getRandom(float, float);
|
||||
int getRandom(int);
|
||||
int getRandom(int, int);
|
||||
float getRandomDegree();
|
||||
float getRandomRadian();
|
||||
void getRandomVector(sead::Vector3f*, float);
|
||||
void getRandomDir(sead::Vector3f*);
|
||||
void getRandomDirH(sead::Vector3f*, const sead::Vector3f&);
|
||||
void getRandomOnCircle(sead::Vector2f*, float);
|
||||
void getRandomInCircle(sead::Vector2f*, float);
|
||||
void getRandomInCircle(sead::Vector3f*, const sead::Vector3f&, const sead::Vector3f&, float);
|
||||
void getRandomOnSphere(sead::Vector3f*, float);
|
||||
void getRandomInSphere(sead::Vector3f*, float);
|
||||
void calcRandomDirInCone(sead::Vector3f*, const sead::Vector3f&, float);
|
||||
void getRandomInSphereMinMaxRadius(sead::Vector3f*, float, float);
|
||||
void initRandomSeed(unsigned int);
|
||||
void initRandomSeedByTick();
|
||||
void initRandomSeedByString(const char*);
|
||||
void getRandomContext(unsigned int*, unsigned int*, unsigned int*, unsigned int*);
|
||||
void setRandomContext(unsigned int, unsigned int, unsigned int, unsigned int);
|
||||
void makeRandomDirXZ(sead::Vector3f*);
|
||||
void calcBoxMullerRandomGauss();
|
||||
void makeBoxMullerRandomGauss(sead::Vector2f*, float, float);
|
||||
void addRandomVector(sead::Vector3f*, const sead::Vector3f&, float);
|
||||
void turnRandomVector(sead::Vector3f*, const sead::Vector3f&, float);
|
||||
bool isHalfProbability();
|
||||
}
|
25
include/al/wipe/WipeHolder.h
Normal file
25
include/al/wipe/WipeHolder.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
namespace al {
|
||||
class WipeSimple;
|
||||
|
||||
class WipeHolder {
|
||||
public:
|
||||
WipeHolder(int);
|
||||
void registerWipe(const char*, al::WipeSimple*);
|
||||
void startClose(const char*, int);
|
||||
void findWipe(const char*) const;
|
||||
void startCloseByInfo(const char*);
|
||||
void findInfo(const char*) const;
|
||||
bool tryStartClose(const char*, int);
|
||||
bool tryStartCloseByInfo(const char*);
|
||||
void startCloseEnd(const char*);
|
||||
void startOpen(int);
|
||||
void isExistInfo(const char*) const;
|
||||
bool tryFindInfo(const char*) const;
|
||||
int getCloseTimeByInfo(const char*) const;
|
||||
bool isCloseEnd() const;
|
||||
bool isOpenEnd() const;
|
||||
bool isCloseWipe(const char*) const;
|
||||
};
|
||||
}
|
22
include/al/wipe/WipeSimple.h
Normal file
22
include/al/wipe/WipeSimple.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/layout/LayoutActor.h"
|
||||
|
||||
namespace al {
|
||||
class WipeSimple : al::LayoutActor {
|
||||
WipeSimple(const char*, char*, const al::LayoutInitInfo*, al::LayoutActor*, const char*);
|
||||
void appear() override;
|
||||
|
||||
void startClose();
|
||||
void tryStartClose();
|
||||
void startCloseEnd();
|
||||
void StartOpen();
|
||||
void tryStartOpen();
|
||||
void isCloseEnd() const;
|
||||
void isOpenEnd() const;
|
||||
|
||||
void exeClose();
|
||||
void exeCloseEnd();
|
||||
void exeOpen();
|
||||
};
|
||||
}
|
41
include/cameras/CameraPoserActorSpectate.h
Normal file
41
include/cameras/CameraPoserActorSpectate.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/LiveActor/LiveActor.h"
|
||||
#include "al/camera/CameraPoser.h"
|
||||
|
||||
#include "game/Player/PlayerActorBase.h"
|
||||
#include "sead/math/seadVector.h"
|
||||
|
||||
// cc = custom cameras
|
||||
namespace cc {
|
||||
class CameraPoserActorSpectate : public al::CameraPoser {
|
||||
public:
|
||||
CameraPoserActorSpectate(char const*);
|
||||
|
||||
virtual void start(al::CameraStartInfo const&) override;
|
||||
virtual void init() override;
|
||||
|
||||
void reset(void);
|
||||
|
||||
virtual void update(void) override;
|
||||
virtual void movement(void) override;
|
||||
|
||||
void setTargetActor(sead::Vector3f* target) { mTargetActorPos = target; }
|
||||
void setPlayer(PlayerActorBase* base) { mPlayer = base; };
|
||||
|
||||
void calcRotVec(sead::Vector3f targetDir, sead::Vector3f* rotatedVec, sead::Vector3f* rightVec);
|
||||
|
||||
float mAngle = 20.f;
|
||||
float mDistMax = 1400.f;
|
||||
float mDistLerp = 1400.f;
|
||||
float mYOffset = 100.f;
|
||||
float mDefaultFovy = 35.f;
|
||||
int mFrameCounter = 0;
|
||||
|
||||
sead::Vector2f mInterpRStick = sead::Vector2f::zero;
|
||||
|
||||
private:
|
||||
sead::Vector3f* mTargetActorPos = nullptr;
|
||||
PlayerActorBase* mPlayer = nullptr;
|
||||
};
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
#include "types.h"
|
||||
#include "UniqueObjInfo.h"
|
||||
#include "GameProgressData.h"
|
||||
#include "game/Player/PlayerHitPointData.h"
|
||||
|
||||
#include "sead/container/seadPtrArray.h"
|
||||
#include "sead/math/seadVector.h"
|
||||
|
@ -35,8 +36,6 @@ class HintPhotoData;
|
|||
class ShopTalkData;
|
||||
class RaceRecord;
|
||||
|
||||
class PlayerHitPointData;
|
||||
|
||||
class GameDataHolder;
|
||||
class ShineInfo;
|
||||
|
||||
|
@ -266,7 +265,7 @@ class GameDataFile {
|
|||
void getPlayerStartId(void);
|
||||
void getStageNameNext(void);
|
||||
void getStageNameCurrent(void);
|
||||
void getPlayerHitPointData(void);
|
||||
PlayerHitPointData* getPlayerHitPointData() const;
|
||||
void getLastUpdateTime(void);
|
||||
void getPlayTimeTotal(void);
|
||||
int getMainScenarioNo(int) const;
|
||||
|
@ -444,7 +443,7 @@ class GameDataFile {
|
|||
void* qword810;
|
||||
bool byte818;
|
||||
void* qword820;
|
||||
bool byte828;
|
||||
bool mIsKidsMode;
|
||||
sead::PtrArrayImpl sead__ptrarrayimpl830;
|
||||
u16 word840;
|
||||
bool byte842;
|
||||
|
|
11
include/game/Interfaces/ByamlSave.h
Normal file
11
include/game/Interfaces/ByamlSave.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/byaml/ByamlIter.h"
|
||||
#include "al/byaml/writer/ByamlWriter.h"
|
||||
#include "al/hio/HioNode.h"
|
||||
|
||||
class ByamlSave : public al::HioNode {
|
||||
public:
|
||||
virtual void write(al::ByamlWriter*) = 0;
|
||||
virtual void read(al::ByamlIter const&) = 0;
|
||||
};
|
10
include/game/Layouts/CounterLifeCtrl.h
Normal file
10
include/game/Layouts/CounterLifeCtrl.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/layout/LayoutActor.h"
|
||||
|
||||
class CounterLifeCtrl : public al::LayoutActor {
|
||||
public:
|
||||
void appear();
|
||||
void end();
|
||||
void kill();
|
||||
};
|
|
@ -8,12 +8,12 @@
|
|||
#include "al/LiveActor/LiveActor.h"
|
||||
#include "game/Interfaces/IUsePlayerHack.h"
|
||||
#include "game/Player/PlayerInput.h"
|
||||
#include "game/Player/PlayerRecoverySafetyPoint.h"
|
||||
#include "game/Player/HackCap.h"
|
||||
#include "game/Player/PlayerCollider.h"
|
||||
#include "game/Player/HackCap/CapTargetInfo.h"
|
||||
|
||||
struct HackEndParam;
|
||||
struct PlayerRecoverySafetyPoint;
|
||||
struct PlayerDamageKeeper;
|
||||
struct IPlayerModelChanger;
|
||||
struct IUsePlayerHeightCheck;
|
||||
|
|
33
include/game/Player/PlayerHitPointData.h
Normal file
33
include/game/Player/PlayerHitPointData.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include "game/Interfaces/ByamlSave.h"
|
||||
|
||||
class PlayerHitPointData : public ByamlSave {
|
||||
public:
|
||||
PlayerHitPointData(void);
|
||||
void write(al::ByamlWriter*) override;
|
||||
void read(al::ByamlIter const&) override;
|
||||
|
||||
void setKidsModeFlag(bool);
|
||||
void init(void);
|
||||
void recoverMax(void);
|
||||
int getCurrent(void) const;
|
||||
int getMaxCurrent(void) const;
|
||||
int getMaxWithItem(void) const;
|
||||
int getMaxWithoutItem(void) const;
|
||||
bool isMaxCurrent(void) const;
|
||||
bool isMaxWithItem(void) const;
|
||||
void getMaxUpItem(void);
|
||||
void recover(void);
|
||||
void recoverForDebug(void);
|
||||
void damage(void);
|
||||
void kill(void);
|
||||
void forceNormalMode(void);
|
||||
void endForceNormalMode(void);
|
||||
bool isForceNormalMode(void) const;
|
||||
|
||||
bool mIsKidsMode; // 0x8
|
||||
int mCurrentHit; // 0xC
|
||||
bool mIsHaveMaxUpItem; // 0x10
|
||||
bool mIsForceNormalHealth; // 0x11
|
||||
};
|
27
include/game/Player/PlayerRecoverySafetyPoint.h
Normal file
27
include/game/Player/PlayerRecoverySafetyPoint.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/LiveActor/LiveActor.h"
|
||||
#include "al/collision/Collider.h"
|
||||
#include "al/sensor/HitSensor.h"
|
||||
#include "game/Interfaces/IUseDimension.h"
|
||||
#include "game/Player/HackCap.h"
|
||||
#include "sead/math/seadVector.h"
|
||||
|
||||
class PlayerRecoverySafetyPoint {
|
||||
public:
|
||||
sead::Vector3f* getSafetyPoint();
|
||||
void startRecovery(float);
|
||||
void startBubbleWait();
|
||||
void noticeDangerousPoint(sead::Vector3f const&, bool);
|
||||
|
||||
al::LiveActor* mActor; //0x00
|
||||
HackCap* mHackCap; //0x08
|
||||
IUseDimension* mIUseDimension; //0x10
|
||||
al::CollisionPartsFilterBase* mCollisionPartsFilterBase; //0x18
|
||||
al::HitSensor* mHitSensor; //0x20
|
||||
const char mUnk1[0x1c]; //0x28
|
||||
sead::Vector3f mSafetyPointPos; //0x44
|
||||
sead::Vector3f mSafetyPointGrav; //0x50
|
||||
const char mUnk2[0x54];
|
||||
const char mUnk3[0x8];
|
||||
};
|
|
@ -4,13 +4,14 @@
|
|||
#include "game/StageScene/StageSceneLayout.h"
|
||||
#include "game/StageScene/StageSceneStatePauseMenu.h"
|
||||
|
||||
#include "al/wipe/WipeHolder.h"
|
||||
|
||||
namespace al {
|
||||
struct LayoutTextureRenderer;
|
||||
struct SimpleAudioUser;
|
||||
struct ParabolicPath;
|
||||
struct DemoSyncedEventKeeper;
|
||||
struct ChromakeyDrawer;
|
||||
struct WipeHolder;
|
||||
}
|
||||
|
||||
class StageScene : public al::Scene {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "al/layout/LayoutInitInfo.h"
|
||||
#include "al/layout/SimpleLayoutAppearWaitEnd.h"
|
||||
#include "al/nerve/NerveStateBase.h"
|
||||
#include "game/Layouts/CounterLifeCtrl.h"
|
||||
#include "game/Layouts/MapMini.h"
|
||||
|
||||
namespace al {
|
||||
|
@ -64,7 +65,7 @@ class StageSceneLayout : public al::NerveStateBase {
|
|||
bool isActionEndAll(void) const;
|
||||
|
||||
CoinCounter* mCoinCountLyt; // 0x18
|
||||
struct CounterLifeCtrl* mHealthLyt; // 0x20
|
||||
CounterLifeCtrl* mHealthLyt; // 0x20
|
||||
struct ShineCounter* mShineCountLyt; // 0x28
|
||||
CoinCounter* mCoinCollectLyt; // 0x30
|
||||
struct ShineChipLayoutParts* mShineChipPartsLyt; // 0x38
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "Packet.h"
|
||||
#include "server/Client.hpp"
|
||||
|
||||
template <typename UpdateType>
|
||||
struct PACKED GameModeInf : Packet {
|
||||
|
@ -11,7 +12,52 @@ struct PACKED GameModeInf : Packet {
|
|||
u8 mModeAndType = 0;
|
||||
|
||||
GameMode gameMode() {
|
||||
return (GameMode)((((mModeAndType >> 4) + 1) % 16) - 1);
|
||||
GameMode mode = (GameMode)((((mModeAndType >> 4) + 1) % 16) - 1);
|
||||
|
||||
if (mode == GameMode::LEGACY) {
|
||||
u8 type = mModeAndType & 0x0f;
|
||||
|
||||
// STATE and TIME (H&S or Sardines)
|
||||
if (type == 3) {
|
||||
return GameMode::LEGACY;
|
||||
}
|
||||
|
||||
// ROUNDCANCEL or FALLOFF (Freeze-Tag)
|
||||
if (type == 4 || type == 8) {
|
||||
return GameMode::FREEZETAG;
|
||||
}
|
||||
|
||||
// (ROUNDSTART or STATE) or (PLAYER or TIME)
|
||||
if (type == 1 || type == 2) {
|
||||
/**
|
||||
* These types could in the past overlap between different gamemodes.
|
||||
*
|
||||
* Individual STATE or TIME updates are only send by the server
|
||||
* when using `tag` server commands.
|
||||
*
|
||||
* If the user ID is for another user, assume it's for Freeze-Tag.
|
||||
*
|
||||
* This could be wrong, though. I assume here that the `tag` server
|
||||
* commands are unlikely to get used while playing Freeze-Tag,
|
||||
* because they don't make sense for it. (They could cause issues
|
||||
* for Freeze-Tag).
|
||||
*
|
||||
* When playing H&S or Sardines this will result in those updates
|
||||
* for other players are ignored. But the update for ourselves will get trough.
|
||||
*
|
||||
* Newer client versions in H&S or Sardines mode will resend their
|
||||
* combined STATUS and TIME after receiving such an update for themselves,
|
||||
* so that missing update should be auto corrected. When playing
|
||||
* together with older client versions, using the `tag` server
|
||||
* commands could cause issues because of this.
|
||||
*/
|
||||
if (this->mUserID != Client::getClientId()) {
|
||||
return GameMode::FREEZETAG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
UpdateType updateType() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "algorithms/PlayerAnims.h"
|
||||
#include "packets/Packet.h"
|
||||
|
||||
|
@ -60,4 +61,19 @@ struct PuppetInfo {
|
|||
inline bool hnsIsHiding() const { return !isIt; }
|
||||
inline bool snhIsPack() const { return isIt; }
|
||||
inline bool snhIsAlone() const { return !isIt; }
|
||||
|
||||
// Freeze Tag Gamemode Info
|
||||
uint16_t freezeTagScore = 0;
|
||||
bool isFreezeInRound = false;
|
||||
bool isFreezeTagRunner = true;
|
||||
bool isFreezeTagFreeze = false;
|
||||
bool isFreezeTagFallenOff = false; // When runner falls off and is automatically frozen, this flag is set
|
||||
float freezeIconSize = 0.f;
|
||||
|
||||
inline uint16_t ftGetScore() const { return freezeTagScore; }
|
||||
inline bool ftIsRunner() const { return isFreezeTagRunner; }
|
||||
inline bool ftIsChaser() const { return !isFreezeTagRunner; }
|
||||
inline bool ftIsFrozen() const { return isFreezeTagFreeze; }
|
||||
inline bool ftIsUnfrozen() const { return !isFreezeTagFreeze; }
|
||||
inline bool ftHasFallenOff() const { return isFreezeTagFallenOff; }
|
||||
};
|
||||
|
|
44
include/server/freeze-tag/FreezeHintArrow.h
Normal file
44
include/server/freeze-tag/FreezeHintArrow.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "al/util.hpp"
|
||||
#include "game/Player/PlayerActorBase.h"
|
||||
#include "sead/math/seadVector.h"
|
||||
|
||||
class FreezeTagInfo;
|
||||
|
||||
class FreezeHintArrow : public al::LiveActor {
|
||||
public:
|
||||
FreezeHintArrow(const char* name);
|
||||
void init(al::ActorInitInfo const&) override;
|
||||
void initAfterPlacement(void) override;
|
||||
bool receiveMsg(const al::SensorMsg* message, al::HitSensor* source, al::HitSensor* target) override;
|
||||
void attackSensor(al::HitSensor* source, al::HitSensor* target) override;
|
||||
void control(void) override;
|
||||
void appear() override;
|
||||
void end();
|
||||
|
||||
void setTarget(sead::Vector3f* targetPosition) { mTargetTrans = targetPosition; };
|
||||
|
||||
void exeWait();
|
||||
|
||||
FreezeTagInfo* mInfo;
|
||||
|
||||
PlayerActorBase* mPlayer;
|
||||
sead::Vector3f* mTargetTrans;
|
||||
sead::Vector3f* mArrowTrans;
|
||||
sead::Vector3f mActorUp = sead::Vector3f::ey;
|
||||
|
||||
const float mMinDistanceSq = 10562500.f; // non-squared: 3250.0
|
||||
float mDistanceSq = -1.f;
|
||||
float mSize = 0.f;
|
||||
|
||||
bool mIsActive = true;
|
||||
bool mIsVisible = false;
|
||||
bool mWasVisible = false;
|
||||
uint8_t mVisibilityCooldown = 0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
NERVE_HEADER(FreezeHintArrow, Wait)
|
||||
}
|
32
include/server/freeze-tag/FreezePlayerBlock.h
Normal file
32
include/server/freeze-tag/FreezePlayerBlock.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/sensor/SensorMsg.h"
|
||||
#include "al/util/NerveUtil.h"
|
||||
|
||||
class FreezePlayerBlock : public al::LiveActor {
|
||||
public:
|
||||
FreezePlayerBlock(const char* name);
|
||||
void init(al::ActorInitInfo const&) override;
|
||||
void initAfterPlacement(void) override;
|
||||
bool receiveMsg(const al::SensorMsg* message, al::HitSensor* source, al::HitSensor* target) override;
|
||||
void attackSensor(al::HitSensor* source, al::HitSensor* target) override;
|
||||
void control(void) override;
|
||||
void appear() override;
|
||||
|
||||
void end();
|
||||
|
||||
void exeAppear();
|
||||
void exeWait();
|
||||
void exeDisappear();
|
||||
void exeDead();
|
||||
|
||||
bool mIsLocked = false;
|
||||
float mDitheringOffset = -150.f; // -150 is fully opaque, 0 is fully dithered, -80 is good looking
|
||||
};
|
||||
|
||||
namespace {
|
||||
NERVE_HEADER(FreezePlayerBlock, Appear)
|
||||
NERVE_HEADER(FreezePlayerBlock, Wait)
|
||||
NERVE_HEADER(FreezePlayerBlock, Disappear)
|
||||
NERVE_HEADER(FreezePlayerBlock, Dead)
|
||||
}
|
45
include/server/freeze-tag/FreezeTagChaserSlot.h
Normal file
45
include/server/freeze-tag/FreezeTagChaserSlot.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/util/LayoutUtil.h"
|
||||
#include "al/util/NerveUtil.h"
|
||||
|
||||
class FreezeTagInfo;
|
||||
|
||||
// TODO: kill layout if going through loading zone or paused
|
||||
|
||||
class FreezeTagChaserSlot : public al::LayoutActor {
|
||||
public:
|
||||
FreezeTagChaserSlot(const char* name, const al::LayoutInitInfo& initInfo);
|
||||
void init(int index);
|
||||
|
||||
void appear() override;
|
||||
|
||||
bool tryStart();
|
||||
bool tryEnd();
|
||||
|
||||
void showSlot();
|
||||
void hideSlot();
|
||||
|
||||
void setSlotName(const char* name) { al::setPaneStringFormat(this, "TxtChaserName", "%s", name); };
|
||||
void setSlotScore(int score) { al::setPaneStringFormat(this, "TxtChaserScore", "%04u", score); };
|
||||
|
||||
void exeAppear();
|
||||
void exeWait();
|
||||
void exeEnd();
|
||||
|
||||
bool mIsVisible = false;
|
||||
bool mIsPlayer = false;
|
||||
|
||||
float mFreezeIconSize = 0.f;
|
||||
float mFreezeIconSpin = 0.f;
|
||||
int mChaserIndex;
|
||||
|
||||
private:
|
||||
struct FreezeTagInfo* mInfo;
|
||||
};
|
||||
|
||||
namespace {
|
||||
NERVE_HEADER(FreezeTagChaserSlot, Appear)
|
||||
NERVE_HEADER(FreezeTagChaserSlot, Wait)
|
||||
NERVE_HEADER(FreezeTagChaserSlot, End)
|
||||
}
|
22
include/server/freeze-tag/FreezeTagConfigMenu.hpp
Normal file
22
include/server/freeze-tag/FreezeTagConfigMenu.hpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
#include "sead/container/seadSafeArray.h"
|
||||
#include "server/gamemode/GameModeConfigMenu.hpp"
|
||||
#include "server/freeze-tag/FreezeTagInfo.h"
|
||||
|
||||
class FreezeTagConfigMenu : public GameModeConfigMenu {
|
||||
public:
|
||||
FreezeTagConfigMenu();
|
||||
|
||||
const sead::WFixedSafeString<0x200>* getStringData() override;
|
||||
GameModeConfigMenu::UpdateAction updateMenu(int selectIndex) override;
|
||||
|
||||
const int getMenuSize() override { return FreezeTagInfo::mIsHostMode ? 8 : 7; }
|
||||
|
||||
private:
|
||||
static constexpr int mItemCount = 8;
|
||||
sead::SafeArray<sead::WFixedSafeString<0x200>, mItemCount>* mItems = nullptr;
|
||||
Keyboard* mScoreKeyboard;
|
||||
Keyboard* mRoundKeyboard;
|
||||
};
|
90
include/server/freeze-tag/FreezeTagIcon.h
Normal file
90
include/server/freeze-tag/FreezeTagIcon.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/layout/LayoutActor.h"
|
||||
#include "al/layout/LayoutInitInfo.h"
|
||||
#include "al/util/NerveUtil.h"
|
||||
#include "sead/container/seadPtrArray.h"
|
||||
#include "sead/math/seadVector.h"
|
||||
|
||||
class FreezeTagInfo;
|
||||
class FreezeTagRunnerSlot;
|
||||
class FreezeTagChaserSlot;
|
||||
class FreezeTagOtherSlot;
|
||||
|
||||
// TODO: kill layout if going through loading zone or paused
|
||||
|
||||
class FreezeTagIcon : public al::LayoutActor {
|
||||
public:
|
||||
FreezeTagIcon(const char* name, const al::LayoutInitInfo& initInfo);
|
||||
|
||||
void appear() override;
|
||||
|
||||
void setSpectateString(const char* spec) { mSpectateName = spec; }
|
||||
void setFreezeOverlayHeight();
|
||||
void setSpectateOverlayHeight();
|
||||
void setRoundTimerOverlay();
|
||||
|
||||
void showEndgameScreen() {
|
||||
mEndgameIsDisplay = true;
|
||||
mEndgameTextAngle = 0.f;
|
||||
mEndgameTextSize = 0.f;
|
||||
};
|
||||
|
||||
void hideEndgameScreen() { mEndgameIsDisplay = false; };
|
||||
|
||||
void queueScoreEvent(int eventValue, const char* eventDesc);
|
||||
|
||||
bool tryStart();
|
||||
bool tryEnd();
|
||||
|
||||
void exeAppear();
|
||||
void exeWait();
|
||||
void exeEnd();
|
||||
|
||||
private:
|
||||
struct FreezeTagInfo* mInfo;
|
||||
|
||||
// Runner and chaser display info
|
||||
sead::PtrArray<FreezeTagRunnerSlot> mRunnerSlots;
|
||||
sead::PtrArray<FreezeTagChaserSlot> mChaserSlots;
|
||||
sead::PtrArray<FreezeTagOtherSlot> mOtherSlots;
|
||||
const int mMaxRunners = 9;
|
||||
const int mMaxChasers = 9;
|
||||
const int mMaxOthers = 9;
|
||||
|
||||
// Spectate and general info
|
||||
bool mIsRunner = true;
|
||||
bool mIsOverlayShowing = false;
|
||||
const char* mSpectateName = nullptr;
|
||||
|
||||
// Score event tracker
|
||||
bool mScoreEventIsQueued = false;
|
||||
int mScoreEventValue = 0;
|
||||
const char* mScoreEventDesc = nullptr;
|
||||
|
||||
float mScoreEventTime = -1.f; // Every time a score event starts, this timer is set to zero, increase over time to control anim
|
||||
sead::Vector3f mScoreEventPos = sead::Vector3f::zero;
|
||||
float mScoreEventScale = 0.f;
|
||||
|
||||
// UI positioning and angle calculations
|
||||
float mRunnerFreezeIconAngle = 0.f;
|
||||
|
||||
float mFreezeOverlayHeight = 415.f;
|
||||
|
||||
float mSpectateOverlayHeight = -400.f;
|
||||
|
||||
float mRoundTimerClockInsideSpin = 0.f;
|
||||
float mRoundTimerHeight = 390.f;
|
||||
float mRoundTimerScale = 1.f;
|
||||
|
||||
// Endgame popup
|
||||
bool mEndgameIsDisplay = false;
|
||||
float mEndgameTextSize = 0.f;
|
||||
float mEndgameTextAngle = 0.f;
|
||||
};
|
||||
|
||||
namespace {
|
||||
NERVE_HEADER(FreezeTagIcon, Appear)
|
||||
NERVE_HEADER(FreezeTagIcon, Wait)
|
||||
NERVE_HEADER(FreezeTagIcon, End)
|
||||
}
|
50
include/server/freeze-tag/FreezeTagInfo.h
Normal file
50
include/server/freeze-tag/FreezeTagInfo.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include "puppets/PuppetInfo.h"
|
||||
#include "server/gamemode/GameModeInfoBase.hpp"
|
||||
#include "server/gamemode/GameModeTimer.hpp"
|
||||
#include "server/freeze-tag/FreezeTagScore.hpp"
|
||||
|
||||
enum FreezeState { // Runner team player's state
|
||||
ALIVE = 0,
|
||||
FREEZE = 1,
|
||||
};
|
||||
|
||||
struct FreezeTagInfo : GameModeInfoBase {
|
||||
FreezeTagInfo() {
|
||||
mMode = GameMode::FREEZETAG;
|
||||
}
|
||||
|
||||
bool mIsPlayerRunner = true;
|
||||
float mFreezeIconSize = 0.f;
|
||||
FreezeState mIsPlayerFreeze = FreezeState::ALIVE;
|
||||
|
||||
bool mIsRound = false;
|
||||
int mFreezeCount = 0; // how often runners were frozen in the current round (including refreezes after unfreeze)
|
||||
GameTime mRoundTimer;
|
||||
|
||||
sead::PtrArray<PuppetInfo> mRunnerPlayers;
|
||||
sead::PtrArray<PuppetInfo> mChaserPlayers;
|
||||
sead::PtrArray<PuppetInfo> mOtherPlayers;
|
||||
|
||||
static FreezeTagScore mPlayerTagScore;
|
||||
static int mRoundLength; // Length of rounds in minutes
|
||||
static bool mIsHostMode; // can start/cancel round
|
||||
static bool mIsDebugMode; // trigger manual actions that are normally automatic (might break game logic)
|
||||
|
||||
static bool mHasMarioCollision;
|
||||
static bool mHasMarioBounce;
|
||||
static bool mHasCappyCollision;
|
||||
static bool mHasCappyBounce;
|
||||
|
||||
inline bool isHost() const { return mIsHostMode; }
|
||||
inline bool isRound() const { return mIsRound; }
|
||||
inline bool isPlayerRunner() const { return mIsPlayerRunner; }
|
||||
inline bool isPlayerChaser() const { return !mIsPlayerRunner; }
|
||||
inline bool isPlayerFrozen() const { return mIsPlayerFreeze; }
|
||||
inline bool isPlayerUnfrozen() const { return !mIsPlayerFreeze; }
|
||||
inline int runners() const { return mRunnerPlayers.size() + isPlayerRunner(); }
|
||||
inline int chasers() const { return mChaserPlayers.size() + isPlayerChaser(); }
|
||||
inline int others() const { return mOtherPlayers.size(); }
|
||||
inline uint16_t getScore() const { return mPlayerTagScore.mScore; }
|
||||
};
|
120
include/server/freeze-tag/FreezeTagMode.hpp
Normal file
120
include/server/freeze-tag/FreezeTagMode.hpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/camera/CameraTicket.h"
|
||||
#include "game/Player/PlayerActorBase.h"
|
||||
#include "game/Player/PlayerActorHakoniwa.h"
|
||||
#include "puppets/PuppetInfo.h"
|
||||
#include "sead/math/seadVector.h"
|
||||
#include "server/freeze-tag/FreezeTagInfo.h"
|
||||
#include "server/freeze-tag/FreezeTagPackets.hpp"
|
||||
#include "server/gamemode/GameModeBase.hpp"
|
||||
#include "server/gamemode/GameModeTimer.hpp"
|
||||
|
||||
class FreezeHintArrow;
|
||||
class FreezePlayerBlock;
|
||||
class FreezeTagIcon;
|
||||
|
||||
struct FreezePostProcessingTypes {
|
||||
enum Type : u8 { // Snapshot mode post processing state
|
||||
PPDISABLED = 0,
|
||||
PPFROZEN = 1,
|
||||
PPENDGAMELOSE = 2,
|
||||
PPENDGAMEWIN = 3,
|
||||
};
|
||||
};
|
||||
typedef FreezePostProcessingTypes::Type FreezePostProcessingType;
|
||||
|
||||
class FreezeTagMode : public GameModeBase {
|
||||
public:
|
||||
// implemented in FreezeTagMode.cpp:
|
||||
FreezeTagMode(const char* name);
|
||||
void init(GameModeInitInfo const& info) override;
|
||||
void processPacket(Packet* packet) override;
|
||||
Packet* createPacket() override;
|
||||
void begin() override;
|
||||
void end() override;
|
||||
void pause() override;
|
||||
void unpause() override;
|
||||
void update() override;
|
||||
bool showNameTag(PuppetInfo* other) override;
|
||||
bool showNameTagEverywhere(PuppetActor* other) override;
|
||||
void debugMenuControls(sead::TextWriter* gTextWriter) override;
|
||||
void debugMenuPlayer(sead::TextWriter* gTextWriter, PuppetInfo* other = nullptr) override;
|
||||
void sendFreezePacket(FreezeUpdateType updateType); // Called instead of Client::sendGameModeInfPacket(), allows setting packet type
|
||||
void onHakoniwaSequenceFirstStep(HakoniwaSequence* sequence) override; // Called with HakoniwaSequence hook, wipe used in recovery event
|
||||
|
||||
// implemented here:
|
||||
inline bool ignoreComboBtn() const override { return true; }
|
||||
inline bool pauseTimeWhenPaused() const override { return true; }
|
||||
inline bool isUseNormalUI() const override { return false; }
|
||||
inline bool hasCustomCamera() const override { return true; }
|
||||
inline bool isScoreEventsEnabled() const { return mIsScoreEventsValid; }
|
||||
inline bool isHost() const { return mInfo->isHost(); }
|
||||
inline bool isRound() const { return mInfo->isRound(); }
|
||||
inline bool isPlayerRunner() const { return mInfo->isPlayerRunner(); }
|
||||
inline bool isPlayerChaser() const { return mInfo->isPlayerChaser(); }
|
||||
inline bool isPlayerFrozen() const { return mInfo->isPlayerFrozen(); }
|
||||
inline bool isPlayerUnfrozen() const { return mInfo->isPlayerUnfrozen(); }
|
||||
inline bool isWipeout() const { return mIsEndgameActive; }
|
||||
inline uint16_t getScore() const { return mInfo->getScore(); }
|
||||
inline int runners() const { return mInfo->runners(); }
|
||||
inline int chasers() const { return mInfo->chasers(); }
|
||||
inline int others() const { return mInfo->others(); }
|
||||
|
||||
// implemented in FreezeTagModeTrigger.cpp:
|
||||
void startRound(int roundMinutes); // Actives round on this specific client
|
||||
void endRound(bool isAbort); // Ends round, allows setting for if this was a natural end or abort (used for scoring)
|
||||
bool trySetPlayerRunnerState(FreezeState state); // Sets runner to alive or frozen, many safety checks
|
||||
void tryStartEndgameEvent(); // Starts the WIPEOUT message event
|
||||
bool tryStartRecoveryEvent(bool isEndgame); // Returns player to a chaser's position or last stood position, unless endgame variant
|
||||
bool tryEndRecoveryEvent(); // Called after the fade of the recovery event
|
||||
void tryScoreEvent(FreezeTagPacket* packet, PuppetInfo* other); // Attempt to score when getting a packet that a runner near us was frozen
|
||||
bool trySetPostProcessingType(FreezePostProcessingType type); // Sets the post processing type, also used for disabling
|
||||
void warpToRecoveryPoint(al::LiveActor* actor); // Warps runner to chaser OR if impossible, last standing position
|
||||
|
||||
// implemented in FreezeTagModeCam.cpp:
|
||||
void createCustomCameraTicket(al::CameraDirector* director) override;
|
||||
void updateSpectateCam(PlayerActorBase* playerBase); // Updates the frozen spectator camera
|
||||
|
||||
// implemented in FreezeTagModeUtil.cpp:
|
||||
bool areAllOtherRunnersFrozen(PuppetInfo* player); // Only meant to be called on getting a packet, starts the endgame
|
||||
PlayerActorHakoniwa* getPlayerActorHakoniwa(); // Returns nullptr if the player is not a PlayerActorHakoniwa
|
||||
|
||||
bool hasMarioCollision() override { return FreezeTagInfo::mHasMarioCollision; }
|
||||
bool hasMarioBounce() override { return FreezeTagInfo::mHasMarioBounce; }
|
||||
bool hasCappyCollision() override { return FreezeTagInfo::mHasCappyCollision; }
|
||||
bool hasCappyBounce() override { return FreezeTagInfo::mHasCappyBounce; }
|
||||
|
||||
private:
|
||||
FreezeUpdateType mNextUpdateType = FreezeUpdateType::PLAYER; // Set for the sendPacket funtion to know what packet type is sent
|
||||
FreezePostProcessingType mPostProcessingType = FreezePostProcessingType::PPDISABLED; // Current post processing mode (snapshot mode)
|
||||
|
||||
GameModeTimer* mModeTimer = nullptr; // Generic timer from H&S used for round timer
|
||||
FreezeTagIcon* mModeLayout = nullptr; // HUD layout (creates sub layout actors for runner and chaser)
|
||||
FreezeTagInfo* mInfo = nullptr; // Our own Freeze-Tag status
|
||||
al::WipeHolder* mWipeHolder = nullptr; // Pointer set by setWipeHolder on first step of hakoniwaSequence hook
|
||||
|
||||
// Scene actors
|
||||
FreezePlayerBlock* mMainPlayerIceBlock = nullptr; // Visual block around player's when frozen
|
||||
FreezeHintArrow* mHintArrow = nullptr; // Arrow that points to nearest runner while being a chaser too far away from runners
|
||||
|
||||
// Recovery event info (when falling off)
|
||||
int mRecoveryEventFrames = 0;
|
||||
const int mRecoveryEventLength = 60; // Length of recovery event in frames
|
||||
sead::Vector3f mRecoverySafetyPoint = sead::Vector3f::zero;
|
||||
|
||||
// Endgame info (wipeout)
|
||||
bool mIsEndgameActive = false;
|
||||
bool mCancelOnlyLegacy = false; // option to send a ROUNDCANCEL packet that only affects legacy clients
|
||||
float mEndgameTimer = -1.0f; // timer to track how long wipeout is shown
|
||||
float mDisconnectTimer = 0.0f; // timer to track how long to wait for a reconnect from disconnected players before cancelling the round
|
||||
|
||||
float mInvulnTime = 0.0f;
|
||||
bool mIsScoreEventsValid = false;
|
||||
|
||||
// Spectate camera ticket and target information
|
||||
al::CameraTicket* mTicket = nullptr;
|
||||
int mPrevSpectateCount = 0;
|
||||
int mPrevSpectateIndex = -2;
|
||||
int mSpectateIndex = -1;
|
||||
};
|
45
include/server/freeze-tag/FreezeTagOtherSlot.h
Normal file
45
include/server/freeze-tag/FreezeTagOtherSlot.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/util/LayoutUtil.h"
|
||||
#include "al/util/NerveUtil.h"
|
||||
|
||||
class FreezeTagInfo;
|
||||
|
||||
// TODO: kill layout if going through loading zone or paused
|
||||
|
||||
class FreezeTagOtherSlot : public al::LayoutActor {
|
||||
public:
|
||||
FreezeTagOtherSlot(const char* name, const al::LayoutInitInfo& initInfo);
|
||||
void init(int index);
|
||||
|
||||
void appear() override;
|
||||
|
||||
bool tryStart();
|
||||
bool tryEnd();
|
||||
|
||||
void showSlot();
|
||||
void hideSlot();
|
||||
|
||||
void setSlotName(const char* name) { al::setPaneStringFormat(this, "TxtOtherName", "%s", name); };
|
||||
void setSlotScore(int score) { al::setPaneStringFormat(this, "TxtOtherScore", "%04u", score); };
|
||||
|
||||
void exeAppear();
|
||||
void exeWait();
|
||||
void exeEnd();
|
||||
|
||||
bool mIsVisible = false;
|
||||
bool mIsPlayer = false;
|
||||
|
||||
float mFreezeIconSize = 0.f;
|
||||
float mFreezeIconSpin = 0.f;
|
||||
int mOtherIndex;
|
||||
|
||||
private:
|
||||
struct FreezeTagInfo* mInfo;
|
||||
};
|
||||
|
||||
namespace {
|
||||
NERVE_HEADER(FreezeTagOtherSlot, Appear)
|
||||
NERVE_HEADER(FreezeTagOtherSlot, Wait)
|
||||
NERVE_HEADER(FreezeTagOtherSlot, End)
|
||||
}
|
43
include/server/freeze-tag/FreezeTagPackets.hpp
Normal file
43
include/server/freeze-tag/FreezeTagPackets.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "packets/GameModeInf.h"
|
||||
|
||||
struct FreezeUpdateTypes {
|
||||
enum Type : u8 { // Type of packets to send between players
|
||||
PLAYER = 1 << 0,
|
||||
ROUNDSTART = 1 << 1,
|
||||
ROUNDCANCEL = 1 << 2,
|
||||
FALLOFF = 1 << 3,
|
||||
};
|
||||
};
|
||||
typedef FreezeUpdateTypes::Type FreezeUpdateType;
|
||||
|
||||
struct PACKED FreezeTagPacket : GameModeInf<FreezeUpdateType> {
|
||||
FreezeTagPacket() : GameModeInf() {
|
||||
setGameMode(GameMode::FREEZETAG);
|
||||
mPacketSize = sizeof(FreezeTagPacket) - sizeof(Packet);
|
||||
};
|
||||
bool isRunner = false;
|
||||
bool isFreeze = false;
|
||||
uint16_t score = 0;
|
||||
};
|
||||
|
||||
struct PACKED FreezeTagRoundStartPacket : GameModeInf<FreezeUpdateType> {
|
||||
FreezeTagRoundStartPacket() : GameModeInf() {
|
||||
setGameMode(GameMode::FREEZETAG);
|
||||
setUpdateType(FreezeUpdateType::ROUNDSTART);
|
||||
mPacketSize = sizeof(FreezeTagRoundStartPacket) - sizeof(Packet);
|
||||
};
|
||||
uint8_t roundTime = 10;
|
||||
const char padding[3] = "\0\0"; // to not break compatibility with old clients/servers that assume a size of 4 bytes minimum for GameModeInf packets
|
||||
};
|
||||
|
||||
struct PACKED FreezeTagRoundCancelPacket : GameModeInf<FreezeUpdateType> {
|
||||
FreezeTagRoundCancelPacket() : GameModeInf() {
|
||||
setGameMode(GameMode::FREEZETAG);
|
||||
setUpdateType(FreezeUpdateType::ROUNDCANCEL);
|
||||
mPacketSize = sizeof(FreezeTagRoundCancelPacket) - sizeof(Packet);
|
||||
};
|
||||
const char padding[4] = "\0\0\0"; // to not break compatibility with old clients/servers that assume a size of 4 bytes minimum for GameModeInf packets
|
||||
bool onlyForLegacy = false; // extra added byte only used by newer clients to ignore this packet conditionally
|
||||
};
|
46
include/server/freeze-tag/FreezeTagRunnerSlot.h
Normal file
46
include/server/freeze-tag/FreezeTagRunnerSlot.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include "al/util/LayoutUtil.h"
|
||||
#include "al/util/NerveUtil.h"
|
||||
|
||||
class FreezeTagInfo;
|
||||
|
||||
// TODO: kill layout if going through loading zone or paused
|
||||
|
||||
class FreezeTagRunnerSlot : public al::LayoutActor {
|
||||
public:
|
||||
FreezeTagRunnerSlot(const char* name, const al::LayoutInitInfo& initInfo);
|
||||
void init(int index);
|
||||
|
||||
void appear() override;
|
||||
|
||||
bool tryStart();
|
||||
bool tryEnd();
|
||||
|
||||
void showSlot();
|
||||
void hideSlot();
|
||||
|
||||
void setFreezeAngle();
|
||||
void setSlotName(const char* name) { al::setPaneStringFormat(this, "TxtRunnerName", "%s", name); };
|
||||
void setSlotScore(int score) { al::setPaneStringFormat(this, "TxtRunnerScore", "%04u", score); };
|
||||
|
||||
void exeAppear();
|
||||
void exeWait();
|
||||
void exeEnd();
|
||||
|
||||
bool mIsVisible = false;
|
||||
bool mIsPlayer = false;
|
||||
|
||||
float mFreezeIconSize = 0.f;
|
||||
float mFreezeIconSpin = 0.f;
|
||||
int mRunnerIndex;
|
||||
|
||||
private:
|
||||
struct FreezeTagInfo* mInfo;
|
||||
};
|
||||
|
||||
namespace {
|
||||
NERVE_HEADER(FreezeTagRunnerSlot, Appear)
|
||||
NERVE_HEADER(FreezeTagRunnerSlot, Wait)
|
||||
NERVE_HEADER(FreezeTagRunnerSlot, End)
|
||||
}
|
52
include/server/freeze-tag/FreezeTagScore.hpp
Normal file
52
include/server/freeze-tag/FreezeTagScore.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "al/util/MathUtil.h"
|
||||
#include "server/freeze-tag/FreezeTagIcon.h"
|
||||
|
||||
class FreezeTagScore {
|
||||
public:
|
||||
uint16_t mScore = 0;
|
||||
uint16_t mPrevScore = 0;
|
||||
|
||||
void setTargetLayout(FreezeTagIcon* icon) { mIcon = icon; };
|
||||
void initBuffer() { buffer = new char[36]; } // max length of 35 chars + \0
|
||||
|
||||
void resetScore() { mScore = 0; };
|
||||
|
||||
// Events
|
||||
void eventScoreDebug() { addScore( 1, "Debugging!"); }
|
||||
void eventScoreSurvivalTime() { addScore( 1, "Survival Time"); }
|
||||
void eventScoreUnfreeze() { addScore( 2, "Rescued"); }
|
||||
void eventScoreFreeze() { addScore( 2, "Caught"); }
|
||||
void eventScoreFallOff() { addScore( 2, "Fell Off L"); }
|
||||
void eventScoreRunnerWin() { addScore( 4, "Survived!"); }
|
||||
void eventScoreWipeout() { addScore(20, "Wipeout!"); }
|
||||
|
||||
void eventNotEnoughRunnersToStart() { addScore(0, "Not enough Runners to start a round"); }
|
||||
void eventNotEnoughChasersToStart() { addScore(0, "Not enough Chasers to start a round"); }
|
||||
void eventNotEnoughRunnersToContinue() { addScore(0, "Round cancelled: not enough Runners"); }
|
||||
void eventNotEnoughChasersToContinue() { addScore(0, "Round cancelled: not enough Chasers"); }
|
||||
|
||||
void eventRoundStarted(const char* name) {
|
||||
strcpy(buffer, "Round started by ");
|
||||
strcat(buffer, name);
|
||||
addScore(0, buffer);
|
||||
}
|
||||
void eventRoundCancelled(const char* name) {
|
||||
strcpy(buffer, "Round cancelled by ");
|
||||
strcat(buffer, name);
|
||||
addScore(0, buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
FreezeTagIcon* mIcon;
|
||||
|
||||
char* buffer;
|
||||
|
||||
void addScore(int add, const char* description) {
|
||||
mScore += add;
|
||||
mScore = al::clamp(mScore, uint16_t(0), uint16_t(9999));
|
||||
mIcon->queueScoreEvent(add, description); // max length of 35 chars
|
||||
};
|
||||
};
|
|
@ -8,6 +8,7 @@ enum GameMode : s8 {
|
|||
LEGACY = 0,
|
||||
HIDEANDSEEK = 1,
|
||||
SARDINE = 2,
|
||||
FREEZETAG = 3,
|
||||
/**
|
||||
* Don't use values 14 or higher before refactoring the GameModeInf packet.
|
||||
* This is necessary because currently there are only 4 bits in the packet for the game mode.
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "al/factory/Factory.h"
|
||||
#include "server/hns/HideAndSeekConfigMenu.hpp"
|
||||
#include "server/sardines/SardineConfigMenu.hpp"
|
||||
#include "server/freeze-tag/FreezeTagConfigMenu.hpp"
|
||||
#include "server/gamemode/GameModeConfigMenu.hpp"
|
||||
|
||||
typedef GameModeConfigMenu* (*createMenu)(const char* name);
|
||||
|
@ -15,6 +16,7 @@ GameModeConfigMenu* createGameModeConfigMenu(const char* name) {
|
|||
__attribute((used)) constexpr al::NameToCreator<createMenu> menuTable[] = {
|
||||
{ "HideAndSeek", &createGameModeConfigMenu<HideAndSeekConfigMenu> },
|
||||
{ "Sardine", &createGameModeConfigMenu<SardineConfigMenu> },
|
||||
{ "FreezeTag", &createGameModeConfigMenu<FreezeTagConfigMenu> },
|
||||
};
|
||||
|
||||
class GameModeConfigMenuFactory : public al::Factory<createMenu> {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "server/gamemode/GameModeBase.hpp"
|
||||
#include "server/hns/HideAndSeekMode.hpp"
|
||||
#include "server/sardines/SardineMode.hpp"
|
||||
#include "server/freeze-tag/FreezeTagMode.hpp"
|
||||
|
||||
typedef GameModeBase* (*createMode)(const char* name);
|
||||
|
||||
|
@ -16,12 +17,15 @@ __attribute((used)) constexpr al::NameToCreator<createMode> modeTable[] = {
|
|||
{ "Legacy", nullptr },
|
||||
{ "HideAndSeek", &createGameMode<HideAndSeekMode> },
|
||||
{ "Sardines", &createGameMode<SardineMode> },
|
||||
{ "FreezeTag", &createGameMode<FreezeTagMode> },
|
||||
|
||||
};
|
||||
|
||||
constexpr const char* modeNames[] = {
|
||||
"Legacy",
|
||||
"Hide & Seek",
|
||||
"Sardines",
|
||||
"Freeze-Tag",
|
||||
};
|
||||
|
||||
class GameModeFactory : public al::Factory<createMode> {
|
||||
|
|
|
@ -31,16 +31,28 @@ class GameModeTimer {
|
|||
void enableControl() { mIsUseControl = true; }
|
||||
void disableControl() { mIsUseControl = false; }
|
||||
|
||||
void setTimerDirection(bool isCountUp) { mIsCountUp = isCountUp; }
|
||||
|
||||
void setTime(float milli, int seconds, int minutes, int hours);
|
||||
void setTime(GameTime const& time);
|
||||
|
||||
void resetTime() { setTime(0, 0, 0, 0); }
|
||||
|
||||
bool isEnabled() { return mIsEnabled; }
|
||||
float getMilliseconds() { return mTime.mMilliseconds; }
|
||||
int getSeconds() { return mTime.mSeconds; }
|
||||
int getMinutes() { return mTime.mMinutes; }
|
||||
int getHours() { return mTime.mHours; }
|
||||
|
||||
float getTimeCombined() {
|
||||
return (
|
||||
(float)mTime.mSeconds
|
||||
+ ((float)mTime.mMinutes * 60.0f)
|
||||
+ ((float)mTime.mHours * 3600.0f)
|
||||
+ (mIsCountUp ? mTime.mMilliseconds : -mTime.mMilliseconds)
|
||||
);
|
||||
}
|
||||
|
||||
GameTime getTime() { return mTime; }
|
||||
GameTime* getTimePtr() { return &mTime; }
|
||||
|
||||
|
|
|
@ -27,9 +27,9 @@ B59E28 B seadPrintHook // sead::system::print
|
|||
|
||||
41B4E4 BL setPlayerModel
|
||||
|
||||
1B3F0C NOP // disables call to open HTML viewer during first time odyssey flight
|
||||
1F2A2C MOV W0, #1 // patches checkpoint system to always allow warping
|
||||
216FAC MOV W0, #0 // disables AppearSwitchTimer's camera switch
|
||||
1B3F0C NOP // disables call to open HTML viewer during first time odyssey flight
|
||||
1F2998 B freezeIsCheckpointWarpAllowed // Patches checkpoint system to always allow warping, disable in freeze tag
|
||||
216FAC MOV W0, #0 // disables AppearSwitchTimer's camera switch
|
||||
|
||||
// Puppet Actor Setup
|
||||
4B5E30 B ProjectActorFactory // patches actor factory ctor with custom matching factory
|
||||
|
@ -68,7 +68,7 @@ B59E28 B seadPrintHook // sead::system::print
|
|||
MOV X0, #0
|
||||
RET
|
||||
|
||||
// Remap Snapshot to !L + Down
|
||||
// Remap Snapshot to !L + Down, disables snapshot mode in freeze tag
|
||||
577014 B comboBtnHook
|
||||
|
||||
// Capture Syncing
|
||||
|
@ -125,4 +125,10 @@ B59E28 B seadPrintHook // sead::system::print
|
|||
8912B8 B drawTableHook
|
||||
|
||||
// 891394 BL drawInitHook
|
||||
// 91328 BL updateInitHook
|
||||
// 91328 BL updateInitHook
|
||||
|
||||
// Freeze tag hooks
|
||||
4272F0 BL freezeDeathArea // Replaces functionality of death areas in freeze tag
|
||||
538A94 B freezePlayerHitPointDamage // Reimplements the damage function, disabling it's functionality in Freeze Tag
|
||||
51B280 B freezeKidsMode // Forces kids mode to be enabled during Freeze Tag
|
||||
1CFBA0 BL freezeMoonHitboxDisable // When mode enabled, disable hitboxes with moons to avoid softlocks
|
||||
|
|
BIN
romfs/LayoutData/FreezeTagChaserSlot.szs
Normal file
BIN
romfs/LayoutData/FreezeTagChaserSlot.szs
Normal file
Binary file not shown.
BIN
romfs/LayoutData/FreezeTagIcon.szs
Normal file
BIN
romfs/LayoutData/FreezeTagIcon.szs
Normal file
Binary file not shown.
BIN
romfs/LayoutData/FreezeTagOtherSlot.szs
Normal file
BIN
romfs/LayoutData/FreezeTagOtherSlot.szs
Normal file
Binary file not shown.
BIN
romfs/LayoutData/FreezeTagRunnerSlot.szs
Normal file
BIN
romfs/LayoutData/FreezeTagRunnerSlot.szs
Normal file
Binary file not shown.
BIN
romfs/LayoutData/KidsMode.szs
Normal file
BIN
romfs/LayoutData/KidsMode.szs
Normal file
Binary file not shown.
BIN
romfs/ObjectData/FreezeHintArrow.szs
Normal file
BIN
romfs/ObjectData/FreezeHintArrow.szs
Normal file
Binary file not shown.
BIN
romfs/ObjectData/FreezePlayerBlock.szs
Normal file
BIN
romfs/ObjectData/FreezePlayerBlock.szs
Normal file
Binary file not shown.
BIN
romfs/SystemData/PostProcessingFilterPreset.szs
Normal file
BIN
romfs/SystemData/PostProcessingFilterPreset.szs
Normal file
Binary file not shown.
138
source/cameras/CameraPoserActorSpectate.cpp
Normal file
138
source/cameras/CameraPoserActorSpectate.cpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
#include "cameras/CameraPoserActorSpectate.h"
|
||||
|
||||
#include "al/alCollisionUtil.h"
|
||||
#include "al/camera/CameraPoser.h"
|
||||
#include "al/camera/alCameraPoserFunction.h"
|
||||
#include "al/util.hpp"
|
||||
#include "al/util/VectorUtil.h"
|
||||
|
||||
#include "sead/gfx/seadCamera.h"
|
||||
#include "sead/math/seadMathCalcCommon.h"
|
||||
#include "sead/math/seadVector.h"
|
||||
|
||||
namespace cc {
|
||||
CameraPoserActorSpectate::CameraPoserActorSpectate(const char* poserName) : CameraPoser(poserName) {
|
||||
this->initOrthoProjectionParam();
|
||||
}
|
||||
|
||||
void CameraPoserActorSpectate::init(void) {
|
||||
// this makes the snapshot camera have the abilities of the normal snapshot cam, but is locked rotationally
|
||||
alCameraPoserFunction::initSnapShotCameraCtrlZoomRollMove(this);
|
||||
|
||||
alCameraPoserFunction::initCameraVerticalAbsorber(this);
|
||||
alCameraPoserFunction::initCameraAngleCtrl(this);
|
||||
}
|
||||
|
||||
void CameraPoserActorSpectate::start(al::CameraStartInfo const&) {
|
||||
if (mTargetActorPos) {
|
||||
mTargetTrans = *mTargetActorPos;
|
||||
}
|
||||
|
||||
if (mPlayer && !mTargetActorPos) {
|
||||
mTargetTrans = *al::getTransPtr(mPlayer);
|
||||
}
|
||||
|
||||
mFovyDegree = 47.5f;
|
||||
mFrameCounter = 0;
|
||||
|
||||
mPoserFlags->mOffVerticalAbsorb = true;
|
||||
mPoserFlags->mInvalidChangeSubjective = true;
|
||||
mPoserFlags->mInvalidPreCameraEndAfterInterpole = true;
|
||||
mPoserFlags->mInvalidChangeSubjective = true;
|
||||
mPoserFlags->mValidKeepPreSelfPoseNextCameraByParam = false;
|
||||
}
|
||||
|
||||
void CameraPoserActorSpectate::movement() {
|
||||
mFrameCounter++;
|
||||
|
||||
if (mTargetActorPos) {
|
||||
al::lerpVec(
|
||||
&mTargetTrans,
|
||||
mTargetTrans - sead::Vector3f(0.f, 100.f, 0.f),
|
||||
*mTargetActorPos,
|
||||
10 <= mFrameCounter ? 0.08f : 1.f
|
||||
);
|
||||
}
|
||||
|
||||
if (mPlayer && !mTargetActorPos) {
|
||||
mTargetTrans = *al::getTransPtr(mPlayer);
|
||||
}
|
||||
|
||||
if (!mPlayer && !mTargetActorPos) {
|
||||
return;
|
||||
}
|
||||
|
||||
mTargetTrans.y += mYOffset;
|
||||
|
||||
// calculates the targets direction through only the X and Z axis
|
||||
sead::Vector3f targetDir = sead::Vector3f(
|
||||
mPosition.x - mTargetTrans.x,
|
||||
0.0f,
|
||||
mPosition.z - mTargetTrans.z
|
||||
);
|
||||
al::tryNormalizeOrDirZ(&targetDir);
|
||||
|
||||
sead::Vector3f rotatedVec;
|
||||
sead::Vector3f rightVec;
|
||||
|
||||
calcRotVec(targetDir, &rotatedVec, &rightVec);
|
||||
|
||||
float maxDist = mDistMax; // This will be cut short if a solid surface is found in the way
|
||||
sead::Vector3f resultVec;
|
||||
bool isHit = mPlayer && alCollisionUtil::getFirstPolyOnArrow(mPlayer, &resultVec, nullptr, mTargetTrans, rotatedVec * mDistMax, nullptr, nullptr);
|
||||
|
||||
if (isHit && mTargetActorPos) {
|
||||
// Formula calculates distance between result vec and target trans
|
||||
maxDist = sqrt(
|
||||
pow((resultVec.x - mTargetTrans.x), 2)
|
||||
+ pow((resultVec.y - mTargetTrans.y), 2)
|
||||
+ pow((resultVec.z - mTargetTrans.z), 2)
|
||||
);
|
||||
|
||||
maxDist -= maxDist / 30.f;
|
||||
if (maxDist <= 1) {
|
||||
maxDist = 1.f;
|
||||
}
|
||||
}
|
||||
|
||||
mDistLerp = (
|
||||
maxDist > mDistLerp
|
||||
? al::lerpValue(mDistLerp, maxDist, 0.07f)
|
||||
: maxDist
|
||||
);
|
||||
|
||||
mPosition = mTargetTrans + (rotatedVec * mDistLerp);
|
||||
}
|
||||
|
||||
void CameraPoserActorSpectate::calcRotVec(sead::Vector3f targetDir, sead::Vector3f* rotatedVec, sead::Vector3f* rightVec) {
|
||||
sead::Vector2f playerRInput(0, 0);
|
||||
alCameraPoserFunction::calcCameraRotateStick(&playerRInput, this);
|
||||
mInterpRStick.x = al::lerpValue(mInterpRStick.x, playerRInput.x, 0.2f);
|
||||
mInterpRStick.y = al::lerpValue(mInterpRStick.y, playerRInput.y, 0.2f);
|
||||
|
||||
// rotates target direction by the cameras X input
|
||||
al::rotateVectorDegreeY(
|
||||
&targetDir,
|
||||
(
|
||||
(mInterpRStick.x > 0.0f ? mInterpRStick.x : -mInterpRStick.x) < 0.02f
|
||||
? 0.0f
|
||||
: mInterpRStick.x * -2.0f
|
||||
)
|
||||
);
|
||||
|
||||
mAngle += mInterpRStick.y * -2.0f;
|
||||
mAngle = al::clamp(mAngle, -89.0f, 89.0f);
|
||||
|
||||
*rotatedVec = targetDir;
|
||||
|
||||
// calculates cross product of target direction and cameras current up direction
|
||||
sead::Vector3f crossVec;
|
||||
crossVec.setCross(targetDir, mCameraUp);
|
||||
// rotates target direction by the cross product and vertical angle
|
||||
al::rotateVectorDegree(rotatedVec, *rotatedVec, crossVec, mAngle);
|
||||
|
||||
// Calculate the camera's right
|
||||
*rightVec = *rotatedVec;
|
||||
al::rotateVectorDegreeY(rightVec, 90.f);
|
||||
}
|
||||
}
|
66
source/hooksFreezeTag.cpp
Normal file
66
source/hooksFreezeTag.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include "al/util.hpp"
|
||||
|
||||
#include "game/GameData/GameDataFile.h"
|
||||
|
||||
#include "server/freeze-tag/FreezeTagMode.hpp"
|
||||
#include "server/gamemode/GameModeManager.hpp"
|
||||
|
||||
|
||||
bool freezeIsCheckpointWarpAllowed() {
|
||||
return !GameModeManager::instance()->isModeAndActive(GameMode::FREEZETAG);
|
||||
}
|
||||
|
||||
bool freezeDeathArea(al::LiveActor const* player) {
|
||||
// If player isn't actively playing freeze tag, perform normal functionality
|
||||
if (!GameModeManager::instance()->isModeAndActive(GameMode::FREEZETAG)) {
|
||||
return al::isInDeathArea(player);
|
||||
}
|
||||
|
||||
// If player is in a death area but in Freeze Tag mode, start a recovery event
|
||||
if (al::isInAreaObj(player, "DeathArea")) {
|
||||
FreezeTagMode* mode = GameModeManager::instance()->getMode<FreezeTagMode>();
|
||||
if (!mode->isWipeout()) {
|
||||
mode->tryStartRecoveryEvent(false);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void freezePlayerHitPointDamage(PlayerHitPointData* thisPtr) {
|
||||
if (GameModeManager::instance()->isModeAndActive(GameMode::FREEZETAG)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int nextHit = 0;
|
||||
int maxUpVal = 0;
|
||||
|
||||
nextHit = thisPtr->mCurrentHit - 1;
|
||||
if (nextHit <= 0) {
|
||||
nextHit = 0;
|
||||
}
|
||||
|
||||
thisPtr->mCurrentHit = nextHit;
|
||||
|
||||
if (!thisPtr->mIsForceNormalHealth ) {
|
||||
if (nextHit <= (thisPtr->mIsKidsMode ? 6 : 3)) {
|
||||
thisPtr->mIsHaveMaxUpItem = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool freezeKidsMode(GameDataFile* thisPtr) {
|
||||
if (GameModeManager::instance()->isModeAndActive(GameMode::FREEZETAG)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return thisPtr->mIsKidsMode;
|
||||
}
|
||||
|
||||
bool freezeMoonHitboxDisable(al::IUseNerve* nrvUse, al::Nerve* nrv) {
|
||||
if (GameModeManager::instance()->isModeAndActive(GameMode::FREEZETAG)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return al::isNerve(nrvUse, nrv);
|
||||
}
|
|
@ -93,6 +93,11 @@ void PuppetActor::init(al::ActorInitInfo const& initInfo) {
|
|||
|
||||
al::validateClipping(normalModel);
|
||||
al::validateClipping(normal2DModel);
|
||||
|
||||
if (GameModeManager::instance()->isMode(GameMode::FREEZETAG)) {
|
||||
mFreezeTagIceBlock = new FreezePlayerBlock("PuppetIceBlock");
|
||||
mFreezeTagIceBlock->init(initInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void PuppetActor::initAfterPlacement() { al::LiveActor::initAfterPlacement(); }
|
||||
|
@ -105,6 +110,23 @@ void PuppetActor::initOnline(PuppetInfo* pupInfo) {
|
|||
|
||||
void PuppetActor::movement() {
|
||||
al::LiveActor::movement();
|
||||
|
||||
if (mFreezeTagIceBlock) {
|
||||
bool isAlive = al::isAlive(mFreezeTagIceBlock);
|
||||
if ( mInfo->ftIsFrozen()
|
||||
&& mInfo->isConnected
|
||||
&& mInfo->isInSameStage
|
||||
) {
|
||||
if (!isAlive) {
|
||||
mFreezeTagIceBlock->appear();
|
||||
}
|
||||
} else if (isAlive && !al::isNerve(mFreezeTagIceBlock, &nrvFreezePlayerBlockDisappear)) {
|
||||
mFreezeTagIceBlock->end();
|
||||
}
|
||||
|
||||
al::setTrans(mFreezeTagIceBlock, mInfo->playerPos);
|
||||
al::setQuat(mFreezeTagIceBlock, mInfo->playerRot);
|
||||
}
|
||||
}
|
||||
|
||||
void PuppetActor::calcAnim() {
|
||||
|
@ -228,6 +250,10 @@ void PuppetActor::makeActorDead() {
|
|||
|
||||
mPuppetCap->makeActorDead();
|
||||
|
||||
if (mFreezeTagIceBlock) {
|
||||
mFreezeTagIceBlock->makeActorDead();
|
||||
}
|
||||
|
||||
al::LiveActor::makeActorDead();
|
||||
}
|
||||
|
||||
|
|
110
source/server/freeze-tag/FreezeHintArrow.cpp
Normal file
110
source/server/freeze-tag/FreezeHintArrow.cpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
#include "server/freeze-tag/FreezeHintArrow.h"
|
||||
|
||||
#include "al/util/VectorUtil.h"
|
||||
|
||||
#include "sead/math/seadQuat.h"
|
||||
|
||||
#include "server/gamemode/GameMode.hpp"
|
||||
#include "server/gamemode/GameModeManager.hpp"
|
||||
#include "server/freeze-tag/FreezeTagInfo.h"
|
||||
|
||||
FreezeHintArrow::FreezeHintArrow(const char* name) : al::LiveActor(name) {}
|
||||
|
||||
void FreezeHintArrow::init(al::ActorInitInfo const& info) {
|
||||
al::initActorWithArchiveName(this, info, "FreezeHintArrow", nullptr);
|
||||
al::initNerve(this, &nrvFreezeHintArrowWait, 0);
|
||||
al::invalidateClipping(this);
|
||||
|
||||
makeActorAlive();
|
||||
}
|
||||
|
||||
void FreezeHintArrow::initAfterPlacement(void) {
|
||||
al::LiveActor::initAfterPlacement();
|
||||
|
||||
mPlayer = al::tryGetPlayerActor(mSceneInfo->mPlayerHolder, 0);
|
||||
mTargetTrans = al::getTransPtr(mPlayer);
|
||||
mArrowTrans = al::getTransPtr(this);
|
||||
|
||||
if (!GameModeManager::instance()->isMode(GameMode::FREEZETAG)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mInfo = GameModeManager::instance()->getInfo<FreezeTagInfo>();
|
||||
}
|
||||
|
||||
bool FreezeHintArrow::receiveMsg(const al::SensorMsg* message, al::HitSensor* source, al::HitSensor* target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void FreezeHintArrow::attackSensor(al::HitSensor* target, al::HitSensor* source) {}
|
||||
|
||||
void FreezeHintArrow::control(void) {
|
||||
al::LiveActor::control();
|
||||
}
|
||||
|
||||
void FreezeHintArrow::appear() {
|
||||
al::LiveActor::appear();
|
||||
al::setNerve(this, &nrvFreezeHintArrowWait);
|
||||
}
|
||||
|
||||
void FreezeHintArrow::end() {
|
||||
kill();
|
||||
}
|
||||
|
||||
void FreezeHintArrow::exeWait() {
|
||||
if (al::isFirstStep(this)) {
|
||||
al::startAction(this, "Wait");
|
||||
}
|
||||
|
||||
if ( !GameModeManager::instance()->isModeAndActive(GameMode::FREEZETAG) // we're not in freeze-tag mode
|
||||
|| mInfo->isPlayerRunner() // or we're a runner
|
||||
|| !mInfo->isRound() // or we're outside of a round
|
||||
|| !mTargetTrans // or there is no runner far enough away
|
||||
|| mPlayer->getPlayerHackKeeper()->currentHackActor // or the target hides in a capture
|
||||
) {
|
||||
// hide the arrow
|
||||
mSize = al::lerpValue(mSize, 0.f, 0.3f);
|
||||
al::setScaleAll(this, mSize);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update translation to player
|
||||
*mArrowTrans = al::getTrans(mPlayer);
|
||||
mArrowTrans->y += 200.f;
|
||||
|
||||
// Update size based on active & visible bools
|
||||
mSize = al::lerpValue(mSize, mIsActive && mIsVisible ? 1.f : 0.f, 0.2f);
|
||||
al::setScaleAll(this, mSize);
|
||||
|
||||
if (!mIsActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check distance and set the visiblity based on that
|
||||
mVisibilityCooldown = al::clamp(mVisibilityCooldown - 1, 0, 255); // Decrease visiblity cooldown, capped at zero
|
||||
|
||||
if (mVisibilityCooldown == 0) {
|
||||
mDistanceSq = vecDistanceSq(al::getTrans(this), *mTargetTrans);
|
||||
mIsVisible = mMinDistanceSq < mDistanceSq;
|
||||
|
||||
if (mIsVisible != mWasVisible) {
|
||||
mWasVisible = mIsVisible;
|
||||
mVisibilityCooldown = 150;
|
||||
}
|
||||
}
|
||||
|
||||
// If the actor is visible, update the rotation based on the target location
|
||||
if (mIsVisible) {
|
||||
sead::Vector3f direction = *mTargetTrans - *mArrowTrans;
|
||||
al::normalize(&direction);
|
||||
|
||||
sead::Quatf newQuat;
|
||||
al::makeQuatUpFront(&newQuat, mActorUp, direction);
|
||||
|
||||
al::setQuat(this, newQuat);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
NERVE_IMPL(FreezeHintArrow, Wait)
|
||||
}
|
104
source/server/freeze-tag/FreezePlayerBlock.cpp
Normal file
104
source/server/freeze-tag/FreezePlayerBlock.cpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
#include "server/freeze-tag/FreezePlayerBlock.h"
|
||||
|
||||
#include "al/util.hpp"
|
||||
|
||||
FreezePlayerBlock::FreezePlayerBlock(const char* name) : al::LiveActor(name) {}
|
||||
|
||||
void FreezePlayerBlock::init(al::ActorInitInfo const& info) {
|
||||
al::initActorWithArchiveName(this, info, "FreezePlayerBlock", nullptr);
|
||||
al::initNerve(this, &nrvFreezePlayerBlockAppear, 0);
|
||||
|
||||
al::invalidateClipping(this);
|
||||
|
||||
al::setDitherAnimSphereRadius(this, 0.f);
|
||||
|
||||
makeActorDead();
|
||||
}
|
||||
|
||||
void FreezePlayerBlock::initAfterPlacement(void) {
|
||||
al::LiveActor::initAfterPlacement();
|
||||
return;
|
||||
}
|
||||
|
||||
bool FreezePlayerBlock::receiveMsg(const al::SensorMsg* message, al::HitSensor* source, al::HitSensor* target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void FreezePlayerBlock::attackSensor(al::HitSensor* target, al::HitSensor* source) {}
|
||||
|
||||
void FreezePlayerBlock::control(void) {
|
||||
al::LiveActor::control();
|
||||
}
|
||||
|
||||
void FreezePlayerBlock::appear() {
|
||||
al::LiveActor::appear();
|
||||
al::setNerve(this, &nrvFreezePlayerBlockAppear);
|
||||
mIsLocked = true;
|
||||
}
|
||||
|
||||
void FreezePlayerBlock::end() {
|
||||
al::setNerve(this, &nrvFreezePlayerBlockDisappear);
|
||||
mIsLocked = false;
|
||||
}
|
||||
|
||||
void FreezePlayerBlock::exeAppear() {
|
||||
if (al::isFirstStep(this)) {
|
||||
al::startAction(this, "Appear");
|
||||
al::setScaleAll(this, 1.f);
|
||||
}
|
||||
|
||||
mDitheringOffset = -420.f; // Resets the dithering offset to slightly beyond the fully opaque value
|
||||
al::setDitherAnimSphereRadius(this, 0.f);
|
||||
|
||||
if (al::isActionEnd(this)) {
|
||||
al::setNerve(this, &nrvFreezePlayerBlockWait);
|
||||
}
|
||||
}
|
||||
|
||||
void FreezePlayerBlock::exeWait() {
|
||||
if (al::isFirstStep(this)) {
|
||||
al::startAction(this, "Wait");
|
||||
}
|
||||
|
||||
// Start by updating the lerp on the dithering offset
|
||||
mDitheringOffset = al::lerpValue(mDitheringOffset, -65.f, 0.02f);
|
||||
|
||||
// Update dithering based on current camera info
|
||||
al::CameraPoser* curPoser;
|
||||
al::CameraDirector* director = mSceneInfo->mCameraDirector;
|
||||
|
||||
// Verify the scene info has a camera director, then set the poser
|
||||
if (director) {
|
||||
al::CameraPoseUpdater* updater = director->getPoseUpdater(0);
|
||||
if (updater && updater->mTicket) {
|
||||
curPoser = updater->mTicket->mPoser;
|
||||
}
|
||||
}
|
||||
|
||||
if (curPoser) { // Actually update the dithering stuff here
|
||||
float dist = al::calcDistance(this, curPoser->mPosition);
|
||||
al::setDitherAnimSphereRadius(this, dist + mDitheringOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void FreezePlayerBlock::exeDisappear() {
|
||||
float scale = al::lerpValue(*al::getScaleX(this), 0.f, 0.2f);
|
||||
al::setScaleAll(this, scale);
|
||||
|
||||
if (al::isNearZero(scale, 0.05f)) {
|
||||
al::setNerve(this, &nrvFreezePlayerBlockDead);
|
||||
}
|
||||
}
|
||||
|
||||
void FreezePlayerBlock::exeDead() {
|
||||
if (al::isFirstStep(this)) {
|
||||
kill();
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
NERVE_IMPL(FreezePlayerBlock, Appear)
|
||||
NERVE_IMPL(FreezePlayerBlock, Wait)
|
||||
NERVE_IMPL(FreezePlayerBlock, Disappear)
|
||||
NERVE_IMPL(FreezePlayerBlock, Dead)
|
||||
}
|
116
source/server/freeze-tag/FreezeTagChaserSlot.cpp
Normal file
116
source/server/freeze-tag/FreezeTagChaserSlot.cpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
#include "server/freeze-tag/FreezeTagChaserSlot.h"
|
||||
|
||||
#include "al/string/StringTmp.h"
|
||||
#include "al/util.hpp"
|
||||
|
||||
#include "puppets/PuppetInfo.h"
|
||||
|
||||
#include "server/Client.hpp"
|
||||
#include "server/gamemode/GameModeManager.hpp"
|
||||
#include "server/freeze-tag/FreezeTagInfo.h"
|
||||
|
||||
FreezeTagChaserSlot::FreezeTagChaserSlot(const char* name, const al::LayoutInitInfo& initInfo) : al::LayoutActor(name) {
|
||||
al::initLayoutActor(this, initInfo, "FreezeTagChaserSlot", 0);
|
||||
mInfo = GameModeManager::instance()->getInfo<FreezeTagInfo>();
|
||||
|
||||
initNerve(&nrvFreezeTagChaserSlotEnd, 0);
|
||||
kill();
|
||||
}
|
||||
|
||||
void FreezeTagChaserSlot::init(int index) {
|
||||
// Place slot based on index and hide
|
||||
al::setPaneLocalTrans(this, "ChaserSlot", { 580.f, 270.f - (index * 55.f), 0.f });
|
||||
al::hidePane(this, "ChaserSlot");
|
||||
|
||||
// Set temporary name string
|
||||
al::setPaneString(this, "TxtChaserName", u"MaxLengthNameAaa", 0);
|
||||
|
||||
mChaserIndex = index;
|
||||
return;
|
||||
}
|
||||
|
||||
void FreezeTagChaserSlot::appear() {
|
||||
al::startAction(this, "Appear", 0);
|
||||
al::setNerve(this, &nrvFreezeTagChaserSlotAppear);
|
||||
al::LayoutActor::appear();
|
||||
}
|
||||
|
||||
bool FreezeTagChaserSlot::tryEnd() {
|
||||
if (!al::isNerve(this, &nrvFreezeTagChaserSlotEnd)) {
|
||||
al::setNerve(this, &nrvFreezeTagChaserSlotEnd);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FreezeTagChaserSlot::tryStart() {
|
||||
if (!al::isNerve(this, &nrvFreezeTagChaserSlotWait) && !al::isNerve(this, &nrvFreezeTagChaserSlotAppear)) {
|
||||
appear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FreezeTagChaserSlot::exeAppear() {
|
||||
if (al::isActionEnd(this, 0)) {
|
||||
al::setNerve(this, &nrvFreezeTagChaserSlotWait);
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagChaserSlot::exeWait() {
|
||||
if (al::isFirstStep(this)) {
|
||||
al::startAction(this, "Wait", 0);
|
||||
}
|
||||
|
||||
mIsPlayer = mChaserIndex == 0 && mInfo->isPlayerChaser();
|
||||
|
||||
// Show/hide icon if player doesn't exist in this slot
|
||||
if (mInfo->chasers() <= mChaserIndex) {
|
||||
if (mIsVisible) {
|
||||
hideSlot();
|
||||
}
|
||||
} else if (!mIsVisible) {
|
||||
showSlot();
|
||||
}
|
||||
|
||||
if (!mIsVisible) { // If icon isn't visible, end wait processing here
|
||||
return;
|
||||
}
|
||||
|
||||
// Update name info in this slot
|
||||
if (mIsPlayer) {
|
||||
setSlotName(Client::instance()->getClientName());
|
||||
setSlotScore(mInfo->getScore());
|
||||
} else {
|
||||
PuppetInfo* other = mInfo->mChaserPlayers.at(mChaserIndex - mInfo->isPlayerChaser());
|
||||
setSlotName(other->puppetName);
|
||||
setSlotScore(other->ftGetScore());
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagChaserSlot::exeEnd() {
|
||||
if (al::isFirstStep(this)) {
|
||||
al::startAction(this, "End", 0);
|
||||
}
|
||||
|
||||
if (al::isActionEnd(this, 0)) {
|
||||
kill();
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagChaserSlot::showSlot() {
|
||||
mIsVisible = true;
|
||||
al::showPane(this, "ChaserSlot");
|
||||
}
|
||||
|
||||
void FreezeTagChaserSlot::hideSlot() {
|
||||
mIsVisible = false;
|
||||
al::hidePane(this, "ChaserSlot");
|
||||
}
|
||||
|
||||
namespace {
|
||||
NERVE_IMPL(FreezeTagChaserSlot, Appear)
|
||||
NERVE_IMPL(FreezeTagChaserSlot, Wait)
|
||||
NERVE_IMPL(FreezeTagChaserSlot, End)
|
||||
}
|
175
source/server/freeze-tag/FreezeTagConfigMenu.cpp
Normal file
175
source/server/freeze-tag/FreezeTagConfigMenu.cpp
Normal file
|
@ -0,0 +1,175 @@
|
|||
#include "server/freeze-tag/FreezeTagConfigMenu.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "nn/util.h"
|
||||
|
||||
FreezeTagConfigMenu::FreezeTagConfigMenu() : GameModeConfigMenu() {
|
||||
mItems = new sead::SafeArray<sead::WFixedSafeString<0x200>, mItemCount>();
|
||||
mItems->mBuffer[0].copy(u"Host Mode (OFF) ");
|
||||
mItems->mBuffer[1].copy(u"Debug Mode (OFF) ");
|
||||
mItems->mBuffer[2].copy(u"Set Score ");
|
||||
mItems->mBuffer[3].copy(u"Set Round Duration ");
|
||||
mItems->mBuffer[4].copy(u"Mario Collision (ON) ");
|
||||
mItems->mBuffer[5].copy(u"Mario Bounce (OFF) ");
|
||||
mItems->mBuffer[6].copy(u"Cappy Collision (ON) ");
|
||||
mItems->mBuffer[7].copy(u"Cappy Bounce (OFF) ");
|
||||
|
||||
mScoreKeyboard = new Keyboard(6);
|
||||
mScoreKeyboard->setHeaderText(u"Set your Freeze Tag score");
|
||||
mScoreKeyboard->setSubText(u"");
|
||||
|
||||
mRoundKeyboard = new Keyboard(3);
|
||||
mRoundKeyboard->setHeaderText(u"Set length of rounds you start in minutes");
|
||||
mRoundKeyboard->setSubText(u"This will be automatically sent to other players (2-60 minutes)");
|
||||
}
|
||||
|
||||
const sead::WFixedSafeString<0x200>* FreezeTagConfigMenu::getStringData() {
|
||||
const char16_t* host = (
|
||||
FreezeTagInfo::mIsHostMode
|
||||
? u"Host Mode (ON) "
|
||||
: u"Host Mode (OFF) "
|
||||
);
|
||||
|
||||
const char16_t* debug = (
|
||||
FreezeTagInfo::mIsDebugMode
|
||||
? u"Debug Mode (ON) "
|
||||
: u"Debug Mode (OFF) "
|
||||
);
|
||||
|
||||
const char16_t* roundDuration = u"Set Round Duration ";
|
||||
|
||||
// Collision Toggles
|
||||
const char16_t* marioCollision = (
|
||||
FreezeTagInfo::mHasMarioCollision
|
||||
? u"Mario Collision (ON) "
|
||||
: u"Mario Collision (OFF)"
|
||||
);
|
||||
const char16_t* marioBounce = (
|
||||
FreezeTagInfo::mHasMarioBounce
|
||||
? u"Mario Bounce (ON) "
|
||||
: u"Mario Bounce (OFF) "
|
||||
);
|
||||
const char16_t* cappyCollision = (
|
||||
FreezeTagInfo::mHasCappyCollision
|
||||
? u"Cappy Collision (ON) "
|
||||
: u"Cappy Collision (OFF)"
|
||||
);
|
||||
const char16_t* cappyBounce = (
|
||||
FreezeTagInfo::mHasCappyBounce
|
||||
? u"Cappy Bounce (ON) "
|
||||
: u"Cappy Bounce (OFF) "
|
||||
);
|
||||
|
||||
mItems->mBuffer[0].copy(host);
|
||||
mItems->mBuffer[1].copy(debug);
|
||||
mItems->mBuffer[3].copy(FreezeTagInfo::mIsHostMode ? roundDuration : marioCollision);
|
||||
mItems->mBuffer[4].copy(FreezeTagInfo::mIsHostMode ? marioCollision : marioBounce);
|
||||
mItems->mBuffer[5].copy(FreezeTagInfo::mIsHostMode ? marioBounce : cappyCollision);
|
||||
mItems->mBuffer[6].copy(FreezeTagInfo::mIsHostMode ? cappyCollision : cappyBounce);
|
||||
mItems->mBuffer[7].copy(cappyBounce);
|
||||
|
||||
return mItems->mBuffer;
|
||||
}
|
||||
|
||||
GameModeConfigMenu::UpdateAction FreezeTagConfigMenu::updateMenu(int selectIndex) {
|
||||
int adjustedIndex = selectIndex + (!FreezeTagInfo::mIsHostMode && selectIndex >= 3);
|
||||
switch (adjustedIndex) {
|
||||
// Toggle Host Mode
|
||||
case 0: {
|
||||
FreezeTagInfo::mIsHostMode = !FreezeTagInfo::mIsHostMode;
|
||||
return UpdateAction::REFRESH;
|
||||
}
|
||||
// Toggle Debug Mode
|
||||
case 1: {
|
||||
FreezeTagInfo::mIsDebugMode = !FreezeTagInfo::mIsDebugMode;
|
||||
return UpdateAction::REFRESH;
|
||||
}
|
||||
// Set Score
|
||||
case 2: {
|
||||
uint16_t oldScore = FreezeTagInfo::mPlayerTagScore.mScore;
|
||||
uint16_t newScore = -1;
|
||||
|
||||
char buf[5];
|
||||
nn::util::SNPrintf(buf, 5, "%u", oldScore);
|
||||
|
||||
mScoreKeyboard->openKeyboard(
|
||||
buf,
|
||||
[](nn::swkbd::KeyboardConfig& config) {
|
||||
config.keyboardMode = nn::swkbd::KeyboardMode::ModeNumeric;
|
||||
config.textMaxLength = 4;
|
||||
config.textMinLength = 1;
|
||||
config.isUseUtf8 = true;
|
||||
config.inputFormMode = nn::swkbd::InputFormMode::OneLine;
|
||||
}
|
||||
);
|
||||
|
||||
while (!mScoreKeyboard->isThreadDone()) {
|
||||
nn::os::YieldThread(); // allow other threads to run
|
||||
}
|
||||
|
||||
if (!mScoreKeyboard->isKeyboardCancelled()) {
|
||||
newScore = ::atoi(mScoreKeyboard->getResult());
|
||||
}
|
||||
|
||||
if (newScore != uint16_t(-1)) {
|
||||
FreezeTagInfo::mPlayerTagScore.mScore = newScore;
|
||||
}
|
||||
|
||||
// We don't need to send the new score to other players here, because
|
||||
// FreezeTagMode::update() checks for score changes every iteration
|
||||
|
||||
return UpdateAction::NOOP;
|
||||
}
|
||||
// Set Round Duration
|
||||
case 3: {
|
||||
uint8_t oldTime = FreezeTagInfo::mRoundLength;
|
||||
uint8_t newTime = -1;
|
||||
|
||||
char buf[3];
|
||||
nn::util::SNPrintf(buf, 3, "%u", oldTime);
|
||||
|
||||
mRoundKeyboard->openKeyboard(
|
||||
buf,
|
||||
[](nn::swkbd::KeyboardConfig& config) {
|
||||
config.keyboardMode = nn::swkbd::KeyboardMode::ModeNumeric;
|
||||
config.textMaxLength = 2;
|
||||
config.textMinLength = 1;
|
||||
config.isUseUtf8 = true;
|
||||
config.inputFormMode = nn::swkbd::InputFormMode::OneLine;
|
||||
}
|
||||
);
|
||||
|
||||
while (!mRoundKeyboard->isThreadDone()) {
|
||||
nn::os::YieldThread(); // allow other threads to run
|
||||
}
|
||||
|
||||
if (!mRoundKeyboard->isKeyboardCancelled()) {
|
||||
newTime = ::atoi(mRoundKeyboard->getResult());
|
||||
}
|
||||
|
||||
if (newTime != uint8_t(-1)) {
|
||||
FreezeTagInfo::mRoundLength = al::clamp(newTime, u8(2), u8(60));
|
||||
}
|
||||
return UpdateAction::NOOP;
|
||||
}
|
||||
case 4: {
|
||||
FreezeTagInfo::mHasMarioCollision = !FreezeTagInfo::mHasMarioCollision;
|
||||
return UpdateAction::REFRESH;
|
||||
}
|
||||
case 5: {
|
||||
FreezeTagInfo::mHasMarioBounce = !FreezeTagInfo::mHasMarioBounce;
|
||||
return UpdateAction::REFRESH;
|
||||
}
|
||||
case 6: {
|
||||
FreezeTagInfo::mHasCappyCollision = !FreezeTagInfo::mHasCappyCollision;
|
||||
return UpdateAction::REFRESH;
|
||||
}
|
||||
case 7: {
|
||||
FreezeTagInfo::mHasCappyBounce = !FreezeTagInfo::mHasCappyBounce;
|
||||
return UpdateAction::REFRESH;
|
||||
}
|
||||
default: {
|
||||
return UpdateAction::NOOP;
|
||||
}
|
||||
}
|
||||
}
|
267
source/server/freeze-tag/FreezeTagIcon.cpp
Normal file
267
source/server/freeze-tag/FreezeTagIcon.cpp
Normal file
|
@ -0,0 +1,267 @@
|
|||
#include "server/freeze-tag/FreezeTagIcon.h"
|
||||
|
||||
#include "al/string/StringTmp.h"
|
||||
#include "al/util.hpp"
|
||||
|
||||
#include "server/DeltaTime.hpp"
|
||||
#include "server/gamemode/GameModeManager.hpp"
|
||||
#include "server/freeze-tag/FreezeTagInfo.h"
|
||||
#include "server/freeze-tag/FreezeTagRunnerSlot.h"
|
||||
#include "server/freeze-tag/FreezeTagChaserSlot.h"
|
||||
#include "server/freeze-tag/FreezeTagOtherSlot.h"
|
||||
|
||||
FreezeTagIcon::FreezeTagIcon(const char* name, const al::LayoutInitInfo& initInfo) : al::LayoutActor(name) {
|
||||
al::initLayoutActor(this, initInfo, "FreezeTagIcon", 0);
|
||||
al::hidePane(this, "Endgame");
|
||||
|
||||
mInfo = GameModeManager::instance()->getInfo<FreezeTagInfo>();
|
||||
mIsRunner = mInfo->isPlayerRunner();
|
||||
|
||||
mRunnerSlots.tryAllocBuffer(mMaxRunners, al::getSceneHeap());
|
||||
for (int i = 0; i < mMaxRunners; i++) {
|
||||
FreezeTagRunnerSlot* newSlot = new (al::getSceneHeap()) FreezeTagRunnerSlot("RunnerSlot", initInfo);
|
||||
newSlot->init(i);
|
||||
mRunnerSlots.pushBack(newSlot);
|
||||
}
|
||||
|
||||
mChaserSlots.tryAllocBuffer(mMaxChasers, al::getSceneHeap());
|
||||
for (int i = 0; i < mMaxChasers; i++) {
|
||||
FreezeTagChaserSlot* newSlot = new (al::getSceneHeap()) FreezeTagChaserSlot("ChaserSlot", initInfo);
|
||||
newSlot->init(i);
|
||||
mChaserSlots.pushBack(newSlot);
|
||||
}
|
||||
|
||||
mOtherSlots.tryAllocBuffer(mMaxOthers, al::getSceneHeap());
|
||||
for (int i = 0; i < mMaxOthers; i++) {
|
||||
FreezeTagOtherSlot* newSlot = new (al::getSceneHeap()) FreezeTagOtherSlot("OtherSlot", initInfo);
|
||||
newSlot->init(i);
|
||||
mOtherSlots.pushBack(newSlot);
|
||||
}
|
||||
|
||||
mSpectateName = nullptr;
|
||||
|
||||
initNerve(&nrvFreezeTagIconEnd, 0);
|
||||
kill();
|
||||
}
|
||||
|
||||
void FreezeTagIcon::appear() {
|
||||
al::startAction(this, "Appear", 0);
|
||||
al::setNerve(this, &nrvFreezeTagIconAppear);
|
||||
|
||||
for (int i = 0; i < mMaxRunners; i++) {
|
||||
mRunnerSlots.at(i)->tryStart();
|
||||
}
|
||||
|
||||
for (int i = 0; i < mMaxChasers; i++) {
|
||||
mChaserSlots.at(i)->tryStart();
|
||||
}
|
||||
|
||||
for (int i = 0; i < mMaxOthers; i++) {
|
||||
mOtherSlots.at(i)->tryStart();
|
||||
}
|
||||
|
||||
al::LayoutActor::appear();
|
||||
}
|
||||
|
||||
bool FreezeTagIcon::tryEnd() {
|
||||
if (!al::isNerve(this, &nrvFreezeTagIconEnd)) {
|
||||
al::setNerve(this, &nrvFreezeTagIconEnd);
|
||||
|
||||
for (int i = 0; i < mMaxRunners; i++) {
|
||||
mRunnerSlots.at(i)->tryEnd();
|
||||
}
|
||||
|
||||
for (int i = 0; i < mMaxChasers; i++) {
|
||||
mChaserSlots.at(i)->tryEnd();
|
||||
}
|
||||
|
||||
for (int i = 0; i < mMaxOthers; i++) {
|
||||
mOtherSlots.at(i)->tryEnd();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FreezeTagIcon::tryStart() {
|
||||
if (!al::isNerve(this, &nrvFreezeTagIconWait) && !al::isNerve(this, &nrvFreezeTagIconAppear)) {
|
||||
appear();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FreezeTagIcon::exeAppear() {
|
||||
if (al::isActionEnd(this, 0)) {
|
||||
al::setNerve(this, &nrvFreezeTagIconWait);
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagIcon::exeWait() {
|
||||
if (al::isFirstStep(this)) {
|
||||
al::startAction(this, "Wait", 0);
|
||||
}
|
||||
|
||||
// Set all overlay positons
|
||||
setFreezeOverlayHeight();
|
||||
setSpectateOverlayHeight();
|
||||
setRoundTimerOverlay();
|
||||
|
||||
// Update score event info
|
||||
if (mScoreEventIsQueued) {
|
||||
mScoreEventIsQueued = false;
|
||||
al::setPaneStringFormat(this, "TxtScoreNum", "%i", mScoreEventValue);
|
||||
al::setPaneStringFormat(this, "TxtScoreDesc", "%s", mScoreEventDesc);
|
||||
}
|
||||
|
||||
if (mScoreEventTime >= 0.f) {
|
||||
mScoreEventTime += Time::deltaTime;
|
||||
|
||||
if (mScoreEventValue != 0) {
|
||||
al::setPaneLocalScale(this, "TxtScoreNum", { 1.f, 1.f });
|
||||
al::setPaneLocalScale(this, "PicScorePlus", { 1.f, 1.f });
|
||||
} else {
|
||||
al::setPaneLocalScale(this, "TxtScoreNum", { 0.f, 0.f });
|
||||
al::setPaneLocalScale(this, "PicScorePlus", { 0.f, 0.f });
|
||||
}
|
||||
|
||||
if (mScoreEventTime > 3.75f) {
|
||||
mScoreEventValue = 0;
|
||||
}
|
||||
|
||||
// Calculate score event pane's position
|
||||
sead::Vector3f targetPos = (
|
||||
mScoreEventTime < 3.f
|
||||
? sead::Vector3f( 0.f, 235.f, 0.f)
|
||||
: sead::Vector3f(-650.f, 420.f, 0.f)
|
||||
);
|
||||
if (mInfo->isPlayerChaser()) {
|
||||
targetPos.x *= -1.f;
|
||||
}
|
||||
|
||||
al::lerpVec(&mScoreEventPos, mScoreEventPos, targetPos, 0.05f);
|
||||
al::setPaneLocalTrans(this, "ScoreEvent", mScoreEventPos);
|
||||
|
||||
// Calculate score event pane's scale
|
||||
float targetScale = mScoreEventTime < 3.f ? 1.0f : 0.f;
|
||||
mScoreEventScale = al::lerpValue(mScoreEventScale, targetScale, 0.05f);
|
||||
al::setPaneLocalScale(this, "ScoreEvent", { mScoreEventScale, mScoreEventScale });
|
||||
}
|
||||
|
||||
// Spectate UI
|
||||
if (mInfo->isPlayerFrozen() && mSpectateName) {
|
||||
al::setPaneStringFormat(this, "TxtSpectateTarget", "%s", mSpectateName);
|
||||
}
|
||||
|
||||
// Endgame UI (Wipeout)
|
||||
if (mEndgameIsDisplay) {
|
||||
if (al::isHidePane(this, "Endgame")) {
|
||||
al::showPane(this, "Endgame");
|
||||
}
|
||||
|
||||
mEndgameTextSize = al::lerpValue(mEndgameTextSize, 1.1f, 0.02f);
|
||||
mEndgameTextAngle = al::lerpValue(mEndgameTextAngle, 5.f, 0.01f);
|
||||
|
||||
al::setPaneLocalScale(this, "PicEndgameText", { mEndgameTextSize, mEndgameTextSize });
|
||||
al::setPaneLocalRotate(this, "PicEndgameText", { 0.f, 0.f, mEndgameTextAngle });
|
||||
}
|
||||
|
||||
if (!mEndgameIsDisplay && !al::isHidePane(this, "Endgame")) {
|
||||
al::hidePane(this, "Endgame");
|
||||
}
|
||||
|
||||
// Other Players
|
||||
if (mInfo->isRound() || mInfo->others() == 0) {
|
||||
if (!al::isHidePane(this, "PicHeaderOther")) {
|
||||
al::hidePane(this, "PicHeaderOther");
|
||||
}
|
||||
} else {
|
||||
if (al::isHidePane(this, "PicHeaderOther")) {
|
||||
al::showPane(this, "PicHeaderOther");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagIcon::queueScoreEvent(int eventValue, const char* eventDesc) {
|
||||
mScoreEventTime = 0.f;
|
||||
mScoreEventIsQueued = true;
|
||||
mScoreEventValue += eventValue;
|
||||
mScoreEventValue = al::clamp(mScoreEventValue, 0, 99);
|
||||
|
||||
mScoreEventDesc = eventDesc;
|
||||
mScoreEventPos = sead::Vector3f(0.f, 245.f, 0.f);
|
||||
mScoreEventScale = 1.35f;
|
||||
}
|
||||
|
||||
void FreezeTagIcon::setFreezeOverlayHeight() {
|
||||
// Show or hide the frozen UI overlay (frozen borders on top and bottom of the screen when being frozen)
|
||||
float targetHeight = mInfo->isPlayerFrozen() ? 360.f : 415.f;
|
||||
mFreezeOverlayHeight = al::lerpValue(mFreezeOverlayHeight, targetHeight, 0.08f);
|
||||
al::setPaneLocalTrans(this, "PicFreezeOverlayTop", { 0.f, mFreezeOverlayHeight + 15.f, 0.f });
|
||||
al::setPaneLocalTrans(this, "PicFreezeOverlayBot", { 0.f, -mFreezeOverlayHeight, 0.f });
|
||||
}
|
||||
|
||||
void FreezeTagIcon::setSpectateOverlayHeight() {
|
||||
// Show or hide the spectator UI
|
||||
float targetHeight = (
|
||||
mInfo->isPlayerFrozen()
|
||||
&& mInfo->runners() > 1
|
||||
&& !mEndgameIsDisplay
|
||||
? -250.f
|
||||
: -400.f
|
||||
);
|
||||
mSpectateOverlayHeight = al::lerpValue(mSpectateOverlayHeight, targetHeight, 0.04f);
|
||||
al::setPaneLocalTrans(this, "Spectate", { 0.f, mSpectateOverlayHeight, 0.f });
|
||||
}
|
||||
|
||||
void FreezeTagIcon::setRoundTimerOverlay() {
|
||||
// If round is active, set the timer's height on screen
|
||||
float targetHeight = (
|
||||
mInfo->isRound()
|
||||
&& !mEndgameIsDisplay
|
||||
? 330.f
|
||||
: 390.f
|
||||
);
|
||||
mRoundTimerHeight = al::lerpValue(mRoundTimerHeight, targetHeight, 0.03f);
|
||||
al::setPaneLocalTrans(this, "RoundTimer", { 0.f, mRoundTimerHeight, 0.f });
|
||||
|
||||
// If time remaining is less than one minute, scale it up to be larger
|
||||
float targetScale = (
|
||||
mInfo->isRound()
|
||||
&& !mEndgameIsDisplay
|
||||
&& mInfo->mRoundTimer.mMinutes <= 0
|
||||
? 1.66f
|
||||
: 1.f
|
||||
);
|
||||
mRoundTimerScale = al::lerpValue(mRoundTimerScale, targetScale, 0.02f);
|
||||
al::setPaneLocalScale(this, "RoundTimer", { mRoundTimerScale, mRoundTimerScale });
|
||||
|
||||
// Spin the inside of the clock if a round is going
|
||||
if (mInfo->isRound()) {
|
||||
mRoundTimerClockInsideSpin -= 1.2f;
|
||||
if (mRoundTimerClockInsideSpin < -360.f) {
|
||||
mRoundTimerClockInsideSpin += 360.f;
|
||||
}
|
||||
|
||||
al::setPaneLocalRotate(this, "PicRoundTimerSpin", { 0.f, 0.f, mRoundTimerClockInsideSpin });
|
||||
}
|
||||
|
||||
al::setPaneStringFormat(this, "TxtRoundTimer", "%02i:%02i", mInfo->mRoundTimer.mMinutes, mInfo->mRoundTimer.mSeconds);
|
||||
}
|
||||
|
||||
void FreezeTagIcon::exeEnd() {
|
||||
if (al::isFirstStep(this)) {
|
||||
al::startAction(this, "End", 0);
|
||||
}
|
||||
|
||||
if (al::isActionEnd(this, 0)) {
|
||||
kill();
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
NERVE_IMPL(FreezeTagIcon, Appear)
|
||||
NERVE_IMPL(FreezeTagIcon, Wait)
|
||||
NERVE_IMPL(FreezeTagIcon, End)
|
||||
}
|
11
source/server/freeze-tag/FreezeTagInfo.cpp
Normal file
11
source/server/freeze-tag/FreezeTagInfo.cpp
Normal file
|
@ -0,0 +1,11 @@
|
|||
#include "server/freeze-tag/FreezeTagInfo.h"
|
||||
|
||||
FreezeTagScore FreezeTagInfo::mPlayerTagScore;
|
||||
int FreezeTagInfo::mRoundLength = 10;
|
||||
bool FreezeTagInfo::mIsHostMode = false;
|
||||
bool FreezeTagInfo::mIsDebugMode = false;
|
||||
|
||||
bool FreezeTagInfo::mHasMarioCollision = true;
|
||||
bool FreezeTagInfo::mHasMarioBounce = false;
|
||||
bool FreezeTagInfo::mHasCappyCollision = true;
|
||||
bool FreezeTagInfo::mHasCappyBounce = false;
|
605
source/server/freeze-tag/FreezeTagMode.cpp
Normal file
605
source/server/freeze-tag/FreezeTagMode.cpp
Normal file
|
@ -0,0 +1,605 @@
|
|||
#include "server/freeze-tag/FreezeTagMode.hpp"
|
||||
|
||||
#include "sead/heap/seadHeapMgr.h"
|
||||
|
||||
#include "actors/PuppetActor.h"
|
||||
|
||||
#include "al/async/FunctorV0M.hpp"
|
||||
#include "al/util.hpp"
|
||||
|
||||
#include "game/Player/PlayerFunction.h"
|
||||
#include "game/Player/PlayerHitPointData.h"
|
||||
#include "game/StageScene/StageScene.h"
|
||||
|
||||
#include "logger.hpp"
|
||||
|
||||
#include "server/Client.hpp"
|
||||
#include "server/DeltaTime.hpp"
|
||||
|
||||
#include "server/gamemode/GameMode.hpp"
|
||||
#include "server/gamemode/GameModeManager.hpp"
|
||||
#include "server/gamemode/GameModeFactory.hpp"
|
||||
|
||||
#include "server/freeze-tag/FreezeTagScore.hpp"
|
||||
#include "server/freeze-tag/FreezeHintArrow.h"
|
||||
#include "server/freeze-tag/FreezePlayerBlock.h"
|
||||
#include "server/freeze-tag/FreezeTagIcon.h"
|
||||
|
||||
FreezeTagMode::FreezeTagMode(const char* name) : GameModeBase(name) {}
|
||||
|
||||
void FreezeTagMode::init(const GameModeInitInfo& info) {
|
||||
mSceneObjHolder = info.mSceneObjHolder;
|
||||
mMode = info.mMode;
|
||||
mCurScene = (StageScene*)info.mScene;
|
||||
mPuppetHolder = info.mPuppetHolder;
|
||||
|
||||
GameModeInfoBase* curGameInfo = GameModeManager::instance()->getInfo<GameModeInfoBase>();
|
||||
|
||||
sead::ScopedCurrentHeapSetter heapSetter(GameModeManager::instance()->getHeap());
|
||||
|
||||
if (curGameInfo) {
|
||||
Logger::log("Gamemode info found: %s %s\n", GameModeFactory::getModeString(curGameInfo->mMode), GameModeFactory::getModeString(info.mMode));
|
||||
} else {
|
||||
Logger::log("No gamemode info found\n");
|
||||
}
|
||||
|
||||
if (curGameInfo && curGameInfo->mMode == mMode) {
|
||||
sead::ScopedCurrentHeapSetter heapSetter(GameModeManager::getSceneHeap());
|
||||
mInfo = (FreezeTagInfo*)curGameInfo;
|
||||
mModeTimer = new GameModeTimer(mInfo->mRoundTimer);
|
||||
Logger::log("Reinitialized timer with time %d:%.2d\n", mInfo->mRoundTimer.mMinutes, mInfo->mRoundTimer.mSeconds);
|
||||
} else {
|
||||
if (curGameInfo) {
|
||||
delete curGameInfo; // attempt to destory previous info before creating new one
|
||||
}
|
||||
|
||||
mInfo = GameModeManager::instance()->createModeInfo<FreezeTagInfo>();
|
||||
|
||||
mModeTimer = new GameModeTimer();
|
||||
}
|
||||
|
||||
sead::ScopedCurrentHeapSetter heapSetterr(GameModeManager::getSceneHeap());
|
||||
|
||||
mModeLayout = new FreezeTagIcon("FreezeTagIcon", *info.mLayoutInitInfo);
|
||||
mInfo->mPlayerTagScore.setTargetLayout(mModeLayout);
|
||||
mInfo->mPlayerTagScore.initBuffer();
|
||||
|
||||
mInfo->mRunnerPlayers.allocBuffer(0x10, al::getSceneHeap());
|
||||
mInfo->mChaserPlayers.allocBuffer(0x10, al::getSceneHeap());
|
||||
mInfo->mOtherPlayers.allocBuffer(0x10, al::getSceneHeap());
|
||||
|
||||
// Create main player's ice block
|
||||
mMainPlayerIceBlock = new FreezePlayerBlock("MainPlayerBlock");
|
||||
mMainPlayerIceBlock->init(*info.mActorInitInfo);
|
||||
|
||||
// Create hint arrow
|
||||
mHintArrow = new FreezeHintArrow("ChaserHintArrow");
|
||||
mHintArrow->init(*info.mActorInitInfo);
|
||||
}
|
||||
|
||||
void FreezeTagMode::processPacket(Packet* _packet) {
|
||||
FreezeTagPacket* packet = (FreezeTagPacket*)_packet;
|
||||
FreezeUpdateType updateType = packet->updateType();
|
||||
|
||||
/**
|
||||
* Ignore legacy game mode packets for other game modes
|
||||
*
|
||||
* Legacy Freeze-Tag packets that we are interested in should have been automatically
|
||||
* transformed from LEGACY to FREEZETAG by the logic in the gameMode() function.
|
||||
*/
|
||||
if (packet->gameMode() == GameMode::LEGACY) {
|
||||
return;
|
||||
}
|
||||
|
||||
PuppetInfo* other = Client::findPuppetInfo(packet->mUserID, false);
|
||||
if (!other) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateType == FreezeUpdateType::PLAYER) {
|
||||
tryScoreEvent(packet, other);
|
||||
|
||||
// When puppet transitioning from frozen to unfrozen, disable the fall off flag
|
||||
if (other->ftIsFrozen() && !packet->isFreeze) {
|
||||
other->isFreezeTagFallenOff = false;
|
||||
}
|
||||
|
||||
other->isFreezeTagRunner = packet->isRunner;
|
||||
other->isFreezeTagFreeze = packet->isFreeze;
|
||||
other->freezeTagScore = packet->score;
|
||||
}
|
||||
|
||||
if (isRound()) {
|
||||
// Abort round early on receiving cancel packet
|
||||
if (updateType == FreezeUpdateType::ROUNDCANCEL) {
|
||||
if (packet->mPacketSize < sizeof(FreezeTagRoundCancelPacket) - sizeof(Packet)) { // From a legacy client
|
||||
endRound(true);
|
||||
mInfo->mPlayerTagScore.eventRoundCancelled(other->puppetName);
|
||||
} else { // From a new client
|
||||
FreezeTagRoundCancelPacket* roundCancel = (FreezeTagRoundCancelPacket*)packet;
|
||||
if (!roundCancel->onlyForLegacy) {
|
||||
endRound(true);
|
||||
mInfo->mPlayerTagScore.eventRoundCancelled(other->puppetName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updateType == FreezeUpdateType::FALLOFF) {
|
||||
other->isFreezeTagFallenOff = true;
|
||||
|
||||
if (isPlayerChaser()) {
|
||||
mInfo->mPlayerTagScore.eventScoreFallOff();
|
||||
}
|
||||
}
|
||||
} else if (updateType == FreezeUpdateType::ROUNDSTART) {
|
||||
FreezeTagRoundStartPacket* roundStart = (FreezeTagRoundStartPacket*)packet;
|
||||
startRound(al::clamp(roundStart->roundTime, u8(2), u8(60))); // Start round if round not already started
|
||||
mInfo->mPlayerTagScore.eventRoundStarted(other->puppetName);
|
||||
}
|
||||
}
|
||||
|
||||
Packet* FreezeTagMode::createPacket() {
|
||||
if (!isModeActive()) {
|
||||
DisabledGameModeInf* packet = new DisabledGameModeInf(Client::getClientId());
|
||||
packet->setUpdateType(0); // so that legacy freeze-tag clients don't wrongly interpret this as a round start
|
||||
return packet;
|
||||
}
|
||||
|
||||
if (mNextUpdateType == FreezeUpdateType::ROUNDSTART) {
|
||||
FreezeTagRoundStartPacket* packet = new FreezeTagRoundStartPacket();
|
||||
packet->mUserID = Client::getClientId();
|
||||
packet->roundTime = u8(mInfo->mRoundLength);
|
||||
return packet;
|
||||
}
|
||||
|
||||
if (mNextUpdateType == FreezeUpdateType::ROUNDCANCEL) {
|
||||
FreezeTagRoundCancelPacket* packet = new FreezeTagRoundCancelPacket();
|
||||
packet->mUserID = Client::getClientId();
|
||||
packet->onlyForLegacy = mCancelOnlyLegacy;
|
||||
mCancelOnlyLegacy = false;
|
||||
return packet;
|
||||
}
|
||||
|
||||
FreezeTagPacket* packet = new FreezeTagPacket();
|
||||
packet->mUserID = Client::getClientId();
|
||||
packet->isRunner = isPlayerRunner();
|
||||
packet->isFreeze = isPlayerFrozen();
|
||||
packet->score = getScore();
|
||||
packet->setUpdateType(mNextUpdateType);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
void FreezeTagMode::begin() {
|
||||
unpause();
|
||||
|
||||
mInvulnTime = 0.f;
|
||||
mSpectateIndex = -1; // ourself
|
||||
mPrevSpectateIndex = -2;
|
||||
mIsScoreEventsValid = true;
|
||||
|
||||
if (isRound()) {
|
||||
mModeTimer->enableTimer();
|
||||
}
|
||||
mModeTimer->disableControl();
|
||||
mModeTimer->setTimerDirection(false);
|
||||
|
||||
PlayerHitPointData* hit = mCurScene->mHolder.mData->mGameDataFile->getPlayerHitPointData();
|
||||
hit->mCurrentHit = hit->getMaxCurrent();
|
||||
hit->mIsKidsMode = true;
|
||||
|
||||
GameModeBase::begin();
|
||||
|
||||
mCurScene->mSceneLayout->end();
|
||||
}
|
||||
|
||||
void FreezeTagMode::end() {
|
||||
pause();
|
||||
|
||||
mInvulnTime = 0.f;
|
||||
mIsScoreEventsValid = false;
|
||||
|
||||
mCurScene->mSceneLayout->start();
|
||||
|
||||
if (!GameModeManager::instance()->isPaused()) {
|
||||
if (isPlayerFrozen()) {
|
||||
trySetPlayerRunnerState(FreezeState::ALIVE);
|
||||
}
|
||||
|
||||
if (mTicket->mIsActive) {
|
||||
al::endCamera(mCurScene, mTicket, 0, false);
|
||||
}
|
||||
|
||||
if (al::isAlive(mMainPlayerIceBlock) && !al::isNerve(mMainPlayerIceBlock, &nrvFreezePlayerBlockDisappear)) {
|
||||
mMainPlayerIceBlock->end();
|
||||
trySetPostProcessingType(FreezePostProcessingType::PPDISABLED);
|
||||
}
|
||||
|
||||
if (isRound()) {
|
||||
endRound(true);
|
||||
}
|
||||
}
|
||||
|
||||
GameModeBase::end();
|
||||
}
|
||||
|
||||
void FreezeTagMode::pause() {
|
||||
GameModeBase::pause();
|
||||
|
||||
mModeLayout->tryEnd();
|
||||
}
|
||||
|
||||
void FreezeTagMode::unpause() {
|
||||
GameModeBase::unpause();
|
||||
|
||||
mModeLayout->appear();
|
||||
}
|
||||
|
||||
void FreezeTagMode::update() {
|
||||
PlayerActorHakoniwa* player = getPlayerActorHakoniwa();
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the mode timer
|
||||
mModeTimer->updateTimer();
|
||||
mModeTimer->disableControl();
|
||||
|
||||
// Check for a decrease in the minute value (how survival time score as a runner is awarded)
|
||||
if (isPlayerRunner() && mModeTimer->getTime().mMinutes < mInfo->mRoundTimer.mMinutes) {
|
||||
mInfo->mPlayerTagScore.eventScoreSurvivalTime();
|
||||
}
|
||||
|
||||
mInfo->mRoundTimer = mModeTimer->getTime();
|
||||
|
||||
// Check if the time has run out for this round
|
||||
if (mModeTimer->isEnabled() && mModeTimer->getTimeCombined() <= 0.f) {
|
||||
endRound(false);
|
||||
}
|
||||
|
||||
// keep track of connected runner and chaser counts
|
||||
int enoughRunners = Client::isSocketActive() ? isPlayerRunner() : 0;
|
||||
int enoughChasers = Client::isSocketActive() ? isPlayerChaser() : 0;
|
||||
|
||||
// Create list of runner and chaser player indicies
|
||||
mInfo->mRunnerPlayers.clear();
|
||||
mInfo->mChaserPlayers.clear();
|
||||
mInfo->mOtherPlayers.clear();
|
||||
for (int i = 0; i < mPuppetHolder->getSize(); i++) {
|
||||
PuppetInfo* other = Client::getPuppetInfo(i);
|
||||
|
||||
if (!other) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!other->isConnected && !other->isFreezeInRound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (other->gameMode != mMode) {
|
||||
mInfo->mOtherPlayers.pushBack(other);
|
||||
other->isFreezeInRound = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isRound() && !other->isFreezeInRound) {
|
||||
mInfo->mOtherPlayers.pushBack(other);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (other->ftIsRunner()) {
|
||||
enoughRunners += other->isConnected;
|
||||
mInfo->mRunnerPlayers.pushBack(other);
|
||||
} else {
|
||||
enoughChasers += other->isConnected;
|
||||
mInfo->mChaserPlayers.pushBack(other);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify you are never frozen on chaser team
|
||||
if (isPlayerChaser() && isPlayerFrozen()) {
|
||||
trySetPlayerRunnerState(FreezeState::ALIVE);
|
||||
}
|
||||
|
||||
mInvulnTime += Time::deltaTime;
|
||||
|
||||
PuppetInfo* closestUnfrozenRunner = nullptr;
|
||||
|
||||
if (isRound() && 3 <= mInvulnTime) {
|
||||
bool isPlayerAlive = !PlayerFunction::isPlayerDeadStatus(player);
|
||||
bool isPlayer2D = ((PlayerActorHakoniwa*)player)->mDimKeeper->is2D;
|
||||
|
||||
sead::Vector3f playerPos = al::getTrans(player);
|
||||
float closestDistanceSq = 9999999.f;
|
||||
float freezeMinTime = isPlayerRunner() ? al::clamp(3.f + (mInfo->mFreezeCount * 0.5f), 3.f, 7.f) : 0.f; // cooldown of 3-7s, +0.5s per frozen runner
|
||||
|
||||
for (size_t i = 0; i < mPuppetHolder->getSize(); i++) {
|
||||
PuppetInfo* other = Client::getPuppetInfo(i);
|
||||
|
||||
if (!other->isConnected || !other->isInSameStage || other->gameMode != mMode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isPlayerRunner()) { // if we're a runner
|
||||
// Check if the chaser freezes us
|
||||
bool chaserFreezesUs = (
|
||||
isPlayerUnfrozen() // we're an unfrozen runner
|
||||
&& isPlayerAlive // that is alive
|
||||
&& other->ftIsChaser() // a chaser
|
||||
&& other->is2D == isPlayer2D // that has the same dimension (2D/3D) as us
|
||||
);
|
||||
|
||||
// Check if the runner unfreezes us
|
||||
bool runnerUnfreezesUs = (
|
||||
!chaserFreezesUs
|
||||
&& isPlayerFrozen() // we're a frozen runner
|
||||
&& freezeMinTime <= mInvulnTime // since some time (cooldown)
|
||||
&& isPlayerAlive // that is alive
|
||||
&& other->ftIsRunner() // another runner
|
||||
&& other->ftIsUnfrozen() // that isn't frozen
|
||||
&& other->is2D == isPlayer2D // and has the same dimension (2D/3D) as us);
|
||||
);
|
||||
|
||||
if (chaserFreezesUs || runnerUnfreezesUs) {
|
||||
float distanceSq = vecDistanceSq(playerPos, other->playerPos);
|
||||
|
||||
if (chaserFreezesUs && distanceSq < 62500.f) { // non-squared: 250.0
|
||||
trySetPlayerRunnerState(FreezeState::FREEZE); // freeze ourselves
|
||||
} else if (runnerUnfreezesUs && distanceSq < 40000.f) { // non-squared: 200.0
|
||||
trySetPlayerRunnerState(FreezeState::ALIVE); // unfreeze ourselves
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} else if (other->ftIsRunner() && other->ftIsUnfrozen()) { // if we're a chaser, and the other player is a unfrozen runner
|
||||
float distanceSq = vecDistanceSq(playerPos, other->playerPos);
|
||||
// If this other player is the new closest, set the closest info to the current player
|
||||
if (!closestUnfrozenRunner || distanceSq < closestDistanceSq) {
|
||||
closestDistanceSq = distanceSq;
|
||||
closestUnfrozenRunner = other;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the arrow target position to the closest unfrozen runner
|
||||
if (closestUnfrozenRunner) {
|
||||
mHintArrow->setTarget(&closestUnfrozenRunner->playerPos);
|
||||
} else {
|
||||
mHintArrow->setTarget(nullptr);
|
||||
}
|
||||
|
||||
// Update recovery event timer
|
||||
if (0 < mRecoveryEventFrames) {
|
||||
mRecoveryEventFrames--;
|
||||
if (mRecoveryEventFrames <= 0) {
|
||||
tryEndRecoveryEvent();
|
||||
}
|
||||
}
|
||||
|
||||
// Update endgame event (show wipeout for 6 seconds)
|
||||
if (isWipeout()) {
|
||||
mEndgameTimer += Time::deltaTime;
|
||||
if (mEndgameTimer > 6.f) {
|
||||
mInfo->mIsPlayerRunner = true;
|
||||
mInvulnTime = 0.f;
|
||||
sendFreezePacket(FreezeUpdateType::PLAYER);
|
||||
|
||||
mIsEndgameActive = false;
|
||||
tryStartRecoveryEvent(true);
|
||||
}
|
||||
}
|
||||
|
||||
// If our score changes, tell that the other players
|
||||
FreezeTagScore* score = &mInfo->mPlayerTagScore;
|
||||
if (score->mScore != score->mPrevScore) {
|
||||
score->mPrevScore = score->mScore;
|
||||
sendFreezePacket(FreezeUpdateType::PLAYER);
|
||||
};
|
||||
|
||||
// Main player's ice block state and post processing
|
||||
if (isPlayerFrozen()) {
|
||||
if (!al::isAlive(mMainPlayerIceBlock)) {
|
||||
mMainPlayerIceBlock->appear();
|
||||
trySetPostProcessingType(FreezePostProcessingType::PPFROZEN);
|
||||
}
|
||||
|
||||
// Lock block onto player
|
||||
al::setTrans(mMainPlayerIceBlock, al::getTrans(player));
|
||||
al::setQuat(mMainPlayerIceBlock, al::getQuat(player));
|
||||
} else {
|
||||
if (al::isAlive(mMainPlayerIceBlock) && mMainPlayerIceBlock->mIsLocked) {
|
||||
mMainPlayerIceBlock->end();
|
||||
trySetPostProcessingType(FreezePostProcessingType::PPDISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
// Not enough chasers/runners to continuing this round => cancel it
|
||||
if (isRound() && (!enoughChasers || !enoughRunners)) {
|
||||
mDisconnectTimer += Time::deltaTime;
|
||||
/**
|
||||
* 5 seconds grace period:
|
||||
* - to allow fixing it w/ automatic reconnects
|
||||
* - to not wrongly trigger by chasers changing to runners at round end (by slightly misalligned timers)
|
||||
*/
|
||||
if (5.0f < mDisconnectTimer) {
|
||||
mDisconnectTimer = 0.0f;
|
||||
|
||||
endRound(true);
|
||||
|
||||
mCancelOnlyLegacy = true;
|
||||
sendFreezePacket(FreezeUpdateType::ROUNDCANCEL);
|
||||
|
||||
if (!enoughChasers) {
|
||||
mInfo->mPlayerTagScore.eventNotEnoughChasersToContinue();
|
||||
} else {
|
||||
mInfo->mPlayerTagScore.eventNotEnoughRunnersToContinue();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mDisconnectTimer = 0.0f;
|
||||
}
|
||||
|
||||
// Up => Toggle Role (chaser/runner)
|
||||
if ( !isRound() // not during a round
|
||||
&& isPlayerUnfrozen() // not when frozen
|
||||
&& mRecoveryEventFrames == 0 // not in recovery
|
||||
&& !isWipeout() // not in endgame (wipeout)
|
||||
&& al::isPadTriggerUp(-1) // D-Pad Up
|
||||
&& !al::isPadHoldZR(-1) // not ZR
|
||||
&& !al::isPadHoldL(-1) // not L
|
||||
&& !al::isPadHoldR(-1) // not R
|
||||
) {
|
||||
mInfo->mIsPlayerRunner = !mInfo->mIsPlayerRunner;
|
||||
mInvulnTime = 0.f;
|
||||
|
||||
sendFreezePacket(FreezeUpdateType::PLAYER);
|
||||
}
|
||||
|
||||
// L + Down => Reset Score
|
||||
if ( isPlayerUnfrozen() // not when frozen
|
||||
&& mRecoveryEventFrames == 0 // not in recovery
|
||||
&& !isWipeout() // not in endgame (wipeout)
|
||||
&& al::isPadHoldL(-1) // hold L
|
||||
&& al::isPadTriggerDown(-1) // D-Pad Down
|
||||
) {
|
||||
mInfo->mPlayerTagScore.resetScore();
|
||||
}
|
||||
|
||||
// [Host] R + Up => Start Round
|
||||
if ( isHost() // when host
|
||||
&& !isRound() // not during a round
|
||||
&& al::isPadHoldR(-1) // hold R
|
||||
&& al::isPadTriggerUp(-1) // D-Pad Up
|
||||
) {
|
||||
if (enoughRunners < 1) {
|
||||
mInfo->mPlayerTagScore.eventNotEnoughRunnersToStart();
|
||||
} else if (enoughChasers < 1) {
|
||||
mInfo->mPlayerTagScore.eventNotEnoughChasersToStart();
|
||||
} else {
|
||||
startRound(mInfo->mRoundLength);
|
||||
sendFreezePacket(FreezeUpdateType::ROUNDSTART);
|
||||
}
|
||||
}
|
||||
|
||||
// [Host] R + Down => End Round
|
||||
if ( isHost() // when host
|
||||
&& isRound() // only during a round
|
||||
&& al::isPadHoldR(-1) // hold R
|
||||
&& al::isPadTriggerDown(-1) // D-Pad Down
|
||||
) {
|
||||
endRound(true);
|
||||
sendFreezePacket(FreezeUpdateType::ROUNDCANCEL);
|
||||
}
|
||||
|
||||
// Debug freeze buttons
|
||||
if (mInfo->mIsDebugMode) {
|
||||
if (isPlayerRunner()) {
|
||||
// [Debug] X + Right => Unfreeze
|
||||
if (al::isPadHoldX(-1) && al::isPadTriggerRight(-1)) {
|
||||
trySetPlayerRunnerState(FreezeState::ALIVE);
|
||||
}
|
||||
// [Debug] Y + Right => Freeze
|
||||
if (al::isPadHoldY(-1) && al::isPadTriggerRight(-1)) {
|
||||
trySetPlayerRunnerState(FreezeState::FREEZE);
|
||||
}
|
||||
}
|
||||
// [Debug] A + Right => Score += 1
|
||||
if (al::isPadHoldA(-1) && al::isPadTriggerRight(-1)) {
|
||||
mInfo->mPlayerTagScore.eventScoreDebug();
|
||||
}
|
||||
// [Debug] A + Left => Set time to 01:05
|
||||
if (al::isPadHoldA(-1) && al::isPadTriggerLeft(-1)) {
|
||||
mModeTimer->setTime(0.f, 5, 1, 0);
|
||||
}
|
||||
// [Debug] B + Right => Wipeout
|
||||
if (al::isPadHoldB(-1) && al::isPadTriggerRight(-1)) {
|
||||
tryStartEndgameEvent();
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the standard HUD is hidden (coins)
|
||||
if (!mCurScene->mSceneLayout->isEnd()) {
|
||||
mCurScene->mSceneLayout->end();
|
||||
}
|
||||
|
||||
// Spectator camera
|
||||
if (!mTicket->mIsActive && isPlayerFrozen()) { // enable the spectator camera when frozen
|
||||
al::startCamera(mCurScene, mTicket, -1);
|
||||
al::requestStopCameraVerticalAbsorb(mCurScene);
|
||||
} else if (mTicket->mIsActive && isPlayerUnfrozen()) { // disable the spectator camera when unfrozen
|
||||
al::endCamera(mCurScene, mTicket, 0, false);
|
||||
al::requestStopCameraVerticalAbsorb(mCurScene);
|
||||
} else if (mTicket->mIsActive && isPlayerFrozen()) { // update spectator camera
|
||||
updateSpectateCam(player);
|
||||
}
|
||||
}
|
||||
|
||||
bool FreezeTagMode::showNameTag(PuppetInfo* other) {
|
||||
// show name tags for non-players and our team mates
|
||||
return other->gameMode != mMode
|
||||
|| (isPlayerRunner() && other->ftIsRunner())
|
||||
|| (isPlayerChaser() && other->ftIsChaser())
|
||||
;
|
||||
}
|
||||
|
||||
bool FreezeTagMode::showNameTagEverywhere(PuppetActor* actor) {
|
||||
// show the name tags of frozen players everywhere (regardless of distance)
|
||||
PuppetInfo* other = actor->getInfo();
|
||||
return other->gameMode == mMode
|
||||
&& other->ftIsRunner()
|
||||
&& other->ftIsFrozen()
|
||||
;
|
||||
}
|
||||
|
||||
void FreezeTagMode::debugMenuControls(sead::TextWriter* gTextWriter) {
|
||||
gTextWriter->printf("- L + ← | Enable/disable Freeze Tag [FT]\n");
|
||||
gTextWriter->printf("- [FT] ↑ | Switch between runners and chasers\n");
|
||||
gTextWriter->printf("- [FT] L + ↓ | Reset score\n");
|
||||
|
||||
if (isHost()) {
|
||||
gTextWriter->printf("- [FT][Host] R + ↑ | Start round\n");
|
||||
gTextWriter->printf("- [FT][Host] R + ↓ | End round\n");
|
||||
}
|
||||
|
||||
if (mInfo->mIsDebugMode) {
|
||||
gTextWriter->printf("- [FT][Debug] A + → | Increment score\n");
|
||||
gTextWriter->printf("- [FT][Debug] A + ← | Set time to 01:05\n");
|
||||
gTextWriter->printf("- [FT][Debug] B + → | Wipeout\n");
|
||||
if (isPlayerRunner()) {
|
||||
gTextWriter->printf("- [FT][Debug][Runner] X + → | Unfreeze\n");
|
||||
gTextWriter->printf("- [FT][Debug][Runner] Y + → | Freeze\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (mTicket && mTicket->mIsActive && isPlayerFrozen()) {
|
||||
gTextWriter->printf("- [FT][Frozen] ← | Spectate previous player\n");
|
||||
gTextWriter->printf("- [FT][Frozen] → | Spectate next player\n");
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagMode::debugMenuPlayer(sead::TextWriter* gTextWriter, PuppetInfo* other) {
|
||||
if (other && other->gameMode != mMode) {
|
||||
gTextWriter->printf("Freeze-Tag: N/A\n");
|
||||
return;
|
||||
}
|
||||
|
||||
bool isRunner = other ? other->ftIsRunner() : isPlayerRunner();
|
||||
bool isFrozen = other ? other->ftIsFrozen() : isPlayerFrozen();
|
||||
|
||||
gTextWriter->printf(
|
||||
"Freeze-Tag: %s%s\n",
|
||||
isRunner ? "Runner" : "Chaser",
|
||||
isFrozen ? " (Frozen)" : ""
|
||||
);
|
||||
}
|
||||
|
||||
void FreezeTagMode::sendFreezePacket(FreezeUpdateType updateType) {
|
||||
mNextUpdateType = updateType;
|
||||
Client::sendGameModeInfPacket();
|
||||
mNextUpdateType = FreezeUpdateType::PLAYER;
|
||||
}
|
||||
|
||||
void FreezeTagMode::onHakoniwaSequenceFirstStep(HakoniwaSequence* sequence) {
|
||||
mWipeHolder = sequence->mWipeHolder;
|
||||
}
|
86
source/server/freeze-tag/FreezeTagModeCam.cpp
Normal file
86
source/server/freeze-tag/FreezeTagModeCam.cpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#include "server/freeze-tag/FreezeTagMode.hpp"
|
||||
|
||||
#include "cameras/CameraPoserActorSpectate.h"
|
||||
|
||||
void FreezeTagMode::createCustomCameraTicket(al::CameraDirector* director) {
|
||||
mTicket = director->createCameraFromFactory("CameraPoserActorSpectate", nullptr, 0, 5, sead::Matrix34f::ident);
|
||||
}
|
||||
|
||||
void FreezeTagMode::updateSpectateCam(PlayerActorBase* playerBase) {
|
||||
// If the specate camera ticket is active, get the camera poser
|
||||
al::CameraPoser* curPoser = nullptr;
|
||||
al::CameraDirector* director = mCurScene->getCameraDirector();
|
||||
|
||||
if (director) {
|
||||
al::CameraPoseUpdater* updater = director->getPoseUpdater(0);
|
||||
if (updater && updater->mTicket) {
|
||||
curPoser = updater->mTicket->mPoser;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify 100% that this poser is the actor spectator
|
||||
if (curPoser && al::isEqualString(curPoser->getName(), "CameraPoserActorSpectate")) {
|
||||
cc::CameraPoserActorSpectate* spectatePoser = (cc::CameraPoserActorSpectate*)curPoser;
|
||||
spectatePoser->setPlayer(playerBase);
|
||||
|
||||
// Increase or decrease spectate index, followed by clamping it
|
||||
int indexDirection = 0;
|
||||
if (!isWipeout() && al::isPadTriggerRight(-1)) { indexDirection = 1; } // Move index right
|
||||
if (!isWipeout() && al::isPadTriggerLeft(-1)) { indexDirection = -1; } // Move index left
|
||||
|
||||
// Force index towards ourself (-1) during endgame (wipeout) if we aren't already spectating ourselves
|
||||
if (isWipeout() && mSpectateIndex != -1) { indexDirection = -1; } // Move index left
|
||||
|
||||
s32 size = mInfo->mRunnerPlayers.size();
|
||||
|
||||
// Force index to decrease if our current index got out of bounds
|
||||
if (size <= mSpectateIndex) {
|
||||
mSpectateIndex = size;
|
||||
indexDirection = -1; // Move index left
|
||||
}
|
||||
|
||||
PuppetInfo* other = (
|
||||
0 <= mSpectateIndex && mSpectateIndex < size
|
||||
? mInfo->mRunnerPlayers.at(mSpectateIndex)
|
||||
: nullptr
|
||||
);
|
||||
|
||||
// Force index to decrease if our current target is not ourself and changes to another stage
|
||||
if (indexDirection == 0 && other && (!other->isInSameStage || !other->isConnected)) {
|
||||
indexDirection = -1; // Move index left
|
||||
}
|
||||
|
||||
if (indexDirection != 0) {
|
||||
// Loop over indicies until we find a runner in the same stage as the player or the player itself (-1 => spectate our own player)
|
||||
do {
|
||||
mSpectateIndex += indexDirection;
|
||||
|
||||
// Circular loop the index around
|
||||
if (mSpectateIndex < -1) {
|
||||
mSpectateIndex = size - 1;
|
||||
} else if (size <= mSpectateIndex) {
|
||||
mSpectateIndex = -1;
|
||||
}
|
||||
|
||||
other = (0 <= mSpectateIndex ? mInfo->mRunnerPlayers.at(mSpectateIndex) : nullptr);
|
||||
} while (other && (!other->isInSameStage || !other->isConnected));
|
||||
}
|
||||
|
||||
// If no index and no size change is happening, end here (a size change could still be a player change at the same index)
|
||||
if (mPrevSpectateIndex == mSpectateIndex && mPrevSpectateCount == size) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply index to target actor and HUD
|
||||
if (other) {
|
||||
spectatePoser->setTargetActor(&other->playerPos);
|
||||
mModeLayout->setSpectateString(other->puppetName);
|
||||
} else {
|
||||
spectatePoser->setTargetActor(al::getTransPtr(playerBase));
|
||||
mModeLayout->setSpectateString("Spectate");
|
||||
}
|
||||
|
||||
mPrevSpectateIndex = mSpectateIndex;
|
||||
mPrevSpectateCount = size;
|
||||
}
|
||||
}
|
341
source/server/freeze-tag/FreezeTagModeTrigger.cpp
Normal file
341
source/server/freeze-tag/FreezeTagModeTrigger.cpp
Normal file
|
@ -0,0 +1,341 @@
|
|||
#include "server/freeze-tag/FreezeTagMode.hpp"
|
||||
|
||||
#include "al/util.hpp"
|
||||
#include "al/util/RandomUtil.h"
|
||||
#include "server/gamemode/GameModeManager.hpp"
|
||||
#include "rs/util.hpp"
|
||||
|
||||
void FreezeTagMode::startRound(int roundMinutes) {
|
||||
mInfo->mIsRound = true;
|
||||
mInfo->mFreezeCount = 0;
|
||||
|
||||
mInvulnTime = 0.f;
|
||||
|
||||
mModeTimer->enableTimer();
|
||||
mModeTimer->disableControl();
|
||||
mModeTimer->setTimerDirection(false);
|
||||
|
||||
// Start timer at roundMinutes - 1s (to not instantly set off a score event that happens every full minute)
|
||||
mModeTimer->setTime(0.f, 59, roundMinutes - 1, 0);
|
||||
mInfo->mRoundTimer = mModeTimer->getTime();
|
||||
|
||||
// remember who is part of this round
|
||||
for (int i = 0; i < mInfo->mRunnerPlayers.size(); i++) {
|
||||
mInfo->mRunnerPlayers.at(i)->isFreezeInRound = true;
|
||||
}
|
||||
for (int i = 0; i < mInfo->mChaserPlayers.size(); i++) {
|
||||
mInfo->mChaserPlayers.at(i)->isFreezeInRound = true;
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagMode::endRound(bool isAbort) {
|
||||
mInfo->mIsRound = false;
|
||||
mInfo->mFreezeCount = 0;
|
||||
|
||||
mModeTimer->disableTimer();
|
||||
|
||||
if (!isWipeout()) {
|
||||
// transform: chaser => runner
|
||||
if (isPlayerChaser()) {
|
||||
mInfo->mIsPlayerRunner = true;
|
||||
sendFreezePacket(FreezeUpdateType::PLAYER);
|
||||
return;
|
||||
}
|
||||
|
||||
// get points for winning
|
||||
if (!isAbort) {
|
||||
mInfo->mPlayerTagScore.eventScoreRunnerWin();
|
||||
}
|
||||
|
||||
// unfreeze
|
||||
if (isPlayerFrozen()) {
|
||||
trySetPlayerRunnerState(FreezeState::ALIVE);
|
||||
}
|
||||
}
|
||||
|
||||
// forget who was part of this round
|
||||
for (int i = 0; i < mPuppetHolder->getSize(); i++) {
|
||||
Client::getPuppetInfo(i)->isFreezeInRound = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* SET THE RUNNER PLAYER'S FROZEN/ALIVE STATE
|
||||
*/
|
||||
bool FreezeTagMode::trySetPlayerRunnerState(FreezeState newState) {
|
||||
if (mInfo->mIsPlayerFreeze == newState || isPlayerChaser()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerActorHakoniwa* player = getPlayerActorHakoniwa();
|
||||
if (!player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mInvulnTime = 0.f;
|
||||
|
||||
if (newState == FreezeState::ALIVE) {
|
||||
mInfo->mIsPlayerFreeze = FreezeState::ALIVE;
|
||||
|
||||
player->endDemoPuppetable();
|
||||
|
||||
sendFreezePacket(FreezeUpdateType::PLAYER);
|
||||
} else if (!isRound()) {
|
||||
return false;
|
||||
} else {
|
||||
mInfo->mIsPlayerFreeze = FreezeState::FREEZE;
|
||||
|
||||
if (player->getPlayerHackKeeper()->currentHackActor) { // we're in a capture
|
||||
player->getPlayerHackKeeper()->cancelHackArea(); // leave the capture
|
||||
}
|
||||
|
||||
player->startDemoPuppetable();
|
||||
player->mPlayerAnimator->endSubAnim();
|
||||
player->mPlayerAnimator->startAnim("DeadIce");
|
||||
player->mHackCap->forcePutOn();
|
||||
|
||||
mSpectateIndex = -1;
|
||||
mInfo->mFreezeCount++;
|
||||
|
||||
sendFreezePacket(FreezeUpdateType::PLAYER);
|
||||
|
||||
if (areAllOtherRunnersFrozen(nullptr)) {
|
||||
// if there is only one runner, then end the round for legacy clients (new clients do this themselves)
|
||||
if (1 == runners()) {
|
||||
mCancelOnlyLegacy = true;
|
||||
sendFreezePacket(FreezeUpdateType::ROUNDCANCEL);
|
||||
}
|
||||
|
||||
tryStartEndgameEvent();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* START AN ENDGAME EVENT (wipeout)
|
||||
*/
|
||||
void FreezeTagMode::tryStartEndgameEvent() {
|
||||
mIsEndgameActive = true;
|
||||
mEndgameTimer = 0.f;
|
||||
mModeLayout->showEndgameScreen();
|
||||
|
||||
PlayerActorHakoniwa* player = getPlayerActorHakoniwa();
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (player->getPlayerHackKeeper()->currentHackActor) { // we're in a capture
|
||||
player->getPlayerHackKeeper()->cancelHackArea(); // leave the capture
|
||||
}
|
||||
|
||||
player->startDemoPuppetable();
|
||||
rs::faceToCamera(player);
|
||||
player->mPlayerAnimator->endSubAnim();
|
||||
|
||||
if (isPlayerRunner()) {
|
||||
player->mPlayerAnimator->startAnim("RaceResultLose");
|
||||
trySetPostProcessingType(FreezePostProcessingType::PPENDGAMELOSE);
|
||||
} else {
|
||||
player->mPlayerAnimator->startAnim("RaceResultWin");
|
||||
trySetPostProcessingType(FreezePostProcessingType::PPENDGAMEWIN);
|
||||
mInfo->mPlayerTagScore.eventScoreWipeout();
|
||||
}
|
||||
|
||||
endRound(false);
|
||||
}
|
||||
|
||||
/*
|
||||
* HANDLE PLAYER RECOVERY
|
||||
* Player recovery is started by entering a death area or an endgame (wipeout)
|
||||
*/
|
||||
bool FreezeTagMode::tryStartRecoveryEvent(bool isEndgame) {
|
||||
if (mRecoveryEventFrames > 0 || !mWipeHolder) {
|
||||
return false; // Something isn't applicable here, return fail
|
||||
}
|
||||
|
||||
PlayerActorHakoniwa* player = getPlayerActorHakoniwa();
|
||||
if (!player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mRecoveryEventFrames = (mRecoveryEventLength / 2) * (isEndgame + 1);
|
||||
mWipeHolder->startClose("FadeBlack", (mRecoveryEventLength / 4) * (isEndgame + 1));
|
||||
|
||||
if (!isEndgame) {
|
||||
mRecoverySafetyPoint = player->mPlayerRecoverySafetyPoint->mSafetyPointPos;
|
||||
if (isPlayerRunner() && isRound()) {
|
||||
sendFreezePacket(FreezeUpdateType::FALLOFF);
|
||||
}
|
||||
} else {
|
||||
mRecoverySafetyPoint = sead::Vector3f::zero;
|
||||
}
|
||||
|
||||
Logger::log("Recovery event %.00fx %.00fy %.00fz\n", mRecoverySafetyPoint.x, mRecoverySafetyPoint.y, mRecoverySafetyPoint.z);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FreezeTagMode::tryEndRecoveryEvent() {
|
||||
if (!mWipeHolder) {
|
||||
return false; //Recovery event is already started, return fail
|
||||
}
|
||||
|
||||
mWipeHolder->startOpen(mRecoveryEventLength / 2);
|
||||
|
||||
PlayerActorHakoniwa* player = getPlayerActorHakoniwa();
|
||||
if (!player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the player to frozen if they are a runner AND they had a valid recovery point
|
||||
if (isRound() && isPlayerRunner() && mRecoverySafetyPoint != sead::Vector3f::zero) {
|
||||
trySetPlayerRunnerState(FreezeState::FREEZE);
|
||||
warpToRecoveryPoint(player);
|
||||
} else {
|
||||
trySetPlayerRunnerState(FreezeState::ALIVE);
|
||||
trySetPostProcessingType(FreezePostProcessingType::PPDISABLED);
|
||||
}
|
||||
|
||||
// If player is a chaser with a valid recovery point, teleport (and disable collisions)
|
||||
if (isPlayerChaser() || !isRound()) {
|
||||
player->startDemoPuppetable();
|
||||
if (mRecoverySafetyPoint != sead::Vector3f::zero) {
|
||||
warpToRecoveryPoint(player);
|
||||
}
|
||||
|
||||
trySetPostProcessingType(FreezePostProcessingType::PPDISABLED);
|
||||
}
|
||||
|
||||
// If player is being made alive, force end demo puppet state
|
||||
if (isPlayerUnfrozen()) {
|
||||
player->endDemoPuppetable();
|
||||
}
|
||||
|
||||
if (!isWipeout()) {
|
||||
mModeLayout->hideEndgameScreen();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* UPDATE PLAYER SCORES
|
||||
* FUNCTION CALLED FROM FreezeTagMode.cpp ON RECEIVING FREEZE TAG PACKETS
|
||||
*/
|
||||
void FreezeTagMode::tryScoreEvent(FreezeTagPacket* packet, PuppetInfo* other) {
|
||||
if (!mCurScene || !mCurScene->mIsAlive || !GameModeManager::instance()->isModeAndActive(GameMode::FREEZETAG)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only if the other player is a runner
|
||||
if (!other || other->ftIsChaser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only if the frozen state of the other player changes
|
||||
if (other->ftIsFrozen() == packet->isFreeze) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we unfreeze a fellow runner
|
||||
bool scoreUnfreeze = (
|
||||
isPlayerRunner() // we are a runner
|
||||
&& isPlayerUnfrozen() // that is unfrozen and are touching another runner
|
||||
&& !other->ftHasFallenOff() // that was not frozen by falling off the map
|
||||
&& !packet->isFreeze // which is unfreezing right now
|
||||
&& isRound()
|
||||
&& !isWipeout()
|
||||
);
|
||||
|
||||
// Check if we freeze a runner as a chaser
|
||||
bool scoreFreeze = (
|
||||
!scoreUnfreeze
|
||||
&& isPlayerChaser() // we are a chaser touching a runner
|
||||
&& packet->isFreeze // which is freezing up right now
|
||||
&& (isRound() || isWipeout())
|
||||
);
|
||||
|
||||
if (other->isInSameStage && (scoreUnfreeze || scoreFreeze)) {
|
||||
PlayerActorBase* playerBase = rs::getPlayerActor(mCurScene);
|
||||
|
||||
// Calculate the distance to the other player
|
||||
float distanceSq = playerBase ? vecDistanceSq(al::getTrans(playerBase), other->playerPos) : 999999.f;
|
||||
|
||||
// Only apply the score event if the runner is less than 600 units away
|
||||
if (distanceSq < 360000.f) { // non-squared: 600.0
|
||||
if (scoreUnfreeze) {
|
||||
mInfo->mPlayerTagScore.eventScoreUnfreeze();
|
||||
} else {
|
||||
mInfo->mPlayerTagScore.eventScoreFreeze();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if every runner is frozen, starts endgame sequence if so
|
||||
if (packet->isFreeze && areAllOtherRunnersFrozen(other)) {
|
||||
// if there is only one runner, then end the round for legacy clients (new clients do this themselves)
|
||||
if (1 == runners()) {
|
||||
mCancelOnlyLegacy = true;
|
||||
sendFreezePacket(FreezeUpdateType::ROUNDCANCEL);
|
||||
}
|
||||
|
||||
tryStartEndgameEvent();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* SET THE POST PROCESSING STYLE IN THE GAMEMODE
|
||||
*/
|
||||
bool FreezeTagMode::trySetPostProcessingType(FreezePostProcessingType type) {
|
||||
if (!mCurScene) { // doesn't have a scene
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 ppIdx = type;
|
||||
u32 curIdx = al::getPostProcessingFilterPresetId(mCurScene);
|
||||
if (ppIdx == curIdx) {
|
||||
return false; // Already set to target post processing type => return fail
|
||||
}
|
||||
|
||||
// loop trough the filters till we reach the desired one
|
||||
while (curIdx != ppIdx) {
|
||||
al::incrementPostProcessingFilterPreset(mCurScene);
|
||||
curIdx = (curIdx + 1) % 18;
|
||||
}
|
||||
|
||||
if (type == FreezePostProcessingType::PPDISABLED) {
|
||||
al::invalidatePostProcessingFilter(mCurScene);
|
||||
return true; // Disabled current post processing mode
|
||||
}
|
||||
|
||||
al::validatePostProcessingFilter(mCurScene);
|
||||
|
||||
Logger::log("Set post processing to %i\n", al::getPostProcessingFilterPresetId(mCurScene));
|
||||
|
||||
return true; // Set post processing mode to on at desired index
|
||||
}
|
||||
|
||||
void FreezeTagMode::warpToRecoveryPoint(al::LiveActor* actor) {
|
||||
// warp to a random chaser if we are a runner during a round
|
||||
if (isRound() && isPlayerRunner() && 0 < chasers()) {
|
||||
int size = chasers();
|
||||
int rnd = al::getRandom(size);
|
||||
int i = rnd;
|
||||
|
||||
do {
|
||||
// to be suitable the chaser needs to be connected and in the same stage
|
||||
PuppetInfo* other = mInfo->mChaserPlayers.at(i);
|
||||
if (other && other->isConnected && other->ftIsChaser() && other->isInSameStage) {
|
||||
al::setTrans(actor, other->playerPos);
|
||||
return;
|
||||
}
|
||||
// check the next chaser, if the randomly chosen one is unsuitable
|
||||
i = (i + 1) % size; // loop around
|
||||
} while (i != rnd); // unless all chasers are unsuitable
|
||||
}
|
||||
|
||||
// warp to the last safe recovery point
|
||||
al::setTrans(actor, mRecoverySafetyPoint);
|
||||
}
|
48
source/server/freeze-tag/FreezeTagModeUtil.cpp
Normal file
48
source/server/freeze-tag/FreezeTagModeUtil.cpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#include "server/freeze-tag/FreezeTagMode.hpp"
|
||||
|
||||
#include "rs/util.hpp"
|
||||
|
||||
bool FreezeTagMode::areAllOtherRunnersFrozen(PuppetInfo* player) {
|
||||
if (runners() < 1) {
|
||||
return false; // Verify there is at least one runners (including yourself), otherwise disable this functionality
|
||||
/**
|
||||
* old legacy clients used a minimum size of 2 runners here.
|
||||
*
|
||||
* this lead to a bug that if there was only one runner a round could never be won by chasers.
|
||||
* changing this breaks compatibility with legacy clients about when a round ends or not.
|
||||
*
|
||||
* therefore legacy clients will reveice an extra ROUNDCANCEL packet that only affects them.
|
||||
* ending a round in this way will prevent legacy clients from getting score points for a WIPEOUT
|
||||
* in this situation (only one runner), though that's better than it was before (round continues even
|
||||
* though the only runner is frozen already).
|
||||
*/
|
||||
}
|
||||
|
||||
if (isPlayerRunner() && isPlayerUnfrozen()) {
|
||||
return false; // If you are a runner but aren't frozen then skip
|
||||
}
|
||||
|
||||
for (int i = 0; i < mInfo->mRunnerPlayers.size(); i++) {
|
||||
PuppetInfo* other = mInfo->mRunnerPlayers.at(i);
|
||||
if (other == player) {
|
||||
continue; // If the puppet getting updated is the one currently being checked, skip this one
|
||||
}
|
||||
|
||||
if (other->ftIsUnfrozen()) {
|
||||
return false; // Found a non-frozen player on the runner team, cancel
|
||||
}
|
||||
}
|
||||
|
||||
return true; // All runners are frozen!
|
||||
}
|
||||
|
||||
PlayerActorHakoniwa* FreezeTagMode::getPlayerActorHakoniwa() {
|
||||
PlayerActorBase* playerBase = rs::getPlayerActor(mCurScene);
|
||||
bool isYukimaru = !playerBase->getPlayerInfo();
|
||||
|
||||
if (isYukimaru) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return (PlayerActorHakoniwa*)playerBase;
|
||||
}
|
105
source/server/freeze-tag/FreezeTagOtherSlot.cpp
Normal file
105
source/server/freeze-tag/FreezeTagOtherSlot.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
#include "server/freeze-tag/FreezeTagOtherSlot.h"
|
||||
|
||||
#include "al/util.hpp"
|
||||
#include "puppets/PuppetInfo.h"
|
||||
#include "server/gamemode/GameModeManager.hpp"
|
||||
#include "server/freeze-tag/FreezeTagInfo.h"
|
||||
|
||||
FreezeTagOtherSlot::FreezeTagOtherSlot(const char* name, const al::LayoutInitInfo& initInfo) : al::LayoutActor(name) {
|
||||
al::initLayoutActor(this, initInfo, "FreezeTagOtherSlot", 0);
|
||||
mInfo = GameModeManager::instance()->getInfo<FreezeTagInfo>();
|
||||
|
||||
initNerve(&nrvFreezeTagOtherSlotEnd, 0);
|
||||
kill();
|
||||
}
|
||||
|
||||
void FreezeTagOtherSlot::init(int index) {
|
||||
// Place slot based on index and hide
|
||||
al::setPaneLocalTrans(this, "OtherSlot", { 0.f, 270.f - (index * 55.f), 0.f });
|
||||
al::hidePane(this, "OtherSlot");
|
||||
|
||||
// Set temporary name string
|
||||
al::setPaneString(this, "TxtOtherName", u"MaxLengthNameAaa", 0);
|
||||
|
||||
mOtherIndex = index;
|
||||
return;
|
||||
}
|
||||
|
||||
void FreezeTagOtherSlot::appear() {
|
||||
al::startAction(this, "Appear", 0);
|
||||
al::setNerve(this, &nrvFreezeTagOtherSlotAppear);
|
||||
al::LayoutActor::appear();
|
||||
}
|
||||
|
||||
bool FreezeTagOtherSlot::tryEnd() {
|
||||
if (!al::isNerve(this, &nrvFreezeTagOtherSlotEnd)) {
|
||||
al::setNerve(this, &nrvFreezeTagOtherSlotEnd);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FreezeTagOtherSlot::tryStart() {
|
||||
if (!al::isNerve(this, &nrvFreezeTagOtherSlotWait) && !al::isNerve(this, &nrvFreezeTagOtherSlotAppear)) {
|
||||
appear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FreezeTagOtherSlot::exeAppear() {
|
||||
if (al::isActionEnd(this, 0)) {
|
||||
al::setNerve(this, &nrvFreezeTagOtherSlotWait);
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagOtherSlot::exeWait() {
|
||||
if (al::isFirstStep(this)) {
|
||||
al::startAction(this, "Wait", 0);
|
||||
}
|
||||
|
||||
// Show/hide icon if player doesn't exist in this slot
|
||||
if (mInfo->others() <= mOtherIndex || mInfo->isRound()) {
|
||||
if (mIsVisible) {
|
||||
hideSlot();
|
||||
}
|
||||
} else if (!mIsVisible) {
|
||||
showSlot();
|
||||
}
|
||||
|
||||
if (!mIsVisible) { // If icon isn't visible, end wait processing here
|
||||
return;
|
||||
}
|
||||
|
||||
// Update name info in this slot
|
||||
PuppetInfo* other = mInfo->mOtherPlayers.at(mOtherIndex);
|
||||
setSlotName(other->puppetName);
|
||||
setSlotScore(other->ftGetScore());
|
||||
}
|
||||
|
||||
void FreezeTagOtherSlot::exeEnd() {
|
||||
if (al::isFirstStep(this)) {
|
||||
al::startAction(this, "End", 0);
|
||||
}
|
||||
|
||||
if (al::isActionEnd(this, 0)) {
|
||||
kill();
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagOtherSlot::showSlot() {
|
||||
mIsVisible = true;
|
||||
al::showPane(this, "OtherSlot");
|
||||
}
|
||||
|
||||
void FreezeTagOtherSlot::hideSlot() {
|
||||
mIsVisible = false;
|
||||
al::hidePane(this, "OtherSlot");
|
||||
}
|
||||
|
||||
namespace {
|
||||
NERVE_IMPL(FreezeTagOtherSlot, Appear)
|
||||
NERVE_IMPL(FreezeTagOtherSlot, Wait)
|
||||
NERVE_IMPL(FreezeTagOtherSlot, End)
|
||||
}
|
138
source/server/freeze-tag/FreezeTagRunnerSlot.cpp
Normal file
138
source/server/freeze-tag/FreezeTagRunnerSlot.cpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
#include "server/freeze-tag/FreezeTagRunnerSlot.h"
|
||||
|
||||
#include "al/util.hpp"
|
||||
#include "puppets/PuppetInfo.h"
|
||||
#include "server/Client.hpp"
|
||||
#include "server/gamemode/GameModeManager.hpp"
|
||||
#include "server/freeze-tag/FreezeTagInfo.h"
|
||||
|
||||
FreezeTagRunnerSlot::FreezeTagRunnerSlot(const char* name, const al::LayoutInitInfo& initInfo) : al::LayoutActor(name) {
|
||||
al::initLayoutActor(this, initInfo, "FreezeTagRunnerSlot", 0);
|
||||
mInfo = GameModeManager::instance()->getInfo<FreezeTagInfo>();
|
||||
|
||||
initNerve(&nrvFreezeTagRunnerSlotEnd, 0);
|
||||
kill();
|
||||
}
|
||||
|
||||
void FreezeTagRunnerSlot::init(int index) {
|
||||
// Place slot based on index and hide
|
||||
al::setPaneLocalTrans(this, "RunnerSlot", { -580.f, 270.f - (index * 55.f), 0.f });
|
||||
al::hidePane(this, "RunnerSlot");
|
||||
|
||||
// Set temporary name string
|
||||
al::setPaneString(this, "TxtRunnerName", u"MaxLengthNameAaa", 0);
|
||||
|
||||
mRunnerIndex = index;
|
||||
return;
|
||||
}
|
||||
|
||||
void FreezeTagRunnerSlot::appear() {
|
||||
al::startAction(this, "Appear", 0);
|
||||
al::setNerve(this, &nrvFreezeTagRunnerSlotAppear);
|
||||
al::LayoutActor::appear();
|
||||
}
|
||||
|
||||
bool FreezeTagRunnerSlot::tryEnd() {
|
||||
if (!al::isNerve(this, &nrvFreezeTagRunnerSlotEnd)) {
|
||||
al::setNerve(this, &nrvFreezeTagRunnerSlotEnd);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FreezeTagRunnerSlot::tryStart() {
|
||||
if (!al::isNerve(this, &nrvFreezeTagRunnerSlotWait) && !al::isNerve(this, &nrvFreezeTagRunnerSlotAppear)) {
|
||||
appear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FreezeTagRunnerSlot::exeAppear() {
|
||||
if (al::isActionEnd(this, 0)) {
|
||||
al::setNerve(this, &nrvFreezeTagRunnerSlotWait);
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagRunnerSlot::exeWait() {
|
||||
if (al::isFirstStep(this)) {
|
||||
al::startAction(this, "Wait", 0);
|
||||
}
|
||||
|
||||
mIsPlayer = mRunnerIndex == 0 && mInfo->isPlayerRunner();
|
||||
|
||||
// Show/hide icon if player doesn't exist in this slot
|
||||
if (mInfo->runners() <= mRunnerIndex) {
|
||||
if (mIsVisible) {
|
||||
hideSlot();
|
||||
}
|
||||
} else if (!mIsVisible) {
|
||||
showSlot();
|
||||
}
|
||||
|
||||
if (!mIsVisible) { // If icon isn't visible, end wait processing here
|
||||
return;
|
||||
}
|
||||
|
||||
mFreezeIconSpin += 1.2f;
|
||||
if (mFreezeIconSpin > 360.f + (mRunnerIndex * 7.5f)) {
|
||||
mFreezeIconSpin -= 360.f;
|
||||
}
|
||||
|
||||
setFreezeAngle();
|
||||
|
||||
// Update name info in this slot
|
||||
if (mIsPlayer) {
|
||||
setSlotName(Client::instance()->getClientName());
|
||||
setSlotScore(mInfo->getScore());
|
||||
} else {
|
||||
PuppetInfo* other = mInfo->mRunnerPlayers.at(mRunnerIndex - mInfo->isPlayerRunner());
|
||||
setSlotName(other->puppetName);
|
||||
setSlotScore(other->ftGetScore());
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagRunnerSlot::exeEnd() {
|
||||
if (al::isFirstStep(this)) {
|
||||
al::startAction(this, "End", 0);
|
||||
}
|
||||
|
||||
if (al::isActionEnd(this, 0)) {
|
||||
kill();
|
||||
}
|
||||
}
|
||||
|
||||
void FreezeTagRunnerSlot::showSlot() {
|
||||
mIsVisible = true;
|
||||
al::showPane(this, "RunnerSlot");
|
||||
}
|
||||
|
||||
void FreezeTagRunnerSlot::hideSlot() {
|
||||
mIsVisible = false;
|
||||
al::hidePane(this, "RunnerSlot");
|
||||
}
|
||||
|
||||
void FreezeTagRunnerSlot::setFreezeAngle() {
|
||||
al::setPaneLocalRotate(this, "PicRunnerFreeze", { 0.f, 0.f, mFreezeIconSpin + (mRunnerIndex * 7.5f) });
|
||||
|
||||
if (mIsPlayer) {
|
||||
float targetSize = mInfo->isPlayerFrozen() ? 1.f : 0.f;
|
||||
mInfo->mFreezeIconSize = al::lerpValue(mInfo->mFreezeIconSize, targetSize, 0.05f);
|
||||
al::setPaneLocalScale(this, "PicRunnerFreeze", { mInfo->mFreezeIconSize, mInfo->mFreezeIconSize });
|
||||
} else if (mInfo->runners() <= mRunnerIndex) {
|
||||
return;
|
||||
} else {
|
||||
PuppetInfo* other = mInfo->mRunnerPlayers.at(mRunnerIndex - mInfo->isPlayerRunner());
|
||||
|
||||
float targetSize = other->ftIsFrozen() ? 1.f : 0.f;
|
||||
other->freezeIconSize = al::lerpValue(other->freezeIconSize, targetSize, 0.05f);
|
||||
al::setPaneLocalScale(this, "PicRunnerFreeze", { other->freezeIconSize, other->freezeIconSize });
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
NERVE_IMPL(FreezeTagRunnerSlot, Appear)
|
||||
NERVE_IMPL(FreezeTagRunnerSlot, Wait)
|
||||
NERVE_IMPL(FreezeTagRunnerSlot, End)
|
||||
}
|
Loading…
Reference in a new issue