//----------------------------------------------------------------------------- // // $Id$ // //----------------------------------------------------------------------------- // // $Log$ // Revision 1.8 2005/02/15 16:33:39 makro // Tons of updates (entity tree attachment system, UI vectors) // // Revision 1.7 2003/03/28 10:36:03 jbravo // Tweaking the replacement system a bit. Reactionmale now the default model // // Revision 1.6 2002/07/26 22:28:38 jbravo // Fixed the server about menu, made the UI handle illegal models and skins // better. // // Revision 1.5 2002/06/16 20:06:15 jbravo // Reindented all the source files with "indent -kr -ut -i8 -l120 -lc120 -sob -bad -bap" // // Revision 1.4 2002/04/22 18:40:58 makro // Model validation // // Revision 1.3 2002/02/21 20:10:16 jbravo // Converted files back from M$ format and added cvs headers again. // // //----------------------------------------------------------------------------- // Copyright (C) 1999-2000 Id Software, Inc. // // ui_players.c #include "ui_local.h" #define UI_TIMER_GESTURE 2300 #define UI_TIMER_JUMP 1000 #define UI_TIMER_LAND 130 #define UI_TIMER_WEAPON_SWITCH 300 #define UI_TIMER_ATTACK 500 #define UI_TIMER_MUZZLE_FLASH 20 #define UI_TIMER_WEAPON_DELAY 250 #define JUMP_HEIGHT 56 #define SWINGSPEED 0.3f #define SPIN_SPEED 0.9f #define COAST_TIME 1000 static int dp_realtime; static float jumpHeight; sfxHandle_t weaponChangeSound; /* * ============ * COM_StripExtensionInPlace * ============ * */ void COM_StripExtensionInPlace(char *name) { char* ext = strrchr(name, '.'); if (ext) *ext = 0; } /* =============== UI_PlayerInfo_SetWeapon =============== */ static void UI_PlayerInfo_SetWeapon(playerInfo_t * pi, weapon_t weaponNum) { gitem_t *item; char path[MAX_QPATH]; pi->currentWeapon = weaponNum; tryagain: pi->realWeapon = weaponNum; pi->weaponModel = 0; pi->barrelModel = 0; pi->flashModel = 0; if (weaponNum == WP_NONE) { return; } for (item = bg_itemlist + 1; item->classname; item++) { if (item->giType != IT_WEAPON) { continue; } if (item->giTag == weaponNum) { break; } } if (item->classname) { pi->weaponModel = trap_R_RegisterModel(item->world_model[0]); } if (pi->weaponModel == 0) { //Blaze: Changed from WP_MACHINEGUN to WP_PISTOL if (weaponNum == WP_PISTOL) { weaponNum = WP_NONE; goto tryagain; } //Blaze: Changed from WP_MACHINEGUN to WP_PISTOL weaponNum = WP_PISTOL; goto tryagain; } //Blaze: none of our weapons have barrel models /*if ( weaponNum == WP_PISTOL || weaponNum == WP_GAUNTLET || weaponNum == WP_BFG ) { strcpy( path, item->world_model[0] ); COM_StripExtension( path, path ); strcat( path, "_barrel.md3" ); pi->barrelModel = trap_R_RegisterModel( path ); } */ strcpy(path, item->world_model[0]); COM_StripExtensionInPlace(path); strcat(path, "_flash.md3"); pi->flashModel = trap_R_RegisterModel(path); //Blaze: I think this makes some funky colors or something, I dont really know ;) //Blaze: Reaction Weapons switch (weaponNum) { case WP_KNIFE: MAKERGB(pi->flashDlightColor, 0.6f, 0.6f, 1); break; case WP_PISTOL: MAKERGB(pi->flashDlightColor, 1, 1, 0); break; case WP_M4: MAKERGB(pi->flashDlightColor, 1, 1, 0); break; case WP_SSG3000: MAKERGB(pi->flashDlightColor, 1, 0.7f, 0.5f); break; case WP_MP5: MAKERGB(pi->flashDlightColor, 1, 0.75f, 0); break; case WP_HANDCANNON: MAKERGB(pi->flashDlightColor, 0.6f, 0.6f, 1); break; case WP_M3: MAKERGB(pi->flashDlightColor, 1, 0.5f, 0); break; case WP_AKIMBO: MAKERGB(pi->flashDlightColor, 0.6f, 0.6f, 1); break; case WP_GRENADE: MAKERGB(pi->flashDlightColor, 1, 0.7f, 1); break; default: MAKERGB(pi->flashDlightColor, 1, 1, 1); break; } } /* =============== UI_ForceLegsAnim =============== */ static void UI_ForceLegsAnim(playerInfo_t * pi, int anim) { pi->legsAnim = ((pi->legsAnim & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; if (anim == LEGS_JUMP) { pi->legsAnimationTimer = UI_TIMER_JUMP; } } /* =============== UI_SetLegsAnim =============== */ static void UI_SetLegsAnim(playerInfo_t * pi, int anim) { if (pi->pendingLegsAnim) { anim = pi->pendingLegsAnim; pi->pendingLegsAnim = 0; } UI_ForceLegsAnim(pi, anim); } /* =============== UI_ForceTorsoAnim =============== */ static void UI_ForceTorsoAnim(playerInfo_t * pi, int anim) { pi->torsoAnim = ((pi->torsoAnim & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; if (anim == TORSO_GESTURE) { pi->torsoAnimationTimer = UI_TIMER_GESTURE; } if (anim == TORSO_ATTACK || anim == TORSO_ATTACK2) { pi->torsoAnimationTimer = UI_TIMER_ATTACK; } } /* =============== UI_SetTorsoAnim =============== */ static void UI_SetTorsoAnim(playerInfo_t * pi, int anim) { if (pi->pendingTorsoAnim) { anim = pi->pendingTorsoAnim; pi->pendingTorsoAnim = 0; } UI_ForceTorsoAnim(pi, anim); } /* =============== UI_TorsoSequencing =============== */ static void UI_TorsoSequencing(playerInfo_t * pi) { int currentAnim; currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; if (pi->weapon != pi->currentWeapon) { if (currentAnim != TORSO_DROP) { pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; UI_ForceTorsoAnim(pi, TORSO_DROP); } } if (pi->torsoAnimationTimer > 0) { return; } if (currentAnim == TORSO_GESTURE) { UI_SetTorsoAnim(pi, TORSO_STAND); return; } if (currentAnim == TORSO_ATTACK || currentAnim == TORSO_ATTACK2) { UI_SetTorsoAnim(pi, TORSO_STAND); return; } if (currentAnim == TORSO_DROP) { UI_PlayerInfo_SetWeapon(pi, pi->weapon); pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH; UI_ForceTorsoAnim(pi, TORSO_RAISE); return; } if (currentAnim == TORSO_RAISE) { UI_SetTorsoAnim(pi, TORSO_STAND); return; } } /* =============== UI_LegsSequencing =============== */ static void UI_LegsSequencing(playerInfo_t * pi) { int currentAnim; currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; if (pi->legsAnimationTimer > 0) { if (currentAnim == LEGS_JUMP) { jumpHeight = JUMP_HEIGHT * sin(M_PI * (UI_TIMER_JUMP - pi->legsAnimationTimer) / UI_TIMER_JUMP); } return; } if (currentAnim == LEGS_JUMP) { UI_ForceLegsAnim(pi, LEGS_LAND); pi->legsAnimationTimer = UI_TIMER_LAND; jumpHeight = 0; return; } if (currentAnim == LEGS_LAND) { UI_SetLegsAnim(pi, LEGS_IDLE); return; } } /* ====================== UI_PositionEntityOnTag ====================== */ static void UI_PositionEntityOnTag(refEntity_t * entity, const refEntity_t * parent, clipHandle_t parentModel, char *tagName) { int i; orientation_t lerped; // lerp the tag trap_CM_LerpTag(&lerped, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, tagName); // FIXME: allow origin offsets along tag? VectorCopy(parent->origin, entity->origin); for (i = 0; i < 3; i++) { VectorMA(entity->origin, lerped.origin[i], parent->axis[i], entity->origin); } // cast away const because of compiler problems MatrixMultiply(lerped.axis, ((refEntity_t *) parent)->axis, entity->axis); entity->backlerp = parent->backlerp; } /* ====================== UI_PositionRotatedEntityOnTag ====================== */ static void UI_PositionRotatedEntityOnTag(refEntity_t * entity, const refEntity_t * parent, clipHandle_t parentModel, char *tagName) { int i; orientation_t lerped; vec3_t tempAxis[3]; // lerp the tag trap_CM_LerpTag(&lerped, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, tagName); // FIXME: allow origin offsets along tag? VectorCopy(parent->origin, entity->origin); for (i = 0; i < 3; i++) { VectorMA(entity->origin, lerped.origin[i], parent->axis[i], entity->origin); } // cast away const because of compiler problems MatrixMultiply(entity->axis, ((refEntity_t *) parent)->axis, tempAxis); MatrixMultiply(lerped.axis, tempAxis, entity->axis); } /* =============== UI_SetLerpFrameAnimation =============== */ static void UI_SetLerpFrameAnimation(playerInfo_t * ci, lerpFrame_t * lf, int newAnimation) { animation_t *anim; lf->animationNumber = newAnimation; newAnimation &= ~ANIM_TOGGLEBIT; if (newAnimation < 0 || newAnimation >= MAX_ANIMATIONS) { trap_Error(va("Bad animation number: %i", newAnimation)); } anim = &ci->animations[newAnimation]; lf->animation = anim; lf->animationTime = lf->frameTime + anim->initialLerp; } /* =============== UI_RunLerpFrame =============== */ static void UI_RunLerpFrame(playerInfo_t * ci, lerpFrame_t * lf, int newAnimation) { int f; animation_t *anim; // see if the animation sequence is switching if (newAnimation != lf->animationNumber || !lf->animation) { UI_SetLerpFrameAnimation(ci, lf, newAnimation); } // if we have passed the current frame, move it to // oldFrame and calculate a new frame if (dp_realtime >= lf->frameTime) { lf->oldFrame = lf->frame; lf->oldFrameTime = lf->frameTime; // get the next frame based on the animation anim = lf->animation; if (dp_realtime < lf->animationTime) { lf->frameTime = lf->animationTime; // initial lerp } else { lf->frameTime = lf->oldFrameTime + anim->frameLerp; } f = (lf->frameTime - lf->animationTime) / anim->frameLerp; if (f >= anim->numFrames) { f -= anim->numFrames; if (anim->loopFrames) { f %= anim->loopFrames; f += anim->numFrames - anim->loopFrames; } else { f = anim->numFrames - 1; // the animation is stuck at the end, so it // can immediately transition to another sequence lf->frameTime = dp_realtime; } } lf->frame = anim->firstFrame + f; if (dp_realtime > lf->frameTime) { lf->frameTime = dp_realtime; } } if (lf->frameTime > dp_realtime + 200) { lf->frameTime = dp_realtime; } if (lf->oldFrameTime > dp_realtime) { lf->oldFrameTime = dp_realtime; } // calculate current lerp value if (lf->frameTime == lf->oldFrameTime) { lf->backlerp = 0; } else { lf->backlerp = 1.0 - (float) (dp_realtime - lf->oldFrameTime) / (lf->frameTime - lf->oldFrameTime); } } /* =============== UI_PlayerAnimation =============== */ static void UI_PlayerAnimation(playerInfo_t * pi, int *legsOld, int *legs, float *legsBackLerp, int *torsoOld, int *torso, float *torsoBackLerp) { // legs animation pi->legsAnimationTimer -= uiInfo.uiDC.frameTime; if (pi->legsAnimationTimer < 0) { pi->legsAnimationTimer = 0; } UI_LegsSequencing(pi); if (pi->legs.yawing && (pi->legsAnim & ~ANIM_TOGGLEBIT) == LEGS_IDLE) { UI_RunLerpFrame(pi, &pi->legs, LEGS_TURN); } else { UI_RunLerpFrame(pi, &pi->legs, pi->legsAnim); } *legsOld = pi->legs.oldFrame; *legs = pi->legs.frame; *legsBackLerp = pi->legs.backlerp; // torso animation pi->torsoAnimationTimer -= uiInfo.uiDC.frameTime; if (pi->torsoAnimationTimer < 0) { pi->torsoAnimationTimer = 0; } UI_TorsoSequencing(pi); UI_RunLerpFrame(pi, &pi->torso, pi->torsoAnim); *torsoOld = pi->torso.oldFrame; *torso = pi->torso.frame; *torsoBackLerp = pi->torso.backlerp; } /* ================== UI_SwingAngles ================== */ static void UI_SwingAngles(float destination, float swingTolerance, float clampTolerance, float speed, float *angle, qboolean * swinging) { float swing; float move; float scale; if (!*swinging) { // see if a swing should be started swing = AngleSubtract(*angle, destination); if (swing > swingTolerance || swing < -swingTolerance) { *swinging = qtrue; } } if (!*swinging) { return; } // modify the speed depending on the delta // so it doesn't seem so linear swing = AngleSubtract(destination, *angle); scale = fabs(swing); if (scale < swingTolerance * 0.5) { scale = 0.5; } else if (scale < swingTolerance) { scale = 1.0; } else { scale = 2.0; } // swing towards the destination angle if (swing >= 0) { move = uiInfo.uiDC.frameTime * scale * speed; if (move >= swing) { move = swing; *swinging = qfalse; } *angle = AngleMod(*angle + move); } else if (swing < 0) { move = uiInfo.uiDC.frameTime * scale * -speed; if (move <= swing) { move = swing; *swinging = qfalse; } *angle = AngleMod(*angle + move); } // clamp to no more than tolerance swing = AngleSubtract(destination, *angle); if (swing > clampTolerance) { *angle = AngleMod(destination - (clampTolerance - 1)); } else if (swing < -clampTolerance) { *angle = AngleMod(destination + (clampTolerance - 1)); } } /* ====================== UI_MovedirAdjustment ====================== */ static float UI_MovedirAdjustment(playerInfo_t * pi) { vec3_t relativeAngles; vec3_t moveVector; VectorSubtract(pi->viewAngles, pi->moveAngles, relativeAngles); AngleVectors(relativeAngles, moveVector, NULL, NULL); if (Q_fabs(moveVector[0]) < 0.01) { moveVector[0] = 0.0; } if (Q_fabs(moveVector[1]) < 0.01) { moveVector[1] = 0.0; } if (moveVector[1] == 0 && moveVector[0] > 0) { return 0; } if (moveVector[1] < 0 && moveVector[0] > 0) { return 22; } if (moveVector[1] < 0 && moveVector[0] == 0) { return 45; } if (moveVector[1] < 0 && moveVector[0] < 0) { return -22; } if (moveVector[1] == 0 && moveVector[0] < 0) { return 0; } if (moveVector[1] > 0 && moveVector[0] < 0) { return 22; } if (moveVector[1] > 0 && moveVector[0] == 0) { return -45; } return -22; } /* =============== UI_PlayerAngles =============== */ static void UI_PlayerAngles(playerInfo_t * pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3]) { vec3_t legsAngles, torsoAngles, headAngles; float dest; float adjust; VectorCopy(pi->viewAngles, headAngles); headAngles[YAW] = AngleMod(headAngles[YAW]); VectorClear(legsAngles); VectorClear(torsoAngles); // --------- yaw ------------- // allow yaw to drift a bit if ((pi->legsAnim & ~ANIM_TOGGLEBIT) != LEGS_IDLE || (pi->torsoAnim & ~ANIM_TOGGLEBIT) != TORSO_STAND) { // if not standing still, always point all in the same direction pi->torso.yawing = qtrue; // always center pi->torso.pitching = qtrue; // always center pi->legs.yawing = qtrue; // always center } // adjust legs for movement dir adjust = UI_MovedirAdjustment(pi); legsAngles[YAW] = headAngles[YAW] + adjust; torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust; // torso UI_SwingAngles(torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing); UI_SwingAngles(legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing); torsoAngles[YAW] = pi->torso.yawAngle; legsAngles[YAW] = pi->legs.yawAngle; // --------- pitch ------------- // only show a fraction of the pitch angle in the torso if (headAngles[PITCH] > 180) { dest = (-360 + headAngles[PITCH]) * 0.75; } else { dest = headAngles[PITCH] * 0.75; } UI_SwingAngles(dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching); torsoAngles[PITCH] = pi->torso.pitchAngle; // pull the angles back out of the hierarchial chain AnglesSubtract(headAngles, torsoAngles, headAngles); AnglesSubtract(torsoAngles, legsAngles, torsoAngles); AnglesToAxis(legsAngles, legs); AnglesToAxis(torsoAngles, torso); AnglesToAxis(headAngles, head); } /* =============== UI_PlayerFloatSprite =============== */ static void UI_PlayerFloatSprite(playerInfo_t * pi, vec3_t origin, qhandle_t shader) { refEntity_t ent; memset(&ent, 0, sizeof(ent)); VectorCopy(origin, ent.origin); ent.origin[2] += 48; ent.reType = RT_SPRITE; ent.customShader = shader; ent.radius = 10; ent.renderfx = 0; trap_R_AddRefEntityToScene(&ent); } /* ====================== UI_MachinegunSpinAngle ====================== */ float UI_MachinegunSpinAngle(playerInfo_t * pi) { int delta; float angle; float speed; int torsoAnim; delta = dp_realtime - pi->barrelTime; if (pi->barrelSpinning) { angle = pi->barrelAngle + delta * SPIN_SPEED; } else { if (delta > COAST_TIME) { delta = COAST_TIME; } speed = 0.5 * (SPIN_SPEED + (float) (COAST_TIME - delta) / COAST_TIME); angle = pi->barrelAngle + delta * speed; } torsoAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; if (torsoAnim == TORSO_ATTACK2) { torsoAnim = TORSO_ATTACK; } if (pi->barrelSpinning == !(torsoAnim == TORSO_ATTACK)) { pi->barrelTime = dp_realtime; pi->barrelAngle = AngleMod(angle); pi->barrelSpinning = !!(torsoAnim == TORSO_ATTACK); } return angle; } /* =============== UI_DrawPlayer =============== */ void UI_DrawPlayer(float x, float y, float w, float h, playerInfo_t * pi, int time) { refdef_t refdef; refEntity_t legs; refEntity_t torso; refEntity_t head; refEntity_t gun; //Makro - barrel is no longer used //refEntity_t barrel; refEntity_t flash; vec3_t origin; int renderfx; vec3_t mins = { -16, -16, -24 }; vec3_t maxs = { 16, 16, 32 }; float len; float xx; if (!pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames) { return; } // this allows the ui to cache the player model on the main menu if (w == 0 || h == 0) { return; } dp_realtime = time; if (pi->pendingWeapon != -1 && dp_realtime > pi->weaponTimer) { pi->weapon = pi->pendingWeapon; pi->lastWeapon = pi->pendingWeapon; pi->pendingWeapon = -1; pi->weaponTimer = 0; if (pi->currentWeapon != pi->weapon) { trap_S_StartLocalSound(weaponChangeSound, CHAN_LOCAL); } } UI_AdjustFrom640(&x, &y, &w, &h); y -= jumpHeight; memset(&refdef, 0, sizeof(refdef)); memset(&legs, 0, sizeof(legs)); memset(&torso, 0, sizeof(torso)); memset(&head, 0, sizeof(head)); refdef.rdflags = RDF_NOWORLDMODEL; AxisClear(refdef.viewaxis); refdef.x = x; refdef.y = y; refdef.width = w; refdef.height = h; refdef.fov_x = (int)((float)refdef.width / uiInfo.uiDC.xscale / 640.0f * 90.0f); xx = refdef.width / uiInfo.uiDC.xscale / tan( refdef.fov_x / 360 * M_PI ); refdef.fov_y = atan2( refdef.height / uiInfo.uiDC.yscale, xx ); refdef.fov_y *= (360 / (float) M_PI); // calculate distance so the player nearly fills the box len = 0.7 * ( maxs[2] - mins[2] ); origin[0] = len / tan(DEG2RAD(refdef.fov_x) * 0.5); origin[1] = 0.5 * (mins[1] + maxs[1]); origin[2] = -0.5 * (mins[2] + maxs[2]); refdef.time = dp_realtime; //trap_R_ClearScene(); uiInfo.uiDC.clearScene(); // get the rotation information UI_PlayerAngles(pi, legs.axis, torso.axis, head.axis); // get the animation state (after rotation, to allow feet shuffle) UI_PlayerAnimation(pi, &legs.oldframe, &legs.frame, &legs.backlerp, &torso.oldframe, &torso.frame, &torso.backlerp); renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW; // // add the legs // legs.hModel = pi->legsModel; legs.customSkin = pi->legsSkin; VectorCopy(origin, legs.origin); VectorCopy(origin, legs.lightingOrigin); legs.renderfx = renderfx; VectorCopy(legs.origin, legs.oldorigin); trap_R_AddRefEntityToScene(&legs); if (!legs.hModel) { return; } // // add the torso // torso.hModel = pi->torsoModel; if (!torso.hModel) { return; } torso.customSkin = pi->torsoSkin; VectorCopy(origin, torso.lightingOrigin); UI_PositionRotatedEntityOnTag(&torso, &legs, pi->legsModel, "tag_torso"); torso.renderfx = renderfx; trap_R_AddRefEntityToScene(&torso); // // add the head // head.hModel = pi->headModel; if (!head.hModel) { return; } head.customSkin = pi->headSkin; VectorCopy(origin, head.lightingOrigin); UI_PositionRotatedEntityOnTag(&head, &torso, pi->torsoModel, "tag_head"); head.renderfx = renderfx; trap_R_AddRefEntityToScene(&head); // // add the gun // if (pi->currentWeapon != WP_NONE) { memset(&gun, 0, sizeof(gun)); gun.hModel = pi->weaponModel; VectorCopy(origin, gun.lightingOrigin); UI_PositionEntityOnTag(&gun, &torso, pi->torsoModel, "tag_weapon"); gun.renderfx = renderfx; trap_R_AddRefEntityToScene(&gun); } // // add the spinning barrel // //Blaze: No spinning barrels in rq3 /* if ( pi->realWeapon == WP_MACHINEGUN || pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) { vec3_t angles; memset( &barrel, 0, sizeof(barrel) ); VectorCopy( origin, barrel.lightingOrigin ); barrel.renderfx = renderfx; barrel.hModel = pi->barrelModel; angles[YAW] = 0; angles[PITCH] = 0; angles[ROLL] = UI_MachinegunSpinAngle( pi ); if( pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) { angles[PITCH] = angles[ROLL]; angles[ROLL] = 0; } AnglesToAxis( angles, barrel.axis ); UI_PositionRotatedEntityOnTag( &barrel, &gun, pi->weaponModel, "tag_barrel"); trap_R_AddRefEntityToScene( &barrel ); } */ // // add muzzle flash // if (dp_realtime <= pi->muzzleFlashTime) { if (pi->flashModel) { memset(&flash, 0, sizeof(flash)); flash.hModel = pi->flashModel; VectorCopy(origin, flash.lightingOrigin); UI_PositionEntityOnTag(&flash, &gun, pi->weaponModel, "tag_flash"); flash.renderfx = renderfx; trap_R_AddRefEntityToScene(&flash); } // make a dlight for the flash if (pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2]) { trap_R_AddLightToScene(flash.origin, 200 + (rand() & 31), pi->flashDlightColor[0], pi->flashDlightColor[1], pi->flashDlightColor[2]); } } // // add the chat icon // if (pi->chat) { UI_PlayerFloatSprite(pi, origin, trap_R_RegisterShaderNoMip("sprites/balloon3")); } // // add an accent light // origin[0] -= 100; // + = behind, - = in front origin[1] += 100; // + = left, - = right origin[2] += 100; // + = above, - = below trap_R_AddLightToScene(origin, 500, 1.0, 1.0, 1.0); origin[0] -= 100; origin[1] -= 100; origin[2] -= 100; trap_R_AddLightToScene(origin, 500, 1.0, 0.0, 0.0); //trap_R_RenderScene(&refdef); uiInfo.uiDC.renderScene(&refdef); } /* ========================== UI_FindClientHeadFile ========================== */ static qboolean UI_FindClientHeadFile(char *filename, int length, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext) { char *team, *headsFolder; int i; team = "default"; if (headModelName[0] == '*') { headsFolder = "heads/"; headModelName++; } else { headsFolder = ""; } while (1) { for (i = 0; i < 2; i++) { if (i == 0 && teamName && *teamName) { Com_sprintf(filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext); } else { Com_sprintf(filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext); } if (UI_FileExists(filename)) { return qtrue; } if (i == 0 && teamName && *teamName) { Com_sprintf(filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext); } else { Com_sprintf(filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext); } if (UI_FileExists(filename)) { return qtrue; } if (!teamName || !*teamName) { break; } } // if tried the heads folder first if (headsFolder[0]) { break; } headsFolder = "heads/"; } return qfalse; } /* ========================== UI_RegisterClientSkin ========================== */ static qboolean UI_RegisterClientSkin(playerInfo_t * pi, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName, const char *teamName) { char filename[MAX_QPATH]; if (teamName && *teamName) { Com_sprintf(filename, sizeof(filename), "models/players/%s/%s/lower_%s.skin", modelName, teamName, skinName); } else { Com_sprintf(filename, sizeof(filename), "models/players/%s/lower_%s.skin", modelName, skinName); } pi->legsSkin = trap_R_RegisterSkin(filename); if (!pi->legsSkin) { if (teamName && *teamName) { Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/%s/lower_%s.skin", modelName, teamName, skinName); } else { Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/lower_%s.skin", modelName, skinName); } pi->legsSkin = trap_R_RegisterSkin(filename); } // JBravo: adding if (!pi->legsSkin) { Com_sprintf(filename, sizeof(filename), "models/players/%s/lower_%s.skin", modelName, skinName); pi->legsSkin = trap_R_RegisterSkin(filename); } if (teamName && *teamName) { Com_sprintf(filename, sizeof(filename), "models/players/%s/%s/upper_%s.skin", modelName, teamName, skinName); } else { Com_sprintf(filename, sizeof(filename), "models/players/%s/upper_%s.skin", modelName, skinName); } pi->torsoSkin = trap_R_RegisterSkin(filename); if (!pi->torsoSkin) { if (teamName && *teamName) { Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/%s/upper_%s.skin", modelName, teamName, skinName); } else { Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/upper_%s.skin", modelName, skinName); } pi->torsoSkin = trap_R_RegisterSkin(filename); } // JBravo: adding if (!pi->torsoSkin) { Com_sprintf(filename, sizeof(filename), "models/players/%s/upper_%s.skin", modelName, skinName); pi->torsoSkin = trap_R_RegisterSkin(filename); } if (UI_FindClientHeadFile(filename, sizeof(filename), teamName, headModelName, headSkinName, "head", "skin")) { pi->headSkin = trap_R_RegisterSkin(filename); } if (!pi->legsSkin || !pi->torsoSkin || !pi->headSkin) { // JBravo: No need for errors if the skin doesnt exsist. Lets drop in a default instead. Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/lower_default.skin"); pi->legsSkin = trap_R_RegisterSkin(filename); Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/upper_default.skin"); pi->torsoSkin = trap_R_RegisterSkin(filename); Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/head_default.skin"); pi->headSkin = trap_R_RegisterSkin(filename); trap_Cvar_Set("model", "reactionmale/default"); trap_Cvar_Set("headmodel", "reactionmale/default"); } return qtrue; } /* ====================== UI_ParseAnimationFile ====================== */ static qboolean UI_ParseAnimationFile(const char *filename, animation_t * animations) { char *text_p, *prev; int len; int i; char *token; float fps; int skip; char text[20000]; fileHandle_t f; memset(animations, 0, sizeof(animation_t) * MAX_ANIMATIONS); // load the file len = trap_FS_FOpenFile(filename, &f, FS_READ); if (len <= 0) { return qfalse; } if (len >= (sizeof(text) - 1)) { Com_Printf("File %s too long\n", filename); return qfalse; } trap_FS_Read(text, len, f); text[len] = 0; trap_FS_FCloseFile(f); COM_Compress(text); // parse the text text_p = text; skip = 0; // quite the compiler warning // read optional parameters while (1) { prev = text_p; // so we can unget token = COM_Parse(&text_p); if (!token) { break; } if (!Q_stricmp(token, "footsteps")) { token = COM_Parse(&text_p); if (!token) { break; } continue; } else if (!Q_stricmp(token, "headoffset")) { for (i = 0; i < 3; i++) { token = COM_Parse(&text_p); if (!token) { break; } } continue; } else if (!Q_stricmp(token, "sex")) { token = COM_Parse(&text_p); if (!token) { break; } continue; } // if it is a number, start parsing animations if (token[0] >= '0' && token[0] <= '9') { text_p = prev; // unget the token break; } Com_Printf("unknown token '%s' is %s\n", token, filename); } // read information for each frame for (i = 0; i < MAX_ANIMATIONS; i++) { token = COM_Parse(&text_p); if (!token) { break; } animations[i].firstFrame = atoi(token); // leg only frames are adjusted to not count the upper body only frames if (i == LEGS_WALKCR) { skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; } if (i >= LEGS_WALKCR) { animations[i].firstFrame -= skip; } token = COM_Parse(&text_p); if (!token) { break; } animations[i].numFrames = atoi(token); token = COM_Parse(&text_p); if (!token) { break; } animations[i].loopFrames = atoi(token); token = COM_Parse(&text_p); if (!token) { break; } fps = atof(token); if (fps == 0) { fps = 1; } animations[i].frameLerp = 1000 / fps; animations[i].initialLerp = 1000 / fps; } if (i != MAX_ANIMATIONS) { Com_Printf("Error parsing animation file: %s", filename); return qfalse; } return qtrue; } /* ========================== UI_RegisterClientModelname ========================== */ qboolean UI_RegisterClientModelname(playerInfo_t * pi, const char *modelSkinName, const char *headModelSkinName, const char *teamName) { char modelName[MAX_QPATH]; char skinName[MAX_QPATH]; char headModelName[MAX_QPATH]; char headSkinName[MAX_QPATH]; char filename[MAX_QPATH]; char *slash; pi->torsoModel = 0; pi->headModel = 0; if (!modelSkinName[0]) { return qfalse; } Q_strncpyz(modelName, modelSkinName, sizeof(modelName)); slash = strchr(modelName, '/'); if (!slash) { // modelName did not include a skin name Q_strncpyz(skinName, "default", sizeof(skinName)); } else { Q_strncpyz(skinName, slash + 1, sizeof(skinName)); *slash = '\0'; } Q_strncpyz(headModelName, headModelSkinName, sizeof(headModelName)); slash = strchr(headModelName, '/'); if (!slash) { // modelName did not include a skin name Q_strncpyz(headSkinName, "default", sizeof(skinName)); } else { Q_strncpyz(headSkinName, slash + 1, sizeof(skinName)); *slash = '\0'; } // load cmodels before models so filecache works Com_sprintf(filename, sizeof(filename), "models/players/%s/lower.md3", modelName); pi->legsModel = trap_R_RegisterModel(filename); if (!pi->legsModel) { Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/lower.md3", modelName); pi->legsModel = trap_R_RegisterModel(filename); if (!pi->legsModel) { // JBravo: no errors on bad models. Defaults in stead. Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/lower.md3"); pi->legsModel = trap_R_RegisterModel(filename); trap_Cvar_Set("model", "reactionmale/default"); //Com_Printf("Failed to load model file %s\n", filename); //return qfalse; } } Com_sprintf(filename, sizeof(filename), "models/players/%s/upper.md3", modelName); pi->torsoModel = trap_R_RegisterModel(filename); if (!pi->torsoModel) { Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/upper.md3", modelName); pi->torsoModel = trap_R_RegisterModel(filename); if (!pi->torsoModel) { // JBravo: no errors on bad models. Defaults in stead. Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/upper.md3"); pi->torsoModel = trap_R_RegisterModel(filename); trap_Cvar_Set("model", "reactionmale/default"); //Com_Printf("Failed to load model file %s\n", filename); //return qfalse; } } // JBravo: fixed a warning... if (headModelName[0] == '*') { Com_sprintf(filename, sizeof(filename), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1]); } else { Com_sprintf(filename, sizeof(filename), "models/players/%s/head.md3", headModelName); } pi->headModel = trap_R_RegisterModel(filename); if (!pi->headModel && headModelName[0] != '*') { Com_sprintf(filename, sizeof(filename), "models/players/heads/%s/%s.md3", headModelName, headModelName); pi->headModel = trap_R_RegisterModel(filename); } if (!pi->headModel) { Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/head.md3"); pi->headModel = trap_R_RegisterModel(filename); trap_Cvar_Set("model", "reactionmale/default"); //Com_Printf("Failed to load model file %s\n", filename); //return qfalse; } // if any skins failed to load, fall back to default if (!UI_RegisterClientSkin(pi, modelName, skinName, headModelName, headSkinName, teamName)) { if (!UI_RegisterClientSkin(pi, modelName, "default", headModelName, "default", teamName)) { Com_Printf("Failed to load skin file: %s : %s\n", modelName, skinName); return qfalse; } } // load the animations Com_sprintf(filename, sizeof(filename), "models/players/%s/animation.cfg", modelName); if (!UI_ParseAnimationFile(filename, pi->animations)) { Com_sprintf(filename, sizeof(filename), "models/players/characters/%s/animation.cfg", modelName); if (!UI_ParseAnimationFile(filename, pi->animations)) { Com_sprintf(filename, sizeof(filename), "models/players/reactionmale/animation.cfg"); if (!UI_ParseAnimationFile(filename, pi->animations)) { Com_Printf("Failed to load animation file %s\n", filename); return qfalse; } } } return qtrue; } /* =============== UI_PlayerInfo_SetModel =============== */ void UI_PlayerInfo_SetModel(playerInfo_t * pi, const char *model, const char *headmodel, char *teamName) { memset(pi, 0, sizeof(*pi)); UI_RegisterClientModelname(pi, model, headmodel, teamName); //Blaze: Changed from WP_MACHINEGUN to WP_PISTOL pi->weapon = WP_PISTOL; pi->currentWeapon = pi->weapon; pi->lastWeapon = pi->weapon; pi->pendingWeapon = -1; pi->weaponTimer = 0; pi->chat = qfalse; pi->newModel = qtrue; UI_PlayerInfo_SetWeapon(pi, pi->weapon); } /* =============== UI_PlayerInfo_SetInfo =============== */ void UI_PlayerInfo_SetInfo(playerInfo_t * pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNumber, qboolean chat) { int currentAnim; weapon_t weaponNum; pi->chat = chat; // view angles VectorCopy(viewAngles, pi->viewAngles); // move angles VectorCopy(moveAngles, pi->moveAngles); if (pi->newModel) { pi->newModel = qfalse; jumpHeight = 0; pi->pendingLegsAnim = 0; UI_ForceLegsAnim(pi, legsAnim); pi->legs.yawAngle = viewAngles[YAW]; pi->legs.yawing = qfalse; pi->pendingTorsoAnim = 0; UI_ForceTorsoAnim(pi, torsoAnim); pi->torso.yawAngle = viewAngles[YAW]; pi->torso.yawing = qfalse; if (weaponNumber != -1) { pi->weapon = weaponNumber; pi->currentWeapon = weaponNumber; pi->lastWeapon = weaponNumber; pi->pendingWeapon = -1; pi->weaponTimer = 0; UI_PlayerInfo_SetWeapon(pi, pi->weapon); } return; } // weapon if (weaponNumber == -1) { pi->pendingWeapon = -1; pi->weaponTimer = 0; } else if (weaponNumber != WP_NONE) { pi->pendingWeapon = weaponNumber; pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY; } weaponNum = pi->lastWeapon; pi->weapon = weaponNum; if (torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1) { torsoAnim = legsAnim = BOTH_DEATH1; pi->weapon = pi->currentWeapon = WP_NONE; UI_PlayerInfo_SetWeapon(pi, pi->weapon); jumpHeight = 0; pi->pendingLegsAnim = 0; UI_ForceLegsAnim(pi, legsAnim); pi->pendingTorsoAnim = 0; UI_ForceTorsoAnim(pi, torsoAnim); return; } // leg animation currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT; if (legsAnim != LEGS_JUMP && (currentAnim == LEGS_JUMP || currentAnim == LEGS_LAND)) { pi->pendingLegsAnim = legsAnim; } else if (legsAnim != currentAnim) { jumpHeight = 0; pi->pendingLegsAnim = 0; UI_ForceLegsAnim(pi, legsAnim); } // torso animation if (torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2) { //Changed from WP_GAUNTLET to WP_KNIFE if (weaponNum == WP_NONE || weaponNum == WP_KNIFE) { torsoAnim = TORSO_STAND2; } else { torsoAnim = TORSO_STAND; } } if (torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2) { //Changed from WP_GAUNTLET to WP_KNIFE if (weaponNum == WP_NONE || weaponNum == WP_KNIFE) { torsoAnim = TORSO_ATTACK2; } else { torsoAnim = TORSO_ATTACK; } pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH; //FIXME play firing sound here } currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT; if (weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISE || currentAnim == TORSO_DROP) { pi->pendingTorsoAnim = torsoAnim; } else if ((currentAnim == TORSO_GESTURE || currentAnim == TORSO_ATTACK) && (torsoAnim != currentAnim)) { pi->pendingTorsoAnim = torsoAnim; } else if (torsoAnim != currentAnim) { pi->pendingTorsoAnim = 0; UI_ForceTorsoAnim(pi, torsoAnim); } }