File WfItemHelpers.c

File List > game > src > items > WfItemHelpers.c

Go to the documentation of this file

#include "WfItemHelpers.h"
#include "GameFramework.h"
#include "Game2DLayer.h"
#include <cglm/cglm.h>
#include "Entities.h"
#include "WfEntities.h"
#include <stdbool.h>
#include "AssertLib.h"
#include "WfTree.h"
#include "WfPlayer.h"
#include "Geometry.h"
#include "EntityQuadTree.h"
#include "WfDebris.h"
#include "WfEntityMessages.h"
#include "Log.h"

struct DamagingItemContext
{
    struct GameFrameworkLayer* pLayer;
    struct GameLayer2DData* pData;
    HEntity2D hEntPlayer;
    struct WfDamagingWeaponDef def;
};

static OBJECT_POOL(struct DamagingItemContext) gDamagingItemContextPool;

void WfInitItemHelpers()
{
    gDamagingItemContextPool = NEW_OBJECT_POOL(struct DamagingItemContext, 16);
}

bool WfGetEntityGroundContactPoint(struct Entity2D* pEnt, vec2 outPoint)
{
    switch (pEnt->type)
    {
    case WfEntityType_Exit:
        // not implemented for this kind
        return false;
        break;
    case WfEntityType_WoodedArea:
    case WfEntityType_DebrisField:
        EASSERT("WfEntityType_WoodedArea and WfEntityType_DebrisField are transient and shouldn't be passed to this function normally" && false);
        return false;
    case WfEntityType_Tree:
        WfTreeGetGroundContactPoint(pEnt, outPoint);
        return true;
    case WfEntityType_Rock:
        WfDebrisGetGroundContactPoint(pEnt, outPoint);
        return true;
    case WfEntityType_Player:
        WfPlayerGetGroundContactPoint(pEnt, outPoint);
        return true;
    }
    return false;
}

HTimer WfScheduleCallbackOnAnimation(struct Entity2D* pPlayer, struct GameFrameworkLayer* pLayer, 
    TimerCallbackFn callback,
    float percentageIntoAnimation,
    char* anim_name, 
    void* pCallbackUserData)
{
    /* delay the actual damage by a certain amount of time based on animation length */
    struct GameLayer2DData* pData = pLayer->userData;
    struct WfPlayerEntData* pEntData = WfGetPlayerEntData(pPlayer);
    struct AtlasAnimation* pCurrentAnim = At_FindAnim(pData->hAtlas, anim_name);
    float animLen = (float)VectorSize(pCurrentAnim->frames) / pCurrentAnim->fps;

    struct SDTimer timerDef = {
        .bActive = true,
        .bRepeat = false,
        .bAutoReset = false,
        .total = animLen * percentageIntoAnimation,
        .fnCallback = callback,
        .pUserData = pCallbackUserData,
        .elapsed = 0.0
    };
    HTimer timer = TP_GetTimer(&pData->timerPool, &timerDef);
    return timer;
}

VECTOR(struct Entity2D*) WfFindEntitiesWithinFan(
    struct WfSearchFan* pFan, 
    struct GameFrameworkLayer* pLayer, 
    struct GameLayer2DData* pData, 
    enum WfEntitySearchType searchType,
    VECTOR(struct Entity2D*) pEntityListOut)
{
    pEntityListOut = VectorClear(pEntityListOut);
    static VECTOR(HEntity2D) sFoundEnts = NULL;
    if(!sFoundEnts)
    {
        sFoundEnts = NEW_VECTOR(HEntity2D);
    }

    sFoundEnts = VectorClear(sFoundEnts);

    vec2 tl = {
        pFan->base[0] - pFan->length,
        pFan->base[1] - pFan->length,
    };
    vec2 br = {
        pFan->base[0] + pFan->length,
        pFan->base[1] + pFan->length,
    };

    if( searchType & WfStaticEntities)
    {
        sFoundEnts = Entity2DQuadTree_Query(pData->hEntitiesQuadTree, tl, br, sFoundEnts, &pData->entities, pLayer);
    }

    if(searchType & WfDynamicEntities)
    {
        sFoundEnts = Et2D_QueryDynEntities(pLayer, &pData->entities, tl, br, sFoundEnts);
    }

    int foundEnts = VectorSize(sFoundEnts);
    for(int i=0; i<foundEnts; i++)
    {
        HEntity2D hEnt = sFoundEnts[i];
        struct Entity2D* pEnt = Et2D_GetEntity(&pData->entities, hEnt);
        vec2 entityGroundPoint;
        bool bSuccess = WfGetEntityGroundContactPoint(pEnt, entityGroundPoint);
        if(!bSuccess)
        {
            continue;
        }
        float distance = Ge_DistanceBetweenPoints(pFan->base, entityGroundPoint);
        if(distance > pFan->length)
        {
            continue;
        }
        vec2 base2Ent;
        glm_vec2_sub(entityGroundPoint, pFan->base, base2Ent);
        float angle = Ge_AngleBetweenVec2s(pFan->direction, base2Ent);
        if(angle <= pFan->widthRadians)
        {
            pEntityListOut = VectorPush(pEntityListOut, &pEnt);
        }

    }
    return pEntityListOut;
}

void WfGetDirectionVector(enum WfDirection dir, vec2 outVec)
{
    static const vec2 dirs[NumDirections] = {
        { 0.0f,-1.0f}, // up
        { 0.0f, 1.0f}, // down
        {-1.0f, 0.0f}, // left
        { 1.0f, 0.0f}, // right
    };
    outVec[0] = dirs[dir][0];
    outVec[1] = dirs[dir][1];
}

struct Entity2D* WfFindClosestEntity(vec2 pointClosestTo, VECTOR(struct Entity2D*) pEnt)
{
    float distance = FLT_MAX;
    struct Entity2D* pRet = NULL;
    for(int i=0; i<VectorSize(pEnt); i++)
    {
        vec2 g;
        WfGetEntityGroundContactPoint(pEnt[i], g);
        float d = Ge_DistanceBetweenPoints(g, pointClosestTo);
        if(d < distance)
        {
            pRet = pEnt[i];
            distance = d;
        }
    }
    return pRet;
}

TileIndex* WfGetTileAtXY(struct TileMapLayer* pLayer, int x, int y)
{
    return pLayer->Tiles + (y * pLayer->widthTiles + x);
}

TileIndex* WfGetTileAtPoint(vec2 pt, struct GameLayer2DData* pData, int tileLayer, int* pXOut, int* pYOut)
{
    int iX = ((int)pt[0] / (int)pData->tilemap.layers[tileLayer].tileWidthPx);
    int iY = ((int)pt[1] / (int)pData->tilemap.layers[tileLayer].tileHeightPx);
    struct TileMapLayer* pLayer = &pData->tilemap.layers[tileLayer];
    TileIndex* pIndex = WfGetTileAtXY(pLayer, iX, iY);
    *pXOut = iX;
    *pYOut = iY;
    return pIndex;
}

TileIndex* WfGetTileInFrontOfPlayer(struct GameLayer2DData* pData, struct WfPlayerEntData* pPlayerData, float distanceInFront, HEntity2D hEntPlayer, int tileLayer, int* pXOut, int* pYOut)
{
    vec2 facingVec, pt;
    WfGetDirectionVector(pPlayerData->directionFacing, facingVec);
    vec2 playerGroundPos;
    struct Entity2D* pPlayerEnt = Et2D_GetEntity(&pData->entities, hEntPlayer);
    WfPlayerGetGroundContactPoint(pPlayerEnt, playerGroundPos);
    glm_vec2_scale(facingVec, distanceInFront, facingVec);
    glm_vec2_add(playerGroundPos, facingVec, pt);

    return WfGetTileAtPoint(pt, pData, tileLayer, pXOut, pYOut);
}

static bool IsIndexInSet(TileIndex index, TileIndex* indices, int numIndices)
{
    for(int i=0; i<numIndices; i++)
    {
        if(indices[i] == index)
        {
            return true;
        }
    }
    return false;
}

u8 WfGetTerrainLUTIndex(int tileX, int tileY, struct TileMap* tilemap, int layer, TileIndex* indices, int numIndices)
{
    u8 r = 0;
    struct TileMapLayer* pLayer = &tilemap->layers[layer];
    TileIndex indices_surrounding[8] = {
        *(WfGetTileAtXY(pLayer, tileX-1, tileY-1)),
        *(WfGetTileAtXY(pLayer, tileX,   tileY-1)),
        *(WfGetTileAtXY(pLayer, tileX+1, tileY-1)),

        *(WfGetTileAtXY(pLayer, tileX-1, tileY)),
        *(WfGetTileAtXY(pLayer, tileX+1, tileY)),

        *(WfGetTileAtXY(pLayer, tileX-1, tileY+1)),
        *(WfGetTileAtXY(pLayer, tileX,   tileY+1)),
        *(WfGetTileAtXY(pLayer, tileX+1, tileY+1)),
    };
    for(int i=0; i<8; i++)
    {
        if(IsIndexInSet(indices_surrounding[i], indices, numIndices))
        {
            r |= (1 << i);
        }
    }
    return r;
}

static bool ProcessDamagingWeaponUsage(struct SDTimer* pTimer)
{
    struct DamagingItemContext* pCtx = &gDamagingItemContextPool[(HGeneric)pTimer->pUserData];

    struct Entity2D* pPlayerEnt = Et2D_GetEntity(&pCtx->pData->entities, pCtx->hEntPlayer);
    struct WfPlayerEntData* pPlayerData = WfGetPlayerEntData(pPlayerEnt);
    vec2 playerGroundPos;
    WfPlayerGetGroundContactPoint(pPlayerEnt, playerGroundPos);

    vec2 dir;
    WfGetDirectionVector(pPlayerData->directionFacing, dir);
    struct WfSearchFan fan = 
    {
        .base[0] = playerGroundPos[0],
        .base[1] = playerGroundPos[1],
        .direction[0] = dir[0],
        .direction[1] = dir[1], 
        .length = pCtx->def.fanLength,
        .widthRadians = pCtx->def.fanWidth,
    };
    static VECTOR(struct Entity2D*) sFoundEnts = NULL;
    if(!sFoundEnts)
    {
        sFoundEnts = NEW_VECTOR(struct Entity2D*);
    }

    sFoundEnts = VectorClear(sFoundEnts);

    sFoundEnts = WfFindEntitiesWithinFan(&fan, pCtx->pLayer, pCtx->pData, WfDynamicEntities | WfStaticEntities, sFoundEnts);

    Log_Info("Entities in fan %i", VectorSize(sFoundEnts));

    struct Entity2D* pHit = VectorSize(sFoundEnts) > 0 ? sFoundEnts[0] : NULL;//WfFindClosestEntity(fan.base, sFoundEnts);

    if(pHit)
    {
        struct WfDamageMsg* pDamageMsg = NULL;

        struct EntityToEntityMessage msg = 
        {
            .type = E2EM_Damage,
            .data.hMsgData = WfAllocateDamageMessageData(&pDamageMsg),
            .freer = &WfDamageMsgFreer,
            .sender = pPlayerEnt->thisEntity,
            .recipient = pHit->thisEntity
        };
        float damage = 0.0f;
        switch (pCtx->def.damageCalculationType)
        {
        case DCT_Callback:
            damage = pCtx->def.damageCalcData.damageCallback(pPlayerEnt, pHit, pCtx->def.pItemDef);
            break;
        case DCT_Constant:
            damage = pCtx->def.damageCalcData.damageConst;
            break;
        default:
            Log_Error("Unknown damage calculation type: %i", pCtx->def.damageCalculationType);
            EASSERT(false);
        }
        pDamageMsg->damage = damage;
        pDamageMsg->type = pCtx->def.damageType;
        Et2D_SendEntity2EntityMsg(&pCtx->pData->entities, &msg);
    }

    FreeObjectPoolIndex(gDamagingItemContextPool, (HGeneric)pTimer->pUserData);
    return true; /* remove timer */
}
void WfDrawDamagingWeaponItemDebugLines(
    struct Entity2D* pEnt, 
    struct GameFrameworkLayer* pLayer, 
    struct Transform2D* pCam, 
    VECTOR(WorldspaceLineVertex)* outVerts, 
    struct WfItemDef* pItemDef, 
    struct WfDamagingWeaponDef* pDef)
{
    vec2 playerGroundPos;
    WfPlayerGetGroundContactPoint(pEnt, playerGroundPos);
    struct WfPlayerEntData* pPlayerData = WfGetPlayerEntData(pEnt);
    vec2 facingVec;
    WfGetDirectionVector(pPlayerData->directionFacing, facingVec);
    vec2 v = {
        facingVec[0] * pDef->fanLength,
        facingVec[1] * pDef->fanLength,
    };
    vec2 pt2 = {0 , 0};
    glm_vec2_add(playerGroundPos, v, pt2);

    WorldspaceLineVertex v1 = {
        .x = playerGroundPos[0],
        .y = playerGroundPos[1],
        .r = 0,
        .g = 1.0,
        .b = 0,
        .a = 1.0
    };
    WorldspaceLineVertex v2 = {
        .x = pt2[0],
        .y = pt2[1],
        .r = 0,
        .g = 1.0,
        .b = 0,
        .a = 1.0
    };
    *outVerts = VectorPush(*outVerts, &v1);
    *outVerts = VectorPush(*outVerts, &v2);
}

bool WfOnUseDamagingWeaponItem(struct Entity2D* pPlayer, struct GameFrameworkLayer* pLayer, struct WfDamagingWeaponDef* pDef)
{
    struct GameLayer2DData* pData = pLayer->userData;

    HGeneric hCtx = NULL_HANDLE;
    gDamagingItemContextPool = GetObjectPoolIndex(gDamagingItemContextPool, &hCtx);

    struct DamagingItemContext ctx = {
        .hEntPlayer = pPlayer->thisEntity,
        .pData = pData,
        .pLayer = pLayer,
        .def = *pDef
    };
    gDamagingItemContextPool[hCtx] = ctx;
    struct WfPlayerEntData* pEntData = WfGetPlayerEntData(pPlayer);
    char* anim_name = pEntData->animationSet.layers[WfToolAnimationLayer].slashAnimations[pEntData->directionFacing];
    HTimer t = WfScheduleCallbackOnAnimation(pPlayer, pLayer, &ProcessDamagingWeaponUsage, 0.3, anim_name, (void*)hCtx);
    return true;
}