/** * @brief Moves actor. * @param[in] self Pointer to the event structure that is currently executed * @param[in] msg The netchannel message * @sa LET_PathMove * @note EV_ACTOR_MOVE */ void CL_ActorDoMove (const eventRegister_t* self, dbuffer* msg) { const int number = NET_ReadShort(msg); /* get le */ le_t* le = LE_Get(number); if (!le) LE_NotFoundError(number); if (!LE_IsActor(le)) Com_Error(ERR_DROP, "Can't move, LE doesn't exist or is not an actor (entnum: %i, type: %i)\n", number, le->type); if (LE_IsDead(le)) Com_Error(ERR_DROP, "Can't move, actor on team %i dead (entnum: %i)", le->team, number); /* lock this le for other events, the corresponding unlock is in LE_DoEndPathMove() */ LE_Lock(le); if (le->isMoving()) { if (le->pathLength == le->pathPos) { LE_DoEndPathMove(le); } else { Com_Error(ERR_DROP, "Actor (entnum: %i) on team %i is still moving (%i steps left). Times: %i, %i, %i", le->entnum, le->team, le->pathLength - le->pathPos, le->startTime, le->endTime, cl.time); } } int i = 0; /* the end of this event is marked with a 0 */ while (NET_PeekLong(msg) != 0) { NET_ReadByte(msg); le->dvtab[i] = NET_ReadShort(msg); /** Don't adjust dv values here- the whole thing is needed to move the actor! */ le->speed[i] = NET_ReadShort(msg); le->pathContents[i] = NET_ReadShort(msg); i++; } le->pathLength = i; if (le->pathLength > MAX_ROUTE) Com_Error(ERR_DROP, "Overflow in pathLength (entnum: %i)", number); /* skip the end of move marker */ NET_ReadLong(msg); /* Also get the final position */ NET_ReadGPos(msg, le->newPos); if (VectorCompare(le->newPos, le->pos)) Com_Error(ERR_DROP, "start and end pos are the same (entnum: %i)", number); /* activate PathMove function */ le->resetFloor(); if (LE_IsInvisible(le)) /* Hack: this relies on the visibility events interrupting the EV_ACTOR_MOVE event */ LE_SetThink(le, LET_HiddenMove); else LE_SetThink(le, LET_StartPathMove); le->pathPos = 0; le->startTime = cl.time; le->endTime = cl.time; }
/** * @brief Change the animation of an actor to the idle animation (which can be * panic, dead or stand) * @note We have more than one animation for dead - the index is given by the * state of the local entity * @note Think function * @note See the *.anm files in the models dir */ void LET_StartIdle (le_t* le) { /* hidden actors don't have models assigned, thus we can not change the * animation for any model */ if (!LE_IsInvisible(le)) { if (LE_IsDead(le)) R_AnimChange(&le->as, le->model1, va("dead%i", LE_GetAnimationIndexForDeath(le))); else if (LE_IsPanicked(le)) R_AnimChange(&le->as, le->model1, "panic0"); else R_AnimChange(&le->as, le->model1, LE_GetAnim("stand", le->right, le->left, le->state)); } le->pathPos = le->pathLength = 0; if (le->stepList != nullptr) { leStep_t* step = le->stepList->next; Mem_Free(le->stepList); le->stepList = step; if (step != nullptr) { le->stepIndex--; } else if (le->stepIndex != 0) { Com_Error(ERR_DROP, "stepindex for entnum %i is out of sync (%i should be 0)\n", le->entnum, le->stepIndex); } } /* keep this animation until something happens */ LE_SetThink(le, nullptr); }
/** * @brief Checks whether the given le is a living and visible actor * @param[in] le The local entity to perform the check for * @sa G_IsLivingActor * @sa LE_IsActor */ bool LE_IsLivingAndVisibleActor (const le_t* le) { assert(le); if (LE_IsInvisible(le)) return false; assert(le->type != ET_ACTORHIDDEN); return LE_IsLivingActor(le); }
/** * @brief Shows a list of current know local entities with type and status */ void LE_List_f (void) { Com_Printf("number | entnum | type | inuse | invis | pnum | team | size | HP | state | level | model/ptl\n"); for (int i = 0; i < cl.numLEs; i++) { le_t& le = cl.LEs[i]; Com_Printf("#%5i | #%5i | %4i | %5i | %5i | %4i | %4i | %4i | %3i | %5i | %5i | ", i, le.entnum, le.type, le.inuse, LE_IsInvisible(&le), le.pnum, le.team, le.fieldSize, le.HP, le.state, le.levelflags); if (le.type == ET_PARTICLE) { if (le.ptl) Com_Printf("%s\n", le.ptl->ctrl->name); else Com_Printf("no ptl\n"); } else if (le.model1) Com_Printf("%s\n", le.model1->name); else Com_Printf("no mdl\n"); } }
/** * @sa CMod_GetMapSize * @note we only need to handle the 2d plane and can ignore the z level * @param[in] node Node description of the radar */ void uiRadarNode::draw (uiNode_t* node) { vec2_t pos; vec2_t screenPos; #ifdef RADARSIZE_DEBUG int textposy = 40; static const vec4_t red = {1, 0, 0, 0.5}; #endif static const vec4_t backgroundColor = {0.0, 0.0, 0.0, 1}; const float mapWidth = cl.mapData->mapBox.getWidthX(); const float mapHeight = cl.mapData->mapBox.getWidthY(); /** @todo use the same coef for x and y */ const float mapCoefX = (float) node->box.size[0] / (float) mapWidth; const float mapCoefY = (float) node->box.size[1] / (float) mapHeight; if (cls.state != ca_active) return; UI_GetNodeAbsPos(node, pos); UI_GetNodeScreenPos(node, screenPos); R_CleanupDepthBuffer(pos[0], pos[1], node->box.size[0], node->box.size[1]); UI_DrawFill(pos[0], pos[1], mapWidth * mapCoefX, mapHeight * mapCoefY, backgroundColor); #ifndef RADARSIZE_DEBUG UI_PushClipRect(screenPos[0], screenPos[1], node->box.size[0], node->box.size[1]); #endif /* the cl struct is wiped with every new map */ if (!cl.radarInitialized) { UI_InitRadar(node); cl.radarInitialized = true; } /* update context */ radar.x = pos[0]; radar.y = pos[1]; radar.w = node->box.size[0]; radar.h = node->box.size[1]; if (radar.gridWidth < 6) radar.gridWidth = 6; if (radar.gridHeight < 6) radar.gridHeight = 6; #ifdef RADARSIZE_DEBUG UI_DrawStringInBox("f_small", ALIGN_UL, 50, textposy, 500, 25, va("%fx%f %fx%f map", cl.mapData->mapBox.getMinX(), cl.mapData->mapBox.getMinY(), cl.mapData->getMaxX(), cl.mapData->getMaxY())); textposy += 25; UI_DrawStringInBox("f_small", ALIGN_UL, 50, textposy, 500, 25, va("%fx%f map", mapWidth, mapHeight)); textposy += 25; #endif /* draw background */ for (int i = 0; i < radar.numImages; i++) { vec2_t imagePos; hudRadarImage_t* tile = &radar.images[i]; int maxlevel = cl_worldlevel->integer; /* check the max level value for this map tile */ if (maxlevel >= tile->maxlevel) maxlevel = tile->maxlevel - 1; assert(tile->path[maxlevel]); imagePos[0] = radar.x + mapCoefX * (tile->mapX - cl.mapData->mapBox.getMinX()); imagePos[1] = radar.y + mapCoefY * (tile->mapY - cl.mapData->mapBox.getMinY()); UI_DrawNormImageByName(false, imagePos[0], imagePos[1], mapCoefX * tile->mapWidth, mapCoefY * tile->mapHeight, 0, 0, 0, 0, tile->path[maxlevel]); #ifdef RADARSIZE_DEBUG UI_DrawStringInBox("f_small", ALIGN_UL, 50, textposy, 500, 25, va("%dx%d %dx%d %s", tile->x, tile->y, tile->width, tile->height, tile->path[maxlevel])); textposy += 25; UI_DrawStringInBox("f_small", ALIGN_UL, imagePos[0], imagePos[1], 500, 25, va("%dx%d", tile->gridX, tile->gridY)); #endif } #ifdef RADARSIZE_DEBUG UI_DrawFill(pos[0], pos[1], 100.0f * mapCoefX, 100.0f * mapCoefY, red); UI_DrawFill(pos[0], pos[1], UNIT_SIZE * mapCoefX, UNIT_SIZE * mapCoefY, red); #endif le_t* le = nullptr; while ((le = LE_GetNextInUse(le))) { vec3_t itempos; if (LE_IsInvisible(le)) continue; /* convert to radar area coordinates */ itempos[0] = pos[0] + (le->origin[0] - cl.mapData->mapBox.getMinX()) * mapCoefX; itempos[1] = pos[1] + (mapHeight - (le->origin[1] - cl.mapData->mapBox.getMinY())) * mapCoefY; switch (le->type) { case ET_ACTOR: case ET_ACTOR2x2: UI_RadarNodeDrawActor(le, itempos); break; case ET_ITEM: UI_RadarNodeDrawItem(le, itempos); break; default: break; } #ifdef RADARSIZE_DEBUG UI_DrawStringInBox("f_small", ALIGN_UL, 50, textposy, 500, 25, va("%fx%f %dx%d actor", le->origin[0], le->origin[1], le->pos[0], le->pos[1])); textposy += 25; UI_DrawFill(itempos[0], itempos[1], UNIT_SIZE * mapCoefX, 1, red); UI_DrawFill(itempos[0], itempos[1], 1, UNIT_SIZE * mapCoefY, red); #endif } #ifndef RADARSIZE_DEBUG UI_PopClipRect(); #endif }
/** * @brief Calculates chance to hit if the actor has a fire mode activated. * @param[in] actor The local entity of the actor to calculate the hit probability for. * @todo The hit probability should work somewhat differently for splash damage weapons. * Since splash damage weapons can deal damage even when they don't directly hit an actor, * the hit probability should be defined as the predicted percentage of the maximum splash * damage of the firemode, assuming the projectile explodes at the desired location. This * means that a percentage should be displayed for EVERY actor in the predicted blast * radius. This will likely require specialized code. */ int CL_GetHitProbability (const le_t* actor) { assert(actor); assert(actor->fd); pos3_t toPos; if (IS_MODE_FIRE_PENDING(actor->actorMode)) VectorCopy(actor->mousePendPos, toPos); else VectorCopy(mousePos, toPos); /** @todo use LE_FindRadius */ const le_t* le = LE_GetFromPos(toPos); if (!le) return 0; /* Target is not visible */ if (LE_IsInvisible(le)) return 0; /* or suicide attempted */ if (LE_IsSelected(le) && !FIRESH_IsMedikit(le->fd)) return 0; vec3_t shooter; VectorCopy(actor->origin, shooter); vec3_t target; VectorCopy(le->origin, target); /* Calculate HitZone: */ const int distx = fabs(shooter[0] - target[0]); const int disty = fabs(shooter[1] - target[1]); const float distance = sqrtf(distx * distx + disty * disty); float pseudosin; if (distx > disty) pseudosin = distance / distx; else pseudosin = distance / disty; float width = 2 * PLAYER_WIDTH * pseudosin; float height = LE_IsCrouched(le) ? PLAYER_CROUCHING_HEIGHT : PLAYER_STANDING_HEIGHT; const character_t* chr = CL_ActorGetChr(actor); if (!chr) Com_Error(ERR_DROP, "No character given for local entity"); const float acc = GET_ACC(chr->score.skills[ABILITY_ACCURACY], actor->fd->weaponSkill ? chr->score.skills[actor->fd->weaponSkill] : 0.0, CL_ActorInjuryModifier(actor, MODIFIER_ACCURACY)); const float crouch = (LE_IsCrouched(actor) && actor->fd->crouch) ? actor->fd->crouch : 1.0; const float commonfactor = crouch * torad * distance; const float stdevupdown = (actor->fd->spread[0] * (WEAPON_BALANCE + SKILL_BALANCE * acc)) * commonfactor; const float stdevleftright = (actor->fd->spread[1] * (WEAPON_BALANCE + SKILL_BALANCE * acc)) * commonfactor; const float hitchance = (stdevupdown > LOOKUP_EPSILON ? CL_LookupErrorFunction(height * 0.3536f / stdevupdown) : 1.0f) * (stdevleftright > LOOKUP_EPSILON ? CL_LookupErrorFunction(width * 0.3536f / stdevleftright) : 1.0f); /* 0.3536=sqrt(2)/4 */ /* Calculate cover: */ int n = 0; height = height / 18; width = width / 18; target[2] -= UNIT_HEIGHT / 2; target[2] += height * 9; float perpX = disty / distance * width; float perpY = 0 - distx / distance * width; target[0] += perpX; perpX *= 2; target[1] += perpY; perpY *= 2; target[2] += 6 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] += perpX; target[1] += perpY; target[2] -= 6 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] += perpX; target[1] += perpY; target[2] += 4 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[2] += 4 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] -= perpX * 3; target[1] -= perpY * 3; target[2] -= 12 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] -= perpX; target[1] -= perpY; target[2] += 6 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] -= perpX; target[1] -= perpY; target[2] -= 4 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] -= perpX; target[1] -= perpY; target[2] += 10 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; return 100 * (hitchance * (0.125) * n); }
/** * @sa CL_ViewRender * @sa CL_AddUGV * @sa CL_AddActor */ void LE_AddToScene (void) { for (int i = 0; i < cl.numLEs; i++) { le_t& le = cl.LEs[i]; if (le.flags & LE_REMOVE_NEXT_FRAME) { le.inuse = false; le.flags &= ~LE_REMOVE_NEXT_FRAME; } if (le.inuse && !LE_IsInvisible(&le)) { if (le.flags & LE_CHECK_LEVELFLAGS) { if (!((1 << cl_worldlevel->integer) & le.levelflags)) continue; } else if (le.flags & LE_ALWAYS_VISIBLE) { /* show them always */ } else if (le.pos[2] > cl_worldlevel->integer) continue; entity_t ent(RF_NONE); ent.alpha = le.alpha; VectorCopy(le.angles, ent.angles); ent.model = le.model1; ent.skinnum = le.bodySkin; ent.lighting = &le.lighting; switch (le.contents) { /* Only breakables do not use their origin; func_doors and func_rotating do!!! * But none of them have animations. */ case CONTENTS_SOLID: case CONTENTS_DETAIL: /* they use mins/maxs */ break; default: /* set entity values */ R_EntitySetOrigin(&ent, le.origin); VectorCopy(le.origin, ent.oldorigin); /* store animation values */ ent.as = le.as; break; } if (LE_IsOriginBrush(&le)) { ent.isOriginBrushModel = true; R_EntitySetOrigin(&ent, le.origin); VectorCopy(le.origin, ent.oldorigin); } if (LE_IsSelected(&le) && le.clientAction != nullptr) { const le_t* action = le.clientAction; if (action->inuse && action->type > ET_NULL && action->type < ET_MAX) LE_AddEdictHighlight(action); } /* call add function */ /* if it returns false, don't draw */ if (le.addFunc) if (!le.addFunc(&le, &ent)) continue; /* add it to the scene */ R_AddEntity(&ent); if (cl_le_debug->integer) CL_ParticleSpawn("cross", 0, le.origin); } } }