Jump to content

eezstreet

Members
  • Posts

    5,207
  • Joined

  • Last visited

Everything posted by eezstreet

  1. Hello, this is another FAQ (Frequently Asked Questions) tutorial - this one covers debugging. Many of you have requested a proper tutorial on debugging, so I have decided to write one in great detail. This tutorial assumes that you are using OpenJK and Visual Studio. Some of the aspects may be similar on Mac using xcode. I will be using Visual Studio 2013 and the WIP JK2 SP code from OpenJK as an example, since I have it readily available. For the duration of this tutorial, make sure your project is in the Debug configuration. You can check which configuration you're in currently by looking at the drop-down menu next to the green arrow (or next to "Local Windows Debugger" if you're using VS2012+) Some Initial Settings Before you begin debugging, you will need to do some basic housekeeping on your generated projects. This needs to be done every time you regenerate your projects using CMake or the .bat script. On each of your projects in the Solution Explorer, right click on a project, and select properties (or press Alt+Enter). You will see a large dialog box open with many different options. On the left, you will find the "C/C++" category. Expand it, and click on the General tab. There will be an option within this panel saying "Debug Information Format". Make sure this is set to "Program Database for Edit And Continue (/ZI)": When you turn this option on, it allows you to change the code while the program is paused, and continue to run with your newly-modified code. Apply this setting to every project. Now you need to tell the debugger to run the correct executable. Right click on the correct project ("SP Client", "MP Client", "JK2 SP Client") and hit "Set As Startup Project". If you'd like to add any extra options (fs_game for instance), you can alter those in Project Properties > Debugging > Command Arguments. This works the same way as it would in a .bat file. You will also need to set fs_basepath to point to your Gamedata folder (not pictured) Your Debugging Toolbox Congratulations! By clicking on the green arrow at the top of the main window, you can now launch your game using the green "Start" arrow at the top of the screen (or by pressing F5). I'll take the time to now explain some of the key features of most debuggers. But first, some vocabulary specific to debugging: Breakpoint: A place where an interruption is made. Instruction Pointer: The next instruction that will be executed by the program Call Stack: A trace of how the instruction pointer reached its destination by scanning the previous functions Breakpoints Breakpoints are one of the most powerful, if not the most powerful tool in your arsenal. By right-clicking a line, you can insert a breakpoint with Breakpoint > Insert Breakpoint. A small red circle should appear to the left of the line. I'm going to put one in Com_Printf (common.cpp, in the engine) now, as a simple example. Let's go ahead and run the program. Make sure you are always compiling before debugging! As you can see, the program has stopped once the game has tried to print something. I will go over the other panels (your display may be different) later. In this paused state, we can change code, check variables at that specific point in time (see the Locals tab below), add or remove breakpoints, alter the current value of any variables (use this with extreme caution), and more. You can configure a breakpoint to have more specific options. By right clicking on the breakpoint, we can alter different aspects of the breakpoint. Delete Breakpoint: Removes the breakpoint Disable Breakpoint: Disables the breakpoint, but doesn't delete it. The breakpoint can then be reinstated by going to the breakpoints panel and turning it back on. Location: Changes the location of the breakpoint. Condition: Allows you to attach a condition to the breakpoint. For instance, you can check the value of a variable. Think of it as an if statement, but for your breakpoints. (NOTE: degrades performance) Hit Count: Here, you can view the number of times the breakpoint is hit. You can also have the debugger ignore the breakpoint, and only trip it if that line of code has been executed a certain number of times. Filter: Not important, as the game is not properly multithreaded. When Hit: You can choose to have the debugger print a message in the Output panel instead of actually breaking. Edit Labels: Allows you to attach a label to a breakpoint, so it has a more descriptive name in the Breakpoints panel. Step Over/Step Into/Step Out These commands allow you to "step through" the program as it is being run. You can see for instance, how a particular function plays out. Here is an explanation of what each one does: Step Over: Executes the current statement, and brings the instruction pointer further. If the instruction pointer is currently at the end of the function, it will also perform a Step Out. Step Into: If the current statement has a function, step into that function. Step Out: Executes the rest of the code past the instruction pointer for that function, and stops once the instruction pointer has reached the outside of the function. Let's continue with the current example. By stepping over twice, the program has executed two more instructions, and the instruction pointer has moved forward two steps. Those two steps modified variables msg and argptr, so note the difference in their values compared to the screenshot above. Since the last instruction modified msg, that variable is now highlighted in red in the Locals panel. Run to Cursor By right clicking a line and selecting this option, the program will execute until it reaches that point. Think of it as a temporary breakpoint, meant to save you the extra step of having to delete it. Variable Control and Monitoring When you are debugging, you will need to keep track of many different variables. Fortunately, there are several tricks and tools that you can use to make things easier. I have already shown you the Locals panel, which keeps track of all local variables within a function. There are also two other methods which are effective at keeping track of a variable's value. The first (and easiest) way to get the value of a variable is by simply hovering over it in the code display: If you want to modify the value of a variable, you can do so by clicking on the value and changing it (and pressing Enter). This is not recommended. The second way is to use a Watch. Watches are great for global variables, as they aren't specific to a scope. There's two ways to add a Watch. You can either enter the variable name into the Watches panel, or right click on the variable and Add Watch. The Watch panel is also good because it serves as an expression parser, and you can perform various math operations on variables within the Watch. Some examples: Call Stack The call stack is a useful tool that allows you to see where your code was called from. So, in our example, I can crawl back up to Com_Init where it was called, by going to the call stack and selecting the second option. The call stack is always displayed in reverse order. Most Common Errors Now that you've got (more or less) all the tools you need to start debugging, let's examine a few common errors. Unhandled Exception: Access Violation (c00000005) This is the most frequent crash you'll encounter in all of your programming in C and C++. Basically, you're trying to dereference a null pointer. Take this code for instance: gentity_t *alwaysNull = nullptr; // or NULL alwaysNull->s.clientNum = 0; The second line will cause a crash, since alwaysNull is a pointer to a null spot in memory. Trying to therefore access s would cause a crash, since you're outside the bounds of the normal program's memory. (You're violating the access that your program is allowed to have, hence the Access Violation.) Stack Overflow There's too many entries in the call stack. One of the most frequent causes of this is calling a function within itself (using a principle known as recursion), and not providing an exit case for the function to stop running. That's a lot of words that might not make a lot of sense if you're a beginner, so here's an example of what I'm referring to: void MyFunction(int x) { MyFunction(x); } If you were to call MyFunction(100), this code would cause a stack overflow. If you look at the call stack, you can see why: MyFunction is calling itself over and over again without a way to escape! Generally recursion should be carefully used (and ideally, as little as possible) to avoid issues of stack overflow. While it's possible that stack overflow may occur for other reasons besides faulty recursion, it's not common. Assertion Failure By using an assert() statement, you can assert that something ought to be true. For example: assert(a == b); This code will assert that a and b are equal to each other. But what happens if they are not equal to each other? Well, in a release or final build of the game, nothing at all will happen. But in a debug build of the game, you'll likely get a message box asking to abort, retry or ignore, stating that the assertion failed. Asserts are a tool that lets you know if something that shouldn't happen, does happen, without using conditional breakpoints which may slow down your performance greatly.
  2. Welcome to part three of the FAQ (Frequently Asked Questions) series. This is a very simple, one step process. Open up bg_pmove.c (or bg_pmove.cpp in SP), and in the function PM_WeaponOkOnVehicle (it will look like this:), //see if a weapon is ok to use on a vehicle qboolean PM_WeaponOkOnVehicle( int weapon ) { //FIXME: check g_vehicleInfo for our vehicle? switch ( weapon ) { //case WP_NONE: case WP_MELEE: case WP_SABER: case WP_BLASTER: //case WP_THERMAL: return qtrue; break; } return qfalse; } Add whatever weapons you want to be included here. So for instance, if we wanted to make the Repeater usable on a swoop, you might want to add it thusly: //see if a weapon is ok to use on a vehicle qboolean PM_WeaponOkOnVehicle( int weapon ) { //FIXME: check g_vehicleInfo for our vehicle? switch ( weapon ) { //case WP_NONE: case WP_MELEE: case WP_SABER: case WP_BLASTER: case WP_REPEATER: // eezstreet add //case WP_THERMAL: return qtrue; break; } return qfalse; } That's all you need to do! There's nothing else needed here.
  3. Welcome to the second entry in the FAQ (Frequently Asked Questions) series. These will teach you how to do things which are frequently asked about on the forums. Important Note: You must compile the engine as well as the DLLs in order to do this. Important Note 2: You are limited to 32 force powers, no more. Important Note 3: This will render your game non-backwards compatible with savegames and connectivity. You will need a new icon, but the rest is up to you. Sorry in advance about the broken indentation, JKHub doesn't like it when I copy/paste indents. Today you're going to learn about the Jedi's ways of the Force, young padawan. Specifically, you're going to learn how to create a new force power. The first thing we're going to do, just like in the weapons tutorial, is define our force power. I'm going to be making a force power called Magic Missiles to use as an example. It fires multiple homing missiles and costs 70 force power to use. So to specify the power, we're going to alter the forcePowers_t enum in q_shared.h. It looks like this: 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_RAGE,//duration FP_PROTECT, FP_ABSORB, FP_TEAM_HEAL, FP_TEAM_FORCE, FP_DRAIN, FP_SEE, FP_SABER_OFFENSE, FP_SABER_DEFENSE, FP_SABERTHROW, NUM_FORCE_POWERS } forcePowers_t; So we'll want to add ourselves a new power here. I'm going to add my power right between Sense and Saber Offense, since that seems most appropriate. 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_RAGE,//duration FP_PROTECT, FP_ABSORB, FP_TEAM_HEAL, FP_TEAM_FORCE, FP_DRAIN, FP_SEE, FP_MAGIC_MISSILES, // eezstreet add FP_SABER_OFFENSE, FP_SABER_DEFENSE, FP_SABERTHROW, NUM_FORCE_POWERS } forcePowers_t; I'm going to skip around here a little bit, and work on a few key areas that need addressed first. In bg_misc.c, we have a number of important structures that need filled out. First off, we have bgForcePowerCost. This defines how many force points are needed for each rank to unlock the power. Magic Missiles is going to be a quite hefty costing power, because it's pretty powerful. So, this is what mine looks like: int bgForcePowerCost[NUM_FORCE_POWERS][NUM_FORCE_POWER_LEVELS] = //0 == neutral { { 0, 2, 4, 6 }, // Heal // FP_HEAL { 0, 0, 2, 6 }, // Jump //FP_LEVITATION,//hold/duration { 0, 2, 4, 6 }, // Speed //FP_SPEED,//duration { 0, 1, 3, 6 }, // Push //FP_PUSH,//hold/duration { 0, 1, 3, 6 }, // Pull //FP_PULL,//hold/duration { 0, 4, 6, 8 }, // Mind Trick //FP_TELEPATHY,//instant { 0, 1, 3, 6 }, // Grip //FP_GRIP,//hold/duration { 0, 2, 5, 8 }, // Lightning //FP_LIGHTNING,//hold/duration { 0, 4, 6, 8 }, // Dark Rage //FP_RAGE,//duration { 0, 2, 5, 8 }, // Protection //FP_PROTECT,//duration { 0, 1, 3, 6 }, // Absorb //FP_ABSORB,//duration { 0, 1, 3, 6 }, // Team Heal //FP_TEAM_HEAL,//instant { 0, 1, 3, 6 }, // Team Force //FP_TEAM_FORCE,//instant { 0, 2, 4, 6 }, // Drain //FP_DRAIN,//hold/duration { 0, 2, 5, 8 }, // Sight //FP_SEE,//duration { 0, 4, 6, 8 }, // Magic Missiles //FP_MAGIC_MISSILES // eezstreet add { 0, 1, 5, 8 }, // Saber Attack //FP_SABER_OFFENSE, { 0, 1, 5, 8 }, // Saber Defend //FP_SABER_DEFENSE, { 0, 4, 6, 8 } // Saber Throw //FP_SABERTHROW, //NUM_FORCE_POWERS }; Directly below that, we have forcePowerSorted, which is the position of the force powers in the wheel (when scrolling through the powers using q/e on the keyboard). I'm going to put mine along with all the other light side powers. int forcePowerSorted[NUM_FORCE_POWERS] = { //rww - always use this order when drawing force powers for any reason FP_TELEPATHY, FP_HEAL, FP_ABSORB, FP_MAGIC_MISSILES, // eezstreet add FP_PROTECT, FP_TEAM_HEAL, FP_LEVITATION, FP_SPEED, FP_PUSH, FP_PULL, FP_SEE, FP_LIGHTNING, FP_DRAIN, FP_RAGE, FP_GRIP, FP_TEAM_FORCE, FP_SABER_OFFENSE, FP_SABER_DEFENSE, FP_SABERTHROW }; Next we have forcePowerDarkLight, which describes the affiliation of a power (whether or not it is dark-sided or light-sided). Mine is going to be a light-sided power, so we're going to make sure we specify that. 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 FORCE_DARKSIDE,//FP_RAGE,//duration FORCE_LIGHTSIDE,//FP_PROTECT,//duration FORCE_LIGHTSIDE,//FP_ABSORB,//duration FORCE_LIGHTSIDE,//FP_TEAM_HEAL,//instant FORCE_DARKSIDE,//FP_TEAM_FORCE,//instant FORCE_DARKSIDE,//FP_DRAIN,//hold/duration 0,//FP_SEE,//duration FORCE_LIGHTSIDE, // FP_MAGIC_MISSILES // eezstreet add 0,//FP_SABER_OFFENSE, 0,//FP_SABER_DEFENSE, 0//FP_SABERTHROW, //NUM_FORCE_POWERS }; That's the last of the edits in bg_misc.c. Next up is bg_saga.c. This is only a minor edit, we're going to add one entry to FPTable so that people can use Magic Missiles in a siege class. 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_RAGE), ENUM2STRING(FP_PROTECT), ENUM2STRING(FP_ABSORB), ENUM2STRING(FP_TEAM_HEAL), ENUM2STRING(FP_TEAM_FORCE), ENUM2STRING(FP_DRAIN), ENUM2STRING(FP_SEE), ENUM2STRING(FP_MAGIC_MISSILES), // eezstreet add ENUM2STRING(FP_SABER_OFFENSE), ENUM2STRING(FP_SABER_DEFENSE), ENUM2STRING(FP_SABERTHROW), {"", -1} }; Now we need to edit showPowersName in cg_draw.c. This defines an entry in .str files that is shown on screen when we're cycling through the powers, e.g. "Force Sight". The string can be replaced in sp_ingame.str for your language. char *showPowersName[] = { "HEAL2",//FP_HEAL "JUMP2",//FP_LEVITATION "SPEED2",//FP_SPEED "PUSH2",//FP_PUSH "PULL2",//FP_PULL "MINDTRICK2",//FP_TELEPTAHY "GRIP2",//FP_GRIP "LIGHTNING2",//FP_LIGHTNING "DARK_RAGE2",//FP_RAGE "PROTECT2",//FP_PROTECT "ABSORB2",//FP_ABSORB "TEAM_HEAL2",//FP_TEAM_HEAL "TEAM_REPLENISH2",//FP_TEAM_FORCE "DRAIN2",//FP_DRAIN "SEEING2",//FP_SEE "MAGICMISSILES2", // FP_MAGIC_MISSILES // eezstreet add "SABER_OFFENSE2",//FP_SABER_OFFENSE "SABER_DEFENSE2",//FP_SABER_DEFENSE "SABER_THROW2",//FP_SABERTHROW NULL }; Optional: Holocron model If you plan on reimplementing Holocron FFA, we need to add an entry to forceHolocronModels in cg_ents.c, so that the game knows what model to use for Holocrons. char *forceHolocronModels[] = { "models/map_objects/mp/lt_heal.md3", //FP_HEAL, "models/map_objects/mp/force_jump.md3", //FP_LEVITATION, "models/map_objects/mp/force_speed.md3", //FP_SPEED, "models/map_objects/mp/force_push.md3", //FP_PUSH, "models/map_objects/mp/force_pull.md3", //FP_PULL, "models/map_objects/mp/lt_telepathy.md3", //FP_TELEPATHY, "models/map_objects/mp/dk_grip.md3", //FP_GRIP, "models/map_objects/mp/dk_lightning.md3", //FP_LIGHTNING, "models/map_objects/mp/dk_rage.md3", //FP_RAGE, "models/map_objects/mp/lt_protect.md3", //FP_PROTECT, "models/map_objects/mp/lt_absorb.md3", //FP_ABSORB, "models/map_objects/mp/lt_healother.md3", //FP_TEAM_HEAL, "models/map_objects/mp/dk_powerother.md3", //FP_TEAM_FORCE, "models/map_objects/mp/dk_drain.md3", //FP_DRAIN, "models/map_objects/mp/force_sight.md3", //FP_SEE, "INSERT_PATH_TO_MODEL_HERE", // FP_MAGIC_MISSILES // eezstreet add "models/map_objects/mp/saber_attack.md3", //FP_SABER_OFFENSE, "models/map_objects/mp/saber_defend.md3", //FP_SABER_DEFENSE, "models/map_objects/mp/saber_throw.md3" //FP_SABERTHROW }; Next up, we need to define the force power icon. These are (very strangely) kept in holocronicons.h, which is unique in that it only contains this structure. const char *HolocronIcons[NUM_FORCE_POWERS] = { "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_dk_rage", //FP_RAGE, "gfx/mp/f_icon_lt_protect", //FP_PROTECT, "gfx/mp/f_icon_lt_absorb", //FP_ABSORB, "gfx/mp/f_icon_lt_healother", //FP_TEAM_HEAL, "gfx/mp/f_icon_dk_forceother", //FP_TEAM_FORCE, "gfx/mp/f_icon_dk_drain", //FP_DRAIN, "gfx/mp/f_icon_sight", //FP_SEE, "gfx/mp/f_icon_pull", // FP_MAGIC_MISSILES // eezstreet add, I'm just using the Pull icon for now as a test. "gfx/mp/f_icon_saber_attack", //FP_SABER_OFFENSE, "gfx/mp/f_icon_saber_defend", //FP_SABER_DEFENSE, "gfx/mp/f_icon_saber_throw" //FP_SABERTHROW }; Alright, cool! Now we're going to dive into the engine source code. Brace yourself, because this is going to be a doozy. We're first going to open up q_shared.h again, and this time we're going to be focusing on a different enum: genCmds_t. This is used for quick commands by the client, and if we don't add a special case for our new power, the game will throw a fit (but still likely work as expected). typedef enum { GENCMD_SABERSWITCH = 1, GENCMD_ENGAGE_DUEL, GENCMD_FORCE_HEAL, GENCMD_FORCE_SPEED, GENCMD_FORCE_THROW, GENCMD_FORCE_PULL, GENCMD_FORCE_DISTRACT, GENCMD_FORCE_RAGE, GENCMD_FORCE_PROTECT, GENCMD_FORCE_ABSORB, GENCMD_FORCE_HEALOTHER, GENCMD_FORCE_FORCEPOWEROTHER, GENCMD_FORCE_SEEING, GENCMD_FORCE_MAGIC_MISSILES, // eezstreet add GENCMD_USE_SEEKER, GENCMD_USE_FIELD, GENCMD_USE_BACTA, GENCMD_USE_ELECTROBINOCULARS, GENCMD_ZOOM, GENCMD_USE_SENTRY, GENCMD_USE_JETPACK, GENCMD_USE_BACTABIG, GENCMD_USE_HEALTHDISP, GENCMD_USE_AMMODISP, GENCMD_USE_EWEB, GENCMD_USE_CLOAK, GENCMD_SABERATTACKCYCLE, GENCMD_TAUNT, GENCMD_BOW, GENCMD_MEDITATE, GENCMD_FLOURISH, GENCMD_GLOAT } genCmds_t; Right. Now in cl_input.cpp (it's in client/), we're going to add a new entry here in the switch within IN_UseGivenForce. Fairly straightforward. switch(forceNum) { case FP_DRAIN: IN_Button11Down(); IN_Button11Up(); break; case FP_PUSH: genCmdNum = GENCMD_FORCE_THROW; break; case FP_SPEED: genCmdNum = GENCMD_FORCE_SPEED; break; case FP_PULL: genCmdNum = GENCMD_FORCE_PULL; break; case FP_TELEPATHY: genCmdNum = GENCMD_FORCE_DISTRACT; break; case FP_GRIP: IN_Button6Down(); IN_Button6Up(); break; case FP_LIGHTNING: IN_Button10Down(); IN_Button10Up(); break; case FP_RAGE: genCmdNum = GENCMD_FORCE_RAGE; break; case FP_PROTECT: genCmdNum = GENCMD_FORCE_PROTECT; break; case FP_ABSORB: genCmdNum = GENCMD_FORCE_ABSORB; break; case FP_SEE: genCmdNum = GENCMD_FORCE_SEEING; break; case FP_HEAL: genCmdNum = GENCMD_FORCE_HEAL; break; case FP_TEAM_HEAL: genCmdNum = GENCMD_FORCE_HEALOTHER; break; case FP_TEAM_FORCE: genCmdNum = GENCMD_FORCE_FORCEPOWEROTHER; break; case FP_MAGIC_MISSILES: // eezstreet add genCmdNum = GENCMD_FORCE_MAGIC_MISSILES; break; default: assert(0); break; } Last, but certainly not least, we have w_force.c. This is the main meat of the force power, and it defines what the force power actually does when used. I'm not going to go too far into detail here, because most of what the power does is up to you. However, I will be setting up the framework for your power. The first change here involves WP_ForcePowerStart. The changes here vary between whether you're making a duration power (such as Sense), a held power (such as Lightning) or a one-press power (such as Heal). In my example, I'm using a one-press power, which is by far the easiest thing to deal with in this case. If you want to make a duration power, I suggest you pay close attention as to how Sense does it, because it's a very good example of how you should be dealing with it. But in my example, there really isn't much that needs to be done here. //hearable and hearDist are merely for the benefit of bots, and not related to if a sound is actually played. //If duration is set, the force power will assume to be timer-based. switch( (int)forcePower ) { case FP_HEAL: hearable = qtrue; hearDist = 256; self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); break; case FP_LEVITATION: hearable = qtrue; hearDist = 256; self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); break; case FP_SPEED: hearable = qtrue; hearDist = 256; if (self->client->ps.fd.forcePowerLevel[FP_SPEED] == FORCE_LEVEL_1) { duration = 10000; } else if (self->client->ps.fd.forcePowerLevel[FP_SPEED] == FORCE_LEVEL_2) { duration = 15000; } else if (self->client->ps.fd.forcePowerLevel[FP_SPEED] == FORCE_LEVEL_3) { duration = 20000; } else //shouldn't get here { break; } if (overrideAmt) { duration = overrideAmt; } self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); break; case FP_PUSH: hearable = qtrue; hearDist = 256; break; case FP_PULL: hearable = qtrue; hearDist = 256; break; case FP_TELEPATHY: hearable = qtrue; hearDist = 256; if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_1) { duration = 20000; } else if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_2) { duration = 25000; } else if (self->client->ps.fd.forcePowerLevel[FP_TELEPATHY] == FORCE_LEVEL_3) { duration = 30000; } else //shouldn't get here { break; } self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); break; case FP_GRIP: hearable = qtrue; hearDist = 256; self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); self->client->ps.powerups[PW_DISINT_4] = level.time + 60000; break; case FP_LIGHTNING: hearable = qtrue; hearDist = 512; duration = overrideAmt; overrideAmt = 0; self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); self->client->ps.activeForcePass = self->client->ps.fd.forcePowerLevel[FP_LIGHTNING]; break; case FP_RAGE: hearable = qtrue; hearDist = 256; if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_1) { duration = 8000; } else if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_2) { duration = 14000; } else if (self->client->ps.fd.forcePowerLevel[FP_RAGE] == FORCE_LEVEL_3) { duration = 20000; } else //shouldn't get here { break; } self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); break; case FP_PROTECT: hearable = qtrue; hearDist = 256; duration = 20000; self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); break; case FP_ABSORB: hearable = qtrue; hearDist = 256; duration = 20000; self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); break; case FP_TEAM_HEAL: hearable = qtrue; hearDist = 256; self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); break; case FP_TEAM_FORCE: hearable = qtrue; hearDist = 256; self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); break; case FP_DRAIN: hearable = qtrue; hearDist = 256; duration = overrideAmt; overrideAmt = 0; self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); //self->client->ps.activeForcePass = self->client->ps.fd.forcePowerLevel[FP_DRAIN]; break; case FP_SEE: hearable = qtrue; hearDist = 256; if (self->client->ps.fd.forcePowerLevel[FP_SEE] == FORCE_LEVEL_1) { duration = 10000; } else if (self->client->ps.fd.forcePowerLevel[FP_SEE] == FORCE_LEVEL_2) { duration = 20000; } else if (self->client->ps.fd.forcePowerLevel[FP_SEE] == FORCE_LEVEL_3) { duration = 30000; } else //shouldn't get here { break; } self->client->ps.fd.forcePowersActive |= ( 1 << forcePower ); break; case FP_SABER_OFFENSE: break; case FP_SABER_DEFENSE: break; case FP_SABERTHROW: break; case FP_MAGIC_MISSILES: // eezstreet add hearDist = 256; hearable = qtrue; break; default: break; } OK! Now we're going to create a function that is where we define the behavior of the force power when used (yay!). Very simply put, we're going to first declare the function in g_local.h (towards the bottom). void ForceMagicMissiles( gentity_t *self ); And then in w_force.c, we're going to actually define the function and get in depth with how we're going to shoot missiles and the like. So right around where the other functions are being defined (such as ForceTeamReplenish), we're going to define our own: void ForceMagicMissiles( gentity_t *self ) { // Write the code here yourself, as this is the behavior of your force power --eez } Okay. We have one last edit that needs to be done here in w_force.c, and we're good to go. After this: case FP_TEAM_FORCE: powerSucceeded = 0; //always 0 for nonhold powers if (self->client->ps.fd.forceButtonNeedRelease) { //need to release before we can use nonhold powers again break; } ForceTeamForceReplenish(self); self->client->ps.fd.forceButtonNeedRelease = 1; break; Add our little block of code, which looks like this: case FP_MAGIC_MISSILES: ForceMagicMissiles( self ); break; Okay, super. Now in bg_pmove.c, we need to make an edit here so that the force power costs don't get thrown out of whack... int forcePowerNeeded[NUM_FORCE_POWER_LEVELS][NUM_FORCE_POWERS] = { { //nothing should be usable at rank 0.. 999,//FP_HEAL,//instant 999,//FP_LEVITATION,//hold/duration 999,//FP_SPEED,//duration 999,//FP_PUSH,//hold/duration 999,//FP_PULL,//hold/duration 999,//FP_TELEPATHY,//instant 999,//FP_GRIP,//hold/duration 999,//FP_LIGHTNING,//hold/duration 999,//FP_RAGE,//duration 999,//FP_PROTECT,//duration 999,//FP_ABSORB,//duration 999,//FP_TEAM_HEAL,//instant 999,//FP_TEAM_FORCE,//instant 999,//FP_DRAIN,//hold/duration 999,//FP_SEE,//duration 999,//FP_MAGIC_MISSILES // eezstreet add 999,//FP_SABER_OFFENSE, 999,//FP_SABER_DEFENSE, 999//FP_SABERTHROW, //NUM_FORCE_POWERS }, { 65,//FP_HEAL,//instant //was 25, but that was way too little 10,//FP_LEVITATION,//hold/duration 50,//FP_SPEED,//duration 20,//FP_PUSH,//hold/duration 20,//FP_PULL,//hold/duration 20,//FP_TELEPATHY,//instant 30,//FP_GRIP,//hold/duration 1,//FP_LIGHTNING,//hold/duration 50,//FP_RAGE,//duration 50,//FP_PROTECT,//duration 50,//FP_ABSORB,//duration 50,//FP_TEAM_HEAL,//instant 50,//FP_TEAM_FORCE,//instant 20,//FP_DRAIN,//hold/duration 20,//FP_SEE,//duration 70,//FP_MAGIC_MISSILES // eezstreet add 0,//FP_SABER_OFFENSE, 2,//FP_SABER_DEFENSE, 20//FP_SABERTHROW, //NUM_FORCE_POWERS }, { 60,//FP_HEAL,//instant 10,//FP_LEVITATION,//hold/duration 50,//FP_SPEED,//duration 20,//FP_PUSH,//hold/duration 20,//FP_PULL,//hold/duration 20,//FP_TELEPATHY,//instant 30,//FP_GRIP,//hold/duration 1,//FP_LIGHTNING,//hold/duration 50,//FP_RAGE,//duration 25,//FP_PROTECT,//duration 25,//FP_ABSORB,//duration 33,//FP_TEAM_HEAL,//instant 33,//FP_TEAM_FORCE,//instant 20,//FP_DRAIN,//hold/duration 20,//FP_SEE,//duration 60,//FP_MAGIC_MISSILES // eezstreet add 0,//FP_SABER_OFFENSE, 1,//FP_SABER_DEFENSE, 20//FP_SABERTHROW, //NUM_FORCE_POWERS }, { 50,//FP_HEAL,//instant //You get 5 points of health.. for 50 force points! 10,//FP_LEVITATION,//hold/duration 50,//FP_SPEED,//duration 20,//FP_PUSH,//hold/duration 20,//FP_PULL,//hold/duration 20,//FP_TELEPATHY,//instant 60,//FP_GRIP,//hold/duration 1,//FP_LIGHTNING,//hold/duration 50,//FP_RAGE,//duration 10,//FP_PROTECT,//duration 10,//FP_ABSORB,//duration 25,//FP_TEAM_HEAL,//instant 25,//FP_TEAM_FORCE,//instant 20,//FP_DRAIN,//hold/duration 20,//FP_SEE,//duration 50,//FP_MAGIC_MISSILES // eezstreet add 0,//FP_SABER_OFFENSE, 0,//FP_SABER_DEFENSE, 20//FP_SABERTHROW, //NUM_FORCE_POWERS } }; One last edit in g_active.c is needed, and then we get to focus on the lovely UI. Right after: case GENCMD_FORCE_SEEING: ForceSeeing(ent); break; Add: case GENCMD_FORCE_MAGIC_MISSILES: ForceMagicMissiles(ent); break; Okay, now it's time for the UI. I'm not going to get into detail as to the changes required to the force menu (the .menu file), because that's pretty easy to figure out and also because I am slightly lazy. But we do need to make some actual changes in the code in order for that stuff to work properly. Most of our changes are going to take place in ui_force.c (of course!) so let's open that up. Towards the top of the file, we already have some things that need additions to them. qboolean uiForcePowersDisabled[NUM_FORCE_POWERS] = { qfalse,//FP_HEAL,//instant qfalse,//FP_LEVITATION,//hold/duration qfalse,//FP_SPEED,//duration qfalse,//FP_PUSH,//hold/duration qfalse,//FP_PULL,//hold/duration qfalse,//FP_TELEPATHY,//instant qfalse,//FP_GRIP,//hold/duration qfalse,//FP_LIGHTNING,//hold/duration qfalse,//FP_RAGE,//duration qfalse,//FP_PROTECT, qfalse,//FP_ABSORB, qfalse,//FP_TEAM_HEAL, qfalse,//FP_TEAM_FORCE, qfalse,//FP_DRAIN, qfalse,//FP_SEE, qfalse,//FP_MAGIC_MISSILES // eezstreet add qfalse,//FP_SABER_OFFENSE, qfalse,//FP_SABER_DEFENSE, qfalse//FP_SABERTHROW, }; int uiForcePowersRank[NUM_FORCE_POWERS] = { 0,//FP_HEAL = 0,//instant 1,//FP_LEVITATION,//hold/duration, this one defaults to 1 (gives a free point) 0,//FP_SPEED,//duration 0,//FP_PUSH,//hold/duration 0,//FP_PULL,//hold/duration 0,//FP_TELEPATHY,//instant 0,//FP_GRIP,//hold/duration 0,//FP_LIGHTNING,//hold/duration 0,//FP_RAGE,//duration 0,//FP_PROTECT, 0,//FP_ABSORB, 0,//FP_TEAM_HEAL, 0,//FP_TEAM_FORCE, 0,//FP_DRAIN, 0,//FP_SEE, 0,//FP_MAGIC_MISSILES // eezstreet add 1,//FP_SABER_OFFENSE, //default to 1 point in attack 1,//FP_SABER_DEFENSE, //defualt to 1 point in defense 0//FP_SABERTHROW, }; int uiForcePowerDarkLight[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 FORCE_DARKSIDE,//FP_RAGE,//duration FORCE_LIGHTSIDE,//FP_PROTECT,//duration FORCE_LIGHTSIDE,//FP_ABSORB,//duration FORCE_LIGHTSIDE,//FP_TEAM_HEAL,//instant FORCE_DARKSIDE,//FP_TEAM_FORCE,//instant FORCE_DARKSIDE,//FP_DRAIN,//hold/duration 0,//FP_SEE,//duration FORCE_LIGHTSIDE,//FP_MAGIC_MISSILES, // eezstreet add 0,//FP_SABER_OFFENSE, 0,//FP_SABER_DEFENSE, 0//FP_SABERTHROW, //NUM_FORCE_POWERS }; One more thing in ui_force.c that needs changing. We need to change this: int gCustPowersRank[NUM_FORCE_POWERS] = { 0,//FP_HEAL = 0,//instant 1,//FP_LEVITATION,//hold/duration, this one defaults to 1 (gives a free point) 0,//FP_SPEED,//duration 0,//FP_PUSH,//hold/duration 0,//FP_PULL,//hold/duration 0,//FP_TELEPATHY,//instant 0,//FP_GRIP,//hold/duration 0,//FP_LIGHTNING,//hold/duration 0,//FP_RAGE,//duration 0,//FP_PROTECT, 0,//FP_ABSORB, 0,//FP_TEAM_HEAL, 0,//FP_TEAM_FORCE, 0,//FP_DRAIN, 0,//FP_SEE, 0,//FP_MAGIC_MISSILES, // eezstreet add 0,//FP_SABER_OFFENSE, 0,//FP_SABER_DEFENSE, 0//FP_SABERTHROW, };
  4. Welcome to the first entry of my FAQ (Frequently Asked Questions) series. These will teach you how to do things which are frequently asked about on the forums. A big thank you goes out to Ensiform and redsaurus, who helped me with the editing process of the tutorial. Important Note: You must compile the engine as well as the DLLs in order to do this. Important Note 2: You are limited to 32 weapons, no more. Important Note 3: This will render your game non-backwards compatible with savegames and connectivity. You'll need: A weapon model, icon, effects, and sounds. You can use preexisting models and so forth, but it obviously isn't as cool as a new weapon! With that being said, let's dive in..I'm going to be creating a new weapon called the T-21 Light Repeating Blaster. First, we'll want to actually get our source code set up and run a test compile. I'll assume that you've already done this, and you have some basic understanding of how the compile process works. Next up, we're going to bg_weapons.h. Right at the top of the file, we're going to do our first modification. typedef enum { WP_NONE, WP_STUN_BATON, WP_MELEE, WP_SABER, WP_BRYAR_PISTOL, WP_BLASTER, WP_DISRUPTOR, WP_BOWCASTER, WP_REPEATER, WP_DEMP2, WP_FLECHETTE, WP_ROCKET_LAUNCHER, WP_THERMAL, WP_TRIP_MINE, WP_DET_PACK, WP_CONCUSSION, WP_BRYAR_OLD, WP_EMPLACED_GUN, WP_TURRET, // WP_GAUNTLET, // WP_MACHINEGUN, // Bryar // WP_SHOTGUN, // Blaster // WP_GRENADE_LAUNCHER, // Thermal // WP_LIGHTNING, // // WP_RAILGUN, // // WP_GRAPPLING_HOOK, WP_NUM_WEAPONS } weapon_t; We're going to add our own little entry here. For the purpose of my example, we're going to do it like this: typedef enum { WP_NONE, WP_STUN_BATON, WP_MELEE, WP_SABER, WP_BRYAR_PISTOL, WP_BLASTER, WP_DISRUPTOR, WP_BOWCASTER, WP_REPEATER, WP_DEMP2, WP_FLECHETTE, WP_ROCKET_LAUNCHER, WP_THERMAL, WP_TRIP_MINE, WP_DET_PACK, WP_CONCUSSION, WP_T21, // this is my new addition WP_BRYAR_OLD, WP_EMPLACED_GUN, WP_TURRET, // WP_GAUNTLET, // WP_MACHINEGUN, // Bryar // WP_SHOTGUN, // Blaster // WP_GRENADE_LAUNCHER, // Thermal // WP_LIGHTNING, // // WP_RAILGUN, // // WP_GRAPPLING_HOOK, WP_NUM_WEAPONS } weapon_t; The reason we put it before WP_BRYAR_OLD is because WP_BRYAR_OLD is always the last selectable weapon in the game, unless you change some other code around. We like shortcuts! Next up, take a look at q_shared.h. We'll need to change MAX_WEAPONS. #define MAX_WEAPONS 19 to #define MAX_WEAPONS 32 // one time change to change the weapon limit to 32 Now we get to modify some fun bits of code. Anyone who has used weapons.dat will be sorta familiar with this chunk of code. This is weaponData, which in SP is defined in weapons.dat. This step is easy enough. We need to add a new entry here for our weapon. Open up bg_weapons.c, and just before: { // WP_BRYAR_OLD, Add in this new entry: { // WP_T21, // "T21 Light Repeater", // char classname[32]; // Spawning name AMMO_BLASTER, // int ammoIndex; // Index to proper ammo slot 40, // int ammoLow; // Count when ammo is low 20, // int energyPerShot; // Amount of energy used per shot 2000, // int fireTime; // Amount of time between firings 8192, // int range; // Range of weapon 2, // int altEnergyPerShot; // Amount of energy used for alt-fire 120, // int altFireTime; // Amount of time between alt-firings 8192, // int altRange; // Range of alt-fire 0, // int chargeSubTime; // ms interval for subtracting ammo during charge 0, // int altChargeSubTime; // above for secondary 0, // int chargeSub; // amount to subtract during charge on each interval 0, //int altChargeSub; // above for secondary 0, // int maxCharge; // stop subtracting once charged for this many ms 0 // int altMaxCharge; // above for secondary }, Next up, we'll want to take a look at bg_misc.c. In the array, int WeaponReadyAnim[WP_NUM_WEAPONS] = we'll want to add a reference to our own animation here. So right after the line that reads: TORSO_WEAPONREADY3,//WP_CONCUSSION We'll want to add something like this: TORSO_WEAPONREADY3,//WP_T21 Do the same for WeaponReadyLegsAnim and WeaponAttackAnim. OpenJK note: You'll want to make sure that BASE_COMPAT is undefined, or that the #ifdef blocks around this chunk of code is removed: #ifndef BASE_COMPAT //JAC: Raven forgot the Concussion's firing animation BOTH_ATTACK3,//WP_CONCUSSION, #endif // BASE_COMPAT Otherwise, your weapon might have weird firing anims. Blame the raven. Further on down the file, there's a really big array called bg_itemlist. Basically, we need to add a new entry to this array. So, just before this line: /*QUAKED weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ Add some code that looks something like this: /*QUAKED weapon_t21 (.3 .3 1) (-16 -16 -16) (16 16 16) suspended New T21 weapon */ { "weapon_t21", "sound/weapons/w_pkup.wav", { "models/weapons2/briar_pistol/briar_pistol_w.glm", // CHANGE THIS PATH TO THE WEAPON MODEL 0, 0, 0}, /* view */ "models/weapons2/briar_pistol/briar_pistol.md3", // CHANGE THIS PATH TO THE FIRST PERSON MODEL /* icon */ "gfx/hud/w_icon_briar",//"gfx/hud/w_icon_rifle", // CHANGE THIS PATH TO THE ICON PATH /* pickup */// "T21 Light Repeating Blaster", 100, // Amount of ammo to start with? IT_WEAPON, WP_T21, /* precache */ "", /* sounds */ "", "@SP_INGAME_T21" // description }, We're done with bg_misc.c, so you can go ahead and close it. The next thing we're going to do is make our weapon usable in Siege mode. This is a piece of cake. Open bg_saga.c and just before: ENUM2STRING(WP_BRYAR_OLD), add: ENUM2STRING(WP_T21), Optional: If you want your weapon to add scorch marks to people, go into cg_event.c. Find this line of code: case WP_BRYAR_OLD: And add this line of code right after it: case WP_T21: Here comes another fun bit of code. We're going to now alter the code that handles all the effects of the weapon, such as the bullet impacts and so forth. Open up cg_weaponinit.c, and cg_local.h. First things first, we need to add a few definitions. In cg_local.h, we need to add the following lines of code, somewhere in cgEffects_t, such as right after the line that says "// BLASTER": // T21 fxHandle_t t21ShotEffect; fxHandle_t t21WallImpactEffect; fxHandle_t t21FleshImpactEffect; fxHandle_t t21DroidImpactEffect; Next, we'll need to get into cg_weaponinit.c. Right after: case WP_SABER: MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ); weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/saber/saber_w.glm" ); break; Add this: case WP_T21: weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/bryar/select.wav"); // CHANGE ME weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bryar/fire.wav"); // CHANGE ME weaponInfo->firingSound = NULL_SOUND; weaponInfo->chargeSound = NULL_SOUND; weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "bryar/muzzle_flash" ); // CHANGE ME weaponInfo->missileModel = NULL_HANDLE; weaponInfo->missileSound = NULL_SOUND; weaponInfo->missileDlight = 0; //weaponInfo->missileDlightColor= {0,0,0}; weaponInfo->missileHitSound = NULL_SOUND; weaponInfo->missileTrailFunc = FX_T21ProjectileThink; weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/bryar/alt_fire.wav"); // CHANGE ME weaponInfo->altFiringSound = NULL_SOUND; weaponInfo->altChargeSound = NULL_SOUND; weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "bryar/muzzle_flash" ); // CHANGE ME weaponInfo->altMissileModel = NULL_HANDLE; weaponInfo->altMissileSound = NULL_SOUND; weaponInfo->altMissileDlight = 0; //weaponInfo->altMissileDlightColor= {0,0,0}; weaponInfo->altMissileHitSound = NULL_SOUND; weaponInfo->altMissileTrailFunc = FX_T21AltProjectileThink; cgs.effects.t21ShotEffect = trap_FX_RegisterEffect( "bryar/shot" ); cgs.effects.t21WallImpactEffect = trap_FX_RegisterEffect( "bryar/wall_impact" ); cgs.effects.t21FleshImpactEffect = trap_FX_RegisterEffect( "bryar/flesh_impact" ); cgs.effects.t21DroidImpactEffect = trap_FX_RegisterEffect( "bryar/droid_impact" ); break; In this next step, you have a bit of choice. You can either create your own file, called fx_t21.c, in the cgame folder, or you can hijack an existing fx_ file, like how the concussion rifle does it. Either way, you'll need to make sure that fx_local.h is #include'd, and that you add a new function called FX_T21ProjectileThink and FX_T21AltProjectileThink. Make sure you add a declaration in fx_local.h for this function, and that the function is similar to other FX_ projectile thinking functions, such as FX_BryarProjectileThink. You'll also need to add a FX_T21ProjectileHitPlayer and FX_T21ProjectileHitWall, and make sure they're used in a similar fashion as FX_BryarHitPlayer and FX_BryarHitWall, respectively. One last step. We need to actually define the behavior of the gun when it fires! This is more simple than it sounds. In g_weapon.c, after: case WP_BRYAR_OLD: WP_FireBryarPistol( ent, altFire ); break; Add something which looks like: case WP_T21: WP_FireT21( ent, altFire ); break; WP_FireT21 can do whatever you want it to do. This is the main meat of your weapon, this is what makes it do what it does. There's plenty of examples to base your work off of. I personally think that the Blaster Rifle and bowcaster are two good weapons to base it off of, as they're fairly simple and don't involve any sort of charging mechanics.
  5. So there's some huge issues with our process: Everything is packed together manually, which has tons of errors due to manual copy errorsPK3s are quite bad; their compression ratio is either too low or their decompression time is too slow for what is neededThere's no central place to put thingsThere's no easy way to patch thingsSo, I've got a few ideas: Use a shared Dropbox or something with Explorer integration. Create a script that builds the PK3s or whatever asset system we wantSupport more filetypes, like LZMA compression. Or maybe invent our own archive format.Create a system of "patching" PK3s through delta compression.
  6. oops Note that you'll only have to edit the .bat if you put the folder into the gamedata folder, i changed it so that it should be placed in the Jedi Academy folder. but the folder is named wrong. I've heard about the crashing issue from someone else. I'll look into it later today and see what's going on with it.
  7. https://jkhub.org/topic/9932-released-jkgalaxies-130a-preview-version/ @@Darth Futuza @ @@Dalo Lorn @@swegmaster @ @@Noodle @@Onysfx @@Ramikad This is the version that will be used in the scrimmage. Please download it and maybe run it, to see if there are any obvious errors.
  8. For more details on what this does, please see this thread. No previous installation is required to run this. DOWNLOAD LINK
  9. Jedi Knight Galaxies v1.4.0 will feature lightsaber combat. While this system was working in previous versions, the move to OpenJK broke the blocking system somehow and it needs to be looked at. JKG's lightsaber combat was developed from scratch, using the base lightsaber code as a starting point. We will also be diversifying the game modes. This version will introduce a new gametype, Duel Mode, which is similar to the original game's duel mode. Like JKA, you take turns fighting each other. The winner of each round is awarded some credits. Players who are spectating can place bets on fighters. The first person to reach the credit limit wins. You can purchase equipment as a spectator or a fighter. More expensive lightsabers are often better than the cheap ones, but you will of course need to pool your credits to win! New Features To be completedBug Fixes To be completed
  10. Our JSON files load faster than COM_Parse reads files because of how the format works. It's also readable by Javascript and Apache, making documentation (through a generated wiki, for example), an extremely easy task. Plus it's not buggy like COM_Parse is.
  11. @@Darth Futuza @ @@Silverfang @@Dalo Lorn @@swegmaster @ @@Noodle @@Onysfx @@Ramikad How does Saturday December 30th, 2017 at 14:00-17:00 GMT-3 sound ?
  12. Paul Blart: Mall Cop is a solid 8/10 for me, I found the humor and Kevin James' portrayal of a mall cop to be startingly accurate in these trying times.

    1. Bek

      Bek

      I'm glad others share my love of Paul Blart: Mall Cop.

    2. Seven

      Seven

      hater. the inventive cinematography and nuanced writing easily puts the film at a 10. at least

  13. No worries, I will compile it in winxp-32.
  14. Most of the new features are pretty minor, but you can check the changelog.
  15. It doesn't even really have to be a force power. It can be scripted in through ICARUS from what I understand.
  16. Probably not. The only thing that mod includes off the top of my head is the velocity halving.
  17. Buffs and debuffs (both controlled by the same system), can either help or harm the player. They are applied to a player using either Lua scripting (WIP) or using a weapon. This replaces the damagetype field in .wpn files. Buffs are defined in buffs.json; a player can have a total of 16 buffs (each stack of a buff consumes one slot) applied to them at a time. Here is the current buffs.json, to help you get an idea of how they work in code: { "standard-fire": { "category": "fire", "canceling": { "priority": 50, "noBuffStack": true, "noCategoryStack": true, "waterRemoval": true, "rollRemoval": true, "cancelOther": [ { "category": "cold", "priority": 50 } ] }, "damage": { "damage": 2, "means": "MOD_IGNITED", "damageRate": 500 }, "visuals": { "efx": { "effect": "player/fire", "bolt": "lower_lumbar", "debounce": 200 } } }, "standard-freeze": { "category": "cold", "canceling": { "priority": 50, "noBuffStack": true, "noCategoryStack": true, "cancelOther": [ { "category": "fire", "priority": 100 } ] }, "damage": { "damage": 2, "means": "MOD_FROZEN", "damagerate": 1000 }, "passive": { "pmove": "PM_FREEZE" }, "visuals": { "shader": { "shader": "gfx/PlayerOverlays/ice", "shaderRGBA": [ 63, 63, 127, 255 ], "shaderLen": 100000 } } }, "standard-stun": { "category": "stun", "canceling": { "noBuffStack": true, "noCategoryStack": true }, "passive": { "pmove": "PM_FREEZE" }, "visuals": { "shader": { "shader": "gfx/PlayerOverlays/stun", "shaderRGBA": [ 0, 0, 127, 255 ], "shaderLen": 400 } } }, "standard-carbonite": { "category": "carbonite", "canceling": { "noBuffStack": true, "noCategoryStack": true }, "damage": { "damage": 2, "means": "MOD_CARBONITE", "damagerate": 1000 }, "passive": { "pmove": "PM_FREEZE" }, "visuals": { "shader": { "shader": "gfx/PlayerOverlays/carbonite", "shaderRGBA": [ 50, 50, 50, 255 ], "shaderLen": 100000 } } }, "standard-bleed": { "category": "bleed", "canceling": { "noBuffStack": true, "noCategoryStack": true }, "damage": { "damage": 1, "means": "MOD_BLEEDING", "damagerate": 1000 } }, "standard-poison": { "category": "poison", "canceling": { "noBuffStack": true, "noCategoryStack": true }, "damage": { "damage": 4, "means": "MOD_POISONED", "damagerate": 1000 } } // movement speed modifier is not implemented yet //"standard-cold": { // "category": "cold", // // "canceling": { // }, // // "damage": { // } //} } (For a full explanation about what all these fields do, check the Extended Data.txt in the source branch) Buffs can cancel out buffs either in their own category or in other categories. For example, fire can cancel out freezing and so on. (Fire can also be canceled out by freezing, or by rolling/swimming). For right now, all they can do is either damage the player or freeze them in place. In the future some buffs might grant you additional stats like invisibility/cloaking, resistance towards certain damage types, etc; fleshing out the content is still a work in progress.
  18. It's actually much easier to implement such a system in SP, since you don't have to worry about networking the data. When you get more proficient on C++ I can talk you through it sometime.
  19. Animations being hardcoded really has nothing to do with the Ghoul2 format, and more to do with how the animation system is coded. You see, the skeleton format is just a series of frames with no rhyme or reason behind them, and the animation.cfg is what binds the frames to specific animations. What makes animations so hardcoded is very simple; the game just wasn't built for adding more animations. It's just a table of animations with strings mapping to integer values which is easier to work with. As far as file formats are concerned, you have to consider the following: - Is this something which I want people to edit quickly, but not necessarily load quickly and space isn't an issue? Go with a plaintext format, like JSON or the formats that JKA has for .sab, .npc, etc. - Is this something which I want to load quickly, or contains so much data that space becomes an issue? Go with binary data, like JPG or something. Loading binary data is way easier unless you use a library. In which case, loading plaintext data can be a cinch: https://github.com/JKGDevs/JediKnightGalaxies/blob/develop/codemp/game/bg_jetpacks.cpp Model data is normally binary but COLLADA (.dae) files are notably plaintext. I think FBX may be, too. Note though, because I feel like this needs repeated. This is just an ideas thread; I don't really have any motivation to work on a new format.
  20. The video options. In setup. Change Fullscreen to On.
  21. Development screenshots showing off how the differences in ammo types can make a huge difference. In 1.3.0, we added the ability to purchase and use new special ammo types. Some weapons have multiple ammo types at the same time, while others have different ammo types that you can cycle through. Some are a mix of both! Explosive Quarrels are extremely powerful, and turn your bowcaster into a glorified grenade launcher: ...while Splitting Quarrels do the exact opposite, and turn your bowcaster into a glorified shotgun: This change has been brought to you by Engine Changes . We had to up the limit of loaded .efx files in order for this to work correctly. HAYO!
  22. So now let's get into the meat of it; what an ammo type can override. I coded all of this over a night or so. In 1.3.0 ammo will not be able to override debuffs granted to a player or clip size because of technical reasons. So no incendiary ammo or extended magazines then. But this other stuff can be overridden: BehaviorDamage TypeProjectile countDamage radiusProjectile hitboxRecoilAmmo usageFiring speedBouncingAccuracy ratingKnockback inflicted on shooterWhether the weapon is hitscan or notProjectile speedWhether projectiles are affected by gravity or notVisualsShoot muzzle flash effectMuzzle flash dynamic lightCharging effectFiring soundCrosshair shader (change crosshair while using this ammo)Traceline effects (if any hitscan weapons use this ammo)Projectile effectProjectile modelProjectile sound effectProjectile dynamic lightProjectile hit effectProjectile miss effectLightsaber deflection effectI'm going to update the OP with some ammo types and their effects. I want to have an open discussion about the types of ammo that are in the game because I think that's something we still need to work on for the scrimmage.
  23. Sabers will be in the next major revision, 1.4.0. You can now hide the inventory panel while in the shop and see detailed information about the item you have selected:
  24. Did you try changing it to fullscreen in the video options?
  25. I like this, but I have the following feedback: There's just simply too many crime tiers. I think if you boil it down to like 3 tiers (maybe show it with a debuff; with 3 skulls overhead being the "ultimate" worst), it'll be a lot more understandable to the player.I think crime points is kind of a bad name, CP as an acronym has some uh...interesting connotations... Maybe call it Notoriety or something like that.It should be noted that this is only in RPG mode (ie, phase 3), not Phase 1 or Phase 2 modes, which a server should still be able to boot into and play after Phase 3 is complete.Do you lose 150 CP per equipped item that you lose? (so for example, at 1000+ CP, you lose >450, +100 for the death?)Do crime points increase if done in self defense? ie, killing a player who was attacking you first, would that increase crime points?
×
×
  • Create New...