Jump to content

Jedi Academy turned 20 this year! We celebrated in a ton of different ways: mod contest, server event, podcast, etc. Thank you to all who have been a part of this game for the last two decades. Check out the anniversary content!

Read more

Welcome to JKHub

This community is dedicated to the games Star Wars: Jedi Outcast (2002) and Jedi Academy (2003). We host over 3,000 mods created by passionate fans around the world, and thousands of threads of people showcasing their works in progress and asking for assistance. From mods to art to troubleshooting help, we probably have it. If we don't, request or contribute!

Get started

This game turned 20 years old this year, and it is still one of the greatest Star Wars games of all time. If you're new or returning from a long hiatus, here are the basics of getting started with Star Wars Jedi Knight Jedi Academy in 2023.

Read more

How to make a new force power (Single player)


Asgarath83

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! :D

 

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 :P )

 

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. :D

 

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.

 

:D


User Feedback

Recommended Comments

Hi, i'm new here so i'm sorry for stupid question, but where exactly this code is? I am very interested in creating new force power, but it seem i know nothing of modding...

Thanks

Link to comment
On 2/24/2021 at 5:59 PM, Samuel Shepard said:

Hi, i'm new here so i'm sorry for stupid question, but where exactly this code is? I am very interested in creating new force power, but it seem i know nothing of modding...

Thanks

https://github.com/JACoders/OpenJK Here.

Download Zip, Enable Git, create your repository and have fun 🙂

Link to comment


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 account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...