I'm having difficulties finding information online about QuakeC in general. Lots of links that go nowhere. Everytime I think I've found the jackpot, I get very little. It's been a bit discouraging.
What I would like is a good Quake compiler, and instructions on how to use to compile the Open Source files (found here: https://github.com/id-Software/Quake) into a development environment to do stuffs like this for example: Inside3D (I'm using Windows 7 64 bit)
Normally I wouldn't beg for help like this, but I've hit so many dead ends. Lurking on the forums you all seem helpful so I thought I'd ask.
Keep Playing Quake!
Announcement
Collapse
No announcement yet.
QuakeC Developer Tools?
Collapse
X
-
what you are calling options are called fields. I'm gonna make it easy for you. All globally declared fields are on every self. So mostly just read defs qc. When a field is created ALL ENTITIES adopt that field.
fields are any type which is declared with a preceeding dot.
.float, .string , etc
andOriginally posted by defs.qc.float modelindex; // *** model index in the precached list
.vector absmin, absmax; // *** origin + mins / maxs
.float ltime; // local time for entity
.float movetype;
.float solid;
.vector origin; // ***
.vector oldorigin; // ***
.vector velocity;
.vector angles;
.vector avelocity;
.vector punchangle; // temp angle adjust from damage or recoil
.string classname; // spawn function
.string model;
.float frame;
.float skin;
.float effects;
.vector mins, maxs; // bounding box extents reletive to origin
.vector size; // maxs - mins
.void() touch;
.void() use;
.void() think;
.void() blocked; // for doors or plats, called when can't push other
.float nextthink;
.entity groundentity;
.float health;
.float frags;
.float weapon; // one of the IT_SHOTGUN, etc flags
.string weaponmodel;
.float weaponframe;
.float currentammo;
.float ammo_shells, ammo_nails, ammo_rockets, ammo_cells;
.float items; // bit flags
.float takedamage;
.entity chain;
.float deadflag;
.vector view_ofs; // add to origin to get eye point
.float button0; // fire
.float button1; // use
.float button2; // jump
.float impulse; // weapon changes
.float fixangle;
.vector v_angle; // view / targeting angle for players
.float idealpitch; // calculated pitch angle for lookup up slopes
.string netname;
.entity enemy;
.float flags;
.float colormap;
.float team;
.float max_health; // players maximum health is stored here
.float teleport_time; // don't back up
.float armortype; // save this fraction of incoming damage
.float armorvalue;
.float waterlevel; // 0 = not in, 1 = feet, 2 = wast, 3 = eyes
.float watertype; // a contents value
.float ideal_yaw;
.float yaw_speed;
.entity aiment;
.entity goalentity; // a movetarget or an enemy
.float spawnflags;
.string target;
.string targetname;
.float dmg_take;
.float dmg_save;
.entity dmg_inflictor;
.entity owner; // who launched a missile
.vector movedir; // mostly for doors, but also used for waterjump
.string message; // trigger messages
.float sounds; // either a cd track number or sound number
.string noise, noise1, noise2, noise3; // contains names of wavs to play
andOriginally posted by defs.qc.void() th_stand;
.void() th_walk;
.void() th_run;
.void(entity attacker, float damage) th_pain;
.void() th_die;
.void() th_missile;
.void() th_melee;
That pretty much covers it. When I say all fields go to all entities I want to be real clear.Originally posted by defs.qc - expected by enginenoref .string wad, map, mdl, killtarget;
noref .entity trigger_field;
noref float skill;
noref .float worldtype, delay, wait, lip, light_lev, speed, style, dmg, attack_finished, cnt, count, state, jump_flag, walkframe;
noref .vector pos1, pos2, mangle, finaldest, finalangle, dest;
func_plat.walkframe exists. You'll never use it but func_plat is an entity and walkframe is a field so,... it exists.Last edited by MadGypsy; 05-01-2015, 12:04 PM.
-
Next Question.
If I wanted to look up all options for a function, and read the function itself (within or without of the Quake infrastructure) how do I do that?
For instance: self
It has all kinds of extensions and ways to instantiate.
For example:
self.classname
self.health
self.max_health
self.takedamage
self.solid
Is there a resource where I can look each option up? I want to know what it is and what it can do.
EDIT: ...and then I want to pee on it and mark it my territory.
Leave a comment:
-
Thanks for the clarification Zop, it really helps get things straight in my head. I'll update the ClientKill definition too, Thank you!Originally posted by Zop View PostGetting back to StartFrame and Pre/PostThink: it might help to recall that if you were to completely freeze/pause a Quake server mid-game, the players' locations would be fixed. A player could be stuck midair, for example, like a single cartoon frame. Yet during regular gameplay, the numbers representing a player's location (in quakec this is the .origin field) change according to physics calculations (like gravity) and player input. This means it's actually quite similar to a cartoon, since a player exists frame by frame in slightly changing locations (say, heading towards the ground).
Anyway, the server does this physics stuff and it will call those functions you named to allow quakec code to control particulars of your choosing, dependent if you want physics effects in your logic or not. The way it looks is like so, multiple times a second:
StartFrame()
PlayerPreThink() <-- player #1 is entity self
PlayerPreThink() <-- player #2 is entity self
PlayerPreThink() <-- etc
[Internal server math moves everything slightly, if it should be moving, plus more]
PlayerPostThink() <-- player #1 is entity self
PlayerPostThink() <-- player #2 is entity self
PlayerPostThink() <-- etc
I believe after all this is done, the server tells all the players their new locations (as well as rockets, grenades, monster, etc). However, there is a big caveat, since this is hardly the only way that the server and quakec interact, but it is the answer to your question.
I also noted that your description of ClientKill() is off. It turns out this function is called when a player does the "kill" command in the console. A player expects to die when doing this command, so the function should do that, but it's not typically used.
Excellent. Thanks gnounc, all my plans are possible.Originally posted by gnounc View Postassuming you know for a fact what their name will be..yes.
anywhere anything happens check netname.
for the water example, you'd probably want to edit watermove, because otherwise you'd need to change the touch function of every water brush (which you could also do).
in watermove, put in
that will make the player suicide.Code:if(self.netname == "derpface"){stuffcmd(self, "kill\n");}
Leave a comment:
-
assuming you know for a fact what their name will be..yes.
anywhere anything happens check netname.
for the water example, you'd probably want to edit watermove, because otherwise you'd need to change the touch function of every water brush (which you could also do).
in watermove, put in
that will make the player suicide.Code:if(self.netname == "derpface"){stuffcmd(self, "kill\n");}
Leave a comment:
-
I don't know much about multiplayer stuff, my coding always concerned itself with singleplayer things such as map entities and other stuff that is practical for SP mod making, such as HUDs and inventories and weapons and monsters and physics and AI and whatnot.Originally posted by QuakeKid View PostTell me something Golden Boy, could I use PlayerPreThink or PlayerPostThink to do the following:
If someone logs in, let’s say his name is B0rk… and I don’t like B0rk… I don’t like B0rk at all. Could I can make their name a trigger, and the moment they touch water, (or teleport, or singe themselves in lava, or get hit by a tomato) their ::face explodes:: or something like that?
I have... high hopes for this-ism.
Leave a comment:
-
Getting back to StartFrame and Pre/PostThink: it might help to recall that if you were to completely freeze/pause a Quake server mid-game, the players' locations would be fixed. A player could be stuck midair, for example, like a single cartoon frame. Yet during regular gameplay, the numbers representing a player's location (in quakec this is the .origin field) change according to physics calculations (like gravity) and player input. This means it's actually quite similar to a cartoon, since a player exists frame by frame in slightly changing locations (say, heading towards the ground).
Anyway, the server does this physics stuff and it will call those functions you named to allow quakec code to control particulars of your choosing, dependent if you want physics effects in your logic or not. The way it looks is like so, multiple times a second:
StartFrame()
PlayerPreThink() <-- player #1 is entity self
PlayerPreThink() <-- player #2 is entity self
PlayerPreThink() <-- etc
[Internal server math moves everything slightly, if it should be moving, plus more]
PlayerPostThink() <-- player #1 is entity self
PlayerPostThink() <-- player #2 is entity self
PlayerPostThink() <-- etc
I believe after all this is done, the server tells all the players their new locations (as well as rockets, grenades, monster, etc). However, there is a big caveat, since this is hardly the only way that the server and quakec interact, but it is the answer to your question.
I also noted that your description of ClientKill() is off. It turns out this function is called when a player does the "kill" command in the console. A player expects to die when doing this command, so the function should do that, but it's not typically used.
Leave a comment:
-
pbbbbbbt.... I can't believe I found this. My complete elevator logic. 2 of the obj_'s in the upper left use move_brush for their logic and individually they could be used for other purposes. The other one is just a fancy info_null
Leave a comment:
-
I actually know most of that now. I wrote this code 2 years ago. I don't really remember why I wanted the idea that I posted here. If I remember at all, I want to say I was thinking that individual monster classes could be eliminated by letting you set animations within array values....in the map.
Never forget that part cause it is very important. The main idea behind this rewrite was to utilize the fuck out of the map entity inspector, have a more generic code base and use the map to truly define your entities. So something like this was probably where I was going. Maybe not with flags though for this way of using it.
I'm not saying it was genius or anything but, I believe this is basically where I was going with this. Actually I just looked at the ent that I posted below and apparently the array was going to be "animator" hence it's string value that I could csv or whatever to concoct the array.Code:float WALK_START = 1; float WALK_END = 2; ... summin with animation[WALK_START] where animation was defined in the entity inspector.
@overcomplicated worst enemy
Yeah, I agree with that. Whatever I was doing with this at the time I know it at least seemed like the only way to accomplish the task from the end of the information coming from the map. Actually, there is no doubt it was the cleanest looking way. The alternative would have been to turn the .ent into a ridiculous list of animation properties for every animate-able thing.
Really, even though I am sure there is some reason that contouring your specific entity in the map editor is bad or whatever... I built an entire really working elevator using this method. It stopped on floors and could be called and everything. There was no specific elevator code. There was move_brush code but move_brush is ANY brush that can move. Door, plat, lift, anything you want... even a fully functioning elevator, with buttons that were move_brush as well. You can literally invent entities in the map. very complicated ones.
That's also why this project was named Gnome. You put a bunch of mostly generic "gnomes" in the map and you can turn them into all kinds of stuff. Like the gnomes you see in the corner of your eye but when you turn your head they disappear behind "stuff". Maybe they disappeared into the "stuff", and that's basically what the gnomes in my map do. You could even make gnome particle clouds.
What else could you expect from a guy that loved radiant and knew qc pretty fair? Of course after tearing apart the entire back-end of possibilities in radiant, the logical next step was to make a "base game" & game pack that made radiant the game studio. I would have done it too if it weren't for you meddling kids and that stupid dog... lolLast edited by MadGypsy; 04-29-2015, 09:46 PM.
Leave a comment:
-
using floats to refer to strings is silly, just pass the string directly. this also means you can use strcat/sprintf/whatever to provide extra info about the error. plus it means the code is more readable too, and you can more easily find the bit of code that triggers the error instead of having to find the bit of code that prints it and THEN the bit of code that triggers it.
over-complicated code is your biggest enemy.
regarding bitflags and arrays, often you'll find it easier to do it the other way around. use bitshifts instead of trying to figure out what the bit index should be from a bit value.
tbh I'm not sure why you'd be doing that sort of thing anyway.
try to avoid arrays, they're typically emulated, and globals. globals are a poor programming practise, and thus evil.
Leave a comment:
-
Tell me something Golden Boy, could I use PlayerPreThink or PlayerPostThink to do the following:Originally posted by golden_boy View PostA frame is a render frame. The engine renders x frames per second, and each frame these functions are run. (edit: I might actually have misremembered this, it might just be a server frame or something since the progs.dat runs on the server of course, not on the client. I hope Spike or someone like that will clarify it.)
Player Pre Think is a function called at the start of every frame, Player Post Think at the end.
That's all there is to it.
You can use these functions to do stuff that should be run every frame (i.e. continuously), such as checking whether the player is in water or whatever.
Animation in Quake is done at 10 frames per second, which is why interpolation is needed at higher frame rates to make the animations look smooth. All the animation macros in the monster code assume a speed of 10 fps. Ancient, I know.
Edit: IIRC main() is never actually run in qc.
Edit 2: Why are you trying to write a progs.dat from scratch? Isn't that kinda brutal?
Edit 3: And before you ask, the QC source is full of functions that are never called from another function - monster_ogre() and such. These functions contain monster spawn directions and similar stuff, they are spawn functions that get called when a map file is loaded. If the map has a monster_ogre entity, monster_ogre() in the qc gets called to set it up with the origin etc supplied by the map.
If someone logs in, let’s say his name is B0rk… and I don’t like B0rk… I don’t like B0rk at all. Could I can make their name a trigger, and the moment they touch water, (or teleport, or singe themselves in lava, or get hit by a tomato) their ::face explodes:: or something like that?
I have... high hopes for this-ism.
Leave a comment:
-
Last one. Here is a concept for you.
The implication here is to make associative arrays. For instance you could designate some flag constants and they can be used to label arrays indexes. The purpose of using a flag is that flag can also be used as a flag normally would, meaning that essentially you could record entity values in an array for use somewhere else.Code:// Gypsy: find and return the power of the flag value float(float fl) index = { local float i; //FFwd ->> i = ceil( ( strlen( ftos(fl) ) - 1 ) * 3.33 ); //this will only have to run a maximum of 3 times to hit the number while(pow(2,i) < fl){ i++; } return i; };
As far as FFwd->> goes. I just figured out a constant algorithm that gets the start index within 3 increments of the final answer. It is a nutty formula based on the amount of numbers in the flag (ie 128 = ceil((3-1)*3.33)=7^2=12Code:float MY_FLAG = 2; float MY_OTHER_FLAG = 4; float A_BUNCH_OF_FLAGS_LATER = 256; float someArray[32]; someArray[index(A_BUNCH_OF_FLAGS_LATER)] = someValue;
and then 3.33 turned out to be the magic number to get it really close every time without ever going over. Probably because power only hold their character count for 3 powers.
2, 4, 8
16, 32, 64
128, 256, 512
1024, 2048, 4096, 8192 <- hah, well until here. It probably gets worse. Whatevs, the index is still fast forwarded without going over. It would skip 9 numbers in this case. Meaning the while has to run 9 less times. As the flag numbers increase those skips ahead could become substantial.
@use as a resource
This isn't even close to all of the code. Code like this will have to be compiled with FTEQCC, because of the array.
@your sig - QuakeKid
That makes me hella uncomfortable, bro. I mean, thank you but, that isn't necessary. how about, "I'm just like MadGypsy, except Imma finish my custom QC." or something entirely different altogether? If you are reading books on C programming and calling them "good" (like you just watched an action thriller or summin) then you probably aren't dumb at all. I dont care if the book was C for Complete Fucking Idiots. Complete fucking idiots can't program in C so.....Last edited by MadGypsy; 04-29-2015, 08:37 PM.
Leave a comment:
-
I'll be honest, a lot of this is still over my head.Originally posted by MadGypsy View PostHere was the "class" that forced errors and recorded warnings. Understand that these custom messages are intended to make sure that MY code is being used the way I intended and to spit out warnings that can help you track down missing properties in YOUR MAP.
In other words. A whole lot of this is stuff that a stack trace isn't going to report.
Code:// Gypsy:Gnome - Exceptions Manager | build 20130513-14:39cn float ERR_GNOMENAME = 1; float ERR_MODELPATH = 2; float ERR_SOUNDPATH = 3; float ERR_EFFECTNAME = 4; float ERR_ANIMATOR = 5; float ERR_ID = 6; float ERR_TARGET = 7; float ERR_TARGETNAME = 8; float ERR_STATIC_NOMODELPATH = 9; float ERR_NOHEALTH_DMG = 10; float ERR_HEALTH_NODMG = 11; float ERR_VIEW_OFS = 12; float ERR_URGENT = 13; float ERR_NOINTERFACE = 14; float ERR_INTERFACEONLY = 15; float ERR_PNTS = 16; float ERR_WEAPON = 17; float ERR_PASSEDFLOOR = 18; float ERR_NODATA2WRITE = 19; float ERR_MOVEDIR = 20; float ERR_SPEED = 21; float ERR_NODIMS = 22; float ERR_TYPE = 23; float ERR_NOTARGETS = 24; float ERR_GROUP = 25; float ERR_GROUPNAME = 26; void(float err) ThrowException = { //hint to the object dprint("\n\n"); dprint("GNOME PHUQUP: an exception was thrown by: "); dprint(self.classname); dprint(" at: "); dprint(vtos(self.origin)); dprint("\n"); switch(err) { case ERR_GNOMENAME: dprint("ERR_GNOMENAME: It is mandatory to set the classname field.\n"); break; case ERR_MODELPATH: dprint("ERR_MODELPATH: It is mandatory to set the model field.\n"); break; case ERR_SOUNDPATH: dprint("ERR_SOUNDPATH: It is mandatory to set the sound field.\n"); break; case ERR_EFFECTNAME: dprint("ERR_EFFECTNAME: It is mandatory to set the effectname field.\n"); break; case ERR_ANIMATOR: dprint("ERR_ANIMATOR: It is mandatory to set the animator field.\n"); break; case ERR_ID: dprint("ERR_ID: It is mandatory to set the id field.\n"); break; case ERR_TARGET: dprint("ERR_TARGET: It is mandatory to set the target field.\n"); break; case ERR_TARGETNAME: dprint("ERR_TARGETNAME: It is mandatory to set the targetname field.\n"); break; case ERR_STATIC_NOMODELPATH: dprint("ERR_STATIC_NOMODELPATH: You tried to turn an empty into a static. set the model field.\n"); break; case ERR_NOHEALTH_DMG: dprint("ERR_NOHEALTH_DMG: You gave a damagable entity no health.\n"); break; case ERR_HEALTH_NODMG: dprint("ERR_HEALTH_NODMG: You gave health to a non-damagable entity.\n"); break; case ERR_VIEW_OFS: dprint("ERR_VIEW_OFS: It is mandatory to set the view_ofs field.\n"); break; case ERR_URGENT: dprint("ERR_URGENT: This entity is being instantiated by an incompatible entity.\n"); break; case ERR_NOINTERFACE: dprint("ERR_NOINTERFACE: This entity cannot be subclassed in another entity.\n"); break; case ERR_INTERFACEONLY: dprint("ERR_INTERFACEONLY: This entity must be subclassed in another entity.\n"); break; case ERR_PNTS: dprint("ERR_PNTS: It is mandatory that both the mins and maxs fields are set.\n"); break; case ERR_WEAPON: dprint("ERR_WEAPON: This entity must be subclassed in another entity.\n"); break; case ERR_PASSEDFLOOR: dprint("ERR_PASSEDFLOOR: It is mandatory that both the mins and maxs fields are set.\n"); break; case ERR_NODATA2WRITE: dprint("ERR_NODATA2WRITE: There was no data to write to the file.\n"); break; case ERR_MOVEDIR: dprint("ERR_MOVEDIR: The movedir field was not set for this entity.\n"); break; case ERR_SPEED: dprint("ERR_SPEED: There was no speed set for this entity.\n"); break; case ERR_NODIMS: dprint("ERR_NODIMS: This entity has no dimensions.\n"); break; case ERR_TYPE: dprint("ERR_TYPE: It is mandatory that the type field is set for this entity.\n"); break; case ERR_NOTARGETS: dprint("ERR_NOTARGETS: It is mandatory that this entity have available targets.\n"); break; case ERR_GROUP: dprint("ERR_GROUP: It is mandatory that the group field is set for this entity.\n"); break; case ERR_GROUPNAME: dprint("ERR_GROUPNAME: It is mandatory that the groupname field is set for this entity.\n"); break; default: dprint(ftos(err)); dprint(" is not a recognized error number.\n"); break; } self.urgent = err; } // Gypsy - force a structure into the code // factor = 0: The entity canNOT be included in another entity // factor = 1: The entity can ONLY be included in another entity void(string match, float factor) InstanceRules = { if(!factor) { if(self.classname != match) { ThrowException(ERR_NOINTERFACE); self.urgent = ERR_NOINTERFACE; return; } } else { if(self.classname == match) { ThrowException(ERR_INTERFACEONLY); self.urgent = ERR_INTERFACEONLY; return; } } } // Gypsy:Gnome - Notification Manager | build 20130513-12:37cn float WARN_GNOMENAME = 1; float WARN_MODELPATH = 2; float WARN_SOUNDPATH = 3; float WARN_EFFECTNAME = 4; float WARN_ANIMATOR = 5; float WARN_ID = 6; float WARN_TARGET = 7; float WARN_TARGETNAME = 8; float WARN_STATIC_EFFNAME = 9; float WARN_SOLID = 10; float WARN_SOLIDNOT_HEALTH = 11; float WARN_HEALTH_NODMG = 12; float WARN_TAKEDAMAGE = 13; float WARN_MOVETYPE = 14; float WARN_VOLUME = 15; float WARN_ATTENUATION = 16; float WARN_VIEW_OFS = 17; float WARN_OVERRIDE_PNTS = 18; float WARN_ABS_PNTS = 19; float WARN_FILE = 20; float WARN_FILE_READ = 21; float WARN_FILE_COMPLETE = 23; float WARN_DMGSETNO = 24; float WARN_ORFIELDSPREAD = 25; //returns the appropriatre string back to an array index that matches the warning number string(float info) get_notice = { local string temp; temp = string_null; //compiler switch(info) { case WARN_GNOMENAME: temp = "gnomename field has not been set.\n"; break; case WARN_MODELPATH: temp = "model field has not been set. this entity must be a brush or empty. \n"; break; case WARN_SOUNDPATH: temp = "sound field has not been set.\n"; break; case WARN_EFFECTNAME: temp = "effectname field has not been set.\n"; break; case WARN_ANIMATOR: temp = "no animator was assigned.\n"; break; case WARN_ID: temp = "id field has not been set.\n"; break; case WARN_TARGET: temp = "target field has not been set.\n"; break; case WARN_TARGETNAME: temp = "targetname field has not been set.\n"; break; case WARN_MOVETYPE: temp = "movetype field was not set and defaulted to MOVETYPE_NONE.\n"; break; case WARN_TAKEDAMAGE: temp = "takedamage field was not set and defaulted to DAMAGE_NO.\n"; break; case WARN_SOLID: temp = "solid field was not set and defaulted to SOLID_NOT.\n"; break; case WARN_VIEW_OFS: temp = "view_ofs has not been set. the eye level of your model is assigned to it's origin point.\n"; break; case WARN_VOLUME: temp = "volume field was not set and defaulted to 0.5\n"; break; case WARN_ATTENUATION: temp = "attenuation field was not set and defaulted to ATTN_NONE\n"; break; case WARN_STATIC_EFFNAME: temp = "you cannot set an effect on static entities. your entity was defaulted to SOLID_NOT and MOVETYPE_NONE to allow the effect.\n"; break; case WARN_SOLIDNOT_HEALTH: temp = "you have health on a SOLID_NOT\n"; break; case WARN_OVERRIDE_PNTS: temp = "you are overriding the absmin/max properties.\n"; break; case WARN_ABS_PNTS: temp = "you are using absmin/max values for your bbox. \n"; break; case WARN_FILE: temp = "some file interaction didn't complete. \n"; break; case WARN_FILE_READ: temp = "couldn't read the file data \n"; break; case WARN_FILE_COMPLETE: temp = "the file interaction completed successfully. \n"; break; case WARN_HEALTH_NODMG: temp = "you have health on a non-damageable entity. \n"; break; case WARN_DMGSETNO: temp = "DAMAGE_NO was set for this entity. \n"; break; case WARN_ORFIELDSPREAD: temp = "you are overriding the default .fieldspread. \n"; break; } if(temp != string_null) self.notify[0] = "1"; else temp = strcat("A notification number that does not exist [", ftos(info), "] was attempted here.\n"); return temp; } /* I use an array here because treating it like a flag made the number rack up too fast. Now I can count in succession and keep the value very low. */ void(float info) manage_notifications = { cfg_var = CFG_ALLOW_NOTIFY; if( self.cfg_notify && cfg_var && !check_string(self.notify[info]) ) { self.notify[info] = get_notice(info); } }; string(string file, float typ, string str) file_manager = { cfg_var = CFG_WRITE_LOGS; //constants can't be placed directly into a conditional without a compiler warning //so let's assign the value to a global float and use that instead if(!cfg_var) { str = string_null; FILES_WRITABLE = 0; } else { local float fhandle = fopen(file, typ);//open a file if(!fhandle) { str = string_null; manage_notifications(WARN_FILE); //it's not a crisis so we just add it to notify[WARN_FILE] //notify[] should be empty - either it was wiped or never existed //this means WARN_FILE should now be the only notice FILES_WRITABLE = 0; //turn off writing ThrowNotifications(); //send the report to the console, which will not try to write again //ending any possible loop. } else { if(typ < 1) { str = fgets(fhandle); if( !check_string(str) ) { manage_notifications(WARN_FILE_READ); FILES_WRITABLE = 0; ThrowNotifications(); } } else { if( check_string(str) ) { fputs(fhandle,str); manage_notifications(WARN_FILE_COMPLETE); FILES_WRITABLE = 0; ThrowNotifications(); } else ThrowException(ERR_NODATA2WRITE); } fclose(fhandle); //close the file if(!FILES_WRITABLE) FILES_WRITABLE = 1; //turn writing back on } } return str; }; //prints a header and then spits out every stored notification void() ThrowNotifications = { local string temp; temp = string_null; if ( check_string(self.notify[0]) ) { //hint to the object temp = strcat("\n\nGNOME NOTIFIER: accumulated field notifications for ",self.classname," at: ",vtos(self.origin),"\n"); local float i; for (i=1; i<32; ++i) { if( check_string(self.notify[i]) ) { temp = strcat(temp,self.notify[i]); //building log string self.notify[i] = string_null; //reset } } dprint( strcat(temp,"\n") ); } cfg_var = CFG_WRITE_LOGS; if(FILES_WRITABLE && cfg_var) file_manager("notify.txt", FILE_APPEND, temp); }
I do have some background in C, but that only because I recently read the "C guide for dummies." It was surprisingly good, I'm going to be re-reading it soon. You gotta start somewhere.
This looks like really good stuff MadGypsy. I'll be using it as a resource as get better. Thank you for sharing, it looks amazing.
Leave a comment:
-
More fun stuff
Here was the "class" that forced errors and recorded warnings. Understand that these custom messages are intended to make sure that MY code is being used the way I intended and to spit out warnings that can help you track down missing properties in YOUR MAP.
In other words. A whole lot of this is stuff that a stack trace isn't going to report.
Code:// Gypsy:Gnome - Exceptions Manager | build 20130513-14:39cn float ERR_GNOMENAME = 1; float ERR_MODELPATH = 2; float ERR_SOUNDPATH = 3; float ERR_EFFECTNAME = 4; float ERR_ANIMATOR = 5; float ERR_ID = 6; float ERR_TARGET = 7; float ERR_TARGETNAME = 8; float ERR_STATIC_NOMODELPATH = 9; float ERR_NOHEALTH_DMG = 10; float ERR_HEALTH_NODMG = 11; float ERR_VIEW_OFS = 12; float ERR_URGENT = 13; float ERR_NOINTERFACE = 14; float ERR_INTERFACEONLY = 15; float ERR_PNTS = 16; float ERR_WEAPON = 17; float ERR_PASSEDFLOOR = 18; float ERR_NODATA2WRITE = 19; float ERR_MOVEDIR = 20; float ERR_SPEED = 21; float ERR_NODIMS = 22; float ERR_TYPE = 23; float ERR_NOTARGETS = 24; float ERR_GROUP = 25; float ERR_GROUPNAME = 26; void(float err) ThrowException = { //hint to the object dprint("\n\n"); dprint("GNOME PHUQUP: an exception was thrown by: "); dprint(self.classname); dprint(" at: "); dprint(vtos(self.origin)); dprint("\n"); switch(err) { case ERR_GNOMENAME: dprint("ERR_GNOMENAME: It is mandatory to set the classname field.\n"); break; case ERR_MODELPATH: dprint("ERR_MODELPATH: It is mandatory to set the model field.\n"); break; case ERR_SOUNDPATH: dprint("ERR_SOUNDPATH: It is mandatory to set the sound field.\n"); break; case ERR_EFFECTNAME: dprint("ERR_EFFECTNAME: It is mandatory to set the effectname field.\n"); break; case ERR_ANIMATOR: dprint("ERR_ANIMATOR: It is mandatory to set the animator field.\n"); break; case ERR_ID: dprint("ERR_ID: It is mandatory to set the id field.\n"); break; case ERR_TARGET: dprint("ERR_TARGET: It is mandatory to set the target field.\n"); break; case ERR_TARGETNAME: dprint("ERR_TARGETNAME: It is mandatory to set the targetname field.\n"); break; case ERR_STATIC_NOMODELPATH: dprint("ERR_STATIC_NOMODELPATH: You tried to turn an empty into a static. set the model field.\n"); break; case ERR_NOHEALTH_DMG: dprint("ERR_NOHEALTH_DMG: You gave a damagable entity no health.\n"); break; case ERR_HEALTH_NODMG: dprint("ERR_HEALTH_NODMG: You gave health to a non-damagable entity.\n"); break; case ERR_VIEW_OFS: dprint("ERR_VIEW_OFS: It is mandatory to set the view_ofs field.\n"); break; case ERR_URGENT: dprint("ERR_URGENT: This entity is being instantiated by an incompatible entity.\n"); break; case ERR_NOINTERFACE: dprint("ERR_NOINTERFACE: This entity cannot be subclassed in another entity.\n"); break; case ERR_INTERFACEONLY: dprint("ERR_INTERFACEONLY: This entity must be subclassed in another entity.\n"); break; case ERR_PNTS: dprint("ERR_PNTS: It is mandatory that both the mins and maxs fields are set.\n"); break; case ERR_WEAPON: dprint("ERR_WEAPON: This entity must be subclassed in another entity.\n"); break; case ERR_PASSEDFLOOR: dprint("ERR_PASSEDFLOOR: It is mandatory that both the mins and maxs fields are set.\n"); break; case ERR_NODATA2WRITE: dprint("ERR_NODATA2WRITE: There was no data to write to the file.\n"); break; case ERR_MOVEDIR: dprint("ERR_MOVEDIR: The movedir field was not set for this entity.\n"); break; case ERR_SPEED: dprint("ERR_SPEED: There was no speed set for this entity.\n"); break; case ERR_NODIMS: dprint("ERR_NODIMS: This entity has no dimensions.\n"); break; case ERR_TYPE: dprint("ERR_TYPE: It is mandatory that the type field is set for this entity.\n"); break; case ERR_NOTARGETS: dprint("ERR_NOTARGETS: It is mandatory that this entity have available targets.\n"); break; case ERR_GROUP: dprint("ERR_GROUP: It is mandatory that the group field is set for this entity.\n"); break; case ERR_GROUPNAME: dprint("ERR_GROUPNAME: It is mandatory that the groupname field is set for this entity.\n"); break; default: dprint(ftos(err)); dprint(" is not a recognized error number.\n"); break; } self.urgent = err; } // Gypsy - force a structure into the code // factor = 0: The entity canNOT be included in another entity // factor = 1: The entity can ONLY be included in another entity void(string match, float factor) InstanceRules = { if(!factor) { if(self.classname != match) { ThrowException(ERR_NOINTERFACE); self.urgent = ERR_NOINTERFACE; return; } } else { if(self.classname == match) { ThrowException(ERR_INTERFACEONLY); self.urgent = ERR_INTERFACEONLY; return; } } } // Gypsy:Gnome - Notification Manager | build 20130513-12:37cn float WARN_GNOMENAME = 1; float WARN_MODELPATH = 2; float WARN_SOUNDPATH = 3; float WARN_EFFECTNAME = 4; float WARN_ANIMATOR = 5; float WARN_ID = 6; float WARN_TARGET = 7; float WARN_TARGETNAME = 8; float WARN_STATIC_EFFNAME = 9; float WARN_SOLID = 10; float WARN_SOLIDNOT_HEALTH = 11; float WARN_HEALTH_NODMG = 12; float WARN_TAKEDAMAGE = 13; float WARN_MOVETYPE = 14; float WARN_VOLUME = 15; float WARN_ATTENUATION = 16; float WARN_VIEW_OFS = 17; float WARN_OVERRIDE_PNTS = 18; float WARN_ABS_PNTS = 19; float WARN_FILE = 20; float WARN_FILE_READ = 21; float WARN_FILE_COMPLETE = 23; float WARN_DMGSETNO = 24; float WARN_ORFIELDSPREAD = 25; //returns the appropriatre string back to an array index that matches the warning number string(float info) get_notice = { local string temp; temp = string_null; //compiler switch(info) { case WARN_GNOMENAME: temp = "gnomename field has not been set.\n"; break; case WARN_MODELPATH: temp = "model field has not been set. this entity must be a brush or empty. \n"; break; case WARN_SOUNDPATH: temp = "sound field has not been set.\n"; break; case WARN_EFFECTNAME: temp = "effectname field has not been set.\n"; break; case WARN_ANIMATOR: temp = "no animator was assigned.\n"; break; case WARN_ID: temp = "id field has not been set.\n"; break; case WARN_TARGET: temp = "target field has not been set.\n"; break; case WARN_TARGETNAME: temp = "targetname field has not been set.\n"; break; case WARN_MOVETYPE: temp = "movetype field was not set and defaulted to MOVETYPE_NONE.\n"; break; case WARN_TAKEDAMAGE: temp = "takedamage field was not set and defaulted to DAMAGE_NO.\n"; break; case WARN_SOLID: temp = "solid field was not set and defaulted to SOLID_NOT.\n"; break; case WARN_VIEW_OFS: temp = "view_ofs has not been set. the eye level of your model is assigned to it's origin point.\n"; break; case WARN_VOLUME: temp = "volume field was not set and defaulted to 0.5\n"; break; case WARN_ATTENUATION: temp = "attenuation field was not set and defaulted to ATTN_NONE\n"; break; case WARN_STATIC_EFFNAME: temp = "you cannot set an effect on static entities. your entity was defaulted to SOLID_NOT and MOVETYPE_NONE to allow the effect.\n"; break; case WARN_SOLIDNOT_HEALTH: temp = "you have health on a SOLID_NOT\n"; break; case WARN_OVERRIDE_PNTS: temp = "you are overriding the absmin/max properties.\n"; break; case WARN_ABS_PNTS: temp = "you are using absmin/max values for your bbox. \n"; break; case WARN_FILE: temp = "some file interaction didn't complete. \n"; break; case WARN_FILE_READ: temp = "couldn't read the file data \n"; break; case WARN_FILE_COMPLETE: temp = "the file interaction completed successfully. \n"; break; case WARN_HEALTH_NODMG: temp = "you have health on a non-damageable entity. \n"; break; case WARN_DMGSETNO: temp = "DAMAGE_NO was set for this entity. \n"; break; case WARN_ORFIELDSPREAD: temp = "you are overriding the default .fieldspread. \n"; break; } if(temp != string_null) self.notify[0] = "1"; else temp = strcat("A notification number that does not exist [", ftos(info), "] was attempted here.\n"); return temp; } /* I use an array here because treating it like a flag made the number rack up too fast. Now I can count in succession and keep the value very low. */ void(float info) manage_notifications = { cfg_var = CFG_ALLOW_NOTIFY; if( self.cfg_notify && cfg_var && !check_string(self.notify[info]) ) { self.notify[info] = get_notice(info); } }; string(string file, float typ, string str) file_manager = { cfg_var = CFG_WRITE_LOGS; //constants can't be placed directly into a conditional without a compiler warning //so let's assign the value to a global float and use that instead if(!cfg_var) { str = string_null; FILES_WRITABLE = 0; } else { local float fhandle = fopen(file, typ);//open a file if(!fhandle) { str = string_null; manage_notifications(WARN_FILE); //it's not a crisis so we just add it to notify[WARN_FILE] //notify[] should be empty - either it was wiped or never existed //this means WARN_FILE should now be the only notice FILES_WRITABLE = 0; //turn off writing ThrowNotifications(); //send the report to the console, which will not try to write again //ending any possible loop. } else { if(typ < 1) { str = fgets(fhandle); if( !check_string(str) ) { manage_notifications(WARN_FILE_READ); FILES_WRITABLE = 0; ThrowNotifications(); } } else { if( check_string(str) ) { fputs(fhandle,str); manage_notifications(WARN_FILE_COMPLETE); FILES_WRITABLE = 0; ThrowNotifications(); } else ThrowException(ERR_NODATA2WRITE); } fclose(fhandle); //close the file if(!FILES_WRITABLE) FILES_WRITABLE = 1; //turn writing back on } } return str; }; //prints a header and then spits out every stored notification void() ThrowNotifications = { local string temp; temp = string_null; if ( check_string(self.notify[0]) ) { //hint to the object temp = strcat("\n\nGNOME NOTIFIER: accumulated field notifications for ",self.classname," at: ",vtos(self.origin),"\n"); local float i; for (i=1; i<32; ++i) { if( check_string(self.notify[i]) ) { temp = strcat(temp,self.notify[i]); //building log string self.notify[i] = string_null; //reset } } dprint( strcat(temp,"\n") ); } cfg_var = CFG_WRITE_LOGS; if(FILES_WRITABLE && cfg_var) file_manager("notify.txt", FILE_APPEND, temp); }
Leave a comment:
-
Here is an example of how my code is very different. Note that I am doing my own error checking, which really isn't necessary and I am also forcing errors if my functions are used differently than I intended. This means that technically you could properly (in a qc way) call one of my functions and it will fail if it isn't how I decided it should be. Mostly the difference is calling a function from within a function versus being a func_entity. These IFunctions can't be entities.
note: ERR_INTERFACEONLY, would be better worded as ERR_SUBCLASSONLY. Cause essentially, these functions can only be called within another function. That function then inherits this functions processes, essentially faking a scenario where this function becomes the Super of the one that called it.... not really but it can be imagined that way. The function that calls the IFuncs will extend the possibilities of the IFunc, never the other way around.Code:/* Gypsy:Gnome - Interface Models | created 20130513-15:18cn */ // Gypsy - instantiate a model | build 20130514-00:29cn void() IModelInstance = //modelpath value could potentially come from somewhere other than self { //INTERFACE InstanceRules("IModelInstance",1); //ERR_INTERFACEONLY if(self.urgent > 0) return; //error /* if model is a required field: check it in self and handle before calling IModelInstance */ if( check_string(self.model) && !(self.spawnflags & INVISIBLE) ) precache_model(self.model); setmodel(self, self.model); setorigin (self, self.origin); //SLIDEBOX size is mandatory if( self.solid == SOLID_SLIDEBOX ) { if( check_vector(self.neg_pnt) && check_vector(self.pos_pnt) ) setsize(self, self.neg_pnt, self.pos_pnt); else { ThrowException(ERR_PNTS); return; } } else { if( check_vector(self.neg_pnt) && check_vector(self.pos_pnt) ) { setsize(self, self.neg_pnt, self.pos_pnt); manage_notifications(WARN_OVERRIDE_PNTS); } else { setsize(self, self.mins, self.maxs); manage_notifications(WARN_ABS_PNTS); } } if(self.spawnflags & INVISIBLE) self.model = ""; }; // Gypsy - set the interaction type attributes and instantiate a model | build 20130514-00:29cn void() ITypedInstance = { //INTERFACE InstanceRules("ITypedInstance",1); //ERR_INTERFACEONLY if(self.urgent > 0) return; //error /* if any of the below is a required field: check it in self and handle before calling ITypedInstance */ if( self.spawnflags & FL_MAKESTATIC ) { if( !check_string(self.effectname) ) //can't apply effects to static entities { if( check_string(self.model) ) { IModelInstance(); makestatic(self); return; } ThrowException(ERR_STATIC_NOMODELPATH); return; } else manage_notifications(WARN_STATIC_EFFNAME); } if(!self.solid) { if(self.health) manage_notifications(WARN_SOLIDNOT_HEALTH); manage_notifications(WARN_SOLID); } if(!self.movetype) { self.movetype = MOVETYPE_NONE; manage_notifications(WARN_MOVETYPE); } if(!self.takedamage) { self.takedamage = DAMAGE_NO; self.health = 0; manage_notifications(WARN_TAKEDAMAGE); } if(!self.health) { if(self.takedamage) { ThrowException(ERR_NOHEALTH_DMG); return; } } else { self.max_health = self.health; if(!self.takedamage) { manage_notifications(WARN_HEALTH_NODMG); } } IModelInstance(); //no point in checking for an error before continuing it will be caught in self }; // Gypsy - instantiate a field | build 20130523-21:06cn entity() IFieldInstance = { //INTERFACE InstanceRules("IFieldInstance",1); //ERR_INTERFACEONLY if(self.urgent > 0) return entity_null; //error local entity field; field = spawn(); field.movetype = MOVETYPE_NONE; field.solid = SOLID_TRIGGER; field.owner = self; //set owner to the parentest entity of this call field.type = OBJ_FIELD; //set a friendly classname field.spawnflags = ( self.spawnflags & 256 ); //player only - force inheritance so movebrush_activate() doesn't have a zillion conditions local vector u, v; //calculate final dimensions u = (self.absmin-self.fieldspread); v = self.absmax+self.fieldspread; if(!u && !v) //check final dimensions { ThrowException(ERR_NODIMS); return entity_null; } setsize (field, u, v); //apply dimensions u = make_multiplier(field_ofs, .5); setorigin(field, self.origin + self.field_ofs * fabs(u * field.size)); return field; }; // Gypsy - attach physics properties to a model | build 20130515-14:02cn void() ITypedPhysicsInstance = { //INTERFACE InstanceRules("ITypedPhysicsInstance",1); //ERR_INTERFACEONLY if(self.urgent > 0) return; //error /* if any of the below is a required field: check it in self and handle before calling ITypedInstance */ }; // Gypsy - create an interactive brush | build 20130527-22:12cn void() IMovebrushInstance = { //INTERFACE InstanceRules("IMovebrushInstance",1); //ERR_INTERFACEONLY if(self.urgent > 0) return; //error self.solid = SOLID_BSP; //set necessary types self.movetype = MOVETYPE_PUSH; //fallback on defaults if (!self.speed) self.speed = 100; if (!self.wait) self.wait = 3; if (!self.dmg) self.dmg = 2; //consider health a determinant of whether the entity can be damaged if(self.health) self.takedamage = DAMAGE_YES; else self.takedamage = DAMAGE_NO; ITypedInstance(); //make the brush a model if(self.urgent > 0) return; //check for creamy goodness if( self.spawnflags & 8 ) //MB_FIELD { //if the trigger is not the brush - size, spawn and make active a trigger field if( !check_vector(self.fieldspread) ) self.fieldspread = '40 40 4'; //default self.trigger_field = IFieldInstance(); //create and store entity } }; // Gypsy - attach an ambient sound to a model | build 20130514-00:29cn void() IAmbientSoundInstance = { //INTERFACE InstanceRules("IAmbientSoundInstance",1); //ERR_INTERFACEONLY if(self.urgent > 0) return; //error /* if soundpath is a required field: check it in self and handle before calling IAmbientSoundInstance */ if( check_string(self.soundpath) ) { if( !self.vol ) { self.vol = 0.5; manage_notifications(WARN_VOLUME); } if( !self.attn ) { self.attn = ATTN_NONE; manage_notifications(WARN_ATTENUATION); } precache_sound (self.soundpath); ambientsound (self.origin, self.soundpath, self.vol, self.attn); } else { manage_notifications(WARN_SOUNDPATH); } }; //spawn_*_effect can act as an interface model but is also a standalone entity void() spawn_point_effect; void() spawn_area_effect; void(entity area) spawn_effect_particle;
Then you can utilize that in this way
notice how spawn_custom calls ITypedInstance which calls IModelInstance. This is the flow for creating any and all entities.Code:// Gypsy - custom entity | build 20130513-16:53cn void() spawn_custom = { //FINAL InstanceRules("spawn_custom",0); //ERR_NOSUBCLASS if(self.urgent > 0) return; //error if(self.gnomename) self.classname = self.gnomename; //changing a classname in a map messes up that entity - fake it else manage_notifications(WARN_GNOMENAME); ITypedInstance(); //abstract.qc - create the appropriate model if(self.urgent > 0) return; //error if( check_vector(self.view_ofs) ) manage_notifications(WARN_VIEW_OFS); if( check_string(self.animator) ) { //animator } else manage_notifications(WARN_ANIMATOR); spawn_point_effect(); //this qc - add an effect from effectinfo.txt //won't throw an error here ThrowNotifications(); //output.qc - print all notifications to the console };
In the map you would use a spawn custom and fill in all the entity properties in the entity inspector. Those properties get cycled through the chain I just described and whatever your entity was "propertied" to be is what is spit out. However, some properties are done for you, like figuring out the solid and movetype of things.
of course you can't really rewrite the qc (in my way) without completely redifining the maps entities.ent file. Here is an example of the spawn_custom ent
Note when I say the above chain is the method for spawning any and all entities I don't mean that you have to use a spawn_custom. I mean that you would call ITypedInstance in your version of "spawn_custom" (ie your more specific entity) after you have checked that any field your entity requires is assigned. This means you never have to keep retyping the basic code to instantiate and assign move/solid properties to your models. That code is done and all you have to do is call the "kick off" function. My spawn_custom is simply the most wide open model that can be made. It is nothing and everything...depending on the properties you select.Code:<point name="spawn_custom" color="0 0 0.5" box="-4 -4 -4 4 4 4"> Fields <string key="target" name="target">target</string> <string key="targetname" name="targetname">targetname</string> <string key="grouptname" name="targetname">targetname</string> <string key="effectname" name="effectname">effectname</string> <string key="model" name="model">The path to your model</string> <integer key="health" name="health">Entity health</integer> <integer key="solid" name="solid"> SOLID_NOT = 0 SOLID_TRIGGER = 1 SOLID_BBOX = 2 SOLID_SLIDEBOX = 3 SOLID_BSP = 4 SOLID_CORPSE = 5 - /GQC/dpdefs/dpextensions.qc - line 1377: make sure you read that </integer> <integer key="movetype" name="movetype"> MOVETYPE_NONE = 0 MOVETYPE_ANGLENOCLIP = 1 MOVETYPE_ANGLECLIP = 2 MOVETYPE_WALK = 3 MOVETYPE_STEP = 4 MOVETYPE_FLY = 5 MOVETYPE_TOSS = 6 MOVETYPE_PUSH = 7 MOVETYPE_NOCLIP = 8 MOVETYPE_FLYMISSILE = 9 MOVETYPE_BOUNCE = 10 MOVETYPE_BOUNCEMISSILE = 11 MOVETYPE_FOLLOW = 12 - /GQC/dpdefs/dpextensions.qc - line 552: make sure you read that MOVETYPE_PHYSICS = 32 </integer> <integer key="takedamage" name="takedamage"> DAMAGE_NO = 0 DAMAGE_YES = 1 DAMAGE_AIM = 2 </integer> <real3 key="neg_pnt" name="mins">bounding box extents reletive to origin</real3> <real3 key="pos_pnt" name="maxs">bounding box extents reletive to origin</real3> <real3 key="view_ofs" name="view_ofs">view offset from origin</real3> <string key="animator" name="animator">animator</string> <string key="physics" name="physics"></string> <real3 key="origin" name="origin"></real3> </point>
Last edited by MadGypsy; 04-29-2015, 06:58 PM.
Leave a comment:

Leave a comment: