/** * @sa CL_InvDel * @sa G_SendInventory * @sa EV_INV_ADD */ void CL_InvAdd (const eventRegister_t* self, dbuffer* msg) { const int number = NET_ReadShort(msg); le_t* le = LE_Get(number); int nr = NET_ReadShort(msg); if (!le) LE_NotFoundError(number); le->flags &= ~LE_REMOVE_NEXT_FRAME; for (; nr-- > 0;) { Item item; containerIndex_t container; int x, y; CL_NetReceiveItem(msg, &item, &container, &x, &y); if (LE_IsItem(le)) { if (container != CID_FLOOR) Com_Error(ERR_DROP, "InvAdd for ET_ITEM but target container is not the floor but %i", container); } else if (INVDEF(container)->temp) { Com_Error(ERR_DROP, "InvAdd for %i to temp container %i", le->type, container); } if (cls.i.addToInventory(&le->inv, &item, INVDEF(container), x, y, item.getAmount()) == nullptr) Com_Error(ERR_DROP, "InvAdd failed - could not add %i item(s) of %s to container %i", item.getAmount(), item.def()->id, container); if (container == CID_RIGHT) le->right = item.def()->idx; else if (container == CID_LEFT) le->left = item.def()->idx; else if (container == CID_HEADGEAR) le->headgear = item.def()->idx; } switch (le->type) { case ET_ACTOR: case ET_ACTOR2x2: if (LE_IsSelected(le)) Cmd_ExecuteString("hud_updateactorload"); LE_SetThink(le, LET_StartIdle); break; case ET_ITEM: LE_PlaceItem(le); break; default: break; } }
/** * @brief Parses the actor stats that comes from the netchannel * @sa CL_ParseEvent * @sa G_SendStats */ void CL_ActorStats (const eventRegister_t *self, dbuffer *msg) { le_t *le; int entnum, oldTUs = 0; entnum = NET_ReadShort(msg); le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); switch (le->type) { case ET_ACTORHIDDEN: case ET_ACTOR: case ET_ACTOR2x2: break; default: Com_Printf("CL_ActorStats: LE (%i) not an actor (type: %i)\n", entnum, le->type); return; } if (LE_IsSelected(le)) oldTUs = le->TU; NET_ReadFormat(msg, self->formatString, &le->TU, &le->HP, &le->STUN, &le->morale); if (le->TU > le->maxTU) le->maxTU = le->TU; if (le->HP > le->maxHP) le->maxHP = le->HP; if (le->morale > le->maxMorale) le->maxMorale = le->morale; /* if selActor's timeunits have changed, update movelength */ if (le->TU != oldTUs && LE_IsSelected(le)) CL_ActorResetMoveLength(le); }
/** * @brief Ends the move of an actor */ void LE_DoEndPathMove (le_t* le) { /* Verify the current position */ if (!VectorCompare(le->pos, le->newPos)) Com_Error(ERR_DROP, "LE_DoEndPathMove: Actor movement is out of sync: %i:%i:%i should be %i:%i:%i (step %i of %i) (team %i)", le->pos[0], le->pos[1], le->pos[2], le->newPos[0], le->newPos[1], le->newPos[2], le->pathPos, le->pathLength, le->team); CL_ActorConditionalMoveCalc(le); /* if the moving actor was not the selected one, */ /* recalc the pathing table for the selected one, too. */ if (!LE_IsSelected(le)) { CL_ActorConditionalMoveCalc(selActor); } LE_LinkFloorContainer(le); LE_SetThink(le, LET_StartIdle); LE_ExecuteThink(le); LE_Unlock(le); }
static void UI_RadarNodeDrawActor (const le_t* le, const vec3_t pos) { vec2_t coords[4]; vec2_t vertices[4]; int i; const float size = 10; const int tileSize = 28; int tilePos = 4; const image_t* image; vec4_t color; const float pov = directionAngles[le->angle] * torad + M_PI; image = UI_LoadImage("ui/radar"); if (image == nullptr) return; /* draw FOV */ if (!LE_IsDead(le)) { vertices[0][0] = - size * 4; vertices[0][1] = + 0; vertices[1][0] = + size * 4; vertices[1][1] = + 0; vertices[2][0] = + size * 4; vertices[2][1] = - size * 4; vertices[3][0] = - size * 4; vertices[3][1] = - size * 4; coords[0][0] = (7) / 128.0f; coords[0][1] = (37 + 63) / 128.0f; coords[1][0] = (7 + 114) / 128.0f; coords[1][1] = (37 + 63) / 128.0f; coords[2][0] = (7 + 114) / 128.0f; coords[2][1] = (37) / 128.0f; coords[3][0] = (7) / 128.0f; coords[3][1] = (37) / 128.0f; /* affine transformation */ for (i = 0; i < 4; i++) { const float dx = vertices[i][0]; const float dy = vertices[i][1]; vertices[i][0] = pos[0] + dx * sin(pov) + dy * cos(pov); vertices[i][1] = pos[1] + dx * cos(pov) - dy * sin(pov); } UI_RadarNodeGetActorColor(le, color); if (LE_IsSelected(le)) { color[3] *= 0.75; } else { color[3] = 0.1f; } UI_RadarNodeDrawArrays(color, coords, vertices, image); } if (LE_IsDead(le)) tilePos = 4; else if (LE_IsSelected(le)) tilePos = 66; else tilePos = 36; /* a 0,0 centered square */ vertices[0][0] = - size; vertices[0][1] = + size; vertices[1][0] = + size; vertices[1][1] = + size; vertices[2][0] = + size; vertices[2][1] = - size; vertices[3][0] = - size; vertices[3][1] = - size; coords[0][0] = (tilePos) / 128.0f; coords[0][1] = (5 + tileSize) / 128.0f; coords[1][0] = (tilePos + tileSize) / 128.0f; coords[1][1] = (5 + tileSize) / 128.0f; coords[2][0] = (tilePos + tileSize) / 128.0f; coords[2][1] = (5) / 128.0f; coords[3][0] = (tilePos) / 128.0f; coords[3][1] = (5) / 128.0f; /* affine transformation */ for (i = 0; i < 4; i++) { const float dx = vertices[i][0]; const float dy = vertices[i][1]; vertices[i][0] = pos[0] + dx * sin(pov) + dy * cos(pov); vertices[i][1] = pos[1] + dx * cos(pov) - dy * sin(pov); } UI_RadarNodeGetActorColor(le, color); UI_RadarNodeDrawArrays(color, coords, vertices, image); }
/** * @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); }
/** * @brief Shoot with weapon. * @sa CL_ActorShoot * @sa CL_ActorShootHidden * @todo Improve detection of left- or right animation. * @sa EV_ACTOR_SHOOT */ void CL_ActorDoShoot (const eventRegister_t* self, dbuffer* msg) { vec3_t muzzle, impact; int flags, normal, shooterEntnum, victimEntnum; int objIdx; int first; weaponFireDefIndex_t weapFdsIdx; fireDefIndex_t fdIdx; int surfaceFlags; shoot_types_t shootType; /* read data */ NET_ReadFormat(msg, self->formatString, &shooterEntnum, &victimEntnum, &first, &objIdx, &weapFdsIdx, &fdIdx, &shootType, &flags, &surfaceFlags, &muzzle, &impact, &normal); le_t* leVictim; if (victimEntnum != SKIP_LOCAL_ENTITY) { leVictim = LE_Get(victimEntnum); if (!leVictim) LE_NotFoundError(victimEntnum); } else { leVictim = nullptr; } /* get shooter le */ le_t* leShooter = LE_Get(shooterEntnum); /* get the fire def */ const objDef_t* obj = INVSH_GetItemByIDX(objIdx); const fireDef_t* fd = FIRESH_GetFiredef(obj, weapFdsIdx, fdIdx); CL_ActorGetMuzzle(leShooter, muzzle, shootType); /* add effect le */ LE_AddProjectile(fd, flags, muzzle, impact, normal, leVictim); /* start the sound */ if ((first || !fd->soundOnce) && fd->fireSound != nullptr && !(flags & SF_BOUNCED)) S_LoadAndPlaySample(fd->fireSound, muzzle, fd->fireAttenuation, SND_VOLUME_WEAPONS); /* do actor related stuff */ if (!leShooter) return; /* maybe hidden or inuse is false? */ if (!LE_IsActor(leShooter)) Com_Error(ERR_DROP, "Can't shoot, LE not an actor (type: %i)", leShooter->type); /* no animations for hidden actors */ if (leShooter->type == ET_ACTORHIDDEN) return; if (LE_IsDead(leShooter)) { Com_DPrintf(DEBUG_CLIENT, "Can't shoot, actor dead or stunned.\n"); return; } /* Animate - we have to check if it is right or left weapon usage. */ if (IS_SHOT_RIGHT(shootType)) { R_AnimChange(&leShooter->as, leShooter->model1, LE_GetAnim("shoot", leShooter->right, leShooter->left, leShooter->state)); R_AnimAppend(&leShooter->as, leShooter->model1, LE_GetAnim("stand", leShooter->right, leShooter->left, leShooter->state)); } else if (IS_SHOT_LEFT(shootType)) { R_AnimChange(&leShooter->as, leShooter->model1, LE_GetAnim("shoot", leShooter->left, leShooter->right, leShooter->state)); R_AnimAppend(&leShooter->as, leShooter->model1, LE_GetAnim("stand", leShooter->left, leShooter->right, leShooter->state)); } else if (IS_SHOT_HEADGEAR(shootType)) { if (fd->irgoggles) { leShooter->state |= RF_IRGOGGLESSHOT; if (LE_IsSelected(leShooter)) refdef.rendererFlags |= RDF_IRGOGGLES; } } else { /* no animation for headgear (yet) */ Com_Error(ERR_DROP, "CL_ActorDoShoot: Invalid shootType given (entnum: %i, shootType: %i).\n", shootType, shooterEntnum); } }
/** * @brief Adds an UGV to the render entities. * @param[in] le The local entity the UGV should be created from * @param[out] ent * @sa CL_AddActor */ bool CL_AddUGV (le_t * le, entity_t * ent) { entity_t add; if (!LE_IsDead(le)) { /* add weapon */ if (le->left != NONE) { OBJZERO(add); add.model = cls.modelPool[le->left]; add.tagent = R_GetFreeEntity() + 2 + (le->right != NONE); add.tagname = "tag_lweapon"; R_AddEntity(&add); } /* add weapon */ if (le->right != NONE) { OBJZERO(add); add.alpha = le->alpha; add.model = cls.modelPool[le->right]; add.tagent = R_GetFreeEntity() + 2; add.tagname = "tag_rweapon"; R_AddEntity(&add); } } /* add head */ OBJZERO(add); add.alpha = le->alpha; add.model = le->model2; add.skinnum = le->bodySkin; /** @todo */ add.tagent = R_GetFreeEntity() + 1; add.tagname = "tag_head"; R_AddEntity(&add); /* add actor special effects */ ent->flags |= RF_SHADOW; ent->flags |= RF_ACTOR; if (!LE_IsDead(le)) { if (LE_IsSelected(le)) ent->flags |= RF_SELECTED; if (le->team == cls.team) { if (le->pnum == cl.pnum) ent->flags |= RF_MEMBER; if (le->pnum != cl.pnum) ent->flags |= RF_ALLIED; } if (le->team == TEAM_CIVILIAN) ent->flags |= RF_NEUTRAL; } return true; }
/** * @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); } } }