Jump to content

eezstreet

Members
  • Posts

    5,207
  • Joined

  • Last visited

Everything posted by eezstreet

  1. Use the same format as last time. Please state your availability and we will try to organize to get the most people online. ------------------------------------------------------ @eezstreet - Not available on Tuesdays, Thursdays, and Fridays. @@Dalo Lorn - European Time, weekends between 12:00 - 22 CET (gmt+1) @@swegmaster - American, weekends @Ramikad - GMT +1, any time before midnight or past midnight ------------------------------------------------------
  2. Small update. I programmed in a crash catcher/stack trace printer, so all we need to do is host another scrimmage match and have Smoo compile it on Debug.
  3. Right but for most purposes you can just use BOTH_CIN_x animations which can be loaded on a per-map basis and used by ICARUS. There's like 50 of them so unless you need more, you don't really need to touch the game code to add new animations which are usable by ICARUS. Anything else (extra death animations for example) would have to be added through code, and I assume it's not a problem for someone to recompile the game code there. Having something like a modder GLA allows you to have an extra set of overrides per player model.
  4. So what are we going to do about the crash? Are we going to organize another test and run on @'s server or are we going to try and run it on my (bad ping) machine?
  5. I kinda think that there should be a third person HUD and the first person HUD should hide certain elements based on which gauges are present. Would kinda use the same system as the ammo display on guns. There's already enough benefits/disadvantages of first person vs third person (first person has better immersion, better aiming and you can see distant targets better, whereas third person makes it easier to do evasive maneuvers and you can see your ship better) Otherwise, I think this old document has some interesting elements to it. Definitely want the ability to lock S-foils into attack position
  6. Small update: I've fixed the chat bug. We just need to fix the crashing and we should be okay I think code-wise.
  7. Something to note, I think that capital ships are not worth the time to get ingame simply because they will require extremely large crews of players to operate, think like 30 players just to run it at minimum capabilities. While I intend to have the entire galaxy on one shard (it can maybe be a distributed system and different regions can run on different machines, but the important part is that the connection has to be seamless) and to remove the player limit, I think it's somewhat ambitious to expect them to be pilotable. Otherwise I pretty much agree spot-on with your ideas. Also, the Millennium Falcon was a YT-1300. Minor nitpick.
  8. If we can get Smoo to run the server in gdb and/or fix the compiler warnings on Linux (and there are a lot of them...) I think that would help us find the server crash. I'm pretty sure it's a linux specific issue but we can run a Windows machine to be sure. If Ramikad is right and every 12th message is lost, that's helpful.
  9. I'm thinking that ships would have crews of multiple players that can walk around inside of them. You *could* run a ship solo but it's a lot of work. I think only starfighters will be in Phase 1. I'm redoing all of the code involving vehicles, including their movement.
  10. As it stands, there are two major-ish bugs with this version. However the trouble is we cannot replicate the bugs reliably enough to fix them. They are: Chat sometimes does not show up in the chatbox (still visible in the console)Server sometimes crashes on LinuxWe have a choice here: either fix the bugs and delay putting out 1.3.0 while we try to debug this, or release now and opt to fix them later. After all, this is sort of like an Early Access game and such things are commonplace, but I have a penchant for perfection. Regardless of the above, there's still some issues we need to fix, specifically: Balance related stuffMore special ammo types (we are going to add the ability for ammo types to add buffs, which would allow us to do incendiary rounds, cryogen/toxic/carbonite tanks for the CR-25, and poison/incendiary canisters for the LS-180/AA-35)There is probably going to be a 1.3.1 patch after 1.3.0 which adds more interesting stuff. Silverfang and I discussed adding new charging effects like making the bowcaster shoot more shots when charged, as an example. For 1.4.0 we need to probably go back to the drawing board and strip out a lot of the JKA and JKG stuff, because in our move to OpenJK we managed to break the solid blocking system we had before. Additionally it's totally worthless to send hilts in the userinfo since that's controlled entirely by the variation now. Lastly, I think I'm going to try and implement dual wielding blasters as part of 1.4.0 since the concept of dual wielding a saber is going to use related code I think. By adding dual wielding, it will probably fix the longstanding issue where blasters appear to shoot from the face. Let me hear your guys' opinions about this.
  11. Vehicles in Jedi Knight Galaxies will have totally different movement, prediction, and feel from Jedi Academy. Generally, all vehicles will have both first person and third person, as well as a separate set of binds specific to different types of vehicle. Vehicles can become damaged and need to be repaired. They can also be outfitted with upgrades. When purchasing player housing, the types of vehicle storage may be an important consideration. There are a few different types of vehicles: Walkers include contraptions like the AT-ST and AT-RT. They walk on legs and can traverse basic slopes and adverse terrain. They may carry a passenger (including on mounted gun turrets). They control fairly similarly to the AT-ST in Jedi Outcast.Speeders include landspeeders as well as swoops. They hover some distance off the ground, but are built for speed, not durability. They may carry a passenger. Like the movies, the pitch and roll can be controlled independently, but banked turns are required for yaw turns. They can have attached guns but you can also perform drive-by shootings (or slicing, with lightsabers) or perhaps even jump on adjacent speeders.Animals include things like tauntauns and banthas. They cannot be repaired and do not require fuel. Instead they must stop to graze for a few minutes every couple of hours. The Tauntaun controls in Jedi Academy are pretty similar to how I envision these working.Starfighters include things like X-Wings and Tie-Fighters. They may have an attached droid and almost never have passengers. Pitch, yaw and roll are fairly easily controllable, and they have a plethora of guns. They can go into space, although some are not capable of lightspeed travel.Cargo Ships include things like the Millennium Falcon. Generally they require a full crew to be operational. They can also serve as mobile bases, and may be able to dock starfighters or store small vehicles, like animals or speeders. Their controls are fairly similar to starfighters but they have some weight behind their movements.Vehicles will be available in limited capacity in Phase 1, in a special Space Battle mode which we will concept at a later point.
  12. Yeah, armor will be fitted to a standard player model. I think the lag is because of the location. It is hosted in Dallas I think. I get like 50 ping.
  13. All in all that was a pretty good scrimmage match. Even found a few bugs: Armor doesn't persist properly across map_restartCanister mode on AA-35 and LS-180 don't seem to work properly
  14. @@Darth Futuza @ @@Dalo Lorn @@swegmaster @ @@Noodle @@Onysfx @@Ramikad @@gerbilOFdoom @@Stoiss The scrimmage match starts in three hours!
  15. I cut RAM usage down by 300mb (task manager doesn't show it very well but it's corrected) I also fixed damage plums not showing up if the target took shield damage.
  16. If you're going to use ICARUS, you're probably better off using the map override BOTH_CIN stuff anyway
  17. It took me like 5+ hours of tooling to get the Jedi Knight 2 SP code to compile, I don't intend to do the same again here. Why not just use OpenJK and edit it?
  18. We found some bugs in the 1.3.0a and fixed them for a version called 1.3.0b. Check this thread for details on it.
  19. 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
  20. 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
  21. 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.
  22. 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
  23. 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.
  24. 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. ---
×
×
  • Create New...