Jump to content

eezstreet

Members
  • Posts

    5,207
  • Joined

  • Last visited

Everything posted by eezstreet

  1. This is an update to the earlier patch, 1.3.0a, and fixes some important stability issues that were fixed the first time around. We tested this one pretty well and it doesn't seem to have issues. Two options: either upgrade from 1.3.0a or if you haven't installed that already, you can download the full version. Upgrade from 1.3.0a 1.3.0b Full Download
  2. This thread will be used for discussing game balance, before/after the scrimmage. Futuza and I did some testing together, here are some issues that we came up with: Bactas only stack to 1 of each. They should stack to 10.Grenades and bactas are too expensive.Side-to-side max speed should increase, but the friction should increase so it's not as easy to dodge and cover is more important.DE-10 is too cheapNot enough special ammo types. The ones that exist are maybe too expensive.Jetpack movement is weird. The burst jetpacks tend to align on the axes, making bursting in diagonal directions feel weird.Crouch movement is weirdStun grenades are too strong, they should stun for 2-3 seconds and not 4Default bounty amount should be raised to 125 instead of 75Armor slows you down too much and doesn't protect enough for how much it slows you downPlayer should maybe start with all of their magazines reloaded instead of where they were when they died
  3. C and C++ Primer by eezstreet (with edits from Xycaleth and mrwonko) Last updated: 8.9.15 Table of Contents IntroductionC++ 101: Variables and typingC++ 102: Control flowC++ 103: FunctionsC++ 201: PointersIntroductionFirst things first, it goes without saying. Learning how to program is difficult. I'm not saying this to drive away potential coders or anything like that. In fact, I readily encourage newcomers to try out programming and see if it suits them. It just might change your life! The trouble comes when one first looks at a program like JKA and sees a big mess of code and it doesn't look anything like the nice, compact programs that C++ tutorials provide. Or even worse, they have you using constructs like std::string or std::cout which are totally irrelevant C++ concepts in an environment like JKA where either they don't work or aren't the preferred way of doing things. On the other end of the spectrum we have tutorials like the ones here on JKHub which are glorified Copy+Paste and nothing more. I hate these. To be clear, a lot of the stuff you'll learn in the beginning involves using copy+paste, but there's no room for creativity. I like to see people empowered by knowledge instead of blindly changing things in files until it works. That's not programming, that's guessing. I aim to solve some of the confusion here. Where to go to get help There are a lot of places where you can seek guidance from others if you need help with something. First and foremost you should try and research your problem. The search feature here on the forums works well, and there's always Google to help reach these ends. If you still haven't solved your issue, here are a number of places you can go: The JACoders IRC channel*. Flash based webclient here.The Coding and Scripts forum on this website if your question is specific to programming.The OpenJK General Discussion forum is good if your question is specific to OpenJK.StackOverflow.com can provide you with coding help if you need it, but don't expect help regarding JKA specific stuffDirect private messages should probably be avoided as it could help solve problems for other people who encounter the same problem that you have. * A note about IRC: IRC is shorthand for "Internet Relay Chat." As such, it's common to idle in the channel. It may take a while for someone to respond to your messages, so please be patient. Many times have I checked and someone has joined, asked a question, and left before I could answer it! Final Note This tutorial (if you can call it that) isn't meant to teach you how to do any one thing in the game. It's a language primer designed specifically to teach C and C++ in the context of making a mod for this game. It's also not a way of teaching you how to setup an environment. There's already a tutorial for that, and this one assumes you have read it. Some stuff is designated as "ENGINE ONLY" or "C++ only." This means that it cannot be used in C code, which is used in the multiplayer DLL code (which is what the original SDK was composed of) C++ 101: Variables and Typing Programs store information in chunks of memory we call "variables." All variables need to be declared before they can be used, otherwise the compiler will not understand what you are doing. In C++ code, we can declare variables anywhere, but in C code we must declare variables at the beginning of any section that starts with {}s (these are known as blocks). Below are some of the most basic types. int: represents a number, ranging ~negative 2 billion to ~positive 2 billion on 32 bit systemsbool (engine only): represents only two values: true and false.qboolean: represents only two values: qtrue and qfalse.float: represents a decimal number, like 3.14.double: like a float, except it can be more precisechar: represents a character, like 'a' or '@'. Some more complex characters like Japanese symbols are only compatible with types other than char.(pointer): "points to" another variable in memory. More on this later.So that being said, there are a number of manipulations you can do with these:int someNumber; // value = unknown. On this line we are declaring the variable 'someNumber' which is of type 'int' someNumber = 0; // value = 0 someNumber += 10; // value = 10 someNumber++; // value = 11 someNumber--; // value = 10 someNumber -= 2; // value = 8 someNumber /= 2; // value = 4 someNumber *= 2; // value = 8 someNumber %= 3; // value = 2 (this is the "remainder of" operator, known as modulus. Divided numbers are rounded when they are int type.)These are the most basic operations that exist. Here are some things we shouldn't do:int someNumber; // we are declaring a variable 'someNumber' of type 'int' someNumber += 2; // undefined behavior. someNumber doesn't have a value assigned to it, so adding 2 to that value is undefined! someNumber = 0; // this would be okay... someNumber /= someNumber; // we are dividing by zero here, which would invoke a crash.In C++ code only, you can define a variable as being "auto". This means that the type will automatically be deduced.int x = 5; auto y = x; // type is intNote that a variable can have prefixes added to them. Some of these will be discussed later.unsigned int x; // can only be positive or 0 signed int x; // can be positive or negative const int x; // can't be changedThere are of course some more complicated things we can do, but before that we need to understand the concept of binary number systems. Binary Number Systems Every modern computer uses binary to represent numbers (and numbers can represent code...but this is rather deep stuff we're talking about!). But just what is binary? Normally we use 10 numbers to represent a digit - 0 through 9, or base 10. Binary on the other hand is only represented by 2 numbers - 0 and 1. There are of course other systems of numbers, and two others which are commonly used in programming are Octal (digits 0 - 7, base 8) and Hexadecimal (digits 0 - 9..and then A B C D E F, so base 16). This raises the question. How do we represent a number in base 10, like say... 10, in a base 2 number system? There are many different ways, and that's a bit beyond the scope of this guide, but you can use the Windows Calculator to do so by switching to Programmer Mode (Alt+3 or View > Programmer). There are some radio buttons that say "Hex Dec Oct Bin". By selecting Dec (short for Decimal, or Base 10) and entering in whatever number you want to convert, and then selecting Bin (short for Binary, or base 2), you will now see the number in binary. There's also various ways to convert by hand. Now that we have a basic grasp of a binary number system, let's discuss size. Each variable type is assigned a size in bytes. A byte is 8 bits (binary digits) and thus can represent 2 ^ 8 different things. Here are some default sizes: int: Depends on machine. 32 bit systems use 4 bytes (because they're 32 bits, get it?), 64 bit systems use 8 bytes bool: Depends on machine. It's either the same in size as an int or it's 1 bit. qboolean: Same as int. short*: 2 bytes char: 1 byte float: 4 bytes double: 8 bytes * Short is just an int with smaller size. So is char, for that matter. By manipulating bits in the variable, we open up a world of new possibilities. int x = 10; // value (binary) = 1010, value (decimal) = 10 x << 1; // shifts all the bits LEFT by 1. value (binary) = 10100. value (decimal) = 20 x >> 2; // shifts all the bits RIGHT by 2. value (binary) = 101. value (decimal) = 5There's also four bitwise functions we can perform with two values: AND (&): Sets all bits to TRUE where the bits in both values are the same, false otherwise. OR (|): Sets all bits to TRUE if said bits in either value are TRUE. false otherwise XOR (^): Sets all bits to TRUE where the bits in both values are different, false otherwise. NOT (~): Flips the bits. The bitwise functions are important for dealing with flags such as spawnflags, weapons, force powers, etc. Arrays So how does one represent a sequence of numbers? Easy. Use an array. int x[5]; // an array with 5 elements int y[5] = { 0, 1, 2, 3, 4 }; // an array with 5 elements that starts at 0 and ends at 4 int z[] = { 0, 1, 2, 3, 4 }; // same as above but pointing out that the size need not be predefinedUsing the above snippet, we can also access values.int nValue = 0; // value = 0 nValue = y[1]; // value = 1. note that in C/C++, we start counting at 0!! nValue++; // value = 2. nValue = y[nValue]; // value = 2. we can also use variables to index arrays!StringsThe astute observer may have realized that we haven't got a type for words, phrases or anything like that. Simply put, these are referred to as strings and will probably be the biggest headache you have to deal with at first the type least desired to use. If you can represent something via a number, it's probably best to do so. But in case you can't- a string is an array of char type. The last character in a string is a null terminator, or a '\0' character. char someString[] = "some string";VectorsSince we are dealing with a three-dimensional game, we often find ourselves working with 3D math often. Vectors are arrays of floats and several functions, like weapon firing and movement, make extensive use of them. Here are the basic types and where they're commonly used: vec_t: also known as float. Not often used. vec2_t: also known as float[2]. Used for 2D coordinates and drawing things onscreen, like with the HUD. vec3_t: also known as float[3]. Used with 3D math most frequently, like movement, projectiles, etc. vec4_t: also known as float[4]. Almost always used with regards to colors, with each value corresponding to a channel (RGBA) A vector can be normalized or denormalized. When a vector is normalized, it is between the values -1.0 and 1.0. When it is denormalized, it...isn't. Since vectors are just arrays of floats, they can be treated as so:vec3_t a = { 1.0, 1.0, 1.0 }; vec3_t b = { a[0], a[1], a[2] };An easier way of writing the above would be to use the VectorCopy function:vec3_t a = { 1.0, 1.0, 1.0 }; vec3_t b; VectorCopy(a, b);3D vector math is a complicated subject that warrants its own guide and is thus out of scope. Enumerations As stated above, if you can represent some idea with a number, an enumeration is the best way to proceed. Weapons, force powers and more are represented by these. Hopefully this block of code explains it best: enum SomeStuff { SOMESTUFF_HERE, // any time you use SOMESTUFF_HERE, it will represent the value 0 SOMEOTHER_STUFF, // any time you use SOMEOTHER_STUFF, it will represent the value 1 // 2.. // 3.. };In C++, an enumeration can also be a type:SomeStuff x; // x can only be SOMESTUFF_HERE or SOMEOTHER_STUFF!StructuresStructures (or 'structs' for short) are big blocks of data comprised of multiple fields. struct SomeStruct { int someField; double doubleRainbow; };We can now use SomeStruct as its own type:SomeStruct x; x.doubleRainbow = 0.01;There's another way to initialize x here too...SomeStruct x = { 0, /*someField*/ 0.01 /*doubleRainbow*/ };Some of the most commonly-used structs in JKA are gentity_t (defined in game/g_local.h), pmove_t and cg_t (defined in cgame/cg_local.h) Type Casting Sometimes you will find yourself needing to convert variables of one type to another. Normally you shouldn't be doing this, but in the rare cases you do, there is a little cheat code - the Type Cast. int x = 10; float y; y = (float)x; // we are converting the type of x to be a float, since y is a float!There are other ways of type casting, but they are C++ only, and explanation of other things is required before they can be used adequately. Macros Macros aren't exactly variables or types, but it's important to understand what they do in order to save time. Basically, a macro places code for you where you expect it to be. Ergo the define statement: #define SOMECODE_HERE = 0 int d SOMECODE_HERE; // what this compiles down to is "int d = 0;"More often than not, you see this being used to define values:#define MAX_WEAPONS 19You can also insert variables into macros thusly:#define SOMECODE_HERE(x) = x int d SOMECODE_HERE(10); // this compiles down to "int d = 10;"C++ 102: Control FlowVariables are great, but they're nothing without actually doing something with them. Enter the almighty if statement: int x = 10; if(x == 0) { // some code that never gets run }Conditional statements and loops are the backbone of your code and help to shape behavior. In order to understand that, a final piece of the previous section needs to be discussed. Boolean Values The bool and qboolean types are interesting in that they can only represent true (qtrue) and false (qfalse). The most obvious way to assign values would be the following: qboolean bSomething; if(/* something */) { bSomething = true; } else { bSomething = false; }However, the more intuitive way would be to do the following:bSomething = /* something */;There are a variety of boolean functions: Comparator (==). This one compares two values to see if they are equal, and returns true if they are. Example: if(a == b) would succeed if a and b are the same value. NOTE THE TWO EQUAL SIGNS, NOT ONE! Inequality comparator (!=). This one compares two values to see if they are not equal and returns true if they are. Example: if(a != b) would succeed if a and b aren't the same value Greater than operator (>). Example: if(a > b) would succeed if a is greater than b Less than operator (<). Example: if(a < b) would succeed if a is less than b Greater than or equal to and Less than or equal to operators (>= and <=). Example: if(a >= b) would succeed if a is greater than or equal to b "and" operator (&&). Allows you to chain multiple boolean functions together. If both are true, then the value is true. Example: if(a != b && b != c) would succeed if a != b AND b != c "or" operator (||). Similar to the above, except it returns true if ONE of the comparisons is true. Example: if(a != b || b != c) would succeed if a != b OR b != c "not" operator (!). This one inverts the value. Example: if(!a) would succeed if a is false if statementAh, the humble if statement. This cornerstone of programming is seen everywhere, including basic circuit boards and deep-level machine programming. Truly something remarkable. qboolean doSomething; if(doSomething) { // we do it! } else { // if the above check failed, we do this instead. }These can be chained into more complex behavior:qboolean doSomething; qboolean doSomethingElse; if(doSomething) { if(doSomethingElse) { // ... } else { // ... } } else { // ... }If statements don't necessarily need the {} if the expression is one line:qboolean doSomething; int x; if(doSomething) x = 10; // no braces needed! else { x = 10; x++; // we still need braces because there's multiple lines here }By abusing the above, we get "else if":if(/* something */) { // ... } else if(/* something else */) { // ... } else { // ... }else if is a nice way of shortening down some more logically complex code. The above would've looked like this:if(/* something */) { // ... } else { if(/* something else */) { // ... } else { // ... } }As you can see, with very long else/if chains this greatly simplifies the code and prevents headaches later down the line when optimizations need to be made. But suppose you're in a situation like this:if(x == 0) { // ... } else if(x == 1) { // ... } else if(x == SOMEENUMERATION) { // ... } else if(x == 1320934) { // ... } else { // ... }There's an even better way still... switch Statement Behold, the switch statement. This allows you to compare one variable to multiple values at once. Simply put, a switch compares a variable to multiple values and performs whatever is in its case block (or default, if there is no case block). What does this look like? Simple. int x = 10; switch(x) { case 10: // this is what gets run, because the value is 10 break; case 20: // this would only get run if the value is 20 break; default: // this would get run if the value isn't 20 or 10 but some other value break; }There's a few points worth mentioning. First, this cannot be used for strings at all. Second, the cases can only be constants, so something like this is totally invalid:switch(x) { case y: // totally invalid break; case 10: // valid break; }There's also another keyword here - the break keyword. This prevents what is known as "fall through" -switch(x) { case 10: // if this were 10, this block would get run. case 20: // this would also get run since there is no break statement }Sometimes this may be intentional, but most of the time it isn't. Take care to include your break statements. A note on floating point numbers and strings Floating point numbers (such as those involved with the "float" and "double" types) have issues with comparisons. Namely, there is no floating point representation for "0" due to the way they are handled internally. Also, you might have issues with comparing other numbers. In order to avoid this, it's best to compare the values to a range using the greater than/less than (or equals to) operators: float x = 0; if(x == 0) { // this might not be true!! } if(x <= 0.001 && x >= -0.001) { // this is definitely true!! }The difference between the two values (in this case being 0.001) is known as an epsilon value. Of course, this need not apply to numbers like integers or character values. It's complicated math reasons that are responsible for this being the case. Read more here if you're interested. Another thing- comparing strings with the == is actually valid code, but it won't produce expected effects. When I cover pointers, you will see why this is the case. Loops Sometimes, you may find yourself in a situation where code needs to be repeated several times, with or without minor modifications. In some cases a macro is needed, but more often than not, you'll need to use a loop. There are three different kinds of loops. If a loop doesn't have a termination sequence, then you will encounter something known as an "infinite loop," where the program freezes (not crashes) while loop This is the most primitive form of a loop in existence. It performs actions while the condition is true. Here is a good example: int x = 0; while(x < 10) { // if x was 10, this wouldn't get run at all! x++; // this gets run exactly 10 times }We can also use "true" (C++) or "qtrue" © or "1" to have a loop which can terminate on its own. Consider this loop. You'll note that there is the keyword "break" again here, but there is also a new keyword which doesn't appear which warrants discussion: continue.Consider that a loop goes through a sequence multiple times. The "break" keyword effectively terminates the loop, while the "continue" keyword says "skip ahead, I don't care about what happens in the next chunk of code." while(1) { if(x == 10) { break; // loop is finished } if(x == 11) { continue; // go back to the beginning of the loop, but do not terminate. } }do-while loopsA modification of the while loop is the do-while loop. At its core, these are no different than the while loop, other than that they are guaranteed to execute the contents of the loop at least once. Here is an example: x = 10; do { x++; // this will happen } while(x < 10); x = 10; while(x < 10) { x++; // this won't }for loopsConsider our first example of a loop, back in the while loops section: int x = 0; while(x < 10) { // if x was 10, this wouldn't get run at all! x++; // this gets run exactly 10 times }C and C++ have a nicer way of writing this as well! Behold the for loop:int x; for(x = 0; x < 10; x++) { // this gets run exactly 10 times }The syntax of the for loop is quite simple:for(/*precondition, what happens prior to the loop getting run*/ ; /*what is needed to make the loop break*/ ; /*what gets run on every cycle*/) { }Any of the three sections can be skipped entirely. If needed, a for-loop can just look like this:for(; { }Of course, that wouldn't be very useful! But you see what I mean.Here is another example which demonstrates the idea of doing multiple things on each section: for(x = 0, y = 10; x < 10 && y < 20; x++, y++) { }The comma operator here can be seen as simply "do this, but ignore the result", which is perfect in this context. We can also use it on a while loop to set something on each cycle of the loop:while(x = 0, y < 10) { // x will always be 0 when we start each cycle of the loop, and we will break if y is >= 10 }C++ 103: FunctionsNow that we've covered both variables and flow of control, it's time to combine both concepts into some workable code: functions. The game is broken up into five distinct parts: the engine, the game code (g_, game/, serverside, jampgamex86.dll, etc), cgame code (cg_, cgame/, clientside, cgamex86.dll, etc), ui code (ui_, ui/, uix86.dll, etc), and the renderer (r_, rd-vanilla, rd-rend2, etc). Each of these parts has an entry point: Engine: WinMain, or main (depends on operating system) Game code: G_InitGame (g_main.c) Cgame: CG_Init (cg_main.c) UI: UI_Init (ui_main.c) Renderer: R_Init (tr_init.cpp) Singleplayer note: In SP, the "Game code" and "cgame" code are moved into a single module, and the UI module is moved into the engine. These entry points occur in what are called "functions" and they are where everything gets executed. Everything that happens, happens because it was called from a function. Functions call other functions, and in that, the code forms a delicate network of function calls, like a spider's web. But we need to understand the basic syntax of a function first: <return type> <function name> (<arguments, if any>) { }void return typeI will start with the void function, which is the simplest type there is: void SomeFunction() { // do some stuff here }Simply put, void functions can be seen as nice little snippets of code we can use anywhere. We can invoke the above example by doing the following:SomeFunction();That's about as easy as it gets. Of course, there are parameters which can be fed into the function:void SomeFunction(int someArgument) { Com_Printf("We called SomeFunction with someArgument = %d\n", someArgument); }...which we later call by doing:SomeFunction(10); // prints "We called SomeFunction with someArgument = 10" to the console SomeFunction(20); // same as above but with 20 SomeFunction(30); // ...Com_Printf is one of the functions that is used by the game. We will talk more about these functions in the CRT chapter. return keyword A function sometimes needs to be able to exit at any point in time. In order to do so, we invoke the "return" keyword: void DoSomething(int someArgument) { if(someArgument == 10) { return; // The argument is 10 so we don't print the message } Com_Printf("someArgument is NOT ten!\n"); }And now if we invoke DoSomething:DoSomething(10); // doesn't print the message DoSomething(20); // doesNon-void functionsFunctions can also return a value. When this happens, the value which is brought back by the function can be passed into a variable. Here is a trivial example: int MultiplyNumberByTwo(int x) { return x * 2; }Note how we are using a variable type (int) instead of the word "void". This designates that we must return a value from the function. Here is an example of how we call it:int x = 2; x = MultiplyNumberByTwo(x); // value = 4 x = MultiplyNumberByTwo(x); // value = 8 x = MultiplyNumberByTwo(x); // value = 16Note that void functions cannot return a value. Non-void functions on the other hand must return a value. Static variables Suppose we wanted to keep track of how many times a function is called. One way to do this would be to use a static variable. Let's add a usage counter to our MultiplyNumberByTwo function. int MultiplyNumberByTwo(int num) { static int usageCount = 1; Com_Printf("MultiplyNumberByTwo has been called %d times\n", usageCount); usageCount++; return num * 2; }If we didn't make the variable static, it wouldn't keep its value after it was finished. This is because functions keep variables in a temporary block of memory known as the stack. Each time a function cleans up, the values in the stack are discarded and used again. But by declaring the variable as being static, we are telling the compiler to keep the variable out of the stack and in a secure section of memory where we can access it again later. Calling functions from other files As you might have figured out, there are two types of files: source files (.c or .cpp) and header files (.h, rarely .hpp). In C, you cannot define a function in a header file, you can only use this to declare them (like you would a variable. In C++ this restriction doesn't exist but it's still good to follow this). Source files can include header files in order to make those declarations available. You commonly see this at the beginning of a .c or .cpp file: #include "g_local.h"So if we have the function written in say g_main.c, and its signature (the function without the stuff in brackets) looks like this:int MultiplyNumberByTwo(int number);We would need to declare the function in g_local.h using its signature in order to make it be accessible by all files which include g_local.h. Simple stuff, if you think about it. Static functions Suppose we wanted a function to only be ever be used in one file. This is where the static keyword comes in again: static int MultiplyNumber(int number) { return number * 3; }Since the function is static, it is a unique function to that file and we shouldn't declare it in a header. Another cheat code: extern Suppose for some reason we didn't want to declare a function in g_local.h. In order to use it in another file, we could use the extern keyword to declare it before use. This is good if we don't want a function to be seen by every single thing that includes g_local.h. One example of where this is used quite prominently is in the SP AI code. C++ 201: Pointers As defined in a previous section, a pointer is a variable which "points to" a chunk of memory. Most often these are used for strings, but there are other uses as well. Creating and Dereferencing Pointers The asterisk (*) not only denotes multiplication, but it is also used to define pointers: int* x; // a pointer to some integer variableBut exactly how do we manipulate pointers? Perhaps one of the most basic methods involves using the ampersand (&) which references (or creates a pointer to) a variable:int x = 10; int* y = &x; // this is now a pointer to the x variableNow that we have a pointer, we can also retrieve the value of what we are pointing to by dereferencing it. Even more confusingly, this uses the asterisk again. Consider this chunk of code:int x = 10; int* y = &x; if(*y == x) { // This is always true. *y is 10 because we are retrieving the value of what we are pointing to. }Strings (and a nice little math hack)Have you tried using == on a string yet and realized that it hasn't worked? This is because strings are usually pointers to character arrays in memory, and when you use the == operator, you're comparing the values of the pointers, not the values of the strings themselves. I will cover all of the string functions in depth in another tutorial/guide, but there are two key functions for comparing functions: Q_stricmp and Q_stricmpn. const char* someString = "Something"; if(Q_stricmp(someString, "something") == 0) { // Q_stricmp returns 0 if the two strings are equal. This is case insensitive. So this chunk of code would get executed. } if(Q_stricmpn(someString, "som", 3) == 0) { // Q_stricmpn returns 0 if the two strings are equal, but compares only a certain number of characters as specified by the third argument. So this code would get executed. }So if that's the case, can we also do something like this?const char* someString = "Something"; if(someString[0] == 'S') { // Code Block }The answer is: yes. Even though someString is a pointer, it's pointing to an array of characters, specifically, the start of the array. Therefore, when you are using the indexing function (the brackets, []), you are really executing a math formula which looks something like this:if(someString[x] == *(someString + x)) { // always true }Remember that pointers are variables which point to chunks of memory. When you are manipulating the value of the pointer, you are changing what it is pointing to. Also remember, all strings end in a '\0' character! Passing variables by reference and by value Another practical application of pointers involves passing something by reference, instead of by value. Consider the following function: void MultiplyValueByTwo(int x) { x *= 2; }Despite calling it like this:int x = 2; MultiplyValueByTwo(x);The variable x in this case doesn't change. Why? Because you're passing the value of x to the function, not the reference. Simply put, you're copying the value that x contains into what is actually passed into the function. If this sounds confusing at first, it probably is. But here's an example of something that passes by reference:void DivideValueByTwo(int* x) { *x /= 2; } ... int x = 20; DivideValueByTwo(&x);Since you are passing the value of the pointer to the function, you have free reign to modify the variable at your whim...as long as you dereference it! While this may be a trivial example, there are lots of applications where this might be useful. Structs and Pointers Structs have a nice little shortcut for dealing with pointers. While your first instinct may be to do something like this: struct SomeStruct { int x; int y; }; void SetXToTwenty(SomeStruct* q) { (*q).x = 20; }...an even nicer way to do this involves using the -> operator:struct SomeStruct { int x; int y; }; void SetXToTwenty(SomeStruct* q) { q->x = 20; }You can see this used almost everywhere in the serverside code. Some of the most glaring examples are in the cheat commands. It's almost always preferred to send a pointer to a structure instead of a copy to a function, even when it's not necessary, simply because you are copying a much smaller number of bytes (4 or 8, compared to however large the structure is...in singleplayer, gentity_t is somewhere in the realm of 1000 bytes!!) and therefore saving on performance. ...going deeper So what happens if you need to pass a pointer by reference into a function? You go deeper, my friend. void MultiplyValueByTwo(int** ppValue) { (*(*ppValue)) *= 2; }This is a pointer-to-a-pointer, and you can make this as deep as you want to go:int******************************************************************************************************* x; // totally valid but wtfFunction pointersYou can also make pointers to functions, although the syntax is a bit weird: int MultiplyValueByTwo(int x) { return x * 2; } // Defining the variable int (*SomeFunctionPointer)(int x) = MultiplyValueByTwo; // Calling the function int y = SomeFunctionPointer(d);There's relatively few uses for these, although perhaps you will find one someday.
  4. 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 programCall Stack: A trace of how the instruction pointer reached its destination by scanning the previous functionsBreakpoints 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 breakpointDisable 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.
  5. This tutorial is for Windows only. So I've gotten this question a lot through PMs: "How do I get Jedi Academy source code to compile? Please help!" My answer to you is simple: Use OpenJK. It's a lot smoother and MUCH less difficult to try and fix than using the base source code. But I suppose people are still having issues getting the source code to compile, so I'm going to outline in general how to compile OpenJK. Here's what you'll need: Microsoft Visual Studio C++ 2013 or later (Express edition is free)CMake (cmake.org)Git (optional)A Github account (optional)First let's determine whether or not you're going to need Git. If you plan on contributing back to the OpenJK project, or you plan on getting future updates from the project easily, then Git is the way to go. Make a new fork of it on Github (there is plenty of help on that site, I'm not going to go over how to make a fork), and then clone it using either the Github App, or SourceTree. If you don't want to deal with Git, then that's understandable. There's a ZIP version of the source code available at the main repository location (http://github.com/JACoders/OpenJK). You won't be able to get updates very easily though. Let's dive in, shall we? Step 0 - Installing Software Okay, okay, I trust that you know how to install some software, but I'm going to make a special note about CMake. CMake has a setting which is disabled by default that needs to be turned on. Towards the end of installation, be on the lookout for a checkbox (change PATH setting), and make sure it's checked. Step 1 - Generating Project Files Visual Studio manages code projects in two groups - Solutions and Projects. Solutions (*.sln) generally contain a number of Projects (*.vcxproj) and each Project correlates to a specific aspect, such as cgamex86.dll or jamp.exe. In past versions of Visual Studio, Solutions were referred to as Workspaces (*.dsw), and Projects had different extensions (*.dsp, *.vcproj). But with newer versions of Visual Studio, the C++ standard was rapidly evolving, and so too must the software being used to produce it. Naturally, there are going to be some conflicts. Such conflicts resulted in the old Jedi Academy projects, which were programmed using Visual Studio 6 (or 2003), to be completely unable to be compiled by any newer compilers (such as Visual Studio 2005, 2008, 2010, 2012 or 2013). So, OpenJK fixes all of these issues and makes it run using more modern code, with more well-defined standards. So as you might have guessed, we need to be opening up a .sln file. And as you might have noticed (or are now noticing), there isn't one. Why is this? Well, Visual Studio is specific to Windows, and OpenJK is meant to run on many different platforms, it would be quite a burden to update all of the platforms when something changes (such as a new file). Instead, we use CMake, which is a program that runs scripts to generate .sln files and .vcxproj files. We can invoke it by using CMake-GUI, which is packaged with CMake. Open that now. We'll need to tell CMake what to do here. Hit "Browse Source Code", and navigate to your OpenJK folder where you've got the source code located. Next, hit "Browse Build". This is going to be the same as your OpenJK folder, but with a /build/ after it. Next hit Configure. Hit "Yes" if you did not make a new folder for /build/, it will make one for you. Pick the generator that fits your Visual Studio version (for example, if you're using Visual Studio 2013, make sure to select Visual Studio 12) Hit Finish. CMake will take a little while to do its thing and will display some stuff in its console that you shouldn't worry about. It'll display some text in red, but don't worry about it (unless it says that errors were reported!) You'll be presented with a screen where you may pick certain options. Most of these options are self explanatory, but I will go over them briefly: BuildJK2Support: Build with JK2 in mind? (experimental)BuildMPCGame: Build cgamex86.dll?BuildMPDed: Build dedicated server? (OpenJK equivalent of jampded.exe)BuildMPEngine: Build engine? (OpenJK equivalent of jamp.exe)BuildMPGame: Build jampgamex86.dll?BuildMPRend2: Not available yet. Build rend2? (New renderer)BuildMPUI: Build uix86.dll?BuildPortableVersion: When selected, will turn off the functionality of fs_homepath. Good for those that want to install on a flash drive.BuildSPEngine: Build SP engine? (OpenJK equivalent of jasp.exe)BuildSPGame: Build jagamex86.dll? (Mod code for SP)Select whichever of these you want. If you're building for SP, you'll probably only want BuildSPEngine if you're building a whole game, and you'll definitely want BuildSPGame. If you're building an MP mod, you'll probably want BuildMPGame, BuildMPUI and BuildMPCGame. Once you get into debugging, BuildSPEngine and BuildMPEngine are valuable to enable as they assist with debugging. At any rate, hit Configure again (to make sure that it configures with what you want) and hit Generate, and if all goes smoothly, it'll spout out "Generating Done". Close CMake and open Visual Studio. Hit File->Open->Project/Solution (or Ctrl+Shift+O), and navigate to your /build/ folder. Open the OpenJK.sln which will be in this folder. Now, to compile the code, all you need to do is go to Build->Build Solution up at the top. If the compilation has succeeded, you will see something along the lines of "X succeeded, 0 failed, ....". If everything goes according to plan, you will now have some .exes and some .dlls to play with! They will be located in /build/Debug. Or, if you compiled in Release mode, they will be in /build/Release. Getting Help with your Code Problems So you wanna learn to code eh? Programming is an ongoing field of study. Once you begin to learn, you can never learn anything there truly is. I'd like to compare programming to magic here for a second. One cannot simply know everything there is about magic. Even the great sages of wizardry and magic have only a minute understanding of the deeper working of it...and like that, no one person knows everything there is to know about programming, especially not about C or C++. I encourage you to pick up a book or three continually poke and prod at the source code until you get something meaningful. Don't be discouraged! At the same time though, understand your limits and what you're working with. Nobody built Rome in a day, and nobody learned how to code C++ in a day either. If you're really seeking the knowledge of code, you should definitely consult JACoders IRC. Naturally, we're a little worn thin as of late, but we're always looking to inspire more talented coders to pick up a copy of the code and learn from it. Here's a good IRC client, and the credentials for the IRC room follow: server: irc.arloria.net channel: #JACoders <no password> Updated 7/26/14 with new details on compiling
  6. After realizing that the original tutorial by Raz0r on JACoders is now dead, I have decided to rewrite this one with a better focus on OpenJK. Note that this tutorial is written with OpenJK in mind. The Command itself Each command has a basic signature that looks something like this: void Cmd_God_f(gentity_t* ent) { // ... } This signature is important and you'll need to use it for your Undying command, otherwise it won't work.Somewhere in game/g_cmds.c we will be adding the function: void Cmd_Undying_f(gentity_t* ent) { // ... } The rest of the function is very simple. When you enter Undying mode, it displays a message stating that you are in Undying mode and sets your health and max health to 999. #define UNDYING_HEALTH 999 void Cmd_Undying_f(gentity_t* ent) { // author: eezstreet if(!ent->client) { return; // don't do anything if they aren't a player } ent->client->ps.stats[STAT_HEALTH] = ent->health = UNDYING_HEALTH; // set our health to 999 ent->client->ps.stats[STAT_HEALTH_MAX] = UNDYING_HEALTH; // set our max health to 999 trap->SendServerCommand(ent-g_entities, "print \"Undying mode: ON\n\""); } It's simple, yet effective. But we also need to add the toggle functionality. Simply checking the player's max health ought to do. #define UNDYING_HEALTH 999 void Cmd_Undying_f(gentity_t* ent) { // author: eezstreet if(!ent->client) { return; // don't do anything if they aren't a player } if(ent->client->ps.stats[STAT_HEALTH_MAX] == UNDYING_HEALTH) { ent->client->ps.stats[STAT_HEALTH_MAX] = 100; // set our max health to 999. if we are over 100 health, it will count down trap->SendServerCommand(ent-g_entities, "print \"Undying mode: OFF\n\""); } else { ent->client->ps.stats[STAT_HEALTH] = ent->health = UNDYING_HEALTH; // set our health to 999 ent->client->ps.stats[STAT_HEALTH_MAX] = UNDYING_HEALTH; // set our max health to 999 trap->SendServerCommand(ent-g_entities, "print \"Undying mode: ON\n\""); } } Linking the command - Multiplayer OpenJKLastly, we need to link the command. This is very simple and easy to do. Look for this table in g_cmds.c. You will need to add your own entry in this table, in alphabetical order. Since the command starts with a "u", it will appear between "t_use" and "voicecmd". The entry should look something like this: { "undying", Cmd_Undying_f, CMD_CHEAT|CMD_NOINTERMISSION|CMD_ALIVE }, The first column in this entry specifies what the command will be if you enter it via the console - /undying in this case.The second column is the function which is called by the command. The third column is flags. I'm only aware of three: CMD_CHEAT (which specifies that it's a cheat), CMD_NOINTERMISSION (which specifies that it can't be used during intermission) and CMD_ALIVE (which specifies that it can't be used while dead). Linking the command - Singleplayer (or not OpenJK) You'll need to add new cases to a large if/elseif block at the end of g_cmds.c/pp. See here. Otherwise, it's largely identical. Your new block will probably look like this: else if(Q_stricmp(cmd, "undying") == 0) { Cmd_Undying_f(ent); } Note that SP and the SDK code don't use the flag system that OpenJK does, so you'll need to check for cheat mode, being alive and not intermission on your own, within Cmd_Undying_f.
  7. After realizing that the original tutorial by Raz0r on JACoders is now dead, I have decided to rewrite this one with a better focus on OpenJK. Note that this tutorial is written with OpenJK in mind. The Command itself Each command has a basic signature that looks something like this: void Cmd_God_f(gentity_t* ent) { // ... } This signature is important and you'll need to use it for your Undying command, otherwise it won't work. Somewhere in game/g_cmds.c we will be adding the function: void Cmd_Undying_f(gentity_t* ent) { // ... } The rest of the function is very simple. When you enter Undying mode, it displays a message stating that you are in Undying mode and sets your health and max health to 999. #define UNDYING_HEALTH 999 void Cmd_Undying_f(gentity_t* ent) { // author: eezstreet if(!ent->client) { return; // don't do anything if they aren't a player } ent->client->ps.stats[STAT_HEALTH] = ent->health = UNDYING_HEALTH; // set our health to 999 ent->client->ps.stats[STAT_HEALTH_MAX] = UNDYING_HEALTH; // set our max health to 999 trap->SendServerCommand(ent-g_entities, "print \"Undying mode: ON\n\""); } It's simple, yet effective. But we also need to add the toggle functionality. Simply checking the player's max health ought to do. #define UNDYING_HEALTH 999 void Cmd_Undying_f(gentity_t* ent) { // author: eezstreet if(!ent->client) { return; // don't do anything if they aren't a player } if(ent->client->ps.stats[STAT_HEALTH_MAX] == UNDYING_HEALTH) { ent->client->ps.stats[STAT_HEALTH_MAX] = 100; // set our max health to 999. if we are over 100 health, it will count down trap->SendServerCommand(ent-g_entities, "print \"Undying mode: OFF\n\""); } else { ent->client->ps.stats[STAT_HEALTH] = ent->health = UNDYING_HEALTH; // set our health to 999 ent->client->ps.stats[STAT_HEALTH_MAX] = UNDYING_HEALTH; // set our max health to 999 trap->SendServerCommand(ent-g_entities, "print \"Undying mode: ON\n\""); } } Linking the command - Multiplayer OpenJK Lastly, we need to link the command. This is very simple and easy to do. Look for this table in g_cmds.c. You will need to add your own entry in this table, in alphabetical order. Since the command starts with a "u", it will appear between "t_use" and "voicecmd". The entry should look something like this: { "undying", Cmd_Undying_f, CMD_CHEAT|CMD_NOINTERMISSION|CMD_ALIVE }, The first column in this entry specifies what the command will be if you enter it via the console - /undying in this case. The second column is the function which is called by the command. The third column is flags. I'm only aware of three: CMD_CHEAT (which specifies that it's a cheat), CMD_NOINTERMISSION (which specifies that it can't be used during intermission) and CMD_ALIVE (which specifies that it can't be used while dead). Linking the command - Singleplayer (or not OpenJK) You'll need to add new cases to a large if/elseif block at the end of g_cmds.c/pp. See here. Otherwise, it's largely identical. Your new block will probably look like this: else if(Q_stricmp(cmd, "undying") == 0) { Cmd_Undying_f(ent); } Note that SP and the SDK code don't use the flag system that OpenJK does, so you'll need to check for cheat mode, being alive and not intermission on your own, within Cmd_Undying_f.
  8. This tutorial is for Windows only. So I've gotten this question a lot through PMs: "How do I get Jedi Academy source code to compile? Please help!" My answer to you is simple: Use OpenJK. It's a lot smoother and MUCH less difficult to try and fix than using the base source code. But I suppose people are still having issues getting the source code to compile, so I'm going to outline in general how to compile OpenJK. Here's what you'll need: Microsoft Visual Studio C++ 2013 or later (Express edition is free) CMake (cmake.org) Git (optional) A Github account (optional) First let's determine whether or not you're going to need Git. If you plan on contributing back to the OpenJK project, or you plan on getting future updates from the project easily, then Git is the way to go. Make a new fork of it on Github (there is plenty of help on that site, I'm not going to go over how to make a fork), and then clone it using either the Github App, or SourceTree. If you don't want to deal with Git, then that's understandable. There's a ZIP version of the source code available at the main repository location (http://github.com/JACoders/OpenJK). You won't be able to get updates very easily though. Let's dive in, shall we? Step 0 - Installing Software Okay, okay, I trust that you know how to install some software, but I'm going to make a special note about CMake. CMake has a setting which is disabled by default that needs to be turned on. Towards the end of installation, be on the lookout for a checkbox (change PATH setting), and make sure it's checked. Step 1 - Generating Project Files Visual Studio manages code projects in two groups - Solutions and Projects. Solutions (*.sln) generally contain a number of Projects (*.vcxproj) and each Project correlates to a specific aspect, such as cgamex86.dll or jamp.exe. In past versions of Visual Studio, Solutions were referred to as Workspaces (*.dsw), and Projects had different extensions (*.dsp, *.vcproj). But with newer versions of Visual Studio, the C++ standard was rapidly evolving, and so too must the software being used to produce it. Naturally, there are going to be some conflicts. Such conflicts resulted in the old Jedi Academy projects, which were programmed using Visual Studio 6 (or 2003), to be completely unable to be compiled by any newer compilers (such as Visual Studio 2005, 2008, 2010, 2012 or 2013). So, OpenJK fixes all of these issues and makes it run using more modern code, with more well-defined standards. So as you might have guessed, we need to be opening up a .sln file. And as you might have noticed (or are now noticing), there isn't one. Why is this? Well, Visual Studio is specific to Windows, and OpenJK is meant to run on many different platforms, it would be quite a burden to update all of the platforms when something changes (such as a new file). Instead, we use CMake, which is a program that runs scripts to generate .sln files and .vcxproj files. We can invoke it by using CMake-GUI, which is packaged with CMake. Open that now. We'll need to tell CMake what to do here. Hit "Browse Source Code", and navigate to your OpenJK folder where you've got the source code located. Next, hit "Browse Build". This is going to be the same as your OpenJK folder, but with a /build/ after it. Next hit Configure. Hit "Yes" if you did not make a new folder for /build/, it will make one for you. Pick the generator that fits your Visual Studio version (for example, if you're using Visual Studio 2013, make sure to select Visual Studio 12) Hit Finish. CMake will take a little while to do its thing and will display some stuff in its console that you shouldn't worry about. It'll display some text in red, but don't worry about it (unless it says that errors were reported!) You'll be presented with a screen where you may pick certain options. Most of these options are self explanatory, but I will go over them briefly: BuildJK2Support: Build with JK2 in mind? (experimental) BuildMPCGame: Build cgamex86.dll? BuildMPDed: Build dedicated server? (OpenJK equivalent of jampded.exe) BuildMPEngine: Build engine? (OpenJK equivalent of jamp.exe) BuildMPGame: Build jampgamex86.dll? BuildMPRend2: Not available yet. Build rend2? (New renderer) BuildMPUI: Build uix86.dll? BuildPortableVersion: When selected, will turn off the functionality of fs_homepath. Good for those that want to install on a flash drive. BuildSPEngine: Build SP engine? (OpenJK equivalent of jasp.exe) BuildSPGame: Build jagamex86.dll? (Mod code for SP) Select whichever of these you want. If you're building for SP, you'll probably only want BuildSPEngine if you're building a whole game, and you'll definitely want BuildSPGame. If you're building an MP mod, you'll probably want BuildMPGame, BuildMPUI and BuildMPCGame. Once you get into debugging, BuildSPEngine and BuildMPEngine are valuable to enable as they assist with debugging. At any rate, hit Configure again (to make sure that it configures with what you want) and hit Generate, and if all goes smoothly, it'll spout out "Generating Done". Close CMake and open Visual Studio. Hit File->Open->Project/Solution (or Ctrl+Shift+O), and navigate to your /build/ folder. Open the OpenJK.sln which will be in this folder. Now, to compile the code, all you need to do is go to Build->Build Solution up at the top. If the compilation has succeeded, you will see something along the lines of "X succeeded, 0 failed, ....". If everything goes according to plan, you will now have some .exes and some .dlls to play with! They will be located in /build/Debug. Or, if you compiled in Release mode, they will be in /build/Release. Getting Help with your Code Problems So you wanna learn to code eh? Programming is an ongoing field of study. Once you begin to learn, you can never learn anything there truly is. I'd like to compare programming to magic here for a second. One cannot simply know everything there is about magic. Even the great sages of wizardry and magic have only a minute understanding of the deeper working of it...and like that, no one person knows everything there is to know about programming, especially not about C or C++. I encourage you to pick up a book or three continually poke and prod at the source code until you get something meaningful. Don't be discouraged! At the same time though, understand your limits and what you're working with. Nobody built Rome in a day, and nobody learned how to code C++ in a day either. If you're really seeking the knowledge of code, you should definitely consult JACoders IRC. Naturally, we're a little worn thin as of late, but we're always looking to inspire more talented coders to pick up a copy of the code and learn from it. Here's a good IRC client, and the credentials for the IRC room follow: server: irc.arloria.net channel: #JACoders <no password> Updated 7/26/14 with new details on compiling
  9. eezstreet

    C and C++ Primer

    C and C++ Primer by eezstreet (with edits from Xycaleth and mrwonko) Last updated: 8.9.15 Table of Contents Introduction C++ 101: Variables and typing C++ 102: Control flow C++ 103: Functions C++ 201: Pointers Introduction First things first, it goes without saying. Learning how to program is difficult. I'm not saying this to drive away potential coders or anything like that. In fact, I readily encourage newcomers to try out programming and see if it suits them. It just might change your life! The trouble comes when one first looks at a program like JKA and sees a big mess of code and it doesn't look anything like the nice, compact programs that C++ tutorials provide. Or even worse, they have you using constructs like std::string or std::cout which are totally irrelevant C++ concepts in an environment like JKA where either they don't work or aren't the preferred way of doing things. On the other end of the spectrum we have tutorials like the ones here on JKHub which are glorified Copy+Paste and nothing more. I hate these. To be clear, a lot of the stuff you'll learn in the beginning involves using copy+paste, but there's no room for creativity. I like to see people empowered by knowledge instead of blindly changing things in files until it works. That's not programming, that's guessing. I aim to solve some of the confusion here. Where to go to get help There are a lot of places where you can seek guidance from others if you need help with something. First and foremost you should try and research your problem. The search feature here on the forums works well, and there's always Google to help reach these ends. If you still haven't solved your issue, here are a number of places you can go: The JACoders IRC channel*. Flash based webclient here. The Coding and Scripts forum on this website if your question is specific to programming. The OpenJK General Discussion forum is good if your question is specific to OpenJK. StackOverflow.com can provide you with coding help if you need it, but don't expect help regarding JKA specific stuff Direct private messages should probably be avoided as it could help solve problems for other people who encounter the same problem that you have. * A note about IRC: IRC is shorthand for "Internet Relay Chat." As such, it's common to idle in the channel. It may take a while for someone to respond to your messages, so please be patient. Many times have I checked and someone has joined, asked a question, and left before I could answer it! Final Note This tutorial (if you can call it that) isn't meant to teach you how to do any one thing in the game. It's a language primer designed specifically to teach C and C++ in the context of making a mod for this game. It's also not a way of teaching you how to setup an environment. There's already a tutorial for that, and this one assumes you have read it. Some stuff is designated as "ENGINE ONLY" or "C++ only." This means that it cannot be used in C code, which is used in the multiplayer DLL code (which is what the original SDK was composed of) C++ 101: Variables and Typing Programs store information in chunks of memory we call "variables." All variables need to be declared before they can be used, otherwise the compiler will not understand what you are doing. In C++ code, we can declare variables anywhere, but in C code we must declare variables at the beginning of any section that starts with {}s (these are known as blocks). Below are some of the most basic types. int: represents a number, ranging ~negative 2 billion to ~positive 2 billion on 32 bit systems bool (engine only): represents only two values: true and false. qboolean: represents only two values: qtrue and qfalse. float: represents a decimal number, like 3.14. double: like a float, except it can be more precise char: represents a character, like 'a' or '@'. Some more complex characters like Japanese symbols are only compatible with types other than char. (pointer): "points to" another variable in memory. More on this later. So that being said, there are a number of manipulations you can do with these: int someNumber; // value = unknown. On this line we are declaring the variable 'someNumber' which is of type 'int' someNumber = 0; // value = 0 someNumber += 10; // value = 10 someNumber++; // value = 11 someNumber--; // value = 10 someNumber -= 2; // value = 8 someNumber /= 2; // value = 4 someNumber *= 2; // value = 8 someNumber %= 3; // value = 2 (this is the "remainder of" operator, known as modulus. Divided numbers are rounded when they are int type.) These are the most basic operations that exist. Here are some things we shouldn't do: int someNumber; // we are declaring a variable 'someNumber' of type 'int' someNumber += 2; // undefined behavior. someNumber doesn't have a value assigned to it, so adding 2 to that value is undefined! someNumber = 0; // this would be okay... someNumber /= someNumber; // we are dividing by zero here, which would invoke a crash. In C++ code only, you can define a variable as being "auto". This means that the type will automatically be deduced. int x = 5; auto y = x; // type is int Note that a variable can have prefixes added to them. Some of these will be discussed later. unsigned int x; // can only be positive or 0 signed int x; // can be positive or negative const int x; // can't be changed There are of course some more complicated things we can do, but before that we need to understand the concept of binary number systems. Binary Number Systems Every modern computer uses binary to represent numbers (and numbers can represent code...but this is rather deep stuff we're talking about!). But just what is binary? Normally we use 10 numbers to represent a digit - 0 through 9, or base 10. Binary on the other hand is only represented by 2 numbers - 0 and 1. There are of course other systems of numbers, and two others which are commonly used in programming are Octal (digits 0 - 7, base and Hexadecimal (digits 0 - 9..and then A B C D E F, so base 16). This raises the question. How do we represent a number in base 10, like say... 10, in a base 2 number system? There are many different ways, and that's a bit beyond the scope of this guide, but you can use the Windows Calculator to do so by switching to Programmer Mode (Alt+3 or View > Programmer). There are some radio buttons that say "Hex Dec Oct Bin". By selecting Dec (short for Decimal, or Base 10) and entering in whatever number you want to convert, and then selecting Bin (short for Binary, or base 2), you will now see the number in binary. There's also various ways to convert by hand. Now that we have a basic grasp of a binary number system, let's discuss size. Each variable type is assigned a size in bytes. A byte is 8 bits (binary digits) and thus can represent 2 ^ 8 different things. Here are some default sizes: int: Depends on machine. 32 bit systems use 4 bytes (because they're 32 bits, get it?), 64 bit systems use 8 bytes bool: Depends on machine. It's either the same in size as an int or it's 1 bit. qboolean: Same as int. short*: 2 bytes char: 1 byte float: 4 bytes double: 8 bytes * Short is just an int with smaller size. So is char, for that matter. By manipulating bits in the variable, we open up a world of new possibilities. int x = 10; // value (binary) = 1010, value (decimal) = 10 x << 1; // shifts all the bits LEFT by 1. value (binary) = 10100. value (decimal) = 20 x >> 2; // shifts all the bits RIGHT by 2. value (binary) = 101. value (decimal) = 5 There's also four bitwise functions we can perform with two values: AND (&): Sets all bits to TRUE where the bits in both values are the same, false otherwise. OR (|): Sets all bits to TRUE if said bits in either value are TRUE. false otherwise XOR (^): Sets all bits to TRUE where the bits in both values are different, false otherwise. NOT (~): Flips the bits. The bitwise functions are important for dealing with flags such as spawnflags, weapons, force powers, etc. Arrays So how does one represent a sequence of numbers? Easy. Use an array. int x[5]; // an array with 5 elements int y[5] = { 0, 1, 2, 3, 4 }; // an array with 5 elements that starts at 0 and ends at 4 int z[] = { 0, 1, 2, 3, 4 }; // same as above but pointing out that the size need not be predefined Using the above snippet, we can also access values. int nValue = 0; // value = 0 nValue = y[1]; // value = 1. note that in C/C++, we start counting at 0!! nValue++; // value = 2. nValue = y[nValue]; // value = 2. we can also use variables to index arrays! Strings The astute observer may have realized that we haven't got a type for words, phrases or anything like that. Simply put, these are referred to as strings and will probably be the biggest headache you have to deal with at first the type least desired to use. If you can represent something via a number, it's probably best to do so. But in case you can't- a string is an array of char type. The last character in a string is a null terminator, or a '\0' character. char someString[] = "some string"; Vectors Since we are dealing with a three-dimensional game, we often find ourselves working with 3D math often. Vectors are arrays of floats and several functions, like weapon firing and movement, make extensive use of them. Here are the basic types and where they're commonly used: vec_t: also known as float. Not often used. vec2_t: also known as float[2]. Used for 2D coordinates and drawing things onscreen, like with the HUD. vec3_t: also known as float[3]. Used with 3D math most frequently, like movement, projectiles, etc. vec4_t: also known as float[4]. Almost always used with regards to colors, with each value corresponding to a channel (RGBA) A vector can be normalized or denormalized. When a vector is normalized, it is between the values -1.0 and 1.0. When it is denormalized, it...isn't. Since vectors are just arrays of floats, they can be treated as so: vec3_t a = { 1.0, 1.0, 1.0 }; vec3_t b = { a[0], a[1], a[2] }; An easier way of writing the above would be to use the VectorCopy function: vec3_t a = { 1.0, 1.0, 1.0 }; vec3_t b; VectorCopy(a, b); 3D vector math is a complicated subject that warrants its own guide and is thus out of scope. Enumerations As stated above, if you can represent some idea with a number, an enumeration is the best way to proceed. Weapons, force powers and more are represented by these. Hopefully this block of code explains it best: enum SomeStuff { SOMESTUFF_HERE, // any time you use SOMESTUFF_HERE, it will represent the value 0 SOMEOTHER_STUFF, // any time you use SOMEOTHER_STUFF, it will represent the value 1 // 2.. // 3.. }; In C++, an enumeration can also be a type: SomeStuff x; // x can only be SOMESTUFF_HERE or SOMEOTHER_STUFF! Structures Structures (or 'structs' for short) are big blocks of data comprised of multiple fields. struct SomeStruct { int someField; double doubleRainbow; }; We can now use SomeStruct as its own type: SomeStruct x; x.doubleRainbow = 0.01; There's another way to initialize x here too... SomeStruct x = { 0, /*someField*/ 0.01 /*doubleRainbow*/ }; Some of the most commonly-used structs in JKA are gentity_t (defined in game/g_local.h), pmove_t and cg_t (defined in cgame/cg_local.h) Type Casting Sometimes you will find yourself needing to convert variables of one type to another. Normally you shouldn't be doing this, but in the rare cases you do, there is a little cheat code - the Type Cast. int x = 10; float y; y = (float)x; // we are converting the type of x to be a float, since y is a float! There are other ways of type casting, but they are C++ only, and explanation of other things is required before they can be used adequately. Macros Macros aren't exactly variables or types, but it's important to understand what they do in order to save time. Basically, a macro places code for you where you expect it to be. Ergo the define statement: #define SOMECODE_HERE = 0 int d SOMECODE_HERE; // what this compiles down to is "int d = 0;" More often than not, you see this being used to define values: #define MAX_WEAPONS 19 You can also insert variables into macros thusly: #define SOMECODE_HERE(x) = x int d SOMECODE_HERE(10); // this compiles down to "int d = 10;" C++ 102: Control Flow Variables are great, but they're nothing without actually doing something with them. Enter the almighty if statement: int x = 10; if(x == 0) { // some code that never gets run } Conditional statements and loops are the backbone of your code and help to shape behavior. In order to understand that, a final piece of the previous section needs to be discussed. Boolean Values The bool and qboolean types are interesting in that they can only represent true (qtrue) and false (qfalse). The most obvious way to assign values would be the following: qboolean bSomething; if(/* something */) { bSomething = true; } else { bSomething = false; } However, the more intuitive way would be to do the following: bSomething = /* something */; There are a variety of boolean functions: Comparator (==). This one compares two values to see if they are equal, and returns true if they are. Example: if(a == b) would succeed if a and b are the same value. NOTE THE TWO EQUAL SIGNS, NOT ONE! Inequality comparator (!=). This one compares two values to see if they are not equal and returns true if they are. Example: if(a != b) would succeed if a and b aren't the same value Greater than operator (>). Example: if(a > b) would succeed if a is greater than b Less than operator (<). Example: if(a < b) would succeed if a is less than b Greater than or equal to and Less than or equal to operators (>= and <=). Example: if(a >= b) would succeed if a is greater than or equal to b "and" operator (&&). Allows you to chain multiple boolean functions together. If both are true, then the value is true. Example: if(a != b && b != c) would succeed if a != b AND b != c "or" operator (||). Similar to the above, except it returns true if ONE of the comparisons is true. Example: if(a != b || b != c) would succeed if a != b OR b != c "not" operator (!). This one inverts the value. Example: if(!a) would succeed if a is false if statement Ah, the humble if statement. This cornerstone of programming is seen everywhere, including basic circuit boards and deep-level machine programming. Truly something remarkable. qboolean doSomething; if(doSomething) { // we do it! } else { // if the above check failed, we do this instead. } These can be chained into more complex behavior: qboolean doSomething; qboolean doSomethingElse; if(doSomething) { if(doSomethingElse) { // ... } else { // ... } } else { // ... } If statements don't necessarily need the {} if the expression is one line: qboolean doSomething; int x; if(doSomething) x = 10; // no braces needed! else { x = 10; x++; // we still need braces because there's multiple lines here } By abusing the above, we get "else if": if(/* something */) { // ... } else if(/* something else */) { // ... } else { // ... } else if is a nice way of shortening down some more logically complex code. The above would've looked like this: if(/* something */) { // ... } else { if(/* something else */) { // ... } else { // ... } } As you can see, with very long else/if chains this greatly simplifies the code and prevents headaches later down the line when optimizations need to be made. But suppose you're in a situation like this: if(x == 0) { // ... } else if(x == 1) { // ... } else if(x == SOMEENUMERATION) { // ... } else if(x == 1320934) { // ... } else { // ... } There's an even better way still... switch Statement Behold, the switch statement. This allows you to compare one variable to multiple values at once. Simply put, a switch compares a variable to multiple values and performs whatever is in its case block (or default, if there is no case block). What does this look like? Simple. int x = 10; switch(x) { case 10: // this is what gets run, because the value is 10 break; case 20: // this would only get run if the value is 20 break; default: // this would get run if the value isn't 20 or 10 but some other value break; } There's a few points worth mentioning. First, this cannot be used for strings at all. Second, the cases can only be constants, so something like this is totally invalid: switch(x) { case y: // totally invalid break; case 10: // valid break; } There's also another keyword here - the break keyword. This prevents what is known as "fall through" - switch(x) { case 10: // if this were 10, this block would get run. case 20: // this would also get run since there is no break statement } Sometimes this may be intentional, but most of the time it isn't. Take care to include your break statements. A note on floating point numbers and strings Floating point numbers (such as those involved with the "float" and "double" types) have issues with comparisons. Namely, there is no floating point representation for "0" due to the way they are handled internally. Also, you might have issues with comparing other numbers. In order to avoid this, it's best to compare the values to a range using the greater than/less than (or equals to) operators: float x = 0; if(x == 0) { // this might not be true!! } if(x <= 0.001 && x >= -0.001) { // this is definitely true!! } The difference between the two values (in this case being 0.001) is known as an epsilon value. Of course, this need not apply to numbers like integers or character values. It's complicated math reasons that are responsible for this being the case. Read more here if you're interested. Another thing- comparing strings with the == is actually valid code, but it won't produce expected effects. When I cover pointers, you will see why this is the case. Loops Sometimes, you may find yourself in a situation where code needs to be repeated several times, with or without minor modifications. In some cases a macro is needed, but more often than not, you'll need to use a loop. There are three different kinds of loops. If a loop doesn't have a termination sequence, then you will encounter something known as an "infinite loop," where the program freezes (not crashes) while loop This is the most primitive form of a loop in existence. It performs actions while the condition is true. Here is a good example: int x = 0; while(x < 10) { // if x was 10, this wouldn't get run at all! x++; // this gets run exactly 10 times } We can also use "true" (C++) or "qtrue" © or "1" to have a loop which can terminate on its own. Consider this loop. You'll note that there is the keyword "break" again here, but there is also a new keyword which doesn't appear which warrants discussion: continue. Consider that a loop goes through a sequence multiple times. The "break" keyword effectively terminates the loop, while the "continue" keyword says "skip ahead, I don't care about what happens in the next chunk of code." while(1) { if(x == 10) { break; // loop is finished } if(x == 11) { continue; // go back to the beginning of the loop, but do not terminate. } } do-while loops A modification of the while loop is the do-while loop. At its core, these are no different than the while loop, other than that they are guaranteed to execute the contents of the loop at least once. Here is an example: x = 10; do { x++; // this will happen } while(x < 10); x = 10; while(x < 10) { x++; // this won't } for loops Consider our first example of a loop, back in the while loops section: int x = 0; while(x < 10) { // if x was 10, this wouldn't get run at all! x++; // this gets run exactly 10 times } C and C++ have a nicer way of writing this as well! Behold the for loop: int x; for(x = 0; x < 10; x++) { // this gets run exactly 10 times } The syntax of the for loop is quite simple: for(/*precondition, what happens prior to the loop getting run*/ ; /*what is needed to make the loop break*/ ; /*what gets run on every cycle*/) { } Any of the three sections can be skipped entirely. If needed, a for-loop can just look like this: for(; { } Of course, that wouldn't be very useful! But you see what I mean. Here is another example which demonstrates the idea of doing multiple things on each section: for(x = 0, y = 10; x < 10 && y < 20; x++, y++) { } The comma operator here can be seen as simply "do this, but ignore the result", which is perfect in this context. We can also use it on a while loop to set something on each cycle of the loop: while(x = 0, y < 10) { // x will always be 0 when we start each cycle of the loop, and we will break if y is >= 10 } C++ 103: Functions Now that we've covered both variables and flow of control, it's time to combine both concepts into some workable code: functions. The game is broken up into five distinct parts: the engine, the game code (g_, game/, serverside, jampgamex86.dll, etc), cgame code (cg_, cgame/, clientside, cgamex86.dll, etc), ui code (ui_, ui/, uix86.dll, etc), and the renderer (r_, rd-vanilla, rd-rend2, etc). Each of these parts has an entry point: Engine: WinMain, or main (depends on operating system) Game code: G_InitGame (g_main.c) Cgame: CG_Init (cg_main.c) UI: UI_Init (ui_main.c) Renderer: R_Init (tr_init.cpp) Singleplayer note: In SP, the "Game code" and "cgame" code are moved into a single module, and the UI module is moved into the engine. These entry points occur in what are called "functions" and they are where everything gets executed. Everything that happens, happens because it was called from a function. Functions call other functions, and in that, the code forms a delicate network of function calls, like a spider's web. But we need to understand the basic syntax of a function first: <return type> <function name> (<arguments, if any>) { } void return type I will start with the void function, which is the simplest type there is: void SomeFunction() { // do some stuff here } Simply put, void functions can be seen as nice little snippets of code we can use anywhere. We can invoke the above example by doing the following: SomeFunction(); That's about as easy as it gets. Of course, there are parameters which can be fed into the function: void SomeFunction(int someArgument) { Com_Printf("We called SomeFunction with someArgument = %d\n", someArgument); } ...which we later call by doing: SomeFunction(10); // prints "We called SomeFunction with someArgument = 10" to the console SomeFunction(20); // same as above but with 20 SomeFunction(30); // ... Com_Printf is one of the functions that is used by the game. We will talk more about these functions in the CRT chapter. return keyword A function sometimes needs to be able to exit at any point in time. In order to do so, we invoke the "return" keyword: void DoSomething(int someArgument) { if(someArgument == 10) { return; // The argument is 10 so we don't print the message } Com_Printf("someArgument is NOT ten!\n"); } And now if we invoke DoSomething: DoSomething(10); // doesn't print the message DoSomething(20); // does Non-void functions Functions can also return a value. When this happens, the value which is brought back by the function can be passed into a variable. Here is a trivial example: int MultiplyNumberByTwo(int x) { return x * 2; } Note how we are using a variable type (int) instead of the word "void". This designates that we must return a value from the function. Here is an example of how we call it: int x = 2; x = MultiplyNumberByTwo(x); // value = 4 x = MultiplyNumberByTwo(x); // value = 8 x = MultiplyNumberByTwo(x); // value = 16 Note that void functions cannot return a value. Non-void functions on the other hand must return a value. Static variables Suppose we wanted to keep track of how many times a function is called. One way to do this would be to use a static variable. Let's add a usage counter to our MultiplyNumberByTwo function. int MultiplyNumberByTwo(int num) { static int usageCount = 1; Com_Printf("MultiplyNumberByTwo has been called %d times\n", usageCount); usageCount++; return num * 2; } If we didn't make the variable static, it wouldn't keep its value after it was finished. This is because functions keep variables in a temporary block of memory known as the stack. Each time a function cleans up, the values in the stack are discarded and used again. But by declaring the variable as being static, we are telling the compiler to keep the variable out of the stack and in a secure section of memory where we can access it again later. Calling functions from other files As you might have figured out, there are two types of files: source files (.c or .cpp) and header files (.h, rarely .hpp). In C, you cannot define a function in a header file, you can only use this to declare them (like you would a variable. In C++ this restriction doesn't exist but it's still good to follow this). Source files can include header files in order to make those declarations available. You commonly see this at the beginning of a .c or .cpp file: #include "g_local.h" So if we have the function written in say g_main.c, and its signature (the function without the stuff in brackets) looks like this: int MultiplyNumberByTwo(int number); We would need to declare the function in g_local.h using its signature in order to make it be accessible by all files which include g_local.h. Simple stuff, if you think about it. Static functions Suppose we wanted a function to only be ever be used in one file. This is where the static keyword comes in again: static int MultiplyNumber(int number) { return number * 3; } Since the function is static, it is a unique function to that file and we shouldn't declare it in a header. Another cheat code: extern Suppose for some reason we didn't want to declare a function in g_local.h. In order to use it in another file, we could use the extern keyword to declare it before use. This is good if we don't want a function to be seen by every single thing that includes g_local.h. One example of where this is used quite prominently is in the SP AI code. C++ 201: Pointers As defined in a previous section, a pointer is a variable which "points to" a chunk of memory. Most often these are used for strings, but there are other uses as well. Creating and Dereferencing Pointers The asterisk (*) not only denotes multiplication, but it is also used to define pointers: int* x; // a pointer to some integer variable But exactly how do we manipulate pointers? Perhaps one of the most basic methods involves using the ampersand (&) which references (or creates a pointer to) a variable: int x = 10; int* y = &x; // this is now a pointer to the x variable Now that we have a pointer, we can also retrieve the value of what we are pointing to by dereferencing it. Even more confusingly, this uses the asterisk again. Consider this chunk of code: int x = 10; int* y = &x; if(*y == x) { // This is always true. *y is 10 because we are retrieving the value of what we are pointing to. } Strings (and a nice little math hack) Have you tried using == on a string yet and realized that it hasn't worked? This is because strings are usually pointers to character arrays in memory, and when you use the == operator, you're comparing the values of the pointers, not the values of the strings themselves. I will cover all of the string functions in depth in another tutorial/guide, but there are two key functions for comparing functions: Q_stricmp and Q_stricmpn. const char* someString = "Something"; if(Q_stricmp(someString, "something") == 0) { // Q_stricmp returns 0 if the two strings are equal. This is case insensitive. So this chunk of code would get executed. } if(Q_stricmpn(someString, "som", 3) == 0) { // Q_stricmpn returns 0 if the two strings are equal, but compares only a certain number of characters as specified by the third argument. So this code would get executed. } So if that's the case, can we also do something like this? const char* someString = "Something"; if(someString[0] == 'S') { // Code Block } The answer is: yes. Even though someString is a pointer, it's pointing to an array of characters, specifically, the start of the array. Therefore, when you are using the indexing function (the brackets, []), you are really executing a math formula which looks something like this: if(someString[x] == *(someString + x)) { // always true } Remember that pointers are variables which point to chunks of memory. When you are manipulating the value of the pointer, you are changing what it is pointing to. Also remember, all strings end in a '\0' character! Passing variables by reference and by value Another practical application of pointers involves passing something by reference, instead of by value. Consider the following function: void MultiplyValueByTwo(int x) { x *= 2; } Despite calling it like this: int x = 2; MultiplyValueByTwo(x); The variable x in this case doesn't change. Why? Because you're passing the value of x to the function, not the reference. Simply put, you're copying the value that x contains into what is actually passed into the function. If this sounds confusing at first, it probably is. But here's an example of something that passes by reference: void DivideValueByTwo(int* x) { *x /= 2; } ... int x = 20; DivideValueByTwo(&x); Since you are passing the value of the pointer to the function, you have free reign to modify the variable at your whim...as long as you dereference it! While this may be a trivial example, there are lots of applications where this might be useful. Structs and Pointers Structs have a nice little shortcut for dealing with pointers. While your first instinct may be to do something like this: struct SomeStruct { int x; int y; }; void SetXToTwenty(SomeStruct* q) { (*q).x = 20; } ...an even nicer way to do this involves using the -> operator: struct SomeStruct { int x; int y; }; void SetXToTwenty(SomeStruct* q) { q->x = 20; } You can see this used almost everywhere in the serverside code. Some of the most glaring examples are in the cheat commands. It's almost always preferred to send a pointer to a structure instead of a copy to a function, even when it's not necessary, simply because you are copying a much smaller number of bytes (4 or 8, compared to however large the structure is...in singleplayer, gentity_t is somewhere in the realm of 1000 bytes!!) and therefore saving on performance. ...going deeper So what happens if you need to pass a pointer by reference into a function? You go deeper, my friend. void MultiplyValueByTwo(int** ppValue) { (*(*ppValue)) *= 2; } This is a pointer-to-a-pointer, and you can make this as deep as you want to go: int******************************************************************************************************* x; // totally valid but wtf Function pointers You can also make pointers to functions, although the syntax is a bit weird: int MultiplyValueByTwo(int x) { return x * 2; } // Defining the variable int (*SomeFunctionPointer)(int x) = MultiplyValueByTwo; // Calling the function int y = SomeFunctionPointer(d); There's relatively few uses for these, although perhaps you will find one someday.
  10. I had a very curious person send me an email asking how to increase the NPC limits in SP, so I went a bit ham and wrote a full guide. This gets asked a lot, so I figured I'd pitch it here. If staff would like (@@Caelum / @@Circa ?) I can post it in the tutorials section if it isn't already there. --- Hi, For starters, you will probably want to use the Visual Studio C++ compiler, not Visual Basic as that's a different language altogether. After you've got that fixed, you'll want to consult this guide for getting the thing compiled: https://github.com/JACoders/OpenJK/wiki/Compilation-guide Once you get it compiled, you'll note that it builds a "jagamex86.dll", an "Openjk.x86.exe" and a "rdsp-vanilla.x86.dll" (or something along those lines, I don't remember the exact names). You'll want to place the jagamex86.dll in a mod folder (you can create a mod folder in Gamedata), and the other files in your Gamedata folder. Then to launch your mod, you'll want to create a .bat script file (it's very easy, all you need to do is open up Notepad, and save a file as .bat), with the following contents: `openjk.x86.exe +set fs_game "MyMod"` Where MyMod is the name of your mod folder. But don't include the `s! If you got that all working, you can start making your edits now. There are a few specific things regarding limits. First of all, the list of NPC files is limited to 2048 characters. Look at this file, at this line I've highlighted, and change the number from 2048 to something higher: https://github.com/JACoders/OpenJK/blob/712627afab20aaa772d062a8fc8aeb5d3efa6152/code/game/NPC_stats.cpp#L4084 Second...the .npc files are all dumped into a giant buffer and read from this buffer. This is limited, and if you go over it, you'll likely run into a "NPC_LoadParms: ran out of space before reading ..." error. This limit is handled here: https://github.com/JACoders/OpenJK/blob/712627afab20aaa772d062a8fc8aeb5d3efa6152/code/game/NPC_stats.cpp#L217 Just note that this is a hexadecimal number. If you fire up good ol' Windows calculator in Programmer mode, you can see that 0x80000 is the same as 524,288 - so the maximum size of all NPC files combined is 524,288 bytes, or 512 kilobytes. To up this limit, you can just add a couple of zeroes to this (so make it 0x8000000 or something), or use a number larger than 524288. Beware though, the highest you can go is about 2000000000 (2 billion) before you will run into issues (and you don't really want to do this anyway - you'll make the game chew up 1gb in RAM !!) If you have any questions or concerns, feel free to send me a response. ---
  11. 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.
  12. 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.
  13. 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.
  14. 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, };
  15. 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, };
  16. 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.
  17. 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.
  18. 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.
  19. 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.
  20. 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.
  21. For more details on what this does, please see this thread. No previous installation is required to run this. DOWNLOAD LINK
  22. 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
  23. 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.
  24. @@Darth Futuza @ @@Silverfang @@Dalo Lorn @@swegmaster @ @@Noodle @@Onysfx @@Ramikad How does Saturday December 30th, 2017 at 14:00-17:00 GMT-3 sound ?
  25. 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

×
×
  • Create New...