cl_actor.c

Go to the documentation of this file.
00001 
00006 /*
00007 Copyright (C) 2002-2010 UFO: Alien Invasion.
00008 
00009 This program is free software; you can redistribute it and/or
00010 modify it under the terms of the GNU General Public License
00011 as published by the Free Software Foundation; either version 2
00012 of the License, or (at your option) any later version.
00013 
00014 This program is distributed in the hope that it will be useful,
00015 but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018 See the GNU General Public License for more details.
00019 
00020 You should have received a copy of the GNU General Public License
00021 along with this program; if not, write to the Free Software
00022 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00023 
00024 */
00025 
00026 #include "../client.h"
00027 #include "cl_actor.h"
00028 #include "../cl_game.h"
00029 #include "cl_hud.h"
00030 #include "cl_parse.h"
00031 #include "cl_particle.h"
00032 #include "../cl_team.h"
00033 #include "cl_view.h"
00034 #include "../ui/ui_main.h"
00035 #include "../ui/ui_popup.h"
00036 #include "../ui/node/ui_node_container.h"
00037 #include "../renderer/r_entity.h"
00038 #include "../renderer/r_mesh_anim.h"
00039 #include "../sound/s_main.h"
00040 #include "../../common/routing.h"
00041 #include "../../common/grid.h"
00042 
00044 static cvar_t *confirm_actions;
00046 static cvar_t *cl_autostand;
00047 static cvar_t *cl_showactors;
00048 static cvar_t *cl_reactionleftover;
00049 
00050 /* public */
00051 le_t *selActor;
00052 pos3_t truePos; 
00053 pos3_t mousePos; 
00063 static int mousePosTargettingAlign = 0;
00064 
00065 static le_t *mouseActor;
00066 static pos3_t mouseLastPos;
00067 
00073 void MSG_Write_PA (player_action_t playerAction, int entnum, ...)
00074 {
00075     va_list ap;
00076     struct dbuffer *msg = new_dbuffer();
00077 
00078     va_start(ap, entnum);
00079     NET_WriteFormat(msg, "bbs", clc_action, playerAction, entnum);
00080     NET_vWriteFormat(msg, pa_format[playerAction], ap);
00081     va_end(ap);
00082     NET_WriteMsg(cls.netStream, msg);
00083 }
00084 
00085 /*
00086 ==============================================================
00087 ACTOR MENU UPDATING
00088 ==============================================================
00089 */
00090 
00096 const char *CL_ActorGetSkillString (const int skill)
00097 {
00098     const int skillLevel = skill * 10 / MAX_SKILL;
00099 #ifdef DEBUG
00100     if (skill > MAX_SKILL) {
00101         Com_Printf("CL_GetSkillString: Skill is bigger than max allowed skill value (%i/%i)\n", skill, MAX_SKILL);
00102     }
00103 #endif
00104     switch (skillLevel) {
00105     case 0:
00106         return _("Poor");
00107     case 1:
00108         return _("Mediocre");
00109     case 2:
00110         return _("Average");
00111     case 3:
00112         return _("Competent");
00113     case 4:
00114         return _("Proficient");
00115     case 5:
00116         return _("Very Good");
00117     case 6:
00118         return _("Highly Proficient");
00119     case 7:
00120         return _("Excellent");
00121     case 8:
00122         return _("Outstanding");
00123     case 9:
00124     case 10:
00125         return _("Superhuman");
00126     default:
00127         Com_Printf("CL_GetSkillString: Unknown skill: %i (index: %i)\n", skill, skillLevel);
00128         return "";
00129     }
00130 }
00131 
00132 void CL_ActorSetFireDef (le_t *actor, const fireDef_t *fd)
00133 {
00134     if (actor->fd != fd)
00135         mousePosTargettingAlign = 0;
00136     actor->fd = fd;
00137 }
00138 
00144 int CL_ActorMoveMode (const le_t *le, int length)
00145 {
00146     assert(le);
00147     if (LE_IsCrouched(le)) {
00148         if (cl_autostand->integer) { /* Is the player using autostand? */
00149             if (SHOULD_USE_AUTOSTAND(length)) {
00150                 return WALKTYPE_AUTOSTAND_BEING_USED;
00151             } else {
00152                 return WALKTYPE_AUTOSTAND_BUT_NOT_FAR_ENOUGH;
00153             }
00154         } else {
00155             return WALKTYPE_CROUCH_WALKING;
00156         }
00157     } else {
00158         return WALKTYPE_WALKING;
00159     }
00160 }
00161 
00167 static int CL_ActorGetNumber (const le_t * le)
00168 {
00169     int actorIdx;
00170 
00171     assert(le);
00172 
00173     for (actorIdx = 0; actorIdx < cl.numTeamList; actorIdx++) {
00174         if (cl.teamList[actorIdx] == le)
00175             return actorIdx;
00176     }
00177     return -1;
00178 }
00179 
00186 character_t *CL_ActorGetChr (const le_t * le)
00187 {
00188     const int actorIdx = CL_ActorGetNumber(le);
00189     if (actorIdx == -1) {
00190         Com_DPrintf(DEBUG_CLIENT, "CL_GetActorChr: Could not find actor in the team list - maybe already dead? (state is %i)!\n",
00191                 le->state);
00192         return NULL;
00193     }
00194 
00195     return cl.chrList.chr[actorIdx];
00196 }
00197 
00205 void CL_ActorSetRFMode (character_t *chr, actorHands_t hand, int fireModeIndex, const objDef_t *weapon)
00206 {
00207     chr->RFmode.hand = hand;
00208     chr->RFmode.fmIdx = fireModeIndex;
00209     chr->RFmode.weapon = weapon;
00210 }
00211 
00219 void CL_ActorSetShotSettings (character_t *chr, actorHands_t hand, int fireModeIndex, const objDef_t *weapon)
00220 {
00221     chr->reservedTus.shotSettings.hand = hand;
00222     chr->reservedTus.shotSettings.fmIdx = fireModeIndex;
00223     chr->reservedTus.shotSettings.weapon = weapon;
00224 }
00225 
00233 int CL_ActorReservedTUs (const le_t * le, const reservation_types_t type)
00234 {
00235     character_t *chr;
00236     int reservedReaction, reservedCrouch, reservedShot;
00237 
00238     if (!le)
00239         return -1;
00240 
00241     chr = CL_ActorGetChr(le);
00242     if (!chr) {
00243         Com_DPrintf(DEBUG_CLIENT, "CL_ActorReservedTUs: No character found for le.\n");
00244         return -1;
00245     }
00246 
00247     reservedReaction = max(0, chr->reservedTus.reaction);
00248     reservedCrouch = max(0, chr->reservedTus.crouch);
00249     reservedShot = max(0, chr->reservedTus.shot);
00250 
00251     switch (type) {
00252     case RES_ALL:
00253         /* A summary of ALL TUs that are reserved. */
00254         return reservedReaction + reservedCrouch + reservedShot;
00255     case RES_ALL_ACTIVE: {
00256         /* A summary of ALL TUs that are reserved depending on their "status". */
00257         /* Only use reaction-value if we are have RF activated. */
00258         if ((le->state & STATE_REACTION))
00259             return reservedReaction + reservedShot + reservedCrouch;
00260         else
00261             return reservedShot + reservedCrouch;
00262     }
00263     case RES_REACTION:
00264         return reservedReaction;
00265     case RES_CROUCH:
00266         return reservedCrouch;
00267     case RES_SHOT:
00268         return reservedShot;
00269     default:
00270         Com_DPrintf(DEBUG_CLIENT, "CL_ActorReservedTUs: Bad type given: %i\n", type);
00271         return -1;
00272     }
00273 }
00274 
00281 int CL_ActorUsableTUs (const le_t * le)
00282 {
00283     if (!le)
00284         return -1;
00285 
00286     return le->TU - CL_ActorReservedTUs(le, RES_ALL_ACTIVE);
00287 }
00288 
00295 void CL_ActorReserveTUs (const le_t * le, const reservation_types_t type, const int tus)
00296 {
00297     character_t *chr;
00298 
00299     assert(type != RES_REACTION);
00300 
00301     if (!le || tus < 0)
00302         return;
00303 
00304     chr = CL_ActorGetChr(le);
00305     if (chr) {
00306         chrReservations_t res = chr->reservedTus;
00307 
00308         if (type == RES_CROUCH)
00309             res.crouch = tus;
00310         else if (type == RES_SHOT)
00311             res.shot = tus;
00312 
00313         MSG_Write_PA(PA_RESERVE_STATE, le->entnum, res.shot, res.crouch);
00314     }
00315 }
00316 
00317 /*
00318 ==============================================================
00319 ACTOR SELECTION AND TEAM LIST
00320 ==============================================================
00321 */
00322 
00329 void CL_ActorAddToTeamList (le_t * le)
00330 {
00331     int actorIdx;
00332     const size_t size = lengthof(cl.teamList);
00333 
00334     /* test team */
00335     if (!le || le->team != cls.team || le->pnum != cl.pnum || LE_IsDead(le))
00336         return;
00337 
00338     /* check list for that actor */
00339     actorIdx = CL_ActorGetNumber(le);
00340 
00341     /* add it */
00342     if (actorIdx == -1) {
00343         /* check list length */
00344         if (cl.numTeamList >= size) {
00345             Com_Printf("Too many actors on the teamlist\n");
00346             return;
00347         }
00348         actorIdx = cl.numTeamList;
00349         le->pathMap = Mem_PoolAlloc(sizeof(*le->pathMap), cl_genericPool, 0);
00350         cl.teamList[cl.numTeamList] = le;
00351         UI_ExecuteConfunc("hudenable %i", cl.numTeamList);
00352         cl.numTeamList++;
00353         if (cl.numTeamList == 1)
00354             CL_ActorSelectList(0);
00355     } else {
00356         UI_ExecuteConfunc("hudenable %i", actorIdx);
00357     }
00358 }
00359 
00360 void CL_ActorCleanup (le_t *le)
00361 {
00362     if (le->pathMap)
00363         Mem_Free(le->pathMap);
00364     le->pathMap = NULL;
00365     cls.i.DestroyInventory(&cls.i, &le->i);
00366 }
00367 
00374 void CL_ActorRemoveFromTeamList (le_t * le)
00375 {
00376     int i;
00377 
00378     if (!le)
00379         return;
00380 
00381     for (i = 0; i < cl.numTeamList; i++) {
00382         if (cl.teamList[i] == le) {
00383             if (!LE_IsStunned(le)) {
00384                 CL_ActorCleanup(le);
00385                 /* remove from list */
00386                 cl.teamList[i] = NULL;
00387             } else {
00389                 le->left = le->right = le->extension = le->headgear = NONE;
00390                 cls.i.DestroyInventory(&cls.i, &le->i);
00391             }
00392 
00393             /* disable hud button */
00394             UI_ExecuteConfunc("huddisable %i", i);
00395 
00396             break;
00397         }
00398     }
00399 
00400     /* check selection */
00401     if (le->selected) {
00402         for (i = 0; i < cl.numTeamList; i++) {
00403             if (cl.teamList[i] && CL_ActorSelect(cl.teamList[i]))
00404                 break;
00405         }
00406 
00407         if (i == cl.numTeamList)
00408             CL_ActorSelect(NULL);
00409     }
00410 }
00411 
00418 qboolean CL_ActorSelect (le_t * le)
00419 {
00420     int actorIdx;
00421     character_t* chr;
00422 
00423     /* test team */
00424     if (!le) {
00425         if (selActor)
00426             selActor->selected = qfalse;
00427         selActor = NULL;
00428         ui_inventory = NULL;
00429         return qfalse;
00430     }
00431 
00432     if (le->team != cls.team || LE_IsDead(le) || !le->inuse)
00433         return qfalse;
00434 
00435     if (le->selected) {
00436         mousePosTargettingAlign = 0;
00437         return qtrue;
00438     }
00439 
00440     if (selActor)
00441         selActor->selected = qfalse;
00442 
00444     HUD_HideFiremodes();
00445 
00446     mousePosTargettingAlign = 0;
00447     selActor = le;
00448     selActor->selected = qtrue;
00449     ui_inventory = &selActor->i;
00450 
00451     actorIdx = CL_ActorGetNumber(le);
00452     if (actorIdx == -1)
00453         return qfalse;
00454 
00455     /* console commands, update cvars */
00456     Cvar_ForceSet("cl_selected", va("%i", actorIdx));
00457 
00458     chr = CL_ActorGetChr(le);
00459     if (!chr)
00460         Com_Error(ERR_DROP, "No character given for local entity");
00461 
00462     CL_UpdateCharacterValues(chr, "mn_");
00463 
00464     CL_ActorConditionalMoveCalc(le);
00465 
00466     return qtrue;
00467 }
00468 
00480 qboolean CL_ActorSelectList (int num)
00481 {
00482     le_t *le;
00483 
00484     /* check if actor exists */
00485     if (num >= cl.numTeamList || num < 0)
00486         return qfalse;
00487 
00488     /* select actor */
00489     le = cl.teamList[num];
00490     if (!le || !CL_ActorSelect(le))
00491         return qfalse;
00492 
00493     /* center view (if wanted) */
00494     LE_CenterView(le);
00495     Cvar_SetValue("cl_worldlevel", le->pos[2]);
00496 
00497     return qtrue;
00498 }
00499 
00503 qboolean CL_ActorSelectNext (void)
00504 {
00505     int selIndex = -1;
00506     const int num = cl.numTeamList;
00507     int i;
00508 
00509     /* find index of currently selected actor */
00510     for (i = 0; i < num; i++) {
00511         const le_t *le = cl.teamList[i];
00512         if (le && le->selected && le->inuse && !LE_IsDead(le)) {
00513             selIndex = i;
00514             break;
00515         }
00516     }
00517     if (selIndex < 0)
00518         return qfalse;          /* no one selected? */
00519 
00520     /* cycle round */
00521     i = selIndex;
00522     while (qtrue) {
00523         i = (i + 1) % num;
00524         if (i == selIndex)
00525             break;
00526         if (CL_ActorSelectList(i))
00527             return qtrue;
00528     }
00529     return qfalse;
00530 }
00531 
00532 
00533 /*
00534 ==============================================================
00535 ACTOR MOVEMENT AND SHOOTING
00536 ==============================================================
00537 */
00538 
00544 static pos_t *forbiddenList[MAX_FORBIDDENLIST];
00550 static int forbiddenListLength;
00551 
00560 static void CL_BuildForbiddenList (void)
00561 {
00562     le_t *le = NULL;
00563 
00564     forbiddenListLength = 0;
00565 
00566     while ((le = LE_GetNextInUse(le))) {
00567         if (le->invis)
00568             continue;
00569         /* Dead ugv will stop walking, too. */
00570         if (le->type == ET_ACTOR2x2 || (!LE_IsStunned(le) && LE_IsLivingAndVisibleActor(le))) {
00571             forbiddenList[forbiddenListLength++] = le->pos;
00572             forbiddenList[forbiddenListLength++] = (byte*)&le->fieldSize;
00573         }
00574     }
00575 
00576 #ifdef PARANOID
00577     if (forbiddenListLength > MAX_FORBIDDENLIST)
00578         Com_Error(ERR_DROP, "CL_BuildForbiddenList: list too long");
00579 #endif
00580 }
00581 
00582 #ifdef DEBUG
00583 
00590 static void CL_DisplayBlockedPaths_f (void)
00591 {
00592     le_t *le = NULL;
00593     int j;
00594     ptl_t *ptl;
00595     vec3_t s;
00596 
00597     while ((le = LE_GetNextInUse(le))) {
00598         switch (le->type) {
00599         case ET_ACTOR:
00600         case ET_ACTOR2x2:
00601             /* draw blocking cursor at le->pos */
00602             if (!LE_IsDead(le))
00603                 Grid_PosToVec(cl.mapData->map, le->fieldSize, le->pos, s);
00604             break;
00605         case ET_DOOR:
00606         case ET_BREAKABLE:
00607         case ET_ROTATING:
00608             VectorCopy(le->origin, s);
00609             break;
00610         default:
00611             continue;
00612         }
00613 
00614         ptl = CL_ParticleSpawn("blocked_field", 0, s, NULL, NULL);
00615         ptl->rounds = 2;
00616         ptl->roundsCnt = 2;
00617         ptl->life = 10000;
00618         ptl->t = 0;
00619         if (le->fieldSize == ACTOR_SIZE_2x2) {
00620             /* If this actor blocks 4 fields draw them as well. */
00621             for (j = 0; j < 3; j++) {
00622                 ptl_t *ptl2 = CL_ParticleSpawn("blocked_field", 0, s, NULL, NULL);
00623                 ptl2->rounds = ptl->rounds;
00624                 ptl2->roundsCnt = ptl->roundsCnt;
00625                 ptl2->life = ptl->life;
00626                 ptl2->t = ptl->t;
00627             }
00628         }
00629     }
00630 }
00631 #endif
00632 
00639 void CL_ActorConditionalMoveCalc (le_t *le)
00640 {
00641     CL_BuildForbiddenList();
00642     if (le && le->selected) {
00643         const byte crouchingState = LE_IsCrouched(le) ? 1 : 0;
00644         Grid_MoveCalc(cl.mapData->map, le->fieldSize, le->pathMap, le->pos, crouchingState, MAX_ROUTE, forbiddenList, forbiddenListLength);
00645         CL_ActorResetMoveLength(le);
00646     }
00647 }
00648 
00654 int CL_ActorCheckAction (const le_t *le)
00655 {
00656     if (!le)
00657         return qfalse;
00658 
00659     /* already moving */
00660     if (le->pathLength)
00661         return qfalse;
00662 
00663     if (cls.team != cl.actTeam) {
00664         HUD_DisplayMessage(_("This isn't your round\n"));
00665         return qfalse;
00666     }
00667 
00668     return qtrue;
00669 }
00670 
00679 static byte CL_ActorMoveLength (const le_t *le, const pos3_t to)
00680 {
00681     const byte crouchingState = LE_IsCrouched(le) ? 1 : 0;
00682     const byte length = Grid_MoveLength(le->pathMap, to, crouchingState, qfalse);
00683     return length;
00684 }
00685 
00690 void CL_ActorResetMoveLength (le_t *le)
00691 {
00692     le->actorMoveLength = CL_ActorMoveLength(le, mousePos);
00693 }
00694 
00702 static qboolean CL_ActorTraceMove (const pos3_t to)
00703 {
00704     byte length;
00705     vec3_t vec, oldVec;
00706     pos3_t pos;
00707     int dv;
00708     byte crouchingState;
00709 
00710     if (!selActor)
00711         return qfalse;
00712 
00713     length = CL_ActorMoveLength(selActor, to);
00714     if (!length || length >= ROUTING_NOT_REACHABLE)
00715         return qfalse;
00716 
00717     crouchingState = LE_IsCrouched(selActor) ? 1 : 0;
00718 
00719     Grid_PosToVec(cl.mapData->map, selActor->fieldSize, to, oldVec);
00720     VectorCopy(to, pos);
00721 
00722     Com_DPrintf(DEBUG_PATHING, "Starting pos: (%i, %i, %i).\n", pos[0], pos[1], pos[2]);
00723 
00724     while ((dv = Grid_MoveNext(selActor->pathMap, pos, crouchingState)) != ROUTING_UNREACHABLE) {
00725         length = CL_ActorMoveLength(selActor, pos);
00726         PosSubDV(pos, crouchingState, dv); /* We are going backwards to the origin. */
00727         Com_DPrintf(DEBUG_PATHING, "Next pos: (%i, %i, %i, %i) [%i].\n", pos[0], pos[1], pos[2], crouchingState, dv);
00728         Grid_PosToVec(cl.mapData->map, selActor->fieldSize, pos, vec);
00729         if (length > CL_ActorUsableTUs(selActor))
00730             CL_ParticleSpawn("longRangeTracer", 0, vec, oldVec, NULL);
00731         else if (crouchingState)
00732             CL_ParticleSpawn("crawlTracer", 0, vec, oldVec, NULL);
00733         else
00734             CL_ParticleSpawn("moveTracer", 0, vec, oldVec, NULL);
00735         VectorCopy(vec, oldVec);
00736     }
00737     return qtrue;
00738 }
00739 
00747 static void CL_ActorMaximumMove (const pos3_t to, const le_t *le, pos3_t pos)
00748 {
00749     int dv;
00750     byte crouchingState = le && LE_IsCrouched(le) ? 1 : 0;
00751     const int tus = CL_ActorUsableTUs(le);
00752     const byte length = CL_ActorMoveLength(le, to);
00753     if (!length || length >= ROUTING_NOT_REACHABLE)
00754         return;
00755 
00756     VectorCopy(to, pos);
00757 
00758     while ((dv = Grid_MoveNext(le->pathMap, pos, crouchingState)) != ROUTING_UNREACHABLE) {
00759         const byte length2 = CL_ActorMoveLength(le, pos);
00760         if (length2 <= tus)
00761             return;
00762         PosSubDV(pos, crouchingState, dv); /* We are going backwards to the origin. */
00763     }
00764 }
00765 
00766 void CL_ActorSetMode (le_t *actor, actorModes_t actorMode)
00767 {
00768     actor->actorMode = actorMode;
00769 }
00770 
00778 void CL_ActorStartMove (le_t * le, const pos3_t to)
00779 {
00780     byte length;
00781     pos3_t toReal;
00782 
00783     if (mouseSpace != MS_WORLD)
00784         return;
00785 
00786     if (!CL_ActorCheckAction(le))
00787         return;
00788 
00789     length = CL_ActorMoveLength(le, to);
00790 
00791     if (!length || length >= ROUTING_NOT_REACHABLE) {
00792         /* move not valid, don't even care to send */
00793         return;
00794     }
00795 
00796     /* Get the last position we can walk to with the usable TUs. */
00797     CL_ActorMaximumMove(to, le, toReal);
00798 
00799     /* Get the cost of the new position just in case. */
00800     length = CL_ActorMoveLength(le, toReal);
00801 
00802     if (CL_ActorUsableTUs(le) < length) {
00803         /* We do not have enough _usable_ TUs to move so don't even try to send. */
00804         /* This includes a check for reserved TUs (which isn't done on the server!) */
00805         return;
00806     }
00807 
00808     /* change mode to move now */
00809     CL_ActorSetMode(le, M_MOVE);
00810 
00811     /* move seems to be possible; send request to server */
00812     MSG_Write_PA(PA_MOVE, le->entnum, toReal);
00813 }
00814 
00815 
00821 void CL_ActorShoot (const le_t * le, const pos3_t at)
00822 {
00823     int type;
00824 
00825     if (mouseSpace != MS_WORLD)
00826         return;
00827 
00828     if (!CL_ActorCheckAction(le))
00829         return;
00830 
00831     if (IS_MODE_FIRE_RIGHT(le->actorMode)) {
00832         type = ST_RIGHT;
00833     } else if (IS_MODE_FIRE_LEFT(le->actorMode)) {
00834         type = ST_LEFT;
00835     } else if (IS_MODE_FIRE_HEADGEAR(le->actorMode)) {
00836         type = ST_HEADGEAR;
00837     } else
00838         return;
00839 
00840     MSG_Write_PA(PA_SHOOT, le->entnum, at, type, le->currentSelectedFiremode, mousePosTargettingAlign);
00841 }
00842 
00850 int CL_ActorGetContainerForReload (invList_t **invList, const inventory_t *inv, const objDef_t *weapon)
00851 {
00852     containerIndex_t container;
00853     int tu = 100;
00854     containerIndex_t bestContainer = NONE;
00855 
00856     /* also search the linked ground floor tile (temp container) */
00857     for (container = 0; container < csi.numIDs; container++) {
00858         if (INVDEF(container)->out < tu) {
00859             invList_t *ic;
00860             /* Once we've found at least one clip, there's no point
00861              * searching other containers if it would take longer
00862              * to retrieve the ammo from them than the one
00863              * we've already found. */
00864             for (ic = inv->c[container]; ic; ic = ic->next) {
00865                 objDef_t *od = ic->item.t;
00866                 if (INVSH_LoadableInWeapon(od, weapon) && GAME_ItemIsUseable(od)) {
00867                     tu = INVDEF(container)->out;
00868                     bestContainer = container;
00869                     *invList = ic;
00870                     break;
00871                 }
00872             }
00873         }
00874     }
00875     return bestContainer;
00876 }
00877 
00884 void CL_ActorReload (le_t *le, containerIndex_t containerID)
00885 {
00886     inventory_t *inv;
00887     invList_t *ic;
00888     objDef_t *weapon;
00889     containerIndex_t bestContainer;
00890 
00891     if (!CL_ActorCheckAction(le))
00892         return;
00893 
00894     /* check weapon */
00895     inv = &le->i;
00896 
00897     if (inv->c[containerID]) {
00898         weapon = inv->c[containerID]->item.t;
00899     } else if (containerID == csi.idLeft && inv->c[csi.idRight]->item.t->holdTwoHanded) {
00900         /* Check for two-handed weapon */
00901         containerID = csi.idRight;
00902         weapon = inv->c[containerID]->item.t;
00903     } else
00904         /* otherwise we could use weapon uninitialized */
00905         return;
00906 
00907     if (!weapon)
00908         return;
00909 
00910     /* return if the weapon is not reloadable */
00911     if (!weapon->reload)
00912         return;
00913 
00914     if (!GAME_ItemIsUseable(weapon)) {
00915         HUD_DisplayMessage(_("You cannot reload this unknown item.\n"));
00916         return;
00917     }
00918 
00919     bestContainer = CL_ActorGetContainerForReload(&ic, inv, weapon);
00920     /* send request */
00921     if (bestContainer != NONE) {
00922         int x, y;
00923 
00924         INVSH_GetFirstShapePosition(ic, &x, &y);
00925         x += ic->x;
00926         y += ic->y;
00927 
00928         CL_ActorInvMove(le, bestContainer, x, y, containerID, 0, 0);
00929     }
00930 }
00931 
00942 void CL_ActorInvMove (const le_t *le, containerIndex_t fromContainer, int fromX, int fromY, containerIndex_t toContainer, int toX, int toY)
00943 {
00944     assert(CL_BattlescapeRunning());
00945     assert(le);
00946     assert(LE_IsActor(le));
00947     MSG_Write_PA(PA_INVMOVE, le->entnum, fromContainer, fromX, fromY, toContainer, toX, toY);
00948 }
00949 
00955 static void CL_ActorUseDoor (const le_t *le)
00956 {
00957     if (!CL_ActorCheckAction(le))
00958         return;
00959 
00960     assert(le->clientAction);
00961 
00962     MSG_Write_PA(PA_USE_DOOR, le->entnum, le->clientAction->entnum);
00963     Com_DPrintf(DEBUG_CLIENT, "CL_ActorUseDoor: Use door number: %i (actor %i)\n", le->clientAction->entnum, le->entnum);
00964 }
00965 
00969 void CL_ActorDoorAction_f (void)
00970 {
00971     if (!CL_ActorCheckAction(selActor))
00972         return;
00973 
00974     /* no client action */
00975     if (selActor->clientAction == NULL) {
00976         Com_DPrintf(DEBUG_CLIENT, "CL_ActorDoorAction_f: No client_action set for actor with entnum %i\n", selActor->entnum);
00977         return;
00978     }
00979 
00980     /* Check if we should even try to send this command (no TUs left or). */
00981     if (CL_ActorUsableTUs(selActor) >= TU_DOOR_ACTION)
00982         CL_ActorUseDoor(selActor);
00983 }
00984 
00990 qboolean CL_ActorFireModeActivated (const actorModes_t mode)
00991 {
00992     return IS_MODE_FIRE_RIGHT(mode) || IS_MODE_FIRE_LEFT(mode) || IS_MODE_FIRE_HEADGEAR(mode);
00993 }
00994 
00998 void CL_ActorTurnMouse (void)
00999 {
01000     vec3_t div;
01001     byte dv;
01002 
01003     if (mouseSpace != MS_WORLD)
01004         return;
01005 
01006     if (!CL_ActorCheckAction(selActor))
01007         return;
01008 
01009     if (CL_ActorUsableTUs(selActor) < TU_TURN) {
01010         /* Cannot turn because of not enough usable TUs. */
01011         return;
01012     }
01013 
01014     /* check for fire-modes, and cancel them */
01015     if (CL_ActorFireModeActivated(selActor->actorMode)) {
01016         CL_ActorActionMouse();
01017         return; /* and return without turning */
01018     }
01019 
01020     /* calculate dv */
01021     VectorSubtract(mousePos, selActor->pos, div);
01022     dv = AngleToDV((int) (atan2(div[1], div[0]) * todeg));
01023 
01024     /* send message to server */
01025     MSG_Write_PA(PA_TURN, selActor->entnum, dv);
01026 }
01027 
01031 static void CL_ActorStandCrouch_f (void)
01032 {
01033     if (!CL_ActorCheckAction(selActor))
01034         return;
01035 
01036     /* Check if we should even try to send this command (no TUs left or). */
01037     if (CL_ActorUsableTUs(selActor) >= TU_CROUCH || CL_ActorReservedTUs(selActor, RES_CROUCH) >= TU_CROUCH) {
01038         /* send a request to toggle crouch to the server */
01039         MSG_Write_PA(PA_STATE, selActor->entnum, STATE_CROUCHED);
01040     }
01041 }
01042 
01046 static void CL_ActorUseHeadgear_f (void)
01047 {
01048     invList_t* headgear;
01049     const int tmpMouseSpace = mouseSpace;
01050 
01051     /* this can be executed by a click on a hud button
01052      * but we need MS_WORLD mouse space to let the shooting
01053      * function work */
01054     mouseSpace = MS_WORLD;
01055 
01056     if (!CL_ActorCheckAction(selActor))
01057         return;
01058 
01059     headgear = HEADGEAR(selActor);
01060     if (!headgear)
01061         return;
01062 
01063     CL_ActorSetMode(selActor, M_FIRE_HEADGEAR);
01065     selActor->currentSelectedFiremode = 0;
01066     CL_ActorShoot(selActor, selActor->pos);
01067     CL_ActorSetMode(selActor, M_MOVE);
01068 
01069     /* restore old mouse space */
01070     mouseSpace = tmpMouseSpace;
01071 }
01072 
01073 /*
01074 ==============================================================
01075 MOUSE INPUT
01076 ==============================================================
01077 */
01078 
01084 static void CL_ActorMoveMouse (void)
01085 {
01086     /* Don't display the cursor if it's above the currently selected level.
01087      * The 2nd part of the if is an attempt to display it anyway when we eg. climb a hill.
01088      * But there are too many situations inside buildings that match the criteria (eg. actorclip around chair).
01089      * So disabled for now.*/
01090     if (mousePos[2] > cl_worldlevel->integer/* && !RT_AllCellsBelowAreFilled(cl.mapData->map, fieldSize, pos)*/)
01091         return;
01092 
01093     if (selActor->actorMode == M_PEND_MOVE) {
01094         if (VectorCompare(mousePos, selActor->mousePendPos)) {
01095             /* Pending move and clicked the same spot (i.e. 2 clicks on the same place) */
01096             CL_ActorStartMove(selActor, mousePos);
01097         } else {
01098             /* Clicked different spot. */
01099             VectorCopy(mousePos, selActor->mousePendPos);
01100         }
01101     } else {
01102         /* either we want to confirm every move, or it's not our round and we prepare the
01103          * movement for the next round */
01104         if (confirm_actions->integer || cls.team != cl.actTeam) {
01105             /* Set our mode to pending move. */
01106             VectorCopy(mousePos, selActor->mousePendPos);
01107 
01108             CL_ActorSetMode(selActor, M_PEND_MOVE);
01109         } else {
01110             /* Just move there */
01111             CL_ActorStartMove(selActor, mousePos);
01112         }
01113     }
01114 }
01115 
01120 void CL_ActorSelectMouse (void)
01121 {
01122     if (mouseSpace != MS_WORLD || !selActor)
01123         return;
01124 
01125     switch (selActor->actorMode) {
01126     case M_MOVE:
01127     case M_PEND_MOVE:
01128         /* Try and select another team member */
01129         if (mouseActor && !mouseActor->selected && CL_ActorSelect(mouseActor)) {
01130             /* Succeeded so go back into move mode. */
01131             CL_ActorSetMode(selActor, M_MOVE);
01132         } else {
01133             CL_ActorMoveMouse();
01134         }
01135         break;
01136     case M_PEND_FIRE_R:
01137     case M_PEND_FIRE_L:
01138         if (VectorCompare(mousePos, selActor->mousePendPos)) {
01139             /* Pending shot and clicked the same spot (i.e. 2 clicks on the same place) */
01140             CL_ActorShoot(selActor, mousePos);
01141 
01142             /* We switch back to aiming mode. */
01143             if (selActor->actorMode == M_PEND_FIRE_R)
01144                 CL_ActorSetMode(selActor, M_FIRE_R);
01145             else
01146                 CL_ActorSetMode(selActor, M_FIRE_L);
01147         } else {
01148             /* Clicked different spot. */
01149             VectorCopy(mousePos, selActor->mousePendPos);
01150         }
01151         break;
01152     case M_FIRE_R:
01153         if (mouseActor && mouseActor->selected)
01154             break;
01155 
01156         /* We either switch to "pending" fire-mode or fire the gun. */
01157         if (confirm_actions->integer == 1) {
01158             CL_ActorSetMode(selActor, M_PEND_FIRE_R);
01159             VectorCopy(mousePos, selActor->mousePendPos);
01160         } else {
01161             CL_ActorShoot(selActor, mousePos);
01162         }
01163         break;
01164     case M_FIRE_L:
01165         if (mouseActor && mouseActor->selected)
01166             break;
01167 
01168         /* We either switch to "pending" fire-mode or fire the gun. */
01169         if (confirm_actions->integer == 1) {
01170             CL_ActorSetMode(selActor, M_PEND_FIRE_L);
01171             VectorCopy(mousePos, selActor->mousePendPos);
01172         } else {
01173             CL_ActorShoot(selActor, mousePos);
01174         }
01175         break;
01176     default:
01177         break;
01178     }
01179 }
01180 
01181 
01187 void CL_ActorActionMouse (void)
01188 {
01189     if (!selActor || mouseSpace != MS_WORLD)
01190         return;
01191 
01192     if (CL_ActorFireModeActivated(selActor->actorMode)) {
01193         CL_ActorSetMode(selActor, M_MOVE);
01194     } else {
01195         CL_ActorMoveMouse();
01196     }
01197 }
01198 
01199 /*
01200 ==============================================================
01201 MOUSE SCANNING
01202 ==============================================================
01203 */
01204 
01210 static le_t* CL_ActorSearchAtGridPos (const pos3_t pos, qboolean includingStunned)
01211 {
01212     le_t *le;
01213 
01214     /* search for an actor on this field */
01215     le = NULL;
01216     while ((le = LE_GetNextInUse(le))) {
01217         if (LE_IsLivingAndVisibleActor(le) && (includingStunned || !LE_IsStunned(le)))
01218             switch (le->fieldSize) {
01219             case ACTOR_SIZE_NORMAL:
01220                 if (VectorCompare(le->pos, pos))
01221                     return le;
01222                 break;
01223             case ACTOR_SIZE_2x2: {
01224                 pos3_t actor2x2[3];
01225 
01226                 VectorSet(actor2x2[0], le->pos[0] + 1, le->pos[1],     le->pos[2]);
01227                 VectorSet(actor2x2[1], le->pos[0],     le->pos[1] + 1, le->pos[2]);
01228                 VectorSet(actor2x2[2], le->pos[0] + 1, le->pos[1] + 1, le->pos[2]);
01229                 if (VectorCompare(le->pos, pos)
01230                 || VectorCompare(actor2x2[0], pos)
01231                 || VectorCompare(actor2x2[1], pos)
01232                 || VectorCompare(actor2x2[2], pos))
01233                     return le;
01234                 break;
01235             }
01236             default:
01237                 Com_Error(ERR_DROP, "Grid_MoveCalc: unknown actor-size: %i", le->fieldSize);
01238         }
01239     }
01240 
01241     return NULL;
01242 }
01243 
01249 qboolean CL_ActorMouseTrace (void)
01250 {
01251     int restingLevel;
01252     float cur[2], frustumSlope[2];
01253     const float projectionDistance = 2048.0f;
01254     float nDotP2minusP1;
01255     vec3_t forward, right, up, stop;
01256     vec3_t from, end;
01257     vec3_t mapNormal, P3, P2minusP1;
01258     vec3_t pA, pB, pC;
01259     pos3_t testPos;
01260 
01261     /* get cursor position as a -1 to +1 range for projection */
01262     cur[0] = (mousePosX * viddef.rx - viddef.viewWidth * 0.5 - viddef.x) / (viddef.viewWidth * 0.5);
01263     cur[1] = (mousePosY * viddef.ry - viddef.viewHeight * 0.5 - viddef.y) / (viddef.viewHeight * 0.5);
01264 
01265     /* get trace vectors */
01266     VectorCopy(cl.cam.camorg, from);
01267     VectorCopy(cl.cam.axis[0], forward);
01268     VectorCopy(cl.cam.axis[1], right);
01269     VectorCopy(cl.cam.axis[2], up);
01270 
01271     if (cl_isometric->integer)
01272         frustumSlope[0] = 10.0 * refdef.fieldOfViewX;
01273     else
01274         frustumSlope[0] = tan(refdef.fieldOfViewX * (M_PI / 360.0)) * projectionDistance;
01275     frustumSlope[1] = frustumSlope[0] * ((float)viddef.viewHeight / (float)viddef.viewWidth);
01276 
01277     /* transform cursor position into perspective space */
01278     VectorMA(from, projectionDistance, forward, stop);
01279     VectorMA(stop, cur[0] * frustumSlope[0], right, stop);
01280     VectorMA(stop, cur[1] * -frustumSlope[1], up, stop);
01281 
01282     /* in isometric mode the camera position has to be calculated from the cursor position so that the trace goes in the right direction */
01283     if (cl_isometric->integer)
01284         VectorMA(stop, -projectionDistance * 2, forward, from);
01285 
01286     /* set stop point to the intersection of the trace line with the desired plane */
01287     /* description of maths used:
01288      *   The equation for the plane can be written:
01289      *     mapNormal dot (end - P3) = 0
01290      *     where mapNormal is the vector normal to the plane,
01291      *         P3 is any point on the plane and
01292      *         end is the point where the line intersects the plane
01293      *   All points on the line can be calculated using:
01294      *     P1 + u*(P2 - P1)
01295      *     where P1 and P2 are points that define the line and
01296      *           u is some scalar
01297      *   The intersection of the line and plane occurs when:
01298      *     mapNormal dot (P1 + u*(P2 - P1)) == mapNormal dot P3
01299      *   The intersection therefore occurs when:
01300      *     u = (mapNormal dot (P3 - P1))/(mapNormal dot (P2 - P1))
01301      * Note: in the code below from & stop represent P1 and P2 respectively
01302      */
01303     VectorSet(P3, 0., 0., cl_worldlevel->integer * UNIT_HEIGHT + CURSOR_OFFSET);
01304     VectorSet(mapNormal, 0., 0., 1.);
01305     VectorSubtract(stop, from, P2minusP1);
01306     nDotP2minusP1 = DotProduct(mapNormal, P2minusP1);
01307 
01308     /* calculate intersection directly if angle is not parallel to the map plane */
01309     if (nDotP2minusP1 > 0.01 || nDotP2minusP1 < -0.01) {
01310         float u;
01311         vec3_t dir, P3minusP1;
01312 
01313         VectorSubtract(P3, from, P3minusP1);
01314         u = DotProduct(mapNormal, P3minusP1) / nDotP2minusP1;
01315         VectorScale(P2minusP1, (vec_t)u, dir);
01316         VectorAdd(from, dir, end);
01317     } else { /* otherwise do a full trace */
01318         CM_EntTestLineDM(cl.mapTiles, from, stop, end, TL_FLAG_ACTORCLIP, cl.leInlineModelList);
01319     }
01320 
01321     VecToPos(end, testPos);
01322 
01323     /* hack to prevent cursor from getting stuck on the top of an invisible
01324      * playerclip surface (in most cases anyway) */
01325     PosToVec(testPos, pA);
01326     /* ensure that the cursor is in the world, if this is not done, the tracer box is
01327      * drawn in the void on the first level and the menu key bindings might get executed
01328      * this could result in different problems like the zooming issue (where you can't zoom
01329      * in again, because in_zoomout->state is not reseted). */
01330     if (CL_OutsideMap(pA, MAP_SIZE_OFFSET))
01331         return qfalse;
01332 
01333     VectorCopy(pA, pB);
01334     pA[2] += UNIT_HEIGHT;
01335     pB[2] -= UNIT_HEIGHT;
01339     restingLevel = Grid_Fall(cl.mapData->map, ACTOR_GET_FIELDSIZE(selActor), testPos);
01340     CM_EntTestLineDM(cl.mapTiles, pA, pB, pC, TL_FLAG_ACTORCLIP, cl.leInlineModelList);
01341     VecToPos(pC, testPos);
01342     restingLevel = min(restingLevel, Grid_Fall(cl.mapData->map, ACTOR_GET_FIELDSIZE(selActor), testPos));
01343 
01344     /* if grid below intersection level, start a trace from the intersection */
01345     if (restingLevel < cl_worldlevel->integer) {
01346         VectorCopy(end, from);
01347         from[2] -= CURSOR_OFFSET;
01348         CM_EntTestLineDM(cl.mapTiles, from, stop, end, TL_FLAG_ACTORCLIP, cl.leInlineModelList);
01349         VecToPos(end, testPos);
01350         restingLevel = Grid_Fall(cl.mapData->map, ACTOR_GET_FIELDSIZE(selActor), testPos);
01351     }
01352 
01353     /* test if the selected grid is out of the world */
01354     if (restingLevel < 0 || restingLevel >= PATHFINDING_HEIGHT)
01355         return qfalse;
01356 
01357     /* Set truePos- test pos is under the cursor. */
01358     VectorCopy(testPos, truePos);
01359     truePos[2] = cl_worldlevel->integer;
01360 
01361     /* Set mousePos to the position that the actor will move to. */
01362     testPos[2] = restingLevel;
01363     VectorCopy(testPos, mousePos);
01364 
01365     mouseActor = CL_ActorSearchAtGridPos(mousePos, qfalse);
01366 
01367     /* calculate move length */
01368     if (selActor && !VectorCompare(mousePos, mouseLastPos)) {
01369         VectorCopy(mousePos, mouseLastPos);
01370         CL_ActorResetMoveLength(selActor);
01371     }
01372 
01373     return qtrue;
01374 }
01375 
01376 /*
01377 ==============================================================
01378 ACTOR GRAPHICS
01379 ==============================================================
01380 */
01381 
01388 static inline qboolean CL_AddActorWeapon (int objID)
01389 {
01390     if (objID != NONE) {
01391         const objDef_t *od = INVSH_GetItemByIDX(objID);
01392         if (od->isVirtual)
01393             return qfalse;
01394         return qtrue;
01395     }
01396     return qfalse;
01397 }
01398 
01408 qboolean CL_AddActor (le_t * le, entity_t * ent)
01409 {
01410     entity_t add;
01411 
01412     if (!cl_showactors->integer)
01413         return qfalse;
01414 
01415     if (LE_IsStunned(le)) {
01416         CL_ParticleSpawn("stunnedactor", 0, le->origin, NULL, NULL);
01417     } else if (!LE_IsDead(le)) {
01418         /* add the weapons to the actor's hands */
01419         const qboolean addLeftHandWeapon = CL_AddActorWeapon(le->left);
01420         const qboolean addRightHandWeapon = CL_AddActorWeapon(le->right);
01421         /* add left hand weapon */
01422         if (addLeftHandWeapon) {
01423             memset(&add, 0, sizeof(add));
01424 
01425             add.model = cls.modelPool[le->left];
01426             if (!add.model)
01427                 Com_Error(ERR_DROP, "Actor model for left hand weapon wasn't found");
01428 
01429             /* point to the body ent which will be added last */
01430             add.tagent = R_GetFreeEntity() + 2 + addRightHandWeapon;
01431             add.tagname = "tag_lweapon";
01432 
01433             R_AddEntity(&add);
01434         }
01435 
01436         /* add right hand weapon */
01437         if (addRightHandWeapon) {
01438             memset(&add, 0, sizeof(add));
01439 
01440             add.alpha = le->alpha;
01441             add.model = cls.modelPool[le->right];
01442             if (!add.model)
01443                 Com_Error(ERR_DROP, "Actor model for right hand weapon wasn't found");
01444 
01445             /* point to the body ent which will be added last */
01446             add.tagent = R_GetFreeEntity() + 2;
01447             add.tagname = "tag_rweapon";
01448 
01449             R_AddEntity(&add);
01450         }
01451     }
01452 
01453     /* add head */
01454     memset(&add, 0, sizeof(add));
01455 
01456     add.alpha = le->alpha;
01457     add.model = le->model2;
01458     if (!add.model)
01459         Com_Error(ERR_DROP, "Actor model wasn't found");
01460     add.skinnum = le->skinnum;
01461 
01462     /* point to the body ent which will be added last */
01463     add.tagent = R_GetFreeEntity() + 1;
01464     add.tagname = "tag_head";
01465 
01466     if (le->team != cls.team)
01467         add.flags |= RF_IRGOGGLES;
01468 
01469     R_AddEntity(&add);
01470 
01474     if (LE_IsStunned(le) && le->HP <= le->maxHP / 2)
01475         ent->flags |= RF_BLOOD;
01476     else if (LE_IsDead(le))
01477         ent->flags |= RF_BLOOD;
01478     else
01479         ent->flags |= RF_SHADOW;
01480 
01481     ent->flags |= RF_ACTOR;
01482     /* actors are highlighted if some other actor uses ir goggles */
01483     if (le->team != cls.team)
01484         ent->flags |= RF_IRGOGGLES;
01485 
01486     if (!LE_IsDead(le) && !LE_IsStunned(le)) {
01487         if (le->selected)
01488             ent->flags |= RF_SELECTED;
01489         if (le->team == cls.team) {
01490             if (le->pnum == cl.pnum)
01491                 ent->flags |= RF_MEMBER;
01492             if (le->pnum != cl.pnum)
01493                 ent->flags |= RF_ALLIED;
01494         }
01495     }
01496 
01497     if (ent->flags & RF_BLOOD) {
01498         const char *deathTextureName;
01499         assert(le->teamDef != NULL);
01500         deathTextureName = le->teamDef->deathTextureName;
01501         ent->deathTexture = R_FindImage(deathTextureName, it_effect);
01502     }
01503 
01504     return qtrue;
01505 }
01506 
01507 /*
01508 ==============================================================
01509 TARGETING GRAPHICS
01510 ==============================================================
01511 */
01512 
01518 static void CL_TargetingRadius (const vec3_t center, const float radius)
01519 {
01520     ptl_t *particle = CL_ParticleSpawn("circle", 0, center, NULL, NULL);
01521     particle->size[0] = radius;
01522 }
01523 
01534 static void CL_TargetingStraight (const pos3_t fromPos, actorSizeEnum_t fromActorSize, const pos3_t toPos)
01535 {
01536     vec3_t start, end;
01537     vec3_t dir, mid, temp;
01538     trace_t tr;
01539     qboolean crossNo;
01540     le_t *target = NULL;
01541     actorSizeEnum_t toActorSize;
01542 
01543     if (!selActor || !selActor->fd)
01544         return;
01545 
01546     /* search for an actor at target */
01547     target = CL_ActorSearchAtGridPos(toPos, qtrue);
01548 
01549     /* Determine the target's size. */
01550     toActorSize = target
01551         ? target->fieldSize
01552         : ACTOR_SIZE_NORMAL;
01553 
01554     Grid_PosToVec(cl.mapData->map, fromActorSize, fromPos, start);
01555     Grid_PosToVec(cl.mapData->map, toActorSize, toPos, end);
01556     if (mousePosTargettingAlign)
01557         end[2] -= mousePosTargettingAlign;
01558 
01559     /* calculate direction */
01560     VectorSubtract(end, start, dir);
01561     VectorNormalize(dir);
01562 
01563     /* calculate 'out of range point' if there is one */
01564     if (VectorDistSqr(start, end) > selActor->fd->range * selActor->fd->range) {
01565         VectorMA(start, selActor->fd->range, dir, mid);
01566         crossNo = qtrue;
01567     } else {
01568         VectorCopy(end, mid);
01569         crossNo = qfalse;
01570     }
01571 
01572     VectorMA(start, UNIT_SIZE * 1.4, dir, temp);
01573     /* switch up to top level, this is needed to make sure our trace doesn't go through ceilings ... */
01574     tr = CL_Trace(start, temp, vec3_origin, vec3_origin, selActor, NULL, MASK_SHOT, cl.mapMaxLevel - 1);
01575     if (tr.le && (tr.le->team == cls.team || LE_IsCivilian(tr.le)) && LE_IsCrouched(tr.le))
01576         VectorMA(start, UNIT_SIZE * 1.4, dir, temp);
01577     else
01578         VectorCopy(start, temp);
01579 
01580     tr = CL_Trace(temp, mid, vec3_origin, vec3_origin, selActor, target, MASK_SHOT, cl.mapMaxLevel - 1);
01581 
01582     if (tr.fraction < 1.0) {
01583         const float d = VectorDist(temp, mid);
01584         VectorMA(start, tr.fraction * d, dir, mid);
01585         crossNo = qtrue;
01586     }
01587 
01588     /* spawn particles */
01589     CL_ParticleSpawn("inRangeTracer", 0, start, mid, NULL);
01590     if (crossNo) {
01591         CL_ParticleSpawn("longRangeTracer", 0, mid, end, NULL);
01592         CL_ParticleSpawn("cross_no", 0, end, NULL, NULL);
01593     } else {
01594         CL_ParticleSpawn("cross", 0, end, NULL, NULL);
01595     }
01596 }
01597 
01598 
01599 #define GRENADE_PARTITIONS  20
01600 
01608 static void CL_TargetingGrenade (const pos3_t fromPos, actorSizeEnum_t fromActorSize, const pos3_t toPos)
01609 {
01610     vec3_t from, at, cross;
01611     float vz, dt;
01612     vec3_t v0, ds, next;
01613     trace_t tr;
01614     qboolean obstructed = qfalse;
01615     int i;
01616     le_t *target = NULL;
01617     actorSizeEnum_t toActorSize;
01618 
01619     if (!selActor || !selActor->fd || Vector2Compare(fromPos, toPos))
01620         return;
01621 
01622     /* search for an actor at target */
01623     target = CL_ActorSearchAtGridPos(toPos, qtrue);
01624 
01625     /* Determine the target's size. */
01626     toActorSize = target
01627         ? target->fieldSize
01628         : ACTOR_SIZE_NORMAL;
01629 
01630     /* get vectors, paint cross */
01631     Grid_PosToVec(cl.mapData->map, fromActorSize, fromPos, from);
01632     Grid_PosToVec(cl.mapData->map, toActorSize, toPos, at);
01633     from[2] += selActor->fd->shotOrg[1];
01634 
01635     /* prefer to aim grenades at the ground */
01636     at[2] -= GROUND_DELTA;
01637     if (mousePosTargettingAlign)
01638         at[2] -= mousePosTargettingAlign;
01639     VectorCopy(at, cross);
01640 
01641     /* calculate parabola */
01642     dt = Com_GrenadeTarget(from, at, selActor->fd->range, selActor->fd->launched, selActor->fd->rolled, v0);
01643     if (!dt) {
01644         CL_ParticleSpawn("cross_no", 0, cross, NULL, NULL);
01645         return;
01646     }
01647 
01648     dt /= GRENADE_PARTITIONS;
01649     VectorSubtract(at, from, ds);
01650     VectorScale(ds, 1.0 / GRENADE_PARTITIONS, ds);
01651     ds[2] = 0;
01652 
01653     /* paint */
01654     vz = v0[2];
01655 
01656     for (i = 0; i < GRENADE_PARTITIONS; i++) {
01657         VectorAdd(from, ds, next);
01658         next[2] += dt * (vz - 0.5 * GRAVITY * dt);
01659         vz -= GRAVITY * dt;
01660         VectorScale(v0, (i + 1.0) / GRENADE_PARTITIONS, at);
01661 
01662         /* trace for obstacles. Switch up to top level, to make sure our trace
01663          * doesn't go through ceilings ... */
01664         tr = CL_Trace(from, next, vec3_origin, vec3_origin, selActor, target, MASK_SHOT, cl.mapMaxLevel - 1);
01665 
01666         /* something was hit */
01667         if (tr.fraction < 1.0) {
01668             obstructed = qtrue;
01669         }
01670 
01671         /* draw particles */
01674         if (obstructed || VectorLength(at) > selActor->fd->range)
01675             CL_ParticleSpawn("longRangeTracer", 0, from, next, NULL);
01676         else
01677             CL_ParticleSpawn("inRangeTracer", 0, from, next, NULL);
01678         VectorCopy(next, from);
01679     }
01680     /* draw targeting cross */
01681     if (obstructed || VectorLength(at) > selActor->fd->range)
01682         CL_ParticleSpawn("cross_no", 0, cross, NULL, NULL);
01683     else
01684         CL_ParticleSpawn("cross", 0, cross, NULL, NULL);
01685 
01686     if (selActor->fd->splrad) {
01687         Grid_PosToVec(cl.mapData->map, toActorSize, toPos, at);
01688         CL_TargetingRadius(at, selActor->fd->splrad);
01689     }
01690 }
01691 
01696 static const vec3_t boxSize = { BOX_DELTA_WIDTH, BOX_DELTA_LENGTH, BOX_DELTA_HEIGHT };
01697 #define BoxSize(i,source,target) (target[0]=i*source[0]+((i-1)*UNIT_SIZE),target[1]=i*source[1]+((i-1)*UNIT_SIZE),target[2]=source[2])
01698 #define BoxOffset(i, target) (target[0]=(i-1)*(UNIT_SIZE+BOX_DELTA_WIDTH), target[1]=(i-1)*(UNIT_SIZE+BOX_DELTA_LENGTH), target[2]=0)
01699 
01705 static void CL_AddTargetingBox (pos3_t pos, qboolean pendBox)
01706 {
01707     entity_t ent;
01708     vec3_t realBoxSize;
01709     vec3_t cursorOffset;
01710 
01711     if (!cl_showactors->integer)
01712         return;
01713 
01714     memset(&ent, 0, sizeof(ent));
01715     ent.flags = RF_BOX;
01716 
01717     Grid_PosToVec(cl.mapData->map, ACTOR_GET_FIELDSIZE(selActor), pos, ent.origin);
01718 
01719     /* Paint the green box if move is possible ...
01720      * OR paint a dark blue one if move is impossible or the
01721      * soldier does not have enough TimeUnits left. */
01722     if (selActor && selActor->actorMoveLength < ROUTING_NOT_REACHABLE
01723      && selActor->actorMoveLength <= CL_ActorUsableTUs(selActor))
01724         VectorSet(ent.color, 0, 1, 0); /* Green */
01725     else
01726         VectorSet(ent.color, 0.6, 0.68, 1); /* Light Blue */
01727 
01728     VectorAdd(ent.origin, boxSize, ent.oldorigin);
01729 
01730     /* color */
01731     if (mouseActor && !mouseActor->selected) {
01732         ent.alpha = 0.6 + 0.2 * sin((float) cl.time / 80);
01733         /* Paint the box red if the soldiers under the cursor is
01734          * not in our team and no civilian either. */
01735         if (mouseActor->team != cls.team) {
01736             switch (mouseActor->team) {
01737             case TEAM_CIVILIAN:
01738                 /* Civilians are yellow */
01739                 VectorSet(ent.color, 1, 1, 0); /* Yellow */
01740                 break;
01741             default:
01742                 if (LE_IsAlien(mouseActor)) {
01743                     if (GAME_TeamIsKnown(mouseActor->teamDef))
01744                         UI_RegisterText(TEXT_MOUSECURSOR_PLAYERNAMES, _(mouseActor->teamDef->name));
01745                     else
01746                         UI_RegisterText(TEXT_MOUSECURSOR_PLAYERNAMES, _("Unknown alien race"));
01747                 } else {
01748                     /* multiplayer names */
01749                     /* see CL_ParseClientinfo */
01750                     UI_RegisterText(TEXT_MOUSECURSOR_PLAYERNAMES, CL_PlayerGetName(mouseActor->pnum));
01751                 }
01752                 /* Aliens (and players not in our team [multiplayer]) are red */
01753                 VectorSet(ent.color, 1, 0, 0); /* Red */
01754                 break;
01755             }
01756         } else {
01757             /* coop multiplayer games */
01758             if (mouseActor->pnum != cl.pnum) {
01759                 UI_RegisterText(TEXT_MOUSECURSOR_PLAYERNAMES, CL_PlayerGetName(mouseActor->pnum));
01760             } else {
01761                 /* we know the names of our own actors */
01762                 character_t* chr = CL_ActorGetChr(mouseActor);
01763                 assert(chr);
01764                 UI_RegisterText(TEXT_MOUSECURSOR_PLAYERNAMES, chr->name);
01765             }
01766             /* Paint a light blue box if on our team */
01767             VectorSet(ent.color, 0.2, 0.3, 1); /* Light Blue */
01768         }
01769 
01770         if (selActor) {
01771             BoxOffset(selActor->fieldSize, cursorOffset);
01772             VectorAdd(ent.oldorigin, cursorOffset, ent.oldorigin);
01773             VectorAdd(ent.origin, cursorOffset, ent.origin);
01774             BoxSize(selActor->fieldSize, boxSize, realBoxSize);
01775             VectorSubtract(ent.origin, realBoxSize, ent.origin);
01776         }
01777     } else {
01778         if (selActor) {
01779             BoxOffset(selActor->fieldSize, cursorOffset);
01780             VectorAdd(ent.oldorigin, cursorOffset, ent.oldorigin);
01781             VectorAdd(ent.origin, cursorOffset, ent.origin);
01782 
01783             BoxSize(selActor->fieldSize, boxSize, realBoxSize);
01784             VectorSubtract(ent.origin, realBoxSize, ent.origin);
01785         } else {
01786             VectorSubtract(ent.origin, boxSize, ent.origin);
01787         }
01788         ent.alpha = 0.3;
01789     }
01790 
01791     /* if pendBox is true then ignore all the previous color considerations and use cyan */
01792     if (pendBox) {
01793         VectorSet(ent.color, 0, 1, 1); /* Cyan */
01794         ent.alpha = 0.15;
01795     }
01796 
01797     /* add it */
01798     R_AddEntity(&ent);
01799 }
01800 
01805 void CL_ActorTargetAlign_f (void)
01806 {
01807     int align = GROUND_DELTA;
01808     static int currentPos = 0;
01809 
01810     /* no firedef selected */
01811     if (!selActor || !selActor->fd)
01812         return;
01813     if (!CL_ActorFireModeActivated(selActor->actorMode))
01814         return;
01815 
01816     /* user defined height align */
01817     if (Cmd_Argc() == 2) {
01818         align = atoi(Cmd_Argv(1));
01819     } else {
01820         switch (currentPos) {
01821         case 0:
01822             if (selActor->fd->gravity)
01823                 align = -align;
01824             currentPos = 1; /* next level */
01825             break;
01826         case 1:
01827             /* only allow to align to lower z level if the actor is
01828              * standing at a higher z-level */
01829             if (selActor->fd->gravity)
01830                 align = -(2 * align);
01831             else
01832                 align = -align;
01833             currentPos = 2;
01834             break;
01835         case 2:
01836             /* the static var is not reseted on weaponswitch or actorswitch */
01837             if (selActor->fd->gravity) {
01838                 align = 0;
01839                 currentPos = 0; /* next level */
01840             } else {
01841                 align = -(2 * align);
01842                 currentPos = 3; /* next level */
01843             }
01844             break;
01845         case 3:
01846             align = 0;
01847             currentPos = 0; /* back to start */
01848             break;
01849         }
01850     }
01851     mousePosTargettingAlign = align;
01852 }
01853 
01863 void CL_AddTargeting (void)
01864 {
01865     if (mouseSpace != MS_WORLD || !selActor)
01866         return;
01867 
01868     switch (selActor->actorMode) {
01869     case M_MOVE:
01870     case M_PEND_MOVE:
01871         /* Don't display the cursor if it's above the currently selected level.
01872          * The 2nd part of the if is an attempt to display it anyway when we eg. climb a hill.
01873          * But there are too many situations inside buildings that match the criteria (eg. actorclip around chair).
01874          * So disabled for now.*/
01875         if (mousePos[2] > cl_worldlevel->integer/* && !RT_AllCellsBelowAreFilled(cl.mapData->map, fieldSize, pos)*/)
01876             return;
01877 
01878         /* Display Move-cursor. */
01879         CL_AddTargetingBox(mousePos, qfalse);
01880 
01881         if (selActor->actorMode == M_PEND_MOVE) {
01882             /* Also display a box for the pending move if we have one. */
01883             CL_AddTargetingBox(selActor->mousePendPos, qtrue);
01884             if (!CL_ActorTraceMove(selActor->mousePendPos))
01885                 CL_ActorSetMode(selActor, M_MOVE);
01886         }
01887         break;
01888     case M_FIRE_R:
01889     case M_FIRE_L:
01890         if (!selActor->fd)
01891             return;
01892 
01893         if (!selActor->fd->gravity)
01894             CL_TargetingStraight(selActor->pos, selActor->fieldSize, mousePos);
01895         else
01896             CL_TargetingGrenade(selActor->pos, selActor->fieldSize, mousePos);
01897         break;
01898     case M_PEND_FIRE_R:
01899     case M_PEND_FIRE_L:
01900         if (!selActor->fd)
01901             return;
01902 
01903         /* Draw cursor at mousepointer */
01904         CL_AddTargetingBox(mousePos, qfalse);
01905 
01906         /* Draw (pending) Cursor at target */
01907         CL_AddTargetingBox(selActor->mousePendPos, qtrue);
01908 
01909         if (!selActor->fd->gravity)
01910             CL_TargetingStraight(selActor->pos, selActor->fieldSize, selActor->mousePendPos);
01911         else
01912             CL_TargetingGrenade(selActor->pos, selActor->fieldSize, selActor->mousePendPos);
01913         break;
01914     default:
01915         break;
01916     }
01917 }
01918 
01919 static const vec3_t boxShift = { PLAYER_WIDTH, PLAYER_WIDTH, UNIT_HEIGHT / 2 - DIST_EPSILON };
01920 
01925 static void CL_AddPathingBox (pos3_t pos)
01926 {
01927     if (selActor) {
01928         entity_t ent;
01929         int height; /* The total opening size */
01930         int base; /* The floor relative to this cell */
01931 
01932         const byte crouchingState = LE_IsCrouched(selActor) ? 1 : 0;
01933         const int TUneed = Grid_MoveLength(selActor->pathMap, pos, crouchingState, qfalse);
01934         const int TUhave = CL_ActorUsableTUs(selActor);
01935 
01936         memset(&ent, 0, sizeof(ent));
01937         ent.flags = RF_PATH;
01938 
01939         Grid_PosToVec(cl.mapData->map, ACTOR_GET_FIELDSIZE(selActor), pos, ent.origin);
01940         VectorSubtract(ent.origin, boxShift, ent.origin);
01941 
01942         base = Grid_Floor(cl.mapData->map, ACTOR_GET_FIELDSIZE(selActor), pos);
01943 
01944         /* Paint the box green if it is reachable,
01945          * yellow if it can be entered but is too far,
01946          * or red if it cannot be entered ever. */
01947         if (base < -QuantToModel(PATHFINDING_MAX_FALL)) {
01948             VectorSet(ent.color, 0.0, 0.0, 0.0); /* Can't enter - black */
01949         } else {
01950             /* Can reach - green
01951              * Passable but unreachable - yellow
01952              * Not passable - red */
01953             VectorSet(ent.color, (TUneed > TUhave), (TUneed != ROUTING_NOT_REACHABLE), 0);
01954         }
01955 
01956         /* Set the box height to the ceiling value of the cell. */
01957         height = 2 + min(TUneed * (UNIT_HEIGHT - 2) / ROUTING_NOT_REACHABLE, 16);
01958         ent.oldorigin[2] = height;
01959         ent.oldorigin[0] = TUneed;
01960         ent.oldorigin[1] = TUhave;
01961 
01962         ent.alpha = 0.25;
01963 
01964         /* add it */
01965         R_AddEntity(&ent);
01966     }
01967 }
01968 
01974 void CL_AddPathing (void)
01975 {
01976     pos3_t pos;
01977 
01978     pos[2] = cl_worldlevel->integer;
01979     for (pos[1] = max(mousePos[1] - 8, 0); pos[1] <= min(mousePos[1] + 8, PATHFINDING_WIDTH - 1); pos[1]++)
01980         for (pos[0] = max(mousePos[0] - 8, 0); pos[0] <= min(mousePos[0] + 8, PATHFINDING_WIDTH - 1); pos[0]++)
01981             CL_AddPathingBox(pos);
01982 }
01983 
01989 void CL_ActorPlaySound (const le_t *le, actorSound_t soundType)
01990 {
01991     const char *actorSound = Com_GetActorSound(le->teamDef, le->gender, soundType);
01992     if (actorSound) {
01993         s_sample_t *sample = S_LoadSample(actorSound);
01994         if (sample) {
01995             Com_DPrintf(DEBUG_SOUND|DEBUG_CLIENT, "CL_PlayActorSound: ActorSound: '%s'\n", actorSound);
01996             S_PlaySample(le->origin, sample, SOUND_ATTN_IDLE, SND_VOLUME_DEFAULT);
01997         }
01998     }
01999 }
02000 
02004 static void CL_AddArrow (vec3_t from, vec3_t to, float red, float green, float blue)
02005 {
02006     entity_t ent;
02007 
02008     /* Com_Printf("Adding arrow (%f, %f, %f) to (%f, %f, %f).\n", from[0], from[1], from[2], to[0], to[1], to[2]); */
02009 
02010     memset(&ent, 0, sizeof(ent));
02011     ent.flags = RF_ARROW;
02012     VectorCopy(from, ent.origin);
02013     VectorCopy(to, ent.oldorigin);
02014     VectorSet(ent.color, red, green, blue);
02015 
02016     ent.alpha = 0.25;
02017 
02018     /* add it */
02019     R_AddEntity(&ent);
02020 }
02021 
02025 void CL_DisplayFloorArrows (void)
02026 {
02027     vec3_t base, start;
02028 
02029     Grid_PosToVec(cl.mapData->map, ACTOR_GET_FIELDSIZE(selActor), truePos, base);
02030     VectorCopy(base, start);
02031     base[2] -= QUANT;
02032     start[2] += QUANT;
02033     CL_AddArrow(base, start, 0.0, 0.0, 0.0);
02034 }
02035 
02039 void CL_DisplayObstructionArrows (void)
02040 {
02041     vec3_t base, start;
02042 
02043     Grid_PosToVec(cl.mapData->map, ACTOR_GET_FIELDSIZE(selActor), truePos, base);
02044     VectorCopy(base, start);
02045     CL_AddArrow(base, start, 0.0, 0.0, 0.0);
02046 }
02047 
02048 #ifdef DEBUG
02049 
02052 static void CL_DumpMoveMark_f (void)
02053 {
02054     const byte crouchingState = selActor ? (LE_IsCrouched(selActor) ? 1 : 0) : 0;
02055     const int temp = developer->integer;
02056 
02057     if (!selActor)
02058         return;
02059 
02060     developer->integer |= DEBUG_PATHING;
02061 
02062     CL_BuildForbiddenList();
02063     Grid_MoveCalc(cl.mapData->map, ACTOR_GET_FIELDSIZE(selActor), selActor->pathMap, truePos, crouchingState, MAX_ROUTE, forbiddenList, forbiddenListLength);
02064 
02065     developer->integer ^= DEBUG_PATHING;
02066 
02067     CL_ActorConditionalMoveCalc(selActor);
02068     developer->integer = temp;
02069 }
02070 
02075 static void CL_DumpTUs_f (void)
02076 {
02077     int x, y, crouchingState;
02078     pos3_t pos, loc;
02079 
02080     if (!selActor)
02081         return;
02082 
02083     crouchingState = LE_IsCrouched(selActor) ? 1 : 0;
02084     VectorCopy(selActor->pos, pos);
02085 
02086     Com_Printf("TUs around (%i, %i, %i)\n", pos[0], pos[1], pos[2]);
02087 
02088     for (y = max(0, pos[1] - 8); y <= min(PATHFINDING_WIDTH, pos[1] + 8); y++) {
02089         for (x = max(0, pos[0] - 8); x <= min(PATHFINDING_WIDTH, pos[0] + 8); x++) {
02090             VectorSet(loc, x, y, pos[2]);
02091             Com_Printf("%3i ", Grid_MoveLength(selActor->pathMap, loc, crouchingState, qfalse));
02092         }
02093         Com_Printf("\n");
02094     }
02095     Com_Printf("TUs at (%i, %i, %i) = %i\n", pos[0], pos[1], pos[2], Grid_MoveLength(selActor->pathMap, pos, crouchingState, qfalse));
02096 }
02097 
02103 static void CL_DebugPathDisplay (actorSizeEnum_t actorSize, int x, int y, int z)
02104 {
02105     Com_Printf("data at cursor XYZ(%i, %i, %i) Floor(%i) Ceiling(%i)\n", x, y, z,
02106         RT_FLOOR(cl.mapData->map, actorSize, x, y, z),
02107         RT_CEILING(cl.mapData->map, actorSize, x, y, z) );
02108     Com_Printf("connections ortho: (PX=%i, NX=%i, PY=%i, NY=%i))\n",
02109         RT_CONN_PX(cl.mapData->map, actorSize, x, y, z),        /* dir = 0 */
02110         RT_CONN_NX(cl.mapData->map, actorSize, x, y, z),        /* 1 */
02111         RT_CONN_PY(cl.mapData->map, actorSize, x, y, z),        /* 2 */
02112         RT_CONN_NY(cl.mapData->map, actorSize, x, y, z) );  /* 3 */
02113     Com_Printf("connections diago: (PX_PY=%i, NX_NY=%i, NX_PY=%i, PX_NY=%i))\n",
02114         RT_CONN_PX_PY(cl.mapData->map, actorSize, x, y, z), /* dir = 4 */
02115         RT_CONN_NX_NY(cl.mapData->map, actorSize, x, y, z), /* 5 */
02116         RT_CONN_NX_PY(cl.mapData->map, actorSize, x, y, z), /* 6 */
02117         RT_CONN_PX_NY(cl.mapData->map, actorSize, x, y, z) );   /* 7 */
02118     Com_Printf("stepup ortho: (PX=%i, NX=%i, PY=%i, NY=%i))\n",
02119         RT_STEPUP_PX(cl.mapData->map, actorSize, x, y, z),      /* dir = 0 */
02120         RT_STEPUP_NX(cl.mapData->map, actorSize, x, y, z),      /* 1 */
02121         RT_STEPUP_PY(cl.mapData->map, actorSize, x, y, z),      /* 2 */
02122         RT_STEPUP_NY(cl.mapData->map, actorSize, x, y, z) );        /* 3 */
02123 }
02124 
02125 static void CL_DebugPath_f (void)
02126 {
02127     const actorSizeEnum_t actorSize = ACTOR_SIZE_NORMAL;
02128     const pos_t x = mousePos[0];
02129     const pos_t y = mousePos[1];
02130     const pos_t z = mousePos[2];
02131     int dir;
02132     dir = 3;
02133 
02134     if (mouseSpace != MS_WORLD)
02135         return;
02136 
02137     CL_DebugPathDisplay(actorSize, x, y, z);
02138 }
02139 #endif
02140 
02141 
02145 static void CL_ActorNext_f (void)
02146 {
02147     if (CL_BattlescapeRunning()) {
02148         CL_ActorSelectNext();
02149     }
02150 }
02151 
02152 static void CL_ActorEquipmentSelect_f (void)
02153 {
02154     int num;
02155     character_t *chr;
02156 
02157     /* check syntax */
02158     if (Cmd_Argc() < 2) {
02159         Com_Printf("Usage: %s <num>\n", Cmd_Argv(0));
02160         return;
02161     }
02162 
02163     num = atoi(Cmd_Argv(1));
02164     if (num < 0 || num >= chrDisplayList.num)
02165         return;
02166 
02167     chr = chrDisplayList.chr[num];
02168     /* update menu inventory */
02169     if (ui_inventory && ui_inventory != &chr->i) {
02170         CONTAINER(chr, csi.idEquip) = ui_inventory->c[csi.idEquip];
02171         /* set 'old' idEquip to NULL */
02172         ui_inventory->c[csi.idEquip] = NULL;
02173     }
02174     ui_inventory = &chr->i;
02175 
02176     /* deselect current selected soldier and select the new one */
02177     UI_ExecuteConfunc("equipdeselect %i", cl_selected->integer);
02178     UI_ExecuteConfunc("equipselect %i", num);
02179 
02180     /* now set the cl_selected cvar to the new actor id */
02181     Cvar_ForceSet("cl_selected", va("%i", num));
02182     Cvar_SetValue("mn_ucn", chrDisplayList.chr[num]->ucn);
02183 
02184     /* set info cvars */
02185     CL_UpdateCharacterValues(chr, "mn_");
02186 }
02187 
02191 static void CL_ActorSelect_f (void)
02192 {
02193     /* check syntax */
02194     if (Cmd_Argc() < 2) {
02195         Com_Printf("Usage: %s <num>\n", Cmd_Argv(0));
02196         return;
02197     }
02198 
02199     /* check whether we are connected (tactical mission) */
02200     if (CL_BattlescapeRunning()) {
02201         const int num = atoi(Cmd_Argv(1));
02202         CL_ActorSelectList(num);
02203     }
02204 }
02205 
02209 static void CL_ActorUpdate_f (void)
02210 {
02211     const int num = cl_selected->integer;
02212 
02213     /* We are in the base or multiplayer inventory */
02214     if (num < chrDisplayList.num) {
02215         const character_t *chr = chrDisplayList.chr[num];
02216         CL_UpdateCharacterValues(chr, "mn_");
02217     }
02218 }
02219 
02226 static qboolean CL_ActorVis (const le_t *le, const le_t *check)
02227 {
02228     vec3_t test, dir;
02229     float delta;
02230     int i;
02231     vec3_t from;
02232 
02233     VectorCopy(le->origin, from);
02234 
02235     /* start on eye height */
02236     VectorCopy(check->origin, test);
02237     if (LE_IsDead(check)) {
02238         test[2] += PLAYER_DEAD;
02239         delta = 0;
02240     } else if (LE_IsCrouched(check)) {
02241         test[2] += PLAYER_CROUCH - 2;
02242         delta = (PLAYER_CROUCH - PLAYER_MIN) / 2 - 2;
02243     } else {
02244         test[2] += PLAYER_STAND;
02245         delta = (PLAYER_STAND - PLAYER_MIN) / 2 - 2;
02246     }
02247 
02248     /* side shifting -> better checks */
02249     dir[0] = from[1] - check->origin[1];
02250     dir[1] = check->origin[0] - from[0];
02251     dir[2] = 0;
02252     VectorNormalize(dir);
02253     VectorMA(test, -7, dir, test);
02254 
02255     /* do 3 tests */
02256     for (i = 0; i < 3; i++) {
02257         const trace_t tr = CL_Trace(from, test, vec3_origin, vec3_origin, le, NULL, MASK_SOLID, cl_worldlevel->integer);
02258         /* trace didn't reach the target - something was hit before */
02259         if (tr.fraction < 1.0) {
02260             /* look further down or stop */
02261             if (!delta)
02262                 return qfalse;
02263             VectorMA(test, 7, dir, test);
02264             test[2] -= delta;
02265             continue;
02266         }
02267 
02268         return qtrue;
02269     }
02270 
02271     return qfalse;
02272 }
02273 
02278 static void CL_NextAlienVisibleFromActor_f (void)
02279 {
02280     static int lastAlien = 0;
02281     int i;
02282 
02283     if (!selActor)
02284         return;
02285 
02286     if (lastAlien >= cl.numLEs)
02287         lastAlien = 0;
02288 
02289     i = lastAlien;
02290     do {
02291         const le_t *le;
02292         if (++i >= cl.numLEs)
02293             i = 0;
02294         le = &cl.LEs[i];
02295         if (le->inuse && LE_IsLivingAndVisibleActor(le) && le->team != cls.team
02296          && !LE_IsCivilian(le)) {
02297             if (CL_ActorVis(selActor, le)) {
02298                 lastAlien = i;
02299                 CL_ViewCenterAtGridPosition(le->pos);
02300                 CL_ParticleSpawn("fadeTracer", 0, selActor->origin, le->origin, NULL);
02301                 return;
02302             }
02303         }
02304     } while (i != lastAlien);
02305 }
02306 
02311 static void CL_NextAlien_f (void)
02312 {
02313     static int lastAlien = 0;
02314     int i;
02315 
02316     if (lastAlien >= cl.numLEs)
02317         lastAlien = 0;
02318 
02319     i = lastAlien;
02320     do {
02321         const le_t *le;
02322         if (++i >= cl.numLEs)
02323             i = 0;
02324         le = &cl.LEs[i];
02325         if (le->inuse && LE_IsLivingAndVisibleActor(le) && le->team != cls.team
02326          && le->team != TEAM_CIVILIAN) {
02327             lastAlien = i;
02328             CL_ViewCenterAtGridPosition(le->pos);
02329             return;
02330         }
02331     } while (i != lastAlien);
02332 }
02333 
02338 static void CL_ActorConfirmAction (le_t *le)
02339 {
02340     if (le->team != cl.actTeam)
02341         return;
02342 
02343     /* might be a friendly player controlled actor */
02344     if (le->pnum != cl.pnum)
02345         return;
02346 
02347     switch (le->actorMode) {
02348     case M_PEND_MOVE:
02349         CL_ActorStartMove(le, le->mousePendPos);
02350         break;
02351     case M_PEND_FIRE_R:
02352     case M_PEND_FIRE_L:
02353         CL_ActorShoot(le, le->mousePendPos);
02354         break;
02355     default:
02356         break;
02357     }
02358 }
02359 
02366 static void CL_ActorConfirmAction_f (void)
02367 {
02368     static int time = 0;
02369 
02370     if (time - cl.time < 1000) {
02371         le_t *le = NULL;
02372         while ((le = LE_GetNextInUse(le))) {
02373             if (LE_IsLivingActor(le) && !LE_IsStunned(le) && le->team == cls.team)
02374                 CL_ActorConfirmAction(le);
02375         }
02376     } else {
02377         time = cl.time;
02378         if (!selActor)
02379             return;
02380         CL_ActorConfirmAction(selActor);
02381     }
02382 }
02383 
02384 void ACTOR_InitStartup (void)
02385 {
02386     cl_reactionleftover = Cvar_Get("cl_reactionleftover", "0", CVAR_USERINFO | CVAR_ARCHIVE, "Minimum TU left over by reaction fire");
02387     cl_autostand = Cvar_Get("cl_autostand","1", CVAR_USERINFO | CVAR_ARCHIVE, "Save accidental TU waste by allowing server to autostand before long walks");
02388     confirm_actions = Cvar_Get("confirm_actions", "0", CVAR_ARCHIVE, "Confirm all actions in tactical mode");
02389     cl_showactors = Cvar_Get("cl_showactors", "1", 0, "Show actors on the battlefield");
02390     Cmd_AddCommand("actor_next", CL_ActorNext_f, _("Toggle to next actor"));
02391     Cmd_AddCommand("actor_select", CL_ActorSelect_f, _("Select an actor from list"));
02392     Cmd_AddCommand("actor_updatecurrent", CL_ActorUpdate_f, _("Update an actor"));
02393     Cmd_AddCommand("actor_select_equip", CL_ActorEquipmentSelect_f, "Select an actor in the equipment menu");
02394     Cmd_AddCommand("actor_standcrouch", CL_ActorStandCrouch_f, _("Toggle stand/crounch"));
02395     Cmd_AddCommand("actor_useheadgear", CL_ActorUseHeadgear_f, _("Toggle the headgear"));
02396     Cmd_AddCommand("actor_dooraction", CL_ActorDoorAction_f, _("Opens or closes a door"));
02397     Cmd_AddCommand("actor_confirmaction", CL_ActorConfirmAction_f, _("Confirm the current action"));
02398     Cmd_AddCommand("actor_nextalien", CL_NextAlienVisibleFromActor_f, _("Toggle to next alien visible from selected actor."));
02399 
02400     Cmd_AddCommand("nextalien", CL_NextAlien_f, _("Toggle to next alien"));
02401 
02402 #ifdef DEBUG
02403     Cmd_AddCommand("debug_path", CL_DebugPath_f, "Display routing data for current mouse position.");
02404     Cmd_AddCommand("debug_drawblocked", CL_DisplayBlockedPaths_f, "Draws a marker for all blocked map-positions.");
02405     Cmd_AddCommand("debug_movemark", CL_DumpMoveMark_f, "Triggers Grid_MoveMark in every direction at the current truePos.");
02406     Cmd_AddCommand("debug_tus", CL_DumpTUs_f, "Shows a table of the TUs that would be used by the current actor to move relative to its current location");
02407     Cmd_AddCommand("debug_actorinvlist", NULL, "Shows the inventory list of all actors");
02408 #endif /* DEBUG */
02409 }

Generated by  doxygen 1.6.2