- SDL will FAIL unless you unset LANG.
- You can run this in Xephyr!! pfexec Xephyr :1.0 & xauth extract - $DISPLAY | DISPLAY=:1.0 xauth merge - DISPLAY=:1.0 ./ioquake3.i386
- This works RELIABLY
- You may have to run ioquake a few times to make it go all the way through.
- See build/run.sh !
Name | Location | Purpose |
---|---|---|
main | sys/sys_main.c | main() of program, both client and server. |
SV_Frame | server/sv_main.c | Primary simulation loop |
SV_BotFrame | server/sv_bot.c | Invoke Bot AI |
BotAIStartFrame | game/g_main.c | Invokes (vm) call to SV_ClientThink, after maintaining |
internal bot-related data structures. Seems relevant | ||
to how bot input’s fed into clients. | ||
SV_ClientThink | server/sv_client.c | Seems to take input commands and simulate a client. |
ClientThink | game/g_client.c | Actually runs the ‘pmove’ set of commands (after a validation |
check, passing ctrl to ClientThink_real) |
Enum | File | Actual Routine Called |
---|---|---|
BOTAI_START_FRAME | game/g_main.c | BotAIStartFrame |
BOTLIB_USER_COMMAND | server/sv_game.c | SV_ClientThink |
GAME_CLIENT_THINK | game/g_main.c | ClientThink |
GAME_CLIENT_COMMAND | game/g_main.c | ClientCommand |
GAME_RUN_FRAME | game/g_main.c | G_RunFrame |
Name | Location | Purpose |
---|---|---|
playerState_t | q_shared.h | State of each player, as received |
over network | ||
entityState_t | q_shared.h | State of any entity, as received |
over network. playerState_t is a | ||
proper superset. |
- Looks like the renderer is pluggable. RE_RenderScene and trap_R_RenderScene().
- ‘tr’ in tr_main.c seems to maintain the draw graph. NO, it’s the vtable.
- “RE” is the “refresh” module. tr_public.h:30
Scans a pair of entity lists (well, really offsets into the same list) , and encodes a delta-set into a ‘msg_t’. Handles entity +/- deltas between lists.
Encodes header-data and the player state, and then calls SV_EmitPacketEntities
Builds a list of entities visible from a given position
sv_snapshot.c:446 Driver. Builds a snapshot by calling SV_AddEntitiesVisibleFromPoint, sorting them by entity number. Finally, updates svs.snapshotEntities[next % num] to be the same entityState_t (value) as svs.gentities[i].
Iterates over all clients, and either:
- sends the next fragment, if there is one for that client
- calls SV_BuildClientSnapshot
Clearly the latter can create fragments.
see cg_snapshot.c
- Note: L245 in cg_snapshot.c: CG_BuildSolidList(). Does this thing store them only as renderables client-side?. There has to be some simulation going on. Nah, just maintains a count of solid vs “trigger” entities.
- cl_main.c:CL_PacketEvent
- cl_parse.c:CL_ParseServerMessage
- cl_parse.c:CL_ParseSnapshot
- Maybe the easiest way is to just have my own routine feed in?
- Some sort of configuration management is needed here.
- Console variables! – NO. They have various archive/replication policies which could really mess things up.
- Added new api under ls_variables.h
Variable Purpose To-enable lsp_simulate Run load simulator lsp_simulate=1 lsp_headless Disable graphics display lsp_headless=1 - [X] Setup a new file, header, and set of flag facilities.
- Can I just get SV_ClientThink called on the actual player’s
commands?
- There are flag-checks in ClientThink (invoked via trap to GAME_CLIENT_THINK). Make sure to take those out/reroute/etc.
- SV_Main (conditional for simulation) invokes a botlib command
that generates commands for client nr 0 (the actual one). Looks
like right now, it’s 1+.
- Umm, SV_Main() is server side. Client 0 isn’t special, it’s just the human in the game, and there’s always at least one (to start the game!). Server-side botlib commands can be studied, but should probably stay in as normal.
which may be sent directly over to the other side. This may be a decent place for a port of the simulator.
- Actually it’s all taken care of in CL_ClientCmd, et al.
- They are created, from the looks of it, to fill a room that has too few other players. Looks like from G_BotConnect
- G_BotConnect (game/g_bot.c:538) is called from ClientConnect (g_client.c:~915), the primary routine invoked upon client login.
- Looks like ClientConnect could be called with a flag saying “I’m
a bot”. Invoked from vm(GAME_CLIENT_CONNECT). 3 Callers to
that VM:
- sv_init.c:538 Change server to new map - SV_SpawnServer - takes all clients with it.
- sv_ccmds.c:319 Map restart. SV_MapRestart_f
- sv_client.c:526 SV_DirectConnect - looks like the initial connection (server side). Question is, how’s this work client-side? Can I just rewire that bit and make it look like a real player to the normal world?
- Must find primary login path for a normal client.
- Then figure out where to get botlib running instead.
- (cl_main.c) CL_Connect_f() sets up a connection state, which is then picked up by CL_CheckForResend
- Primary packet input processing (client-side, at least) is CL_ParseServerMessage.
- Server-side packet input processing is SV_ExecuteClientMessage
- SV_PacketEvent->SV_ExecuteClientMessage->SV_ClientCommand ->SV_ExecuteClientCommand
- Before the “netchan” can be set up, a
“connect/challenge/connectResponse” cycle is executed.
- CL_ConnectionLessPacket
- SV_DirectConnect
- How do I bind the client to botlib?
- Likely, the bots are designed for use only on the server.
- [ ] Instead, check out the line “CL_SendCmd()” in
cl_main.c/CL_Frame().
- If that was a botlib call instead, we may have a reasonable simulator on our hands.
- [ ] G_BotConnect (int clientnum, qboolean restart)
- clientnum (set to 0 should work)
- restart = false
- it’s for looking up existing bot data that was saved.
- [ ] We can modify CL_SendCmd() to send in botlib commands instead (or added to) the local user.
- [ ] How do bots sense their environment?
- bot_entitystate_t is the internal state of a bot (origin, angles, type, flags, model, weapon). Doesn’t seem to contain any health points, etc.
VTable | Purpose |
---|---|
botlib_import_t | exported functions to botlib |
aas_export_t | provided by aas_*.[ch] |
ea_export_t | exported by be_ea.c |
ai_export_t | exported by be_ai_*.[ch] |
- Acronyms (!!)
- AAS - Area Awareness System
- EA - Elementary Action
- I maybe able to run an independent copy of botlib on the client
side, with a few mods.. First, call GetBotLibAPI(), with my own
set of ‘import’ functions (a vtable that’s passed in).
- YES, start with copying SV_BotInitBotLib(), and modifying it as needed to setup a client-side botlib instance.
- NOTE: all the imports passed in by SV_BotInitBotLib() are server-side. We’ll have to construct client-side equivalents where they can’t be directly ported over.
- playerState_t holds damage
- Where are the client-side entities held? Can I find a routine to
scan an area for me?
- Scan the renderer.
- The pointers in ls_core now hold references to all that now.
Overall, wire up a dumb “move-random” dummy client, then integrate botlib.
- [X] Modify CL_SendCmd. It’s easier.
- [X] A command-line switch, lsp_simulate (already wired into argc/argv, but I need to check it) wired into LS_Enabled. It should just flip s_enabled. Read in at first-run of LS_Enabled now.
One of the key integration routines. It links, most relevantly, to SV_Trace(), which goes to SV_Trace_r(), which then goes to scan sv_worldSectors[], a bsp tree of the world.
- How do I go about scanning the client-side view of the world?
- Find it in the renderer
- No, that’s too poly-based. Let’s try client snapshot reading
instead. There’s playerState_t, a superset of entityState_t.
- Well, let’s consider this in terms of the API SV_Trace()
actually needs.
- What does SV_Trace() use? – it’s maintaining an internal BSP of all the entities in the world. I won’t be doing that, but then again, it’s to save server-side CPU. I can waste it on the client-side happily with little ill effect.
- What can I use instead of the server-side BSP?
- All I have is what the client receives. I can scan that, I suppose.
- Looking at the interface for BotImport_Trace(), the result
is stored in a parameter: bsp_trace_t *bsptrace, which is a
structure in botlib.h:
= typedef struct bsp_trace_s = { = qboolean allsolid; // if true, plane is not valid = qboolean startsolid; // if true, the initial point was in a solid area = float fraction; // time completed, 1.0 = didn’t hit anything = vec3_t endpos; // final position = cplane_t plane; // surface normal at impact = float exp_dist; // expanded plane distance = int sidenum; // number of the brush side hit = bsp_surface_t surface; // the hit point surface = int contents; // contents on other side of surface hit = int ent; // number of entity hit = } bsp_trace_t;
- Note the last element `ent`, which corresponds to an
entityState_t.
- I could sort all entities across the vector, and scan that ways:
nlogn * compare_time<entity>().
- What’s the current representational shape of an entity? And
that of the box being slid across the vector?
Type Sliding Box Entity AABB YUP OOBB
- What’s the current representational shape of an entity? And
that of the box being slid across the vector?
- I could sort all entities across the vector, and scan that ways:
nlogn * compare_time<entity>().
CM_Trace usage: Called as:
Arg Got? Calculate &trace start_l end_l symetricSize[0] symetricSize[1] model origin brushmask capsule Called from CM_TransformedBoxTrace(
Arg Got? Calculate &trace (float *) clip->start (float *) clip->end (float *) clip->mins (float *) clip->maxs clipHandle clip->contentmask origin angles clip->capsule ) Called from SV_ClipMoveToEntities(moveclip_t)
Called from SV_Trace
Arg Origin contentmask arg start arg end arg mins arg maxs arg passEntityNum arg capsule qfalse - Note the last element `ent`, which corresponds to an
entityState_t.
“trace a bbox against a specific entity”
- Just a wrapper around SV_ClipToEntity
Calls SV_GentityNum
- Just returns an address to a sharedEntity_t. A one-liner address-of from an array.
Calls SV_ClipHandleForEntity
- two if()s and a final branch. Each calling one of
[CM_InlineModel, CM_TempBoxModel] Calls CM_TransformedBoxTrace
Calls SV_PointContents(point, -1)
Calls SV_inPVS(p1,p2)
Surprisingly easy. Just shove a usercmd_t into the client state, and it’ll get shoved across when it’s time.
A priority, as Xepher’s unhappy with DISPLAY=:2.0 Initial questions
- At least some part is in R_Init() in renderer/tr_init.c
- Additionally, it looks like it’s hooked in via RE_BeginRegistration(), which is an implementation of a vtable setup by GetRefAPI(). GetRefAPI()’s getting called from:
- Uses for glVertex:
File Use tr_surface.c RB_SurfaceBeam, RB_SurfaceAxis tr_init.c glxInfo_f tr_backend.c RE_StretchRaw, RB_ShowImages tr_shadows.c R_RenderShadowEdges, RB_ShadowFinish tr_sky.c tr_shade.c tr_main.c - Skip it. It’s all in the callbacks coming from tr_init.
- All calls to it are via the GetRefAPI() wrapper. It’s called
from cl_main.c:2879. I can easily make that point somewhere
else. Then it’s a matter of stubbing out the APIs.
Note that I could just copy the entire subsystem and stub-out 99% of the code.
API E. Diff. Status Notes Shutdown - BeginRegistration RegisterModel ? Returns a qhandle_t RegisterSkin ? ” RegisterShader ? ” RegisterShaderNoMip ? ” LoadWorld - SetWorldVisData - EndRegistration ClearScene AddRefEntityToScene AddPolyToScene LightForPoint AddLightToScene AddAdditiveLightToScene RenderScene SetColor DrawStretchPic DrawStretchRaw UploadCinematic BeginFrame EndFrame MarkFragments LerpTag ModelBounds RegisterFont RemapShader GetEntityToken inPVS TakeVideoFrame
- Bug 1: r_fullscreen isn’t setup. I need to setup some console variables, it seems.
- Holy shit, the fucker’s working. I forgot sound.
- Sound’s up.
TOPLEVEL: These are console commands, how to run from the command line?
- There’s already likely a way
- I just need to expose them simply.
- + signs are separators, just like \n
Comment from common.c:353:
COMMAND LINE FUNCTIONS
- characters seperate the commandLine string into multiple console
command lines.
All of these are valid:
quake3 +set test blah +map test quake3 set test blah+map test quake3 set test blah + map test
Sometimes the client won’t connect unless it has the right port # for the right instance of quake. This shouldn’t be a problem for big-simulation, as there will only be one instance on the server vhost.
Still, if there’s a problem, the server’s log will show which port it could connect to. Then run the client with a host_ip:host_port command-line
- What’s the connect() string like? cl_main.c:1510: Com_Printf( “usage: connect [-4|-6] server\n”);
- [X] Wrap that fucker in a shell script
- Yup, called build/client.sh Need to add in the option for running botlib, however.
- [X] Stop this nslookup for update.quake3arena.com first
- SUCCESS! The client runs, automatically logs in, and respawns. Holy Fuck.
- use +set name “Foo” to set the client’s name.
- Full example: DISPLAY=:1.0 ./ioquake3.i386 lsp_headless=1 lsp_simulator=1 +connect 24.103.248.74:27961 +set name “Simulator1” & (note that DISPLAY=:1.0 isn’t actually used at all, but it’s safer in case I screw something else up)
First guess at the right routine: SV_Startup sv_init.c:268
- Called only by: SV_SpawnServer sv_init.c:404
- Called in: sv_ccmds.c:204,268.
Command | Function | Desc |
---|---|---|
heartbeat | SV_Heartbeat_f | |
kick | SV_Kick_f | |
banUser | SV_Ban_f | |
banClient | SV_BanNum_f | |
clientkick | SV_Kick_f | |
status | SV_Status_f | |
serverinfo | ||
systeminfo | ||
dumpuser | ||
map_restart | ||
sectorlist | ||
map | ||
killserver | ||
say | ||
rehashbans | ||
listbans | ||
banaddr | ||
exceptaddr | ||
bandel | ||
exceptdel | ||
flushbans |
- Odd, doesn’t the server have a dedicated server mode? I’m a moron. ./ioq3ded.i386 +map q3dm1
- Check out G_AddBot g_bot.c:564, copy its contents, alter as needed.
- I (think I) really just need to add a properly-initialized value to g_entities (g_main.c:38).
See SV_BotFrame(), called by SV_Frame() in sv_main.c SV_BotFrame() just calls VM_CALL(.., BOTAI_START_FRAME, ..); -> ai_main.c:1379 - BotAIStartFrame(int time) BotAIStartFrame() is the PRIMARY routine for bot computation.
- in CL_Init(), call VM_Call( QUESTION, CLIENT_SETAI, QUESTION)
- Make the VM call in CL_Frame if I’m simulating
- Translate the resulting botlib commands back to usercmd_ts and transmit them as our own.
VM Name | Var | Desc |
---|---|---|
uivm | vm_ui | |
cgvm | vm_cgame | |
gvm | vm_game |
The system one. Can I access it from the client?
- Look at initialization
- Currently, no. I may be able to later. WARNING I may have to implement/initialize some traps for botlib to call back into the C runtime (as it’s not otherwise used on the client side).
- See SV_GameSystemCalls sv_game.c:303 Write a new routine for system calls, copy-paste the botlib handlers (and standard-looking ones). assert(0) on the rest.
SV_BotGetSnapshotEntity WTF is it doing? Two args (client, sequence) Final eqn: svs.snapshotEntities[ (frame->first_entity+sequence) % svs.numSnapshotEntities ].number snapshotEntities is an array of entityState_t. To figure this out, I’ll have to understand how snapshotting works, exactly.
AH. For the given client ‘client’, it returns the nth entity sent to it, in the order the entity snapshots were sent.
Note that it returns ‘sequence+1’ BotAI() -> BotDeathmatchAI() -> BotCheckSnapshot() -> BotAI_GetSnapshotEntity() ->
- [X] Can I get away with just one valid value for ‘client’ ?
- So far, I’m only seeing this method called from a child of ‘BotDeathmatchAI()’, which we probably won’t use. WRONG, it’s called from BotAI, which sounds pretty important. Called for every bot. We only have one, so can we get away with this?
- YES. It’s only caring about its own setting.
- [ ] Do I need more information than what the client already has?
- Well, ‘g_entities’ has a entityShared_t, which we don’t get on the client. And that’s used quite a bit in botlib.
- Does it NEED it?
SV_BotGetConsoleMessage
SV_CLientThink
- Some are vm-side. Thankfully I wrote down the procedure
somewhere for this. On paper, I believe. I’ll have to add a
new ENUM for it.
- Existing path:
VM ->ConsoleCommand() -> Svcmd_AddBot_f() -> G_AddBot().
- Our path: the same one used for command-line arguments That’s setup in CL_Init(), which we’re already modifying pretty heavily for setting up our normal state.
- Consider making a new vm’d .c file for this stuff. Look at the visibility of data structures, etc. for this.
- [ ] Call the VM function, just like SV_BotFrame() did.
- See CL_Frame() for where to do it.
- [X] Figure out where the return value from the botlib init
routine is used. My guess is it’s fed into the/a vm somehow?
- [ ] Convert the resulting botlib commands back into usercmd_ts.
botlib woudl probably require compiling the whole game into the binary. Which is likely painful (and kills your ability to use the manifest on ec2). Instead, put together a console var (or find one(!)) that has the curent player’s location (or all of them, in an array), so I can print them. Then print key location sin a level, and make them a hard-codd table.
ALREADY THERE: `viewpos` in the console
I got a little help from Adam Leventhal (creator of dtrace(1)) on this one.
HOWTO: native-side, regular dtrace probes. vm-side, traps from g_public’s gameImport_t.
See status.org for the real status of these:
- Number of objects under simulation
- Total count
- Number of logged-in players
- Core loop time
- Per-player simulation time
- Per-(nonplayer) object simulation time
- Projectiles
- Per-player synchronization time
- Total memory used
(this is based on observing the sizes reported from top(1)).
- Total bandwidth
- In
- Out
- Counters for each
I’m modifying the VM environment with new traps (at the end, so it won’t break backwards compat), but I’ll have to build new .pk3 files to handle my changes.
I can do a git status-compare against the original checkin to see what I’ve changed.
- g_public.h:64 - Max clients must be <= 32 for ‘singleClient’
- I’ve seen other uses of a maxclient=32, such as using a normal uint32_t for a bitmask for clients (snapshotting, I believe).