HOW TO MAKE A NEW FORCE POWER – SINGLE PLAYER
Welcome to alls to my third tutorial.
In this tutorial i wanna tell you how you can add a new force power on JKA Single player game. I wanna to share with you the code of my force controlmind power. It’s act just like the force mindtrick level 4. it’s really a simple power to make and not take much time or work.
Warning: some power require more complex works.
That’s depend by you and by your code skill. I will not tell you how program a new power. I wanna tell you exactly what section of SP code you need to edit for add a new power and allow to the power to be usable.
About programming, you need to think carefully about the kind of force power you want to do.
If you want force shockwave power, for example, you need to dive the code and to see how work work the splashdamage of sabers and the force push power. If you want instead a freezing power, you need to study careful the d_npcfreeze cheat, the force grip power level 1 for locking enemy in a position and the force mindtrick power for the casting and visual efx of stasys enemy, or maybe, if you want an aura effect related to this, you need to study the FP_PROTECT or absorb aura power.
I wanna tell you an indication about where you can find the code stuff related to these powers.
FP_SENSE: wp_saber.cpp storing power working stuff. Cg_players.cpp storing the aura effect and the mask effect with the 2d scrolling.
FP_SABER_DEFENSE, OFFENSE, THROW: check WP_SABER.CPP
FP_SPEED: basically wp_saber.cpp
FP_LIGHTNING: cgplayer for lightning visual effect, WP_SABER.cpp for lightning programming.
FP_DRAIN: same as lightning.
FP_GRIP, it’s all on wp_saber.cpp, but it’s in 4 different block. You can find a part of grip code into the void forcegrip, and also more stuff into forcepowerstop, forcepowerstart and forcepowerrun function.
FP_MINDTRICK: it’s all on wp_saber.cpp, in only a block , void forcetelepathy.
FP_RAGE: wp_saber.cpp.
FP_PROTECT and FP_ABSORB: programmed on wp_saber.cpp, the aura effect is created on CG_PLAYERS.cpp
FP_HEAL: on WP_saber.cpp.
Sound of force powers: all these are definitated in a large case and switch functions array into wp_saber.cpp
Okay, now let we start with the true tutorial.
What you need:
- Microsoft visual studio 2010 \ 2012
- OpenJK
- force power icon.
- a description of your force power into SP_INGAME.
I will teach how to do. You got all that? Good! We start!
First off, you need to add your power to the force power list.
WARNING: you can have only total amount of 32 force power, not more. But in single player there are only 16 force power active, so you should got not much trouble about this.
Go to code/qcommon\q_shared.h
And find this enum string:
typedef enum { FP_FIRST = 0,//marker FP_HEAL = 0,//instant FP_LEVITATION,//hold/duration FP_SPEED,//duration FP_PUSH,//hold/duration FP_PULL,//hold/duration FP_TELEPATHY,//instant FP_GRIP,//hold/duration FP_LIGHTNING,//hold/duration FP_SABERTHROW, FP_SABER_DEFENSE, FP_SABER_OFFENSE, //new Jedi Academy powers FP_RAGE,//duration - speed, invincibility and extra damage for short period, drains your health and leaves you weak and slow afterwards. FP_PROTECT,//duration - protect against physical/energy (level 1 stops blaster/energy bolts, level 2 stops projectiles, level 3 protects against explosions)FP_ABSORB,/duration - protect against dark force powers (grip, lightning, drain - maybe push/pull, too?) FP_DRAIN,//hold/duration - drain force power for health FP_SEE,//duration - detect/see hidden enemies FP_STUN,//instant FP_HATE,//instant FP_CONTROLMIND,//instant FP_FREEZE,//istant FP_FEAR,//instant NUM_FORCE_POWERS } forcePowers_t;
Add your new power at the end of the list, how i did with my new force staff.
Now scroll down q_shared and find this:
typedef enum { GENCMD_FORCE_HEAL = 1, GENCMD_FORCE_SPEED, GENCMD_FORCE_THROW, GENCMD_FORCE_PULL, GENCMD_FORCE_DISTRACT, GENCMD_FORCE_GRIP, GENCMD_FORCE_LIGHTNING, GENCMD_FORCE_RAGE, GENCMD_FORCE_PROTECT, GENCMD_FORCE_ABSORB, GENCMD_FORCE_DRAIN, GENCMD_FORCE_SEEING, GENCMD_FORCE_STUN, GENCMD_FORCE_HATE, GENCMD_FORCE_CONTROLMIND, GENCMD_FORCE_FREEZE, GENCMD_FORCE_FEAR, } genCmds_t;
gencmd is necessary for assign key button to use the force powers. You need to add a definition for your new force power here related this.
So for q_shared. You’are okay.
Now go to g_active.cpp and add the external void definition for the function of your new powers here, upon the genericcmd switch.
extern void ForceGrip(gentity_t *ent); extern void ForceLightning(gentity_t *ent); extern void ForceProtect(gentity_t *ent); extern void ForceRage(gentity_t *ent); extern void ForceSeeing(gentity_t *ent); extern void ForceTelepathy(gentity_t *ent); extern void ForceStun(gentity_t *ent); extern void ForceHate(gentity_t *ent); extern void ForceControlMind(gentity_t *ent); extern void ForceFreeze(gentity_t *ent); extern void ForceFear(gentity_t *ent); extern void ForceAbsorb(gentity_t *ent); extern void ForceHeal(gentity_t *ent);
as you see i added the mine.
Now scroll down and you found immediatly
This switch, when you can assign to every command the function of his respective power.
static void ProcessGenericCmd(gentity_t *ent, byte cmd) { switch(cmd) { case 0: break; case GENCMD_FORCE_DRAIN: ForceDrain2(ent); break; case GENCMD_FORCE_THROW: ForceThrow(ent, qfalse); break; case GENCMD_FORCE_SPEED: ForceSpeed(ent); break; case GENCMD_FORCE_PULL: ForceThrow(ent, qtrue); break; case GENCMD_FORCE_DISTRACT: ForceTelepathy(ent); break; case GENCMD_FORCE_CONTROLMIND: ForceControlMind(ent); break; case GENCMD_FORCE_FEAR: ForceFear(ent); break; case GENCMD_FORCE_STUN: ForceStun(ent); break; case GENCMD_FORCE_HATE: ForceHate(ent); break; case GENCMD_FORCE_GRIP: ForceGrip(ent); case GENCMD_FORCE_FREEZE: ForceFreeze(ent); break; case GENCMD_FORCE_LIGHTNING: ForceLightning(ent); break; case GENCMD_FORCE_RAGE: ForceRage(ent); break; case GENCMD_FORCE_PROTECT: ForceProtect(ent); break; case GENCMD_FORCE_ABSORB:ForceAbsorb(ent); break; case GENCMD_FORCE_SEEING: ForceSeeing(ent); break; case GENCMD_FORCE_HEAL: ForceHeal(ent); break; } }
under every case, you need to add the void function of the power is called by the command.
Okay, now we need to add force power as item available, exactly how we did with the weapons:
Go to g_itemload.cpp and after the weapon qstrimp tags you will found the force power tags. Add the new power in that mode:
else if (!Q_stricmp(tokenStr,"FP_HEAL")) { tag = FP_HEAL; } else if (!Q_stricmp(tokenStr,"FP_LEVITATION")) { tag = FP_LEVITATION; } else if (!Q_stricmp(tokenStr,"FP_SPEED")) { tag = FP_SPEED; } else if (!Q_stricmp(tokenStr,"FP_PUSH")) { tag = FP_PUSH; } else if (!Q_stricmp(tokenStr,"FP_PULL")) { tag = FP_PULL; } else if (!Q_stricmp(tokenStr,"FP_TELEPATHY")) { tag = FP_TELEPATHY; } else if (!Q_stricmp(tokenStr,"FP_SABERTHROW")) { tag = FP_SABERTHROW; } else if (!Q_stricmp(tokenStr,"FP_STUN")) { tag = FP_STUN; } else if (!Q_stricmp(tokenStr,"FP_HATE")) { tag = FP_HATE; } else if (!Q_stricmp(tokenStr,"FP_CONTROLMIND")) { tag = FP_CONTROLMIND; } else if (!Q_stricmp(tokenStr,"FP_FREEZE")) { tag = FP_FREEZE; } else if (!Q_stricmp(tokenStr,"FP_FEAR")) { tag = FP_FEAR; } else if (!Q_stricmp(tokenStr,"FP_LIGHTNING")) { tag = FP_LIGHTNING; } else if (!Q_stricmp(tokenStr,"FP_SABERTHROW")) { tag = FP_SABERTHROW; } else if (!Q_stricmp(tokenStr,"ITM_BATTERY_PICKUP")) { tag = ITM_BATTERY_PICKUP; }
as you can see i added tags for the my new force powers.
Now... if you wanna that your power can be used with icarus scripting, you need to make some editing.
Go to q3_interface.h and add into the set list the new icarus command for your new force powers.
On booleans:
SET_NO_PVS_CULL,//## %t="BOOL_TYPES" # This entity will *always* be drawn - use only for special case cinematic NPCs that have anims that cover multiple rooms!!! SET_CLOAK,//## %t="BOOL_TYPES" # Set a Saboteur to cloak (true) or un-cloak (false). SET_FORCE_HEAL,//## %t="BOOL_TYPES" # Causes this ent to start force healing at whatever level of force heal they have SET_FORCE_SPEED,//## %t="BOOL_TYPES" # Causes this ent to start force speeding at whatever level of force speed they have (may not do anything for NPCs?) SET_FORCE_PUSH,//## %t="BOOL_TYPES" # Causes this ent to do a force push at whatever level of force push they have - will not fail SET_FORCE_PUSH_FAKE,//## %t="BOOL_TYPES" # Causes this ent to do a force push anim, sound and effect, will not push anything SET_FORCE_PULL,//## %t="BOOL_TYPES" # Causes this ent to do a force push at whatever level of force push they have - will not fail SET_FORCE_MIND_TRICK,//## %t="BOOL_TYPES" # Causes this ent to do a jedi mind trick at whatever level of mind trick they have (may not do anything for NPCs?) SET_FORCE_GRIP,//## %t="BOOL_TYPES" # Causes this ent to grip their enemy at whatever level of grip they have (will grip until scripted to stop) SET_FORCE_LIGHTNING,//## %t="BOOL_TYPES" # Causes this ent to lightning at whatever level of lightning they have (will lightning until scripted to stop) SET_FORCE_SABERTHROW,//## %t="BOOL_TYPES" # Causes this ent to throw their saber at whatever level of saber throw they have (will throw saber until scripted to stop) SET_FORCE_RAGE,//## %t="BOOL_TYPES" # Causes this ent to go into force rage at whatever level of force rage they have SET_FORCE_PROTECT,//## %t="BOOL_TYPES" # Causes this ent to start a force protect at whatever level of force protect they have SET_FORCE_ABSORB,//## %t="BOOL_TYPES" # Causes this ent to do start a force absorb at whatever level of force absorb they have SET_FORCE_DRAIN,//## %t="BOOL_TYPES" # Causes this ent to start force draining their enemy at whatever level of force drain they have (will drain until scripted to stop) SET_FORCE_STUN,//## %t="BOOL_TYPES" # Causes this ent to start force draining their enemy at whatever level of force drain they have (will drain until scripted to stop) SET_FORCE_HATE,//## %t="BOOL_TYPES" # Causes this ent to start force draining their enemy at whatever level of force drain they have (will drain until scripted to stop) SET_FORCE_CONTROLMIND,//## %t="BOOL_TYPES" # Causes this ent to start force draining their enemy at whatever level of force drain they have (will drain until scripted to stop) SET_FORCE_FREEZE,//## %t="BOOL_TYPES" # Causes this ent to start force draining their enemy at whatever level of force drain they have (will drain until scripted to stop) SET_FORCE_FEAR,//## %t="BOOL_TYPES" # This NPC/player will not have any bone angle overrides or pitch or roll (should only be used in cinematics) SET_WINTER_GEAR, /## %t="BOOL_TYPES" # Set the player to wear his/her winter gear (skins torso_g1 and lower_e1), or restore the default skins. SET_NO_ANGLES, /## %t="BOOL_TYPES" # This NPC/player will not have any bone angle overrides or pitch or roll (should only be used in cinematics)
And below, on the SET_FORCE_POWER_LEVELS. SET_FORCE_HEAL_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_FORCE_JUMP_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_FORCE_SPEED_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_FORCE_PUSH_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_FORCE_PULL_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_FORCE_MINDTRICK_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_FORCE_GRIP_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_FORCE_LIGHTNING_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_SABER_THROW,/## %t="FORCE_LEVELS" # Change force power level SET_SABER_DEFENSE,/## %t="FORCE_LEVELS" # Change force power level SET_SABER_OFFENSE,/## %t="SABER_STYLES" # Change force power level SET_FORCE_RAGE_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_FORCE_PROTECT_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_FORCE_ABSORB_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_FORCE_DRAIN_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_FORCE_SIGHT_LEVEL,/## %t="FORCE_LEVELS" # Change force power level SET_SABER1_COLOR1,/## %t="SABER_COLORS" # Set color of first blade of first saber SET_SABER1_COLOR2,/## %t="SABER_COLORS" # Set color of second blade of first saber SET_SABER2_COLOR1,/## %t="SABER_COLORS" # Set color of first blade of first saber SET_SABER2_COLOR2,/## %t="SABER_COLORS" # Set color of second blade of first saber SET_DISMEMBER_LIMB,/## %t="HIT_LOCATIONS" # Cut off a part of a body and send the limb flying
But adding string voice is unuseful if you will not program also WHAT do this voice when they are executed on scripts. So you need to set this on Q3_INTERFACE.CPP
You need to add here:
case SET_FORCE_HEAL_LEVEL: case SET_FORCE_JUMP_LEVEL: case SET_FORCE_SPEED_LEVEL: case SET_FORCE_PUSH_LEVEL: case SET_FORCE_PULL_LEVEL: case SET_FORCE_MINDTRICK_LEVEL: case SET_FORCE_STUN_LEVEL: case SET_FORCE_HATE_LEVEL: case SET_FORCE_CONTROLMIND_LEVEL: case SET_FORCE_FREEZE_LEVEL: case SET_FORCE_FEAR_LEVEL: case SET_FORCE_GRIP_LEVEL: case SET_FORCE_LIGHTNING_LEVEL: case SET_SABER_THROW:case SET_SABER_DEFENSE: case SET_SABER_OFFENSE:case SET_FORCE_RAGE_LEVEL: case SET_FORCE_PROTECT_LEVEL: case SET_FORCE_ABSORB_LEVEL: case SET_FORCE_DRAIN_LEVEL: case SET_FORCE_SIGHT_LEVEL: int_data = atoi((char *) data); Q3_SetForcePowerLevel( entID, (toSet-SET_FORCE_HEAL_LEVEL), int_data ); break; case SET_SABER1: case SET_SABER2: WP_SetSaber( &g_entities[entID], toSet-SET_SABER1, (char *)data ); break; case SET_PLAYERMODEL: G_ChangePlayerModel( &g_entities[entID], (const char *)data ); break; // NOTE: Preconditions for entering a vehicle still exist. This is not assured to work. -Areis case SET_VEHICLE: Use( entID, (char *)data ); //G_DriveVehicle( &g_entities[entID], NULL, (char *)data ); break; case SET_SECURITY_KEY: Q3_GiveSecurityKey( entID, (char *)data ); break; case SET_SABER1_COLOR1: case SET_SABER1_COLOR2: WP_SaberSetColor( &g_entities[entID], 0, toSet-SET_SABER1_COLOR1, (char *)data ); break; case SET_SABER2_COLOR1: case SET_SABER2_COLOR2: WP_SaberSetColor( &g_entities[entID], 1, toSet-SET_SABER2_COLOR1, (char *)data ); break; case SET_DISMEMBER_LIMB: Q3_DismemberLimb( entID, (char *)data ); break; case SET_NO_PVS_CULL: Q3_SetBroadcast( entID, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; // Set a Saboteur to cloak (true) or un-cloak (false). case SET_CLOAK: // Created: 01/08/03 by AReis. //extern void Jedi_Cloak( gentity_t *self ); extern void Saboteur_Cloak( gentity_t *self ); if( Q_stricmp("true", ((char *)data)) == 0 ) { Saboteur_Cloak( &g_entities[entID] ); } else { Saboteur_Decloak( &g_entities[entID] ); } break; case SET_FORCE_HEAL: Q3_SetForcePower( entID, FP_HEAL, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_SPEED: Q3_SetForcePower( entID, FP_SPEED, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_PUSH: Q3_SetForcePower( entID, FP_PUSH, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_PUSH_FAKE: ForceThrow( &g_entities[entID], qfalse, qtrue ); break; case SET_FORCE_PULL: Q3_SetForcePower( entID, FP_PULL, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_MIND_TRICK: Q3_SetForcePower( entID, FP_TELEPATHY, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_STUN:Q3_SetForcePower( entID, FP_STUN, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_HATE:Q3_SetForcePower( entID, FP_HATE, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_CONTROLMIND: Q3_SetForcePower( entID, FP_CONTROLMIND, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_FREEZE: Q3_SetForcePower( entID, FP_FREEZE, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_FEAR: Q3_SetForcePower( entID, FP_FEAR, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_GRIP: Q3_SetForcePower( entID, FP_GRIP, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_LIGHTNING: Q3_SetForcePower( entID, FP_LIGHTNING, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_SABERTHROW: Q3_SetForcePower( entID, FP_SABERTHROW, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_RAGE: Q3_SetForcePower( entID, FP_RAGE, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_PROTECT: Q3_SetForcePower( entID, FP_PROTECT, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_ABSORB: Q3_SetForcePower( entID, FP_ABSORB, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break; case SET_FORCE_DRAIN: Q3_SetForcePower( entID, FP_DRAIN, (qboolean)(Q_stricmp("true",(char*)data)==0) ); break;
Exactly how i did. Well done. Now your force powers are available with scripts. And engine will recognize that need to execute a force power or assign a power level to a new force power with Icarus, if you need to make a huge mod with maps and scripts, this can be useful.
Now... it’s turn of Npcs. You want NPCS can use your force power so you can make enemy sith that attack you with these new ability? So you need to add as NPC command. You can make this on NPCs_stats.cpp
stringID_table_t FPTable[] = { ENUM2STRING(FP_HEAL), ENUM2STRING(FP_LEVITATION), ENUM2STRING(FP_SPEED), ENUM2STRING(FP_PUSH), ENUM2STRING(FP_PULL), ENUM2STRING(FP_TELEPATHY), ENUM2STRING(FP_GRIP), ENUM2STRING(FP_LIGHTNING), ENUM2STRING(FP_SABERTHROW), ENUM2STRING(FP_SABER_DEFENSE), ENUM2STRING(FP_SABER_OFFENSE), //new Jedi Academy powers ENUM2STRING(FP_RAGE), ENUM2STRING(FP_PROTECT), ENUM2STRING(FP_ABSORB), ENUM2STRING(FP_DRAIN), ENUM2STRING(FP_SEE), ENUM2STRING(FP_STUN), ENUM2STRING(FP_HATE), ENUM2STRING(FP_CONTROLMIND), ENUM2STRING(FP_FREEZE), ENUM2STRING(FP_FEAR), { "", -1 } };
Now... the menu interface related to force power. If you they appears in your custom menu when your SP mission are completed, you need to add the new power on g_target.cpp
On:
void set_mission_stats_cvars(void) { char text[1024] = { 0 }; //we'll assume that the activator is the player gclient_t * const client = & level.clients[0]; if (!client) { return; } gi.cvar_set("ui_stats_enemieskilled", va("%d", client - > sess.missionStats.enemiesKilled)); //pass this on to the menu if (cg_entities[0].gent - > client - > sess.missionStats.totalSecrets) { cgi_SP_GetStringTextString("SP_INGAME_SECRETAREAS_OF", text, sizeof(text)); gi.cvar_set("ui_stats_secretsfound", va("%d %s %d", cg_entities[0].gent - > client - > sess.missionStats.secretsFound, text, cg_entities[0].gent - > client - > sess.missionStats.totalSecrets)); } else // Setting ui_stats_secretsfound to 0 will hide the text on screen { gi.cvar_set("ui_stats_secretsfound", "0"); } // Find the favorite weapon int wpn = 0, i; int max_wpn = cg_entities[0].gent - > client - > sess.missionStats.weaponUsed[0]; for (i = 1; i < WP_NUM_WEAPONS; i++) { if (cg_entities[0].gent - > client - > sess.missionStats.weaponUsed[i] > max_wpn) { max_wpn = cg_entities[0].gent - > client - > sess.missionStats.weaponUsed[i]; wpn = i; } } if (wpn) { gitem_t * wItem = FindItemForWeapon((weapon_t) wpn); cgi_SP_GetStringTextString(va("SP_INGAME_%s", wItem - > classname), text, sizeof(text)); gi.cvar_set("ui_stats_fave", va("%s", text)); //pass this on to the menu } gi.cvar_set("ui_stats_shots", va("%d", client - > sess.missionStats.shotsFired)); //pass this on to the menu gi.cvar_set("ui_stats_hits", va("%d", client - > sess.missionStats.hits)); //pass this on to the menu const float percent = cg_entities[0].gent - > client - > sess.missionStats.shotsFired ? 100.0 f * (float) cg_entities[0].gent - > client - > sess.missionStats.hits / cg_entities[0].gent - > client - > sess.missionStats.shotsFired : 0; gi.cvar_set("ui_stats_accuracy", va("%.2f%%", percent)); /pass this on to the menugi.cvar_set("ui_stats_thrown", va("%d",client->sess.missionStats.saberThrownCnt)); // pass this on to the menu gi.cvar_set("ui_stats_blocks", va("%d", client - > sess.missionStats.saberBlocksCnt)); gi.cvar_set("ui_stats_legattacks", va("%d", client - > sess.missionStats.legAttacksCnt)); gi.cvar_set("ui_stats_armattacks", va("%d", client - > sess.missionStats.armAttacksCnt)); gi.cvar_set("ui_stats_bodyattacks", va("%d", client - > sess.missionStats.torsoAttacksCnt)); gi.cvar_set("ui_stats_absorb", va("%d", client - > sess.missionStats.forceUsed[FP_ABSORB])); gi.cvar_set("ui_stats_heal", va("%d", client - > sess.missionStats.forceUsed[FP_HEAL])); gi.cvar_set("ui_stats_mindtrick", va("%d", client - > sess.missionStats.forceUsed[FP_TELEPATHY])); gi.cvar_set("ui_stats_protect", va("%d", client - > sess.missionStats.forceUsed[FP_PROTECT])); gi.cvar_set("ui_stats_jump", va("%d", client - > sess.missionStats.forceUsed[FP_LEVITATION])); gi.cvar_set("ui_stats_pull", va("%d", client - > sess.missionStats.forceUsed[FP_PULL])); gi.cvar_set("ui_stats_push", va("%d", client - > sess.missionStats.forceUsed[FP_PUSH])); gi.cvar_set("ui_stats_sense", va("%d", client - > sess.missionStats.forceUsed[FP_SEE])); gi.cvar_set("ui_stats_speed", va("%d", client - > sess.missionStats.forceUsed[FP_SPEED])); gi.cvar_set("ui_stats_defense", va("%d", client - > sess.missionStats.forceUsed[FP_SABER_DEFENSE])); gi.cvar_set("ui_stats_offense", va("%d", client - > sess.missionStats.forceUsed[FP_SABER_OFFENSE])); gi.cvar_set("ui_stats_throw", va("%d", client - > sess.missionStats.forceUsed[FP_SABERTHROW])); gi.cvar_set("ui_stats_drain", va("%d", client - > sess.missionStats.forceUsed[FP_DRAIN])); gi.cvar_set("ui_stats_grip", va("%d", client - > sess.missionStats.forceUsed[FP_GRIP])); gi.cvar_set("ui_stats_lightning", va("%d", client - > sess.missionStats.forceUsed[FP_LIGHTNING])); gi.cvar_set("ui_stats_rage", va("%d", client - > sess.missionStats.forceUsed[FP_RAGE])); gi.cvar_set("ui_stats_stun", va("%d", client - > sess.missionStats.forceUsed[FP_STUN])); gi.cvar_set("ui_stats_hate", va("%d", client - > sess.missionStats.forceUsed[FP_HATE])); gi.cvar_set("ui_stats_controlmind", va("%d", client - > sess.missionStats.forceUsed[FP_CONTROLMIND])); gi.cvar_set("ui_stats_incapacitate", va("%d", client - > sess.missionStats.forceUsed[FP_FREEZE])); gi.cvar_set("ui_stats_fear", va("%d", client - > sess.missionStats.forceUsed[FP_FEAR])); }
As you can see, these programs the stats for ingamemissionselect menu.
Now... the consolle command.
Into the game cheats, you can add powers with setforceall (force power value)
And with command like setforceprotect 3 (add force protect level 3, remember?)
Sure you will know these cheats. They are important because you need to call the power into the consolle and assign to your jedi for test it on game when you will work on that.
For force stun, i wrote this special static void:
The file to edit is: g_svcmds.cpp
static void Svcmd_ForceStun_f(void) { if (! & g_entities[0] || !g_entities[0].client) { return; } if (!g_cheats - > integer) { gi.SendServerCommand(0, "print \"Cheats are not enabled on this server.\n\""); return; } const char * newVal = gi.argv(1); if (!VALIDSTRING(newVal)) { gi.Printf("Current stun level is %d\n", g_entities[0].client - > ps.forcePowerLevel[FP_STUN]); gi.Printf("Usage: stun <level> (1 - 3)\n"); return; } int val = atoi(newVal); if (val > FORCE_LEVEL_0) { g_entities[0].client - > ps.forcePowersKnown |= (1 << FP_STUN); } else { g_entities[0].client - > ps.forcePowersKnown &= ~(1 << FP_STUN); } g_entities[0].client - > ps.forcePowerLevel[FP_STUN] = val; if (g_entities[0].client - > ps.forcePowerLevel[FP_STUN] < FORCE_LEVEL_0) { g_entities[0].client - > ps.forcePowerLevel[FP_STUN] = FORCE_LEVEL_0; } else if (g_entities[0].client - > ps.forcePowerLevel[FP_STUN] > FORCE_LEVEL_4) { g_entities[0].client - > ps.forcePowerLevel[FP_STUN] = FORCE_LEVEL_4; } }
this will program your server command. For making it available and create something like setforcestun and give you the power with the setforceall cheat, you need to add also these strings:
locate this
/*=================ConsoleCommand/ these are added in cg_main, CG_Init so they tab-complete=================*/
down add into the if case lists:
if ( Q_stricmp( cmd, "setForceStun" ) == 0 ){Svcmd_ForceStun_f();return qtrue;}
add also about the command setforceall:
if (Q_stricmp(cmd, "setForceAll") == 0) { if (!g_cheats - > integer) { gi.SendServerCommand(0, "print \"Cheats are not enabled on this server.\n\""); return qfalse; } Svcmd_ForceJump_f(); Svcmd_SaberThrow_f(); Svcmd_ForceHeal_f(); Svcmd_ForcePush_f(); Svcmd_ForcePull_f(); Svcmd_ForceSpeed_f(); Svcmd_ForceGrip_f(); Svcmd_ForceLightning_f(); Svcmd_MindTrick_f(); Svcmd_ForceStun_f(); Svcmd_ForceHate_f(); Svcmd_ControlMind_f(); Svcmd_ForceFreeze_f(); Svcmd_ForceFear_f(); Svcmd_SaberDefense_f(); Svcmd_SaberOffense_f(); Svcmd_ForceSetLevel_f(FP_RAGE); Svcmd_ForceSetLevel_f(FP_DRAIN); Svcmd_ForceSetLevel_f(FP_PROTECT); Svcmd_ForceSetLevel_f(FP_ABSORB); Svcmd_ForceSetLevel_f(FP_SEE); for (int i = SS_NONE + 1; i < SS_NUM_SABER_STYLES; i++) { g_entities[0].client - > ps.saberStylesKnown |= (1 << i); } return qtrue; }
well! Now you got consolle command about your new powers for debug when you will test against NPCs during programming.
Now... well Now it’s time to define the new force power icon path of the your new force powers.
You can do these on cg_main.cpp. go on void cg_init
void CG_Init(int serverCommandSequence) { cgs.serverCommandSequence = serverCommandSequence; cgi_Cvar_Set("cg_drawHUD", "1"); // fonts... // cgs.media.charsetShader = cgi_R_RegisterShaderNoMip("gfx/2d/charsgrid_med"); cgs.media.qhFontSmall = cgi_R_RegisterFont("ocr_a"); cgs.media.qhFontMedium = cgi_R_RegisterFont("ergoec"); cgs.media.whiteShader = cgi_R_RegisterShader("white"); cgs.media.loadTick = cgi_R_RegisterShaderNoMip("gfx/hud/load_tick"); cgs.media.loadTickCap = cgi_R_RegisterShaderNoMip("gfx/hud/load_tick_cap"); const char * force_icon_files[NUM_FORCE_POWERS] = { //icons matching enums forcePowers_t "gfx/mp/f_icon_lt_heal", //FP_HEAL, "gfx/mp/f_icon_levitation", //FP_LEVITATION, "gfx/mp/f_icon_speed", //FP_SPEED, "gfx/mp/f_icon_push", //FP_PUSH, "gfx/mp/f_icon_pull", //FP_PULL, "gfx/mp/f_icon_lt_telepathy", //FP_TELEPATHY, "gfx/mp/f_icon_dk_grip", //FP_GRIP, "gfx/mp/f_icon_dk_l1", //FP_LIGHTNING, "gfx/mp/f_icon_saber_throw", //FP_SABERTHROW "gfx/mp/f_icon_saber_defend", //FP_SABERDEFEND, "gfx/mp/f_icon_saber_attack", //FP_SABERATTACK, "gfx/mp/f_icon_dk_rage", //FP_RAGE, "gfx/mp/f_icon_lt_protect", //FP_PROTECT, "gfx/mp/f_icon_lt_absorb", //FP_ABSORB, "gfx/mp/f_icon_dk_drain", //FP_DRAIN, "gfx/mp/f_icon_sight", //FP_SEE, "gfx/mp/f_icon_stun", //FP_STUN "gfx/mp/f_icon_hate", //FP_HATE "gfx/mp/f_icon_controlmind", //FP_CONTROLMIND "gfx/mp/f_icon_incapacitate", //FP_INCAPACITATE "gfx/mp/f_icon_fear", //FP_FEAR};
as you can see i added the mine. VERY important: the icon need to match exactly the enumeration of force power we did at the start of this tutorial on Q_shared list.
Now go to int showpowers and add your new powers here.
int showPowers[MAX_SHOWPOWERS] = { FP_ABSORB, FP_HEAL, FP_PROTECT, FP_TELEPATHY, FP_SPEED, FP_PUSH, FP_PULL, FP_SEE, FP_DRAIN, FP_LIGHTNING, FP_RAGE, FP_GRIP, FP_STUN, FP_HATE, FP_CONTROLMIND, FP_FREEZE, FP_FEAR, };
at the end of the list. This will show the new force power icons when you scroll the force icon menu on HUD.
Now it’s time to set a name for your power. When you select an icon of aforce power in game, appear the name of the power in yellow. Remember?
You can add the strings with the name of your powers here:
const char * showPowersName[MAX_SHOWPOWERS] = { "SP_INGAME_ABSORB2", "SP_INGAME_HEAL2", "SP_INGAME_PROTECT2", "SP_INGAME_MINDTRICK2", "SP_INGAME_SPEED2", "SP_INGAME_PUSH2", "SP_INGAME_PULL2", "SP_INGAME_SEEING2", "SP_INGAME_DRAIN2", "SP_INGAME_LIGHTNING2", "SP_INGAME_DARK_RAGE2", "SP_INGAME_GRIP2", "SP_INGAME_STUN2", "SP_INGAME_HATE2", "SP_INGAME_CONTROLMIND2", "SP_INGAME_FREEZE2", "SP_INGAME_FEAR2", };
This list need to merge with the show powers list. It’s IMPORTANT.
Now… when you open datapad, you can get a descritpion of each force power and also information about the power level you get into your mission. You can set this chars description here:
const char * forcepowerDesc[NUM_FORCE_POWERS] = { "FORCE_ABSORB_DESC", "FORCE_HEAL_DESC", "FORCE_PROTECT_DESC", "FORCE_MIND_TRICK_DESC", "FORCE_STUN_DESC", "FORCE_JUMP_DESC", "FORCE_SPEED_DESC", "FORCE_PUSH_DESC", "FORCE_PULL_DESC", "FORCE_SABER_THROW_DESC", "FORCE_SABER_DEFENSE_DESC", "FORCE_SABER_OFFENSE_DESC", "FORCE_SENSE_DESC", "FORCE_FREEZE_DESC", "FORCE_DRAIN_DESC", "FORCE_LIGHTNING_DESC", "FORCE_RAGE_DESC", "FORCE_GRIP_DESC", "FORCE_HATE_DESC", "FORCE_CONTROLMIND_DESC", "FORCE_FEAR_DESC", };
add the voice for the chars descrition of your new powers-
Now, you need to make the same thing for other 3 lists, the force power level list strings:
const char * forcepowerLvl1Desc[NUM_FORCE_POWERS] = { "FORCE_ABSORB_LVL1_DESC", "FORCE_HEAL_LVL1_DESC", "FORCE_PROTECT_LVL1_DESC", "FORCE_MIND_TRICK_LVL1_DESC", "FORCE_JUMP_LVL1_DESC", "FORCE_SPEED_LVL1_DESC", "FORCE_PUSH_LVL1_DESC", "FORCE_PULL_LVL1_DESC", "FORCE_SABER_THROW_LVL1_DESC", "FORCE_SABER_DEFENSE_LVL1_DESC", "FORCE_SABER_OFFENSE_LVL1_DESC", "FORCE_SENSE_LVL1_DESC", "FORCE_DRAIN_LVL1_DESC", "FORCE_LIGHTNING_LVL1_DESC", "FORCE_RAGE_LVL1_DESC", "FORCE_GRIP_LVL1_DESC", "FORCE_STUN_LVL1_DESC", "FORCE_HATE_LVL1_DESC", "FORCE_CONTROLMIND_LVL1_DESC", "FORCE_FREEZE_LVL1_DESC", "FORCE_FEAR_LVL1_DESC", }; const char * forcepowerLvl2Desc[NUM_FORCE_POWERS] = { "FORCE_ABSORB_LVL2_DESC", "FORCE_HEAL_LVL2_DESC", "FORCE_PROTECT_LVL2_DESC", "FORCE_MIND_TRICK_LVL2_DESC", "FORCE_JUMP_LVL2_DESC", "FORCE_SPEED_LVL2_DESC", "FORCE_PUSH_LVL2_DESC", "FORCE_PULL_LVL2_DESC", "FORCE_SABER_THROW_LVL2_DESC", "FORCE_SABER_DEFENSE_LVL2_DESC", "FORCE_SABER_OFFENSE_LVL2_DESC", "FORCE_SENSE_LVL2_DESC", "FORCE_DRAIN_LVL2_DESC", "FORCE_LIGHTNING_LVL2_DESC", "FORCE_RAGE_LVL2_DESC", "FORCE_GRIP_LVL2_DESC", "FORCE_STUN_LVL2_DESC", "FORCE_HATE_LVL2_DESC", "FORCE_CONTROLMIND_LVL2_DESC", "FORCE_FREEZE_LVL2_DESC", "FORCE_FEAR_LVL2_DESC", }; const char * forcepowerLvl3Desc[NUM_FORCE_POWERS] = { "FORCE_ABSORB_LVL3_DESC", "FORCE_HEAL_LVL3_DESC", "FORCE_PROTECT_LVL3_DESC", "FORCE_MIND_TRICK_LVL3_DESC", "FORCE_JUMP_LVL3_DESC", "FORCE_SPEED_LVL3_DESC", "FORCE_PUSH_LVL3_DESC", "FORCE_PULL_LVL3_DESC", "FORCE_SABER_THROW_LVL3_DESC", "FORCE_SABER_DEFENSE_LVL3_DESC", "FORCE_SABER_OFFENSE_LVL3_DESC", "FORCE_SENSE_LVL3_DESC", "FORCE_DRAIN_LVL3_DESC", "FORCE_LIGHTNING_LVL3_DESC", "FORCE_RAGE_LVL3_DESC", "FORCE_GRIP_LVL3_DESC", "FORCE_STUN_LVL3_DESC", "FORCE_HATE_LVL3_DESC", "FORCE_CONTROLMIND_LVL3_DESC", "FORCE_FREEZE_LVL3_DESC", "FORCE_FEAR_LVL3_DESC", };
so you got description for all the 3 levels of your powers!
Now it’s time to add a last thing and we end with this file:
// Keep these with groups light side, core, and dark side int showDataPadPowers[MAX_DPSHOWPOWERS] = { // Light side FP_ABSORB, FP_HEAL, FP_PROTECT, FP_TELEPATHY, FP_STUN, // Core Powers FP_LEVITATION, FP_SPEED, FP_PUSH, FP_PULL, FP_SABERTHROW, FP_SABER_DEFENSE, FP_SABER_OFFENSE, FP_SEE, FP_FREEZE, //Dark Side FP_DRAIN, FP_LIGHTNING, FP_RAGE, FP_GRIP, FP_HATE, FP_CONTROLMIND, FP_FEAR, };
that’s will show the powers into the your datapad. Respect the list enumeration for show the light side, dark side and core powers. In my case, hate, fear and controlmind are power of dark side, freeze is neutral, and stun is a light side power.
One last thing and we end with menus staff:
Open cg_info.cpp and add your power in this char array.
Remember: you need to put into the SAME ORDER of enumeration of q_shared forcepowers_t.
const char * showLoadPowersName[] = { "SP_INGAME_HEAL2", "SP_INGAME_JUMP2", "SP_INGAME_SPEED2", "SP_INGAME_PUSH2", "SP_INGAME_PULL2", "SP_INGAME_MINDTRICK2", "SP_INGAME_GRIP2", "SP_INGAME_LIGHTNING2", "SP_INGAME_SABER_THROW2", "SP_INGAME_SABER_OFFENSE2", "SP_INGAME_SABER_DEFENSE2", "SP_INGAME_STUN2", "SP_INGAME_HATE2", "SP_INGAME_CONTROLMIND2", "SP_INGAME_INCAPACITATE2", "SP_INGAME_FEAR2", NULL, };
Okay, now we need to add some other stuff.
Wait a moment, before dream of become a jedi. We are missing a thing.
If you build now your SP dll and exe, you will notice something really bad: it’s possible that your HUD will crash when you scroll the new power icon.
But why? Because there is a limitation related to the max number of power that can be showed into HUD and datapad.
If you go here:
int showDataPadPowers[MAX_DPSHOWPOWERS]
and here:
const char *showPowersName[MAX_SHOWPOWERS] =
you can see there are called two definition. MAX_DPSHOWPOWERS define the maximum amount of force power that show the Datapad menu. MAX_SHOWPOWERS instead, the max amount drawed into HUD. You can change this amount until the value of 32.
Go to cg_local.h
And find this definition:
#define MAX_SHOWPOWERS 12e xtern int showPowers[MAX_SHOWPOWERS]; extern const char * showPowersName[MAX_SHOWPOWERS]; extern int force_icons[NUM_FORCE_POWERS]; #define MAX_DPSHOWPOWERS 16
now ask to you... how many force power you added into your mod? Well, you need to add the answer to the MAX values. In my case, because i added 5 new force powers. (stun, freeze, fear, controlmind and hate) i need to change 12 to 17 and 16 to 21.
And so i get:
#define MAX_SHOWPOWERS 17extern int showPowers[MAX_SHOWPOWERS]; extern const char *showPowersName[MAX_SHOWPOWERS]; extern int force_icons[NUM_FORCE_POWERS]; #define MAX_DPSHOWPOWERS 21
17 is now the number of maximum force power can display my HUD.
21 is the maximun force power displayed of my datapad related to force power description.
Now we have and with all interface and description and hud staff related to force powers.
Let pass to second step: programming the force power.
Your force power get icon and hud description, but again it’s incomplete. Maybe you can see into HUD, and select, or you can give with consolle to your jedi or assign a power level, but it will not work. Is just passive.
So you need know to build the true functionality of your new force power.
For first thing, we need to tell to the code this: when you activate a force power with “f” key, after you selected the power into your menu or hud, the jedi need to cast the power, right? This istruction is envolved here.
Open Bg_misc.cpp and find void PM_CheckForceUseButton( gentity_t *ent, usercmd_t *ucmd ) function.
You will see a switch.
Into the swith there are various case.
When you press F key with one of these power selected, each power run the specific function of the power.
You need into the cases your new force powers.
In my case, i add this:
if (!(ent - > client - > ps.pm_flags & PMF_USEFORCE_HELD)) { //impulse one shot switch (showPowers[cg.forcepowerSelect]) { case FP_HEAL: ForceHeal(ent); break; case FP_SPEED: ForceSpeed(ent); break; case FP_PUSH: ForceThrow(ent, qfalse); break; case FP_PULL: ForceThrow(ent, qtrue); break; case FP_TELEPATHY: ForceTelepathy(ent); break; case FP_STUN: ForceStun(ent); break; case FP_HATE: ForceHate(ent); break; case FP_CONTROLMIND: ForceControlMind(ent); break; case FP_FREEZE: ForceFreeze(ent); break; case FP_FEAR: ForceFear(ent); break; // Added 01/20/03 by AReis. // New Jedi Academy powers. case FP_RAGE: //duration - speed, invincibility and extra damage for short period, drains your health and leaves you weak and slow afterwards. ForceRage(ent); break; case FP_PROTECT: //duration - protect against physical/energy (level 1 stops blaster/energy bolts, level 2 stops projectiles, level 3 protects against explosions) ForceProtect(ent); break; case FP_ABSORB: //duration - protect against dark force powers (grip, lightning, drain - maybe push/pull, too?) ForceAbsorb(ent); break; case FP_SEE: //duration - detect/see hidden enemies ForceSeeing(ent); break; } }
And so, when i select my power, and i press “f” engine run the force power function.
Now we need just a last thing... Write the functions!
And THAT’S is the hardest part, men. I hope you are expert coder and you know good the JKA engine source code.
If you’ll not, at max you can copy \ paste an existing power and simply edit its values. Not really a scientific approach, right?
Well, we shall continue now.
Open WP_SABER.cpp. It’s time to end our task.
At line 172 you will found an array.
This array define the alignment of your force power. They are light side, dark side or neutral core power?
Add add the end of the array your new force power. Important: you need to respect the NUM_FORCE_POWER enumeration stored into q_shared.h
int forcePowerDarkLight[NUM_FORCE_POWERS] = /0 == neutral{ / / nothing should be usable at rank 0 FORCE_LIGHTSIDE, //FP_HEAL,//instant 0, //FP_LEVITATION,//hold/duration 0, //FP_SPEED,//duration 0, //FP_PUSH,//hold/duration 0, //FP_PULL,//hold/duration FORCE_LIGHTSIDE, //FP_TELEPATHY,//instant FORCE_DARKSIDE, /FP_GRIP,/ / hold / duration FORCE_DARKSIDE, //FP_LIGHTNING,//hold/duration 0, //FP_SABERATTACK, 0, //FP_SABERDEFEND, 0, //FP_SABERTHROW, //new Jedi Academy powers FORCE_DARKSIDE, //FP_RAGE,//duration FORCE_LIGHTSIDE, //FP_PROTECT,//duration FORCE_LIGHTSIDE, //FP_ABSORB,//duration FORCE_DARKSIDE, //FP_DRAIN,//hold/duration 0, //FP_SEE,//duration FORCE_LIGHTSIDE, //FP_STUN,//duration FORCE_DARKSIDE, //FP_HATE,//duration FORCE_DARKSIDE, //FP_CONTROLMIND,//duration 0, //FP_FREEZE,//hold/duration/NUM_FORCE_POWERS FORCE_DARKSIDE, //FP_FEAR,//duration};
if the power is a light side power, put FORCE_LIGHTSIDE, if is a Dark power, FORCE_DARKSIDE, if is a core \ neutral power, put 0.
Now scroll below and you can found int forcePowerNeeded[NUM_FORCE_POWERS] =
This set how many force power point need your power for be casted.
So you can set here the values.
REMEMBER: respect the enumeration of q_share NUM_FORCE_POWERS value!
int forcePowerNeeded[NUM_FORCE_POWERS] = { 0, //FP_HEAL,//instant 10, //FP_LEVITATION,//hold/duration 50, //FP_SPEED,//duration 15, //FP_PUSH,//hold/duration 15, //FP_PULL,/hold/duration 20, //FP_TELEPATHY,//instant 1, //FP_GRIP,//hold/duration - FIXME: 30? 1, //FP_LIGHTNING,/hold/duration 20, //FP_SABERTHROW, 1, //FP_SABER_DEFENSE, 0, //FP_SABER_OFFENSE, //new Jedi Academy powers 50, //FP_RAGE,//duration - speed, invincibility and extra damage for short period, drains your health and leaves you weak and slow afterwards. 30, //FP_PROTECT,//duration - protect against physical/energy (level 1 stops blaster/energy bolts, level 2 stops projectiles, level 3 protects against explosions) 30, //FP_ABSORB,/duration - protect against dark force powers (grip, lightning, drain) 1, //FP_DRAIN,//hold/duration - drain force power for health 20, //FP_SEE,//duration - detect/see hidden enemies 20, //FP_STUN 30, //INSPIREHATE 40, //CONTROLMIND 50, //INCAPACITATE 30, //FEAR/NUM_FORCE_POWERS};
Now, go down and you can find a large amount of float and int value. Depending about WHAT your power does, you need to add a float or int value in this fields.
My powers are focused on stun, anger, lock, fear and control the enemy so in my case i added int value, related about the duration of time in millisecond of the force power efx.
Below mindtrick time i added:
int mindTrickTime[NUM_FORCE_POWER_LEVELS] = { 0, //none 10000, //5000, 15000, //10000, 30000 //15000};int StunTime[NUM_FORCE_POWER_LEVELS] ={0,//none 10000, //5000, 20000, //10000, 30000 //15000};int HateTime[NUM_FORCE_POWER_LEVELS] ={0,//none 10000, //5000, 20000, //10000, 30000 //15000};int ControlMindTime[NUM_FORCE_POWER_LEVELS] ={0,//none 60000, //5000, 120000, //10000, 300000 //15000};int FreezeTime[NUM_FORCE_POWER_LEVELS] ={0,//none 10000, //5000, 15000, //10000, 20000 //15000};int FearTime[NUM_FORCE_POWER_LEVELS] ={0,//none 10000, //5000, 20000, //10000, 30000 //15000};
as first value you need to put 0 because is the duration of force power at level 0. no power, no duration.
The other value are related to the duration of force power at level 1 , 2 and 3 of the force.
As you can see: freeze at level one during ten second, at level 2 15, at level three, 20 second. Fear ten, twenty and thirty second, controlmind 1 minut, 2 minut and 5 minutes at level 3. (control an enemy for an entire map area basically XD, so the NPC can reach and activate switchs many far by player position before control expiring.)
Now you will see a loooong serious of void and other parameters. There all related to lightsabers. But if you go down until line 8800 will begin the void related to the force powers.
/OTHER JEDI POWERS=========================================================================
/OTHER JEDI POWERS=========================================================================
/OTHER JEDI POWERS=========================================================================
/OTHER JEDI POWERS=========================================================================
/OTHER JEDI POWERS=========================================================================
And now, well, you need to make a void about YOUR power. And programming it! That’s depend only by you, i cannot help you. I suggest you for deep study each force power code function for understand how works any force power in every part of its effect. As i told at begin of tutorial, some powers function are also into cg_players.cpp (protect, absorb and seeing aura, lightning and draind visual effect) here are setted the efx, sound and functions of grip, mindtrick, push, pull, rage and heal... and, obviously, also of the lightning, drain, protect and absorb.
I will show you as example my force controlmind code. It’s a gift.
So you will understand exactly what you weel need to do. And also, if you want, you can begin your path into the coding force with a cool power. This power will allow you to control your enemies for a LONG time as mindtrick level 4. basically.
You can esc with jump key or you can wait expiration of power. Enemy will not attack you and you cannot damage other enemies. (it’s not correct )
void ForceControlMind(gentity_t * self) { trace_t tr; vec3_t end, forward; gentity_t * traceEnt; qboolean targetLive = qfalse; if (WP_CheckBreakControlMind(self)) { return; } if (self - > health <= 0) { return; } //FIXME: if mind trick 3 and aiming at an enemy need more force power if (!WP_ForcePowerUsable(self, FP_CONTROLMIND, 0)) { return; } if (self - > client - > ps.weaponTime >= 800) { //just did one! return; } if (self - > client - > ps.saberLockTime > level.time) { //FIXME: can this be a way to break out? return; } AngleVectors(self - > client - > ps.viewangles, forward, NULL, NULL); VectorNormalize(forward); VectorMA(self - > client - > renderInfo.headPoint, 2048, forward, end); //Cause a distraction if enemy is not fighting gi.trace( & tr, self - > client - > renderInfo.torsoPoint, vec3_origin, vec3_origin, end, self - > s.number, MASK_OPAQUE | CONTENTS_BODY, (EG2_Collision) 0, 0); if (tr.entityNum == ENTITYNUM_NONE || tr.fraction == 1.0 || tr.allsolid || tr.startsolid) { return; } traceEnt = & g_entities[tr.entityNum]; if (traceEnt - > NPC && traceEnt - > NPC - > scriptFlags & SCF_NO_FORCE) { return; } if (traceEnt && traceEnt - > client) { switch (traceEnt - > client - > NPC_class) { // CLASS CANNOT BE DOMINATED case CLASS_GALAKMECH: case CLASS_ATST: case CLASS_SAND_CREATURE: case CLASS_TAVION: case CLASS_DESANN: case CLASS_JAN: case CLASS_GALAK: case CLASS_LIZARD: case CLASS_MURJJ: case CLASS_FLIER2: case CLASS_GLIDER: case CLASS_FISH: case CLASS_CLAW: case CLASS_REBORN: case CLASS_JEDI: case CLASS_ALORA: case CLASS_MORGANKATARN: case CLASS_WAMPA: case CLASS_MINEMONSTER: case CLASS_KYLE: case CLASS_LUKE: case CLASS_SHADOWTROOPER: case CLASS_LANDO: case CLASS_BARTENDER: case CLASS_UGNAUGHT: case CLASS_WEEQUAY: case CLASS_NOGHRI: case CLASS_TUSKEN: case CLASS_SWAMPTROOPER: case CLASS_GRAN: case CLASS_BESPIN_COP: case CLASS_SABOTEUR: case CLASS_HAZARD_TROOPER: case CLASS_ROCKETTROOPER: case CLASS_REBEL: case CLASS_PROBE: //no droids either case CLASS_GONK: case CLASS_R2D2: case CLASS_MARK1: case CLASS_MARK2: case CLASS_MOUSE: case CLASS_JAWA: case CLASS_HOWLER: case CLASS_SEEKER: case CLASS_SENTRY: case CLASS_REMOTE: case CLASS_PROTOCOL: case CLASS_ASSASSIN_DROID: case CLASS_SABER_DROID: case CLASS_BOBAFETT: // you wanna to control galesh and arcidemon? Just try! // tutti gli elementali e i boss non posson esser stunnati! break; case CLASS_RANCOR: if (!(traceEnt - > spawnflags & 1)) { targetLive = qtrue; } break; default: targetLive = qtrue; break; } } if (targetLive && traceEnt - > NPC && traceEnt - > health > 0) { //hit an organic non-player if (G_ActivateBehavior(traceEnt, BSET_MINDTRICK)) { //activated a script on him //FIXME: do the visual sparkles effect on their heads, still? WP_ForcePowerStart(self, FP_CONTROLMIND, 0); } else if (traceEnt - > client - > playerTeam != self - > client - > playerTeam) { //an enemy int override = 0; if ((traceEnt - > NPC - > scriptFlags & SCF_NO_MIND_TRICK)) { if (traceEnt - > client - > NPC_class == CLASS_GALAKMECH) { G_AddVoiceEvent(traceEnt, Q_irand(EV_CONFUSE1, EV_CONFUSE3), Q_irand(3000, 5000)); } } /*else if ( self->client->ps.forcePowerLevel[FP_CONTROLMIND] > FORCE_LEVEL_0 ) {//control them, even jedi G_SetViewEntity( self, traceEnt ); traceEnt->NPC->controlledTime = level.time+30000; }*/ else if ( /*traceEnt->s.weapon != WP_SABER &&*/ traceEnt - > client - > NPC_class != CLASS_REBORN) { //haha! Jedi aren't easily confused! if (self - > client - > ps.forcePowerLevel[FP_CONTROLMIND] == FORCE_LEVEL_1 && traceEnt - > s.weapon != WP_NONE && traceEnt - > s.weapon != WP_SABER // AT LEVEL 1 YOU CAN CHARM ONLY SHOOTERS //don't charm people who aren't capable of fighting... like ugnaughts and droids, just confuse them /*&& traceEnt->client->NPC_class != CLASS_TUSKEN//don't charm them, just confuse them && traceEnt->client->NPC_class != CLASS_NOGHRI//don't charm them, just confuse them*/ && !Pilot_AnyVehiclesRegistered() //also, don't charm guys when bikes are near ) { //turn them to our side //if mind trick 3 and aiming at an enemy need more force power override = 50; if (self - > client - > ps.forcePower < 50) { return; } if (traceEnt - > enemy) { G_ClearEnemy(traceEnt); } if (traceEnt - > NPC) { traceEnt - > NPC - > tempBehavior = BS_HUNT_AND_KILL; } //FIXME: maybe pick an enemy right here? //FIXME: does nothing to TEAM_FREE and TEAM_NEUTRALs!!! team_t saveTeam = traceEnt - > client - > enemyTeam; traceEnt - > client - > enemyTeam = traceEnt - > client - > playerTeam; traceEnt - > client - > playerTeam = saveTeam; G_SetViewEntity(self, traceEnt); //FIXME: need a *charmed* timer on this...? Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done? traceEnt - > NPC - > controlledTime = level.time + ControlMindTime[self - > client - > ps.forcePowerLevel[FP_CONTROLMIND]]; if (traceEnt - > ghoul2.size() && traceEnt - > headBolt != -1) { //FIXME: what if already playing effect? G_PlayEffect(G_EffectIndex("force/charm"), traceEnt - > playerModel, traceEnt - > headBolt, traceEnt - > s.number, traceEnt - > currentOrigin, ControlMindTime[self - > client - > ps.forcePowerLevel[FP_CONTROLMIND]], qtrue); G_SoundOnEnt(self, CHAN_ITEM, "sound/weapons/force/charm.mp3"); } if (WP_ForcePowerStop, FP_CONTROLMIND) { team_t saveTeam = traceEnt - > client - > playerTeam; traceEnt - > client - > playerTeam = traceEnt - > client - > enemyTeam; traceEnt - > client - > enemyTeam = saveTeam; NPC_PlayConfusionSound(traceEnt); } // AT LEVEL 1 ENEMY NOT TURN TO YOUR SIDE } else if (self - > client - > ps.forcePowerLevel[FP_CONTROLMIND] == FORCE_LEVEL_2 && traceEnt - > s.weapon != WP_NONE // AT LEVEL 2 YOU CAN CHARM SABERIST //don't charm people who aren't capable of fighting... like ugnaughts and droids, just confuse them /*&& traceEnt->client->NPC_class != CLASS_TUSKEN//don't charm them, just confuse them && traceEnt->client->NPC_class != CLASS_NOGHRI//don't charm them, just confuse them*/ && !Pilot_AnyVehiclesRegistered() //also, don't charm guys when bikes are near ) { //turn them to our side //if mind trick 3 and aiming at an enemy need more force power override = 50; if (self - > client - > ps.forcePower < 50) { return; } if (traceEnt - > enemy) { G_ClearEnemy(traceEnt); } if (traceEnt - > NPC) { traceEnt - > NPC - > tempBehavior = BS_HUNT_AND_KILL; } //FIXME: maybe pick an enemy right here? //FIXME: does nothing to TEAM_FREE and TEAM_NEUTRALs!!! team_t saveTeam = traceEnt - > client - > enemyTeam; traceEnt - > client - > enemyTeam = traceEnt - > client - > playerTeam; traceEnt - > client - > playerTeam = saveTeam; G_SetViewEntity(self, traceEnt); // traceEnt->NPC->controlledTime = level.time+20000; //FIXME: need a *charmed* timer on this...? Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done? traceEnt - > NPC - > controlledTime = level.time + ControlMindTime[self - > client - > ps.forcePowerLevel[FP_CONTROLMIND]]; if (traceEnt - > ghoul2.size() && traceEnt - > headBolt != -1) { //FIXME: what if already playing effect? G_PlayEffect(G_EffectIndex("force/charm"), traceEnt - > playerModel, traceEnt - > headBolt, traceEnt - > s.number, traceEnt - > currentOrigin, ControlMindTime[self - > client - > ps.forcePowerLevel[FP_CONTROLMIND]], qtrue); G_SoundOnEnt(self, CHAN_ITEM, "sound/weapons/force/charm.mp3"); } if (WP_ForcePowerStop, FP_CONTROLMIND) { team_t saveTeam = traceEnt - > client - > playerTeam; traceEnt - > client - > playerTeam = traceEnt - > client - > enemyTeam; traceEnt - > client - > enemyTeam = saveTeam; NPC_PlayConfusionSound(traceEnt); // AT LEVEL 2 ENEMY TURN TO ENEMY SIDE WHEN POWER END } } else if (self - > client - > ps.forcePowerLevel[FP_CONTROLMIND] == FORCE_LEVEL_3 && traceEnt - > s.weapon != WP_NONE // AT LEVEL 2 YOU CAN CHARM SABERIST //don't charm people who aren't capable of fighting... like ugnaughts and droids, just confuse them && !Pilot_AnyVehiclesRegistered() //also, don't charm guys when bikes are near ) { //turn them to our side //if mind trick 3 and aiming at an enemy need more force power override = 50; if (self - > client - > ps.forcePower < 50) { return; } if (traceEnt - > enemy) { G_ClearEnemy(traceEnt); } if (traceEnt - > NPC) { traceEnt - > NPC - > tempBehavior = BS_HUNT_AND_KILL; } //FIXME: maybe pick an enemy right here? //FIXME: does nothing to TEAM_FREE and TEAM_NEUTRALs!!! G_SetViewEntity(self, traceEnt); //FIXME: need a *charmed* timer on this...? Or do TEAM_PLAYERS assume that "confusion" means they should switch to team_enemy when done? traceEnt - > NPC - > controlledTime = level.time + ControlMindTime[self - > client - > ps.forcePowerLevel[FP_CONTROLMIND]]; if (traceEnt - > ghoul2.size() && traceEnt - > headBolt != -1) { //FIXME: what if already playing effect? G_PlayEffect(G_EffectIndex("force/charm"), traceEnt - > playerModel, traceEnt - > headBolt, traceEnt - > s.number, traceEnt - > currentOrigin, ControlMindTime[self - > client - > ps.forcePowerLevel[FP_CONTROLMIND]], qtrue); G_SoundOnEnt(self, CHAN_ITEM, "sound/weapons/force/charm.mp3"); } else if (WP_ForcePowerStop, FP_CONTROLMIND) { NPC_PlayConfusionSound(traceEnt); } /*AT LEVEL 3 ENEMY WHEN POWER END BECOME ALLIED. */ } else { //enemy will not attack at this control mind mastery //somehow confuse them? Set don't fire to true for a while? Drop their aggression? Maybe just take their enemy away and don't let them pick one up for a while unless shot? // AT LEVEL 3 ENEMY WILL NOT ATTACK YOU AND TAKE YOU FOR A TEAMMATE. if (self - > client - > ps.forcePower < 90) { return; } if (traceEnt - > enemy) { G_ClearEnemy(traceEnt); } if (traceEnt - > NPC) { traceEnt - > NPC - > tempBehavior = BS_HUNT_AND_KILL; } G_SetViewEntity(self, traceEnt); traceEnt - > NPC - > controlledTime = level.time + ControlMindTime[self - > client - > ps.forcePowerLevel[FP_CONTROLMIND]]; //confused for about 10 seconds*/ if (traceEnt - > ghoul2.size() && traceEnt - > headBolt != -1) { //FIXME: what if already playing effect? G_PlayEffect(G_EffectIndex("force/charm"), traceEnt - > playerModel, traceEnt - > headBolt, traceEnt - > s.number, traceEnt - > currentOrigin, FreezeTime[self - > client - > ps.forcePowerLevel[FP_CONTROLMIND]], qtrue); G_SoundOnEnt(self, CHAN_ITEM, "sound/weapons/force/charm.mp3"); } } } else { NPC_Jedi_PlayConfusionSound(traceEnt); } WP_ForcePowerStart(self, FP_CONTROLMIND, override); } /*else if ( traceEnt->client->playerTeam == self->client->playerTeam ) {//an ally //maybe just have him look at you? Respond? Take your enemy? if ( traceEnt->client->ps.pm_type < PM_DEAD && traceEnt->NPC!=NULL && !(traceEnt->NPC->scriptFlags&SCF_NO_RESPONSE) ) { NPC_UseResponse( traceEnt, self, qfalse ); WP_ForcePowerStart( self, FP_CONTROLMIND, 1 ); } }*/ //NOTE: no effect on TEAM_NEUTRAL? charmed enemy can be possessed again. vec3_t eyeDir; AngleVectors(traceEnt - > client - > renderInfo.eyeAngles, eyeDir, NULL, NULL); VectorNormalize(eyeDir); G_PlayEffect("force/controlmind_touch", traceEnt - > client - > renderInfo.eyePoint, eyeDir); //make sure this plays and that you cannot press fire for about 1 second after this //FIXME: BOTH_FORCEMINDTRICK or BOTH_FORCEDISTRACT NPC_SetAnim(self, SETANIM_TORSO, BOTH_FORCEPUSH, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_RESTART | SETANIM_FLAG_HOLD); //FIXME: build-up or delay this until in proper part of anim } else { if (self - > client - > ps.forcePowerLevel[FP_CONTROLMIND] == FORCE_LEVEL_1 && tr.fraction * 2048 > 64) { //don't create a diversion less than 64 from you of if at power level 1 //use distraction anim instead G_PlayEffect(G_EffectIndex("force/force_controlmind_fail"), tr.endpos, tr.plane.normal); //FIXME: these events don't seem to always be picked up...? AddSoundEvent(self, tr.endpos, 512, AEL_SUSPICIOUS, qtrue, qtrue); AddSightEvent(self, tr.endpos, 512, AEL_SUSPICIOUS, 50); WP_ForcePowerStart(self, FP_CONTROLMIND, 0); } NPC_SetAnim(self, SETANIM_TORSO, BOTH_FORCEPUSH, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_RESTART | SETANIM_FLAG_HOLD); } self - > client - > ps.saberMove = self - > client - > ps.saberBounceMove = LS_READY; //don't finish whatever saber anim you may have been in self - > client - > ps.saberBlocked = BLOCKED_NONE; self - > client - > ps.weaponTime = 1000; if (self - > client - > ps.forcePowersActive & (1 << FP_SPEED)) { self - > client - > ps.weaponTime = floor(self - > client - > ps.weaponTime * g_timescale - > value); } }
THIS is the key for the possession:
G_SetViewEntity( self, traceEnt );
This function change your point of view with the NPC you affected with power.
TraceEnt is the function that allow you to slam your force power against your foe.
A power with traceent, hit NPCs and other clients. A power with self, instead is casted on the player and the jedi. Understood?
At the start of my power code there is this function:
WP_CheckBreakControlMind
This control the camera, exaclty as the WP_Checkbreakcontrol of mindtrick.
You can found wp_cheackbreakcontrol upon void forcetelepathy code.
Here the original:
boolean WP_CheckBreakControl(gentity_t * self) { if (!self) { return qfalse; } if (!self - > s.number) { //player if (self - > client && self - > client - > ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3) { //control-level if (self - > client - > ps.viewEntity > 0 && self - > client - > ps.viewEntity < ENTITYNUM_WORLD) { //we are in a viewentity gentity_t * controlled = & g_entities[self - > client - > ps.viewEntity]; if (controlled - > NPC && controlled - > NPC - > controlledTime > level.time) { //it is an NPC we controlled //clear it and return G_ClearViewEntity(self); return qtrue; } } } } else { //NPC if (self - > NPC && self - > NPC - > controlledTime > level.time) { //being controlled gentity_t * controller = & g_entities[0]; if (controller - > client && controller - > client - > ps.viewEntity == self - > s.number) { //we are being controlled by player if (controller - > client - > ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3) { //control-level mind trick //clear the control and return G_ClearViewEntity(controller); return qtrue; } } } } return qfalse; }
here my called function:
qboolean WP_CheckBreakControlMind(gentity_t * self) { if (!self) { return qfalse; } if (!self - > s.number) { //player if (self - > client && self - > client - > ps.forcePowerLevel[FP_CONTROLMIND] > FORCE_LEVEL_0) { //control-level if (self - > client - > ps.viewEntity > 0 && self - > client - > ps.viewEntity < ENTITYNUM_WORLD) { //we are in a viewentity gentity_t * controlled = & g_entities[self - > client - > ps.viewEntity]; if (controlled - > NPC && controlled - > NPC - > controlledTime > level.time) { //it is an NPC we controlled //clear it and return G_ClearViewEntity(self); return qtrue; } } } } else { //NPC if (self - > NPC && self - > NPC - > controlledTime > level.time) { //being controlled gentity_t * controller = & g_entities[0]; if (controller - > client && controller - > client - > ps.viewEntity == self - > s.number) { //we are being controlled by player if (controller - > client - > ps.forcePowerLevel[FP_CONTROLMIND] > FORCE_LEVEL_0) { //control-level mind trick //clear the control and return G_ClearViewEntity(controller); return qtrue; } } } } return qfalse; }
but this is NOT sufficient. You need also that the Jump key will work for allow you to return into your body. If not, you can stay undefinitley locked into NPC body until he die!
For allow this, you need to go into g_active.cpp and find the ClientThink function.
/* ================== ClientThink A new command has arrived from the client ================== */ extern void PM_CheckForceUseButton(gentity_t * ent, usercmd_t * ucmd); extern qboolean PM_GentCantJump(gentity_t * gent); extern qboolean PM_WeaponOkOnVehicle(int weapon); void ClientThink(int clientNum, usercmd_t * ucmd) { gentity_t * ent; qboolean restore_ucmd = qfalse; usercmd_t sav_ucmd = { 0 }; ent = g_entities + clientNum; if (ent - > s.number < MAX_CLIENTS) { if (ent - > client - > ps.viewEntity > 0 && ent - > client - > ps.viewEntity < ENTITYNUM_WORLD) { //you're controlling another NPC gentity_t * controlled = & g_entities[ent - > client - > ps.viewEntity]; qboolean freed = qfalse; if (controlled - > NPC && controlled - > NPC - > controlledTime && ent - > client - > ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 || controlled - > NPC && controlled - > NPC - > controlledTime && ent - > client - > ps.forcePowerLevel[FP_CONTROLMIND] > FORCE_LEVEL_0) { //An NPC I'm controlling with mind trick if (controlled - > NPC - > controlledTime < level.time) { //time's up! G_ClearViewEntity(ent); freed = qtrue; }
as you can see, i added the FP_CONTROLMIND to the if definition related this. If the npc is controlled by player, when controlled time value is lesser of leveltime of CONTROLMNIND, camera free the NPC and will return to player body.
else if (controlled - > client //an NPC && PM_GentCantJump(controlled) //that cannot jump && controlled - > client - > moveType != MT_FLYSWIM) //and does not use upmove to fly { //these types use jump to get out if (ucmd - > upmove > 0) { //jumping gets you out of it FIXME: check some other button instead... like ESCAPE... so you could even have total control over an NPC? G_ClearViewEntity(ent); ucmd - > upmove = 0; //ucmd->buttons = 0; //stop player from doing anything for a half second after ent - > aimDebounceTime = level.time + 500; freed = qtrue; } }
as you can see, just below is definied this: when player press JUMp button ucmd->upmove > 0 the Npc is released and camera turn back to player.
Now the FP_CONTROLMIND works!
Techniclaly we have end. But making a power like force telepathy,m an istant power, is pretty easy, more difficult is to make an power like grip, heal, rage, or protect. They are more complicated.
I see chunks of code of FP_PROTECT into g_combat.cpp too! Some powers are pretty had to do because they functions are scattered into all the codes. So be careful.
Making a complex force power can be really hard and long, respect at this example. So keep up and get patiente and strenght.
For this, i wanna tell you a last hint.
go to the end of WP_SABER.CPP you can find four voids: WP_forcepowerstart, WP_forcepowerstop, wp_forcepowerrun and wp_checkforcepowers.
These function define special event that happened at the start, the end, the running of force power.
They are all switches and you need to add your power setting into the cases.
In my case i make this:
void WP_ForcePowerStart(gentity_t * self, forcePowers_t forcePower, int overrideAmt) { int duration = 0; //FIXME: debounce some of these? self - > client - > ps.forcePowerDebounce[forcePower] = 0; //and it in //set up duration time switch ((int) forcePower) { case FP_HEAL: self - > client - > ps.forcePowersActive |= (1 << forcePower); self - > client - > ps.forceHealCount = 0; WP_StartForceHealEffects(self); break; case FP_LEVITATION: self - > client - > ps.forcePowersActive |= (1 << forcePower); break; case FP_SPEED: //duration is always 5 seconds, player time duration = ceil(FORCE_SPEED_DURATION * forceSpeedValue[self - > client - > ps.forcePowerLevel[FP_SPEED]]); //FIXME: because the timescale scales down (not instant), this doesn't end up being exactly right... self - > client - > ps.forcePowersActive |= (1 << forcePower); self - > s.loopSound = G_SoundIndex("sound/weapons/force/speedloop.wav"); if (self - > client - > ps.forcePowerLevel[FP_SPEED] > FORCE_LEVEL_2) { //HACK: just using this as a timestamp for when the power started, setting debounce to current time shouldn't adversely affect anything else self - > client - > ps.forcePowerDebounce[FP_SPEED] = level.time; } break; case FP_PUSH: break; case FP_PULL: self - > client - > ps.forcePowersActive |= (1 << forcePower); break; case FP_TELEPATHY: break; case FP_STUN: break; case FP_HATE: break; case FP_CONTROLMIND: break;
qboolean WP_ForcePowerAvailable(gentity_t * self, forcePowers_t forcePower, int overrideAmt) { if (forcePower == FP_LEVITATION) { return qtrue; } int drain = overrideAmt ? overrideAmt : forcePowerNeeded[forcePower]; if (!drain) { return qtrue; } if (self - > client - > ps.forcePower < drain) { //G_AddEvent( self, EV_NOAMMO, 0 ); return qfalse; } return qtrue; }
void WP_ForcePowerStop(gentity_t * self, forcePowers_t forcePower) { gentity_t * gripEnt; gentity_t * drainEnt; if (!(self - > client - > ps.forcePowersActive & (1 << forcePower))) { //umm, wasn't doing it, so... return; } self - > client - > ps.forcePowersActive &= ~(1 << forcePower); switch ((int) forcePower) { case FP_HEAL: //if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 ) { //wasn't an instant heal and heal is now done if (self - > client - > ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2) { //if in meditation pose, must come out of it //FIXME: BOTH_FORCEHEAL_STOP if (self - > client - > ps.legsAnim == BOTH_FORCEHEAL_START) { NPC_SetAnim(self, SETANIM_LEGS, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD); } if (self - > client - > ps.torsoAnim == BOTH_FORCEHEAL_START) { NPC_SetAnim(self, SETANIM_TORSO, BOTH_FORCEHEAL_STOP, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD); } self - > client - > ps.saberMove = self - > client - > ps.saberBounceMove = LS_READY; //don't finish whatever saber anim you may have been in self - > client - > ps.saberBlocked = BLOCKED_NONE; } } WP_StopForceHealEffects(self); if (self - > health >= self - > client - > ps.stats[STAT_MAX_HEALTH] / 3) { gi.G2API_ClearSkinGore(self - > ghoul2); } break; case FP_LEVITATION: self - > client - > ps.forcePowerDebounce[FP_LEVITATION] = 0; break; case FP_SPEED: if (!self - > s.number) { //player using force speed if (g_timescale - > value != 1.0) { if (!(self - > client - > ps.forcePowersActive & (1 << FP_RAGE)) || self - > client - > ps.forcePowerLevel[FP_RAGE] < FORCE_LEVEL_2) { //not slowed down because of force rage gi.cvar_set("timescale", "1"); } } } //FIXME: reset my current anim, keeping current frame, but with proper anim speed // otherwise, the anim will continue playing at high speed self - > s.loopSound = 0; break; case FP_PUSH: break; case FP_PULL: break; case FP_TELEPATHY: break; case FP_STUN: break; case FP_HATE: break; case FP_CONTROLMIND: break;
extern qboolean PM_ForceJumpingUp( gentity_t *gent ); static void WP_ForcePowerRun( gentity_t *self, forcePowers_t forcePower, usercmd_t *cmd ) { float speed, newSpeed; gentity_t *gripEnt, *drainEnt; vec3_t angles, dir, gripOrg, gripEntOrg; float dist; extern usercmd_t ucmd; switch( (int)forcePower ) { case FP_HEAL: if ( self->client->ps.forceHealCount >= FP_MaxForceHeal(self) || self->health >= self->client->ps.stats[STAT_MAX_HEALTH] ) {//fully healed or used up all 25 if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) ) { int index = Q_irand( 1, 4 ); if ( self->s.number < MAX_CLIENTS ) { G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_%c.mp3", index, g_sex->string[0] ) ); } else if ( self->NPC ) { if ( self->NPC->blockedSpeechDebounceTime <= level.time ) {//enough time has passed since our last speech if ( Q3_TaskIDPending( self, TID_CHAN_VOICE ) ) {//not playing a scripted line //say "Ahhh...." if ( self->NPC->stats.sex == SEX_MALE || self->NPC->stats.sex == SEX_NEUTRAL ) { G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_m.mp3", index ) ); } else//all other sexes use female sounds { G_SoundOnEnt( self, CHAN_VOICE, va( "sound/weapons/force/heal%d_f.mp3", index ) ); } } } } } WP_ForcePowerStop( self, forcePower ); } else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_3 && ( (cmd->buttons&BUTTON_ATTACK) || (cmd->buttons&BUTTON_ALT_ATTACK) || self->painDebounceTime > level.time || (self->client->ps.weaponTime&&self->client->ps.weapon!=WP_NONE) ) ) {//attacked or was hit while healing... //stop healing WP_ForcePowerStop( self, forcePower ); } else if ( self->client->ps.forcePowerLevel[FP_HEAL] < FORCE_LEVEL_2 && ( cmd->rightmove || cmd->forwardmove || cmd->upmove > 0 ) ) {//moved while healing... FIXME: also, in WP_ForcePowerStart, stop healing if any other force power is used //stop healing WP_ForcePowerStop( self, forcePower ); } else if ( self->client->ps.forcePowerDebounce[FP_HEAL] < level.time ) {//time to heal again if ( WP_ForcePowerAvailable( self, forcePower, 4 ) ) {//have available power int healInterval = FP_ForceHealInterval( self ); int healAmount = 1;//hard, normal healing rate if ( self->s.number < MAX_CLIENTS ) { if ( g_spskill->integer == 1 ) {//medium, heal twice as fast healAmount *= 2; } else if ( g_spskill->integer == 0 ) {//easy, heal 3 times as fast... healAmount *= 3; } } if ( self->health + healAmount > self->client->ps.stats[STAT_MAX_HEALTH] ) { healAmount = self->client->ps.stats[STAT_MAX_HEALTH] - self->health; } self->health += healAmount; self->client->ps.forceHealCount += healAmount; self->client->ps.forcePowerDebounce[FP_HEAL] = level.time + healInterval; WP_ForcePowerDrain( self, forcePower, 4 ); } else {//stop WP_ForcePowerStop( self, forcePower ); } } break; case FP_LEVITATION: if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !self->client->ps.forceJumpZStart ) {//done with jump WP_ForcePowerStop( self, forcePower ); } else { if ( PM_ForceJumpingUp( self ) ) {//holding jump in air if ( cmd->upmove > 10 ) {//still trying to go up if ( WP_ForcePowerAvailable( self, FP_LEVITATION, 1 ) ) { if ( self->client->ps.forcePowerDebounce[FP_LEVITATION] < level.time ) { WP_ForcePowerDrain( self, FP_LEVITATION, 5 ); self->client->ps.forcePowerDebounce[FP_LEVITATION] = level.time + 100; } self->client->ps.forcePowersActive |= ( 1 << FP_LEVITATION ); self->client->ps.forceJumpCharge = 1;//just used as a flag for the player, cleared when he lands } else {//cut the jump short WP_ForcePowerStop( self, forcePower ); } } else {//cut the jump short WP_ForcePowerStop( self, forcePower ); } } else { WP_ForcePowerStop( self, forcePower ); } } break; case FP_SPEED: speed = forceSpeedValue[self->client->ps.forcePowerLevel[FP_SPEED]]; if ( !self->s.number ) {//player using force speed if ( !(self->client->ps.forcePowersActive&(1<<FP_RAGE)) || self->client->ps.forcePowerLevel[FP_SPEED] >= self->client->ps.forcePowerLevel[FP_RAGE] ) {//either not using rage or rage is at a lower level than speed gi.cvar_set("timescale", va("%4.2f", speed)); if ( g_timescale->value > speed ) { newSpeed = g_timescale->value - 0.05; if ( newSpeed < speed ) { newSpeed = speed; } gi.cvar_set("timescale", va("%4.2f", newSpeed)); } } } break; case FP_PUSH: break; case FP_PULL: break; case FP_TELEPATHY: break; case FP_STUN: break; case FP_HATE: break; case FP_CONTROLMIND: break; case FP_FEAR: break;
void WP_CheckForcedPowers(gentity_t * self, usercmd_t * ucmd) { for (int forcePower = FP_FIRST; forcePower < NUM_FORCE_POWERS; forcePower++) { if ((self - > client - > ps.forcePowersForced & (1 << forcePower))) { switch (forcePower) { case FP_HEAL: ForceHeal(self); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_LEVITATION: //nothing break; case FP_SPEED: ForceSpeed(self); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_PUSH: ForceThrow(self, qfalse); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_PULL: ForceThrow(self, qtrue); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_TELEPATHY: //FIXME: target at enemy? ForceTelepathy(self); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_FREEZE: //FIXME: target at enemy? ForceFreeze(self); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_GRIP: ucmd - > buttons &= ~(BUTTON_ATTACK | BUTTON_ALT_ATTACK | BUTTON_FORCE_FOCUS | BUTTON_FORCE_DRAIN | BUTTON_FORCE_LIGHTNING); ucmd - > buttons |= BUTTON_FORCEGRIP; //holds until cleared break; case FP_LIGHTNING: ucmd - > buttons &= ~(BUTTON_ATTACK | BUTTON_ALT_ATTACK | BUTTON_FORCE_FOCUS | BUTTON_FORCEGRIP | BUTTON_FORCE_DRAIN); ucmd - > buttons |= BUTTON_FORCE_LIGHTNING; //holds until cleared break; case FP_SABERTHROW: ucmd - > buttons &= ~(BUTTON_ATTACK | BUTTON_FORCE_FOCUS | BUTTON_FORCEGRIP | BUTTON_FORCE_DRAIN | BUTTON_FORCE_LIGHTNING); ucmd - > buttons |= BUTTON_ALT_ATTACK; //holds until cleared? break; case FP_SABER_DEFENSE: //nothing break; case FP_SABER_OFFENSE: //nothing break; case FP_RAGE: ForceRage(self); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_PROTECT: ForceProtect(self); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_ABSORB: ForceAbsorb(self); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_DRAIN: ucmd - > buttons &= ~(BUTTON_ATTACK | BUTTON_ALT_ATTACK | BUTTON_FORCE_FOCUS | BUTTON_FORCEGRIP | BUTTON_FORCE_LIGHTNING); ucmd - > buttons |= BUTTON_FORCE_DRAIN; //holds until cleared break; case FP_SEE: //nothing break; case FP_STUN: //FIXME: target at enemy? ForceStun(self); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_HATE: //FIXME: target at enemy? ForceHate(self); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_CONTROLMIND: //FIXME: target at enemy? ForceControlMind(self); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; case FP_FEAR: //FIXME: target at enemy? ForceFear(self); //do only once self - > client - > ps.forcePowersForced &= ~(1 << forcePower); break; } } } }
At the end, there is also the WP_Initforcepower.
What did it... i am not much sure. I think it setting a preset value of force power level for various force power when you start a map with devmapall command.
If you want to get your new power by the start of a level, you need to add it here, and also with the level value you desire.
void WP_InitForcePowers(gentity_t * ent) { if (!ent || !ent - > client) { return; } if (!ent - > client - > ps.forcePowerMax) { ent - > client - > ps.forcePowerMax = FORCE_POWER_MAX; } if (!ent - > client - > ps.forcePowerRegenRate) { ent - > client - > ps.forcePowerRegenRate = 100; } ent - > client - > ps.forcePower = ent - > client - > ps.forcePowerMax; ent - > client - > ps.forcePowerRegenDebounceTime = 0; ent - > client - > ps.forceGripEntityNum = ent - > client - > ps.forceDrainEntityNum = ent - > client - > ps.pullAttackEntNum = ENTITYNUM_NONE; ent - > client - > ps.forceRageRecoveryTime = 0; ent - > client - > ps.forceDrainTime = 0; ent - > client - > ps.pullAttackTime = 0; if (ent - > s.number < MAX_CLIENTS) { //player if (!g_cheats - > integer) //devmaps give you all the FP { ent - > client - > ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_1; } else { ent - > client - > ps.forcePowersKnown = (1 << FP_HEAL) | (1 << FP_LEVITATION) | (1 << FP_SPEED) | (1 << FP_PUSH) | (1 << FP_PULL) | (1 << FP_TELEPATHY) | (1 << FP_GRIP) | (1 << FP_LIGHTNING) | (1 << FP_SABERTHROW) | (1 << FP_SABER_DEFENSE) | (1 << FP_SABER_OFFENSE) | (1 << FP_RAGE) | (1 << FP_DRAIN) | (1 << FP_PROTECT) | (1 << FP_ABSORB) | (1 << FP_SEE); ent - > client - > ps.forcePowerLevel[FP_HEAL] = FORCE_LEVEL_2; ent - > client - > ps.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_2; ent - > client - > ps.forcePowerLevel[FP_PUSH] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_PULL] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_SABERTHROW] = FORCE_LEVEL_2; ent - > client - > ps.forcePowerLevel[FP_SPEED] = FORCE_LEVEL_2; ent - > client - > ps.forcePowerLevel[FP_LIGHTNING] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_TELEPATHY] = FORCE_LEVEL_2; ent - > client - > ps.forcePowerLevel[FP_RAGE] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_PROTECT] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_ABSORB] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_DRAIN] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_SEE] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_SABER_DEFENSE] = FORCE_LEVEL_3; ent - > client - > ps.forcePowerLevel[FP_SABER_OFFENSE] = FORCE_LEVEL_3; ent - > client - > ps.forcePowerLevel[FP_GRIP] = FORCE_LEVEL_2; ent - > client - > ps.forcePowerLevel[FP_STUN] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_HATE] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_CONTROLMIND] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_FREEZE] = FORCE_LEVEL_1; ent - > client - > ps.forcePowerLevel[FP_FEAR] = FORCE_LEVEL_1; } } }
Well. We have end! Good luck and
May Force be with you.
Build the openjk_sp.86.dll and jagamex86.dll and have fun with your new power.
Recommended Comments
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now