#ifndef Projectile_h #define Projectile_h #include "Platform.h" #include "SimpleTypes.h" #include "CDGString.h" // these must never change, unless in tandem with the editor. // and even then it'd break previous work. so don't do it. #define DANMAKU_TYPE_BASIC 0 #define DANMAKU_TYPE_HOMING 1 #define DANMAKU_TYPE_COMPLEX 2 #define DANMAKU_TYPE_SCRIPT 3 // probably removing shape entirely, or will keep the shell of a class around for another unforeseen type #define DANMAKU_OWNER_INVALID -1 #define DANMAKU_OWNER_PLAYER 0 #define DANMAKU_OWNER_FIRST_CPU 1 // second, third, fourth are merely +1's... #define DANMAKU_TURN_NORMAL 0 #define DANMAKU_TURN_CONTINUOUS 1 #define DANMAKU_TURN_HARD_NEG_POS_ZERO 2 #define DANMAKU_TURN_HARD_NEG_POS 3 #define DANMAKU_TURN_COUNT 4 #define NUM_PROJECTILE_LAYERS 10 // forward decs class Image; class File; // more, needed for efficient collision detection class SoulShield; class SoulBarrier; // for tells class AnimationData; struct ProjectileSpec { int id; // a staple of these lists I keep creating... bool canRotate; // not rotating a projectile will reduce expense, and it's counterproductive for circular projectiles. bool isRectHitbox; // rectangle hitboxes are bloody expensive. these should be the minority. double hitboxRadius; // if the above is false, and 99% of the time it will be. // and for that other 1%... double hitboxHalfWidth, hitboxHalfHeight; bool useFixedRotation; double fixedAnglePerMS; // is exported in java as degrees angle/sec. Rect imageCoords; // source coordinates of this projectile on the image sheet String name; // not sure what, if anything, I'll do with this. // NOT IMPORTED, THESE ARE DETERMINED UPON IMPORT double imageHalfWidthInTiles; double imageHalfHeightInTiles; }; class DanmakuProjectile { public: DanmakuProjectile(); // children should call projectileInit() first virtual ~DanmakuProjectile(); void* operator new(size_t sz); void operator delete(void* p); #ifdef PLATFORM_ANDROID virtual void update(int deltaMS); // children should call projectileUpdate() first #else virtual void update(int deltaMS) = 0; // children should call projectileUpdate() first #endif void updateBase(int deltaMS); virtual void render(); // probably no reason this should ever be overriden, but just in case... void renderSpecialEffects(); // becuase frames will drop greatly if sparkles keep interrupting long sheets of projectiles void setProjectileColor(uint32 colorRGBA); void setProjectileColors(uint32 topLeftRGBA, uint32 bottomLeftRGBA, uint32 bottomRightRGBA, uint32 topRightRGBA); bool isOOB(); // spawner needs to do this void postProcess(); protected: void projectileUpdate(int deltaMS); // this specification does not belong to this object, and should never ever be set. ProjectileSpec* m_spec; // seriously, DO NOT mess with its contents! protected: // motion-related variables that all projectiles need double m_motionTilesPerMS; // tiles per second...finally moved it here since it's needed for flee double m_motionAngle; bool m_tieVisualAndMotionAngles; double m_angleCurvePerMS; // the last of the original "basic" variables, now moved out of basic. LOL // projectile facing -- special int m_turnStyle; int m_hardTurnIntervalMS; // subclass-controlled rotation bool m_subclassControlsRotation; double m_subclassFixedAngle; private: // don't let subclasses set these directly double m_x; // center X, even for rectangles. 1.0 is one tile. double m_y; // center Y double m_prevX; double m_prevY; ColorSet m_colorSet; // REMEMBER, it's RGBA! int m_damage; bool m_persistOnHit; bool m_canTeamkill; bool m_immuneToBarriers; // projectile "flight", since this isn't like Touhou or other danmaku games where it's score based // remaining projectiles need to go somewhere, so they'll flee the player. bool m_fleeing; void updateFlee(int deltaMS); // owner int m_owner; // projectile hit special int m_phsIndex; protected: // need this for sparkles and orbitals (a complex type) int m_uptimeMS; private: // fixed rotation, used for things like buzzsaws double m_fixedAngle; // projectile scaling, used for hit detection and rendering bool m_usesScale; double m_scaleBy; double m_scaledRadius; DRect m_imageOffsetRect; double m_hitboxHalfWidth; double m_hitboxHalfHeight; protected: // every projectile needs an expiration date, lest the game crash. also going fully offscreen is a good indicator. int m_expireTimeMS; bool m_shouldDieOffscreen; bool m_immuneToShields; int m_offscreenGraceMS; public: inline double getX() { return m_x; } inline double getY() { return m_y; } void setXY(double newX, double newY); inline void storeLocation() { m_prevX = m_x; m_prevY = m_y; } inline int getDamage() { return m_damage; } inline void setDamage(int damage) { m_damage = damage; } inline void setColor(uint32 rgba) { m_colorSet.recolor(rgba); } inline void setColor(ColorSet& colorSet) { m_colorSet.recolor(colorSet); } inline void setColor(ColorSet* colorSet) { m_colorSet.recolor(colorSet); } inline void setLifetimeMS(int lifetimeMS) { m_expireTimeMS = lifetimeMS; } inline void allowProjectileOOB(bool allowOOB) { m_shouldDieOffscreen = !allowOOB; } inline void setImmuneToShields(bool isImmune) { m_immuneToShields = isImmune; } inline void setPersistOnHit(bool persistOnHit) { m_persistOnHit = persistOnHit; } inline void setOffscreenGrace(int graceMS) { m_offscreenGraceMS = graceMS; } inline void setImmuneToBarriers() { m_immuneToBarriers = true; } void setOwner(int owner); // helpers inline bool ownedByPlayer() { return m_owner == DANMAKU_OWNER_PLAYER; } inline bool ownedByCPU() { return m_owner != DANMAKU_OWNER_PLAYER; } // getters inline int getOwner() { return m_owner; } // projectile scale inline void setProjectileScale(double scaleBy) { m_usesScale = true; m_scaleBy = scaleBy; } // turn style inline void setTurnStyle(int turnStyle, int hardTurnIntervalMS) { m_turnStyle = turnStyle; m_hardTurnIntervalMS = hardTurnIntervalMS; } void spawnSetXY(double x, double y); inline void setPHSIndex(int index) { m_phsIndex = index; } protected: // inline inline void queueImmediateSelfDestruction() { m_expireTimeMS = 0; } private: // the linked list for these projectiles, a mix of statics and instance variables DanmakuProjectile* m_prev; DanmakuProjectile* m_next; int m_layer; // destruction/placement would be a disaster without this. static DanmakuProjectile* sm_heads[NUM_PROJECTILE_LAYERS]; static DanmakuProjectile* sm_tails[NUM_PROJECTILE_LAYERS]; static double sm_leftX, sm_topY, sm_rightX, sm_bottomY; public: // and the means to manipulate the linked list static void addProjectile(DanmakuProjectile* proj, ProjectileSpec* spec, int layer); static void updateAll(int deltaMS); // destruction of expired projectiles also triggered here. static void renderAll(); static void destroyProjectile(DanmakuProjectile* proj); static void destroyAllProjectiles(); static void handleShieldCollision(SoulShield* shield, int deltaMS); static void handleCharCollision(int characterIdx, int deltaMS, double& outDamage, bool& tookDamage); static void handleBarrierCollision(SoulBarrier* barrier); static void triggerFlee(double tilesPerMS, double fromX, double fromY); // causes all projectiles to flee. it's tiles/MS because except the console version, it'll be entirely internal. private: static int sm_numSpecs; static int sm_healingFeatures[DANMAKU_DIFFICULTY_COUNT]; static AnimationData* sm_sparkle; // doesn't belong to this class static bool sm_sparkleLoaded; static bool sm_shouldDestroyPlayerHomingProjectiles; // special handling for reflect shields suddenly created by the boss public: // gotta be public since this needs to be loaded in Platform static ColorSet* sm_tmpColorSet; private: static int sm_playerProjectileCount; static int sm_enemyProjectileCount; public: // other statics // in the interest of minimalism, Projectile types will be loaded all at once. static int sm_imageFileLocation; static void loadProjectileData(File& inFile); // this should occur on startup static void loadProjectileImage(File& inFile); // this should shortly after the above, once PICReader is usable static void destroyProjectileData(); static ProjectileSpec* getProjectileSpecWithId(int id); static ProjectileSpec* getProjectileSpecWithName(const char* name); inline static ProjectileSpec* getProjectileSpecWithName(String* name) { return getProjectileSpecWithName(name->getCharArray()); } inline static ProjectileSpec* getProjectileSpecWithName(String& name) { return getProjectileSpecWithName(name.getCharArray()); } inline static int getNumSpecs() { return sm_numSpecs; } static ProjectileSpec* debugGetSpecByArrayIdx(int arrayIdx); inline static int getNumEnemyProjectiles() { return sm_enemyProjectileCount; } inline static int getNumPlayerProjectiles() { return sm_playerProjectileCount; } inline static int getNumProjectiles() { return sm_enemyProjectileCount + sm_playerProjectileCount; } inline static bool shouldDestroyPlayerHomingProjectiles() { return sm_shouldDestroyPlayerHomingProjectiles; } inline static void destroyPlayerHomingProjectilesNextTick() { sm_shouldDestroyPlayerHomingProjectiles = true; } // obligatory debug static int memGetNumProjectilesLoaded(); // loaded into memory, NOT SUITABLE FOR GAMEPLAY PURPOSES. will break if I implement recycling. static void debugPrintStats(); static ProjectileSpec* debugGetProjectileSpecWithArrayIdx(int arrayIdx); private: static Image* sm_projectileSheet; // if all projectiles in a game can't be stored as a single 2048x2048 sheet...I'd be surprised. static ProjectileSpec* sm_projectileSpecs; // image coordinates and collision data private: // static static int sm_projectileCreated; static int sm_projectileDestroyed; }; class BasicProjectile : public DanmakuProjectile { public: BasicProjectile(); virtual ~BasicProjectile(); void* operator new(size_t sz); void operator delete(void* p); virtual void update(int deltaMS); private: void adjustMotionAngle(int& deltaMS, double& newX, double& newY); public: void basicProjectileSettings(double motionTilesPerMS, double motionAngleRad, double angleCurvePerMS, bool tieVisualAndMotionAngles); void basicAccelerationSettings(double linearAccelerationTilesPerMS); protected: double m_linearAccelerationTilesPerMS; // CAN BE NEGATIVE FOR DECELERATION bool m_accelerationHasLimit; double m_minSpeedTPMS; double m_maxSpeedTPMS; void basicProjectileUpdate(int deltaMS); void applyAcceleration(int deltaMS); void adjustSpecialPosition(int deltaMS, double& posToAdjustX, double& posToAdjustY); public: // inline inline void clampProjectileSpeed(double minTPMS, double maxTPMS) { m_minSpeedTPMS = minTPMS; m_maxSpeedTPMS = maxTPMS; } public: // static static void debugPrintStats(); private: // static static int sm_basicProjectileCreated; static int sm_basicProjectileDestroyed; }; class HomingProjectile : public BasicProjectile { public: HomingProjectile(); virtual ~HomingProjectile(); void* operator new(size_t sz); void operator delete(void* p); virtual void update(int deltaMS); protected: void homingProjectileUpdate(int deltaMS); bool m_isPerfectHoming; bool m_homingMovesWhenPlayerDoes; // projectiles only move when player moves. double m_homingAnglePerMS; // this and most other variables below are unused if above is true double m_homingDeviationPerMS; // using this can make wonky moving projectiles int m_delayToStartHomingMS; double m_homingRange; // projectile is "dumb" until it gets close enough. if this is 0.0, range is infinite. public: inline void setHomingQuirks(bool isPerfect, bool movesWhenPlayerDoes) { m_isPerfectHoming = isPerfect; m_homingMovesWhenPlayerDoes = movesWhenPlayerDoes; } // DO NOT COPY THE * 0.5 TO SPAWNER! inline void setHomingTurnSpeedAndDeviation(double angle, double deviation) { m_homingAnglePerMS = angle; m_homingDeviationPerMS = deviation * 0.5; } inline void setHomingStartDelay(int delayMS) { m_delayToStartHomingMS = delayMS; } inline void setHomingRange(double range) { m_homingRange = range; } public: // static static void debugPrintStats(); private: // static static int sm_homingProjectileCreated; static int sm_homingProjectileDestroyed; }; // complex danmaku type #define CDT_STOP_START 0 // a projectile that suddenly stops dead, and then restarts after an interval #define CDT_MIMIC 1 #define CDT_ORBITAL 2 #define CDT_COUNT 3 class ComplexProjectile : public BasicProjectile { public: ComplexProjectile(); virtual ~ComplexProjectile(); void* operator new(size_t sz); void operator delete(void* p); virtual void update(int deltaMS); protected: void complexProjectileUpdate(int deltaMS); int m_complexDanmakuType; /** * STOP/START */ int m_shutdownDelayMS; int m_shutdownDurationMS; // set this to 0 to last until time out bool m_restartShouldReorient; bool m_restartTargetEnemy; double m_restartMotionTilesMS; double m_restartAngleOffset; // ignored if restartTargetsPlayer double m_restartMotionAngle; // ignored if restartTargetsPlayer double m_restartAngleDeviation; // adds randomness to the angle in any situation double m_restartMotionCurveMS; /** * MIMIC */ bool m_mimicInvertX; bool m_mimicInvertY; double m_mimicRelativeSpeed; // relative to player /** * ORBITAL */ double m_orbitLastCenterX; double m_orbitLastCenterY; double m_orbitAngleAtSpawn; double m_orbitDistance; double m_orbitMinSpeedTPMS; double m_orbitTPMSPerTile; int m_orbitPeriod; bool m_orbitCenterOnPosition; double m_orbitCenterOnX; double m_orbitCenterOnY; int m_orbitOscillationPeriodMS; double m_orbitOscillateDistance; bool m_orbitOscillateSmooth; bool m_orbitRotateCCW; bool m_orbitHalfOscillate; bool m_orbitStartAtZero; public: // inline // intentionally the same as the spawner has inline void setComplexType(int type) { m_complexDanmakuType = type; } /** * STOP/START */ inline void setShutdownSettings(int delayMS, int durationMS) { m_shutdownDelayMS = delayMS; m_shutdownDurationMS = durationMS; } inline void setRestartTargeting(bool shouldReorient, bool targetEnemy) { m_restartShouldReorient = shouldReorient; m_restartTargetEnemy = targetEnemy; } inline void setRestartMotion(double tilesPerMS, double curveMS) { m_restartMotionTilesMS = tilesPerMS; m_restartMotionCurveMS = curveMS; } inline void setRestartDeviation(double deviation) { m_restartAngleDeviation = deviation; } inline void setRestartAngleOffset(double angleOffset) { m_restartAngleOffset = angleOffset; } inline void setRestartFixedAngle(double fixedAngle) { m_restartMotionAngle = fixedAngle; } /** * MIMIC */ inline void setMimicInvert(bool invertX, bool invertY) { m_mimicInvertX = invertX; m_mimicInvertY = invertY; } inline void setMimicRelativeSpeed(double relSpeed) { m_mimicRelativeSpeed = relSpeed; } /** * ORBITAL */ inline void setOrbitCenterXY(double centerX, double centerY) { m_orbitLastCenterX = centerX; m_orbitLastCenterY = centerY; } inline void setOrbitLocationInfo(double angleAtSpawn, double distance, int periodMS) { m_orbitAngleAtSpawn = angleAtSpawn; m_orbitDistance = distance; m_orbitPeriod = periodMS; } inline void setOrbitSpeeds(double minSpeedTPMS, double tpmsPerTile) { m_orbitMinSpeedTPMS = minSpeedTPMS; m_orbitTPMSPerTile = tpmsPerTile; } inline void setOrbitalCenterPos(bool center, double centerX, double centerY) { m_orbitCenterOnPosition = centerX; m_orbitCenterOnX = centerX; m_orbitCenterOnY = centerY; } inline void setOrbitalOscillation(int periodMS, double distance, bool smooth) { m_orbitOscillationPeriodMS = periodMS; m_orbitOscillateDistance = distance; m_orbitOscillateSmooth = smooth; } inline void setOrbitalRotateCCW(bool torf) { m_orbitRotateCCW = torf; } inline void setOrbitalHalfOscillate(bool torf) { m_orbitHalfOscillate = torf; } inline void setOrbitalStartAtZero(bool torf) { m_orbitStartAtZero = torf; } public: // static static void debugPrintStats(); private: // static static int sm_complexProjectileCreated; static int sm_complexProjectileDestroyed; }; class ScriptProjectile : public BasicProjectile { public: ScriptProjectile(); virtual ~ScriptProjectile(); void* operator new(size_t sz); void operator delete(void* p); public: // static static void debugPrintStats(); private: // static static int sm_scriptProjectileCreated; static int sm_scriptProjectileDestroyed; }; void Projectile_PrintAllStats(); #endif