Repository: id-Software/Quake-III-Arena Branch: master Commit: dbe4ddb10315 Files: 1370 Total size: 18.4 MB Directory structure: gitextract_s_acjfki/ ├── COPYING.txt ├── README.txt ├── code/ │ ├── Construct │ ├── Makefile │ ├── botlib/ │ │ ├── aasfile.h │ │ ├── be_aas_bsp.h │ │ ├── be_aas_bspq3.c │ │ ├── be_aas_cluster.c │ │ ├── be_aas_cluster.h │ │ ├── be_aas_debug.c │ │ ├── be_aas_debug.h │ │ ├── be_aas_def.h │ │ ├── be_aas_entity.c │ │ ├── be_aas_entity.h │ │ ├── be_aas_file.c │ │ ├── be_aas_file.h │ │ ├── be_aas_funcs.h │ │ ├── be_aas_main.c │ │ ├── be_aas_main.h │ │ ├── be_aas_move.c │ │ ├── be_aas_move.h │ │ ├── be_aas_optimize.c │ │ ├── be_aas_optimize.h │ │ ├── be_aas_reach.c │ │ ├── be_aas_reach.h │ │ ├── be_aas_route.c │ │ ├── be_aas_route.h │ │ ├── be_aas_routealt.c │ │ ├── be_aas_routealt.h │ │ ├── be_aas_sample.c │ │ ├── be_aas_sample.h │ │ ├── be_ai_char.c │ │ ├── be_ai_chat.c │ │ ├── be_ai_gen.c │ │ ├── be_ai_goal.c │ │ ├── be_ai_move.c │ │ ├── be_ai_weap.c │ │ ├── be_ai_weight.c │ │ ├── be_ai_weight.h │ │ ├── be_ea.c │ │ ├── be_interface.c │ │ ├── be_interface.h │ │ ├── botlib.vcproj │ │ ├── l_crc.c │ │ ├── l_crc.h │ │ ├── l_libvar.c │ │ ├── l_libvar.h │ │ ├── l_log.c │ │ ├── l_log.h │ │ ├── l_memory.c │ │ ├── l_memory.h │ │ ├── l_precomp.c │ │ ├── l_precomp.h │ │ ├── l_script.c │ │ ├── l_script.h │ │ ├── l_struct.c │ │ ├── l_struct.h │ │ ├── l_utils.h │ │ ├── lcc.mak │ │ └── linux-i386.mak │ ├── bspc/ │ │ ├── Conscript │ │ ├── Makefile │ │ ├── _files.c │ │ ├── aas_areamerging.c │ │ ├── aas_areamerging.h │ │ ├── aas_cfg.c │ │ ├── aas_cfg.h │ │ ├── aas_create.c │ │ ├── aas_create.h │ │ ├── aas_edgemelting.c │ │ ├── aas_edgemelting.h │ │ ├── aas_facemerging.c │ │ ├── aas_facemerging.h │ │ ├── aas_file.c │ │ ├── aas_file.h │ │ ├── aas_gsubdiv.c │ │ ├── aas_gsubdiv.h │ │ ├── aas_map.c │ │ ├── aas_map.h │ │ ├── aas_prunenodes.c │ │ ├── aas_prunenodes.h │ │ ├── aas_store.c │ │ ├── aas_store.h │ │ ├── aasfile.h │ │ ├── be_aas_bspc.c │ │ ├── be_aas_bspc.h │ │ ├── brushbsp.c │ │ ├── bspc.c │ │ ├── bspc.sln │ │ ├── bspc.vcproj │ │ ├── cfgq3.c │ │ ├── csg.c │ │ ├── faces.c │ │ ├── gldraw.c │ │ ├── glfile.c │ │ ├── l_bsp_ent.c │ │ ├── l_bsp_ent.h │ │ ├── l_bsp_hl.c │ │ ├── l_bsp_hl.h │ │ ├── l_bsp_q1.c │ │ ├── l_bsp_q1.h │ │ ├── l_bsp_q2.c │ │ ├── l_bsp_q2.h │ │ ├── l_bsp_q3.c │ │ ├── l_bsp_q3.h │ │ ├── l_bsp_sin.c │ │ ├── l_bsp_sin.h │ │ ├── l_cmd.c │ │ ├── l_cmd.h │ │ ├── l_log.c │ │ ├── l_log.h │ │ ├── l_math.c │ │ ├── l_math.h │ │ ├── l_mem.c │ │ ├── l_mem.h │ │ ├── l_poly.c │ │ ├── l_poly.h │ │ ├── l_qfiles.c │ │ ├── l_qfiles.h │ │ ├── l_threads.c │ │ ├── l_threads.h │ │ ├── l_utils.c │ │ ├── l_utils.h │ │ ├── lcc.mak │ │ ├── leakfile.c │ │ ├── linux-i386.mak │ │ ├── map.c │ │ ├── map_hl.c │ │ ├── map_q1.c │ │ ├── map_q2.c │ │ ├── map_q3.c │ │ ├── map_sin.c │ │ ├── nodraw.c │ │ ├── portals.c │ │ ├── prtfile.c │ │ ├── q2files.h │ │ ├── q3files.h │ │ ├── qbsp.h │ │ ├── qfiles.h │ │ ├── sinfiles.h │ │ ├── tetrahedron.c │ │ ├── tetrahedron.h │ │ ├── textures.c │ │ ├── tree.c │ │ └── writebsp.c │ ├── cgame/ │ │ ├── Conscript │ │ ├── cg_consolecmds.c │ │ ├── cg_draw.c │ │ ├── cg_drawtools.c │ │ ├── cg_effects.c │ │ ├── cg_ents.c │ │ ├── cg_event.c │ │ ├── cg_info.c │ │ ├── cg_local.h │ │ ├── cg_localents.c │ │ ├── cg_main.c │ │ ├── cg_marks.c │ │ ├── cg_newdraw.c │ │ ├── cg_particles.c │ │ ├── cg_players.c │ │ ├── cg_playerstate.c │ │ ├── cg_predict.c │ │ ├── cg_public.h │ │ ├── cg_scoreboard.c │ │ ├── cg_servercmds.c │ │ ├── cg_snapshot.c │ │ ├── cg_syscalls.asm │ │ ├── cg_syscalls.c │ │ ├── cg_view.c │ │ ├── cg_weapons.c │ │ ├── cgame.bat │ │ ├── cgame.def │ │ ├── cgame.plg │ │ ├── cgame.q3asm │ │ ├── cgame.sh │ │ ├── cgame.vcproj │ │ ├── cgame_ta.bat │ │ ├── cgame_ta.q3asm │ │ ├── cgame_ta.sh │ │ └── tr_types.h │ ├── cgame.lnt │ ├── clean.bat │ ├── client/ │ │ ├── cl_cgame.c │ │ ├── cl_cin.c │ │ ├── cl_console.c │ │ ├── cl_input.c │ │ ├── cl_keys.c │ │ ├── cl_main.c │ │ ├── cl_net_chan.c │ │ ├── cl_parse.c │ │ ├── cl_scrn.c │ │ ├── cl_ui.c │ │ ├── client.h │ │ ├── keys.h │ │ ├── snd_adpcm.c │ │ ├── snd_dma.c │ │ ├── snd_local.h │ │ ├── snd_mem.c │ │ ├── snd_mix.c │ │ ├── snd_public.h │ │ └── snd_wavelet.c │ ├── game/ │ │ ├── Conscript │ │ ├── ai_chat.c │ │ ├── ai_chat.h │ │ ├── ai_cmd.c │ │ ├── ai_cmd.h │ │ ├── ai_dmnet.c │ │ ├── ai_dmnet.h │ │ ├── ai_dmq3.c │ │ ├── ai_dmq3.h │ │ ├── ai_main.c │ │ ├── ai_main.h │ │ ├── ai_team.c │ │ ├── ai_team.h │ │ ├── ai_vcmd.c │ │ ├── ai_vcmd.h │ │ ├── be_aas.h │ │ ├── be_ai_char.h │ │ ├── be_ai_chat.h │ │ ├── be_ai_gen.h │ │ ├── be_ai_goal.h │ │ ├── be_ai_move.h │ │ ├── be_ai_weap.h │ │ ├── be_ea.h │ │ ├── bg_lib.c │ │ ├── bg_lib.h │ │ ├── bg_local.h │ │ ├── bg_misc.c │ │ ├── bg_pmove.c │ │ ├── bg_public.h │ │ ├── bg_slidemove.c │ │ ├── botlib.h │ │ ├── chars.h │ │ ├── g_active.c │ │ ├── g_arenas.c │ │ ├── g_bot.c │ │ ├── g_client.c │ │ ├── g_cmds.c │ │ ├── g_combat.c │ │ ├── g_items.c │ │ ├── g_local.h │ │ ├── g_main.c │ │ ├── g_mem.c │ │ ├── g_misc.c │ │ ├── g_missile.c │ │ ├── g_mover.c │ │ ├── g_public.h │ │ ├── g_rankings.c │ │ ├── g_rankings.h │ │ ├── g_session.c │ │ ├── g_spawn.c │ │ ├── g_svcmds.c │ │ ├── g_syscalls.asm │ │ ├── g_syscalls.c │ │ ├── g_target.c │ │ ├── g_team.c │ │ ├── g_team.h │ │ ├── g_trigger.c │ │ ├── g_utils.c │ │ ├── g_weapon.c │ │ ├── game.bat │ │ ├── game.def │ │ ├── game.q3asm │ │ ├── game.sh │ │ ├── game.vcproj │ │ ├── game_ta.bat │ │ ├── game_ta.q3asm │ │ ├── game_ta.sh │ │ ├── inv.h │ │ ├── match.h │ │ ├── q_math.c │ │ ├── q_shared.c │ │ ├── q_shared.h │ │ ├── surfaceflags.h │ │ └── syn.h │ ├── game.lnt │ ├── installdebug.bat │ ├── installrelease.bat │ ├── installvms.bat │ ├── jpeg-6/ │ │ ├── README │ │ ├── jcapimin.c │ │ ├── jcapistd.c │ │ ├── jccoefct.c │ │ ├── jccolor.c │ │ ├── jcdctmgr.c │ │ ├── jchuff.c │ │ ├── jchuff.h │ │ ├── jcinit.c │ │ ├── jcmainct.c │ │ ├── jcmarker.c │ │ ├── jcmaster.c │ │ ├── jcomapi.c │ │ ├── jconfig.h │ │ ├── jcparam.c │ │ ├── jcphuff.c │ │ ├── jcprepct.c │ │ ├── jcsample.c │ │ ├── jctrans.c │ │ ├── jdapimin.c │ │ ├── jdapistd.c │ │ ├── jdatadst.c │ │ ├── jdatasrc.c │ │ ├── jdcoefct.c │ │ ├── jdcolor.c │ │ ├── jdct.h │ │ ├── jddctmgr.c │ │ ├── jdhuff.c │ │ ├── jdhuff.h │ │ ├── jdinput.c │ │ ├── jdmainct.c │ │ ├── jdmarker.c │ │ ├── jdmaster.c │ │ ├── jdmerge.c │ │ ├── jdphuff.c │ │ ├── jdpostct.c │ │ ├── jdsample.c │ │ ├── jdtrans.c │ │ ├── jerror.c │ │ ├── jerror.h │ │ ├── jfdctflt.c │ │ ├── jfdctfst.c │ │ ├── jfdctint.c │ │ ├── jidctflt.c │ │ ├── jidctfst.c │ │ ├── jidctint.c │ │ ├── jidctred.c │ │ ├── jinclude.h │ │ ├── jload.c │ │ ├── jmemansi.c │ │ ├── jmemdos.c │ │ ├── jmemmgr.c │ │ ├── jmemname.c │ │ ├── jmemnobs.c │ │ ├── jmemsys.h │ │ ├── jmorecfg.h │ │ ├── jpegint.h │ │ ├── jpeglib.h │ │ ├── jpegtran.c │ │ ├── jquant1.c │ │ ├── jquant2.c │ │ ├── jutils.c │ │ └── jversion.h │ ├── macosx/ │ │ ├── BuildRelease │ │ ├── CGMouseDeltaFix.h │ │ ├── CGMouseDeltaFix.m │ │ ├── CGPrivateAPI.h │ │ ├── GenerateQGL.pl │ │ ├── Performance.rtf │ │ ├── Q3Controller.h │ │ ├── Q3Controller.m │ │ ├── Quake3.icns │ │ ├── Quake3.nib/ │ │ │ ├── classes.nib │ │ │ ├── info.nib │ │ │ └── objects.nib │ │ ├── Quake3.pbproj/ │ │ │ ├── apple.pbxuser │ │ │ └── project.pbxproj │ │ ├── RecordDemo.zsh │ │ ├── botlib.log │ │ ├── macosx_display.h │ │ ├── macosx_display.m │ │ ├── macosx_glimp.h │ │ ├── macosx_glimp.m │ │ ├── macosx_glsmp_mutex.m │ │ ├── macosx_glsmp_null.m │ │ ├── macosx_glsmp_ports.m │ │ ├── macosx_input.m │ │ ├── macosx_local.h │ │ ├── macosx_qgl.h │ │ ├── macosx_sndcore.m │ │ ├── macosx_snddma.m │ │ ├── macosx_sys.m │ │ ├── macosx_timers.h │ │ ├── macosx_timers.m │ │ └── timedemo.zsh │ ├── null/ │ │ ├── mac_net.c │ │ ├── null_client.c │ │ ├── null_glimp.c │ │ ├── null_input.c │ │ ├── null_main.c │ │ ├── null_net.c │ │ └── null_snddma.c │ ├── opts.lnt │ ├── q3_ui/ │ │ ├── Conscript │ │ ├── compile.bat │ │ ├── keycodes.h │ │ ├── q3_ui.bat │ │ ├── q3_ui.q3asm │ │ ├── q3_ui.sh │ │ ├── q3_ui.vcproj │ │ ├── ui.def │ │ ├── ui.q3asm │ │ ├── ui_addbots.c │ │ ├── ui_atoms.c │ │ ├── ui_cdkey.c │ │ ├── ui_cinematics.c │ │ ├── ui_confirm.c │ │ ├── ui_connect.c │ │ ├── ui_controls2.c │ │ ├── ui_credits.c │ │ ├── ui_demo2.c │ │ ├── ui_display.c │ │ ├── ui_gameinfo.c │ │ ├── ui_ingame.c │ │ ├── ui_loadconfig.c │ │ ├── ui_local.h │ │ ├── ui_login.c │ │ ├── ui_main.c │ │ ├── ui_menu.c │ │ ├── ui_mfield.c │ │ ├── ui_mods.c │ │ ├── ui_network.c │ │ ├── ui_options.c │ │ ├── ui_playermodel.c │ │ ├── ui_players.c │ │ ├── ui_playersettings.c │ │ ├── ui_preferences.c │ │ ├── ui_qmenu.c │ │ ├── ui_rankings.c │ │ ├── ui_rankstatus.c │ │ ├── ui_removebots.c │ │ ├── ui_saveconfig.c │ │ ├── ui_serverinfo.c │ │ ├── ui_servers2.c │ │ ├── ui_setup.c │ │ ├── ui_signup.c │ │ ├── ui_sound.c │ │ ├── ui_sparena.c │ │ ├── ui_specifyleague.c │ │ ├── ui_specifyserver.c │ │ ├── ui_splevel.c │ │ ├── ui_sppostgame.c │ │ ├── ui_spreset.c │ │ ├── ui_spskill.c │ │ ├── ui_startserver.c │ │ ├── ui_team.c │ │ ├── ui_teamorders.c │ │ └── ui_video.c │ ├── qcommon/ │ │ ├── cm_load.c │ │ ├── cm_local.h │ │ ├── cm_patch.c │ │ ├── cm_patch.h │ │ ├── cm_polylib.c │ │ ├── cm_polylib.h │ │ ├── cm_public.h │ │ ├── cm_test.c │ │ ├── cm_trace.c │ │ ├── cmd.c │ │ ├── cmd.c.save │ │ ├── common.c │ │ ├── cvar.c │ │ ├── files.c │ │ ├── huffman.c │ │ ├── md4.c │ │ ├── msg.c │ │ ├── net_chan.c │ │ ├── qcommon.h │ │ ├── qfiles.h │ │ ├── unzip.c │ │ ├── unzip.h │ │ ├── vm.c │ │ ├── vm_interpreted.c │ │ ├── vm_local.h │ │ ├── vm_ppc.c │ │ ├── vm_ppc_new.c │ │ └── vm_x86.c │ ├── quake3.sln │ ├── quake3.vcproj │ ├── renderer/ │ │ ├── qgl.h │ │ ├── qgl_linked.h │ │ ├── ref_trin.def │ │ ├── renderer.vcproj │ │ ├── tr_animation.c │ │ ├── tr_backend.c │ │ ├── tr_bsp.c │ │ ├── tr_cmds.c │ │ ├── tr_curve.c │ │ ├── tr_flares.c │ │ ├── tr_font.c │ │ ├── tr_image.c │ │ ├── tr_init.c │ │ ├── tr_light.c │ │ ├── tr_local.h │ │ ├── tr_main.c │ │ ├── tr_marks.c │ │ ├── tr_mesh.c │ │ ├── tr_model.c │ │ ├── tr_noise.c │ │ ├── tr_public.h │ │ ├── tr_scene.c │ │ ├── tr_shade.c │ │ ├── tr_shade_calc.c │ │ ├── tr_shader.c │ │ ├── tr_shadows.c │ │ ├── tr_sky.c │ │ ├── tr_surface.c │ │ └── tr_world.c │ ├── renderer.lnt │ ├── run.bat │ ├── runrelease.bat │ ├── server/ │ │ ├── server.h │ │ ├── sv_bot.c │ │ ├── sv_ccmds.c │ │ ├── sv_client.c │ │ ├── sv_game.c │ │ ├── sv_init.c │ │ ├── sv_main.c │ │ ├── sv_net_chan.c │ │ ├── sv_rankings.c │ │ ├── sv_snapshot.c │ │ └── sv_world.c │ ├── splines/ │ │ ├── Splines.vcproj │ │ ├── math_angles.cpp │ │ ├── math_angles.h │ │ ├── math_matrix.cpp │ │ ├── math_matrix.h │ │ ├── math_quaternion.cpp │ │ ├── math_quaternion.h │ │ ├── math_vector.cpp │ │ ├── math_vector.h │ │ ├── q_parse.cpp │ │ ├── q_shared.cpp │ │ ├── q_shared.h │ │ ├── q_shared.hpp │ │ ├── splines.cpp │ │ ├── splines.h │ │ ├── util_list.h │ │ ├── util_str.cpp │ │ └── util_str.h │ ├── ui/ │ │ ├── Conscript │ │ ├── compile.bat │ │ ├── keycodes.h │ │ ├── ui.bat │ │ ├── ui.def │ │ ├── ui.q3asm │ │ ├── ui.vcproj │ │ ├── ui_atoms.c │ │ ├── ui_gameinfo.c │ │ ├── ui_local.h │ │ ├── ui_main.c │ │ ├── ui_players.c │ │ ├── ui_public.h │ │ ├── ui_shared.c │ │ ├── ui_shared.h │ │ ├── ui_syscalls.asm │ │ ├── ui_syscalls.c │ │ └── ui_util.c │ ├── unix/ │ │ ├── ChangeLog │ │ ├── Cons_gcc.pm │ │ ├── Conscript-client │ │ ├── Conscript-dedicated │ │ ├── Conscript-pk3 │ │ ├── Conscript-sdk │ │ ├── Conscript-setup │ │ ├── LinuxSupport/ │ │ │ ├── CHANGES-1.32.txt │ │ │ ├── INSTALL │ │ │ ├── index.html │ │ │ ├── udp_wide_README.txt │ │ │ └── udp_wide_broadcast.patch │ │ ├── Makefile │ │ ├── Makefile.Game │ │ ├── Quake3.kdelnk │ │ ├── README.EULA │ │ ├── README.Linux │ │ ├── README.Q3Test │ │ ├── build_setup.sh │ │ ├── cons │ │ ├── extract_ver.pl │ │ ├── ftol.nasm │ │ ├── linux_common.c │ │ ├── linux_glimp.c │ │ ├── linux_joystick.c │ │ ├── linux_local.h │ │ ├── linux_qgl.c │ │ ├── linux_signals.c │ │ ├── linux_snd.c │ │ ├── matha.s │ │ ├── pcons-2.3.1 │ │ ├── q3test.spec.sh │ │ ├── qasm.h │ │ ├── quake3.xpm │ │ ├── run-target.sh │ │ ├── snapvector.nasm │ │ ├── snd_mixa.s │ │ ├── sys_dosa.s │ │ ├── unix_glw.h │ │ ├── unix_main.c │ │ ├── unix_net.c │ │ ├── unix_shared.c │ │ ├── vm_x86.c │ │ └── vm_x86a.s │ └── win32/ │ ├── glw_win.h │ ├── mod-sdk-setup/ │ │ ├── GameSource.VCT │ │ └── QIIIA Game Source License.doc │ ├── resource.h │ ├── win_gamma.c │ ├── win_glimp.c │ ├── win_input.c │ ├── win_local.h │ ├── win_main.c │ ├── win_net.c │ ├── win_qgl.c │ ├── win_shared.c │ ├── win_snd.c │ ├── win_syscon.c │ ├── win_wndproc.c │ └── winquake.rc ├── common/ │ ├── aselib.c │ ├── aselib.h │ ├── bspfile.c │ ├── bspfile.h │ ├── cmdlib.c │ ├── cmdlib.h │ ├── imagelib.c │ ├── imagelib.h │ ├── l3dslib.c │ ├── l3dslib.h │ ├── mathlib.c │ ├── mathlib.h │ ├── md4.c │ ├── mutex.c │ ├── mutex.h │ ├── polylib.c │ ├── polylib.h │ ├── polyset.h │ ├── qfiles.h │ ├── scriplib.c │ ├── scriplib.h │ ├── surfaceflags.h │ ├── threads.c │ ├── threads.h │ ├── trilib.c │ └── trilib.h ├── lcc/ │ ├── COPYRIGHT │ ├── LOG │ ├── README │ ├── README.id │ ├── alpha/ │ │ └── osf/ │ │ └── tst/ │ │ ├── 8q.1bk │ │ ├── 8q.2bk │ │ ├── 8q.sbk │ │ ├── array.1bk │ │ ├── array.2bk │ │ ├── array.sbk │ │ ├── cf.1bk │ │ ├── cf.2bk │ │ ├── cf.sbk │ │ ├── cq.1bk │ │ ├── cq.2bk │ │ ├── cq.sbk │ │ ├── cvt.1bk │ │ ├── cvt.2bk │ │ ├── cvt.sbk │ │ ├── fields.1bk │ │ ├── fields.2bk │ │ ├── fields.sbk │ │ ├── front.2bk │ │ ├── front.sbk │ │ ├── incr.1bk │ │ ├── incr.2bk │ │ ├── incr.sbk │ │ ├── init.1bk │ │ ├── init.2bk │ │ ├── init.sbk │ │ ├── limits.1bk │ │ ├── limits.2bk │ │ ├── limits.sbk │ │ ├── paranoia.1bk │ │ ├── paranoia.2bk │ │ ├── paranoia.sbk │ │ ├── sort.1bk │ │ ├── sort.2bk │ │ ├── sort.sbk │ │ ├── spill.1bk │ │ ├── spill.2bk │ │ ├── spill.sbk │ │ ├── stdarg.1bk │ │ ├── stdarg.2bk │ │ ├── stdarg.sbk │ │ ├── struct.1bk │ │ ├── struct.2bk │ │ ├── struct.sbk │ │ ├── switch.1bk │ │ ├── switch.2bk │ │ ├── switch.sbk │ │ ├── wf1.1bk │ │ ├── wf1.2bk │ │ ├── wf1.sbk │ │ ├── yacc.1bk │ │ ├── yacc.2bk │ │ └── yacc.sbk │ ├── buildnt.bat │ ├── buildnt.sh │ ├── cpp/ │ │ ├── cpp.c │ │ ├── cpp.h │ │ ├── eval.c │ │ ├── getopt.c │ │ ├── hideset.c │ │ ├── include.c │ │ ├── lex.c │ │ ├── macro.c │ │ ├── nlist.c │ │ ├── tokens.c │ │ └── unix.c │ ├── custom.mk │ ├── doc/ │ │ ├── 4.html │ │ ├── bprint.1 │ │ ├── install.html │ │ └── lcc.1 │ ├── etc/ │ │ ├── bprint.c │ │ ├── gcc-solaris.c │ │ ├── irix.c │ │ ├── lcc.c │ │ ├── linux.c │ │ ├── ops.c │ │ ├── osf.c │ │ ├── solaris.c │ │ └── win32.c │ ├── include/ │ │ ├── alpha/ │ │ │ └── osf/ │ │ │ ├── assert.h │ │ │ ├── ctype.h │ │ │ ├── errno.h │ │ │ ├── float.h │ │ │ ├── limits.h │ │ │ ├── locale.h │ │ │ ├── math.h │ │ │ ├── setjmp.h │ │ │ ├── signal.h │ │ │ ├── stdarg.h │ │ │ ├── stddef.h │ │ │ ├── stdio.h │ │ │ ├── stdlib.h │ │ │ ├── string.h │ │ │ └── time.h │ │ ├── mips/ │ │ │ └── irix/ │ │ │ ├── assert.h │ │ │ ├── ctype.h │ │ │ ├── errno.h │ │ │ ├── float.h │ │ │ ├── limits.h │ │ │ ├── locale.h │ │ │ ├── math.h │ │ │ ├── setjmp.h │ │ │ ├── signal.h │ │ │ ├── stdarg.h │ │ │ ├── stddef.h │ │ │ ├── stdio.h │ │ │ ├── stdlib.h │ │ │ ├── string.h │ │ │ └── time.h │ │ ├── sparc/ │ │ │ └── solaris/ │ │ │ ├── assert.h │ │ │ ├── ctype.h │ │ │ ├── errno.h │ │ │ ├── float.h │ │ │ ├── limits.h │ │ │ ├── locale.h │ │ │ ├── math.h │ │ │ ├── setjmp.h │ │ │ ├── signal.h │ │ │ ├── stdarg.h │ │ │ ├── stddef.h │ │ │ ├── stdio.h │ │ │ ├── stdlib.h │ │ │ ├── string.h │ │ │ └── time.h │ │ └── x86/ │ │ └── linux/ │ │ ├── assert.h │ │ ├── float.h │ │ └── stdarg.h │ ├── lburg/ │ │ ├── gram.c │ │ ├── gram.y │ │ ├── lburg.1 │ │ ├── lburg.c │ │ └── lburg.h │ ├── lib/ │ │ ├── assert.c │ │ ├── bbexit.c │ │ └── yynull.c │ ├── makefile │ ├── makefile.nt │ ├── mips/ │ │ └── irix/ │ │ └── tst/ │ │ ├── 8q.1bk │ │ ├── 8q.2bk │ │ ├── 8q.sbk │ │ ├── array.1bk │ │ ├── array.2bk │ │ ├── array.sbk │ │ ├── cf.1bk │ │ ├── cf.2bk │ │ ├── cf.sbk │ │ ├── cq.1bk │ │ ├── cq.2bk │ │ ├── cq.sbk │ │ ├── cvt.1bk │ │ ├── cvt.2bk │ │ ├── cvt.sbk │ │ ├── fields.1bk │ │ ├── fields.2bk │ │ ├── fields.sbk │ │ ├── front.2bk │ │ ├── front.sbk │ │ ├── incr.2bk │ │ ├── incr.sbk │ │ ├── init.1bk │ │ ├── init.2bk │ │ ├── init.sbk │ │ ├── limits.1bk │ │ ├── limits.2bk │ │ ├── limits.sbk │ │ ├── paranoia.1bk │ │ ├── paranoia.2bk │ │ ├── paranoia.sbk │ │ ├── sort.1bk │ │ ├── sort.2bk │ │ ├── sort.sbk │ │ ├── spill.2bk │ │ ├── spill.sbk │ │ ├── stdarg.1bk │ │ ├── stdarg.2bk │ │ ├── stdarg.sbk │ │ ├── struct.1bk │ │ ├── struct.2bk │ │ ├── struct.sbk │ │ ├── switch.1bk │ │ ├── switch.2bk │ │ ├── switch.sbk │ │ ├── wf1.1bk │ │ ├── wf1.2bk │ │ ├── wf1.sbk │ │ ├── yacc.1bk │ │ ├── yacc.2bk │ │ └── yacc.sbk │ ├── msdev/ │ │ ├── rcc.dsp │ │ └── rcc.dsw │ ├── packing.lst │ ├── sparc/ │ │ └── solaris/ │ │ └── tst/ │ │ ├── 8q.1bk │ │ ├── 8q.2bk │ │ ├── 8q.sbk │ │ ├── array.1bk │ │ ├── array.2bk │ │ ├── array.sbk │ │ ├── cf.1bk │ │ ├── cf.2bk │ │ ├── cf.sbk │ │ ├── cq.1bk │ │ ├── cq.2bk │ │ ├── cq.sbk │ │ ├── cvt.1bk │ │ ├── cvt.2bk │ │ ├── cvt.sbk │ │ ├── fields.1bk │ │ ├── fields.2bk │ │ ├── fields.sbk │ │ ├── front.2bk │ │ ├── front.sbk │ │ ├── incr.1bk │ │ ├── incr.2bk │ │ ├── incr.sbk │ │ ├── init.1bk │ │ ├── init.2bk │ │ ├── init.sbk │ │ ├── limits.1bk │ │ ├── limits.2bk │ │ ├── limits.sbk │ │ ├── paranoia.1bk │ │ ├── paranoia.2bk │ │ ├── paranoia.sbk │ │ ├── sort.1bk │ │ ├── sort.2bk │ │ ├── sort.sbk │ │ ├── spill.1bk │ │ ├── spill.2bk │ │ ├── spill.sbk │ │ ├── stdarg.1bk │ │ ├── stdarg.2bk │ │ ├── stdarg.sbk │ │ ├── struct.1bk │ │ ├── struct.2bk │ │ ├── struct.sbk │ │ ├── switch.1bk │ │ ├── switch.2bk │ │ ├── switch.sbk │ │ ├── wf1.1bk │ │ ├── wf1.2bk │ │ ├── wf1.sbk │ │ ├── yacc.1bk │ │ ├── yacc.2bk │ │ └── yacc.sbk │ ├── src/ │ │ ├── 2html.c │ │ ├── alloc.c │ │ ├── alpha.md │ │ ├── asdl.c │ │ ├── bind.c │ │ ├── bytecode.c │ │ ├── c.h │ │ ├── config.h │ │ ├── dag.c │ │ ├── dagcheck.md │ │ ├── decl.c │ │ ├── enode.c │ │ ├── error.c │ │ ├── event.c │ │ ├── expr.c │ │ ├── gen.c │ │ ├── init.c │ │ ├── inits.c │ │ ├── input.c │ │ ├── lex.c │ │ ├── list.c │ │ ├── main.c │ │ ├── mips.md │ │ ├── null.c │ │ ├── output.c │ │ ├── pass2.c │ │ ├── prof.c │ │ ├── profio.c │ │ ├── rcc.asdl │ │ ├── run.sh │ │ ├── simp.c │ │ ├── sparc.md │ │ ├── stab.c │ │ ├── stab.h │ │ ├── stmt.c │ │ ├── string.c │ │ ├── sym.c │ │ ├── symbolic.c │ │ ├── token.h │ │ ├── trace.c │ │ ├── tree.c │ │ ├── types.c │ │ ├── x86.md │ │ └── x86linux.md │ ├── tst/ │ │ ├── 8q.0 │ │ ├── 8q.c │ │ ├── array.0 │ │ ├── array.c │ │ ├── cf.0 │ │ ├── cf.c │ │ ├── cq.0 │ │ ├── cq.c │ │ ├── cvt.0 │ │ ├── cvt.c │ │ ├── fields.0 │ │ ├── fields.c │ │ ├── front.0 │ │ ├── front.c │ │ ├── incr.0 │ │ ├── incr.c │ │ ├── init.0 │ │ ├── init.c │ │ ├── limits.0 │ │ ├── limits.c │ │ ├── paranoia.0 │ │ ├── paranoia.c │ │ ├── sort.0 │ │ ├── sort.c │ │ ├── spill.0 │ │ ├── spill.c │ │ ├── stdarg.0 │ │ ├── stdarg.c │ │ ├── struct.0 │ │ ├── struct.c │ │ ├── switch.0 │ │ ├── switch.c │ │ ├── wf1.0 │ │ ├── wf1.c │ │ ├── yacc.0 │ │ └── yacc.c │ └── x86/ │ ├── linux/ │ │ └── tst/ │ │ ├── 8q.1bk │ │ ├── 8q.2bk │ │ ├── 8q.sbk │ │ ├── array.1bk │ │ ├── array.2bk │ │ ├── array.sbk │ │ ├── cf.1bk │ │ ├── cf.2bk │ │ ├── cf.sbk │ │ ├── cq.1bk │ │ ├── cq.2bk │ │ ├── cq.sbk │ │ ├── cvt.1bk │ │ ├── cvt.2bk │ │ ├── cvt.sbk │ │ ├── fields.1bk │ │ ├── fields.2bk │ │ ├── fields.sbk │ │ ├── front.2bk │ │ ├── front.sbk │ │ ├── incr.1bk │ │ ├── incr.2bk │ │ ├── incr.sbk │ │ ├── init.1bk │ │ ├── init.2bk │ │ ├── init.sbk │ │ ├── limits.1bk │ │ ├── limits.2bk │ │ ├── limits.sbk │ │ ├── paranoia.1bk │ │ ├── paranoia.2bk │ │ ├── paranoia.sbk │ │ ├── sort.1bk │ │ ├── sort.2bk │ │ ├── sort.sbk │ │ ├── spill.1bk │ │ ├── spill.2bk │ │ ├── spill.sbk │ │ ├── stdarg.1bk │ │ ├── stdarg.2bk │ │ ├── stdarg.sbk │ │ ├── struct.1bk │ │ ├── struct.2bk │ │ ├── struct.sbk │ │ ├── switch.1bk │ │ ├── switch.2bk │ │ ├── switch.sbk │ │ ├── wf1.1bk │ │ ├── wf1.2bk │ │ ├── wf1.sbk │ │ ├── yacc.1bk │ │ ├── yacc.2bk │ │ └── yacc.sbk │ └── win32/ │ └── tst/ │ ├── 8q.1bk │ ├── 8q.2bk │ ├── 8q.sbk │ ├── array.1bk │ ├── array.2bk │ ├── array.sbk │ ├── cf.1bk │ ├── cf.2bk │ ├── cf.sbk │ ├── cq.1bk │ ├── cq.2bk │ ├── cq.sbk │ ├── cvt.1bk │ ├── cvt.2bk │ ├── cvt.sbk │ ├── fields.1bk │ ├── fields.2bk │ ├── fields.sbk │ ├── front.2bk │ ├── front.sbk │ ├── incr.1bk │ ├── incr.2bk │ ├── incr.sbk │ ├── init.1bk │ ├── init.2bk │ ├── init.sbk │ ├── limits.1bk │ ├── limits.2bk │ ├── limits.sbk │ ├── paranoia.1bk │ ├── paranoia.2bk │ ├── paranoia.sbk │ ├── sort.1bk │ ├── sort.2bk │ ├── sort.sbk │ ├── spill.1bk │ ├── spill.2bk │ ├── spill.sbk │ ├── stdarg.1bk │ ├── stdarg.2bk │ ├── stdarg.sbk │ ├── struct.1bk │ ├── struct.2bk │ ├── struct.sbk │ ├── switch.1bk │ ├── switch.2bk │ ├── switch.sbk │ ├── wf1.1bk │ ├── wf1.2bk │ ├── wf1.sbk │ ├── yacc.1bk │ ├── yacc.2bk │ └── yacc.sbk ├── libs/ │ ├── cmdlib/ │ │ ├── cmdlib.cpp │ │ └── cmdlib.vcproj │ ├── cmdlib.h │ ├── jpeg6/ │ │ ├── README │ │ ├── jchuff.h │ │ ├── jcomapi.cpp │ │ ├── jconfig.h │ │ ├── jdapimin.cpp │ │ ├── jdapistd.cpp │ │ ├── jdatasrc.cpp │ │ ├── jdcoefct.cpp │ │ ├── jdcolor.cpp │ │ ├── jdct.h │ │ ├── jddctmgr.cpp │ │ ├── jdhuff.cpp │ │ ├── jdhuff.h │ │ ├── jdinput.cpp │ │ ├── jdmainct.cpp │ │ ├── jdmarker.cpp │ │ ├── jdmaster.cpp │ │ ├── jdpostct.cpp │ │ ├── jdsample.cpp │ │ ├── jdtrans.cpp │ │ ├── jerror.cpp │ │ ├── jerror.h │ │ ├── jfdctflt.cpp │ │ ├── jidctflt.cpp │ │ ├── jinclude.h │ │ ├── jmemmgr.cpp │ │ ├── jmemnobs.cpp │ │ ├── jmemsys.h │ │ ├── jmorecfg.h │ │ ├── jpeg6.vcproj │ │ ├── jpegint.h │ │ ├── jpgload.cpp │ │ ├── jutils.cpp │ │ └── jversion.h │ ├── jpeglib.h │ ├── pak/ │ │ ├── pak.vcproj │ │ ├── pakstuff.cpp │ │ ├── unzip.cpp │ │ └── unzip.h │ ├── pakstuff.h │ └── str.h ├── q3asm/ │ ├── Makefile │ ├── README.Id │ ├── cmdlib.c │ ├── cmdlib.h │ ├── lib.txt │ ├── mathlib.h │ ├── notes.txt │ ├── ops.txt │ ├── opstrings.h │ ├── q3asm.c │ ├── q3asm.sln │ ├── q3asm.vcproj │ └── qfiles.h ├── q3map/ │ ├── brush.c │ ├── brush_primit.c │ ├── bsp.c │ ├── facebsp.c │ ├── fog.c │ ├── gldraw.c │ ├── glfile.c │ ├── leakfile.c │ ├── light.c │ ├── light.h │ ├── light_trace.c │ ├── lightmaps.c │ ├── lightv.c │ ├── makefile │ ├── map.c │ ├── mesh.c │ ├── mesh.h │ ├── misc_model.c │ ├── nodraw.c │ ├── patch.c │ ├── portals.c │ ├── prtfile.c │ ├── q3map.sln │ ├── q3map.vcproj │ ├── qbsp.h │ ├── shaders.c │ ├── shaders.h │ ├── soundv.c │ ├── surface.c │ ├── terrain.c │ ├── tjunction.c │ ├── tree.c │ ├── vis.c │ ├── vis.h │ ├── visflow.c │ └── writebsp.c ├── q3radiant/ │ ├── BMP.H │ ├── BRUSH.H │ ├── BSInput.cpp │ ├── BSInput.h │ ├── BSPFILE.H │ ├── Bmp.cpp │ ├── Brush.cpp │ ├── BrushScript.cpp │ ├── CAMERA.H │ ├── CSG.CPP │ ├── CamWnd.cpp │ ├── CamWnd.h │ ├── CapDialog.cpp │ ├── CapDialog.h │ ├── CharBuffer.h │ ├── ChildFrm.cpp │ ├── ChildFrm.h │ ├── CommandsDlg.cpp │ ├── CommandsDlg.h │ ├── DRAG.CPP │ ├── DialogInfo.cpp │ ├── DialogInfo.h │ ├── DialogTextures.cpp │ ├── DialogTextures.h │ ├── DialogThick.cpp │ ├── DialogThick.h │ ├── DlgEvent.cpp │ ├── DlgEvent.h │ ├── ECLASS.CPP │ ├── ENTITY.CPP │ ├── ENTITY.H │ ├── ENTITYW.H │ ├── EPAIRS.H │ ├── EditWnd.cpp │ ├── EditWnd.h │ ├── EntityListDlg.cpp │ ├── EntityListDlg.h │ ├── EpairsWrapper.h │ ├── FNMATCH.CPP │ ├── FNMATCH.H │ ├── FindTextureDlg.cpp │ ├── FindTextureDlg.h │ ├── GLINGR.H │ ├── GLInterface.cpp │ ├── GLW_WIN.H │ ├── GroupBar.cpp │ ├── GroupBar.h │ ├── GroupDlg.cpp │ ├── GroupDlg.h │ ├── IBSPFrontend.h │ ├── IEpairs.cpp │ ├── IEpairs.h │ ├── IMessaging.h │ ├── IPluginEntities.h │ ├── ISelectedFace.h │ ├── IShaders.cpp │ ├── IShaders.h │ ├── LBMLIB.CPP │ ├── LBMLIB.H │ ├── LstToolBar.cpp │ ├── LstToolBar.h │ ├── MAP.CPP │ ├── MAP.H │ ├── MATHLIB.CPP │ ├── MATHLIB.H │ ├── MRU.CPP │ ├── MRU.H │ ├── MainFrm.cpp │ ├── MainFrm.h │ ├── MapInfo.cpp │ ├── MapInfo.h │ ├── Messaging.cpp │ ├── Messaging.h │ ├── NameDlg.cpp │ ├── NameDlg.h │ ├── NewProjDlg.cpp │ ├── NewProjDlg.h │ ├── PARSE.CPP │ ├── PARSE.H │ ├── PMESH.CPP │ ├── PMESH.H │ ├── POINTS.CPP │ ├── PatchDensityDlg.cpp │ ├── PatchDensityDlg.h │ ├── PatchDialog.cpp │ ├── PatchDialog.h │ ├── PlugIn.cpp │ ├── PlugIn.h │ ├── PlugInManager.cpp │ ├── PlugInManager.h │ ├── PluginEntities.cpp │ ├── PrefsDlg.cpp │ ├── PrefsDlg.h │ ├── QE3.CPP │ ├── QE3.H │ ├── QEDEFS.H │ ├── QERTYPES.H │ ├── QFILES.H │ ├── QGL.H │ ├── QGL_WIN.C │ ├── QGL_WIN.CPP │ ├── RADBSP.CPP │ ├── RADEditView.cpp │ ├── RADEditView.h │ ├── RADEditWnd.cpp │ ├── RADEditWnd.h │ ├── RADKEYS.INI │ ├── Radiant.clw │ ├── Radiant.cpp │ ├── Radiant.h │ ├── Radiant.rc │ ├── Radiant.sln │ ├── Radiant.vcproj │ ├── RadiantDoc.cpp │ ├── RadiantDoc.h │ ├── RadiantView.cpp │ ├── RadiantView.h │ ├── RotateDlg.cpp │ ├── RotateDlg.h │ ├── SELECT.CPP │ ├── SELECT.H │ ├── ScaleDialog.cpp │ ├── ScaleDialog.h │ ├── ScriptDlg.cpp │ ├── ScriptDlg.h │ ├── SelectedFace.cpp │ ├── ShaderEdit.cpp │ ├── ShaderEdit.h │ ├── ShaderInfo.cpp │ ├── ShaderInfo.h │ ├── StdAfx.cpp │ ├── StdAfx.h │ ├── SurfaceDlg.cpp │ ├── SurfaceDlg.h │ ├── SurfacePlugin.cpp │ ├── TexEdit.cpp │ ├── TexEdit.h │ ├── TexWnd.cpp │ ├── TexWnd.h │ ├── TextureBar.cpp │ ├── TextureBar.h │ ├── TextureLayout.cpp │ ├── TextureLayout.h │ ├── TextureLoad.cpp │ ├── TextureLoad.h │ ├── Textures.h │ ├── ToolWnd.cpp │ ├── ToolWnd.h │ ├── Undo.cpp │ ├── Undo.h │ ├── VERTSEL.CPP │ ├── VIEW.H │ ├── WIN_CAM.CPP │ ├── WIN_DLG.CPP │ ├── WIN_QE3.CPP │ ├── WIN_QE3.RC2 │ ├── WIN_XY.CPP │ ├── WIN_Z.CPP │ ├── WaveOpen.cpp │ ├── WaveOpen.h │ ├── Win_ent.cpp │ ├── Win_main.cpp │ ├── Winding.cpp │ ├── Winding.h │ ├── XY.H │ ├── XYWnd.cpp │ ├── XYWnd.h │ ├── Z.CPP │ ├── Z.H │ ├── ZView.cpp │ ├── ZView.h │ ├── ZWnd.cpp │ ├── ZWnd.h │ ├── brush_primit.cpp │ ├── cameratargetdlg.cpp │ ├── cameratargetdlg.h │ ├── cbrushstub.cpp │ ├── dlgcamera.cpp │ ├── dlgcamera.h │ ├── igl.h │ ├── iscriplib.h │ ├── isurfaceplugin.h │ ├── qerplugin.h │ ├── res/ │ │ ├── DEFTEX.WAL │ │ └── Radiant.rc2 │ ├── resource.h │ ├── splines/ │ │ ├── Splines.vcproj │ │ ├── math_angles.cpp │ │ ├── math_angles.h │ │ ├── math_matrix.cpp │ │ ├── math_matrix.h │ │ ├── math_quaternion.cpp │ │ ├── math_quaternion.h │ │ ├── math_vector.cpp │ │ ├── math_vector.h │ │ ├── q_parse.cpp │ │ ├── q_shared.cpp │ │ ├── q_shared.h │ │ ├── splines.cpp │ │ ├── splines.h │ │ ├── util_list.h │ │ ├── util_str.cpp │ │ └── util_str.h │ ├── terrain.cpp │ └── terrain.h └── ui/ ├── hud.txt ├── hud2.txt ├── ingame.txt ├── menudef.h └── menus.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: COPYING.txt ================================================ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS ================================================ FILE: README.txt ================================================ Quake III Arena GPL source release ================================== This file contains the following sections: LICENSE GENERAL NOTES COMPILING ON WIN32 COMPILING ON GNU/LINUX COMPILING ON MAC LICENSE ======= See COPYING.txt for the GNU GENERAL PUBLIC LICENSE Some source code in this release is not covered by the GPL: IO on .zip files using portions of zlib ----------------------------------------------------------------------------- lines file(s) 4299 code/qcommon/unzip.c 4546 libs/pak/unzip.cpp Copyright (C) 1998 Gilles Vollant zlib is Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. MD4 Message-Digest Algorithm ----------------------------------------------------------------------------- lines file(s) 299 code/qcommon/md4.c 277 common/md4.c Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All rights reserved. License to copy and use this software is granted provided that it is identified as the <93>RSA Data Security, Inc. MD4 Message-Digest Algorithm<94> in all mater ial mentioning or referencing this software or this function. License is also granted to make and use derivative works provided that such work s are identified as <93>derived from the RSA Data Security, Inc. MD4 Message-Dig est Algorithm<94> in all material mentioning or referencing the derived work. RSA Data Security, Inc. makes no representations concerning either the merchanta bility of this software or the suitability of this software for any particular p urpose. It is provided <93>as is<94> without express or implied warranty of any kind. checksums are used to validate pak files standard C library replacement routines ----------------------------------------------------------------------------- lines file(s) 1324 code/game/bg_lib.c Copyright (c) 1992, 1993 The Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the University of California, Berkeley and its contributors. 4. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ADPCM coder/decoder ----------------------------------------------------------------------------- lines file(s) 330 code/client/snd_adpcm.c Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The Netherlands. All Rights Reserved Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the names of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. JPEG library ----------------------------------------------------------------------------- code/jpeg-6 libs/jpeg6 Copyright (C) 1991-1995, Thomas G. Lane Permission is hereby granted to use, copy, modify, and distribute this software (or portions thereof) for any purpose, without fee, subject to these conditions: (1) If any part of the source code for this software is distributed, then this README file must be included, with this copyright and no-warranty notice unaltered; and any additions, deletions, or changes to the original files must be clearly indicated in accompanying documentation. (2) If only executable code is distributed, then the accompanying documentation must state that "this software is based in part on the work of the Independent JPEG Group". (3) Permission for use of this software is granted only if the user accepts full responsibility for any undesirable consequences; the authors accept NO LIABILITY for damages of any kind. These conditions apply to any software derived from or based on the IJG code, not just to the unmodified library. If you use our work, you ought to acknowledge us. NOTE: unfortunately the README that came with our copy of the library has been lost, so the one from release 6b is included instead. There are a few 'glue type' modifications to the library to make it easier to use from the engine, but otherwise the dependency can be easily cleaned up to a better release of the library. GENERAL NOTES ============= A short summary of the file layout: code/ Quake III Arena source code ( renderer, game code, OS layer etc. ) code/bspc bot routes compiler source code lcc/ the retargetable C compiler ( produces assembly to be turned into qvm bytecode by q3asm ) q3asm/ assembly to qvm bytecode compiler q3map/ map compiler ( .map -> .bsp ) - this is the version that comes with Q3Radiant 200f q3radiant/ Q3Radiant map editor build 200f ( common/ and libs/ are support dirs for radiant ) While we made sure we were still able to compile the game on Windows, GNU/Linux and Mac, this build didn't get any kind of extensive testing so it may not work completely right. Whenever an id game is released under GPL, several projects start making the source code more friendly to nowaday's compilers and environements. If you are picking up this release weeks/months/years after we uploaded it, you probably want to look around on the net for cleaned up versions of this codebase as well. COMPILING ON WIN32 ================== VC7 / Visual C++ 2003 project files are provided: code/quake3.sln q3radiant/Radiant.sln To compile the qvms, you need to run some batch files: you will need to have lcc.exe q3cpp.exe q3rcc.exe and q3asm.exe in your path ( some precompiled binaries are provided in lcc/bin and code/win32/mod-sdk-setup/bin ) the qvm batch files are in code/game code/cgame code/q3_ui code/ui .. COMPILING ON GNU/LINUX ================== the build system using cons, which may be known as scons's perl ancestor now you don't have to track it down though, the build script is provided in the tree you will need nasm and gcc 2.95 make sure you have the X Direct Graphics Access and X Video Mode extensions headers for your X11 a typical compile command goes like this: [..]/code$ ./unix/cons -- gcc=gcc-2.95 g++=g++-2.95 COMPILING ON MAC ================ project file for OSX compile is in code/macosx/Quake3.pbproj ================================================ FILE: code/Construct ================================================ # -*- mode: perl -*- # cons script for cgame game q3_ui ui .so and .qvm builds # # Oct. 2001 TTimo # # the top directory is # --- # where: # is "debug" or "release" # is "x86" or "ppc" # is "Linux" "BSD" "IRIX" etc. # is major.minor of libc config # source the compiler version utility BEGIN { push @INC, './unix'; } use Cons_gcc; # defaults $config = 'debug'; $do_smp = 1; $do_masterserver = 0; $do_authserver = 0; $do_authport = 0; $do_setup = 0; $do_bspc = 0; $do_sdk = 0; $do_pk3 = 0; # those are exported $DO_WIN32 = 0; $NO_VM = 0; $NO_SO = 0; $CC='gcc'; $CXX='g++'; # detect an sdk build (don't attempt client build and other things) if ( -r 'unix/Conscript-client' ) { $no_core = 0; } else { $no_core = 1; } # detection of CPU type $cpu = `uname -m`; chop ($cpu); if ($cpu +~ /i?86/) { $cpu = 'x86'; } # OS $OS = `uname`; chop ($OS); # hacky win32 detection and win32 specifics code if ($OS =~ CYGWIN) { $DO_WIN32 = 1; } else { # libc .. do the little magic! $libc_cmd = '/lib/libc.so.6 |grep "GNU C "|grep version|awk -F "version " \'{ print $2 }\'|cut -b -3'; $libc = `$libc_cmd`; chop ($libc); } if ($DO_WIN32 eq 1) { print("Win32 build\n"); $config = $ARGV[0]; # TODO: option to override $Q3BASE from command line $Q3BASE = $ENV{Q3BASE}; # FIXME: this doesn't play nice with cygwin path syntax print("\$Q3BASE: $Q3BASE\n"); if($config eq 'debug') { $DIR = 'Debug'; system("cp -v $DIR/quake3.exe \$Q3BASE"); system("cp -v $DIR/cgamex86.dll $DIR/qagamex86.dll $DIR/uix86.dll \$Q3BASE/baseq3"); } elsif ($config eq 'debug-TA') { $DIR = 'Debug_TA'; system("cp -v $DIR/quake3.exe \$Q3BASE"); system("cp -v $DIR/cgamex86.dll $DIR/qagamex86.dll $DIR/uix86.dll \$Q3BASE/missionpack"); } elsif($config eq 'release-TA') { $DIR = 'Release_TA'; # spank! system("./spank.sh"); system("cp -v $DIR/quake3.exe \$Q3BASE"); } else { printf("ERROR: no config option (debug debug-TA release-TA)"); exit; } # copy selected stuff to shared media $DESTDIR='/cygdrive/e/incoming/Id/q3-1.32'; system("cp -v $DIR/quake3.exe $DESTDIR"); system("cp -v /cygdrive/e/Q3SetupMedia/quake3/CHANGES-1.32.txt $DESTDIR"); exit; } if(@ARGV gt 0) { foreach $cmdopt (@ARGV) { if(lc($cmdopt) eq 'release') { $config = 'release'; next; } elsif(lc($cmdopt) eq 'debug') { $config = 'debug'; next; } elsif(lc($cmdopt) eq 'novm') { $NO_VM = 1; next; } elsif(lc($cmdopt) eq 'noso') { $NO_SO = 1; next; } elsif(lc($cmdopt) eq 'nosmp') { $do_smp = 0; next; } elsif(lc($cmdopt) =~ 'master_server=.*') { $do_masterserver = 1; $master_server = lc($cmdopt); $master_server =~ s/master_server=(.*)/\1/; next; } elsif(lc($cmdopt) =~ 'auth_server=.*') { $do_authserver = 1; $auth_server = lc($cmdopt); $auth_server =~ s/auth_server=(.*)/\1/; next; } elsif(lc($cmdopt) =~ 'auth_port=.*') { $do_authport = 1; $auth_port = lc($cmdopt); $auth_port =~ s/auth_port=(.*)/\1/; next; } elsif(lc($cmdopt) =~ 'setup') { $do_setup = 1; next; } elsif(lc($cmdopt) =~ 'bspc') { $do_bspc = 1; next; } elsif(lc($cmdopt) =~ 'sdk') { $do_sdk = 1; next; } elsif(lc($cmdopt) =~ 'pk3') { $do_pk3 = 1; next; } elsif(lc($cmdopt) =~ 'gcc=.*') { $CC=lc($cmdopt); $CC =~ s/gcc=(.*)/\1/; next; } elsif(lc($cmdopt) =~ 'g\+\+=.*') { $CXX=lc($cmdopt); $CXX=~s/g\+\+=(.*)/\1/; next; } else { # output an error & exit print("Error\n $0: Unknown command line option: [ $cmdopt ]\n"); system("cons -h"); exit; } } } if (($do_setup eq 1) && ($config ne 'release')) { print("Error\n $0: 'setup' requires 'release'\n"); exit; } # sdk if ($do_sdk eq 1) { # extract the Q3 version from q_shared.h $line = `cat game/q_shared.h | grep Q3_VERSION`; chomp $line; $line =~ s/.*Q3\ (.*)\"/$1/; $Q3_VER = $line; $SDK_NAME = "linuxq3a-sdk-$Q3_VER.x86.run"; Default "unix/$SDK_NAME"; Export qw( SDK_NAME Q3_VER ); Build 'unix/Conscript-sdk'; return; } # build the config directory $CONFIG_DIR = $config . '-' . $cpu . '-' . $OS . '-' . $libc; $COMMON_CFLAGS = '-pipe -fsigned-char '; if ($config eq 'debug') { # use -Werror for better QA $BASE_CFLAGS = $COMMON_CFLAGS . '-g -Wall -Werror -O '; $BSPC_BASE_CFLAGS = $COMMON_CFLAGS . '-g -O -DLINUX -DBSPC -Dstricmp=strcasecmp '; } else { $BASE_CFLAGS = $COMMON_CFLAGS . '-DNDEBUG -O6 -mcpu=pentiumpro -march=pentium -fomit-frame-pointer -ffast-math -malign-loops=2 -malign-jumps=2 -malign-functions=2 -fno-strict-aliasing -fstrength-reduce '; $BSPC_BASE_CFLAGS = $BASE_CFLAGS . '-DLINUX -DBSPC -Dstricmp=strcasecmp '; } if ($do_masterserver eq 1) { $BASE_CFLAGS .= "-DMASTER_SERVER_NAME=\\\"$master_server\\\" "; } if ($do_authserver eq 1) { $BASE_CFLAGS .= "-DAUTHORIZE_SERVER_NAME=\\\"$auth_server\\\" "; } if ($do_authport eq 1) { $BASE_CFLAGS .= "-DPORT_AUTHORIZE=$auth_port "; } my @gcc_version = Cons_gcc::get_gcc_version($CC); print("GCC version: $gcc_version[1] - $gcc_version[2]\n"); # with 2.95 you can link with gcc, this avoids nasty useless libstdc++ dependency if ($gcc_version[0] eq '2') { $LINK = $CC; } else { $LINK = $CXX; } my @ccache = Cons_gcc::get_ccache(); if ($ccache[0] eq '1') { $CC = $ccache[1] . " " . $CC; $CXX = $ccache[1] . " " . $CXX; } print 'cpu : ' . $cpu . "\nOS : " . $OS . "\n"; print "libc: " . $libc . "\n"; print "configured for " . $config . " build\n"; print 'CFLAGS: ' . $BASE_CFLAGS . "\n"; # install config $INSTALL_BASEDIR='#install'; Default $INSTALL_BASEDIR; sub build_tools { system("mkdir qvmtools 2>/dev/null"); if (@_[0] eq 'q3lcc') { system("cd ../lcc ; make all ; cp /tmp/lcc ../code/qvmtools/q3lcc ; cp /tmp/rcc ../code/qvmtools/q3rcc ; cp /tmp/cpp ../code/qvmtools/q3cpp"); } elsif (@_[0] eq 'q3asm') { system("cd ../q3asm ; make ; cp q3asm ../code/qvmtools"); } else { printf("build_tools: @_[0] unrecognized command\n"); die; } return 1; } # build tools $env_tools = new cons(); Command $env_tools 'qvmtools/q3lcc', '[perl] &build_tools(\'q3lcc\')'; Command $env_tools 'qvmtools/q3asm', '[perl] &build_tools(\'q3asm\')'; if ($do_bspc eq 1) { # build bspc $BUILD_DIR = $CONFIG_DIR . '/bspc'; Link $BUILD_DIR => '.'; $INSTALL_DIR = $INSTALL_BASEDIR . '/utils'; Export qw( BSPC_BASE_CFLAGS BUILD_DIR INSTALL_DIR CC CXX LINK ); Build $BUILD_DIR . '/bspc/Conscript'; } # build vanilla Q3 $TARGET_DIR='Q3'; $INSTALL_DIR = $INSTALL_BASEDIR . '/baseq3'; $BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/cgame'; Link $BUILD_DIR => '.'; Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); Build $BUILD_DIR . '/cgame/Conscript'; $BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/game'; Link $BUILD_DIR => '.'; Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); Build $BUILD_DIR . '/game/Conscript'; $BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/q3_ui'; Link $BUILD_DIR => '.'; Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); Build $BUILD_DIR . '/q3_ui/Conscript'; # build TA $TARGET_DIR='TA'; $INSTALL_DIR = $INSTALL_BASEDIR . '/missionpack'; $BUILD_DIR = $CONFIG_DIR . "/" . $TARGET_DIR . '/cgame'; Link $BUILD_DIR => '.'; Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); Build $BUILD_DIR . '/cgame/Conscript'; $BUILD_DIR = $CONFIG_DIR . "/" . $TARGET_DIR . '/game'; Link $BUILD_DIR => '.'; Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); Build $BUILD_DIR . '/game/Conscript'; $BUILD_DIR = $CONFIG_DIR . '/' . $TARGET_DIR . '/ui'; Link $BUILD_DIR => '.'; Export qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); Build $BUILD_DIR . '/ui/Conscript'; # core if ($no_core eq 1) { return; } $INSTALL_DIR = $INSTALL_BASEDIR; $BUILD_DIR = $CONFIG_DIR . '/core/dedicated'; Link $BUILD_DIR => '.'; $hack = $BASE_CFLAGS; # hit me! $BASE_CFLAGS .= '-DDEDICATED '; Export qw( BASE_CFLAGS BUILD_DIR INSTALL_DIR CC CXX LINK ); Build $BUILD_DIR . '/unix/Conscript-dedicated'; $BASE_CFLAGS = $hack; $TARGETNAME = 'linuxquake3'; $BUILD_DIR = $CONFIG_DIR . '/core/client'; $BASE_LDFLAGS = ''; Link $BUILD_DIR => '.'; Export qw( BASE_CFLAGS BASE_LDFLAGS BUILD_DIR INSTALL_DIR TARGETNAME CC CXX LINK ); Build $BUILD_DIR . '/unix/Conscript-client'; if ($do_smp eq 1) { $TARGETNAME = 'linuxquake3-smp'; $BUILD_DIR = $CONFIG_DIR . '/core/client-smp'; $BASE_CFLAGS .= '-DSMP '; $BASE_LDFLAGS = '-lpthread '; Link $BUILD_DIR => '.'; Export qw( BASE_CFLAGS BASE_LDFLAGS BUILD_DIR INSTALL_DIR TARGETNAME CC CXX LINK ); Build $BUILD_DIR . '/unix/Conscript-client'; } if ($NO_VM eq 0 && $do_pk3 eq 1) { # build the PK3s $INSTALL_DIR = $INSTALL_BASEDIR; $BUILD_DIR = $CONFIG_DIR . '/pk3-builder'; Link $BUILD_DIR => 'unix'; Export qw( INSTALL_DIR BUILD_DIR CONFIG_DIR CC CXX LINK ); Build $BUILD_DIR . '/Conscript-pk3'; } if ($do_setup eq 1) { Link $CONFIG_DIR => '.'; Export qw( INSTALL_BASEDIR ); Build $CONFIG_DIR . '/unix/Conscript-setup'; } Help " Usage: cons [-h] [ -- [release|debug] [novm] [noso] [nosmp] [master_server=] [auth_server=] [auth_port=] [pk3] [bspc] [setup] [sdk]] Default build type is Debug, specifying '-- release' on the command line builds a Release version (NOTE that this option only affects the native libraries). novm: will not build the VMs noso: will not build the so below are for core builds only: nosmp : do not build the SMP-enabled version of the renderer pk3 : generate the pk3s on the fly (defined in unix/Conscript-pk3) bspc : build bspc setup : build setup sdk : build the mod sdk " ; ================================================ FILE: code/Makefile ================================================ # nasty ugly to get build system working from Anjuta all: if [ `hostname` == sparkle ] ; then ./unix/pcons-2.3.1 -j4 -- novm noso ; else ./unix/cons ; fi ================================================ FILE: code/botlib/aasfile.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ //NOTE: int = default signed // default long #define AASID (('S'<<24)+('A'<<16)+('A'<<8)+'E') #define AASVERSION_OLD 4 #define AASVERSION 5 //presence types #define PRESENCE_NONE 1 #define PRESENCE_NORMAL 2 #define PRESENCE_CROUCH 4 //travel types #define MAX_TRAVELTYPES 32 #define TRAVEL_INVALID 1 //temporary not possible #define TRAVEL_WALK 2 //walking #define TRAVEL_CROUCH 3 //crouching #define TRAVEL_BARRIERJUMP 4 //jumping onto a barrier #define TRAVEL_JUMP 5 //jumping #define TRAVEL_LADDER 6 //climbing a ladder #define TRAVEL_WALKOFFLEDGE 7 //walking of a ledge #define TRAVEL_SWIM 8 //swimming #define TRAVEL_WATERJUMP 9 //jump out of the water #define TRAVEL_TELEPORT 10 //teleportation #define TRAVEL_ELEVATOR 11 //travel by elevator #define TRAVEL_ROCKETJUMP 12 //rocket jumping required for travel #define TRAVEL_BFGJUMP 13 //bfg jumping required for travel #define TRAVEL_GRAPPLEHOOK 14 //grappling hook required for travel #define TRAVEL_DOUBLEJUMP 15 //double jump #define TRAVEL_RAMPJUMP 16 //ramp jump #define TRAVEL_STRAFEJUMP 17 //strafe jump #define TRAVEL_JUMPPAD 18 //jump pad #define TRAVEL_FUNCBOB 19 //func bob //additional travel flags #define TRAVELTYPE_MASK 0xFFFFFF #define TRAVELFLAG_NOTTEAM1 (1 << 24) #define TRAVELFLAG_NOTTEAM2 (2 << 24) //face flags #define FACE_SOLID 1 //just solid at the other side #define FACE_LADDER 2 //ladder #define FACE_GROUND 4 //standing on ground when in this face #define FACE_GAP 8 //gap in the ground #define FACE_LIQUID 16 //face seperating two areas with liquid #define FACE_LIQUIDSURFACE 32 //face seperating liquid and air #define FACE_BRIDGE 64 //can walk over this face if bridge is closed //area contents #define AREACONTENTS_WATER 1 #define AREACONTENTS_LAVA 2 #define AREACONTENTS_SLIME 4 #define AREACONTENTS_CLUSTERPORTAL 8 #define AREACONTENTS_TELEPORTAL 16 #define AREACONTENTS_ROUTEPORTAL 32 #define AREACONTENTS_TELEPORTER 64 #define AREACONTENTS_JUMPPAD 128 #define AREACONTENTS_DONOTENTER 256 #define AREACONTENTS_VIEWPORTAL 512 #define AREACONTENTS_MOVER 1024 #define AREACONTENTS_NOTTEAM1 2048 #define AREACONTENTS_NOTTEAM2 4096 //number of model of the mover inside this area #define AREACONTENTS_MODELNUMSHIFT 24 #define AREACONTENTS_MAXMODELNUM 0xFF #define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) //area flags #define AREA_GROUNDED 1 //bot can stand on the ground #define AREA_LADDER 2 //area contains one or more ladder faces #define AREA_LIQUID 4 //area contains a liquid #define AREA_DISABLED 8 //area is disabled for routing when set #define AREA_BRIDGE 16 //area ontop of a bridge //aas file header lumps #define AAS_LUMPS 14 #define AASLUMP_BBOXES 0 #define AASLUMP_VERTEXES 1 #define AASLUMP_PLANES 2 #define AASLUMP_EDGES 3 #define AASLUMP_EDGEINDEX 4 #define AASLUMP_FACES 5 #define AASLUMP_FACEINDEX 6 #define AASLUMP_AREAS 7 #define AASLUMP_AREASETTINGS 8 #define AASLUMP_REACHABILITY 9 #define AASLUMP_NODES 10 #define AASLUMP_PORTALS 11 #define AASLUMP_PORTALINDEX 12 #define AASLUMP_CLUSTERS 13 //========== bounding box ========= //bounding box typedef struct aas_bbox_s { int presencetype; int flags; vec3_t mins, maxs; } aas_bbox_t; //============ settings =========== //reachability to another area typedef struct aas_reachability_s { int areanum; //number of the reachable area int facenum; //number of the face towards the other area int edgenum; //number of the edge towards the other area vec3_t start; //start point of inter area movement vec3_t end; //end point of inter area movement int traveltype; //type of travel required to get to the area unsigned short int traveltime;//travel time of the inter area movement } aas_reachability_t; //area settings typedef struct aas_areasettings_s { //could also add all kind of statistic fields int contents; //contents of the area int areaflags; //several area flags int presencetype; //how a bot can be present in this area int cluster; //cluster the area belongs to, if negative it's a portal int clusterareanum; //number of the area in the cluster int numreachableareas; //number of reachable areas from this one int firstreachablearea; //first reachable area in the reachable area index } aas_areasettings_t; //cluster portal typedef struct aas_portal_s { int areanum; //area that is the actual portal int frontcluster; //cluster at front of portal int backcluster; //cluster at back of portal int clusterareanum[2]; //number of the area in the front and back cluster } aas_portal_t; //cluster portal index typedef int aas_portalindex_t; //cluster typedef struct aas_cluster_s { int numareas; //number of areas in the cluster int numreachabilityareas; //number of areas with reachabilities int numportals; //number of cluster portals int firstportal; //first cluster portal in the index } aas_cluster_t; //============ 3d definition ============ typedef vec3_t aas_vertex_t; //just a plane in the third dimension typedef struct aas_plane_s { vec3_t normal; //normal vector of the plane float dist; //distance of the plane (normal vector * distance = point in plane) int type; } aas_plane_t; //edge typedef struct aas_edge_s { int v[2]; //numbers of the vertexes of this edge } aas_edge_t; //edge index, negative if vertexes are reversed typedef int aas_edgeindex_t; //a face bounds an area, often it will also seperate two areas typedef struct aas_face_s { int planenum; //number of the plane this face is in int faceflags; //face flags (no use to create face settings for just this field) int numedges; //number of edges in the boundary of the face int firstedge; //first edge in the edge index int frontarea; //area at the front of this face int backarea; //area at the back of this face } aas_face_t; //face index, stores a negative index if backside of face typedef int aas_faceindex_t; //area with a boundary of faces typedef struct aas_area_s { int areanum; //number of this area //3d definition int numfaces; //number of faces used for the boundary of the area int firstface; //first face in the face index used for the boundary of the area vec3_t mins; //mins of the area vec3_t maxs; //maxs of the area vec3_t center; //'center' of the area } aas_area_t; //nodes of the bsp tree typedef struct aas_node_s { int planenum; int children[2]; //child nodes of this node, or areas as leaves when negative //when a child is zero it's a solid leaf } aas_node_t; //=========== aas file =============== //header lump typedef struct { int fileofs; int filelen; } aas_lump_t; //aas file header typedef struct aas_header_s { int ident; int version; int bspchecksum; //data entries aas_lump_t lumps[AAS_LUMPS]; } aas_header_t; //====== additional information ====== /* - when a node child is a solid leaf the node child number is zero - two adjacent areas (sharing a plane at opposite sides) share a face this face is a portal between the areas - when an area uses a face from the faceindex with a positive index then the face plane normal points into the area - the face edges are stored counter clockwise using the edgeindex - two adjacent convex areas (sharing a face) only share One face this is a simple result of the areas being convex - the areas can't have a mixture of ground and gap faces other mixtures of faces in one area are allowed - areas with the AREACONTENTS_CLUSTERPORTAL in the settings have the cluster number set to the negative portal number - edge zero is a dummy - face zero is a dummy - area zero is a dummy - node zero is a dummy */ ================================================ FILE: code/botlib/be_aas_bsp.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_bsp.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_bsp.h $ * *****************************************************************************/ #ifdef AASINTERN //loads the given BSP file int AAS_LoadBSPFile(void); //dump the loaded BSP data void AAS_DumpBSPData(void); //unlink the given entity from the bsp tree leaves void AAS_UnlinkFromBSPLeaves(bsp_link_t *leaves); //link the given entity to the bsp tree leaves of the given model bsp_link_t *AAS_BSPLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum, int modelnum); //calculates collision with given entity qboolean AAS_EntityCollision(int entnum, vec3_t start, vec3_t boxmins, vec3_t boxmaxs, vec3_t end, int contentmask, bsp_trace_t *trace); //for debugging void AAS_PrintFreeBSPLinks(char *str); // #endif //AASINTERN #define MAX_EPAIRKEY 128 //trace through the world bsp_trace_t AAS_Trace( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); //returns the contents at the given point int AAS_PointContents(vec3_t point); //returns true when p2 is in the PVS of p1 qboolean AAS_inPVS(vec3_t p1, vec3_t p2); //returns true when p2 is in the PHS of p1 qboolean AAS_inPHS(vec3_t p1, vec3_t p2); //returns true if the given areas are connected qboolean AAS_AreasConnected(int area1, int area2); //creates a list with entities totally or partly within the given box int AAS_BoxEntities(vec3_t absmins, vec3_t absmaxs, int *list, int maxcount); //gets the mins, maxs and origin of a BSP model void AAS_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); //handle to the next bsp entity int AAS_NextBSPEntity(int ent); //return the value of the BSP epair key int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size); //get a vector for the BSP epair key int AAS_VectorForBSPEpairKey(int ent, char *key, vec3_t v); //get a float for the BSP epair key int AAS_FloatForBSPEpairKey(int ent, char *key, float *value); //get an integer for the BSP epair key int AAS_IntForBSPEpairKey(int ent, char *key, int *value); ================================================ FILE: code/botlib/be_aas_bspq3.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_bspq3.c * * desc: BSP, Environment Sampling * * $Archive: /MissionPack/code/botlib/be_aas_bspq3.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_aas_def.h" extern botlib_import_t botimport; //#define TRACE_DEBUG #define ON_EPSILON 0.005 //#define DEG2RAD( a ) (( a * M_PI ) / 180.0F) #define MAX_BSPENTITIES 2048 typedef struct rgb_s { int red; int green; int blue; } rgb_t; //bsp entity epair typedef struct bsp_epair_s { char *key; char *value; struct bsp_epair_s *next; } bsp_epair_t; //bsp data entity typedef struct bsp_entity_s { bsp_epair_t *epairs; } bsp_entity_t; //id Sofware BSP data typedef struct bsp_s { //true when bsp file is loaded int loaded; //entity data int entdatasize; char *dentdata; //bsp entities int numentities; bsp_entity_t entities[MAX_BSPENTITIES]; } bsp_t; //global bsp bsp_t bspworld; #ifdef BSP_DEBUG typedef struct cname_s { int value; char *name; } cname_t; cname_t contentnames[] = { {CONTENTS_SOLID,"CONTENTS_SOLID"}, {CONTENTS_WINDOW,"CONTENTS_WINDOW"}, {CONTENTS_AUX,"CONTENTS_AUX"}, {CONTENTS_LAVA,"CONTENTS_LAVA"}, {CONTENTS_SLIME,"CONTENTS_SLIME"}, {CONTENTS_WATER,"CONTENTS_WATER"}, {CONTENTS_MIST,"CONTENTS_MIST"}, {LAST_VISIBLE_CONTENTS,"LAST_VISIBLE_CONTENTS"}, {CONTENTS_AREAPORTAL,"CONTENTS_AREAPORTAL"}, {CONTENTS_PLAYERCLIP,"CONTENTS_PLAYERCLIP"}, {CONTENTS_MONSTERCLIP,"CONTENTS_MONSTERCLIP"}, {CONTENTS_CURRENT_0,"CONTENTS_CURRENT_0"}, {CONTENTS_CURRENT_90,"CONTENTS_CURRENT_90"}, {CONTENTS_CURRENT_180,"CONTENTS_CURRENT_180"}, {CONTENTS_CURRENT_270,"CONTENTS_CURRENT_270"}, {CONTENTS_CURRENT_UP,"CONTENTS_CURRENT_UP"}, {CONTENTS_CURRENT_DOWN,"CONTENTS_CURRENT_DOWN"}, {CONTENTS_ORIGIN,"CONTENTS_ORIGIN"}, {CONTENTS_MONSTER,"CONTENTS_MONSTER"}, {CONTENTS_DEADMONSTER,"CONTENTS_DEADMONSTER"}, {CONTENTS_DETAIL,"CONTENTS_DETAIL"}, {CONTENTS_TRANSLUCENT,"CONTENTS_TRANSLUCENT"}, {CONTENTS_LADDER,"CONTENTS_LADDER"}, {0, 0} }; void PrintContents(int contents) { int i; for (i = 0; contentnames[i].value; i++) { if (contents & contentnames[i].value) { botimport.Print(PRT_MESSAGE, "%s\n", contentnames[i].name); } //end if } //end for } //end of the function PrintContents #endif // BSP_DEBUG //=========================================================================== // traces axial boxes of any size through the world // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bsp_trace_t AAS_Trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { bsp_trace_t bsptrace; botimport.Trace(&bsptrace, start, mins, maxs, end, passent, contentmask); return bsptrace; } //end of the function AAS_Trace //=========================================================================== // returns the contents at the given point // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_PointContents(vec3_t point) { return botimport.PointContents(point); } //end of the function AAS_PointContents //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_EntityCollision(int entnum, vec3_t start, vec3_t boxmins, vec3_t boxmaxs, vec3_t end, int contentmask, bsp_trace_t *trace) { bsp_trace_t enttrace; botimport.EntityTrace(&enttrace, start, boxmins, boxmaxs, end, entnum, contentmask); if (enttrace.fraction < trace->fraction) { Com_Memcpy(trace, &enttrace, sizeof(bsp_trace_t)); return qtrue; } //end if return qfalse; } //end of the function AAS_EntityCollision //=========================================================================== // returns true if in Potentially Hearable Set // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_inPVS(vec3_t p1, vec3_t p2) { return botimport.inPVS(p1, p2); } //end of the function AAS_InPVS //=========================================================================== // returns true if in Potentially Visible Set // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_inPHS(vec3_t p1, vec3_t p2) { return qtrue; } //end of the function AAS_inPHS //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin) { botimport.BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); } //end of the function AAS_BSPModelMinsMaxs //=========================================================================== // unlinks the entity from all leaves // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_UnlinkFromBSPLeaves(bsp_link_t *leaves) { } //end of the function AAS_UnlinkFromBSPLeaves //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bsp_link_t *AAS_BSPLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum, int modelnum) { return NULL; } //end of the function AAS_BSPLinkEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_BoxEntities(vec3_t absmins, vec3_t absmaxs, int *list, int maxcount) { return 0; } //end of the function AAS_BoxEntities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_NextBSPEntity(int ent) { ent++; if (ent >= 1 && ent < bspworld.numentities) return ent; return 0; } //end of the function AAS_NextBSPEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_BSPEntityInRange(int ent) { if (ent <= 0 || ent >= bspworld.numentities) { botimport.Print(PRT_MESSAGE, "bsp entity out of range\n"); return qfalse; } //end if return qtrue; } //end of the function AAS_BSPEntityInRange //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size) { bsp_epair_t *epair; value[0] = '\0'; if (!AAS_BSPEntityInRange(ent)) return qfalse; for (epair = bspworld.entities[ent].epairs; epair; epair = epair->next) { if (!strcmp(epair->key, key)) { strncpy(value, epair->value, size-1); value[size-1] = '\0'; return qtrue; } //end if } //end for return qfalse; } //end of the function AAS_FindBSPEpair //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_VectorForBSPEpairKey(int ent, char *key, vec3_t v) { char buf[MAX_EPAIRKEY]; double v1, v2, v3; VectorClear(v); if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; //scanf into doubles, then assign, so it is vec_t size independent v1 = v2 = v3 = 0; sscanf(buf, "%lf %lf %lf", &v1, &v2, &v3); v[0] = v1; v[1] = v2; v[2] = v3; return qtrue; } //end of the function AAS_VectorForBSPEpairKey //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_FloatForBSPEpairKey(int ent, char *key, float *value) { char buf[MAX_EPAIRKEY]; *value = 0; if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; *value = atof(buf); return qtrue; } //end of the function AAS_FloatForBSPEpairKey //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_IntForBSPEpairKey(int ent, char *key, int *value) { char buf[MAX_EPAIRKEY]; *value = 0; if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; *value = atoi(buf); return qtrue; } //end of the function AAS_IntForBSPEpairKey //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeBSPEntities(void) { int i; bsp_entity_t *ent; bsp_epair_t *epair, *nextepair; for (i = 1; i < bspworld.numentities; i++) { ent = &bspworld.entities[i]; for (epair = ent->epairs; epair; epair = nextepair) { nextepair = epair->next; // if (epair->key) FreeMemory(epair->key); if (epair->value) FreeMemory(epair->value); FreeMemory(epair); } //end for } //end for bspworld.numentities = 0; } //end of the function AAS_FreeBSPEntities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ParseBSPEntities(void) { script_t *script; token_t token; bsp_entity_t *ent; bsp_epair_t *epair; script = LoadScriptMemory(bspworld.dentdata, bspworld.entdatasize, "entdata"); SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES|SCFL_NOSTRINGESCAPECHARS);//SCFL_PRIMITIVE); bspworld.numentities = 1; while(PS_ReadToken(script, &token)) { if (strcmp(token.string, "{")) { ScriptError(script, "invalid %s\n", token.string); AAS_FreeBSPEntities(); FreeScript(script); return; } //end if if (bspworld.numentities >= MAX_BSPENTITIES) { botimport.Print(PRT_MESSAGE, "too many entities in BSP file\n"); break; } //end if ent = &bspworld.entities[bspworld.numentities]; bspworld.numentities++; ent->epairs = NULL; while(PS_ReadToken(script, &token)) { if (!strcmp(token.string, "}")) break; epair = (bsp_epair_t *) GetClearedHunkMemory(sizeof(bsp_epair_t)); epair->next = ent->epairs; ent->epairs = epair; if (token.type != TT_STRING) { ScriptError(script, "invalid %s\n", token.string); AAS_FreeBSPEntities(); FreeScript(script); return; } //end if StripDoubleQuotes(token.string); epair->key = (char *) GetHunkMemory(strlen(token.string) + 1); strcpy(epair->key, token.string); if (!PS_ExpectTokenType(script, TT_STRING, 0, &token)) { AAS_FreeBSPEntities(); FreeScript(script); return; } //end if StripDoubleQuotes(token.string); epair->value = (char *) GetHunkMemory(strlen(token.string) + 1); strcpy(epair->value, token.string); } //end while if (strcmp(token.string, "}")) { ScriptError(script, "missing }\n"); AAS_FreeBSPEntities(); FreeScript(script); return; } //end if } //end while FreeScript(script); } //end of the function AAS_ParseBSPEntities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_BSPTraceLight(vec3_t start, vec3_t end, vec3_t endpos, int *red, int *green, int *blue) { return 0; } //end of the function AAS_BSPTraceLight //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DumpBSPData(void) { AAS_FreeBSPEntities(); if (bspworld.dentdata) FreeMemory(bspworld.dentdata); bspworld.dentdata = NULL; bspworld.entdatasize = 0; // bspworld.loaded = qfalse; Com_Memset( &bspworld, 0, sizeof(bspworld) ); } //end of the function AAS_DumpBSPData //=========================================================================== // load an bsp file // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_LoadBSPFile(void) { AAS_DumpBSPData(); bspworld.entdatasize = strlen(botimport.BSPEntityData()) + 1; bspworld.dentdata = (char *) GetClearedHunkMemory(bspworld.entdatasize); Com_Memcpy(bspworld.dentdata, botimport.BSPEntityData(), bspworld.entdatasize); AAS_ParseBSPEntities(); bspworld.loaded = qtrue; return BLERR_NOERROR; } //end of the function AAS_LoadBSPFile ================================================ FILE: code/botlib/be_aas_cluster.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_cluster.c * * desc: area clustering * * $Archive: /MissionPack/code/botlib/be_aas_cluster.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_log.h" #include "l_memory.h" #include "l_libvar.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_aas_def.h" extern botlib_import_t botimport; #define AAS_MAX_PORTALS 65536 #define AAS_MAX_PORTALINDEXSIZE 65536 #define AAS_MAX_CLUSTERS 65536 // #define MAX_PORTALAREAS 1024 // do not flood through area faces, only use reachabilities int nofaceflood = qtrue; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RemoveClusterAreas(void) { int i; for (i = 1; i < aasworld.numareas; i++) { aasworld.areasettings[i].cluster = 0; } //end for } //end of the function AAS_RemoveClusterAreas //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ClearCluster(int clusternum) { int i; for (i = 1; i < aasworld.numareas; i++) { if (aasworld.areasettings[i].cluster == clusternum) { aasworld.areasettings[i].cluster = 0; } //end if } //end for } //end of the function AAS_ClearCluster //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RemovePortalsClusterReference(int clusternum) { int portalnum; for (portalnum = 1; portalnum < aasworld.numportals; portalnum++) { if (aasworld.portals[portalnum].frontcluster == clusternum) { aasworld.portals[portalnum].frontcluster = 0; } //end if if (aasworld.portals[portalnum].backcluster == clusternum) { aasworld.portals[portalnum].backcluster = 0; } //end if } //end for } //end of the function AAS_RemovePortalsClusterReference //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_UpdatePortal(int areanum, int clusternum) { int portalnum; aas_portal_t *portal; aas_cluster_t *cluster; //find the portal of the area for (portalnum = 1; portalnum < aasworld.numportals; portalnum++) { if (aasworld.portals[portalnum].areanum == areanum) break; } //end for // if (portalnum == aasworld.numportals) { AAS_Error("no portal of area %d", areanum); return qtrue; } //end if // portal = &aasworld.portals[portalnum]; //if the portal is already fully updated if (portal->frontcluster == clusternum) return qtrue; if (portal->backcluster == clusternum) return qtrue; //if the portal has no front cluster yet if (!portal->frontcluster) { portal->frontcluster = clusternum; } //end if //if the portal has no back cluster yet else if (!portal->backcluster) { portal->backcluster = clusternum; } //end else if else { //remove the cluster portal flag contents aasworld.areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; Log_Write("portal area %d is seperating more than two clusters\r\n", areanum); return qfalse; } //end else if (aasworld.portalindexsize >= AAS_MAX_PORTALINDEXSIZE) { AAS_Error("AAS_MAX_PORTALINDEXSIZE"); return qtrue; } //end if //set the area cluster number to the negative portal number aasworld.areasettings[areanum].cluster = -portalnum; //add the portal to the cluster using the portal index cluster = &aasworld.clusters[clusternum]; aasworld.portalindex[cluster->firstportal + cluster->numportals] = portalnum; aasworld.portalindexsize++; cluster->numportals++; return qtrue; } //end of the function AAS_UpdatePortal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_FloodClusterAreas_r(int areanum, int clusternum) { aas_area_t *area; aas_face_t *face; int facenum, i; // if (areanum <= 0 || areanum >= aasworld.numareas) { AAS_Error("AAS_FloodClusterAreas_r: areanum out of range"); return qfalse; } //end if //if the area is already part of a cluster if (aasworld.areasettings[areanum].cluster > 0) { if (aasworld.areasettings[areanum].cluster == clusternum) return qtrue; // //there's a reachability going from one cluster to another only in one direction // AAS_Error("cluster %d touched cluster %d at area %d\r\n", clusternum, aasworld.areasettings[areanum].cluster, areanum); return qfalse; } //end if //don't add the cluster portal areas to the clusters if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) { return AAS_UpdatePortal(areanum, clusternum); } //end if //set the area cluster number aasworld.areasettings[areanum].cluster = clusternum; aasworld.areasettings[areanum].clusterareanum = aasworld.clusters[clusternum].numareas; //the cluster has an extra area aasworld.clusters[clusternum].numareas++; area = &aasworld.areas[areanum]; //use area faces to flood into adjacent areas if (!nofaceflood) { for (i = 0; i < area->numfaces; i++) { facenum = abs(aasworld.faceindex[area->firstface + i]); face = &aasworld.faces[facenum]; if (face->frontarea == areanum) { if (face->backarea) if (!AAS_FloodClusterAreas_r(face->backarea, clusternum)) return qfalse; } //end if else { if (face->frontarea) if (!AAS_FloodClusterAreas_r(face->frontarea, clusternum)) return qfalse; } //end else } //end for } //end if //use the reachabilities to flood into other areas for (i = 0; i < aasworld.areasettings[areanum].numreachableareas; i++) { if (!aasworld.reachability[ aasworld.areasettings[areanum].firstreachablearea + i].areanum) { continue; } //end if if (!AAS_FloodClusterAreas_r(aasworld.reachability[ aasworld.areasettings[areanum].firstreachablearea + i].areanum, clusternum)) return qfalse; } //end for return qtrue; } //end of the function AAS_FloodClusterAreas_r //=========================================================================== // try to flood from all areas without cluster into areas with a cluster set // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_FloodClusterAreasUsingReachabilities(int clusternum) { int i, j, areanum; for (i = 1; i < aasworld.numareas; i++) { //if this area already has a cluster set if (aasworld.areasettings[i].cluster) continue; //if this area is a cluster portal if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) continue; //loop over the reachable areas from this area for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) { //the reachable area areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; //if this area is a cluster portal if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; //if this area has a cluster set if (aasworld.areasettings[areanum].cluster) { if (!AAS_FloodClusterAreas_r(i, clusternum)) return qfalse; i = 0; break; } //end if } //end for } //end for return qtrue; } //end of the function AAS_FloodClusterAreasUsingReachabilities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_NumberClusterPortals(int clusternum) { int i, portalnum; aas_cluster_t *cluster; aas_portal_t *portal; cluster = &aasworld.clusters[clusternum]; for (i = 0; i < cluster->numportals; i++) { portalnum = aasworld.portalindex[cluster->firstportal + i]; portal = &aasworld.portals[portalnum]; if (portal->frontcluster == clusternum) { portal->clusterareanum[0] = cluster->numareas++; } //end if else { portal->clusterareanum[1] = cluster->numareas++; } //end else } //end for } //end of the function AAS_NumberClusterPortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_NumberClusterAreas(int clusternum) { int i, portalnum; aas_cluster_t *cluster; aas_portal_t *portal; aasworld.clusters[clusternum].numareas = 0; aasworld.clusters[clusternum].numreachabilityareas = 0; //number all areas in this cluster WITH reachabilities for (i = 1; i < aasworld.numareas; i++) { // if (aasworld.areasettings[i].cluster != clusternum) continue; // if (!AAS_AreaReachability(i)) continue; // aasworld.areasettings[i].clusterareanum = aasworld.clusters[clusternum].numareas; //the cluster has an extra area aasworld.clusters[clusternum].numareas++; aasworld.clusters[clusternum].numreachabilityareas++; } //end for //number all portals in this cluster WITH reachabilities cluster = &aasworld.clusters[clusternum]; for (i = 0; i < cluster->numportals; i++) { portalnum = aasworld.portalindex[cluster->firstportal + i]; portal = &aasworld.portals[portalnum]; if (!AAS_AreaReachability(portal->areanum)) continue; if (portal->frontcluster == clusternum) { portal->clusterareanum[0] = cluster->numareas++; aasworld.clusters[clusternum].numreachabilityareas++; } //end if else { portal->clusterareanum[1] = cluster->numareas++; aasworld.clusters[clusternum].numreachabilityareas++; } //end else } //end for //number all areas in this cluster WITHOUT reachabilities for (i = 1; i < aasworld.numareas; i++) { // if (aasworld.areasettings[i].cluster != clusternum) continue; // if (AAS_AreaReachability(i)) continue; // aasworld.areasettings[i].clusterareanum = aasworld.clusters[clusternum].numareas; //the cluster has an extra area aasworld.clusters[clusternum].numareas++; } //end for //number all portals in this cluster WITHOUT reachabilities cluster = &aasworld.clusters[clusternum]; for (i = 0; i < cluster->numportals; i++) { portalnum = aasworld.portalindex[cluster->firstportal + i]; portal = &aasworld.portals[portalnum]; if (AAS_AreaReachability(portal->areanum)) continue; if (portal->frontcluster == clusternum) { portal->clusterareanum[0] = cluster->numareas++; } //end if else { portal->clusterareanum[1] = cluster->numareas++; } //end else } //end for } //end of the function AAS_NumberClusterAreas //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_FindClusters(void) { int i; aas_cluster_t *cluster; AAS_RemoveClusterAreas(); // for (i = 1; i < aasworld.numareas; i++) { //if the area is already part of a cluster if (aasworld.areasettings[i].cluster) continue; // if not flooding through faces only use areas that have reachabilities if (nofaceflood) { if (!aasworld.areasettings[i].numreachableareas) continue; } //end if //if the area is a cluster portal if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) continue; if (aasworld.numclusters >= AAS_MAX_CLUSTERS) { AAS_Error("AAS_MAX_CLUSTERS"); return qfalse; } //end if cluster = &aasworld.clusters[aasworld.numclusters]; cluster->numareas = 0; cluster->numreachabilityareas = 0; cluster->firstportal = aasworld.portalindexsize; cluster->numportals = 0; //flood the areas in this cluster if (!AAS_FloodClusterAreas_r(i, aasworld.numclusters)) return qfalse; if (!AAS_FloodClusterAreasUsingReachabilities(aasworld.numclusters)) return qfalse; //number the cluster areas //AAS_NumberClusterPortals(aasworld.numclusters); AAS_NumberClusterAreas(aasworld.numclusters); //Log_Write("cluster %d has %d areas\r\n", aasworld.numclusters, cluster->numareas); aasworld.numclusters++; } //end for return qtrue; } //end of the function AAS_FindClusters //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CreatePortals(void) { int i; aas_portal_t *portal; for (i = 1; i < aasworld.numareas; i++) { //if the area is a cluster portal if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) { if (aasworld.numportals >= AAS_MAX_PORTALS) { AAS_Error("AAS_MAX_PORTALS"); return; } //end if portal = &aasworld.portals[aasworld.numportals]; portal->areanum = i; portal->frontcluster = 0; portal->backcluster = 0; aasworld.numportals++; } //end if } //end for } //end of the function AAS_CreatePortals /* //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_MapContainsTeleporters(void) { bsp_entity_t *entities, *ent; char *classname; entities = AAS_ParseBSPEntities(); for (ent = entities; ent; ent = ent->next) { classname = AAS_ValueForBSPEpairKey(ent, "classname"); if (classname && !strcmp(classname, "misc_teleporter")) { AAS_FreeBSPEntities(entities); return qtrue; } //end if } //end for return qfalse; } //end of the function AAS_MapContainsTeleporters //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_NonConvexFaces(aas_face_t *face1, aas_face_t *face2, int side1, int side2) { int i, j, edgenum; aas_plane_t *plane1, *plane2; aas_edge_t *edge; plane1 = &aasworld.planes[face1->planenum ^ side1]; plane2 = &aasworld.planes[face2->planenum ^ side2]; //check if one of the points of face1 is at the back of the plane of face2 for (i = 0; i < face1->numedges; i++) { edgenum = abs(aasworld.edgeindex[face1->firstedge + i]); edge = &aasworld.edges[edgenum]; for (j = 0; j < 2; j++) { if (DotProduct(plane2->normal, aasworld.vertexes[edge->v[j]]) - plane2->dist < -0.01) return qtrue; } //end for } //end for for (i = 0; i < face2->numedges; i++) { edgenum = abs(aasworld.edgeindex[face2->firstedge + i]); edge = &aasworld.edges[edgenum]; for (j = 0; j < 2; j++) { if (DotProduct(plane1->normal, aasworld.vertexes[edge->v[j]]) - plane1->dist < -0.01) return qtrue; } //end for } //end for return qfalse; } //end of the function AAS_NonConvexFaces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_CanMergeAreas(int *areanums, int numareas) { int i, j, s, face1num, face2num, side1, side2, fn1, fn2; aas_face_t *face1, *face2; aas_area_t *area1, *area2; for (i = 0; i < numareas; i++) { area1 = &aasworld.areas[areanums[i]]; for (fn1 = 0; fn1 < area1->numfaces; fn1++) { face1num = abs(aasworld.faceindex[area1->firstface + fn1]); face1 = &aasworld.faces[face1num]; side1 = face1->frontarea != areanums[i]; //check if the face isn't a shared one with one of the other areas for (s = 0; s < numareas; s++) { if (s == i) continue; if (face1->frontarea == s || face1->backarea == s) break; } //end for //if the face was a shared one if (s != numareas) continue; // for (j = 0; j < numareas; j++) { if (j == i) continue; area2 = &aasworld.areas[areanums[j]]; for (fn2 = 0; fn2 < area2->numfaces; fn2++) { face2num = abs(aasworld.faceindex[area2->firstface + fn2]); face2 = &aasworld.faces[face2num]; side2 = face2->frontarea != areanums[j]; //check if the face isn't a shared one with one of the other areas for (s = 0; s < numareas; s++) { if (s == j) continue; if (face2->frontarea == s || face2->backarea == s) break; } //end for //if the face was a shared one if (s != numareas) continue; // if (AAS_NonConvexFaces(face1, face2, side1, side2)) return qfalse; } //end for } //end for } //end for } //end for return qtrue; } //end of the function AAS_CanMergeAreas //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_NonConvexEdges(aas_edge_t *edge1, aas_edge_t *edge2, int side1, int side2, int planenum) { int i; vec3_t edgevec1, edgevec2, normal1, normal2; float dist1, dist2; aas_plane_t *plane; plane = &aasworld.planes[planenum]; VectorSubtract(aasworld.vertexes[edge1->v[1]], aasworld.vertexes[edge1->v[0]], edgevec1); VectorSubtract(aasworld.vertexes[edge2->v[1]], aasworld.vertexes[edge2->v[0]], edgevec2); if (side1) VectorInverse(edgevec1); if (side2) VectorInverse(edgevec2); // CrossProduct(edgevec1, plane->normal, normal1); dist1 = DotProduct(normal1, aasworld.vertexes[edge1->v[0]]); CrossProduct(edgevec2, plane->normal, normal2); dist2 = DotProduct(normal2, aasworld.vertexes[edge2->v[0]]); for (i = 0; i < 2; i++) { if (DotProduct(aasworld.vertexes[edge1->v[i]], normal2) - dist2 < -0.01) return qfalse; } //end for for (i = 0; i < 2; i++) { if (DotProduct(aasworld.vertexes[edge2->v[i]], normal1) - dist1 < -0.01) return qfalse; } //end for return qtrue; } //end of the function AAS_NonConvexEdges //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_CanMergeFaces(int *facenums, int numfaces, int planenum) { int i, j, s, edgenum1, edgenum2, side1, side2, en1, en2, ens; aas_face_t *face1, *face2, *otherface; aas_edge_t *edge1, *edge2; for (i = 0; i < numfaces; i++) { face1 = &aasworld.faces[facenums[i]]; for (en1 = 0; en1 < face1->numedges; en1++) { edgenum1 = aasworld.edgeindex[face1->firstedge + en1]; side1 = (edgenum1 < 0) ^ (face1->planenum != planenum); edgenum1 = abs(edgenum1); edge1 = &aasworld.edges[edgenum1]; //check if the edge is shared with another face for (s = 0; s < numfaces; s++) { if (s == i) continue; otherface = &aasworld.faces[facenums[s]]; for (ens = 0; ens < otherface->numedges; ens++) { if (edgenum1 == abs(aasworld.edgeindex[otherface->firstedge + ens])) break; } //end for if (ens != otherface->numedges) break; } //end for //if the edge was shared if (s != numfaces) continue; // for (j = 0; j < numfaces; j++) { if (j == i) continue; face2 = &aasworld.faces[facenums[j]]; for (en2 = 0; en2 < face2->numedges; en2++) { edgenum2 = aasworld.edgeindex[face2->firstedge + en2]; side2 = (edgenum2 < 0) ^ (face2->planenum != planenum); edgenum2 = abs(edgenum2); edge2 = &aasworld.edges[edgenum2]; //check if the edge is shared with another face for (s = 0; s < numfaces; s++) { if (s == i) continue; otherface = &aasworld.faces[facenums[s]]; for (ens = 0; ens < otherface->numedges; ens++) { if (edgenum2 == abs(aasworld.edgeindex[otherface->firstedge + ens])) break; } //end for if (ens != otherface->numedges) break; } //end for //if the edge was shared if (s != numfaces) continue; // if (AAS_NonConvexEdges(edge1, edge2, side1, side2, planenum)) return qfalse; } //end for } //end for } //end for } //end for return qtrue; } //end of the function AAS_CanMergeFaces*/ //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ConnectedAreas_r(int *areanums, int numareas, int *connectedareas, int curarea) { int i, j, otherareanum, facenum; aas_area_t *area; aas_face_t *face; connectedareas[curarea] = qtrue; area = &aasworld.areas[areanums[curarea]]; for (i = 0; i < area->numfaces; i++) { facenum = abs(aasworld.faceindex[area->firstface + i]); face = &aasworld.faces[facenum]; //if the face is solid if (face->faceflags & FACE_SOLID) continue; //get the area at the other side of the face if (face->frontarea != areanums[curarea]) otherareanum = face->frontarea; else otherareanum = face->backarea; //check if the face is leading to one of the other areas for (j = 0; j < numareas; j++) { if (areanums[j] == otherareanum) break; } //end for //if the face isn't leading to one of the other areas if (j == numareas) continue; //if the other area is already connected if (connectedareas[j]) continue; //recursively proceed with the other area AAS_ConnectedAreas_r(areanums, numareas, connectedareas, j); } //end for } //end of the function AAS_ConnectedAreas_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_ConnectedAreas(int *areanums, int numareas) { int connectedareas[MAX_PORTALAREAS], i; Com_Memset(connectedareas, 0, sizeof(connectedareas)); if (numareas < 1) return qfalse; if (numareas == 1) return qtrue; AAS_ConnectedAreas_r(areanums, numareas, connectedareas, 0); for (i = 0; i < numareas; i++) { if (!connectedareas[i]) return qfalse; } //end for return qtrue; } //end of the function AAS_ConnectedAreas //=========================================================================== // gets adjacent areas with less presence types recursively // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_GetAdjacentAreasWithLessPresenceTypes_r(int *areanums, int numareas, int curareanum) { int i, j, presencetype, otherpresencetype, otherareanum, facenum; aas_area_t *area; aas_face_t *face; areanums[numareas++] = curareanum; area = &aasworld.areas[curareanum]; presencetype = aasworld.areasettings[curareanum].presencetype; for (i = 0; i < area->numfaces; i++) { facenum = abs(aasworld.faceindex[area->firstface + i]); face = &aasworld.faces[facenum]; //if the face is solid if (face->faceflags & FACE_SOLID) continue; //the area at the other side of the face if (face->frontarea != curareanum) otherareanum = face->frontarea; else otherareanum = face->backarea; // otherpresencetype = aasworld.areasettings[otherareanum].presencetype; //if the other area has less presence types if ((presencetype & ~otherpresencetype) && !(otherpresencetype & ~presencetype)) { //check if the other area isn't already in the list for (j = 0; j < numareas; j++) { if (otherareanum == areanums[j]) break; } //end for //if the other area isn't already in the list if (j == numareas) { if (numareas >= MAX_PORTALAREAS) { AAS_Error("MAX_PORTALAREAS"); return numareas; } //end if numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, numareas, otherareanum); } //end if } //end if } //end for return numareas; } //end of the function AAS_GetAdjacentAreasWithLessPresenceTypes_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_CheckAreaForPossiblePortals(int areanum) { int i, j, k, fen, ben, frontedgenum, backedgenum, facenum; int areanums[MAX_PORTALAREAS], numareas, otherareanum; int numareafrontfaces[MAX_PORTALAREAS], numareabackfaces[MAX_PORTALAREAS]; int frontfacenums[MAX_PORTALAREAS], backfacenums[MAX_PORTALAREAS]; int numfrontfaces, numbackfaces; int frontareanums[MAX_PORTALAREAS], backareanums[MAX_PORTALAREAS]; int numfrontareas, numbackareas; int frontplanenum, backplanenum, faceplanenum; aas_area_t *area; aas_face_t *frontface, *backface, *face; //if it isn't already a portal if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return 0; //it must be a grounded area if (!(aasworld.areasettings[areanum].areaflags & AREA_GROUNDED)) return 0; // Com_Memset(numareafrontfaces, 0, sizeof(numareafrontfaces)); Com_Memset(numareabackfaces, 0, sizeof(numareabackfaces)); numareas = numfrontfaces = numbackfaces = 0; numfrontareas = numbackareas = 0; frontplanenum = backplanenum = -1; //add any adjacent areas with less presence types numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, 0, areanum); // for (i = 0; i < numareas; i++) { area = &aasworld.areas[areanums[i]]; for (j = 0; j < area->numfaces; j++) { facenum = abs(aasworld.faceindex[area->firstface + j]); face = &aasworld.faces[facenum]; //if the face is solid if (face->faceflags & FACE_SOLID) continue; //check if the face is shared with one of the other areas for (k = 0; k < numareas; k++) { if (k == i) continue; if (face->frontarea == areanums[k] || face->backarea == areanums[k]) break; } //end for //if the face is shared if (k != numareas) continue; //the number of the area at the other side of the face if (face->frontarea == areanums[i]) otherareanum = face->backarea; else otherareanum = face->frontarea; //if the other area already is a cluter portal if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) return 0; //number of the plane of the area faceplanenum = face->planenum & ~1; // if (frontplanenum < 0 || faceplanenum == frontplanenum) { frontplanenum = faceplanenum; frontfacenums[numfrontfaces++] = facenum; for (k = 0; k < numfrontareas; k++) { if (frontareanums[k] == otherareanum) break; } //end for if (k == numfrontareas) frontareanums[numfrontareas++] = otherareanum; numareafrontfaces[i]++; } //end if else if (backplanenum < 0 || faceplanenum == backplanenum) { backplanenum = faceplanenum; backfacenums[numbackfaces++] = facenum; for (k = 0; k < numbackareas; k++) { if (backareanums[k] == otherareanum) break; } //end for if (k == numbackareas) backareanums[numbackareas++] = otherareanum; numareabackfaces[i]++; } //end else else { return 0; } //end else } //end for } //end for //every area should have at least one front face and one back face for (i = 0; i < numareas; i++) { if (!numareafrontfaces[i] || !numareabackfaces[i]) return 0; } //end for //the front areas should all be connected if (!AAS_ConnectedAreas(frontareanums, numfrontareas)) return 0; //the back areas should all be connected if (!AAS_ConnectedAreas(backareanums, numbackareas)) return 0; //none of the front faces should have a shared edge with a back face for (i = 0; i < numfrontfaces; i++) { frontface = &aasworld.faces[frontfacenums[i]]; for (fen = 0; fen < frontface->numedges; fen++) { frontedgenum = abs(aasworld.edgeindex[frontface->firstedge + fen]); for (j = 0; j < numbackfaces; j++) { backface = &aasworld.faces[backfacenums[j]]; for (ben = 0; ben < backface->numedges; ben++) { backedgenum = abs(aasworld.edgeindex[backface->firstedge + ben]); if (frontedgenum == backedgenum) break; } //end for if (ben != backface->numedges) break; } //end for if (j != numbackfaces) break; } //end for if (fen != frontface->numedges) break; } //end for if (i != numfrontfaces) return 0; //set the cluster portal contents for (i = 0; i < numareas; i++) { aasworld.areasettings[areanums[i]].contents |= AREACONTENTS_CLUSTERPORTAL; //this area can be used as a route portal aasworld.areasettings[areanums[i]].contents |= AREACONTENTS_ROUTEPORTAL; Log_Write("possible portal: %d\r\n", areanums[i]); } //end for // return numareas; } //end of the function AAS_CheckAreaForPossiblePortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FindPossiblePortals(void) { int i, numpossibleportals; numpossibleportals = 0; for (i = 1; i < aasworld.numareas; i++) { numpossibleportals += AAS_CheckAreaForPossiblePortals(i); } //end for botimport.Print(PRT_MESSAGE, "\r%6d possible portal areas\n", numpossibleportals); } //end of the function AAS_FindPossiblePortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RemoveAllPortals(void) { int i; for (i = 1; i < aasworld.numareas; i++) { aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; } //end for } //end of the function AAS_RemoveAllPortals #if 0 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FloodCluster_r(int areanum, int clusternum) { int i, otherareanum; aas_face_t *face; aas_area_t *area; //set cluster mark aasworld.areasettings[areanum].cluster = clusternum; //if the area is a portal //if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return; // area = &aasworld.areas[areanum]; //use area faces to flood into adjacent areas for (i = 0; i < area->numfaces; i++) { face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; // if (face->frontarea != areanum) otherareanum = face->frontarea; else otherareanum = face->backarea; //if there's no area at the other side if (!otherareanum) continue; //if the area is a portal if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; //if the area is already marked if (aasworld.areasettings[otherareanum].cluster) continue; // AAS_FloodCluster_r(otherareanum, clusternum); } //end for //use the reachabilities to flood into other areas for (i = 0; i < aasworld.areasettings[areanum].numreachableareas; i++) { otherareanum = aasworld.reachability[ aasworld.areasettings[areanum].firstreachablearea + i].areanum; if (!otherareanum) { continue; AAS_Error("reachability %d has zero area\n", aasworld.areasettings[areanum].firstreachablearea + i); } //end if //if the area is a portal if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; //if the area is already marked if (aasworld.areasettings[otherareanum].cluster) continue; // AAS_FloodCluster_r(otherareanum, clusternum); } //end for } //end of the function AAS_FloodCluster_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RemoveTeleporterPortals(void) { int i, j, areanum; for (i = 1; i < aasworld.numareas; i++) { for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) { areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; if (aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].traveltype == TRAVEL_TELEPORT) { aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; aasworld.areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; break; } //end if } //end for } //end for } //end of the function AAS_RemoveTeleporterPortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FloodClusterReachabilities(int clusternum) { int i, j, areanum; for (i = 1; i < aasworld.numareas; i++) { //if this area already has a cluster set if (aasworld.areasettings[i].cluster) continue; //if this area is a cluster portal if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) continue; //loop over the reachable areas from this area for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) { //the reachable area areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; //if this area is a cluster portal if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; //if this area has a cluster set if (aasworld.areasettings[areanum].cluster == clusternum) { AAS_FloodCluster_r(i, clusternum); i = 0; break; } //end if } //end for } //end for } //end of the function AAS_FloodClusterReachabilities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RemoveNotClusterClosingPortals(void) { int i, j, k, facenum, otherareanum, nonclosingportals; aas_area_t *area; aas_face_t *face; AAS_RemoveTeleporterPortals(); // nonclosingportals = 0; for (i = 1; i < aasworld.numareas; i++) { if (!(aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; //find a non-portal area adjacent to the portal area and flood //the cluster from there area = &aasworld.areas[i]; for (j = 0; j < area->numfaces; j++) { facenum = abs(aasworld.faceindex[area->firstface + j]); face = &aasworld.faces[facenum]; // if (face->frontarea != i) otherareanum = face->frontarea; else otherareanum = face->backarea; // if (!otherareanum) continue; // if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) { continue; } //end if //reset all cluster fields AAS_RemoveClusterAreas(); // AAS_FloodCluster_r(otherareanum, 1); AAS_FloodClusterReachabilities(1); //check if all adjacent non-portal areas have a cluster set for (k = 0; k < area->numfaces; k++) { facenum = abs(aasworld.faceindex[area->firstface + k]); face = &aasworld.faces[facenum]; // if (face->frontarea != i) otherareanum = face->frontarea; else otherareanum = face->backarea; // if (!otherareanum) continue; // if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) { continue; } //end if // if (!aasworld.areasettings[otherareanum].cluster) break; } //end for //if all adjacent non-portal areas have a cluster set then the portal //didn't seal a cluster if (k >= area->numfaces) { aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; nonclosingportals++; //recheck all the other portals again i = 0; break; } //end if } //end for } //end for botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); } //end of the function AAS_RemoveNotClusterClosingPortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RemoveNotClusterClosingPortals(void) { int i, j, facenum, otherareanum, nonclosingportals, numseperatedclusters; aas_area_t *area; aas_face_t *face; AAS_RemoveTeleporterPortals(); // nonclosingportals = 0; for (i = 1; i < aasworld.numareas; i++) { if (!(aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; // numseperatedclusters = 0; //reset all cluster fields AAS_RemoveClusterAreas(); //find a non-portal area adjacent to the portal area and flood //the cluster from there area = &aasworld.areas[i]; for (j = 0; j < area->numfaces; j++) { facenum = abs(aasworld.faceindex[area->firstface + j]); face = &aasworld.faces[facenum]; // if (face->frontarea != i) otherareanum = face->frontarea; else otherareanum = face->backarea; //if not solid at the other side of the face if (!otherareanum) continue; //don't flood into other portals if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; //if the area already has a cluster set if (aasworld.areasettings[otherareanum].cluster) continue; //another cluster is seperated by this portal numseperatedclusters++; //flood the cluster AAS_FloodCluster_r(otherareanum, numseperatedclusters); AAS_FloodClusterReachabilities(numseperatedclusters); } //end for //use the reachabilities to flood into other areas for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) { otherareanum = aasworld.reachability[ aasworld.areasettings[i].firstreachablearea + j].areanum; //this should never be qtrue but we check anyway if (!otherareanum) continue; //don't flood into other portals if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; //if the area already has a cluster set if (aasworld.areasettings[otherareanum].cluster) continue; //another cluster is seperated by this portal numseperatedclusters++; //flood the cluster AAS_FloodCluster_r(otherareanum, numseperatedclusters); AAS_FloodClusterReachabilities(numseperatedclusters); } //end for //a portal must seperate no more and no less than 2 clusters if (numseperatedclusters != 2) { aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; nonclosingportals++; //recheck all the other portals again i = 0; } //end if } //end for botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); } //end of the function AAS_RemoveNotClusterClosingPortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_AddTeleporterPortals(void) { int j, area2num, facenum, otherareanum; char *target, *targetname, *classname; bsp_entity_t *entities, *ent, *dest; vec3_t origin, destorigin, mins, maxs, end; vec3_t bbmins, bbmaxs; aas_area_t *area; aas_face_t *face; aas_trace_t trace; aas_link_t *areas, *link; entities = AAS_ParseBSPEntities(); for (ent = entities; ent; ent = ent->next) { classname = AAS_ValueForBSPEpairKey(ent, "classname"); if (classname && !strcmp(classname, "misc_teleporter")) { if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) { botimport.Print(PRT_ERROR, "teleporter (%s) without origin\n", target); continue; } //end if // target = AAS_ValueForBSPEpairKey(ent, "target"); if (!target) { botimport.Print(PRT_ERROR, "teleporter (%s) without target\n", target); continue; } //end if for (dest = entities; dest; dest = dest->next) { classname = AAS_ValueForBSPEpairKey(dest, "classname"); if (classname && !strcmp(classname, "misc_teleporter_dest")) { targetname = AAS_ValueForBSPEpairKey(dest, "targetname"); if (targetname && !strcmp(targetname, target)) { break; } //end if } //end if } //end for if (!dest) { botimport.Print(PRT_ERROR, "teleporter without destination (%s)\n", target); continue; } //end if if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) { botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); continue; } //end if destorigin[2] += 24; //just for q2e1m2, the dork has put the telepads in the ground VectorCopy(destorigin, end); end[2] -= 100; trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); if (trace.startsolid) { botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); continue; } //end if VectorCopy(trace.endpos, destorigin); area2num = AAS_PointAreaNum(destorigin); //reset all cluster fields for (j = 0; j < aasworld.numareas; j++) { aasworld.areasettings[j].cluster = 0; } //end for // VectorSet(mins, -8, -8, 8); VectorSet(maxs, 8, 8, 24); // AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); // VectorAdd(origin, mins, mins); VectorAdd(origin, maxs, maxs); //add bounding box size VectorSubtract(mins, bbmaxs, mins); VectorSubtract(maxs, bbmins, maxs); //link an invalid (-1) entity areas = AAS_AASLinkEntity(mins, maxs, -1); // for (link = areas; link; link = link->next_area) { if (!AAS_AreaGrounded(link->areanum)) continue; //add the teleporter portal mark aasworld.areasettings[link->areanum].contents |= AREACONTENTS_CLUSTERPORTAL | AREACONTENTS_TELEPORTAL; } //end for // for (link = areas; link; link = link->next_area) { if (!AAS_AreaGrounded(link->areanum)) continue; //find a non-portal area adjacent to the portal area and flood //the cluster from there area = &aasworld.areas[link->areanum]; for (j = 0; j < area->numfaces; j++) { facenum = abs(aasworld.faceindex[area->firstface + j]); face = &aasworld.faces[facenum]; // if (face->frontarea != link->areanum) otherareanum = face->frontarea; else otherareanum = face->backarea; // if (!otherareanum) continue; // if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) { continue; } //end if // AAS_FloodCluster_r(otherareanum, 1); } //end for } //end for //if the teleport destination IS in the same cluster if (aasworld.areasettings[area2num].cluster) { for (link = areas; link; link = link->next_area) { if (!AAS_AreaGrounded(link->areanum)) continue; //add the teleporter portal mark aasworld.areasettings[link->areanum].contents &= ~(AREACONTENTS_CLUSTERPORTAL | AREACONTENTS_TELEPORTAL); } //end for } //end if } //end if } //end for AAS_FreeBSPEntities(entities); } //end of the function AAS_AddTeleporterPortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_AddTeleporterPortals(void) { int i, j, areanum; for (i = 1; i < aasworld.numareas; i++) { for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) { if (aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].traveltype != TRAVEL_TELEPORT) continue; areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; aasworld.areasettings[areanum].contents |= AREACONTENTS_CLUSTERPORTAL; } //end for } //end for } //end of the function AAS_AddTeleporterPortals #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_TestPortals(void) { int i; aas_portal_t *portal; for (i = 1; i < aasworld.numportals; i++) { portal = &aasworld.portals[i]; if (!portal->frontcluster) { aasworld.areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; Log_Write("portal area %d has no front cluster\r\n", portal->areanum); return qfalse; } //end if if (!portal->backcluster) { aasworld.areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; Log_Write("portal area %d has no back cluster\r\n", portal->areanum); return qfalse; } //end if } //end for return qtrue; } //end of the function //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CountForcedClusterPortals(void) { int num, i; num = 0; for (i = 1; i < aasworld.numareas; i++) { if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) { Log_Write("area %d is a forced portal area\r\n", i); num++; } //end if } //end for botimport.Print(PRT_MESSAGE, "%6d forced portal areas\n", num); } //end of the function AAS_CountForcedClusterPortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CreateViewPortals(void) { int i; for (i = 1; i < aasworld.numareas; i++) { if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) { aasworld.areasettings[i].contents |= AREACONTENTS_VIEWPORTAL; } //end if } //end for } //end of the function AAS_CreateViewPortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_SetViewPortalsAsClusterPortals(void) { int i; for (i = 1; i < aasworld.numareas; i++) { if (aasworld.areasettings[i].contents & AREACONTENTS_VIEWPORTAL) { aasworld.areasettings[i].contents |= AREACONTENTS_CLUSTERPORTAL; } //end if } //end for } //end of the function AAS_SetViewPortalsAsClusterPortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitClustering(void) { int i, removedPortalAreas; int n, total, numreachabilityareas; if (!aasworld.loaded) return; //if there are clusters if (aasworld.numclusters >= 1) { #ifndef BSPC //if clustering isn't forced if (!((int)LibVarGetValue("forceclustering")) && !((int)LibVarGetValue("forcereachability"))) return; #endif } //end if //set all view portals as cluster portals in case we re-calculate the reachabilities and clusters (with -reach) AAS_SetViewPortalsAsClusterPortals(); //count the number of forced cluster portals AAS_CountForcedClusterPortals(); //remove all area cluster marks AAS_RemoveClusterAreas(); //find possible cluster portals AAS_FindPossiblePortals(); //craete portals to for the bot view AAS_CreateViewPortals(); //remove all portals that are not closing a cluster //AAS_RemoveNotClusterClosingPortals(); //initialize portal memory if (aasworld.portals) FreeMemory(aasworld.portals); aasworld.portals = (aas_portal_t *) GetClearedMemory(AAS_MAX_PORTALS * sizeof(aas_portal_t)); //initialize portal index memory if (aasworld.portalindex) FreeMemory(aasworld.portalindex); aasworld.portalindex = (aas_portalindex_t *) GetClearedMemory(AAS_MAX_PORTALINDEXSIZE * sizeof(aas_portalindex_t)); //initialize cluster memory if (aasworld.clusters) FreeMemory(aasworld.clusters); aasworld.clusters = (aas_cluster_t *) GetClearedMemory(AAS_MAX_CLUSTERS * sizeof(aas_cluster_t)); // removedPortalAreas = 0; botimport.Print(PRT_MESSAGE, "\r%6d removed portal areas", removedPortalAreas); while(1) { botimport.Print(PRT_MESSAGE, "\r%6d", removedPortalAreas); //initialize the number of portals and clusters aasworld.numportals = 1; //portal 0 is a dummy aasworld.portalindexsize = 0; aasworld.numclusters = 1; //cluster 0 is a dummy //create the portals from the portal areas AAS_CreatePortals(); // removedPortalAreas++; //find the clusters if (!AAS_FindClusters()) continue; //test the portals if (!AAS_TestPortals()) continue; // break; } //end while botimport.Print(PRT_MESSAGE, "\n"); //the AAS file should be saved aasworld.savefile = qtrue; //write the portal areas to the log file for (i = 1; i < aasworld.numportals; i++) { Log_Write("portal %d: area %d\r\n", i, aasworld.portals[i].areanum); } //end for // report cluster info botimport.Print(PRT_MESSAGE, "%6d portals created\n", aasworld.numportals); botimport.Print(PRT_MESSAGE, "%6d clusters created\n", aasworld.numclusters); for (i = 1; i < aasworld.numclusters; i++) { botimport.Print(PRT_MESSAGE, "cluster %d has %d reachability areas\n", i, aasworld.clusters[i].numreachabilityareas); } //end for // report AAS file efficiency numreachabilityareas = 0; total = 0; for (i = 0; i < aasworld.numclusters; i++) { n = aasworld.clusters[i].numreachabilityareas; numreachabilityareas += n; total += n * n; } total += numreachabilityareas * aasworld.numportals; // botimport.Print(PRT_MESSAGE, "%6i total reachability areas\n", numreachabilityareas); botimport.Print(PRT_MESSAGE, "%6i AAS memory/CPU usage (the lower the better)\n", total * 3); } //end of the function AAS_InitClustering ================================================ FILE: code/botlib/be_aas_cluster.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_cluster.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_cluster.h $ * *****************************************************************************/ #ifdef AASINTERN //initialize the AAS clustering void AAS_InitClustering(void); // void AAS_SetViewPortalsAsClusterPortals(void); #endif //AASINTERN ================================================ FILE: code/botlib/be_aas_debug.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_debug.c * * desc: AAS debug code * * $Archive: /MissionPack/code/botlib/be_aas_debug.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_libvar.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_interface.h" #include "be_aas_funcs.h" #include "be_aas_def.h" #define MAX_DEBUGLINES 1024 #define MAX_DEBUGPOLYGONS 8192 int debuglines[MAX_DEBUGLINES]; int debuglinevisible[MAX_DEBUGLINES]; int numdebuglines; static int debugpolygons[MAX_DEBUGPOLYGONS]; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ClearShownPolygons(void) { int i; //* for (i = 0; i < MAX_DEBUGPOLYGONS; i++) { if (debugpolygons[i]) botimport.DebugPolygonDelete(debugpolygons[i]); debugpolygons[i] = 0; } //end for //*/ /* for (i = 0; i < MAX_DEBUGPOLYGONS; i++) { botimport.DebugPolygonDelete(i); debugpolygons[i] = 0; } //end for */ } //end of the function AAS_ClearShownPolygons //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShowPolygon(int color, int numpoints, vec3_t *points) { int i; for (i = 0; i < MAX_DEBUGPOLYGONS; i++) { if (!debugpolygons[i]) { debugpolygons[i] = botimport.DebugPolygonCreate(color, numpoints, points); break; } //end if } //end for } //end of the function AAS_ShowPolygon //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ClearShownDebugLines(void) { int i; //make all lines invisible for (i = 0; i < MAX_DEBUGLINES; i++) { if (debuglines[i]) { //botimport.DebugLineShow(debuglines[i], NULL, NULL, LINECOLOR_NONE); botimport.DebugLineDelete(debuglines[i]); debuglines[i] = 0; debuglinevisible[i] = qfalse; } //end if } //end for } //end of the function AAS_ClearShownDebugLines //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DebugLine(vec3_t start, vec3_t end, int color) { int line; for (line = 0; line < MAX_DEBUGLINES; line++) { if (!debuglines[line]) { debuglines[line] = botimport.DebugLineCreate(); debuglinevisible[line] = qfalse; numdebuglines++; } //end if if (!debuglinevisible[line]) { botimport.DebugLineShow(debuglines[line], start, end, color); debuglinevisible[line] = qtrue; return; } //end else } //end for } //end of the function AAS_DebugLine //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_PermanentLine(vec3_t start, vec3_t end, int color) { int line; line = botimport.DebugLineCreate(); botimport.DebugLineShow(line, start, end, color); } //end of the function AAS_PermenentLine //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DrawPermanentCross(vec3_t origin, float size, int color) { int i, debugline; vec3_t start, end; for (i = 0; i < 3; i++) { VectorCopy(origin, start); start[i] += size; VectorCopy(origin, end); end[i] -= size; AAS_DebugLine(start, end, color); debugline = botimport.DebugLineCreate(); botimport.DebugLineShow(debugline, start, end, color); } //end for } //end of the function AAS_DrawPermanentCross //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color) { int n0, n1, n2, j, line, lines[2]; vec3_t start1, end1, start2, end2; //make a cross in the hit plane at the hit point VectorCopy(point, start1); VectorCopy(point, end1); VectorCopy(point, start2); VectorCopy(point, end2); n0 = type % 3; n1 = (type + 1) % 3; n2 = (type + 2) % 3; start1[n1] -= 6; start1[n2] -= 6; end1[n1] += 6; end1[n2] += 6; start2[n1] += 6; start2[n2] -= 6; end2[n1] -= 6; end2[n2] += 6; start1[n0] = (dist - (start1[n1] * normal[n1] + start1[n2] * normal[n2])) / normal[n0]; end1[n0] = (dist - (end1[n1] * normal[n1] + end1[n2] * normal[n2])) / normal[n0]; start2[n0] = (dist - (start2[n1] * normal[n1] + start2[n2] * normal[n2])) / normal[n0]; end2[n0] = (dist - (end2[n1] * normal[n1] + end2[n2] * normal[n2])) / normal[n0]; for (j = 0, line = 0; j < 2 && line < MAX_DEBUGLINES; line++) { if (!debuglines[line]) { debuglines[line] = botimport.DebugLineCreate(); lines[j++] = debuglines[line]; debuglinevisible[line] = qtrue; numdebuglines++; } //end if else if (!debuglinevisible[line]) { lines[j++] = debuglines[line]; debuglinevisible[line] = qtrue; } //end else } //end for botimport.DebugLineShow(lines[0], start1, end1, color); botimport.DebugLineShow(lines[1], start2, end2, color); } //end of the function AAS_DrawPlaneCross //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs) { vec3_t bboxcorners[8]; int lines[3]; int i, j, line; //upper corners bboxcorners[0][0] = origin[0] + maxs[0]; bboxcorners[0][1] = origin[1] + maxs[1]; bboxcorners[0][2] = origin[2] + maxs[2]; // bboxcorners[1][0] = origin[0] + mins[0]; bboxcorners[1][1] = origin[1] + maxs[1]; bboxcorners[1][2] = origin[2] + maxs[2]; // bboxcorners[2][0] = origin[0] + mins[0]; bboxcorners[2][1] = origin[1] + mins[1]; bboxcorners[2][2] = origin[2] + maxs[2]; // bboxcorners[3][0] = origin[0] + maxs[0]; bboxcorners[3][1] = origin[1] + mins[1]; bboxcorners[3][2] = origin[2] + maxs[2]; //lower corners Com_Memcpy(bboxcorners[4], bboxcorners[0], sizeof(vec3_t) * 4); for (i = 0; i < 4; i++) bboxcorners[4 + i][2] = origin[2] + mins[2]; //draw bounding box for (i = 0; i < 4; i++) { for (j = 0, line = 0; j < 3 && line < MAX_DEBUGLINES; line++) { if (!debuglines[line]) { debuglines[line] = botimport.DebugLineCreate(); lines[j++] = debuglines[line]; debuglinevisible[line] = qtrue; numdebuglines++; } //end if else if (!debuglinevisible[line]) { lines[j++] = debuglines[line]; debuglinevisible[line] = qtrue; } //end else } //end for //top plane botimport.DebugLineShow(lines[0], bboxcorners[i], bboxcorners[(i+1)&3], LINECOLOR_RED); //bottom plane botimport.DebugLineShow(lines[1], bboxcorners[4+i], bboxcorners[4+((i+1)&3)], LINECOLOR_RED); //vertical lines botimport.DebugLineShow(lines[2], bboxcorners[i], bboxcorners[4+i], LINECOLOR_RED); } //end for } //end of the function AAS_ShowBoundingBox //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShowFace(int facenum) { int i, color, edgenum; aas_edge_t *edge; aas_face_t *face; aas_plane_t *plane; vec3_t start, end; color = LINECOLOR_YELLOW; //check if face number is in range if (facenum >= aasworld.numfaces) { botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); } //end if face = &aasworld.faces[facenum]; //walk through the edges of the face for (i = 0; i < face->numedges; i++) { //edge number edgenum = abs(aasworld.edgeindex[face->firstedge + i]); //check if edge number is in range if (edgenum >= aasworld.numedges) { botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); } //end if edge = &aasworld.edges[edgenum]; if (color == LINECOLOR_RED) color = LINECOLOR_GREEN; else if (color == LINECOLOR_GREEN) color = LINECOLOR_BLUE; else if (color == LINECOLOR_BLUE) color = LINECOLOR_YELLOW; else color = LINECOLOR_RED; AAS_DebugLine(aasworld.vertexes[edge->v[0]], aasworld.vertexes[edge->v[1]], color); } //end for plane = &aasworld.planes[face->planenum]; edgenum = abs(aasworld.edgeindex[face->firstedge]); edge = &aasworld.edges[edgenum]; VectorCopy(aasworld.vertexes[edge->v[0]], start); VectorMA(start, 20, plane->normal, end); AAS_DebugLine(start, end, LINECOLOR_RED); } //end of the function AAS_ShowFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShowFacePolygon(int facenum, int color, int flip) { int i, edgenum, numpoints; vec3_t points[128]; aas_edge_t *edge; aas_face_t *face; //check if face number is in range if (facenum >= aasworld.numfaces) { botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); } //end if face = &aasworld.faces[facenum]; //walk through the edges of the face numpoints = 0; if (flip) { for (i = face->numedges-1; i >= 0; i--) { //edge number edgenum = aasworld.edgeindex[face->firstedge + i]; edge = &aasworld.edges[abs(edgenum)]; VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]); numpoints++; } //end for } //end if else { for (i = 0; i < face->numedges; i++) { //edge number edgenum = aasworld.edgeindex[face->firstedge + i]; edge = &aasworld.edges[abs(edgenum)]; VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]); numpoints++; } //end for } //end else AAS_ShowPolygon(color, numpoints, points); } //end of the function AAS_ShowFacePolygon //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShowArea(int areanum, int groundfacesonly) { int areaedges[MAX_DEBUGLINES]; int numareaedges, i, j, n, color = 0, line; int facenum, edgenum; aas_area_t *area; aas_face_t *face; aas_edge_t *edge; // numareaedges = 0; // if (areanum < 0 || areanum >= aasworld.numareas) { botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", areanum, aasworld.numareas); return; } //end if //pointer to the convex area area = &aasworld.areas[areanum]; //walk through the faces of the area for (i = 0; i < area->numfaces; i++) { facenum = abs(aasworld.faceindex[area->firstface + i]); //check if face number is in range if (facenum >= aasworld.numfaces) { botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); } //end if face = &aasworld.faces[facenum]; //ground faces only if (groundfacesonly) { if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) continue; } //end if //walk through the edges of the face for (j = 0; j < face->numedges; j++) { //edge number edgenum = abs(aasworld.edgeindex[face->firstedge + j]); //check if edge number is in range if (edgenum >= aasworld.numedges) { botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); } //end if //check if the edge is stored already for (n = 0; n < numareaedges; n++) { if (areaedges[n] == edgenum) break; } //end for if (n == numareaedges && numareaedges < MAX_DEBUGLINES) { areaedges[numareaedges++] = edgenum; } //end if } //end for //AAS_ShowFace(facenum); } //end for //draw all the edges for (n = 0; n < numareaedges; n++) { for (line = 0; line < MAX_DEBUGLINES; line++) { if (!debuglines[line]) { debuglines[line] = botimport.DebugLineCreate(); debuglinevisible[line] = qfalse; numdebuglines++; } //end if if (!debuglinevisible[line]) { break; } //end else } //end for if (line >= MAX_DEBUGLINES) return; edge = &aasworld.edges[areaedges[n]]; if (color == LINECOLOR_RED) color = LINECOLOR_BLUE; else if (color == LINECOLOR_BLUE) color = LINECOLOR_GREEN; else if (color == LINECOLOR_GREEN) color = LINECOLOR_YELLOW; else color = LINECOLOR_RED; botimport.DebugLineShow(debuglines[line], aasworld.vertexes[edge->v[0]], aasworld.vertexes[edge->v[1]], color); debuglinevisible[line] = qtrue; } //end for*/ } //end of the function AAS_ShowArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly) { int i, facenum; aas_area_t *area; aas_face_t *face; // if (areanum < 0 || areanum >= aasworld.numareas) { botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", areanum, aasworld.numareas); return; } //end if //pointer to the convex area area = &aasworld.areas[areanum]; //walk through the faces of the area for (i = 0; i < area->numfaces; i++) { facenum = abs(aasworld.faceindex[area->firstface + i]); //check if face number is in range if (facenum >= aasworld.numfaces) { botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); } //end if face = &aasworld.faces[facenum]; //ground faces only if (groundfacesonly) { if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) continue; } //end if AAS_ShowFacePolygon(facenum, color, face->frontarea != areanum); } //end for } //end of the function AAS_ShowAreaPolygons //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DrawCross(vec3_t origin, float size, int color) { int i; vec3_t start, end; for (i = 0; i < 3; i++) { VectorCopy(origin, start); start[i] += size; VectorCopy(origin, end); end[i] -= size; AAS_DebugLine(start, end, color); } //end for } //end of the function AAS_DrawCross //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_PrintTravelType(int traveltype) { #ifdef DEBUG char *str; // switch(traveltype & TRAVELTYPE_MASK) { case TRAVEL_INVALID: str = "TRAVEL_INVALID"; break; case TRAVEL_WALK: str = "TRAVEL_WALK"; break; case TRAVEL_CROUCH: str = "TRAVEL_CROUCH"; break; case TRAVEL_BARRIERJUMP: str = "TRAVEL_BARRIERJUMP"; break; case TRAVEL_JUMP: str = "TRAVEL_JUMP"; break; case TRAVEL_LADDER: str = "TRAVEL_LADDER"; break; case TRAVEL_WALKOFFLEDGE: str = "TRAVEL_WALKOFFLEDGE"; break; case TRAVEL_SWIM: str = "TRAVEL_SWIM"; break; case TRAVEL_WATERJUMP: str = "TRAVEL_WATERJUMP"; break; case TRAVEL_TELEPORT: str = "TRAVEL_TELEPORT"; break; case TRAVEL_ELEVATOR: str = "TRAVEL_ELEVATOR"; break; case TRAVEL_ROCKETJUMP: str = "TRAVEL_ROCKETJUMP"; break; case TRAVEL_BFGJUMP: str = "TRAVEL_BFGJUMP"; break; case TRAVEL_GRAPPLEHOOK: str = "TRAVEL_GRAPPLEHOOK"; break; case TRAVEL_JUMPPAD: str = "TRAVEL_JUMPPAD"; break; case TRAVEL_FUNCBOB: str = "TRAVEL_FUNCBOB"; break; default: str = "UNKNOWN TRAVEL TYPE"; break; } //end switch botimport.Print(PRT_MESSAGE, "%s", str); #endif } //end of the function AAS_PrintTravelType //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor) { vec3_t dir, cross, p1, p2, up = {0, 0, 1}; float dot; VectorSubtract(end, start, dir); VectorNormalize(dir); dot = DotProduct(dir, up); if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0); else CrossProduct(dir, up, cross); VectorMA(end, -6, dir, p1); VectorCopy(p1, p2); VectorMA(p1, 6, cross, p1); VectorMA(p2, -6, cross, p2); AAS_DebugLine(start, end, linecolor); AAS_DebugLine(p1, end, arrowcolor); AAS_DebugLine(p2, end, arrowcolor); } //end of the function AAS_DrawArrow //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShowReachability(aas_reachability_t *reach) { vec3_t dir, cmdmove, velocity; float speed, zvel; aas_clientmove_t move; AAS_ShowAreaPolygons(reach->areanum, 5, qtrue); //AAS_ShowArea(reach->areanum, qtrue); AAS_DrawArrow(reach->start, reach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); // if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP || (reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE) { AAS_HorizontalVelocityForJump(aassettings.phys_jumpvel, reach->start, reach->end, &speed); // VectorSubtract(reach->end, reach->start, dir); dir[2] = 0; VectorNormalize(dir); //set the velocity VectorScale(dir, speed, velocity); //set the command movement VectorClear(cmdmove); cmdmove[2] = aassettings.phys_jumpvel; // AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, velocity, cmdmove, 3, 30, 0.1f, SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| SE_ENTERLAVA|SE_HITGROUNDDAMAGE, 0, qtrue); // if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) { AAS_JumpReachRunStart(reach, dir); AAS_DrawCross(dir, 4, LINECOLOR_BLUE); } //end if } //end if else if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) { zvel = AAS_RocketJumpZVelocity(reach->start); AAS_HorizontalVelocityForJump(zvel, reach->start, reach->end, &speed); // VectorSubtract(reach->end, reach->start, dir); dir[2] = 0; VectorNormalize(dir); //get command movement VectorScale(dir, speed, cmdmove); VectorSet(velocity, 0, 0, zvel); // AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, velocity, cmdmove, 30, 30, 0.1f, SE_ENTERWATER|SE_ENTERSLIME| SE_ENTERLAVA|SE_HITGROUNDDAMAGE| SE_TOUCHJUMPPAD|SE_HITGROUNDAREA, reach->areanum, qtrue); } //end else if else if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) { VectorSet(cmdmove, 0, 0, 0); // VectorSubtract(reach->end, reach->start, dir); dir[2] = 0; VectorNormalize(dir); //set the velocity //NOTE: the edgenum is the horizontal velocity VectorScale(dir, reach->edgenum, velocity); //NOTE: the facenum is the Z velocity velocity[2] = reach->facenum; // AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, velocity, cmdmove, 30, 30, 0.1f, SE_ENTERWATER|SE_ENTERSLIME| SE_ENTERLAVA|SE_HITGROUNDDAMAGE| SE_TOUCHJUMPPAD|SE_HITGROUNDAREA, reach->areanum, qtrue); } //end else if } //end of the function AAS_ShowReachability //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShowReachableAreas(int areanum) { aas_areasettings_t *settings; static aas_reachability_t reach; static int index, lastareanum; static float lasttime; if (areanum != lastareanum) { index = 0; lastareanum = areanum; } //end if settings = &aasworld.areasettings[areanum]; // if (!settings->numreachableareas) return; // if (index >= settings->numreachableareas) index = 0; // if (AAS_Time() - lasttime > 1.5) { Com_Memcpy(&reach, &aasworld.reachability[settings->firstreachablearea + index], sizeof(aas_reachability_t)); index++; lasttime = AAS_Time(); AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); botimport.Print(PRT_MESSAGE, "\n"); } //end if AAS_ShowReachability(&reach); } //end of the function ShowReachableAreas void AAS_FloodAreas_r(int areanum, int cluster, int *done) { int nextareanum, i, facenum; aas_area_t *area; aas_face_t *face; aas_areasettings_t *settings; aas_reachability_t *reach; AAS_ShowAreaPolygons(areanum, 1, qtrue); //pointer to the convex area area = &aasworld.areas[areanum]; settings = &aasworld.areasettings[areanum]; //walk through the faces of the area for (i = 0; i < area->numfaces; i++) { facenum = abs(aasworld.faceindex[area->firstface + i]); face = &aasworld.faces[facenum]; if (face->frontarea == areanum) nextareanum = face->backarea; else nextareanum = face->frontarea; if (!nextareanum) continue; if (done[nextareanum]) continue; done[nextareanum] = qtrue; if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL) continue; if (AAS_AreaCluster(nextareanum) != cluster) continue; AAS_FloodAreas_r(nextareanum, cluster, done); } //end for // for (i = 0; i < settings->numreachableareas; i++) { reach = &aasworld.reachability[settings->firstreachablearea + i]; nextareanum = reach->areanum; if (!nextareanum) continue; if (done[nextareanum]) continue; done[nextareanum] = qtrue; if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL) continue; if (AAS_AreaCluster(nextareanum) != cluster) continue; /* if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE) { AAS_DebugLine(reach->start, reach->end, 1); } */ AAS_FloodAreas_r(nextareanum, cluster, done); } } void AAS_FloodAreas(vec3_t origin) { int areanum, cluster, *done; done = (int *) GetClearedMemory(aasworld.numareas * sizeof(int)); areanum = AAS_PointAreaNum(origin); cluster = AAS_AreaCluster(areanum); AAS_FloodAreas_r(areanum, cluster, done); } ================================================ FILE: code/botlib/be_aas_debug.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_debug.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_debug.h $ * *****************************************************************************/ //clear the shown debug lines void AAS_ClearShownDebugLines(void); // void AAS_ClearShownPolygons(void); //show a debug line void AAS_DebugLine(vec3_t start, vec3_t end, int color); //show a permenent line void AAS_PermanentLine(vec3_t start, vec3_t end, int color); //show a permanent cross void AAS_DrawPermanentCross(vec3_t origin, float size, int color); //draw a cross in the plane void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color); //show a bounding box void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs); //show a face void AAS_ShowFace(int facenum); //show an area void AAS_ShowArea(int areanum, int groundfacesonly); // void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly); //draw a cros void AAS_DrawCross(vec3_t origin, float size, int color); //print the travel type void AAS_PrintTravelType(int traveltype); //draw an arrow void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor); //visualize the given reachability void AAS_ShowReachability(struct aas_reachability_s *reach); //show the reachable areas from the given area void AAS_ShowReachableAreas(int areanum); ================================================ FILE: code/botlib/be_aas_def.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_def.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_def.h $ * *****************************************************************************/ //debugging on #define AAS_DEBUG #define MAX_CLIENTS 64 #define MAX_MODELS 256 // these are sent over the net as 8 bits #define MAX_SOUNDS 256 // so they cannot be blindly increased #define MAX_CONFIGSTRINGS 1024 #define CS_SCORES 32 #define CS_MODELS (CS_SCORES+MAX_CLIENTS) #define CS_SOUNDS (CS_MODELS+MAX_MODELS) #define DF_AASENTNUMBER(x) (x - aasworld.entities) #define DF_NUMBERAASENT(x) (&aasworld.entities[x]) #define DF_AASENTCLIENT(x) (x - aasworld.entities - 1) #define DF_CLIENTAASENT(x) (&aasworld.entities[x + 1]) #ifndef MAX_PATH #define MAX_PATH MAX_QPATH #endif //string index (for model, sound and image index) typedef struct aas_stringindex_s { int numindexes; char **index; } aas_stringindex_t; //structure to link entities to areas and areas to entities typedef struct aas_link_s { int entnum; int areanum; struct aas_link_s *next_ent, *prev_ent; struct aas_link_s *next_area, *prev_area; } aas_link_t; //structure to link entities to leaves and leaves to entities typedef struct bsp_link_s { int entnum; int leafnum; struct bsp_link_s *next_ent, *prev_ent; struct bsp_link_s *next_leaf, *prev_leaf; } bsp_link_t; typedef struct bsp_entdata_s { vec3_t origin; vec3_t angles; vec3_t absmins; vec3_t absmaxs; int solid; int modelnum; } bsp_entdata_t; //entity typedef struct aas_entity_s { //entity info aas_entityinfo_t i; //links into the AAS areas aas_link_t *areas; //links into the BSP leaves bsp_link_t *leaves; } aas_entity_t; typedef struct aas_settings_s { vec3_t phys_gravitydirection; float phys_friction; float phys_stopspeed; float phys_gravity; float phys_waterfriction; float phys_watergravity; float phys_maxvelocity; float phys_maxwalkvelocity; float phys_maxcrouchvelocity; float phys_maxswimvelocity; float phys_walkaccelerate; float phys_airaccelerate; float phys_swimaccelerate; float phys_maxstep; float phys_maxsteepness; float phys_maxwaterjump; float phys_maxbarrier; float phys_jumpvel; float phys_falldelta5; float phys_falldelta10; float rs_waterjump; float rs_teleport; float rs_barrierjump; float rs_startcrouch; float rs_startgrapple; float rs_startwalkoffledge; float rs_startjump; float rs_rocketjump; float rs_bfgjump; float rs_jumppad; float rs_aircontrolledjumppad; float rs_funcbob; float rs_startelevator; float rs_falldamage5; float rs_falldamage10; float rs_maxfallheight; float rs_maxjumpfallheight; } aas_settings_t; #define CACHETYPE_PORTAL 0 #define CACHETYPE_AREA 1 //routing cache typedef struct aas_routingcache_s { byte type; //portal or area cache float time; //last time accessed or updated int size; //size of the routing cache int cluster; //cluster the cache is for int areanum; //area the cache is created for vec3_t origin; //origin within the area float starttraveltime; //travel time to start with int travelflags; //combinations of the travel flags struct aas_routingcache_s *prev, *next; struct aas_routingcache_s *time_prev, *time_next; unsigned char *reachabilities; //reachabilities used for routing unsigned short int traveltimes[1]; //travel time for every area (variable sized) } aas_routingcache_t; //fields for the routing algorithm typedef struct aas_routingupdate_s { int cluster; int areanum; //area number of the update vec3_t start; //start point the area was entered unsigned short int tmptraveltime; //temporary travel time unsigned short int *areatraveltimes; //travel times within the area qboolean inlist; //true if the update is in the list struct aas_routingupdate_s *next; struct aas_routingupdate_s *prev; } aas_routingupdate_t; //reversed reachability link typedef struct aas_reversedlink_s { int linknum; //the aas_areareachability_t int areanum; //reachable from this area struct aas_reversedlink_s *next; //next link } aas_reversedlink_t; //reversed area reachability typedef struct aas_reversedreachability_s { int numlinks; aas_reversedlink_t *first; } aas_reversedreachability_t; //areas a reachability goes through typedef struct aas_reachabilityareas_s { int firstarea, numareas; } aas_reachabilityareas_t; typedef struct aas_s { int loaded; //true when an AAS file is loaded int initialized; //true when AAS has been initialized int savefile; //set true when file should be saved int bspchecksum; //current time float time; int numframes; //name of the aas file char filename[MAX_PATH]; char mapname[MAX_PATH]; //bounding boxes int numbboxes; aas_bbox_t *bboxes; //vertexes int numvertexes; aas_vertex_t *vertexes; //planes int numplanes; aas_plane_t *planes; //edges int numedges; aas_edge_t *edges; //edge index int edgeindexsize; aas_edgeindex_t *edgeindex; //faces int numfaces; aas_face_t *faces; //face index int faceindexsize; aas_faceindex_t *faceindex; //convex areas int numareas; aas_area_t *areas; //convex area settings int numareasettings; aas_areasettings_t *areasettings; //reachablity list int reachabilitysize; aas_reachability_t *reachability; //nodes of the bsp tree int numnodes; aas_node_t *nodes; //cluster portals int numportals; aas_portal_t *portals; //cluster portal index int portalindexsize; aas_portalindex_t *portalindex; //clusters int numclusters; aas_cluster_t *clusters; // int numreachabilityareas; float reachabilitytime; //enities linked in the areas aas_link_t *linkheap; //heap with link structures int linkheapsize; //size of the link heap aas_link_t *freelinks; //first free link aas_link_t **arealinkedentities; //entities linked into areas //entities int maxentities; int maxclients; aas_entity_t *entities; //string indexes char *configstrings[MAX_CONFIGSTRINGS]; int indexessetup; //index to retrieve travel flag for a travel type int travelflagfortype[MAX_TRAVELTYPES]; //travel flags for each area based on contents int *areacontentstravelflags; //routing update aas_routingupdate_t *areaupdate; aas_routingupdate_t *portalupdate; //number of routing updates during a frame (reset every frame) int frameroutingupdates; //reversed reachability links aas_reversedreachability_t *reversedreachability; //travel times within the areas unsigned short ***areatraveltimes; //array of size numclusters with cluster cache aas_routingcache_t ***clusterareacache; aas_routingcache_t **portalcache; //cache list sorted on time aas_routingcache_t *oldestcache; // start of cache list sorted on time aas_routingcache_t *newestcache; // end of cache list sorted on time //maximum travel time through portal areas int *portalmaxtraveltimes; //areas the reachabilities go through int *reachabilityareaindex; aas_reachabilityareas_t *reachabilityareas; } aas_t; #define AASINTERN #ifndef BSPCINCLUDE #include "be_aas_main.h" #include "be_aas_entity.h" #include "be_aas_sample.h" #include "be_aas_cluster.h" #include "be_aas_reach.h" #include "be_aas_route.h" #include "be_aas_routealt.h" #include "be_aas_debug.h" #include "be_aas_file.h" #include "be_aas_optimize.h" #include "be_aas_bsp.h" #include "be_aas_move.h" #endif //BSPCINCLUDE ================================================ FILE: code/botlib/be_aas_entity.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_entity.c * * desc: AAS entities * * $Archive: /MissionPack/code/botlib/be_aas_entity.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_utils.h" #include "l_log.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "be_aas_def.h" #define MASK_SOLID CONTENTS_PLAYERCLIP //FIXME: these might change enum { ET_GENERAL, ET_PLAYER, ET_ITEM, ET_MISSILE, ET_MOVER }; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_UpdateEntity(int entnum, bot_entitystate_t *state) { int relink; aas_entity_t *ent; vec3_t absmins, absmaxs; if (!aasworld.loaded) { botimport.Print(PRT_MESSAGE, "AAS_UpdateEntity: not loaded\n"); return BLERR_NOAASFILE; } //end if ent = &aasworld.entities[entnum]; if (!state) { //unlink the entity AAS_UnlinkFromAreas(ent->areas); //unlink the entity from the BSP leaves AAS_UnlinkFromBSPLeaves(ent->leaves); // ent->areas = NULL; // ent->leaves = NULL; return BLERR_NOERROR; } ent->i.update_time = AAS_Time() - ent->i.ltime; ent->i.type = state->type; ent->i.flags = state->flags; ent->i.ltime = AAS_Time(); VectorCopy(ent->i.origin, ent->i.lastvisorigin); VectorCopy(state->old_origin, ent->i.old_origin); ent->i.solid = state->solid; ent->i.groundent = state->groundent; ent->i.modelindex = state->modelindex; ent->i.modelindex2 = state->modelindex2; ent->i.frame = state->frame; ent->i.event = state->event; ent->i.eventParm = state->eventParm; ent->i.powerups = state->powerups; ent->i.weapon = state->weapon; ent->i.legsAnim = state->legsAnim; ent->i.torsoAnim = state->torsoAnim; //number of the entity ent->i.number = entnum; //updated so set valid flag ent->i.valid = qtrue; //link everything the first frame if (aasworld.numframes == 1) relink = qtrue; else relink = qfalse; // if (ent->i.solid == SOLID_BSP) { //if the angles of the model changed if (!VectorCompare(state->angles, ent->i.angles)) { VectorCopy(state->angles, ent->i.angles); relink = qtrue; } //end if //get the mins and maxs of the model //FIXME: rotate mins and maxs AAS_BSPModelMinsMaxsOrigin(ent->i.modelindex, ent->i.angles, ent->i.mins, ent->i.maxs, NULL); } //end if else if (ent->i.solid == SOLID_BBOX) { //if the bounding box size changed if (!VectorCompare(state->mins, ent->i.mins) || !VectorCompare(state->maxs, ent->i.maxs)) { VectorCopy(state->mins, ent->i.mins); VectorCopy(state->maxs, ent->i.maxs); relink = qtrue; } //end if VectorCopy(state->angles, ent->i.angles); } //end if //if the origin changed if (!VectorCompare(state->origin, ent->i.origin)) { VectorCopy(state->origin, ent->i.origin); relink = qtrue; } //end if //if the entity should be relinked if (relink) { //don't link the world model if (entnum != ENTITYNUM_WORLD) { //absolute mins and maxs VectorAdd(ent->i.mins, ent->i.origin, absmins); VectorAdd(ent->i.maxs, ent->i.origin, absmaxs); //unlink the entity AAS_UnlinkFromAreas(ent->areas); //relink the entity to the AAS areas (use the larges bbox) ent->areas = AAS_LinkEntityClientBBox(absmins, absmaxs, entnum, PRESENCE_NORMAL); //unlink the entity from the BSP leaves AAS_UnlinkFromBSPLeaves(ent->leaves); //link the entity to the world BSP tree ent->leaves = AAS_BSPLinkEntity(absmins, absmaxs, entnum, 0); } //end if } //end if return BLERR_NOERROR; } //end of the function AAS_UpdateEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_EntityInfo(int entnum, aas_entityinfo_t *info) { if (!aasworld.initialized) { botimport.Print(PRT_FATAL, "AAS_EntityInfo: aasworld not initialized\n"); Com_Memset(info, 0, sizeof(aas_entityinfo_t)); return; } //end if if (entnum < 0 || entnum >= aasworld.maxentities) { botimport.Print(PRT_FATAL, "AAS_EntityInfo: entnum %d out of range\n", entnum); Com_Memset(info, 0, sizeof(aas_entityinfo_t)); return; } //end if Com_Memcpy(info, &aasworld.entities[entnum].i, sizeof(aas_entityinfo_t)); } //end of the function AAS_EntityInfo //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_EntityOrigin(int entnum, vec3_t origin) { if (entnum < 0 || entnum >= aasworld.maxentities) { botimport.Print(PRT_FATAL, "AAS_EntityOrigin: entnum %d out of range\n", entnum); VectorClear(origin); return; } //end if VectorCopy(aasworld.entities[entnum].i.origin, origin); } //end of the function AAS_EntityOrigin //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_EntityModelindex(int entnum) { if (entnum < 0 || entnum >= aasworld.maxentities) { botimport.Print(PRT_FATAL, "AAS_EntityModelindex: entnum %d out of range\n", entnum); return 0; } //end if return aasworld.entities[entnum].i.modelindex; } //end of the function AAS_EntityModelindex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_EntityType(int entnum) { if (!aasworld.initialized) return 0; if (entnum < 0 || entnum >= aasworld.maxentities) { botimport.Print(PRT_FATAL, "AAS_EntityType: entnum %d out of range\n", entnum); return 0; } //end if return aasworld.entities[entnum].i.type; } //end of the AAS_EntityType //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_EntityModelNum(int entnum) { if (!aasworld.initialized) return 0; if (entnum < 0 || entnum >= aasworld.maxentities) { botimport.Print(PRT_FATAL, "AAS_EntityModelNum: entnum %d out of range\n", entnum); return 0; } //end if return aasworld.entities[entnum].i.modelindex; } //end of the function AAS_EntityModelNum //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_OriginOfMoverWithModelNum(int modelnum, vec3_t origin) { int i; aas_entity_t *ent; for (i = 0; i < aasworld.maxentities; i++) { ent = &aasworld.entities[i]; if (ent->i.type == ET_MOVER) { if (ent->i.modelindex == modelnum) { VectorCopy(ent->i.origin, origin); return qtrue; } //end if } //end if } //end for return qfalse; } //end of the function AAS_OriginOfMoverWithModelNum //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs) { aas_entity_t *ent; if (!aasworld.initialized) return; if (entnum < 0 || entnum >= aasworld.maxentities) { botimport.Print(PRT_FATAL, "AAS_EntitySize: entnum %d out of range\n", entnum); return; } //end if ent = &aasworld.entities[entnum]; VectorCopy(ent->i.mins, mins); VectorCopy(ent->i.maxs, maxs); } //end of the function AAS_EntitySize //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata) { aas_entity_t *ent; ent = &aasworld.entities[entnum]; VectorCopy(ent->i.origin, entdata->origin); VectorCopy(ent->i.angles, entdata->angles); VectorAdd(ent->i.origin, ent->i.mins, entdata->absmins); VectorAdd(ent->i.origin, ent->i.maxs, entdata->absmaxs); entdata->solid = ent->i.solid; entdata->modelnum = ent->i.modelindex - 1; } //end of the function AAS_EntityBSPData //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ResetEntityLinks(void) { int i; for (i = 0; i < aasworld.maxentities; i++) { aasworld.entities[i].areas = NULL; aasworld.entities[i].leaves = NULL; } //end for } //end of the function AAS_ResetEntityLinks //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InvalidateEntities(void) { int i; for (i = 0; i < aasworld.maxentities; i++) { aasworld.entities[i].i.valid = qfalse; aasworld.entities[i].i.number = i; } //end for } //end of the function AAS_InvalidateEntities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_UnlinkInvalidEntities(void) { int i; aas_entity_t *ent; for (i = 0; i < aasworld.maxentities; i++) { ent = &aasworld.entities[i]; if (!ent->i.valid) { AAS_UnlinkFromAreas( ent->areas ); ent->areas = NULL; AAS_UnlinkFromBSPLeaves( ent->leaves ); ent->leaves = NULL; } //end for } //end for } //end of the function AAS_UnlinkInvalidEntities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_NearestEntity(vec3_t origin, int modelindex) { int i, bestentnum; float dist, bestdist; aas_entity_t *ent; vec3_t dir; bestentnum = 0; bestdist = 99999; for (i = 0; i < aasworld.maxentities; i++) { ent = &aasworld.entities[i]; if (ent->i.modelindex != modelindex) continue; VectorSubtract(ent->i.origin, origin, dir); if (abs(dir[0]) < 40) { if (abs(dir[1]) < 40) { dist = VectorLength(dir); if (dist < bestdist) { bestdist = dist; bestentnum = i; } //end if } //end if } //end if } //end for return bestentnum; } //end of the function AAS_NearestEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_BestReachableEntityArea(int entnum) { aas_entity_t *ent; ent = &aasworld.entities[entnum]; return AAS_BestReachableLinkArea(ent->areas); } //end of the function AAS_BestReachableEntityArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_NextEntity(int entnum) { if (!aasworld.loaded) return 0; if (entnum < 0) entnum = -1; while(++entnum < aasworld.maxentities) { if (aasworld.entities[entnum].i.valid) return entnum; } //end while return 0; } //end of the function AAS_NextEntity ================================================ FILE: code/botlib/be_aas_entity.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_entity.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_entity.h $ * *****************************************************************************/ #ifdef AASINTERN //invalidates all entity infos void AAS_InvalidateEntities(void); //unlink not updated entities void AAS_UnlinkInvalidEntities(void); //resets the entity AAS and BSP links (sets areas and leaves pointers to NULL) void AAS_ResetEntityLinks(void); //updates an entity int AAS_UpdateEntity(int ent, bot_entitystate_t *state); //gives the entity data used for collision detection void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata); #endif //AASINTERN //returns the size of the entity bounding box in mins and maxs void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs); //returns the BSP model number of the entity int AAS_EntityModelNum(int entnum); //returns the origin of an entity with the given model number int AAS_OriginOfMoverWithModelNum(int modelnum, vec3_t origin); //returns the best reachable area the entity is situated in int AAS_BestReachableEntityArea(int entnum); //returns the info of the given entity void AAS_EntityInfo(int entnum, aas_entityinfo_t *info); //returns the next entity int AAS_NextEntity(int entnum); //returns the origin of the entity void AAS_EntityOrigin(int entnum, vec3_t origin); //returns the entity type int AAS_EntityType(int entnum); //returns the model index of the entity int AAS_EntityModelindex(int entnum); ================================================ FILE: code/botlib/be_aas_file.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_file.c * * desc: AAS file loading/writing * * $Archive: /MissionPack/code/botlib/be_aas_file.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_libvar.h" #include "l_utils.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "be_aas_def.h" //#define AASFILEDEBUG //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_SwapAASData(void) { int i, j; //bounding boxes for (i = 0; i < aasworld.numbboxes; i++) { aasworld.bboxes[i].presencetype = LittleLong(aasworld.bboxes[i].presencetype); aasworld.bboxes[i].flags = LittleLong(aasworld.bboxes[i].flags); for (j = 0; j < 3; j++) { aasworld.bboxes[i].mins[j] = LittleLong(aasworld.bboxes[i].mins[j]); aasworld.bboxes[i].maxs[j] = LittleLong(aasworld.bboxes[i].maxs[j]); } //end for } //end for //vertexes for (i = 0; i < aasworld.numvertexes; i++) { for (j = 0; j < 3; j++) aasworld.vertexes[i][j] = LittleFloat(aasworld.vertexes[i][j]); } //end for //planes for (i = 0; i < aasworld.numplanes; i++) { for (j = 0; j < 3; j++) aasworld.planes[i].normal[j] = LittleFloat(aasworld.planes[i].normal[j]); aasworld.planes[i].dist = LittleFloat(aasworld.planes[i].dist); aasworld.planes[i].type = LittleLong(aasworld.planes[i].type); } //end for //edges for (i = 0; i < aasworld.numedges; i++) { aasworld.edges[i].v[0] = LittleLong(aasworld.edges[i].v[0]); aasworld.edges[i].v[1] = LittleLong(aasworld.edges[i].v[1]); } //end for //edgeindex for (i = 0; i < aasworld.edgeindexsize; i++) { aasworld.edgeindex[i] = LittleLong(aasworld.edgeindex[i]); } //end for //faces for (i = 0; i < aasworld.numfaces; i++) { aasworld.faces[i].planenum = LittleLong(aasworld.faces[i].planenum); aasworld.faces[i].faceflags = LittleLong(aasworld.faces[i].faceflags); aasworld.faces[i].numedges = LittleLong(aasworld.faces[i].numedges); aasworld.faces[i].firstedge = LittleLong(aasworld.faces[i].firstedge); aasworld.faces[i].frontarea = LittleLong(aasworld.faces[i].frontarea); aasworld.faces[i].backarea = LittleLong(aasworld.faces[i].backarea); } //end for //face index for (i = 0; i < aasworld.faceindexsize; i++) { aasworld.faceindex[i] = LittleLong(aasworld.faceindex[i]); } //end for //convex areas for (i = 0; i < aasworld.numareas; i++) { aasworld.areas[i].areanum = LittleLong(aasworld.areas[i].areanum); aasworld.areas[i].numfaces = LittleLong(aasworld.areas[i].numfaces); aasworld.areas[i].firstface = LittleLong(aasworld.areas[i].firstface); for (j = 0; j < 3; j++) { aasworld.areas[i].mins[j] = LittleFloat(aasworld.areas[i].mins[j]); aasworld.areas[i].maxs[j] = LittleFloat(aasworld.areas[i].maxs[j]); aasworld.areas[i].center[j] = LittleFloat(aasworld.areas[i].center[j]); } //end for } //end for //area settings for (i = 0; i < aasworld.numareasettings; i++) { aasworld.areasettings[i].contents = LittleLong(aasworld.areasettings[i].contents); aasworld.areasettings[i].areaflags = LittleLong(aasworld.areasettings[i].areaflags); aasworld.areasettings[i].presencetype = LittleLong(aasworld.areasettings[i].presencetype); aasworld.areasettings[i].cluster = LittleLong(aasworld.areasettings[i].cluster); aasworld.areasettings[i].clusterareanum = LittleLong(aasworld.areasettings[i].clusterareanum); aasworld.areasettings[i].numreachableareas = LittleLong(aasworld.areasettings[i].numreachableareas); aasworld.areasettings[i].firstreachablearea = LittleLong(aasworld.areasettings[i].firstreachablearea); } //end for //area reachability for (i = 0; i < aasworld.reachabilitysize; i++) { aasworld.reachability[i].areanum = LittleLong(aasworld.reachability[i].areanum); aasworld.reachability[i].facenum = LittleLong(aasworld.reachability[i].facenum); aasworld.reachability[i].edgenum = LittleLong(aasworld.reachability[i].edgenum); for (j = 0; j < 3; j++) { aasworld.reachability[i].start[j] = LittleFloat(aasworld.reachability[i].start[j]); aasworld.reachability[i].end[j] = LittleFloat(aasworld.reachability[i].end[j]); } //end for aasworld.reachability[i].traveltype = LittleLong(aasworld.reachability[i].traveltype); aasworld.reachability[i].traveltime = LittleShort(aasworld.reachability[i].traveltime); } //end for //nodes for (i = 0; i < aasworld.numnodes; i++) { aasworld.nodes[i].planenum = LittleLong(aasworld.nodes[i].planenum); aasworld.nodes[i].children[0] = LittleLong(aasworld.nodes[i].children[0]); aasworld.nodes[i].children[1] = LittleLong(aasworld.nodes[i].children[1]); } //end for //cluster portals for (i = 0; i < aasworld.numportals; i++) { aasworld.portals[i].areanum = LittleLong(aasworld.portals[i].areanum); aasworld.portals[i].frontcluster = LittleLong(aasworld.portals[i].frontcluster); aasworld.portals[i].backcluster = LittleLong(aasworld.portals[i].backcluster); aasworld.portals[i].clusterareanum[0] = LittleLong(aasworld.portals[i].clusterareanum[0]); aasworld.portals[i].clusterareanum[1] = LittleLong(aasworld.portals[i].clusterareanum[1]); } //end for //cluster portal index for (i = 0; i < aasworld.portalindexsize; i++) { aasworld.portalindex[i] = LittleLong(aasworld.portalindex[i]); } //end for //cluster for (i = 0; i < aasworld.numclusters; i++) { aasworld.clusters[i].numareas = LittleLong(aasworld.clusters[i].numareas); aasworld.clusters[i].numreachabilityareas = LittleLong(aasworld.clusters[i].numreachabilityareas); aasworld.clusters[i].numportals = LittleLong(aasworld.clusters[i].numportals); aasworld.clusters[i].firstportal = LittleLong(aasworld.clusters[i].firstportal); } //end for } //end of the function AAS_SwapAASData //=========================================================================== // dump the current loaded aas file // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DumpAASData(void) { aasworld.numbboxes = 0; if (aasworld.bboxes) FreeMemory(aasworld.bboxes); aasworld.bboxes = NULL; aasworld.numvertexes = 0; if (aasworld.vertexes) FreeMemory(aasworld.vertexes); aasworld.vertexes = NULL; aasworld.numplanes = 0; if (aasworld.planes) FreeMemory(aasworld.planes); aasworld.planes = NULL; aasworld.numedges = 0; if (aasworld.edges) FreeMemory(aasworld.edges); aasworld.edges = NULL; aasworld.edgeindexsize = 0; if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); aasworld.edgeindex = NULL; aasworld.numfaces = 0; if (aasworld.faces) FreeMemory(aasworld.faces); aasworld.faces = NULL; aasworld.faceindexsize = 0; if (aasworld.faceindex) FreeMemory(aasworld.faceindex); aasworld.faceindex = NULL; aasworld.numareas = 0; if (aasworld.areas) FreeMemory(aasworld.areas); aasworld.areas = NULL; aasworld.numareasettings = 0; if (aasworld.areasettings) FreeMemory(aasworld.areasettings); aasworld.areasettings = NULL; aasworld.reachabilitysize = 0; if (aasworld.reachability) FreeMemory(aasworld.reachability); aasworld.reachability = NULL; aasworld.numnodes = 0; if (aasworld.nodes) FreeMemory(aasworld.nodes); aasworld.nodes = NULL; aasworld.numportals = 0; if (aasworld.portals) FreeMemory(aasworld.portals); aasworld.portals = NULL; aasworld.numportals = 0; if (aasworld.portalindex) FreeMemory(aasworld.portalindex); aasworld.portalindex = NULL; aasworld.portalindexsize = 0; if (aasworld.clusters) FreeMemory(aasworld.clusters); aasworld.clusters = NULL; aasworld.numclusters = 0; // aasworld.loaded = qfalse; aasworld.initialized = qfalse; aasworld.savefile = qfalse; } //end of the function AAS_DumpAASData //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef AASFILEDEBUG void AAS_FileInfo(void) { int i, n, optimized; botimport.Print(PRT_MESSAGE, "version = %d\n", AASVERSION); botimport.Print(PRT_MESSAGE, "numvertexes = %d\n", aasworld.numvertexes); botimport.Print(PRT_MESSAGE, "numplanes = %d\n", aasworld.numplanes); botimport.Print(PRT_MESSAGE, "numedges = %d\n", aasworld.numedges); botimport.Print(PRT_MESSAGE, "edgeindexsize = %d\n", aasworld.edgeindexsize); botimport.Print(PRT_MESSAGE, "numfaces = %d\n", aasworld.numfaces); botimport.Print(PRT_MESSAGE, "faceindexsize = %d\n", aasworld.faceindexsize); botimport.Print(PRT_MESSAGE, "numareas = %d\n", aasworld.numareas); botimport.Print(PRT_MESSAGE, "numareasettings = %d\n", aasworld.numareasettings); botimport.Print(PRT_MESSAGE, "reachabilitysize = %d\n", aasworld.reachabilitysize); botimport.Print(PRT_MESSAGE, "numnodes = %d\n", aasworld.numnodes); botimport.Print(PRT_MESSAGE, "numportals = %d\n", aasworld.numportals); botimport.Print(PRT_MESSAGE, "portalindexsize = %d\n", aasworld.portalindexsize); botimport.Print(PRT_MESSAGE, "numclusters = %d\n", aasworld.numclusters); // for (n = 0, i = 0; i < aasworld.numareasettings; i++) { if (aasworld.areasettings[i].areaflags & AREA_GROUNDED) n++; } //end for botimport.Print(PRT_MESSAGE, "num grounded areas = %d\n", n); // botimport.Print(PRT_MESSAGE, "planes size %d bytes\n", aasworld.numplanes * sizeof(aas_plane_t)); botimport.Print(PRT_MESSAGE, "areas size %d bytes\n", aasworld.numareas * sizeof(aas_area_t)); botimport.Print(PRT_MESSAGE, "areasettings size %d bytes\n", aasworld.numareasettings * sizeof(aas_areasettings_t)); botimport.Print(PRT_MESSAGE, "nodes size %d bytes\n", aasworld.numnodes * sizeof(aas_node_t)); botimport.Print(PRT_MESSAGE, "reachability size %d bytes\n", aasworld.reachabilitysize * sizeof(aas_reachability_t)); botimport.Print(PRT_MESSAGE, "portals size %d bytes\n", aasworld.numportals * sizeof(aas_portal_t)); botimport.Print(PRT_MESSAGE, "clusters size %d bytes\n", aasworld.numclusters * sizeof(aas_cluster_t)); optimized = aasworld.numplanes * sizeof(aas_plane_t) + aasworld.numareas * sizeof(aas_area_t) + aasworld.numareasettings * sizeof(aas_areasettings_t) + aasworld.numnodes * sizeof(aas_node_t) + aasworld.reachabilitysize * sizeof(aas_reachability_t) + aasworld.numportals * sizeof(aas_portal_t) + aasworld.numclusters * sizeof(aas_cluster_t); botimport.Print(PRT_MESSAGE, "optimzed size %d KB\n", optimized >> 10); } //end of the function AAS_FileInfo #endif //AASFILEDEBUG //=========================================================================== // allocate memory and read a lump of a AAS file // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *AAS_LoadAASLump(fileHandle_t fp, int offset, int length, int *lastoffset, int size) { char *buf; // if (!length) { //just alloc a dummy return (char *) GetClearedHunkMemory(size+1); } //end if //seek to the data if (offset != *lastoffset) { botimport.Print(PRT_WARNING, "AAS file not sequentially read\n"); if (botimport.FS_Seek(fp, offset, FS_SEEK_SET)) { AAS_Error("can't seek to aas lump\n"); AAS_DumpAASData(); botimport.FS_FCloseFile(fp); return 0; } //end if } //end if //allocate memory buf = (char *) GetClearedHunkMemory(length+1); //read the data if (length) { botimport.FS_Read(buf, length, fp ); *lastoffset += length; } //end if return buf; } //end of the function AAS_LoadAASLump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DData(unsigned char *data, int size) { int i; for (i = 0; i < size; i++) { data[i] ^= (unsigned char) i * 119; } //end for } //end of the function AAS_DData //=========================================================================== // load an aas file // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_LoadAASFile(char *filename) { fileHandle_t fp; aas_header_t header; int offset, length, lastoffset; botimport.Print(PRT_MESSAGE, "trying to load %s\n", filename); //dump current loaded aas file AAS_DumpAASData(); //open the file botimport.FS_FOpenFile( filename, &fp, FS_READ ); if (!fp) { AAS_Error("can't open %s\n", filename); return BLERR_CANNOTOPENAASFILE; } //end if //read the header botimport.FS_Read(&header, sizeof(aas_header_t), fp ); lastoffset = sizeof(aas_header_t); //check header identification header.ident = LittleLong(header.ident); if (header.ident != AASID) { AAS_Error("%s is not an AAS file\n", filename); botimport.FS_FCloseFile(fp); return BLERR_WRONGAASFILEID; } //end if //check the version header.version = LittleLong(header.version); // if (header.version != AASVERSION_OLD && header.version != AASVERSION) { AAS_Error("aas file %s is version %i, not %i\n", filename, header.version, AASVERSION); botimport.FS_FCloseFile(fp); return BLERR_WRONGAASFILEVERSION; } //end if // if (header.version == AASVERSION) { AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); } //end if // aasworld.bspchecksum = atoi(LibVarGetString( "sv_mapChecksum")); if (LittleLong(header.bspchecksum) != aasworld.bspchecksum) { AAS_Error("aas file %s is out of date\n", filename); botimport.FS_FCloseFile(fp); return BLERR_WRONGAASFILEVERSION; } //end if //load the lumps: //bounding boxes offset = LittleLong(header.lumps[AASLUMP_BBOXES].fileofs); length = LittleLong(header.lumps[AASLUMP_BBOXES].filelen); aasworld.bboxes = (aas_bbox_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_bbox_t)); aasworld.numbboxes = length / sizeof(aas_bbox_t); if (aasworld.numbboxes && !aasworld.bboxes) return BLERR_CANNOTREADAASLUMP; //vertexes offset = LittleLong(header.lumps[AASLUMP_VERTEXES].fileofs); length = LittleLong(header.lumps[AASLUMP_VERTEXES].filelen); aasworld.vertexes = (aas_vertex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_vertex_t)); aasworld.numvertexes = length / sizeof(aas_vertex_t); if (aasworld.numvertexes && !aasworld.vertexes) return BLERR_CANNOTREADAASLUMP; //planes offset = LittleLong(header.lumps[AASLUMP_PLANES].fileofs); length = LittleLong(header.lumps[AASLUMP_PLANES].filelen); aasworld.planes = (aas_plane_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_plane_t)); aasworld.numplanes = length / sizeof(aas_plane_t); if (aasworld.numplanes && !aasworld.planes) return BLERR_CANNOTREADAASLUMP; //edges offset = LittleLong(header.lumps[AASLUMP_EDGES].fileofs); length = LittleLong(header.lumps[AASLUMP_EDGES].filelen); aasworld.edges = (aas_edge_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_edge_t)); aasworld.numedges = length / sizeof(aas_edge_t); if (aasworld.numedges && !aasworld.edges) return BLERR_CANNOTREADAASLUMP; //edgeindex offset = LittleLong(header.lumps[AASLUMP_EDGEINDEX].fileofs); length = LittleLong(header.lumps[AASLUMP_EDGEINDEX].filelen); aasworld.edgeindex = (aas_edgeindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_edgeindex_t)); aasworld.edgeindexsize = length / sizeof(aas_edgeindex_t); if (aasworld.edgeindexsize && !aasworld.edgeindex) return BLERR_CANNOTREADAASLUMP; //faces offset = LittleLong(header.lumps[AASLUMP_FACES].fileofs); length = LittleLong(header.lumps[AASLUMP_FACES].filelen); aasworld.faces = (aas_face_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_face_t)); aasworld.numfaces = length / sizeof(aas_face_t); if (aasworld.numfaces && !aasworld.faces) return BLERR_CANNOTREADAASLUMP; //faceindex offset = LittleLong(header.lumps[AASLUMP_FACEINDEX].fileofs); length = LittleLong(header.lumps[AASLUMP_FACEINDEX].filelen); aasworld.faceindex = (aas_faceindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_faceindex_t)); aasworld.faceindexsize = length / sizeof(aas_faceindex_t); if (aasworld.faceindexsize && !aasworld.faceindex) return BLERR_CANNOTREADAASLUMP; //convex areas offset = LittleLong(header.lumps[AASLUMP_AREAS].fileofs); length = LittleLong(header.lumps[AASLUMP_AREAS].filelen); aasworld.areas = (aas_area_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_area_t)); aasworld.numareas = length / sizeof(aas_area_t); if (aasworld.numareas && !aasworld.areas) return BLERR_CANNOTREADAASLUMP; //area settings offset = LittleLong(header.lumps[AASLUMP_AREASETTINGS].fileofs); length = LittleLong(header.lumps[AASLUMP_AREASETTINGS].filelen); aasworld.areasettings = (aas_areasettings_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_areasettings_t)); aasworld.numareasettings = length / sizeof(aas_areasettings_t); if (aasworld.numareasettings && !aasworld.areasettings) return BLERR_CANNOTREADAASLUMP; //reachability list offset = LittleLong(header.lumps[AASLUMP_REACHABILITY].fileofs); length = LittleLong(header.lumps[AASLUMP_REACHABILITY].filelen); aasworld.reachability = (aas_reachability_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_reachability_t)); aasworld.reachabilitysize = length / sizeof(aas_reachability_t); if (aasworld.reachabilitysize && !aasworld.reachability) return BLERR_CANNOTREADAASLUMP; //nodes offset = LittleLong(header.lumps[AASLUMP_NODES].fileofs); length = LittleLong(header.lumps[AASLUMP_NODES].filelen); aasworld.nodes = (aas_node_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_node_t)); aasworld.numnodes = length / sizeof(aas_node_t); if (aasworld.numnodes && !aasworld.nodes) return BLERR_CANNOTREADAASLUMP; //cluster portals offset = LittleLong(header.lumps[AASLUMP_PORTALS].fileofs); length = LittleLong(header.lumps[AASLUMP_PORTALS].filelen); aasworld.portals = (aas_portal_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_portal_t)); aasworld.numportals = length / sizeof(aas_portal_t); if (aasworld.numportals && !aasworld.portals) return BLERR_CANNOTREADAASLUMP; //cluster portal index offset = LittleLong(header.lumps[AASLUMP_PORTALINDEX].fileofs); length = LittleLong(header.lumps[AASLUMP_PORTALINDEX].filelen); aasworld.portalindex = (aas_portalindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_portalindex_t)); aasworld.portalindexsize = length / sizeof(aas_portalindex_t); if (aasworld.portalindexsize && !aasworld.portalindex) return BLERR_CANNOTREADAASLUMP; //clusters offset = LittleLong(header.lumps[AASLUMP_CLUSTERS].fileofs); length = LittleLong(header.lumps[AASLUMP_CLUSTERS].filelen); aasworld.clusters = (aas_cluster_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_cluster_t)); aasworld.numclusters = length / sizeof(aas_cluster_t); if (aasworld.numclusters && !aasworld.clusters) return BLERR_CANNOTREADAASLUMP; //swap everything AAS_SwapAASData(); //aas file is loaded aasworld.loaded = qtrue; //close the file botimport.FS_FCloseFile(fp); // #ifdef AASFILEDEBUG AAS_FileInfo(); #endif //AASFILEDEBUG // return BLERR_NOERROR; } //end of the function AAS_LoadAASFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static int AAS_WriteAASLump_offset; int AAS_WriteAASLump(fileHandle_t fp, aas_header_t *h, int lumpnum, void *data, int length) { aas_lump_t *lump; lump = &h->lumps[lumpnum]; lump->fileofs = LittleLong(AAS_WriteAASLump_offset); //LittleLong(ftell(fp)); lump->filelen = LittleLong(length); if (length > 0) { botimport.FS_Write(data, length, fp ); } //end if AAS_WriteAASLump_offset += length; return qtrue; } //end of the function AAS_WriteAASLump //=========================================================================== // aas data is useless after writing to file because it is byte swapped // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_WriteAASFile(char *filename) { aas_header_t header; fileHandle_t fp; botimport.Print(PRT_MESSAGE, "writing %s\n", filename); //swap the aas data AAS_SwapAASData(); //initialize the file header Com_Memset(&header, 0, sizeof(aas_header_t)); header.ident = LittleLong(AASID); header.version = LittleLong(AASVERSION); header.bspchecksum = LittleLong(aasworld.bspchecksum); //open a new file botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); if (!fp) { botimport.Print(PRT_ERROR, "error opening %s\n", filename); return qfalse; } //end if //write the header botimport.FS_Write(&header, sizeof(aas_header_t), fp); AAS_WriteAASLump_offset = sizeof(aas_header_t); //add the data lumps to the file if (!AAS_WriteAASLump(fp, &header, AASLUMP_BBOXES, aasworld.bboxes, aasworld.numbboxes * sizeof(aas_bbox_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_VERTEXES, aasworld.vertexes, aasworld.numvertexes * sizeof(aas_vertex_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_PLANES, aasworld.planes, aasworld.numplanes * sizeof(aas_plane_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGES, aasworld.edges, aasworld.numedges * sizeof(aas_edge_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGEINDEX, aasworld.edgeindex, aasworld.edgeindexsize * sizeof(aas_edgeindex_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACES, aasworld.faces, aasworld.numfaces * sizeof(aas_face_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACEINDEX, aasworld.faceindex, aasworld.faceindexsize * sizeof(aas_faceindex_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREAS, aasworld.areas, aasworld.numareas * sizeof(aas_area_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREASETTINGS, aasworld.areasettings, aasworld.numareasettings * sizeof(aas_areasettings_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_REACHABILITY, aasworld.reachability, aasworld.reachabilitysize * sizeof(aas_reachability_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_NODES, aasworld.nodes, aasworld.numnodes * sizeof(aas_node_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALS, aasworld.portals, aasworld.numportals * sizeof(aas_portal_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALINDEX, aasworld.portalindex, aasworld.portalindexsize * sizeof(aas_portalindex_t))) return qfalse; if (!AAS_WriteAASLump(fp, &header, AASLUMP_CLUSTERS, aasworld.clusters, aasworld.numclusters * sizeof(aas_cluster_t))) return qfalse; //rewrite the header with the added lumps botimport.FS_Seek(fp, 0, FS_SEEK_SET); AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); botimport.FS_Write(&header, sizeof(aas_header_t), fp); //close the file botimport.FS_FCloseFile(fp); return qtrue; } //end of the function AAS_WriteAASFile ================================================ FILE: code/botlib/be_aas_file.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_file.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_file.h $ * *****************************************************************************/ #ifdef AASINTERN //loads the AAS file with the given name int AAS_LoadAASFile(char *filename); //writes an AAS file with the given name qboolean AAS_WriteAASFile(char *filename); //dumps the loaded AAS data void AAS_DumpAASData(void); //print AAS file information void AAS_FileInfo(void); #endif //AASINTERN ================================================ FILE: code/botlib/be_aas_funcs.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_funcs.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_funcs.h $ * *****************************************************************************/ #ifndef BSPCINCLUDE #include "be_aas_main.h" #include "be_aas_entity.h" #include "be_aas_sample.h" #include "be_aas_cluster.h" #include "be_aas_reach.h" #include "be_aas_route.h" #include "be_aas_routealt.h" #include "be_aas_debug.h" #include "be_aas_file.h" #include "be_aas_optimize.h" #include "be_aas_bsp.h" #include "be_aas_move.h" #endif //BSPCINCLUDE ================================================ FILE: code/botlib/be_aas_main.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_main.c * * desc: AAS * * $Archive: /MissionPack/code/botlib/be_aas_main.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_libvar.h" #include "l_utils.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_log.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "be_aas_def.h" aas_t aasworld; libvar_t *saveroutingcache; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void QDECL AAS_Error(char *fmt, ...) { char str[1024]; va_list arglist; va_start(arglist, fmt); vsprintf(str, fmt, arglist); va_end(arglist); botimport.Print(PRT_FATAL, str); } //end of the function AAS_Error //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *AAS_StringFromIndex(char *indexname, char *stringindex[], int numindexes, int index) { if (!aasworld.indexessetup) { botimport.Print(PRT_ERROR, "%s: index %d not setup\n", indexname, index); return ""; } //end if if (index < 0 || index >= numindexes) { botimport.Print(PRT_ERROR, "%s: index %d out of range\n", indexname, index); return ""; } //end if if (!stringindex[index]) { if (index) { botimport.Print(PRT_ERROR, "%s: reference to unused index %d\n", indexname, index); } //end if return ""; } //end if return stringindex[index]; } //end of the function AAS_StringFromIndex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_IndexFromString(char *indexname, char *stringindex[], int numindexes, char *string) { int i; if (!aasworld.indexessetup) { botimport.Print(PRT_ERROR, "%s: index not setup \"%s\"\n", indexname, string); return 0; } //end if for (i = 0; i < numindexes; i++) { if (!stringindex[i]) continue; if (!Q_stricmp(stringindex[i], string)) return i; } //end for return 0; } //end of the function AAS_IndexFromString //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *AAS_ModelFromIndex(int index) { return AAS_StringFromIndex("ModelFromIndex", &aasworld.configstrings[CS_MODELS], MAX_MODELS, index); } //end of the function AAS_ModelFromIndex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_IndexFromModel(char *modelname) { return AAS_IndexFromString("IndexFromModel", &aasworld.configstrings[CS_MODELS], MAX_MODELS, modelname); } //end of the function AAS_IndexFromModel //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_UpdateStringIndexes(int numconfigstrings, char *configstrings[]) { int i; //set string pointers and copy the strings for (i = 0; i < numconfigstrings; i++) { if (configstrings[i]) { //if (aasworld.configstrings[i]) FreeMemory(aasworld.configstrings[i]); aasworld.configstrings[i] = (char *) GetMemory(strlen(configstrings[i]) + 1); strcpy(aasworld.configstrings[i], configstrings[i]); } //end if } //end for aasworld.indexessetup = qtrue; } //end of the function AAS_UpdateStringIndexes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_Loaded(void) { return aasworld.loaded; } //end of the function AAS_Loaded //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_Initialized(void) { return aasworld.initialized; } //end of the function AAS_Initialized //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_SetInitialized(void) { aasworld.initialized = qtrue; botimport.Print(PRT_MESSAGE, "AAS initialized.\n"); #ifdef DEBUG //create all the routing cache //AAS_CreateAllRoutingCache(); // //AAS_RoutingInfo(); #endif } //end of the function AAS_SetInitialized //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ContinueInit(float time) { //if no AAS file loaded if (!aasworld.loaded) return; //if AAS is already initialized if (aasworld.initialized) return; //calculate reachability, if not finished return if (AAS_ContinueInitReachability(time)) return; //initialize clustering for the new map AAS_InitClustering(); //if reachability has been calculated and an AAS file should be written //or there is a forced data optimization if (aasworld.savefile || ((int)LibVarGetValue("forcewrite"))) { //optimize the AAS data if ((int)LibVarValue("aasoptimize", "0")) AAS_Optimize(); //save the AAS file if (AAS_WriteAASFile(aasworld.filename)) { botimport.Print(PRT_MESSAGE, "%s written succesfully\n", aasworld.filename); } //end if else { botimport.Print(PRT_ERROR, "couldn't write %s\n", aasworld.filename); } //end else } //end if //initialize the routing AAS_InitRouting(); //at this point AAS is initialized AAS_SetInitialized(); } //end of the function AAS_ContinueInit //=========================================================================== // called at the start of every frame // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_StartFrame(float time) { aasworld.time = time; //unlink all entities that were not updated last frame AAS_UnlinkInvalidEntities(); //invalidate the entities AAS_InvalidateEntities(); //initialize AAS AAS_ContinueInit(time); // aasworld.frameroutingupdates = 0; // if (bot_developer) { if (LibVarGetValue("showcacheupdates")) { AAS_RoutingInfo(); LibVarSet("showcacheupdates", "0"); } //end if if (LibVarGetValue("showmemoryusage")) { PrintUsedMemorySize(); LibVarSet("showmemoryusage", "0"); } //end if if (LibVarGetValue("memorydump")) { PrintMemoryLabels(); LibVarSet("memorydump", "0"); } //end if } //end if // if (saveroutingcache->value) { AAS_WriteRouteCache(); LibVarSet("saveroutingcache", "0"); } //end if // aasworld.numframes++; return BLERR_NOERROR; } //end of the function AAS_StartFrame //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float AAS_Time(void) { return aasworld.time; } //end of the function AAS_Time //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ) { vec3_t pVec, vec; VectorSubtract( point, vStart, pVec ); VectorSubtract( vEnd, vStart, vec ); VectorNormalize( vec ); // project onto the directional vector for this segment VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj ); } //end of the function AAS_ProjectPointOntoVector //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_LoadFiles(const char *mapname) { int errnum; char aasfile[MAX_PATH]; // char bspfile[MAX_PATH]; strcpy(aasworld.mapname, mapname); //NOTE: first reset the entity links into the AAS areas and BSP leaves // the AAS link heap and BSP link heap are reset after respectively the // AAS file and BSP file are loaded AAS_ResetEntityLinks(); // load bsp info AAS_LoadBSPFile(); //load the aas file Com_sprintf(aasfile, MAX_PATH, "maps/%s.aas", mapname); errnum = AAS_LoadAASFile(aasfile); if (errnum != BLERR_NOERROR) return errnum; botimport.Print(PRT_MESSAGE, "loaded %s\n", aasfile); strncpy(aasworld.filename, aasfile, MAX_PATH); return BLERR_NOERROR; } //end of the function AAS_LoadFiles //=========================================================================== // called everytime a map changes // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_LoadMap(const char *mapname) { int errnum; //if no mapname is provided then the string indexes are updated if (!mapname) { return 0; } //end if // aasworld.initialized = qfalse; //NOTE: free the routing caches before loading a new map because // to free the caches the old number of areas, number of clusters // and number of areas in a clusters must be available AAS_FreeRoutingCaches(); //load the map errnum = AAS_LoadFiles(mapname); if (errnum != BLERR_NOERROR) { aasworld.loaded = qfalse; return errnum; } //end if // AAS_InitSettings(); //initialize the AAS link heap for the new map AAS_InitAASLinkHeap(); //initialize the AAS linked entities for the new map AAS_InitAASLinkedEntities(); //initialize reachability for the new map AAS_InitReachability(); //initialize the alternative routing AAS_InitAlternativeRouting(); //everything went ok return 0; } //end of the function AAS_LoadMap //=========================================================================== // called when the library is first loaded // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_Setup(void) { aasworld.maxclients = (int) LibVarValue("maxclients", "128"); aasworld.maxentities = (int) LibVarValue("maxentities", "1024"); // as soon as it's set to 1 the routing cache will be saved saveroutingcache = LibVar("saveroutingcache", "0"); //allocate memory for the entities if (aasworld.entities) FreeMemory(aasworld.entities); aasworld.entities = (aas_entity_t *) GetClearedHunkMemory(aasworld.maxentities * sizeof(aas_entity_t)); //invalidate all the entities AAS_InvalidateEntities(); //force some recalculations //LibVarSet("forceclustering", "1"); //force clustering calculation //LibVarSet("forcereachability", "1"); //force reachability calculation aasworld.numframes = 0; return BLERR_NOERROR; } //end of the function AAS_Setup //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_Shutdown(void) { AAS_ShutdownAlternativeRouting(); // AAS_DumpBSPData(); //free routing caches AAS_FreeRoutingCaches(); //free aas link heap AAS_FreeAASLinkHeap(); //free aas linked entities AAS_FreeAASLinkedEntities(); //free the aas data AAS_DumpAASData(); //free the entities if (aasworld.entities) FreeMemory(aasworld.entities); //clear the aasworld structure Com_Memset(&aasworld, 0, sizeof(aas_t)); //aas has not been initialized aasworld.initialized = qfalse; //NOTE: as soon as a new .bsp file is loaded the .bsp file memory is // freed an reallocated, so there's no need to free that memory here //print shutdown botimport.Print(PRT_MESSAGE, "AAS shutdown.\n"); } //end of the function AAS_Shutdown ================================================ FILE: code/botlib/be_aas_main.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_main.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_main.h $ * *****************************************************************************/ #ifdef AASINTERN extern aas_t aasworld; //AAS error message void QDECL AAS_Error(char *fmt, ...); //set AAS initialized void AAS_SetInitialized(void); //setup AAS with the given number of entities and clients int AAS_Setup(void); //shutdown AAS void AAS_Shutdown(void); //start a new map int AAS_LoadMap(const char *mapname); //start a new time frame int AAS_StartFrame(float time); #endif //AASINTERN //returns true if AAS is initialized int AAS_Initialized(void); //returns true if the AAS file is loaded int AAS_Loaded(void); //returns the model name from the given index char *AAS_ModelFromIndex(int index); //returns the index from the given model name int AAS_IndexFromModel(char *modelname); //returns the current time float AAS_Time(void); // void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ); ================================================ FILE: code/botlib/be_aas_move.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_move.c * * desc: AAS * * $Archive: /MissionPack/code/botlib/be_aas_move.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_libvar.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_aas_def.h" extern botlib_import_t botimport; aas_settings_t aassettings; //#define AAS_MOVE_DEBUG //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs) { vec3_t end; bsp_trace_t trace; VectorCopy(origin, end); end[2] -= 100; trace = AAS_Trace(origin, mins, maxs, end, 0, CONTENTS_SOLID); if (trace.startsolid) return qfalse; VectorCopy(trace.endpos, origin); return qtrue; } //end of the function AAS_DropToFloor //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitSettings(void) { aassettings.phys_gravitydirection[0] = 0; aassettings.phys_gravitydirection[1] = 0; aassettings.phys_gravitydirection[2] = -1; aassettings.phys_friction = LibVarValue("phys_friction", "6"); aassettings.phys_stopspeed = LibVarValue("phys_stopspeed", "100"); aassettings.phys_gravity = LibVarValue("phys_gravity", "800"); aassettings.phys_waterfriction = LibVarValue("phys_waterfriction", "1"); aassettings.phys_watergravity = LibVarValue("phys_watergravity", "400"); aassettings.phys_maxvelocity = LibVarValue("phys_maxvelocity", "320"); aassettings.phys_maxwalkvelocity = LibVarValue("phys_maxwalkvelocity", "320"); aassettings.phys_maxcrouchvelocity = LibVarValue("phys_maxcrouchvelocity", "100"); aassettings.phys_maxswimvelocity = LibVarValue("phys_maxswimvelocity", "150"); aassettings.phys_walkaccelerate = LibVarValue("phys_walkaccelerate", "10"); aassettings.phys_airaccelerate = LibVarValue("phys_airaccelerate", "1"); aassettings.phys_swimaccelerate = LibVarValue("phys_swimaccelerate", "4"); aassettings.phys_maxstep = LibVarValue("phys_maxstep", "19"); aassettings.phys_maxsteepness = LibVarValue("phys_maxsteepness", "0.7"); aassettings.phys_maxwaterjump = LibVarValue("phys_maxwaterjump", "18"); aassettings.phys_maxbarrier = LibVarValue("phys_maxbarrier", "33"); aassettings.phys_jumpvel = LibVarValue("phys_jumpvel", "270"); aassettings.phys_falldelta5 = LibVarValue("phys_falldelta5", "40"); aassettings.phys_falldelta10 = LibVarValue("phys_falldelta10", "60"); aassettings.rs_waterjump = LibVarValue("rs_waterjump", "400"); aassettings.rs_teleport = LibVarValue("rs_teleport", "50"); aassettings.rs_barrierjump = LibVarValue("rs_barrierjump", "100"); aassettings.rs_startcrouch = LibVarValue("rs_startcrouch", "300"); aassettings.rs_startgrapple = LibVarValue("rs_startgrapple", "500"); aassettings.rs_startwalkoffledge = LibVarValue("rs_startwalkoffledge", "70"); aassettings.rs_startjump = LibVarValue("rs_startjump", "300"); aassettings.rs_rocketjump = LibVarValue("rs_rocketjump", "500"); aassettings.rs_bfgjump = LibVarValue("rs_bfgjump", "500"); aassettings.rs_jumppad = LibVarValue("rs_jumppad", "250"); aassettings.rs_aircontrolledjumppad = LibVarValue("rs_aircontrolledjumppad", "300"); aassettings.rs_funcbob = LibVarValue("rs_funcbob", "300"); aassettings.rs_startelevator = LibVarValue("rs_startelevator", "50"); aassettings.rs_falldamage5 = LibVarValue("rs_falldamage5", "300"); aassettings.rs_falldamage10 = LibVarValue("rs_falldamage10", "500"); aassettings.rs_maxfallheight = LibVarValue("rs_maxfallheight", "0"); aassettings.rs_maxjumpfallheight = LibVarValue("rs_maxjumpfallheight", "450"); } //end of the function AAS_InitSettings //=========================================================================== // returns qtrue if the bot is against a ladder // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AgainstLadder(vec3_t origin) { int areanum, i, facenum, side; vec3_t org; aas_plane_t *plane; aas_face_t *face; aas_area_t *area; VectorCopy(origin, org); areanum = AAS_PointAreaNum(org); if (!areanum) { org[0] += 1; areanum = AAS_PointAreaNum(org); if (!areanum) { org[1] += 1; areanum = AAS_PointAreaNum(org); if (!areanum) { org[0] -= 2; areanum = AAS_PointAreaNum(org); if (!areanum) { org[1] -= 2; areanum = AAS_PointAreaNum(org); } //end if } //end if } //end if } //end if //if in solid... wrrr shouldn't happen if (!areanum) return qfalse; //if not in a ladder area if (!(aasworld.areasettings[areanum].areaflags & AREA_LADDER)) return qfalse; //if a crouch only area if (!(aasworld.areasettings[areanum].presencetype & PRESENCE_NORMAL)) return qfalse; // area = &aasworld.areas[areanum]; for (i = 0; i < area->numfaces; i++) { facenum = aasworld.faceindex[area->firstface + i]; side = facenum < 0; face = &aasworld.faces[abs(facenum)]; //if the face isn't a ladder face if (!(face->faceflags & FACE_LADDER)) continue; //get the plane the face is in plane = &aasworld.planes[face->planenum ^ side]; //if the origin is pretty close to the plane if (abs(DotProduct(plane->normal, origin) - plane->dist) < 3) { if (AAS_PointInsideFace(abs(facenum), origin, 0.1f)) return qtrue; } //end if } //end for return qfalse; } //end of the function AAS_AgainstLadder //=========================================================================== // returns qtrue if the bot is on the ground // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_OnGround(vec3_t origin, int presencetype, int passent) { aas_trace_t trace; vec3_t end, up = {0, 0, 1}; aas_plane_t *plane; VectorCopy(origin, end); end[2] -= 10; trace = AAS_TraceClientBBox(origin, end, presencetype, passent); //if in solid if (trace.startsolid) return qfalse; //if nothing hit at all if (trace.fraction >= 1.0) return qfalse; //if too far from the hit plane if (origin[2] - trace.endpos[2] > 10) return qfalse; //check if the plane isn't too steep plane = AAS_PlaneFromNum(trace.planenum); if (DotProduct(plane->normal, up) < aassettings.phys_maxsteepness) return qfalse; //the bot is on the ground return qtrue; } //end of the function AAS_OnGround //=========================================================================== // returns qtrue if a bot at the given position is swimming // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_Swimming(vec3_t origin) { vec3_t testorg; VectorCopy(origin, testorg); testorg[2] -= 2; if (AAS_PointContents(testorg) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) return qtrue; return qfalse; } //end of the function AAS_Swimming //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== static vec3_t VEC_UP = {0, -1, 0}; static vec3_t MOVEDIR_UP = {0, 0, 1}; static vec3_t VEC_DOWN = {0, -2, 0}; static vec3_t MOVEDIR_DOWN = {0, 0, -1}; void AAS_SetMovedir(vec3_t angles, vec3_t movedir) { if (VectorCompare(angles, VEC_UP)) { VectorCopy(MOVEDIR_UP, movedir); } //end if else if (VectorCompare(angles, VEC_DOWN)) { VectorCopy(MOVEDIR_DOWN, movedir); } //end else if else { AngleVectors(angles, movedir, NULL, NULL); } //end else } //end of the function AAS_SetMovedir //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_JumpReachRunStart(aas_reachability_t *reach, vec3_t runstart) { vec3_t hordir, start, cmdmove; aas_clientmove_t move; // hordir[0] = reach->start[0] - reach->end[0]; hordir[1] = reach->start[1] - reach->end[1]; hordir[2] = 0; VectorNormalize(hordir); //start point VectorCopy(reach->start, start); start[2] += 1; //get command movement VectorScale(hordir, 400, cmdmove); // AAS_PredictClientMovement(&move, -1, start, PRESENCE_NORMAL, qtrue, vec3_origin, cmdmove, 1, 2, 0.1f, SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA| SE_HITGROUNDDAMAGE|SE_GAP, 0, qfalse); VectorCopy(move.endpos, runstart); //don't enter slime or lava and don't fall from too high if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) { VectorCopy(start, runstart); } //end if } //end of the function AAS_JumpReachRunStart //=========================================================================== // returns the Z velocity when rocket jumping at the origin // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float AAS_WeaponJumpZVelocity(vec3_t origin, float radiusdamage) { vec3_t kvel, v, start, end, forward, right, viewangles, dir; float mass, knockback, points; vec3_t rocketoffset = {8, 8, -8}; vec3_t botmins = {-16, -16, -24}; vec3_t botmaxs = {16, 16, 32}; bsp_trace_t bsptrace; //look down (90 degrees) viewangles[PITCH] = 90; viewangles[YAW] = 0; viewangles[ROLL] = 0; //get the start point shooting from VectorCopy(origin, start); start[2] += 8; //view offset Z AngleVectors(viewangles, forward, right, NULL); start[0] += forward[0] * rocketoffset[0] + right[0] * rocketoffset[1]; start[1] += forward[1] * rocketoffset[0] + right[1] * rocketoffset[1]; start[2] += forward[2] * rocketoffset[0] + right[2] * rocketoffset[1] + rocketoffset[2]; //end point of the trace VectorMA(start, 500, forward, end); //trace a line to get the impact point bsptrace = AAS_Trace(start, NULL, NULL, end, 1, CONTENTS_SOLID); //calculate the damage the bot will get from the rocket impact VectorAdd(botmins, botmaxs, v); VectorMA(origin, 0.5, v, v); VectorSubtract(bsptrace.endpos, v, v); // points = radiusdamage - 0.5 * VectorLength(v); if (points < 0) points = 0; //the owner of the rocket gets half the damage points *= 0.5; //mass of the bot (p_client.c: PutClientInServer) mass = 200; //knockback is the same as the damage points knockback = points; //direction of the damage (from trace.endpos to bot origin) VectorSubtract(origin, bsptrace.endpos, dir); VectorNormalize(dir); //damage velocity VectorScale(dir, 1600.0 * (float)knockback / mass, kvel); //the rocket jump hack... //rocket impact velocity + jump velocity return kvel[2] + aassettings.phys_jumpvel; } //end of the function AAS_WeaponJumpZVelocity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float AAS_RocketJumpZVelocity(vec3_t origin) { //rocket radius damage is 120 (p_weapon.c: Weapon_RocketLauncher_Fire) return AAS_WeaponJumpZVelocity(origin, 120); } //end of the function AAS_RocketJumpZVelocity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float AAS_BFGJumpZVelocity(vec3_t origin) { //bfg radius damage is 1000 (p_weapon.c: weapon_bfg_fire) return AAS_WeaponJumpZVelocity(origin, 120); } //end of the function AAS_BFGJumpZVelocity //=========================================================================== // applies ground friction to the given velocity // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_Accelerate(vec3_t velocity, float frametime, vec3_t wishdir, float wishspeed, float accel) { // q2 style int i; float addspeed, accelspeed, currentspeed; currentspeed = DotProduct(velocity, wishdir); addspeed = wishspeed - currentspeed; if (addspeed <= 0) { return; } accelspeed = accel*frametime*wishspeed; if (accelspeed > addspeed) { accelspeed = addspeed; } for (i=0 ; i<3 ; i++) { velocity[i] += accelspeed*wishdir[i]; } } //end of the function AAS_Accelerate //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_AirControl(vec3_t start, vec3_t end, vec3_t velocity, vec3_t cmdmove) { vec3_t dir; VectorSubtract(end, start, dir); } //end of the function AAS_AirControl //=========================================================================== // applies ground friction to the given velocity // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ApplyFriction(vec3_t vel, float friction, float stopspeed, float frametime) { float speed, control, newspeed; //horizontal speed speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1]); if (speed) { control = speed < stopspeed ? stopspeed : speed; newspeed = speed - frametime * control * friction; if (newspeed < 0) newspeed = 0; newspeed /= speed; vel[0] *= newspeed; vel[1] *= newspeed; } //end if } //end of the function AAS_ApplyFriction //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_ClipToBBox(aas_trace_t *trace, vec3_t start, vec3_t end, int presencetype, vec3_t mins, vec3_t maxs) { int i, j, side; float front, back, frac, planedist; vec3_t bboxmins, bboxmaxs, absmins, absmaxs, dir, mid; AAS_PresenceTypeBoundingBox(presencetype, bboxmins, bboxmaxs); VectorSubtract(mins, bboxmaxs, absmins); VectorSubtract(maxs, bboxmins, absmaxs); // VectorCopy(end, trace->endpos); trace->fraction = 1; for (i = 0; i < 3; i++) { if (start[i] < absmins[i] && end[i] < absmins[i]) return qfalse; if (start[i] > absmaxs[i] && end[i] > absmaxs[i]) return qfalse; } //end for //check bounding box collision VectorSubtract(end, start, dir); frac = 1; for (i = 0; i < 3; i++) { //get plane to test collision with for the current axis direction if (dir[i] > 0) planedist = absmins[i]; else planedist = absmaxs[i]; //calculate collision fraction front = start[i] - planedist; back = end[i] - planedist; frac = front / (front-back); //check if between bounding planes of next axis side = i + 1; if (side > 2) side = 0; mid[side] = start[side] + dir[side] * frac; if (mid[side] > absmins[side] && mid[side] < absmaxs[side]) { //check if between bounding planes of next axis side++; if (side > 2) side = 0; mid[side] = start[side] + dir[side] * frac; if (mid[side] > absmins[side] && mid[side] < absmaxs[side]) { mid[i] = planedist; break; } //end if } //end if } //end for //if there was a collision if (i != 3) { trace->startsolid = qfalse; trace->fraction = frac; trace->ent = 0; trace->planenum = 0; trace->area = 0; trace->lastarea = 0; //trace endpos for (j = 0; j < 3; j++) trace->endpos[j] = start[j] + dir[j] * frac; return qtrue; } //end if return qfalse; } //end of the function AAS_ClipToBBox //=========================================================================== // predicts the movement // assumes regular bounding box sizes // NOTE: out of water jumping is not included // NOTE: grappling hook is not included // // Parameter: origin : origin to start with // presencetype : presence type to start with // velocity : velocity to start with // cmdmove : client command movement // cmdframes : number of frame cmdmove is valid // maxframes : maximum number of predicted frames // frametime : duration of one predicted frame // stopevent : events that stop the prediction // stopareanum : stop as soon as entered this area // Returns: aas_clientmove_t // Changes Globals: - //=========================================================================== int AAS_ClientMovementPrediction(struct aas_clientmove_s *move, int entnum, vec3_t origin, int presencetype, int onground, vec3_t velocity, vec3_t cmdmove, int cmdframes, int maxframes, float frametime, int stopevent, int stopareanum, vec3_t mins, vec3_t maxs, int visualize) { float phys_friction, phys_stopspeed, phys_gravity, phys_waterfriction; float phys_watergravity; float phys_walkaccelerate, phys_airaccelerate, phys_swimaccelerate; float phys_maxwalkvelocity, phys_maxcrouchvelocity, phys_maxswimvelocity; float phys_maxstep, phys_maxsteepness, phys_jumpvel, friction; float gravity, delta, maxvel, wishspeed, accelerate; //float velchange, newvel; int n, i, j, pc, step, swimming, ax, crouch, event, jump_frame, areanum; int areas[20], numareas; vec3_t points[20]; vec3_t org, end, feet, start, stepend, lastorg, wishdir; vec3_t frame_test_vel, old_frame_test_vel, left_test_vel; vec3_t up = {0, 0, 1}; aas_plane_t *plane, *plane2; aas_trace_t trace, steptrace; if (frametime <= 0) frametime = 0.1f; // phys_friction = aassettings.phys_friction; phys_stopspeed = aassettings.phys_stopspeed; phys_gravity = aassettings.phys_gravity; phys_waterfriction = aassettings.phys_waterfriction; phys_watergravity = aassettings.phys_watergravity; phys_maxwalkvelocity = aassettings.phys_maxwalkvelocity;// * frametime; phys_maxcrouchvelocity = aassettings.phys_maxcrouchvelocity;// * frametime; phys_maxswimvelocity = aassettings.phys_maxswimvelocity;// * frametime; phys_walkaccelerate = aassettings.phys_walkaccelerate; phys_airaccelerate = aassettings.phys_airaccelerate; phys_swimaccelerate = aassettings.phys_swimaccelerate; phys_maxstep = aassettings.phys_maxstep; phys_maxsteepness = aassettings.phys_maxsteepness; phys_jumpvel = aassettings.phys_jumpvel * frametime; // Com_Memset(move, 0, sizeof(aas_clientmove_t)); Com_Memset(&trace, 0, sizeof(aas_trace_t)); //start at the current origin VectorCopy(origin, org); org[2] += 0.25; //velocity to test for the first frame VectorScale(velocity, frametime, frame_test_vel); // jump_frame = -1; //predict a maximum of 'maxframes' ahead for (n = 0; n < maxframes; n++) { swimming = AAS_Swimming(org); //get gravity depending on swimming or not gravity = swimming ? phys_watergravity : phys_gravity; //apply gravity at the START of the frame frame_test_vel[2] = frame_test_vel[2] - (gravity * 0.1 * frametime); //if on the ground or swimming if (onground || swimming) { friction = swimming ? phys_friction : phys_waterfriction; //apply friction VectorScale(frame_test_vel, 1/frametime, frame_test_vel); AAS_ApplyFriction(frame_test_vel, friction, phys_stopspeed, frametime); VectorScale(frame_test_vel, frametime, frame_test_vel); } //end if crouch = qfalse; //apply command movement if (n < cmdframes) { ax = 0; maxvel = phys_maxwalkvelocity; accelerate = phys_airaccelerate; VectorCopy(cmdmove, wishdir); if (onground) { if (cmdmove[2] < -300) { crouch = qtrue; maxvel = phys_maxcrouchvelocity; } //end if //if not swimming and upmove is positive then jump if (!swimming && cmdmove[2] > 1) { //jump velocity minus the gravity for one frame + 5 for safety frame_test_vel[2] = phys_jumpvel - (gravity * 0.1 * frametime) + 5; jump_frame = n; //jumping so air accelerate accelerate = phys_airaccelerate; } //end if else { accelerate = phys_walkaccelerate; } //end else ax = 2; } //end if if (swimming) { maxvel = phys_maxswimvelocity; accelerate = phys_swimaccelerate; ax = 3; } //end if else { wishdir[2] = 0; } //end else // wishspeed = VectorNormalize(wishdir); if (wishspeed > maxvel) wishspeed = maxvel; VectorScale(frame_test_vel, 1/frametime, frame_test_vel); AAS_Accelerate(frame_test_vel, frametime, wishdir, wishspeed, accelerate); VectorScale(frame_test_vel, frametime, frame_test_vel); /* for (i = 0; i < ax; i++) { velchange = (cmdmove[i] * frametime) - frame_test_vel[i]; if (velchange > phys_maxacceleration) velchange = phys_maxacceleration; else if (velchange < -phys_maxacceleration) velchange = -phys_maxacceleration; newvel = frame_test_vel[i] + velchange; // if (frame_test_vel[i] <= maxvel && newvel > maxvel) frame_test_vel[i] = maxvel; else if (frame_test_vel[i] >= -maxvel && newvel < -maxvel) frame_test_vel[i] = -maxvel; else frame_test_vel[i] = newvel; } //end for */ } //end if if (crouch) { presencetype = PRESENCE_CROUCH; } //end if else if (presencetype == PRESENCE_CROUCH) { if (AAS_PointPresenceType(org) & PRESENCE_NORMAL) { presencetype = PRESENCE_NORMAL; } //end if } //end else //save the current origin VectorCopy(org, lastorg); //move linear during one frame VectorCopy(frame_test_vel, left_test_vel); j = 0; do { VectorAdd(org, left_test_vel, end); //trace a bounding box trace = AAS_TraceClientBBox(org, end, presencetype, entnum); // //#ifdef AAS_MOVE_DEBUG if (visualize) { if (trace.startsolid) botimport.Print(PRT_MESSAGE, "PredictMovement: start solid\n"); AAS_DebugLine(org, trace.endpos, LINECOLOR_RED); } //end if //#endif //AAS_MOVE_DEBUG // if (stopevent & (SE_ENTERAREA|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER|SE_TOUCHCLUSTERPORTAL)) { numareas = AAS_TraceAreas(org, trace.endpos, areas, points, 20); for (i = 0; i < numareas; i++) { if (stopevent & SE_ENTERAREA) { if (areas[i] == stopareanum) { VectorCopy(points[i], move->endpos); VectorScale(frame_test_vel, 1/frametime, move->velocity); move->endarea = areas[i]; move->trace = trace; move->stopevent = SE_ENTERAREA; move->presencetype = presencetype; move->endcontents = 0; move->time = n * frametime; move->frames = n; return qtrue; } //end if } //end if //NOTE: if not the first frame if ((stopevent & SE_TOUCHJUMPPAD) && n) { if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_JUMPPAD) { VectorCopy(points[i], move->endpos); VectorScale(frame_test_vel, 1/frametime, move->velocity); move->endarea = areas[i]; move->trace = trace; move->stopevent = SE_TOUCHJUMPPAD; move->presencetype = presencetype; move->endcontents = 0; move->time = n * frametime; move->frames = n; return qtrue; } //end if } //end if if (stopevent & SE_TOUCHTELEPORTER) { if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_TELEPORTER) { VectorCopy(points[i], move->endpos); move->endarea = areas[i]; VectorScale(frame_test_vel, 1/frametime, move->velocity); move->trace = trace; move->stopevent = SE_TOUCHTELEPORTER; move->presencetype = presencetype; move->endcontents = 0; move->time = n * frametime; move->frames = n; return qtrue; } //end if } //end if if (stopevent & SE_TOUCHCLUSTERPORTAL) { if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_CLUSTERPORTAL) { VectorCopy(points[i], move->endpos); move->endarea = areas[i]; VectorScale(frame_test_vel, 1/frametime, move->velocity); move->trace = trace; move->stopevent = SE_TOUCHCLUSTERPORTAL; move->presencetype = presencetype; move->endcontents = 0; move->time = n * frametime; move->frames = n; return qtrue; } //end if } //end if } //end for } //end if // if (stopevent & SE_HITBOUNDINGBOX) { if (AAS_ClipToBBox(&trace, org, trace.endpos, presencetype, mins, maxs)) { VectorCopy(trace.endpos, move->endpos); move->endarea = AAS_PointAreaNum(move->endpos); VectorScale(frame_test_vel, 1/frametime, move->velocity); move->trace = trace; move->stopevent = SE_HITBOUNDINGBOX; move->presencetype = presencetype; move->endcontents = 0; move->time = n * frametime; move->frames = n; return qtrue; } //end if } //end if //move the entity to the trace end point VectorCopy(trace.endpos, org); //if there was a collision if (trace.fraction < 1.0) { //get the plane the bounding box collided with plane = AAS_PlaneFromNum(trace.planenum); // if (stopevent & SE_HITGROUNDAREA) { if (DotProduct(plane->normal, up) > phys_maxsteepness) { VectorCopy(org, start); start[2] += 0.5; if (AAS_PointAreaNum(start) == stopareanum) { VectorCopy(start, move->endpos); move->endarea = stopareanum; VectorScale(frame_test_vel, 1/frametime, move->velocity); move->trace = trace; move->stopevent = SE_HITGROUNDAREA; move->presencetype = presencetype; move->endcontents = 0; move->time = n * frametime; move->frames = n; return qtrue; } //end if } //end if } //end if //assume there's no step step = qfalse; //if it is a vertical plane and the bot didn't jump recently if (plane->normal[2] == 0 && (jump_frame < 0 || n - jump_frame > 2)) { //check for a step VectorMA(org, -0.25, plane->normal, start); VectorCopy(start, stepend); start[2] += phys_maxstep; steptrace = AAS_TraceClientBBox(start, stepend, presencetype, entnum); // if (!steptrace.startsolid) { plane2 = AAS_PlaneFromNum(steptrace.planenum); if (DotProduct(plane2->normal, up) > phys_maxsteepness) { VectorSubtract(end, steptrace.endpos, left_test_vel); left_test_vel[2] = 0; frame_test_vel[2] = 0; //#ifdef AAS_MOVE_DEBUG if (visualize) { if (steptrace.endpos[2] - org[2] > 0.125) { VectorCopy(org, start); start[2] = steptrace.endpos[2]; AAS_DebugLine(org, start, LINECOLOR_BLUE); } //end if } //end if //#endif //AAS_MOVE_DEBUG org[2] = steptrace.endpos[2]; step = qtrue; } //end if } //end if } //end if // if (!step) { //velocity left to test for this frame is the projection //of the current test velocity into the hit plane VectorMA(left_test_vel, -DotProduct(left_test_vel, plane->normal), plane->normal, left_test_vel); //store the old velocity for landing check VectorCopy(frame_test_vel, old_frame_test_vel); //test velocity for the next frame is the projection //of the velocity of the current frame into the hit plane VectorMA(frame_test_vel, -DotProduct(frame_test_vel, plane->normal), plane->normal, frame_test_vel); //check for a landing on an almost horizontal floor if (DotProduct(plane->normal, up) > phys_maxsteepness) { onground = qtrue; } //end if if (stopevent & SE_HITGROUNDDAMAGE) { delta = 0; if (old_frame_test_vel[2] < 0 && frame_test_vel[2] > old_frame_test_vel[2] && !onground) { delta = old_frame_test_vel[2]; } //end if else if (onground) { delta = frame_test_vel[2] - old_frame_test_vel[2]; } //end else if (delta) { delta = delta * 10; delta = delta * delta * 0.0001; if (swimming) delta = 0; // never take falling damage if completely underwater /* if (ent->waterlevel == 3) return; if (ent->waterlevel == 2) delta *= 0.25; if (ent->waterlevel == 1) delta *= 0.5; */ if (delta > 40) { VectorCopy(org, move->endpos); move->endarea = AAS_PointAreaNum(org); VectorCopy(frame_test_vel, move->velocity); move->trace = trace; move->stopevent = SE_HITGROUNDDAMAGE; move->presencetype = presencetype; move->endcontents = 0; move->time = n * frametime; move->frames = n; return qtrue; } //end if } //end if } //end if } //end if } //end if //extra check to prevent endless loop if (++j > 20) return qfalse; //while there is a plane hit } while(trace.fraction < 1.0); //if going down if (frame_test_vel[2] <= 10) { //check for a liquid at the feet of the bot VectorCopy(org, feet); feet[2] -= 22; pc = AAS_PointContents(feet); //get event from pc event = SE_NONE; if (pc & CONTENTS_LAVA) event |= SE_ENTERLAVA; if (pc & CONTENTS_SLIME) event |= SE_ENTERSLIME; if (pc & CONTENTS_WATER) event |= SE_ENTERWATER; // areanum = AAS_PointAreaNum(org); if (aasworld.areasettings[areanum].contents & AREACONTENTS_LAVA) event |= SE_ENTERLAVA; if (aasworld.areasettings[areanum].contents & AREACONTENTS_SLIME) event |= SE_ENTERSLIME; if (aasworld.areasettings[areanum].contents & AREACONTENTS_WATER) event |= SE_ENTERWATER; //if in lava or slime if (event & stopevent) { VectorCopy(org, move->endpos); move->endarea = areanum; VectorScale(frame_test_vel, 1/frametime, move->velocity); move->stopevent = event & stopevent; move->presencetype = presencetype; move->endcontents = pc; move->time = n * frametime; move->frames = n; return qtrue; } //end if } //end if // onground = AAS_OnGround(org, presencetype, entnum); //if onground and on the ground for at least one whole frame if (onground) { if (stopevent & SE_HITGROUND) { VectorCopy(org, move->endpos); move->endarea = AAS_PointAreaNum(org); VectorScale(frame_test_vel, 1/frametime, move->velocity); move->trace = trace; move->stopevent = SE_HITGROUND; move->presencetype = presencetype; move->endcontents = 0; move->time = n * frametime; move->frames = n; return qtrue; } //end if } //end if else if (stopevent & SE_LEAVEGROUND) { VectorCopy(org, move->endpos); move->endarea = AAS_PointAreaNum(org); VectorScale(frame_test_vel, 1/frametime, move->velocity); move->trace = trace; move->stopevent = SE_LEAVEGROUND; move->presencetype = presencetype; move->endcontents = 0; move->time = n * frametime; move->frames = n; return qtrue; } //end else if else if (stopevent & SE_GAP) { aas_trace_t gaptrace; VectorCopy(org, start); VectorCopy(start, end); end[2] -= 48 + aassettings.phys_maxbarrier; gaptrace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); //if solid is found the bot cannot walk any further and will not fall into a gap if (!gaptrace.startsolid) { //if it is a gap (lower than one step height) if (gaptrace.endpos[2] < org[2] - aassettings.phys_maxstep - 1) { if (!(AAS_PointContents(end) & CONTENTS_WATER)) { VectorCopy(lastorg, move->endpos); move->endarea = AAS_PointAreaNum(lastorg); VectorScale(frame_test_vel, 1/frametime, move->velocity); move->trace = trace; move->stopevent = SE_GAP; move->presencetype = presencetype; move->endcontents = 0; move->time = n * frametime; move->frames = n; return qtrue; } //end if } //end if } //end if } //end else if } //end for // VectorCopy(org, move->endpos); move->endarea = AAS_PointAreaNum(org); VectorScale(frame_test_vel, 1/frametime, move->velocity); move->stopevent = SE_NONE; move->presencetype = presencetype; move->endcontents = 0; move->time = n * frametime; move->frames = n; // return qtrue; } //end of the function AAS_ClientMovementPrediction //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_PredictClientMovement(struct aas_clientmove_s *move, int entnum, vec3_t origin, int presencetype, int onground, vec3_t velocity, vec3_t cmdmove, int cmdframes, int maxframes, float frametime, int stopevent, int stopareanum, int visualize) { vec3_t mins, maxs; return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground, velocity, cmdmove, cmdframes, maxframes, frametime, stopevent, stopareanum, mins, maxs, visualize); } //end of the function AAS_PredictClientMovement //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_ClientMovementHitBBox(struct aas_clientmove_s *move, int entnum, vec3_t origin, int presencetype, int onground, vec3_t velocity, vec3_t cmdmove, int cmdframes, int maxframes, float frametime, vec3_t mins, vec3_t maxs, int visualize) { return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground, velocity, cmdmove, cmdframes, maxframes, frametime, SE_HITBOUNDINGBOX, 0, mins, maxs, visualize); } //end of the function AAS_ClientMovementHitBBox //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir) { vec3_t velocity, cmdmove; aas_clientmove_t move; VectorClear(velocity); if (!AAS_Swimming(origin)) dir[2] = 0; VectorNormalize(dir); VectorScale(dir, 400, cmdmove); cmdmove[2] = 224; AAS_ClearShownDebugLines(); AAS_PredictClientMovement(&move, entnum, origin, PRESENCE_NORMAL, qtrue, velocity, cmdmove, 13, 13, 0.1f, SE_HITGROUND, 0, qtrue);//SE_LEAVEGROUND); if (move.stopevent & SE_LEAVEGROUND) { botimport.Print(PRT_MESSAGE, "leave ground\n"); } //end if } //end of the function TestMovementPrediction //=========================================================================== // calculates the horizontal velocity needed to perform a jump from start // to end // // Parameter: zvel : z velocity for jump // start : start position of jump // end : end position of jump // *speed : returned speed for jump // Returns: qfalse if too high or too far from start to end // Changes Globals: - //=========================================================================== int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity) { float phys_gravity, phys_maxvelocity; float maxjump, height2fall, t, top; vec3_t dir; phys_gravity = aassettings.phys_gravity; phys_maxvelocity = aassettings.phys_maxvelocity; //maximum height a player can jump with the given initial z velocity maxjump = 0.5 * phys_gravity * (zvel / phys_gravity) * (zvel / phys_gravity); //top of the parabolic jump top = start[2] + maxjump; //height the bot will fall from the top height2fall = top - end[2]; //if the goal is to high to jump to if (height2fall < 0) { *velocity = phys_maxvelocity; return 0; } //end if //time a player takes to fall the height t = sqrt(height2fall / (0.5 * phys_gravity)); //direction from start to end VectorSubtract(end, start, dir); // if ( (t + zvel / phys_gravity) == 0.0f ) { *velocity = phys_maxvelocity; return 0; } //calculate horizontal speed *velocity = sqrt(dir[0]*dir[0] + dir[1]*dir[1]) / (t + zvel / phys_gravity); //the horizontal speed must be lower than the max speed if (*velocity > phys_maxvelocity) { *velocity = phys_maxvelocity; return 0; } //end if return 1; } //end of the function AAS_HorizontalVelocityForJump ================================================ FILE: code/botlib/be_aas_move.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_move.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_move.h $ * *****************************************************************************/ #ifdef AASINTERN extern aas_settings_t aassettings; #endif //AASINTERN //movement prediction int AAS_PredictClientMovement(struct aas_clientmove_s *move, int entnum, vec3_t origin, int presencetype, int onground, vec3_t velocity, vec3_t cmdmove, int cmdframes, int maxframes, float frametime, int stopevent, int stopareanum, int visualize); //predict movement until bounding box is hit int AAS_ClientMovementHitBBox(struct aas_clientmove_s *move, int entnum, vec3_t origin, int presencetype, int onground, vec3_t velocity, vec3_t cmdmove, int cmdframes, int maxframes, float frametime, vec3_t mins, vec3_t maxs, int visualize); //returns true if on the ground at the given origin int AAS_OnGround(vec3_t origin, int presencetype, int passent); //returns true if swimming at the given origin int AAS_Swimming(vec3_t origin); //returns the jump reachability run start point void AAS_JumpReachRunStart(struct aas_reachability_s *reach, vec3_t runstart); //returns true if against a ladder at the given origin int AAS_AgainstLadder(vec3_t origin); //rocket jump Z velocity when rocket-jumping at origin float AAS_RocketJumpZVelocity(vec3_t origin); //bfg jump Z velocity when bfg-jumping at origin float AAS_BFGJumpZVelocity(vec3_t origin); //calculates the horizontal velocity needed for a jump and returns true this velocity could be calculated int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity); // void AAS_SetMovedir(vec3_t angles, vec3_t movedir); // int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs); // void AAS_InitSettings(void); ================================================ FILE: code/botlib/be_aas_optimize.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_optimize.c * * desc: decreases the .aas file size after the reachabilities have * been calculated, just dumps all the faces, edges and vertexes * * $Archive: /MissionPack/code/botlib/be_aas_optimize.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_libvar.h" #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "be_aas_def.h" typedef struct optimized_s { //vertexes int numvertexes; aas_vertex_t *vertexes; //edges int numedges; aas_edge_t *edges; //edge index int edgeindexsize; aas_edgeindex_t *edgeindex; //faces int numfaces; aas_face_t *faces; //face index int faceindexsize; aas_faceindex_t *faceindex; //convex areas int numareas; aas_area_t *areas; // int *vertexoptimizeindex; int *edgeoptimizeindex; int *faceoptimizeindex; } optimized_t; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_KeepEdge(aas_edge_t *edge) { return 1; } //end of the function AAS_KeepFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_OptimizeEdge(optimized_t *optimized, int edgenum) { int i, optedgenum; aas_edge_t *edge, *optedge; edge = &aasworld.edges[abs(edgenum)]; if (!AAS_KeepEdge(edge)) return 0; optedgenum = optimized->edgeoptimizeindex[abs(edgenum)]; if (optedgenum) { //keep the edge reversed sign if (edgenum > 0) return optedgenum; else return -optedgenum; } //end if optedge = &optimized->edges[optimized->numedges]; for (i = 0; i < 2; i++) { if (optimized->vertexoptimizeindex[edge->v[i]]) { optedge->v[i] = optimized->vertexoptimizeindex[edge->v[i]]; } //end if else { VectorCopy(aasworld.vertexes[edge->v[i]], optimized->vertexes[optimized->numvertexes]); optedge->v[i] = optimized->numvertexes; optimized->vertexoptimizeindex[edge->v[i]] = optimized->numvertexes; optimized->numvertexes++; } //end else } //end for optimized->edgeoptimizeindex[abs(edgenum)] = optimized->numedges; optedgenum = optimized->numedges; optimized->numedges++; //keep the edge reversed sign if (edgenum > 0) return optedgenum; else return -optedgenum; } //end of the function AAS_OptimizeEdge //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_KeepFace(aas_face_t *face) { if (!(face->faceflags & FACE_LADDER)) return 0; else return 1; } //end of the function AAS_KeepFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_OptimizeFace(optimized_t *optimized, int facenum) { int i, edgenum, optedgenum, optfacenum; aas_face_t *face, *optface; face = &aasworld.faces[abs(facenum)]; if (!AAS_KeepFace(face)) return 0; optfacenum = optimized->faceoptimizeindex[abs(facenum)]; if (optfacenum) { //keep the face side sign if (facenum > 0) return optfacenum; else return -optfacenum; } //end if optface = &optimized->faces[optimized->numfaces]; Com_Memcpy(optface, face, sizeof(aas_face_t)); optface->numedges = 0; optface->firstedge = optimized->edgeindexsize; for (i = 0; i < face->numedges; i++) { edgenum = aasworld.edgeindex[face->firstedge + i]; optedgenum = AAS_OptimizeEdge(optimized, edgenum); if (optedgenum) { optimized->edgeindex[optface->firstedge + optface->numedges] = optedgenum; optface->numedges++; optimized->edgeindexsize++; } //end if } //end for optimized->faceoptimizeindex[abs(facenum)] = optimized->numfaces; optfacenum = optimized->numfaces; optimized->numfaces++; //keep the face side sign if (facenum > 0) return optfacenum; else return -optfacenum; } //end of the function AAS_OptimizeFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_OptimizeArea(optimized_t *optimized, int areanum) { int i, facenum, optfacenum; aas_area_t *area, *optarea; area = &aasworld.areas[areanum]; optarea = &optimized->areas[areanum]; Com_Memcpy(optarea, area, sizeof(aas_area_t)); optarea->numfaces = 0; optarea->firstface = optimized->faceindexsize; for (i = 0; i < area->numfaces; i++) { facenum = aasworld.faceindex[area->firstface + i]; optfacenum = AAS_OptimizeFace(optimized, facenum); if (optfacenum) { optimized->faceindex[optarea->firstface + optarea->numfaces] = optfacenum; optarea->numfaces++; optimized->faceindexsize++; } //end if } //end for } //end of the function AAS_OptimizeArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_OptimizeAlloc(optimized_t *optimized) { optimized->vertexes = (aas_vertex_t *) GetClearedMemory(aasworld.numvertexes * sizeof(aas_vertex_t)); optimized->numvertexes = 0; optimized->edges = (aas_edge_t *) GetClearedMemory(aasworld.numedges * sizeof(aas_edge_t)); optimized->numedges = 1; //edge zero is a dummy optimized->edgeindex = (aas_edgeindex_t *) GetClearedMemory(aasworld.edgeindexsize * sizeof(aas_edgeindex_t)); optimized->edgeindexsize = 0; optimized->faces = (aas_face_t *) GetClearedMemory(aasworld.numfaces * sizeof(aas_face_t)); optimized->numfaces = 1; //face zero is a dummy optimized->faceindex = (aas_faceindex_t *) GetClearedMemory(aasworld.faceindexsize * sizeof(aas_faceindex_t)); optimized->faceindexsize = 0; optimized->areas = (aas_area_t *) GetClearedMemory(aasworld.numareas * sizeof(aas_area_t)); optimized->numareas = aasworld.numareas; // optimized->vertexoptimizeindex = (int *) GetClearedMemory(aasworld.numvertexes * sizeof(int)); optimized->edgeoptimizeindex = (int *) GetClearedMemory(aasworld.numedges * sizeof(int)); optimized->faceoptimizeindex = (int *) GetClearedMemory(aasworld.numfaces * sizeof(int)); } //end of the function AAS_OptimizeAlloc //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_OptimizeStore(optimized_t *optimized) { //store the optimized vertexes if (aasworld.vertexes) FreeMemory(aasworld.vertexes); aasworld.vertexes = optimized->vertexes; aasworld.numvertexes = optimized->numvertexes; //store the optimized edges if (aasworld.edges) FreeMemory(aasworld.edges); aasworld.edges = optimized->edges; aasworld.numedges = optimized->numedges; //store the optimized edge index if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); aasworld.edgeindex = optimized->edgeindex; aasworld.edgeindexsize = optimized->edgeindexsize; //store the optimized faces if (aasworld.faces) FreeMemory(aasworld.faces); aasworld.faces = optimized->faces; aasworld.numfaces = optimized->numfaces; //store the optimized face index if (aasworld.faceindex) FreeMemory(aasworld.faceindex); aasworld.faceindex = optimized->faceindex; aasworld.faceindexsize = optimized->faceindexsize; //store the optimized areas if (aasworld.areas) FreeMemory(aasworld.areas); aasworld.areas = optimized->areas; aasworld.numareas = optimized->numareas; //free optimize indexes FreeMemory(optimized->vertexoptimizeindex); FreeMemory(optimized->edgeoptimizeindex); FreeMemory(optimized->faceoptimizeindex); } //end of the function AAS_OptimizeStore //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_Optimize(void) { int i, sign; optimized_t optimized; AAS_OptimizeAlloc(&optimized); for (i = 1; i < aasworld.numareas; i++) { AAS_OptimizeArea(&optimized, i); } //end for //reset the reachability face pointers for (i = 0; i < aasworld.reachabilitysize; i++) { //NOTE: for TRAVEL_ELEVATOR the facenum is the model number of // the elevator if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) continue; //NOTE: for TRAVEL_JUMPPAD the facenum is the Z velocity and the edgenum is the hor velocity if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) continue; //NOTE: for TRAVEL_FUNCBOB the facenum and edgenum contain other coded information if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) continue; // sign = aasworld.reachability[i].facenum; aasworld.reachability[i].facenum = optimized.faceoptimizeindex[abs(aasworld.reachability[i].facenum)]; if (sign < 0) aasworld.reachability[i].facenum = -aasworld.reachability[i].facenum; sign = aasworld.reachability[i].edgenum; aasworld.reachability[i].edgenum = optimized.edgeoptimizeindex[abs(aasworld.reachability[i].edgenum)]; if (sign < 0) aasworld.reachability[i].edgenum = -aasworld.reachability[i].edgenum; } //end for //store the optimized AAS data into aasworld AAS_OptimizeStore(&optimized); //print some nice stuff :) botimport.Print(PRT_MESSAGE, "AAS data optimized.\n"); } //end of the function AAS_Optimize ================================================ FILE: code/botlib/be_aas_optimize.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_optimize.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_optimize.h $ * *****************************************************************************/ void AAS_Optimize(void); ================================================ FILE: code/botlib/be_aas_reach.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_reach.c * * desc: reachability calculations * * $Archive: /MissionPack/code/botlib/be_aas_reach.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_log.h" #include "l_memory.h" #include "l_script.h" #include "l_libvar.h" #include "l_precomp.h" #include "l_struct.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_aas_def.h" extern int Sys_MilliSeconds(void); extern botlib_import_t botimport; //#define REACH_DEBUG //NOTE: all travel times are in hundreth of a second //maximum number of reachability links #define AAS_MAX_REACHABILITYSIZE 65536 //number of areas reachability is calculated for each frame #define REACHABILITYAREASPERCYCLE 15 //number of units reachability points are placed inside the areas #define INSIDEUNITS 2 #define INSIDEUNITS_WALKEND 5 #define INSIDEUNITS_WALKSTART 0.1 #define INSIDEUNITS_WATERJUMP 15 //area flag used for weapon jumping #define AREA_WEAPONJUMP 8192 //valid area to weapon jump to //number of reachabilities of each type int reach_swim; //swim int reach_equalfloor; //walk on floors with equal height int reach_step; //step up int reach_walk; //walk of step int reach_barrier; //jump up to a barrier int reach_waterjump; //jump out of water int reach_walkoffledge; //walk of a ledge int reach_jump; //jump int reach_ladder; //climb or descent a ladder int reach_teleport; //teleport int reach_elevator; //use an elevator int reach_funcbob; //use a func bob int reach_grapple; //grapple hook int reach_doublejump; //double jump int reach_rampjump; //ramp jump int reach_strafejump; //strafe jump (just normal jump but further) int reach_rocketjump; //rocket jump int reach_bfgjump; //bfg jump int reach_jumppad; //jump pads //if true grapple reachabilities are skipped int calcgrapplereach; //linked reachability typedef struct aas_lreachability_s { int areanum; //number of the reachable area int facenum; //number of the face towards the other area int edgenum; //number of the edge towards the other area vec3_t start; //start point of inter area movement vec3_t end; //end point of inter area movement int traveltype; //type of travel required to get to the area unsigned short int traveltime; //travel time of the inter area movement // struct aas_lreachability_s *next; } aas_lreachability_t; //temporary reachabilities aas_lreachability_t *reachabilityheap; //heap with reachabilities aas_lreachability_t *nextreachability; //next free reachability from the heap aas_lreachability_t **areareachability; //reachability links for every area int numlreachabilities; //=========================================================================== // returns the surface area of the given face // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float AAS_FaceArea(aas_face_t *face) { int i, edgenum, side; float total; vec_t *v; vec3_t d1, d2, cross; aas_edge_t *edge; edgenum = aasworld.edgeindex[face->firstedge]; side = edgenum < 0; edge = &aasworld.edges[abs(edgenum)]; v = aasworld.vertexes[edge->v[side]]; total = 0; for (i = 1; i < face->numedges - 1; i++) { edgenum = aasworld.edgeindex[face->firstedge + i]; side = edgenum < 0; edge = &aasworld.edges[abs(edgenum)]; VectorSubtract(aasworld.vertexes[edge->v[side]], v, d1); VectorSubtract(aasworld.vertexes[edge->v[!side]], v, d2); CrossProduct(d1, d2, cross); total += 0.5 * VectorLength(cross); } //end for return total; } //end of the function AAS_FaceArea //=========================================================================== // returns the volume of an area // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float AAS_AreaVolume(int areanum) { int i, edgenum, facenum, side; vec_t d, a, volume; vec3_t corner; aas_plane_t *plane; aas_edge_t *edge; aas_face_t *face; aas_area_t *area; area = &aasworld.areas[areanum]; facenum = aasworld.faceindex[area->firstface]; face = &aasworld.faces[abs(facenum)]; edgenum = aasworld.edgeindex[face->firstedge]; edge = &aasworld.edges[abs(edgenum)]; // VectorCopy(aasworld.vertexes[edge->v[0]], corner); //make tetrahedrons to all other faces volume = 0; for (i = 0; i < area->numfaces; i++) { facenum = abs(aasworld.faceindex[area->firstface + i]); face = &aasworld.faces[facenum]; side = face->backarea != areanum; plane = &aasworld.planes[face->planenum ^ side]; d = -(DotProduct (corner, plane->normal) - plane->dist); a = AAS_FaceArea(face); volume += d * a; } //end for volume /= 3; return volume; } //end of the function AAS_AreaVolume //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_BestReachableLinkArea(aas_link_t *areas) { aas_link_t *link; for (link = areas; link; link = link->next_area) { if (AAS_AreaGrounded(link->areanum) || AAS_AreaSwim(link->areanum)) { return link->areanum; } //end if } //end for // for (link = areas; link; link = link->next_area) { if (link->areanum) return link->areanum; //FIXME: this is a bad idea when the reachability is not yet // calculated when the level items are loaded if (AAS_AreaReachability(link->areanum)) return link->areanum; } //end for return 0; } //end of the function AAS_BestReachableLinkArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_GetJumpPadInfo(int ent, vec3_t areastart, vec3_t absmins, vec3_t absmaxs, vec3_t velocity) { int modelnum, ent2; float speed, height, gravity, time, dist, forward; vec3_t origin, angles, teststart, ent2origin; aas_trace_t trace; char model[MAX_EPAIRKEY]; char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; // AAS_FloatForBSPEpairKey(ent, "speed", &speed); if (!speed) speed = 1000; VectorClear(angles); //get the mins, maxs and origin of the model AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); if (model[0]) modelnum = atoi(model+1); else modelnum = 0; AAS_BSPModelMinsMaxsOrigin(modelnum, angles, absmins, absmaxs, origin); VectorAdd(origin, absmins, absmins); VectorAdd(origin, absmaxs, absmaxs); VectorAdd(absmins, absmaxs, origin); VectorScale (origin, 0.5, origin); //get the start areas VectorCopy(origin, teststart); teststart[2] += 64; trace = AAS_TraceClientBBox(teststart, origin, PRESENCE_CROUCH, -1); if (trace.startsolid) { botimport.Print(PRT_MESSAGE, "trigger_push start solid\n"); VectorCopy(origin, areastart); } //end if else { VectorCopy(trace.endpos, areastart); } //end else areastart[2] += 0.125; // //AAS_DrawPermanentCross(origin, 4, 4); //get the target entity AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY); for (ent2 = AAS_NextBSPEntity(0); ent2; ent2 = AAS_NextBSPEntity(ent2)) { if (!AAS_ValueForBSPEpairKey(ent2, "targetname", targetname, MAX_EPAIRKEY)) continue; if (!strcmp(targetname, target)) break; } //end for if (!ent2) { botimport.Print(PRT_MESSAGE, "trigger_push without target entity %s\n", target); return qfalse; } //end if AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin); // height = ent2origin[2] - origin[2]; gravity = aassettings.phys_gravity; time = sqrt( height / ( 0.5 * gravity ) ); if (!time) { botimport.Print(PRT_MESSAGE, "trigger_push without time\n"); return qfalse; } //end if // set s.origin2 to the push velocity VectorSubtract ( ent2origin, origin, velocity); dist = VectorNormalize( velocity); forward = dist / time; //FIXME: why multiply by 1.1 forward *= 1.1f; VectorScale(velocity, forward, velocity); velocity[2] = time * gravity; return qtrue; } //end of the function AAS_GetJumpPadInfo //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs) { int area2num, ent, bot_visualizejumppads, bestareanum; float volume, bestareavolume; vec3_t areastart, cmdmove, bboxmins, bboxmaxs; vec3_t absmins, absmaxs, velocity; aas_clientmove_t move; aas_link_t *areas, *link; char classname[MAX_EPAIRKEY]; #ifdef BSPC bot_visualizejumppads = 0; #else bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0"); #endif VectorAdd(origin, mins, bboxmins); VectorAdd(origin, maxs, bboxmaxs); for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) { if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; if (strcmp(classname, "trigger_push")) continue; // if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) continue; //get the areas the jump pad brush is in areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); for (link = areas; link; link = link->next_area) { if (AAS_AreaJumpPad(link->areanum)) break; } //end for if (!link) { botimport.Print(PRT_MESSAGE, "trigger_push not in any jump pad area\n"); AAS_UnlinkFromAreas(areas); continue; } //end if // //botimport.Print(PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2]); // VectorSet(cmdmove, 0, 0, 0); Com_Memset(&move, 0, sizeof(aas_clientmove_t)); area2num = 0; AAS_ClientMovementHitBBox(&move, -1, areastart, PRESENCE_NORMAL, qfalse, velocity, cmdmove, 0, 30, 0.1f, bboxmins, bboxmaxs, bot_visualizejumppads); if (move.frames < 30) { bestareanum = 0; bestareavolume = 0; for (link = areas; link; link = link->next_area) { if (!AAS_AreaJumpPad(link->areanum)) continue; volume = AAS_AreaVolume(link->areanum); if (volume >= bestareavolume) { bestareanum = link->areanum; bestareavolume = volume; } //end if } //end if AAS_UnlinkFromAreas(areas); return bestareanum; } //end if AAS_UnlinkFromAreas(areas); } //end for return 0; } //end of the function AAS_BestReachableFromJumpPadArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin) { int areanum, i, j, k, l; aas_link_t *areas; vec3_t absmins, absmaxs; //vec3_t bbmins, bbmaxs; vec3_t start, end; aas_trace_t trace; if (!aasworld.loaded) { botimport.Print(PRT_ERROR, "AAS_BestReachableArea: aas not loaded\n"); return 0; } //end if //find a point in an area VectorCopy(origin, start); areanum = AAS_PointAreaNum(start); //while no area found fudge around a little for (i = 0; i < 5 && !areanum; i++) { for (j = 0; j < 5 && !areanum; j++) { for (k = -1; k <= 1 && !areanum; k++) { for (l = -1; l <= 1 && !areanum; l++) { VectorCopy(origin, start); start[0] += (float) j * 4 * k; start[1] += (float) j * 4 * l; start[2] += (float) i * 4; areanum = AAS_PointAreaNum(start); } //end for } //end for } //end for } //end for //if an area was found if (areanum) { //drop client bbox down and try again VectorCopy(start, end); start[2] += 0.25; end[2] -= 50; trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); if (!trace.startsolid) { areanum = AAS_PointAreaNum(trace.endpos); VectorCopy(trace.endpos, goalorigin); //FIXME: cannot enable next line right now because the reachability // does not have to be calculated when the level items are loaded //if the origin is in an area with reachability //if (AAS_AreaReachability(areanum)) return areanum; if (areanum) return areanum; } //end if else { //it can very well happen that the AAS_PointAreaNum function tells that //a point is in an area and that starting a AAS_TraceClientBBox from that //point will return trace.startsolid qtrue #if 0 if (AAS_PointAreaNum(start)) { Log_Write("point %f %f %f in area %d but trace startsolid", start[0], start[1], start[2], areanum); AAS_DrawPermanentCross(start, 4, LINECOLOR_RED); } //end if botimport.Print(PRT_MESSAGE, "AAS_BestReachableArea: start solid\n"); #endif VectorCopy(start, goalorigin); return areanum; } //end else } //end if // //AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); //NOTE: the goal origin does not have to be in the goal area // because the bot will have to move towards the item origin anyway VectorCopy(origin, goalorigin); // VectorAdd(origin, mins, absmins); VectorAdd(origin, maxs, absmaxs); //add bounding box size //VectorSubtract(absmins, bbmaxs, absmins); //VectorSubtract(absmaxs, bbmins, absmaxs); //link an invalid (-1) entity areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); //get the reachable link arae areanum = AAS_BestReachableLinkArea(areas); //unlink the invalid entity AAS_UnlinkFromAreas(areas); // return areanum; } //end of the function AAS_BestReachableArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_SetupReachabilityHeap(void) { int i; reachabilityheap = (aas_lreachability_t *) GetClearedMemory( AAS_MAX_REACHABILITYSIZE * sizeof(aas_lreachability_t)); for (i = 0; i < AAS_MAX_REACHABILITYSIZE-1; i++) { reachabilityheap[i].next = &reachabilityheap[i+1]; } //end for reachabilityheap[AAS_MAX_REACHABILITYSIZE-1].next = NULL; nextreachability = reachabilityheap; numlreachabilities = 0; } //end of the function AAS_InitReachabilityHeap //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShutDownReachabilityHeap(void) { FreeMemory(reachabilityheap); numlreachabilities = 0; } //end of the function AAS_ShutDownReachabilityHeap //=========================================================================== // returns a reachability link // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_lreachability_t *AAS_AllocReachability(void) { aas_lreachability_t *r; if (!nextreachability) return NULL; //make sure the error message only shows up once if (!nextreachability->next) AAS_Error("AAS_MAX_REACHABILITYSIZE"); // r = nextreachability; nextreachability = nextreachability->next; numlreachabilities++; return r; } //end of the function AAS_AllocReachability //=========================================================================== // frees a reachability link // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeReachability(aas_lreachability_t *lreach) { Com_Memset(lreach, 0, sizeof(aas_lreachability_t)); lreach->next = nextreachability; nextreachability = lreach; numlreachabilities--; } //end of the function AAS_FreeReachability //=========================================================================== // returns qtrue if the area has reachability links // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaReachability(int areanum) { if (areanum < 0 || areanum >= aasworld.numareas) { AAS_Error("AAS_AreaReachability: areanum %d out of range", areanum); return 0; } //end if return aasworld.areasettings[areanum].numreachableareas; } //end of the function AAS_AreaReachability //=========================================================================== // returns the surface area of all ground faces together of the area // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float AAS_AreaGroundFaceArea(int areanum) { int i; float total; aas_area_t *area; aas_face_t *face; total = 0; area = &aasworld.areas[areanum]; for (i = 0; i < area->numfaces; i++) { face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; if (!(face->faceflags & FACE_GROUND)) continue; // total += AAS_FaceArea(face); } //end for return total; } //end of the function AAS_AreaGroundFaceArea //=========================================================================== // returns the center of a face // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FaceCenter(int facenum, vec3_t center) { int i; float scale; aas_face_t *face; aas_edge_t *edge; face = &aasworld.faces[facenum]; VectorClear(center); for (i = 0; i < face->numedges; i++) { edge = &aasworld.edges[abs(aasworld.edgeindex[face->firstedge + i])]; VectorAdd(center, aasworld.vertexes[edge->v[0]], center); VectorAdd(center, aasworld.vertexes[edge->v[1]], center); } //end for scale = 0.5 / face->numedges; VectorScale(center, scale, center); } //end of the function AAS_FaceCenter //=========================================================================== // returns the maximum distance a player can fall before being damaged // damage = deltavelocity*deltavelocity * 0.0001 // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_FallDamageDistance(void) { float maxzvelocity, gravity, t; maxzvelocity = sqrt(30 * 10000); gravity = aassettings.phys_gravity; t = maxzvelocity / gravity; return 0.5 * gravity * t * t; } //end of the function AAS_FallDamageDistance //=========================================================================== // distance = 0.5 * gravity * t * t // vel = t * gravity // damage = vel * vel * 0.0001 // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float AAS_FallDelta(float distance) { float t, delta, gravity; gravity = aassettings.phys_gravity; t = sqrt(fabs(distance) * 2 / gravity); delta = t * gravity; return delta * delta * 0.0001; } //end of the function AAS_FallDelta //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float AAS_MaxJumpHeight(float phys_jumpvel) { float phys_gravity; phys_gravity = aassettings.phys_gravity; //maximum height a player can jump with the given initial z velocity return 0.5 * phys_gravity * (phys_jumpvel / phys_gravity) * (phys_jumpvel / phys_gravity); } //end of the function MaxJumpHeight //=========================================================================== // returns true if a player can only crouch in the area // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float AAS_MaxJumpDistance(float phys_jumpvel) { float phys_gravity, phys_maxvelocity, t; phys_gravity = aassettings.phys_gravity; phys_maxvelocity = aassettings.phys_maxvelocity; //time a player takes to fall the height t = sqrt(aassettings.rs_maxjumpfallheight / (0.5 * phys_gravity)); //maximum distance return phys_maxvelocity * (t + phys_jumpvel / phys_gravity); } //end of the function AAS_MaxJumpDistance //=========================================================================== // returns true if a player can only crouch in the area // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaCrouch(int areanum) { if (!(aasworld.areasettings[areanum].presencetype & PRESENCE_NORMAL)) return qtrue; else return qfalse; } //end of the function AAS_AreaCrouch //=========================================================================== // returns qtrue if it is possible to swim in the area // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaSwim(int areanum) { if (aasworld.areasettings[areanum].areaflags & AREA_LIQUID) return qtrue; else return qfalse; } //end of the function AAS_AreaSwim //=========================================================================== // returns qtrue if the area contains a liquid // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaLiquid(int areanum) { if (aasworld.areasettings[areanum].areaflags & AREA_LIQUID) return qtrue; else return qfalse; } //end of the function AAS_AreaLiquid //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaLava(int areanum) { return (aasworld.areasettings[areanum].contents & AREACONTENTS_LAVA); } //end of the function AAS_AreaLava //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaSlime(int areanum) { return (aasworld.areasettings[areanum].contents & AREACONTENTS_SLIME); } //end of the function AAS_AreaSlime //=========================================================================== // returns qtrue if the area contains ground faces // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaGrounded(int areanum) { return (aasworld.areasettings[areanum].areaflags & AREA_GROUNDED); } //end of the function AAS_AreaGround //=========================================================================== // returns true if the area contains ladder faces // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaLadder(int areanum) { return (aasworld.areasettings[areanum].areaflags & AREA_LADDER); } //end of the function AAS_AreaLadder //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaJumpPad(int areanum) { return (aasworld.areasettings[areanum].contents & AREACONTENTS_JUMPPAD); } //end of the function AAS_AreaJumpPad //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaTeleporter(int areanum) { return (aasworld.areasettings[areanum].contents & AREACONTENTS_TELEPORTER); } //end of the function AAS_AreaTeleporter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaClusterPortal(int areanum) { return (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL); } //end of the function AAS_AreaClusterPortal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaDoNotEnter(int areanum) { return (aasworld.areasettings[areanum].contents & AREACONTENTS_DONOTENTER); } //end of the function AAS_AreaDoNotEnter //=========================================================================== // returns the time it takes perform a barrier jump // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== unsigned short int AAS_BarrierJumpTravelTime(void) { return aassettings.phys_jumpvel / (aassettings.phys_gravity * 0.1); } //end op the function AAS_BarrierJumpTravelTime //=========================================================================== // returns true if there already exists a reachability from area1 to area2 // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_ReachabilityExists(int area1num, int area2num) { aas_lreachability_t *r; for (r = areareachability[area1num]; r; r = r->next) { if (r->areanum == area2num) return qtrue; } //end for return qfalse; } //end of the function AAS_ReachabilityExists //=========================================================================== // returns true if there is a solid just after the end point when going // from start to end // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_NearbySolidOrGap(vec3_t start, vec3_t end) { vec3_t dir, testpoint; int areanum; VectorSubtract(end, start, dir); dir[2] = 0; VectorNormalize(dir); VectorMA(end, 48, dir, testpoint); areanum = AAS_PointAreaNum(testpoint); if (!areanum) { testpoint[2] += 16; areanum = AAS_PointAreaNum(testpoint); if (!areanum) return qtrue; } //end if VectorMA(end, 64, dir, testpoint); areanum = AAS_PointAreaNum(testpoint); if (areanum) { if (!AAS_AreaSwim(areanum) && !AAS_AreaGrounded(areanum)) return qtrue; } //end if return qfalse; } //end of the function AAS_SolidGapTime //=========================================================================== // searches for swim reachabilities between adjacent areas // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_Reachability_Swim(int area1num, int area2num) { int i, j, face1num, face2num, side1; aas_area_t *area1, *area2; aas_areasettings_t *areasettings; aas_lreachability_t *lreach; aas_face_t *face1; aas_plane_t *plane; vec3_t start; if (!AAS_AreaSwim(area1num) || !AAS_AreaSwim(area2num)) return qfalse; //if the second area is crouch only if (!(aasworld.areasettings[area2num].presencetype & PRESENCE_NORMAL)) return qfalse; area1 = &aasworld.areas[area1num]; area2 = &aasworld.areas[area2num]; //if the areas are not near anough for (i = 0; i < 3; i++) { if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; } //end for //find a shared face and create a reachability link for (i = 0; i < area1->numfaces; i++) { face1num = aasworld.faceindex[area1->firstface + i]; side1 = face1num < 0; face1num = abs(face1num); // for (j = 0; j < area2->numfaces; j++) { face2num = abs(aasworld.faceindex[area2->firstface + j]); // if (face1num == face2num) { AAS_FaceCenter(face1num, start); // if (AAS_PointContents(start) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { // face1 = &aasworld.faces[face1num]; areasettings = &aasworld.areasettings[area1num]; //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area2num; lreach->facenum = face1num; lreach->edgenum = 0; VectorCopy(start, lreach->start); plane = &aasworld.planes[face1->planenum ^ side1]; VectorMA(lreach->start, -INSIDEUNITS, plane->normal, lreach->end); lreach->traveltype = TRAVEL_SWIM; lreach->traveltime = 1; //if the volume of the area is rather small if (AAS_AreaVolume(area2num) < 800) lreach->traveltime += 200; //if (!(AAS_PointContents(start) & MASK_WATER)) lreach->traveltime += 500; //link the reachability lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; reach_swim++; return qtrue; } //end if } //end if } //end for } //end for return qfalse; } //end of the function AAS_Reachability_Swim //=========================================================================== // searches for reachabilities between adjacent areas with equal floor // heights // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_Reachability_EqualFloorHeight(int area1num, int area2num) { int i, j, edgenum, edgenum1, edgenum2, foundreach, side; float height, bestheight, length, bestlength; vec3_t dir, start, end, normal, invgravity, gravitydirection = {0, 0, -1}; vec3_t edgevec; aas_area_t *area1, *area2; aas_face_t *face1, *face2; aas_edge_t *edge; aas_plane_t *plane2; aas_lreachability_t lr, *lreach; if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) return qfalse; area1 = &aasworld.areas[area1num]; area2 = &aasworld.areas[area2num]; //if the areas are not near anough in the x-y direction for (i = 0; i < 2; i++) { if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; } //end for //if area 2 is too high above area 1 if (area2->mins[2] > area1->maxs[2]) return qfalse; // VectorCopy(gravitydirection, invgravity); VectorInverse(invgravity); // bestheight = 99999; bestlength = 0; foundreach = qfalse; Com_Memset(&lr, 0, sizeof(aas_lreachability_t)); //make the compiler happy // //check if the areas have ground faces with a common edge //if existing use the lowest common edge for a reachability link for (i = 0; i < area1->numfaces; i++) { face1 = &aasworld.faces[abs(aasworld.faceindex[area1->firstface + i])]; if (!(face1->faceflags & FACE_GROUND)) continue; // for (j = 0; j < area2->numfaces; j++) { face2 = &aasworld.faces[abs(aasworld.faceindex[area2->firstface + j])]; if (!(face2->faceflags & FACE_GROUND)) continue; //if there is a common edge for (edgenum1 = 0; edgenum1 < face1->numedges; edgenum1++) { for (edgenum2 = 0; edgenum2 < face2->numedges; edgenum2++) { if (abs(aasworld.edgeindex[face1->firstedge + edgenum1]) != abs(aasworld.edgeindex[face2->firstedge + edgenum2])) continue; edgenum = aasworld.edgeindex[face1->firstedge + edgenum1]; side = edgenum < 0; edge = &aasworld.edges[abs(edgenum)]; //get the length of the edge VectorSubtract(aasworld.vertexes[edge->v[1]], aasworld.vertexes[edge->v[0]], dir); length = VectorLength(dir); //get the start point VectorAdd(aasworld.vertexes[edge->v[0]], aasworld.vertexes[edge->v[1]], start); VectorScale(start, 0.5, start); VectorCopy(start, end); //get the end point several units inside area2 //and the start point several units inside area1 //NOTE: normal is pointing into area2 because the //face edges are stored counter clockwise VectorSubtract(aasworld.vertexes[edge->v[side]], aasworld.vertexes[edge->v[!side]], edgevec); plane2 = &aasworld.planes[face2->planenum]; CrossProduct(edgevec, plane2->normal, normal); VectorNormalize(normal); // //VectorMA(start, -1, normal, start); VectorMA(end, INSIDEUNITS_WALKEND, normal, end); VectorMA(start, INSIDEUNITS_WALKSTART, normal, start); end[2] += 0.125; // height = DotProduct(invgravity, start); //NOTE: if there's nearby solid or a gap area after this area //disabled this crap //if (AAS_NearbySolidOrGap(start, end)) height += 200; //NOTE: disabled because it disables reachabilities to very small areas //if (AAS_PointAreaNum(end) != area2num) continue; //get the longest lowest edge if (height < bestheight || (height < bestheight + 1 && length > bestlength)) { bestheight = height; bestlength = length; //create a new reachability link lr.areanum = area2num; lr.facenum = 0; lr.edgenum = edgenum; VectorCopy(start, lr.start); VectorCopy(end, lr.end); lr.traveltype = TRAVEL_WALK; lr.traveltime = 1; foundreach = qtrue; } //end if } //end for } //end for } //end for } //end for if (foundreach) { //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = lr.areanum; lreach->facenum = lr.facenum; lreach->edgenum = lr.edgenum; VectorCopy(lr.start, lreach->start); VectorCopy(lr.end, lreach->end); lreach->traveltype = lr.traveltype; lreach->traveltime = lr.traveltime; lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; //if going into a crouch area if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) { lreach->traveltime += aassettings.rs_startcrouch; } //end if /* //NOTE: if there's nearby solid or a gap area after this area if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) { lreach->traveltime += 100; } //end if */ //avoid rather small areas //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; // reach_equalfloor++; return qtrue; } //end if return qfalse; } //end of the function AAS_Reachability_EqualFloorHeight //=========================================================================== // searches step, barrier, waterjump and walk off ledge reachabilities // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(int area1num, int area2num) { int i, j, k, l, edge1num, edge2num, areas[10], numareas; int ground_bestarea2groundedgenum, ground_foundreach; int water_bestarea2groundedgenum, water_foundreach; int side1, area1swim, faceside1, groundface1num; float dist, dist1, dist2, diff, invgravitydot, ortdot; float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y; float length, ground_bestlength, water_bestlength, ground_bestdist, water_bestdist; vec3_t v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2; vec3_t normal, ort, edgevec, start, end, dir; vec3_t ground_beststart, ground_bestend, ground_bestnormal; vec3_t water_beststart, water_bestend, water_bestnormal; vec3_t invgravity = {0, 0, 1}; vec3_t testpoint; aas_plane_t *plane; aas_area_t *area1, *area2; aas_face_t *groundface1, *groundface2, *ground_bestface1, *water_bestface1; aas_edge_t *edge1, *edge2; aas_lreachability_t *lreach; aas_trace_t trace; //must be able to walk or swim in the first area if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) return qfalse; // if (!AAS_AreaGrounded(area2num) && !AAS_AreaSwim(area2num)) return qfalse; // area1 = &aasworld.areas[area1num]; area2 = &aasworld.areas[area2num]; //if the first area contains a liquid area1swim = AAS_AreaSwim(area1num); //if the areas are not near anough in the x-y direction for (i = 0; i < 2; i++) { if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; } //end for // ground_foundreach = qfalse; ground_bestdist = 99999; ground_bestlength = 0; ground_bestarea2groundedgenum = 0; // water_foundreach = qfalse; water_bestdist = 99999; water_bestlength = 0; water_bestarea2groundedgenum = 0; // for (i = 0; i < area1->numfaces; i++) { groundface1num = aasworld.faceindex[area1->firstface + i]; faceside1 = groundface1num < 0; groundface1 = &aasworld.faces[abs(groundface1num)]; //if this isn't a ground face if (!(groundface1->faceflags & FACE_GROUND)) { //if we can swim in the first area if (area1swim) { //face plane must be more or less horizontal plane = &aasworld.planes[groundface1->planenum ^ (!faceside1)]; if (DotProduct(plane->normal, invgravity) < 0.7) continue; } //end if else { //if we can't swim in the area it must be a ground face continue; } //end else } //end if // for (k = 0; k < groundface1->numedges; k++) { edge1num = aasworld.edgeindex[groundface1->firstedge + k]; side1 = (edge1num < 0); //NOTE: for water faces we must take the side area 1 is // on into account because the face is shared and doesn't // have to be oriented correctly if (!(groundface1->faceflags & FACE_GROUND)) side1 = (side1 == faceside1); edge1num = abs(edge1num); edge1 = &aasworld.edges[edge1num]; //vertexes of the edge VectorCopy(aasworld.vertexes[edge1->v[!side1]], v1); VectorCopy(aasworld.vertexes[edge1->v[side1]], v2); //get a vertical plane through the edge //NOTE: normal is pointing into area 2 because the //face edges are stored counter clockwise VectorSubtract(v2, v1, edgevec); CrossProduct(edgevec, invgravity, normal); VectorNormalize(normal); dist = DotProduct(normal, v1); //check the faces from the second area for (j = 0; j < area2->numfaces; j++) { groundface2 = &aasworld.faces[abs(aasworld.faceindex[area2->firstface + j])]; //must be a ground face if (!(groundface2->faceflags & FACE_GROUND)) continue; //check the edges of this ground face for (l = 0; l < groundface2->numedges; l++) { edge2num = abs(aasworld.edgeindex[groundface2->firstedge + l]); edge2 = &aasworld.edges[edge2num]; //vertexes of the edge VectorCopy(aasworld.vertexes[edge2->v[0]], v3); VectorCopy(aasworld.vertexes[edge2->v[1]], v4); //check the distance between the two points and the vertical plane //through the edge of area1 diff = DotProduct(normal, v3) - dist; if (diff < -0.1 || diff > 0.1) continue; diff = DotProduct(normal, v4) - dist; if (diff < -0.1 || diff > 0.1) continue; // //project the two ground edges into the step side plane //and calculate the shortest distance between the two //edges if they overlap in the direction orthogonal to //the gravity direction CrossProduct(invgravity, normal, ort); invgravitydot = DotProduct(invgravity, invgravity); ortdot = DotProduct(ort, ort); //projection into the step plane //NOTE: since gravity is vertical this is just the z coordinate y1 = v1[2];//DotProduct(v1, invgravity) / invgravitydot; y2 = v2[2];//DotProduct(v2, invgravity) / invgravitydot; y3 = v3[2];//DotProduct(v3, invgravity) / invgravitydot; y4 = v4[2];//DotProduct(v4, invgravity) / invgravitydot; // x1 = DotProduct(v1, ort) / ortdot; x2 = DotProduct(v2, ort) / ortdot; x3 = DotProduct(v3, ort) / ortdot; x4 = DotProduct(v4, ort) / ortdot; // if (x1 > x2) { tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; VectorCopy(v1, tmpv); VectorCopy(v2, v1); VectorCopy(tmpv, v2); } //end if if (x3 > x4) { tmp = x3; x3 = x4; x4 = tmp; tmp = y3; y3 = y4; y4 = tmp; VectorCopy(v3, tmpv); VectorCopy(v4, v3); VectorCopy(tmpv, v4); } //end if //if the two projected edge lines have no overlap if (x2 <= x3 || x4 <= x1) { // Log_Write("lines no overlap: from area %d to %d\r\n", area1num, area2num); continue; } //end if //if the two lines fully overlap if ((x1 - 0.5 < x3 && x4 < x2 + 0.5) && (x3 - 0.5 < x1 && x2 < x4 + 0.5)) { dist1 = y3 - y1; dist2 = y4 - y2; VectorCopy(v1, p1area1); VectorCopy(v2, p2area1); VectorCopy(v3, p1area2); VectorCopy(v4, p2area2); } //end if else { //if the points are equal if (x1 > x3 - 0.1 && x1 < x3 + 0.1) { dist1 = y3 - y1; VectorCopy(v1, p1area1); VectorCopy(v3, p1area2); } //end if else if (x1 < x3) { y = y1 + (x3 - x1) * (y2 - y1) / (x2 - x1); dist1 = y3 - y; VectorCopy(v3, p1area1); p1area1[2] = y; VectorCopy(v3, p1area2); } //end if else { y = y3 + (x1 - x3) * (y4 - y3) / (x4 - x3); dist1 = y - y1; VectorCopy(v1, p1area1); VectorCopy(v1, p1area2); p1area2[2] = y; } //end if //if the points are equal if (x2 > x4 - 0.1 && x2 < x4 + 0.1) { dist2 = y4 - y2; VectorCopy(v2, p2area1); VectorCopy(v4, p2area2); } //end if else if (x2 < x4) { y = y3 + (x2 - x3) * (y4 - y3) / (x4 - x3); dist2 = y - y2; VectorCopy(v2, p2area1); VectorCopy(v2, p2area2); p2area2[2] = y; } //end if else { y = y1 + (x4 - x1) * (y2 - y1) / (x2 - x1); dist2 = y4 - y; VectorCopy(v4, p2area1); p2area1[2] = y; VectorCopy(v4, p2area2); } //end else } //end else //if both distances are pretty much equal //then we take the middle of the points if (dist1 > dist2 - 1 && dist1 < dist2 + 1) { dist = dist1; VectorAdd(p1area1, p2area1, start); VectorScale(start, 0.5, start); VectorAdd(p1area2, p2area2, end); VectorScale(end, 0.5, end); } //end if else if (dist1 < dist2) { dist = dist1; VectorCopy(p1area1, start); VectorCopy(p1area2, end); } //end else if else { dist = dist2; VectorCopy(p2area1, start); VectorCopy(p2area2, end); } //end else //get the length of the overlapping part of the edges of the two areas VectorSubtract(p2area2, p1area2, dir); length = VectorLength(dir); // if (groundface1->faceflags & FACE_GROUND) { //if the vertical distance is smaller if (dist < ground_bestdist || //or the vertical distance is pretty much the same //but the overlapping part of the edges is longer (dist < ground_bestdist + 1 && length > ground_bestlength)) { ground_bestdist = dist; ground_bestlength = length; ground_foundreach = qtrue; ground_bestarea2groundedgenum = edge1num; ground_bestface1 = groundface1; //best point towards area1 VectorCopy(start, ground_beststart); //normal is pointing into area2 VectorCopy(normal, ground_bestnormal); //best point towards area2 VectorCopy(end, ground_bestend); } //end if } //end if else { //if the vertical distance is smaller if (dist < water_bestdist || //or the vertical distance is pretty much the same //but the overlapping part of the edges is longer (dist < water_bestdist + 1 && length > water_bestlength)) { water_bestdist = dist; water_bestlength = length; water_foundreach = qtrue; water_bestarea2groundedgenum = edge1num; water_bestface1 = groundface1; //best point towards area1 VectorCopy(start, water_beststart); //normal is pointing into area2 VectorCopy(normal, water_bestnormal); //best point towards area2 VectorCopy(end, water_bestend); } //end if } //end else } //end for } //end for } //end for } //end for // // NOTE: swim reachabilities are already filtered out // // Steps // // --------- // | step height -> TRAVEL_WALK //--------| // // --------- //~~~~~~~~| step height and low water -> TRAVEL_WALK //--------| // //~~~~~~~~~~~~~~~~~~ // --------- // | step height and low water up to the step -> TRAVEL_WALK //--------| // //check for a step reachability if (ground_foundreach) { //if area2 is higher but lower than the maximum step height //NOTE: ground_bestdist >= 0 also catches equal floor reachabilities if (ground_bestdist >= 0 && ground_bestdist < aassettings.phys_maxstep) { //create walk reachability from area1 to area2 lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area2num; lreach->facenum = 0; lreach->edgenum = ground_bestarea2groundedgenum; VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); lreach->traveltype = TRAVEL_WALK; lreach->traveltime = 0;//1; //if going into a crouch area if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) { lreach->traveltime += aassettings.rs_startcrouch; } //end if lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; //NOTE: if there's nearby solid or a gap area after this area /* if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) { lreach->traveltime += 100; } //end if */ //avoid rather small areas //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; // reach_step++; return qtrue; } //end if } //end if // // Water Jumps // // --------- // | //~~~~~~~~| // | // | higher than step height and water up to waterjump height -> TRAVEL_WATERJUMP //--------| // //~~~~~~~~~~~~~~~~~~ // --------- // | // | // | // | higher than step height and low water up to the step -> TRAVEL_WATERJUMP //--------| // //check for a waterjump reachability if (water_foundreach) { //get a test point a little bit towards area1 VectorMA(water_bestend, -INSIDEUNITS, water_bestnormal, testpoint); //go down the maximum waterjump height testpoint[2] -= aassettings.phys_maxwaterjump; //if there IS water the sv_maxwaterjump height below the bestend point if (aasworld.areasettings[AAS_PointAreaNum(testpoint)].areaflags & AREA_LIQUID) { //don't create rediculous water jump reachabilities from areas very far below //the water surface if (water_bestdist < aassettings.phys_maxwaterjump + 24) { //waterjumping from or towards a crouch only area is not possible in Quake2 if ((aasworld.areasettings[area1num].presencetype & PRESENCE_NORMAL) && (aasworld.areasettings[area2num].presencetype & PRESENCE_NORMAL)) { //create water jump reachability from area1 to area2 lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area2num; lreach->facenum = 0; lreach->edgenum = water_bestarea2groundedgenum; VectorCopy(water_beststart, lreach->start); VectorMA(water_bestend, INSIDEUNITS_WATERJUMP, water_bestnormal, lreach->end); lreach->traveltype = TRAVEL_WATERJUMP; lreach->traveltime = aassettings.rs_waterjump; lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; //we've got another waterjump reachability reach_waterjump++; return qtrue; } //end if } //end if } //end if } //end if // // Barrier Jumps // // --------- // | // | // | // | higher than step height lower than barrier height -> TRAVEL_BARRIERJUMP //--------| // // --------- // | // | // | //~~~~~~~~| higher than step height lower than barrier height //--------| and a thin layer of water in the area to jump from -> TRAVEL_BARRIERJUMP // //check for a barrier jump reachability if (ground_foundreach) { //if area2 is higher but lower than the maximum barrier jump height if (ground_bestdist > 0 && ground_bestdist < aassettings.phys_maxbarrier) { //if no water in area1 or a very thin layer of water on the ground if (!water_foundreach || (ground_bestdist - water_bestdist < 16)) { //cannot perform a barrier jump towards or from a crouch area in Quake2 if (!AAS_AreaCrouch(area1num) && !AAS_AreaCrouch(area2num)) { //create barrier jump reachability from area1 to area2 lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area2num; lreach->facenum = 0; lreach->edgenum = ground_bestarea2groundedgenum; VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); lreach->traveltype = TRAVEL_BARRIERJUMP; lreach->traveltime = aassettings.rs_barrierjump;//AAS_BarrierJumpTravelTime(); lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; //we've got another barrierjump reachability reach_barrier++; return qtrue; } //end if } //end if } //end if } //end if // // Walk and Walk Off Ledge // //--------| // | can walk or step back -> TRAVEL_WALK // --------- // //--------| // | // | // | // | cannot walk/step back -> TRAVEL_WALKOFFLEDGE // --------- // //--------| // | // |~~~~~~~~ // | // | cannot step back but can waterjump back -> TRAVEL_WALKOFFLEDGE // --------- FIXME: create TRAVEL_WALK reach?? // //check for a walk or walk off ledge reachability if (ground_foundreach) { if (ground_bestdist < 0) { if (ground_bestdist > -aassettings.phys_maxstep) { //create walk reachability from area1 to area2 lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area2num; lreach->facenum = 0; lreach->edgenum = ground_bestarea2groundedgenum; VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); lreach->traveltype = TRAVEL_WALK; lreach->traveltime = 1; lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; //we've got another walk reachability reach_walk++; return qtrue; } //end if // if no maximum fall height set or less than the max if (!aassettings.rs_maxfallheight || fabs(ground_bestdist) < aassettings.rs_maxfallheight) { //trace a bounding box vertically to check for solids VectorMA(ground_bestend, INSIDEUNITS, ground_bestnormal, ground_bestend); VectorCopy(ground_bestend, start); start[2] = ground_beststart[2]; VectorCopy(ground_bestend, end); end[2] += 4; trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); //if no solids were found if (!trace.startsolid && trace.fraction >= 1.0) { //the trace end point must be in the goal area trace.endpos[2] += 1; if (AAS_PointAreaNum(trace.endpos) == area2num) { //if not going through a cluster portal numareas = AAS_TraceAreas(start, end, areas, NULL, sizeof(areas) / sizeof(int)); for (i = 0; i < numareas; i++) if (AAS_AreaClusterPortal(areas[i])) break; if (i >= numareas) { //create a walk off ledge reachability from area1 to area2 lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area2num; lreach->facenum = 0; lreach->edgenum = ground_bestarea2groundedgenum; VectorCopy(ground_beststart, lreach->start); VectorCopy(ground_bestend, lreach->end); lreach->traveltype = TRAVEL_WALKOFFLEDGE; lreach->traveltime = aassettings.rs_startwalkoffledge + fabs(ground_bestdist) * 50 / aassettings.phys_gravity; //if falling from too high and not falling into water if (!AAS_AreaSwim(area2num) && !AAS_AreaJumpPad(area2num)) { if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta5) { lreach->traveltime += aassettings.rs_falldamage5; } //end if if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta10) { lreach->traveltime += aassettings.rs_falldamage10; } //end if } //end if lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; // reach_walkoffledge++; //NOTE: don't create a weapon (rl, bfg) jump reachability here //because it interferes with other reachabilities //like the ladder reachability return qtrue; } //end if } //end if } //end if } //end if } //end else } //end if return qfalse; } //end of the function AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge //=========================================================================== // returns the distance between the two vectors // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float VectorDistance(vec3_t v1, vec3_t v2) { vec3_t dir; VectorSubtract(v2, v1, dir); return VectorLength(dir); } //end of the function VectorDistance //=========================================================================== // returns true if the first vector is between the last two vectors // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int VectorBetweenVectors(vec3_t v, vec3_t v1, vec3_t v2) { vec3_t dir1, dir2; VectorSubtract(v, v1, dir1); VectorSubtract(v, v2, dir2); return (DotProduct(dir1, dir2) <= 0); } //end of the function VectorBetweenVectors //=========================================================================== // returns the mid point between the two vectors // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void VectorMiddle(vec3_t v1, vec3_t v2, vec3_t middle) { VectorAdd(v1, v2, middle); VectorScale(middle, 0.5, middle); } //end of the function VectorMiddle //=========================================================================== // calculate a range of points closest to each other on both edges // // Parameter: beststart1 start of the range of points on edge v1-v2 // beststart2 end of the range of points on edge v1-v2 // bestend1 start of the range of points on edge v3-v4 // bestend2 end of the range of points on edge v3-v4 // bestdist best distance so far // Returns: - // Changes Globals: - //=========================================================================== /* float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, aas_plane_t *plane1, aas_plane_t *plane2, vec3_t beststart, vec3_t bestend, float bestdist) { vec3_t dir1, dir2, p1, p2, p3, p4; float a1, a2, b1, b2, dist; int founddist; //edge vectors VectorSubtract(v2, v1, dir1); VectorSubtract(v4, v3, dir2); //get the horizontal directions dir1[2] = 0; dir2[2] = 0; // // p1 = point on an edge vector of area2 closest to v1 // p2 = point on an edge vector of area2 closest to v2 // p3 = point on an edge vector of area1 closest to v3 // p4 = point on an edge vector of area1 closest to v4 // if (dir2[0]) { a2 = dir2[1] / dir2[0]; b2 = v3[1] - a2 * v3[0]; //point on the edge vector of area2 closest to v1 p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; p1[1] = a2 * p1[0] + b2; //point on the edge vector of area2 closest to v2 p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; p2[1] = a2 * p2[0] + b2; } //end if else { //point on the edge vector of area2 closest to v1 p1[0] = v3[0]; p1[1] = v1[1]; //point on the edge vector of area2 closest to v2 p2[0] = v3[0]; p2[1] = v2[1]; } //end else // if (dir1[0]) { // a1 = dir1[1] / dir1[0]; b1 = v1[1] - a1 * v1[0]; //point on the edge vector of area1 closest to v3 p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; p3[1] = a1 * p3[0] + b1; //point on the edge vector of area1 closest to v4 p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; p4[1] = a1 * p4[0] + b1; } //end if else { //point on the edge vector of area1 closest to v3 p3[0] = v1[0]; p3[1] = v3[1]; //point on the edge vector of area1 closest to v4 p4[0] = v1[0]; p4[1] = v4[1]; } //end else //start with zero z-coordinates p1[2] = 0; p2[2] = 0; p3[2] = 0; p4[2] = 0; //calculate the z-coordinates from the ground planes p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; // founddist = qfalse; // if (VectorBetweenVectors(p1, v3, v4)) { dist = VectorDistance(v1, p1); if (dist > bestdist - 0.5 && dist < bestdist + 0.5) { VectorMiddle(beststart, v1, beststart); VectorMiddle(bestend, p1, bestend); } //end if else if (dist < bestdist) { bestdist = dist; VectorCopy(v1, beststart); VectorCopy(p1, bestend); } //end if founddist = qtrue; } //end if if (VectorBetweenVectors(p2, v3, v4)) { dist = VectorDistance(v2, p2); if (dist > bestdist - 0.5 && dist < bestdist + 0.5) { VectorMiddle(beststart, v2, beststart); VectorMiddle(bestend, p2, bestend); } //end if else if (dist < bestdist) { bestdist = dist; VectorCopy(v2, beststart); VectorCopy(p2, bestend); } //end if founddist = qtrue; } //end else if if (VectorBetweenVectors(p3, v1, v2)) { dist = VectorDistance(v3, p3); if (dist > bestdist - 0.5 && dist < bestdist + 0.5) { VectorMiddle(beststart, p3, beststart); VectorMiddle(bestend, v3, bestend); } //end if else if (dist < bestdist) { bestdist = dist; VectorCopy(p3, beststart); VectorCopy(v3, bestend); } //end if founddist = qtrue; } //end else if if (VectorBetweenVectors(p4, v1, v2)) { dist = VectorDistance(v4, p4); if (dist > bestdist - 0.5 && dist < bestdist + 0.5) { VectorMiddle(beststart, p4, beststart); VectorMiddle(bestend, v4, bestend); } //end if else if (dist < bestdist) { bestdist = dist; VectorCopy(p4, beststart); VectorCopy(v4, bestend); } //end if founddist = qtrue; } //end else if //if no shortest distance was found the shortest distance //is between one of the vertexes of edge1 and one of edge2 if (!founddist) { dist = VectorDistance(v1, v3); if (dist < bestdist) { bestdist = dist; VectorCopy(v1, beststart); VectorCopy(v3, bestend); } //end if dist = VectorDistance(v1, v4); if (dist < bestdist) { bestdist = dist; VectorCopy(v1, beststart); VectorCopy(v4, bestend); } //end if dist = VectorDistance(v2, v3); if (dist < bestdist) { bestdist = dist; VectorCopy(v2, beststart); VectorCopy(v3, bestend); } //end if dist = VectorDistance(v2, v4); if (dist < bestdist) { bestdist = dist; VectorCopy(v2, beststart); VectorCopy(v4, bestend); } //end if } //end if return bestdist; } //end of the function AAS_ClosestEdgePoints*/ float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, aas_plane_t *plane1, aas_plane_t *plane2, vec3_t beststart1, vec3_t bestend1, vec3_t beststart2, vec3_t bestend2, float bestdist) { vec3_t dir1, dir2, p1, p2, p3, p4; float a1, a2, b1, b2, dist, dist1, dist2; int founddist; //edge vectors VectorSubtract(v2, v1, dir1); VectorSubtract(v4, v3, dir2); //get the horizontal directions dir1[2] = 0; dir2[2] = 0; // // p1 = point on an edge vector of area2 closest to v1 // p2 = point on an edge vector of area2 closest to v2 // p3 = point on an edge vector of area1 closest to v3 // p4 = point on an edge vector of area1 closest to v4 // if (dir2[0]) { a2 = dir2[1] / dir2[0]; b2 = v3[1] - a2 * v3[0]; //point on the edge vector of area2 closest to v1 p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; p1[1] = a2 * p1[0] + b2; //point on the edge vector of area2 closest to v2 p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; p2[1] = a2 * p2[0] + b2; } //end if else { //point on the edge vector of area2 closest to v1 p1[0] = v3[0]; p1[1] = v1[1]; //point on the edge vector of area2 closest to v2 p2[0] = v3[0]; p2[1] = v2[1]; } //end else // if (dir1[0]) { // a1 = dir1[1] / dir1[0]; b1 = v1[1] - a1 * v1[0]; //point on the edge vector of area1 closest to v3 p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; p3[1] = a1 * p3[0] + b1; //point on the edge vector of area1 closest to v4 p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; p4[1] = a1 * p4[0] + b1; } //end if else { //point on the edge vector of area1 closest to v3 p3[0] = v1[0]; p3[1] = v3[1]; //point on the edge vector of area1 closest to v4 p4[0] = v1[0]; p4[1] = v4[1]; } //end else //start with zero z-coordinates p1[2] = 0; p2[2] = 0; p3[2] = 0; p4[2] = 0; //calculate the z-coordinates from the ground planes p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; // founddist = qfalse; // if (VectorBetweenVectors(p1, v3, v4)) { dist = VectorDistance(v1, p1); if (dist > bestdist - 0.5 && dist < bestdist + 0.5) { dist1 = VectorDistance(beststart1, v1); dist2 = VectorDistance(beststart2, v1); if (dist1 > dist2) { if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(v1, beststart2); } //end if else { if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(v1, beststart1); } //end else dist1 = VectorDistance(bestend1, p1); dist2 = VectorDistance(bestend2, p1); if (dist1 > dist2) { if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(p1, bestend2); } //end if else { if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(p1, bestend1); } //end else } //end if else if (dist < bestdist) { bestdist = dist; VectorCopy(v1, beststart1); VectorCopy(v1, beststart2); VectorCopy(p1, bestend1); VectorCopy(p1, bestend2); } //end if founddist = qtrue; } //end if if (VectorBetweenVectors(p2, v3, v4)) { dist = VectorDistance(v2, p2); if (dist > bestdist - 0.5 && dist < bestdist + 0.5) { dist1 = VectorDistance(beststart1, v2); dist2 = VectorDistance(beststart2, v2); if (dist1 > dist2) { if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(v2, beststart2); } //end if else { if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(v2, beststart1); } //end else dist1 = VectorDistance(bestend1, p2); dist2 = VectorDistance(bestend2, p2); if (dist1 > dist2) { if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(p2, bestend2); } //end if else { if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(p2, bestend1); } //end else } //end if else if (dist < bestdist) { bestdist = dist; VectorCopy(v2, beststart1); VectorCopy(v2, beststart2); VectorCopy(p2, bestend1); VectorCopy(p2, bestend2); } //end if founddist = qtrue; } //end else if if (VectorBetweenVectors(p3, v1, v2)) { dist = VectorDistance(v3, p3); if (dist > bestdist - 0.5 && dist < bestdist + 0.5) { dist1 = VectorDistance(beststart1, p3); dist2 = VectorDistance(beststart2, p3); if (dist1 > dist2) { if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(p3, beststart2); } //end if else { if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(p3, beststart1); } //end else dist1 = VectorDistance(bestend1, v3); dist2 = VectorDistance(bestend2, v3); if (dist1 > dist2) { if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(v3, bestend2); } //end if else { if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(v3, bestend1); } //end else } //end if else if (dist < bestdist) { bestdist = dist; VectorCopy(p3, beststart1); VectorCopy(p3, beststart2); VectorCopy(v3, bestend1); VectorCopy(v3, bestend2); } //end if founddist = qtrue; } //end else if if (VectorBetweenVectors(p4, v1, v2)) { dist = VectorDistance(v4, p4); if (dist > bestdist - 0.5 && dist < bestdist + 0.5) { dist1 = VectorDistance(beststart1, p4); dist2 = VectorDistance(beststart2, p4); if (dist1 > dist2) { if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(p4, beststart2); } //end if else { if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(p4, beststart1); } //end else dist1 = VectorDistance(bestend1, v4); dist2 = VectorDistance(bestend2, v4); if (dist1 > dist2) { if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(v4, bestend2); } //end if else { if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(v4, bestend1); } //end else } //end if else if (dist < bestdist) { bestdist = dist; VectorCopy(p4, beststart1); VectorCopy(p4, beststart2); VectorCopy(v4, bestend1); VectorCopy(v4, bestend2); } //end if founddist = qtrue; } //end else if //if no shortest distance was found the shortest distance //is between one of the vertexes of edge1 and one of edge2 if (!founddist) { dist = VectorDistance(v1, v3); if (dist < bestdist) { bestdist = dist; VectorCopy(v1, beststart1); VectorCopy(v1, beststart2); VectorCopy(v3, bestend1); VectorCopy(v3, bestend2); } //end if dist = VectorDistance(v1, v4); if (dist < bestdist) { bestdist = dist; VectorCopy(v1, beststart1); VectorCopy(v1, beststart2); VectorCopy(v4, bestend1); VectorCopy(v4, bestend2); } //end if dist = VectorDistance(v2, v3); if (dist < bestdist) { bestdist = dist; VectorCopy(v2, beststart1); VectorCopy(v2, beststart2); VectorCopy(v3, bestend1); VectorCopy(v3, bestend2); } //end if dist = VectorDistance(v2, v4); if (dist < bestdist) { bestdist = dist; VectorCopy(v2, beststart1); VectorCopy(v2, beststart2); VectorCopy(v4, bestend1); VectorCopy(v4, bestend2); } //end if } //end if return bestdist; } //end of the function AAS_ClosestEdgePoints //=========================================================================== // creates possible jump reachabilities between the areas // // The two closest points on the ground of the areas are calculated // One of the points will be on an edge of a ground face of area1 and // one on an edge of a ground face of area2. // If there is a range of closest points the point in the middle of this range // is selected. // Between these two points there must be one or more gaps. // If the gaps exist a potential jump is predicted. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_Reachability_Jump(int area1num, int area2num) { int i, j, k, l, face1num, face2num, edge1num, edge2num, traveltype; int stopevent, areas[10], numareas; float phys_jumpvel, maxjumpdistance, maxjumpheight, height, bestdist, speed; vec_t *v1, *v2, *v3, *v4; vec3_t beststart, beststart2, bestend, bestend2; vec3_t teststart, testend, dir, velocity, cmdmove, up = {0, 0, 1}, sidewards; aas_area_t *area1, *area2; aas_face_t *face1, *face2; aas_edge_t *edge1, *edge2; aas_plane_t *plane1, *plane2, *plane; aas_trace_t trace; aas_clientmove_t move; aas_lreachability_t *lreach; if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) return qfalse; //cannot jump from or to a crouch area if (AAS_AreaCrouch(area1num) || AAS_AreaCrouch(area2num)) return qfalse; // area1 = &aasworld.areas[area1num]; area2 = &aasworld.areas[area2num]; // phys_jumpvel = aassettings.phys_jumpvel; //maximum distance a player can jump maxjumpdistance = 2 * AAS_MaxJumpDistance(phys_jumpvel); //maximum height a player can jump with the given initial z velocity maxjumpheight = AAS_MaxJumpHeight(phys_jumpvel); //if the areas are not near anough in the x-y direction for (i = 0; i < 2; i++) { if (area1->mins[i] > area2->maxs[i] + maxjumpdistance) return qfalse; if (area1->maxs[i] < area2->mins[i] - maxjumpdistance) return qfalse; } //end for //if area2 is way to high to jump up to if (area2->mins[2] > area1->maxs[2] + maxjumpheight) return qfalse; // bestdist = 999999; // for (i = 0; i < area1->numfaces; i++) { face1num = aasworld.faceindex[area1->firstface + i]; face1 = &aasworld.faces[abs(face1num)]; //if not a ground face if (!(face1->faceflags & FACE_GROUND)) continue; // for (j = 0; j < area2->numfaces; j++) { face2num = aasworld.faceindex[area2->firstface + j]; face2 = &aasworld.faces[abs(face2num)]; //if not a ground face if (!(face2->faceflags & FACE_GROUND)) continue; // for (k = 0; k < face1->numedges; k++) { edge1num = abs(aasworld.edgeindex[face1->firstedge + k]); edge1 = &aasworld.edges[edge1num]; for (l = 0; l < face2->numedges; l++) { edge2num = abs(aasworld.edgeindex[face2->firstedge + l]); edge2 = &aasworld.edges[edge2num]; //calculate the minimum distance between the two edges v1 = aasworld.vertexes[edge1->v[0]]; v2 = aasworld.vertexes[edge1->v[1]]; v3 = aasworld.vertexes[edge2->v[0]]; v4 = aasworld.vertexes[edge2->v[1]]; //get the ground planes plane1 = &aasworld.planes[face1->planenum]; plane2 = &aasworld.planes[face2->planenum]; // bestdist = AAS_ClosestEdgePoints(v1, v2, v3, v4, plane1, plane2, beststart, bestend, beststart2, bestend2, bestdist); } //end for } //end for } //end for } //end for VectorMiddle(beststart, beststart2, beststart); VectorMiddle(bestend, bestend2, bestend); if (bestdist > 4 && bestdist < maxjumpdistance) { // Log_Write("shortest distance between %d and %d is %f\r\n", area1num, area2num, bestdist); // if very close and almost no height difference then the bot can walk if (bestdist <= 48 && fabs(beststart[2] - bestend[2]) < 8) { speed = 400; traveltype = TRAVEL_WALKOFFLEDGE; } //end if else if (AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) { //FIXME: why multiply with 1.2??? speed *= 1.2f; traveltype = TRAVEL_WALKOFFLEDGE; } //end else if else { //get the horizontal speed for the jump, if it isn't possible to calculate this //speed (the jump is not possible) then there's no jump reachability created if (!AAS_HorizontalVelocityForJump(phys_jumpvel, beststart, bestend, &speed)) return qfalse; speed *= 1.05f; traveltype = TRAVEL_JUMP; // //NOTE: test if the horizontal distance isn't too small VectorSubtract(bestend, beststart, dir); dir[2] = 0; if (VectorLength(dir) < 10) return qfalse; } //end if // VectorSubtract(bestend, beststart, dir); VectorNormalize(dir); VectorMA(beststart, 1, dir, teststart); // VectorCopy(teststart, testend); testend[2] -= 100; trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); // if (trace.startsolid) return qfalse; if (trace.fraction < 1) { plane = &aasworld.planes[trace.planenum]; // if the bot can stand on the surface if (DotProduct(plane->normal, up) >= 0.7) { // if no lava or slime below if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) { if (teststart[2] - trace.endpos[2] <= aassettings.phys_maxbarrier) return qfalse; } //end if } //end if } //end if // VectorMA(bestend, -1, dir, teststart); // VectorCopy(teststart, testend); testend[2] -= 100; trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); // if (trace.startsolid) return qfalse; if (trace.fraction < 1) { plane = &aasworld.planes[trace.planenum]; // if the bot can stand on the surface if (DotProduct(plane->normal, up) >= 0.7) { // if no lava or slime below if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) { if (teststart[2] - trace.endpos[2] <= aassettings.phys_maxbarrier) return qfalse; } //end if } //end if } //end if // // get command movement VectorClear(cmdmove); if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) cmdmove[2] = aassettings.phys_jumpvel; else cmdmove[2] = 0; // VectorSubtract(bestend, beststart, dir); dir[2] = 0; VectorNormalize(dir); CrossProduct(dir, up, sidewards); // stopevent = SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE; if (!AAS_AreaClusterPortal(area1num) && !AAS_AreaClusterPortal(area2num)) stopevent |= SE_TOUCHCLUSTERPORTAL; // for (i = 0; i < 3; i++) { // if (i == 1) VectorAdd(testend, sidewards, testend); else if (i == 2) VectorSubtract(bestend, sidewards, testend); else VectorCopy(bestend, testend); VectorSubtract(testend, beststart, dir); dir[2] = 0; VectorNormalize(dir); VectorScale(dir, speed, velocity); // AAS_PredictClientMovement(&move, -1, beststart, PRESENCE_NORMAL, qtrue, velocity, cmdmove, 3, 30, 0.1f, stopevent, 0, qfalse); // if prediction time wasn't enough to fully predict the movement if (move.frames >= 30) return qfalse; // don't enter slime or lava and don't fall from too high if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) return qfalse; // never jump or fall through a cluster portal if (move.stopevent & SE_TOUCHCLUSTERPORTAL) return qfalse; //the end position should be in area2, also test a little bit back //because the predicted jump could have rushed through the area VectorMA(move.endpos, -64, dir, teststart); teststart[2] += 1; numareas = AAS_TraceAreas(move.endpos, teststart, areas, NULL, sizeof(areas) / sizeof(int)); for (j = 0; j < numareas; j++) { if (areas[j] == area2num) break; } //end for if (j < numareas) break; } if (i >= 3) return qfalse; // #ifdef REACH_DEBUG //create the reachability Log_Write("jump reachability between %d and %d\r\n", area1num, area2num); #endif //REACH_DEBUG //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area2num; lreach->facenum = 0; lreach->edgenum = 0; VectorCopy(beststart, lreach->start); VectorCopy(bestend, lreach->end); lreach->traveltype = traveltype; VectorSubtract(bestend, beststart, dir); height = dir[2]; dir[2] = 0; if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE && height > VectorLength(dir)) { lreach->traveltime = aassettings.rs_startwalkoffledge + height * 50 / aassettings.phys_gravity; } else { lreach->traveltime = aassettings.rs_startjump + VectorDistance(bestend, beststart) * 240 / aassettings.phys_maxwalkvelocity; } //end if // if (!AAS_AreaJumpPad(area2num)) { if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta5) { lreach->traveltime += aassettings.rs_falldamage5; } //end if else if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta10) { lreach->traveltime += aassettings.rs_falldamage10; } //end if } //end if lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; // if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) reach_jump++; else reach_walkoffledge++; } //end if return qfalse; } //end of the function AAS_Reachability_Jump //=========================================================================== // create a possible ladder reachability from area1 to area2 // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_Reachability_Ladder(int area1num, int area2num) { int i, j, k, l, edge1num, edge2num, sharededgenum, lowestedgenum; int face1num, face2num, ladderface1num, ladderface2num; int ladderface1vertical, ladderface2vertical, firstv; float face1area, face2area, bestface1area, bestface2area; float phys_jumpvel, maxjumpheight; vec3_t area1point, area2point, v1, v2, up = {0, 0, 1}; vec3_t mid, lowestpoint, start, end, sharededgevec, dir; aas_area_t *area1, *area2; aas_face_t *face1, *face2, *ladderface1, *ladderface2; aas_plane_t *plane1, *plane2; aas_edge_t *sharededge, *edge1; aas_lreachability_t *lreach; aas_trace_t trace; if (!AAS_AreaLadder(area1num) || !AAS_AreaLadder(area2num)) return qfalse; // phys_jumpvel = aassettings.phys_jumpvel; //maximum height a player can jump with the given initial z velocity maxjumpheight = AAS_MaxJumpHeight(phys_jumpvel); area1 = &aasworld.areas[area1num]; area2 = &aasworld.areas[area2num]; // ladderface1 = NULL; ladderface2 = NULL; ladderface1num = 0; //make compiler happy ladderface2num = 0; //make compiler happy bestface1area = -9999; bestface2area = -9999; sharededgenum = 0; //make compiler happy lowestedgenum = 0; //make compiler happy // for (i = 0; i < area1->numfaces; i++) { face1num = aasworld.faceindex[area1->firstface + i]; face1 = &aasworld.faces[abs(face1num)]; //if not a ladder face if (!(face1->faceflags & FACE_LADDER)) continue; // for (j = 0; j < area2->numfaces; j++) { face2num = aasworld.faceindex[area2->firstface + j]; face2 = &aasworld.faces[abs(face2num)]; //if not a ladder face if (!(face2->faceflags & FACE_LADDER)) continue; //check if the faces share an edge for (k = 0; k < face1->numedges; k++) { edge1num = aasworld.edgeindex[face1->firstedge + k]; for (l = 0; l < face2->numedges; l++) { edge2num = aasworld.edgeindex[face2->firstedge + l]; if (abs(edge1num) == abs(edge2num)) { //get the face with the largest area face1area = AAS_FaceArea(face1); face2area = AAS_FaceArea(face2); if (face1area > bestface1area && face2area > bestface2area) { bestface1area = face1area; bestface2area = face2area; ladderface1 = face1; ladderface2 = face2; ladderface1num = face1num; ladderface2num = face2num; sharededgenum = edge1num; } //end if break; } //end if } //end for if (l != face2->numedges) break; } //end for } //end for } //end for // if (ladderface1 && ladderface2) { //get the middle of the shared edge sharededge = &aasworld.edges[abs(sharededgenum)]; firstv = sharededgenum < 0; // VectorCopy(aasworld.vertexes[sharededge->v[firstv]], v1); VectorCopy(aasworld.vertexes[sharededge->v[!firstv]], v2); VectorAdd(v1, v2, area1point); VectorScale(area1point, 0.5, area1point); VectorCopy(area1point, area2point); // //if the face plane in area 1 is pretty much vertical plane1 = &aasworld.planes[ladderface1->planenum ^ (ladderface1num < 0)]; plane2 = &aasworld.planes[ladderface2->planenum ^ (ladderface2num < 0)]; // //get the points really into the areas VectorSubtract(v2, v1, sharededgevec); CrossProduct(plane1->normal, sharededgevec, dir); VectorNormalize(dir); //NOTE: 32 because that's larger than 16 (bot bbox x,y) VectorMA(area1point, -32, dir, area1point); VectorMA(area2point, 32, dir, area2point); // ladderface1vertical = abs(DotProduct(plane1->normal, up)) < 0.1; ladderface2vertical = abs(DotProduct(plane2->normal, up)) < 0.1; //there's only reachability between vertical ladder faces if (!ladderface1vertical && !ladderface2vertical) return qfalse; //if both vertical ladder faces if (ladderface1vertical && ladderface2vertical //and the ladder faces do not make a sharp corner && DotProduct(plane1->normal, plane2->normal) > 0.7 //and the shared edge is not too vertical && abs(DotProduct(sharededgevec, up)) < 0.7) { //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area2num; lreach->facenum = ladderface1num; lreach->edgenum = abs(sharededgenum); VectorCopy(area1point, lreach->start); //VectorCopy(area2point, lreach->end); VectorMA(area2point, -3, plane1->normal, lreach->end); lreach->traveltype = TRAVEL_LADDER; lreach->traveltime = 10; lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; // reach_ladder++; //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area1num; lreach->facenum = ladderface2num; lreach->edgenum = abs(sharededgenum); VectorCopy(area2point, lreach->start); //VectorCopy(area1point, lreach->end); VectorMA(area1point, -3, plane1->normal, lreach->end); lreach->traveltype = TRAVEL_LADDER; lreach->traveltime = 10; lreach->next = areareachability[area2num]; areareachability[area2num] = lreach; // reach_ladder++; // return qtrue; } //end if //if the second ladder face is also a ground face //create ladder end (just ladder) reachability and //walk off a ladder (ledge) reachability if (ladderface1vertical && (ladderface2->faceflags & FACE_GROUND)) { //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area2num; lreach->facenum = ladderface1num; lreach->edgenum = abs(sharededgenum); VectorCopy(area1point, lreach->start); VectorCopy(area2point, lreach->end); lreach->end[2] += 16; VectorMA(lreach->end, -15, plane1->normal, lreach->end); lreach->traveltype = TRAVEL_LADDER; lreach->traveltime = 10; lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; // reach_ladder++; //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area1num; lreach->facenum = ladderface2num; lreach->edgenum = abs(sharededgenum); VectorCopy(area2point, lreach->start); VectorCopy(area1point, lreach->end); lreach->traveltype = TRAVEL_WALKOFFLEDGE; lreach->traveltime = 10; lreach->next = areareachability[area2num]; areareachability[area2num] = lreach; // reach_walkoffledge++; // return qtrue; } //end if // if (ladderface1vertical) { //find lowest edge of the ladder face lowestpoint[2] = 99999; for (i = 0; i < ladderface1->numedges; i++) { edge1num = abs(aasworld.edgeindex[ladderface1->firstedge + i]); edge1 = &aasworld.edges[edge1num]; // VectorCopy(aasworld.vertexes[edge1->v[0]], v1); VectorCopy(aasworld.vertexes[edge1->v[1]], v2); // VectorAdd(v1, v2, mid); VectorScale(mid, 0.5, mid); // if (mid[2] < lowestpoint[2]) { VectorCopy(mid, lowestpoint); lowestedgenum = edge1num; } //end if } //end for // plane1 = &aasworld.planes[ladderface1->planenum]; //trace down in the middle of this edge VectorMA(lowestpoint, 5, plane1->normal, start); VectorCopy(start, end); start[2] += 5; end[2] -= 100; //trace without entity collision trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); // // #ifdef REACH_DEBUG if (trace.startsolid) { Log_Write("trace from area %d started in solid\r\n", area1num); } //end if #endif //REACH_DEBUG // trace.endpos[2] += 1; area2num = AAS_PointAreaNum(trace.endpos); // area2 = &aasworld.areas[area2num]; for (i = 0; i < area2->numfaces; i++) { face2num = aasworld.faceindex[area2->firstface + i]; face2 = &aasworld.faces[abs(face2num)]; // if (face2->faceflags & FACE_LADDER) { plane2 = &aasworld.planes[face2->planenum]; if (abs(DotProduct(plane2->normal, up)) < 0.1) break; } //end if } //end for //if from another area without vertical ladder faces if (i >= area2->numfaces && area2num != area1num && //the reachabilities shouldn't exist already !AAS_ReachabilityExists(area1num, area2num) && !AAS_ReachabilityExists(area2num, area1num)) { //if the height is jumpable if (start[2] - trace.endpos[2] < maxjumpheight) { //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area2num; lreach->facenum = ladderface1num; lreach->edgenum = lowestedgenum; VectorCopy(lowestpoint, lreach->start); VectorCopy(trace.endpos, lreach->end); lreach->traveltype = TRAVEL_LADDER; lreach->traveltime = 10; lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; // reach_ladder++; //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area1num; lreach->facenum = ladderface1num; lreach->edgenum = lowestedgenum; VectorCopy(trace.endpos, lreach->start); //get the end point a little bit into the ladder VectorMA(lowestpoint, -5, plane1->normal, lreach->end); //get the end point a little higher lreach->end[2] += 10; lreach->traveltype = TRAVEL_JUMP; lreach->traveltime = 10; lreach->next = areareachability[area2num]; areareachability[area2num] = lreach; // reach_jump++; // return qtrue; #ifdef REACH_DEBUG Log_Write("jump up to ladder reach between %d and %d\r\n", area2num, area1num); #endif //REACH_DEBUG } //end if #ifdef REACH_DEBUG else Log_Write("jump too high between area %d and %d\r\n", area2num, area1num); #endif //REACH_DEBUG } //end if /*//if slime or lava below the ladder //try jump reachability from far towards the ladder if (aasworld.areasettings[area2num].contents & (AREACONTENTS_SLIME | AREACONTENTS_LAVA)) { for (i = 20; i <= 120; i += 20) { //trace down in the middle of this edge VectorMA(lowestpoint, i, plane1->normal, start); VectorCopy(start, end); start[2] += 5; end[2] -= 100; //trace without entity collision trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); // if (trace.startsolid) break; trace.endpos[2] += 1; area2num = AAS_PointAreaNum(trace.endpos); if (area2num == area1num) continue; // if (start[2] - trace.endpos[2] > maxjumpheight) continue; if (aasworld.areasettings[area2num].contents & (AREACONTENTS_SLIME | AREACONTENTS_LAVA)) continue; // //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area1num; lreach->facenum = ladderface1num; lreach->edgenum = lowestedgenum; VectorCopy(trace.endpos, lreach->start); VectorCopy(lowestpoint, lreach->end); lreach->end[2] += 5; lreach->traveltype = TRAVEL_JUMP; lreach->traveltime = 10; lreach->next = areareachability[area2num]; areareachability[area2num] = lreach; // reach_jump++; // Log_Write("jump far to ladder reach between %d and %d\r\n", area2num, area1num); // break; } //end for } //end if*/ } //end if } //end if return qfalse; } //end of the function AAS_Reachability_Ladder //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_TravelFlagsForTeam(int ent) { int notteam; if (!AAS_IntForBSPEpairKey(ent, "bot_notteam", ¬team)) return 0; if (notteam == 1) return TRAVELFLAG_NOTTEAM1; if (notteam == 2) return TRAVELFLAG_NOTTEAM2; return 0; } //end of the function AAS_TravelFlagsForTeam //=========================================================================== // create possible teleporter reachabilities // this is very game dependent.... :( // // classname = trigger_multiple or trigger_teleport // target = "t1" // // classname = target_teleporter // targetname = "t1" // target = "t2" // // classname = misc_teleporter_dest // targetname = "t2" // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_Reachability_Teleport(void) { int area1num, area2num; char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; int ent, dest; float angle; vec3_t origin, destorigin, mins, maxs, end, angles; vec3_t mid, velocity, cmdmove; aas_lreachability_t *lreach; aas_clientmove_t move; aas_trace_t trace; aas_link_t *areas, *link; for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) { if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; if (!strcmp(classname, "trigger_multiple")) { AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); //#ifdef REACH_DEBUG botimport.Print(PRT_MESSAGE, "trigger_multiple model = \"%s\"\n", model); //#endif REACH_DEBUG VectorClear(angles); AAS_BSPModelMinsMaxsOrigin(atoi(model+1), angles, mins, maxs, origin); // if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) { botimport.Print(PRT_ERROR, "trigger_multiple at %1.0f %1.0f %1.0f without target\n", origin[0], origin[1], origin[2]); continue; } //end if for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) { if (!AAS_ValueForBSPEpairKey(dest, "classname", classname, MAX_EPAIRKEY)) continue; if (!strcmp(classname, "target_teleporter")) { if (!AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) continue; if (!strcmp(targetname, target)) { break; } //end if } //end if } //end for if (!dest) { continue; } //end if if (!AAS_ValueForBSPEpairKey(dest, "target", target, MAX_EPAIRKEY)) { botimport.Print(PRT_ERROR, "target_teleporter without target\n"); continue; } //end if } //end else else if (!strcmp(classname, "trigger_teleport")) { AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); //#ifdef REACH_DEBUG botimport.Print(PRT_MESSAGE, "trigger_teleport model = \"%s\"\n", model); //#endif REACH_DEBUG VectorClear(angles); AAS_BSPModelMinsMaxsOrigin(atoi(model+1), angles, mins, maxs, origin); // if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) { botimport.Print(PRT_ERROR, "trigger_teleport at %1.0f %1.0f %1.0f without target\n", origin[0], origin[1], origin[2]); continue; } //end if } //end if else { continue; } //end else // for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) { //classname should be misc_teleporter_dest //but I've also seen target_position and actually any //entity could be used... burp if (AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) { if (!strcmp(targetname, target)) { break; } //end if } //end if } //end for if (!dest) { botimport.Print(PRT_ERROR, "teleporter without misc_teleporter_dest (%s)\n", target); continue; } //end if if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) { botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); continue; } //end if // area2num = AAS_PointAreaNum(destorigin); //if not teleported into a teleporter or into a jumppad if (!AAS_AreaTeleporter(area2num) && !AAS_AreaJumpPad(area2num)) { VectorCopy(destorigin, end); end[2] -= 64; trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); if (trace.startsolid) { botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); continue; } //end if area2num = AAS_PointAreaNum(trace.endpos); // /* if (!AAS_AreaTeleporter(area2num) && !AAS_AreaJumpPad(area2num) && !AAS_AreaGrounded(area2num)) { VectorCopy(trace.endpos, destorigin); } else*/ { //predict where you'll end up AAS_FloatForBSPEpairKey(dest, "angle", &angle); if (angle) { VectorSet(angles, 0, angle, 0); AngleVectors(angles, velocity, NULL, NULL); VectorScale(velocity, 400, velocity); } //end if else { VectorClear(velocity); } //end else VectorClear(cmdmove); AAS_PredictClientMovement(&move, -1, destorigin, PRESENCE_NORMAL, qfalse, velocity, cmdmove, 0, 30, 0.1f, SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, qfalse); //qtrue); area2num = AAS_PointAreaNum(move.endpos); if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) { botimport.Print(PRT_WARNING, "teleported into slime or lava at dest %s\n", target); } //end if VectorCopy(move.endpos, destorigin); } //end else } //end if // //botimport.Print(PRT_MESSAGE, "teleporter brush origin at %f %f %f\n", origin[0], origin[1], origin[2]); //botimport.Print(PRT_MESSAGE, "teleporter brush mins = %f %f %f\n", mins[0], mins[1], mins[2]); //botimport.Print(PRT_MESSAGE, "teleporter brush maxs = %f %f %f\n", maxs[0], maxs[1], maxs[2]); VectorAdd(origin, mins, mins); VectorAdd(origin, maxs, maxs); // VectorAdd(mins, maxs, mid); VectorScale(mid, 0.5, mid); //link an invalid (-1) entity areas = AAS_LinkEntityClientBBox(mins, maxs, -1, PRESENCE_CROUCH); if (!areas) botimport.Print(PRT_MESSAGE, "trigger_multiple not in any area\n"); // for (link = areas; link; link = link->next_area) { //if (!AAS_AreaGrounded(link->areanum)) continue; if (!AAS_AreaTeleporter(link->areanum)) continue; // area1num = link->areanum; //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) break; lreach->areanum = area2num; lreach->facenum = 0; lreach->edgenum = 0; VectorCopy(mid, lreach->start); VectorCopy(destorigin, lreach->end); lreach->traveltype = TRAVEL_TELEPORT; lreach->traveltype |= AAS_TravelFlagsForTeam(ent); lreach->traveltime = aassettings.rs_teleport; lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; // reach_teleport++; } //end for //unlink the invalid entity AAS_UnlinkFromAreas(areas); } //end for } //end of the function AAS_Reachability_Teleport //=========================================================================== // create possible elevator (func_plat) reachabilities // this is very game dependent.... :( // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_Reachability_Elevator(void) { int area1num, area2num, modelnum, i, j, k, l, n, p; float lip, height, speed; char model[MAX_EPAIRKEY], classname[MAX_EPAIRKEY]; int ent; vec3_t mins, maxs, origin, angles = {0, 0, 0}; vec3_t pos1, pos2, mids, platbottom, plattop; vec3_t bottomorg, toporg, start, end, dir; vec_t xvals[8], yvals[8], xvals_top[8], yvals_top[8]; aas_lreachability_t *lreach; aas_trace_t trace; #ifdef REACH_DEBUG Log_Write("AAS_Reachability_Elevator\r\n"); #endif //REACH_DEBUG for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) { if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; if (!strcmp(classname, "func_plat")) { #ifdef REACH_DEBUG Log_Write("found func plat\r\n"); #endif //REACH_DEBUG if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) { botimport.Print(PRT_ERROR, "func_plat without model\n"); continue; } //end if //get the model number, and skip the leading * modelnum = atoi(model+1); if (modelnum <= 0) { botimport.Print(PRT_ERROR, "func_plat with invalid model number\n"); continue; } //end if //get the mins, maxs and origin of the model //NOTE: the origin is usually (0,0,0) and the mins and maxs // are the absolute mins and maxs AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); // AAS_VectorForBSPEpairKey(ent, "origin", origin); //pos1 is the top position, pos2 is the bottom VectorCopy(origin, pos1); VectorCopy(origin, pos2); //get the lip of the plat AAS_FloatForBSPEpairKey(ent, "lip", &lip); if (!lip) lip = 8; //get the movement height of the plat AAS_FloatForBSPEpairKey(ent, "height", &height); if (!height) height = (maxs[2] - mins[2]) - lip; //get the speed of the plat AAS_FloatForBSPEpairKey(ent, "speed", &speed); if (!speed) speed = 200; //get bottom position below pos1 pos2[2] -= height; // //get a point just above the plat in the bottom position VectorAdd(mins, maxs, mids); VectorMA(pos2, 0.5, mids, platbottom); platbottom[2] = maxs[2] - (pos1[2] - pos2[2]) + 2; //get a point just above the plat in the top position VectorAdd(mins, maxs, mids); VectorMA(pos2, 0.5, mids, plattop); plattop[2] = maxs[2] + 2; // /*if (!area1num) { Log_Write("no grounded area near plat bottom\r\n"); continue; } //end if*/ //get the mins and maxs a little larger for (i = 0; i < 3; i++) { mins[i] -= 1; maxs[i] += 1; } //end for // //botimport.Print(PRT_MESSAGE, "platbottom[2] = %1.1f plattop[2] = %1.1f\n", platbottom[2], plattop[2]); // VectorAdd(mins, maxs, mids); VectorScale(mids, 0.5, mids); // xvals[0] = mins[0]; xvals[1] = mids[0]; xvals[2] = maxs[0]; xvals[3] = mids[0]; yvals[0] = mids[1]; yvals[1] = maxs[1]; yvals[2] = mids[1]; yvals[3] = mins[1]; // xvals[4] = mins[0]; xvals[5] = maxs[0]; xvals[6] = maxs[0]; xvals[7] = mins[0]; yvals[4] = maxs[1]; yvals[5] = maxs[1]; yvals[6] = mins[1]; yvals[7] = mins[1]; //find adjacent areas around the bottom of the plat for (i = 0; i < 9; i++) { if (i < 8) //check at the sides of the plat { bottomorg[0] = origin[0] + xvals[i]; bottomorg[1] = origin[1] + yvals[i]; bottomorg[2] = platbottom[2] + 16; //get a grounded or swim area near the plat in the bottom position area1num = AAS_PointAreaNum(bottomorg); for (k = 0; k < 16; k++) { if (area1num) { if (AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) break; } //end if bottomorg[2] += 4; area1num = AAS_PointAreaNum(bottomorg); } //end if //if in solid if (k >= 16) { continue; } //end if } //end if else //at the middle of the plat { VectorCopy(plattop, bottomorg); bottomorg[2] += 24; area1num = AAS_PointAreaNum(bottomorg); if (!area1num) continue; VectorCopy(platbottom, bottomorg); bottomorg[2] += 24; } //end else //look at adjacent areas around the top of the plat //make larger steps to outside the plat everytime for (n = 0; n < 3; n++) { for (k = 0; k < 3; k++) { mins[k] -= 4; maxs[k] += 4; } //end for xvals_top[0] = mins[0]; xvals_top[1] = mids[0]; xvals_top[2] = maxs[0]; xvals_top[3] = mids[0]; yvals_top[0] = mids[1]; yvals_top[1] = maxs[1]; yvals_top[2] = mids[1]; yvals_top[3] = mins[1]; // xvals_top[4] = mins[0]; xvals_top[5] = maxs[0]; xvals_top[6] = maxs[0]; xvals_top[7] = mins[0]; yvals_top[4] = maxs[1]; yvals_top[5] = maxs[1]; yvals_top[6] = mins[1]; yvals_top[7] = mins[1]; // for (j = 0; j < 8; j++) { toporg[0] = origin[0] + xvals_top[j]; toporg[1] = origin[1] + yvals_top[j]; toporg[2] = plattop[2] + 16; //get a grounded or swim area near the plat in the top position area2num = AAS_PointAreaNum(toporg); for (l = 0; l < 16; l++) { if (area2num) { if (AAS_AreaGrounded(area2num) || AAS_AreaSwim(area2num)) { VectorCopy(plattop, start); start[2] += 32; VectorCopy(toporg, end); end[2] += 1; trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); if (trace.fraction >= 1) break; } //end if } //end if toporg[2] += 4; area2num = AAS_PointAreaNum(toporg); } //end if //if in solid if (l >= 16) continue; //never create a reachability in the same area if (area2num == area1num) continue; //if the area isn't grounded if (!AAS_AreaGrounded(area2num)) continue; //if there already exists reachability between the areas if (AAS_ReachabilityExists(area1num, area2num)) continue; //if the reachability start is within the elevator bounding box VectorSubtract(bottomorg, platbottom, dir); VectorNormalize(dir); dir[0] = bottomorg[0] + 24 * dir[0]; dir[1] = bottomorg[1] + 24 * dir[1]; dir[2] = bottomorg[2]; // for (p = 0; p < 3; p++) if (dir[p] < origin[p] + mins[p] || dir[p] > origin[p] + maxs[p]) break; if (p >= 3) continue; //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) continue; lreach->areanum = area2num; //the facenum is the model number lreach->facenum = modelnum; //the edgenum is the height lreach->edgenum = (int) height; // VectorCopy(dir, lreach->start); VectorCopy(toporg, lreach->end); lreach->traveltype = TRAVEL_ELEVATOR; lreach->traveltype |= AAS_TravelFlagsForTeam(ent); lreach->traveltime = aassettings.rs_startelevator + height * 100 / speed; lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; //don't go any further to the outside n = 9999; // #ifdef REACH_DEBUG Log_Write("elevator reach from %d to %d\r\n", area1num, area2num); #endif //REACH_DEBUG // reach_elevator++; } //end for } //end for } //end for } //end if } //end for } //end of the function AAS_Reachability_Elevator //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_lreachability_t *AAS_FindFaceReachabilities(vec3_t *facepoints, int numpoints, aas_plane_t *plane, int towardsface) { int i, j, k, l; int facenum, edgenum, bestfacenum; float *v1, *v2, *v3, *v4; float bestdist, speed, hordist, dist; vec3_t beststart, beststart2, bestend, bestend2, tmp, hordir, testpoint; aas_lreachability_t *lreach, *lreachabilities; aas_area_t *area; aas_face_t *face; aas_edge_t *edge; aas_plane_t *faceplane, *bestfaceplane; // lreachabilities = NULL; bestfacenum = 0; bestfaceplane = NULL; // for (i = 1; i < aasworld.numareas; i++) { area = &aasworld.areas[i]; // get the shortest distance between one of the func_bob start edges and // one of the face edges of area1 bestdist = 999999; for (j = 0; j < area->numfaces; j++) { facenum = aasworld.faceindex[area->firstface + j]; face = &aasworld.faces[abs(facenum)]; //if not a ground face if (!(face->faceflags & FACE_GROUND)) continue; //get the ground planes faceplane = &aasworld.planes[face->planenum]; // for (k = 0; k < face->numedges; k++) { edgenum = abs(aasworld.edgeindex[face->firstedge + k]); edge = &aasworld.edges[edgenum]; //calculate the minimum distance between the two edges v1 = aasworld.vertexes[edge->v[0]]; v2 = aasworld.vertexes[edge->v[1]]; // for (l = 0; l < numpoints; l++) { v3 = facepoints[l]; v4 = facepoints[(l+1) % numpoints]; dist = AAS_ClosestEdgePoints(v1, v2, v3, v4, faceplane, plane, beststart, bestend, beststart2, bestend2, bestdist); if (dist < bestdist) { bestfacenum = facenum; bestfaceplane = faceplane; bestdist = dist; } //end if } //end for } //end for } //end for // if (bestdist > 192) continue; // VectorMiddle(beststart, beststart2, beststart); VectorMiddle(bestend, bestend2, bestend); // if (!towardsface) { VectorCopy(beststart, tmp); VectorCopy(bestend, beststart); VectorCopy(tmp, bestend); } //end if // VectorSubtract(bestend, beststart, hordir); hordir[2] = 0; hordist = VectorLength(hordir); // if (hordist > 2 * AAS_MaxJumpDistance(aassettings.phys_jumpvel)) continue; //the end point should not be significantly higher than the start point if (bestend[2] - 32 > beststart[2]) continue; //don't fall down too far if (bestend[2] < beststart[2] - 128) continue; //the distance should not be too far if (hordist > 32) { //check for walk off ledge if (!AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) continue; } //end if // beststart[2] += 1; bestend[2] += 1; // if (towardsface) VectorCopy(bestend, testpoint); else VectorCopy(beststart, testpoint); testpoint[2] = 0; testpoint[2] = (bestfaceplane->dist - DotProduct(bestfaceplane->normal, testpoint)) / bestfaceplane->normal[2]; // if (!AAS_PointInsideFace(bestfacenum, testpoint, 0.1f)) { //if the faces are not overlapping then only go down if (bestend[2] - 16 > beststart[2]) continue; } //end if lreach = AAS_AllocReachability(); if (!lreach) return lreachabilities; lreach->areanum = i; lreach->facenum = 0; lreach->edgenum = 0; VectorCopy(beststart, lreach->start); VectorCopy(bestend, lreach->end); lreach->traveltype = 0; lreach->traveltime = 0; lreach->next = lreachabilities; lreachabilities = lreach; #ifndef BSPC if (towardsface) AAS_PermanentLine(lreach->start, lreach->end, 1); else AAS_PermanentLine(lreach->start, lreach->end, 2); #endif } //end for return lreachabilities; } //end of the function AAS_FindFaceReachabilities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_Reachability_FuncBobbing(void) { int ent, spawnflags, modelnum, axis; int i, numareas, areas[10]; char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; vec3_t origin, move_end, move_start, move_start_top, move_end_top; vec3_t mins, maxs, angles = {0, 0, 0}; vec3_t start_edgeverts[4], end_edgeverts[4], mid; vec3_t org, start, end, dir, points[10]; float height; aas_plane_t start_plane, end_plane; aas_lreachability_t *startreach, *endreach, *nextstartreach, *nextendreach, *lreach; aas_lreachability_t *firststartreach, *firstendreach; for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) { if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; if (strcmp(classname, "func_bobbing")) continue; AAS_FloatForBSPEpairKey(ent, "height", &height); if (!height) height = 32; // if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) { botimport.Print(PRT_ERROR, "func_bobbing without model\n"); continue; } //end if //get the model number, and skip the leading * modelnum = atoi(model+1); if (modelnum <= 0) { botimport.Print(PRT_ERROR, "func_bobbing with invalid model number\n"); continue; } //end if //if the entity has an origin set then use it if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) VectorSet(origin, 0, 0, 0); // AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); // VectorAdd(mins, origin, mins); VectorAdd(maxs, origin, maxs); // VectorAdd(mins, maxs, mid); VectorScale(mid, 0.5, mid); VectorCopy(mid, origin); // VectorCopy(origin, move_end); VectorCopy(origin, move_start); // AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); // set the axis of bobbing if (spawnflags & 1) axis = 0; else if (spawnflags & 2) axis = 1; else axis = 2; // move_start[axis] -= height; move_end[axis] += height; // Log_Write("funcbob model %d, start = {%1.1f, %1.1f, %1.1f} end = {%1.1f, %1.1f, %1.1f}\n", modelnum, move_start[0], move_start[1], move_start[2], move_end[0], move_end[1], move_end[2]); // #ifndef BSPC /* AAS_DrawPermanentCross(move_start, 4, 1); AAS_DrawPermanentCross(move_end, 4, 2); */ #endif // for (i = 0; i < 4; i++) { VectorCopy(move_start, start_edgeverts[i]); start_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z start_edgeverts[i][2] += 24; //+ player origin to ground dist } //end for start_edgeverts[0][0] += maxs[0] - mid[0]; start_edgeverts[0][1] += maxs[1] - mid[1]; start_edgeverts[1][0] += maxs[0] - mid[0]; start_edgeverts[1][1] += mins[1] - mid[1]; start_edgeverts[2][0] += mins[0] - mid[0]; start_edgeverts[2][1] += mins[1] - mid[1]; start_edgeverts[3][0] += mins[0] - mid[0]; start_edgeverts[3][1] += maxs[1] - mid[1]; // start_plane.dist = start_edgeverts[0][2]; VectorSet(start_plane.normal, 0, 0, 1); // for (i = 0; i < 4; i++) { VectorCopy(move_end, end_edgeverts[i]); end_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z end_edgeverts[i][2] += 24; //+ player origin to ground dist } //end for end_edgeverts[0][0] += maxs[0] - mid[0]; end_edgeverts[0][1] += maxs[1] - mid[1]; end_edgeverts[1][0] += maxs[0] - mid[0]; end_edgeverts[1][1] += mins[1] - mid[1]; end_edgeverts[2][0] += mins[0] - mid[0]; end_edgeverts[2][1] += mins[1] - mid[1]; end_edgeverts[3][0] += mins[0] - mid[0]; end_edgeverts[3][1] += maxs[1] - mid[1]; // end_plane.dist = end_edgeverts[0][2]; VectorSet(end_plane.normal, 0, 0, 1); // #ifndef BSPC #if 0 for (i = 0; i < 4; i++) { AAS_PermanentLine(start_edgeverts[i], start_edgeverts[(i+1)%4], 1); AAS_PermanentLine(end_edgeverts[i], end_edgeverts[(i+1)%4], 1); } //end for #endif #endif VectorCopy(move_start, move_start_top); move_start_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z VectorCopy(move_end, move_end_top); move_end_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z // if (!AAS_PointAreaNum(move_start_top)) continue; if (!AAS_PointAreaNum(move_end_top)) continue; // for (i = 0; i < 2; i++) { firststartreach = firstendreach = NULL; // if (i == 0) { firststartreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qtrue); firstendreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qfalse); } //end if else { firststartreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qtrue); firstendreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qfalse); } //end else // //create reachabilities from start to end for (startreach = firststartreach; startreach; startreach = nextstartreach) { nextstartreach = startreach->next; // //trace = AAS_TraceClientBBox(startreach->start, move_start_top, PRESENCE_NORMAL, -1); //if (trace.fraction < 1) continue; // for (endreach = firstendreach; endreach; endreach = nextendreach) { nextendreach = endreach->next; // //trace = AAS_TraceClientBBox(endreach->end, move_end_top, PRESENCE_NORMAL, -1); //if (trace.fraction < 1) continue; // Log_Write("funcbob reach from area %d to %d\n", startreach->areanum, endreach->areanum); // // if (i == 0) VectorCopy(move_start_top, org); else VectorCopy(move_end_top, org); VectorSubtract(startreach->start, org, dir); dir[2] = 0; VectorNormalize(dir); VectorCopy(startreach->start, start); VectorMA(startreach->start, 1, dir, start); start[2] += 1; VectorMA(startreach->start, 16, dir, end); end[2] += 1; // numareas = AAS_TraceAreas(start, end, areas, points, 10); if (numareas <= 0) continue; if (numareas > 1) VectorCopy(points[1], startreach->start); else VectorCopy(end, startreach->start); // if (!AAS_PointAreaNum(startreach->start)) continue; if (!AAS_PointAreaNum(endreach->end)) continue; // lreach = AAS_AllocReachability(); lreach->areanum = endreach->areanum; if (i == 0) lreach->edgenum = ((int)move_start[axis] << 16) | ((int) move_end[axis] & 0x0000ffff); else lreach->edgenum = ((int)move_end[axis] << 16) | ((int) move_start[axis] & 0x0000ffff); lreach->facenum = (spawnflags << 16) | modelnum; VectorCopy(startreach->start, lreach->start); VectorCopy(endreach->end, lreach->end); #ifndef BSPC // AAS_DrawArrow(lreach->start, lreach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); // AAS_PermanentLine(lreach->start, lreach->end, 1); #endif lreach->traveltype = TRAVEL_FUNCBOB; lreach->traveltype |= AAS_TravelFlagsForTeam(ent); lreach->traveltime = aassettings.rs_funcbob; reach_funcbob++; lreach->next = areareachability[startreach->areanum]; areareachability[startreach->areanum] = lreach; // } //end for } //end for for (startreach = firststartreach; startreach; startreach = nextstartreach) { nextstartreach = startreach->next; AAS_FreeReachability(startreach); } //end for for (endreach = firstendreach; endreach; endreach = nextendreach) { nextendreach = endreach->next; AAS_FreeReachability(endreach); } //end for //only go up with func_bobbing entities that go up and down if (!(spawnflags & 1) && !(spawnflags & 2)) break; } //end for } //end for } //end of the function AAS_Reachability_FuncBobbing //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_Reachability_JumpPad(void) { int face2num, i, ret, area2num, visualize, ent, bot_visualizejumppads; //int modelnum, ent2; //float dist, time, height, gravity, forward; float speed, zvel, hordist; aas_face_t *face2; aas_area_t *area2; aas_lreachability_t *lreach; vec3_t areastart, facecenter, dir, cmdmove; vec3_t velocity, absmins, absmaxs; //vec3_t origin, ent2origin, angles, teststart; aas_clientmove_t move; //aas_trace_t trace; aas_link_t *areas, *link; //char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; char classname[MAX_EPAIRKEY]; #ifdef BSPC bot_visualizejumppads = 0; #else bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0"); #endif for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) { if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; if (strcmp(classname, "trigger_push")) continue; // if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) continue; /* // AAS_FloatForBSPEpairKey(ent, "speed", &speed); if (!speed) speed = 1000; // AAS_VectorForBSPEpairKey(ent, "angles", angles); // AAS_SetMovedir(angles, velocity); // VectorScale(velocity, speed, velocity); VectorClear(angles); //get the mins, maxs and origin of the model AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); if (model[0]) modelnum = atoi(model+1); else modelnum = 0; AAS_BSPModelMinsMaxsOrigin(modelnum, angles, absmins, absmaxs, origin); VectorAdd(origin, absmins, absmins); VectorAdd(origin, absmaxs, absmaxs); // #ifdef REACH_DEBUG botimport.Print(PRT_MESSAGE, "absmins = %f %f %f\n", absmins[0], absmins[1], absmins[2]); botimport.Print(PRT_MESSAGE, "absmaxs = %f %f %f\n", absmaxs[0], absmaxs[1], absmaxs[2]); #endif REACH_DEBUG VectorAdd(absmins, absmaxs, origin); VectorScale (origin, 0.5, origin); //get the start areas VectorCopy(origin, teststart); teststart[2] += 64; trace = AAS_TraceClientBBox(teststart, origin, PRESENCE_CROUCH, -1); if (trace.startsolid) { botimport.Print(PRT_MESSAGE, "trigger_push start solid\n"); VectorCopy(origin, areastart); } //end if else { VectorCopy(trace.endpos, areastart); } //end else areastart[2] += 0.125; // //AAS_DrawPermanentCross(origin, 4, 4); //get the target entity AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY); for (ent2 = AAS_NextBSPEntity(0); ent2; ent2 = AAS_NextBSPEntity(ent2)) { if (!AAS_ValueForBSPEpairKey(ent2, "targetname", targetname, MAX_EPAIRKEY)) continue; if (!strcmp(targetname, target)) break; } //end for if (!ent2) { botimport.Print(PRT_MESSAGE, "trigger_push without target entity %s\n", target); continue; } //end if AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin); // height = ent2origin[2] - origin[2]; gravity = aassettings.sv_gravity; time = sqrt( height / ( 0.5 * gravity ) ); if (!time) { botimport.Print(PRT_MESSAGE, "trigger_push without time\n"); continue; } //end if // set s.origin2 to the push velocity VectorSubtract ( ent2origin, origin, velocity); dist = VectorNormalize( velocity); forward = dist / time; //FIXME: why multiply by 1.1 forward *= 1.1; VectorScale(velocity, forward, velocity); velocity[2] = time * gravity; */ //get the areas the jump pad brush is in areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); /* for (link = areas; link; link = link->next_area) { if (link->areanum == 563) { ret = qfalse; } } */ for (link = areas; link; link = link->next_area) { if (AAS_AreaJumpPad(link->areanum)) break; } //end for if (!link) { botimport.Print(PRT_MESSAGE, "trigger_push not in any jump pad area\n"); AAS_UnlinkFromAreas(areas); continue; } //end if // botimport.Print(PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2]); //if there is a horizontal velocity check for a reachability without air control if (velocity[0] || velocity[1]) { VectorSet(cmdmove, 0, 0, 0); //VectorCopy(velocity, cmdmove); //cmdmove[2] = 0; Com_Memset(&move, 0, sizeof(aas_clientmove_t)); area2num = 0; for (i = 0; i < 20; i++) { AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, velocity, cmdmove, 0, 30, 0.1f, SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, bot_visualizejumppads); area2num = move.endarea; for (link = areas; link; link = link->next_area) { if (!AAS_AreaJumpPad(link->areanum)) continue; if (link->areanum == area2num) break; } //end if if (!link) break; VectorCopy(move.endpos, areastart); VectorCopy(move.velocity, velocity); } //end for if (area2num && i < 20) { for (link = areas; link; link = link->next_area) { if (!AAS_AreaJumpPad(link->areanum)) continue; if (AAS_ReachabilityExists(link->areanum, area2num)) continue; //create a rocket or bfg jump reachability from area1 to area2 lreach = AAS_AllocReachability(); if (!lreach) { AAS_UnlinkFromAreas(areas); return; } //end if lreach->areanum = area2num; //NOTE: the facenum is the Z velocity lreach->facenum = velocity[2]; //NOTE: the edgenum is the horizontal velocity lreach->edgenum = sqrt(velocity[0] * velocity[0] + velocity[1] * velocity[1]); VectorCopy(areastart, lreach->start); VectorCopy(move.endpos, lreach->end); lreach->traveltype = TRAVEL_JUMPPAD; lreach->traveltype |= AAS_TravelFlagsForTeam(ent); lreach->traveltime = aassettings.rs_jumppad; lreach->next = areareachability[link->areanum]; areareachability[link->areanum] = lreach; // reach_jumppad++; } //end for } //end if } //end if // if (fabs(velocity[0]) > 100 || fabs(velocity[1]) > 100) continue; //check for areas we can reach with air control for (area2num = 1; area2num < aasworld.numareas; area2num++) { visualize = qfalse; /* if (area2num == 3568) { for (link = areas; link; link = link->next_area) { if (link->areanum == 3380) { visualize = qtrue; botimport.Print(PRT_MESSAGE, "bah\n"); } //end if } //end for } //end if*/ //never try to go back to one of the original jumppad areas //and don't create reachabilities if they already exist for (link = areas; link; link = link->next_area) { if (AAS_ReachabilityExists(link->areanum, area2num)) break; if (AAS_AreaJumpPad(link->areanum)) { if (link->areanum == area2num) break; } //end if } //end if if (link) continue; // area2 = &aasworld.areas[area2num]; for (i = 0; i < area2->numfaces; i++) { face2num = aasworld.faceindex[area2->firstface + i]; face2 = &aasworld.faces[abs(face2num)]; //if it is not a ground face if (!(face2->faceflags & FACE_GROUND)) continue; //get the center of the face AAS_FaceCenter(face2num, facecenter); //only go higher up if (facecenter[2] < areastart[2]) continue; //get the jumppad jump z velocity zvel = velocity[2]; //get the horizontal speed for the jump, if it isn't possible to calculate this //speed ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); if (ret && speed < 150) { //direction towards the face center VectorSubtract(facecenter, areastart, dir); dir[2] = 0; hordist = VectorNormalize(dir); //if (hordist < 1.6 * facecenter[2] - areastart[2]) { //get command movement VectorScale(dir, speed, cmdmove); // AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, velocity, cmdmove, 30, 30, 0.1f, SE_ENTERWATER|SE_ENTERSLIME| SE_ENTERLAVA|SE_HITGROUNDDAMAGE| SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER|SE_HITGROUNDAREA, area2num, visualize); //if prediction time wasn't enough to fully predict the movement //don't enter slime or lava and don't fall from too high if (move.frames < 30 && !(move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) && (move.stopevent & (SE_HITGROUNDAREA|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER))) { //never go back to the same jumppad for (link = areas; link; link = link->next_area) { if (link->areanum == move.endarea) break; } if (!link) { for (link = areas; link; link = link->next_area) { if (!AAS_AreaJumpPad(link->areanum)) continue; if (AAS_ReachabilityExists(link->areanum, area2num)) continue; //create a jumppad reachability from area1 to area2 lreach = AAS_AllocReachability(); if (!lreach) { AAS_UnlinkFromAreas(areas); return; } //end if lreach->areanum = move.endarea; //NOTE: the facenum is the Z velocity lreach->facenum = velocity[2]; //NOTE: the edgenum is the horizontal velocity lreach->edgenum = sqrt(cmdmove[0] * cmdmove[0] + cmdmove[1] * cmdmove[1]); VectorCopy(areastart, lreach->start); VectorCopy(facecenter, lreach->end); lreach->traveltype = TRAVEL_JUMPPAD; lreach->traveltype |= AAS_TravelFlagsForTeam(ent); lreach->traveltime = aassettings.rs_aircontrolledjumppad; lreach->next = areareachability[link->areanum]; areareachability[link->areanum] = lreach; // reach_jumppad++; } //end for } } //end if } //end if } //end for } //end for } //end for AAS_UnlinkFromAreas(areas); } //end for } //end of the function AAS_Reachability_JumpPad //=========================================================================== // never point at ground faces // always a higher and pretty far area // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_Reachability_Grapple(int area1num, int area2num) { int face2num, i, j, areanum, numareas, areas[20]; float mingrappleangle, z, hordist; bsp_trace_t bsptrace; aas_trace_t trace; aas_face_t *face2; aas_area_t *area1, *area2; aas_lreachability_t *lreach; vec3_t areastart, facecenter, start, end, dir, down = {0, 0, -1}; vec_t *v; //only grapple when on the ground or swimming if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) return qfalse; //don't grapple from a crouch area if (!(AAS_AreaPresenceType(area1num) & PRESENCE_NORMAL)) return qfalse; //NOTE: disabled area swim it doesn't work right if (AAS_AreaSwim(area1num)) return qfalse; // area1 = &aasworld.areas[area1num]; area2 = &aasworld.areas[area2num]; //don't grapple towards way lower areas if (area2->maxs[2] < area1->mins[2]) return qfalse; // VectorCopy(aasworld.areas[area1num].center, start); //if not a swim area if (!AAS_AreaSwim(area1num)) { if (!AAS_PointAreaNum(start)) Log_Write("area %d center %f %f %f in solid?\r\n", area1num, start[0], start[1], start[2]); VectorCopy(start, end); end[2] -= 1000; trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); if (trace.startsolid) return qfalse; VectorCopy(trace.endpos, areastart); } //end if else { if (!(AAS_PointContents(start) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) return qfalse; } //end else // //start is now the start point // for (i = 0; i < area2->numfaces; i++) { face2num = aasworld.faceindex[area2->firstface + i]; face2 = &aasworld.faces[abs(face2num)]; //if it is not a solid face if (!(face2->faceflags & FACE_SOLID)) continue; //direction towards the first vertex of the face v = aasworld.vertexes[aasworld.edges[abs(aasworld.edgeindex[face2->firstedge])].v[0]]; VectorSubtract(v, areastart, dir); //if the face plane is facing away if (DotProduct(aasworld.planes[face2->planenum].normal, dir) > 0) continue; //get the center of the face AAS_FaceCenter(face2num, facecenter); //only go higher up with the grapple if (facecenter[2] < areastart[2] + 64) continue; //only use vertical faces or downward facing faces if (DotProduct(aasworld.planes[face2->planenum].normal, down) < 0) continue; //direction towards the face center VectorSubtract(facecenter, areastart, dir); // z = dir[2]; dir[2] = 0; hordist = VectorLength(dir); if (!hordist) continue; //if too far if (hordist > 2000) continue; //check the minimal angle of the movement mingrappleangle = 15; //15 degrees if (z / hordist < tan(2 * M_PI * mingrappleangle / 360)) continue; // VectorCopy(facecenter, start); VectorMA(facecenter, -500, aasworld.planes[face2->planenum].normal, end); // bsptrace = AAS_Trace(start, NULL, NULL, end, 0, CONTENTS_SOLID); //the grapple won't stick to the sky and the grapple point should be near the AAS wall if ((bsptrace.surface.flags & SURF_SKY) || (bsptrace.fraction * 500 > 32)) continue; //trace a full bounding box from the area center on the ground to //the center of the face VectorSubtract(facecenter, areastart, dir); VectorNormalize(dir); VectorMA(areastart, 4, dir, start); VectorCopy(bsptrace.endpos, end); trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); VectorSubtract(trace.endpos, facecenter, dir); if (VectorLength(dir) > 24) continue; // VectorCopy(trace.endpos, start); VectorCopy(trace.endpos, end); end[2] -= AAS_FallDamageDistance(); trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); if (trace.fraction >= 1) continue; //area to end in areanum = AAS_PointAreaNum(trace.endpos); //if not in lava or slime if (aasworld.areasettings[areanum].contents & (AREACONTENTS_SLIME|AREACONTENTS_LAVA)) { continue; } //end if //do not go the the source area if (areanum == area1num) continue; //don't create reachabilities if they already exist if (AAS_ReachabilityExists(area1num, areanum)) continue; //only end in areas we can stand if (!AAS_AreaGrounded(areanum)) continue; //never go through cluster portals!! numareas = AAS_TraceAreas(areastart, bsptrace.endpos, areas, NULL, 20); if (numareas >= 20) continue; for (j = 0; j < numareas; j++) { if (aasworld.areasettings[areas[j]].contents & AREACONTENTS_CLUSTERPORTAL) break; } //end for if (j < numareas) continue; //create a new reachability link lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = areanum; lreach->facenum = face2num; lreach->edgenum = 0; VectorCopy(areastart, lreach->start); //VectorCopy(facecenter, lreach->end); VectorCopy(bsptrace.endpos, lreach->end); lreach->traveltype = TRAVEL_GRAPPLEHOOK; VectorSubtract(lreach->end, lreach->start, dir); lreach->traveltime = aassettings.rs_startgrapple + VectorLength(dir) * 0.25; lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; // reach_grapple++; } //end for // return qfalse; } //end of the function AAS_Reachability_Grapple //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_SetWeaponJumpAreaFlags(void) { int ent, i; vec3_t mins = {-15, -15, -15}, maxs = {15, 15, 15}; vec3_t origin; int areanum, weaponjumpareas, spawnflags; char classname[MAX_EPAIRKEY]; weaponjumpareas = 0; for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) { if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; if ( !strcmp(classname, "item_armor_body") || !strcmp(classname, "item_armor_combat") || !strcmp(classname, "item_health_mega") || !strcmp(classname, "weapon_grenadelauncher") || !strcmp(classname, "weapon_rocketlauncher") || !strcmp(classname, "weapon_lightning") || !strcmp(classname, "weapon_plasmagun") || !strcmp(classname, "weapon_railgun") || !strcmp(classname, "weapon_bfg") || !strcmp(classname, "item_quad") || !strcmp(classname, "item_regen") || !strcmp(classname, "item_invulnerability")) { if (AAS_VectorForBSPEpairKey(ent, "origin", origin)) { spawnflags = 0; AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); //if not a stationary item if (!(spawnflags & 1)) { if (!AAS_DropToFloor(origin, mins, maxs)) { botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", classname, origin[0], origin[1], origin[2]); } //end if } //end if //areanum = AAS_PointAreaNum(origin); areanum = AAS_BestReachableArea(origin, mins, maxs, origin); //the bot may rocket jump towards this area aasworld.areasettings[areanum].areaflags |= AREA_WEAPONJUMP; // //if (!AAS_AreaGrounded(areanum)) // botimport.Print(PRT_MESSAGE, "area not grounded\n"); // weaponjumpareas++; } //end if } //end if } //end for for (i = 1; i < aasworld.numareas; i++) { if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) { aasworld.areasettings[i].areaflags |= AREA_WEAPONJUMP; weaponjumpareas++; } //end if } //end for botimport.Print(PRT_MESSAGE, "%d weapon jump areas\n", weaponjumpareas); } //end of the function AAS_SetWeaponJumpAreaFlags //=========================================================================== // create a possible weapon jump reachability from area1 to area2 // // check if there's a cool item in the second area // check if area1 is lower than area2 // check if the bot can rocketjump from area1 to area2 // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_Reachability_WeaponJump(int area1num, int area2num) { int face2num, i, n, ret, visualize; float speed, zvel, hordist; aas_face_t *face2; aas_area_t *area1, *area2; aas_lreachability_t *lreach; vec3_t areastart, facecenter, start, end, dir, cmdmove;// teststart; vec3_t velocity; aas_clientmove_t move; aas_trace_t trace; visualize = qfalse; // if (area1num == 4436 && area2num == 4318) // { // visualize = qtrue; // } if (!AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) return qfalse; if (!AAS_AreaGrounded(area2num)) return qfalse; //NOTE: only weapon jump towards areas with an interesting item in it?? if (!(aasworld.areasettings[area2num].areaflags & AREA_WEAPONJUMP)) return qfalse; // area1 = &aasworld.areas[area1num]; area2 = &aasworld.areas[area2num]; //don't weapon jump towards way lower areas if (area2->maxs[2] < area1->mins[2]) return qfalse; // VectorCopy(aasworld.areas[area1num].center, start); //if not a swim area if (!AAS_PointAreaNum(start)) Log_Write("area %d center %f %f %f in solid?\r\n", area1num, start[0], start[1], start[2]); VectorCopy(start, end); end[2] -= 1000; trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); if (trace.startsolid) return qfalse; VectorCopy(trace.endpos, areastart); // //areastart is now the start point // for (i = 0; i < area2->numfaces; i++) { face2num = aasworld.faceindex[area2->firstface + i]; face2 = &aasworld.faces[abs(face2num)]; //if it is not a solid face if (!(face2->faceflags & FACE_GROUND)) continue; //get the center of the face AAS_FaceCenter(face2num, facecenter); //only go higher up with weapon jumps if (facecenter[2] < areastart[2] + 64) continue; //NOTE: set to 2 to allow bfg jump reachabilities for (n = 0; n < 1/*2*/; n++) { //get the rocket jump z velocity if (n) zvel = AAS_BFGJumpZVelocity(areastart); else zvel = AAS_RocketJumpZVelocity(areastart); //get the horizontal speed for the jump, if it isn't possible to calculate this //speed (the jump is not possible) then there's no jump reachability created ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); if (ret && speed < 300) { //direction towards the face center VectorSubtract(facecenter, areastart, dir); dir[2] = 0; hordist = VectorNormalize(dir); //if (hordist < 1.6 * (facecenter[2] - areastart[2])) { //get command movement VectorScale(dir, speed, cmdmove); VectorSet(velocity, 0, 0, zvel); /* //get command movement VectorScale(dir, speed, velocity); velocity[2] = zvel; VectorSet(cmdmove, 0, 0, 0); */ // AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qtrue, velocity, cmdmove, 30, 30, 0.1f, SE_ENTERWATER|SE_ENTERSLIME| SE_ENTERLAVA|SE_HITGROUNDDAMAGE| SE_TOUCHJUMPPAD|SE_HITGROUND|SE_HITGROUNDAREA, area2num, visualize); //if prediction time wasn't enough to fully predict the movement //don't enter slime or lava and don't fall from too high if (move.frames < 30 && !(move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) && (move.stopevent & (SE_HITGROUNDAREA|SE_TOUCHJUMPPAD))) { //create a rocket or bfg jump reachability from area1 to area2 lreach = AAS_AllocReachability(); if (!lreach) return qfalse; lreach->areanum = area2num; lreach->facenum = 0; lreach->edgenum = 0; VectorCopy(areastart, lreach->start); VectorCopy(facecenter, lreach->end); if (n) { lreach->traveltype = TRAVEL_BFGJUMP; lreach->traveltime = aassettings.rs_bfgjump; } //end if else { lreach->traveltype = TRAVEL_ROCKETJUMP; lreach->traveltime = aassettings.rs_rocketjump; } //end else lreach->next = areareachability[area1num]; areareachability[area1num] = lreach; // reach_rocketjump++; return qtrue; } //end if } //end if } //end if } //end for } //end for // return qfalse; } //end of the function AAS_Reachability_WeaponJump //=========================================================================== // calculates additional walk off ledge reachabilities for the given area // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_Reachability_WalkOffLedge(int areanum) { int i, j, k, l, m, n, p, areas[10], numareas; int face1num, face2num, face3num, edge1num, edge2num, edge3num; int otherareanum, gap, reachareanum, side; aas_area_t *area, *area2; aas_face_t *face1, *face2, *face3; aas_edge_t *edge; aas_plane_t *plane; vec_t *v1, *v2; vec3_t sharededgevec, mid, dir, testend; aas_lreachability_t *lreach; aas_trace_t trace; if (!AAS_AreaGrounded(areanum) || AAS_AreaSwim(areanum)) return; // area = &aasworld.areas[areanum]; // for (i = 0; i < area->numfaces; i++) { face1num = aasworld.faceindex[area->firstface + i]; face1 = &aasworld.faces[abs(face1num)]; //face 1 must be a ground face if (!(face1->faceflags & FACE_GROUND)) continue; //go through all the edges of this ground face for (k = 0; k < face1->numedges; k++) { edge1num = aasworld.edgeindex[face1->firstedge + k]; //find another not ground face using this same edge for (j = 0; j < area->numfaces; j++) { face2num = aasworld.faceindex[area->firstface + j]; face2 = &aasworld.faces[abs(face2num)]; //face 2 may not be a ground face if (face2->faceflags & FACE_GROUND) continue; //compare all the edges for (l = 0; l < face2->numedges; l++) { edge2num = aasworld.edgeindex[face2->firstedge + l]; if (abs(edge1num) == abs(edge2num)) { //get the area at the other side of the face if (face2->frontarea == areanum) otherareanum = face2->backarea; else otherareanum = face2->frontarea; // area2 = &aasworld.areas[otherareanum]; //if the other area is grounded! if (aasworld.areasettings[otherareanum].areaflags & AREA_GROUNDED) { //check for a possible gap gap = qfalse; for (n = 0; n < area2->numfaces; n++) { face3num = aasworld.faceindex[area2->firstface + n]; //may not be the shared face of the two areas if (abs(face3num) == abs(face2num)) continue; // face3 = &aasworld.faces[abs(face3num)]; //find an edge shared by all three faces for (m = 0; m < face3->numedges; m++) { edge3num = aasworld.edgeindex[face3->firstedge + m]; //but the edge should be shared by all three faces if (abs(edge3num) == abs(edge1num)) { if (!(face3->faceflags & FACE_SOLID)) { gap = qtrue; break; } //end if // if (face3->faceflags & FACE_GROUND) { gap = qfalse; break; } //end if //FIXME: there are more situations to be handled gap = qtrue; break; } //end if } //end for if (m < face3->numedges) break; } //end for if (!gap) break; } //end if //check for a walk off ledge reachability edge = &aasworld.edges[abs(edge1num)]; side = edge1num < 0; // v1 = aasworld.vertexes[edge->v[side]]; v2 = aasworld.vertexes[edge->v[!side]]; // plane = &aasworld.planes[face1->planenum]; //get the points really into the areas VectorSubtract(v2, v1, sharededgevec); CrossProduct(plane->normal, sharededgevec, dir); VectorNormalize(dir); // VectorAdd(v1, v2, mid); VectorScale(mid, 0.5, mid); VectorMA(mid, 8, dir, mid); // VectorCopy(mid, testend); testend[2] -= 1000; trace = AAS_TraceClientBBox(mid, testend, PRESENCE_CROUCH, -1); // if (trace.startsolid) { //Log_Write("area %d: trace.startsolid\r\n", areanum); break; } //end if reachareanum = AAS_PointAreaNum(trace.endpos); if (reachareanum == areanum) { //Log_Write("area %d: same area\r\n", areanum); break; } //end if if (AAS_ReachabilityExists(areanum, reachareanum)) { //Log_Write("area %d: reachability already exists\r\n", areanum); break; } //end if if (!AAS_AreaGrounded(reachareanum) && !AAS_AreaSwim(reachareanum)) { //Log_Write("area %d, reach area %d: not grounded and not swim\r\n", areanum, reachareanum); break; } //end if // if (aasworld.areasettings[reachareanum].contents & (AREACONTENTS_SLIME | AREACONTENTS_LAVA)) { //Log_Write("area %d, reach area %d: lava or slime\r\n", areanum, reachareanum); break; } //end if //if not going through a cluster portal numareas = AAS_TraceAreas(mid, testend, areas, NULL, sizeof(areas) / sizeof(int)); for (p = 0; p < numareas; p++) if (AAS_AreaClusterPortal(areas[p])) break; if (p < numareas) break; // if a maximum fall height is set and the bot would fall down further if (aassettings.rs_maxfallheight && fabs(mid[2] - trace.endpos[2]) > aassettings.rs_maxfallheight) break; // lreach = AAS_AllocReachability(); if (!lreach) break; lreach->areanum = reachareanum; lreach->facenum = 0; lreach->edgenum = edge1num; VectorCopy(mid, lreach->start); VectorCopy(trace.endpos, lreach->end); lreach->traveltype = TRAVEL_WALKOFFLEDGE; lreach->traveltime = aassettings.rs_startwalkoffledge + fabs(mid[2] - trace.endpos[2]) * 50 / aassettings.phys_gravity; if (!AAS_AreaSwim(reachareanum) && !AAS_AreaJumpPad(reachareanum)) { if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta5) { lreach->traveltime += aassettings.rs_falldamage5; } //end if else if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta10) { lreach->traveltime += aassettings.rs_falldamage10; } //end if } //end if lreach->next = areareachability[areanum]; areareachability[areanum] = lreach; //we've got another walk off ledge reachability reach_walkoffledge++; } //end if } //end for } //end for } //end for } //end for } //end of the function AAS_Reachability_WalkOffLedge //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_StoreReachability(void) { int i; aas_areasettings_t *areasettings; aas_lreachability_t *lreach; aas_reachability_t *reach; if (aasworld.reachability) FreeMemory(aasworld.reachability); aasworld.reachability = (aas_reachability_t *) GetClearedMemory((numlreachabilities + 10) * sizeof(aas_reachability_t)); aasworld.reachabilitysize = 1; for (i = 0; i < aasworld.numareas; i++) { areasettings = &aasworld.areasettings[i]; areasettings->firstreachablearea = aasworld.reachabilitysize; areasettings->numreachableareas = 0; for (lreach = areareachability[i]; lreach; lreach = lreach->next) { reach = &aasworld.reachability[areasettings->firstreachablearea + areasettings->numreachableareas]; reach->areanum = lreach->areanum; reach->facenum = lreach->facenum; reach->edgenum = lreach->edgenum; VectorCopy(lreach->start, reach->start); VectorCopy(lreach->end, reach->end); reach->traveltype = lreach->traveltype; reach->traveltime = lreach->traveltime; // areasettings->numreachableareas++; } //end for aasworld.reachabilitysize += areasettings->numreachableareas; } //end for } //end of the function AAS_StoreReachability //=========================================================================== // // TRAVEL_WALK 100% equal floor height + steps // TRAVEL_CROUCH 100% // TRAVEL_BARRIERJUMP 100% // TRAVEL_JUMP 80% // TRAVEL_LADDER 100% + fall down from ladder + jump up to ladder // TRAVEL_WALKOFFLEDGE 90% walk off very steep walls? // TRAVEL_SWIM 100% // TRAVEL_WATERJUMP 100% // TRAVEL_TELEPORT 100% // TRAVEL_ELEVATOR 100% // TRAVEL_GRAPPLEHOOK 100% // TRAVEL_DOUBLEJUMP 0% // TRAVEL_RAMPJUMP 0% // TRAVEL_STRAFEJUMP 0% // TRAVEL_ROCKETJUMP 100% (currently limited towards areas with items) // TRAVEL_BFGJUMP 0% (currently disabled) // TRAVEL_JUMPPAD 100% // TRAVEL_FUNCBOB 100% // // Parameter: - // Returns: true if NOT finished // Changes Globals: - //=========================================================================== int AAS_ContinueInitReachability(float time) { int i, j, todo, start_time; static float framereachability, reachability_delay; static int lastpercentage; if (!aasworld.loaded) return qfalse; //if reachability is calculated for all areas if (aasworld.numreachabilityareas >= aasworld.numareas + 2) return qfalse; //if starting with area 1 (area 0 is a dummy) if (aasworld.numreachabilityareas == 1) { botimport.Print(PRT_MESSAGE, "calculating reachability...\n"); lastpercentage = 0; framereachability = 2000; reachability_delay = 1000; } //end if //number of areas to calculate reachability for this cycle todo = aasworld.numreachabilityareas + (int) framereachability; start_time = Sys_MilliSeconds(); //loop over the areas for (i = aasworld.numreachabilityareas; i < aasworld.numareas && i < todo; i++) { aasworld.numreachabilityareas++; //only create jumppad reachabilities from jumppad areas if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) { continue; } //end if //loop over the areas for (j = 1; j < aasworld.numareas; j++) { if (i == j) continue; //never create reachabilities from teleporter or jumppad areas to regular areas if (aasworld.areasettings[i].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD)) { if (!(aasworld.areasettings[j].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD))) { continue; } //end if } //end if //if there already is a reachability link from area i to j if (AAS_ReachabilityExists(i, j)) continue; //check for a swim reachability if (AAS_Reachability_Swim(i, j)) continue; //check for a simple walk on equal floor height reachability if (AAS_Reachability_EqualFloorHeight(i, j)) continue; //check for step, barrier, waterjump and walk off ledge reachabilities if (AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(i, j)) continue; //check for ladder reachabilities if (AAS_Reachability_Ladder(i, j)) continue; //check for a jump reachability if (AAS_Reachability_Jump(i, j)) continue; } //end for //never create these reachabilities from teleporter or jumppad areas if (aasworld.areasettings[i].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD)) { continue; } //end if //loop over the areas for (j = 1; j < aasworld.numareas; j++) { if (i == j) continue; // if (AAS_ReachabilityExists(i, j)) continue; //check for a grapple hook reachability if (calcgrapplereach) AAS_Reachability_Grapple(i, j); //check for a weapon jump reachability AAS_Reachability_WeaponJump(i, j); } //end for //if the calculation took more time than the max reachability delay if (Sys_MilliSeconds() - start_time > (int) reachability_delay) break; // if (aasworld.numreachabilityareas * 1000 / aasworld.numareas > lastpercentage) break; } //end for // if (aasworld.numreachabilityareas == aasworld.numareas) { botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) 100.0); botimport.Print(PRT_MESSAGE, "\nplease wait while storing reachability...\n"); aasworld.numreachabilityareas++; } //end if //if this is the last step in the reachability calculations else if (aasworld.numreachabilityareas == aasworld.numareas + 1) { //create additional walk off ledge reachabilities for every area for (i = 1; i < aasworld.numareas; i++) { //only create jumppad reachabilities from jumppad areas if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) { continue; } //end if AAS_Reachability_WalkOffLedge(i); } //end for //create jump pad reachabilities AAS_Reachability_JumpPad(); //create teleporter reachabilities AAS_Reachability_Teleport(); //create elevator (func_plat) reachabilities AAS_Reachability_Elevator(); //create func_bobbing reachabilities AAS_Reachability_FuncBobbing(); // #ifdef DEBUG botimport.Print(PRT_MESSAGE, "%6d reach swim\n", reach_swim); botimport.Print(PRT_MESSAGE, "%6d reach equal floor\n", reach_equalfloor); botimport.Print(PRT_MESSAGE, "%6d reach step\n", reach_step); botimport.Print(PRT_MESSAGE, "%6d reach barrier\n", reach_barrier); botimport.Print(PRT_MESSAGE, "%6d reach waterjump\n", reach_waterjump); botimport.Print(PRT_MESSAGE, "%6d reach walkoffledge\n", reach_walkoffledge); botimport.Print(PRT_MESSAGE, "%6d reach jump\n", reach_jump); botimport.Print(PRT_MESSAGE, "%6d reach ladder\n", reach_ladder); botimport.Print(PRT_MESSAGE, "%6d reach walk\n", reach_walk); botimport.Print(PRT_MESSAGE, "%6d reach teleport\n", reach_teleport); botimport.Print(PRT_MESSAGE, "%6d reach funcbob\n", reach_funcbob); botimport.Print(PRT_MESSAGE, "%6d reach elevator\n", reach_elevator); botimport.Print(PRT_MESSAGE, "%6d reach grapple\n", reach_grapple); botimport.Print(PRT_MESSAGE, "%6d reach rocketjump\n", reach_rocketjump); botimport.Print(PRT_MESSAGE, "%6d reach jumppad\n", reach_jumppad); #endif //*/ //store all the reachabilities AAS_StoreReachability(); //free the reachability link heap AAS_ShutDownReachabilityHeap(); // FreeMemory(areareachability); // aasworld.numreachabilityareas++; // botimport.Print(PRT_MESSAGE, "calculating clusters...\n"); } //end if else { lastpercentage = aasworld.numreachabilityareas * 1000 / aasworld.numareas; botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) lastpercentage / 10); } //end else //not yet finished return qtrue; } //end of the function AAS_ContinueInitReachability //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitReachability(void) { if (!aasworld.loaded) return; if (aasworld.reachabilitysize) { #ifndef BSPC if (!((int)LibVarGetValue("forcereachability"))) { aasworld.numreachabilityareas = aasworld.numareas + 2; return; } //end if #else aasworld.numreachabilityareas = aasworld.numareas + 2; return; #endif //BSPC } //end if #ifndef BSPC calcgrapplereach = LibVarGetValue("grapplereach"); #endif aasworld.savefile = qtrue; //start with area 1 because area zero is a dummy aasworld.numreachabilityareas = 1; ////aasworld.numreachabilityareas = aasworld.numareas + 1; //only calculate entity reachabilities //setup the heap with reachability links AAS_SetupReachabilityHeap(); //allocate area reachability link array areareachability = (aas_lreachability_t **) GetClearedMemory( aasworld.numareas * sizeof(aas_lreachability_t *)); // AAS_SetWeaponJumpAreaFlags(); } //end of the function AAS_InitReachable ================================================ FILE: code/botlib/be_aas_reach.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_reach.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_reach.h $ * *****************************************************************************/ #ifdef AASINTERN //initialize calculating the reachabilities void AAS_InitReachability(void); //continue calculating the reachabilities int AAS_ContinueInitReachability(float time); // int AAS_BestReachableLinkArea(aas_link_t *areas); #endif //AASINTERN //returns true if the are has reachabilities to other areas int AAS_AreaReachability(int areanum); //returns the best reachable area and goal origin for a bounding box at the given origin int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin); //returns the best jumppad area from which the bbox at origin is reachable int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs); //returns the next reachability using the given model int AAS_NextModelReachability(int num, int modelnum); //returns the total area of the ground faces of the given area float AAS_AreaGroundFaceArea(int areanum); //returns true if the area is crouch only int AAS_AreaCrouch(int areanum); //returns true if a player can swim in this area int AAS_AreaSwim(int areanum); //returns true if the area is filled with a liquid int AAS_AreaLiquid(int areanum); //returns true if the area contains lava int AAS_AreaLava(int areanum); //returns true if the area contains slime int AAS_AreaSlime(int areanum); //returns true if the area has one or more ground faces int AAS_AreaGrounded(int areanum); //returns true if the area has one or more ladder faces int AAS_AreaLadder(int areanum); //returns true if the area is a jump pad int AAS_AreaJumpPad(int areanum); //returns true if the area is donotenter int AAS_AreaDoNotEnter(int areanum); ================================================ FILE: code/botlib/be_aas_route.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_route.c * * desc: AAS * * $Archive: /MissionPack/code/botlib/be_aas_route.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_utils.h" #include "l_memory.h" #include "l_log.h" #include "l_crc.h" #include "l_libvar.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "be_aas_def.h" #define ROUTING_DEBUG //travel time in hundreths of a second = distance * 100 / speed #define DISTANCEFACTOR_CROUCH 1.3f //crouch speed = 100 #define DISTANCEFACTOR_SWIM 1 //should be 0.66, swim speed = 150 #define DISTANCEFACTOR_WALK 0.33f //walk speed = 300 //cache refresh time #define CACHE_REFRESHTIME 15.0f //15 seconds refresh time //maximum number of routing updates each frame #define MAX_FRAMEROUTINGUPDATES 10 /* area routing cache: stores the distances within one cluster to a specific goal area this goal area is in this same cluster and could be a cluster portal for every cluster there's a list with routing cache for every area in that cluster (including the portals of that cluster) area cache stores aasworld.clusters[?].numreachabilityareas travel times portal routing cache: stores the distances of all portals to a specific goal area this goal area could be in any cluster and could also be a cluster portal for every area (aasworld.numareas) the portal cache stores aasworld.numportals travel times */ #ifdef ROUTING_DEBUG int numareacacheupdates; int numportalcacheupdates; #endif //ROUTING_DEBUG int routingcachesize; int max_routingcachesize; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef ROUTING_DEBUG void AAS_RoutingInfo(void) { botimport.Print(PRT_MESSAGE, "%d area cache updates\n", numareacacheupdates); botimport.Print(PRT_MESSAGE, "%d portal cache updates\n", numportalcacheupdates); botimport.Print(PRT_MESSAGE, "%d bytes routing cache\n", routingcachesize); } //end of the function AAS_RoutingInfo #endif //ROUTING_DEBUG //=========================================================================== // returns the number of the area in the cluster // assumes the given area is in the given cluster or a portal of the cluster // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== __inline int AAS_ClusterAreaNum(int cluster, int areanum) { int side, areacluster; areacluster = aasworld.areasettings[areanum].cluster; if (areacluster > 0) return aasworld.areasettings[areanum].clusterareanum; else { /*#ifdef ROUTING_DEBUG if (aasworld.portals[-areacluster].frontcluster != cluster && aasworld.portals[-areacluster].backcluster != cluster) { botimport.Print(PRT_ERROR, "portal %d: does not belong to cluster %d\n" , -areacluster, cluster); } //end if #endif //ROUTING_DEBUG*/ side = aasworld.portals[-areacluster].frontcluster != cluster; return aasworld.portals[-areacluster].clusterareanum[side]; } //end else } //end of the function AAS_ClusterAreaNum //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitTravelFlagFromType(void) { int i; for (i = 0; i < MAX_TRAVELTYPES; i++) { aasworld.travelflagfortype[i] = TFL_INVALID; } //end for aasworld.travelflagfortype[TRAVEL_INVALID] = TFL_INVALID; aasworld.travelflagfortype[TRAVEL_WALK] = TFL_WALK; aasworld.travelflagfortype[TRAVEL_CROUCH] = TFL_CROUCH; aasworld.travelflagfortype[TRAVEL_BARRIERJUMP] = TFL_BARRIERJUMP; aasworld.travelflagfortype[TRAVEL_JUMP] = TFL_JUMP; aasworld.travelflagfortype[TRAVEL_LADDER] = TFL_LADDER; aasworld.travelflagfortype[TRAVEL_WALKOFFLEDGE] = TFL_WALKOFFLEDGE; aasworld.travelflagfortype[TRAVEL_SWIM] = TFL_SWIM; aasworld.travelflagfortype[TRAVEL_WATERJUMP] = TFL_WATERJUMP; aasworld.travelflagfortype[TRAVEL_TELEPORT] = TFL_TELEPORT; aasworld.travelflagfortype[TRAVEL_ELEVATOR] = TFL_ELEVATOR; aasworld.travelflagfortype[TRAVEL_ROCKETJUMP] = TFL_ROCKETJUMP; aasworld.travelflagfortype[TRAVEL_BFGJUMP] = TFL_BFGJUMP; aasworld.travelflagfortype[TRAVEL_GRAPPLEHOOK] = TFL_GRAPPLEHOOK; aasworld.travelflagfortype[TRAVEL_DOUBLEJUMP] = TFL_DOUBLEJUMP; aasworld.travelflagfortype[TRAVEL_RAMPJUMP] = TFL_RAMPJUMP; aasworld.travelflagfortype[TRAVEL_STRAFEJUMP] = TFL_STRAFEJUMP; aasworld.travelflagfortype[TRAVEL_JUMPPAD] = TFL_JUMPPAD; aasworld.travelflagfortype[TRAVEL_FUNCBOB] = TFL_FUNCBOB; } //end of the function AAS_InitTravelFlagFromType //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== __inline int AAS_TravelFlagForType_inline(int traveltype) { int tfl; tfl = 0; if (tfl & TRAVELFLAG_NOTTEAM1) tfl |= TFL_NOTTEAM1; if (tfl & TRAVELFLAG_NOTTEAM2) tfl |= TFL_NOTTEAM2; traveltype &= TRAVELTYPE_MASK; if (traveltype < 0 || traveltype >= MAX_TRAVELTYPES) return TFL_INVALID; tfl |= aasworld.travelflagfortype[traveltype]; return tfl; } //end of the function AAS_TravelFlagForType_inline //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_TravelFlagForType(int traveltype) { return AAS_TravelFlagForType_inline(traveltype); } //end of the function AAS_TravelFlagForType_inline //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_UnlinkCache(aas_routingcache_t *cache) { if (cache->time_next) cache->time_next->time_prev = cache->time_prev; else aasworld.newestcache = cache->time_prev; if (cache->time_prev) cache->time_prev->time_next = cache->time_next; else aasworld.oldestcache = cache->time_next; cache->time_next = NULL; cache->time_prev = NULL; } //end of the function AAS_UnlinkCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_LinkCache(aas_routingcache_t *cache) { if (aasworld.newestcache) { aasworld.newestcache->time_next = cache; cache->time_prev = aasworld.newestcache; } //end if else { aasworld.oldestcache = cache; cache->time_prev = NULL; } //end else cache->time_next = NULL; aasworld.newestcache = cache; } //end of the function AAS_LinkCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeRoutingCache(aas_routingcache_t *cache) { AAS_UnlinkCache(cache); routingcachesize -= cache->size; FreeMemory(cache); } //end of the function AAS_FreeRoutingCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RemoveRoutingCacheInCluster( int clusternum ) { int i; aas_routingcache_t *cache, *nextcache; aas_cluster_t *cluster; if (!aasworld.clusterareacache) return; cluster = &aasworld.clusters[clusternum]; for (i = 0; i < cluster->numareas; i++) { for (cache = aasworld.clusterareacache[clusternum][i]; cache; cache = nextcache) { nextcache = cache->next; AAS_FreeRoutingCache(cache); } //end for aasworld.clusterareacache[clusternum][i] = NULL; } //end for } //end of the function AAS_RemoveRoutingCacheInCluster //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RemoveRoutingCacheUsingArea( int areanum ) { int i, clusternum; aas_routingcache_t *cache, *nextcache; clusternum = aasworld.areasettings[areanum].cluster; if (clusternum > 0) { //remove all the cache in the cluster the area is in AAS_RemoveRoutingCacheInCluster( clusternum ); } //end if else { // if this is a portal remove all cache in both the front and back cluster AAS_RemoveRoutingCacheInCluster( aasworld.portals[-clusternum].frontcluster ); AAS_RemoveRoutingCacheInCluster( aasworld.portals[-clusternum].backcluster ); } //end else // remove all portal cache for (i = 0; i < aasworld.numareas; i++) { //refresh portal cache for (cache = aasworld.portalcache[i]; cache; cache = nextcache) { nextcache = cache->next; AAS_FreeRoutingCache(cache); } //end for aasworld.portalcache[i] = NULL; } //end for } //end of the function AAS_RemoveRoutingCacheUsingArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_EnableRoutingArea(int areanum, int enable) { int flags; if (areanum <= 0 || areanum >= aasworld.numareas) { if (bot_developer) { botimport.Print(PRT_ERROR, "AAS_EnableRoutingArea: areanum %d out of range\n", areanum); } //end if return 0; } //end if flags = aasworld.areasettings[areanum].areaflags & AREA_DISABLED; if (enable < 0) return !flags; if (enable) aasworld.areasettings[areanum].areaflags &= ~AREA_DISABLED; else aasworld.areasettings[areanum].areaflags |= AREA_DISABLED; // if the status of the area changed if ( (flags & AREA_DISABLED) != (aasworld.areasettings[areanum].areaflags & AREA_DISABLED) ) { //remove all routing cache involving this area AAS_RemoveRoutingCacheUsingArea( areanum ); } //end if return !flags; } //end of the function AAS_EnableRoutingArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== __inline float AAS_RoutingTime(void) { return AAS_Time(); } //end of the function AAS_RoutingTime //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_GetAreaContentsTravelFlags(int areanum) { int contents, tfl; contents = aasworld.areasettings[areanum].contents; tfl = 0; if (contents & AREACONTENTS_WATER) tfl |= TFL_WATER; else if (contents & AREACONTENTS_SLIME) tfl |= TFL_SLIME; else if (contents & AREACONTENTS_LAVA) tfl |= TFL_LAVA; else tfl |= TFL_AIR; if (contents & AREACONTENTS_DONOTENTER) tfl |= TFL_DONOTENTER; if (contents & AREACONTENTS_NOTTEAM1) tfl |= TFL_NOTTEAM1; if (contents & AREACONTENTS_NOTTEAM2) tfl |= TFL_NOTTEAM2; if (aasworld.areasettings[areanum].areaflags & AREA_BRIDGE) tfl |= TFL_BRIDGE; return tfl; } //end of the function AAS_GetAreaContentsTravelFlags //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== __inline int AAS_AreaContentsTravelFlags_inline(int areanum) { return aasworld.areacontentstravelflags[areanum]; } //end of the function AAS_AreaContentsTravelFlags //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaContentsTravelFlags(int areanum) { return aasworld.areacontentstravelflags[areanum]; } //end of the function AAS_AreaContentsTravelFlags //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitAreaContentsTravelFlags(void) { int i; if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags); aasworld.areacontentstravelflags = (int *) GetClearedMemory(aasworld.numareas * sizeof(int)); // for (i = 0; i < aasworld.numareas; i++) { aasworld.areacontentstravelflags[i] = AAS_GetAreaContentsTravelFlags(i); } } //end of the function AAS_InitAreaContentsTravelFlags //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CreateReversedReachability(void) { int i, n; aas_reversedlink_t *revlink; aas_reachability_t *reach; aas_areasettings_t *settings; char *ptr; #ifdef DEBUG int starttime; starttime = Sys_MilliSeconds(); #endif //free reversed links that have already been created if (aasworld.reversedreachability) FreeMemory(aasworld.reversedreachability); //allocate memory for the reversed reachability links ptr = (char *) GetClearedMemory(aasworld.numareas * sizeof(aas_reversedreachability_t) + aasworld.reachabilitysize * sizeof(aas_reversedlink_t)); // aasworld.reversedreachability = (aas_reversedreachability_t *) ptr; //pointer to the memory for the reversed links ptr += aasworld.numareas * sizeof(aas_reversedreachability_t); //check all reachabilities of all areas for (i = 1; i < aasworld.numareas; i++) { //settings of the area settings = &aasworld.areasettings[i]; // if (settings->numreachableareas >= 128) botimport.Print(PRT_WARNING, "area %d has more than 128 reachabilities\n", i); //create reversed links for the reachabilities for (n = 0; n < settings->numreachableareas && n < 128; n++) { //reachability link reach = &aasworld.reachability[settings->firstreachablearea + n]; // revlink = (aas_reversedlink_t *) ptr; ptr += sizeof(aas_reversedlink_t); // revlink->areanum = i; revlink->linknum = settings->firstreachablearea + n; revlink->next = aasworld.reversedreachability[reach->areanum].first; aasworld.reversedreachability[reach->areanum].first = revlink; aasworld.reversedreachability[reach->areanum].numlinks++; } //end for } //end for #ifdef DEBUG botimport.Print(PRT_MESSAGE, "reversed reachability %d msec\n", Sys_MilliSeconds() - starttime); #endif } //end of the function AAS_CreateReversedReachability //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end) { int intdist; float dist; vec3_t dir; VectorSubtract(start, end, dir); dist = VectorLength(dir); //if crouch only area if (AAS_AreaCrouch(areanum)) dist *= DISTANCEFACTOR_CROUCH; //if swim area else if (AAS_AreaSwim(areanum)) dist *= DISTANCEFACTOR_SWIM; //normal walk area else dist *= DISTANCEFACTOR_WALK; // intdist = (int) dist; //make sure the distance isn't zero if (intdist <= 0) intdist = 1; return intdist; } //end of the function AAS_AreaTravelTime //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CalculateAreaTravelTimes(void) { int i, l, n, size; char *ptr; vec3_t end; aas_reversedreachability_t *revreach; aas_reversedlink_t *revlink; aas_reachability_t *reach; aas_areasettings_t *settings; int starttime; starttime = Sys_MilliSeconds(); //if there are still area travel times, free the memory if (aasworld.areatraveltimes) FreeMemory(aasworld.areatraveltimes); //get the total size of all the area travel times size = aasworld.numareas * sizeof(unsigned short **); for (i = 0; i < aasworld.numareas; i++) { revreach = &aasworld.reversedreachability[i]; //settings of the area settings = &aasworld.areasettings[i]; // size += settings->numreachableareas * sizeof(unsigned short *); // size += settings->numreachableareas * revreach->numlinks * sizeof(unsigned short); } //end for //allocate memory for the area travel times ptr = (char *) GetClearedMemory(size); aasworld.areatraveltimes = (unsigned short ***) ptr; ptr += aasworld.numareas * sizeof(unsigned short **); //calcluate the travel times for all the areas for (i = 0; i < aasworld.numareas; i++) { //reversed reachabilities of this area revreach = &aasworld.reversedreachability[i]; //settings of the area settings = &aasworld.areasettings[i]; // aasworld.areatraveltimes[i] = (unsigned short **) ptr; ptr += settings->numreachableareas * sizeof(unsigned short *); // for (l = 0; l < settings->numreachableareas; l++) { aasworld.areatraveltimes[i][l] = (unsigned short *) ptr; ptr += revreach->numlinks * sizeof(unsigned short); //reachability link reach = &aasworld.reachability[settings->firstreachablearea + l]; // for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) { VectorCopy(aasworld.reachability[revlink->linknum].end, end); // aasworld.areatraveltimes[i][l][n] = AAS_AreaTravelTime(i, end, reach->start); } //end for } //end for } //end for #ifdef DEBUG botimport.Print(PRT_MESSAGE, "area travel times %d msec\n", Sys_MilliSeconds() - starttime); #endif } //end of the function AAS_CalculateAreaTravelTimes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_PortalMaxTravelTime(int portalnum) { int l, n, t, maxt; aas_portal_t *portal; aas_reversedreachability_t *revreach; aas_reversedlink_t *revlink; aas_areasettings_t *settings; portal = &aasworld.portals[portalnum]; //reversed reachabilities of this portal area revreach = &aasworld.reversedreachability[portal->areanum]; //settings of the portal area settings = &aasworld.areasettings[portal->areanum]; // maxt = 0; for (l = 0; l < settings->numreachableareas; l++) { for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) { t = aasworld.areatraveltimes[portal->areanum][l][n]; if (t > maxt) { maxt = t; } //end if } //end for } //end for return maxt; } //end of the function AAS_PortalMaxTravelTime //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitPortalMaxTravelTimes(void) { int i; if (aasworld.portalmaxtraveltimes) FreeMemory(aasworld.portalmaxtraveltimes); aasworld.portalmaxtraveltimes = (int *) GetClearedMemory(aasworld.numportals * sizeof(int)); for (i = 0; i < aasworld.numportals; i++) { aasworld.portalmaxtraveltimes[i] = AAS_PortalMaxTravelTime(i); //botimport.Print(PRT_MESSAGE, "portal %d max tt = %d\n", i, aasworld.portalmaxtraveltimes[i]); } //end for } //end of the function AAS_InitPortalMaxTravelTimes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== /* int AAS_FreeOldestCache(void) { int i, j, bestcluster, bestarea, freed; float besttime; aas_routingcache_t *cache, *bestcache; freed = qfalse; besttime = 999999999; bestcache = NULL; bestcluster = 0; bestarea = 0; //refresh cluster cache for (i = 0; i < aasworld.numclusters; i++) { for (j = 0; j < aasworld.clusters[i].numareas; j++) { for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) { //never remove cache leading towards a portal if (aasworld.areasettings[cache->areanum].cluster < 0) continue; //if this cache is older than the cache we found so far if (cache->time < besttime) { bestcache = cache; bestcluster = i; bestarea = j; besttime = cache->time; } //end if } //end for } //end for } //end for if (bestcache) { cache = bestcache; if (cache->prev) cache->prev->next = cache->next; else aasworld.clusterareacache[bestcluster][bestarea] = cache->next; if (cache->next) cache->next->prev = cache->prev; AAS_FreeRoutingCache(cache); freed = qtrue; } //end if besttime = 999999999; bestcache = NULL; bestarea = 0; for (i = 0; i < aasworld.numareas; i++) { //refresh portal cache for (cache = aasworld.portalcache[i]; cache; cache = cache->next) { if (cache->time < besttime) { bestcache = cache; bestarea = i; besttime = cache->time; } //end if } //end for } //end for if (bestcache) { cache = bestcache; if (cache->prev) cache->prev->next = cache->next; else aasworld.portalcache[bestarea] = cache->next; if (cache->next) cache->next->prev = cache->prev; AAS_FreeRoutingCache(cache); freed = qtrue; } //end if return freed; } //end of the function AAS_FreeOldestCache */ //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_FreeOldestCache(void) { int clusterareanum; aas_routingcache_t *cache; for (cache = aasworld.oldestcache; cache; cache = cache->time_next) { // never free area cache leading towards a portal if (cache->type == CACHETYPE_AREA && aasworld.areasettings[cache->areanum].cluster < 0) { continue; } break; } if (cache) { // unlink the cache if (cache->type == CACHETYPE_AREA) { //number of the area in the cluster clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum); // unlink from cluster area cache if (cache->prev) cache->prev->next = cache->next; else aasworld.clusterareacache[cache->cluster][clusterareanum] = cache->next; if (cache->next) cache->next->prev = cache->prev; } else { // unlink from portal cache if (cache->prev) cache->prev->next = cache->next; else aasworld.portalcache[cache->areanum] = cache->next; if (cache->next) cache->next->prev = cache->prev; } AAS_FreeRoutingCache(cache); return qtrue; } return qfalse; } //end of the function AAS_FreeOldestCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_routingcache_t *AAS_AllocRoutingCache(int numtraveltimes) { aas_routingcache_t *cache; int size; // size = sizeof(aas_routingcache_t) + numtraveltimes * sizeof(unsigned short int) + numtraveltimes * sizeof(unsigned char); // routingcachesize += size; // cache = (aas_routingcache_t *) GetClearedMemory(size); cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) + numtraveltimes * sizeof(unsigned short int); cache->size = size; return cache; } //end of the function AAS_AllocRoutingCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeAllClusterAreaCache(void) { int i, j; aas_routingcache_t *cache, *nextcache; aas_cluster_t *cluster; //free all cluster cache if existing if (!aasworld.clusterareacache) return; //free caches for (i = 0; i < aasworld.numclusters; i++) { cluster = &aasworld.clusters[i]; for (j = 0; j < cluster->numareas; j++) { for (cache = aasworld.clusterareacache[i][j]; cache; cache = nextcache) { nextcache = cache->next; AAS_FreeRoutingCache(cache); } //end for aasworld.clusterareacache[i][j] = NULL; } //end for } //end for //free the cluster cache array FreeMemory(aasworld.clusterareacache); aasworld.clusterareacache = NULL; } //end of the function AAS_FreeAllClusterAreaCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitClusterAreaCache(void) { int i, size; char *ptr; // for (size = 0, i = 0; i < aasworld.numclusters; i++) { size += aasworld.clusters[i].numareas; } //end for //two dimensional array with pointers for every cluster to routing cache //for every area in that cluster ptr = (char *) GetClearedMemory( aasworld.numclusters * sizeof(aas_routingcache_t **) + size * sizeof(aas_routingcache_t *)); aasworld.clusterareacache = (aas_routingcache_t ***) ptr; ptr += aasworld.numclusters * sizeof(aas_routingcache_t **); for (i = 0; i < aasworld.numclusters; i++) { aasworld.clusterareacache[i] = (aas_routingcache_t **) ptr; ptr += aasworld.clusters[i].numareas * sizeof(aas_routingcache_t *); } //end for } //end of the function AAS_InitClusterAreaCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeAllPortalCache(void) { int i; aas_routingcache_t *cache, *nextcache; //free all portal cache if existing if (!aasworld.portalcache) return; //free portal caches for (i = 0; i < aasworld.numareas; i++) { for (cache = aasworld.portalcache[i]; cache; cache = nextcache) { nextcache = cache->next; AAS_FreeRoutingCache(cache); } //end for aasworld.portalcache[i] = NULL; } //end for FreeMemory(aasworld.portalcache); aasworld.portalcache = NULL; } //end of the function AAS_FreeAllPortalCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitPortalCache(void) { // aasworld.portalcache = (aas_routingcache_t **) GetClearedMemory( aasworld.numareas * sizeof(aas_routingcache_t *)); } //end of the function AAS_InitPortalCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitRoutingUpdate(void) { int i, maxreachabilityareas; //free routing update fields if already existing if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate); // maxreachabilityareas = 0; for (i = 0; i < aasworld.numclusters; i++) { if (aasworld.clusters[i].numreachabilityareas > maxreachabilityareas) { maxreachabilityareas = aasworld.clusters[i].numreachabilityareas; } //end if } //end for //allocate memory for the routing update fields aasworld.areaupdate = (aas_routingupdate_t *) GetClearedMemory( maxreachabilityareas * sizeof(aas_routingupdate_t)); // if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate); //allocate memory for the portal update fields aasworld.portalupdate = (aas_routingupdate_t *) GetClearedMemory( (aasworld.numportals+1) * sizeof(aas_routingupdate_t)); } //end of the function AAS_InitRoutingUpdate //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CreateAllRoutingCache(void) { int i, j, t; aasworld.initialized = qtrue; botimport.Print(PRT_MESSAGE, "AAS_CreateAllRoutingCache\n"); for (i = 1; i < aasworld.numareas; i++) { if (!AAS_AreaReachability(i)) continue; for (j = 1; j < aasworld.numareas; j++) { if (i == j) continue; if (!AAS_AreaReachability(j)) continue; t = AAS_AreaTravelTimeToGoalArea(i, aasworld.areas[i].center, j, TFL_DEFAULT); //Log_Write("traveltime from %d to %d is %d", i, j, t); } //end for } //end for aasworld.initialized = qfalse; } //end of the function AAS_CreateAllRoutingCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== //the route cache header //this header is followed by numportalcache + numareacache aas_routingcache_t //structures that store routing cache typedef struct routecacheheader_s { int ident; int version; int numareas; int numclusters; int areacrc; int clustercrc; int numportalcache; int numareacache; } routecacheheader_t; #define RCID (('C'<<24)+('R'<<16)+('E'<<8)+'M') #define RCVERSION 2 //void AAS_DecompressVis(byte *in, int numareas, byte *decompressed); //int AAS_CompressVis(byte *vis, int numareas, byte *dest); void AAS_WriteRouteCache(void) { int i, j, numportalcache, numareacache, totalsize; aas_routingcache_t *cache; aas_cluster_t *cluster; fileHandle_t fp; char filename[MAX_QPATH]; routecacheheader_t routecacheheader; numportalcache = 0; for (i = 0; i < aasworld.numareas; i++) { for (cache = aasworld.portalcache[i]; cache; cache = cache->next) { numportalcache++; } //end for } //end for numareacache = 0; for (i = 0; i < aasworld.numclusters; i++) { cluster = &aasworld.clusters[i]; for (j = 0; j < cluster->numareas; j++) { for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) { numareacache++; } //end for } //end for } //end for // open the file for writing Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld.mapname); botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); if (!fp) { AAS_Error("Unable to open file: %s\n", filename); return; } //end if //create the header routecacheheader.ident = RCID; routecacheheader.version = RCVERSION; routecacheheader.numareas = aasworld.numareas; routecacheheader.numclusters = aasworld.numclusters; routecacheheader.areacrc = CRC_ProcessString( (unsigned char *)aasworld.areas, sizeof(aas_area_t) * aasworld.numareas ); routecacheheader.clustercrc = CRC_ProcessString( (unsigned char *)aasworld.clusters, sizeof(aas_cluster_t) * aasworld.numclusters ); routecacheheader.numportalcache = numportalcache; routecacheheader.numareacache = numareacache; //write the header botimport.FS_Write(&routecacheheader, sizeof(routecacheheader_t), fp); // totalsize = 0; //write all the cache for (i = 0; i < aasworld.numareas; i++) { for (cache = aasworld.portalcache[i]; cache; cache = cache->next) { botimport.FS_Write(cache, cache->size, fp); totalsize += cache->size; } //end for } //end for for (i = 0; i < aasworld.numclusters; i++) { cluster = &aasworld.clusters[i]; for (j = 0; j < cluster->numareas; j++) { for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) { botimport.FS_Write(cache, cache->size, fp); totalsize += cache->size; } //end for } //end for } //end for // write the visareas /* for (i = 0; i < aasworld.numareas; i++) { if (!aasworld.areavisibility[i]) { size = 0; botimport.FS_Write(&size, sizeof(int), fp); continue; } AAS_DecompressVis( aasworld.areavisibility[i], aasworld.numareas, aasworld.decompressedvis ); size = AAS_CompressVis( aasworld.decompressedvis, aasworld.numareas, aasworld.decompressedvis ); botimport.FS_Write(&size, sizeof(int), fp); botimport.FS_Write(aasworld.decompressedvis, size, fp); } */ // botimport.FS_FCloseFile(fp); botimport.Print(PRT_MESSAGE, "\nroute cache written to %s\n", filename); botimport.Print(PRT_MESSAGE, "written %d bytes of routing cache\n", totalsize); } //end of the function AAS_WriteRouteCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_routingcache_t *AAS_ReadCache(fileHandle_t fp) { int size; aas_routingcache_t *cache; botimport.FS_Read(&size, sizeof(size), fp); cache = (aas_routingcache_t *) GetMemory(size); cache->size = size; botimport.FS_Read((unsigned char *)cache + sizeof(size), size - sizeof(size), fp); cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) - sizeof(unsigned short) + (size - sizeof(aas_routingcache_t) + sizeof(unsigned short)) / 3 * 2; return cache; } //end of the function AAS_ReadCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_ReadRouteCache(void) { int i, clusterareanum;//, size; fileHandle_t fp; char filename[MAX_QPATH]; routecacheheader_t routecacheheader; aas_routingcache_t *cache; Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld.mapname); botimport.FS_FOpenFile( filename, &fp, FS_READ ); if (!fp) { return qfalse; } //end if botimport.FS_Read(&routecacheheader, sizeof(routecacheheader_t), fp ); if (routecacheheader.ident != RCID) { AAS_Error("%s is not a route cache dump\n"); return qfalse; } //end if if (routecacheheader.version != RCVERSION) { AAS_Error("route cache dump has wrong version %d, should be %d", routecacheheader.version, RCVERSION); return qfalse; } //end if if (routecacheheader.numareas != aasworld.numareas) { //AAS_Error("route cache dump has wrong number of areas\n"); return qfalse; } //end if if (routecacheheader.numclusters != aasworld.numclusters) { //AAS_Error("route cache dump has wrong number of clusters\n"); return qfalse; } //end if if (routecacheheader.areacrc != CRC_ProcessString( (unsigned char *)aasworld.areas, sizeof(aas_area_t) * aasworld.numareas )) { //AAS_Error("route cache dump area CRC incorrect\n"); return qfalse; } //end if if (routecacheheader.clustercrc != CRC_ProcessString( (unsigned char *)aasworld.clusters, sizeof(aas_cluster_t) * aasworld.numclusters )) { //AAS_Error("route cache dump cluster CRC incorrect\n"); return qfalse; } //end if //read all the portal cache for (i = 0; i < routecacheheader.numportalcache; i++) { cache = AAS_ReadCache(fp); cache->next = aasworld.portalcache[cache->areanum]; cache->prev = NULL; if (aasworld.portalcache[cache->areanum]) aasworld.portalcache[cache->areanum]->prev = cache; aasworld.portalcache[cache->areanum] = cache; } //end for //read all the cluster area cache for (i = 0; i < routecacheheader.numareacache; i++) { cache = AAS_ReadCache(fp); clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum); cache->next = aasworld.clusterareacache[cache->cluster][clusterareanum]; cache->prev = NULL; if (aasworld.clusterareacache[cache->cluster][clusterareanum]) aasworld.clusterareacache[cache->cluster][clusterareanum]->prev = cache; aasworld.clusterareacache[cache->cluster][clusterareanum] = cache; } //end for // read the visareas /* aasworld.areavisibility = (byte **) GetClearedMemory(aasworld.numareas * sizeof(byte *)); aasworld.decompressedvis = (byte *) GetClearedMemory(aasworld.numareas * sizeof(byte)); for (i = 0; i < aasworld.numareas; i++) { botimport.FS_Read(&size, sizeof(size), fp ); if (size) { aasworld.areavisibility[i] = (byte *) GetMemory(size); botimport.FS_Read(aasworld.areavisibility[i], size, fp ); } } */ // botimport.FS_FCloseFile(fp); return qtrue; } //end of the function AAS_ReadRouteCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #define MAX_REACHABILITYPASSAREAS 32 void AAS_InitReachabilityAreas(void) { int i, j, numareas, areas[MAX_REACHABILITYPASSAREAS]; int numreachareas; aas_reachability_t *reach; vec3_t start, end; if (aasworld.reachabilityareas) FreeMemory(aasworld.reachabilityareas); if (aasworld.reachabilityareaindex) FreeMemory(aasworld.reachabilityareaindex); aasworld.reachabilityareas = (aas_reachabilityareas_t *) GetClearedMemory(aasworld.reachabilitysize * sizeof(aas_reachabilityareas_t)); aasworld.reachabilityareaindex = (int *) GetClearedMemory(aasworld.reachabilitysize * MAX_REACHABILITYPASSAREAS * sizeof(int)); numreachareas = 0; for (i = 0; i < aasworld.reachabilitysize; i++) { reach = &aasworld.reachability[i]; numareas = 0; switch(reach->traveltype & TRAVELTYPE_MASK) { //trace areas from start to end case TRAVEL_BARRIERJUMP: case TRAVEL_WATERJUMP: VectorCopy(reach->start, end); end[2] = reach->end[2]; numareas = AAS_TraceAreas(reach->start, end, areas, NULL, MAX_REACHABILITYPASSAREAS); break; case TRAVEL_WALKOFFLEDGE: VectorCopy(reach->end, start); start[2] = reach->start[2]; numareas = AAS_TraceAreas(start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS); break; case TRAVEL_GRAPPLEHOOK: numareas = AAS_TraceAreas(reach->start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS); break; //trace arch case TRAVEL_JUMP: break; case TRAVEL_ROCKETJUMP: break; case TRAVEL_BFGJUMP: break; case TRAVEL_JUMPPAD: break; //trace from reach->start to entity center, along entity movement //and from entity center to reach->end case TRAVEL_ELEVATOR: break; case TRAVEL_FUNCBOB: break; //no areas in between case TRAVEL_WALK: break; case TRAVEL_CROUCH: break; case TRAVEL_LADDER: break; case TRAVEL_SWIM: break; case TRAVEL_TELEPORT: break; default: break; } //end switch aasworld.reachabilityareas[i].firstarea = numreachareas; aasworld.reachabilityareas[i].numareas = numareas; for (j = 0; j < numareas; j++) { aasworld.reachabilityareaindex[numreachareas++] = areas[j]; } //end for } //end for } //end of the function AAS_InitReachabilityAreas //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitRouting(void) { AAS_InitTravelFlagFromType(); // AAS_InitAreaContentsTravelFlags(); //initialize the routing update fields AAS_InitRoutingUpdate(); //create reversed reachability links used by the routing update algorithm AAS_CreateReversedReachability(); //initialize the cluster cache AAS_InitClusterAreaCache(); //initialize portal cache AAS_InitPortalCache(); //initialize the area travel times AAS_CalculateAreaTravelTimes(); //calculate the maximum travel times through portals AAS_InitPortalMaxTravelTimes(); //get the areas reachabilities go through AAS_InitReachabilityAreas(); // #ifdef ROUTING_DEBUG numareacacheupdates = 0; numportalcacheupdates = 0; #endif //ROUTING_DEBUG // routingcachesize = 0; max_routingcachesize = 1024 * (int) LibVarValue("max_routingcache", "4096"); // read any routing cache if available AAS_ReadRouteCache(); } //end of the function AAS_InitRouting //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeRoutingCaches(void) { // free all the existing cluster area cache AAS_FreeAllClusterAreaCache(); // free all the existing portal cache AAS_FreeAllPortalCache(); // free cached travel times within areas if (aasworld.areatraveltimes) FreeMemory(aasworld.areatraveltimes); aasworld.areatraveltimes = NULL; // free cached maximum travel time through cluster portals if (aasworld.portalmaxtraveltimes) FreeMemory(aasworld.portalmaxtraveltimes); aasworld.portalmaxtraveltimes = NULL; // free reversed reachability links if (aasworld.reversedreachability) FreeMemory(aasworld.reversedreachability); aasworld.reversedreachability = NULL; // free routing algorithm memory if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate); aasworld.areaupdate = NULL; if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate); aasworld.portalupdate = NULL; // free lists with areas the reachabilities go through if (aasworld.reachabilityareas) FreeMemory(aasworld.reachabilityareas); aasworld.reachabilityareas = NULL; // free the reachability area index if (aasworld.reachabilityareaindex) FreeMemory(aasworld.reachabilityareaindex); aasworld.reachabilityareaindex = NULL; // free area contents travel flags look up table if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags); aasworld.areacontentstravelflags = NULL; } //end of the function AAS_FreeRoutingCaches //=========================================================================== // update the given routing cache // // Parameter: areacache : routing cache to update // Returns: - // Changes Globals: - //=========================================================================== void AAS_UpdateAreaRoutingCache(aas_routingcache_t *areacache) { int i, nextareanum, cluster, badtravelflags, clusterareanum, linknum; int numreachabilityareas; unsigned short int t, startareatraveltimes[128]; //NOTE: not more than 128 reachabilities per area allowed aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; aas_reachability_t *reach; aas_reversedreachability_t *revreach; aas_reversedlink_t *revlink; #ifdef ROUTING_DEBUG numareacacheupdates++; #endif //ROUTING_DEBUG //number of reachability areas within this cluster numreachabilityareas = aasworld.clusters[areacache->cluster].numreachabilityareas; // aasworld.frameroutingupdates++; //clear the routing update fields // Com_Memset(aasworld.areaupdate, 0, aasworld.numareas * sizeof(aas_routingupdate_t)); // badtravelflags = ~areacache->travelflags; // clusterareanum = AAS_ClusterAreaNum(areacache->cluster, areacache->areanum); if (clusterareanum >= numreachabilityareas) return; // Com_Memset(startareatraveltimes, 0, sizeof(startareatraveltimes)); // curupdate = &aasworld.areaupdate[clusterareanum]; curupdate->areanum = areacache->areanum; //VectorCopy(areacache->origin, curupdate->start); curupdate->areatraveltimes = startareatraveltimes; curupdate->tmptraveltime = areacache->starttraveltime; // areacache->traveltimes[clusterareanum] = areacache->starttraveltime; //put the area to start with in the current read list curupdate->next = NULL; curupdate->prev = NULL; updateliststart = curupdate; updatelistend = curupdate; //while there are updates in the current list while (updateliststart) { curupdate = updateliststart; // if (curupdate->next) curupdate->next->prev = NULL; else updatelistend = NULL; updateliststart = curupdate->next; // curupdate->inlist = qfalse; //check all reversed reachability links revreach = &aasworld.reversedreachability[curupdate->areanum]; // for (i = 0, revlink = revreach->first; revlink; revlink = revlink->next, i++) { linknum = revlink->linknum; reach = &aasworld.reachability[linknum]; //if there is used an undesired travel type if (AAS_TravelFlagForType_inline(reach->traveltype) & badtravelflags) continue; //if not allowed to enter the next area if (aasworld.areasettings[reach->areanum].areaflags & AREA_DISABLED) continue; //if the next area has a not allowed travel flag if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & badtravelflags) continue; //number of the area the reversed reachability leads to nextareanum = revlink->areanum; //get the cluster number of the area cluster = aasworld.areasettings[nextareanum].cluster; //don't leave the cluster if (cluster > 0 && cluster != areacache->cluster) continue; //get the number of the area in the cluster clusterareanum = AAS_ClusterAreaNum(areacache->cluster, nextareanum); if (clusterareanum >= numreachabilityareas) continue; //time already travelled plus the traveltime through //the current area plus the travel time from the reachability t = curupdate->tmptraveltime + //AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->end) + curupdate->areatraveltimes[i] + reach->traveltime; // if (!areacache->traveltimes[clusterareanum] || areacache->traveltimes[clusterareanum] > t) { areacache->traveltimes[clusterareanum] = t; areacache->reachabilities[clusterareanum] = linknum - aasworld.areasettings[nextareanum].firstreachablearea; nextupdate = &aasworld.areaupdate[clusterareanum]; nextupdate->areanum = nextareanum; nextupdate->tmptraveltime = t; //VectorCopy(reach->start, nextupdate->start); nextupdate->areatraveltimes = aasworld.areatraveltimes[nextareanum][linknum - aasworld.areasettings[nextareanum].firstreachablearea]; if (!nextupdate->inlist) { // we add the update to the end of the list // we could also use a B+ tree to have a real sorted list // on travel time which makes for faster routing updates nextupdate->next = NULL; nextupdate->prev = updatelistend; if (updatelistend) updatelistend->next = nextupdate; else updateliststart = nextupdate; updatelistend = nextupdate; nextupdate->inlist = qtrue; } //end if } //end if } //end for } //end while } //end of the function AAS_UpdateAreaRoutingCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_routingcache_t *AAS_GetAreaRoutingCache(int clusternum, int areanum, int travelflags) { int clusterareanum; aas_routingcache_t *cache, *clustercache; //number of the area in the cluster clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); //pointer to the cache for the area in the cluster clustercache = aasworld.clusterareacache[clusternum][clusterareanum]; //find the cache without undesired travel flags for (cache = clustercache; cache; cache = cache->next) { //if there aren't used any undesired travel types for the cache if (cache->travelflags == travelflags) break; } //end for //if there was no cache if (!cache) { cache = AAS_AllocRoutingCache(aasworld.clusters[clusternum].numreachabilityareas); cache->cluster = clusternum; cache->areanum = areanum; VectorCopy(aasworld.areas[areanum].center, cache->origin); cache->starttraveltime = 1; cache->travelflags = travelflags; cache->prev = NULL; cache->next = clustercache; if (clustercache) clustercache->prev = cache; aasworld.clusterareacache[clusternum][clusterareanum] = cache; AAS_UpdateAreaRoutingCache(cache); } //end if else { AAS_UnlinkCache(cache); } //end else //the cache has been accessed cache->time = AAS_RoutingTime(); cache->type = CACHETYPE_AREA; AAS_LinkCache(cache); return cache; } //end of the function AAS_GetAreaRoutingCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_UpdatePortalRoutingCache(aas_routingcache_t *portalcache) { int i, portalnum, clusterareanum, clusternum; unsigned short int t; aas_portal_t *portal; aas_cluster_t *cluster; aas_routingcache_t *cache; aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; #ifdef ROUTING_DEBUG numportalcacheupdates++; #endif //ROUTING_DEBUG //clear the routing update fields // Com_Memset(aasworld.portalupdate, 0, (aasworld.numportals+1) * sizeof(aas_routingupdate_t)); // curupdate = &aasworld.portalupdate[aasworld.numportals]; curupdate->cluster = portalcache->cluster; curupdate->areanum = portalcache->areanum; curupdate->tmptraveltime = portalcache->starttraveltime; //if the start area is a cluster portal, store the travel time for that portal clusternum = aasworld.areasettings[portalcache->areanum].cluster; if (clusternum < 0) { portalcache->traveltimes[-clusternum] = portalcache->starttraveltime; } //end if //put the area to start with in the current read list curupdate->next = NULL; curupdate->prev = NULL; updateliststart = curupdate; updatelistend = curupdate; //while there are updates in the current list while (updateliststart) { curupdate = updateliststart; //remove the current update from the list if (curupdate->next) curupdate->next->prev = NULL; else updatelistend = NULL; updateliststart = curupdate->next; //current update is removed from the list curupdate->inlist = qfalse; // cluster = &aasworld.clusters[curupdate->cluster]; // cache = AAS_GetAreaRoutingCache(curupdate->cluster, curupdate->areanum, portalcache->travelflags); //take all portals of the cluster for (i = 0; i < cluster->numportals; i++) { portalnum = aasworld.portalindex[cluster->firstportal + i]; portal = &aasworld.portals[portalnum]; //if this is the portal of the current update continue if (portal->areanum == curupdate->areanum) continue; // clusterareanum = AAS_ClusterAreaNum(curupdate->cluster, portal->areanum); if (clusterareanum >= cluster->numreachabilityareas) continue; // t = cache->traveltimes[clusterareanum]; if (!t) continue; t += curupdate->tmptraveltime; // if (!portalcache->traveltimes[portalnum] || portalcache->traveltimes[portalnum] > t) { portalcache->traveltimes[portalnum] = t; nextupdate = &aasworld.portalupdate[portalnum]; if (portal->frontcluster == curupdate->cluster) { nextupdate->cluster = portal->backcluster; } //end if else { nextupdate->cluster = portal->frontcluster; } //end else nextupdate->areanum = portal->areanum; //add travel time through the actual portal area for the next update nextupdate->tmptraveltime = t + aasworld.portalmaxtraveltimes[portalnum]; if (!nextupdate->inlist) { // we add the update to the end of the list // we could also use a B+ tree to have a real sorted list // on travel time which makes for faster routing updates nextupdate->next = NULL; nextupdate->prev = updatelistend; if (updatelistend) updatelistend->next = nextupdate; else updateliststart = nextupdate; updatelistend = nextupdate; nextupdate->inlist = qtrue; } //end if } //end if } //end for } //end while } //end of the function AAS_UpdatePortalRoutingCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_routingcache_t *AAS_GetPortalRoutingCache(int clusternum, int areanum, int travelflags) { aas_routingcache_t *cache; //find the cached portal routing if existing for (cache = aasworld.portalcache[areanum]; cache; cache = cache->next) { if (cache->travelflags == travelflags) break; } //end for //if the portal routing isn't cached if (!cache) { cache = AAS_AllocRoutingCache(aasworld.numportals); cache->cluster = clusternum; cache->areanum = areanum; VectorCopy(aasworld.areas[areanum].center, cache->origin); cache->starttraveltime = 1; cache->travelflags = travelflags; //add the cache to the cache list cache->prev = NULL; cache->next = aasworld.portalcache[areanum]; if (aasworld.portalcache[areanum]) aasworld.portalcache[areanum]->prev = cache; aasworld.portalcache[areanum] = cache; //update the cache AAS_UpdatePortalRoutingCache(cache); } //end if else { AAS_UnlinkCache(cache); } //end else //the cache has been accessed cache->time = AAS_RoutingTime(); cache->type = CACHETYPE_PORTAL; AAS_LinkCache(cache); return cache; } //end of the function AAS_GetPortalRoutingCache //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaRouteToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum) { int clusternum, goalclusternum, portalnum, i, clusterareanum, bestreachnum; unsigned short int t, besttime; aas_portal_t *portal; aas_cluster_t *cluster; aas_routingcache_t *areacache, *portalcache; aas_reachability_t *reach; if (!aasworld.initialized) return qfalse; if (areanum == goalareanum) { *traveltime = 1; *reachnum = 0; return qtrue; } // if (areanum <= 0 || areanum >= aasworld.numareas) { if (bot_developer) { botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: areanum %d out of range\n", areanum); } //end if return qfalse; } //end if if (goalareanum <= 0 || goalareanum >= aasworld.numareas) { if (bot_developer) { botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: goalareanum %d out of range\n", goalareanum); } //end if return qfalse; } //end if // make sure the routing cache doesn't grow to large while(AvailableMemory() < 1 * 1024 * 1024) { if (!AAS_FreeOldestCache()) break; } // if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goalareanum)) { travelflags |= TFL_DONOTENTER; } //end if //NOTE: the number of routing updates is limited per frame /* if (aasworld.frameroutingupdates > MAX_FRAMEROUTINGUPDATES) { #ifdef DEBUG //Log_Write("WARNING: AAS_AreaTravelTimeToGoalArea: frame routing updates overflowed"); #endif return 0; } //end if */ // clusternum = aasworld.areasettings[areanum].cluster; goalclusternum = aasworld.areasettings[goalareanum].cluster; //check if the area is a portal of the goal area cluster if (clusternum < 0 && goalclusternum > 0) { portal = &aasworld.portals[-clusternum]; if (portal->frontcluster == goalclusternum || portal->backcluster == goalclusternum) { clusternum = goalclusternum; } //end if } //end if //check if the goalarea is a portal of the area cluster else if (clusternum > 0 && goalclusternum < 0) { portal = &aasworld.portals[-goalclusternum]; if (portal->frontcluster == clusternum || portal->backcluster == clusternum) { goalclusternum = clusternum; } //end if } //end if //if both areas are in the same cluster //NOTE: there might be a shorter route via another cluster!!! but we don't care if (clusternum > 0 && goalclusternum > 0 && clusternum == goalclusternum) { // areacache = AAS_GetAreaRoutingCache(clusternum, goalareanum, travelflags); //the number of the area in the cluster clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); //the cluster the area is in cluster = &aasworld.clusters[clusternum]; //if the area is NOT a reachability area if (clusterareanum >= cluster->numreachabilityareas) return 0; //if it is possible to travel to the goal area through this cluster if (areacache->traveltimes[clusterareanum] != 0) { *reachnum = aasworld.areasettings[areanum].firstreachablearea + areacache->reachabilities[clusterareanum]; if (!origin) { *traveltime = areacache->traveltimes[clusterareanum]; return qtrue; } reach = &aasworld.reachability[*reachnum]; *traveltime = areacache->traveltimes[clusterareanum] + AAS_AreaTravelTime(areanum, origin, reach->start); // return qtrue; } //end if } //end if // clusternum = aasworld.areasettings[areanum].cluster; goalclusternum = aasworld.areasettings[goalareanum].cluster; //if the goal area is a portal if (goalclusternum < 0) { //just assume the goal area is part of the front cluster portal = &aasworld.portals[-goalclusternum]; goalclusternum = portal->frontcluster; } //end if //get the portal routing cache portalcache = AAS_GetPortalRoutingCache(goalclusternum, goalareanum, travelflags); //if the area is a cluster portal, read directly from the portal cache if (clusternum < 0) { *traveltime = portalcache->traveltimes[-clusternum]; *reachnum = aasworld.areasettings[areanum].firstreachablearea + portalcache->reachabilities[-clusternum]; return qtrue; } //end if // besttime = 0; bestreachnum = -1; //the cluster the area is in cluster = &aasworld.clusters[clusternum]; //find the portal of the area cluster leading towards the goal area for (i = 0; i < cluster->numportals; i++) { portalnum = aasworld.portalindex[cluster->firstportal + i]; //if the goal area isn't reachable from the portal if (!portalcache->traveltimes[portalnum]) continue; // portal = &aasworld.portals[portalnum]; //get the cache of the portal area areacache = AAS_GetAreaRoutingCache(clusternum, portal->areanum, travelflags); //current area inside the current cluster clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); //if the area is NOT a reachability area if (clusterareanum >= cluster->numreachabilityareas) continue; //if the portal is NOT reachable from this area if (!areacache->traveltimes[clusterareanum]) continue; //total travel time is the travel time the portal area is from //the goal area plus the travel time towards the portal area t = portalcache->traveltimes[portalnum] + areacache->traveltimes[clusterareanum]; //FIXME: add the exact travel time through the actual portal area //NOTE: for now we just add the largest travel time through the portal area // because we can't directly calculate the exact travel time // to be more specific we don't know which reachability was used to travel // into the portal area t += aasworld.portalmaxtraveltimes[portalnum]; // if (origin) { *reachnum = aasworld.areasettings[areanum].firstreachablearea + areacache->reachabilities[clusterareanum]; reach = aasworld.reachability + *reachnum; t += AAS_AreaTravelTime(areanum, origin, reach->start); } //end if //if the time is better than the one already found if (!besttime || t < besttime) { bestreachnum = *reachnum; besttime = t; } //end if } //end for if (bestreachnum < 0) { return qfalse; } *reachnum = bestreachnum; *traveltime = besttime; return qtrue; } //end of the function AAS_AreaRouteToGoalArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) { int traveltime, reachnum; if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) { return traveltime; } return 0; } //end of the function AAS_AreaTravelTimeToGoalArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaReachabilityToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) { int traveltime, reachnum; if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) { return reachnum; } return 0; } //end of the function AAS_AreaReachabilityToGoalArea //=========================================================================== // predict the route and stop on one of the stop events // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_PredictRoute(struct aas_predictroute_s *route, int areanum, vec3_t origin, int goalareanum, int travelflags, int maxareas, int maxtime, int stopevent, int stopcontents, int stoptfl, int stopareanum) { int curareanum, reachnum, i, j, testareanum; vec3_t curorigin; aas_reachability_t *reach; aas_reachabilityareas_t *reachareas; //init output route->stopevent = RSE_NONE; route->endarea = goalareanum; route->endcontents = 0; route->endtravelflags = 0; VectorCopy(origin, route->endpos); route->time = 0; curareanum = areanum; VectorCopy(origin, curorigin); for (i = 0; curareanum != goalareanum && (!maxareas || i < maxareas) && i < aasworld.numareas; i++) { reachnum = AAS_AreaReachabilityToGoalArea(curareanum, curorigin, goalareanum, travelflags); if (!reachnum) { route->stopevent = RSE_NOROUTE; return qfalse; } //end if reach = &aasworld.reachability[reachnum]; // if (stopevent & RSE_USETRAVELTYPE) { if (AAS_TravelFlagForType_inline(reach->traveltype) & stoptfl) { route->stopevent = RSE_USETRAVELTYPE; route->endarea = curareanum; route->endcontents = aasworld.areasettings[curareanum].contents; route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype); VectorCopy(reach->start, route->endpos); return qtrue; } //end if if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & stoptfl) { route->stopevent = RSE_USETRAVELTYPE; route->endarea = reach->areanum; route->endcontents = aasworld.areasettings[reach->areanum].contents; route->endtravelflags = AAS_AreaContentsTravelFlags_inline(reach->areanum); VectorCopy(reach->end, route->endpos); route->time += AAS_AreaTravelTime(areanum, origin, reach->start); route->time += reach->traveltime; return qtrue; } //end if } //end if reachareas = &aasworld.reachabilityareas[reachnum]; for (j = 0; j < reachareas->numareas + 1; j++) { if (j >= reachareas->numareas) testareanum = reach->areanum; else testareanum = aasworld.reachabilityareaindex[reachareas->firstarea + j]; if (stopevent & RSE_ENTERCONTENTS) { if (aasworld.areasettings[testareanum].contents & stopcontents) { route->stopevent = RSE_ENTERCONTENTS; route->endarea = testareanum; route->endcontents = aasworld.areasettings[testareanum].contents; VectorCopy(reach->end, route->endpos); route->time += AAS_AreaTravelTime(areanum, origin, reach->start); route->time += reach->traveltime; return qtrue; } //end if } //end if if (stopevent & RSE_ENTERAREA) { if (testareanum == stopareanum) { route->stopevent = RSE_ENTERAREA; route->endarea = testareanum; route->endcontents = aasworld.areasettings[testareanum].contents; VectorCopy(reach->start, route->endpos); return qtrue; } //end if } //end if } //end for route->time += AAS_AreaTravelTime(areanum, origin, reach->start); route->time += reach->traveltime; route->endarea = reach->areanum; route->endcontents = aasworld.areasettings[reach->areanum].contents; route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype); VectorCopy(reach->end, route->endpos); // curareanum = reach->areanum; VectorCopy(reach->end, curorigin); // if (maxtime && route->time > maxtime) break; } //end while if (curareanum != goalareanum) return qfalse; return qtrue; } //end of the function AAS_PredictRoute //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_BridgeWalkable(int areanum) { return qfalse; } //end of the function AAS_BridgeWalkable //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach) { if (!aasworld.initialized) { Com_Memset(reach, 0, sizeof(aas_reachability_t)); return; } //end if if (num < 0 || num >= aasworld.reachabilitysize) { Com_Memset(reach, 0, sizeof(aas_reachability_t)); return; } //end if Com_Memcpy(reach, &aasworld.reachability[num], sizeof(aas_reachability_t));; } //end of the function AAS_ReachabilityFromNum //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_NextAreaReachability(int areanum, int reachnum) { aas_areasettings_t *settings; if (!aasworld.initialized) return 0; if (areanum <= 0 || areanum >= aasworld.numareas) { botimport.Print(PRT_ERROR, "AAS_NextAreaReachability: areanum %d out of range\n", areanum); return 0; } //end if settings = &aasworld.areasettings[areanum]; if (!reachnum) { return settings->firstreachablearea; } //end if if (reachnum < settings->firstreachablearea) { botimport.Print(PRT_FATAL, "AAS_NextAreaReachability: reachnum < settings->firstreachableara"); return 0; } //end if reachnum++; if (reachnum >= settings->firstreachablearea + settings->numreachableareas) { return 0; } //end if return reachnum; } //end of the function AAS_NextAreaReachability //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_NextModelReachability(int num, int modelnum) { int i; if (num <= 0) num = 1; else if (num >= aasworld.reachabilitysize) return 0; else num++; // for (i = num; i < aasworld.reachabilitysize; i++) { if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) { if (aasworld.reachability[i].facenum == modelnum) return i; } //end if else if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) { if ((aasworld.reachability[i].facenum & 0x0000FFFF) == modelnum) return i; } //end if } //end for return 0; } //end of the function AAS_NextModelReachability //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin) { int i, n, t; vec3_t start, end; aas_trace_t trace; //if the area has no reachabilities if (!AAS_AreaReachability(areanum)) return qfalse; // n = aasworld.numareas * random(); for (i = 0; i < aasworld.numareas; i++) { if (n <= 0) n = 1; if (n >= aasworld.numareas) n = 1; if (AAS_AreaReachability(n)) { t = AAS_AreaTravelTimeToGoalArea(areanum, aasworld.areas[areanum].center, n, travelflags); //if the goal is reachable if (t > 0) { if (AAS_AreaSwim(n)) { *goalareanum = n; VectorCopy(aasworld.areas[n].center, goalorigin); //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); return qtrue; } //end if VectorCopy(aasworld.areas[n].center, start); if (!AAS_PointAreaNum(start)) Log_Write("area %d center %f %f %f in solid?", n, start[0], start[1], start[2]); VectorCopy(start, end); end[2] -= 300; trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); if (!trace.startsolid && trace.fraction < 1 && AAS_PointAreaNum(trace.endpos) == n) { if (AAS_AreaGroundFaceArea(n) > 300) { *goalareanum = n; VectorCopy(trace.endpos, goalorigin); //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); return qtrue; } //end if } //end if } //end if } //end if n++; } //end for return qfalse; } //end of the function AAS_RandomGoalArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaVisible(int srcarea, int destarea) { return qfalse; } //end of the function AAS_AreaVisible //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float DistancePointToLine(vec3_t v1, vec3_t v2, vec3_t point) { vec3_t vec, p2; AAS_ProjectPointOntoVector(point, v1, v2, p2); VectorSubtract(point, p2, vec); return VectorLength(vec); } //end of the function DistancePointToLine //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_NearestHideArea(int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags) { int i, j, nextareanum, badtravelflags, numreach, bestarea; unsigned short int t, besttraveltime; static unsigned short int *hidetraveltimes; aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; aas_reachability_t *reach; float dist1, dist2; vec3_t v1, v2, p; qboolean startVisible; // if (!hidetraveltimes) { hidetraveltimes = (unsigned short int *) GetClearedMemory(aasworld.numareas * sizeof(unsigned short int)); } //end if else { Com_Memset(hidetraveltimes, 0, aasworld.numareas * sizeof(unsigned short int)); } //end else besttraveltime = 0; bestarea = 0; //assume visible startVisible = qtrue; // badtravelflags = ~travelflags; // curupdate = &aasworld.areaupdate[areanum]; curupdate->areanum = areanum; VectorCopy(origin, curupdate->start); curupdate->areatraveltimes = aasworld.areatraveltimes[areanum][0]; curupdate->tmptraveltime = 0; //put the area to start with in the current read list curupdate->next = NULL; curupdate->prev = NULL; updateliststart = curupdate; updatelistend = curupdate; //while there are updates in the list while (updateliststart) { curupdate = updateliststart; // if (curupdate->next) curupdate->next->prev = NULL; else updatelistend = NULL; updateliststart = curupdate->next; // curupdate->inlist = qfalse; //check all reversed reachability links numreach = aasworld.areasettings[curupdate->areanum].numreachableareas; reach = &aasworld.reachability[aasworld.areasettings[curupdate->areanum].firstreachablearea]; // for (i = 0; i < numreach; i++, reach++) { //if an undesired travel type is used if (AAS_TravelFlagForType_inline(reach->traveltype) & badtravelflags) continue; // if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & badtravelflags) continue; //number of the area the reachability leads to nextareanum = reach->areanum; // if this moves us into the enemies area, skip it if (nextareanum == enemyareanum) continue; //time already travelled plus the traveltime through //the current area plus the travel time from the reachability t = curupdate->tmptraveltime + AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->start) + reach->traveltime; //avoid going near the enemy AAS_ProjectPointOntoVector(enemyorigin, curupdate->start, reach->end, p); for (j = 0; j < 3; j++) if ((p[j] > curupdate->start[j] && p[j] > reach->end[j]) || (p[j] < curupdate->start[j] && p[j] < reach->end[j])) break; if (j < 3) { VectorSubtract(enemyorigin, reach->end, v2); } //end if else { VectorSubtract(enemyorigin, p, v2); } //end else dist2 = VectorLength(v2); //never go through the enemy if (dist2 < 40) continue; // VectorSubtract(enemyorigin, curupdate->start, v1); dist1 = VectorLength(v1); // if (dist2 < dist1) { t += (dist1 - dist2) * 10; } // if we weren't visible when starting, make sure we don't move into their view if (!startVisible && AAS_AreaVisible(enemyareanum, nextareanum)) { continue; } // if (besttraveltime && t >= besttraveltime) continue; // if (!hidetraveltimes[nextareanum] || hidetraveltimes[nextareanum] > t) { //if the nextarea is not visible from the enemy area if (!AAS_AreaVisible(enemyareanum, nextareanum)) { besttraveltime = t; bestarea = nextareanum; } //end if hidetraveltimes[nextareanum] = t; nextupdate = &aasworld.areaupdate[nextareanum]; nextupdate->areanum = nextareanum; nextupdate->tmptraveltime = t; //remember where we entered this area VectorCopy(reach->end, nextupdate->start); //if this update is not in the list yet if (!nextupdate->inlist) { //add the new update to the end of the list nextupdate->next = NULL; nextupdate->prev = updatelistend; if (updatelistend) updatelistend->next = nextupdate; else updateliststart = nextupdate; updatelistend = nextupdate; nextupdate->inlist = qtrue; } //end if } //end if } //end for } //end while return bestarea; } //end of the function AAS_NearestHideArea ================================================ FILE: code/botlib/be_aas_route.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_route.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_route.h $ * *****************************************************************************/ #ifdef AASINTERN //initialize the AAS routing void AAS_InitRouting(void); //free the AAS routing caches void AAS_FreeRoutingCaches(void); //returns the travel time from start to end in the given area unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); // void AAS_CreateAllRoutingCache(void); void AAS_WriteRouteCache(void); // void AAS_RoutingInfo(void); #endif //AASINTERN //returns the travel flag for the given travel type int AAS_TravelFlagForType(int traveltype); //return the travel flag(s) for traveling through this area int AAS_AreaContentsTravelFlags(int areanum); //returns the index of the next reachability for the given area int AAS_NextAreaReachability(int areanum, int reachnum); //returns the reachability with the given index void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach); //returns a random goal area and goal origin int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin); //enable or disable an area for routing int AAS_EnableRoutingArea(int areanum, int enable); //returns the travel time within the given area from start to end unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); //returns the travel time from the area to the goal area using the given travel flags int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags); //predict a route up to a stop event int AAS_PredictRoute(struct aas_predictroute_s *route, int areanum, vec3_t origin, int goalareanum, int travelflags, int maxareas, int maxtime, int stopevent, int stopcontents, int stoptfl, int stopareanum); ================================================ FILE: code/botlib/be_aas_routealt.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_routealt.c * * desc: AAS * * $Archive: /MissionPack/code/botlib/be_aas_routealt.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_utils.h" #include "l_memory.h" #include "l_log.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "be_aas_def.h" #define ENABLE_ALTROUTING //#define ALTROUTE_DEBUG typedef struct midrangearea_s { int valid; unsigned short starttime; unsigned short goaltime; } midrangearea_t; midrangearea_t *midrangeareas; int *clusterareas; int numclusterareas; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_AltRoutingFloodCluster_r(int areanum) { int i, otherareanum; aas_area_t *area; aas_face_t *face; //add the current area to the areas of the current cluster clusterareas[numclusterareas] = areanum; numclusterareas++; //remove the area from the mid range areas midrangeareas[areanum].valid = qfalse; //flood to other areas through the faces of this area area = &aasworld.areas[areanum]; for (i = 0; i < area->numfaces; i++) { face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; //get the area at the other side of the face if (face->frontarea == areanum) otherareanum = face->backarea; else otherareanum = face->frontarea; //if there is an area at the other side of this face if (!otherareanum) continue; //if the other area is not a midrange area if (!midrangeareas[otherareanum].valid) continue; // AAS_AltRoutingFloodCluster_r(otherareanum); } //end for } //end of the function AAS_AltRoutingFloodCluster_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, int type) { #ifndef ENABLE_ALTROUTING return 0; #else int i, j, bestareanum; int numaltroutegoals, nummidrangeareas; int starttime, goaltime, goaltraveltime; float dist, bestdist; vec3_t mid, dir; #ifdef ALTROUTE_DEBUG int startmillisecs; startmillisecs = Sys_MilliSeconds(); #endif if (!startareanum || !goalareanum) return 0; //travel time towards the goal area goaltraveltime = AAS_AreaTravelTimeToGoalArea(startareanum, start, goalareanum, travelflags); //clear the midrange areas Com_Memset(midrangeareas, 0, aasworld.numareas * sizeof(midrangearea_t)); numaltroutegoals = 0; // nummidrangeareas = 0; // for (i = 1; i < aasworld.numareas; i++) { // if (!(type & ALTROUTEGOAL_ALL)) { if (!(type & ALTROUTEGOAL_CLUSTERPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL))) { if (!(type & ALTROUTEGOAL_VIEWPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_VIEWPORTAL))) { continue; } //end if } //end if } //end if //if the area has no reachabilities if (!AAS_AreaReachability(i)) continue; //tavel time from the area to the start area starttime = AAS_AreaTravelTimeToGoalArea(startareanum, start, i, travelflags); if (!starttime) continue; //if the travel time from the start to the area is greater than the shortest goal travel time if (starttime > (float) 1.1 * goaltraveltime) continue; //travel time from the area to the goal area goaltime = AAS_AreaTravelTimeToGoalArea(i, NULL, goalareanum, travelflags); if (!goaltime) continue; //if the travel time from the area to the goal is greater than the shortest goal travel time if (goaltime > (float) 0.8 * goaltraveltime) continue; //this is a mid range area midrangeareas[i].valid = qtrue; midrangeareas[i].starttime = starttime; midrangeareas[i].goaltime = goaltime; Log_Write("%d midrange area %d", nummidrangeareas, i); nummidrangeareas++; } //end for // for (i = 1; i < aasworld.numareas; i++) { if (!midrangeareas[i].valid) continue; //get the areas in one cluster numclusterareas = 0; AAS_AltRoutingFloodCluster_r(i); //now we've got a cluster with areas through which an alternative route could go //get the 'center' of the cluster VectorClear(mid); for (j = 0; j < numclusterareas; j++) { VectorAdd(mid, aasworld.areas[clusterareas[j]].center, mid); } //end for VectorScale(mid, 1.0 / numclusterareas, mid); //get the area closest to the center of the cluster bestdist = 999999; bestareanum = 0; for (j = 0; j < numclusterareas; j++) { VectorSubtract(mid, aasworld.areas[clusterareas[j]].center, dir); dist = VectorLength(dir); if (dist < bestdist) { bestdist = dist; bestareanum = clusterareas[j]; } //end if } //end for //now we've got an area for an alternative route //FIXME: add alternative goal origin VectorCopy(aasworld.areas[bestareanum].center, altroutegoals[numaltroutegoals].origin); altroutegoals[numaltroutegoals].areanum = bestareanum; altroutegoals[numaltroutegoals].starttraveltime = midrangeareas[bestareanum].starttime; altroutegoals[numaltroutegoals].goaltraveltime = midrangeareas[bestareanum].goaltime; altroutegoals[numaltroutegoals].extratraveltime = (midrangeareas[bestareanum].starttime + midrangeareas[bestareanum].goaltime) - goaltraveltime; numaltroutegoals++; // #ifdef ALTROUTE_DEBUG AAS_ShowAreaPolygons(bestareanum, 1, qtrue); #endif //don't return more than the maximum alternative route goals if (numaltroutegoals >= maxaltroutegoals) break; } //end for #ifdef ALTROUTE_DEBUG botimport.Print(PRT_MESSAGE, "alternative route goals in %d msec\n", Sys_MilliSeconds() - startmillisecs); #endif return numaltroutegoals; #endif } //end of the function AAS_AlternativeRouteGoals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitAlternativeRouting(void) { #ifdef ENABLE_ALTROUTING if (midrangeareas) FreeMemory(midrangeareas); midrangeareas = (midrangearea_t *) GetMemory(aasworld.numareas * sizeof(midrangearea_t)); if (clusterareas) FreeMemory(clusterareas); clusterareas = (int *) GetMemory(aasworld.numareas * sizeof(int)); #endif } //end of the function AAS_InitAlternativeRouting //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShutdownAlternativeRouting(void) { #ifdef ENABLE_ALTROUTING if (midrangeareas) FreeMemory(midrangeareas); midrangeareas = NULL; if (clusterareas) FreeMemory(clusterareas); clusterareas = NULL; numclusterareas = 0; #endif } //end of the function AAS_ShutdownAlternativeRouting ================================================ FILE: code/botlib/be_aas_routealt.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_routealt.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_routealt.h $ * *****************************************************************************/ #ifdef AASINTERN void AAS_InitAlternativeRouting(void); void AAS_ShutdownAlternativeRouting(void); #endif //AASINTERN int AAS_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, int type); ================================================ FILE: code/botlib/be_aas_sample.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_sample.c * * desc: AAS environment sampling * * $Archive: /MissionPack/code/botlib/be_aas_sample.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #ifndef BSPC #include "l_libvar.h" #endif #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_interface.h" #include "be_aas_funcs.h" #include "be_aas_def.h" extern botlib_import_t botimport; //#define AAS_SAMPLE_DEBUG #define BBOX_NORMAL_EPSILON 0.001 #define ON_EPSILON 0 //0.0005 #define TRACEPLANE_EPSILON 0.125 typedef struct aas_tracestack_s { vec3_t start; //start point of the piece of line to trace vec3_t end; //end point of the piece of line to trace int planenum; //last plane used as splitter int nodenum; //node found after splitting with planenum } aas_tracestack_t; int numaaslinks; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs) { int index; //bounding box size for each presence type vec3_t boxmins[3] = {{0, 0, 0}, {-15, -15, -24}, {-15, -15, -24}}; vec3_t boxmaxs[3] = {{0, 0, 0}, { 15, 15, 32}, { 15, 15, 8}}; if (presencetype == PRESENCE_NORMAL) index = 1; else if (presencetype == PRESENCE_CROUCH) index = 2; else { botimport.Print(PRT_FATAL, "AAS_PresenceTypeBoundingBox: unknown presence type\n"); index = 2; } //end if VectorCopy(boxmins[index], mins); VectorCopy(boxmaxs[index], maxs); } //end of the function AAS_PresenceTypeBoundingBox //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitAASLinkHeap(void) { int i, max_aaslinks; max_aaslinks = aasworld.linkheapsize; //if there's no link heap present if (!aasworld.linkheap) { #ifdef BSPC max_aaslinks = 6144; #else max_aaslinks = (int) LibVarValue("max_aaslinks", "6144"); #endif if (max_aaslinks < 0) max_aaslinks = 0; aasworld.linkheapsize = max_aaslinks; aasworld.linkheap = (aas_link_t *) GetHunkMemory(max_aaslinks * sizeof(aas_link_t)); } //end if //link the links on the heap aasworld.linkheap[0].prev_ent = NULL; aasworld.linkheap[0].next_ent = &aasworld.linkheap[1]; for (i = 1; i < max_aaslinks-1; i++) { aasworld.linkheap[i].prev_ent = &aasworld.linkheap[i - 1]; aasworld.linkheap[i].next_ent = &aasworld.linkheap[i + 1]; } //end for aasworld.linkheap[max_aaslinks-1].prev_ent = &aasworld.linkheap[max_aaslinks-2]; aasworld.linkheap[max_aaslinks-1].next_ent = NULL; //pointer to the first free link aasworld.freelinks = &aasworld.linkheap[0]; // numaaslinks = max_aaslinks; } //end of the function AAS_InitAASLinkHeap //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeAASLinkHeap(void) { if (aasworld.linkheap) FreeMemory(aasworld.linkheap); aasworld.linkheap = NULL; aasworld.linkheapsize = 0; } //end of the function AAS_FreeAASLinkHeap //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_link_t *AAS_AllocAASLink(void) { aas_link_t *link; link = aasworld.freelinks; if (!link) { #ifndef BSPC if (bot_developer) #endif { botimport.Print(PRT_FATAL, "empty aas link heap\n"); } //end if return NULL; } //end if if (aasworld.freelinks) aasworld.freelinks = aasworld.freelinks->next_ent; if (aasworld.freelinks) aasworld.freelinks->prev_ent = NULL; numaaslinks--; return link; } //end of the function AAS_AllocAASLink //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DeAllocAASLink(aas_link_t *link) { if (aasworld.freelinks) aasworld.freelinks->prev_ent = link; link->prev_ent = NULL; link->next_ent = aasworld.freelinks; link->prev_area = NULL; link->next_area = NULL; aasworld.freelinks = link; numaaslinks++; } //end of the function AAS_DeAllocAASLink //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitAASLinkedEntities(void) { if (!aasworld.loaded) return; if (aasworld.arealinkedentities) FreeMemory(aasworld.arealinkedentities); aasworld.arealinkedentities = (aas_link_t **) GetClearedHunkMemory( aasworld.numareas * sizeof(aas_link_t *)); } //end of the function AAS_InitAASLinkedEntities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeAASLinkedEntities(void) { if (aasworld.arealinkedentities) FreeMemory(aasworld.arealinkedentities); aasworld.arealinkedentities = NULL; } //end of the function AAS_InitAASLinkedEntities //=========================================================================== // returns the AAS area the point is in // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_PointAreaNum(vec3_t point) { int nodenum; vec_t dist; aas_node_t *node; aas_plane_t *plane; if (!aasworld.loaded) { botimport.Print(PRT_ERROR, "AAS_PointAreaNum: aas not loaded\n"); return 0; } //end if //start with node 1 because node zero is a dummy used for solid leafs nodenum = 1; while (nodenum > 0) { // botimport.Print(PRT_MESSAGE, "[%d]", nodenum); #ifdef AAS_SAMPLE_DEBUG if (nodenum >= aasworld.numnodes) { botimport.Print(PRT_ERROR, "nodenum = %d >= aasworld.numnodes = %d\n", nodenum, aasworld.numnodes); return 0; } //end if #endif //AAS_SAMPLE_DEBUG node = &aasworld.nodes[nodenum]; #ifdef AAS_SAMPLE_DEBUG if (node->planenum < 0 || node->planenum >= aasworld.numplanes) { botimport.Print(PRT_ERROR, "node->planenum = %d >= aasworld.numplanes = %d\n", node->planenum, aasworld.numplanes); return 0; } //end if #endif //AAS_SAMPLE_DEBUG plane = &aasworld.planes[node->planenum]; dist = DotProduct(point, plane->normal) - plane->dist; if (dist > 0) nodenum = node->children[0]; else nodenum = node->children[1]; } //end while if (!nodenum) { #ifdef AAS_SAMPLE_DEBUG botimport.Print(PRT_MESSAGE, "in solid\n"); #endif //AAS_SAMPLE_DEBUG return 0; } //end if return -nodenum; } //end of the function AAS_PointAreaNum //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_PointReachabilityAreaIndex( vec3_t origin ) { int areanum, cluster, i, index; if (!aasworld.initialized) return 0; if ( !origin ) { index = 0; for (i = 0; i < aasworld.numclusters; i++) { index += aasworld.clusters[i].numreachabilityareas; } //end for return index; } //end if areanum = AAS_PointAreaNum( origin ); if ( !areanum || !AAS_AreaReachability(areanum) ) return 0; cluster = aasworld.areasettings[areanum].cluster; areanum = aasworld.areasettings[areanum].clusterareanum; if (cluster < 0) { cluster = aasworld.portals[-cluster].frontcluster; areanum = aasworld.portals[-cluster].clusterareanum[0]; } //end if index = 0; for (i = 0; i < cluster; i++) { index += aasworld.clusters[i].numreachabilityareas; } //end for index += areanum; return index; } //end of the function AAS_PointReachabilityAreaIndex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaCluster(int areanum) { if (areanum <= 0 || areanum >= aasworld.numareas) { botimport.Print(PRT_ERROR, "AAS_AreaCluster: invalid area number\n"); return 0; } //end if return aasworld.areasettings[areanum].cluster; } //end of the function AAS_AreaCluster //=========================================================================== // returns the presence types of the given area // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaPresenceType(int areanum) { if (!aasworld.loaded) return 0; if (areanum <= 0 || areanum >= aasworld.numareas) { botimport.Print(PRT_ERROR, "AAS_AreaPresenceType: invalid area number\n"); return 0; } //end if return aasworld.areasettings[areanum].presencetype; } //end of the function AAS_AreaPresenceType //=========================================================================== // returns the presence type at the given point // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_PointPresenceType(vec3_t point) { int areanum; if (!aasworld.loaded) return 0; areanum = AAS_PointAreaNum(point); if (!areanum) return PRESENCE_NONE; return aasworld.areasettings[areanum].presencetype; } //end of the function AAS_PointPresenceType //=========================================================================== // calculates the minimum distance between the origin of the box and the // given plane when both will collide on the given side of the plane // // normal = normal vector of plane to calculate distance from // mins = minimums of box relative to origin // maxs = maximums of box relative to origin // side = side of the plane we want to calculate the distance from // 0 normal vector side // 1 not normal vector side // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== vec_t AAS_BoxOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs, int side) { vec3_t v1, v2; int i; //swap maxs and mins when on the other side of the plane if (side) { //get a point of the box that would be one of the first //to collide with the plane for (i = 0; i < 3; i++) { if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = mins[i]; else v1[i] = 0; } //end for } //end if else { //get a point of the box that would be one of the first //to collide with the plane for (i = 0; i < 3; i++) { if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = mins[i]; else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; else v1[i] = 0; } //end for } //end else // VectorCopy(normal, v2); VectorInverse(v2); // VectorNegate(normal, v2); return DotProduct(v1, v2); } //end of the function AAS_BoxOriginDistanceFromPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_AreaEntityCollision(int areanum, vec3_t start, vec3_t end, int presencetype, int passent, aas_trace_t *trace) { int collision; vec3_t boxmins, boxmaxs; aas_link_t *link; bsp_trace_t bsptrace; AAS_PresenceTypeBoundingBox(presencetype, boxmins, boxmaxs); Com_Memset(&bsptrace, 0, sizeof(bsp_trace_t)); //make compiler happy //assume no collision bsptrace.fraction = 1; collision = qfalse; for (link = aasworld.arealinkedentities[areanum]; link; link = link->next_ent) { //ignore the pass entity if (link->entnum == passent) continue; // if (AAS_EntityCollision(link->entnum, start, boxmins, boxmaxs, end, CONTENTS_SOLID|CONTENTS_PLAYERCLIP, &bsptrace)) { collision = qtrue; } //end if } //end for if (collision) { trace->startsolid = bsptrace.startsolid; trace->ent = bsptrace.ent; VectorCopy(bsptrace.endpos, trace->endpos); trace->area = 0; trace->planenum = 0; return qtrue; } //end if return qfalse; } //end of the function AAS_AreaEntityCollision //=========================================================================== // recursive subdivision of the line by the BSP tree. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, int passent) { int side, nodenum, tmpplanenum; float front, back, frac; vec3_t cur_start, cur_end, cur_mid, v1, v2; aas_tracestack_t tracestack[127]; aas_tracestack_t *tstack_p; aas_node_t *aasnode; aas_plane_t *plane; aas_trace_t trace; //clear the trace structure Com_Memset(&trace, 0, sizeof(aas_trace_t)); if (!aasworld.loaded) return trace; tstack_p = tracestack; //we start with the whole line on the stack VectorCopy(start, tstack_p->start); VectorCopy(end, tstack_p->end); tstack_p->planenum = 0; //start with node 1 because node zero is a dummy for a solid leaf tstack_p->nodenum = 1; //starting at the root of the tree tstack_p++; while (1) { //pop up the stack tstack_p--; //if the trace stack is empty (ended up with a piece of the //line to be traced in an area) if (tstack_p < tracestack) { tstack_p++; //nothing was hit trace.startsolid = qfalse; trace.fraction = 1.0; //endpos is the end of the line VectorCopy(end, trace.endpos); //nothing hit trace.ent = 0; trace.area = 0; trace.planenum = 0; return trace; } //end if //number of the current node to test the line against nodenum = tstack_p->nodenum; //if it is an area if (nodenum < 0) { #ifdef AAS_SAMPLE_DEBUG if (-nodenum > aasworld.numareasettings) { botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: -nodenum out of range\n"); return trace; } //end if #endif //AAS_SAMPLE_DEBUG //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); //if can't enter the area because it hasn't got the right presence type if (!(aasworld.areasettings[-nodenum].presencetype & presencetype)) { //if the start point is still the initial start point //NOTE: no need for epsilons because the points will be //exactly the same when they're both the start point if (tstack_p->start[0] == start[0] && tstack_p->start[1] == start[1] && tstack_p->start[2] == start[2]) { trace.startsolid = qtrue; trace.fraction = 0.0; VectorClear(v1); } //end if else { trace.startsolid = qfalse; VectorSubtract(end, start, v1); VectorSubtract(tstack_p->start, start, v2); trace.fraction = VectorLength(v2) / VectorNormalize(v1); VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); } //end else VectorCopy(tstack_p->start, trace.endpos); trace.ent = 0; trace.area = -nodenum; // VectorSubtract(end, start, v1); trace.planenum = tstack_p->planenum; //always take the plane with normal facing towards the trace start plane = &aasworld.planes[trace.planenum]; if (DotProduct(v1, plane->normal) > 0) trace.planenum ^= 1; return trace; } //end if else { if (passent >= 0) { if (AAS_AreaEntityCollision(-nodenum, tstack_p->start, tstack_p->end, presencetype, passent, &trace)) { if (!trace.startsolid) { VectorSubtract(end, start, v1); VectorSubtract(trace.endpos, start, v2); trace.fraction = VectorLength(v2) / VectorLength(v1); } //end if return trace; } //end if } //end if } //end else trace.lastarea = -nodenum; continue; } //end if //if it is a solid leaf if (!nodenum) { //if the start point is still the initial start point //NOTE: no need for epsilons because the points will be //exactly the same when they're both the start point if (tstack_p->start[0] == start[0] && tstack_p->start[1] == start[1] && tstack_p->start[2] == start[2]) { trace.startsolid = qtrue; trace.fraction = 0.0; VectorClear(v1); } //end if else { trace.startsolid = qfalse; VectorSubtract(end, start, v1); VectorSubtract(tstack_p->start, start, v2); trace.fraction = VectorLength(v2) / VectorNormalize(v1); VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); } //end else VectorCopy(tstack_p->start, trace.endpos); trace.ent = 0; trace.area = 0; //hit solid leaf // VectorSubtract(end, start, v1); trace.planenum = tstack_p->planenum; //always take the plane with normal facing towards the trace start plane = &aasworld.planes[trace.planenum]; if (DotProduct(v1, plane->normal) > 0) trace.planenum ^= 1; return trace; } //end if #ifdef AAS_SAMPLE_DEBUG if (nodenum > aasworld.numnodes) { botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: nodenum out of range\n"); return trace; } //end if #endif //AAS_SAMPLE_DEBUG //the node to test against aasnode = &aasworld.nodes[nodenum]; //start point of current line to test against node VectorCopy(tstack_p->start, cur_start); //end point of the current line to test against node VectorCopy(tstack_p->end, cur_end); //the current node plane plane = &aasworld.planes[aasnode->planenum]; switch(plane->type) {/*FIXME: wtf doesn't this work? obviously the axial node planes aren't always facing positive!!! //check for axial planes case PLANE_X: { front = cur_start[0] - plane->dist; back = cur_end[0] - plane->dist; break; } //end case case PLANE_Y: { front = cur_start[1] - plane->dist; back = cur_end[1] - plane->dist; break; } //end case case PLANE_Z: { front = cur_start[2] - plane->dist; back = cur_end[2] - plane->dist; break; } //end case*/ default: //gee it's not an axial plane { front = DotProduct(cur_start, plane->normal) - plane->dist; back = DotProduct(cur_end, plane->normal) - plane->dist; break; } //end default } //end switch // bk010221 - old location of FPE hack and divide by zero expression //if the whole to be traced line is totally at the front of this node //only go down the tree with the front child if ((front >= -ON_EPSILON && back >= -ON_EPSILON)) { //keep the current start and end point on the stack //and go down the tree with the front child tstack_p->nodenum = aasnode->children[0]; tstack_p++; if (tstack_p >= &tracestack[127]) { botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); return trace; } //end if } //end if //if the whole to be traced line is totally at the back of this node //only go down the tree with the back child else if ((front < ON_EPSILON && back < ON_EPSILON)) { //keep the current start and end point on the stack //and go down the tree with the back child tstack_p->nodenum = aasnode->children[1]; tstack_p++; if (tstack_p >= &tracestack[127]) { botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); return trace; } //end if } //end if //go down the tree both at the front and back of the node else { tmpplanenum = tstack_p->planenum; // bk010221 - new location of divide by zero (see above) if ( front == back ) front -= 0.001f; // bk0101022 - hack/FPE //calculate the hitpoint with the node (split point of the line) //put the crosspoint TRACEPLANE_EPSILON pixels on the near side if (front < 0) frac = (front + TRACEPLANE_EPSILON)/(front-back); else frac = (front - TRACEPLANE_EPSILON)/(front-back); // bk010221 // if (frac < 0) frac = 0.001f; //0 else if (frac > 1) frac = 0.999f; //1 //frac = front / (front-back); // cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; // AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); //side the front part of the line is on side = front < 0; //first put the end part of the line on the stack (back side) VectorCopy(cur_mid, tstack_p->start); //not necesary to store because still on stack //VectorCopy(cur_end, tstack_p->end); tstack_p->planenum = aasnode->planenum; tstack_p->nodenum = aasnode->children[!side]; tstack_p++; if (tstack_p >= &tracestack[127]) { botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); return trace; } //end if //now put the part near the start of the line on the stack so we will //continue with thats part first. This way we'll find the first //hit of the bbox VectorCopy(cur_start, tstack_p->start); VectorCopy(cur_mid, tstack_p->end); tstack_p->planenum = tmpplanenum; tstack_p->nodenum = aasnode->children[side]; tstack_p++; if (tstack_p >= &tracestack[127]) { botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); return trace; } //end if } //end else } //end while // return trace; } //end of the function AAS_TraceClientBBox //=========================================================================== // recursive subdivision of the line by the BSP tree. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas) { int side, nodenum, tmpplanenum; int numareas; float front, back, frac; vec3_t cur_start, cur_end, cur_mid; aas_tracestack_t tracestack[127]; aas_tracestack_t *tstack_p; aas_node_t *aasnode; aas_plane_t *plane; numareas = 0; areas[0] = 0; if (!aasworld.loaded) return numareas; tstack_p = tracestack; //we start with the whole line on the stack VectorCopy(start, tstack_p->start); VectorCopy(end, tstack_p->end); tstack_p->planenum = 0; //start with node 1 because node zero is a dummy for a solid leaf tstack_p->nodenum = 1; //starting at the root of the tree tstack_p++; while (1) { //pop up the stack tstack_p--; //if the trace stack is empty (ended up with a piece of the //line to be traced in an area) if (tstack_p < tracestack) { return numareas; } //end if //number of the current node to test the line against nodenum = tstack_p->nodenum; //if it is an area if (nodenum < 0) { #ifdef AAS_SAMPLE_DEBUG if (-nodenum > aasworld.numareasettings) { botimport.Print(PRT_ERROR, "AAS_TraceAreas: -nodenum = %d out of range\n", -nodenum); return numareas; } //end if #endif //AAS_SAMPLE_DEBUG //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); areas[numareas] = -nodenum; if (points) VectorCopy(tstack_p->start, points[numareas]); numareas++; if (numareas >= maxareas) return numareas; continue; } //end if //if it is a solid leaf if (!nodenum) { continue; } //end if #ifdef AAS_SAMPLE_DEBUG if (nodenum > aasworld.numnodes) { botimport.Print(PRT_ERROR, "AAS_TraceAreas: nodenum out of range\n"); return numareas; } //end if #endif //AAS_SAMPLE_DEBUG //the node to test against aasnode = &aasworld.nodes[nodenum]; //start point of current line to test against node VectorCopy(tstack_p->start, cur_start); //end point of the current line to test against node VectorCopy(tstack_p->end, cur_end); //the current node plane plane = &aasworld.planes[aasnode->planenum]; switch(plane->type) {/*FIXME: wtf doesn't this work? obviously the node planes aren't always facing positive!!! //check for axial planes case PLANE_X: { front = cur_start[0] - plane->dist; back = cur_end[0] - plane->dist; break; } //end case case PLANE_Y: { front = cur_start[1] - plane->dist; back = cur_end[1] - plane->dist; break; } //end case case PLANE_Z: { front = cur_start[2] - plane->dist; back = cur_end[2] - plane->dist; break; } //end case*/ default: //gee it's not an axial plane { front = DotProduct(cur_start, plane->normal) - plane->dist; back = DotProduct(cur_end, plane->normal) - plane->dist; break; } //end default } //end switch //if the whole to be traced line is totally at the front of this node //only go down the tree with the front child if (front > 0 && back > 0) { //keep the current start and end point on the stack //and go down the tree with the front child tstack_p->nodenum = aasnode->children[0]; tstack_p++; if (tstack_p >= &tracestack[127]) { botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); return numareas; } //end if } //end if //if the whole to be traced line is totally at the back of this node //only go down the tree with the back child else if (front <= 0 && back <= 0) { //keep the current start and end point on the stack //and go down the tree with the back child tstack_p->nodenum = aasnode->children[1]; tstack_p++; if (tstack_p >= &tracestack[127]) { botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); return numareas; } //end if } //end if //go down the tree both at the front and back of the node else { tmpplanenum = tstack_p->planenum; //calculate the hitpoint with the node (split point of the line) //put the crosspoint TRACEPLANE_EPSILON pixels on the near side if (front < 0) frac = (front)/(front-back); else frac = (front)/(front-back); if (frac < 0) frac = 0; else if (frac > 1) frac = 1; //frac = front / (front-back); // cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; // AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); //side the front part of the line is on side = front < 0; //first put the end part of the line on the stack (back side) VectorCopy(cur_mid, tstack_p->start); //not necesary to store because still on stack //VectorCopy(cur_end, tstack_p->end); tstack_p->planenum = aasnode->planenum; tstack_p->nodenum = aasnode->children[!side]; tstack_p++; if (tstack_p >= &tracestack[127]) { botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); return numareas; } //end if //now put the part near the start of the line on the stack so we will //continue with thats part first. This way we'll find the first //hit of the bbox VectorCopy(cur_start, tstack_p->start); VectorCopy(cur_mid, tstack_p->end); tstack_p->planenum = tmpplanenum; tstack_p->nodenum = aasnode->children[side]; tstack_p++; if (tstack_p >= &tracestack[127]) { botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); return numareas; } //end if } //end else } //end while // return numareas; } //end of the function AAS_TraceAreas //=========================================================================== // a simple cross product // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== // void AAS_OrthogonalToVectors(vec3_t v1, vec3_t v2, vec3_t res) #define AAS_OrthogonalToVectors(v1, v2, res) \ (res)[0] = ((v1)[1] * (v2)[2]) - ((v1)[2] * (v2)[1]);\ (res)[1] = ((v1)[2] * (v2)[0]) - ((v1)[0] * (v2)[2]);\ (res)[2] = ((v1)[0] * (v2)[1]) - ((v1)[1] * (v2)[0]); //=========================================================================== // tests if the given point is within the face boundaries // // Parameter: face : face to test if the point is in it // pnormal : normal of the plane to use for the face // point : point to test if inside face boundaries // Returns: qtrue if the point is within the face boundaries // Changes Globals: - //=========================================================================== qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon) { int i, firstvertex, edgenum; vec3_t v0; vec3_t edgevec, pointvec, sepnormal; aas_edge_t *edge; #ifdef AAS_SAMPLE_DEBUG int lastvertex = 0; #endif //AAS_SAMPLE_DEBUG if (!aasworld.loaded) return qfalse; for (i = 0; i < face->numedges; i++) { edgenum = aasworld.edgeindex[face->firstedge + i]; edge = &aasworld.edges[abs(edgenum)]; //get the first vertex of the edge firstvertex = edgenum < 0; VectorCopy(aasworld.vertexes[edge->v[firstvertex]], v0); //edge vector VectorSubtract(aasworld.vertexes[edge->v[!firstvertex]], v0, edgevec); // #ifdef AAS_SAMPLE_DEBUG if (lastvertex && lastvertex != edge->v[firstvertex]) { botimport.Print(PRT_MESSAGE, "winding not counter clockwise\n"); } //end if lastvertex = edge->v[!firstvertex]; #endif //AAS_SAMPLE_DEBUG //vector from first edge point to point possible in face VectorSubtract(point, v0, pointvec); //get a vector pointing inside the face orthogonal to both the //edge vector and the normal vector of the plane the face is in //this vector defines a plane through the origin (first vertex of //edge) and through both the edge vector and the normal vector //of the plane AAS_OrthogonalToVectors(edgevec, pnormal, sepnormal); //check on wich side of the above plane the point is //this is done by checking the sign of the dot product of the //vector orthogonal vector from above and the vector from the //origin (first vertex of edge) to the point //if the dotproduct is smaller than zero the point is outside the face if (DotProduct(pointvec, sepnormal) < -epsilon) return qfalse; } //end for return qtrue; } //end of the function AAS_InsideFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon) { int i, firstvertex, edgenum; vec_t *v1, *v2; vec3_t edgevec, pointvec, sepnormal; aas_edge_t *edge; aas_plane_t *plane; aas_face_t *face; if (!aasworld.loaded) return qfalse; face = &aasworld.faces[facenum]; plane = &aasworld.planes[face->planenum]; // for (i = 0; i < face->numedges; i++) { edgenum = aasworld.edgeindex[face->firstedge + i]; edge = &aasworld.edges[abs(edgenum)]; //get the first vertex of the edge firstvertex = edgenum < 0; v1 = aasworld.vertexes[edge->v[firstvertex]]; v2 = aasworld.vertexes[edge->v[!firstvertex]]; //edge vector VectorSubtract(v2, v1, edgevec); //vector from first edge point to point possible in face VectorSubtract(point, v1, pointvec); // CrossProduct(edgevec, plane->normal, sepnormal); // if (DotProduct(pointvec, sepnormal) < -epsilon) return qfalse; } //end for return qtrue; } //end of the function AAS_PointInsideFace //=========================================================================== // returns the ground face the given point is above in the given area // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point) { int i, facenum; vec3_t up = {0, 0, 1}; vec3_t normal; aas_area_t *area; aas_face_t *face; if (!aasworld.loaded) return NULL; area = &aasworld.areas[areanum]; for (i = 0; i < area->numfaces; i++) { facenum = aasworld.faceindex[area->firstface + i]; face = &aasworld.faces[abs(facenum)]; //if this is a ground face if (face->faceflags & FACE_GROUND) { //get the up or down normal if (aasworld.planes[face->planenum].normal[2] < 0) VectorNegate(up, normal); else VectorCopy(up, normal); //check if the point is in the face if (AAS_InsideFace(face, normal, point, 0.01f)) return face; } //end if } //end for return NULL; } //end of the function AAS_AreaGroundFace //=========================================================================== // returns the face the trace end position is situated in // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FacePlane(int facenum, vec3_t normal, float *dist) { aas_plane_t *plane; plane = &aasworld.planes[aasworld.faces[facenum].planenum]; VectorCopy(plane->normal, normal); *dist = plane->dist; } //end of the function AAS_FacePlane //=========================================================================== // returns the face the trace end position is situated in // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_face_t *AAS_TraceEndFace(aas_trace_t *trace) { int i, facenum; aas_area_t *area; aas_face_t *face, *firstface = NULL; if (!aasworld.loaded) return NULL; //if started in solid no face was hit if (trace->startsolid) return NULL; //trace->lastarea is the last area the trace was in area = &aasworld.areas[trace->lastarea]; //check which face the trace.endpos was in for (i = 0; i < area->numfaces; i++) { facenum = aasworld.faceindex[area->firstface + i]; face = &aasworld.faces[abs(facenum)]; //if the face is in the same plane as the trace end point if ((face->planenum & ~1) == (trace->planenum & ~1)) { //firstface is used for optimization, if theres only one //face in the plane then it has to be the good one //if there are more faces in the same plane then always //check the one with the fewest edges first /* if (firstface) { if (firstface->numedges < face->numedges) { if (AAS_InsideFace(firstface, aasworld.planes[face->planenum].normal, trace->endpos)) { return firstface; } //end if firstface = face; } //end if else { if (AAS_InsideFace(face, aasworld.planes[face->planenum].normal, trace->endpos)) { return face; } //end if } //end else } //end if else { firstface = face; } //end else*/ if (AAS_InsideFace(face, aasworld.planes[face->planenum].normal, trace->endpos, 0.01f)) return face; } //end if } //end for return firstface; } //end of the function AAS_TraceEndFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_BoxOnPlaneSide2(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) { int i, sides; float dist1, dist2; vec3_t corners[2]; for (i = 0; i < 3; i++) { if (p->normal[i] < 0) { corners[0][i] = absmins[i]; corners[1][i] = absmaxs[i]; } //end if else { corners[1][i] = absmins[i]; corners[0][i] = absmaxs[i]; } //end else } //end for dist1 = DotProduct(p->normal, corners[0]) - p->dist; dist2 = DotProduct(p->normal, corners[1]) - p->dist; sides = 0; if (dist1 >= 0) sides = 1; if (dist2 < 0) sides |= 2; return sides; } //end of the function AAS_BoxOnPlaneSide2 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== //int AAS_BoxOnPlaneSide(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) #define AAS_BoxOnPlaneSide(absmins, absmaxs, p) (\ ( (p)->type < 3) ?\ (\ ( (p)->dist <= (absmins)[(p)->type]) ?\ (\ 1\ )\ :\ (\ ( (p)->dist >= (absmaxs)[(p)->type]) ?\ (\ 2\ )\ :\ (\ 3\ )\ )\ )\ :\ (\ AAS_BoxOnPlaneSide2((absmins), (absmaxs), (p))\ )\ ) //end of the function AAS_BoxOnPlaneSide //=========================================================================== // remove the links to this entity from all areas // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_UnlinkFromAreas(aas_link_t *areas) { aas_link_t *link, *nextlink; for (link = areas; link; link = nextlink) { //next area the entity is linked in nextlink = link->next_area; //remove the entity from the linked list of this area if (link->prev_ent) link->prev_ent->next_ent = link->next_ent; else aasworld.arealinkedentities[link->areanum] = link->next_ent; if (link->next_ent) link->next_ent->prev_ent = link->prev_ent; //deallocate the link structure AAS_DeAllocAASLink(link); } //end for } //end of the function AAS_UnlinkFromAreas //=========================================================================== // link the entity to the areas the bounding box is totally or partly // situated in. This is done with recursion down the tree using the // bounding box to test for plane sides // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== typedef struct { int nodenum; //node found after splitting } aas_linkstack_t; aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum) { int side, nodenum; aas_linkstack_t linkstack[128]; aas_linkstack_t *lstack_p; aas_node_t *aasnode; aas_plane_t *plane; aas_link_t *link, *areas; if (!aasworld.loaded) { botimport.Print(PRT_ERROR, "AAS_LinkEntity: aas not loaded\n"); return NULL; } //end if areas = NULL; // lstack_p = linkstack; //we start with the whole line on the stack //start with node 1 because node zero is a dummy used for solid leafs lstack_p->nodenum = 1; //starting at the root of the tree lstack_p++; while (1) { //pop up the stack lstack_p--; //if the trace stack is empty (ended up with a piece of the //line to be traced in an area) if (lstack_p < linkstack) break; //number of the current node to test the line against nodenum = lstack_p->nodenum; //if it is an area if (nodenum < 0) { //NOTE: the entity might have already been linked into this area // because several node children can point to the same area for (link = aasworld.arealinkedentities[-nodenum]; link; link = link->next_ent) { if (link->entnum == entnum) break; } //end for if (link) continue; // link = AAS_AllocAASLink(); if (!link) return areas; link->entnum = entnum; link->areanum = -nodenum; //put the link into the double linked area list of the entity link->prev_area = NULL; link->next_area = areas; if (areas) areas->prev_area = link; areas = link; //put the link into the double linked entity list of the area link->prev_ent = NULL; link->next_ent = aasworld.arealinkedentities[-nodenum]; if (aasworld.arealinkedentities[-nodenum]) aasworld.arealinkedentities[-nodenum]->prev_ent = link; aasworld.arealinkedentities[-nodenum] = link; // continue; } //end if //if solid leaf if (!nodenum) continue; //the node to test against aasnode = &aasworld.nodes[nodenum]; //the current node plane plane = &aasworld.planes[aasnode->planenum]; //get the side(s) the box is situated relative to the plane side = AAS_BoxOnPlaneSide2(absmins, absmaxs, plane); //if on the front side of the node if (side & 1) { lstack_p->nodenum = aasnode->children[0]; lstack_p++; } //end if if (lstack_p >= &linkstack[127]) { botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); break; } //end if //if on the back side of the node if (side & 2) { lstack_p->nodenum = aasnode->children[1]; lstack_p++; } //end if if (lstack_p >= &linkstack[127]) { botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); break; } //end if } //end while return areas; } //end of the function AAS_AASLinkEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype) { vec3_t mins, maxs; vec3_t newabsmins, newabsmaxs; AAS_PresenceTypeBoundingBox(presencetype, mins, maxs); VectorSubtract(absmins, maxs, newabsmins); VectorSubtract(absmaxs, mins, newabsmaxs); //relink the entity return AAS_AASLinkEntity(newabsmins, newabsmaxs, entnum); } //end of the function AAS_LinkEntityClientBBox //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas) { aas_link_t *linkedareas, *link; int num; linkedareas = AAS_AASLinkEntity(absmins, absmaxs, -1); num = 0; for (link = linkedareas; link; link = link->next_area) { areas[num] = link->areanum; num++; if (num >= maxareas) break; } //end for AAS_UnlinkFromAreas(linkedareas); return num; } //end of the function AAS_BBoxAreas //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_AreaInfo( int areanum, aas_areainfo_t *info ) { aas_areasettings_t *settings; if (!info) return 0; if (areanum <= 0 || areanum >= aasworld.numareas) { botimport.Print(PRT_ERROR, "AAS_AreaInfo: areanum %d out of range\n", areanum); return 0; } //end if settings = &aasworld.areasettings[areanum]; info->cluster = settings->cluster; info->contents = settings->contents; info->flags = settings->areaflags; info->presencetype = settings->presencetype; VectorCopy(aasworld.areas[areanum].mins, info->mins); VectorCopy(aasworld.areas[areanum].maxs, info->maxs); VectorCopy(aasworld.areas[areanum].center, info->center); return sizeof(aas_areainfo_t); } //end of the function AAS_AreaInfo //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== aas_plane_t *AAS_PlaneFromNum(int planenum) { if (!aasworld.loaded) return 0; return &aasworld.planes[planenum]; } //end of the function AAS_PlaneFromNum ================================================ FILE: code/botlib/be_aas_sample.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_aas_sample.h * * desc: AAS * * $Archive: /source/code/botlib/be_aas_sample.h $ * *****************************************************************************/ #ifdef AASINTERN void AAS_InitAASLinkHeap(void); void AAS_InitAASLinkedEntities(void); void AAS_FreeAASLinkHeap(void); void AAS_FreeAASLinkedEntities(void); aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point); aas_face_t *AAS_TraceEndFace(aas_trace_t *trace); aas_plane_t *AAS_PlaneFromNum(int planenum); aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum); aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype); qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon); qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon); void AAS_UnlinkFromAreas(aas_link_t *areas); #endif //AASINTERN //returns the mins and maxs of the bounding box for the given presence type void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs); //returns the cluster the area is in (negative portal number if the area is a portal) int AAS_AreaCluster(int areanum); //returns the presence type(s) of the area int AAS_AreaPresenceType(int areanum); //returns the presence type(s) at the given point int AAS_PointPresenceType(vec3_t point); //returns the result of the trace of a client bbox aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, int passent); //stores the areas the trace went through and returns the number of passed areas int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); //returns the areas the bounding box is in int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); //return area information int AAS_AreaInfo( int areanum, aas_areainfo_t *info ); //returns the area the point is in int AAS_PointAreaNum(vec3_t point); // int AAS_PointReachabilityAreaIndex( vec3_t point ); //returns the plane the given face is in void AAS_FacePlane(int facenum, vec3_t normal, float *dist); ================================================ FILE: code/botlib/be_ai_char.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_ai_char.c * * desc: bot characters * * $Archive: /MissionPack/code/botlib/be_ai_char.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_log.h" #include "l_memory.h" #include "l_utils.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_libvar.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "../game/be_ai_char.h" #define MAX_CHARACTERISTICS 80 #define CT_INTEGER 1 #define CT_FLOAT 2 #define CT_STRING 3 #define DEFAULT_CHARACTER "bots/default_c.c" //characteristic value union cvalue { int integer; float _float; char *string; }; //a characteristic typedef struct bot_characteristic_s { char type; //characteristic type union cvalue value; //characteristic value } bot_characteristic_t; //a bot character typedef struct bot_character_s { char filename[MAX_QPATH]; float skill; bot_characteristic_t c[1]; //variable sized } bot_character_t; bot_character_t *botcharacters[MAX_CLIENTS + 1]; //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== bot_character_t *BotCharacterFromHandle(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); return NULL; } //end if if (!botcharacters[handle]) { botimport.Print(PRT_FATAL, "invalid character %d\n", handle); return NULL; } //end if return botcharacters[handle]; } //end of the function BotCharacterFromHandle //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotDumpCharacter(bot_character_t *ch) { int i; Log_Write("%s", ch->filename); Log_Write("skill %d\n", ch->skill); Log_Write("{\n"); for (i = 0; i < MAX_CHARACTERISTICS; i++) { switch(ch->c[i].type) { case CT_INTEGER: Log_Write(" %4d %d\n", i, ch->c[i].value.integer); break; case CT_FLOAT: Log_Write(" %4d %f\n", i, ch->c[i].value._float); break; case CT_STRING: Log_Write(" %4d %s\n", i, ch->c[i].value.string); break; } //end case } //end for Log_Write("}\n"); } //end of the function BotDumpCharacter //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeCharacterStrings(bot_character_t *ch) { int i; for (i = 0; i < MAX_CHARACTERISTICS; i++) { if (ch->c[i].type == CT_STRING) { FreeMemory(ch->c[i].value.string); } //end if } //end for } //end of the function BotFreeCharacterStrings //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeCharacter2(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); return; } //end if if (!botcharacters[handle]) { botimport.Print(PRT_FATAL, "invalid character %d\n", handle); return; } //end if BotFreeCharacterStrings(botcharacters[handle]); FreeMemory(botcharacters[handle]); botcharacters[handle] = NULL; } //end of the function BotFreeCharacter2 //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeCharacter(int handle) { if (!LibVarGetValue("bot_reloadcharacters")) return; BotFreeCharacter2(handle); } //end of the function BotFreeCharacter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotDefaultCharacteristics(bot_character_t *ch, bot_character_t *defaultch) { int i; for (i = 0; i < MAX_CHARACTERISTICS; i++) { if (ch->c[i].type) continue; // if (defaultch->c[i].type == CT_FLOAT) { ch->c[i].type = CT_FLOAT; ch->c[i].value._float = defaultch->c[i].value._float; } //end if else if (defaultch->c[i].type == CT_INTEGER) { ch->c[i].type = CT_INTEGER; ch->c[i].value.integer = defaultch->c[i].value.integer; } //end else if else if (defaultch->c[i].type == CT_STRING) { ch->c[i].type = CT_STRING; ch->c[i].value.string = (char *) GetMemory(strlen(defaultch->c[i].value.string)+1); strcpy(ch->c[i].value.string, defaultch->c[i].value.string); } //end else if } //end for } //end of the function BotDefaultCharacteristics //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_character_t *BotLoadCharacterFromFile(char *charfile, int skill) { int indent, index, foundcharacter; bot_character_t *ch; source_t *source; token_t token; foundcharacter = qfalse; //a bot character is parsed in two phases PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(charfile); if (!source) { botimport.Print(PRT_ERROR, "counldn't load %s\n", charfile); return NULL; } //end if ch = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); strcpy(ch->filename, charfile); while(PC_ReadToken(source, &token)) { if (!strcmp(token.string, "skill")) { if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) { FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if if (!PC_ExpectTokenString(source, "{")) { FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if //if it's the correct skill if (skill < 0 || token.intvalue == skill) { foundcharacter = qtrue; ch->skill = token.intvalue; while(PC_ExpectAnyToken(source, &token)) { if (!strcmp(token.string, "}")) break; if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) { SourceError(source, "expected integer index, found %s\n", token.string); FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if index = token.intvalue; if (index < 0 || index > MAX_CHARACTERISTICS) { SourceError(source, "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS); FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if if (ch->c[index].type) { SourceError(source, "characteristic %d already initialized\n", index); FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if if (!PC_ExpectAnyToken(source, &token)) { FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if if (token.type == TT_NUMBER) { if (token.subtype & TT_FLOAT) { ch->c[index].value._float = token.floatvalue; ch->c[index].type = CT_FLOAT; } //end if else { ch->c[index].value.integer = token.intvalue; ch->c[index].type = CT_INTEGER; } //end else } //end if else if (token.type == TT_STRING) { StripDoubleQuotes(token.string); ch->c[index].value.string = GetMemory(strlen(token.string)+1); strcpy(ch->c[index].value.string, token.string); ch->c[index].type = CT_STRING; } //end else if else { SourceError(source, "expected integer, float or string, found %s\n", token.string); FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end else } //end if break; } //end if else { indent = 1; while(indent) { if (!PC_ExpectAnyToken(source, &token)) { FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if if (!strcmp(token.string, "{")) indent++; else if (!strcmp(token.string, "}")) indent--; } //end while } //end else } //end if else { SourceError(source, "unknown definition %s\n", token.string); FreeSource(source); BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end else } //end while FreeSource(source); // if (!foundcharacter) { BotFreeCharacterStrings(ch); FreeMemory(ch); return NULL; } //end if return ch; } //end of the function BotLoadCharacterFromFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotFindCachedCharacter(char *charfile, float skill) { int handle; for (handle = 1; handle <= MAX_CLIENTS; handle++) { if ( !botcharacters[handle] ) continue; if ( strcmp( botcharacters[handle]->filename, charfile ) == 0 && (skill < 0 || fabs(botcharacters[handle]->skill - skill) < 0.01) ) { return handle; } //end if } //end for return 0; } //end of the function BotFindCachedCharacter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadCachedCharacter(char *charfile, float skill, int reload) { int handle, cachedhandle, intskill; bot_character_t *ch = NULL; #ifdef DEBUG int starttime; starttime = Sys_MilliSeconds(); #endif //DEBUG //find a free spot for a character for (handle = 1; handle <= MAX_CLIENTS; handle++) { if (!botcharacters[handle]) break; } //end for if (handle > MAX_CLIENTS) return 0; //try to load a cached character with the given skill if (!reload) { cachedhandle = BotFindCachedCharacter(charfile, skill); if (cachedhandle) { botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); return cachedhandle; } //end if } //end else // intskill = (int) (skill + 0.5); //try to load the character with the given skill ch = BotLoadCharacterFromFile(charfile, intskill); if (ch) { botcharacters[handle] = ch; // botimport.Print(PRT_MESSAGE, "loaded skill %d from %s\n", intskill, charfile); #ifdef DEBUG if (bot_developer) { botimport.Print(PRT_MESSAGE, "skill %d loaded in %d msec from %s\n", intskill, Sys_MilliSeconds() - starttime, charfile); } //end if #endif //DEBUG return handle; } //end if // botimport.Print(PRT_WARNING, "couldn't find skill %d in %s\n", intskill, charfile); // if (!reload) { //try to load a cached default character with the given skill cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, skill); if (cachedhandle) { botimport.Print(PRT_MESSAGE, "loaded cached default skill %d from %s\n", intskill, charfile); return cachedhandle; } //end if } //end if //try to load the default character with the given skill ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, intskill); if (ch) { botcharacters[handle] = ch; botimport.Print(PRT_MESSAGE, "loaded default skill %d from %s\n", intskill, charfile); return handle; } //end if // if (!reload) { //try to load a cached character with any skill cachedhandle = BotFindCachedCharacter(charfile, -1); if (cachedhandle) { botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); return cachedhandle; } //end if } //end if //try to load a character with any skill ch = BotLoadCharacterFromFile(charfile, -1); if (ch) { botcharacters[handle] = ch; botimport.Print(PRT_MESSAGE, "loaded skill %f from %s\n", ch->skill, charfile); return handle; } //end if // if (!reload) { //try to load a cached character with any skill cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, -1); if (cachedhandle) { botimport.Print(PRT_MESSAGE, "loaded cached default skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); return cachedhandle; } //end if } //end if //try to load a character with any skill ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, -1); if (ch) { botcharacters[handle] = ch; botimport.Print(PRT_MESSAGE, "loaded default skill %f from %s\n", ch->skill, charfile); return handle; } //end if // botimport.Print(PRT_WARNING, "couldn't load any skill from %s\n", charfile); //couldn't load any character return 0; } //end of the function BotLoadCachedCharacter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadCharacterSkill(char *charfile, float skill) { int ch, defaultch; defaultch = BotLoadCachedCharacter(DEFAULT_CHARACTER, skill, qfalse); ch = BotLoadCachedCharacter(charfile, skill, LibVarGetValue("bot_reloadcharacters")); if (defaultch && ch) { BotDefaultCharacteristics(botcharacters[ch], botcharacters[defaultch]); } //end if return ch; } //end of the function BotLoadCharacterSkill //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotInterpolateCharacters(int handle1, int handle2, float desiredskill) { bot_character_t *ch1, *ch2, *out; int i, handle; float scale; ch1 = BotCharacterFromHandle(handle1); ch2 = BotCharacterFromHandle(handle2); if (!ch1 || !ch2) return 0; //find a free spot for a character for (handle = 1; handle <= MAX_CLIENTS; handle++) { if (!botcharacters[handle]) break; } //end for if (handle > MAX_CLIENTS) return 0; out = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); out->skill = desiredskill; strcpy(out->filename, ch1->filename); botcharacters[handle] = out; scale = (float) (desiredskill - ch1->skill) / (ch2->skill - ch1->skill); for (i = 0; i < MAX_CHARACTERISTICS; i++) { // if (ch1->c[i].type == CT_FLOAT && ch2->c[i].type == CT_FLOAT) { out->c[i].type = CT_FLOAT; out->c[i].value._float = ch1->c[i].value._float + (ch2->c[i].value._float - ch1->c[i].value._float) * scale; } //end if else if (ch1->c[i].type == CT_INTEGER) { out->c[i].type = CT_INTEGER; out->c[i].value.integer = ch1->c[i].value.integer; } //end else if else if (ch1->c[i].type == CT_STRING) { out->c[i].type = CT_STRING; out->c[i].value.string = (char *) GetMemory(strlen(ch1->c[i].value.string)+1); strcpy(out->c[i].value.string, ch1->c[i].value.string); } //end else if } //end for return handle; } //end of the function BotInterpolateCharacters //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadCharacter(char *charfile, float skill) { int firstskill, secondskill, handle; //make sure the skill is in the valid range if (skill < 1.0) skill = 1.0; else if (skill > 5.0) skill = 5.0; //skill 1, 4 and 5 should be available in the character files if (skill == 1.0 || skill == 4.0 || skill == 5.0) { return BotLoadCharacterSkill(charfile, skill); } //end if //check if there's a cached skill handle = BotFindCachedCharacter(charfile, skill); if (handle) { botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); return handle; } //end if if (skill < 4.0) { //load skill 1 and 4 firstskill = BotLoadCharacterSkill(charfile, 1); if (!firstskill) return 0; secondskill = BotLoadCharacterSkill(charfile, 4); if (!secondskill) return firstskill; } //end if else { //load skill 4 and 5 firstskill = BotLoadCharacterSkill(charfile, 4); if (!firstskill) return 0; secondskill = BotLoadCharacterSkill(charfile, 5); if (!secondskill) return firstskill; } //end else //interpolate between the two skills handle = BotInterpolateCharacters(firstskill, secondskill, skill); if (!handle) return 0; //write the character to the log file BotDumpCharacter(botcharacters[handle]); // return handle; } //end of the function BotLoadCharacter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int CheckCharacteristicIndex(int character, int index) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return qfalse; if (index < 0 || index >= MAX_CHARACTERISTICS) { botimport.Print(PRT_ERROR, "characteristic %d does not exist\n", index); return qfalse; } //end if if (!ch->c[index].type) { botimport.Print(PRT_ERROR, "characteristic %d is not initialized\n", index); return qfalse; } //end if return qtrue; } //end of the function CheckCharacteristicIndex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float Characteristic_Float(int character, int index) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return 0; //check if the index is in range if (!CheckCharacteristicIndex(character, index)) return 0; //an integer will be converted to a float if (ch->c[index].type == CT_INTEGER) { return (float) ch->c[index].value.integer; } //end if //floats are just returned else if (ch->c[index].type == CT_FLOAT) { return ch->c[index].value._float; } //end else if //cannot convert a string pointer to a float else { botimport.Print(PRT_ERROR, "characteristic %d is not a float\n", index); return 0; } //end else if // return 0; } //end of the function Characteristic_Float //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float Characteristic_BFloat(int character, int index, float min, float max) { float value; bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return 0; if (min > max) { botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %f and %f\n", index, min, max); return 0; } //end if value = Characteristic_Float(character, index); if (value < min) return min; if (value > max) return max; return value; } //end of the function Characteristic_BFloat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Characteristic_Integer(int character, int index) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return 0; //check if the index is in range if (!CheckCharacteristicIndex(character, index)) return 0; //an integer will just be returned if (ch->c[index].type == CT_INTEGER) { return ch->c[index].value.integer; } //end if //floats are casted to integers else if (ch->c[index].type == CT_FLOAT) { return (int) ch->c[index].value._float; } //end else if else { botimport.Print(PRT_ERROR, "characteristic %d is not a integer\n", index); return 0; } //end else if // return 0; } //end of the function Characteristic_Integer //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Characteristic_BInteger(int character, int index, int min, int max) { int value; bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return 0; if (min > max) { botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %d and %d\n", index, min, max); return 0; } //end if value = Characteristic_Integer(character, index); if (value < min) return min; if (value > max) return max; return value; } //end of the function Characteristic_BInteger //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Characteristic_String(int character, int index, char *buf, int size) { bot_character_t *ch; ch = BotCharacterFromHandle(character); if (!ch) return; //check if the index is in range if (!CheckCharacteristicIndex(character, index)) return; //an integer will be converted to a float if (ch->c[index].type == CT_STRING) { strncpy(buf, ch->c[index].value.string, size-1); buf[size-1] = '\0'; return; } //end if else { botimport.Print(PRT_ERROR, "characteristic %d is not a string\n", index); return; } //end else if return; } //end of the function Characteristic_String //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotShutdownCharacters(void) { int handle; for (handle = 1; handle <= MAX_CLIENTS; handle++) { if (botcharacters[handle]) { BotFreeCharacter2(handle); } //end if } //end for } //end of the function BotShutdownCharacters ================================================ FILE: code/botlib/be_ai_chat.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_ai_chat.c * * desc: bot chat AI * * $Archive: /MissionPack/code/botlib/be_ai_chat.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_libvar.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_utils.h" #include "l_log.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "../game/be_ea.h" #include "../game/be_ai_chat.h" //escape character #define ESCAPE_CHAR 0x01 //'_' // // "hi ", people, " ", 0, " entered the game" //becomes: // "hi _rpeople_ _v0_ entered the game" // //match piece types #define MT_VARIABLE 1 //variable match piece #define MT_STRING 2 //string match piece //reply chat key flags #define RCKFL_AND 1 //key must be present #define RCKFL_NOT 2 //key must be absent #define RCKFL_NAME 4 //name of bot must be present #define RCKFL_STRING 8 //key is a string #define RCKFL_VARIABLES 16 //key is a match template #define RCKFL_BOTNAMES 32 //key is a series of botnames #define RCKFL_GENDERFEMALE 64 //bot must be female #define RCKFL_GENDERMALE 128 //bot must be male #define RCKFL_GENDERLESS 256 //bot must be genderless //time to ignore a chat message after using it #define CHATMESSAGE_RECENTTIME 20 //the actuall chat messages typedef struct bot_chatmessage_s { char *chatmessage; //chat message string float time; //last time used struct bot_chatmessage_s *next; //next chat message in a list } bot_chatmessage_t; //bot chat type with chat lines typedef struct bot_chattype_s { char name[MAX_CHATTYPE_NAME]; int numchatmessages; bot_chatmessage_t *firstchatmessage; struct bot_chattype_s *next; } bot_chattype_t; //bot chat lines typedef struct bot_chat_s { bot_chattype_t *types; } bot_chat_t; //random string typedef struct bot_randomstring_s { char *string; struct bot_randomstring_s *next; } bot_randomstring_t; //list with random strings typedef struct bot_randomlist_s { char *string; int numstrings; bot_randomstring_t *firstrandomstring; struct bot_randomlist_s *next; } bot_randomlist_t; //synonym typedef struct bot_synonym_s { char *string; float weight; struct bot_synonym_s *next; } bot_synonym_t; //list with synonyms typedef struct bot_synonymlist_s { unsigned long int context; float totalweight; bot_synonym_t *firstsynonym; struct bot_synonymlist_s *next; } bot_synonymlist_t; //fixed match string typedef struct bot_matchstring_s { char *string; struct bot_matchstring_s *next; } bot_matchstring_t; //piece of a match template typedef struct bot_matchpiece_s { int type; bot_matchstring_t *firststring; int variable; struct bot_matchpiece_s *next; } bot_matchpiece_t; //match template typedef struct bot_matchtemplate_s { unsigned long int context; int type; int subtype; bot_matchpiece_t *first; struct bot_matchtemplate_s *next; } bot_matchtemplate_t; //reply chat key typedef struct bot_replychatkey_s { int flags; char *string; bot_matchpiece_t *match; struct bot_replychatkey_s *next; } bot_replychatkey_t; //reply chat typedef struct bot_replychat_s { bot_replychatkey_t *keys; float priority; int numchatmessages; bot_chatmessage_t *firstchatmessage; struct bot_replychat_s *next; } bot_replychat_t; //string list typedef struct bot_stringlist_s { char *string; struct bot_stringlist_s *next; } bot_stringlist_t; //chat state of a bot typedef struct bot_chatstate_s { int gender; //0=it, 1=female, 2=male int client; //client number char name[32]; //name of the bot char chatmessage[MAX_MESSAGE_SIZE]; int handle; //the console messages visible to the bot bot_consolemessage_t *firstmessage; //first message is the first typed message bot_consolemessage_t *lastmessage; //last message is the last typed message, bottom of console //number of console messages stored in the state int numconsolemessages; //the bot chat lines bot_chat_t *chat; } bot_chatstate_t; typedef struct { bot_chat_t *chat; char filename[MAX_QPATH]; char chatname[MAX_QPATH]; } bot_ichatdata_t; bot_ichatdata_t *ichatdata[MAX_CLIENTS]; bot_chatstate_t *botchatstates[MAX_CLIENTS+1]; //console message heap bot_consolemessage_t *consolemessageheap = NULL; bot_consolemessage_t *freeconsolemessages = NULL; //list with match strings bot_matchtemplate_t *matchtemplates = NULL; //list with synonyms bot_synonymlist_t *synonyms = NULL; //list with random strings bot_randomlist_t *randomstrings = NULL; //reply chats bot_replychat_t *replychats = NULL; //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== bot_chatstate_t *BotChatStateFromHandle(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); return NULL; } //end if if (!botchatstates[handle]) { botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); return NULL; } //end if return botchatstates[handle]; } //end of the function BotChatStateFromHandle //=========================================================================== // initialize the heap with unused console messages // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void InitConsoleMessageHeap(void) { int i, max_messages; if (consolemessageheap) FreeMemory(consolemessageheap); // max_messages = (int) LibVarValue("max_messages", "1024"); consolemessageheap = (bot_consolemessage_t *) GetClearedHunkMemory(max_messages * sizeof(bot_consolemessage_t)); consolemessageheap[0].prev = NULL; consolemessageheap[0].next = &consolemessageheap[1]; for (i = 1; i < max_messages-1; i++) { consolemessageheap[i].prev = &consolemessageheap[i - 1]; consolemessageheap[i].next = &consolemessageheap[i + 1]; } //end for consolemessageheap[max_messages-1].prev = &consolemessageheap[max_messages-2]; consolemessageheap[max_messages-1].next = NULL; //pointer to the free console messages freeconsolemessages = consolemessageheap; } //end of the function InitConsoleMessageHeap //=========================================================================== // allocate one console message from the heap // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_consolemessage_t *AllocConsoleMessage(void) { bot_consolemessage_t *message; message = freeconsolemessages; if (freeconsolemessages) freeconsolemessages = freeconsolemessages->next; if (freeconsolemessages) freeconsolemessages->prev = NULL; return message; } //end of the function AllocConsoleMessage //=========================================================================== // deallocate one console message from the heap // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeConsoleMessage(bot_consolemessage_t *message) { if (freeconsolemessages) freeconsolemessages->prev = message; message->prev = NULL; message->next = freeconsolemessages; freeconsolemessages = message; } //end of the function FreeConsoleMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotRemoveConsoleMessage(int chatstate, int handle) { bot_consolemessage_t *m, *nextm; bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; for (m = cs->firstmessage; m; m = nextm) { nextm = m->next; if (m->handle == handle) { if (m->next) m->next->prev = m->prev; else cs->lastmessage = m->prev; if (m->prev) m->prev->next = m->next; else cs->firstmessage = m->next; FreeConsoleMessage(m); cs->numconsolemessages--; break; } //end if } //end for } //end of the function BotRemoveConsoleMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotQueueConsoleMessage(int chatstate, int type, char *message) { bot_consolemessage_t *m; bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; m = AllocConsoleMessage(); if (!m) { botimport.Print(PRT_ERROR, "empty console message heap\n"); return; } //end if cs->handle++; if (cs->handle <= 0 || cs->handle > 8192) cs->handle = 1; m->handle = cs->handle; m->time = AAS_Time(); m->type = type; strncpy(m->message, message, MAX_MESSAGE_SIZE); m->next = NULL; if (cs->lastmessage) { cs->lastmessage->next = m; m->prev = cs->lastmessage; cs->lastmessage = m; } //end if else { cs->lastmessage = m; cs->firstmessage = m; m->prev = NULL; } //end if cs->numconsolemessages++; } //end of the function BotQueueConsoleMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return 0; if (cs->firstmessage) { Com_Memcpy(cm, cs->firstmessage, sizeof(bot_consolemessage_t)); cm->next = cm->prev = NULL; return cm->handle; } //end if return 0; } //end of the function BotConsoleMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotNumConsoleMessages(int chatstate) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return 0; return cs->numconsolemessages; } //end of the function BotNumConsoleMessages //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int IsWhiteSpace(char c) { if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '(' || c == ')' || c == '?' || c == ':' || c == '\''|| c == '/' || c == ',' || c == '.' || c == '[' || c == ']' || c == '-' || c == '_' || c == '+' || c == '=') return qfalse; return qtrue; } //end of the function IsWhiteSpace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotRemoveTildes(char *message) { int i; //remove all tildes from the chat message for (i = 0; message[i]; i++) { if (message[i] == '~') { memmove(&message[i], &message[i+1], strlen(&message[i+1])+1); } //end if } //end for } //end of the function BotRemoveTildes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void UnifyWhiteSpaces(char *string) { char *ptr, *oldptr; for (ptr = oldptr = string; *ptr; oldptr = ptr) { while(*ptr && IsWhiteSpace(*ptr)) ptr++; if (ptr > oldptr) { //if not at the start and not at the end of the string //write only one space if (oldptr > string && *ptr) *oldptr++ = ' '; //remove all other white spaces if (ptr > oldptr) memmove(oldptr, ptr, strlen(ptr)+1); } //end if while(*ptr && !IsWhiteSpace(*ptr)) ptr++; } //end while } //end of the function UnifyWhiteSpaces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int StringContains(char *str1, char *str2, int casesensitive) { int len, i, j, index; if (str1 == NULL || str2 == NULL) return -1; len = strlen(str1) - strlen(str2); index = 0; for (i = 0; i <= len; i++, str1++, index++) { for (j = 0; str2[j]; j++) { if (casesensitive) { if (str1[j] != str2[j]) break; } //end if else { if (toupper(str1[j]) != toupper(str2[j])) break; } //end else } //end for if (!str2[j]) return index; } //end for return -1; } //end of the function StringContains //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *StringContainsWord(char *str1, char *str2, int casesensitive) { int len, i, j; len = strlen(str1) - strlen(str2); for (i = 0; i <= len; i++, str1++) { //if not at the start of the string if (i) { //skip to the start of the next word while(*str1 && *str1 != ' ' && *str1 != '.' && *str1 != ',' && *str1 != '!') str1++; if (!*str1) break; str1++; } //end for //compare the word for (j = 0; str2[j]; j++) { if (casesensitive) { if (str1[j] != str2[j]) break; } //end if else { if (toupper(str1[j]) != toupper(str2[j])) break; } //end else } //end for //if there was a word match if (!str2[j]) { //if the first string has an end of word if (!str1[j] || str1[j] == ' ' || str1[j] == '.' || str1[j] == ',' || str1[j] == '!') return str1; } //end if } //end for return NULL; } //end of the function StringContainsWord //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void StringReplaceWords(char *string, char *synonym, char *replacement) { char *str, *str2; //find the synonym in the string str = StringContainsWord(string, synonym, qfalse); //if the synonym occured in the string while(str) { //if the synonym isn't part of the replacement which is already in the string //usefull for abreviations str2 = StringContainsWord(string, replacement, qfalse); while(str2) { if (str2 <= str && str < str2 + strlen(replacement)) break; str2 = StringContainsWord(str2+1, replacement, qfalse); } //end while if (!str2) { memmove(str + strlen(replacement), str+strlen(synonym), strlen(str+strlen(synonym))+1); //append the synonum replacement Com_Memcpy(str, replacement, strlen(replacement)); } //end if //find the next synonym in the string str = StringContainsWord(str+strlen(replacement), synonym, qfalse); } //end if } //end of the function StringReplaceWords //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotDumpSynonymList(bot_synonymlist_t *synlist) { FILE *fp; bot_synonymlist_t *syn; bot_synonym_t *synonym; fp = Log_FilePointer(); if (!fp) return; for (syn = synlist; syn; syn = syn->next) { fprintf(fp, "%ld : [", syn->context); for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) { fprintf(fp, "(\"%s\", %1.2f)", synonym->string, synonym->weight); if (synonym->next) fprintf(fp, ", "); } //end for fprintf(fp, "]\n"); } //end for } //end of the function BotDumpSynonymList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_synonymlist_t *BotLoadSynonyms(char *filename) { int pass, size, contextlevel, numsynonyms; unsigned long int context, contextstack[32]; char *ptr = NULL; source_t *source; token_t token; bot_synonymlist_t *synlist, *lastsyn, *syn; bot_synonym_t *synonym, *lastsynonym; size = 0; synlist = NULL; //make compiler happy syn = NULL; //make compiler happy synonym = NULL; //make compiler happy //the synonyms are parsed in two phases for (pass = 0; pass < 2; pass++) { // if (pass && size) ptr = (char *) GetClearedHunkMemory(size); // PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(filename); if (!source) { botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); return NULL; } //end if // context = 0; contextlevel = 0; synlist = NULL; //list synonyms lastsyn = NULL; //last synonym in the list // while(PC_ReadToken(source, &token)) { if (token.type == TT_NUMBER) { context |= token.intvalue; contextstack[contextlevel] = token.intvalue; contextlevel++; if (contextlevel >= 32) { SourceError(source, "more than 32 context levels"); FreeSource(source); return NULL; } //end if if (!PC_ExpectTokenString(source, "{")) { FreeSource(source); return NULL; } //end if } //end if else if (token.type == TT_PUNCTUATION) { if (!strcmp(token.string, "}")) { contextlevel--; if (contextlevel < 0) { SourceError(source, "too many }"); FreeSource(source); return NULL; } //end if context &= ~contextstack[contextlevel]; } //end if else if (!strcmp(token.string, "[")) { size += sizeof(bot_synonymlist_t); if (pass) { syn = (bot_synonymlist_t *) ptr; ptr += sizeof(bot_synonymlist_t); syn->context = context; syn->firstsynonym = NULL; syn->next = NULL; if (lastsyn) lastsyn->next = syn; else synlist = syn; lastsyn = syn; } //end if numsynonyms = 0; lastsynonym = NULL; while(1) { if (!PC_ExpectTokenString(source, "(") || !PC_ExpectTokenType(source, TT_STRING, 0, &token)) { FreeSource(source); return NULL; } //end if StripDoubleQuotes(token.string); if (strlen(token.string) <= 0) { SourceError(source, "empty string", token.string); FreeSource(source); return NULL; } //end if size += sizeof(bot_synonym_t) + strlen(token.string) + 1; if (pass) { synonym = (bot_synonym_t *) ptr; ptr += sizeof(bot_synonym_t); synonym->string = ptr; ptr += strlen(token.string) + 1; strcpy(synonym->string, token.string); // if (lastsynonym) lastsynonym->next = synonym; else syn->firstsynonym = synonym; lastsynonym = synonym; } //end if numsynonyms++; if (!PC_ExpectTokenString(source, ",") || !PC_ExpectTokenType(source, TT_NUMBER, 0, &token) || !PC_ExpectTokenString(source, ")")) { FreeSource(source); return NULL; } //end if if (pass) { synonym->weight = token.floatvalue; syn->totalweight += synonym->weight; } //end if if (PC_CheckTokenString(source, "]")) break; if (!PC_ExpectTokenString(source, ",")) { FreeSource(source); return NULL; } //end if } //end while if (numsynonyms < 2) { SourceError(source, "synonym must have at least two entries\n"); FreeSource(source); return NULL; } //end if } //end else else { SourceError(source, "unexpected %s", token.string); FreeSource(source); return NULL; } //end if } //end else if } //end while // FreeSource(source); // if (contextlevel > 0) { SourceError(source, "missing }"); return NULL; } //end if } //end for botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); // //BotDumpSynonymList(synlist); // return synlist; } //end of the function BotLoadSynonyms //=========================================================================== // replace all the synonyms in the string // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotReplaceSynonyms(char *string, unsigned long int context) { bot_synonymlist_t *syn; bot_synonym_t *synonym; for (syn = synonyms; syn; syn = syn->next) { if (!(syn->context & context)) continue; for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) { StringReplaceWords(string, synonym->string, syn->firstsynonym->string); } //end for } //end for } //end of the function BotReplaceSynonyms //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotReplaceWeightedSynonyms(char *string, unsigned long int context) { bot_synonymlist_t *syn; bot_synonym_t *synonym, *replacement; float weight, curweight; for (syn = synonyms; syn; syn = syn->next) { if (!(syn->context & context)) continue; //choose a weighted random replacement synonym weight = random() * syn->totalweight; if (!weight) continue; curweight = 0; for (replacement = syn->firstsynonym; replacement; replacement = replacement->next) { curweight += replacement->weight; if (weight < curweight) break; } //end for if (!replacement) continue; //replace all synonyms with the replacement for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) { if (synonym == replacement) continue; StringReplaceWords(string, synonym->string, replacement->string); } //end for } //end for } //end of the function BotReplaceWeightedSynonyms //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotReplaceReplySynonyms(char *string, unsigned long int context) { char *str1, *str2, *replacement; bot_synonymlist_t *syn; bot_synonym_t *synonym; for (str1 = string; *str1; ) { //go to the start of the next word while(*str1 && *str1 <= ' ') str1++; if (!*str1) break; // for (syn = synonyms; syn; syn = syn->next) { if (!(syn->context & context)) continue; for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) { str2 = synonym->string; //if the synonym is not at the front of the string continue str2 = StringContainsWord(str1, synonym->string, qfalse); if (!str2 || str2 != str1) continue; // replacement = syn->firstsynonym->string; //if the replacement IS in front of the string continue str2 = StringContainsWord(str1, replacement, qfalse); if (str2 && str2 == str1) continue; // memmove(str1 + strlen(replacement), str1+strlen(synonym->string), strlen(str1+strlen(synonym->string)) + 1); //append the synonum replacement Com_Memcpy(str1, replacement, strlen(replacement)); // break; } //end for //if a synonym has been replaced if (synonym) break; } //end for //skip over this word while(*str1 && *str1 > ' ') str1++; if (!*str1) break; } //end while } //end of the function BotReplaceReplySynonyms //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadChatMessage(source_t *source, char *chatmessagestring) { char *ptr; token_t token; ptr = chatmessagestring; *ptr = 0; // while(1) { if (!PC_ExpectAnyToken(source, &token)) return qfalse; //fixed string if (token.type == TT_STRING) { StripDoubleQuotes(token.string); if (strlen(ptr) + strlen(token.string) + 1 > MAX_MESSAGE_SIZE) { SourceError(source, "chat message too long\n"); return qfalse; } //end if strcat(ptr, token.string); } //end else if //variable string else if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) { if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) { SourceError(source, "chat message too long\n"); return qfalse; } //end if sprintf(&ptr[strlen(ptr)], "%cv%ld%c", ESCAPE_CHAR, token.intvalue, ESCAPE_CHAR); } //end if //random string else if (token.type == TT_NAME) { if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) { SourceError(source, "chat message too long\n"); return qfalse; } //end if sprintf(&ptr[strlen(ptr)], "%cr%s%c", ESCAPE_CHAR, token.string, ESCAPE_CHAR); } //end else if else { SourceError(source, "unknown message component %s\n", token.string); return qfalse; } //end else if (PC_CheckTokenString(source, ";")) break; if (!PC_ExpectTokenString(source, ",")) return qfalse; } //end while // return qtrue; } //end of the function BotLoadChatMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotDumpRandomStringList(bot_randomlist_t *randomlist) { FILE *fp; bot_randomlist_t *random; bot_randomstring_t *rs; fp = Log_FilePointer(); if (!fp) return; for (random = randomlist; random; random = random->next) { fprintf(fp, "%s = {", random->string); for (rs = random->firstrandomstring; rs; rs = rs->next) { fprintf(fp, "\"%s\"", rs->string); if (rs->next) fprintf(fp, ", "); else fprintf(fp, "}\n"); } //end for } //end for } //end of the function BotDumpRandomStringList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_randomlist_t *BotLoadRandomStrings(char *filename) { int pass, size; char *ptr = NULL, chatmessagestring[MAX_MESSAGE_SIZE]; source_t *source; token_t token; bot_randomlist_t *randomlist, *lastrandom, *random; bot_randomstring_t *randomstring; #ifdef DEBUG int starttime = Sys_MilliSeconds(); #endif //DEBUG size = 0; randomlist = NULL; random = NULL; //the synonyms are parsed in two phases for (pass = 0; pass < 2; pass++) { // if (pass && size) ptr = (char *) GetClearedHunkMemory(size); // PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(filename); if (!source) { botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); return NULL; } //end if // randomlist = NULL; //list lastrandom = NULL; //last // while(PC_ReadToken(source, &token)) { if (token.type != TT_NAME) { SourceError(source, "unknown random %s", token.string); FreeSource(source); return NULL; } //end if size += sizeof(bot_randomlist_t) + strlen(token.string) + 1; if (pass) { random = (bot_randomlist_t *) ptr; ptr += sizeof(bot_randomlist_t); random->string = ptr; ptr += strlen(token.string) + 1; strcpy(random->string, token.string); random->firstrandomstring = NULL; random->numstrings = 0; // if (lastrandom) lastrandom->next = random; else randomlist = random; lastrandom = random; } //end if if (!PC_ExpectTokenString(source, "=") || !PC_ExpectTokenString(source, "{")) { FreeSource(source); return NULL; } //end if while(!PC_CheckTokenString(source, "}")) { if (!BotLoadChatMessage(source, chatmessagestring)) { FreeSource(source); return NULL; } //end if size += sizeof(bot_randomstring_t) + strlen(chatmessagestring) + 1; if (pass) { randomstring = (bot_randomstring_t *) ptr; ptr += sizeof(bot_randomstring_t); randomstring->string = ptr; ptr += strlen(chatmessagestring) + 1; strcpy(randomstring->string, chatmessagestring); // random->numstrings++; randomstring->next = random->firstrandomstring; random->firstrandomstring = randomstring; } //end if } //end while } //end while //free the source after one pass FreeSource(source); } //end for botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); // #ifdef DEBUG botimport.Print(PRT_MESSAGE, "random strings %d msec\n", Sys_MilliSeconds() - starttime); //BotDumpRandomStringList(randomlist); #endif //DEBUG // return randomlist; } //end of the function BotLoadRandomStrings //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *RandomString(char *name) { bot_randomlist_t *random; bot_randomstring_t *rs; int i; for (random = randomstrings; random; random = random->next) { if (!strcmp(random->string, name)) { i = random() * random->numstrings; for (rs = random->firstrandomstring; rs; rs = rs->next) { if (--i < 0) break; } //end for if (rs) { return rs->string; } //end if } //end for } //end for return NULL; } //end of the function RandomString //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotDumpMatchTemplates(bot_matchtemplate_t *matches) { FILE *fp; bot_matchtemplate_t *mt; bot_matchpiece_t *mp; bot_matchstring_t *ms; fp = Log_FilePointer(); if (!fp) return; for (mt = matches; mt; mt = mt->next) { fprintf(fp, "{ " ); for (mp = mt->first; mp; mp = mp->next) { if (mp->type == MT_STRING) { for (ms = mp->firststring; ms; ms = ms->next) { fprintf(fp, "\"%s\"", ms->string); if (ms->next) fprintf(fp, "|"); } //end for } //end if else if (mp->type == MT_VARIABLE) { fprintf(fp, "%d", mp->variable); } //end else if if (mp->next) fprintf(fp, ", "); } //end for fprintf(fp, " = (%d, %d);}\n", mt->type, mt->subtype); } //end for } //end of the function BotDumpMatchTemplates //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotFreeMatchPieces(bot_matchpiece_t *matchpieces) { bot_matchpiece_t *mp, *nextmp; bot_matchstring_t *ms, *nextms; for (mp = matchpieces; mp; mp = nextmp) { nextmp = mp->next; if (mp->type == MT_STRING) { for (ms = mp->firststring; ms; ms = nextms) { nextms = ms->next; FreeMemory(ms); } //end for } //end if FreeMemory(mp); } //end for } //end of the function BotFreeMatchPieces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_matchpiece_t *BotLoadMatchPieces(source_t *source, char *endtoken) { int lastwasvariable, emptystring; token_t token; bot_matchpiece_t *matchpiece, *firstpiece, *lastpiece; bot_matchstring_t *matchstring, *lastmatchstring; firstpiece = NULL; lastpiece = NULL; // lastwasvariable = qfalse; // while(PC_ReadToken(source, &token)) { if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) { if (token.intvalue < 0 || token.intvalue >= MAX_MATCHVARIABLES) { SourceError(source, "can't have more than %d match variables\n", MAX_MATCHVARIABLES); FreeSource(source); BotFreeMatchPieces(firstpiece); return NULL; } //end if if (lastwasvariable) { SourceError(source, "not allowed to have adjacent variables\n"); FreeSource(source); BotFreeMatchPieces(firstpiece); return NULL; } //end if lastwasvariable = qtrue; // matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); matchpiece->type = MT_VARIABLE; matchpiece->variable = token.intvalue; matchpiece->next = NULL; if (lastpiece) lastpiece->next = matchpiece; else firstpiece = matchpiece; lastpiece = matchpiece; } //end if else if (token.type == TT_STRING) { // matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); matchpiece->firststring = NULL; matchpiece->type = MT_STRING; matchpiece->variable = 0; matchpiece->next = NULL; if (lastpiece) lastpiece->next = matchpiece; else firstpiece = matchpiece; lastpiece = matchpiece; // lastmatchstring = NULL; emptystring = qfalse; // do { if (matchpiece->firststring) { if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) { FreeSource(source); BotFreeMatchPieces(firstpiece); return NULL; } //end if } //end if StripDoubleQuotes(token.string); matchstring = (bot_matchstring_t *) GetClearedHunkMemory(sizeof(bot_matchstring_t) + strlen(token.string) + 1); matchstring->string = (char *) matchstring + sizeof(bot_matchstring_t); strcpy(matchstring->string, token.string); if (!strlen(token.string)) emptystring = qtrue; matchstring->next = NULL; if (lastmatchstring) lastmatchstring->next = matchstring; else matchpiece->firststring = matchstring; lastmatchstring = matchstring; } while(PC_CheckTokenString(source, "|")); //if there was no empty string found if (!emptystring) lastwasvariable = qfalse; } //end if else { SourceError(source, "invalid token %s\n", token.string); FreeSource(source); BotFreeMatchPieces(firstpiece); return NULL; } //end else if (PC_CheckTokenString(source, endtoken)) break; if (!PC_ExpectTokenString(source, ",")) { FreeSource(source); BotFreeMatchPieces(firstpiece); return NULL; } //end if } //end while return firstpiece; } //end of the function BotLoadMatchPieces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotFreeMatchTemplates(bot_matchtemplate_t *mt) { bot_matchtemplate_t *nextmt; for (; mt; mt = nextmt) { nextmt = mt->next; BotFreeMatchPieces(mt->first); FreeMemory(mt); } //end for } //end of the function BotFreeMatchTemplates //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_matchtemplate_t *BotLoadMatchTemplates(char *matchfile) { source_t *source; token_t token; bot_matchtemplate_t *matchtemplate, *matches, *lastmatch; unsigned long int context; PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(matchfile); if (!source) { botimport.Print(PRT_ERROR, "counldn't load %s\n", matchfile); return NULL; } //end if // matches = NULL; //list with matches lastmatch = NULL; //last match in the list while(PC_ReadToken(source, &token)) { if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) { SourceError(source, "expected integer, found %s\n", token.string); BotFreeMatchTemplates(matches); FreeSource(source); return NULL; } //end if //the context context = token.intvalue; // if (!PC_ExpectTokenString(source, "{")) { BotFreeMatchTemplates(matches); FreeSource(source); return NULL; } //end if // while(PC_ReadToken(source, &token)) { if (!strcmp(token.string, "}")) break; // PC_UnreadLastToken(source); // matchtemplate = (bot_matchtemplate_t *) GetClearedHunkMemory(sizeof(bot_matchtemplate_t)); matchtemplate->context = context; matchtemplate->next = NULL; //add the match template to the list if (lastmatch) lastmatch->next = matchtemplate; else matches = matchtemplate; lastmatch = matchtemplate; //load the match template matchtemplate->first = BotLoadMatchPieces(source, "="); if (!matchtemplate->first) { BotFreeMatchTemplates(matches); return NULL; } //end if //read the match type if (!PC_ExpectTokenString(source, "(") || !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) { BotFreeMatchTemplates(matches); FreeSource(source); return NULL; } //end if matchtemplate->type = token.intvalue; //read the match subtype if (!PC_ExpectTokenString(source, ",") || !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) { BotFreeMatchTemplates(matches); FreeSource(source); return NULL; } //end if matchtemplate->subtype = token.intvalue; //read trailing punctuations if (!PC_ExpectTokenString(source, ")") || !PC_ExpectTokenString(source, ";")) { BotFreeMatchTemplates(matches); FreeSource(source); return NULL; } //end if } //end while } //end while //free the source FreeSource(source); botimport.Print(PRT_MESSAGE, "loaded %s\n", matchfile); // //BotDumpMatchTemplates(matches); // return matches; } //end of the function BotLoadMatchTemplates //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int StringsMatch(bot_matchpiece_t *pieces, bot_match_t *match) { int lastvariable, index; char *strptr, *newstrptr; bot_matchpiece_t *mp; bot_matchstring_t *ms; //no last variable lastvariable = -1; //pointer to the string to compare the match string with strptr = match->string; //Log_Write("match: %s", strptr); //compare the string with the current match string for (mp = pieces; mp; mp = mp->next) { //if it is a piece of string if (mp->type == MT_STRING) { newstrptr = NULL; for (ms = mp->firststring; ms; ms = ms->next) { if (!strlen(ms->string)) { newstrptr = strptr; break; } //end if //Log_Write("MT_STRING: %s", mp->string); index = StringContains(strptr, ms->string, qfalse); if (index >= 0) { newstrptr = strptr + index; if (lastvariable >= 0) { match->variables[lastvariable].length = (newstrptr - match->string) - match->variables[lastvariable].offset; //newstrptr - match->variables[lastvariable].ptr; lastvariable = -1; break; } //end if else if (index == 0) { break; } //end else newstrptr = NULL; } //end if } //end for if (!newstrptr) return qfalse; strptr = newstrptr + strlen(ms->string); } //end if //if it is a variable piece of string else if (mp->type == MT_VARIABLE) { //Log_Write("MT_VARIABLE"); match->variables[mp->variable].offset = strptr - match->string; lastvariable = mp->variable; } //end else if } //end for //if a match was found if (!mp && (lastvariable >= 0 || !strlen(strptr))) { //if the last piece was a variable string if (lastvariable >= 0) { assert( match->variables[lastvariable].offset >= 0 ); // bk001204 match->variables[lastvariable].length = strlen(&match->string[ (int) match->variables[lastvariable].offset]); } //end if return qtrue; } //end if return qfalse; } //end of the function StringsMatch //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotFindMatch(char *str, bot_match_t *match, unsigned long int context) { int i; bot_matchtemplate_t *ms; strncpy(match->string, str, MAX_MESSAGE_SIZE); //remove any trailing enters while(strlen(match->string) && match->string[strlen(match->string)-1] == '\n') { match->string[strlen(match->string)-1] = '\0'; } //end while //compare the string with all the match strings for (ms = matchtemplates; ms; ms = ms->next) { if (!(ms->context & context)) continue; //reset the match variable offsets for (i = 0; i < MAX_MATCHVARIABLES; i++) match->variables[i].offset = -1; // if (StringsMatch(ms->first, match)) { match->type = ms->type; match->subtype = ms->subtype; return qtrue; } //end if } //end for return qfalse; } //end of the function BotFindMatch //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size) { if (variable < 0 || variable >= MAX_MATCHVARIABLES) { botimport.Print(PRT_FATAL, "BotMatchVariable: variable out of range\n"); strcpy(buf, ""); return; } //end if if (match->variables[variable].offset >= 0) { if (match->variables[variable].length < size) size = match->variables[variable].length+1; assert( match->variables[variable].offset >= 0 ); // bk001204 strncpy(buf, &match->string[ (int) match->variables[variable].offset], size-1); buf[size-1] = '\0'; } //end if else { strcpy(buf, ""); } //end else return; } //end of the function BotMatchVariable //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_stringlist_t *BotFindStringInList(bot_stringlist_t *list, char *string) { bot_stringlist_t *s; for (s = list; s; s = s->next) { if (!strcmp(s->string, string)) return s; } //end for return NULL; } //end of the function BotFindStringInList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_stringlist_t *BotCheckChatMessageIntegrety(char *message, bot_stringlist_t *stringlist) { int i; char *msgptr; char temp[MAX_MESSAGE_SIZE]; bot_stringlist_t *s; msgptr = message; // while(*msgptr) { if (*msgptr == ESCAPE_CHAR) { msgptr++; switch(*msgptr) { case 'v': //variable { //step over the 'v' msgptr++; while(*msgptr && *msgptr != ESCAPE_CHAR) msgptr++; //step over the trailing escape char if (*msgptr) msgptr++; break; } //end case case 'r': //random { //step over the 'r' msgptr++; for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) { temp[i] = *msgptr++; } //end while temp[i] = '\0'; //step over the trailing escape char if (*msgptr) msgptr++; //find the random keyword if (!RandomString(temp)) { if (!BotFindStringInList(stringlist, temp)) { Log_Write("%s = {\"%s\"} //MISSING RANDOM\r\n", temp, temp); s = GetClearedMemory(sizeof(bot_stringlist_t) + strlen(temp) + 1); s->string = (char *) s + sizeof(bot_stringlist_t); strcpy(s->string, temp); s->next = stringlist; stringlist = s; } //end if } //end if break; } //end case default: { botimport.Print(PRT_FATAL, "BotCheckChatMessageIntegrety: message \"%s\" invalid escape char\n", message); break; } //end default } //end switch } //end if else { msgptr++; } //end else } //end while return stringlist; } //end of the function BotCheckChatMessageIntegrety //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotCheckInitialChatIntegrety(bot_chat_t *chat) { bot_chattype_t *t; bot_chatmessage_t *cm; bot_stringlist_t *stringlist, *s, *nexts; stringlist = NULL; for (t = chat->types; t; t = t->next) { for (cm = t->firstchatmessage; cm; cm = cm->next) { stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); } //end for } //end for for (s = stringlist; s; s = nexts) { nexts = s->next; FreeMemory(s); } //end for } //end of the function BotCheckInitialChatIntegrety //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotCheckReplyChatIntegrety(bot_replychat_t *replychat) { bot_replychat_t *rp; bot_chatmessage_t *cm; bot_stringlist_t *stringlist, *s, *nexts; stringlist = NULL; for (rp = replychat; rp; rp = rp->next) { for (cm = rp->firstchatmessage; cm; cm = cm->next) { stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); } //end for } //end for for (s = stringlist; s; s = nexts) { nexts = s->next; FreeMemory(s); } //end for } //end of the function BotCheckReplyChatIntegrety //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotDumpReplyChat(bot_replychat_t *replychat) { FILE *fp; bot_replychat_t *rp; bot_replychatkey_t *key; bot_chatmessage_t *cm; bot_matchpiece_t *mp; fp = Log_FilePointer(); if (!fp) return; fprintf(fp, "BotDumpReplyChat:\n"); for (rp = replychat; rp; rp = rp->next) { fprintf(fp, "["); for (key = rp->keys; key; key = key->next) { if (key->flags & RCKFL_AND) fprintf(fp, "&"); else if (key->flags & RCKFL_NOT) fprintf(fp, "!"); // if (key->flags & RCKFL_NAME) fprintf(fp, "name"); else if (key->flags & RCKFL_GENDERFEMALE) fprintf(fp, "female"); else if (key->flags & RCKFL_GENDERMALE) fprintf(fp, "male"); else if (key->flags & RCKFL_GENDERLESS) fprintf(fp, "it"); else if (key->flags & RCKFL_VARIABLES) { fprintf(fp, "("); for (mp = key->match; mp; mp = mp->next) { if (mp->type == MT_STRING) fprintf(fp, "\"%s\"", mp->firststring->string); else fprintf(fp, "%d", mp->variable); if (mp->next) fprintf(fp, ", "); } //end for fprintf(fp, ")"); } //end if else if (key->flags & RCKFL_STRING) { fprintf(fp, "\"%s\"", key->string); } //end if if (key->next) fprintf(fp, ", "); else fprintf(fp, "] = %1.0f\n", rp->priority); } //end for fprintf(fp, "{\n"); for (cm = rp->firstchatmessage; cm; cm = cm->next) { fprintf(fp, "\t\"%s\";\n", cm->chatmessage); } //end for fprintf(fp, "}\n"); } //end for } //end of the function BotDumpReplyChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotFreeReplyChat(bot_replychat_t *replychat) { bot_replychat_t *rp, *nextrp; bot_replychatkey_t *key, *nextkey; bot_chatmessage_t *cm, *nextcm; for (rp = replychat; rp; rp = nextrp) { nextrp = rp->next; for (key = rp->keys; key; key = nextkey) { nextkey = key->next; if (key->match) BotFreeMatchPieces(key->match); if (key->string) FreeMemory(key->string); FreeMemory(key); } //end for for (cm = rp->firstchatmessage; cm; cm = nextcm) { nextcm = cm->next; FreeMemory(cm); } //end for FreeMemory(rp); } //end for } //end of the function BotFreeReplyChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotCheckValidReplyChatKeySet(source_t *source, bot_replychatkey_t *keys) { int allprefixed, hasvariableskey, hasstringkey; bot_matchpiece_t *m; bot_matchstring_t *ms; bot_replychatkey_t *key, *key2; // allprefixed = qtrue; hasvariableskey = hasstringkey = qfalse; for (key = keys; key; key = key->next) { if (!(key->flags & (RCKFL_AND|RCKFL_NOT))) { allprefixed = qfalse; if (key->flags & RCKFL_VARIABLES) { for (m = key->match; m; m = m->next) { if (m->type == MT_VARIABLE) hasvariableskey = qtrue; } //end for } //end if else if (key->flags & RCKFL_STRING) { hasstringkey = qtrue; } //end else if } //end if else if ((key->flags & RCKFL_AND) && (key->flags & RCKFL_STRING)) { for (key2 = keys; key2; key2 = key2->next) { if (key2 == key) continue; if (key2->flags & RCKFL_NOT) continue; if (key2->flags & RCKFL_VARIABLES) { for (m = key2->match; m; m = m->next) { if (m->type == MT_STRING) { for (ms = m->firststring; ms; ms = ms->next) { if (StringContains(ms->string, key->string, qfalse) != -1) { break; } //end if } //end for if (ms) break; } //end if else if (m->type == MT_VARIABLE) { break; } //end if } //end for if (!m) { SourceWarning(source, "one of the match templates does not " "leave space for the key %s with the & prefix", key->string); } //end if } //end if } //end for } //end else if ((key->flags & RCKFL_NOT) && (key->flags & RCKFL_STRING)) { for (key2 = keys; key2; key2 = key2->next) { if (key2 == key) continue; if (key2->flags & RCKFL_NOT) continue; if (key2->flags & RCKFL_STRING) { if (StringContains(key2->string, key->string, qfalse) != -1) { SourceWarning(source, "the key %s with prefix ! is inside the key %s", key->string, key2->string); } //end if } //end if else if (key2->flags & RCKFL_VARIABLES) { for (m = key2->match; m; m = m->next) { if (m->type == MT_STRING) { for (ms = m->firststring; ms; ms = ms->next) { if (StringContains(ms->string, key->string, qfalse) != -1) { SourceWarning(source, "the key %s with prefix ! is inside " "the match template string %s", key->string, ms->string); } //end if } //end for } //end if } //end for } //end else if } //end for } //end if } //end for if (allprefixed) SourceWarning(source, "all keys have a & or ! prefix"); if (hasvariableskey && hasstringkey) { SourceWarning(source, "variables from the match template(s) could be " "invalid when outputting one of the chat messages"); } //end if } //end of the function BotCheckValidReplyChatKeySet //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_replychat_t *BotLoadReplyChat(char *filename) { char chatmessagestring[MAX_MESSAGE_SIZE]; char namebuffer[MAX_MESSAGE_SIZE]; source_t *source; token_t token; bot_chatmessage_t *chatmessage = NULL; bot_replychat_t *replychat, *replychatlist; bot_replychatkey_t *key; PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(filename); if (!source) { botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); return NULL; } //end if // replychatlist = NULL; // while(PC_ReadToken(source, &token)) { if (strcmp(token.string, "[")) { SourceError(source, "expected [, found %s", token.string); BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if // replychat = GetClearedHunkMemory(sizeof(bot_replychat_t)); replychat->keys = NULL; replychat->next = replychatlist; replychatlist = replychat; //read the keys, there must be at least one key do { //allocate a key key = (bot_replychatkey_t *) GetClearedHunkMemory(sizeof(bot_replychatkey_t)); key->flags = 0; key->string = NULL; key->match = NULL; key->next = replychat->keys; replychat->keys = key; //check for MUST BE PRESENT and MUST BE ABSENT keys if (PC_CheckTokenString(source, "&")) key->flags |= RCKFL_AND; else if (PC_CheckTokenString(source, "!")) key->flags |= RCKFL_NOT; //special keys if (PC_CheckTokenString(source, "name")) key->flags |= RCKFL_NAME; else if (PC_CheckTokenString(source, "female")) key->flags |= RCKFL_GENDERFEMALE; else if (PC_CheckTokenString(source, "male")) key->flags |= RCKFL_GENDERMALE; else if (PC_CheckTokenString(source, "it")) key->flags |= RCKFL_GENDERLESS; else if (PC_CheckTokenString(source, "(")) //match key { key->flags |= RCKFL_VARIABLES; key->match = BotLoadMatchPieces(source, ")"); if (!key->match) { BotFreeReplyChat(replychatlist); return NULL; } //end if } //end else if else if (PC_CheckTokenString(source, "<")) //bot names { key->flags |= RCKFL_BOTNAMES; strcpy(namebuffer, ""); do { if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if StripDoubleQuotes(token.string); if (strlen(namebuffer)) strcat(namebuffer, "\\"); strcat(namebuffer, token.string); } while(PC_CheckTokenString(source, ",")); if (!PC_ExpectTokenString(source, ">")) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if key->string = (char *) GetClearedHunkMemory(strlen(namebuffer) + 1); strcpy(key->string, namebuffer); } //end else if else //normal string key { key->flags |= RCKFL_STRING; if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if StripDoubleQuotes(token.string); key->string = (char *) GetClearedHunkMemory(strlen(token.string) + 1); strcpy(key->string, token.string); } //end else // PC_CheckTokenString(source, ","); } while(!PC_CheckTokenString(source, "]")); // BotCheckValidReplyChatKeySet(source, replychat->keys); //read the = sign and the priority if (!PC_ExpectTokenString(source, "=") || !PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if replychat->priority = token.floatvalue; //read the leading { if (!PC_ExpectTokenString(source, "{")) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if replychat->numchatmessages = 0; //while the trailing } is not found while(!PC_CheckTokenString(source, "}")) { if (!BotLoadChatMessage(source, chatmessagestring)) { BotFreeReplyChat(replychatlist); FreeSource(source); return NULL; } //end if chatmessage = (bot_chatmessage_t *) GetClearedHunkMemory(sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1); chatmessage->chatmessage = (char *) chatmessage + sizeof(bot_chatmessage_t); strcpy(chatmessage->chatmessage, chatmessagestring); chatmessage->time = -2*CHATMESSAGE_RECENTTIME; chatmessage->next = replychat->firstchatmessage; //add the chat message to the reply chat replychat->firstchatmessage = chatmessage; replychat->numchatmessages++; } //end while } //end while FreeSource(source); botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); // //BotDumpReplyChat(replychatlist); if (bot_developer) { BotCheckReplyChatIntegrety(replychatlist); } //end if // if (!replychatlist) botimport.Print(PRT_MESSAGE, "no rchats\n"); // return replychatlist; } //end of the function BotLoadReplyChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotDumpInitialChat(bot_chat_t *chat) { bot_chattype_t *t; bot_chatmessage_t *m; Log_Write("{"); for (t = chat->types; t; t = t->next) { Log_Write(" type \"%s\"", t->name); Log_Write(" {"); Log_Write(" numchatmessages = %d", t->numchatmessages); for (m = t->firstchatmessage; m; m = m->next) { Log_Write(" \"%s\"", m->chatmessage); } //end for Log_Write(" }"); } //end for Log_Write("}"); } //end of the function BotDumpInitialChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_chat_t *BotLoadInitialChat(char *chatfile, char *chatname) { int pass, foundchat, indent, size; char *ptr = NULL; char chatmessagestring[MAX_MESSAGE_SIZE]; source_t *source; token_t token; bot_chat_t *chat = NULL; bot_chattype_t *chattype = NULL; bot_chatmessage_t *chatmessage = NULL; #ifdef DEBUG int starttime; starttime = Sys_MilliSeconds(); #endif //DEBUG // size = 0; foundchat = qfalse; //a bot chat is parsed in two phases for (pass = 0; pass < 2; pass++) { //allocate memory if (pass && size) ptr = (char *) GetClearedMemory(size); //load the source file PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(chatfile); if (!source) { botimport.Print(PRT_ERROR, "counldn't load %s\n", chatfile); return NULL; } //end if //chat structure if (pass) { chat = (bot_chat_t *) ptr; ptr += sizeof(bot_chat_t); } //end if size = sizeof(bot_chat_t); // while(PC_ReadToken(source, &token)) { if (!strcmp(token.string, "chat")) { if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) { FreeSource(source); return NULL; } //end if StripDoubleQuotes(token.string); //after the chat name we expect a opening brace if (!PC_ExpectTokenString(source, "{")) { FreeSource(source); return NULL; } //end if //if the chat name is found if (!Q_stricmp(token.string, chatname)) { foundchat = qtrue; //read the chat types while(1) { if (!PC_ExpectAnyToken(source, &token)) { FreeSource(source); return NULL; } //end if if (!strcmp(token.string, "}")) break; if (strcmp(token.string, "type")) { SourceError(source, "expected type found %s\n", token.string); FreeSource(source); return NULL; } //end if //expect the chat type name if (!PC_ExpectTokenType(source, TT_STRING, 0, &token) || !PC_ExpectTokenString(source, "{")) { FreeSource(source); return NULL; } //end if StripDoubleQuotes(token.string); if (pass) { chattype = (bot_chattype_t *) ptr; strncpy(chattype->name, token.string, MAX_CHATTYPE_NAME); chattype->firstchatmessage = NULL; //add the chat type to the chat chattype->next = chat->types; chat->types = chattype; // ptr += sizeof(bot_chattype_t); } //end if size += sizeof(bot_chattype_t); //read the chat messages while(!PC_CheckTokenString(source, "}")) { if (!BotLoadChatMessage(source, chatmessagestring)) { FreeSource(source); return NULL; } //end if if (pass) { chatmessage = (bot_chatmessage_t *) ptr; chatmessage->time = -2*CHATMESSAGE_RECENTTIME; //put the chat message in the list chatmessage->next = chattype->firstchatmessage; chattype->firstchatmessage = chatmessage; //store the chat message ptr += sizeof(bot_chatmessage_t); chatmessage->chatmessage = ptr; strcpy(chatmessage->chatmessage, chatmessagestring); ptr += strlen(chatmessagestring) + 1; //the number of chat messages increased chattype->numchatmessages++; } //end if size += sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1; } //end if } //end while } //end if else //skip the bot chat { indent = 1; while(indent) { if (!PC_ExpectAnyToken(source, &token)) { FreeSource(source); return NULL; } //end if if (!strcmp(token.string, "{")) indent++; else if (!strcmp(token.string, "}")) indent--; } //end while } //end else } //end if else { SourceError(source, "unknown definition %s\n", token.string); FreeSource(source); return NULL; } //end else } //end while //free the source FreeSource(source); //if the requested character is not found if (!foundchat) { botimport.Print(PRT_ERROR, "couldn't find chat %s in %s\n", chatname, chatfile); return NULL; } //end if } //end for // botimport.Print(PRT_MESSAGE, "loaded %s from %s\n", chatname, chatfile); // //BotDumpInitialChat(chat); if (bot_developer) { BotCheckInitialChatIntegrety(chat); } //end if #ifdef DEBUG botimport.Print(PRT_MESSAGE, "initial chats loaded in %d msec\n", Sys_MilliSeconds() - starttime); #endif //DEBUG //character was read succesfully return chat; } //end of the function BotLoadInitialChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotFreeChatFile(int chatstate) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; if (cs->chat) FreeMemory(cs->chat); cs->chat = NULL; } //end of the function BotFreeChatFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadChatFile(int chatstate, char *chatfile, char *chatname) { bot_chatstate_t *cs; int n, avail = 0; cs = BotChatStateFromHandle(chatstate); if (!cs) return BLERR_CANNOTLOADICHAT; BotFreeChatFile(chatstate); if (!LibVarGetValue("bot_reloadcharacters")) { avail = -1; for( n = 0; n < MAX_CLIENTS; n++ ) { if( !ichatdata[n] ) { if( avail == -1 ) { avail = n; } continue; } if( strcmp( chatfile, ichatdata[n]->filename ) != 0 ) { continue; } if( strcmp( chatname, ichatdata[n]->chatname ) != 0 ) { continue; } cs->chat = ichatdata[n]->chat; // botimport.Print( PRT_MESSAGE, "retained %s from %s\n", chatname, chatfile ); return BLERR_NOERROR; } if( avail == -1 ) { botimport.Print(PRT_FATAL, "ichatdata table full; couldn't load chat %s from %s\n", chatname, chatfile); return BLERR_CANNOTLOADICHAT; } } cs->chat = BotLoadInitialChat(chatfile, chatname); if (!cs->chat) { botimport.Print(PRT_FATAL, "couldn't load chat %s from %s\n", chatname, chatfile); return BLERR_CANNOTLOADICHAT; } //end if if (!LibVarGetValue("bot_reloadcharacters")) { ichatdata[avail] = GetClearedMemory( sizeof(bot_ichatdata_t) ); ichatdata[avail]->chat = cs->chat; Q_strncpyz( ichatdata[avail]->chatname, chatname, sizeof(ichatdata[avail]->chatname) ); Q_strncpyz( ichatdata[avail]->filename, chatfile, sizeof(ichatdata[avail]->filename) ); } //end if return BLERR_NOERROR; } //end of the function BotLoadChatFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotExpandChatMessage(char *outmessage, char *message, unsigned long mcontext, bot_match_t *match, unsigned long vcontext, int reply) { int num, len, i, expansion; char *outputbuf, *ptr, *msgptr; char temp[MAX_MESSAGE_SIZE]; expansion = qfalse; msgptr = message; outputbuf = outmessage; len = 0; // while(*msgptr) { if (*msgptr == ESCAPE_CHAR) { msgptr++; switch(*msgptr) { case 'v': //variable { msgptr++; num = 0; while(*msgptr && *msgptr != ESCAPE_CHAR) { num = num * 10 + (*msgptr++) - '0'; } //end while //step over the trailing escape char if (*msgptr) msgptr++; if (num > MAX_MATCHVARIABLES) { botimport.Print(PRT_ERROR, "BotConstructChat: message %s variable %d out of range\n", message, num); return qfalse; } //end if if (match->variables[num].offset >= 0) { assert( match->variables[num].offset >= 0 ); // bk001204 ptr = &match->string[ (int) match->variables[num].offset]; for (i = 0; i < match->variables[num].length; i++) { temp[i] = ptr[i]; } //end for temp[i] = 0; //if it's a reply message if (reply) { //replace the reply synonyms in the variables BotReplaceReplySynonyms(temp, vcontext); } //end if else { //replace synonyms in the variable context BotReplaceSynonyms(temp, vcontext); } //end else // if (len + strlen(temp) >= MAX_MESSAGE_SIZE) { botimport.Print(PRT_ERROR, "BotConstructChat: message %s too long\n", message); return qfalse; } //end if strcpy(&outputbuf[len], temp); len += strlen(temp); } //end if break; } //end case case 'r': //random { msgptr++; for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) { temp[i] = *msgptr++; } //end while temp[i] = '\0'; //step over the trailing escape char if (*msgptr) msgptr++; //find the random keyword ptr = RandomString(temp); if (!ptr) { botimport.Print(PRT_ERROR, "BotConstructChat: unknown random string %s\n", temp); return qfalse; } //end if if (len + strlen(ptr) >= MAX_MESSAGE_SIZE) { botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); return qfalse; } //end if strcpy(&outputbuf[len], ptr); len += strlen(ptr); expansion = qtrue; break; } //end case default: { botimport.Print(PRT_FATAL, "BotConstructChat: message \"%s\" invalid escape char\n", message); break; } //end default } //end switch } //end if else { outputbuf[len++] = *msgptr++; if (len >= MAX_MESSAGE_SIZE) { botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); break; } //end if } //end else } //end while outputbuf[len] = '\0'; //replace synonyms weighted in the message context BotReplaceWeightedSynonyms(outputbuf, mcontext); //return true if a random was expanded return expansion; } //end of the function BotExpandChatMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotConstructChatMessage(bot_chatstate_t *chatstate, char *message, unsigned long mcontext, bot_match_t *match, unsigned long vcontext, int reply) { int i; char srcmessage[MAX_MESSAGE_SIZE]; strcpy(srcmessage, message); for (i = 0; i < 10; i++) { if (!BotExpandChatMessage(chatstate->chatmessage, srcmessage, mcontext, match, vcontext, reply)) { break; } //end if strcpy(srcmessage, chatstate->chatmessage); } //end for if (i >= 10) { botimport.Print(PRT_WARNING, "too many expansions in chat message\n"); botimport.Print(PRT_WARNING, "%s\n", chatstate->chatmessage); } //end if } //end of the function BotConstructChatMessage //=========================================================================== // randomly chooses one of the chat message of the given type // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *BotChooseInitialChatMessage(bot_chatstate_t *cs, char *type) { int n, numchatmessages; float besttime; bot_chattype_t *t; bot_chatmessage_t *m, *bestchatmessage; bot_chat_t *chat; chat = cs->chat; for (t = chat->types; t; t = t->next) { if (!Q_stricmp(t->name, type)) { numchatmessages = 0; for (m = t->firstchatmessage; m; m = m->next) { if (m->time > AAS_Time()) continue; numchatmessages++; } //end if //if all chat messages have been used recently if (numchatmessages <= 0) { besttime = 0; bestchatmessage = NULL; for (m = t->firstchatmessage; m; m = m->next) { if (!besttime || m->time < besttime) { bestchatmessage = m; besttime = m->time; } //end if } //end for if (bestchatmessage) return bestchatmessage->chatmessage; } //end if else //choose a chat message randomly { n = random() * numchatmessages; for (m = t->firstchatmessage; m; m = m->next) { if (m->time > AAS_Time()) continue; if (--n < 0) { m->time = AAS_Time() + CHATMESSAGE_RECENTTIME; return m->chatmessage; } //end if } //end for } //end else return NULL; } //end if } //end for return NULL; } //end of the function BotChooseInitialChatMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotNumInitialChats(int chatstate, char *type) { bot_chatstate_t *cs; bot_chattype_t *t; cs = BotChatStateFromHandle(chatstate); if (!cs) return 0; for (t = cs->chat->types; t; t = t->next) { if (!Q_stricmp(t->name, type)) { if (LibVarGetValue("bot_testichat")) { botimport.Print(PRT_MESSAGE, "%s has %d chat lines\n", type, t->numchatmessages); botimport.Print(PRT_MESSAGE, "-------------------\n"); } return t->numchatmessages; } //end if } //end for return 0; } //end of the function BotNumInitialChats //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7) { char *message; int index; bot_match_t match; bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; //if no chat file is loaded if (!cs->chat) return; //choose a chat message randomly of the given type message = BotChooseInitialChatMessage(cs, type); //if there's no message of the given type if (!message) { #ifdef DEBUG botimport.Print(PRT_MESSAGE, "no chat messages of type %s\n", type); #endif //DEBUG return; } //end if // Com_Memset(&match, 0, sizeof(match)); index = 0; if( var0 ) { strcat(match.string, var0); match.variables[0].offset = index; match.variables[0].length = strlen(var0); index += strlen(var0); } if( var1 ) { strcat(match.string, var1); match.variables[1].offset = index; match.variables[1].length = strlen(var1); index += strlen(var1); } if( var2 ) { strcat(match.string, var2); match.variables[2].offset = index; match.variables[2].length = strlen(var2); index += strlen(var2); } if( var3 ) { strcat(match.string, var3); match.variables[3].offset = index; match.variables[3].length = strlen(var3); index += strlen(var3); } if( var4 ) { strcat(match.string, var4); match.variables[4].offset = index; match.variables[4].length = strlen(var4); index += strlen(var4); } if( var5 ) { strcat(match.string, var5); match.variables[5].offset = index; match.variables[5].length = strlen(var5); index += strlen(var5); } if( var6 ) { strcat(match.string, var6); match.variables[6].offset = index; match.variables[6].length = strlen(var6); index += strlen(var6); } if( var7 ) { strcat(match.string, var7); match.variables[7].offset = index; match.variables[7].length = strlen(var7); index += strlen(var7); } // BotConstructChatMessage(cs, message, mcontext, &match, 0, qfalse); } //end of the function BotInitialChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotPrintReplyChatKeys(bot_replychat_t *replychat) { bot_replychatkey_t *key; bot_matchpiece_t *mp; botimport.Print(PRT_MESSAGE, "["); for (key = replychat->keys; key; key = key->next) { if (key->flags & RCKFL_AND) botimport.Print(PRT_MESSAGE, "&"); else if (key->flags & RCKFL_NOT) botimport.Print(PRT_MESSAGE, "!"); // if (key->flags & RCKFL_NAME) botimport.Print(PRT_MESSAGE, "name"); else if (key->flags & RCKFL_GENDERFEMALE) botimport.Print(PRT_MESSAGE, "female"); else if (key->flags & RCKFL_GENDERMALE) botimport.Print(PRT_MESSAGE, "male"); else if (key->flags & RCKFL_GENDERLESS) botimport.Print(PRT_MESSAGE, "it"); else if (key->flags & RCKFL_VARIABLES) { botimport.Print(PRT_MESSAGE, "("); for (mp = key->match; mp; mp = mp->next) { if (mp->type == MT_STRING) botimport.Print(PRT_MESSAGE, "\"%s\"", mp->firststring->string); else botimport.Print(PRT_MESSAGE, "%d", mp->variable); if (mp->next) botimport.Print(PRT_MESSAGE, ", "); } //end for botimport.Print(PRT_MESSAGE, ")"); } //end if else if (key->flags & RCKFL_STRING) { botimport.Print(PRT_MESSAGE, "\"%s\"", key->string); } //end if if (key->next) botimport.Print(PRT_MESSAGE, ", "); else botimport.Print(PRT_MESSAGE, "] = %1.0f\n", replychat->priority); } //end for botimport.Print(PRT_MESSAGE, "{\n"); } //end of the function BotPrintReplyChatKeys //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7) { bot_replychat_t *rchat, *bestrchat; bot_replychatkey_t *key; bot_chatmessage_t *m, *bestchatmessage; bot_match_t match, bestmatch; int bestpriority, num, found, res, numchatmessages, index; bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return qfalse; Com_Memset(&match, 0, sizeof(bot_match_t)); strcpy(match.string, message); bestpriority = -1; bestchatmessage = NULL; bestrchat = NULL; //go through all the reply chats for (rchat = replychats; rchat; rchat = rchat->next) { found = qfalse; for (key = rchat->keys; key; key = key->next) { res = qfalse; //get the match result if (key->flags & RCKFL_NAME) res = (StringContains(message, cs->name, qfalse) != -1); else if (key->flags & RCKFL_BOTNAMES) res = (StringContains(key->string, cs->name, qfalse) != -1); else if (key->flags & RCKFL_GENDERFEMALE) res = (cs->gender == CHAT_GENDERFEMALE); else if (key->flags & RCKFL_GENDERMALE) res = (cs->gender == CHAT_GENDERMALE); else if (key->flags & RCKFL_GENDERLESS) res = (cs->gender == CHAT_GENDERLESS); else if (key->flags & RCKFL_VARIABLES) res = StringsMatch(key->match, &match); else if (key->flags & RCKFL_STRING) res = (StringContainsWord(message, key->string, qfalse) != NULL); //if the key must be present if (key->flags & RCKFL_AND) { if (!res) { found = qfalse; break; } //end if } //end else if //if the key must be absent else if (key->flags & RCKFL_NOT) { if (res) { found = qfalse; break; } //end if } //end if else if (res) { found = qtrue; } //end else } //end for // if (found) { if (rchat->priority > bestpriority) { numchatmessages = 0; for (m = rchat->firstchatmessage; m; m = m->next) { if (m->time > AAS_Time()) continue; numchatmessages++; } //end if num = random() * numchatmessages; for (m = rchat->firstchatmessage; m; m = m->next) { if (--num < 0) break; if (m->time > AAS_Time()) continue; } //end for //if the reply chat has a message if (m) { Com_Memcpy(&bestmatch, &match, sizeof(bot_match_t)); bestchatmessage = m; bestrchat = rchat; bestpriority = rchat->priority; } //end if } //end if } //end if } //end for if (bestchatmessage) { index = strlen(bestmatch.string); if( var0 ) { strcat(bestmatch.string, var0); bestmatch.variables[0].offset = index; bestmatch.variables[0].length = strlen(var0); index += strlen(var0); } if( var1 ) { strcat(bestmatch.string, var1); bestmatch.variables[1].offset = index; bestmatch.variables[1].length = strlen(var1); index += strlen(var1); } if( var2 ) { strcat(bestmatch.string, var2); bestmatch.variables[2].offset = index; bestmatch.variables[2].length = strlen(var2); index += strlen(var2); } if( var3 ) { strcat(bestmatch.string, var3); bestmatch.variables[3].offset = index; bestmatch.variables[3].length = strlen(var3); index += strlen(var3); } if( var4 ) { strcat(bestmatch.string, var4); bestmatch.variables[4].offset = index; bestmatch.variables[4].length = strlen(var4); index += strlen(var4); } if( var5 ) { strcat(bestmatch.string, var5); bestmatch.variables[5].offset = index; bestmatch.variables[5].length = strlen(var5); index += strlen(var5); } if( var6 ) { strcat(bestmatch.string, var6); bestmatch.variables[6].offset = index; bestmatch.variables[6].length = strlen(var6); index += strlen(var6); } if( var7 ) { strcat(bestmatch.string, var7); bestmatch.variables[7].offset = index; bestmatch.variables[7].length = strlen(var7); index += strlen(var7); } if (LibVarGetValue("bot_testrchat")) { for (m = bestrchat->firstchatmessage; m; m = m->next) { BotConstructChatMessage(cs, m->chatmessage, mcontext, &bestmatch, vcontext, qtrue); BotRemoveTildes(cs->chatmessage); botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); } //end if } //end if else { bestchatmessage->time = AAS_Time() + CHATMESSAGE_RECENTTIME; BotConstructChatMessage(cs, bestchatmessage->chatmessage, mcontext, &bestmatch, vcontext, qtrue); } //end else return qtrue; } //end if return qfalse; } //end of the function BotReplyChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotChatLength(int chatstate) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return 0; return strlen(cs->chatmessage); } //end of the function BotChatLength //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotEnterChat(int chatstate, int clientto, int sendto) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; if (strlen(cs->chatmessage)) { BotRemoveTildes(cs->chatmessage); if (LibVarGetValue("bot_testichat")) { botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); } else { switch(sendto) { case CHAT_TEAM: EA_Command(cs->client, va("say_team %s", cs->chatmessage)); break; case CHAT_TELL: EA_Command(cs->client, va("tell %d %s", clientto, cs->chatmessage)); break; default: //CHAT_ALL EA_Command(cs->client, va("say %s", cs->chatmessage)); break; } } //clear the chat message from the state strcpy(cs->chatmessage, ""); } //end if } //end of the function BotEnterChat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotGetChatMessage(int chatstate, char *buf, int size) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; BotRemoveTildes(cs->chatmessage); strncpy(buf, cs->chatmessage, size-1); buf[size-1] = '\0'; //clear the chat message from the state strcpy(cs->chatmessage, ""); } //end of the function BotGetChatMessage //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotSetChatGender(int chatstate, int gender) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; switch(gender) { case CHAT_GENDERFEMALE: cs->gender = CHAT_GENDERFEMALE; break; case CHAT_GENDERMALE: cs->gender = CHAT_GENDERMALE; break; default: cs->gender = CHAT_GENDERLESS; break; } //end switch } //end of the function BotSetChatGender //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotSetChatName(int chatstate, char *name, int client) { bot_chatstate_t *cs; cs = BotChatStateFromHandle(chatstate); if (!cs) return; cs->client = client; Com_Memset(cs->name, 0, sizeof(cs->name)); strncpy(cs->name, name, sizeof(cs->name)); cs->name[sizeof(cs->name)-1] = '\0'; } //end of the function BotSetChatName //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotResetChatAI(void) { bot_replychat_t *rchat; bot_chatmessage_t *m; for (rchat = replychats; rchat; rchat = rchat->next) { for (m = rchat->firstchatmessage; m; m = m->next) { m->time = 0; } //end for } //end for } //end of the function BotResetChatAI //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== int BotAllocChatState(void) { int i; for (i = 1; i <= MAX_CLIENTS; i++) { if (!botchatstates[i]) { botchatstates[i] = GetClearedMemory(sizeof(bot_chatstate_t)); return i; } //end if } //end for return 0; } //end of the function BotAllocChatState //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeChatState(int handle) { bot_chatstate_t *cs; bot_consolemessage_t m; int h; if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); return; } //end if if (!botchatstates[handle]) { botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); return; } //end if cs = botchatstates[handle]; if (LibVarGetValue("bot_reloadcharacters")) { BotFreeChatFile(handle); } //end if //free all the console messages left in the chat state for (h = BotNextConsoleMessage(handle, &m); h; h = BotNextConsoleMessage(handle, &m)) { //remove the console message BotRemoveConsoleMessage(handle, h); } //end for FreeMemory(botchatstates[handle]); botchatstates[handle] = NULL; } //end of the function BotFreeChatState //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotSetupChatAI(void) { char *file; #ifdef DEBUG int starttime = Sys_MilliSeconds(); #endif //DEBUG file = LibVarString("synfile", "syn.c"); synonyms = BotLoadSynonyms(file); file = LibVarString("rndfile", "rnd.c"); randomstrings = BotLoadRandomStrings(file); file = LibVarString("matchfile", "match.c"); matchtemplates = BotLoadMatchTemplates(file); // if (!LibVarValue("nochat", "0")) { file = LibVarString("rchatfile", "rchat.c"); replychats = BotLoadReplyChat(file); } //end if InitConsoleMessageHeap(); #ifdef DEBUG botimport.Print(PRT_MESSAGE, "setup chat AI %d msec\n", Sys_MilliSeconds() - starttime); #endif //DEBUG return BLERR_NOERROR; } //end of the function BotSetupChatAI //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotShutdownChatAI(void) { int i; //free all remaining chat states for(i = 0; i < MAX_CLIENTS; i++) { if (botchatstates[i]) { BotFreeChatState(i); } //end if } //end for //free all cached chats for(i = 0; i < MAX_CLIENTS; i++) { if (ichatdata[i]) { FreeMemory(ichatdata[i]->chat); FreeMemory(ichatdata[i]); ichatdata[i] = NULL; } //end if } //end for if (consolemessageheap) FreeMemory(consolemessageheap); consolemessageheap = NULL; if (matchtemplates) BotFreeMatchTemplates(matchtemplates); matchtemplates = NULL; if (randomstrings) FreeMemory(randomstrings); randomstrings = NULL; if (synonyms) FreeMemory(synonyms); synonyms = NULL; if (replychats) BotFreeReplyChat(replychats); replychats = NULL; } //end of the function BotShutdownChatAI ================================================ FILE: code/botlib/be_ai_gen.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_ai_gen.c * * desc: genetic selection * * $Archive: /MissionPack/code/botlib/be_ai_gen.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_log.h" #include "l_utils.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "../game/be_ai_gen.h" //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int GeneticSelection(int numranks, float *rankings) { float sum, select; int i, index; sum = 0; for (i = 0; i < numranks; i++) { if (rankings[i] < 0) continue; sum += rankings[i]; } //end for if (sum > 0) { //select a bot where the ones with the higest rankings have //the highest chance of being selected select = random() * sum; for (i = 0; i < numranks; i++) { if (rankings[i] < 0) continue; sum -= rankings[i]; if (sum <= 0) return i; } //end for } //end if //select a bot randomly index = random() * numranks; for (i = 0; i < numranks; i++) { if (rankings[index] >= 0) return index; index = (index + 1) % numranks; } //end for return 0; } //end of the function GeneticSelection //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child) { float rankings[256], max; int i; if (numranks > 256) { botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too many bots\n"); *parent1 = *parent2 = *child = 0; return qfalse; } //end if for (max = 0, i = 0; i < numranks; i++) { if (ranks[i] < 0) continue; max++; } //end for if (max < 3) { botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too few valid bots\n"); *parent1 = *parent2 = *child = 0; return qfalse; } //end if Com_Memcpy(rankings, ranks, sizeof(float) * numranks); //select first parent *parent1 = GeneticSelection(numranks, rankings); rankings[*parent1] = -1; //select second parent *parent2 = GeneticSelection(numranks, rankings); rankings[*parent2] = -1; //reverse the rankings max = 0; for (i = 0; i < numranks; i++) { if (rankings[i] < 0) continue; if (rankings[i] > max) max = rankings[i]; } //end for for (i = 0; i < numranks; i++) { if (rankings[i] < 0) continue; rankings[i] = max - rankings[i]; } //end for //select child *child = GeneticSelection(numranks, rankings); return qtrue; } //end of the function GeneticParentsAndChildSelection ================================================ FILE: code/botlib/be_ai_goal.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_ai_goal.c * * desc: goal AI * * $Archive: /MissionPack/code/botlib/be_ai_goal.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_utils.h" #include "l_libvar.h" #include "l_memory.h" #include "l_log.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "be_ai_weight.h" #include "../game/be_ai_goal.h" #include "../game/be_ai_move.h" //#define DEBUG_AI_GOAL #ifdef RANDOMIZE #define UNDECIDEDFUZZY #endif //RANDOMIZE #define DROPPEDWEIGHT //minimum avoid goal time #define AVOID_MINIMUM_TIME 10 //default avoid goal time #define AVOID_DEFAULT_TIME 30 //avoid dropped goal time #define AVOID_DROPPED_TIME 10 // #define TRAVELTIME_SCALE 0.01 //item flags #define IFL_NOTFREE 1 //not in free for all #define IFL_NOTTEAM 2 //not in team play #define IFL_NOTSINGLE 4 //not in single player #define IFL_NOTBOT 8 //bot should never go for this #define IFL_ROAM 16 //bot roam goal //location in the map "target_location" typedef struct maplocation_s { vec3_t origin; int areanum; char name[MAX_EPAIRKEY]; struct maplocation_s *next; } maplocation_t; //camp spots "info_camp" typedef struct campspot_s { vec3_t origin; int areanum; char name[MAX_EPAIRKEY]; float range; float weight; float wait; float random; struct campspot_s *next; } campspot_t; //FIXME: these are game specific typedef enum { GT_FFA, // free for all GT_TOURNAMENT, // one on one tournament GT_SINGLE_PLAYER, // single player tournament //-- team games go after this -- GT_TEAM, // team deathmatch GT_CTF, // capture the flag #ifdef MISSIONPACK GT_1FCTF, GT_OBELISK, GT_HARVESTER, #endif GT_MAX_GAME_TYPE } gametype_t; typedef struct levelitem_s { int number; //number of the level item int iteminfo; //index into the item info int flags; //item flags float weight; //fixed roam weight vec3_t origin; //origin of the item int goalareanum; //area the item is in vec3_t goalorigin; //goal origin within the area int entitynum; //entity number float timeout; //item is removed after this time struct levelitem_s *prev, *next; } levelitem_t; typedef struct iteminfo_s { char classname[32]; //classname of the item char name[MAX_STRINGFIELD]; //name of the item char model[MAX_STRINGFIELD]; //model of the item int modelindex; //model index int type; //item type int index; //index in the inventory float respawntime; //respawn time vec3_t mins; //mins of the item vec3_t maxs; //maxs of the item int number; //number of the item info } iteminfo_t; #define ITEMINFO_OFS(x) (int)&(((iteminfo_t *)0)->x) fielddef_t iteminfo_fields[] = { {"name", ITEMINFO_OFS(name), FT_STRING}, {"model", ITEMINFO_OFS(model), FT_STRING}, {"modelindex", ITEMINFO_OFS(modelindex), FT_INT}, {"type", ITEMINFO_OFS(type), FT_INT}, {"index", ITEMINFO_OFS(index), FT_INT}, {"respawntime", ITEMINFO_OFS(respawntime), FT_FLOAT}, {"mins", ITEMINFO_OFS(mins), FT_FLOAT|FT_ARRAY, 3}, {"maxs", ITEMINFO_OFS(maxs), FT_FLOAT|FT_ARRAY, 3}, {0, 0, 0} }; structdef_t iteminfo_struct = { sizeof(iteminfo_t), iteminfo_fields }; typedef struct itemconfig_s { int numiteminfo; iteminfo_t *iteminfo; } itemconfig_t; //goal state typedef struct bot_goalstate_s { struct weightconfig_s *itemweightconfig; //weight config int *itemweightindex; //index from item to weight // int client; //client using this goal state int lastreachabilityarea; //last area with reachabilities the bot was in // bot_goal_t goalstack[MAX_GOALSTACK]; //goal stack int goalstacktop; //the top of the goal stack // int avoidgoals[MAX_AVOIDGOALS]; //goals to avoid float avoidgoaltimes[MAX_AVOIDGOALS]; //times to avoid the goals } bot_goalstate_t; bot_goalstate_t *botgoalstates[MAX_CLIENTS + 1]; // bk001206 - FIXME: init? //item configuration itemconfig_t *itemconfig = NULL; // bk001206 - init //level items levelitem_t *levelitemheap = NULL; // bk001206 - init levelitem_t *freelevelitems = NULL; // bk001206 - init levelitem_t *levelitems = NULL; // bk001206 - init int numlevelitems = 0; //map locations maplocation_t *maplocations = NULL; // bk001206 - init //camp spots campspot_t *campspots = NULL; // bk001206 - init //the game type int g_gametype = 0; // bk001206 - init //additional dropped item weight libvar_t *droppedweight = NULL; // bk001206 - init //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== bot_goalstate_t *BotGoalStateFromHandle(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); return NULL; } //end if if (!botgoalstates[handle]) { botimport.Print(PRT_FATAL, "invalid goal state %d\n", handle); return NULL; } //end if return botgoalstates[handle]; } //end of the function BotGoalStateFromHandle //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child) { bot_goalstate_t *p1, *p2, *c; p1 = BotGoalStateFromHandle(parent1); p2 = BotGoalStateFromHandle(parent2); c = BotGoalStateFromHandle(child); InterbreedWeightConfigs(p1->itemweightconfig, p2->itemweightconfig, c->itemweightconfig); } //end of the function BotInterbreedingGoalFuzzyLogic //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotSaveGoalFuzzyLogic(int goalstate, char *filename) { bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); //WriteWeightConfig(filename, gs->itemweightconfig); } //end of the function BotSaveGoalFuzzyLogic //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotMutateGoalFuzzyLogic(int goalstate, float range) { bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); EvolveWeightConfig(gs->itemweightconfig); } //end of the function BotMutateGoalFuzzyLogic //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== itemconfig_t *LoadItemConfig(char *filename) { int max_iteminfo; token_t token; char path[MAX_PATH]; source_t *source; itemconfig_t *ic; iteminfo_t *ii; max_iteminfo = (int) LibVarValue("max_iteminfo", "256"); if (max_iteminfo < 0) { botimport.Print(PRT_ERROR, "max_iteminfo = %d\n", max_iteminfo); max_iteminfo = 256; LibVarSet( "max_iteminfo", "256" ); } strncpy( path, filename, MAX_PATH ); PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile( path ); if( !source ) { botimport.Print( PRT_ERROR, "counldn't load %s\n", path ); return NULL; } //end if //initialize item config ic = (itemconfig_t *) GetClearedHunkMemory(sizeof(itemconfig_t) + max_iteminfo * sizeof(iteminfo_t)); ic->iteminfo = (iteminfo_t *) ((char *) ic + sizeof(itemconfig_t)); ic->numiteminfo = 0; //parse the item config file while(PC_ReadToken(source, &token)) { if (!strcmp(token.string, "iteminfo")) { if (ic->numiteminfo >= max_iteminfo) { SourceError(source, "more than %d item info defined\n", max_iteminfo); FreeMemory(ic); FreeSource(source); return NULL; } //end if ii = &ic->iteminfo[ic->numiteminfo]; Com_Memset(ii, 0, sizeof(iteminfo_t)); if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) { FreeMemory(ic); FreeMemory(source); return NULL; } //end if StripDoubleQuotes(token.string); strncpy(ii->classname, token.string, sizeof(ii->classname)-1); if (!ReadStructure(source, &iteminfo_struct, (char *) ii)) { FreeMemory(ic); FreeSource(source); return NULL; } //end if ii->number = ic->numiteminfo; ic->numiteminfo++; } //end if else { SourceError(source, "unknown definition %s\n", token.string); FreeMemory(ic); FreeSource(source); return NULL; } //end else } //end while FreeSource(source); // if (!ic->numiteminfo) botimport.Print(PRT_WARNING, "no item info loaded\n"); botimport.Print(PRT_MESSAGE, "loaded %s\n", path); return ic; } //end of the function LoadItemConfig //=========================================================================== // index to find the weight function of an iteminfo // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int *ItemWeightIndex(weightconfig_t *iwc, itemconfig_t *ic) { int *index, i; //initialize item weight index index = (int *) GetClearedMemory(sizeof(int) * ic->numiteminfo); for (i = 0; i < ic->numiteminfo; i++) { index[i] = FindFuzzyWeight(iwc, ic->iteminfo[i].classname); if (index[i] < 0) { Log_Write("item info %d \"%s\" has no fuzzy weight\r\n", i, ic->iteminfo[i].classname); } //end if } //end for return index; } //end of the function ItemWeightIndex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void InitLevelItemHeap(void) { int i, max_levelitems; if (levelitemheap) FreeMemory(levelitemheap); max_levelitems = (int) LibVarValue("max_levelitems", "256"); levelitemheap = (levelitem_t *) GetClearedMemory(max_levelitems * sizeof(levelitem_t)); for (i = 0; i < max_levelitems-1; i++) { levelitemheap[i].next = &levelitemheap[i + 1]; } //end for levelitemheap[max_levelitems-1].next = NULL; // freelevelitems = levelitemheap; } //end of the function InitLevelItemHeap //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== levelitem_t *AllocLevelItem(void) { levelitem_t *li; li = freelevelitems; if (!li) { botimport.Print(PRT_FATAL, "out of level items\n"); return NULL; } //end if // freelevelitems = freelevelitems->next; Com_Memset(li, 0, sizeof(levelitem_t)); return li; } //end of the function AllocLevelItem //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeLevelItem(levelitem_t *li) { li->next = freelevelitems; freelevelitems = li; } //end of the function FreeLevelItem //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AddLevelItemToList(levelitem_t *li) { if (levelitems) levelitems->prev = li; li->prev = NULL; li->next = levelitems; levelitems = li; } //end of the function AddLevelItemToList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RemoveLevelItemFromList(levelitem_t *li) { if (li->prev) li->prev->next = li->next; else levelitems = li->next; if (li->next) li->next->prev = li->prev; } //end of the function RemoveLevelItemFromList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotFreeInfoEntities(void) { maplocation_t *ml, *nextml; campspot_t *cs, *nextcs; for (ml = maplocations; ml; ml = nextml) { nextml = ml->next; FreeMemory(ml); } //end for maplocations = NULL; for (cs = campspots; cs; cs = nextcs) { nextcs = cs->next; FreeMemory(cs); } //end for campspots = NULL; } //end of the function BotFreeInfoEntities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotInitInfoEntities(void) { char classname[MAX_EPAIRKEY]; maplocation_t *ml; campspot_t *cs; int ent, numlocations, numcampspots; BotFreeInfoEntities(); // numlocations = 0; numcampspots = 0; for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) { if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; //map locations if (!strcmp(classname, "target_location")) { ml = (maplocation_t *) GetClearedMemory(sizeof(maplocation_t)); AAS_VectorForBSPEpairKey(ent, "origin", ml->origin); AAS_ValueForBSPEpairKey(ent, "message", ml->name, sizeof(ml->name)); ml->areanum = AAS_PointAreaNum(ml->origin); ml->next = maplocations; maplocations = ml; numlocations++; } //end if //camp spots else if (!strcmp(classname, "info_camp")) { cs = (campspot_t *) GetClearedMemory(sizeof(campspot_t)); AAS_VectorForBSPEpairKey(ent, "origin", cs->origin); //cs->origin[2] += 16; AAS_ValueForBSPEpairKey(ent, "message", cs->name, sizeof(cs->name)); AAS_FloatForBSPEpairKey(ent, "range", &cs->range); AAS_FloatForBSPEpairKey(ent, "weight", &cs->weight); AAS_FloatForBSPEpairKey(ent, "wait", &cs->wait); AAS_FloatForBSPEpairKey(ent, "random", &cs->random); cs->areanum = AAS_PointAreaNum(cs->origin); if (!cs->areanum) { botimport.Print(PRT_MESSAGE, "camp spot at %1.1f %1.1f %1.1f in solid\n", cs->origin[0], cs->origin[1], cs->origin[2]); FreeMemory(cs); continue; } //end if cs->next = campspots; campspots = cs; //AAS_DrawPermanentCross(cs->origin, 4, LINECOLOR_YELLOW); numcampspots++; } //end else if } //end for if (bot_developer) { botimport.Print(PRT_MESSAGE, "%d map locations\n", numlocations); botimport.Print(PRT_MESSAGE, "%d camp spots\n", numcampspots); } //end if } //end of the function BotInitInfoEntities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotInitLevelItems(void) { int i, spawnflags, value; char classname[MAX_EPAIRKEY]; vec3_t origin, end; int ent, goalareanum; itemconfig_t *ic; levelitem_t *li; bsp_trace_t trace; //initialize the map locations and camp spots BotInitInfoEntities(); //initialize the level item heap InitLevelItemHeap(); levelitems = NULL; numlevelitems = 0; // ic = itemconfig; if (!ic) return; //if there's no AAS file loaded if (!AAS_Loaded()) return; //update the modelindexes of the item info for (i = 0; i < ic->numiteminfo; i++) { //ic->iteminfo[i].modelindex = AAS_IndexFromModel(ic->iteminfo[i].model); if (!ic->iteminfo[i].modelindex) { Log_Write("item %s has modelindex 0", ic->iteminfo[i].classname); } //end if } //end for for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) { if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; // spawnflags = 0; AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); // for (i = 0; i < ic->numiteminfo; i++) { if (!strcmp(classname, ic->iteminfo[i].classname)) break; } //end for if (i >= ic->numiteminfo) { Log_Write("entity %s unknown item\r\n", classname); continue; } //end if //get the origin of the item if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) { botimport.Print(PRT_ERROR, "item %s without origin\n", classname); continue; } //end else // goalareanum = 0; //if it is a floating item if (spawnflags & 1) { //if the item is not floating in water if (!(AAS_PointContents(origin) & CONTENTS_WATER)) { VectorCopy(origin, end); end[2] -= 32; trace = AAS_Trace(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs, end, -1, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); //if the item not near the ground if (trace.fraction >= 1) { //if the item is not reachable from a jumppad goalareanum = AAS_BestReachableFromJumpPadArea(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs); Log_Write("item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum); //botimport.Print(PRT_MESSAGE, "item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum); if (!goalareanum) continue; } //end if } //end if } //end if li = AllocLevelItem(); if (!li) return; // li->number = ++numlevelitems; li->timeout = 0; li->entitynum = 0; // li->flags = 0; AAS_IntForBSPEpairKey(ent, "notfree", &value); if (value) li->flags |= IFL_NOTFREE; AAS_IntForBSPEpairKey(ent, "notteam", &value); if (value) li->flags |= IFL_NOTTEAM; AAS_IntForBSPEpairKey(ent, "notsingle", &value); if (value) li->flags |= IFL_NOTSINGLE; AAS_IntForBSPEpairKey(ent, "notbot", &value); if (value) li->flags |= IFL_NOTBOT; if (!strcmp(classname, "item_botroam")) { li->flags |= IFL_ROAM; AAS_FloatForBSPEpairKey(ent, "weight", &li->weight); } //end if //if not a stationary item if (!(spawnflags & 1)) { if (!AAS_DropToFloor(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs)) { botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", classname, origin[0], origin[1], origin[2]); } //end if } //end if //item info of the level item li->iteminfo = i; //origin of the item VectorCopy(origin, li->origin); // if (goalareanum) { li->goalareanum = goalareanum; VectorCopy(origin, li->goalorigin); } //end if else { //get the item goal area and goal origin li->goalareanum = AAS_BestReachableArea(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs, li->goalorigin); if (!li->goalareanum) { botimport.Print(PRT_MESSAGE, "%s not reachable for bots at (%1.1f %1.1f %1.1f)\n", classname, origin[0], origin[1], origin[2]); } //end if } //end else // AddLevelItemToList(li); } //end for botimport.Print(PRT_MESSAGE, "found %d level items\n", numlevelitems); } //end of the function BotInitLevelItems //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotGoalName(int number, char *name, int size) { levelitem_t *li; if (!itemconfig) return; // for (li = levelitems; li; li = li->next) { if (li->number == number) { strncpy(name, itemconfig->iteminfo[li->iteminfo].name, size-1); name[size-1] = '\0'; return; } //end for } //end for strcpy(name, ""); return; } //end of the function BotGoalName //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotResetAvoidGoals(int goalstate) { bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return; Com_Memset(gs->avoidgoals, 0, MAX_AVOIDGOALS * sizeof(int)); Com_Memset(gs->avoidgoaltimes, 0, MAX_AVOIDGOALS * sizeof(float)); } //end of the function BotResetAvoidGoals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotDumpAvoidGoals(int goalstate) { int i; bot_goalstate_t *gs; char name[32]; gs = BotGoalStateFromHandle(goalstate); if (!gs) return; for (i = 0; i < MAX_AVOIDGOALS; i++) { if (gs->avoidgoaltimes[i] >= AAS_Time()) { BotGoalName(gs->avoidgoals[i], name, 32); Log_Write("avoid goal %s, number %d for %f seconds", name, gs->avoidgoals[i], gs->avoidgoaltimes[i] - AAS_Time()); } //end if } //end for } //end of the function BotDumpAvoidGoals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotAddToAvoidGoals(bot_goalstate_t *gs, int number, float avoidtime) { int i; for (i = 0; i < MAX_AVOIDGOALS; i++) { //if the avoid goal is already stored if (gs->avoidgoals[i] == number) { gs->avoidgoals[i] = number; gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; return; } //end if } //end for for (i = 0; i < MAX_AVOIDGOALS; i++) { //if this avoid goal has expired if (gs->avoidgoaltimes[i] < AAS_Time()) { gs->avoidgoals[i] = number; gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; return; } //end if } //end for } //end of the function BotAddToAvoidGoals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotRemoveFromAvoidGoals(int goalstate, int number) { int i; bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return; //don't use the goals the bot wants to avoid for (i = 0; i < MAX_AVOIDGOALS; i++) { if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) { gs->avoidgoaltimes[i] = 0; return; } //end if } //end for } //end of the function BotRemoveFromAvoidGoals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float BotAvoidGoalTime(int goalstate, int number) { int i; bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return 0; //don't use the goals the bot wants to avoid for (i = 0; i < MAX_AVOIDGOALS; i++) { if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) { return gs->avoidgoaltimes[i] - AAS_Time(); } //end if } //end for return 0; } //end of the function BotAvoidGoalTime //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime) { bot_goalstate_t *gs; levelitem_t *li; gs = BotGoalStateFromHandle(goalstate); if (!gs) return; if (avoidtime < 0) { if (!itemconfig) return; // for (li = levelitems; li; li = li->next) { if (li->number == number) { avoidtime = itemconfig->iteminfo[li->iteminfo].respawntime; if (!avoidtime) avoidtime = AVOID_DEFAULT_TIME; if (avoidtime < AVOID_MINIMUM_TIME) avoidtime = AVOID_MINIMUM_TIME; BotAddToAvoidGoals(gs, number, avoidtime); return; } //end for } //end for return; } //end if else { BotAddToAvoidGoals(gs, number, avoidtime); } //end else } //end of the function BotSetAvoidGoalTime //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotGetLevelItemGoal(int index, char *name, bot_goal_t *goal) { levelitem_t *li; if (!itemconfig) return -1; li = levelitems; if (index >= 0) { for (; li; li = li->next) { if (li->number == index) { li = li->next; break; } //end if } //end for } //end for for (; li; li = li->next) { // if (g_gametype == GT_SINGLE_PLAYER) { if (li->flags & IFL_NOTSINGLE) continue; } else if (g_gametype >= GT_TEAM) { if (li->flags & IFL_NOTTEAM) continue; } else { if (li->flags & IFL_NOTFREE) continue; } if (li->flags & IFL_NOTBOT) continue; // if (!Q_stricmp(name, itemconfig->iteminfo[li->iteminfo].name)) { goal->areanum = li->goalareanum; VectorCopy(li->goalorigin, goal->origin); goal->entitynum = li->entitynum; VectorCopy(itemconfig->iteminfo[li->iteminfo].mins, goal->mins); VectorCopy(itemconfig->iteminfo[li->iteminfo].maxs, goal->maxs); goal->number = li->number; goal->flags = GFL_ITEM; if (li->timeout) goal->flags |= GFL_DROPPED; //botimport.Print(PRT_MESSAGE, "found li %s\n", itemconfig->iteminfo[li->iteminfo].name); return li->number; } //end if } //end for return -1; } //end of the function BotGetLevelItemGoal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotGetMapLocationGoal(char *name, bot_goal_t *goal) { maplocation_t *ml; vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; for (ml = maplocations; ml; ml = ml->next) { if (!Q_stricmp(ml->name, name)) { goal->areanum = ml->areanum; VectorCopy(ml->origin, goal->origin); goal->entitynum = 0; VectorCopy(mins, goal->mins); VectorCopy(maxs, goal->maxs); return qtrue; } //end if } //end for return qfalse; } //end of the function BotGetMapLocationGoal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotGetNextCampSpotGoal(int num, bot_goal_t *goal) { int i; campspot_t *cs; vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; if (num < 0) num = 0; i = num; for (cs = campspots; cs; cs = cs->next) { if (--i < 0) { goal->areanum = cs->areanum; VectorCopy(cs->origin, goal->origin); goal->entitynum = 0; VectorCopy(mins, goal->mins); VectorCopy(maxs, goal->maxs); return num+1; } //end if } //end for return 0; } //end of the function BotGetNextCampSpotGoal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotFindEntityForLevelItem(levelitem_t *li) { int ent, modelindex; itemconfig_t *ic; aas_entityinfo_t entinfo; vec3_t dir; ic = itemconfig; if (!itemconfig) return; for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent)) { //get the model index of the entity modelindex = AAS_EntityModelindex(ent); // if (!modelindex) continue; //get info about the entity AAS_EntityInfo(ent, &entinfo); //if the entity is still moving if (entinfo.origin[0] != entinfo.lastvisorigin[0] || entinfo.origin[1] != entinfo.lastvisorigin[1] || entinfo.origin[2] != entinfo.lastvisorigin[2]) continue; // if (ic->iteminfo[li->iteminfo].modelindex == modelindex) { //check if the entity is very close VectorSubtract(li->origin, entinfo.origin, dir); if (VectorLength(dir) < 30) { //found an entity for this level item li->entitynum = ent; } //end if } //end if } //end for } //end of the function BotFindEntityForLevelItem //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== //NOTE: enum entityType_t in bg_public.h #define ET_ITEM 2 void BotUpdateEntityItems(void) { int ent, i, modelindex; vec3_t dir; levelitem_t *li, *nextli; aas_entityinfo_t entinfo; itemconfig_t *ic; //timeout current entity items if necessary for (li = levelitems; li; li = nextli) { nextli = li->next; //if it is a item that will time out if (li->timeout) { //timeout the item if (li->timeout < AAS_Time()) { RemoveLevelItemFromList(li); FreeLevelItem(li); } //end if } //end if } //end for //find new entity items ic = itemconfig; if (!itemconfig) return; // for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent)) { if (AAS_EntityType(ent) != ET_ITEM) continue; //get the model index of the entity modelindex = AAS_EntityModelindex(ent); // if (!modelindex) continue; //get info about the entity AAS_EntityInfo(ent, &entinfo); //FIXME: don't do this //skip all floating items for now //if (entinfo.groundent != ENTITYNUM_WORLD) continue; //if the entity is still moving if (entinfo.origin[0] != entinfo.lastvisorigin[0] || entinfo.origin[1] != entinfo.lastvisorigin[1] || entinfo.origin[2] != entinfo.lastvisorigin[2]) continue; //check if the entity is already stored as a level item for (li = levelitems; li; li = li->next) { //if the level item is linked to an entity if (li->entitynum && li->entitynum == ent) { //the entity is re-used if the models are different if (ic->iteminfo[li->iteminfo].modelindex != modelindex) { //remove this level item RemoveLevelItemFromList(li); FreeLevelItem(li); li = NULL; break; } //end if else { if (entinfo.origin[0] != li->origin[0] || entinfo.origin[1] != li->origin[1] || entinfo.origin[2] != li->origin[2]) { VectorCopy(entinfo.origin, li->origin); //also update the goal area number li->goalareanum = AAS_BestReachableArea(li->origin, ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, li->goalorigin); } //end if break; } //end else } //end if } //end for if (li) continue; //try to link the entity to a level item for (li = levelitems; li; li = li->next) { //if this level item is already linked if (li->entitynum) continue; // if (g_gametype == GT_SINGLE_PLAYER) { if (li->flags & IFL_NOTSINGLE) continue; } else if (g_gametype >= GT_TEAM) { if (li->flags & IFL_NOTTEAM) continue; } else { if (li->flags & IFL_NOTFREE) continue; } //if the model of the level item and the entity are the same if (ic->iteminfo[li->iteminfo].modelindex == modelindex) { //check if the entity is very close VectorSubtract(li->origin, entinfo.origin, dir); if (VectorLength(dir) < 30) { //found an entity for this level item li->entitynum = ent; //if the origin is different if (entinfo.origin[0] != li->origin[0] || entinfo.origin[1] != li->origin[1] || entinfo.origin[2] != li->origin[2]) { //update the level item origin VectorCopy(entinfo.origin, li->origin); //also update the goal area number li->goalareanum = AAS_BestReachableArea(li->origin, ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, li->goalorigin); } //end if #ifdef DEBUG Log_Write("linked item %s to an entity", ic->iteminfo[li->iteminfo].classname); #endif //DEBUG break; } //end if } //end else } //end for if (li) continue; //check if the model is from a known item for (i = 0; i < ic->numiteminfo; i++) { if (ic->iteminfo[i].modelindex == modelindex) { break; } //end if } //end for //if the model is not from a known item if (i >= ic->numiteminfo) continue; //allocate a new level item li = AllocLevelItem(); // if (!li) continue; //entity number of the level item li->entitynum = ent; //number for the level item li->number = numlevelitems + ent; //set the item info index for the level item li->iteminfo = i; //origin of the item VectorCopy(entinfo.origin, li->origin); //get the item goal area and goal origin li->goalareanum = AAS_BestReachableArea(li->origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs, li->goalorigin); //never go for items dropped into jumppads if (AAS_AreaJumpPad(li->goalareanum)) { FreeLevelItem(li); continue; } //end if //time this item out after 30 seconds //dropped items disappear after 30 seconds li->timeout = AAS_Time() + 30; //add the level item to the list AddLevelItemToList(li); //botimport.Print(PRT_MESSAGE, "found new level item %s\n", ic->iteminfo[i].classname); } //end for /* for (li = levelitems; li; li = li->next) { if (!li->entitynum) { BotFindEntityForLevelItem(li); } //end if } //end for*/ } //end of the function BotUpdateEntityItems //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotDumpGoalStack(int goalstate) { int i; bot_goalstate_t *gs; char name[32]; gs = BotGoalStateFromHandle(goalstate); if (!gs) return; for (i = 1; i <= gs->goalstacktop; i++) { BotGoalName(gs->goalstack[i].number, name, 32); Log_Write("%d: %s", i, name); } //end for } //end of the function BotDumpGoalStack //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotPushGoal(int goalstate, bot_goal_t *goal) { bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return; if (gs->goalstacktop >= MAX_GOALSTACK-1) { botimport.Print(PRT_ERROR, "goal heap overflow\n"); BotDumpGoalStack(goalstate); return; } //end if gs->goalstacktop++; Com_Memcpy(&gs->goalstack[gs->goalstacktop], goal, sizeof(bot_goal_t)); } //end of the function BotPushGoal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotPopGoal(int goalstate) { bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return; if (gs->goalstacktop > 0) gs->goalstacktop--; } //end of the function BotPopGoal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotEmptyGoalStack(int goalstate) { bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return; gs->goalstacktop = 0; } //end of the function BotEmptyGoalStack //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotGetTopGoal(int goalstate, bot_goal_t *goal) { bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return qfalse; if (!gs->goalstacktop) return qfalse; Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop], sizeof(bot_goal_t)); return qtrue; } //end of the function BotGetTopGoal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotGetSecondGoal(int goalstate, bot_goal_t *goal) { bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return qfalse; if (gs->goalstacktop <= 1) return qfalse; Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop-1], sizeof(bot_goal_t)); return qtrue; } //end of the function BotGetSecondGoal //=========================================================================== // pops a new long term goal on the goal stack in the goalstate // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags) { int areanum, t, weightnum; float weight, bestweight, avoidtime; iteminfo_t *iteminfo; itemconfig_t *ic; levelitem_t *li, *bestitem; bot_goal_t goal; bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return qfalse; if (!gs->itemweightconfig) return qfalse; //get the area the bot is in areanum = BotReachabilityArea(origin, gs->client); //if the bot is in solid or if the area the bot is in has no reachability links if (!areanum || !AAS_AreaReachability(areanum)) { //use the last valid area the bot was in areanum = gs->lastreachabilityarea; } //end if //remember the last area with reachabilities the bot was in gs->lastreachabilityarea = areanum; //if still in solid if (!areanum) return qfalse; //the item configuration ic = itemconfig; if (!itemconfig) return qfalse; //best weight and item so far bestweight = 0; bestitem = NULL; Com_Memset(&goal, 0, sizeof(bot_goal_t)); //go through the items in the level for (li = levelitems; li; li = li->next) { if (g_gametype == GT_SINGLE_PLAYER) { if (li->flags & IFL_NOTSINGLE) continue; } else if (g_gametype >= GT_TEAM) { if (li->flags & IFL_NOTTEAM) continue; } else { if (li->flags & IFL_NOTFREE) continue; } if (li->flags & IFL_NOTBOT) continue; //if the item is not in a possible goal area if (!li->goalareanum) continue; //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk) if (!li->entitynum && !(li->flags & IFL_ROAM)) continue; //get the fuzzy weight function for this item iteminfo = &ic->iteminfo[li->iteminfo]; weightnum = gs->itemweightindex[iteminfo->number]; if (weightnum < 0) continue; #ifdef UNDECIDEDFUZZY weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); #else weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); #endif //UNDECIDEDFUZZY #ifdef DROPPEDWEIGHT //HACK: to make dropped items more attractive if (li->timeout) weight += droppedweight->value; #endif //DROPPEDWEIGHT //use weight scale for item_botroam if (li->flags & IFL_ROAM) weight *= li->weight; // if (weight > 0) { //get the travel time towards the goal area t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); //if the goal is reachable if (t > 0) { //if this item won't respawn before we get there avoidtime = BotAvoidGoalTime(goalstate, li->number); if (avoidtime - t * 0.009 > 0) continue; // weight /= (float) t * TRAVELTIME_SCALE; // if (weight > bestweight) { bestweight = weight; bestitem = li; } //end if } //end if } //end if } //end for //if no goal item found if (!bestitem) { /* //if not in lava or slime if (!AAS_AreaLava(areanum) && !AAS_AreaSlime(areanum)) { if (AAS_RandomGoalArea(areanum, travelflags, &goal.areanum, goal.origin)) { VectorSet(goal.mins, -15, -15, -15); VectorSet(goal.maxs, 15, 15, 15); goal.entitynum = 0; goal.number = 0; goal.flags = GFL_ROAM; goal.iteminfo = 0; //push the goal on the stack BotPushGoal(goalstate, &goal); // #ifdef DEBUG botimport.Print(PRT_MESSAGE, "chosen roam goal area %d\n", goal.areanum); #endif //DEBUG return qtrue; } //end if } //end if */ return qfalse; } //end if //create a bot goal for this item iteminfo = &ic->iteminfo[bestitem->iteminfo]; VectorCopy(bestitem->goalorigin, goal.origin); VectorCopy(iteminfo->mins, goal.mins); VectorCopy(iteminfo->maxs, goal.maxs); goal.areanum = bestitem->goalareanum; goal.entitynum = bestitem->entitynum; goal.number = bestitem->number; goal.flags = GFL_ITEM; if (bestitem->timeout) goal.flags |= GFL_DROPPED; if (bestitem->flags & IFL_ROAM) goal.flags |= GFL_ROAM; goal.iteminfo = bestitem->iteminfo; //if it's a dropped item if (bestitem->timeout) { avoidtime = AVOID_DROPPED_TIME; } //end if else { avoidtime = iteminfo->respawntime; if (!avoidtime) avoidtime = AVOID_DEFAULT_TIME; if (avoidtime < AVOID_MINIMUM_TIME) avoidtime = AVOID_MINIMUM_TIME; } //end else //add the chosen goal to the goals to avoid for a while BotAddToAvoidGoals(gs, bestitem->number, avoidtime); //push the goal on the stack BotPushGoal(goalstate, &goal); // return qtrue; } //end of the function BotChooseLTGItem //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, bot_goal_t *ltg, float maxtime) { int areanum, t, weightnum, ltg_time; float weight, bestweight, avoidtime; iteminfo_t *iteminfo; itemconfig_t *ic; levelitem_t *li, *bestitem; bot_goal_t goal; bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return qfalse; if (!gs->itemweightconfig) return qfalse; //get the area the bot is in areanum = BotReachabilityArea(origin, gs->client); //if the bot is in solid or if the area the bot is in has no reachability links if (!areanum || !AAS_AreaReachability(areanum)) { //use the last valid area the bot was in areanum = gs->lastreachabilityarea; } //end if //remember the last area with reachabilities the bot was in gs->lastreachabilityarea = areanum; //if still in solid if (!areanum) return qfalse; // if (ltg) ltg_time = AAS_AreaTravelTimeToGoalArea(areanum, origin, ltg->areanum, travelflags); else ltg_time = 99999; //the item configuration ic = itemconfig; if (!itemconfig) return qfalse; //best weight and item so far bestweight = 0; bestitem = NULL; Com_Memset(&goal, 0, sizeof(bot_goal_t)); //go through the items in the level for (li = levelitems; li; li = li->next) { if (g_gametype == GT_SINGLE_PLAYER) { if (li->flags & IFL_NOTSINGLE) continue; } else if (g_gametype >= GT_TEAM) { if (li->flags & IFL_NOTTEAM) continue; } else { if (li->flags & IFL_NOTFREE) continue; } if (li->flags & IFL_NOTBOT) continue; //if the item is in a possible goal area if (!li->goalareanum) continue; //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk) if (!li->entitynum && !(li->flags & IFL_ROAM)) continue; //get the fuzzy weight function for this item iteminfo = &ic->iteminfo[li->iteminfo]; weightnum = gs->itemweightindex[iteminfo->number]; if (weightnum < 0) continue; // #ifdef UNDECIDEDFUZZY weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); #else weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); #endif //UNDECIDEDFUZZY #ifdef DROPPEDWEIGHT //HACK: to make dropped items more attractive if (li->timeout) weight += droppedweight->value; #endif //DROPPEDWEIGHT //use weight scale for item_botroam if (li->flags & IFL_ROAM) weight *= li->weight; // if (weight > 0) { //get the travel time towards the goal area t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); //if the goal is reachable if (t > 0 && t < maxtime) { //if this item won't respawn before we get there avoidtime = BotAvoidGoalTime(goalstate, li->number); if (avoidtime - t * 0.009 > 0) continue; // weight /= (float) t * TRAVELTIME_SCALE; // if (weight > bestweight) { t = 0; if (ltg && !li->timeout) { //get the travel time from the goal to the long term goal t = AAS_AreaTravelTimeToGoalArea(li->goalareanum, li->goalorigin, ltg->areanum, travelflags); } //end if //if the travel back is possible and doesn't take too long if (t <= ltg_time) { bestweight = weight; bestitem = li; } //end if } //end if } //end if } //end if } //end for //if no goal item found if (!bestitem) return qfalse; //create a bot goal for this item iteminfo = &ic->iteminfo[bestitem->iteminfo]; VectorCopy(bestitem->goalorigin, goal.origin); VectorCopy(iteminfo->mins, goal.mins); VectorCopy(iteminfo->maxs, goal.maxs); goal.areanum = bestitem->goalareanum; goal.entitynum = bestitem->entitynum; goal.number = bestitem->number; goal.flags = GFL_ITEM; if (bestitem->timeout) goal.flags |= GFL_DROPPED; if (bestitem->flags & IFL_ROAM) goal.flags |= GFL_ROAM; goal.iteminfo = bestitem->iteminfo; //if it's a dropped item if (bestitem->timeout) { avoidtime = AVOID_DROPPED_TIME; } //end if else { avoidtime = iteminfo->respawntime; if (!avoidtime) avoidtime = AVOID_DEFAULT_TIME; if (avoidtime < AVOID_MINIMUM_TIME) avoidtime = AVOID_MINIMUM_TIME; } //end else //add the chosen goal to the goals to avoid for a while BotAddToAvoidGoals(gs, bestitem->number, avoidtime); //push the goal on the stack BotPushGoal(goalstate, &goal); // return qtrue; } //end of the function BotChooseNBGItem //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotTouchingGoal(vec3_t origin, bot_goal_t *goal) { int i; vec3_t boxmins, boxmaxs; vec3_t absmins, absmaxs; vec3_t safety_maxs = {0, 0, 0}; //{4, 4, 10}; vec3_t safety_mins = {0, 0, 0}; //{-4, -4, 0}; AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, boxmins, boxmaxs); VectorSubtract(goal->mins, boxmaxs, absmins); VectorSubtract(goal->maxs, boxmins, absmaxs); VectorAdd(absmins, goal->origin, absmins); VectorAdd(absmaxs, goal->origin, absmaxs); //make the box a little smaller for safety VectorSubtract(absmaxs, safety_maxs, absmaxs); VectorSubtract(absmins, safety_mins, absmins); for (i = 0; i < 3; i++) { if (origin[i] < absmins[i] || origin[i] > absmaxs[i]) return qfalse; } //end for return qtrue; } //end of the function BotTouchingGoal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal) { aas_entityinfo_t entinfo; bsp_trace_t trace; vec3_t middle; if (!(goal->flags & GFL_ITEM)) return qfalse; // VectorAdd(goal->mins, goal->mins, middle); VectorScale(middle, 0.5, middle); VectorAdd(goal->origin, middle, middle); // trace = AAS_Trace(eye, NULL, NULL, middle, viewer, CONTENTS_SOLID); //if the goal middle point is visible if (trace.fraction >= 1) { //the goal entity number doesn't have to be valid //just assume it's valid if (goal->entitynum <= 0) return qfalse; // //if the entity data isn't valid AAS_EntityInfo(goal->entitynum, &entinfo); //NOTE: for some wacko reason entities are sometimes // not updated //if (!entinfo.valid) return qtrue; if (entinfo.ltime < AAS_Time() - 0.5) return qtrue; } //end if return qfalse; } //end of the function BotItemGoalInVisButNotVisible //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotResetGoalState(int goalstate) { bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return; Com_Memset(gs->goalstack, 0, MAX_GOALSTACK * sizeof(bot_goal_t)); gs->goalstacktop = 0; BotResetAvoidGoals(goalstate); } //end of the function BotResetGoalState //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadItemWeights(int goalstate, char *filename) { bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return BLERR_CANNOTLOADITEMWEIGHTS; //load the weight configuration gs->itemweightconfig = ReadWeightConfig(filename); if (!gs->itemweightconfig) { botimport.Print(PRT_FATAL, "couldn't load weights\n"); return BLERR_CANNOTLOADITEMWEIGHTS; } //end if //if there's no item configuration if (!itemconfig) return BLERR_CANNOTLOADITEMWEIGHTS; //create the item weight index gs->itemweightindex = ItemWeightIndex(gs->itemweightconfig, itemconfig); //everything went ok return BLERR_NOERROR; } //end of the function BotLoadItemWeights //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotFreeItemWeights(int goalstate) { bot_goalstate_t *gs; gs = BotGoalStateFromHandle(goalstate); if (!gs) return; if (gs->itemweightconfig) FreeWeightConfig(gs->itemweightconfig); if (gs->itemweightindex) FreeMemory(gs->itemweightindex); } //end of the function BotFreeItemWeights //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotAllocGoalState(int client) { int i; for (i = 1; i <= MAX_CLIENTS; i++) { if (!botgoalstates[i]) { botgoalstates[i] = GetClearedMemory(sizeof(bot_goalstate_t)); botgoalstates[i]->client = client; return i; } //end if } //end for return 0; } //end of the function BotAllocGoalState //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeGoalState(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); return; } //end if if (!botgoalstates[handle]) { botimport.Print(PRT_FATAL, "invalid goal state handle %d\n", handle); return; } //end if BotFreeItemWeights(handle); FreeMemory(botgoalstates[handle]); botgoalstates[handle] = NULL; } //end of the function BotFreeGoalState //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotSetupGoalAI(void) { char *filename; //check if teamplay is on g_gametype = LibVarValue("g_gametype", "0"); //item configuration file filename = LibVarString("itemconfig", "items.c"); //load the item configuration itemconfig = LoadItemConfig(filename); if (!itemconfig) { botimport.Print(PRT_FATAL, "couldn't load item config\n"); return BLERR_CANNOTLOADITEMCONFIG; } //end if // droppedweight = LibVar("droppedweight", "1000"); //everything went ok return BLERR_NOERROR; } //end of the function BotSetupGoalAI //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotShutdownGoalAI(void) { int i; if (itemconfig) FreeMemory(itemconfig); itemconfig = NULL; if (levelitemheap) FreeMemory(levelitemheap); levelitemheap = NULL; freelevelitems = NULL; levelitems = NULL; numlevelitems = 0; BotFreeInfoEntities(); for (i = 1; i <= MAX_CLIENTS; i++) { if (botgoalstates[i]) { BotFreeGoalState(i); } //end if } //end for } //end of the function BotShutdownGoalAI ================================================ FILE: code/botlib/be_ai_move.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_ai_move.c * * desc: bot movement AI * * $Archive: /MissionPack/code/botlib/be_ai_move.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_libvar.h" #include "l_utils.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "../game/be_ea.h" #include "../game/be_ai_goal.h" #include "../game/be_ai_move.h" //#define DEBUG_AI_MOVE //#define DEBUG_ELEVATOR //#define DEBUG_GRAPPLE // bk001204 - redundant bot_avoidspot_t, see ../game/be_ai_move.h //movement state //NOTE: the moveflags MFL_ONGROUND, MFL_TELEPORTED, MFL_WATERJUMP and // MFL_GRAPPLEPULL must be set outside the movement code typedef struct bot_movestate_s { //input vars (all set outside the movement code) vec3_t origin; //origin of the bot vec3_t velocity; //velocity of the bot vec3_t viewoffset; //view offset int entitynum; //entity number of the bot int client; //client number of the bot float thinktime; //time the bot thinks int presencetype; //presencetype of the bot vec3_t viewangles; //view angles of the bot //state vars int areanum; //area the bot is in int lastareanum; //last area the bot was in int lastgoalareanum; //last goal area number int lastreachnum; //last reachability number vec3_t lastorigin; //origin previous cycle int reachareanum; //area number of the reachabilty int moveflags; //movement flags int jumpreach; //set when jumped float grapplevisible_time; //last time the grapple was visible float lastgrappledist; //last distance to the grapple end float reachability_time; //time to use current reachability int avoidreach[MAX_AVOIDREACH]; //reachabilities to avoid float avoidreachtimes[MAX_AVOIDREACH]; //times to avoid the reachabilities int avoidreachtries[MAX_AVOIDREACH]; //number of tries before avoiding // bot_avoidspot_t avoidspots[MAX_AVOIDSPOTS]; //spots to avoid int numavoidspots; } bot_movestate_t; //used to avoid reachability links for some time after being used #define AVOIDREACH #define AVOIDREACH_TIME 6 //avoid links for 6 seconds after use #define AVOIDREACH_TRIES 4 //prediction times #define PREDICTIONTIME_JUMP 3 //in seconds #define PREDICTIONTIME_MOVE 2 //in seconds //weapon indexes for weapon jumping #define WEAPONINDEX_ROCKET_LAUNCHER 5 #define WEAPONINDEX_BFG 9 #define MODELTYPE_FUNC_PLAT 1 #define MODELTYPE_FUNC_BOB 2 #define MODELTYPE_FUNC_DOOR 3 #define MODELTYPE_FUNC_STATIC 4 libvar_t *sv_maxstep; libvar_t *sv_maxbarrier; libvar_t *sv_gravity; libvar_t *weapindex_rocketlauncher; libvar_t *weapindex_bfg10k; libvar_t *weapindex_grapple; libvar_t *entitytypemissile; libvar_t *offhandgrapple; libvar_t *cmd_grappleoff; libvar_t *cmd_grappleon; //type of model, func_plat or func_bobbing int modeltypes[MAX_MODELS]; bot_movestate_t *botmovestates[MAX_CLIENTS+1]; //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== int BotAllocMoveState(void) { int i; for (i = 1; i <= MAX_CLIENTS; i++) { if (!botmovestates[i]) { botmovestates[i] = GetClearedMemory(sizeof(bot_movestate_t)); return i; } //end if } //end for return 0; } //end of the function BotAllocMoveState //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeMoveState(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); return; } //end if if (!botmovestates[handle]) { botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); return; } //end if FreeMemory(botmovestates[handle]); botmovestates[handle] = NULL; } //end of the function BotFreeMoveState //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== bot_movestate_t *BotMoveStateFromHandle(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); return NULL; } //end if if (!botmovestates[handle]) { botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); return NULL; } //end if return botmovestates[handle]; } //end of the function BotMoveStateFromHandle //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotInitMoveState(int handle, bot_initmove_t *initmove) { bot_movestate_t *ms; ms = BotMoveStateFromHandle(handle); if (!ms) return; VectorCopy(initmove->origin, ms->origin); VectorCopy(initmove->velocity, ms->velocity); VectorCopy(initmove->viewoffset, ms->viewoffset); ms->entitynum = initmove->entitynum; ms->client = initmove->client; ms->thinktime = initmove->thinktime; ms->presencetype = initmove->presencetype; VectorCopy(initmove->viewangles, ms->viewangles); // ms->moveflags &= ~MFL_ONGROUND; if (initmove->or_moveflags & MFL_ONGROUND) ms->moveflags |= MFL_ONGROUND; ms->moveflags &= ~MFL_TELEPORTED; if (initmove->or_moveflags & MFL_TELEPORTED) ms->moveflags |= MFL_TELEPORTED; ms->moveflags &= ~MFL_WATERJUMP; if (initmove->or_moveflags & MFL_WATERJUMP) ms->moveflags |= MFL_WATERJUMP; ms->moveflags &= ~MFL_WALK; if (initmove->or_moveflags & MFL_WALK) ms->moveflags |= MFL_WALK; ms->moveflags &= ~MFL_GRAPPLEPULL; if (initmove->or_moveflags & MFL_GRAPPLEPULL) ms->moveflags |= MFL_GRAPPLEPULL; } //end of the function BotInitMoveState //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== float AngleDiff(float ang1, float ang2) { float diff; diff = ang1 - ang2; if (ang1 > ang2) { if (diff > 180.0) diff -= 360.0; } //end if else { if (diff < -180.0) diff += 360.0; } //end else return diff; } //end of the function AngleDiff //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotFuzzyPointReachabilityArea(vec3_t origin) { int firstareanum, j, x, y, z; int areas[10], numareas, areanum, bestareanum; float dist, bestdist; vec3_t points[10], v, end; firstareanum = 0; areanum = AAS_PointAreaNum(origin); if (areanum) { firstareanum = areanum; if (AAS_AreaReachability(areanum)) return areanum; } //end if VectorCopy(origin, end); end[2] += 4; numareas = AAS_TraceAreas(origin, end, areas, points, 10); for (j = 0; j < numareas; j++) { if (AAS_AreaReachability(areas[j])) return areas[j]; } //end for bestdist = 999999; bestareanum = 0; for (z = 1; z >= -1; z -= 1) { for (x = 1; x >= -1; x -= 1) { for (y = 1; y >= -1; y -= 1) { VectorCopy(origin, end); end[0] += x * 8; end[1] += y * 8; end[2] += z * 12; numareas = AAS_TraceAreas(origin, end, areas, points, 10); for (j = 0; j < numareas; j++) { if (AAS_AreaReachability(areas[j])) { VectorSubtract(points[j], origin, v); dist = VectorLength(v); if (dist < bestdist) { bestareanum = areas[j]; bestdist = dist; } //end if } //end if if (!firstareanum) firstareanum = areas[j]; } //end for } //end for } //end for if (bestareanum) return bestareanum; } //end for return firstareanum; } //end of the function BotFuzzyPointReachabilityArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotReachabilityArea(vec3_t origin, int client) { int modelnum, modeltype, reachnum, areanum; aas_reachability_t reach; vec3_t org, end, mins, maxs, up = {0, 0, 1}; bsp_trace_t bsptrace; aas_trace_t trace; //check if the bot is standing on something AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); VectorMA(origin, -3, up, end); bsptrace = AAS_Trace(origin, mins, maxs, end, client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); if (!bsptrace.startsolid && bsptrace.fraction < 1 && bsptrace.ent != ENTITYNUM_NONE) { //if standing on the world the bot should be in a valid area if (bsptrace.ent == ENTITYNUM_WORLD) { return BotFuzzyPointReachabilityArea(origin); } //end if modelnum = AAS_EntityModelindex(bsptrace.ent); modeltype = modeltypes[modelnum]; //if standing on a func_plat or func_bobbing then the bot is assumed to be //in the area the reachability points to if (modeltype == MODELTYPE_FUNC_PLAT || modeltype == MODELTYPE_FUNC_BOB) { reachnum = AAS_NextModelReachability(0, modelnum); if (reachnum) { AAS_ReachabilityFromNum(reachnum, &reach); return reach.areanum; } //end if } //end else if //if the bot is swimming the bot should be in a valid area if (AAS_Swimming(origin)) { return BotFuzzyPointReachabilityArea(origin); } //end if // areanum = BotFuzzyPointReachabilityArea(origin); //if the bot is in an area with reachabilities if (areanum && AAS_AreaReachability(areanum)) return areanum; //trace down till the ground is hit because the bot is standing on some other entity VectorCopy(origin, org); VectorCopy(org, end); end[2] -= 800; trace = AAS_TraceClientBBox(org, end, PRESENCE_CROUCH, -1); if (!trace.startsolid) { VectorCopy(trace.endpos, org); } //end if // return BotFuzzyPointReachabilityArea(org); } //end if // return BotFuzzyPointReachabilityArea(origin); } //end of the function BotReachabilityArea //=========================================================================== // returns the reachability area the bot is in // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== /* int BotReachabilityArea(vec3_t origin, int testground) { int firstareanum, i, j, x, y, z; int areas[10], numareas, areanum, bestareanum; float dist, bestdist; vec3_t org, end, points[10], v; aas_trace_t trace; firstareanum = 0; for (i = 0; i < 2; i++) { VectorCopy(origin, org); //if test at the ground (used when bot is standing on an entity) if (i > 0) { VectorCopy(origin, end); end[2] -= 800; trace = AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); if (!trace.startsolid) { VectorCopy(trace.endpos, org); } //end if } //end if firstareanum = 0; areanum = AAS_PointAreaNum(org); if (areanum) { firstareanum = areanum; if (AAS_AreaReachability(areanum)) return areanum; } //end if bestdist = 999999; bestareanum = 0; for (z = 1; z >= -1; z -= 1) { for (x = 1; x >= -1; x -= 1) { for (y = 1; y >= -1; y -= 1) { VectorCopy(org, end); end[0] += x * 8; end[1] += y * 8; end[2] += z * 12; numareas = AAS_TraceAreas(org, end, areas, points, 10); for (j = 0; j < numareas; j++) { if (AAS_AreaReachability(areas[j])) { VectorSubtract(points[j], org, v); dist = VectorLength(v); if (dist < bestdist) { bestareanum = areas[j]; bestdist = dist; } //end if } //end if } //end for } //end for } //end for if (bestareanum) return bestareanum; } //end for if (!testground) break; } //end for //#ifdef DEBUG //botimport.Print(PRT_MESSAGE, "no reachability area\n"); //#endif //DEBUG return firstareanum; } //end of the function BotReachabilityArea*/ //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotOnMover(vec3_t origin, int entnum, aas_reachability_t *reach) { int i, modelnum; vec3_t mins, maxs, modelorigin, org, end; vec3_t angles = {0, 0, 0}; vec3_t boxmins = {-16, -16, -8}, boxmaxs = {16, 16, 8}; bsp_trace_t trace; modelnum = reach->facenum & 0x0000FFFF; //get some bsp model info AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); // if (!AAS_OriginOfMoverWithModelNum(modelnum, modelorigin)) { botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); return qfalse; } //end if // for (i = 0; i < 2; i++) { if (origin[i] > modelorigin[i] + maxs[i] + 16) return qfalse; if (origin[i] < modelorigin[i] + mins[i] - 16) return qfalse; } //end for // VectorCopy(origin, org); org[2] += 24; VectorCopy(origin, end); end[2] -= 48; // trace = AAS_Trace(org, boxmins, boxmaxs, end, entnum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); if (!trace.startsolid && !trace.allsolid) { //NOTE: the reachability face number is the model number of the elevator if (trace.ent != ENTITYNUM_NONE && AAS_EntityModelNum(trace.ent) == modelnum) { return qtrue; } //end if } //end if return qfalse; } //end of the function BotOnMover //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int MoverDown(aas_reachability_t *reach) { int modelnum; vec3_t mins, maxs, origin; vec3_t angles = {0, 0, 0}; modelnum = reach->facenum & 0x0000FFFF; //get some bsp model info AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); // if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) { botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); return qfalse; } //end if //if the top of the plat is below the reachability start point if (origin[2] + maxs[2] < reach->start[2]) return qtrue; return qfalse; } //end of the function MoverDown //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotSetBrushModelTypes(void) { int ent, modelnum; char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; Com_Memset(modeltypes, 0, MAX_MODELS * sizeof(int)); // for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) { if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) continue; if (model[0]) modelnum = atoi(model+1); else modelnum = 0; if (modelnum < 0 || modelnum > MAX_MODELS) { botimport.Print(PRT_MESSAGE, "entity %s model number out of range\n", classname); continue; } //end if if (!Q_stricmp(classname, "func_bobbing")) modeltypes[modelnum] = MODELTYPE_FUNC_BOB; else if (!Q_stricmp(classname, "func_plat")) modeltypes[modelnum] = MODELTYPE_FUNC_PLAT; else if (!Q_stricmp(classname, "func_door")) modeltypes[modelnum] = MODELTYPE_FUNC_DOOR; else if (!Q_stricmp(classname, "func_static")) modeltypes[modelnum] = MODELTYPE_FUNC_STATIC; } //end for } //end of the function BotSetBrushModelTypes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotOnTopOfEntity(bot_movestate_t *ms) { vec3_t mins, maxs, end, up = {0, 0, 1}; bsp_trace_t trace; AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); VectorMA(ms->origin, -3, up, end); trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) { return trace.ent; } //end if return -1; } //end of the function BotOnTopOfEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotValidTravel(vec3_t origin, aas_reachability_t *reach, int travelflags) { //if the reachability uses an unwanted travel type if (AAS_TravelFlagForType(reach->traveltype) & ~travelflags) return qfalse; //don't go into areas with bad travel types if (AAS_AreaContentsTravelFlags(reach->areanum) & ~travelflags) return qfalse; return qtrue; } //end of the function BotValidTravel //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotAddToAvoidReach(bot_movestate_t *ms, int number, float avoidtime) { int i; for (i = 0; i < MAX_AVOIDREACH; i++) { if (ms->avoidreach[i] == number) { if (ms->avoidreachtimes[i] > AAS_Time()) ms->avoidreachtries[i]++; else ms->avoidreachtries[i] = 1; ms->avoidreachtimes[i] = AAS_Time() + avoidtime; return; } //end if } //end for //add the reachability to the reachabilities to avoid for a while for (i = 0; i < MAX_AVOIDREACH; i++) { if (ms->avoidreachtimes[i] < AAS_Time()) { ms->avoidreach[i] = number; ms->avoidreachtimes[i] = AAS_Time() + avoidtime; ms->avoidreachtries[i] = 1; return; } //end if } //end for } //end of the function BotAddToAvoidReach //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2) { vec3_t proj, dir; int j; AAS_ProjectPointOntoVector(p, lp1, lp2, proj); for (j = 0; j < 3; j++) if ((proj[j] > lp1[j] && proj[j] > lp2[j]) || (proj[j] < lp1[j] && proj[j] < lp2[j])) break; if (j < 3) { if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j])) VectorSubtract(p, lp1, dir); else VectorSubtract(p, lp2, dir); return VectorLengthSquared(dir); } VectorSubtract(p, proj, dir); return VectorLengthSquared(dir); } //end of the function DistanceFromLineSquared //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float VectorDistanceSquared(vec3_t p1, vec3_t p2) { vec3_t dir; VectorSubtract(p2, p1, dir); return VectorLengthSquared(dir); } //end of the function VectorDistanceSquared //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotAvoidSpots(vec3_t origin, aas_reachability_t *reach, bot_avoidspot_t *avoidspots, int numavoidspots) { int checkbetween, i, type; float squareddist, squaredradius; switch(reach->traveltype & TRAVELTYPE_MASK) { case TRAVEL_WALK: checkbetween = qtrue; break; case TRAVEL_CROUCH: checkbetween = qtrue; break; case TRAVEL_BARRIERJUMP: checkbetween = qtrue; break; case TRAVEL_LADDER: checkbetween = qtrue; break; case TRAVEL_WALKOFFLEDGE: checkbetween = qfalse; break; case TRAVEL_JUMP: checkbetween = qfalse; break; case TRAVEL_SWIM: checkbetween = qtrue; break; case TRAVEL_WATERJUMP: checkbetween = qtrue; break; case TRAVEL_TELEPORT: checkbetween = qfalse; break; case TRAVEL_ELEVATOR: checkbetween = qfalse; break; case TRAVEL_GRAPPLEHOOK: checkbetween = qfalse; break; case TRAVEL_ROCKETJUMP: checkbetween = qfalse; break; case TRAVEL_BFGJUMP: checkbetween = qfalse; break; case TRAVEL_JUMPPAD: checkbetween = qfalse; break; case TRAVEL_FUNCBOB: checkbetween = qfalse; break; default: checkbetween = qtrue; break; } //end switch type = AVOID_CLEAR; for (i = 0; i < numavoidspots; i++) { squaredradius = Square(avoidspots[i].radius); squareddist = DistanceFromLineSquared(avoidspots[i].origin, origin, reach->start); // if moving towards the avoid spot if (squareddist < squaredradius && VectorDistanceSquared(avoidspots[i].origin, origin) > squareddist) { type = avoidspots[i].type; } //end if else if (checkbetween) { squareddist = DistanceFromLineSquared(avoidspots[i].origin, reach->start, reach->end); // if moving towards the avoid spot if (squareddist < squaredradius && VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist) { type = avoidspots[i].type; } //end if } //end if else { VectorDistanceSquared(avoidspots[i].origin, reach->end); // if the reachability leads closer to the avoid spot if (squareddist < squaredradius && VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist) { type = avoidspots[i].type; } //end if } //end else if (type == AVOID_ALWAYS) return type; } //end for return type; } //end of the function BotAvoidSpots //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type) { bot_movestate_t *ms; ms = BotMoveStateFromHandle(movestate); if (!ms) return; if (type == AVOID_CLEAR) { ms->numavoidspots = 0; return; } //end if if (ms->numavoidspots >= MAX_AVOIDSPOTS) return; VectorCopy(origin, ms->avoidspots[ms->numavoidspots].origin); ms->avoidspots[ms->numavoidspots].radius = radius; ms->avoidspots[ms->numavoidspots].type = type; ms->numavoidspots++; } //end of the function BotAddAvoidSpot //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotGetReachabilityToGoal(vec3_t origin, int areanum, int lastgoalareanum, int lastareanum, int *avoidreach, float *avoidreachtimes, int *avoidreachtries, bot_goal_t *goal, int travelflags, int movetravelflags, struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags) { int i, t, besttime, bestreachnum, reachnum; aas_reachability_t reach; //if not in a valid area if (!areanum) return 0; // if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goal->areanum)) { travelflags |= TFL_DONOTENTER; movetravelflags |= TFL_DONOTENTER; } //end if //use the routing to find the next area to go to besttime = 0; bestreachnum = 0; // for (reachnum = AAS_NextAreaReachability(areanum, 0); reachnum; reachnum = AAS_NextAreaReachability(areanum, reachnum)) { #ifdef AVOIDREACH //check if it isn't an reachability to avoid for (i = 0; i < MAX_AVOIDREACH; i++) { if (avoidreach[i] == reachnum && avoidreachtimes[i] >= AAS_Time()) break; } //end for if (i != MAX_AVOIDREACH && avoidreachtries[i] > AVOIDREACH_TRIES) { #ifdef DEBUG if (bot_developer) { botimport.Print(PRT_MESSAGE, "avoiding reachability %d\n", avoidreach[i]); } //end if #endif //DEBUG continue; } //end if #endif //AVOIDREACH //get the reachability from the number AAS_ReachabilityFromNum(reachnum, &reach); //NOTE: do not go back to the previous area if the goal didn't change //NOTE: is this actually avoidance of local routing minima between two areas??? if (lastgoalareanum == goal->areanum && reach.areanum == lastareanum) continue; //if (AAS_AreaContentsTravelFlags(reach.areanum) & ~travelflags) continue; //if the travel isn't valid if (!BotValidTravel(origin, &reach, movetravelflags)) continue; //get the travel time t = AAS_AreaTravelTimeToGoalArea(reach.areanum, reach.end, goal->areanum, travelflags); //if the goal area isn't reachable from the reachable area if (!t) continue; //if the bot should not use this reachability to avoid bad spots if (BotAvoidSpots(origin, &reach, avoidspots, numavoidspots)) { if (flags) { *flags |= MOVERESULT_BLOCKEDBYAVOIDSPOT; } continue; } //add the travel time towards the area t += reach.traveltime;// + AAS_AreaTravelTime(areanum, origin, reach.start); //if the travel time is better than the ones already found if (!besttime || t < besttime) { besttime = t; bestreachnum = reachnum; } //end if } //end for // return bestreachnum; } //end of the function BotGetReachabilityToGoal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotAddToTarget(vec3_t start, vec3_t end, float maxdist, float *dist, vec3_t target) { vec3_t dir; float curdist; VectorSubtract(end, start, dir); curdist = VectorNormalize(dir); if (*dist + curdist < maxdist) { VectorCopy(end, target); *dist += curdist; return qfalse; } //end if else { VectorMA(start, maxdist - *dist, dir, target); *dist = maxdist; return qtrue; } //end else } //end of the function BotAddToTarget int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target) { aas_reachability_t reach; int reachnum, lastareanum; bot_movestate_t *ms; vec3_t end; float dist; ms = BotMoveStateFromHandle(movestate); if (!ms) return qfalse; reachnum = 0; //if the bot has no goal or no last reachability if (!ms->lastreachnum || !goal) return qfalse; reachnum = ms->lastreachnum; VectorCopy(ms->origin, end); lastareanum = ms->lastareanum; dist = 0; while(reachnum && dist < lookahead) { AAS_ReachabilityFromNum(reachnum, &reach); if (BotAddToTarget(end, reach.start, lookahead, &dist, target)) return qtrue; //never look beyond teleporters if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_TELEPORT) return qtrue; //never look beyond the weapon jump point if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) return qtrue; if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_BFGJUMP) return qtrue; //don't add jump pad distances if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_JUMPPAD && (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR && (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB) { if (BotAddToTarget(reach.start, reach.end, lookahead, &dist, target)) return qtrue; } //end if reachnum = BotGetReachabilityToGoal(reach.end, reach.areanum, ms->lastgoalareanum, lastareanum, ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, goal, travelflags, travelflags, NULL, 0, NULL); VectorCopy(reach.end, end); lastareanum = reach.areanum; if (lastareanum == goal->areanum) { BotAddToTarget(reach.end, goal->origin, lookahead, &dist, target); return qtrue; } //end if } //end while // return qfalse; } //end of the function BotMovementViewTarget //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotVisible(int ent, vec3_t eye, vec3_t target) { bsp_trace_t trace; trace = AAS_Trace(eye, NULL, NULL, target, ent, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); if (trace.fraction >= 1) return qtrue; return qfalse; } //end of the function BotVisible //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target) { aas_reachability_t reach; int reachnum, lastgoalareanum, lastareanum, i; int avoidreach[MAX_AVOIDREACH]; float avoidreachtimes[MAX_AVOIDREACH]; int avoidreachtries[MAX_AVOIDREACH]; vec3_t end; //if the bot has no goal or no last reachability if (!goal) return qfalse; //if the areanum is not valid if (!areanum) return qfalse; //if the goal areanum is not valid if (!goal->areanum) return qfalse; Com_Memset(avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); lastgoalareanum = goal->areanum; lastareanum = areanum; VectorCopy(origin, end); //only do 20 hops for (i = 0; i < 20 && (areanum != goal->areanum); i++) { // reachnum = BotGetReachabilityToGoal(end, areanum, lastgoalareanum, lastareanum, avoidreach, avoidreachtimes, avoidreachtries, goal, travelflags, travelflags, NULL, 0, NULL); if (!reachnum) return qfalse; AAS_ReachabilityFromNum(reachnum, &reach); // if (BotVisible(goal->entitynum, goal->origin, reach.start)) { VectorCopy(reach.start, target); return qtrue; } //end if // if (BotVisible(goal->entitynum, goal->origin, reach.end)) { VectorCopy(reach.end, target); return qtrue; } //end if // if (reach.areanum == goal->areanum) { VectorCopy(reach.end, target); return qtrue; } //end if // lastareanum = areanum; areanum = reach.areanum; VectorCopy(reach.end, end); // } //end while // return qfalse; } //end of the function BotPredictVisiblePosition //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void MoverBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter) { int modelnum; vec3_t mins, maxs, origin, mids; vec3_t angles = {0, 0, 0}; modelnum = reach->facenum & 0x0000FFFF; //get some bsp model info AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); // if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) { botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); } //end if //get a point just above the plat in the bottom position VectorAdd(mins, maxs, mids); VectorMA(origin, 0.5, mids, bottomcenter); bottomcenter[2] = reach->start[2]; } //end of the function MoverBottomCenter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum) { float dist, startz; vec3_t start, end; aas_trace_t trace; //do gap checking startz = origin[2]; //this enables walking down stairs more fluidly { VectorCopy(origin, start); VectorCopy(origin, end); end[2] -= 60; trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); if (trace.fraction >= 1) return 1; startz = trace.endpos[2] + 1; } // for (dist = 8; dist <= 100; dist += 8) { VectorMA(origin, dist, hordir, start); start[2] = startz + 24; VectorCopy(start, end); end[2] -= 48 + sv_maxbarrier->value; trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); //if solid is found the bot can't walk any further and fall into a gap if (!trace.startsolid) { //if it is a gap if (trace.endpos[2] < startz - sv_maxstep->value - 8) { VectorCopy(trace.endpos, end); end[2] -= 20; if (AAS_PointContents(end) & CONTENTS_WATER) break; //if a gap is found slow down //botimport.Print(PRT_MESSAGE, "gap at %f\n", dist); return dist; } //end if startz = trace.endpos[2]; } //end if } //end for return 0; } //end of the function BotGapDistance //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotCheckBarrierJump(bot_movestate_t *ms, vec3_t dir, float speed) { vec3_t start, hordir, end; aas_trace_t trace; VectorCopy(ms->origin, end); end[2] += sv_maxbarrier->value; //trace right up trace = AAS_TraceClientBBox(ms->origin, end, PRESENCE_NORMAL, ms->entitynum); //this shouldn't happen... but we check anyway if (trace.startsolid) return qfalse; //if very low ceiling it isn't possible to jump up to a barrier if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse; // hordir[0] = dir[0]; hordir[1] = dir[1]; hordir[2] = 0; VectorNormalize(hordir); VectorMA(ms->origin, ms->thinktime * speed * 0.5, hordir, end); VectorCopy(trace.endpos, start); end[2] = trace.endpos[2]; //trace from previous trace end pos horizontally in the move direction trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); //again this shouldn't happen if (trace.startsolid) return qfalse; // VectorCopy(trace.endpos, start); VectorCopy(trace.endpos, end); end[2] = ms->origin[2]; //trace down from the previous trace end pos trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); //if solid if (trace.startsolid) return qfalse; //if no obstacle at all if (trace.fraction >= 1.0) return qfalse; //if less than the maximum step height if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse; // EA_Jump(ms->client); EA_Move(ms->client, hordir, speed); ms->moveflags |= MFL_BARRIERJUMP; //there is a barrier return qtrue; } //end of the function BotCheckBarrierJump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotSwimInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) { vec3_t normdir; VectorCopy(dir, normdir); VectorNormalize(normdir); EA_Move(ms->client, normdir, speed); return qtrue; } //end of the function BotSwimInDirection //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotWalkInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) { vec3_t hordir, cmdmove, velocity, tmpdir, origin; int presencetype, maxframes, cmdframes, stopevent; aas_clientmove_t move; float dist; if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND; //if the bot is on the ground if (ms->moveflags & MFL_ONGROUND) { //if there is a barrier the bot can jump on if (BotCheckBarrierJump(ms, dir, speed)) return qtrue; //remove barrier jump flag ms->moveflags &= ~MFL_BARRIERJUMP; //get the presence type for the movement if ((type & MOVE_CROUCH) && !(type & MOVE_JUMP)) presencetype = PRESENCE_CROUCH; else presencetype = PRESENCE_NORMAL; //horizontal direction hordir[0] = dir[0]; hordir[1] = dir[1]; hordir[2] = 0; VectorNormalize(hordir); //if the bot is not supposed to jump if (!(type & MOVE_JUMP)) { //if there is a gap, try to jump over it if (BotGapDistance(ms->origin, hordir, ms->entitynum) > 0) type |= MOVE_JUMP; } //end if //get command movement VectorScale(hordir, speed, cmdmove); VectorCopy(ms->velocity, velocity); // if (type & MOVE_JUMP) { //botimport.Print(PRT_MESSAGE, "trying jump\n"); cmdmove[2] = 400; maxframes = PREDICTIONTIME_JUMP / 0.1; cmdframes = 1; stopevent = SE_HITGROUND|SE_HITGROUNDDAMAGE| SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA; } //end if else { maxframes = 2; cmdframes = 2; stopevent = SE_HITGROUNDDAMAGE| SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA; } //end else //AAS_ClearShownDebugLines(); // VectorCopy(ms->origin, origin); origin[2] += 0.5; AAS_PredictClientMovement(&move, ms->entitynum, origin, presencetype, qtrue, velocity, cmdmove, cmdframes, maxframes, 0.1f, stopevent, 0, qfalse);//qtrue); //if prediction time wasn't enough to fully predict the movement if (move.frames >= maxframes && (type & MOVE_JUMP)) { //botimport.Print(PRT_MESSAGE, "client %d: max prediction frames\n", ms->client); return qfalse; } //end if //don't enter slime or lava and don't fall from too high if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) { //botimport.Print(PRT_MESSAGE, "client %d: would be hurt ", ms->client); //if (move.stopevent & SE_ENTERSLIME) botimport.Print(PRT_MESSAGE, "slime\n"); //if (move.stopevent & SE_ENTERLAVA) botimport.Print(PRT_MESSAGE, "lava\n"); //if (move.stopevent & SE_HITGROUNDDAMAGE) botimport.Print(PRT_MESSAGE, "hitground\n"); return qfalse; } //end if //if ground was hit if (move.stopevent & SE_HITGROUND) { //check for nearby gap VectorNormalize2(move.velocity, tmpdir); dist = BotGapDistance(move.endpos, tmpdir, ms->entitynum); if (dist > 0) return qfalse; // dist = BotGapDistance(move.endpos, hordir, ms->entitynum); if (dist > 0) return qfalse; } //end if //get horizontal movement tmpdir[0] = move.endpos[0] - ms->origin[0]; tmpdir[1] = move.endpos[1] - ms->origin[1]; tmpdir[2] = 0; // //AAS_DrawCross(move.endpos, 4, LINECOLOR_BLUE); //the bot is blocked by something if (VectorLength(tmpdir) < speed * ms->thinktime * 0.5) return qfalse; //perform the movement if (type & MOVE_JUMP) EA_Jump(ms->client); if (type & MOVE_CROUCH) EA_Crouch(ms->client); EA_Move(ms->client, hordir, speed); //movement was succesfull return qtrue; } //end if else { if (ms->moveflags & MFL_BARRIERJUMP) { //if near the top or going down if (ms->velocity[2] < 50) { EA_Move(ms->client, dir, speed); } //end if } //end if //FIXME: do air control to avoid hazards return qtrue; } //end else } //end of the function BotWalkInDirection //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type) { bot_movestate_t *ms; ms = BotMoveStateFromHandle(movestate); if (!ms) return qfalse; //if swimming if (AAS_Swimming(ms->origin)) { return BotSwimInDirection(ms, dir, speed, type); } //end if else { return BotWalkInDirection(ms, dir, speed, type); } //end else } //end of the function BotMoveInDirection //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Intersection(vec2_t p1, vec2_t p2, vec2_t p3, vec2_t p4, vec2_t out) { float x1, dx1, dy1, x2, dx2, dy2, d; dx1 = p2[0] - p1[0]; dy1 = p2[1] - p1[1]; dx2 = p4[0] - p3[0]; dy2 = p4[1] - p3[1]; d = dy1 * dx2 - dx1 * dy2; if (d != 0) { x1 = p1[1] * dx1 - p1[0] * dy1; x2 = p3[1] * dx2 - p3[0] * dy2; out[0] = (int) ((dx1 * x2 - dx2 * x1) / d); out[1] = (int) ((dy1 * x2 - dy2 * x1) / d); return qtrue; } //end if else { return qfalse; } //end else } //end of the function Intersection //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotCheckBlocked(bot_movestate_t *ms, vec3_t dir, int checkbottom, bot_moveresult_t *result) { vec3_t mins, maxs, end, up = {0, 0, 1}; bsp_trace_t trace; //test for entities obstructing the bot's path AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); // if (fabs(DotProduct(dir, up)) < 0.7) { mins[2] += sv_maxstep->value; //if the bot can step on maxs[2] -= 10; //a little lower to avoid low ceiling } //end if VectorMA(ms->origin, 3, dir, end); trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY); //if not started in solid and not hitting the world entity if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) { result->blocked = qtrue; result->blockentity = trace.ent; #ifdef DEBUG //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); #endif //DEBUG } //end if //if not in an area with reachability else if (checkbottom && !AAS_AreaReachability(ms->areanum)) { //check if the bot is standing on something AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); VectorMA(ms->origin, -3, up, end); trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) { result->blocked = qtrue; result->blockentity = trace.ent; result->flags |= MOVERESULT_ONTOPOFOBSTACLE; #ifdef DEBUG //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); #endif //DEBUG } //end if } //end else } //end of the function BotCheckBlocked //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotClearMoveResult(bot_moveresult_t *moveresult) { moveresult->failure = qfalse; moveresult->type = 0; moveresult->blocked = qfalse; moveresult->blockentity = 0; moveresult->traveltype = 0; moveresult->flags = 0; } //end of the function BotClearMoveResult //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) { float dist, speed; vec3_t hordir; bot_moveresult_t result; BotClearMoveResult(&result); //first walk straight to the reachability start hordir[0] = reach->start[0] - ms->origin[0]; hordir[1] = reach->start[1] - ms->origin[1]; hordir[2] = 0; dist = VectorNormalize(hordir); // BotCheckBlocked(ms, hordir, qtrue, &result); // if (dist < 10) { //walk straight to the reachability end hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; dist = VectorNormalize(hordir); } //end if //if going towards a crouch area if (!(AAS_AreaPresenceType(reach->areanum) & PRESENCE_NORMAL)) { //if pretty close to the reachable area if (dist < 20) EA_Crouch(ms->client); } //end if // dist = BotGapDistance(ms->origin, hordir, ms->entitynum); // if (ms->moveflags & MFL_WALK) { if (dist > 0) speed = 200 - (180 - 1 * dist); else speed = 200; EA_Walk(ms->client); } //end if else { if (dist > 0) speed = 400 - (360 - 2 * dist); else speed = 400; } //end else //elemantary action move in direction EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); // return result; } //end of the function BotTravel_Walk //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotFinishTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t hordir; float dist, speed; bot_moveresult_t result; BotClearMoveResult(&result); //if not on the ground and changed areas... don't walk back!! //(doesn't seem to help) /* ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); if (ms->areanum == reach->areanum) { #ifdef DEBUG botimport.Print(PRT_MESSAGE, "BotFinishTravel_Walk: already in reach area\n"); #endif //DEBUG return result; } //end if*/ //go straight to the reachability end hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; dist = VectorNormalize(hordir); // if (dist > 100) dist = 100; speed = 400 - (400 - 3 * dist); // EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); // return result; } //end of the function BotFinishTravel_Walk //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_Crouch(bot_movestate_t *ms, aas_reachability_t *reach) { float speed; vec3_t hordir; bot_moveresult_t result; BotClearMoveResult(&result); // speed = 400; //walk straight to reachability end hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; VectorNormalize(hordir); // BotCheckBlocked(ms, hordir, qtrue, &result); //elemantary actions EA_Crouch(ms->client); EA_Move(ms->client, hordir, speed); // VectorCopy(hordir, result.movedir); // return result; } //end of the function BotTravel_Crouch //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) { float dist, speed; vec3_t hordir; bot_moveresult_t result; BotClearMoveResult(&result); //walk straight to reachability start hordir[0] = reach->start[0] - ms->origin[0]; hordir[1] = reach->start[1] - ms->origin[1]; hordir[2] = 0; dist = VectorNormalize(hordir); // BotCheckBlocked(ms, hordir, qtrue, &result); //if pretty close to the barrier if (dist < 9) { EA_Jump(ms->client); } //end if else { if (dist > 60) dist = 60; speed = 360 - (360 - 6 * dist); EA_Move(ms->client, hordir, speed); } //end else VectorCopy(hordir, result.movedir); // return result; } //end of the function BotTravel_BarrierJump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotFinishTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) { float dist; vec3_t hordir; bot_moveresult_t result; BotClearMoveResult(&result); //if near the top or going down if (ms->velocity[2] < 250) { hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; dist = VectorNormalize(hordir); // BotCheckBlocked(ms, hordir, qtrue, &result); // EA_Move(ms->client, hordir, 400); VectorCopy(hordir, result.movedir); } //end if // return result; } //end of the function BotFinishTravel_BarrierJump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_Swim(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t dir; bot_moveresult_t result; BotClearMoveResult(&result); //swim straight to reachability end VectorSubtract(reach->start, ms->origin, dir); VectorNormalize(dir); // BotCheckBlocked(ms, dir, qtrue, &result); //elemantary actions EA_Move(ms->client, dir, 400); // VectorCopy(dir, result.movedir); Vector2Angles(dir, result.ideal_viewangles); result.flags |= MOVERESULT_SWIMVIEW; // return result; } //end of the function BotTravel_Swim //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t dir, hordir; float dist; bot_moveresult_t result; BotClearMoveResult(&result); //swim straight to reachability end VectorSubtract(reach->end, ms->origin, dir); VectorCopy(dir, hordir); hordir[2] = 0; dir[2] += 15 + crandom() * 40; //botimport.Print(PRT_MESSAGE, "BotTravel_WaterJump: dir[2] = %f\n", dir[2]); VectorNormalize(dir); dist = VectorNormalize(hordir); //elemantary actions //EA_Move(ms->client, dir, 400); EA_MoveForward(ms->client); //move up if close to the actual out of water jump spot if (dist < 40) EA_MoveUp(ms->client); //set the ideal view angles Vector2Angles(dir, result.ideal_viewangles); result.flags |= MOVERESULT_MOVEMENTVIEW; // VectorCopy(dir, result.movedir); // return result; } //end of the function BotTravel_WaterJump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotFinishTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t dir, pnt; float dist; bot_moveresult_t result; //botimport.Print(PRT_MESSAGE, "BotFinishTravel_WaterJump\n"); BotClearMoveResult(&result); //if waterjumping there's nothing to do if (ms->moveflags & MFL_WATERJUMP) return result; //if not touching any water anymore don't do anything //otherwise the bot sometimes keeps jumping? VectorCopy(ms->origin, pnt); pnt[2] -= 32; //extra for q2dm4 near red armor/mega health if (!(AAS_PointContents(pnt) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) return result; //swim straight to reachability end VectorSubtract(reach->end, ms->origin, dir); dir[0] += crandom() * 10; dir[1] += crandom() * 10; dir[2] += 70 + crandom() * 10; dist = VectorNormalize(dir); //elemantary actions EA_Move(ms->client, dir, 400); //set the ideal view angles Vector2Angles(dir, result.ideal_viewangles); result.flags |= MOVERESULT_MOVEMENTVIEW; // VectorCopy(dir, result.movedir); // return result; } //end of the function BotFinishTravel_WaterJump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t hordir, dir; float dist, speed, reachhordist; bot_moveresult_t result; BotClearMoveResult(&result); //check if the bot is blocked by anything VectorSubtract(reach->start, ms->origin, dir); VectorNormalize(dir); BotCheckBlocked(ms, dir, qtrue, &result); //if the reachability start and end are practially above each other VectorSubtract(reach->end, reach->start, dir); dir[2] = 0; reachhordist = VectorLength(dir); //walk straight to the reachability start hordir[0] = reach->start[0] - ms->origin[0]; hordir[1] = reach->start[1] - ms->origin[1]; hordir[2] = 0; dist = VectorNormalize(hordir); //if pretty close to the start focus on the reachability end if (dist < 48) { hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; VectorNormalize(hordir); // if (reachhordist < 20) { speed = 100; } //end if else if (!AAS_HorizontalVelocityForJump(0, reach->start, reach->end, &speed)) { speed = 400; } //end if } //end if else { if (reachhordist < 20) { if (dist > 64) dist = 64; speed = 400 - (256 - 4 * dist); } //end if else { speed = 400; } //end else } //end else // BotCheckBlocked(ms, hordir, qtrue, &result); //elemantary action EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); // return result; } //end of the function BotTravel_WalkOffLedge //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotAirControl(vec3_t origin, vec3_t velocity, vec3_t goal, vec3_t dir, float *speed) { vec3_t org, vel; float dist; int i; VectorCopy(origin, org); VectorScale(velocity, 0.1, vel); for (i = 0; i < 50; i++) { vel[2] -= sv_gravity->value * 0.01; //if going down and next position would be below the goal if (vel[2] < 0 && org[2] + vel[2] < goal[2]) { VectorScale(vel, (goal[2] - org[2]) / vel[2], vel); VectorAdd(org, vel, org); VectorSubtract(goal, org, dir); dist = VectorNormalize(dir); if (dist > 32) dist = 32; *speed = 400 - (400 - 13 * dist); return qtrue; } //end if else { VectorAdd(org, vel, org); } //end else } //end for VectorSet(dir, 0, 0, 0); *speed = 400; return qfalse; } //end of the function BotAirControl //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotFinishTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t dir, hordir, end, v; float dist, speed; bot_moveresult_t result; BotClearMoveResult(&result); // VectorSubtract(reach->end, ms->origin, dir); BotCheckBlocked(ms, dir, qtrue, &result); // VectorSubtract(reach->end, ms->origin, v); v[2] = 0; dist = VectorNormalize(v); if (dist > 16) VectorMA(reach->end, 16, v, end); else VectorCopy(reach->end, end); // if (!BotAirControl(ms->origin, ms->velocity, end, hordir, &speed)) { //go straight to the reachability end VectorCopy(dir, hordir); hordir[2] = 0; // dist = VectorNormalize(hordir); speed = 400; } //end if // EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); // return result; } //end of the function BotFinishTravel_WalkOffLedge //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== /* bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t hordir; float dist, gapdist, speed, horspeed, sv_jumpvel; bot_moveresult_t result; BotClearMoveResult(&result); // sv_jumpvel = botlibglobals.sv_jumpvel->value; //walk straight to the reachability start hordir[0] = reach->start[0] - ms->origin[0]; hordir[1] = reach->start[1] - ms->origin[1]; hordir[2] = 0; dist = VectorNormalize(hordir); // speed = 350; // gapdist = BotGapDistance(ms, hordir, ms->entitynum); //if pretty close to the start focus on the reachability end if (dist < 50 || (gapdist && gapdist < 50)) { //NOTE: using max speed (400) works best //if (AAS_HorizontalVelocityForJump(sv_jumpvel, ms->origin, reach->end, &horspeed)) //{ // speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; //} //end if hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; VectorNormalize(hordir); //elemantary action jump EA_Jump(ms->client); // ms->jumpreach = ms->lastreachnum; speed = 600; } //end if else { if (AAS_HorizontalVelocityForJump(sv_jumpvel, reach->start, reach->end, &horspeed)) { speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; } //end if } //end else //elemantary action EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); // return result; } //end of the function BotTravel_Jump*/ /* bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t hordir, dir1, dir2, mins, maxs, start, end; float dist1, dist2, speed; bot_moveresult_t result; bsp_trace_t trace; BotClearMoveResult(&result); // hordir[0] = reach->start[0] - reach->end[0]; hordir[1] = reach->start[1] - reach->end[1]; hordir[2] = 0; VectorNormalize(hordir); // VectorCopy(reach->start, start); start[2] += 1; //minus back the bouding box size plus 16 VectorMA(reach->start, 80, hordir, end); // AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs); //check for solids trace = AAS_Trace(start, mins, maxs, end, ms->entitynum, MASK_PLAYERSOLID); if (trace.startsolid) VectorCopy(start, trace.endpos); //check for a gap for (dist1 = 0; dist1 < 80; dist1 += 10) { VectorMA(start, dist1+10, hordir, end); end[2] += 1; if (AAS_PointAreaNum(end) != ms->reachareanum) break; } //end for if (dist1 < 80) VectorMA(reach->start, dist1, hordir, trace.endpos); // dist1 = BotGapDistance(start, hordir, ms->entitynum); // if (dist1 && dist1 <= trace.fraction * 80) VectorMA(reach->start, dist1-20, hordir, trace.endpos); // VectorSubtract(ms->origin, reach->start, dir1); dir1[2] = 0; dist1 = VectorNormalize(dir1); VectorSubtract(ms->origin, trace.endpos, dir2); dir2[2] = 0; dist2 = VectorNormalize(dir2); //if just before the reachability start if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) { //botimport.Print(PRT_MESSAGE, "between jump start and run to point\n"); hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; VectorNormalize(hordir); //elemantary action jump if (dist1 < 24) EA_Jump(ms->client); else if (dist1 < 32) EA_DelayedJump(ms->client); EA_Move(ms->client, hordir, 600); // ms->jumpreach = ms->lastreachnum; } //end if else { //botimport.Print(PRT_MESSAGE, "going towards run to point\n"); hordir[0] = trace.endpos[0] - ms->origin[0]; hordir[1] = trace.endpos[1] - ms->origin[1]; hordir[2] = 0; VectorNormalize(hordir); // if (dist2 > 80) dist2 = 80; speed = 400 - (400 - 5 * dist2); EA_Move(ms->client, hordir, speed); } //end else VectorCopy(hordir, result.movedir); // return result; } //end of the function BotTravel_Jump*/ //* bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t hordir, dir1, dir2, start, end, runstart; // vec3_t runstart, dir1, dir2, hordir; float dist1, dist2, speed; bot_moveresult_t result; BotClearMoveResult(&result); // AAS_JumpReachRunStart(reach, runstart); //* hordir[0] = runstart[0] - reach->start[0]; hordir[1] = runstart[1] - reach->start[1]; hordir[2] = 0; VectorNormalize(hordir); // VectorCopy(reach->start, start); start[2] += 1; VectorMA(reach->start, 80, hordir, runstart); //check for a gap for (dist1 = 0; dist1 < 80; dist1 += 10) { VectorMA(start, dist1+10, hordir, end); end[2] += 1; if (AAS_PointAreaNum(end) != ms->reachareanum) break; } //end for if (dist1 < 80) VectorMA(reach->start, dist1, hordir, runstart); // VectorSubtract(ms->origin, reach->start, dir1); dir1[2] = 0; dist1 = VectorNormalize(dir1); VectorSubtract(ms->origin, runstart, dir2); dir2[2] = 0; dist2 = VectorNormalize(dir2); //if just before the reachability start if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) { // botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; VectorNormalize(hordir); //elemantary action jump if (dist1 < 24) EA_Jump(ms->client); else if (dist1 < 32) EA_DelayedJump(ms->client); EA_Move(ms->client, hordir, 600); // ms->jumpreach = ms->lastreachnum; } //end if else { // botimport.Print(PRT_MESSAGE, "going towards run start point\n"); hordir[0] = runstart[0] - ms->origin[0]; hordir[1] = runstart[1] - ms->origin[1]; hordir[2] = 0; VectorNormalize(hordir); // if (dist2 > 80) dist2 = 80; speed = 400 - (400 - 5 * dist2); EA_Move(ms->client, hordir, speed); } //end else VectorCopy(hordir, result.movedir); // return result; } //end of the function BotTravel_Jump*/ //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotFinishTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t hordir, hordir2; float speed, dist; bot_moveresult_t result; BotClearMoveResult(&result); //if not jumped yet if (!ms->jumpreach) return result; //go straight to the reachability end hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; dist = VectorNormalize(hordir); // hordir2[0] = reach->end[0] - reach->start[0]; hordir2[1] = reach->end[1] - reach->start[1]; hordir2[2] = 0; VectorNormalize(hordir2); // if (DotProduct(hordir, hordir2) < -0.5 && dist < 24) return result; //always use max speed when traveling through the air speed = 800; // EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); // return result; } //end of the function BotFinishTravel_Jump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_Ladder(bot_movestate_t *ms, aas_reachability_t *reach) { //float dist, speed; vec3_t dir, viewdir;//, hordir; vec3_t origin = {0, 0, 0}; // vec3_t up = {0, 0, 1}; bot_moveresult_t result; BotClearMoveResult(&result); // // if ((ms->moveflags & MFL_AGAINSTLADDER)) //NOTE: not a good idea for ladders starting in water // || !(ms->moveflags & MFL_ONGROUND)) { //botimport.Print(PRT_MESSAGE, "against ladder or not on ground\n"); VectorSubtract(reach->end, ms->origin, dir); VectorNormalize(dir); //set the ideal view angles, facing the ladder up or down viewdir[0] = dir[0]; viewdir[1] = dir[1]; viewdir[2] = 3 * dir[2]; Vector2Angles(viewdir, result.ideal_viewangles); //elemantary action EA_Move(ms->client, origin, 0); EA_MoveForward(ms->client); //set movement view flag so the AI can see the view is focussed result.flags |= MOVERESULT_MOVEMENTVIEW; } //end if /* else { //botimport.Print(PRT_MESSAGE, "moving towards ladder\n"); VectorSubtract(reach->end, ms->origin, dir); //make sure the horizontal movement is large anough VectorCopy(dir, hordir); hordir[2] = 0; dist = VectorNormalize(hordir); // dir[0] = hordir[0]; dir[1] = hordir[1]; if (dir[2] > 0) dir[2] = 1; else dir[2] = -1; if (dist > 50) dist = 50; speed = 400 - (200 - 4 * dist); EA_Move(ms->client, dir, speed); } //end else*/ //save the movement direction VectorCopy(dir, result.movedir); // return result; } //end of the function BotTravel_Ladder //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_Teleport(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t hordir; float dist; bot_moveresult_t result; BotClearMoveResult(&result); //if the bot is being teleported if (ms->moveflags & MFL_TELEPORTED) return result; //walk straight to center of the teleporter VectorSubtract(reach->start, ms->origin, hordir); if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; dist = VectorNormalize(hordir); // BotCheckBlocked(ms, hordir, qtrue, &result); if (dist < 30) EA_Move(ms->client, hordir, 200); else EA_Move(ms->client, hordir, 400); if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; VectorCopy(hordir, result.movedir); return result; } //end of the function BotTravel_Teleport //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t dir, dir1, dir2, hordir, bottomcenter; float dist, dist1, dist2, speed; bot_moveresult_t result; BotClearMoveResult(&result); //if standing on the plat if (BotOnMover(ms->origin, ms->entitynum, reach)) { #ifdef DEBUG_ELEVATOR botimport.Print(PRT_MESSAGE, "bot on elevator\n"); #endif //DEBUG_ELEVATOR //if vertically not too far from the end point if (abs(ms->origin[2] - reach->end[2]) < sv_maxbarrier->value) { #ifdef DEBUG_ELEVATOR botimport.Print(PRT_MESSAGE, "bot moving to end\n"); #endif //DEBUG_ELEVATOR //move to the end point VectorSubtract(reach->end, ms->origin, hordir); hordir[2] = 0; VectorNormalize(hordir); if (!BotCheckBarrierJump(ms, hordir, 100)) { EA_Move(ms->client, hordir, 400); } //end if VectorCopy(hordir, result.movedir); } //end else //if not really close to the center of the elevator else { MoverBottomCenter(reach, bottomcenter); VectorSubtract(bottomcenter, ms->origin, hordir); hordir[2] = 0; dist = VectorNormalize(hordir); // if (dist > 10) { #ifdef DEBUG_ELEVATOR botimport.Print(PRT_MESSAGE, "bot moving to center\n"); #endif //DEBUG_ELEVATOR //move to the center of the plat if (dist > 100) dist = 100; speed = 400 - (400 - 4 * dist); // EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); } //end if } //end else } //end if else { #ifdef DEBUG_ELEVATOR botimport.Print(PRT_MESSAGE, "bot not on elevator\n"); #endif //DEBUG_ELEVATOR //if very near the reachability end VectorSubtract(reach->end, ms->origin, dir); dist = VectorLength(dir); if (dist < 64) { if (dist > 60) dist = 60; speed = 360 - (360 - 6 * dist); // if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) { if (speed > 5) EA_Move(ms->client, dir, speed); } //end if VectorCopy(dir, result.movedir); // if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; //stop using this reachability ms->reachability_time = 0; return result; } //end if //get direction and distance to reachability start VectorSubtract(reach->start, ms->origin, dir1); if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0; dist1 = VectorNormalize(dir1); //if the elevator isn't down if (!MoverDown(reach)) { #ifdef DEBUG_ELEVATOR botimport.Print(PRT_MESSAGE, "elevator not down\n"); #endif //DEBUG_ELEVATOR dist = dist1; VectorCopy(dir1, dir); // BotCheckBlocked(ms, dir, qfalse, &result); // if (dist > 60) dist = 60; speed = 360 - (360 - 6 * dist); // if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) { if (speed > 5) EA_Move(ms->client, dir, speed); } //end if VectorCopy(dir, result.movedir); // if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; //this isn't a failure... just wait till the elevator comes down result.type = RESULTTYPE_ELEVATORUP; result.flags |= MOVERESULT_WAITING; return result; } //end if //get direction and distance to elevator bottom center MoverBottomCenter(reach, bottomcenter); VectorSubtract(bottomcenter, ms->origin, dir2); if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0; dist2 = VectorNormalize(dir2); //if very close to the reachability start or //closer to the elevator center or //between reachability start and elevator center if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) { #ifdef DEBUG_ELEVATOR botimport.Print(PRT_MESSAGE, "bot moving to center\n"); #endif //DEBUG_ELEVATOR dist = dist2; VectorCopy(dir2, dir); } //end if else //closer to the reachability start { #ifdef DEBUG_ELEVATOR botimport.Print(PRT_MESSAGE, "bot moving to start\n"); #endif //DEBUG_ELEVATOR dist = dist1; VectorCopy(dir1, dir); } //end else // BotCheckBlocked(ms, dir, qfalse, &result); // if (dist > 60) dist = 60; speed = 400 - (400 - 6 * dist); // if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) { EA_Move(ms->client, dir, speed); } //end if VectorCopy(dir, result.movedir); // if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; } //end else return result; } //end of the function BotTravel_Elevator //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotFinishTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t bottomcenter, bottomdir, topdir; bot_moveresult_t result; BotClearMoveResult(&result); // MoverBottomCenter(reach, bottomcenter); VectorSubtract(bottomcenter, ms->origin, bottomdir); // VectorSubtract(reach->end, ms->origin, topdir); // if (fabs(bottomdir[2]) < fabs(topdir[2])) { VectorNormalize(bottomdir); EA_Move(ms->client, bottomdir, 300); } //end if else { VectorNormalize(topdir); EA_Move(ms->client, topdir, 300); } //end else return result; } //end of the function BotFinishTravel_Elevator //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotFuncBobStartEnd(aas_reachability_t *reach, vec3_t start, vec3_t end, vec3_t origin) { int spawnflags, modelnum; vec3_t mins, maxs, mid, angles = {0, 0, 0}; int num0, num1; modelnum = reach->facenum & 0x0000FFFF; if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) { botimport.Print(PRT_MESSAGE, "BotFuncBobStartEnd: no entity with model %d\n", modelnum); VectorSet(start, 0, 0, 0); VectorSet(end, 0, 0, 0); return; } //end if AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); VectorAdd(mins, maxs, mid); VectorScale(mid, 0.5, mid); VectorCopy(mid, start); VectorCopy(mid, end); spawnflags = reach->facenum >> 16; num0 = reach->edgenum >> 16; if (num0 > 0x00007FFF) num0 |= 0xFFFF0000; num1 = reach->edgenum & 0x0000FFFF; if (num1 > 0x00007FFF) num1 |= 0xFFFF0000; if (spawnflags & 1) { start[0] = num0; end[0] = num1; // origin[0] += mid[0]; origin[1] = mid[1]; origin[2] = mid[2]; } //end if else if (spawnflags & 2) { start[1] = num0; end[1] = num1; // origin[0] = mid[0]; origin[1] += mid[1]; origin[2] = mid[2]; } //end else if else { start[2] = num0; end[2] = num1; // origin[0] = mid[0]; origin[1] = mid[1]; origin[2] += mid[2]; } //end else } //end of the function BotFuncBobStartEnd //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t dir, dir1, dir2, hordir, bottomcenter, bob_start, bob_end, bob_origin; float dist, dist1, dist2, speed; bot_moveresult_t result; BotClearMoveResult(&result); // BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); //if standing ontop of the func_bobbing if (BotOnMover(ms->origin, ms->entitynum, reach)) { #ifdef DEBUG_FUNCBOB botimport.Print(PRT_MESSAGE, "bot on func_bobbing\n"); #endif //if near end point of reachability VectorSubtract(bob_origin, bob_end, dir); if (VectorLength(dir) < 24) { #ifdef DEBUG_FUNCBOB botimport.Print(PRT_MESSAGE, "bot moving to reachability end\n"); #endif //move to the end point VectorSubtract(reach->end, ms->origin, hordir); hordir[2] = 0; VectorNormalize(hordir); if (!BotCheckBarrierJump(ms, hordir, 100)) { EA_Move(ms->client, hordir, 400); } //end if VectorCopy(hordir, result.movedir); } //end else //if not really close to the center of the elevator else { MoverBottomCenter(reach, bottomcenter); VectorSubtract(bottomcenter, ms->origin, hordir); hordir[2] = 0; dist = VectorNormalize(hordir); // if (dist > 10) { #ifdef DEBUG_FUNCBOB botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); #endif //move to the center of the plat if (dist > 100) dist = 100; speed = 400 - (400 - 4 * dist); // EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); } //end if } //end else } //end if else { #ifdef DEBUG_FUNCBOB botimport.Print(PRT_MESSAGE, "bot not ontop of func_bobbing\n"); #endif //if very near the reachability end VectorSubtract(reach->end, ms->origin, dir); dist = VectorLength(dir); if (dist < 64) { #ifdef DEBUG_FUNCBOB botimport.Print(PRT_MESSAGE, "bot moving to end\n"); #endif if (dist > 60) dist = 60; speed = 360 - (360 - 6 * dist); //if swimming or no barrier jump if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) { if (speed > 5) EA_Move(ms->client, dir, speed); } //end if VectorCopy(dir, result.movedir); // if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; //stop using this reachability ms->reachability_time = 0; return result; } //end if //get direction and distance to reachability start VectorSubtract(reach->start, ms->origin, dir1); if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0; dist1 = VectorNormalize(dir1); //if func_bobbing is Not it's start position VectorSubtract(bob_origin, bob_start, dir); if (VectorLength(dir) > 16) { #ifdef DEBUG_FUNCBOB botimport.Print(PRT_MESSAGE, "func_bobbing not at start\n"); #endif dist = dist1; VectorCopy(dir1, dir); // BotCheckBlocked(ms, dir, qfalse, &result); // if (dist > 60) dist = 60; speed = 360 - (360 - 6 * dist); // if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) { if (speed > 5) EA_Move(ms->client, dir, speed); } //end if VectorCopy(dir, result.movedir); // if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; //this isn't a failure... just wait till the func_bobbing arrives result.type = RESULTTYPE_WAITFORFUNCBOBBING; result.flags |= MOVERESULT_WAITING; return result; } //end if //get direction and distance to func_bob bottom center MoverBottomCenter(reach, bottomcenter); VectorSubtract(bottomcenter, ms->origin, dir2); if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0; dist2 = VectorNormalize(dir2); //if very close to the reachability start or //closer to the elevator center or //between reachability start and func_bobbing center if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) { #ifdef DEBUG_FUNCBOB botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); #endif dist = dist2; VectorCopy(dir2, dir); } //end if else //closer to the reachability start { #ifdef DEBUG_FUNCBOB botimport.Print(PRT_MESSAGE, "bot moving to reachability start\n"); #endif dist = dist1; VectorCopy(dir1, dir); } //end else // BotCheckBlocked(ms, dir, qfalse, &result); // if (dist > 60) dist = 60; speed = 400 - (400 - 6 * dist); // if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) { EA_Move(ms->client, dir, speed); } //end if VectorCopy(dir, result.movedir); // if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; } //end else return result; } //end of the function BotTravel_FuncBobbing //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotFinishTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t bob_origin, bob_start, bob_end, dir, hordir, bottomcenter; bot_moveresult_t result; float dist, speed; BotClearMoveResult(&result); // BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); // VectorSubtract(bob_origin, bob_end, dir); dist = VectorLength(dir); //if the func_bobbing is near the end if (dist < 16) { VectorSubtract(reach->end, ms->origin, hordir); if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; dist = VectorNormalize(hordir); // if (dist > 60) dist = 60; speed = 360 - (360 - 6 * dist); // if (speed > 5) EA_Move(ms->client, dir, speed); VectorCopy(dir, result.movedir); // if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; } //end if else { MoverBottomCenter(reach, bottomcenter); VectorSubtract(bottomcenter, ms->origin, hordir); if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; dist = VectorNormalize(hordir); // if (dist > 5) { //move to the center of the plat if (dist > 100) dist = 100; speed = 400 - (400 - 4 * dist); // EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); } //end if } //end else return result; } //end of the function BotFinishTravel_FuncBobbing //=========================================================================== // 0 no valid grapple hook visible // 1 the grapple hook is still flying // 2 the grapple hooked into a wall // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int GrappleState(bot_movestate_t *ms, aas_reachability_t *reach) { int i; aas_entityinfo_t entinfo; //if the grapple hook is pulling if (ms->moveflags & MFL_GRAPPLEPULL) return 2; //check for a visible grapple missile entity //or visible grapple entity for (i = AAS_NextEntity(0); i; i = AAS_NextEntity(i)) { if (AAS_EntityType(i) == (int) entitytypemissile->value) { AAS_EntityInfo(i, &entinfo); if (entinfo.weapon == (int) weapindex_grapple->value) { return 1; } //end if } //end if } //end for //no valid grapple at all return 0; } //end of the function GrappleState //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotResetGrapple(bot_movestate_t *ms) { aas_reachability_t reach; AAS_ReachabilityFromNum(ms->lastreachnum, &reach); //if not using the grapple hook reachability anymore if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_GRAPPLEHOOK) { if ((ms->moveflags & MFL_ACTIVEGRAPPLE) || ms->grapplevisible_time) { if (offhandgrapple->value) EA_Command(ms->client, cmd_grappleoff->string); ms->moveflags &= ~MFL_ACTIVEGRAPPLE; ms->grapplevisible_time = 0; #ifdef DEBUG_GRAPPLE botimport.Print(PRT_MESSAGE, "reset grapple\n"); #endif //DEBUG_GRAPPLE } //end if } //end if } //end of the function BotResetGrapple //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_Grapple(bot_movestate_t *ms, aas_reachability_t *reach) { bot_moveresult_t result; float dist, speed; vec3_t dir, viewdir, org; int state, areanum; bsp_trace_t trace; #ifdef DEBUG_GRAPPLE static int debugline; if (!debugline) debugline = botimport.DebugLineCreate(); botimport.DebugLineShow(debugline, reach->start, reach->end, LINECOLOR_BLUE); #endif //DEBUG_GRAPPLE BotClearMoveResult(&result); // if (ms->moveflags & MFL_GRAPPLERESET) { if (offhandgrapple->value) EA_Command(ms->client, cmd_grappleoff->string); ms->moveflags &= ~MFL_ACTIVEGRAPPLE; return result; } //end if // if (!(int) offhandgrapple->value) { result.weapon = weapindex_grapple->value; result.flags |= MOVERESULT_MOVEMENTWEAPON; } //end if // if (ms->moveflags & MFL_ACTIVEGRAPPLE) { #ifdef DEBUG_GRAPPLE botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: active grapple\n"); #endif //DEBUG_GRAPPLE // state = GrappleState(ms, reach); // VectorSubtract(reach->end, ms->origin, dir); dir[2] = 0; dist = VectorLength(dir); //if very close to the grapple end or the grappled is hooked and //the bot doesn't get any closer if (state && dist < 48) { if (ms->lastgrappledist - dist < 1) { #ifdef DEBUG_GRAPPLE botimport.Print(PRT_ERROR, "grapple normal end\n"); #endif //DEBUG_GRAPPLE if (offhandgrapple->value) EA_Command(ms->client, cmd_grappleoff->string); ms->moveflags &= ~MFL_ACTIVEGRAPPLE; ms->moveflags |= MFL_GRAPPLERESET; ms->reachability_time = 0; //end the reachability return result; } //end if } //end if //if no valid grapple at all, or the grapple hooked and the bot //isn't moving anymore else if (!state || (state == 2 && dist > ms->lastgrappledist - 2)) { if (ms->grapplevisible_time < AAS_Time() - 0.4) { #ifdef DEBUG_GRAPPLE botimport.Print(PRT_ERROR, "grapple not visible\n"); #endif //DEBUG_GRAPPLE if (offhandgrapple->value) EA_Command(ms->client, cmd_grappleoff->string); ms->moveflags &= ~MFL_ACTIVEGRAPPLE; ms->moveflags |= MFL_GRAPPLERESET; ms->reachability_time = 0; //end the reachability return result; } //end if } //end if else { ms->grapplevisible_time = AAS_Time(); } //end else // if (!(int) offhandgrapple->value) { EA_Attack(ms->client); } //end if //remember the current grapple distance ms->lastgrappledist = dist; } //end if else { #ifdef DEBUG_GRAPPLE botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: inactive grapple\n"); #endif //DEBUG_GRAPPLE // ms->grapplevisible_time = AAS_Time(); // VectorSubtract(reach->start, ms->origin, dir); if (!(ms->moveflags & MFL_SWIMMING)) dir[2] = 0; VectorAdd(ms->origin, ms->viewoffset, org); VectorSubtract(reach->end, org, viewdir); // dist = VectorNormalize(dir); Vector2Angles(viewdir, result.ideal_viewangles); result.flags |= MOVERESULT_MOVEMENTVIEW; // if (dist < 5 && fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 2 && fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 2) { #ifdef DEBUG_GRAPPLE botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: activating grapple\n"); #endif //DEBUG_GRAPPLE //check if the grapple missile path is clear VectorAdd(ms->origin, ms->viewoffset, org); trace = AAS_Trace(org, NULL, NULL, reach->end, ms->entitynum, CONTENTS_SOLID); VectorSubtract(reach->end, trace.endpos, dir); if (VectorLength(dir) > 16) { result.failure = qtrue; return result; } //end if //activate the grapple if (offhandgrapple->value) { EA_Command(ms->client, cmd_grappleon->string); } //end if else { EA_Attack(ms->client); } //end else ms->moveflags |= MFL_ACTIVEGRAPPLE; ms->lastgrappledist = 999999; } //end if else { if (dist < 70) speed = 300 - (300 - 4 * dist); else speed = 400; // BotCheckBlocked(ms, dir, qtrue, &result); //elemantary action move in direction EA_Move(ms->client, dir, speed); VectorCopy(dir, result.movedir); } //end else //if in another area before actually grappling areanum = AAS_PointAreaNum(ms->origin); if (areanum && areanum != ms->reachareanum) ms->reachability_time = 0; } //end else return result; } //end of the function BotTravel_Grapple //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_RocketJump(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t hordir; float dist, speed; bot_moveresult_t result; //botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n"); BotClearMoveResult(&result); // hordir[0] = reach->start[0] - ms->origin[0]; hordir[1] = reach->start[1] - ms->origin[1]; hordir[2] = 0; // dist = VectorNormalize(hordir); //look in the movement direction Vector2Angles(hordir, result.ideal_viewangles); //look straight down result.ideal_viewangles[PITCH] = 90; // if (dist < 5 && fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 && fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5) { //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; VectorNormalize(hordir); //elemantary action jump EA_Jump(ms->client); EA_Attack(ms->client); EA_Move(ms->client, hordir, 800); // ms->jumpreach = ms->lastreachnum; } //end if else { if (dist > 80) dist = 80; speed = 400 - (400 - 5 * dist); EA_Move(ms->client, hordir, speed); } //end else //look in the movement direction Vector2Angles(hordir, result.ideal_viewangles); //look straight down result.ideal_viewangles[PITCH] = 90; //set the view angles directly EA_View(ms->client, result.ideal_viewangles); //view is important for the movment result.flags |= MOVERESULT_MOVEMENTVIEWSET; //select the rocket launcher EA_SelectWeapon(ms->client, (int) weapindex_rocketlauncher->value); //weapon is used for movement result.weapon = (int) weapindex_rocketlauncher->value; result.flags |= MOVERESULT_MOVEMENTWEAPON; // VectorCopy(hordir, result.movedir); // return result; } //end of the function BotTravel_RocketJump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_BFGJump(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t hordir; float dist, speed; bot_moveresult_t result; //botimport.Print(PRT_MESSAGE, "BotTravel_BFGJump: bah\n"); BotClearMoveResult(&result); // hordir[0] = reach->start[0] - ms->origin[0]; hordir[1] = reach->start[1] - ms->origin[1]; hordir[2] = 0; // dist = VectorNormalize(hordir); // if (dist < 5 && fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 && fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5) { //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; VectorNormalize(hordir); //elemantary action jump EA_Jump(ms->client); EA_Attack(ms->client); EA_Move(ms->client, hordir, 800); // ms->jumpreach = ms->lastreachnum; } //end if else { if (dist > 80) dist = 80; speed = 400 - (400 - 5 * dist); EA_Move(ms->client, hordir, speed); } //end else //look in the movement direction Vector2Angles(hordir, result.ideal_viewangles); //look straight down result.ideal_viewangles[PITCH] = 90; //set the view angles directly EA_View(ms->client, result.ideal_viewangles); //view is important for the movment result.flags |= MOVERESULT_MOVEMENTVIEWSET; //select the rocket launcher EA_SelectWeapon(ms->client, (int) weapindex_bfg10k->value); //weapon is used for movement result.weapon = (int) weapindex_bfg10k->value; result.flags |= MOVERESULT_MOVEMENTWEAPON; // VectorCopy(hordir, result.movedir); // return result; } //end of the function BotTravel_BFGJump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotFinishTravel_WeaponJump(bot_movestate_t *ms, aas_reachability_t *reach) { vec3_t hordir; float speed; bot_moveresult_t result; BotClearMoveResult(&result); //if not jumped yet if (!ms->jumpreach) return result; /* //go straight to the reachability end hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; VectorNormalize(hordir); //always use max speed when traveling through the air EA_Move(ms->client, hordir, 800); VectorCopy(hordir, result.movedir); */ // if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed)) { //go straight to the reachability end VectorSubtract(reach->end, ms->origin, hordir); hordir[2] = 0; VectorNormalize(hordir); speed = 400; } //end if // EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); // return result; } //end of the function BotFinishTravel_WeaponJump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) { float dist, speed; vec3_t hordir; bot_moveresult_t result; BotClearMoveResult(&result); //first walk straight to the reachability start hordir[0] = reach->start[0] - ms->origin[0]; hordir[1] = reach->start[1] - ms->origin[1]; hordir[2] = 0; dist = VectorNormalize(hordir); // BotCheckBlocked(ms, hordir, qtrue, &result); speed = 400; //elemantary action move in direction EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); // return result; } //end of the function BotTravel_JumpPad //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotFinishTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) { float speed; vec3_t hordir; bot_moveresult_t result; BotClearMoveResult(&result); if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed)) { hordir[0] = reach->end[0] - ms->origin[0]; hordir[1] = reach->end[1] - ms->origin[1]; hordir[2] = 0; VectorNormalize(hordir); speed = 400; } //end if BotCheckBlocked(ms, hordir, qtrue, &result); //elemantary action move in direction EA_Move(ms->client, hordir, speed); VectorCopy(hordir, result.movedir); // return result; } //end of the function BotFinishTravel_JumpPad //=========================================================================== // time before the reachability times out // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotReachabilityTime(aas_reachability_t *reach) { switch(reach->traveltype & TRAVELTYPE_MASK) { case TRAVEL_WALK: return 5; case TRAVEL_CROUCH: return 5; case TRAVEL_BARRIERJUMP: return 5; case TRAVEL_LADDER: return 6; case TRAVEL_WALKOFFLEDGE: return 5; case TRAVEL_JUMP: return 5; case TRAVEL_SWIM: return 5; case TRAVEL_WATERJUMP: return 5; case TRAVEL_TELEPORT: return 5; case TRAVEL_ELEVATOR: return 10; case TRAVEL_GRAPPLEHOOK: return 8; case TRAVEL_ROCKETJUMP: return 6; case TRAVEL_BFGJUMP: return 6; case TRAVEL_JUMPPAD: return 10; case TRAVEL_FUNCBOB: return 10; default: { botimport.Print(PRT_ERROR, "travel type %d not implemented yet\n", reach->traveltype); return 8; } //end case } //end switch } //end of the function BotReachabilityTime //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bot_moveresult_t BotMoveInGoalArea(bot_movestate_t *ms, bot_goal_t *goal) { bot_moveresult_t result; vec3_t dir; float dist, speed; #ifdef DEBUG //botimport.Print(PRT_MESSAGE, "%s: moving straight to goal\n", ClientName(ms->entitynum-1)); //AAS_ClearShownDebugLines(); //AAS_DebugLine(ms->origin, goal->origin, LINECOLOR_RED); #endif //DEBUG BotClearMoveResult(&result); //walk straight to the goal origin dir[0] = goal->origin[0] - ms->origin[0]; dir[1] = goal->origin[1] - ms->origin[1]; if (ms->moveflags & MFL_SWIMMING) { dir[2] = goal->origin[2] - ms->origin[2]; result.traveltype = TRAVEL_SWIM; } //end if else { dir[2] = 0; result.traveltype = TRAVEL_WALK; } //endif // dist = VectorNormalize(dir); if (dist > 100) dist = 100; speed = 400 - (400 - 4 * dist); if (speed < 10) speed = 0; // BotCheckBlocked(ms, dir, qtrue, &result); //elemantary action move in direction EA_Move(ms->client, dir, speed); VectorCopy(dir, result.movedir); // if (ms->moveflags & MFL_SWIMMING) { Vector2Angles(dir, result.ideal_viewangles); result.flags |= MOVERESULT_SWIMVIEW; } //end if //if (!debugline) debugline = botimport.DebugLineCreate(); //botimport.DebugLineShow(debugline, ms->origin, goal->origin, LINECOLOR_BLUE); // ms->lastreachnum = 0; ms->lastareanum = 0; ms->lastgoalareanum = goal->areanum; VectorCopy(ms->origin, ms->lastorigin); // return result; } //end of the function BotMoveInGoalArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags) { int reachnum, lastreachnum, foundjumppad, ent, resultflags; aas_reachability_t reach, lastreach; bot_movestate_t *ms; //vec3_t mins, maxs, up = {0, 0, 1}; //bsp_trace_t trace; //static int debugline; BotClearMoveResult(result); // ms = BotMoveStateFromHandle(movestate); if (!ms) return; //reset the grapple before testing if the bot has a valid goal //because the bot could loose all it's goals when stuck to a wall BotResetGrapple(ms); // if (!goal) { #ifdef DEBUG botimport.Print(PRT_MESSAGE, "client %d: movetogoal -> no goal\n", ms->client); #endif //DEBUG result->failure = qtrue; return; } //end if //botimport.Print(PRT_MESSAGE, "numavoidreach = %d\n", ms->numavoidreach); //remove some of the move flags ms->moveflags &= ~(MFL_SWIMMING|MFL_AGAINSTLADDER); //set some of the move flags //NOTE: the MFL_ONGROUND flag is also set in the higher AI if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND; // if (ms->moveflags & MFL_ONGROUND) { int modeltype, modelnum; ent = BotOnTopOfEntity(ms); if (ent != -1) { modelnum = AAS_EntityModelindex(ent); if (modelnum >= 0 && modelnum < MAX_MODELS) { modeltype = modeltypes[modelnum]; if (modeltype == MODELTYPE_FUNC_PLAT) { AAS_ReachabilityFromNum(ms->lastreachnum, &reach); //if the bot is Not using the elevator if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR || //NOTE: the face number is the plat model number (reach.facenum & 0x0000FFFF) != modelnum) { reachnum = AAS_NextModelReachability(0, modelnum); if (reachnum) { //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_plat\n", ms->client); AAS_ReachabilityFromNum(reachnum, &reach); ms->lastreachnum = reachnum; ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); } //end if else { if (bot_developer) { botimport.Print(PRT_MESSAGE, "client %d: on func_plat without reachability\n", ms->client); } //end if result->blocked = qtrue; result->blockentity = ent; result->flags |= MOVERESULT_ONTOPOFOBSTACLE; return; } //end else } //end if result->flags |= MOVERESULT_ONTOPOF_ELEVATOR; } //end if else if (modeltype == MODELTYPE_FUNC_BOB) { AAS_ReachabilityFromNum(ms->lastreachnum, &reach); //if the bot is Not using the func bobbing if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB || //NOTE: the face number is the func_bobbing model number (reach.facenum & 0x0000FFFF) != modelnum) { reachnum = AAS_NextModelReachability(0, modelnum); if (reachnum) { //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_bobbing\n", ms->client); AAS_ReachabilityFromNum(reachnum, &reach); ms->lastreachnum = reachnum; ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); } //end if else { if (bot_developer) { botimport.Print(PRT_MESSAGE, "client %d: on func_bobbing without reachability\n", ms->client); } //end if result->blocked = qtrue; result->blockentity = ent; result->flags |= MOVERESULT_ONTOPOFOBSTACLE; return; } //end else } //end if result->flags |= MOVERESULT_ONTOPOF_FUNCBOB; } //end if else if (modeltype == MODELTYPE_FUNC_STATIC || modeltype == MODELTYPE_FUNC_DOOR) { // check if ontop of a door bridge ? ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); // if not in a reachability area if (!AAS_AreaReachability(ms->areanum)) { result->blocked = qtrue; result->blockentity = ent; result->flags |= MOVERESULT_ONTOPOFOBSTACLE; return; } //end if } //end else if else { result->blocked = qtrue; result->blockentity = ent; result->flags |= MOVERESULT_ONTOPOFOBSTACLE; return; } //end else } //end if } //end if } //end if //if swimming if (AAS_Swimming(ms->origin)) ms->moveflags |= MFL_SWIMMING; //if against a ladder if (AAS_AgainstLadder(ms->origin)) ms->moveflags |= MFL_AGAINSTLADDER; //if the bot is on the ground, swimming or against a ladder if (ms->moveflags & (MFL_ONGROUND|MFL_SWIMMING|MFL_AGAINSTLADDER)) { //botimport.Print(PRT_MESSAGE, "%s: onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); // AAS_ReachabilityFromNum(ms->lastreachnum, &lastreach); //reachability area the bot is in //ms->areanum = BotReachabilityArea(ms->origin, ((lastreach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR)); ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); // if ( !ms->areanum ) { result->failure = qtrue; result->blocked = qtrue; result->blockentity = 0; result->type = RESULTTYPE_INSOLIDAREA; return; } //end if //if the bot is in the goal area if (ms->areanum == goal->areanum) { *result = BotMoveInGoalArea(ms, goal); return; } //end if //assume we can use the reachability from the last frame reachnum = ms->lastreachnum; //if there is a last reachability if (reachnum) { AAS_ReachabilityFromNum(reachnum, &reach); //check if the reachability is still valid if (!(AAS_TravelFlagForType(reach.traveltype) & travelflags)) { reachnum = 0; } //end if //special grapple hook case else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_GRAPPLEHOOK) { if (ms->reachability_time < AAS_Time() || (ms->moveflags & MFL_GRAPPLERESET)) { reachnum = 0; } //end if } //end if //special elevator case else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR || (reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) { if ((result->flags & MOVERESULT_ONTOPOF_FUNCBOB) || (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)) { ms->reachability_time = AAS_Time() + 5; } //end if //if the bot was going for an elevator and reached the reachability area if (ms->areanum == reach.areanum || ms->reachability_time < AAS_Time()) { reachnum = 0; } //end if } //end if else { #ifdef DEBUG if (bot_developer) { if (ms->reachability_time < AAS_Time()) { botimport.Print(PRT_MESSAGE, "client %d: reachability timeout in ", ms->client); AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); botimport.Print(PRT_MESSAGE, "\n"); } //end if /* if (ms->lastareanum != ms->areanum) { botimport.Print(PRT_MESSAGE, "changed from area %d to %d\n", ms->lastareanum, ms->areanum); } //end if*/ } //end if #endif //DEBUG //if the goal area changed or the reachability timed out //or the area changed if (ms->lastgoalareanum != goal->areanum || ms->reachability_time < AAS_Time() || ms->lastareanum != ms->areanum) { reachnum = 0; //botimport.Print(PRT_MESSAGE, "area change or timeout\n"); } //end else if } //end else } //end if resultflags = 0; //if the bot needs a new reachability if (!reachnum) { //if the area has no reachability links if (!AAS_AreaReachability(ms->areanum)) { #ifdef DEBUG if (bot_developer) { botimport.Print(PRT_MESSAGE, "area %d no reachability\n", ms->areanum); } //end if #endif //DEBUG } //end if //get a new reachability leading towards the goal reachnum = BotGetReachabilityToGoal(ms->origin, ms->areanum, ms->lastgoalareanum, ms->lastareanum, ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, goal, travelflags, travelflags, ms->avoidspots, ms->numavoidspots, &resultflags); //the area number the reachability starts in ms->reachareanum = ms->areanum; //reset some state variables ms->jumpreach = 0; //for TRAVEL_JUMP ms->moveflags &= ~MFL_GRAPPLERESET; //for TRAVEL_GRAPPLEHOOK //if there is a reachability to the goal if (reachnum) { AAS_ReachabilityFromNum(reachnum, &reach); //set a timeout for this reachability ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); // #ifdef AVOIDREACH //add the reachability to the reachabilities to avoid for a while BotAddToAvoidReach(ms, reachnum, AVOIDREACH_TIME); #endif //AVOIDREACH } //end if #ifdef DEBUG else if (bot_developer) { botimport.Print(PRT_MESSAGE, "goal not reachable\n"); Com_Memset(&reach, 0, sizeof(aas_reachability_t)); //make compiler happy } //end else if (bot_developer) { //if still going for the same goal if (ms->lastgoalareanum == goal->areanum) { if (ms->lastareanum == reach.areanum) { botimport.Print(PRT_MESSAGE, "same goal, going back to previous area\n"); } //end if } //end if } //end if #endif //DEBUG } //end else // ms->lastreachnum = reachnum; ms->lastgoalareanum = goal->areanum; ms->lastareanum = ms->areanum; //if the bot has a reachability if (reachnum) { //get the reachability from the number AAS_ReachabilityFromNum(reachnum, &reach); result->traveltype = reach.traveltype; // #ifdef DEBUG_AI_MOVE AAS_ClearShownDebugLines(); AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); AAS_ShowReachability(&reach); #endif //DEBUG_AI_MOVE // #ifdef DEBUG //botimport.Print(PRT_MESSAGE, "client %d: ", ms->client); //AAS_PrintTravelType(reach.traveltype); //botimport.Print(PRT_MESSAGE, "\n"); #endif //DEBUG switch(reach.traveltype & TRAVELTYPE_MASK) { case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break; case TRAVEL_CROUCH: *result = BotTravel_Crouch(ms, &reach); break; case TRAVEL_BARRIERJUMP: *result = BotTravel_BarrierJump(ms, &reach); break; case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; case TRAVEL_WALKOFFLEDGE: *result = BotTravel_WalkOffLedge(ms, &reach); break; case TRAVEL_JUMP: *result = BotTravel_Jump(ms, &reach); break; case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; case TRAVEL_WATERJUMP: *result = BotTravel_WaterJump(ms, &reach); break; case TRAVEL_TELEPORT: *result = BotTravel_Teleport(ms, &reach); break; case TRAVEL_ELEVATOR: *result = BotTravel_Elevator(ms, &reach); break; case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; case TRAVEL_ROCKETJUMP: *result = BotTravel_RocketJump(ms, &reach); break; case TRAVEL_BFGJUMP: *result = BotTravel_BFGJump(ms, &reach); break; case TRAVEL_JUMPPAD: *result = BotTravel_JumpPad(ms, &reach); break; case TRAVEL_FUNCBOB: *result = BotTravel_FuncBobbing(ms, &reach); break; default: { botimport.Print(PRT_FATAL, "travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK)); break; } //end case } //end switch result->traveltype = reach.traveltype; result->flags |= resultflags; } //end if else { result->failure = qtrue; result->flags |= resultflags; Com_Memset(&reach, 0, sizeof(aas_reachability_t)); } //end else #ifdef DEBUG if (bot_developer) { if (result->failure) { botimport.Print(PRT_MESSAGE, "client %d: movement failure in ", ms->client); AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); botimport.Print(PRT_MESSAGE, "\n"); } //end if } //end if #endif //DEBUG } //end if else { int i, numareas, areas[16]; vec3_t end; //special handling of jump pads when the bot uses a jump pad without knowing it foundjumppad = qfalse; VectorMA(ms->origin, -2 * ms->thinktime, ms->velocity, end); numareas = AAS_TraceAreas(ms->origin, end, areas, NULL, 16); for (i = numareas-1; i >= 0; i--) { if (AAS_AreaJumpPad(areas[i])) { //botimport.Print(PRT_MESSAGE, "client %d used a jumppad without knowing, area %d\n", ms->client, areas[i]); foundjumppad = qtrue; lastreachnum = BotGetReachabilityToGoal(end, areas[i], ms->lastgoalareanum, ms->lastareanum, ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, goal, travelflags, TFL_JUMPPAD, ms->avoidspots, ms->numavoidspots, NULL); if (lastreachnum) { ms->lastreachnum = lastreachnum; ms->lastareanum = areas[i]; //botimport.Print(PRT_MESSAGE, "found jumppad reachability\n"); break; } //end if else { for (lastreachnum = AAS_NextAreaReachability(areas[i], 0); lastreachnum; lastreachnum = AAS_NextAreaReachability(areas[i], lastreachnum)) { //get the reachability from the number AAS_ReachabilityFromNum(lastreachnum, &reach); if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) { ms->lastreachnum = lastreachnum; ms->lastareanum = areas[i]; //botimport.Print(PRT_MESSAGE, "found jumppad reachability hard!!\n"); break; } //end if } //end for if (lastreachnum) break; } //end else } //end if } //end for if (bot_developer) { //if a jumppad is found with the trace but no reachability is found if (foundjumppad && !ms->lastreachnum) { botimport.Print(PRT_MESSAGE, "client %d didn't find jumppad reachability\n", ms->client); } //end if } //end if // if (ms->lastreachnum) { //botimport.Print(PRT_MESSAGE, "%s: NOT onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); AAS_ReachabilityFromNum(ms->lastreachnum, &reach); result->traveltype = reach.traveltype; #ifdef DEBUG //botimport.Print(PRT_MESSAGE, "client %d finish: ", ms->client); //AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); //botimport.Print(PRT_MESSAGE, "\n"); #endif //DEBUG // switch(reach.traveltype & TRAVELTYPE_MASK) { case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break;//BotFinishTravel_Walk(ms, &reach); break; case TRAVEL_CROUCH: /*do nothing*/ break; case TRAVEL_BARRIERJUMP: *result = BotFinishTravel_BarrierJump(ms, &reach); break; case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; case TRAVEL_WALKOFFLEDGE: *result = BotFinishTravel_WalkOffLedge(ms, &reach); break; case TRAVEL_JUMP: *result = BotFinishTravel_Jump(ms, &reach); break; case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; case TRAVEL_WATERJUMP: *result = BotFinishTravel_WaterJump(ms, &reach); break; case TRAVEL_TELEPORT: /*do nothing*/ break; case TRAVEL_ELEVATOR: *result = BotFinishTravel_Elevator(ms, &reach); break; case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; case TRAVEL_ROCKETJUMP: case TRAVEL_BFGJUMP: *result = BotFinishTravel_WeaponJump(ms, &reach); break; case TRAVEL_JUMPPAD: *result = BotFinishTravel_JumpPad(ms, &reach); break; case TRAVEL_FUNCBOB: *result = BotFinishTravel_FuncBobbing(ms, &reach); break; default: { botimport.Print(PRT_FATAL, "(last) travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK)); break; } //end case } //end switch result->traveltype = reach.traveltype; #ifdef DEBUG if (bot_developer) { if (result->failure) { botimport.Print(PRT_MESSAGE, "client %d: movement failure in finish ", ms->client); AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); botimport.Print(PRT_MESSAGE, "\n"); } //end if } //end if #endif //DEBUG } //end if } //end else //FIXME: is it right to do this here? if (result->blocked) ms->reachability_time -= 10 * ms->thinktime; //copy the last origin VectorCopy(ms->origin, ms->lastorigin); //return the movement result return; } //end of the function BotMoveToGoal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotResetAvoidReach(int movestate) { bot_movestate_t *ms; ms = BotMoveStateFromHandle(movestate); if (!ms) return; Com_Memset(ms->avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); Com_Memset(ms->avoidreachtimes, 0, MAX_AVOIDREACH * sizeof(float)); Com_Memset(ms->avoidreachtries, 0, MAX_AVOIDREACH * sizeof(int)); } //end of the function BotResetAvoidReach //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotResetLastAvoidReach(int movestate) { int i, latest; float latesttime; bot_movestate_t *ms; ms = BotMoveStateFromHandle(movestate); if (!ms) return; latesttime = 0; latest = 0; for (i = 0; i < MAX_AVOIDREACH; i++) { if (ms->avoidreachtimes[i] > latesttime) { latesttime = ms->avoidreachtimes[i]; latest = i; } //end if } //end for if (latesttime) { ms->avoidreachtimes[latest] = 0; if (ms->avoidreachtries[i] > 0) ms->avoidreachtries[latest]--; } //end if } //end of the function BotResetLastAvoidReach //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotResetMoveState(int movestate) { bot_movestate_t *ms; ms = BotMoveStateFromHandle(movestate); if (!ms) return; Com_Memset(ms, 0, sizeof(bot_movestate_t)); } //end of the function BotResetMoveState //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotSetupMoveAI(void) { BotSetBrushModelTypes(); sv_maxstep = LibVar("sv_step", "18"); sv_maxbarrier = LibVar("sv_maxbarrier", "32"); sv_gravity = LibVar("sv_gravity", "800"); weapindex_rocketlauncher = LibVar("weapindex_rocketlauncher", "5"); weapindex_bfg10k = LibVar("weapindex_bfg10k", "9"); weapindex_grapple = LibVar("weapindex_grapple", "10"); entitytypemissile = LibVar("entitytypemissile", "3"); offhandgrapple = LibVar("offhandgrapple", "0"); cmd_grappleon = LibVar("cmd_grappleon", "grappleon"); cmd_grappleoff = LibVar("cmd_grappleoff", "grappleoff"); return BLERR_NOERROR; } //end of the function BotSetupMoveAI //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotShutdownMoveAI(void) { int i; for (i = 1; i <= MAX_CLIENTS; i++) { if (botmovestates[i]) { FreeMemory(botmovestates[i]); botmovestates[i] = NULL; } //end if } //end for } //end of the function BotShutdownMoveAI ================================================ FILE: code/botlib/be_ai_weap.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_ai_weap.c * * desc: weapon AI * * $Archive: /MissionPack/code/botlib/be_ai_weap.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_libvar.h" #include "l_log.h" #include "l_memory.h" #include "l_utils.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "be_ai_weight.h" //fuzzy weights #include "../game/be_ai_weap.h" //#define DEBUG_AI_WEAP //structure field offsets #define WEAPON_OFS(x) (int)&(((weaponinfo_t *)0)->x) #define PROJECTILE_OFS(x) (int)&(((projectileinfo_t *)0)->x) //weapon definition // bk001212 - static static fielddef_t weaponinfo_fields[] = { {"number", WEAPON_OFS(number), FT_INT}, //weapon number {"name", WEAPON_OFS(name), FT_STRING}, //name of the weapon {"level", WEAPON_OFS(level), FT_INT}, {"model", WEAPON_OFS(model), FT_STRING}, //model of the weapon {"weaponindex", WEAPON_OFS(weaponindex), FT_INT}, //index of weapon in inventory {"flags", WEAPON_OFS(flags), FT_INT}, //special flags {"projectile", WEAPON_OFS(projectile), FT_STRING}, //projectile used by the weapon {"numprojectiles", WEAPON_OFS(numprojectiles), FT_INT}, //number of projectiles {"hspread", WEAPON_OFS(hspread), FT_FLOAT}, //horizontal spread of projectiles (degrees from middle) {"vspread", WEAPON_OFS(vspread), FT_FLOAT}, //vertical spread of projectiles (degrees from middle) {"speed", WEAPON_OFS(speed), FT_FLOAT}, //speed of the projectile (0 = instant hit) {"acceleration", WEAPON_OFS(acceleration), FT_FLOAT}, //"acceleration" * time (in seconds) + "speed" = projectile speed {"recoil", WEAPON_OFS(recoil), FT_FLOAT|FT_ARRAY, 3}, //amount of recoil the player gets from the weapon {"offset", WEAPON_OFS(offset), FT_FLOAT|FT_ARRAY, 3}, //projectile start offset relative to eye and view angles {"angleoffset", WEAPON_OFS(angleoffset), FT_FLOAT|FT_ARRAY, 3},//offset of the shoot angles relative to the view angles {"extrazvelocity", WEAPON_OFS(extrazvelocity), FT_FLOAT},//extra z velocity the projectile gets {"ammoamount", WEAPON_OFS(ammoamount), FT_INT}, //ammo amount used per shot {"ammoindex", WEAPON_OFS(ammoindex), FT_INT}, //index of ammo in inventory {"activate", WEAPON_OFS(activate), FT_FLOAT}, //time it takes to select the weapon {"reload", WEAPON_OFS(reload), FT_FLOAT}, //time it takes to reload the weapon {"spinup", WEAPON_OFS(spinup), FT_FLOAT}, //time it takes before first shot {"spindown", WEAPON_OFS(spindown), FT_FLOAT}, //time it takes before weapon stops firing {NULL, 0, 0, 0} }; //projectile definition static fielddef_t projectileinfo_fields[] = { {"name", PROJECTILE_OFS(name), FT_STRING}, //name of the projectile {"model", WEAPON_OFS(model), FT_STRING}, //model of the projectile {"flags", PROJECTILE_OFS(flags), FT_INT}, //special flags {"gravity", PROJECTILE_OFS(gravity), FT_FLOAT}, //amount of gravity applied to the projectile [0,1] {"damage", PROJECTILE_OFS(damage), FT_INT}, //damage of the projectile {"radius", PROJECTILE_OFS(radius), FT_FLOAT}, //radius of damage {"visdamage", PROJECTILE_OFS(visdamage), FT_INT}, //damage of the projectile to visible entities {"damagetype", PROJECTILE_OFS(damagetype), FT_INT}, //type of damage (combination of the DAMAGETYPE_? flags) {"healthinc", PROJECTILE_OFS(healthinc), FT_INT}, //health increase the owner gets {"push", PROJECTILE_OFS(push), FT_FLOAT}, //amount a player is pushed away from the projectile impact {"detonation", PROJECTILE_OFS(detonation), FT_FLOAT}, //time before projectile explodes after fire pressed {"bounce", PROJECTILE_OFS(bounce), FT_FLOAT}, //amount the projectile bounces {"bouncefric", PROJECTILE_OFS(bouncefric), FT_FLOAT}, //amount the bounce decreases per bounce {"bouncestop", PROJECTILE_OFS(bouncestop), FT_FLOAT}, //minimum bounce value before bouncing stops //recurive projectile definition?? {NULL, 0, 0, 0} }; static structdef_t weaponinfo_struct = { sizeof(weaponinfo_t), weaponinfo_fields }; static structdef_t projectileinfo_struct = { sizeof(projectileinfo_t), projectileinfo_fields }; //weapon configuration: set of weapons with projectiles typedef struct weaponconfig_s { int numweapons; int numprojectiles; projectileinfo_t *projectileinfo; weaponinfo_t *weaponinfo; } weaponconfig_t; //the bot weapon state typedef struct bot_weaponstate_s { struct weightconfig_s *weaponweightconfig; //weapon weight configuration int *weaponweightindex; //weapon weight index } bot_weaponstate_t; static bot_weaponstate_t *botweaponstates[MAX_CLIENTS+1]; static weaponconfig_t *weaponconfig; //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== int BotValidWeaponNumber(int weaponnum) { if (weaponnum <= 0 || weaponnum > weaponconfig->numweapons) { botimport.Print(PRT_ERROR, "weapon number out of range\n"); return qfalse; } //end if return qtrue; } //end of the function BotValidWeaponNumber //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== bot_weaponstate_t *BotWeaponStateFromHandle(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); return NULL; } //end if if (!botweaponstates[handle]) { botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); return NULL; } //end if return botweaponstates[handle]; } //end of the function BotWeaponStateFromHandle //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef DEBUG_AI_WEAP void DumpWeaponConfig(weaponconfig_t *wc) { FILE *fp; int i; fp = Log_FileStruct(); if (!fp) return; for (i = 0; i < wc->numprojectiles; i++) { WriteStructure(fp, &projectileinfo_struct, (char *) &wc->projectileinfo[i]); Log_Flush(); } //end for for (i = 0; i < wc->numweapons; i++) { WriteStructure(fp, &weaponinfo_struct, (char *) &wc->weaponinfo[i]); Log_Flush(); } //end for } //end of the function DumpWeaponConfig #endif //DEBUG_AI_WEAP //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== weaponconfig_t *LoadWeaponConfig(char *filename) { int max_weaponinfo, max_projectileinfo; token_t token; char path[MAX_PATH]; int i, j; source_t *source; weaponconfig_t *wc; weaponinfo_t weaponinfo; max_weaponinfo = (int) LibVarValue("max_weaponinfo", "32"); if (max_weaponinfo < 0) { botimport.Print(PRT_ERROR, "max_weaponinfo = %d\n", max_weaponinfo); max_weaponinfo = 32; LibVarSet("max_weaponinfo", "32"); } //end if max_projectileinfo = (int) LibVarValue("max_projectileinfo", "32"); if (max_projectileinfo < 0) { botimport.Print(PRT_ERROR, "max_projectileinfo = %d\n", max_projectileinfo); max_projectileinfo = 32; LibVarSet("max_projectileinfo", "32"); } //end if strncpy(path, filename, MAX_PATH); PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(path); if (!source) { botimport.Print(PRT_ERROR, "counldn't load %s\n", path); return NULL; } //end if //initialize weapon config wc = (weaponconfig_t *) GetClearedHunkMemory(sizeof(weaponconfig_t) + max_weaponinfo * sizeof(weaponinfo_t) + max_projectileinfo * sizeof(projectileinfo_t)); wc->weaponinfo = (weaponinfo_t *) ((char *) wc + sizeof(weaponconfig_t)); wc->projectileinfo = (projectileinfo_t *) ((char *) wc->weaponinfo + max_weaponinfo * sizeof(weaponinfo_t)); wc->numweapons = max_weaponinfo; wc->numprojectiles = 0; //parse the source file while(PC_ReadToken(source, &token)) { if (!strcmp(token.string, "weaponinfo")) { Com_Memset(&weaponinfo, 0, sizeof(weaponinfo_t)); if (!ReadStructure(source, &weaponinfo_struct, (char *) &weaponinfo)) { FreeMemory(wc); FreeSource(source); return NULL; } //end if if (weaponinfo.number < 0 || weaponinfo.number >= max_weaponinfo) { botimport.Print(PRT_ERROR, "weapon info number %d out of range in %s\n", weaponinfo.number, path); FreeMemory(wc); FreeSource(source); return NULL; } //end if Com_Memcpy(&wc->weaponinfo[weaponinfo.number], &weaponinfo, sizeof(weaponinfo_t)); wc->weaponinfo[weaponinfo.number].valid = qtrue; } //end if else if (!strcmp(token.string, "projectileinfo")) { if (wc->numprojectiles >= max_projectileinfo) { botimport.Print(PRT_ERROR, "more than %d projectiles defined in %s\n", max_projectileinfo, path); FreeMemory(wc); FreeSource(source); return NULL; } //end if Com_Memset(&wc->projectileinfo[wc->numprojectiles], 0, sizeof(projectileinfo_t)); if (!ReadStructure(source, &projectileinfo_struct, (char *) &wc->projectileinfo[wc->numprojectiles])) { FreeMemory(wc); FreeSource(source); return NULL; } //end if wc->numprojectiles++; } //end if else { botimport.Print(PRT_ERROR, "unknown definition %s in %s\n", token.string, path); FreeMemory(wc); FreeSource(source); return NULL; } //end else } //end while FreeSource(source); //fix up weapons for (i = 0; i < wc->numweapons; i++) { if (!wc->weaponinfo[i].valid) continue; if (!wc->weaponinfo[i].name[0]) { botimport.Print(PRT_ERROR, "weapon %d has no name in %s\n", i, path); FreeMemory(wc); return NULL; } //end if if (!wc->weaponinfo[i].projectile[0]) { botimport.Print(PRT_ERROR, "weapon %s has no projectile in %s\n", wc->weaponinfo[i].name, path); FreeMemory(wc); return NULL; } //end if //find the projectile info and copy it to the weapon info for (j = 0; j < wc->numprojectiles; j++) { if (!strcmp(wc->projectileinfo[j].name, wc->weaponinfo[i].projectile)) { Com_Memcpy(&wc->weaponinfo[i].proj, &wc->projectileinfo[j], sizeof(projectileinfo_t)); break; } //end if } //end for if (j == wc->numprojectiles) { botimport.Print(PRT_ERROR, "weapon %s uses undefined projectile in %s\n", wc->weaponinfo[i].name, path); FreeMemory(wc); return NULL; } //end if } //end for if (!wc->numweapons) botimport.Print(PRT_WARNING, "no weapon info loaded\n"); botimport.Print(PRT_MESSAGE, "loaded %s\n", path); return wc; } //end of the function LoadWeaponConfig //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int *WeaponWeightIndex(weightconfig_t *wwc, weaponconfig_t *wc) { int *index, i; //initialize item weight index index = (int *) GetClearedMemory(sizeof(int) * wc->numweapons); for (i = 0; i < wc->numweapons; i++) { index[i] = FindFuzzyWeight(wwc, wc->weaponinfo[i].name); } //end for return index; } //end of the function WeaponWeightIndex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotFreeWeaponWeights(int weaponstate) { bot_weaponstate_t *ws; ws = BotWeaponStateFromHandle(weaponstate); if (!ws) return; if (ws->weaponweightconfig) FreeWeightConfig(ws->weaponweightconfig); if (ws->weaponweightindex) FreeMemory(ws->weaponweightindex); } //end of the function BotFreeWeaponWeights //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotLoadWeaponWeights(int weaponstate, char *filename) { bot_weaponstate_t *ws; ws = BotWeaponStateFromHandle(weaponstate); if (!ws) return BLERR_CANNOTLOADWEAPONWEIGHTS; BotFreeWeaponWeights(weaponstate); // ws->weaponweightconfig = ReadWeightConfig(filename); if (!ws->weaponweightconfig) { botimport.Print(PRT_FATAL, "couldn't load weapon config %s\n", filename); return BLERR_CANNOTLOADWEAPONWEIGHTS; } //end if if (!weaponconfig) return BLERR_CANNOTLOADWEAPONCONFIG; ws->weaponweightindex = WeaponWeightIndex(ws->weaponweightconfig, weaponconfig); return BLERR_NOERROR; } //end of the function BotLoadWeaponWeights //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo) { bot_weaponstate_t *ws; if (!BotValidWeaponNumber(weapon)) return; ws = BotWeaponStateFromHandle(weaponstate); if (!ws) return; if (!weaponconfig) return; Com_Memcpy(weaponinfo, &weaponconfig->weaponinfo[weapon], sizeof(weaponinfo_t)); } //end of the function BotGetWeaponInfo //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotChooseBestFightWeapon(int weaponstate, int *inventory) { int i, index, bestweapon; float weight, bestweight; weaponconfig_t *wc; bot_weaponstate_t *ws; ws = BotWeaponStateFromHandle(weaponstate); if (!ws) return 0; wc = weaponconfig; if (!weaponconfig) return 0; //if the bot has no weapon weight configuration if (!ws->weaponweightconfig) return 0; bestweight = 0; bestweapon = 0; for (i = 0; i < wc->numweapons; i++) { if (!wc->weaponinfo[i].valid) continue; index = ws->weaponweightindex[i]; if (index < 0) continue; weight = FuzzyWeight(inventory, ws->weaponweightconfig, index); if (weight > bestweight) { bestweight = weight; bestweapon = i; } //end if } //end for return bestweapon; } //end of the function BotChooseBestFightWeapon //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotResetWeaponState(int weaponstate) { struct weightconfig_s *weaponweightconfig; int *weaponweightindex; bot_weaponstate_t *ws; ws = BotWeaponStateFromHandle(weaponstate); if (!ws) return; weaponweightconfig = ws->weaponweightconfig; weaponweightindex = ws->weaponweightindex; //Com_Memset(ws, 0, sizeof(bot_weaponstate_t)); ws->weaponweightconfig = weaponweightconfig; ws->weaponweightindex = weaponweightindex; } //end of the function BotResetWeaponState //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== int BotAllocWeaponState(void) { int i; for (i = 1; i <= MAX_CLIENTS; i++) { if (!botweaponstates[i]) { botweaponstates[i] = GetClearedMemory(sizeof(bot_weaponstate_t)); return i; } //end if } //end for return 0; } //end of the function BotAllocWeaponState //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void BotFreeWeaponState(int handle) { if (handle <= 0 || handle > MAX_CLIENTS) { botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); return; } //end if if (!botweaponstates[handle]) { botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); return; } //end if BotFreeWeaponWeights(handle); FreeMemory(botweaponstates[handle]); botweaponstates[handle] = NULL; } //end of the function BotFreeWeaponState //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotSetupWeaponAI(void) { char *file; file = LibVarString("weaponconfig", "weapons.c"); weaponconfig = LoadWeaponConfig(file); if (!weaponconfig) { botimport.Print(PRT_FATAL, "couldn't load the weapon config\n"); return BLERR_CANNOTLOADWEAPONCONFIG; } //end if #ifdef DEBUG_AI_WEAP DumpWeaponConfig(weaponconfig); #endif //DEBUG_AI_WEAP // return BLERR_NOERROR; } //end of the function BotSetupWeaponAI //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotShutdownWeaponAI(void) { int i; if (weaponconfig) FreeMemory(weaponconfig); weaponconfig = NULL; for (i = 1; i <= MAX_CLIENTS; i++) { if (botweaponstates[i]) { BotFreeWeaponState(i); } //end if } //end for } //end of the function BotShutdownWeaponAI ================================================ FILE: code/botlib/be_ai_weight.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_ai_weight.c * * desc: fuzzy logic * * $Archive: /MissionPack/code/botlib/be_ai_weight.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_log.h" #include "l_utils.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_libvar.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_interface.h" #include "be_ai_weight.h" #define MAX_INVENTORYVALUE 999999 #define EVALUATERECURSIVELY #define MAX_WEIGHT_FILES 128 weightconfig_t *weightFileList[MAX_WEIGHT_FILES]; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int ReadValue(source_t *source, float *value) { token_t token; if (!PC_ExpectAnyToken(source, &token)) return qfalse; if (!strcmp(token.string, "-")) { SourceWarning(source, "negative value set to zero\n"); if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) return qfalse; } //end if if (token.type != TT_NUMBER) { SourceError(source, "invalid return value %s\n", token.string); return qfalse; } //end if *value = token.floatvalue; return qtrue; } //end of the function ReadValue //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int ReadFuzzyWeight(source_t *source, fuzzyseperator_t *fs) { if (PC_CheckTokenString(source, "balance")) { fs->type = WT_BALANCE; if (!PC_ExpectTokenString(source, "(")) return qfalse; if (!ReadValue(source, &fs->weight)) return qfalse; if (!PC_ExpectTokenString(source, ",")) return qfalse; if (!ReadValue(source, &fs->minweight)) return qfalse; if (!PC_ExpectTokenString(source, ",")) return qfalse; if (!ReadValue(source, &fs->maxweight)) return qfalse; if (!PC_ExpectTokenString(source, ")")) return qfalse; } //end if else { fs->type = 0; if (!ReadValue(source, &fs->weight)) return qfalse; fs->minweight = fs->weight; fs->maxweight = fs->weight; } //end if if (!PC_ExpectTokenString(source, ";")) return qfalse; return qtrue; } //end of the function ReadFuzzyWeight //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeFuzzySeperators_r(fuzzyseperator_t *fs) { if (!fs) return; if (fs->child) FreeFuzzySeperators_r(fs->child); if (fs->next) FreeFuzzySeperators_r(fs->next); FreeMemory(fs); } //end of the function FreeFuzzySeperators //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeWeightConfig2(weightconfig_t *config) { int i; for (i = 0; i < config->numweights; i++) { FreeFuzzySeperators_r(config->weights[i].firstseperator); if (config->weights[i].name) FreeMemory(config->weights[i].name); } //end for FreeMemory(config); } //end of the function FreeWeightConfig2 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeWeightConfig(weightconfig_t *config) { if (!LibVarGetValue("bot_reloadcharacters")) return; FreeWeightConfig2(config); } //end of the function FreeWeightConfig //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== fuzzyseperator_t *ReadFuzzySeperators_r(source_t *source) { int newindent, index, def, founddefault; token_t token; fuzzyseperator_t *fs, *lastfs, *firstfs; founddefault = qfalse; firstfs = NULL; lastfs = NULL; if (!PC_ExpectTokenString(source, "(")) return NULL; if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) return NULL; index = token.intvalue; if (!PC_ExpectTokenString(source, ")")) return NULL; if (!PC_ExpectTokenString(source, "{")) return NULL; if (!PC_ExpectAnyToken(source, &token)) return NULL; do { def = !strcmp(token.string, "default"); if (def || !strcmp(token.string, "case")) { fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); fs->index = index; if (lastfs) lastfs->next = fs; else firstfs = fs; lastfs = fs; if (def) { if (founddefault) { SourceError(source, "switch already has a default\n"); FreeFuzzySeperators_r(firstfs); return NULL; } //end if fs->value = MAX_INVENTORYVALUE; founddefault = qtrue; } //end if else { if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) { FreeFuzzySeperators_r(firstfs); return NULL; } //end if fs->value = token.intvalue; } //end else if (!PC_ExpectTokenString(source, ":") || !PC_ExpectAnyToken(source, &token)) { FreeFuzzySeperators_r(firstfs); return NULL; } //end if newindent = qfalse; if (!strcmp(token.string, "{")) { newindent = qtrue; if (!PC_ExpectAnyToken(source, &token)) { FreeFuzzySeperators_r(firstfs); return NULL; } //end if } //end if if (!strcmp(token.string, "return")) { if (!ReadFuzzyWeight(source, fs)) { FreeFuzzySeperators_r(firstfs); return NULL; } //end if } //end if else if (!strcmp(token.string, "switch")) { fs->child = ReadFuzzySeperators_r(source); if (!fs->child) { FreeFuzzySeperators_r(firstfs); return NULL; } //end if } //end else if else { SourceError(source, "invalid name %s\n", token.string); return NULL; } //end else if (newindent) { if (!PC_ExpectTokenString(source, "}")) { FreeFuzzySeperators_r(firstfs); return NULL; } //end if } //end if } //end if else { FreeFuzzySeperators_r(firstfs); SourceError(source, "invalid name %s\n", token.string); return NULL; } //end else if (!PC_ExpectAnyToken(source, &token)) { FreeFuzzySeperators_r(firstfs); return NULL; } //end if } while(strcmp(token.string, "}")); // if (!founddefault) { SourceWarning(source, "switch without default\n"); fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); fs->index = index; fs->value = MAX_INVENTORYVALUE; fs->weight = 0; fs->next = NULL; fs->child = NULL; if (lastfs) lastfs->next = fs; else firstfs = fs; lastfs = fs; } //end if // return firstfs; } //end of the function ReadFuzzySeperators_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== weightconfig_t *ReadWeightConfig(char *filename) { int newindent, avail = 0, n; token_t token; source_t *source; fuzzyseperator_t *fs; weightconfig_t *config = NULL; #ifdef DEBUG int starttime; starttime = Sys_MilliSeconds(); #endif //DEBUG if (!LibVarGetValue("bot_reloadcharacters")) { avail = -1; for( n = 0; n < MAX_WEIGHT_FILES; n++ ) { config = weightFileList[n]; if( !config ) { if( avail == -1 ) { avail = n; } //end if continue; } //end if if( strcmp( filename, config->filename ) == 0 ) { //botimport.Print( PRT_MESSAGE, "retained %s\n", filename ); return config; } //end if } //end for if( avail == -1 ) { botimport.Print( PRT_ERROR, "weightFileList was full trying to load %s\n", filename ); return NULL; } //end if } //end if PC_SetBaseFolder(BOTFILESBASEFOLDER); source = LoadSourceFile(filename); if (!source) { botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); return NULL; } //end if // config = (weightconfig_t *) GetClearedMemory(sizeof(weightconfig_t)); config->numweights = 0; Q_strncpyz( config->filename, filename, sizeof(config->filename) ); //parse the item config file while(PC_ReadToken(source, &token)) { if (!strcmp(token.string, "weight")) { if (config->numweights >= MAX_WEIGHTS) { SourceWarning(source, "too many fuzzy weights\n"); break; } //end if if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) { FreeWeightConfig(config); FreeSource(source); return NULL; } //end if StripDoubleQuotes(token.string); config->weights[config->numweights].name = (char *) GetClearedMemory(strlen(token.string) + 1); strcpy(config->weights[config->numweights].name, token.string); if (!PC_ExpectAnyToken(source, &token)) { FreeWeightConfig(config); FreeSource(source); return NULL; } //end if newindent = qfalse; if (!strcmp(token.string, "{")) { newindent = qtrue; if (!PC_ExpectAnyToken(source, &token)) { FreeWeightConfig(config); FreeSource(source); return NULL; } //end if } //end if if (!strcmp(token.string, "switch")) { fs = ReadFuzzySeperators_r(source); if (!fs) { FreeWeightConfig(config); FreeSource(source); return NULL; } //end if config->weights[config->numweights].firstseperator = fs; } //end if else if (!strcmp(token.string, "return")) { fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); fs->index = 0; fs->value = MAX_INVENTORYVALUE; fs->next = NULL; fs->child = NULL; if (!ReadFuzzyWeight(source, fs)) { FreeMemory(fs); FreeWeightConfig(config); FreeSource(source); return NULL; } //end if config->weights[config->numweights].firstseperator = fs; } //end else if else { SourceError(source, "invalid name %s\n", token.string); FreeWeightConfig(config); FreeSource(source); return NULL; } //end else if (newindent) { if (!PC_ExpectTokenString(source, "}")) { FreeWeightConfig(config); FreeSource(source); return NULL; } //end if } //end if config->numweights++; } //end if else { SourceError(source, "invalid name %s\n", token.string); FreeWeightConfig(config); FreeSource(source); return NULL; } //end else } //end while //free the source at the end of a pass FreeSource(source); //if the file was located in a pak file botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); #ifdef DEBUG if (bot_developer) { botimport.Print(PRT_MESSAGE, "weights loaded in %d msec\n", Sys_MilliSeconds() - starttime); } //end if #endif //DEBUG // if (!LibVarGetValue("bot_reloadcharacters")) { weightFileList[avail] = config; } //end if // return config; } //end of the function ReadWeightConfig #if 0 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WriteFuzzyWeight(FILE *fp, fuzzyseperator_t *fs) { if (fs->type == WT_BALANCE) { if (fprintf(fp, " return balance(") < 0) return qfalse; if (!WriteFloat(fp, fs->weight)) return qfalse; if (fprintf(fp, ",") < 0) return qfalse; if (!WriteFloat(fp, fs->minweight)) return qfalse; if (fprintf(fp, ",") < 0) return qfalse; if (!WriteFloat(fp, fs->maxweight)) return qfalse; if (fprintf(fp, ");\n") < 0) return qfalse; } //end if else { if (fprintf(fp, " return ") < 0) return qfalse; if (!WriteFloat(fp, fs->weight)) return qfalse; if (fprintf(fp, ";\n") < 0) return qfalse; } //end else return qtrue; } //end of the function WriteFuzzyWeight //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WriteFuzzySeperators_r(FILE *fp, fuzzyseperator_t *fs, int indent) { if (!WriteIndent(fp, indent)) return qfalse; if (fprintf(fp, "switch(%d)\n", fs->index) < 0) return qfalse; if (!WriteIndent(fp, indent)) return qfalse; if (fprintf(fp, "{\n") < 0) return qfalse; indent++; do { if (!WriteIndent(fp, indent)) return qfalse; if (fs->next) { if (fprintf(fp, "case %d:", fs->value) < 0) return qfalse; } //end if else { if (fprintf(fp, "default:") < 0) return qfalse; } //end else if (fs->child) { if (fprintf(fp, "\n") < 0) return qfalse; if (!WriteIndent(fp, indent)) return qfalse; if (fprintf(fp, "{\n") < 0) return qfalse; if (!WriteFuzzySeperators_r(fp, fs->child, indent + 1)) return qfalse; if (!WriteIndent(fp, indent)) return qfalse; if (fs->next) { if (fprintf(fp, "} //end case\n") < 0) return qfalse; } //end if else { if (fprintf(fp, "} //end default\n") < 0) return qfalse; } //end else } //end if else { if (!WriteFuzzyWeight(fp, fs)) return qfalse; } //end else fs = fs->next; } while(fs); indent--; if (!WriteIndent(fp, indent)) return qfalse; if (fprintf(fp, "} //end switch\n") < 0) return qfalse; return qtrue; } //end of the function WriteItemFuzzyWeights_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WriteWeightConfig(char *filename, weightconfig_t *config) { int i; FILE *fp; weight_t *ifw; fp = fopen(filename, "wb"); if (!fp) return qfalse; for (i = 0; i < config->numweights; i++) { ifw = &config->weights[i]; if (fprintf(fp, "\nweight \"%s\"\n", ifw->name) < 0) return qfalse; if (fprintf(fp, "{\n") < 0) return qfalse; if (ifw->firstseperator->index > 0) { if (!WriteFuzzySeperators_r(fp, ifw->firstseperator, 1)) return qfalse; } //end if else { if (!WriteIndent(fp, 1)) return qfalse; if (!WriteFuzzyWeight(fp, ifw->firstseperator)) return qfalse; } //end else if (fprintf(fp, "} //end weight\n") < 0) return qfalse; } //end for fclose(fp); return qtrue; } //end of the function WriteWeightConfig #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int FindFuzzyWeight(weightconfig_t *wc, char *name) { int i; for (i = 0; i < wc->numweights; i++) { if (!strcmp(wc->weights[i].name, name)) { return i; } //end if } //end if return -1; } //end of the function FindFuzzyWeight //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float FuzzyWeight_r(int *inventory, fuzzyseperator_t *fs) { float scale, w1, w2; if (inventory[fs->index] < fs->value) { if (fs->child) return FuzzyWeight_r(inventory, fs->child); else return fs->weight; } //end if else if (fs->next) { if (inventory[fs->index] < fs->next->value) { //first weight if (fs->child) w1 = FuzzyWeight_r(inventory, fs->child); else w1 = fs->weight; //second weight if (fs->next->child) w2 = FuzzyWeight_r(inventory, fs->next->child); else w2 = fs->next->weight; //the scale factor scale = (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); //scale between the two weights return scale * w1 + (1 - scale) * w2; } //end if return FuzzyWeight_r(inventory, fs->next); } //end else if return fs->weight; } //end of the function FuzzyWeight_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float FuzzyWeightUndecided_r(int *inventory, fuzzyseperator_t *fs) { float scale, w1, w2; if (inventory[fs->index] < fs->value) { if (fs->child) return FuzzyWeightUndecided_r(inventory, fs->child); else return fs->minweight + random() * (fs->maxweight - fs->minweight); } //end if else if (fs->next) { if (inventory[fs->index] < fs->next->value) { //first weight if (fs->child) w1 = FuzzyWeightUndecided_r(inventory, fs->child); else w1 = fs->minweight + random() * (fs->maxweight - fs->minweight); //second weight if (fs->next->child) w2 = FuzzyWeight_r(inventory, fs->next->child); else w2 = fs->next->minweight + random() * (fs->next->maxweight - fs->next->minweight); //the scale factor scale = (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); //scale between the two weights return scale * w1 + (1 - scale) * w2; } //end if return FuzzyWeightUndecided_r(inventory, fs->next); } //end else if return fs->weight; } //end of the function FuzzyWeightUndecided_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum) { #ifdef EVALUATERECURSIVELY return FuzzyWeight_r(inventory, wc->weights[weightnum].firstseperator); #else fuzzyseperator_t *s; s = wc->weights[weightnum].firstseperator; if (!s) return 0; while(1) { if (inventory[s->index] < s->value) { if (s->child) s = s->child; else return s->weight; } //end if else { if (s->next) s = s->next; else return s->weight; } //end else } //end if return 0; #endif } //end of the function FuzzyWeight //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum) { #ifdef EVALUATERECURSIVELY return FuzzyWeightUndecided_r(inventory, wc->weights[weightnum].firstseperator); #else fuzzyseperator_t *s; s = wc->weights[weightnum].firstseperator; if (!s) return 0; while(1) { if (inventory[s->index] < s->value) { if (s->child) s = s->child; else return s->minweight + random() * (s->maxweight - s->minweight); } //end if else { if (s->next) s = s->next; else return s->minweight + random() * (s->maxweight - s->minweight); } //end else } //end if return 0; #endif } //end of the function FuzzyWeightUndecided //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EvolveFuzzySeperator_r(fuzzyseperator_t *fs) { if (fs->child) { EvolveFuzzySeperator_r(fs->child); } //end if else if (fs->type == WT_BALANCE) { //every once in a while an evolution leap occurs, mutation if (random() < 0.01) fs->weight += crandom() * (fs->maxweight - fs->minweight); else fs->weight += crandom() * (fs->maxweight - fs->minweight) * 0.5; //modify bounds if necesary because of mutation if (fs->weight < fs->minweight) fs->minweight = fs->weight; else if (fs->weight > fs->maxweight) fs->maxweight = fs->weight; } //end else if if (fs->next) EvolveFuzzySeperator_r(fs->next); } //end of the function EvolveFuzzySeperator_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EvolveWeightConfig(weightconfig_t *config) { int i; for (i = 0; i < config->numweights; i++) { EvolveFuzzySeperator_r(config->weights[i].firstseperator); } //end for } //end of the function EvolveWeightConfig //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ScaleFuzzySeperator_r(fuzzyseperator_t *fs, float scale) { if (fs->child) { ScaleFuzzySeperator_r(fs->child, scale); } //end if else if (fs->type == WT_BALANCE) { // fs->weight = (fs->maxweight + fs->minweight) * scale; //get the weight between bounds if (fs->weight < fs->minweight) fs->weight = fs->minweight; else if (fs->weight > fs->maxweight) fs->weight = fs->maxweight; } //end else if if (fs->next) ScaleFuzzySeperator_r(fs->next, scale); } //end of the function ScaleFuzzySeperator_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ScaleWeight(weightconfig_t *config, char *name, float scale) { int i; if (scale < 0) scale = 0; else if (scale > 1) scale = 1; for (i = 0; i < config->numweights; i++) { if (!strcmp(name, config->weights[i].name)) { ScaleFuzzySeperator_r(config->weights[i].firstseperator, scale); break; } //end if } //end for } //end of the function ScaleWeight //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ScaleFuzzySeperatorBalanceRange_r(fuzzyseperator_t *fs, float scale) { if (fs->child) { ScaleFuzzySeperatorBalanceRange_r(fs->child, scale); } //end if else if (fs->type == WT_BALANCE) { float mid = (fs->minweight + fs->maxweight) * 0.5; //get the weight between bounds fs->maxweight = mid + (fs->maxweight - mid) * scale; fs->minweight = mid + (fs->minweight - mid) * scale; if (fs->maxweight < fs->minweight) { fs->maxweight = fs->minweight; } //end if } //end else if if (fs->next) ScaleFuzzySeperatorBalanceRange_r(fs->next, scale); } //end of the function ScaleFuzzySeperatorBalanceRange_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ScaleFuzzyBalanceRange(weightconfig_t *config, float scale) { int i; if (scale < 0) scale = 0; else if (scale > 100) scale = 100; for (i = 0; i < config->numweights; i++) { ScaleFuzzySeperatorBalanceRange_r(config->weights[i].firstseperator, scale); } //end for } //end of the function ScaleFuzzyBalanceRange //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int InterbreedFuzzySeperator_r(fuzzyseperator_t *fs1, fuzzyseperator_t *fs2, fuzzyseperator_t *fsout) { if (fs1->child) { if (!fs2->child || !fsout->child) { botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal child\n"); return qfalse; } //end if if (!InterbreedFuzzySeperator_r(fs2->child, fs2->child, fsout->child)) { return qfalse; } //end if } //end if else if (fs1->type == WT_BALANCE) { if (fs2->type != WT_BALANCE || fsout->type != WT_BALANCE) { botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal balance\n"); return qfalse; } //end if fsout->weight = (fs1->weight + fs2->weight) / 2; if (fsout->weight > fsout->maxweight) fsout->maxweight = fsout->weight; if (fsout->weight > fsout->minweight) fsout->minweight = fsout->weight; } //end else if if (fs1->next) { if (!fs2->next || !fsout->next) { botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal next\n"); return qfalse; } //end if if (!InterbreedFuzzySeperator_r(fs1->next, fs2->next, fsout->next)) { return qfalse; } //end if } //end if return qtrue; } //end of the function InterbreedFuzzySeperator_r //=========================================================================== // config1 and config2 are interbreeded and stored in configout // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, weightconfig_t *configout) { int i; if (config1->numweights != config2->numweights || config1->numweights != configout->numweights) { botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal numweights\n"); return; } //end if for (i = 0; i < config1->numweights; i++) { InterbreedFuzzySeperator_r(config1->weights[i].firstseperator, config2->weights[i].firstseperator, configout->weights[i].firstseperator); } //end for } //end of the function InterbreedWeightConfigs //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotShutdownWeights(void) { int i; for( i = 0; i < MAX_WEIGHT_FILES; i++ ) { if (weightFileList[i]) { FreeWeightConfig2(weightFileList[i]); weightFileList[i] = NULL; } //end if } //end for } //end of the function BotShutdownWeights ================================================ FILE: code/botlib/be_ai_weight.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_ai_weight.h * * desc: fuzzy weights * * $Archive: /source/code/botlib/be_ai_weight.h $ * *****************************************************************************/ #define WT_BALANCE 1 #define MAX_WEIGHTS 128 //fuzzy seperator typedef struct fuzzyseperator_s { int index; int value; int type; float weight; float minweight; float maxweight; struct fuzzyseperator_s *child; struct fuzzyseperator_s *next; } fuzzyseperator_t; //fuzzy weight typedef struct weight_s { char *name; struct fuzzyseperator_s *firstseperator; } weight_t; //weight configuration typedef struct weightconfig_s { int numweights; weight_t weights[MAX_WEIGHTS]; char filename[MAX_QPATH]; } weightconfig_t; //reads a weight configuration weightconfig_t *ReadWeightConfig(char *filename); //free a weight configuration void FreeWeightConfig(weightconfig_t *config); //writes a weight configuration, returns true if successfull qboolean WriteWeightConfig(char *filename, weightconfig_t *config); //find the fuzzy weight with the given name int FindFuzzyWeight(weightconfig_t *wc, char *name); //returns the fuzzy weight for the given inventory and weight float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum); float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum); //scales the weight with the given name void ScaleWeight(weightconfig_t *config, char *name, float scale); //scale the balance range void ScaleBalanceRange(weightconfig_t *config, float scale); //evolves the weight configuration void EvolveWeightConfig(weightconfig_t *config); //interbreed the weight configurations and stores the interbreeded one in configout void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, weightconfig_t *configout); //frees cached weight configurations void BotShutdownWeights(void); ================================================ FILE: code/botlib/be_ea.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_ea.c * * desc: elementary actions * * $Archive: /MissionPack/code/botlib/be_ea.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "../game/botlib.h" #include "be_interface.h" #define MAX_USERMOVE 400 #define MAX_COMMANDARGUMENTS 10 #define ACTION_JUMPEDLASTFRAME 128 bot_input_t *botinputs; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Say(int client, char *str) { botimport.BotClientCommand(client, va("say %s", str) ); } //end of the function EA_Say //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_SayTeam(int client, char *str) { botimport.BotClientCommand(client, va("say_team %s", str)); } //end of the function EA_SayTeam //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Tell(int client, int clientto, char *str) { botimport.BotClientCommand(client, va("tell %d, %s", clientto, str)); } //end of the function EA_SayTeam //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_UseItem(int client, char *it) { botimport.BotClientCommand(client, va("use %s", it)); } //end of the function EA_UseItem //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_DropItem(int client, char *it) { botimport.BotClientCommand(client, va("drop %s", it)); } //end of the function EA_DropItem //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_UseInv(int client, char *inv) { botimport.BotClientCommand(client, va("invuse %s", inv)); } //end of the function EA_UseInv //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_DropInv(int client, char *inv) { botimport.BotClientCommand(client, va("invdrop %s", inv)); } //end of the function EA_DropInv //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Gesture(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_GESTURE; } //end of the function EA_Gesture //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Command(int client, char *command) { botimport.BotClientCommand(client, command); } //end of the function EA_Command //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_SelectWeapon(int client, int weapon) { bot_input_t *bi; bi = &botinputs[client]; bi->weapon = weapon; } //end of the function EA_SelectWeapon //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Attack(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_ATTACK; } //end of the function EA_Attack //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Talk(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_TALK; } //end of the function EA_Talk //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Use(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_USE; } //end of the function EA_Use //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Respawn(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_RESPAWN; } //end of the function EA_Respawn //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Jump(int client) { bot_input_t *bi; bi = &botinputs[client]; if (bi->actionflags & ACTION_JUMPEDLASTFRAME) { bi->actionflags &= ~ACTION_JUMP; } //end if else { bi->actionflags |= ACTION_JUMP; } //end if } //end of the function EA_Jump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_DelayedJump(int client) { bot_input_t *bi; bi = &botinputs[client]; if (bi->actionflags & ACTION_JUMPEDLASTFRAME) { bi->actionflags &= ~ACTION_DELAYEDJUMP; } //end if else { bi->actionflags |= ACTION_DELAYEDJUMP; } //end if } //end of the function EA_DelayedJump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Crouch(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_CROUCH; } //end of the function EA_Crouch //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Walk(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_WALK; } //end of the function EA_Walk //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Action(int client, int action) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= action; } //end of function EA_Action //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_MoveUp(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_MOVEUP; } //end of the function EA_MoveUp //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_MoveDown(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_MOVEDOWN; } //end of the function EA_MoveDown //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_MoveForward(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_MOVEFORWARD; } //end of the function EA_MoveForward //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_MoveBack(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_MOVEBACK; } //end of the function EA_MoveBack //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_MoveLeft(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_MOVELEFT; } //end of the function EA_MoveLeft //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_MoveRight(int client) { bot_input_t *bi; bi = &botinputs[client]; bi->actionflags |= ACTION_MOVERIGHT; } //end of the function EA_MoveRight //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Move(int client, vec3_t dir, float speed) { bot_input_t *bi; bi = &botinputs[client]; VectorCopy(dir, bi->dir); //cap speed if (speed > MAX_USERMOVE) speed = MAX_USERMOVE; else if (speed < -MAX_USERMOVE) speed = -MAX_USERMOVE; bi->speed = speed; } //end of the function EA_Move //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_View(int client, vec3_t viewangles) { bot_input_t *bi; bi = &botinputs[client]; VectorCopy(viewangles, bi->viewangles); } //end of the function EA_View //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_EndRegular(int client, float thinktime) { /* bot_input_t *bi; int jumped = qfalse; bi = &botinputs[client]; bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; bi->thinktime = thinktime; botimport.BotInput(client, bi); bi->thinktime = 0; VectorClear(bi->dir); bi->speed = 0; jumped = bi->actionflags & ACTION_JUMP; bi->actionflags = 0; if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; */ } //end of the function EA_EndRegular //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_GetInput(int client, float thinktime, bot_input_t *input) { bot_input_t *bi; // int jumped = qfalse; bi = &botinputs[client]; // bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; bi->thinktime = thinktime; Com_Memcpy(input, bi, sizeof(bot_input_t)); /* bi->thinktime = 0; VectorClear(bi->dir); bi->speed = 0; jumped = bi->actionflags & ACTION_JUMP; bi->actionflags = 0; if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; */ } //end of the function EA_GetInput //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_ResetInput(int client) { bot_input_t *bi; int jumped = qfalse; bi = &botinputs[client]; bi->actionflags &= ~ACTION_JUMPEDLASTFRAME; bi->thinktime = 0; VectorClear(bi->dir); bi->speed = 0; jumped = bi->actionflags & ACTION_JUMP; bi->actionflags = 0; if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; } //end of the function EA_ResetInput //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int EA_Setup(void) { //initialize the bot inputs botinputs = (bot_input_t *) GetClearedHunkMemory( botlibglobals.maxclients * sizeof(bot_input_t)); return BLERR_NOERROR; } //end of the function EA_Setup //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void EA_Shutdown(void) { FreeMemory(botinputs); botinputs = NULL; } //end of the function EA_Shutdown ================================================ FILE: code/botlib/be_interface.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_interface.c // bk010221 - FIXME - DEAD code elimination * * desc: bot library interface * * $Archive: /MissionPack/code/botlib/be_interface.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_log.h" #include "l_libvar.h" #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "be_aas_funcs.h" #include "be_aas_def.h" #include "be_interface.h" #include "../game/be_ea.h" #include "be_ai_weight.h" #include "../game/be_ai_goal.h" #include "../game/be_ai_move.h" #include "../game/be_ai_weap.h" #include "../game/be_ai_chat.h" #include "../game/be_ai_char.h" #include "../game/be_ai_gen.h" //library globals in a structure botlib_globals_t botlibglobals; botlib_export_t be_botlib_export; botlib_import_t botimport; // int bot_developer; //qtrue if the library is setup int botlibsetup = qfalse; //=========================================================================== // // several functions used by the exported functions // //=========================================================================== //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Sys_MilliSeconds(void) { return clock() * 1000 / CLOCKS_PER_SEC; } //end of the function Sys_MilliSeconds //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean ValidClientNumber(int num, char *str) { if (num < 0 || num > botlibglobals.maxclients) { //weird: the disabled stuff results in a crash botimport.Print(PRT_ERROR, "%s: invalid client number %d, [0, %d]\n", str, num, botlibglobals.maxclients); return qfalse; } //end if return qtrue; } //end of the function BotValidateClientNumber //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean ValidEntityNumber(int num, char *str) { if (num < 0 || num > botlibglobals.maxentities) { botimport.Print(PRT_ERROR, "%s: invalid entity number %d, [0, %d]\n", str, num, botlibglobals.maxentities); return qfalse; } //end if return qtrue; } //end of the function BotValidateClientNumber //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean BotLibSetup(char *str) { if (!botlibglobals.botlibsetup) { botimport.Print(PRT_ERROR, "%s: bot library used before being setup\n", str); return qfalse; } //end if return qtrue; } //end of the function BotLibSetup //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Export_BotLibSetup(void) { int errnum; bot_developer = LibVarGetValue("bot_developer"); memset( &botlibglobals, 0, sizeof(botlibglobals) ); // bk001207 - init //initialize byte swapping (litte endian etc.) // Swap_Init(); Log_Open("botlib.log"); // botimport.Print(PRT_MESSAGE, "------- BotLib Initialization -------\n"); // botlibglobals.maxclients = (int) LibVarValue("maxclients", "128"); botlibglobals.maxentities = (int) LibVarValue("maxentities", "1024"); errnum = AAS_Setup(); //be_aas_main.c if (errnum != BLERR_NOERROR) return errnum; errnum = EA_Setup(); //be_ea.c if (errnum != BLERR_NOERROR) return errnum; errnum = BotSetupWeaponAI(); //be_ai_weap.c if (errnum != BLERR_NOERROR)return errnum; errnum = BotSetupGoalAI(); //be_ai_goal.c if (errnum != BLERR_NOERROR) return errnum; errnum = BotSetupChatAI(); //be_ai_chat.c if (errnum != BLERR_NOERROR) return errnum; errnum = BotSetupMoveAI(); //be_ai_move.c if (errnum != BLERR_NOERROR) return errnum; botlibsetup = qtrue; botlibglobals.botlibsetup = qtrue; return BLERR_NOERROR; } //end of the function Export_BotLibSetup //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Export_BotLibShutdown(void) { if (!BotLibSetup("BotLibShutdown")) return BLERR_LIBRARYNOTSETUP; #ifndef DEMO //DumpFileCRCs(); #endif //DEMO // BotShutdownChatAI(); //be_ai_chat.c BotShutdownMoveAI(); //be_ai_move.c BotShutdownGoalAI(); //be_ai_goal.c BotShutdownWeaponAI(); //be_ai_weap.c BotShutdownWeights(); //be_ai_weight.c BotShutdownCharacters(); //be_ai_char.c //shud down aas AAS_Shutdown(); //shut down bot elemantary actions EA_Shutdown(); //free all libvars LibVarDeAllocAll(); //remove all global defines from the pre compiler PC_RemoveAllGlobalDefines(); //dump all allocated memory // DumpMemory(); #ifdef DEBUG PrintMemoryLabels(); #endif //shut down library log file Log_Shutdown(); // botlibsetup = qfalse; botlibglobals.botlibsetup = qfalse; // print any files still open PC_CheckOpenSourceHandles(); // return BLERR_NOERROR; } //end of the function Export_BotLibShutdown //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Export_BotLibVarSet(char *var_name, char *value) { LibVarSet(var_name, value); return BLERR_NOERROR; } //end of the function Export_BotLibVarSet //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Export_BotLibVarGet(char *var_name, char *value, int size) { char *varvalue; varvalue = LibVarGetString(var_name); strncpy(value, varvalue, size-1); value[size-1] = '\0'; return BLERR_NOERROR; } //end of the function Export_BotLibVarGet //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Export_BotLibStartFrame(float time) { if (!BotLibSetup("BotStartFrame")) return BLERR_LIBRARYNOTSETUP; return AAS_StartFrame(time); } //end of the function Export_BotLibStartFrame //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Export_BotLibLoadMap(const char *mapname) { #ifdef DEBUG int starttime = Sys_MilliSeconds(); #endif int errnum; if (!BotLibSetup("BotLoadMap")) return BLERR_LIBRARYNOTSETUP; // botimport.Print(PRT_MESSAGE, "------------ Map Loading ------------\n"); //startup AAS for the current map, model and sound index errnum = AAS_LoadMap(mapname); if (errnum != BLERR_NOERROR) return errnum; //initialize the items in the level BotInitLevelItems(); //be_ai_goal.h BotSetBrushModelTypes(); //be_ai_move.h // botimport.Print(PRT_MESSAGE, "-------------------------------------\n"); #ifdef DEBUG botimport.Print(PRT_MESSAGE, "map loaded in %d msec\n", Sys_MilliSeconds() - starttime); #endif // return BLERR_NOERROR; } //end of the function Export_BotLibLoadMap //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Export_BotLibUpdateEntity(int ent, bot_entitystate_t *state) { if (!BotLibSetup("BotUpdateEntity")) return BLERR_LIBRARYNOTSETUP; if (!ValidEntityNumber(ent, "BotUpdateEntity")) return BLERR_INVALIDENTITYNUMBER; return AAS_UpdateEntity(ent, state); } //end of the function Export_BotLibUpdateEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir); void ElevatorBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter); int BotGetReachabilityToGoal(vec3_t origin, int areanum, int lastgoalareanum, int lastareanum, int *avoidreach, float *avoidreachtimes, int *avoidreachtries, bot_goal_t *goal, int travelflags, int movetravelflags, struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags); int AAS_PointLight(vec3_t origin, int *red, int *green, int *blue); int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); int AAS_Reachability_WeaponJump(int area1num, int area2num); int BotFuzzyPointReachabilityArea(vec3_t origin); float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum); void AAS_FloodAreas(vec3_t origin); int BotExportTest(int parm0, char *parm1, vec3_t parm2, vec3_t parm3) { // return AAS_PointLight(parm2, NULL, NULL, NULL); #ifdef DEBUG static int area = -1; static int line[2]; int newarea, i, highlightarea, flood; // int reachnum; vec3_t eye, forward, right, end, origin; // vec3_t bottomcenter; // aas_trace_t trace; // aas_face_t *face; // aas_entity_t *ent; // bsp_trace_t bsptrace; // aas_reachability_t reach; // bot_goal_t goal; // clock_t start_time, end_time; vec3_t mins = {-16, -16, -24}; vec3_t maxs = {16, 16, 32}; // int areas[10], numareas; //return 0; if (!aasworld.loaded) return 0; /* if (parm0 & 1) { AAS_ClearShownPolygons(); AAS_FloodAreas(parm2); } //end if return 0; */ for (i = 0; i < 2; i++) if (!line[i]) line[i] = botimport.DebugLineCreate(); // AAS_ClearShownDebugLines(); //if (AAS_AgainstLadder(parm2)) botimport.Print(PRT_MESSAGE, "against ladder\n"); //BotOnGround(parm2, PRESENCE_NORMAL, 1, &newarea, &newarea); //botimport.Print(PRT_MESSAGE, "%f %f %f\n", parm2[0], parm2[1], parm2[2]); //* highlightarea = LibVarGetValue("bot_highlightarea"); if (highlightarea > 0) { newarea = highlightarea; } //end if else { VectorCopy(parm2, origin); origin[2] += 0.5; //newarea = AAS_PointAreaNum(origin); newarea = BotFuzzyPointReachabilityArea(origin); } //end else botimport.Print(PRT_MESSAGE, "\rtravel time to goal (%d) = %d ", botlibglobals.goalareanum, AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT)); //newarea = BotReachabilityArea(origin, qtrue); if (newarea != area) { botimport.Print(PRT_MESSAGE, "origin = %f, %f, %f\n", origin[0], origin[1], origin[2]); area = newarea; botimport.Print(PRT_MESSAGE, "new area %d, cluster %d, presence type %d\n", area, AAS_AreaCluster(area), AAS_PointPresenceType(origin)); botimport.Print(PRT_MESSAGE, "area contents: "); if (aasworld.areasettings[area].contents & AREACONTENTS_WATER) { botimport.Print(PRT_MESSAGE, "water &"); } //end if if (aasworld.areasettings[area].contents & AREACONTENTS_LAVA) { botimport.Print(PRT_MESSAGE, "lava &"); } //end if if (aasworld.areasettings[area].contents & AREACONTENTS_SLIME) { botimport.Print(PRT_MESSAGE, "slime &"); } //end if if (aasworld.areasettings[area].contents & AREACONTENTS_JUMPPAD) { botimport.Print(PRT_MESSAGE, "jump pad &"); } //end if if (aasworld.areasettings[area].contents & AREACONTENTS_CLUSTERPORTAL) { botimport.Print(PRT_MESSAGE, "cluster portal &"); } //end if if (aasworld.areasettings[area].contents & AREACONTENTS_VIEWPORTAL) { botimport.Print(PRT_MESSAGE, "view portal &"); } //end if if (aasworld.areasettings[area].contents & AREACONTENTS_DONOTENTER) { botimport.Print(PRT_MESSAGE, "do not enter &"); } //end if if (aasworld.areasettings[area].contents & AREACONTENTS_MOVER) { botimport.Print(PRT_MESSAGE, "mover &"); } //end if if (!aasworld.areasettings[area].contents) { botimport.Print(PRT_MESSAGE, "empty"); } //end if botimport.Print(PRT_MESSAGE, "\n"); botimport.Print(PRT_MESSAGE, "travel time to goal (%d) = %d\n", botlibglobals.goalareanum, AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT|TFL_ROCKETJUMP)); /* VectorCopy(origin, end); end[2] += 5; numareas = AAS_TraceAreas(origin, end, areas, NULL, 10); AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); botimport.Print(PRT_MESSAGE, "num areas = %d, area = %d\n", numareas, areas[0]); */ /* botlibglobals.goalareanum = newarea; VectorCopy(parm2, botlibglobals.goalorigin); botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", origin[0], origin[1], origin[2], newarea); */ } //end if //* flood = LibVarGetValue("bot_flood"); if (parm0 & 1) { if (flood) { AAS_ClearShownPolygons(); AAS_ClearShownDebugLines(); AAS_FloodAreas(parm2); } else { botlibglobals.goalareanum = newarea; VectorCopy(parm2, botlibglobals.goalorigin); botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", origin[0], origin[1], origin[2], newarea); } } //end if*/ if (flood) return 0; // if (parm0 & BUTTON_USE) // { // botlibglobals.runai = !botlibglobals.runai; // if (botlibglobals.runai) botimport.Print(PRT_MESSAGE, "started AI\n"); // else botimport.Print(PRT_MESSAGE, "stopped AI\n"); //* / /* goal.areanum = botlibglobals.goalareanum; reachnum = BotGetReachabilityToGoal(parm2, newarea, 1, ms.avoidreach, ms.avoidreachtimes, &goal, TFL_DEFAULT); if (!reachnum) { botimport.Print(PRT_MESSAGE, "goal not reachable\n"); } //end if else { AAS_ReachabilityFromNum(reachnum, &reach); AAS_ClearShownDebugLines(); AAS_ShowArea(area, qtrue); AAS_ShowArea(reach.areanum, qtrue); AAS_DrawCross(reach.start, 6, LINECOLOR_BLUE); AAS_DrawCross(reach.end, 6, LINECOLOR_RED); // if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) { ElevatorBottomCenter(&reach, bottomcenter); AAS_DrawCross(bottomcenter, 10, LINECOLOR_GREEN); } //end if } //end else*/ // botimport.Print(PRT_MESSAGE, "travel time to goal = %d\n", // AAS_AreaTravelTimeToGoalArea(area, origin, botlibglobals.goalareanum, TFL_DEFAULT)); // botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); // AAS_Reachability_WeaponJump(703, 716); // } //end if*/ /* face = AAS_AreaGroundFace(newarea, parm2); if (face) { AAS_ShowFace(face - aasworld.faces); } //end if*/ /* AAS_ClearShownDebugLines(); AAS_ShowArea(newarea, parm0 & BUTTON_USE); AAS_ShowReachableAreas(area); */ AAS_ClearShownPolygons(); AAS_ClearShownDebugLines(); AAS_ShowAreaPolygons(newarea, 1, parm0 & 4); if (parm0 & 2) AAS_ShowReachableAreas(area); else { static int lastgoalareanum, lastareanum; static int avoidreach[MAX_AVOIDREACH]; static float avoidreachtimes[MAX_AVOIDREACH]; static int avoidreachtries[MAX_AVOIDREACH]; int reachnum, resultFlags; bot_goal_t goal; aas_reachability_t reach; /* goal.areanum = botlibglobals.goalareanum; VectorCopy(botlibglobals.goalorigin, goal.origin); reachnum = BotGetReachabilityToGoal(origin, newarea, lastgoalareanum, lastareanum, avoidreach, avoidreachtimes, avoidreachtries, &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, NULL, 0, &resultFlags); AAS_ReachabilityFromNum(reachnum, &reach); AAS_ShowReachability(&reach); */ int curarea; vec3_t curorigin; goal.areanum = botlibglobals.goalareanum; VectorCopy(botlibglobals.goalorigin, goal.origin); VectorCopy(origin, curorigin); curarea = newarea; for ( i = 0; i < 100; i++ ) { if ( curarea == goal.areanum ) { break; } reachnum = BotGetReachabilityToGoal(curorigin, curarea, lastgoalareanum, lastareanum, avoidreach, avoidreachtimes, avoidreachtries, &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, NULL, 0, &resultFlags); AAS_ReachabilityFromNum(reachnum, &reach); AAS_ShowReachability(&reach); VectorCopy(reach.end, origin); lastareanum = curarea; curarea = reach.areanum; } } //end else VectorClear(forward); //BotGapDistance(origin, forward, 0); /* if (parm0 & BUTTON_USE) { botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); AAS_Reachability_WeaponJump(703, 716); } //end if*/ AngleVectors(parm3, forward, right, NULL); //get the eye 16 units to the right of the origin VectorMA(parm2, 8, right, eye); //get the eye 24 units up eye[2] += 24; //get the end point for the line to be traced VectorMA(eye, 800, forward, end); // AAS_TestMovementPrediction(1, parm2, forward); /* //trace the line to find the hit point trace = AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); if (!line[0]) line[0] = botimport.DebugLineCreate(); botimport.DebugLineShow(line[0], eye, trace.endpos, LINECOLOR_BLUE); // AAS_ClearShownDebugLines(); if (trace.ent) { ent = &aasworld.entities[trace.ent]; AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); } //end if */ /* start_time = clock(); for (i = 0; i < 2000; i++) { AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); // AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); } //end for end_time = clock(); botimport.Print(PRT_MESSAGE, "me %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); start_time = clock(); for (i = 0; i < 2000; i++) { AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); } //end for end_time = clock(); botimport.Print(PRT_MESSAGE, "id %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); */ // TTimo: nested comments are BAD for gcc -Werror, use #if 0 instead.. #if 0 AAS_ClearShownDebugLines(); //bsptrace = AAS_Trace(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); bsptrace = AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); if (!line[0]) line[0] = botimport.DebugLineCreate(); botimport.DebugLineShow(line[0], eye, bsptrace.endpos, LINECOLOR_YELLOW); if (bsptrace.fraction < 1.0) { face = AAS_TraceEndFace(&trace); if (face) { AAS_ShowFace(face - aasworld.faces); } //end if AAS_DrawPlaneCross(bsptrace.endpos, bsptrace.plane.normal, bsptrace.plane.dist + bsptrace.exp_dist, bsptrace.plane.type, LINECOLOR_GREEN); if (trace.ent) { ent = &aasworld.entities[trace.ent]; AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); } //end if } //end if //bsptrace = AAS_Trace2(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); bsptrace = AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); botimport.DebugLineShow(line[1], eye, bsptrace.endpos, LINECOLOR_BLUE); if (bsptrace.fraction < 1.0) { AAS_DrawPlaneCross(bsptrace.endpos, bsptrace.plane.normal, bsptrace.plane.dist,// + bsptrace.exp_dist, bsptrace.plane.type, LINECOLOR_RED); if (bsptrace.ent) { ent = &aasworld.entities[bsptrace.ent]; AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); } //end if } //end if #endif #endif return 0; } //end of the function BotExportTest /* ============ Init_AAS_Export ============ */ static void Init_AAS_Export( aas_export_t *aas ) { //-------------------------------------------- // be_aas_entity.c //-------------------------------------------- aas->AAS_EntityInfo = AAS_EntityInfo; //-------------------------------------------- // be_aas_main.c //-------------------------------------------- aas->AAS_Initialized = AAS_Initialized; aas->AAS_PresenceTypeBoundingBox = AAS_PresenceTypeBoundingBox; aas->AAS_Time = AAS_Time; //-------------------------------------------- // be_aas_sample.c //-------------------------------------------- aas->AAS_PointAreaNum = AAS_PointAreaNum; aas->AAS_PointReachabilityAreaIndex = AAS_PointReachabilityAreaIndex; aas->AAS_TraceAreas = AAS_TraceAreas; aas->AAS_BBoxAreas = AAS_BBoxAreas; aas->AAS_AreaInfo = AAS_AreaInfo; //-------------------------------------------- // be_aas_bspq3.c //-------------------------------------------- aas->AAS_PointContents = AAS_PointContents; aas->AAS_NextBSPEntity = AAS_NextBSPEntity; aas->AAS_ValueForBSPEpairKey = AAS_ValueForBSPEpairKey; aas->AAS_VectorForBSPEpairKey = AAS_VectorForBSPEpairKey; aas->AAS_FloatForBSPEpairKey = AAS_FloatForBSPEpairKey; aas->AAS_IntForBSPEpairKey = AAS_IntForBSPEpairKey; //-------------------------------------------- // be_aas_reach.c //-------------------------------------------- aas->AAS_AreaReachability = AAS_AreaReachability; //-------------------------------------------- // be_aas_route.c //-------------------------------------------- aas->AAS_AreaTravelTimeToGoalArea = AAS_AreaTravelTimeToGoalArea; aas->AAS_EnableRoutingArea = AAS_EnableRoutingArea; aas->AAS_PredictRoute = AAS_PredictRoute; //-------------------------------------------- // be_aas_altroute.c //-------------------------------------------- aas->AAS_AlternativeRouteGoals = AAS_AlternativeRouteGoals; //-------------------------------------------- // be_aas_move.c //-------------------------------------------- aas->AAS_Swimming = AAS_Swimming; aas->AAS_PredictClientMovement = AAS_PredictClientMovement; } /* ============ Init_EA_Export ============ */ static void Init_EA_Export( ea_export_t *ea ) { //ClientCommand elementary actions ea->EA_Command = EA_Command; ea->EA_Say = EA_Say; ea->EA_SayTeam = EA_SayTeam; ea->EA_Action = EA_Action; ea->EA_Gesture = EA_Gesture; ea->EA_Talk = EA_Talk; ea->EA_Attack = EA_Attack; ea->EA_Use = EA_Use; ea->EA_Respawn = EA_Respawn; ea->EA_Crouch = EA_Crouch; ea->EA_MoveUp = EA_MoveUp; ea->EA_MoveDown = EA_MoveDown; ea->EA_MoveForward = EA_MoveForward; ea->EA_MoveBack = EA_MoveBack; ea->EA_MoveLeft = EA_MoveLeft; ea->EA_MoveRight = EA_MoveRight; ea->EA_SelectWeapon = EA_SelectWeapon; ea->EA_Jump = EA_Jump; ea->EA_DelayedJump = EA_DelayedJump; ea->EA_Move = EA_Move; ea->EA_View = EA_View; ea->EA_GetInput = EA_GetInput; ea->EA_EndRegular = EA_EndRegular; ea->EA_ResetInput = EA_ResetInput; } /* ============ Init_AI_Export ============ */ static void Init_AI_Export( ai_export_t *ai ) { //----------------------------------- // be_ai_char.h //----------------------------------- ai->BotLoadCharacter = BotLoadCharacter; ai->BotFreeCharacter = BotFreeCharacter; ai->Characteristic_Float = Characteristic_Float; ai->Characteristic_BFloat = Characteristic_BFloat; ai->Characteristic_Integer = Characteristic_Integer; ai->Characteristic_BInteger = Characteristic_BInteger; ai->Characteristic_String = Characteristic_String; //----------------------------------- // be_ai_chat.h //----------------------------------- ai->BotAllocChatState = BotAllocChatState; ai->BotFreeChatState = BotFreeChatState; ai->BotQueueConsoleMessage = BotQueueConsoleMessage; ai->BotRemoveConsoleMessage = BotRemoveConsoleMessage; ai->BotNextConsoleMessage = BotNextConsoleMessage; ai->BotNumConsoleMessages = BotNumConsoleMessages; ai->BotInitialChat = BotInitialChat; ai->BotNumInitialChats = BotNumInitialChats; ai->BotReplyChat = BotReplyChat; ai->BotChatLength = BotChatLength; ai->BotEnterChat = BotEnterChat; ai->BotGetChatMessage = BotGetChatMessage; ai->StringContains = StringContains; ai->BotFindMatch = BotFindMatch; ai->BotMatchVariable = BotMatchVariable; ai->UnifyWhiteSpaces = UnifyWhiteSpaces; ai->BotReplaceSynonyms = BotReplaceSynonyms; ai->BotLoadChatFile = BotLoadChatFile; ai->BotSetChatGender = BotSetChatGender; ai->BotSetChatName = BotSetChatName; //----------------------------------- // be_ai_goal.h //----------------------------------- ai->BotResetGoalState = BotResetGoalState; ai->BotResetAvoidGoals = BotResetAvoidGoals; ai->BotRemoveFromAvoidGoals = BotRemoveFromAvoidGoals; ai->BotPushGoal = BotPushGoal; ai->BotPopGoal = BotPopGoal; ai->BotEmptyGoalStack = BotEmptyGoalStack; ai->BotDumpAvoidGoals = BotDumpAvoidGoals; ai->BotDumpGoalStack = BotDumpGoalStack; ai->BotGoalName = BotGoalName; ai->BotGetTopGoal = BotGetTopGoal; ai->BotGetSecondGoal = BotGetSecondGoal; ai->BotChooseLTGItem = BotChooseLTGItem; ai->BotChooseNBGItem = BotChooseNBGItem; ai->BotTouchingGoal = BotTouchingGoal; ai->BotItemGoalInVisButNotVisible = BotItemGoalInVisButNotVisible; ai->BotGetLevelItemGoal = BotGetLevelItemGoal; ai->BotGetNextCampSpotGoal = BotGetNextCampSpotGoal; ai->BotGetMapLocationGoal = BotGetMapLocationGoal; ai->BotAvoidGoalTime = BotAvoidGoalTime; ai->BotSetAvoidGoalTime = BotSetAvoidGoalTime; ai->BotInitLevelItems = BotInitLevelItems; ai->BotUpdateEntityItems = BotUpdateEntityItems; ai->BotLoadItemWeights = BotLoadItemWeights; ai->BotFreeItemWeights = BotFreeItemWeights; ai->BotInterbreedGoalFuzzyLogic = BotInterbreedGoalFuzzyLogic; ai->BotSaveGoalFuzzyLogic = BotSaveGoalFuzzyLogic; ai->BotMutateGoalFuzzyLogic = BotMutateGoalFuzzyLogic; ai->BotAllocGoalState = BotAllocGoalState; ai->BotFreeGoalState = BotFreeGoalState; //----------------------------------- // be_ai_move.h //----------------------------------- ai->BotResetMoveState = BotResetMoveState; ai->BotMoveToGoal = BotMoveToGoal; ai->BotMoveInDirection = BotMoveInDirection; ai->BotResetAvoidReach = BotResetAvoidReach; ai->BotResetLastAvoidReach = BotResetLastAvoidReach; ai->BotReachabilityArea = BotReachabilityArea; ai->BotMovementViewTarget = BotMovementViewTarget; ai->BotPredictVisiblePosition = BotPredictVisiblePosition; ai->BotAllocMoveState = BotAllocMoveState; ai->BotFreeMoveState = BotFreeMoveState; ai->BotInitMoveState = BotInitMoveState; ai->BotAddAvoidSpot = BotAddAvoidSpot; //----------------------------------- // be_ai_weap.h //----------------------------------- ai->BotChooseBestFightWeapon = BotChooseBestFightWeapon; ai->BotGetWeaponInfo = BotGetWeaponInfo; ai->BotLoadWeaponWeights = BotLoadWeaponWeights; ai->BotAllocWeaponState = BotAllocWeaponState; ai->BotFreeWeaponState = BotFreeWeaponState; ai->BotResetWeaponState = BotResetWeaponState; //----------------------------------- // be_ai_gen.h //----------------------------------- ai->GeneticParentsAndChildSelection = GeneticParentsAndChildSelection; } /* ============ GetBotLibAPI ============ */ botlib_export_t *GetBotLibAPI(int apiVersion, botlib_import_t *import) { assert(import); // bk001129 - this wasn't set for baseq3/ botimport = *import; assert(botimport.Print); // bk001129 - pars pro toto Com_Memset( &be_botlib_export, 0, sizeof( be_botlib_export ) ); if ( apiVersion != BOTLIB_API_VERSION ) { botimport.Print( PRT_ERROR, "Mismatched BOTLIB_API_VERSION: expected %i, got %i\n", BOTLIB_API_VERSION, apiVersion ); return NULL; } Init_AAS_Export(&be_botlib_export.aas); Init_EA_Export(&be_botlib_export.ea); Init_AI_Export(&be_botlib_export.ai); be_botlib_export.BotLibSetup = Export_BotLibSetup; be_botlib_export.BotLibShutdown = Export_BotLibShutdown; be_botlib_export.BotLibVarSet = Export_BotLibVarSet; be_botlib_export.BotLibVarGet = Export_BotLibVarGet; be_botlib_export.PC_AddGlobalDefine = PC_AddGlobalDefine; be_botlib_export.PC_LoadSourceHandle = PC_LoadSourceHandle; be_botlib_export.PC_FreeSourceHandle = PC_FreeSourceHandle; be_botlib_export.PC_ReadTokenHandle = PC_ReadTokenHandle; be_botlib_export.PC_SourceFileAndLine = PC_SourceFileAndLine; be_botlib_export.BotLibStartFrame = Export_BotLibStartFrame; be_botlib_export.BotLibLoadMap = Export_BotLibLoadMap; be_botlib_export.BotLibUpdateEntity = Export_BotLibUpdateEntity; be_botlib_export.Test = BotExportTest; return &be_botlib_export; } ================================================ FILE: code/botlib/be_interface.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: be_interface.h * * desc: botlib interface * * $Archive: /source/code/botlib/be_interface.h $ * *****************************************************************************/ //#define DEBUG //debug code #define RANDOMIZE //randomize bot behaviour //FIXME: get rid of this global structure typedef struct botlib_globals_s { int botlibsetup; //true when the bot library has been setup int maxentities; //maximum number of entities int maxclients; //maximum number of clients float time; //the global time #ifdef DEBUG qboolean debug; //true if debug is on int goalareanum; vec3_t goalorigin; int runai; #endif } botlib_globals_t; extern botlib_globals_t botlibglobals; extern botlib_import_t botimport; extern int bot_developer; //true if developer is on // int Sys_MilliSeconds(void); ================================================ FILE: code/botlib/botlib.vcproj ================================================ ================================================ FILE: code/botlib/l_crc.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_crc.c * * desc: CRC calculation * * $Archive: /MissionPack/CODE/botlib/l_crc.c $ * *****************************************************************************/ #include #include #include #include "../game/q_shared.h" #include "../game/botlib.h" #include "be_interface.h" //for botimport.Print // FIXME: byte swap? // this is a 16 bit, non-reflected CRC using the polynomial 0x1021 // and the initial and final xor values shown below... in other words, the // CCITT standard CRC used by XMODEM #define CRC_INIT_VALUE 0xffff #define CRC_XOR_VALUE 0x0000 unsigned short crctable[257] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void CRC_Init(unsigned short *crcvalue) { *crcvalue = CRC_INIT_VALUE; } //end of the function CRC_Init //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void CRC_ProcessByte(unsigned short *crcvalue, byte data) { *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; } //end of the function CRC_ProcessByte //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== unsigned short CRC_Value(unsigned short crcvalue) { return crcvalue ^ CRC_XOR_VALUE; } //end of the function CRC_Value //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== unsigned short CRC_ProcessString(unsigned char *data, int length) { unsigned short crcvalue; int i, ind; CRC_Init(&crcvalue); for (i = 0; i < length; i++) { ind = (crcvalue >> 8) ^ data[i]; if (ind < 0 || ind > 256) ind = 0; crcvalue = (crcvalue << 8) ^ crctable[ind]; } //end for return CRC_Value(crcvalue); } //end of the function CRC_ProcessString //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void CRC_ContinueProcessString(unsigned short *crc, char *data, int length) { int i; for (i = 0; i < length; i++) { *crc = (*crc << 8) ^ crctable[(*crc >> 8) ^ data[i]]; } //end for } //end of the function CRC_ProcessString ================================================ FILE: code/botlib/l_crc.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ typedef unsigned short crc_t; void CRC_Init(unsigned short *crcvalue); void CRC_ProcessByte(unsigned short *crcvalue, byte data); unsigned short CRC_Value(unsigned short crcvalue); unsigned short CRC_ProcessString(unsigned char *data, int length); void CRC_ContinueProcessString(unsigned short *crc, char *data, int length); ================================================ FILE: code/botlib/l_libvar.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_libvar.c * * desc: bot library variables * * $Archive: /MissionPack/code/botlib/l_libvar.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "l_memory.h" #include "l_libvar.h" //list with library variables libvar_t *libvarlist; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float LibVarStringValue(char *string) { int dotfound = 0; float value = 0; while(*string) { if (*string < '0' || *string > '9') { if (dotfound || *string != '.') { return 0; } //end if else { dotfound = 10; string++; } //end if } //end if if (dotfound) { value = value + (float) (*string - '0') / (float) dotfound; dotfound *= 10; } //end if else { value = value * 10.0 + (float) (*string - '0'); } //end else string++; } //end while return value; } //end of the function LibVarStringValue //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== libvar_t *LibVarAlloc(char *var_name) { libvar_t *v; v = (libvar_t *) GetMemory(sizeof(libvar_t) + strlen(var_name) + 1); Com_Memset(v, 0, sizeof(libvar_t)); v->name = (char *) v + sizeof(libvar_t); strcpy(v->name, var_name); //add the variable in the list v->next = libvarlist; libvarlist = v; return v; } //end of the function LibVarAlloc //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void LibVarDeAlloc(libvar_t *v) { if (v->string) FreeMemory(v->string); FreeMemory(v); } //end of the function LibVarDeAlloc //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void LibVarDeAllocAll(void) { libvar_t *v; for (v = libvarlist; v; v = libvarlist) { libvarlist = libvarlist->next; LibVarDeAlloc(v); } //end for libvarlist = NULL; } //end of the function LibVarDeAllocAll //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== libvar_t *LibVarGet(char *var_name) { libvar_t *v; for (v = libvarlist; v; v = v->next) { if (!Q_stricmp(v->name, var_name)) { return v; } //end if } //end for return NULL; } //end of the function LibVarGet //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *LibVarGetString(char *var_name) { libvar_t *v; v = LibVarGet(var_name); if (v) { return v->string; } //end if else { return ""; } //end else } //end of the function LibVarGetString //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float LibVarGetValue(char *var_name) { libvar_t *v; v = LibVarGet(var_name); if (v) { return v->value; } //end if else { return 0; } //end else } //end of the function LibVarGetValue //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== libvar_t *LibVar(char *var_name, char *value) { libvar_t *v; v = LibVarGet(var_name); if (v) return v; //create new variable v = LibVarAlloc(var_name); //variable string v->string = (char *) GetMemory(strlen(value) + 1); strcpy(v->string, value); //the value v->value = LibVarStringValue(v->string); //variable is modified v->modified = qtrue; // return v; } //end of the function LibVar //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *LibVarString(char *var_name, char *value) { libvar_t *v; v = LibVar(var_name, value); return v->string; } //end of the function LibVarString //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float LibVarValue(char *var_name, char *value) { libvar_t *v; v = LibVar(var_name, value); return v->value; } //end of the function LibVarValue //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void LibVarSet(char *var_name, char *value) { libvar_t *v; v = LibVarGet(var_name); if (v) { FreeMemory(v->string); } //end if else { v = LibVarAlloc(var_name); } //end else //variable string v->string = (char *) GetMemory(strlen(value) + 1); strcpy(v->string, value); //the value v->value = LibVarStringValue(v->string); //variable is modified v->modified = qtrue; } //end of the function LibVarSet //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean LibVarChanged(char *var_name) { libvar_t *v; v = LibVarGet(var_name); if (v) { return v->modified; } //end if else { return qfalse; } //end else } //end of the function LibVarChanged //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void LibVarSetNotModified(char *var_name) { libvar_t *v; v = LibVarGet(var_name); if (v) { v->modified = qfalse; } //end if } //end of the function LibVarSetNotModified ================================================ FILE: code/botlib/l_libvar.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_libvar.h * * desc: botlib vars * * $Archive: /source/code/botlib/l_libvar.h $ * *****************************************************************************/ //library variable typedef struct libvar_s { char *name; char *string; int flags; qboolean modified; // set each time the cvar is changed float value; struct libvar_s *next; } libvar_t; //removes all library variables void LibVarDeAllocAll(void); //gets the library variable with the given name libvar_t *LibVarGet(char *var_name); //gets the string of the library variable with the given name char *LibVarGetString(char *var_name); //gets the value of the library variable with the given name float LibVarGetValue(char *var_name); //creates the library variable if not existing already and returns it libvar_t *LibVar(char *var_name, char *value); //creates the library variable if not existing already and returns the value float LibVarValue(char *var_name, char *value); //creates the library variable if not existing already and returns the value string char *LibVarString(char *var_name, char *value); //sets the library variable void LibVarSet(char *var_name, char *value); //returns true if the library variable has been modified qboolean LibVarChanged(char *var_name); //sets the library variable to unmodified void LibVarSetNotModified(char *var_name); ================================================ FILE: code/botlib/l_log.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_log.c * * desc: log file * * $Archive: /MissionPack/CODE/botlib/l_log.c $ * *****************************************************************************/ #include #include #include #include "../game/q_shared.h" #include "../game/botlib.h" #include "be_interface.h" //for botimport.Print #include "l_libvar.h" #define MAX_LOGFILENAMESIZE 1024 typedef struct logfile_s { char filename[MAX_LOGFILENAMESIZE]; FILE *fp; int numwrites; } logfile_t; static logfile_t logfile; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_Open(char *filename) { if (!LibVarValue("log", "0")) return; if (!filename || !strlen(filename)) { botimport.Print(PRT_MESSAGE, "openlog \n"); return; } //end if if (logfile.fp) { botimport.Print(PRT_ERROR, "log file %s is already opened\n", logfile.filename); return; } //end if logfile.fp = fopen(filename, "wb"); if (!logfile.fp) { botimport.Print(PRT_ERROR, "can't open the log file %s\n", filename); return; } //end if strncpy(logfile.filename, filename, MAX_LOGFILENAMESIZE); botimport.Print(PRT_MESSAGE, "Opened log %s\n", logfile.filename); } //end of the function Log_Create //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_Close(void) { if (!logfile.fp) return; if (fclose(logfile.fp)) { botimport.Print(PRT_ERROR, "can't close log file %s\n", logfile.filename); return; } //end if logfile.fp = NULL; botimport.Print(PRT_MESSAGE, "Closed log %s\n", logfile.filename); } //end of the function Log_Close //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_Shutdown(void) { if (logfile.fp) Log_Close(); } //end of the function Log_Shutdown //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void QDECL Log_Write(char *fmt, ...) { va_list ap; if (!logfile.fp) return; va_start(ap, fmt); vfprintf(logfile.fp, fmt, ap); va_end(ap); //fprintf(logfile.fp, "\r\n"); fflush(logfile.fp); } //end of the function Log_Write //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void QDECL Log_WriteTimeStamped(char *fmt, ...) { va_list ap; if (!logfile.fp) return; fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", logfile.numwrites, (int) (botlibglobals.time / 60 / 60), (int) (botlibglobals.time / 60), (int) (botlibglobals.time), (int) ((int) (botlibglobals.time * 100)) - ((int) botlibglobals.time) * 100); va_start(ap, fmt); vfprintf(logfile.fp, fmt, ap); va_end(ap); fprintf(logfile.fp, "\r\n"); logfile.numwrites++; fflush(logfile.fp); } //end of the function Log_Write //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== FILE *Log_FilePointer(void) { return logfile.fp; } //end of the function Log_FilePointer //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_Flush(void) { if (logfile.fp) fflush(logfile.fp); } //end of the function Log_Flush ================================================ FILE: code/botlib/l_log.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_log.h * * desc: log file * * $Archive: /source/code/botlib/l_log.h $ * *****************************************************************************/ //open a log file void Log_Open(char *filename); //close the current log file void Log_Close(void); //close log file if present void Log_Shutdown(void); //write to the current opened log file void QDECL Log_Write(char *fmt, ...); //write to the current opened log file with a time stamp void QDECL Log_WriteTimeStamped(char *fmt, ...); //returns a pointer to the log file FILE *Log_FilePointer(void); //flush log file void Log_Flush(void); ================================================ FILE: code/botlib/l_memory.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_memory.c * * desc: memory allocation * * $Archive: /MissionPack/code/botlib/l_memory.c $ * *****************************************************************************/ #include "../game/q_shared.h" #include "../game/botlib.h" #include "l_log.h" #include "be_interface.h" //#define MEMDEBUG //#define MEMORYMANEGER #define MEM_ID 0x12345678l #define HUNK_ID 0x87654321l int allocatedmemory; int totalmemorysize; int numblocks; #ifdef MEMORYMANEGER typedef struct memoryblock_s { unsigned long int id; void *ptr; int size; #ifdef MEMDEBUG char *label; char *file; int line; #endif //MEMDEBUG struct memoryblock_s *prev, *next; } memoryblock_t; memoryblock_t *memory; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void LinkMemoryBlock(memoryblock_t *block) { block->prev = NULL; block->next = memory; if (memory) memory->prev = block; memory = block; } //end of the function LinkMemoryBlock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void UnlinkMemoryBlock(memoryblock_t *block) { if (block->prev) block->prev->next = block->next; else memory = block->next; if (block->next) block->next->prev = block->prev; } //end of the function UnlinkMemoryBlock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef MEMDEBUG void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) #else void *GetMemory(unsigned long size) #endif //MEMDEBUG { void *ptr; memoryblock_t *block; assert(botimport.GetMemory); // bk001129 - was NULL'ed ptr = botimport.GetMemory(size + sizeof(memoryblock_t)); block = (memoryblock_t *) ptr; block->id = MEM_ID; block->ptr = (char *) ptr + sizeof(memoryblock_t); block->size = size + sizeof(memoryblock_t); #ifdef MEMDEBUG block->label = label; block->file = file; block->line = line; #endif //MEMDEBUG LinkMemoryBlock(block); allocatedmemory += block->size; totalmemorysize += block->size + sizeof(memoryblock_t); numblocks++; return block->ptr; } //end of the function GetMemoryDebug //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef MEMDEBUG void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) #else void *GetClearedMemory(unsigned long size) #endif //MEMDEBUG { void *ptr; #ifdef MEMDEBUG ptr = GetMemoryDebug(size, label, file, line); #else ptr = GetMemory(size); #endif //MEMDEBUG Com_Memset(ptr, 0, size); return ptr; } //end of the function GetClearedMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef MEMDEBUG void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line) #else void *GetHunkMemory(unsigned long size) #endif //MEMDEBUG { void *ptr; memoryblock_t *block; ptr = botimport.HunkAlloc(size + sizeof(memoryblock_t)); block = (memoryblock_t *) ptr; block->id = HUNK_ID; block->ptr = (char *) ptr + sizeof(memoryblock_t); block->size = size + sizeof(memoryblock_t); #ifdef MEMDEBUG block->label = label; block->file = file; block->line = line; #endif //MEMDEBUG LinkMemoryBlock(block); allocatedmemory += block->size; totalmemorysize += block->size + sizeof(memoryblock_t); numblocks++; return block->ptr; } //end of the function GetHunkMemoryDebug //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef MEMDEBUG void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line) #else void *GetClearedHunkMemory(unsigned long size) #endif //MEMDEBUG { void *ptr; #ifdef MEMDEBUG ptr = GetHunkMemoryDebug(size, label, file, line); #else ptr = GetHunkMemory(size); #endif //MEMDEBUG Com_Memset(ptr, 0, size); return ptr; } //end of the function GetClearedHunkMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== memoryblock_t *BlockFromPointer(void *ptr, char *str) { memoryblock_t *block; if (!ptr) { #ifdef MEMDEBUG //char *crash = (char *) NULL; //crash[0] = 1; botimport.Print(PRT_FATAL, "%s: NULL pointer\n", str); #endif // MEMDEBUG return NULL; } //end if block = (memoryblock_t *) ((char *) ptr - sizeof(memoryblock_t)); if (block->id != MEM_ID && block->id != HUNK_ID) { botimport.Print(PRT_FATAL, "%s: invalid memory block\n", str); return NULL; } //end if if (block->ptr != ptr) { botimport.Print(PRT_FATAL, "%s: memory block pointer invalid\n", str); return NULL; } //end if return block; } //end of the function BlockFromPointer //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeMemory(void *ptr) { memoryblock_t *block; block = BlockFromPointer(ptr, "FreeMemory"); if (!block) return; UnlinkMemoryBlock(block); allocatedmemory -= block->size; totalmemorysize -= block->size + sizeof(memoryblock_t); numblocks--; // if (block->id == MEM_ID) { botimport.FreeMemory(block); } //end if } //end of the function FreeMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AvailableMemory(void) { return botimport.AvailableMemory(); } //end of the function AvailableMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int MemoryByteSize(void *ptr) { memoryblock_t *block; block = BlockFromPointer(ptr, "MemoryByteSize"); if (!block) return 0; return block->size; } //end of the function MemoryByteSize //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintUsedMemorySize(void) { botimport.Print(PRT_MESSAGE, "total allocated memory: %d KB\n", allocatedmemory >> 10); botimport.Print(PRT_MESSAGE, "total botlib memory: %d KB\n", totalmemorysize >> 10); botimport.Print(PRT_MESSAGE, "total memory blocks: %d\n", numblocks); } //end of the function PrintUsedMemorySize //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintMemoryLabels(void) { memoryblock_t *block; int i; PrintUsedMemorySize(); i = 0; Log_Write("============= Botlib memory log ==============\r\n"); Log_Write("\r\n"); for (block = memory; block; block = block->next) { #ifdef MEMDEBUG if (block->id == HUNK_ID) { Log_Write("%6d, hunk %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label); } //end if else { Log_Write("%6d, %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label); } //end else #endif //MEMDEBUG i++; } //end for } //end of the function PrintMemoryLabels //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void DumpMemory(void) { memoryblock_t *block; for (block = memory; block; block = memory) { FreeMemory(block->ptr); } //end for totalmemorysize = 0; allocatedmemory = 0; } //end of the function DumpMemory #else //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef MEMDEBUG void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) #else void *GetMemory(unsigned long size) #endif //MEMDEBUG { void *ptr; unsigned long int *memid; ptr = botimport.GetMemory(size + sizeof(unsigned long int)); if (!ptr) return NULL; memid = (unsigned long int *) ptr; *memid = MEM_ID; return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int)); } //end of the function GetMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef MEMDEBUG void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) #else void *GetClearedMemory(unsigned long size) #endif //MEMDEBUG { void *ptr; #ifdef MEMDEBUG ptr = GetMemoryDebug(size, label, file, line); #else ptr = GetMemory(size); #endif //MEMDEBUG Com_Memset(ptr, 0, size); return ptr; } //end of the function GetClearedMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef MEMDEBUG void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line) #else void *GetHunkMemory(unsigned long size) #endif //MEMDEBUG { void *ptr; unsigned long int *memid; ptr = botimport.HunkAlloc(size + sizeof(unsigned long int)); if (!ptr) return NULL; memid = (unsigned long int *) ptr; *memid = HUNK_ID; return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int)); } //end of the function GetHunkMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef MEMDEBUG void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line) #else void *GetClearedHunkMemory(unsigned long size) #endif //MEMDEBUG { void *ptr; #ifdef MEMDEBUG ptr = GetHunkMemoryDebug(size, label, file, line); #else ptr = GetHunkMemory(size); #endif //MEMDEBUG Com_Memset(ptr, 0, size); return ptr; } //end of the function GetClearedHunkMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeMemory(void *ptr) { unsigned long int *memid; memid = (unsigned long int *) ((char *) ptr - sizeof(unsigned long int)); if (*memid == MEM_ID) { botimport.FreeMemory(memid); } //end if } //end of the function FreeMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AvailableMemory(void) { return botimport.AvailableMemory(); } //end of the function AvailableMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintUsedMemorySize(void) { } //end of the function PrintUsedMemorySize //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintMemoryLabels(void) { } //end of the function PrintMemoryLabels #endif ================================================ FILE: code/botlib/l_memory.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_memory.h * * desc: memory management * * $Archive: /source/code/botlib/l_memory.h $ * *****************************************************************************/ //#define MEMDEBUG #ifdef MEMDEBUG #define GetMemory(size) GetMemoryDebug(size, #size, __FILE__, __LINE__); #define GetClearedMemory(size) GetClearedMemoryDebug(size, #size, __FILE__, __LINE__); //allocate a memory block of the given size void *GetMemoryDebug(unsigned long size, char *label, char *file, int line); //allocate a memory block of the given size and clear it void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line); // #define GetHunkMemory(size) GetHunkMemoryDebug(size, #size, __FILE__, __LINE__); #define GetClearedHunkMemory(size) GetClearedHunkMemoryDebug(size, #size, __FILE__, __LINE__); //allocate a memory block of the given size void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line); //allocate a memory block of the given size and clear it void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line); #else //allocate a memory block of the given size void *GetMemory(unsigned long size); //allocate a memory block of the given size and clear it void *GetClearedMemory(unsigned long size); // #ifdef BSPC #define GetHunkMemory GetMemory #define GetClearedHunkMemory GetClearedMemory #else //allocate a memory block of the given size void *GetHunkMemory(unsigned long size); //allocate a memory block of the given size and clear it void *GetClearedHunkMemory(unsigned long size); #endif #endif //free the given memory block void FreeMemory(void *ptr); //returns the amount available memory int AvailableMemory(void); //prints the total used memory size void PrintUsedMemorySize(void); //print all memory blocks with label void PrintMemoryLabels(void); //returns the size of the memory block in bytes int MemoryByteSize(void *ptr); //free all allocated memory void DumpMemory(void); ================================================ FILE: code/botlib/l_precomp.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: l_precomp.c * * desc: pre compiler * * $Archive: /MissionPack/code/botlib/l_precomp.c $ * *****************************************************************************/ //Notes: fix: PC_StringizeTokens //#define SCREWUP //#define BOTLIB //#define QUAKE //#define QUAKEC //#define MEQCC #ifdef SCREWUP #include #include #include #include #include #include #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" typedef enum {qfalse, qtrue} qboolean; #endif //SCREWUP #ifdef BOTLIB #include "../game/q_shared.h" #include "../game/botlib.h" #include "be_interface.h" #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_log.h" #endif //BOTLIB #ifdef MEQCC #include "qcc.h" #include "time.h" //time & ctime #include "math.h" //fabs #include "l_memory.h" #include "l_script.h" #include "l_precomp.h" #include "l_log.h" #define qtrue true #define qfalse false #endif //MEQCC #ifdef BSPC //include files for usage in the BSP Converter #include "../bspc/qbsp.h" #include "../bspc/l_log.h" #include "../bspc/l_mem.h" #include "l_precomp.h" #define qtrue true #define qfalse false #define Q_stricmp stricmp #endif //BSPC #if defined(QUAKE) && !defined(BSPC) #include "l_utils.h" #endif //QUAKE //#define DEBUG_EVAL #define MAX_DEFINEPARMS 128 #define DEFINEHASHING 1 //directive name with parse function typedef struct directive_s { char *name; int (*func)(source_t *source); } directive_t; #define DEFINEHASHSIZE 1024 #define TOKEN_HEAP_SIZE 4096 int numtokens; /* int tokenheapinitialized; //true when the token heap is initialized token_t token_heap[TOKEN_HEAP_SIZE]; //heap with tokens token_t *freetokens; //free tokens from the heap */ //list with global defines added to every source loaded define_t *globaldefines; //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void QDECL SourceError(source_t *source, char *str, ...) { char text[1024]; va_list ap; va_start(ap, str); vsprintf(text, str, ap); va_end(ap); #ifdef BOTLIB botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); #endif //BOTLIB #ifdef MEQCC printf("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); #endif //MEQCC #ifdef BSPC Log_Print("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); #endif //BSPC } //end of the function SourceError //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void QDECL SourceWarning(source_t *source, char *str, ...) { char text[1024]; va_list ap; va_start(ap, str); vsprintf(text, str, ap); va_end(ap); #ifdef BOTLIB botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); #endif //BOTLIB #ifdef MEQCC printf("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); #endif //MEQCC #ifdef BSPC Log_Print("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); #endif //BSPC } //end of the function ScriptWarning //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_PushIndent(source_t *source, int type, int skip) { indent_t *indent; indent = (indent_t *) GetMemory(sizeof(indent_t)); indent->type = type; indent->script = source->scriptstack; indent->skip = (skip != 0); source->skip += indent->skip; indent->next = source->indentstack; source->indentstack = indent; } //end of the function PC_PushIndent //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_PopIndent(source_t *source, int *type, int *skip) { indent_t *indent; *type = 0; *skip = 0; indent = source->indentstack; if (!indent) return; //must be an indent from the current script if (source->indentstack->script != source->scriptstack) return; *type = indent->type; *skip = indent->skip; source->indentstack = source->indentstack->next; source->skip -= indent->skip; FreeMemory(indent); } //end of the function PC_PopIndent //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_PushScript(source_t *source, script_t *script) { script_t *s; for (s = source->scriptstack; s; s = s->next) { if (!Q_stricmp(s->filename, script->filename)) { SourceError(source, "%s recursively included", script->filename); return; } //end if } //end for //push the script on the script stack script->next = source->scriptstack; source->scriptstack = script; } //end of the function PC_PushScript //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_InitTokenHeap(void) { /* int i; if (tokenheapinitialized) return; freetokens = NULL; for (i = 0; i < TOKEN_HEAP_SIZE; i++) { token_heap[i].next = freetokens; freetokens = &token_heap[i]; } //end for tokenheapinitialized = qtrue; */ } //end of the function PC_InitTokenHeap //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ token_t *PC_CopyToken(token_t *token) { token_t *t; // t = (token_t *) malloc(sizeof(token_t)); t = (token_t *) GetMemory(sizeof(token_t)); // t = freetokens; if (!t) { #ifdef BSPC Error("out of token space\n"); #else Com_Error(ERR_FATAL, "out of token space\n"); #endif return NULL; } //end if // freetokens = freetokens->next; Com_Memcpy(t, token, sizeof(token_t)); t->next = NULL; numtokens++; return t; } //end of the function PC_CopyToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_FreeToken(token_t *token) { //free(token); FreeMemory(token); // token->next = freetokens; // freetokens = token; numtokens--; } //end of the function PC_FreeToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_ReadSourceToken(source_t *source, token_t *token) { token_t *t; script_t *script; int type, skip; //if there's no token already available while(!source->tokens) { //if there's a token to read from the script if (PS_ReadToken(source->scriptstack, token)) return qtrue; //if at the end of the script if (EndOfScript(source->scriptstack)) { //remove all indents of the script while(source->indentstack && source->indentstack->script == source->scriptstack) { SourceWarning(source, "missing #endif"); PC_PopIndent(source, &type, &skip); } //end if } //end if //if this was the initial script if (!source->scriptstack->next) return qfalse; //remove the script and return to the last one script = source->scriptstack; source->scriptstack = source->scriptstack->next; FreeScript(script); } //end while //copy the already available token Com_Memcpy(token, source->tokens, sizeof(token_t)); //free the read token t = source->tokens; source->tokens = source->tokens->next; PC_FreeToken(t); return qtrue; } //end of the function PC_ReadSourceToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_UnreadSourceToken(source_t *source, token_t *token) { token_t *t; t = PC_CopyToken(token); t->next = source->tokens; source->tokens = t; return qtrue; } //end of the function PC_UnreadSourceToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_ReadDefineParms(source_t *source, define_t *define, token_t **parms, int maxparms) { token_t token, *t, *last; int i, done, lastcomma, numparms, indent; if (!PC_ReadSourceToken(source, &token)) { SourceError(source, "define %s missing parms", define->name); return qfalse; } //end if // if (define->numparms > maxparms) { SourceError(source, "define with more than %d parameters", maxparms); return qfalse; } //end if // for (i = 0; i < define->numparms; i++) parms[i] = NULL; //if no leading "(" if (strcmp(token.string, "(")) { PC_UnreadSourceToken(source, &token); SourceError(source, "define %s missing parms", define->name); return qfalse; } //end if //read the define parameters for (done = 0, numparms = 0, indent = 0; !done;) { if (numparms >= maxparms) { SourceError(source, "define %s with too many parms", define->name); return qfalse; } //end if if (numparms >= define->numparms) { SourceWarning(source, "define %s has too many parms", define->name); return qfalse; } //end if parms[numparms] = NULL; lastcomma = 1; last = NULL; while(!done) { // if (!PC_ReadSourceToken(source, &token)) { SourceError(source, "define %s incomplete", define->name); return qfalse; } //end if // if (!strcmp(token.string, ",")) { if (indent <= 0) { if (lastcomma) SourceWarning(source, "too many comma's"); lastcomma = 1; break; } //end if } //end if lastcomma = 0; // if (!strcmp(token.string, "(")) { indent++; continue; } //end if else if (!strcmp(token.string, ")")) { if (--indent <= 0) { if (!parms[define->numparms-1]) { SourceWarning(source, "too few define parms"); } //end if done = 1; break; } //end if } //end if // if (numparms < define->numparms) { // t = PC_CopyToken(&token); t->next = NULL; if (last) last->next = t; else parms[numparms] = t; last = t; } //end if } //end while numparms++; } //end for return qtrue; } //end of the function PC_ReadDefineParms //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_StringizeTokens(token_t *tokens, token_t *token) { token_t *t; token->type = TT_STRING; token->whitespace_p = NULL; token->endwhitespace_p = NULL; token->string[0] = '\0'; strcat(token->string, "\""); for (t = tokens; t; t = t->next) { strncat(token->string, t->string, MAX_TOKEN - strlen(token->string)); } //end for strncat(token->string, "\"", MAX_TOKEN - strlen(token->string)); return qtrue; } //end of the function PC_StringizeTokens //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_MergeTokens(token_t *t1, token_t *t2) { //merging of a name with a name or number if (t1->type == TT_NAME && (t2->type == TT_NAME || t2->type == TT_NUMBER)) { strcat(t1->string, t2->string); return qtrue; } //end if //merging of two strings if (t1->type == TT_STRING && t2->type == TT_STRING) { //remove trailing double quote t1->string[strlen(t1->string)-1] = '\0'; //concat without leading double quote strcat(t1->string, &t2->string[1]); return qtrue; } //end if //FIXME: merging of two number of the same sub type return qfalse; } //end of the function PC_MergeTokens //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ /* void PC_PrintDefine(define_t *define) { printf("define->name = %s\n", define->name); printf("define->flags = %d\n", define->flags); printf("define->builtin = %d\n", define->builtin); printf("define->numparms = %d\n", define->numparms); // token_t *parms; //define parameters // token_t *tokens; //macro tokens (possibly containing parm tokens) // struct define_s *next; //next defined macro in a list } //end of the function PC_PrintDefine*/ #if DEFINEHASHING //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_PrintDefineHashTable(define_t **definehash) { int i; define_t *d; for (i = 0; i < DEFINEHASHSIZE; i++) { Log_Write("%4d:", i); for (d = definehash[i]; d; d = d->hashnext) { Log_Write(" %s", d->name); } //end for Log_Write("\n"); } //end for } //end of the function PC_PrintDefineHashTable //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ //char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47}; int PC_NameHash(char *name) { int register hash, i; hash = 0; for (i = 0; name[i] != '\0'; i++) { hash += name[i] * (119 + i); //hash += (name[i] << 7) + i; //hash += (name[i] << (i&15)); } //end while hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); return hash; } //end of the function PC_NameHash //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_AddDefineToHash(define_t *define, define_t **definehash) { int hash; hash = PC_NameHash(define->name); define->hashnext = definehash[hash]; definehash[hash] = define; } //end of the function PC_AddDefineToHash //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ define_t *PC_FindHashedDefine(define_t **definehash, char *name) { define_t *d; int hash; hash = PC_NameHash(name); for (d = definehash[hash]; d; d = d->hashnext) { if (!strcmp(d->name, name)) return d; } //end for return NULL; } //end of the function PC_FindHashedDefine #endif //DEFINEHASHING //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ define_t *PC_FindDefine(define_t *defines, char *name) { define_t *d; for (d = defines; d; d = d->next) { if (!strcmp(d->name, name)) return d; } //end for return NULL; } //end of the function PC_FindDefine //============================================================================ // // Parameter: - // Returns: number of the parm // if no parm found with the given name -1 is returned // Changes Globals: - //============================================================================ int PC_FindDefineParm(define_t *define, char *name) { token_t *p; int i; i = 0; for (p = define->parms; p; p = p->next) { if (!strcmp(p->string, name)) return i; i++; } //end for return -1; } //end of the function PC_FindDefineParm //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_FreeDefine(define_t *define) { token_t *t, *next; //free the define parameters for (t = define->parms; t; t = next) { next = t->next; PC_FreeToken(t); } //end for //free the define tokens for (t = define->tokens; t; t = next) { next = t->next; PC_FreeToken(t); } //end for //free the define FreeMemory(define); } //end of the function PC_FreeDefine //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_AddBuiltinDefines(source_t *source) { int i; define_t *define; struct builtin { char *string; int builtin; } builtin[] = { // bk001204 - brackets { "__LINE__", BUILTIN_LINE }, { "__FILE__", BUILTIN_FILE }, { "__DATE__", BUILTIN_DATE }, { "__TIME__", BUILTIN_TIME }, // { "__STDC__", BUILTIN_STDC }, { NULL, 0 } }; for (i = 0; builtin[i].string; i++) { define = (define_t *) GetMemory(sizeof(define_t) + strlen(builtin[i].string) + 1); Com_Memset(define, 0, sizeof(define_t)); define->name = (char *) define + sizeof(define_t); strcpy(define->name, builtin[i].string); define->flags |= DEFINE_FIXED; define->builtin = builtin[i].builtin; //add the define to the source #if DEFINEHASHING PC_AddDefineToHash(define, source->definehash); #else define->next = source->defines; source->defines = define; #endif //DEFINEHASHING } //end for } //end of the function PC_AddBuiltinDefines //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_ExpandBuiltinDefine(source_t *source, token_t *deftoken, define_t *define, token_t **firsttoken, token_t **lasttoken) { token_t *token; unsigned long t; // time_t t; //to prevent LCC warning char *curtime; token = PC_CopyToken(deftoken); switch(define->builtin) { case BUILTIN_LINE: { sprintf(token->string, "%d", deftoken->line); #ifdef NUMBERVALUE token->intvalue = deftoken->line; token->floatvalue = deftoken->line; #endif //NUMBERVALUE token->type = TT_NUMBER; token->subtype = TT_DECIMAL | TT_INTEGER; *firsttoken = token; *lasttoken = token; break; } //end case case BUILTIN_FILE: { strcpy(token->string, source->scriptstack->filename); token->type = TT_NAME; token->subtype = strlen(token->string); *firsttoken = token; *lasttoken = token; break; } //end case case BUILTIN_DATE: { t = time(NULL); curtime = ctime(&t); strcpy(token->string, "\""); strncat(token->string, curtime+4, 7); strncat(token->string+7, curtime+20, 4); strcat(token->string, "\""); free(curtime); token->type = TT_NAME; token->subtype = strlen(token->string); *firsttoken = token; *lasttoken = token; break; } //end case case BUILTIN_TIME: { t = time(NULL); curtime = ctime(&t); strcpy(token->string, "\""); strncat(token->string, curtime+11, 8); strcat(token->string, "\""); free(curtime); token->type = TT_NAME; token->subtype = strlen(token->string); *firsttoken = token; *lasttoken = token; break; } //end case case BUILTIN_STDC: default: { *firsttoken = NULL; *lasttoken = NULL; break; } //end case } //end switch return qtrue; } //end of the function PC_ExpandBuiltinDefine //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_ExpandDefine(source_t *source, token_t *deftoken, define_t *define, token_t **firsttoken, token_t **lasttoken) { token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t; token_t *t1, *t2, *first, *last, *nextpt, token; int parmnum, i; //if it is a builtin define if (define->builtin) { return PC_ExpandBuiltinDefine(source, deftoken, define, firsttoken, lasttoken); } //end if //if the define has parameters if (define->numparms) { if (!PC_ReadDefineParms(source, define, parms, MAX_DEFINEPARMS)) return qfalse; #ifdef DEBUG_EVAL for (i = 0; i < define->numparms; i++) { Log_Write("define parms %d:", i); for (pt = parms[i]; pt; pt = pt->next) { Log_Write("%s", pt->string); } //end for } //end for #endif //DEBUG_EVAL } //end if //empty list at first first = NULL; last = NULL; //create a list with tokens of the expanded define for (dt = define->tokens; dt; dt = dt->next) { parmnum = -1; //if the token is a name, it could be a define parameter if (dt->type == TT_NAME) { parmnum = PC_FindDefineParm(define, dt->string); } //end if //if it is a define parameter if (parmnum >= 0) { for (pt = parms[parmnum]; pt; pt = pt->next) { t = PC_CopyToken(pt); //add the token to the list t->next = NULL; if (last) last->next = t; else first = t; last = t; } //end for } //end if else { //if stringizing operator if (dt->string[0] == '#' && dt->string[1] == '\0') { //the stringizing operator must be followed by a define parameter if (dt->next) parmnum = PC_FindDefineParm(define, dt->next->string); else parmnum = -1; // if (parmnum >= 0) { //step over the stringizing operator dt = dt->next; //stringize the define parameter tokens if (!PC_StringizeTokens(parms[parmnum], &token)) { SourceError(source, "can't stringize tokens"); return qfalse; } //end if t = PC_CopyToken(&token); } //end if else { SourceWarning(source, "stringizing operator without define parameter"); continue; } //end if } //end if else { t = PC_CopyToken(dt); } //end else //add the token to the list t->next = NULL; if (last) last->next = t; else first = t; last = t; } //end else } //end for //check for the merging operator for (t = first; t; ) { if (t->next) { //if the merging operator if (t->next->string[0] == '#' && t->next->string[1] == '#') { t1 = t; t2 = t->next->next; if (t2) { if (!PC_MergeTokens(t1, t2)) { SourceError(source, "can't merge %s with %s", t1->string, t2->string); return qfalse; } //end if PC_FreeToken(t1->next); t1->next = t2->next; if (t2 == last) last = t1; PC_FreeToken(t2); continue; } //end if } //end if } //end if t = t->next; } //end for //store the first and last token of the list *firsttoken = first; *lasttoken = last; //free all the parameter tokens for (i = 0; i < define->numparms; i++) { for (pt = parms[i]; pt; pt = nextpt) { nextpt = pt->next; PC_FreeToken(pt); } //end for } //end for // return qtrue; } //end of the function PC_ExpandDefine //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_ExpandDefineIntoSource(source_t *source, token_t *deftoken, define_t *define) { token_t *firsttoken, *lasttoken; if (!PC_ExpandDefine(source, deftoken, define, &firsttoken, &lasttoken)) return qfalse; if (firsttoken && lasttoken) { lasttoken->next = source->tokens; source->tokens = firsttoken; return qtrue; } //end if return qfalse; } //end of the function PC_ExpandDefineIntoSource //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_ConvertPath(char *path) { char *ptr; //remove double path seperators for (ptr = path; *ptr;) { if ((*ptr == '\\' || *ptr == '/') && (*(ptr+1) == '\\' || *(ptr+1) == '/')) { strcpy(ptr, ptr+1); } //end if else { ptr++; } //end else } //end while //set OS dependent path seperators for (ptr = path; *ptr;) { if (*ptr == '/' || *ptr == '\\') *ptr = PATHSEPERATOR_CHAR; ptr++; } //end while } //end of the function PC_ConvertPath //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_include(source_t *source) { script_t *script; token_t token; char path[MAX_PATH]; #ifdef QUAKE foundfile_t file; #endif //QUAKE if (source->skip > 0) return qtrue; // if (!PC_ReadSourceToken(source, &token)) { SourceError(source, "#include without file name"); return qfalse; } //end if if (token.linescrossed > 0) { SourceError(source, "#include without file name"); return qfalse; } //end if if (token.type == TT_STRING) { StripDoubleQuotes(token.string); PC_ConvertPath(token.string); script = LoadScriptFile(token.string); if (!script) { strcpy(path, source->includepath); strcat(path, token.string); script = LoadScriptFile(path); } //end if } //end if else if (token.type == TT_PUNCTUATION && *token.string == '<') { strcpy(path, source->includepath); while(PC_ReadSourceToken(source, &token)) { if (token.linescrossed > 0) { PC_UnreadSourceToken(source, &token); break; } //end if if (token.type == TT_PUNCTUATION && *token.string == '>') break; strncat(path, token.string, MAX_PATH); } //end while if (*token.string != '>') { SourceWarning(source, "#include missing trailing >"); } //end if if (!strlen(path)) { SourceError(source, "#include without file name between < >"); return qfalse; } //end if PC_ConvertPath(path); script = LoadScriptFile(path); } //end if else { SourceError(source, "#include without file name"); return qfalse; } //end else #ifdef QUAKE if (!script) { Com_Memset(&file, 0, sizeof(foundfile_t)); script = LoadScriptFile(path); if (script) strncpy(script->filename, path, MAX_PATH); } //end if #endif //QUAKE if (!script) { #ifdef SCREWUP SourceWarning(source, "file %s not found", path); return qtrue; #else SourceError(source, "file %s not found", path); return qfalse; #endif //SCREWUP } //end if PC_PushScript(source, script); return qtrue; } //end of the function PC_Directive_include //============================================================================ // reads a token from the current line, continues reading on the next // line only if a backslash '\' is encountered. // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_ReadLine(source_t *source, token_t *token) { int crossline; crossline = 0; do { if (!PC_ReadSourceToken(source, token)) return qfalse; if (token->linescrossed > crossline) { PC_UnreadSourceToken(source, token); return qfalse; } //end if crossline = 1; } while(!strcmp(token->string, "\\")); return qtrue; } //end of the function PC_ReadLine //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_WhiteSpaceBeforeToken(token_t *token) { return token->endwhitespace_p - token->whitespace_p > 0; } //end of the function PC_WhiteSpaceBeforeToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_ClearTokenWhiteSpace(token_t *token) { token->whitespace_p = NULL; token->endwhitespace_p = NULL; token->linescrossed = 0; } //end of the function PC_ClearTokenWhiteSpace //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_undef(source_t *source) { token_t token; define_t *define, *lastdefine; int hash; if (source->skip > 0) return qtrue; // if (!PC_ReadLine(source, &token)) { SourceError(source, "undef without name"); return qfalse; } //end if if (token.type != TT_NAME) { PC_UnreadSourceToken(source, &token); SourceError(source, "expected name, found %s", token.string); return qfalse; } //end if #if DEFINEHASHING hash = PC_NameHash(token.string); for (lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext) { if (!strcmp(define->name, token.string)) { if (define->flags & DEFINE_FIXED) { SourceWarning(source, "can't undef %s", token.string); } //end if else { if (lastdefine) lastdefine->hashnext = define->hashnext; else source->definehash[hash] = define->hashnext; PC_FreeDefine(define); } //end else break; } //end if lastdefine = define; } //end for #else //DEFINEHASHING for (lastdefine = NULL, define = source->defines; define; define = define->next) { if (!strcmp(define->name, token.string)) { if (define->flags & DEFINE_FIXED) { SourceWarning(source, "can't undef %s", token.string); } //end if else { if (lastdefine) lastdefine->next = define->next; else source->defines = define->next; PC_FreeDefine(define); } //end else break; } //end if lastdefine = define; } //end for #endif //DEFINEHASHING return qtrue; } //end of the function PC_Directive_undef //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_define(source_t *source) { token_t token, *t, *last; define_t *define; if (source->skip > 0) return qtrue; // if (!PC_ReadLine(source, &token)) { SourceError(source, "#define without name"); return qfalse; } //end if if (token.type != TT_NAME) { PC_UnreadSourceToken(source, &token); SourceError(source, "expected name after #define, found %s", token.string); return qfalse; } //end if //check if the define already exists #if DEFINEHASHING define = PC_FindHashedDefine(source->definehash, token.string); #else define = PC_FindDefine(source->defines, token.string); #endif //DEFINEHASHING if (define) { if (define->flags & DEFINE_FIXED) { SourceError(source, "can't redefine %s", token.string); return qfalse; } //end if SourceWarning(source, "redefinition of %s", token.string); //unread the define name before executing the #undef directive PC_UnreadSourceToken(source, &token); if (!PC_Directive_undef(source)) return qfalse; //if the define was not removed (define->flags & DEFINE_FIXED) #if DEFINEHASHING define = PC_FindHashedDefine(source->definehash, token.string); #else define = PC_FindDefine(source->defines, token.string); #endif //DEFINEHASHING } //end if //allocate define define = (define_t *) GetMemory(sizeof(define_t) + strlen(token.string) + 1); Com_Memset(define, 0, sizeof(define_t)); define->name = (char *) define + sizeof(define_t); strcpy(define->name, token.string); //add the define to the source #if DEFINEHASHING PC_AddDefineToHash(define, source->definehash); #else //DEFINEHASHING define->next = source->defines; source->defines = define; #endif //DEFINEHASHING //if nothing is defined, just return if (!PC_ReadLine(source, &token)) return qtrue; //if it is a define with parameters if (!PC_WhiteSpaceBeforeToken(&token) && !strcmp(token.string, "(")) { //read the define parameters last = NULL; if (!PC_CheckTokenString(source, ")")) { while(1) { if (!PC_ReadLine(source, &token)) { SourceError(source, "expected define parameter"); return qfalse; } //end if //if it isn't a name if (token.type != TT_NAME) { SourceError(source, "invalid define parameter"); return qfalse; } //end if // if (PC_FindDefineParm(define, token.string) >= 0) { SourceError(source, "two the same define parameters"); return qfalse; } //end if //add the define parm t = PC_CopyToken(&token); PC_ClearTokenWhiteSpace(t); t->next = NULL; if (last) last->next = t; else define->parms = t; last = t; define->numparms++; //read next token if (!PC_ReadLine(source, &token)) { SourceError(source, "define parameters not terminated"); return qfalse; } //end if // if (!strcmp(token.string, ")")) break; //then it must be a comma if (strcmp(token.string, ",")) { SourceError(source, "define not terminated"); return qfalse; } //end if } //end while } //end if if (!PC_ReadLine(source, &token)) return qtrue; } //end if //read the defined stuff last = NULL; do { t = PC_CopyToken(&token); if (t->type == TT_NAME && !strcmp(t->string, define->name)) { SourceError(source, "recursive define (removed recursion)"); continue; } //end if PC_ClearTokenWhiteSpace(t); t->next = NULL; if (last) last->next = t; else define->tokens = t; last = t; } while(PC_ReadLine(source, &token)); // if (last) { //check for merge operators at the beginning or end if (!strcmp(define->tokens->string, "##") || !strcmp(last->string, "##")) { SourceError(source, "define with misplaced ##"); return qfalse; } //end if } //end if return qtrue; } //end of the function PC_Directive_define //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ define_t *PC_DefineFromString(char *string) { script_t *script; source_t src; token_t *t; int res, i; define_t *def; PC_InitTokenHeap(); script = LoadScriptMemory(string, strlen(string), "*extern"); //create a new source Com_Memset(&src, 0, sizeof(source_t)); strncpy(src.filename, "*extern", MAX_PATH); src.scriptstack = script; #if DEFINEHASHING src.definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); #endif //DEFINEHASHING //create a define from the source res = PC_Directive_define(&src); //free any tokens if left for (t = src.tokens; t; t = src.tokens) { src.tokens = src.tokens->next; PC_FreeToken(t); } //end for #ifdef DEFINEHASHING def = NULL; for (i = 0; i < DEFINEHASHSIZE; i++) { if (src.definehash[i]) { def = src.definehash[i]; break; } //end if } //end for #else def = src.defines; #endif //DEFINEHASHING // #if DEFINEHASHING FreeMemory(src.definehash); #endif //DEFINEHASHING // FreeScript(script); //if the define was created succesfully if (res > 0) return def; //free the define is created if (src.defines) PC_FreeDefine(def); // return NULL; } //end of the function PC_DefineFromString //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_AddDefine(source_t *source, char *string) { define_t *define; define = PC_DefineFromString(string); if (!define) return qfalse; #if DEFINEHASHING PC_AddDefineToHash(define, source->definehash); #else //DEFINEHASHING define->next = source->defines; source->defines = define; #endif //DEFINEHASHING return qtrue; } //end of the function PC_AddDefine //============================================================================ // add a globals define that will be added to all opened sources // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_AddGlobalDefine(char *string) { define_t *define; define = PC_DefineFromString(string); if (!define) return qfalse; define->next = globaldefines; globaldefines = define; return qtrue; } //end of the function PC_AddGlobalDefine //============================================================================ // remove the given global define // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_RemoveGlobalDefine(char *name) { define_t *define; define = PC_FindDefine(globaldefines, name); if (define) { PC_FreeDefine(define); return qtrue; } //end if return qfalse; } //end of the function PC_RemoveGlobalDefine //============================================================================ // remove all globals defines // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_RemoveAllGlobalDefines(void) { define_t *define; for (define = globaldefines; define; define = globaldefines) { globaldefines = globaldefines->next; PC_FreeDefine(define); } //end for } //end of the function PC_RemoveAllGlobalDefines //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ define_t *PC_CopyDefine(source_t *source, define_t *define) { define_t *newdefine; token_t *token, *newtoken, *lasttoken; newdefine = (define_t *) GetMemory(sizeof(define_t) + strlen(define->name) + 1); //copy the define name newdefine->name = (char *) newdefine + sizeof(define_t); strcpy(newdefine->name, define->name); newdefine->flags = define->flags; newdefine->builtin = define->builtin; newdefine->numparms = define->numparms; //the define is not linked newdefine->next = NULL; newdefine->hashnext = NULL; //copy the define tokens newdefine->tokens = NULL; for (lasttoken = NULL, token = define->tokens; token; token = token->next) { newtoken = PC_CopyToken(token); newtoken->next = NULL; if (lasttoken) lasttoken->next = newtoken; else newdefine->tokens = newtoken; lasttoken = newtoken; } //end for //copy the define parameters newdefine->parms = NULL; for (lasttoken = NULL, token = define->parms; token; token = token->next) { newtoken = PC_CopyToken(token); newtoken->next = NULL; if (lasttoken) lasttoken->next = newtoken; else newdefine->parms = newtoken; lasttoken = newtoken; } //end for return newdefine; } //end of the function PC_CopyDefine //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_AddGlobalDefinesToSource(source_t *source) { define_t *define, *newdefine; for (define = globaldefines; define; define = define->next) { newdefine = PC_CopyDefine(source, define); #if DEFINEHASHING PC_AddDefineToHash(newdefine, source->definehash); #else //DEFINEHASHING newdefine->next = source->defines; source->defines = newdefine; #endif //DEFINEHASHING } //end for } //end of the function PC_AddGlobalDefinesToSource //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_if_def(source_t *source, int type) { token_t token; define_t *d; int skip; if (!PC_ReadLine(source, &token)) { SourceError(source, "#ifdef without name"); return qfalse; } //end if if (token.type != TT_NAME) { PC_UnreadSourceToken(source, &token); SourceError(source, "expected name after #ifdef, found %s", token.string); return qfalse; } //end if #if DEFINEHASHING d = PC_FindHashedDefine(source->definehash, token.string); #else d = PC_FindDefine(source->defines, token.string); #endif //DEFINEHASHING skip = (type == INDENT_IFDEF) == (d == NULL); PC_PushIndent(source, type, skip); return qtrue; } //end of the function PC_Directiveif_def //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_ifdef(source_t *source) { return PC_Directive_if_def(source, INDENT_IFDEF); } //end of the function PC_Directive_ifdef //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_ifndef(source_t *source) { return PC_Directive_if_def(source, INDENT_IFNDEF); } //end of the function PC_Directive_ifndef //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_else(source_t *source) { int type, skip; PC_PopIndent(source, &type, &skip); if (!type) { SourceError(source, "misplaced #else"); return qfalse; } //end if if (type == INDENT_ELSE) { SourceError(source, "#else after #else"); return qfalse; } //end if PC_PushIndent(source, INDENT_ELSE, !skip); return qtrue; } //end of the function PC_Directive_else //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_endif(source_t *source) { int type, skip; PC_PopIndent(source, &type, &skip); if (!type) { SourceError(source, "misplaced #endif"); return qfalse; } //end if return qtrue; } //end of the function PC_Directive_endif //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ typedef struct operator_s { int operator; int priority; int parentheses; struct operator_s *prev, *next; } operator_t; typedef struct value_s { signed long int intvalue; double floatvalue; int parentheses; struct value_s *prev, *next; } value_t; int PC_OperatorPriority(int op) { switch(op) { case P_MUL: return 15; case P_DIV: return 15; case P_MOD: return 15; case P_ADD: return 14; case P_SUB: return 14; case P_LOGIC_AND: return 7; case P_LOGIC_OR: return 6; case P_LOGIC_GEQ: return 12; case P_LOGIC_LEQ: return 12; case P_LOGIC_EQ: return 11; case P_LOGIC_UNEQ: return 11; case P_LOGIC_NOT: return 16; case P_LOGIC_GREATER: return 12; case P_LOGIC_LESS: return 12; case P_RSHIFT: return 13; case P_LSHIFT: return 13; case P_BIN_AND: return 10; case P_BIN_OR: return 8; case P_BIN_XOR: return 9; case P_BIN_NOT: return 16; case P_COLON: return 5; case P_QUESTIONMARK: return 5; } //end switch return qfalse; } //end of the function PC_OperatorPriority //#define AllocValue() GetClearedMemory(sizeof(value_t)); //#define FreeValue(val) FreeMemory(val) //#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t)); //#define FreeOperator(op) FreeMemory(op); #define MAX_VALUES 64 #define MAX_OPERATORS 64 #define AllocValue(val) \ if (numvalues >= MAX_VALUES) { \ SourceError(source, "out of value space\n"); \ error = 1; \ break; \ } \ else \ val = &value_heap[numvalues++]; #define FreeValue(val) // #define AllocOperator(op) \ if (numoperators >= MAX_OPERATORS) { \ SourceError(source, "out of operator space\n"); \ error = 1; \ break; \ } \ else \ op = &operator_heap[numoperators++]; #define FreeOperator(op) int PC_EvaluateTokens(source_t *source, token_t *tokens, signed long int *intvalue, double *floatvalue, int integer) { operator_t *o, *firstoperator, *lastoperator; value_t *v, *firstvalue, *lastvalue, *v1, *v2; token_t *t; int brace = 0; int parentheses = 0; int error = 0; int lastwasvalue = 0; int negativevalue = 0; int questmarkintvalue = 0; double questmarkfloatvalue = 0; int gotquestmarkvalue = qfalse; int lastoperatortype = 0; // operator_t operator_heap[MAX_OPERATORS]; int numoperators = 0; value_t value_heap[MAX_VALUES]; int numvalues = 0; firstoperator = lastoperator = NULL; firstvalue = lastvalue = NULL; if (intvalue) *intvalue = 0; if (floatvalue) *floatvalue = 0; for (t = tokens; t; t = t->next) { switch(t->type) { case TT_NAME: { if (lastwasvalue || negativevalue) { SourceError(source, "syntax error in #if/#elif"); error = 1; break; } //end if if (strcmp(t->string, "defined")) { SourceError(source, "undefined name %s in #if/#elif", t->string); error = 1; break; } //end if t = t->next; if (!strcmp(t->string, "(")) { brace = qtrue; t = t->next; } //end if if (!t || t->type != TT_NAME) { SourceError(source, "defined without name in #if/#elif"); error = 1; break; } //end if //v = (value_t *) GetClearedMemory(sizeof(value_t)); AllocValue(v); #if DEFINEHASHING if (PC_FindHashedDefine(source->definehash, t->string)) #else if (PC_FindDefine(source->defines, t->string)) #endif //DEFINEHASHING { v->intvalue = 1; v->floatvalue = 1; } //end if else { v->intvalue = 0; v->floatvalue = 0; } //end else v->parentheses = parentheses; v->next = NULL; v->prev = lastvalue; if (lastvalue) lastvalue->next = v; else firstvalue = v; lastvalue = v; if (brace) { t = t->next; if (!t || strcmp(t->string, ")")) { SourceError(source, "defined without ) in #if/#elif"); error = 1; break; } //end if } //end if brace = qfalse; // defined() creates a value lastwasvalue = 1; break; } //end case case TT_NUMBER: { if (lastwasvalue) { SourceError(source, "syntax error in #if/#elif"); error = 1; break; } //end if //v = (value_t *) GetClearedMemory(sizeof(value_t)); AllocValue(v); if (negativevalue) { v->intvalue = - (signed int) t->intvalue; v->floatvalue = - t->floatvalue; } //end if else { v->intvalue = t->intvalue; v->floatvalue = t->floatvalue; } //end else v->parentheses = parentheses; v->next = NULL; v->prev = lastvalue; if (lastvalue) lastvalue->next = v; else firstvalue = v; lastvalue = v; //last token was a value lastwasvalue = 1; // negativevalue = 0; break; } //end case case TT_PUNCTUATION: { if (negativevalue) { SourceError(source, "misplaced minus sign in #if/#elif"); error = 1; break; } //end if if (t->subtype == P_PARENTHESESOPEN) { parentheses++; break; } //end if else if (t->subtype == P_PARENTHESESCLOSE) { parentheses--; if (parentheses < 0) { SourceError(source, "too many ) in #if/#elsif"); error = 1; } //end if break; } //end else if //check for invalid operators on floating point values if (!integer) { if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || t->subtype == P_BIN_XOR) { SourceError(source, "illigal operator %s on floating point operands\n", t->string); error = 1; break; } //end if } //end if switch(t->subtype) { case P_LOGIC_NOT: case P_BIN_NOT: { if (lastwasvalue) { SourceError(source, "! or ~ after value in #if/#elif"); error = 1; break; } //end if break; } //end case case P_INC: case P_DEC: { SourceError(source, "++ or -- used in #if/#elif"); break; } //end case case P_SUB: { if (!lastwasvalue) { negativevalue = 1; break; } //end if } //end case case P_MUL: case P_DIV: case P_MOD: case P_ADD: case P_LOGIC_AND: case P_LOGIC_OR: case P_LOGIC_GEQ: case P_LOGIC_LEQ: case P_LOGIC_EQ: case P_LOGIC_UNEQ: case P_LOGIC_GREATER: case P_LOGIC_LESS: case P_RSHIFT: case P_LSHIFT: case P_BIN_AND: case P_BIN_OR: case P_BIN_XOR: case P_COLON: case P_QUESTIONMARK: { if (!lastwasvalue) { SourceError(source, "operator %s after operator in #if/#elif", t->string); error = 1; break; } //end if break; } //end case default: { SourceError(source, "invalid operator %s in #if/#elif", t->string); error = 1; break; } //end default } //end switch if (!error && !negativevalue) { //o = (operator_t *) GetClearedMemory(sizeof(operator_t)); AllocOperator(o); o->operator = t->subtype; o->priority = PC_OperatorPriority(t->subtype); o->parentheses = parentheses; o->next = NULL; o->prev = lastoperator; if (lastoperator) lastoperator->next = o; else firstoperator = o; lastoperator = o; lastwasvalue = 0; } //end if break; } //end case default: { SourceError(source, "unknown %s in #if/#elif", t->string); error = 1; break; } //end default } //end switch if (error) break; } //end for if (!error) { if (!lastwasvalue) { SourceError(source, "trailing operator in #if/#elif"); error = 1; } //end if else if (parentheses) { SourceError(source, "too many ( in #if/#elif"); error = 1; } //end else if } //end if // gotquestmarkvalue = qfalse; questmarkintvalue = 0; questmarkfloatvalue = 0; //while there are operators while(!error && firstoperator) { v = firstvalue; for (o = firstoperator; o->next; o = o->next) { //if the current operator is nested deeper in parentheses //than the next operator if (o->parentheses > o->next->parentheses) break; //if the current and next operator are nested equally deep in parentheses if (o->parentheses == o->next->parentheses) { //if the priority of the current operator is equal or higher //than the priority of the next operator if (o->priority >= o->next->priority) break; } //end if //if the arity of the operator isn't equal to 1 if (o->operator != P_LOGIC_NOT && o->operator != P_BIN_NOT) v = v->next; //if there's no value or no next value if (!v) { SourceError(source, "mising values in #if/#elif"); error = 1; break; } //end if } //end for if (error) break; v1 = v; v2 = v->next; #ifdef DEBUG_EVAL if (integer) { Log_Write("operator %s, value1 = %d", PunctuationFromNum(source->scriptstack, o->operator), v1->intvalue); if (v2) Log_Write("value2 = %d", v2->intvalue); } //end if else { Log_Write("operator %s, value1 = %f", PunctuationFromNum(source->scriptstack, o->operator), v1->floatvalue); if (v2) Log_Write("value2 = %f", v2->floatvalue); } //end else #endif //DEBUG_EVAL switch(o->operator) { case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; v1->floatvalue = !v1->floatvalue; break; case P_BIN_NOT: v1->intvalue = ~v1->intvalue; break; case P_MUL: v1->intvalue *= v2->intvalue; v1->floatvalue *= v2->floatvalue; break; case P_DIV: if (!v2->intvalue || !v2->floatvalue) { SourceError(source, "divide by zero in #if/#elif\n"); error = 1; break; } v1->intvalue /= v2->intvalue; v1->floatvalue /= v2->floatvalue; break; case P_MOD: if (!v2->intvalue) { SourceError(source, "divide by zero in #if/#elif\n"); error = 1; break; } v1->intvalue %= v2->intvalue; break; case P_ADD: v1->intvalue += v2->intvalue; v1->floatvalue += v2->floatvalue; break; case P_SUB: v1->intvalue -= v2->intvalue; v1->floatvalue -= v2->floatvalue; break; case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; v1->floatvalue = v1->floatvalue && v2->floatvalue; break; case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; v1->floatvalue = v1->floatvalue || v2->floatvalue; break; case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; v1->floatvalue = v1->floatvalue == v2->floatvalue; break; case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; v1->floatvalue = v1->floatvalue != v2->floatvalue; break; case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; v1->floatvalue = v1->floatvalue > v2->floatvalue; break; case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; v1->floatvalue = v1->floatvalue < v2->floatvalue; break; case P_RSHIFT: v1->intvalue >>= v2->intvalue; break; case P_LSHIFT: v1->intvalue <<= v2->intvalue; break; case P_BIN_AND: v1->intvalue &= v2->intvalue; break; case P_BIN_OR: v1->intvalue |= v2->intvalue; break; case P_BIN_XOR: v1->intvalue ^= v2->intvalue; break; case P_COLON: { if (!gotquestmarkvalue) { SourceError(source, ": without ? in #if/#elif"); error = 1; break; } //end if if (integer) { if (!questmarkintvalue) v1->intvalue = v2->intvalue; } //end if else { if (!questmarkfloatvalue) v1->floatvalue = v2->floatvalue; } //end else gotquestmarkvalue = qfalse; break; } //end case case P_QUESTIONMARK: { if (gotquestmarkvalue) { SourceError(source, "? after ? in #if/#elif"); error = 1; break; } //end if questmarkintvalue = v1->intvalue; questmarkfloatvalue = v1->floatvalue; gotquestmarkvalue = qtrue; break; } //end if } //end switch #ifdef DEBUG_EVAL if (integer) Log_Write("result value = %d", v1->intvalue); else Log_Write("result value = %f", v1->floatvalue); #endif //DEBUG_EVAL if (error) break; lastoperatortype = o->operator; //if not an operator with arity 1 if (o->operator != P_LOGIC_NOT && o->operator != P_BIN_NOT) { //remove the second value if not question mark operator if (o->operator != P_QUESTIONMARK) v = v->next; // if (v->prev) v->prev->next = v->next; else firstvalue = v->next; if (v->next) v->next->prev = v->prev; else lastvalue = v->prev; //FreeMemory(v); FreeValue(v); } //end if //remove the operator if (o->prev) o->prev->next = o->next; else firstoperator = o->next; if (o->next) o->next->prev = o->prev; else lastoperator = o->prev; //FreeMemory(o); FreeOperator(o); } //end while if (firstvalue) { if (intvalue) *intvalue = firstvalue->intvalue; if (floatvalue) *floatvalue = firstvalue->floatvalue; } //end if for (o = firstoperator; o; o = lastoperator) { lastoperator = o->next; //FreeMemory(o); FreeOperator(o); } //end for for (v = firstvalue; v; v = lastvalue) { lastvalue = v->next; //FreeMemory(v); FreeValue(v); } //end for if (!error) return qtrue; if (intvalue) *intvalue = 0; if (floatvalue) *floatvalue = 0; return qfalse; } //end of the function PC_EvaluateTokens //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Evaluate(source_t *source, signed long int *intvalue, double *floatvalue, int integer) { token_t token, *firsttoken, *lasttoken; token_t *t, *nexttoken; define_t *define; int defined = qfalse; if (intvalue) *intvalue = 0; if (floatvalue) *floatvalue = 0; // if (!PC_ReadLine(source, &token)) { SourceError(source, "no value after #if/#elif"); return qfalse; } //end if firsttoken = NULL; lasttoken = NULL; do { //if the token is a name if (token.type == TT_NAME) { if (defined) { defined = qfalse; t = PC_CopyToken(&token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } //end if else if (!strcmp(token.string, "defined")) { defined = qtrue; t = PC_CopyToken(&token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } //end if else { //then it must be a define #if DEFINEHASHING define = PC_FindHashedDefine(source->definehash, token.string); #else define = PC_FindDefine(source->defines, token.string); #endif //DEFINEHASHING if (!define) { SourceError(source, "can't evaluate %s, not defined", token.string); return qfalse; } //end if if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse; } //end else } //end if //if the token is a number or a punctuation else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) { t = PC_CopyToken(&token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } //end else else //can't evaluate the token { SourceError(source, "can't evaluate %s", token.string); return qfalse; } //end else } while(PC_ReadLine(source, &token)); // if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; // #ifdef DEBUG_EVAL Log_Write("eval:"); #endif //DEBUG_EVAL for (t = firsttoken; t; t = nexttoken) { #ifdef DEBUG_EVAL Log_Write(" %s", t->string); #endif //DEBUG_EVAL nexttoken = t->next; PC_FreeToken(t); } //end for #ifdef DEBUG_EVAL if (integer) Log_Write("eval result: %d", *intvalue); else Log_Write("eval result: %f", *floatvalue); #endif //DEBUG_EVAL // return qtrue; } //end of the function PC_Evaluate //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_DollarEvaluate(source_t *source, signed long int *intvalue, double *floatvalue, int integer) { int indent, defined = qfalse; token_t token, *firsttoken, *lasttoken; token_t *t, *nexttoken; define_t *define; if (intvalue) *intvalue = 0; if (floatvalue) *floatvalue = 0; // if (!PC_ReadSourceToken(source, &token)) { SourceError(source, "no leading ( after $evalint/$evalfloat"); return qfalse; } //end if if (!PC_ReadSourceToken(source, &token)) { SourceError(source, "nothing to evaluate"); return qfalse; } //end if indent = 1; firsttoken = NULL; lasttoken = NULL; do { //if the token is a name if (token.type == TT_NAME) { if (defined) { defined = qfalse; t = PC_CopyToken(&token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } //end if else if (!strcmp(token.string, "defined")) { defined = qtrue; t = PC_CopyToken(&token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } //end if else { //then it must be a define #if DEFINEHASHING define = PC_FindHashedDefine(source->definehash, token.string); #else define = PC_FindDefine(source->defines, token.string); #endif //DEFINEHASHING if (!define) { SourceError(source, "can't evaluate %s, not defined", token.string); return qfalse; } //end if if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse; } //end else } //end if //if the token is a number or a punctuation else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) { if (*token.string == '(') indent++; else if (*token.string == ')') indent--; if (indent <= 0) break; t = PC_CopyToken(&token); t->next = NULL; if (lasttoken) lasttoken->next = t; else firsttoken = t; lasttoken = t; } //end else else //can't evaluate the token { SourceError(source, "can't evaluate %s", token.string); return qfalse; } //end else } while(PC_ReadSourceToken(source, &token)); // if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; // #ifdef DEBUG_EVAL Log_Write("$eval:"); #endif //DEBUG_EVAL for (t = firsttoken; t; t = nexttoken) { #ifdef DEBUG_EVAL Log_Write(" %s", t->string); #endif //DEBUG_EVAL nexttoken = t->next; PC_FreeToken(t); } //end for #ifdef DEBUG_EVAL if (integer) Log_Write("$eval result: %d", *intvalue); else Log_Write("$eval result: %f", *floatvalue); #endif //DEBUG_EVAL // return qtrue; } //end of the function PC_DollarEvaluate //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_elif(source_t *source) { signed long int value; int type, skip; PC_PopIndent(source, &type, &skip); if (!type || type == INDENT_ELSE) { SourceError(source, "misplaced #elif"); return qfalse; } //end if if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; skip = (value == 0); PC_PushIndent(source, INDENT_ELIF, skip); return qtrue; } //end of the function PC_Directive_elif //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_if(source_t *source) { signed long int value; int skip; if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; skip = (value == 0); PC_PushIndent(source, INDENT_IF, skip); return qtrue; } //end of the function PC_Directive //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_line(source_t *source) { SourceError(source, "#line directive not supported"); return qfalse; } //end of the function PC_Directive_line //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_error(source_t *source) { token_t token; strcpy(token.string, ""); PC_ReadSourceToken(source, &token); SourceError(source, "#error directive: %s", token.string); return qfalse; } //end of the function PC_Directive_error //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_pragma(source_t *source) { token_t token; SourceWarning(source, "#pragma directive not supported"); while(PC_ReadLine(source, &token)) ; return qtrue; } //end of the function PC_Directive_pragma //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void UnreadSignToken(source_t *source) { token_t token; token.line = source->scriptstack->line; token.whitespace_p = source->scriptstack->script_p; token.endwhitespace_p = source->scriptstack->script_p; token.linescrossed = 0; strcpy(token.string, "-"); token.type = TT_PUNCTUATION; token.subtype = P_SUB; PC_UnreadSourceToken(source, &token); } //end of the function UnreadSignToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_eval(source_t *source) { signed long int value; token_t token; if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; // token.line = source->scriptstack->line; token.whitespace_p = source->scriptstack->script_p; token.endwhitespace_p = source->scriptstack->script_p; token.linescrossed = 0; sprintf(token.string, "%d", abs(value)); token.type = TT_NUMBER; token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; PC_UnreadSourceToken(source, &token); if (value < 0) UnreadSignToken(source); return qtrue; } //end of the function PC_Directive_eval //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_Directive_evalfloat(source_t *source) { double value; token_t token; if (!PC_Evaluate(source, NULL, &value, qfalse)) return qfalse; token.line = source->scriptstack->line; token.whitespace_p = source->scriptstack->script_p; token.endwhitespace_p = source->scriptstack->script_p; token.linescrossed = 0; sprintf(token.string, "%1.2f", fabs(value)); token.type = TT_NUMBER; token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; PC_UnreadSourceToken(source, &token); if (value < 0) UnreadSignToken(source); return qtrue; } //end of the function PC_Directive_evalfloat //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ directive_t directives[20] = { {"if", PC_Directive_if}, {"ifdef", PC_Directive_ifdef}, {"ifndef", PC_Directive_ifndef}, {"elif", PC_Directive_elif}, {"else", PC_Directive_else}, {"endif", PC_Directive_endif}, {"include", PC_Directive_include}, {"define", PC_Directive_define}, {"undef", PC_Directive_undef}, {"line", PC_Directive_line}, {"error", PC_Directive_error}, {"pragma", PC_Directive_pragma}, {"eval", PC_Directive_eval}, {"evalfloat", PC_Directive_evalfloat}, {NULL, NULL} }; int PC_ReadDirective(source_t *source) { token_t token; int i; //read the directive name if (!PC_ReadSourceToken(source, &token)) { SourceError(source, "found # without name"); return qfalse; } //end if //directive name must be on the same line if (token.linescrossed > 0) { PC_UnreadSourceToken(source, &token); SourceError(source, "found # at end of line"); return qfalse; } //end if //if if is a name if (token.type == TT_NAME) { //find the precompiler directive for (i = 0; directives[i].name; i++) { if (!strcmp(directives[i].name, token.string)) { return directives[i].func(source); } //end if } //end for } //end if SourceError(source, "unknown precompiler directive %s", token.string); return qfalse; } //end of the function PC_ReadDirective //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_DollarDirective_evalint(source_t *source) { signed long int value; token_t token; if (!PC_DollarEvaluate(source, &value, NULL, qtrue)) return qfalse; // token.line = source->scriptstack->line; token.whitespace_p = source->scriptstack->script_p; token.endwhitespace_p = source->scriptstack->script_p; token.linescrossed = 0; sprintf(token.string, "%d", abs(value)); token.type = TT_NUMBER; token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; #ifdef NUMBERVALUE token.intvalue = value; token.floatvalue = value; #endif //NUMBERVALUE PC_UnreadSourceToken(source, &token); if (value < 0) UnreadSignToken(source); return qtrue; } //end of the function PC_DollarDirective_evalint //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_DollarDirective_evalfloat(source_t *source) { double value; token_t token; if (!PC_DollarEvaluate(source, NULL, &value, qfalse)) return qfalse; token.line = source->scriptstack->line; token.whitespace_p = source->scriptstack->script_p; token.endwhitespace_p = source->scriptstack->script_p; token.linescrossed = 0; sprintf(token.string, "%1.2f", fabs(value)); token.type = TT_NUMBER; token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; #ifdef NUMBERVALUE token.intvalue = (unsigned long) value; token.floatvalue = value; #endif //NUMBERVALUE PC_UnreadSourceToken(source, &token); if (value < 0) UnreadSignToken(source); return qtrue; } //end of the function PC_DollarDirective_evalfloat //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ directive_t dollardirectives[20] = { {"evalint", PC_DollarDirective_evalint}, {"evalfloat", PC_DollarDirective_evalfloat}, {NULL, NULL} }; int PC_ReadDollarDirective(source_t *source) { token_t token; int i; //read the directive name if (!PC_ReadSourceToken(source, &token)) { SourceError(source, "found $ without name"); return qfalse; } //end if //directive name must be on the same line if (token.linescrossed > 0) { PC_UnreadSourceToken(source, &token); SourceError(source, "found $ at end of line"); return qfalse; } //end if //if if is a name if (token.type == TT_NAME) { //find the precompiler directive for (i = 0; dollardirectives[i].name; i++) { if (!strcmp(dollardirectives[i].name, token.string)) { return dollardirectives[i].func(source); } //end if } //end for } //end if PC_UnreadSourceToken(source, &token); SourceError(source, "unknown precompiler directive %s", token.string); return qfalse; } //end of the function PC_ReadDirective #ifdef QUAKEC //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int BuiltinFunction(source_t *source) { token_t token; if (!PC_ReadSourceToken(source, &token)) return qfalse; if (token.type == TT_NUMBER) { PC_UnreadSourceToken(source, &token); return qtrue; } //end if else { PC_UnreadSourceToken(source, &token); return qfalse; } //end else } //end of the function BuiltinFunction //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int QuakeCMacro(source_t *source) { int i; token_t token; if (!PC_ReadSourceToken(source, &token)) return qtrue; if (token.type != TT_NAME) { PC_UnreadSourceToken(source, &token); return qtrue; } //end if //find the precompiler directive for (i = 0; dollardirectives[i].name; i++) { if (!strcmp(dollardirectives[i].name, token.string)) { PC_UnreadSourceToken(source, &token); return qfalse; } //end if } //end for PC_UnreadSourceToken(source, &token); return qtrue; } //end of the function QuakeCMacro #endif //QUAKEC //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_ReadToken(source_t *source, token_t *token) { define_t *define; while(1) { if (!PC_ReadSourceToken(source, token)) return qfalse; //check for precompiler directives if (token->type == TT_PUNCTUATION && *token->string == '#') { #ifdef QUAKEC if (!BuiltinFunction(source)) #endif //QUAKC { //read the precompiler directive if (!PC_ReadDirective(source)) return qfalse; continue; } //end if } //end if if (token->type == TT_PUNCTUATION && *token->string == '$') { #ifdef QUAKEC if (!QuakeCMacro(source)) #endif //QUAKEC { //read the precompiler directive if (!PC_ReadDollarDirective(source)) return qfalse; continue; } //end if } //end if // recursively concatenate strings that are behind each other still resolving defines if (token->type == TT_STRING) { token_t newtoken; if (PC_ReadToken(source, &newtoken)) { if (newtoken.type == TT_STRING) { token->string[strlen(token->string)-1] = '\0'; if (strlen(token->string) + strlen(newtoken.string+1) + 1 >= MAX_TOKEN) { SourceError(source, "string longer than MAX_TOKEN %d\n", MAX_TOKEN); return qfalse; } strcat(token->string, newtoken.string+1); } else { PC_UnreadToken(source, &newtoken); } } } //end if //if skipping source because of conditional compilation if (source->skip) continue; //if the token is a name if (token->type == TT_NAME) { //check if the name is a define macro #if DEFINEHASHING define = PC_FindHashedDefine(source->definehash, token->string); #else define = PC_FindDefine(source->defines, token->string); #endif //DEFINEHASHING //if it is a define macro if (define) { //expand the defined macro if (!PC_ExpandDefineIntoSource(source, token, define)) return qfalse; continue; } //end if } //end if //copy token for unreading Com_Memcpy(&source->token, token, sizeof(token_t)); //found a token return qtrue; } //end while } //end of the function PC_ReadToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_ExpectTokenString(source_t *source, char *string) { token_t token; if (!PC_ReadToken(source, &token)) { SourceError(source, "couldn't find expected %s", string); return qfalse; } //end if if (strcmp(token.string, string)) { SourceError(source, "expected %s, found %s", string, token.string); return qfalse; } //end if return qtrue; } //end of the function PC_ExpectTokenString //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token) { char str[MAX_TOKEN]; if (!PC_ReadToken(source, token)) { SourceError(source, "couldn't read expected token"); return qfalse; } //end if if (token->type != type) { strcpy(str, ""); if (type == TT_STRING) strcpy(str, "string"); if (type == TT_LITERAL) strcpy(str, "literal"); if (type == TT_NUMBER) strcpy(str, "number"); if (type == TT_NAME) strcpy(str, "name"); if (type == TT_PUNCTUATION) strcpy(str, "punctuation"); SourceError(source, "expected a %s, found %s", str, token->string); return qfalse; } //end if if (token->type == TT_NUMBER) { if ((token->subtype & subtype) != subtype) { if (subtype & TT_DECIMAL) strcpy(str, "decimal"); if (subtype & TT_HEX) strcpy(str, "hex"); if (subtype & TT_OCTAL) strcpy(str, "octal"); if (subtype & TT_BINARY) strcpy(str, "binary"); if (subtype & TT_LONG) strcat(str, " long"); if (subtype & TT_UNSIGNED) strcat(str, " unsigned"); if (subtype & TT_FLOAT) strcat(str, " float"); if (subtype & TT_INTEGER) strcat(str, " integer"); SourceError(source, "expected %s, found %s", str, token->string); return qfalse; } //end if } //end if else if (token->type == TT_PUNCTUATION) { if (token->subtype != subtype) { SourceError(source, "found %s", token->string); return qfalse; } //end if } //end else if return qtrue; } //end of the function PC_ExpectTokenType //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_ExpectAnyToken(source_t *source, token_t *token) { if (!PC_ReadToken(source, token)) { SourceError(source, "couldn't read expected token"); return qfalse; } //end if else { return qtrue; } //end else } //end of the function PC_ExpectAnyToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_CheckTokenString(source_t *source, char *string) { token_t tok; if (!PC_ReadToken(source, &tok)) return qfalse; //if the token is available if (!strcmp(tok.string, string)) return qtrue; // PC_UnreadSourceToken(source, &tok); return qfalse; } //end of the function PC_CheckTokenString //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token) { token_t tok; if (!PC_ReadToken(source, &tok)) return qfalse; //if the type matches if (tok.type == type && (tok.subtype & subtype) == subtype) { Com_Memcpy(token, &tok, sizeof(token_t)); return qtrue; } //end if // PC_UnreadSourceToken(source, &tok); return qfalse; } //end of the function PC_CheckTokenType //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_SkipUntilString(source_t *source, char *string) { token_t token; while(PC_ReadToken(source, &token)) { if (!strcmp(token.string, string)) return qtrue; } //end while return qfalse; } //end of the function PC_SkipUntilString //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_UnreadLastToken(source_t *source) { PC_UnreadSourceToken(source, &source->token); } //end of the function PC_UnreadLastToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_UnreadToken(source_t *source, token_t *token) { PC_UnreadSourceToken(source, token); } //end of the function PC_UnreadToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_SetIncludePath(source_t *source, char *path) { strncpy(source->includepath, path, MAX_PATH); //add trailing path seperator if (source->includepath[strlen(source->includepath)-1] != '\\' && source->includepath[strlen(source->includepath)-1] != '/') { strcat(source->includepath, PATHSEPERATOR_STR); } //end if } //end of the function PC_SetIncludePath //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_SetPunctuations(source_t *source, punctuation_t *p) { source->punctuations = p; } //end of the function PC_SetPunctuations //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ source_t *LoadSourceFile(const char *filename) { source_t *source; script_t *script; PC_InitTokenHeap(); script = LoadScriptFile(filename); if (!script) return NULL; script->next = NULL; source = (source_t *) GetMemory(sizeof(source_t)); Com_Memset(source, 0, sizeof(source_t)); strncpy(source->filename, filename, MAX_PATH); source->scriptstack = script; source->tokens = NULL; source->defines = NULL; source->indentstack = NULL; source->skip = 0; #if DEFINEHASHING source->definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); #endif //DEFINEHASHING PC_AddGlobalDefinesToSource(source); return source; } //end of the function LoadSourceFile //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ source_t *LoadSourceMemory(char *ptr, int length, char *name) { source_t *source; script_t *script; PC_InitTokenHeap(); script = LoadScriptMemory(ptr, length, name); if (!script) return NULL; script->next = NULL; source = (source_t *) GetMemory(sizeof(source_t)); Com_Memset(source, 0, sizeof(source_t)); strncpy(source->filename, name, MAX_PATH); source->scriptstack = script; source->tokens = NULL; source->defines = NULL; source->indentstack = NULL; source->skip = 0; #if DEFINEHASHING source->definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); #endif //DEFINEHASHING PC_AddGlobalDefinesToSource(source); return source; } //end of the function LoadSourceMemory //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void FreeSource(source_t *source) { script_t *script; token_t *token; define_t *define; indent_t *indent; int i; //PC_PrintDefineHashTable(source->definehash); //free all the scripts while(source->scriptstack) { script = source->scriptstack; source->scriptstack = source->scriptstack->next; FreeScript(script); } //end for //free all the tokens while(source->tokens) { token = source->tokens; source->tokens = source->tokens->next; PC_FreeToken(token); } //end for #if DEFINEHASHING for (i = 0; i < DEFINEHASHSIZE; i++) { while(source->definehash[i]) { define = source->definehash[i]; source->definehash[i] = source->definehash[i]->hashnext; PC_FreeDefine(define); } //end while } //end for #else //DEFINEHASHING //free all defines while(source->defines) { define = source->defines; source->defines = source->defines->next; PC_FreeDefine(define); } //end for #endif //DEFINEHASHING //free all indents while(source->indentstack) { indent = source->indentstack; source->indentstack = source->indentstack->next; FreeMemory(indent); } //end for #if DEFINEHASHING // if (source->definehash) FreeMemory(source->definehash); #endif //DEFINEHASHING //free the source itself FreeMemory(source); } //end of the function FreeSource //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ #define MAX_SOURCEFILES 64 source_t *sourceFiles[MAX_SOURCEFILES]; int PC_LoadSourceHandle(const char *filename) { source_t *source; int i; for (i = 1; i < MAX_SOURCEFILES; i++) { if (!sourceFiles[i]) break; } //end for if (i >= MAX_SOURCEFILES) return 0; PS_SetBaseFolder(""); source = LoadSourceFile(filename); if (!source) return 0; sourceFiles[i] = source; return i; } //end of the function PC_LoadSourceHandle //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_FreeSourceHandle(int handle) { if (handle < 1 || handle >= MAX_SOURCEFILES) return qfalse; if (!sourceFiles[handle]) return qfalse; FreeSource(sourceFiles[handle]); sourceFiles[handle] = NULL; return qtrue; } //end of the function PC_FreeSourceHandle //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_ReadTokenHandle(int handle, pc_token_t *pc_token) { token_t token; int ret; if (handle < 1 || handle >= MAX_SOURCEFILES) return 0; if (!sourceFiles[handle]) return 0; ret = PC_ReadToken(sourceFiles[handle], &token); strcpy(pc_token->string, token.string); pc_token->type = token.type; pc_token->subtype = token.subtype; pc_token->intvalue = token.intvalue; pc_token->floatvalue = token.floatvalue; if (pc_token->type == TT_STRING) StripDoubleQuotes(pc_token->string); return ret; } //end of the function PC_ReadTokenHandle //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PC_SourceFileAndLine(int handle, char *filename, int *line) { if (handle < 1 || handle >= MAX_SOURCEFILES) return qfalse; if (!sourceFiles[handle]) return qfalse; strcpy(filename, sourceFiles[handle]->filename); if (sourceFiles[handle]->scriptstack) *line = sourceFiles[handle]->scriptstack->line; else *line = 0; return qtrue; } //end of the function PC_SourceFileAndLine //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_SetBaseFolder(char *path) { PS_SetBaseFolder(path); } //end of the function PC_SetBaseFolder //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PC_CheckOpenSourceHandles(void) { int i; for (i = 1; i < MAX_SOURCEFILES; i++) { if (sourceFiles[i]) { #ifdef BOTLIB botimport.Print(PRT_ERROR, "file %s still open in precompiler\n", sourceFiles[i]->scriptstack->filename); #endif //BOTLIB } //end if } //end for } //end of the function PC_CheckOpenSourceHandles ================================================ FILE: code/botlib/l_precomp.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_precomp.h * * desc: pre compiler * * $Archive: /source/code/botlib/l_precomp.h $ * *****************************************************************************/ #ifndef MAX_PATH #define MAX_PATH MAX_QPATH #endif #ifndef PATH_SEPERATORSTR #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) #define PATHSEPERATOR_STR "\\" #else #define PATHSEPERATOR_STR "/" #endif #endif #ifndef PATH_SEPERATORCHAR #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) #define PATHSEPERATOR_CHAR '\\' #else #define PATHSEPERATOR_CHAR '/' #endif #endif #if defined(BSPC) && !defined(QDECL) #define QDECL #endif #define DEFINE_FIXED 0x0001 #define BUILTIN_LINE 1 #define BUILTIN_FILE 2 #define BUILTIN_DATE 3 #define BUILTIN_TIME 4 #define BUILTIN_STDC 5 #define INDENT_IF 0x0001 #define INDENT_ELSE 0x0002 #define INDENT_ELIF 0x0004 #define INDENT_IFDEF 0x0008 #define INDENT_IFNDEF 0x0010 //macro definitions typedef struct define_s { char *name; //define name int flags; //define flags int builtin; // > 0 if builtin define int numparms; //number of define parameters token_t *parms; //define parameters token_t *tokens; //macro tokens (possibly containing parm tokens) struct define_s *next; //next defined macro in a list struct define_s *hashnext; //next define in the hash chain } define_t; //indents //used for conditional compilation directives: //#if, #else, #elif, #ifdef, #ifndef typedef struct indent_s { int type; //indent type int skip; //true if skipping current indent script_t *script; //script the indent was in struct indent_s *next; //next indent on the indent stack } indent_t; //source file typedef struct source_s { char filename[1024]; //file name of the script char includepath[1024]; //path to include files punctuation_t *punctuations; //punctuations to use script_t *scriptstack; //stack with scripts of the source token_t *tokens; //tokens to read first define_t *defines; //list with macro definitions define_t **definehash; //hash chain with defines indent_t *indentstack; //stack with indents int skip; // > 0 if skipping conditional code token_t token; //last read token } source_t; //read a token from the source int PC_ReadToken(source_t *source, token_t *token); //expect a certain token int PC_ExpectTokenString(source_t *source, char *string); //expect a certain token type int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token); //expect a token int PC_ExpectAnyToken(source_t *source, token_t *token); //returns true when the token is available int PC_CheckTokenString(source_t *source, char *string); //returns true an reads the token when a token with the given type is available int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token); //skip tokens until the given token string is read int PC_SkipUntilString(source_t *source, char *string); //unread the last token read from the script void PC_UnreadLastToken(source_t *source); //unread the given token void PC_UnreadToken(source_t *source, token_t *token); //read a token only if on the same line, lines are concatenated with a slash int PC_ReadLine(source_t *source, token_t *token); //returns true if there was a white space in front of the token int PC_WhiteSpaceBeforeToken(token_t *token); //add a define to the source int PC_AddDefine(source_t *source, char *string); //add a globals define that will be added to all opened sources int PC_AddGlobalDefine(char *string); //remove the given global define int PC_RemoveGlobalDefine(char *name); //remove all globals defines void PC_RemoveAllGlobalDefines(void); //add builtin defines void PC_AddBuiltinDefines(source_t *source); //set the source include path void PC_SetIncludePath(source_t *source, char *path); //set the punction set void PC_SetPunctuations(source_t *source, punctuation_t *p); //set the base folder to load files from void PC_SetBaseFolder(char *path); //load a source file source_t *LoadSourceFile(const char *filename); //load a source from memory source_t *LoadSourceMemory(char *ptr, int length, char *name); //free the given source void FreeSource(source_t *source); //print a source error void QDECL SourceError(source_t *source, char *str, ...); //print a source warning void QDECL SourceWarning(source_t *source, char *str, ...); #ifdef BSPC // some of BSPC source does include game/q_shared.h and some does not // we define pc_token_s pc_token_t if needed (yes, it's ugly) #ifndef __Q_SHARED_H #define MAX_TOKENLENGTH 1024 typedef struct pc_token_s { int type; int subtype; int intvalue; float floatvalue; char string[MAX_TOKENLENGTH]; } pc_token_t; #endif //!_Q_SHARED_H #endif //BSPC // int PC_LoadSourceHandle(const char *filename); int PC_FreeSourceHandle(int handle); int PC_ReadTokenHandle(int handle, pc_token_t *pc_token); int PC_SourceFileAndLine(int handle, char *filename, int *line); void PC_CheckOpenSourceHandles(void); ================================================ FILE: code/botlib/l_script.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_script.c * * desc: lexicographical parser * * $Archive: /MissionPack/code/botlib/l_script.c $ * *****************************************************************************/ //#define SCREWUP //#define BOTLIB //#define MEQCC //#define BSPC #ifdef SCREWUP #include #include #include #include #include #include "l_memory.h" #include "l_script.h" typedef enum {qfalse, qtrue} qboolean; #endif //SCREWUP #ifdef BOTLIB //include files for usage in the bot library #include "../game/q_shared.h" #include "../game/botlib.h" #include "be_interface.h" #include "l_script.h" #include "l_memory.h" #include "l_log.h" #include "l_libvar.h" #endif //BOTLIB #ifdef MEQCC //include files for usage in MrElusive's QuakeC Compiler #include "qcc.h" #include "l_script.h" #include "l_memory.h" #include "l_log.h" #define qtrue true #define qfalse false #endif //MEQCC #ifdef BSPC //include files for usage in the BSP Converter #include "../bspc/qbsp.h" #include "../bspc/l_log.h" #include "../bspc/l_mem.h" #define qtrue true #define qfalse false #endif //BSPC #define PUNCTABLE //longer punctuations first punctuation_t default_punctuations[] = { //binary operators {">>=",P_RSHIFT_ASSIGN, NULL}, {"<<=",P_LSHIFT_ASSIGN, NULL}, // {"...",P_PARMS, NULL}, //define merge operator {"##",P_PRECOMPMERGE, NULL}, //logic operators {"&&",P_LOGIC_AND, NULL}, {"||",P_LOGIC_OR, NULL}, {">=",P_LOGIC_GEQ, NULL}, {"<=",P_LOGIC_LEQ, NULL}, {"==",P_LOGIC_EQ, NULL}, {"!=",P_LOGIC_UNEQ, NULL}, //arithmatic operators {"*=",P_MUL_ASSIGN, NULL}, {"/=",P_DIV_ASSIGN, NULL}, {"%=",P_MOD_ASSIGN, NULL}, {"+=",P_ADD_ASSIGN, NULL}, {"-=",P_SUB_ASSIGN, NULL}, {"++",P_INC, NULL}, {"--",P_DEC, NULL}, //binary operators {"&=",P_BIN_AND_ASSIGN, NULL}, {"|=",P_BIN_OR_ASSIGN, NULL}, {"^=",P_BIN_XOR_ASSIGN, NULL}, {">>",P_RSHIFT, NULL}, {"<<",P_LSHIFT, NULL}, //reference operators {"->",P_POINTERREF, NULL}, //C++ {"::",P_CPP1, NULL}, {".*",P_CPP2, NULL}, //arithmatic operators {"*",P_MUL, NULL}, {"/",P_DIV, NULL}, {"%",P_MOD, NULL}, {"+",P_ADD, NULL}, {"-",P_SUB, NULL}, {"=",P_ASSIGN, NULL}, //binary operators {"&",P_BIN_AND, NULL}, {"|",P_BIN_OR, NULL}, {"^",P_BIN_XOR, NULL}, {"~",P_BIN_NOT, NULL}, //logic operators {"!",P_LOGIC_NOT, NULL}, {">",P_LOGIC_GREATER, NULL}, {"<",P_LOGIC_LESS, NULL}, //reference operator {".",P_REF, NULL}, //seperators {",",P_COMMA, NULL}, {";",P_SEMICOLON, NULL}, //label indication {":",P_COLON, NULL}, //if statement {"?",P_QUESTIONMARK, NULL}, //embracements {"(",P_PARENTHESESOPEN, NULL}, {")",P_PARENTHESESCLOSE, NULL}, {"{",P_BRACEOPEN, NULL}, {"}",P_BRACECLOSE, NULL}, {"[",P_SQBRACKETOPEN, NULL}, {"]",P_SQBRACKETCLOSE, NULL}, // {"\\",P_BACKSLASH, NULL}, //precompiler operator {"#",P_PRECOMP, NULL}, #ifdef DOLLAR {"$",P_DOLLAR, NULL}, #endif //DOLLAR {NULL, 0} }; #ifdef BSPC char basefolder[MAX_PATH]; #else char basefolder[MAX_QPATH]; #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PS_CreatePunctuationTable(script_t *script, punctuation_t *punctuations) { int i; punctuation_t *p, *lastp, *newp; //get memory for the table if (!script->punctuationtable) script->punctuationtable = (punctuation_t **) GetMemory(256 * sizeof(punctuation_t *)); Com_Memset(script->punctuationtable, 0, 256 * sizeof(punctuation_t *)); //add the punctuations in the list to the punctuation table for (i = 0; punctuations[i].p; i++) { newp = &punctuations[i]; lastp = NULL; //sort the punctuations in this table entry on length (longer punctuations first) for (p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next) { if (strlen(p->p) < strlen(newp->p)) { newp->next = p; if (lastp) lastp->next = newp; else script->punctuationtable[(unsigned int) newp->p[0]] = newp; break; } //end if lastp = p; } //end for if (!p) { newp->next = NULL; if (lastp) lastp->next = newp; else script->punctuationtable[(unsigned int) newp->p[0]] = newp; } //end if } //end for } //end of the function PS_CreatePunctuationTable //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *PunctuationFromNum(script_t *script, int num) { int i; for (i = 0; script->punctuations[i].p; i++) { if (script->punctuations[i].n == num) return script->punctuations[i].p; } //end for return "unkown punctuation"; } //end of the function PunctuationFromNum //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void QDECL ScriptError(script_t *script, char *str, ...) { char text[1024]; va_list ap; if (script->flags & SCFL_NOERRORS) return; va_start(ap, str); vsprintf(text, str, ap); va_end(ap); #ifdef BOTLIB botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", script->filename, script->line, text); #endif //BOTLIB #ifdef MEQCC printf("error: file %s, line %d: %s\n", script->filename, script->line, text); #endif //MEQCC #ifdef BSPC Log_Print("error: file %s, line %d: %s\n", script->filename, script->line, text); #endif //BSPC } //end of the function ScriptError //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void QDECL ScriptWarning(script_t *script, char *str, ...) { char text[1024]; va_list ap; if (script->flags & SCFL_NOWARNINGS) return; va_start(ap, str); vsprintf(text, str, ap); va_end(ap); #ifdef BOTLIB botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", script->filename, script->line, text); #endif //BOTLIB #ifdef MEQCC printf("warning: file %s, line %d: %s\n", script->filename, script->line, text); #endif //MEQCC #ifdef BSPC Log_Print("warning: file %s, line %d: %s\n", script->filename, script->line, text); #endif //BSPC } //end of the function ScriptWarning //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SetScriptPunctuations(script_t *script, punctuation_t *p) { #ifdef PUNCTABLE if (p) PS_CreatePunctuationTable(script, p); else PS_CreatePunctuationTable(script, default_punctuations); #endif //PUNCTABLE if (p) script->punctuations = p; else script->punctuations = default_punctuations; } //end of the function SetScriptPunctuations //============================================================================ // Reads spaces, tabs, C-like comments etc. // When a newline character is found the scripts line counter is increased. // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_ReadWhiteSpace(script_t *script) { while(1) { //skip white space while(*script->script_p <= ' ') { if (!*script->script_p) return 0; if (*script->script_p == '\n') script->line++; script->script_p++; } //end while //skip comments if (*script->script_p == '/') { //comments // if (*(script->script_p+1) == '/') { script->script_p++; do { script->script_p++; if (!*script->script_p) return 0; } //end do while(*script->script_p != '\n'); script->line++; script->script_p++; if (!*script->script_p) return 0; continue; } //end if //comments /* */ else if (*(script->script_p+1) == '*') { script->script_p++; do { script->script_p++; if (!*script->script_p) return 0; if (*script->script_p == '\n') script->line++; } //end do while(!(*script->script_p == '*' && *(script->script_p+1) == '/')); script->script_p++; if (!*script->script_p) return 0; script->script_p++; if (!*script->script_p) return 0; continue; } //end if } //end if break; } //end while return 1; } //end of the function PS_ReadWhiteSpace //============================================================================ // Reads an escape character. // // Parameter: script : script to read from // ch : place to store the read escape character // Returns: - // Changes Globals: - //============================================================================ int PS_ReadEscapeCharacter(script_t *script, char *ch) { int c, val, i; //step over the leading '\\' script->script_p++; //determine the escape character switch(*script->script_p) { case '\\': c = '\\'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'a': c = '\a'; break; case '\'': c = '\''; break; case '\"': c = '\"'; break; case '\?': c = '\?'; break; case 'x': { script->script_p++; for (i = 0, val = 0; ; i++, script->script_p++) { c = *script->script_p; if (c >= '0' && c <= '9') c = c - '0'; else if (c >= 'A' && c <= 'Z') c = c - 'A' + 10; else if (c >= 'a' && c <= 'z') c = c - 'a' + 10; else break; val = (val << 4) + c; } //end for script->script_p--; if (val > 0xFF) { ScriptWarning(script, "too large value in escape character"); val = 0xFF; } //end if c = val; break; } //end case default: //NOTE: decimal ASCII code, NOT octal { if (*script->script_p < '0' || *script->script_p > '9') ScriptError(script, "unknown escape char"); for (i = 0, val = 0; ; i++, script->script_p++) { c = *script->script_p; if (c >= '0' && c <= '9') c = c - '0'; else break; val = val * 10 + c; } //end for script->script_p--; if (val > 0xFF) { ScriptWarning(script, "too large value in escape character"); val = 0xFF; } //end if c = val; break; } //end default } //end switch //step over the escape character or the last digit of the number script->script_p++; //store the escape character *ch = c; //succesfully read escape character return 1; } //end of the function PS_ReadEscapeCharacter //============================================================================ // Reads C-like string. Escape characters are interpretted. // Quotes are included with the string. // Reads two strings with a white space between them as one string. // // Parameter: script : script to read from // token : buffer to store the string // Returns: qtrue when a string was read succesfully // Changes Globals: - //============================================================================ int PS_ReadString(script_t *script, token_t *token, int quote) { int len, tmpline; char *tmpscript_p; if (quote == '\"') token->type = TT_STRING; else token->type = TT_LITERAL; len = 0; //leading quote token->string[len++] = *script->script_p++; // while(1) { //minus 2 because trailing double quote and zero have to be appended if (len >= MAX_TOKEN - 2) { ScriptError(script, "string longer than MAX_TOKEN = %d", MAX_TOKEN); return 0; } //end if //if there is an escape character and //if escape characters inside a string are allowed if (*script->script_p == '\\' && !(script->flags & SCFL_NOSTRINGESCAPECHARS)) { if (!PS_ReadEscapeCharacter(script, &token->string[len])) { token->string[len] = 0; return 0; } //end if len++; } //end if //if a trailing quote else if (*script->script_p == quote) { //step over the double quote script->script_p++; //if white spaces in a string are not allowed if (script->flags & SCFL_NOSTRINGWHITESPACES) break; // tmpscript_p = script->script_p; tmpline = script->line; //read unusefull stuff between possible two following strings if (!PS_ReadWhiteSpace(script)) { script->script_p = tmpscript_p; script->line = tmpline; break; } //end if //if there's no leading double qoute if (*script->script_p != quote) { script->script_p = tmpscript_p; script->line = tmpline; break; } //end if //step over the new leading double quote script->script_p++; } //end if else { if (*script->script_p == '\0') { token->string[len] = 0; ScriptError(script, "missing trailing quote"); return 0; } //end if if (*script->script_p == '\n') { token->string[len] = 0; ScriptError(script, "newline inside string %s", token->string); return 0; } //end if token->string[len++] = *script->script_p++; } //end else } //end while //trailing quote token->string[len++] = quote; //end string with a zero token->string[len] = '\0'; //the sub type is the length of the string token->subtype = len; return 1; } //end of the function PS_ReadString //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_ReadName(script_t *script, token_t *token) { int len = 0; char c; token->type = TT_NAME; do { token->string[len++] = *script->script_p++; if (len >= MAX_TOKEN) { ScriptError(script, "name longer than MAX_TOKEN = %d", MAX_TOKEN); return 0; } //end if c = *script->script_p; } while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'); token->string[len] = '\0'; //the sub type is the length of the name token->subtype = len; return 1; } //end of the function PS_ReadName //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void NumberValue(char *string, int subtype, unsigned long int *intvalue, long double *floatvalue) { unsigned long int dotfound = 0; *intvalue = 0; *floatvalue = 0; //floating point number if (subtype & TT_FLOAT) { while(*string) { if (*string == '.') { if (dotfound) return; dotfound = 10; string++; } //end if if (dotfound) { *floatvalue = *floatvalue + (long double) (*string - '0') / (long double) dotfound; dotfound *= 10; } //end if else { *floatvalue = *floatvalue * 10.0 + (long double) (*string - '0'); } //end else string++; } //end while *intvalue = (unsigned long) *floatvalue; } //end if else if (subtype & TT_DECIMAL) { while(*string) *intvalue = *intvalue * 10 + (*string++ - '0'); *floatvalue = *intvalue; } //end else if else if (subtype & TT_HEX) { //step over the leading 0x or 0X string += 2; while(*string) { *intvalue <<= 4; if (*string >= 'a' && *string <= 'f') *intvalue += *string - 'a' + 10; else if (*string >= 'A' && *string <= 'F') *intvalue += *string - 'A' + 10; else *intvalue += *string - '0'; string++; } //end while *floatvalue = *intvalue; } //end else if else if (subtype & TT_OCTAL) { //step over the first zero string += 1; while(*string) *intvalue = (*intvalue << 3) + (*string++ - '0'); *floatvalue = *intvalue; } //end else if else if (subtype & TT_BINARY) { //step over the leading 0b or 0B string += 2; while(*string) *intvalue = (*intvalue << 1) + (*string++ - '0'); *floatvalue = *intvalue; } //end else if } //end of the function NumberValue //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_ReadNumber(script_t *script, token_t *token) { int len = 0, i; int octal, dot; char c; // unsigned long int intvalue = 0; // long double floatvalue = 0; token->type = TT_NUMBER; //check for a hexadecimal number if (*script->script_p == '0' && (*(script->script_p + 1) == 'x' || *(script->script_p + 1) == 'X')) { token->string[len++] = *script->script_p++; token->string[len++] = *script->script_p++; c = *script->script_p; //hexadecimal while((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'A')) { token->string[len++] = *script->script_p++; if (len >= MAX_TOKEN) { ScriptError(script, "hexadecimal number longer than MAX_TOKEN = %d", MAX_TOKEN); return 0; } //end if c = *script->script_p; } //end while token->subtype |= TT_HEX; } //end if #ifdef BINARYNUMBERS //check for a binary number else if (*script->script_p == '0' && (*(script->script_p + 1) == 'b' || *(script->script_p + 1) == 'B')) { token->string[len++] = *script->script_p++; token->string[len++] = *script->script_p++; c = *script->script_p; //binary while(c == '0' || c == '1') { token->string[len++] = *script->script_p++; if (len >= MAX_TOKEN) { ScriptError(script, "binary number longer than MAX_TOKEN = %d", MAX_TOKEN); return 0; } //end if c = *script->script_p; } //end while token->subtype |= TT_BINARY; } //end if #endif //BINARYNUMBERS else //decimal or octal integer or floating point number { octal = qfalse; dot = qfalse; if (*script->script_p == '0') octal = qtrue; while(1) { c = *script->script_p; if (c == '.') dot = qtrue; else if (c == '8' || c == '9') octal = qfalse; else if (c < '0' || c > '9') break; token->string[len++] = *script->script_p++; if (len >= MAX_TOKEN - 1) { ScriptError(script, "number longer than MAX_TOKEN = %d", MAX_TOKEN); return 0; } //end if } //end while if (octal) token->subtype |= TT_OCTAL; else token->subtype |= TT_DECIMAL; if (dot) token->subtype |= TT_FLOAT; } //end else for (i = 0; i < 2; i++) { c = *script->script_p; //check for a LONG number if ( (c == 'l' || c == 'L') // bk001204 - brackets && !(token->subtype & TT_LONG)) { script->script_p++; token->subtype |= TT_LONG; } //end if //check for an UNSIGNED number else if ( (c == 'u' || c == 'U') // bk001204 - brackets && !(token->subtype & (TT_UNSIGNED | TT_FLOAT))) { script->script_p++; token->subtype |= TT_UNSIGNED; } //end if } //end for token->string[len] = '\0'; #ifdef NUMBERVALUE NumberValue(token->string, token->subtype, &token->intvalue, &token->floatvalue); #endif //NUMBERVALUE if (!(token->subtype & TT_FLOAT)) token->subtype |= TT_INTEGER; return 1; } //end of the function PS_ReadNumber //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_ReadLiteral(script_t *script, token_t *token) { token->type = TT_LITERAL; //first quote token->string[0] = *script->script_p++; //check for end of file if (!*script->script_p) { ScriptError(script, "end of file before trailing \'"); return 0; } //end if //if it is an escape character if (*script->script_p == '\\') { if (!PS_ReadEscapeCharacter(script, &token->string[1])) return 0; } //end if else { token->string[1] = *script->script_p++; } //end else //check for trailing quote if (*script->script_p != '\'') { ScriptWarning(script, "too many characters in literal, ignored"); while(*script->script_p && *script->script_p != '\'' && *script->script_p != '\n') { script->script_p++; } //end while if (*script->script_p == '\'') script->script_p++; } //end if //store the trailing quote token->string[2] = *script->script_p++; //store trailing zero to end the string token->string[3] = '\0'; //the sub type is the integer literal value token->subtype = token->string[1]; // return 1; } //end of the function PS_ReadLiteral //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_ReadPunctuation(script_t *script, token_t *token) { int len; char *p; punctuation_t *punc; #ifdef PUNCTABLE for (punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next) { #else int i; for (i = 0; script->punctuations[i].p; i++) { punc = &script->punctuations[i]; #endif //PUNCTABLE p = punc->p; len = strlen(p); //if the script contains at least as much characters as the punctuation if (script->script_p + len <= script->end_p) { //if the script contains the punctuation if (!strncmp(script->script_p, p, len)) { strncpy(token->string, p, MAX_TOKEN); script->script_p += len; token->type = TT_PUNCTUATION; //sub type is the number of the punctuation token->subtype = punc->n; return 1; } //end if } //end if } //end for return 0; } //end of the function PS_ReadPunctuation //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_ReadPrimitive(script_t *script, token_t *token) { int len; len = 0; while(*script->script_p > ' ' && *script->script_p != ';') { if (len >= MAX_TOKEN) { ScriptError(script, "primitive token longer than MAX_TOKEN = %d", MAX_TOKEN); return 0; } //end if token->string[len++] = *script->script_p++; } //end while token->string[len] = 0; //copy the token into the script structure Com_Memcpy(&script->token, token, sizeof(token_t)); //primitive reading successfull return 1; } //end of the function PS_ReadPrimitive //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_ReadToken(script_t *script, token_t *token) { //if there is a token available (from UnreadToken) if (script->tokenavailable) { script->tokenavailable = 0; Com_Memcpy(token, &script->token, sizeof(token_t)); return 1; } //end if //save script pointer script->lastscript_p = script->script_p; //save line counter script->lastline = script->line; //clear the token stuff Com_Memset(token, 0, sizeof(token_t)); //start of the white space script->whitespace_p = script->script_p; token->whitespace_p = script->script_p; //read unusefull stuff if (!PS_ReadWhiteSpace(script)) return 0; //end of the white space script->endwhitespace_p = script->script_p; token->endwhitespace_p = script->script_p; //line the token is on token->line = script->line; //number of lines crossed before token token->linescrossed = script->line - script->lastline; //if there is a leading double quote if (*script->script_p == '\"') { if (!PS_ReadString(script, token, '\"')) return 0; } //end if //if an literal else if (*script->script_p == '\'') { //if (!PS_ReadLiteral(script, token)) return 0; if (!PS_ReadString(script, token, '\'')) return 0; } //end if //if there is a number else if ((*script->script_p >= '0' && *script->script_p <= '9') || (*script->script_p == '.' && (*(script->script_p + 1) >= '0' && *(script->script_p + 1) <= '9'))) { if (!PS_ReadNumber(script, token)) return 0; } //end if //if this is a primitive script else if (script->flags & SCFL_PRIMITIVE) { return PS_ReadPrimitive(script, token); } //end else if //if there is a name else if ((*script->script_p >= 'a' && *script->script_p <= 'z') || (*script->script_p >= 'A' && *script->script_p <= 'Z') || *script->script_p == '_') { if (!PS_ReadName(script, token)) return 0; } //end if //check for punctuations else if (!PS_ReadPunctuation(script, token)) { ScriptError(script, "can't read token"); return 0; } //end if //copy the token into the script structure Com_Memcpy(&script->token, token, sizeof(token_t)); //succesfully read a token return 1; } //end of the function PS_ReadToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_ExpectTokenString(script_t *script, char *string) { token_t token; if (!PS_ReadToken(script, &token)) { ScriptError(script, "couldn't find expected %s", string); return 0; } //end if if (strcmp(token.string, string)) { ScriptError(script, "expected %s, found %s", string, token.string); return 0; } //end if return 1; } //end of the function PS_ExpectToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token) { char str[MAX_TOKEN]; if (!PS_ReadToken(script, token)) { ScriptError(script, "couldn't read expected token"); return 0; } //end if if (token->type != type) { if (type == TT_STRING) strcpy(str, "string"); if (type == TT_LITERAL) strcpy(str, "literal"); if (type == TT_NUMBER) strcpy(str, "number"); if (type == TT_NAME) strcpy(str, "name"); if (type == TT_PUNCTUATION) strcpy(str, "punctuation"); ScriptError(script, "expected a %s, found %s", str, token->string); return 0; } //end if if (token->type == TT_NUMBER) { if ((token->subtype & subtype) != subtype) { if (subtype & TT_DECIMAL) strcpy(str, "decimal"); if (subtype & TT_HEX) strcpy(str, "hex"); if (subtype & TT_OCTAL) strcpy(str, "octal"); if (subtype & TT_BINARY) strcpy(str, "binary"); if (subtype & TT_LONG) strcat(str, " long"); if (subtype & TT_UNSIGNED) strcat(str, " unsigned"); if (subtype & TT_FLOAT) strcat(str, " float"); if (subtype & TT_INTEGER) strcat(str, " integer"); ScriptError(script, "expected %s, found %s", str, token->string); return 0; } //end if } //end if else if (token->type == TT_PUNCTUATION) { if (subtype < 0) { ScriptError(script, "BUG: wrong punctuation subtype"); return 0; } //end if if (token->subtype != subtype) { ScriptError(script, "expected %s, found %s", script->punctuations[subtype], token->string); return 0; } //end if } //end else if return 1; } //end of the function PS_ExpectTokenType //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_ExpectAnyToken(script_t *script, token_t *token) { if (!PS_ReadToken(script, token)) { ScriptError(script, "couldn't read expected token"); return 0; } //end if else { return 1; } //end else } //end of the function PS_ExpectAnyToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_CheckTokenString(script_t *script, char *string) { token_t tok; if (!PS_ReadToken(script, &tok)) return 0; //if the token is available if (!strcmp(tok.string, string)) return 1; //token not available script->script_p = script->lastscript_p; return 0; } //end of the function PS_CheckTokenString //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token) { token_t tok; if (!PS_ReadToken(script, &tok)) return 0; //if the type matches if (tok.type == type && (tok.subtype & subtype) == subtype) { Com_Memcpy(token, &tok, sizeof(token_t)); return 1; } //end if //token is not available script->script_p = script->lastscript_p; return 0; } //end of the function PS_CheckTokenType //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int PS_SkipUntilString(script_t *script, char *string) { token_t token; while(PS_ReadToken(script, &token)) { if (!strcmp(token.string, string)) return 1; } //end while return 0; } //end of the function PS_SkipUntilString //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PS_UnreadLastToken(script_t *script) { script->tokenavailable = 1; } //end of the function UnreadLastToken //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PS_UnreadToken(script_t *script, token_t *token) { Com_Memcpy(&script->token, token, sizeof(token_t)); script->tokenavailable = 1; } //end of the function UnreadToken //============================================================================ // returns the next character of the read white space, returns NULL if none // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ char PS_NextWhiteSpaceChar(script_t *script) { if (script->whitespace_p != script->endwhitespace_p) { return *script->whitespace_p++; } //end if else { return 0; } //end else } //end of the function PS_NextWhiteSpaceChar //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void StripDoubleQuotes(char *string) { if (*string == '\"') { strcpy(string, string+1); } //end if if (string[strlen(string)-1] == '\"') { string[strlen(string)-1] = '\0'; } //end if } //end of the function StripDoubleQuotes //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void StripSingleQuotes(char *string) { if (*string == '\'') { strcpy(string, string+1); } //end if if (string[strlen(string)-1] == '\'') { string[strlen(string)-1] = '\0'; } //end if } //end of the function StripSingleQuotes //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ long double ReadSignedFloat(script_t *script) { token_t token; long double sign = 1; PS_ExpectAnyToken(script, &token); if (!strcmp(token.string, "-")) { sign = -1; PS_ExpectTokenType(script, TT_NUMBER, 0, &token); } //end if else if (token.type != TT_NUMBER) { ScriptError(script, "expected float value, found %s\n", token.string); } //end else if return sign * token.floatvalue; } //end of the function ReadSignedFloat //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ signed long int ReadSignedInt(script_t *script) { token_t token; signed long int sign = 1; PS_ExpectAnyToken(script, &token); if (!strcmp(token.string, "-")) { sign = -1; PS_ExpectTokenType(script, TT_NUMBER, TT_INTEGER, &token); } //end if else if (token.type != TT_NUMBER || token.subtype == TT_FLOAT) { ScriptError(script, "expected integer value, found %s\n", token.string); } //end else if return sign * token.intvalue; } //end of the function ReadSignedInt //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void SetScriptFlags(script_t *script, int flags) { script->flags = flags; } //end of the function SetScriptFlags //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int GetScriptFlags(script_t *script) { return script->flags; } //end of the function GetScriptFlags //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void ResetScript(script_t *script) { //pointer in script buffer script->script_p = script->buffer; //pointer in script buffer before reading token script->lastscript_p = script->buffer; //begin of white space script->whitespace_p = NULL; //end of white space script->endwhitespace_p = NULL; //set if there's a token available in script->token script->tokenavailable = 0; // script->line = 1; script->lastline = 1; //clear the saved token Com_Memset(&script->token, 0, sizeof(token_t)); } //end of the function ResetScript //============================================================================ // returns true if at the end of the script // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int EndOfScript(script_t *script) { return script->script_p >= script->end_p; } //end of the function EndOfScript //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int NumLinesCrossed(script_t *script) { return script->line - script->lastline; } //end of the function NumLinesCrossed //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int ScriptSkipTo(script_t *script, char *value) { int len; char firstchar; firstchar = *value; len = strlen(value); do { if (!PS_ReadWhiteSpace(script)) return 0; if (*script->script_p == firstchar) { if (!strncmp(script->script_p, value, len)) { return 1; } //end if } //end if script->script_p++; } while(1); } //end of the function ScriptSkipTo #ifndef BOTLIB //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ int FileLength(FILE *fp) { int pos; int end; pos = ftell(fp); fseek(fp, 0, SEEK_END); end = ftell(fp); fseek(fp, pos, SEEK_SET); return end; } //end of the function FileLength #endif //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ script_t *LoadScriptFile(const char *filename) { #ifdef BOTLIB fileHandle_t fp; char pathname[MAX_QPATH]; #else FILE *fp; #endif int length; void *buffer; script_t *script; #ifdef BOTLIB if (strlen(basefolder)) Com_sprintf(pathname, sizeof(pathname), "%s/%s", basefolder, filename); else Com_sprintf(pathname, sizeof(pathname), "%s", filename); length = botimport.FS_FOpenFile( pathname, &fp, FS_READ ); if (!fp) return NULL; #else fp = fopen(filename, "rb"); if (!fp) return NULL; length = FileLength(fp); #endif buffer = GetClearedMemory(sizeof(script_t) + length + 1); script = (script_t *) buffer; Com_Memset(script, 0, sizeof(script_t)); strcpy(script->filename, filename); script->buffer = (char *) buffer + sizeof(script_t); script->buffer[length] = 0; script->length = length; //pointer in script buffer script->script_p = script->buffer; //pointer in script buffer before reading token script->lastscript_p = script->buffer; //pointer to end of script buffer script->end_p = &script->buffer[length]; //set if there's a token available in script->token script->tokenavailable = 0; // script->line = 1; script->lastline = 1; // SetScriptPunctuations(script, NULL); // #ifdef BOTLIB botimport.FS_Read(script->buffer, length, fp); botimport.FS_FCloseFile(fp); #else if (fread(script->buffer, length, 1, fp) != 1) { FreeMemory(buffer); script = NULL; } //end if fclose(fp); #endif // script->length = COM_Compress(script->buffer); return script; } //end of the function LoadScriptFile //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ script_t *LoadScriptMemory(char *ptr, int length, char *name) { void *buffer; script_t *script; buffer = GetClearedMemory(sizeof(script_t) + length + 1); script = (script_t *) buffer; Com_Memset(script, 0, sizeof(script_t)); strcpy(script->filename, name); script->buffer = (char *) buffer + sizeof(script_t); script->buffer[length] = 0; script->length = length; //pointer in script buffer script->script_p = script->buffer; //pointer in script buffer before reading token script->lastscript_p = script->buffer; //pointer to end of script buffer script->end_p = &script->buffer[length]; //set if there's a token available in script->token script->tokenavailable = 0; // script->line = 1; script->lastline = 1; // SetScriptPunctuations(script, NULL); // Com_Memcpy(script->buffer, ptr, length); // return script; } //end of the function LoadScriptMemory //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void FreeScript(script_t *script) { #ifdef PUNCTABLE if (script->punctuationtable) FreeMemory(script->punctuationtable); #endif //PUNCTABLE FreeMemory(script); } //end of the function FreeScript //============================================================================ // // Parameter: - // Returns: - // Changes Globals: - //============================================================================ void PS_SetBaseFolder(char *path) { #ifdef BSPC sprintf(basefolder, path); #else Com_sprintf(basefolder, sizeof(basefolder), path); #endif } //end of the function PS_SetBaseFolder ================================================ FILE: code/botlib/l_script.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_script.h * * desc: lexicographical parser * * $Archive: /source/code/botlib/l_script.h $ * *****************************************************************************/ //undef if binary numbers of the form 0b... or 0B... are not allowed #define BINARYNUMBERS //undef if not using the token.intvalue and token.floatvalue #define NUMBERVALUE //use dollar sign also as punctuation #define DOLLAR //maximum token length #define MAX_TOKEN 1024 #if defined(BSPC) && !defined(QDECL) #define QDECL #endif //script flags #define SCFL_NOERRORS 0x0001 #define SCFL_NOWARNINGS 0x0002 #define SCFL_NOSTRINGWHITESPACES 0x0004 #define SCFL_NOSTRINGESCAPECHARS 0x0008 #define SCFL_PRIMITIVE 0x0010 #define SCFL_NOBINARYNUMBERS 0x0020 #define SCFL_NONUMBERVALUES 0x0040 //token types #define TT_STRING 1 // string #define TT_LITERAL 2 // literal #define TT_NUMBER 3 // number #define TT_NAME 4 // name #define TT_PUNCTUATION 5 // punctuation //string sub type //--------------- // the length of the string //literal sub type //---------------- // the ASCII code of the literal //number sub type //--------------- #define TT_DECIMAL 0x0008 // decimal number #define TT_HEX 0x0100 // hexadecimal number #define TT_OCTAL 0x0200 // octal number #ifdef BINARYNUMBERS #define TT_BINARY 0x0400 // binary number #endif //BINARYNUMBERS #define TT_FLOAT 0x0800 // floating point number #define TT_INTEGER 0x1000 // integer number #define TT_LONG 0x2000 // long number #define TT_UNSIGNED 0x4000 // unsigned number //punctuation sub type //-------------------- #define P_RSHIFT_ASSIGN 1 #define P_LSHIFT_ASSIGN 2 #define P_PARMS 3 #define P_PRECOMPMERGE 4 #define P_LOGIC_AND 5 #define P_LOGIC_OR 6 #define P_LOGIC_GEQ 7 #define P_LOGIC_LEQ 8 #define P_LOGIC_EQ 9 #define P_LOGIC_UNEQ 10 #define P_MUL_ASSIGN 11 #define P_DIV_ASSIGN 12 #define P_MOD_ASSIGN 13 #define P_ADD_ASSIGN 14 #define P_SUB_ASSIGN 15 #define P_INC 16 #define P_DEC 17 #define P_BIN_AND_ASSIGN 18 #define P_BIN_OR_ASSIGN 19 #define P_BIN_XOR_ASSIGN 20 #define P_RSHIFT 21 #define P_LSHIFT 22 #define P_POINTERREF 23 #define P_CPP1 24 #define P_CPP2 25 #define P_MUL 26 #define P_DIV 27 #define P_MOD 28 #define P_ADD 29 #define P_SUB 30 #define P_ASSIGN 31 #define P_BIN_AND 32 #define P_BIN_OR 33 #define P_BIN_XOR 34 #define P_BIN_NOT 35 #define P_LOGIC_NOT 36 #define P_LOGIC_GREATER 37 #define P_LOGIC_LESS 38 #define P_REF 39 #define P_COMMA 40 #define P_SEMICOLON 41 #define P_COLON 42 #define P_QUESTIONMARK 43 #define P_PARENTHESESOPEN 44 #define P_PARENTHESESCLOSE 45 #define P_BRACEOPEN 46 #define P_BRACECLOSE 47 #define P_SQBRACKETOPEN 48 #define P_SQBRACKETCLOSE 49 #define P_BACKSLASH 50 #define P_PRECOMP 51 #define P_DOLLAR 52 //name sub type //------------- // the length of the name //punctuation typedef struct punctuation_s { char *p; //punctuation character(s) int n; //punctuation indication struct punctuation_s *next; //next punctuation } punctuation_t; //token typedef struct token_s { char string[MAX_TOKEN]; //available token int type; //last read token type int subtype; //last read token sub type #ifdef NUMBERVALUE unsigned long int intvalue; //integer value long double floatvalue; //floating point value #endif //NUMBERVALUE char *whitespace_p; //start of white space before token char *endwhitespace_p; //start of white space before token int line; //line the token was on int linescrossed; //lines crossed in white space struct token_s *next; //next token in chain } token_t; //script file typedef struct script_s { char filename[1024]; //file name of the script char *buffer; //buffer containing the script char *script_p; //current pointer in the script char *end_p; //pointer to the end of the script char *lastscript_p; //script pointer before reading token char *whitespace_p; //begin of the white space char *endwhitespace_p; //end of the white space int length; //length of the script in bytes int line; //current line in script int lastline; //line before reading token int tokenavailable; //set by UnreadLastToken int flags; //several script flags punctuation_t *punctuations; //the punctuations used in the script punctuation_t **punctuationtable; token_t token; //available token struct script_s *next; //next script in a chain } script_t; //read a token from the script int PS_ReadToken(script_t *script, token_t *token); //expect a certain token int PS_ExpectTokenString(script_t *script, char *string); //expect a certain token type int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token); //expect a token int PS_ExpectAnyToken(script_t *script, token_t *token); //returns true when the token is available int PS_CheckTokenString(script_t *script, char *string); //returns true an reads the token when a token with the given type is available int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token); //skip tokens until the given token string is read int PS_SkipUntilString(script_t *script, char *string); //unread the last token read from the script void PS_UnreadLastToken(script_t *script); //unread the given token void PS_UnreadToken(script_t *script, token_t *token); //returns the next character of the read white space, returns NULL if none char PS_NextWhiteSpaceChar(script_t *script); //remove any leading and trailing double quotes from the token void StripDoubleQuotes(char *string); //remove any leading and trailing single quotes from the token void StripSingleQuotes(char *string); //read a possible signed integer signed long int ReadSignedInt(script_t *script); //read a possible signed floating point number long double ReadSignedFloat(script_t *script); //set an array with punctuations, NULL restores default C/C++ set void SetScriptPunctuations(script_t *script, punctuation_t *p); //set script flags void SetScriptFlags(script_t *script, int flags); //get script flags int GetScriptFlags(script_t *script); //reset a script void ResetScript(script_t *script); //returns true if at the end of the script int EndOfScript(script_t *script); //returns a pointer to the punctuation with the given number char *PunctuationFromNum(script_t *script, int num); //load a script from the given file at the given offset with the given length script_t *LoadScriptFile(const char *filename); //load a script from the given memory with the given length script_t *LoadScriptMemory(char *ptr, int length, char *name); //free a script void FreeScript(script_t *script); //set the base folder to load files from void PS_SetBaseFolder(char *path); //print a script error with filename and line number void QDECL ScriptError(script_t *script, char *str, ...); //print a script warning with filename and line number void QDECL ScriptWarning(script_t *script, char *str, ...); ================================================ FILE: code/botlib/l_struct.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_struct.c * * desc: structure reading / writing * * $Archive: /MissionPack/CODE/botlib/l_struct.c $ * *****************************************************************************/ #ifdef BOTLIB #include "../game/q_shared.h" #include "../game/botlib.h" //for the include of be_interface.h #include "l_script.h" #include "l_precomp.h" #include "l_struct.h" #include "l_utils.h" #include "be_interface.h" #endif //BOTLIB #ifdef BSPC //include files for usage in the BSP Converter #include "../bspc/qbsp.h" #include "../bspc/l_log.h" #include "../bspc/l_mem.h" #include "l_precomp.h" #include "l_struct.h" #define qtrue true #define qfalse false #endif //BSPC //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== fielddef_t *FindField(fielddef_t *defs, char *name) { int i; for (i = 0; defs[i].name; i++) { if (!strcmp(defs[i].name, name)) return &defs[i]; } //end for return NULL; } //end of the function FindField //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean ReadNumber(source_t *source, fielddef_t *fd, void *p) { token_t token; int negative = qfalse; long int intval, intmin = 0, intmax = 0; double floatval; if (!PC_ExpectAnyToken(source, &token)) return 0; //check for minus sign if (token.type == TT_PUNCTUATION) { if (fd->type & FT_UNSIGNED) { SourceError(source, "expected unsigned value, found %s", token.string); return 0; } //end if //if not a minus sign if (strcmp(token.string, "-")) { SourceError(source, "unexpected punctuation %s", token.string); return 0; } //end if negative = qtrue; //read the number if (!PC_ExpectAnyToken(source, &token)) return 0; } //end if //check if it is a number if (token.type != TT_NUMBER) { SourceError(source, "expected number, found %s", token.string); return 0; } //end if //check for a float value if (token.subtype & TT_FLOAT) { if ((fd->type & FT_TYPE) != FT_FLOAT) { SourceError(source, "unexpected float"); return 0; } //end if floatval = token.floatvalue; if (negative) floatval = -floatval; if (fd->type & FT_BOUNDED) { if (floatval < fd->floatmin || floatval > fd->floatmax) { SourceError(source, "float out of range [%f, %f]", fd->floatmin, fd->floatmax); return 0; } //end if } //end if *(float *) p = (float) floatval; return 1; } //end if // intval = token.intvalue; if (negative) intval = -intval; //check bounds if ((fd->type & FT_TYPE) == FT_CHAR) { if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 255;} else {intmin = -128; intmax = 127;} } //end if if ((fd->type & FT_TYPE) == FT_INT) { if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 65535;} else {intmin = -32768; intmax = 32767;} } //end else if if ((fd->type & FT_TYPE) == FT_CHAR || (fd->type & FT_TYPE) == FT_INT) { if (fd->type & FT_BOUNDED) { intmin = Maximum(intmin, fd->floatmin); intmax = Minimum(intmax, fd->floatmax); } //end if if (intval < intmin || intval > intmax) { SourceError(source, "value %d out of range [%d, %d]", intval, intmin, intmax); return 0; } //end if } //end if else if ((fd->type & FT_TYPE) == FT_FLOAT) { if (fd->type & FT_BOUNDED) { if (intval < fd->floatmin || intval > fd->floatmax) { SourceError(source, "value %d out of range [%f, %f]", intval, fd->floatmin, fd->floatmax); return 0; } //end if } //end if } //end else if //store the value if ((fd->type & FT_TYPE) == FT_CHAR) { if (fd->type & FT_UNSIGNED) *(unsigned char *) p = (unsigned char) intval; else *(char *) p = (char) intval; } //end if else if ((fd->type & FT_TYPE) == FT_INT) { if (fd->type & FT_UNSIGNED) *(unsigned int *) p = (unsigned int) intval; else *(int *) p = (int) intval; } //end else else if ((fd->type & FT_TYPE) == FT_FLOAT) { *(float *) p = (float) intval; } //end else return 1; } //end of the function ReadNumber //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean ReadChar(source_t *source, fielddef_t *fd, void *p) { token_t token; if (!PC_ExpectAnyToken(source, &token)) return 0; //take literals into account if (token.type == TT_LITERAL) { StripSingleQuotes(token.string); *(char *) p = token.string[0]; } //end if else { PC_UnreadLastToken(source); if (!ReadNumber(source, fd, p)) return 0; } //end if return 1; } //end of the function ReadChar //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int ReadString(source_t *source, fielddef_t *fd, void *p) { token_t token; if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) return 0; //remove the double quotes StripDoubleQuotes(token.string); //copy the string strncpy((char *) p, token.string, MAX_STRINGFIELD); //make sure the string is closed with a zero ((char *)p)[MAX_STRINGFIELD-1] = '\0'; // return 1; } //end of the function ReadString //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int ReadStructure(source_t *source, structdef_t *def, char *structure) { token_t token; fielddef_t *fd; void *p; int num; if (!PC_ExpectTokenString(source, "{")) return 0; while(1) { if (!PC_ExpectAnyToken(source, &token)) return qfalse; //if end of structure if (!strcmp(token.string, "}")) break; //find the field with the name fd = FindField(def->fields, token.string); if (!fd) { SourceError(source, "unknown structure field %s", token.string); return qfalse; } //end if if (fd->type & FT_ARRAY) { num = fd->maxarray; if (!PC_ExpectTokenString(source, "{")) return qfalse; } //end if else { num = 1; } //end else p = (void *)(structure + fd->offset); while (num-- > 0) { if (fd->type & FT_ARRAY) { if (PC_CheckTokenString(source, "}")) break; } //end if switch(fd->type & FT_TYPE) { case FT_CHAR: { if (!ReadChar(source, fd, p)) return qfalse; p = (char *) p + sizeof(char); break; } //end case case FT_INT: { if (!ReadNumber(source, fd, p)) return qfalse; p = (char *) p + sizeof(int); break; } //end case case FT_FLOAT: { if (!ReadNumber(source, fd, p)) return qfalse; p = (char *) p + sizeof(float); break; } //end case case FT_STRING: { if (!ReadString(source, fd, p)) return qfalse; p = (char *) p + MAX_STRINGFIELD; break; } //end case case FT_STRUCT: { if (!fd->substruct) { SourceError(source, "BUG: no sub structure defined"); return qfalse; } //end if ReadStructure(source, fd->substruct, (char *) p); p = (char *) p + fd->substruct->size; break; } //end case } //end switch if (fd->type & FT_ARRAY) { if (!PC_ExpectAnyToken(source, &token)) return qfalse; if (!strcmp(token.string, "}")) break; if (strcmp(token.string, ",")) { SourceError(source, "expected a comma, found %s", token.string); return qfalse; } //end if } //end if } //end while } //end while return qtrue; } //end of the function ReadStructure //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int WriteIndent(FILE *fp, int indent) { while(indent-- > 0) { if (fprintf(fp, "\t") < 0) return qfalse; } //end while return qtrue; } //end of the function WriteIndent //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int WriteFloat(FILE *fp, float value) { char buf[128]; int l; sprintf(buf, "%f", value); l = strlen(buf); //strip any trailing zeros while(l-- > 1) { if (buf[l] != '0' && buf[l] != '.') break; if (buf[l] == '.') { buf[l] = 0; break; } //end if buf[l] = 0; } //end while //write the float to file if (fprintf(fp, "%s", buf) < 0) return 0; return 1; } //end of the function WriteFloat //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int WriteStructWithIndent(FILE *fp, structdef_t *def, char *structure, int indent) { int i, num; void *p; fielddef_t *fd; if (!WriteIndent(fp, indent)) return qfalse; if (fprintf(fp, "{\r\n") < 0) return qfalse; indent++; for (i = 0; def->fields[i].name; i++) { fd = &def->fields[i]; if (!WriteIndent(fp, indent)) return qfalse; if (fprintf(fp, "%s\t", fd->name) < 0) return qfalse; p = (void *)(structure + fd->offset); if (fd->type & FT_ARRAY) { num = fd->maxarray; if (fprintf(fp, "{") < 0) return qfalse; } //end if else { num = 1; } //end else while(num-- > 0) { switch(fd->type & FT_TYPE) { case FT_CHAR: { if (fprintf(fp, "%d", *(char *) p) < 0) return qfalse; p = (char *) p + sizeof(char); break; } //end case case FT_INT: { if (fprintf(fp, "%d", *(int *) p) < 0) return qfalse; p = (char *) p + sizeof(int); break; } //end case case FT_FLOAT: { if (!WriteFloat(fp, *(float *)p)) return qfalse; p = (char *) p + sizeof(float); break; } //end case case FT_STRING: { if (fprintf(fp, "\"%s\"", (char *) p) < 0) return qfalse; p = (char *) p + MAX_STRINGFIELD; break; } //end case case FT_STRUCT: { if (!WriteStructWithIndent(fp, fd->substruct, structure, indent)) return qfalse; p = (char *) p + fd->substruct->size; break; } //end case } //end switch if (fd->type & FT_ARRAY) { if (num > 0) { if (fprintf(fp, ",") < 0) return qfalse; } //end if else { if (fprintf(fp, "}") < 0) return qfalse; } //end else } //end if } //end while if (fprintf(fp, "\r\n") < 0) return qfalse; } //end for indent--; if (!WriteIndent(fp, indent)) return qfalse; if (fprintf(fp, "}\r\n") < 0) return qfalse; return qtrue; } //end of the function WriteStructWithIndent //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int WriteStructure(FILE *fp, structdef_t *def, char *structure) { return WriteStructWithIndent(fp, def, structure, 0); } //end of the function WriteStructure ================================================ FILE: code/botlib/l_struct.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_struct.h * * desc: structure reading/writing * * $Archive: /source/code/botlib/l_struct.h $ * *****************************************************************************/ #define MAX_STRINGFIELD 80 //field types #define FT_CHAR 1 // char #define FT_INT 2 // int #define FT_FLOAT 3 // float #define FT_STRING 4 // char [MAX_STRINGFIELD] #define FT_STRUCT 6 // struct (sub structure) //type only mask #define FT_TYPE 0x00FF // only type, clear subtype //sub types #define FT_ARRAY 0x0100 // array of type #define FT_BOUNDED 0x0200 // bounded value #define FT_UNSIGNED 0x0400 //structure field definition typedef struct fielddef_s { char *name; //name of the field int offset; //offset in the structure int type; //type of the field //type specific fields int maxarray; //maximum array size float floatmin, floatmax; //float min and max struct structdef_s *substruct; //sub structure } fielddef_t; //structure definition typedef struct structdef_s { int size; fielddef_t *fields; } structdef_t; //read a structure from a script int ReadStructure(source_t *source, structdef_t *def, char *structure); //write a structure to a file int WriteStructure(FILE *fp, structdef_t *def, char *structure); //writes indents int WriteIndent(FILE *fp, int indent); //writes a float without traling zeros int WriteFloat(FILE *fp, float value); ================================================ FILE: code/botlib/l_utils.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: l_util.h * * desc: utils * * $Archive: /source/code/botlib/l_util.h $ * *****************************************************************************/ #define Vector2Angles(v,a) vectoangles(v,a) #define MAX_PATH MAX_QPATH #define Maximum(x,y) (x > y ? x : y) #define Minimum(x,y) (x < y ? x : y) ================================================ FILE: code/botlib/lcc.mak ================================================ # # Makefile for Gladiator Bot library: gladiator.dll # Intended for LCC-Win32 # CC=lcc CFLAGS=-DC_ONLY -o OBJS= be_aas_bspq2.obj \ be_aas_bsphl.obj \ be_aas_cluster.obj \ be_aas_debug.obj \ be_aas_entity.obj \ be_aas_file.obj \ be_aas_light.obj \ be_aas_main.obj \ be_aas_move.obj \ be_aas_optimize.obj \ be_aas_reach.obj \ be_aas_route.obj \ be_aas_routealt.obj \ be_aas_sample.obj \ be_aas_sound.obj \ be_ai2_dm.obj \ be_ai2_dmnet.obj \ be_ai2_main.obj \ be_ai_char.obj \ be_ai_chat.obj \ be_ai_goal.obj \ be_ai_load.obj \ be_ai_move.obj \ be_ai_weap.obj \ be_ai_weight.obj \ be_ea.obj \ be_interface.obj \ l_crc.obj \ l_libvar.obj \ l_log.obj \ l_memory.obj \ l_precomp.obj \ l_script.obj \ l_struct.obj \ l_utils.obj \ q_shared.obj all: gladiator.dll gladiator.dll: $(OBJS) lcclnk -dll -entry GetBotAPI *.obj botlib.def -o gladiator.dll clean: del *.obj gladiator.dll %.obj: %.c $(CC) $(CFLAGS) $< ================================================ FILE: code/botlib/linux-i386.mak ================================================ # # Makefile for Gladiator Bot library: gladiator.so # Intended for gcc/Linux # ARCH=i386 CC=gcc BASE_CFLAGS=-Dstricmp=strcasecmp #use these cflags to optimize it CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \ -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ -malign-jumps=2 -malign-functions=2 #use these when debugging #CFLAGS=$(BASE_CFLAGS) -g LDFLAGS=-ldl -lm SHLIBEXT=so SHLIBCFLAGS=-fPIC SHLIBLDFLAGS=-shared DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< ############################################################################# # SETUP AND BUILD # GLADIATOR BOT ############################################################################# .c.o: $(DO_CC) GAME_OBJS = \ be_aas_bsphl.o\ be_aas_bspq2.o\ be_aas_cluster.o\ be_aas_debug.o\ be_aas_entity.o\ be_aas_file.o\ be_aas_light.o\ be_aas_main.o\ be_aas_move.o\ be_aas_optimize.o\ be_aas_reach.o\ be_aas_route.o\ be_aas_routealt.o\ be_aas_sample.o\ be_aas_sound.o\ be_ai2_dmq2.o\ be_ai2_dmhl.o\ be_ai2_dmnet.o\ be_ai2_main.o\ be_ai_char.o\ be_ai_chat.o\ be_ai_goal.o\ be_ai_load.o\ be_ai_move.o\ be_ai_weap.o\ be_ai_weight.o\ be_ea.o\ be_interface.o\ l_crc.o\ l_libvar.o\ l_log.o\ l_memory.o\ l_precomp.o\ l_script.o\ l_struct.o\ l_utils.o\ q_shared.o glad$(ARCH).$(SHLIBEXT) : $(GAME_OBJS) $(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(GAME_OBJS) ############################################################################# # MISC ############################################################################# clean: -rm -f $(GAME_OBJS) depend: gcc -MM $(GAME_OBJS:.o=.c) install: cp gladiator.so .. # # From "make depend" # ================================================ FILE: code/bspc/Conscript ================================================ # bspc compile Import qw( BSPC_BASE_CFLAGS BUILD_DIR INSTALL_DIR CC CXX LINK ); @BSPC_FILES = qw( aas_areamerging.c aas_cfg.c aas_create.c aas_edgemelting.c aas_facemerging.c aas_file.c aas_gsubdiv.c aas_map.c aas_prunenodes.c aas_store.c be_aas_bspc.c ../botlib/be_aas_bspq3.c ../botlib/be_aas_cluster.c ../botlib/be_aas_move.c ../botlib/be_aas_optimize.c ../botlib/be_aas_reach.c ../botlib/be_aas_sample.c brushbsp.c bspc.c ../qcommon/cm_load.c ../qcommon/cm_patch.c ../qcommon/cm_test.c ../qcommon/cm_trace.c csg.c glfile.c l_bsp_ent.c l_bsp_hl.c l_bsp_q1.c l_bsp_q2.c l_bsp_q3.c l_bsp_sin.c l_cmd.c ../botlib/l_libvar.c l_log.c l_math.c l_mem.c l_poly.c ../botlib/l_precomp.c l_qfiles.c ../botlib/l_script.c ../botlib/l_struct.c l_threads.c l_utils.c leakfile.c map.c map_hl.c map_q1.c map_q2.c map_q3.c map_sin.c ../qcommon/md4.c nodraw.c portals.c textures.c tree.c ../qcommon/unzip.c ); $BSPC_REF = \@BSPC_FILES; $env = new cons( CC => $CC, CXX => $CXX, LINK => $LINK, CFLAGS => $BSPC_BASE_CFLAGS, LIBS => '-ldl -lm -lpthread' ); Program $env 'bspc', @$BSPC_REF; # this should install to Q3 or something? Install $env $INSTALL_DIR, 'bspc'; ================================================ FILE: code/bspc/Makefile ================================================ # # Makefile for the BSPC tool for the Gladiator Bot # Intended for gcc/Linux # # TTimo 5/15/2001 # some cleanup .. only used on i386 for GtkRadiant setups AFAIK .. removing the i386 tag # TODO: the intermediate object files should go into their own directory # specially for ../botlib and ../qcommon, the compilation flags on those might not be what you expect #ARCH=i386 CC=gcc BASE_CFLAGS=-Dstricmp=strcasecmp #use these cflags to optimize it CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \ -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ -malign-jumps=2 -malign-functions=2 -DLINUX -DBSPC #use these when debugging #CFLAGS=$(BASE_CFLAGS) -g LDFLAGS=-ldl -lm -lpthread DO_CC=$(CC) $(CFLAGS) -o $@ -c $< ############################################################################# # SETUP AND BUILD BSPC ############################################################################# .c.o: $(DO_CC) GAME_OBJS = \ _files.o\ aas_areamerging.o\ aas_cfg.o\ aas_create.o\ aas_edgemelting.o\ aas_facemerging.o\ aas_file.o\ aas_gsubdiv.o\ aas_map.o\ aas_prunenodes.o\ aas_store.o\ be_aas_bspc.o\ ../botlib/be_aas_bspq3.o\ ../botlib/be_aas_cluster.o\ ../botlib/be_aas_move.o\ ../botlib/be_aas_optimize.o\ ../botlib/be_aas_reach.o\ ../botlib/be_aas_sample.o\ brushbsp.o\ bspc.o\ ../qcommon/cm_load.o\ ../qcommon/cm_patch.o\ ../qcommon/cm_test.o\ ../qcommon/cm_trace.o\ csg.o\ glfile.o\ l_bsp_ent.o\ l_bsp_hl.o\ l_bsp_q1.o\ l_bsp_q2.o\ l_bsp_q3.o\ l_bsp_sin.o\ l_cmd.o\ ../botlib/l_libvar.o\ l_log.o\ l_math.o\ l_mem.o\ l_poly.o\ ../botlib/l_precomp.o\ l_qfiles.o\ ../botlib/l_script.o\ ../botlib/l_struct.o\ l_threads.o\ l_utils.o\ leakfile.o\ map.o\ map_hl.o\ map_q1.o\ map_q2.o\ map_q3.o\ map_sin.o\ ../qcommon/md4.o\ nodraw.o\ portals.o\ textures.o\ tree.o\ ../qcommon/unzip.o #tetrahedron.o bspc : $(GAME_OBJS) $(CC) $(CFLAGS) -o $@ $(GAME_OBJS) $(LDFLAGS) strip $@ ############################################################################# # MISC ############################################################################# clean: -rm -f $(GAME_OBJS) depend: gcc -MM $(GAME_OBJS:.o=.c) #install: # cp bspci386 .. # # From "make depend" # ================================================ FILE: code/bspc/_files.c ================================================ //=========================================================================== // // Name: _files.c // Function: // Programmer: Mr Elusive // Last update: 1999-12-02 // Tab Size: 4 //=========================================================================== /* aas_areamerging.c //AAS area merging aas_cfg.c //AAS configuration for different games aas_create.c //AAS creating aas_edgemelting.c //AAS edge melting aas_facemerging.c //AAS face merging aas_file.c //AAS file writing aas_gsubdiv.c //AAS gravitational and ladder subdivision aas_map.c //AAS map brush creation aas_prunenodes.c //AAS node pruning aas_store.c //AAS file storing map.c //map file loading and writing map_hl.c //Half-Life map loading map_q1.c //Quake1 map loading map_q2.c //Quake2 map loading map_q3.c //Quake3 map loading map_sin.c //Sin map loading tree.c //BSP tree management + node pruning (*) brushbsp.c //brush bsp creation (*) portals.c //BSP portal creation and leaf filling (*) csg.c //Constructive Solid Geometry brush chopping (*) leakfile.c //leak file writing (*) textures.c //Quake2 BSP textures (*) l_bsp_ent.c //BSP entity parsing l_bsp_hl.c //Half-Life BSP loading and writing l_bsp_q1.c //Quake1 BSP loading and writing l_bsp_q2.c //Quake2 BSP loading and writing l_bsp_q3.c //Quake2 BSP loading and writing l_bsp_sin.c //Sin BSP loading and writing l_cmd.c //cmd library l_log.c //log file library l_math.c //math library l_mem.c //memory management library l_poly.c //polygon (winding) library l_script.c //script file parsing library l_threads.c //multi-threading library l_utils.c //utility library l_qfiles.c //loading of quake files gldraw.c //GL drawing (*) glfile.c //GL file writing (*) nodraw.c //no draw module (*) bspc.c //BSPC Win32 console version winbspc.c //WinBSPC Win32 GUI version win32_terminal.c //Win32 terminal output win32_qfiles.c //Win32 game file management (also .pak .sin) win32_font.c //Win32 fonts win32_folder.c //Win32 folder dialogs */ ================================================ FILE: code/bspc/aas_areamerging.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "../botlib/aasfile.h" #include "aas_create.h" #include "aas_store.h" #define CONVEX_EPSILON 0.3 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_node_t *AAS_RefreshMergedTree_r(tmp_node_t *tmpnode) { tmp_area_t *tmparea; //if this is a solid leaf if (!tmpnode) return NULL; //if this is an area leaf if (tmpnode->tmparea) { tmparea = tmpnode->tmparea; while(tmparea->mergedarea) tmparea = tmparea->mergedarea; tmpnode->tmparea = tmparea; return tmpnode; } //end if //do the children recursively tmpnode->children[0] = AAS_RefreshMergedTree_r(tmpnode->children[0]); tmpnode->children[1] = AAS_RefreshMergedTree_r(tmpnode->children[1]); return tmpnode; } //end of the function AAS_RefreshMergedTree_r //=========================================================================== // returns true if the two given faces would create a non-convex area at // the given sides, otherwise false is returned // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int NonConvex(tmp_face_t *face1, tmp_face_t *face2, int side1, int side2) { int i; winding_t *w1, *w2; plane_t *plane1, *plane2; w1 = face1->winding; w2 = face2->winding; plane1 = &mapplanes[face1->planenum ^ side1]; plane2 = &mapplanes[face2->planenum ^ side2]; //check if one of the points of face1 is at the back of the plane of face2 for (i = 0; i < w1->numpoints; i++) { if (DotProduct(plane2->normal, w1->p[i]) - plane2->dist < -CONVEX_EPSILON) return true; } //end for //check if one of the points of face2 is at the back of the plane of face1 for (i = 0; i < w2->numpoints; i++) { if (DotProduct(plane1->normal, w2->p[i]) - plane1->dist < -CONVEX_EPSILON) return true; } //end for return false; } //end of the function NonConvex //=========================================================================== // try to merge the areas at both sides of the given face // // Parameter: seperatingface : face that seperates two areas // Returns: - // Changes Globals: - //=========================================================================== int AAS_TryMergeFaceAreas(tmp_face_t *seperatingface) { int side1, side2, area1faceflags, area2faceflags; tmp_area_t *tmparea1, *tmparea2, *newarea; tmp_face_t *face1, *face2, *nextface1, *nextface2; tmparea1 = seperatingface->frontarea; tmparea2 = seperatingface->backarea; //areas must have the same presence type if (tmparea1->presencetype != tmparea2->presencetype) return false; //areas must have the same area contents if (tmparea1->contents != tmparea2->contents) return false; //areas must have the same bsp model inside (or both none) if (tmparea1->modelnum != tmparea2->modelnum) return false; area1faceflags = 0; area2faceflags = 0; for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side1]) { side1 = (face1->frontarea != tmparea1); //debug: check if the area belongs to the area if (face1->frontarea != tmparea1 && face1->backarea != tmparea1) Error("face does not belong to area1"); //just continue if the face is seperating the two areas //NOTE: a result of this is that ground and gap areas can // be merged if the seperating face is the gap if ((face1->frontarea == tmparea1 && face1->backarea == tmparea2) || (face1->frontarea == tmparea2 && face1->backarea == tmparea1)) continue; //get area1 face flags area1faceflags |= face1->faceflags; if (AAS_GapFace(face1, side1)) area1faceflags |= FACE_GAP; // for (face2 = tmparea2->tmpfaces; face2; face2 = face2->next[side2]) { side2 = (face2->frontarea != tmparea2); //debug: check if the area belongs to the area if (face2->frontarea != tmparea2 && face2->backarea != tmparea2) Error("face does not belong to area2"); //just continue if the face is seperating the two areas //NOTE: a result of this is that ground and gap areas can // be merged if the seperating face is the gap if ((face2->frontarea == tmparea1 && face2->backarea == tmparea2) || (face2->frontarea == tmparea2 && face2->backarea == tmparea1)) continue; //get area2 face flags area2faceflags |= face2->faceflags; if (AAS_GapFace(face2, side2)) area2faceflags |= FACE_GAP; //if the two faces would create a non-convex area if (NonConvex(face1, face2, side1, side2)) return false; } //end for } //end for //if one area has gap faces (that aren't seperating the two areas) //and the other has ground faces (that aren't seperating the two areas), //the areas can't be merged if (((area1faceflags & FACE_GROUND) && (area2faceflags & FACE_GAP)) || ((area2faceflags & FACE_GROUND) && (area1faceflags & FACE_GAP))) { // Log_Print(" can't merge: ground/gap\n"); return false; } //end if // Log_Print("merged area %d & %d to %d with %d faces\n", tmparea1->areanum, tmparea2->areanum, newarea->areanum, numfaces); // return false; // //AAS_CheckArea(tmparea1); //AAS_CheckArea(tmparea2); //create the new area newarea = AAS_AllocTmpArea(); newarea->presencetype = tmparea1->presencetype; newarea->contents = tmparea1->contents; newarea->modelnum = tmparea1->modelnum; newarea->tmpfaces = NULL; //add all the faces (except the seperating ones) from the first area //to the new area for (face1 = tmparea1->tmpfaces; face1; face1 = nextface1) { side1 = (face1->frontarea != tmparea1); nextface1 = face1->next[side1]; //don't add seperating faces if ((face1->frontarea == tmparea1 && face1->backarea == tmparea2) || (face1->frontarea == tmparea2 && face1->backarea == tmparea1)) { continue; } //end if // AAS_RemoveFaceFromArea(face1, tmparea1); AAS_AddFaceSideToArea(face1, side1, newarea); } //end for //add all the faces (except the seperating ones) from the second area //to the new area for (face2 = tmparea2->tmpfaces; face2; face2 = nextface2) { side2 = (face2->frontarea != tmparea2); nextface2 = face2->next[side2]; //don't add seperating faces if ((face2->frontarea == tmparea1 && face2->backarea == tmparea2) || (face2->frontarea == tmparea2 && face2->backarea == tmparea1)) { continue; } //end if // AAS_RemoveFaceFromArea(face2, tmparea2); AAS_AddFaceSideToArea(face2, side2, newarea); } //end for //free all shared faces for (face1 = tmparea1->tmpfaces; face1; face1 = nextface1) { side1 = (face1->frontarea != tmparea1); nextface1 = face1->next[side1]; // AAS_RemoveFaceFromArea(face1, face1->frontarea); AAS_RemoveFaceFromArea(face1, face1->backarea); AAS_FreeTmpFace(face1); } //end for // tmparea1->mergedarea = newarea; tmparea1->invalid = true; tmparea2->mergedarea = newarea; tmparea2->invalid = true; // AAS_CheckArea(newarea); AAS_FlipAreaFaces(newarea); // Log_Print("merged area %d & %d to %d with %d faces\n", tmparea1->areanum, tmparea2->areanum, newarea->areanum); return true; } //end of the function AAS_TryMergeFaceAreas //=========================================================================== // try to merge areas // merged areas are added to the end of the convex area list so merging // will be tried for those areas as well // // Parameter: - // Returns: - // Changes Globals: tmpaasworld //=========================================================================== /* void AAS_MergeAreas(void) { int side, nummerges; tmp_area_t *tmparea, *othertmparea; tmp_face_t *face; nummerges = 0; Log_Write("AAS_MergeAreas\r\n"); qprintf("%6d areas merged", 1); //first merge grounded areas only //NOTE: this is useless because the area settings aren't available yet for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) { // Log_Print("checking area %d\n", i); //if the area is invalid if (tmparea->invalid) { // Log_Print(" area invalid\n"); continue; } //end if // // if (!(tmparea->settings->areaflags & AREA_GROUNDED)) continue; // for (face = tmparea->tmpfaces; face; face = face->next[side]) { side = (face->frontarea != tmparea); //if the face has both a front and back area if (face->frontarea && face->backarea) { // if (face->frontarea == tmparea) othertmparea = face->backarea; else othertmparea = face->frontarea; // if (!(othertmparea->settings->areaflags & AREA_GROUNDED)) continue; // Log_Print(" checking area %d with %d\n", face->frontarea, face->backarea); if (AAS_TryMergeFaceAreas(face)) { qprintf("\r%6d", ++nummerges); break; } //end if } //end if } //end for } //end for //merge all areas for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) { // Log_Print("checking area %d\n", i); //if the area is invalid if (tmparea->invalid) { // Log_Print(" area invalid\n"); continue; } //end if // for (face = tmparea->tmpfaces; face; face = face->next[side]) { side = (face->frontarea != tmparea); //if the face has both a front and back area if (face->frontarea && face->backarea) { // Log_Print(" checking area %d with %d\n", face->frontarea, face->backarea); if (AAS_TryMergeFaceAreas(face)) { qprintf("\r%6d", ++nummerges); break; } //end if } //end if } //end for } //end for Log_Print("\r%6d areas merged\n", nummerges); //refresh the merged tree AAS_RefreshMergedTree_r(tmpaasworld.nodes); } //end of the function AAS_MergeAreas*/ int AAS_GroundArea(tmp_area_t *tmparea) { tmp_face_t *face; int side; for (face = tmparea->tmpfaces; face; face = face->next[side]) { side = (face->frontarea != tmparea); if (face->faceflags & FACE_GROUND) return true; } //end for return false; } //end of the function AAS_GroundArea void AAS_MergeAreas(void) { int side, nummerges, merges, groundfirst; tmp_area_t *tmparea, *othertmparea; tmp_face_t *face; nummerges = 0; Log_Write("AAS_MergeAreas\r\n"); qprintf("%6d areas merged", 1); // groundfirst = true; //for (i = 0; i < 4 || merges; i++) while(1) { //if (i < 2) groundfirst = true; //else groundfirst = false; // merges = 0; //first merge grounded areas only for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) { //if the area is invalid if (tmparea->invalid) { continue; } //end if // if (groundfirst) { if (!AAS_GroundArea(tmparea)) continue; } //end if // for (face = tmparea->tmpfaces; face; face = face->next[side]) { side = (face->frontarea != tmparea); //if the face has both a front and back area if (face->frontarea && face->backarea) { // if (face->frontarea == tmparea) othertmparea = face->backarea; else othertmparea = face->frontarea; // if (groundfirst) { if (!AAS_GroundArea(othertmparea)) continue; } //end if if (AAS_TryMergeFaceAreas(face)) { qprintf("\r%6d", ++nummerges); merges++; break; } //end if } //end if } //end for } //end for if (!merges) { if (groundfirst) groundfirst = false; else break; } //end if } //end for qprintf("\n"); Log_Write("%6d areas merged\r\n", nummerges); //refresh the merged tree AAS_RefreshMergedTree_r(tmpaasworld.nodes); } //end of the function AAS_MergeAreas ================================================ FILE: code/bspc/aas_areamerging.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ void AAS_MergeAreas(void); ================================================ FILE: code/bspc/aas_cfg.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "float.h" #include "../botlib/aasfile.h" #include "aas_store.h" #include "aas_cfg.h" #include "../botlib/l_precomp.h" #include "../botlib/l_struct.h" #include "../botlib/l_libvar.h" //structure field offsets #define BBOX_OFS(x) (int)&(((aas_bbox_t *)0)->x) #define CFG_OFS(x) (int)&(((cfg_t *)0)->x) //bounding box definition fielddef_t bbox_fields[] = { {"presencetype", BBOX_OFS(presencetype), FT_INT}, {"flags", BBOX_OFS(flags), FT_INT}, {"mins", BBOX_OFS(mins), FT_FLOAT|FT_ARRAY, 3}, {"maxs", BBOX_OFS(maxs), FT_FLOAT|FT_ARRAY, 3}, {NULL, 0, 0, 0} }; fielddef_t cfg_fields[] = { {"phys_gravitydirection", CFG_OFS(phys_gravitydirection), FT_FLOAT|FT_ARRAY, 3}, {"phys_friction", CFG_OFS(phys_friction), FT_FLOAT}, {"phys_stopspeed", CFG_OFS(phys_stopspeed), FT_FLOAT}, {"phys_gravity", CFG_OFS(phys_gravity), FT_FLOAT}, {"phys_waterfriction", CFG_OFS(phys_waterfriction), FT_FLOAT}, {"phys_watergravity", CFG_OFS(phys_watergravity), FT_FLOAT}, {"phys_maxvelocity", CFG_OFS(phys_maxvelocity), FT_FLOAT}, {"phys_maxwalkvelocity", CFG_OFS(phys_maxwalkvelocity), FT_FLOAT}, {"phys_maxcrouchvelocity", CFG_OFS(phys_maxcrouchvelocity), FT_FLOAT}, {"phys_maxswimvelocity", CFG_OFS(phys_maxswimvelocity), FT_FLOAT}, {"phys_walkaccelerate", CFG_OFS(phys_walkaccelerate), FT_FLOAT}, {"phys_airaccelerate", CFG_OFS(phys_airaccelerate), FT_FLOAT}, {"phys_swimaccelerate", CFG_OFS(phys_swimaccelerate), FT_FLOAT}, {"phys_maxstep", CFG_OFS(phys_maxstep), FT_FLOAT}, {"phys_maxsteepness", CFG_OFS(phys_maxsteepness), FT_FLOAT}, {"phys_maxwaterjump", CFG_OFS(phys_maxwaterjump), FT_FLOAT}, {"phys_maxbarrier", CFG_OFS(phys_maxbarrier), FT_FLOAT}, {"phys_jumpvel", CFG_OFS(phys_jumpvel), FT_FLOAT}, {"phys_falldelta5", CFG_OFS(phys_falldelta5), FT_FLOAT}, {"phys_falldelta10", CFG_OFS(phys_falldelta10), FT_FLOAT}, {"rs_waterjump", CFG_OFS(rs_waterjump), FT_FLOAT}, {"rs_teleport", CFG_OFS(rs_teleport), FT_FLOAT}, {"rs_barrierjump", CFG_OFS(rs_barrierjump), FT_FLOAT}, {"rs_startcrouch", CFG_OFS(rs_startcrouch), FT_FLOAT}, {"rs_startgrapple", CFG_OFS(rs_startgrapple), FT_FLOAT}, {"rs_startwalkoffledge", CFG_OFS(rs_startwalkoffledge), FT_FLOAT}, {"rs_startjump", CFG_OFS(rs_startjump), FT_FLOAT}, {"rs_rocketjump", CFG_OFS(rs_rocketjump), FT_FLOAT}, {"rs_bfgjump", CFG_OFS(rs_bfgjump), FT_FLOAT}, {"rs_jumppad", CFG_OFS(rs_jumppad), FT_FLOAT}, {"rs_aircontrolledjumppad", CFG_OFS(rs_aircontrolledjumppad), FT_FLOAT}, {"rs_funcbob", CFG_OFS(rs_funcbob), FT_FLOAT}, {"rs_startelevator", CFG_OFS(rs_startelevator), FT_FLOAT}, {"rs_falldamage5", CFG_OFS(rs_falldamage5), FT_FLOAT}, {"rs_falldamage10", CFG_OFS(rs_falldamage10), FT_FLOAT}, {"rs_maxjumpfallheight", CFG_OFS(rs_maxjumpfallheight), FT_FLOAT}, {NULL, 0, 0, 0} }; structdef_t bbox_struct = { sizeof(aas_bbox_t), bbox_fields }; structdef_t cfg_struct = { sizeof(cfg_t), cfg_fields }; //global cfg cfg_t cfg; //=========================================================================== // the default Q3A configuration // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void DefaultCfg(void) { int i; // default all float values to infinite for (i = 0; cfg_fields[i].name; i++) { if ((cfg_fields[i].type & FT_TYPE) == FT_FLOAT) *(float *)( ((char*)&cfg) + cfg_fields[i].offset ) = FLT_MAX; } //end for // cfg.numbboxes = 2; //bbox 0 cfg.bboxes[0].presencetype = PRESENCE_NORMAL; cfg.bboxes[0].flags = 0; cfg.bboxes[0].mins[0] = -15; cfg.bboxes[0].mins[1] = -15; cfg.bboxes[0].mins[2] = -24; cfg.bboxes[0].maxs[0] = 15; cfg.bboxes[0].maxs[1] = 15; cfg.bboxes[0].maxs[2] = 32; //bbox 1 cfg.bboxes[1].presencetype = PRESENCE_CROUCH; cfg.bboxes[1].flags = 1; cfg.bboxes[1].mins[0] = -15; cfg.bboxes[1].mins[1] = -15; cfg.bboxes[1].mins[2] = -24; cfg.bboxes[1].maxs[0] = 15; cfg.bboxes[1].maxs[1] = 15; cfg.bboxes[1].maxs[2] = 16; // cfg.allpresencetypes = PRESENCE_NORMAL|PRESENCE_CROUCH; cfg.phys_gravitydirection[0] = 0; cfg.phys_gravitydirection[1] = 0; cfg.phys_gravitydirection[2] = -1; cfg.phys_maxsteepness = 0.7; } //end of the function DefaultCfg //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char * QDECL va( char *format, ... ) { va_list argptr; static char string[2][32000]; // in case va is called by nested functions static int index = 0; char *buf; buf = string[index & 1]; index++; va_start (argptr, format); vsprintf (buf, format,argptr); va_end (argptr); return buf; } //end of the function va //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SetCfgLibVars(void) { int i; float value; for (i = 0; cfg_fields[i].name; i++) { if ((cfg_fields[i].type & FT_TYPE) == FT_FLOAT) { value = *(float *)(((char*)&cfg) + cfg_fields[i].offset); if (value != FLT_MAX) { LibVarSet(cfg_fields[i].name, va("%f", value)); } //end if } //end if } //end for } //end of the function SetCfgLibVars //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int LoadCfgFile(char *filename) { source_t *source; token_t token; int settingsdefined; source = LoadSourceFile(filename); if (!source) { Log_Print("couldn't open cfg file %s\n", filename); return false; } //end if settingsdefined = false; memset(&cfg, 0, sizeof(cfg_t)); while(PC_ReadToken(source, &token)) { if (!stricmp(token.string, "bbox")) { if (cfg.numbboxes >= AAS_MAX_BBOXES) { SourceError(source, "too many bounding box volumes defined"); } //end if if (!ReadStructure(source, &bbox_struct, (char *) &cfg.bboxes[cfg.numbboxes])) { FreeSource(source); return false; } //end if cfg.allpresencetypes |= cfg.bboxes[cfg.numbboxes].presencetype; cfg.numbboxes++; } //end if else if (!stricmp(token.string, "settings")) { if (settingsdefined) { SourceWarning(source, "settings already defined\n"); } //end if settingsdefined = true; if (!ReadStructure(source, &cfg_struct, (char *) &cfg)) { FreeSource(source); return false; } //end if } //end else if } //end while if (VectorLength(cfg.phys_gravitydirection) < 0.9 || VectorLength(cfg.phys_gravitydirection) > 1.1) { SourceError(source, "invalid gravity direction specified"); } //end if if (cfg.numbboxes <= 0) { SourceError(source, "no bounding volumes specified"); } //end if FreeSource(source); SetCfgLibVars(); Log_Print("using cfg file %s\n", filename); return true; } //end of the function LoadCfgFile ================================================ FILE: code/bspc/aas_cfg.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #define BBOXFL_GROUNDED 1 //bounding box only valid when on ground #define BBOXFL_NOTGROUNDED 2 //bounding box only valid when NOT on ground typedef struct cfg_s { int numbboxes; //number of bounding boxes aas_bbox_t bboxes[AAS_MAX_BBOXES]; //all the bounding boxes int allpresencetypes; //or of all presence types // aas settings vec3_t phys_gravitydirection; float phys_friction; float phys_stopspeed; float phys_gravity; float phys_waterfriction; float phys_watergravity; float phys_maxvelocity; float phys_maxwalkvelocity; float phys_maxcrouchvelocity; float phys_maxswimvelocity; float phys_walkaccelerate; float phys_airaccelerate; float phys_swimaccelerate; float phys_maxstep; float phys_maxsteepness; float phys_maxwaterjump; float phys_maxbarrier; float phys_jumpvel; float phys_falldelta5; float phys_falldelta10; float rs_waterjump; float rs_teleport; float rs_barrierjump; float rs_startcrouch; float rs_startgrapple; float rs_startwalkoffledge; float rs_startjump; float rs_rocketjump; float rs_bfgjump; float rs_jumppad; float rs_aircontrolledjumppad; float rs_funcbob; float rs_startelevator; float rs_falldamage5; float rs_falldamage10; float rs_maxjumpfallheight; } cfg_t; extern cfg_t cfg; void DefaultCfg(void); int LoadCfgFile(char *filename); ================================================ FILE: code/bspc/aas_create.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "../botlib/aasfile.h" #include "aas_create.h" #include "aas_store.h" #include "aas_gsubdiv.h" #include "aas_facemerging.h" #include "aas_areamerging.h" #include "aas_edgemelting.h" #include "aas_prunenodes.h" #include "aas_cfg.h" #include "../game/surfaceflags.h" //#define AW_DEBUG //#define L_DEBUG #define AREAONFACESIDE(face, area) (face->frontarea != area) tmp_aas_t tmpaasworld; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitTmpAAS(void) { //tmp faces tmpaasworld.numfaces = 0; tmpaasworld.facenum = 0; tmpaasworld.faces = NULL; //tmp convex areas tmpaasworld.numareas = 0; tmpaasworld.areanum = 0; tmpaasworld.areas = NULL; //tmp nodes tmpaasworld.numnodes = 0; tmpaasworld.nodes = NULL; // tmpaasworld.nodebuffer = NULL; } //end of the function AAS_InitTmpAAS //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeTmpAAS(void) { tmp_face_t *f, *nextf; tmp_area_t *a, *nexta; tmp_nodebuf_t *nb, *nextnb; //free all the faces for (f = tmpaasworld.faces; f; f = nextf) { nextf = f->l_next; if (f->winding) FreeWinding(f->winding); FreeMemory(f); } //end if //free all tmp areas for (a = tmpaasworld.areas; a; a = nexta) { nexta = a->l_next; if (a->settings) FreeMemory(a->settings); FreeMemory(a); } //end for //free all the tmp nodes for (nb = tmpaasworld.nodebuffer; nb; nb = nextnb) { nextnb = nb->next; FreeMemory(nb); } //end for } //end of the function AAS_FreeTmpAAS //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_face_t *AAS_AllocTmpFace(void) { tmp_face_t *tmpface; tmpface = (tmp_face_t *) GetClearedMemory(sizeof(tmp_face_t)); tmpface->num = tmpaasworld.facenum++; tmpface->l_prev = NULL; tmpface->l_next = tmpaasworld.faces; if (tmpaasworld.faces) tmpaasworld.faces->l_prev = tmpface; tmpaasworld.faces = tmpface; tmpaasworld.numfaces++; return tmpface; } //end of the function AAS_AllocTmpFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeTmpFace(tmp_face_t *tmpface) { if (tmpface->l_next) tmpface->l_next->l_prev = tmpface->l_prev; if (tmpface->l_prev) tmpface->l_prev->l_next = tmpface->l_next; else tmpaasworld.faces = tmpface->l_next; //free the winding if (tmpface->winding) FreeWinding(tmpface->winding); //free the face FreeMemory(tmpface); tmpaasworld.numfaces--; } //end of the function AAS_FreeTmpFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_area_t *AAS_AllocTmpArea(void) { tmp_area_t *tmparea; tmparea = (tmp_area_t *) GetClearedMemory(sizeof(tmp_area_t)); tmparea->areanum = tmpaasworld.areanum++; tmparea->l_prev = NULL; tmparea->l_next = tmpaasworld.areas; if (tmpaasworld.areas) tmpaasworld.areas->l_prev = tmparea; tmpaasworld.areas = tmparea; tmpaasworld.numareas++; return tmparea; } //end of the function AAS_AllocTmpArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeTmpArea(tmp_area_t *tmparea) { if (tmparea->l_next) tmparea->l_next->l_prev = tmparea->l_prev; if (tmparea->l_prev) tmparea->l_prev->l_next = tmparea->l_next; else tmpaasworld.areas = tmparea->l_next; if (tmparea->settings) FreeMemory(tmparea->settings); FreeMemory(tmparea); tmpaasworld.numareas--; } //end of the function AAS_FreeTmpArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_node_t *AAS_AllocTmpNode(void) { tmp_nodebuf_t *nodebuf; if (!tmpaasworld.nodebuffer || tmpaasworld.nodebuffer->numnodes >= NODEBUF_SIZE) { nodebuf = (tmp_nodebuf_t *) GetClearedMemory(sizeof(tmp_nodebuf_t)); nodebuf->next = tmpaasworld.nodebuffer; nodebuf->numnodes = 0; tmpaasworld.nodebuffer = nodebuf; } //end if tmpaasworld.numnodes++; return &tmpaasworld.nodebuffer->nodes[tmpaasworld.nodebuffer->numnodes++]; } //end of the function AAS_AllocTmpNode //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeTmpNode(tmp_node_t *tmpnode) { tmpaasworld.numnodes--; } //end of the function AAS_FreeTmpNode //=========================================================================== // returns true if the face is a gap from the given side // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_GapFace(tmp_face_t *tmpface, int side) { vec3_t invgravity; //if the face is a solid or ground face it can't be a gap if (tmpface->faceflags & (FACE_GROUND | FACE_SOLID)) return 0; VectorCopy(cfg.phys_gravitydirection, invgravity); VectorInverse(invgravity); return (DotProduct(invgravity, mapplanes[tmpface->planenum ^ side].normal) > cfg.phys_maxsteepness); } //end of the function AAS_GapFace //=========================================================================== // returns true if the face is a ground face // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_GroundFace(tmp_face_t *tmpface) { vec3_t invgravity; //must be a solid face if (!(tmpface->faceflags & FACE_SOLID)) return 0; VectorCopy(cfg.phys_gravitydirection, invgravity); VectorInverse(invgravity); return (DotProduct(invgravity, mapplanes[tmpface->planenum].normal) > cfg.phys_maxsteepness); } //end of the function AAS_GroundFace //=========================================================================== // adds the side of a face to an area // // side : 0 = front side // 1 = back side // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_AddFaceSideToArea(tmp_face_t *tmpface, int side, tmp_area_t *tmparea) { int tmpfaceside; if (side) { if (tmpface->backarea) Error("AAS_AddFaceSideToArea: already a back area\n"); } //end if else { if (tmpface->frontarea) Error("AAS_AddFaceSideToArea: already a front area\n"); } //end else if (side) tmpface->backarea = tmparea; else tmpface->frontarea = tmparea; if (tmparea->tmpfaces) { tmpfaceside = tmparea->tmpfaces->frontarea != tmparea; tmparea->tmpfaces->prev[tmpfaceside] = tmpface; } //end if tmpface->next[side] = tmparea->tmpfaces; tmpface->prev[side] = NULL; tmparea->tmpfaces = tmpface; } //end of the function AAS_AddFaceSideToArea //=========================================================================== // remove (a side of) a face from an area // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RemoveFaceFromArea(tmp_face_t *tmpface, tmp_area_t *tmparea) { int side, prevside, nextside; if (tmpface->frontarea != tmparea && tmpface->backarea != tmparea) { Error("AAS_RemoveFaceFromArea: face not part of the area"); } //end if side = tmpface->frontarea != tmparea; if (tmpface->prev[side]) { prevside = tmpface->prev[side]->frontarea != tmparea; tmpface->prev[side]->next[prevside] = tmpface->next[side]; } //end if else { tmparea->tmpfaces = tmpface->next[side]; } //end else if (tmpface->next[side]) { nextside = tmpface->next[side]->frontarea != tmparea; tmpface->next[side]->prev[nextside] = tmpface->prev[side]; } //end if //remove the area number from the face depending on the side if (side) tmpface->backarea = NULL; else tmpface->frontarea = NULL; tmpface->prev[side] = NULL; tmpface->next[side] = NULL; } //end of the function AAS_RemoveFaceFromArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CheckArea(tmp_area_t *tmparea) { int side; tmp_face_t *face; plane_t *plane; vec3_t wcenter, acenter = {0, 0, 0}; vec3_t normal; float n, dist; if (tmparea->invalid) Log_Print("AAS_CheckArea: invalid area\n"); for (n = 0, face = tmparea->tmpfaces; face; face = face->next[side]) { //side of the face the area is on side = face->frontarea != tmparea; WindingCenter(face->winding, wcenter); VectorAdd(acenter, wcenter, acenter); n++; } //end for n = 1 / n; VectorScale(acenter, n, acenter); for (face = tmparea->tmpfaces; face; face = face->next[side]) { //side of the face the area is on side = face->frontarea != tmparea; #ifdef L_DEBUG if (WindingError(face->winding)) { Log_Write("AAS_CheckArea: area %d face %d: %s\r\n", tmparea->areanum, face->num, WindingErrorString()); } //end if #endif L_DEBUG plane = &mapplanes[face->planenum ^ side]; if (DotProduct(plane->normal, acenter) - plane->dist < 0) { Log_Print("AAS_CheckArea: area %d face %d is flipped\n", tmparea->areanum, face->num); Log_Print("AAS_CheckArea: area %d center is %f %f %f\n", tmparea->areanum, acenter[0], acenter[1], acenter[2]); } //end if //check if the winding plane is the same as the face plane WindingPlane(face->winding, normal, &dist); plane = &mapplanes[face->planenum]; #ifdef L_DEBUG if (fabs(dist - plane->dist) > 0.4 || fabs(normal[0] - plane->normal[0]) > 0.0001 || fabs(normal[1] - plane->normal[1]) > 0.0001 || fabs(normal[2] - plane->normal[2]) > 0.0001) { Log_Write("AAS_CheckArea: area %d face %d winding plane unequal to face plane\r\n", tmparea->areanum, face->num); } //end if #endif L_DEBUG } //end for } //end of the function AAS_CheckArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CheckFaceWindingPlane(tmp_face_t *face) { float dist, sign1, sign2; vec3_t normal; plane_t *plane; winding_t *w; //check if the winding plane is the same as the face plane WindingPlane(face->winding, normal, &dist); plane = &mapplanes[face->planenum]; // sign1 = DotProduct(plane->normal, normal); // if (fabs(dist - plane->dist) > 0.4 || fabs(normal[0] - plane->normal[0]) > 0.0001 || fabs(normal[1] - plane->normal[1]) > 0.0001 || fabs(normal[2] - plane->normal[2]) > 0.0001) { VectorInverse(normal); dist = -dist; if (fabs(dist - plane->dist) > 0.4 || fabs(normal[0] - plane->normal[0]) > 0.0001 || fabs(normal[1] - plane->normal[1]) > 0.0001 || fabs(normal[2] - plane->normal[2]) > 0.0001) { Log_Write("AAS_CheckFaceWindingPlane: face %d winding plane unequal to face plane\r\n", face->num); // sign2 = DotProduct(plane->normal, normal); if ((sign1 < 0 && sign2 > 0) || (sign1 > 0 && sign2 < 0)) { Log_Write("AAS_CheckFaceWindingPlane: face %d winding reversed\r\n", face->num); w = face->winding; face->winding = ReverseWinding(w); FreeWinding(w); } //end if } //end if else { Log_Write("AAS_CheckFaceWindingPlane: face %d winding reversed\r\n", face->num); w = face->winding; face->winding = ReverseWinding(w); FreeWinding(w); } //end else } //end if } //end of the function AAS_CheckFaceWindingPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CheckAreaWindingPlanes(void) { int side; tmp_area_t *tmparea; tmp_face_t *face; Log_Write("AAS_CheckAreaWindingPlanes:\r\n"); for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) { if (tmparea->invalid) continue; for (face = tmparea->tmpfaces; face; face = face->next[side]) { side = face->frontarea != tmparea; AAS_CheckFaceWindingPlane(face); } //end for } //end for } //end of the function AAS_CheckAreaWindingPlanes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FlipAreaFaces(tmp_area_t *tmparea) { int side; tmp_face_t *face; plane_t *plane; vec3_t wcenter, acenter = {0, 0, 0}; //winding_t *w; float n; for (n = 0, face = tmparea->tmpfaces; face; face = face->next[side]) { if (!face->frontarea) Error("face %d has no front area\n", face->num); //side of the face the area is on side = face->frontarea != tmparea; WindingCenter(face->winding, wcenter); VectorAdd(acenter, wcenter, acenter); n++; } //end for n = 1 / n; VectorScale(acenter, n, acenter); for (face = tmparea->tmpfaces; face; face = face->next[side]) { //side of the face the area is on side = face->frontarea != tmparea; plane = &mapplanes[face->planenum ^ side]; if (DotProduct(plane->normal, acenter) - plane->dist < 0) { Log_Print("area %d face %d flipped: front area %d, back area %d\n", tmparea->areanum, face->num, face->frontarea ? face->frontarea->areanum : 0, face->backarea ? face->backarea->areanum : 0); /* face->planenum = face->planenum ^ 1; w = face->winding; face->winding = ReverseWinding(w); FreeWinding(w); */ } //end if #ifdef L_DEBUG { float dist; vec3_t normal; //check if the winding plane is the same as the face plane WindingPlane(face->winding, normal, &dist); plane = &mapplanes[face->planenum]; if (fabs(dist - plane->dist) > 0.4 || fabs(normal[0] - plane->normal[0]) > 0.0001 || fabs(normal[1] - plane->normal[1]) > 0.0001 || fabs(normal[2] - plane->normal[2]) > 0.0001) { Log_Write("area %d face %d winding plane unequal to face plane\r\n", tmparea->areanum, face->num); } //end if } #endif } //end for } //end of the function AAS_FlipAreaFaces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RemoveAreaFaceColinearPoints(void) { int side; tmp_face_t *face; tmp_area_t *tmparea; //FIXME: loop over the faces instead of area->faces for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) { for (face = tmparea->tmpfaces; face; face = face->next[side]) { side = face->frontarea != tmparea; RemoveColinearPoints(face->winding); // RemoveEqualPoints(face->winding, 0.1); } //end for } //end for } //end of the function AAS_RemoveAreaFaceColinearPoints //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_RemoveTinyFaces(void) { int side, num; tmp_face_t *face, *nextface; tmp_area_t *tmparea; //FIXME: loop over the faces instead of area->faces Log_Write("AAS_RemoveTinyFaces\r\n"); num = 0; for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) { for (face = tmparea->tmpfaces; face; face = nextface) { side = face->frontarea != tmparea; nextface = face->next[side]; // if (WindingArea(face->winding) < 1) { if (face->frontarea) AAS_RemoveFaceFromArea(face, face->frontarea); if (face->backarea) AAS_RemoveFaceFromArea(face, face->backarea); AAS_FreeTmpFace(face); //Log_Write("area %d face %d is tiny\r\n", tmparea->areanum, face->num); num++; } //end if } //end for } //end for Log_Write("%d tiny faces removed\r\n", num); } //end of the function AAS_RemoveTinyFaces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CreateAreaSettings(void) { int i, flags, side, numgrounded, numladderareas, numliquidareas; tmp_face_t *face; tmp_area_t *tmparea; numgrounded = 0; numladderareas = 0; numliquidareas = 0; Log_Write("AAS_CreateAreaSettings\r\n"); i = 0; qprintf("%6d areas provided with settings", i); for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) { //if the area is invalid there no need to create settings for it if (tmparea->invalid) continue; tmparea->settings = (tmp_areasettings_t *) GetClearedMemory(sizeof(tmp_areasettings_t)); tmparea->settings->contents = tmparea->contents; tmparea->settings->modelnum = tmparea->modelnum; flags = 0; for (face = tmparea->tmpfaces; face; face = face->next[side]) { side = face->frontarea != tmparea; flags |= face->faceflags; } //end for tmparea->settings->areaflags = 0; if (flags & FACE_GROUND) { tmparea->settings->areaflags |= AREA_GROUNDED; numgrounded++; } //end if if (flags & FACE_LADDER) { tmparea->settings->areaflags |= AREA_LADDER; numladderareas++; } //end if if (tmparea->contents & (AREACONTENTS_WATER | AREACONTENTS_SLIME | AREACONTENTS_LAVA)) { tmparea->settings->areaflags |= AREA_LIQUID; numliquidareas++; } //end if //presence type of the area tmparea->settings->presencetype = tmparea->presencetype; // qprintf("\r%6d", ++i); } //end for qprintf("\n"); #ifdef AASINFO Log_Print("%6d grounded areas\n", numgrounded); Log_Print("%6d ladder areas\n", numladderareas); Log_Print("%6d liquid areas\n", numliquidareas); #endif //AASINFO } //end of the function AAS_CreateAreaSettings //=========================================================================== // create a tmp AAS area from a leaf node // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_node_t *AAS_CreateArea(node_t *node) { int pside; int areafaceflags; portal_t *p; tmp_face_t *tmpface; tmp_area_t *tmparea; tmp_node_t *tmpnode; vec3_t up = {0, 0, 1}; //create an area from this leaf tmparea = AAS_AllocTmpArea(); tmparea->tmpfaces = NULL; //clear the area face flags areafaceflags = 0; //make aas faces from the portals for (p = node->portals; p; p = p->next[pside]) { pside = (p->nodes[1] == node); //don't create faces from very small portals // if (WindingArea(p->winding) < 1) continue; //if there's already a face created for this portal if (p->tmpface) { //add the back side of the face to the area AAS_AddFaceSideToArea(p->tmpface, 1, tmparea); } //end if else { tmpface = AAS_AllocTmpFace(); //set the face pointer at the portal so we can see from //the portal there's a face created for it p->tmpface = tmpface; //FIXME: test this change //tmpface->planenum = (p->planenum & ~1) | pside; tmpface->planenum = p->planenum ^ pside; if (pside) tmpface->winding = ReverseWinding(p->winding); else tmpface->winding = CopyWinding(p->winding); #ifdef L_DEBUG // AAS_CheckFaceWindingPlane(tmpface); #endif //L_DEBUG //if there's solid at the other side of the portal if (p->nodes[!pside]->contents & (CONTENTS_SOLID | CONTENTS_PLAYERCLIP)) { tmpface->faceflags |= FACE_SOLID; } //end if //else there is no solid at the other side and if there //is a liquid at this side else if (node->contents & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { tmpface->faceflags |= FACE_LIQUID; //if there's no liquid at the other side if (!(p->nodes[!pside]->contents & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) { tmpface->faceflags |= FACE_LIQUIDSURFACE; } //end if } //end else //if there's ladder contents at other side of the portal if ((p->nodes[pside]->contents & CONTENTS_LADDER) || (p->nodes[!pside]->contents & CONTENTS_LADDER)) { //NOTE: doesn't have to be solid at the other side because // when standing one can use a crouch area (which is not solid) // as a ladder // imagine a ladder one can walk underthrough, // under the ladder against the ladder is a crouch area // the (vertical) sides of this crouch area area also used as // ladder sides when standing (not crouched) tmpface->faceflags |= FACE_LADDER; } //end if //if it is possible to stand on the face if (AAS_GroundFace(tmpface)) { tmpface->faceflags |= FACE_GROUND; } //end if // areafaceflags |= tmpface->faceflags; //no aas face number yet (zero is a dummy in the aasworld faces) tmpface->aasfacenum = 0; //add the front side of the face to the area AAS_AddFaceSideToArea(tmpface, 0, tmparea); } //end else } //end for qprintf("\r%6d", tmparea->areanum); //presence type in the area tmparea->presencetype = ~node->expansionbboxes & cfg.allpresencetypes; // tmparea->contents = 0; if (node->contents & CONTENTS_CLUSTERPORTAL) tmparea->contents |= AREACONTENTS_CLUSTERPORTAL; if (node->contents & CONTENTS_MOVER) tmparea->contents |= AREACONTENTS_MOVER; if (node->contents & CONTENTS_TELEPORTER) tmparea->contents |= AREACONTENTS_TELEPORTER; if (node->contents & CONTENTS_JUMPPAD) tmparea->contents |= AREACONTENTS_JUMPPAD; if (node->contents & CONTENTS_DONOTENTER) tmparea->contents |= AREACONTENTS_DONOTENTER; if (node->contents & CONTENTS_WATER) tmparea->contents |= AREACONTENTS_WATER; if (node->contents & CONTENTS_LAVA) tmparea->contents |= AREACONTENTS_LAVA; if (node->contents & CONTENTS_SLIME) tmparea->contents |= AREACONTENTS_SLIME; if (node->contents & CONTENTS_NOTTEAM1) tmparea->contents |= AREACONTENTS_NOTTEAM1; if (node->contents & CONTENTS_NOTTEAM2) tmparea->contents |= AREACONTENTS_NOTTEAM2; //store the bsp model that's inside this node tmparea->modelnum = node->modelnum; //sorta check for flipped area faces (remove??) AAS_FlipAreaFaces(tmparea); //check if the area is ok (remove??) AAS_CheckArea(tmparea); // tmpnode = AAS_AllocTmpNode(); tmpnode->planenum = 0; tmpnode->children[0] = 0; tmpnode->children[1] = 0; tmpnode->tmparea = tmparea; // return tmpnode; } //end of the function AAS_CreateArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_node_t *AAS_CreateAreas_r(node_t *node) { tmp_node_t *tmpnode; //recurse down to leafs if (node->planenum != PLANENUM_LEAF) { //the first tmp node is a dummy tmpnode = AAS_AllocTmpNode(); tmpnode->planenum = node->planenum; tmpnode->children[0] = AAS_CreateAreas_r(node->children[0]); tmpnode->children[1] = AAS_CreateAreas_r(node->children[1]); return tmpnode; } //end if //areas won't be created for solid leafs if (node->contents & CONTENTS_SOLID) { //just return zero for a solid leaf (in tmp AAS NULL is a solid leaf) return NULL; } //end if return AAS_CreateArea(node); } //end of the function AAS_CreateAreas_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CreateAreas(node_t *node) { Log_Write("AAS_CreateAreas\r\n"); qprintf("%6d areas created", 0); tmpaasworld.nodes = AAS_CreateAreas_r(node); qprintf("\n"); Log_Write("%6d areas created\r\n", tmpaasworld.numareas); } //end of the function AAS_CreateAreas //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_PrintNumGroundFaces(void) { tmp_face_t *tmpface; int numgroundfaces = 0; for (tmpface = tmpaasworld.faces; tmpface; tmpface = tmpface->l_next) { if (tmpface->faceflags & FACE_GROUND) { numgroundfaces++; } //end if } //end for qprintf("%6d ground faces\n", numgroundfaces); } //end of the function AAS_PrintNumGroundFaces //=========================================================================== // checks the number of shared faces between the given two areas // since areas are convex they should only have ONE shared face // however due to crappy face merging there are sometimes several // shared faces // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CheckAreaSharedFaces(tmp_area_t *tmparea1, tmp_area_t *tmparea2) { int numsharedfaces, side; tmp_face_t *face1, *sharedface; if (tmparea1->invalid || tmparea2->invalid) return; sharedface = NULL; numsharedfaces = 0; for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) { side = face1->frontarea != tmparea1; if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) { sharedface = face1; numsharedfaces++; } //end if } //end if if (!sharedface) return; //the areas should only have one shared face if (numsharedfaces > 1) { Log_Write("---- tmp area %d and %d have %d shared faces\r\n", tmparea1->areanum, tmparea2->areanum, numsharedfaces); for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) { side = face1->frontarea != tmparea1; if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) { Log_Write("face %d, planenum = %d, face->frontarea = %d face->backarea = %d\r\n", face1->num, face1->planenum, face1->frontarea->areanum, face1->backarea->areanum); } //end if } //end if } //end if } //end of the function AAS_CheckAreaSharedFaces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CheckSharedFaces(void) { tmp_area_t *tmparea1, *tmparea2; for (tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next) { for (tmparea2 = tmpaasworld.areas; tmparea2; tmparea2 = tmparea2->l_next) { if (tmparea1 == tmparea2) continue; AAS_CheckAreaSharedFaces(tmparea1, tmparea2); } //end for } //end for } //end of the function AAS_CheckSharedFaces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FlipFace(tmp_face_t *face) { tmp_area_t *frontarea, *backarea; winding_t *w; frontarea = face->frontarea; backarea = face->backarea; //must have an area at both sides before flipping is allowed if (!frontarea || !backarea) return; //flip the face winding w = face->winding; face->winding = ReverseWinding(w); FreeWinding(w); //flip the face plane face->planenum ^= 1; //flip the face areas AAS_RemoveFaceFromArea(face, frontarea); AAS_RemoveFaceFromArea(face, backarea); AAS_AddFaceSideToArea(face, 1, frontarea); AAS_AddFaceSideToArea(face, 0, backarea); } //end of the function AAS_FlipFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== /* void AAS_FlipAreaSharedFaces(tmp_area_t *tmparea1, tmp_area_t *tmparea2) { int numsharedfaces, side, area1facing, area2facing; tmp_face_t *face1, *sharedface; if (tmparea1->invalid || tmparea2->invalid) return; sharedface = NULL; numsharedfaces = 0; area1facing = 0; //number of shared faces facing towards area 1 area2facing = 0; //number of shared faces facing towards area 2 for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) { side = face1->frontarea != tmparea1; if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) { sharedface = face1; numsharedfaces++; if (face1->frontarea == tmparea1) area1facing++; else area2facing++; } //end if } //end if if (!sharedface) return; //if there's only one shared face if (numsharedfaces <= 1) return; //if all the shared faces are facing to the same area if (numsharedfaces == area1facing || numsharedfaces == area2facing) return; // do { for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side]) { side = face1->frontarea != tmparea1; if (face1->backarea == tmparea2 || face1->frontarea == tmparea2) { if (face1->frontarea != tmparea1) { AAS_FlipFace(face1); break; } //end if } //end if } //end for } while(face1); } //end of the function AAS_FlipAreaSharedFaces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FlipSharedFaces(void) { int i; tmp_area_t *tmparea1, *tmparea2; i = 0; qprintf("%6d areas checked for shared face flipping", i); for (tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next) { if (tmparea1->invalid) continue; for (tmparea2 = tmpaasworld.areas; tmparea2; tmparea2 = tmparea2->l_next) { if (tmparea2->invalid) continue; if (tmparea1 == tmparea2) continue; AAS_FlipAreaSharedFaces(tmparea1, tmparea2); } //end for qprintf("\r%6d", ++i); } //end for Log_Print("\r%6d areas checked for shared face flipping\n", i); } //end of the function AAS_FlipSharedFaces */ //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FlipSharedFaces(void) { int i, side1, side2; tmp_area_t *tmparea1; tmp_face_t *face1, *face2; i = 0; qprintf("%6d areas checked for shared face flipping", i); for (tmparea1 = tmpaasworld.areas; tmparea1; tmparea1 = tmparea1->l_next) { if (tmparea1->invalid) continue; for (face1 = tmparea1->tmpfaces; face1; face1 = face1->next[side1]) { side1 = face1->frontarea != tmparea1; if (!face1->frontarea || !face1->backarea) continue; // for (face2 = face1->next[side1]; face2; face2 = face2->next[side2]) { side2 = face2->frontarea != tmparea1; if (!face2->frontarea || !face2->backarea) continue; // if (face1->frontarea == face2->backarea && face1->backarea == face2->frontarea) { AAS_FlipFace(face2); } //end if //recheck side side2 = face2->frontarea != tmparea1; } //end for } //end for qprintf("\r%6d", ++i); } //end for qprintf("\n"); Log_Write("%6d areas checked for shared face flipping\r\n", i); } //end of the function AAS_FlipSharedFaces //=========================================================================== // creates an .AAS file with the given name // a MAP should be loaded before calling this // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_Create(char *aasfile) { entity_t *e; tree_t *tree; double start_time; //for a possible leak file strcpy(source, aasfile); StripExtension(source); //the time started start_time = I_FloatTime(); //set the default number of threads (depends on number of processors) ThreadSetDefault(); //set the global entity number to the world model entity_num = 0; //the world entity e = &entities[entity_num]; //process the whole world tree = ProcessWorldBrushes(e->firstbrush, e->firstbrush + e->numbrushes); //if the conversion is cancelled if (cancelconversion) { Tree_Free(tree); return; } //end if //display BSP tree creation time Log_Print("BSP tree created in %5.0f seconds\n", I_FloatTime() - start_time); //prune the bsp tree Tree_PruneNodes(tree->headnode); //if the conversion is cancelled if (cancelconversion) { Tree_Free(tree); return; } //end if //create the tree portals MakeTreePortals(tree); //if the conversion is cancelled if (cancelconversion) { Tree_Free(tree); return; } //end if //Marks all nodes that can be reached by entites if (FloodEntities(tree)) { //fill out nodes that can't be reached FillOutside(tree->headnode); } //end if else { LeakFile(tree); Error("**** leaked ****\n"); return; } //end else //create AAS from the BSP tree //========================================== //initialize tmp aas AAS_InitTmpAAS(); //create the convex areas from the leaves AAS_CreateAreas(tree->headnode); //free the BSP tree because it isn't used anymore if (freetree) Tree_Free(tree); //try to merge area faces AAS_MergeAreaFaces(); //do gravitational subdivision AAS_GravitationalSubdivision(); //merge faces if possible AAS_MergeAreaFaces(); AAS_RemoveAreaFaceColinearPoints(); //merge areas if possible AAS_MergeAreas(); //NOTE: prune nodes directly after area merging AAS_PruneNodes(); //flip shared faces so they are all facing to the same area AAS_FlipSharedFaces(); AAS_RemoveAreaFaceColinearPoints(); //merge faces if possible AAS_MergeAreaFaces(); //merge area faces in the same plane AAS_MergeAreaPlaneFaces(); //do ladder subdivision AAS_LadderSubdivision(); //FIXME: melting is buggy AAS_MeltAreaFaceWindings(); //remove tiny faces AAS_RemoveTinyFaces(); //create area settings AAS_CreateAreaSettings(); //check if the winding plane is equal to the face plane //AAS_CheckAreaWindingPlanes(); // //AAS_CheckSharedFaces(); //========================================== //if the conversion is cancelled if (cancelconversion) { Tree_Free(tree); AAS_FreeTmpAAS(); return; } //end if //store the created AAS stuff in the AAS file format and write the file AAS_StoreFile(aasfile); //free the temporary AAS memory AAS_FreeTmpAAS(); //display creation time Log_Print("\nAAS created in %5.0f seconds\n", I_FloatTime() - start_time); } //end of the function AAS_Create ================================================ FILE: code/bspc/aas_create.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #define AREA_PORTAL 1 //temporary AAS face typedef struct tmp_face_s { int num; //face number int planenum; //number of the plane the face is in winding_t *winding; //winding of the face struct tmp_area_s *frontarea; //area at the front of the face struct tmp_area_s *backarea; //area at the back of the face int faceflags; //flags of this face int aasfacenum; //the number of the aas face used for this face //double link list pointers for front and back area struct tmp_face_s *prev[2], *next[2]; //links in the list with faces struct tmp_face_s *l_prev, *l_next; } tmp_face_t; //temporary AAS area settings typedef struct tmp_areasettings_s { //could also add all kind of statistic fields int contents; //contents of the area int modelnum; //bsp model inside this area int areaflags; //area flags int presencetype; //how a bot can be present in this area int numreachableareas; //number of reachable areas from this one int firstreachablearea; //first reachable area in the reachable area index } tmp_areasettings_t; //temporary AAS area typedef struct tmp_area_s { int areanum; //number of the area struct tmp_face_s *tmpfaces; //the faces of the area int presencetype; //presence type of the area int contents; //area contents int modelnum; //bsp model inside this area int invalid; //true if the area is invalid tmp_areasettings_t *settings; //area settings struct tmp_area_s *mergedarea; //points to the new area after merging //when mergedarea != 0 the area has only the //seperating face of the merged areas int aasareanum; //number of the aas area created for this tmp area //links in the list with areas struct tmp_area_s *l_prev, *l_next; } tmp_area_t; //temporary AAS node typedef struct tmp_node_s { int planenum; //node plane number struct tmp_area_s *tmparea; //points to an area if this node is an area struct tmp_node_s *children[2]; //child nodes of this node } tmp_node_t; #define NODEBUF_SIZE 128 //node buffer typedef struct tmp_nodebuf_s { int numnodes; struct tmp_nodebuf_s *next; tmp_node_t nodes[NODEBUF_SIZE]; } tmp_nodebuf_t; //the whole temorary AAS typedef struct tmp_aas_s { //faces int numfaces; int facenum; tmp_face_t *faces; //areas int numareas; int areanum; tmp_area_t *areas; //area settings int numareasettings; tmp_areasettings_t *areasettings; //nodes int numnodes; tmp_node_t *nodes; //node buffer tmp_nodebuf_t *nodebuffer; } tmp_aas_t; extern tmp_aas_t tmpaasworld; //creates a .AAS file with the given name from an already loaded map void AAS_Create(char *aasfile); //adds a face side to an area void AAS_AddFaceSideToArea(tmp_face_t *tmpface, int side, tmp_area_t *tmparea); //remvoes a face from an area void AAS_RemoveFaceFromArea(tmp_face_t *tmpface, tmp_area_t *tmparea); //allocate a tmp face tmp_face_t *AAS_AllocTmpFace(void); //free the tmp face void AAS_FreeTmpFace(tmp_face_t *tmpface); //allocate a tmp area tmp_area_t *AAS_AllocTmpArea(void); //free a tmp area void AAS_FreeTmpArea(tmp_area_t *tmparea); //allocate a tmp node tmp_node_t *AAS_AllocTmpNode(void); //free a tmp node void AAS_FreeTmpNode(tmp_node_t *node); //checks if an area is ok void AAS_CheckArea(tmp_area_t *tmparea); //flips the area faces where needed void AAS_FlipAreaFaces(tmp_area_t *tmparea); //returns true if the face is a gap seen from the given side int AAS_GapFace(tmp_face_t *tmpface, int side); //returns true if the face is a ground face int AAS_GroundFace(tmp_face_t *tmpface); ================================================ FILE: code/bspc/aas_edgemelting.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "../botlib/aasfile.h" #include "aas_create.h" //=========================================================================== // try to melt the windings of the two faces // FIXME: this is buggy // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_MeltFaceWinding(tmp_face_t *face1, tmp_face_t *face2) { int i, n; int splits = 0; winding_t *w2, *neww; plane_t *plane1; #ifdef DEBUG if (!face1->winding) Error("face1 %d without winding", face1->num); if (!face2->winding) Error("face2 %d without winding", face2->num); #endif //DEBUG w2 = face2->winding; plane1 = &mapplanes[face1->planenum]; for (i = 0; i < w2->numpoints; i++) { if (PointOnWinding(face1->winding, plane1->normal, plane1->dist, w2->p[i], &n)) { neww = AddWindingPoint(face1->winding, w2->p[i], n); FreeWinding(face1->winding); face1->winding = neww; splits++; } //end if } //end for return splits; } //end of the function AAS_MeltFaceWinding //=========================================================================== // melt the windings of the area faces // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_MeltFaceWindingsOfArea(tmp_area_t *tmparea) { int side1, side2, num_windingsplits = 0; tmp_face_t *face1, *face2; for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) { side1 = face1->frontarea != tmparea; for (face2 = tmparea->tmpfaces; face2; face2 = face2->next[side2]) { side2 = face2->frontarea != tmparea; if (face1 == face2) continue; num_windingsplits += AAS_MeltFaceWinding(face1, face2); } //end for } //end for return num_windingsplits; } //end of the function AAS_MeltFaceWindingsOfArea //=========================================================================== // melt the windings of the faces of all areas // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_MeltAreaFaceWindings(void) { tmp_area_t *tmparea; int num_windingsplits = 0; Log_Write("AAS_MeltAreaFaceWindings\r\n"); qprintf("%6d edges melted", num_windingsplits); //NOTE: first convex area (zero) is a dummy for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) { num_windingsplits += AAS_MeltFaceWindingsOfArea(tmparea); qprintf("\r%6d", num_windingsplits); } //end for qprintf("\n"); Log_Write("%6d edges melted\r\n", num_windingsplits); } //end of the function AAS_MeltAreaFaceWindings ================================================ FILE: code/bspc/aas_edgemelting.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ void AAS_MeltAreaFaceWindings(void); ================================================ FILE: code/bspc/aas_facemerging.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "../botlib/aasfile.h" #include "aas_create.h" //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_TryMergeFaces(tmp_face_t *face1, tmp_face_t *face2) { winding_t *neww; #ifdef DEBUG if (!face1->winding) Error("face1 %d without winding", face1->num); if (!face2->winding) Error("face2 %d without winding", face2->num); #endif //DEBUG // if (face1->faceflags != face2->faceflags) return false; //NOTE: if the front or back area is zero this doesn't mean there's //a real area. It means there's solid at that side of the face //if both faces have the same front area if (face1->frontarea == face2->frontarea) { //if both faces have the same back area if (face1->backarea == face2->backarea) { //if the faces are in the same plane if (face1->planenum == face2->planenum) { //if they have both a front and a back area (no solid on either side) if (face1->frontarea && face1->backarea) { neww = MergeWindings(face1->winding, face2->winding, mapplanes[face1->planenum].normal); } //end if else { //this function is to be found in l_poly.c neww = TryMergeWinding(face1->winding, face2->winding, mapplanes[face1->planenum].normal); } //end else if (neww) { FreeWinding(face1->winding); face1->winding = neww; if (face2->frontarea) AAS_RemoveFaceFromArea(face2, face2->frontarea); if (face2->backarea) AAS_RemoveFaceFromArea(face2, face2->backarea); AAS_FreeTmpFace(face2); return true; } //end if } //end if else if ((face1->planenum & ~1) == (face2->planenum & ~1)) { Log_Write("face %d and %d, same front and back area but flipped planes\r\n", face1->num, face2->num); } //end if } //end if } //end if return false; } //end of the function AAS_TryMergeFaces /* int AAS_TryMergeFaces(tmp_face_t *face1, tmp_face_t *face2) { winding_t *neww; #ifdef DEBUG if (!face1->winding) Error("face1 %d without winding", face1->num); if (!face2->winding) Error("face2 %d without winding", face2->num); #endif //DEBUG //if the faces are in the same plane if ((face1->planenum & ~1) != (face2->planenum & ~1)) return false; // if (face1->planenum != face2->planenum) return false; //NOTE: if the front or back area is zero this doesn't mean there's //a real area. It means there's solid at that side of the face //if both faces have the same front area if (face1->frontarea != face2->frontarea || face1->backarea != face2->backarea) { if (!face1->frontarea || !face1->backarea || !face2->frontarea || !face2->backarea) return false; else if (face1->frontarea != face2->backarea || face1->backarea != face2->frontarea) return false; // return false; } //end if //this function is to be found in l_poly.c neww = TryMergeWinding(face1->winding, face2->winding, mapplanes[face1->planenum].normal); if (!neww) return false; // FreeWinding(face1->winding); face1->winding = neww; //remove face2 if (face2->frontarea) AAS_RemoveFaceFromArea(face2, &tmpaasworld.areas[face2->frontarea]); if (face2->backarea) AAS_RemoveFaceFromArea(face2, &tmpaasworld.areas[face2->backarea]); return true; } //end of the function AAS_TryMergeFaces*/ //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_MergeAreaFaces(void) { int num_facemerges = 0; int side1, side2, restart; tmp_area_t *tmparea, *lasttmparea; tmp_face_t *face1, *face2; Log_Write("AAS_MergeAreaFaces\r\n"); qprintf("%6d face merges", num_facemerges); //NOTE: first convex area is a dummy lasttmparea = tmpaasworld.areas; for (tmparea = tmpaasworld.areas; tmparea; tmparea = tmparea->l_next) { restart = false; // if (tmparea->invalid) continue; // for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) { side1 = face1->frontarea != tmparea; for (face2 = face1->next[side1]; face2; face2 = face2->next[side2]) { side2 = face2->frontarea != tmparea; //if succesfully merged if (AAS_TryMergeFaces(face1, face2)) { //start over again after merging two faces restart = true; num_facemerges++; qprintf("\r%6d", num_facemerges); AAS_CheckArea(tmparea); break; } //end if } //end for if (restart) { tmparea = lasttmparea; break; } //end if } //end for lasttmparea = tmparea; } //end for qprintf("\n"); Log_Write("%6d face merges\r\n", num_facemerges); } //end of the function AAS_MergeAreaFaces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_MergePlaneFaces(tmp_area_t *tmparea, int planenum) { tmp_face_t *face1, *face2, *nextface2; winding_t *neww; int side1, side2; for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) { side1 = face1->frontarea != tmparea; if (face1->planenum != planenum) continue; // for (face2 = face1->next[side1]; face2; face2 = nextface2) { side2 = face2->frontarea != tmparea; nextface2 = face2->next[side2]; // if ((face2->planenum & ~1) != (planenum & ~1)) continue; // neww = MergeWindings(face1->winding, face2->winding, mapplanes[face1->planenum].normal); FreeWinding(face1->winding); face1->winding = neww; if (face2->frontarea) AAS_RemoveFaceFromArea(face2, face2->frontarea); if (face2->backarea) AAS_RemoveFaceFromArea(face2, face2->backarea); AAS_FreeTmpFace(face2); // nextface2 = face1->next[side1]; } //end for } //end for } //end of the function AAS_MergePlaneFaces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_CanMergePlaneFaces(tmp_area_t *tmparea, int planenum) { tmp_area_t *frontarea, *backarea; tmp_face_t *face1; int side1, merge, faceflags; frontarea = backarea = NULL; merge = false; for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) { side1 = face1->frontarea != tmparea; if ((face1->planenum & ~1) != (planenum & ~1)) continue; if (!frontarea && !backarea) { frontarea = face1->frontarea; backarea = face1->backarea; faceflags = face1->faceflags; } //end if else { if (frontarea != face1->frontarea) return false; if (backarea != face1->backarea) return false; if (faceflags != face1->faceflags) return false; merge = true; } //end else } //end for return merge; } //end of the function AAS_CanMergePlaneFaces //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_MergeAreaPlaneFaces(void) { int num_facemerges = 0; int side1; tmp_area_t *tmparea, *nexttmparea; tmp_face_t *face1; Log_Write("AAS_MergePlaneFaces\r\n"); qprintf("%6d plane face merges", num_facemerges); //NOTE: first convex area is a dummy for (tmparea = tmpaasworld.areas; tmparea; tmparea = nexttmparea) { nexttmparea = tmparea->l_next; // if (tmparea->invalid) continue; // for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) { side1 = face1->frontarea != tmparea; // if (AAS_CanMergePlaneFaces(tmparea, face1->planenum)) { AAS_MergePlaneFaces(tmparea, face1->planenum); nexttmparea = tmparea; num_facemerges++; qprintf("\r%6d", num_facemerges); break; } //end if } //end for } //end for qprintf("\n"); Log_Write("%6d plane face merges\r\n", num_facemerges); } //end of the function AAS_MergeAreaPlaneFaces ================================================ FILE: code/bspc/aas_facemerging.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ void AAS_MergeAreaFaces(void); void AAS_MergeAreaPlaneFaces(void); ================================================ FILE: code/bspc/aas_file.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "../botlib/aasfile.h" #include "aas_file.h" #include "aas_store.h" #include "aas_create.h" #define AAS_Error Error //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_SwapAASData(void) { int i, j; //bounding boxes for (i = 0; i < aasworld.numbboxes; i++) { aasworld.bboxes[i].presencetype = LittleLong(aasworld.bboxes[i].presencetype); aasworld.bboxes[i].flags = LittleLong(aasworld.bboxes[i].flags); for (j = 0; j < 3; j++) { aasworld.bboxes[i].mins[j] = LittleLong(aasworld.bboxes[i].mins[j]); aasworld.bboxes[i].maxs[j] = LittleLong(aasworld.bboxes[i].maxs[j]); } //end for } //end for //vertexes for (i = 0; i < aasworld.numvertexes; i++) { for (j = 0; j < 3; j++) aasworld.vertexes[i][j] = LittleFloat(aasworld.vertexes[i][j]); } //end for //planes for (i = 0; i < aasworld.numplanes; i++) { for (j = 0; j < 3; j++) aasworld.planes[i].normal[j] = LittleFloat(aasworld.planes[i].normal[j]); aasworld.planes[i].dist = LittleFloat(aasworld.planes[i].dist); aasworld.planes[i].type = LittleLong(aasworld.planes[i].type); } //end for //edges for (i = 0; i < aasworld.numedges; i++) { aasworld.edges[i].v[0] = LittleLong(aasworld.edges[i].v[0]); aasworld.edges[i].v[1] = LittleLong(aasworld.edges[i].v[1]); } //end for //edgeindex for (i = 0; i < aasworld.edgeindexsize; i++) { aasworld.edgeindex[i] = LittleLong(aasworld.edgeindex[i]); } //end for //faces for (i = 0; i < aasworld.numfaces; i++) { aasworld.faces[i].planenum = LittleLong(aasworld.faces[i].planenum); aasworld.faces[i].faceflags = LittleLong(aasworld.faces[i].faceflags); aasworld.faces[i].numedges = LittleLong(aasworld.faces[i].numedges); aasworld.faces[i].firstedge = LittleLong(aasworld.faces[i].firstedge); aasworld.faces[i].frontarea = LittleLong(aasworld.faces[i].frontarea); aasworld.faces[i].backarea = LittleLong(aasworld.faces[i].backarea); } //end for //face index for (i = 0; i < aasworld.faceindexsize; i++) { aasworld.faceindex[i] = LittleLong(aasworld.faceindex[i]); } //end for //convex areas for (i = 0; i < aasworld.numareas; i++) { aasworld.areas[i].areanum = LittleLong(aasworld.areas[i].areanum); aasworld.areas[i].numfaces = LittleLong(aasworld.areas[i].numfaces); aasworld.areas[i].firstface = LittleLong(aasworld.areas[i].firstface); for (j = 0; j < 3; j++) { aasworld.areas[i].mins[j] = LittleFloat(aasworld.areas[i].mins[j]); aasworld.areas[i].maxs[j] = LittleFloat(aasworld.areas[i].maxs[j]); aasworld.areas[i].center[j] = LittleFloat(aasworld.areas[i].center[j]); } //end for } //end for //area settings for (i = 0; i < aasworld.numareasettings; i++) { aasworld.areasettings[i].contents = LittleLong(aasworld.areasettings[i].contents); aasworld.areasettings[i].areaflags = LittleLong(aasworld.areasettings[i].areaflags); aasworld.areasettings[i].presencetype = LittleLong(aasworld.areasettings[i].presencetype); aasworld.areasettings[i].cluster = LittleLong(aasworld.areasettings[i].cluster); aasworld.areasettings[i].clusterareanum = LittleLong(aasworld.areasettings[i].clusterareanum); aasworld.areasettings[i].numreachableareas = LittleLong(aasworld.areasettings[i].numreachableareas); aasworld.areasettings[i].firstreachablearea = LittleLong(aasworld.areasettings[i].firstreachablearea); } //end for //area reachability for (i = 0; i < aasworld.reachabilitysize; i++) { aasworld.reachability[i].areanum = LittleLong(aasworld.reachability[i].areanum); aasworld.reachability[i].facenum = LittleLong(aasworld.reachability[i].facenum); aasworld.reachability[i].edgenum = LittleLong(aasworld.reachability[i].edgenum); for (j = 0; j < 3; j++) { aasworld.reachability[i].start[j] = LittleFloat(aasworld.reachability[i].start[j]); aasworld.reachability[i].end[j] = LittleFloat(aasworld.reachability[i].end[j]); } //end for aasworld.reachability[i].traveltype = LittleLong(aasworld.reachability[i].traveltype); aasworld.reachability[i].traveltime = LittleShort(aasworld.reachability[i].traveltime); } //end for //nodes for (i = 0; i < aasworld.numnodes; i++) { aasworld.nodes[i].planenum = LittleLong(aasworld.nodes[i].planenum); aasworld.nodes[i].children[0] = LittleLong(aasworld.nodes[i].children[0]); aasworld.nodes[i].children[1] = LittleLong(aasworld.nodes[i].children[1]); } //end for //cluster portals for (i = 0; i < aasworld.numportals; i++) { aasworld.portals[i].areanum = LittleLong(aasworld.portals[i].areanum); aasworld.portals[i].frontcluster = LittleLong(aasworld.portals[i].frontcluster); aasworld.portals[i].backcluster = LittleLong(aasworld.portals[i].backcluster); aasworld.portals[i].clusterareanum[0] = LittleLong(aasworld.portals[i].clusterareanum[0]); aasworld.portals[i].clusterareanum[1] = LittleLong(aasworld.portals[i].clusterareanum[1]); } //end for //cluster portal index for (i = 0; i < aasworld.portalindexsize; i++) { aasworld.portalindex[i] = LittleLong(aasworld.portalindex[i]); } //end for //cluster for (i = 0; i < aasworld.numclusters; i++) { aasworld.clusters[i].numareas = LittleLong(aasworld.clusters[i].numareas); aasworld.clusters[i].numportals = LittleLong(aasworld.clusters[i].numportals); aasworld.clusters[i].firstportal = LittleLong(aasworld.clusters[i].firstportal); } //end for } //end of the function AAS_SwapAASData //=========================================================================== // dump the current loaded aas file // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DumpAASData(void) { /* if (aasworld.vertexes) FreeMemory(aasworld.vertexes); aasworld.vertexes = NULL; if (aasworld.planes) FreeMemory(aasworld.planes); aasworld.planes = NULL; if (aasworld.edges) FreeMemory(aasworld.edges); aasworld.edges = NULL; if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); aasworld.edgeindex = NULL; if (aasworld.faces) FreeMemory(aasworld.faces); aasworld.faces = NULL; if (aasworld.faceindex) FreeMemory(aasworld.faceindex); aasworld.faceindex = NULL; if (aasworld.areas) FreeMemory(aasworld.areas); aasworld.areas = NULL; if (aasworld.areasettings) FreeMemory(aasworld.areasettings); aasworld.areasettings = NULL; if (aasworld.reachability) FreeMemory(aasworld.reachability); aasworld.reachability = NULL; */ aasworld.loaded = false; } //end of the function AAS_DumpAASData //=========================================================================== // allocate memory and read a lump of a AAS file // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *AAS_LoadAASLump(FILE *fp, int offset, int length, void *buf) { if (!length) { printf("lump size 0\n"); return buf; } //end if //seek to the data if (fseek(fp, offset, SEEK_SET)) { AAS_Error("can't seek to lump\n"); AAS_DumpAASData(); fclose(fp); return 0; } //end if //allocate memory if (!buf) buf = (void *) GetClearedMemory(length); //read the data if (fread((char *) buf, 1, length, fp) != length) { AAS_Error("can't read lump\n"); FreeMemory(buf); AAS_DumpAASData(); fclose(fp); return NULL; } //end if return buf; } //end of the function AAS_LoadAASLump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DData(unsigned char *data, int size) { int i; for (i = 0; i < size; i++) { data[i] ^= (unsigned char) i * 119; } //end for } //end of the function AAS_DData //=========================================================================== // load an aas file // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_LoadAASFile(char *filename, int fpoffset, int fplength) { FILE *fp; aas_header_t header; int offset, length; //dump current loaded aas file AAS_DumpAASData(); //open the file fp = fopen(filename, "rb"); if (!fp) { AAS_Error("can't open %s\n", filename); return false; } //end if //seek to the correct position (in the pak file) if (fseek(fp, fpoffset, SEEK_SET)) { AAS_Error("can't seek to file %s\n"); fclose(fp); return false; } //end if //read the header if (fread(&header, sizeof(aas_header_t), 1, fp) != 1) { AAS_Error("can't read header of file %s\n", filename); fclose(fp); return false; } //end if //check header identification header.ident = LittleLong(header.ident); if (header.ident != AASID) { AAS_Error("%s is not an AAS file\n", filename); fclose(fp); return false; } //end if //check the version header.version = LittleLong(header.version); if (header.version != AASVERSION_OLD && header.version != AASVERSION) { AAS_Error("%s is version %i, not %i\n", filename, header.version, AASVERSION); fclose(fp); return false; } //end if // if (header.version == AASVERSION) { AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); } //end if aasworld.bspchecksum = LittleLong(header.bspchecksum); //load the lumps: //bounding boxes offset = fpoffset + LittleLong(header.lumps[AASLUMP_BBOXES].fileofs); length = LittleLong(header.lumps[AASLUMP_BBOXES].filelen); aasworld.bboxes = (aas_bbox_t *) AAS_LoadAASLump(fp, offset, length, aasworld.bboxes); if (!aasworld.bboxes) return false; aasworld.numbboxes = length / sizeof(aas_bbox_t); //vertexes offset = fpoffset + LittleLong(header.lumps[AASLUMP_VERTEXES].fileofs); length = LittleLong(header.lumps[AASLUMP_VERTEXES].filelen); aasworld.vertexes = (aas_vertex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.vertexes); if (!aasworld.vertexes) return false; aasworld.numvertexes = length / sizeof(aas_vertex_t); //planes offset = fpoffset + LittleLong(header.lumps[AASLUMP_PLANES].fileofs); length = LittleLong(header.lumps[AASLUMP_PLANES].filelen); aasworld.planes = (aas_plane_t *) AAS_LoadAASLump(fp, offset, length, aasworld.planes); if (!aasworld.planes) return false; aasworld.numplanes = length / sizeof(aas_plane_t); //edges offset = fpoffset + LittleLong(header.lumps[AASLUMP_EDGES].fileofs); length = LittleLong(header.lumps[AASLUMP_EDGES].filelen); aasworld.edges = (aas_edge_t *) AAS_LoadAASLump(fp, offset, length, aasworld.edges); if (!aasworld.edges) return false; aasworld.numedges = length / sizeof(aas_edge_t); //edgeindex offset = fpoffset + LittleLong(header.lumps[AASLUMP_EDGEINDEX].fileofs); length = LittleLong(header.lumps[AASLUMP_EDGEINDEX].filelen); aasworld.edgeindex = (aas_edgeindex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.edgeindex); if (!aasworld.edgeindex) return false; aasworld.edgeindexsize = length / sizeof(aas_edgeindex_t); //faces offset = fpoffset + LittleLong(header.lumps[AASLUMP_FACES].fileofs); length = LittleLong(header.lumps[AASLUMP_FACES].filelen); aasworld.faces = (aas_face_t *) AAS_LoadAASLump(fp, offset, length, aasworld.faces); if (!aasworld.faces) return false; aasworld.numfaces = length / sizeof(aas_face_t); //faceindex offset = fpoffset + LittleLong(header.lumps[AASLUMP_FACEINDEX].fileofs); length = LittleLong(header.lumps[AASLUMP_FACEINDEX].filelen); aasworld.faceindex = (aas_faceindex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.faceindex); if (!aasworld.faceindex) return false; aasworld.faceindexsize = length / sizeof(int); //convex areas offset = fpoffset + LittleLong(header.lumps[AASLUMP_AREAS].fileofs); length = LittleLong(header.lumps[AASLUMP_AREAS].filelen); aasworld.areas = (aas_area_t *) AAS_LoadAASLump(fp, offset, length, aasworld.areas); if (!aasworld.areas) return false; aasworld.numareas = length / sizeof(aas_area_t); //area settings offset = fpoffset + LittleLong(header.lumps[AASLUMP_AREASETTINGS].fileofs); length = LittleLong(header.lumps[AASLUMP_AREASETTINGS].filelen); aasworld.areasettings = (aas_areasettings_t *) AAS_LoadAASLump(fp, offset, length, aasworld.areasettings); if (!aasworld.areasettings) return false; aasworld.numareasettings = length / sizeof(aas_areasettings_t); //reachability list offset = fpoffset + LittleLong(header.lumps[AASLUMP_REACHABILITY].fileofs); length = LittleLong(header.lumps[AASLUMP_REACHABILITY].filelen); aasworld.reachability = (aas_reachability_t *) AAS_LoadAASLump(fp, offset, length, aasworld.reachability); if (length && !aasworld.reachability) return false; aasworld.reachabilitysize = length / sizeof(aas_reachability_t); //nodes offset = fpoffset + LittleLong(header.lumps[AASLUMP_NODES].fileofs); length = LittleLong(header.lumps[AASLUMP_NODES].filelen); aasworld.nodes = (aas_node_t *) AAS_LoadAASLump(fp, offset, length, aasworld.nodes); if (!aasworld.nodes) return false; aasworld.numnodes = length / sizeof(aas_node_t); //cluster portals offset = fpoffset + LittleLong(header.lumps[AASLUMP_PORTALS].fileofs); length = LittleLong(header.lumps[AASLUMP_PORTALS].filelen); aasworld.portals = (aas_portal_t *) AAS_LoadAASLump(fp, offset, length, aasworld.portals); if (length && !aasworld.portals) return false; aasworld.numportals = length / sizeof(aas_portal_t); //cluster portal index offset = fpoffset + LittleLong(header.lumps[AASLUMP_PORTALINDEX].fileofs); length = LittleLong(header.lumps[AASLUMP_PORTALINDEX].filelen); aasworld.portalindex = (aas_portalindex_t *) AAS_LoadAASLump(fp, offset, length, aasworld.portalindex); if (length && !aasworld.portalindex) return false; aasworld.portalindexsize = length / sizeof(aas_portalindex_t); //clusters offset = fpoffset + LittleLong(header.lumps[AASLUMP_CLUSTERS].fileofs); length = LittleLong(header.lumps[AASLUMP_CLUSTERS].filelen); aasworld.clusters = (aas_cluster_t *) AAS_LoadAASLump(fp, offset, length, aasworld.clusters); if (length && !aasworld.clusters) return false; aasworld.numclusters = length / sizeof(aas_cluster_t); //swap everything AAS_SwapAASData(); //aas file is loaded aasworld.loaded = true; //close the file fclose(fp); return true; } //end of the function AAS_LoadAASFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_WriteAASLump(FILE *fp, aas_header_t *h, int lumpnum, void *data, int length) { aas_lump_t *lump; lump = &h->lumps[lumpnum]; lump->fileofs = LittleLong(ftell(fp)); lump->filelen = LittleLong(length); if (length > 0) { if (fwrite(data, length, 1, fp) < 1) { Log_Print("error writing lump %s\n", lumpnum); fclose(fp); return false; } //end if } //end if return true; } //end of the function AAS_WriteAASLump //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShowNumReachabilities(int tt, char *name) { int i, num; num = 0; for (i = 0; i < aasworld.reachabilitysize; i++) { if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == tt) num++; } //end for Log_Print("%6d %s\n", num, name); } //end of the function AAS_ShowNumReachabilities //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ShowTotals(void) { Log_Print("numvertexes = %d\r\n", aasworld.numvertexes); Log_Print("numplanes = %d\r\n", aasworld.numplanes); Log_Print("numedges = %d\r\n", aasworld.numedges); Log_Print("edgeindexsize = %d\r\n", aasworld.edgeindexsize); Log_Print("numfaces = %d\r\n", aasworld.numfaces); Log_Print("faceindexsize = %d\r\n", aasworld.faceindexsize); Log_Print("numareas = %d\r\n", aasworld.numareas); Log_Print("numareasettings = %d\r\n", aasworld.numareasettings); Log_Print("reachabilitysize = %d\r\n", aasworld.reachabilitysize); Log_Print("numnodes = %d\r\n", aasworld.numnodes); Log_Print("numportals = %d\r\n", aasworld.numportals); Log_Print("portalindexsize = %d\r\n", aasworld.portalindexsize); Log_Print("numclusters = %d\r\n", aasworld.numclusters); AAS_ShowNumReachabilities(TRAVEL_WALK, "walk"); AAS_ShowNumReachabilities(TRAVEL_CROUCH, "crouch"); AAS_ShowNumReachabilities(TRAVEL_BARRIERJUMP, "barrier jump"); AAS_ShowNumReachabilities(TRAVEL_JUMP, "jump"); AAS_ShowNumReachabilities(TRAVEL_LADDER, "ladder"); AAS_ShowNumReachabilities(TRAVEL_WALKOFFLEDGE, "walk off ledge"); AAS_ShowNumReachabilities(TRAVEL_SWIM, "swim"); AAS_ShowNumReachabilities(TRAVEL_WATERJUMP, "water jump"); AAS_ShowNumReachabilities(TRAVEL_TELEPORT, "teleport"); AAS_ShowNumReachabilities(TRAVEL_ELEVATOR, "elevator"); AAS_ShowNumReachabilities(TRAVEL_ROCKETJUMP, "rocket jump"); AAS_ShowNumReachabilities(TRAVEL_BFGJUMP, "bfg jump"); AAS_ShowNumReachabilities(TRAVEL_GRAPPLEHOOK, "grapple hook"); AAS_ShowNumReachabilities(TRAVEL_DOUBLEJUMP, "double jump"); AAS_ShowNumReachabilities(TRAVEL_RAMPJUMP, "ramp jump"); AAS_ShowNumReachabilities(TRAVEL_STRAFEJUMP, "strafe jump"); AAS_ShowNumReachabilities(TRAVEL_JUMPPAD, "jump pad"); AAS_ShowNumReachabilities(TRAVEL_FUNCBOB, "func bob"); } //end of the function AAS_ShowTotals //=========================================================================== // aas data is useless after writing to file because it is byte swapped // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_WriteAASFile(char *filename) { aas_header_t header; FILE *fp; Log_Print("writing %s\n", filename); AAS_ShowTotals(); //swap the aas data AAS_SwapAASData(); //initialize the file header memset(&header, 0, sizeof(aas_header_t)); header.ident = LittleLong(AASID); header.version = LittleLong(AASVERSION); header.bspchecksum = LittleLong(aasworld.bspchecksum); //open a new file fp = fopen(filename, "wb"); if (!fp) { Log_Print("error opening %s\n", filename); return false; } //end if //write the header if (fwrite(&header, sizeof(aas_header_t), 1, fp) < 1) { fclose(fp); return false; } //end if //add the data lumps to the file if (!AAS_WriteAASLump(fp, &header, AASLUMP_BBOXES, aasworld.bboxes, aasworld.numbboxes * sizeof(aas_bbox_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_VERTEXES, aasworld.vertexes, aasworld.numvertexes * sizeof(aas_vertex_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_PLANES, aasworld.planes, aasworld.numplanes * sizeof(aas_plane_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGES, aasworld.edges, aasworld.numedges * sizeof(aas_edge_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGEINDEX, aasworld.edgeindex, aasworld.edgeindexsize * sizeof(aas_edgeindex_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACES, aasworld.faces, aasworld.numfaces * sizeof(aas_face_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACEINDEX, aasworld.faceindex, aasworld.faceindexsize * sizeof(aas_faceindex_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREAS, aasworld.areas, aasworld.numareas * sizeof(aas_area_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREASETTINGS, aasworld.areasettings, aasworld.numareasettings * sizeof(aas_areasettings_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_REACHABILITY, aasworld.reachability, aasworld.reachabilitysize * sizeof(aas_reachability_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_NODES, aasworld.nodes, aasworld.numnodes * sizeof(aas_node_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALS, aasworld.portals, aasworld.numportals * sizeof(aas_portal_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALINDEX, aasworld.portalindex, aasworld.portalindexsize * sizeof(aas_portalindex_t))) return false; if (!AAS_WriteAASLump(fp, &header, AASLUMP_CLUSTERS, aasworld.clusters, aasworld.numclusters * sizeof(aas_cluster_t))) return false; //rewrite the header with the added lumps fseek(fp, 0, SEEK_SET); AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); if (fwrite(&header, sizeof(aas_header_t), 1, fp) < 1) { fclose(fp); return false; } //end if //close the file fclose(fp); return true; } //end of the function AAS_WriteAASFile ================================================ FILE: code/bspc/aas_file.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ qboolean AAS_WriteAASFile(char *filename); qboolean AAS_LoadAASFile(char *filename, int fpoffset, int fplength); ================================================ FILE: code/bspc/aas_gsubdiv.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "../botlib/aasfile.h" #include "aas_create.h" #include "aas_store.h" #include "aas_cfg.h" #define FACECLIP_EPSILON 0.2 #define FACE_EPSILON 1.0 int numgravitationalsubdivisions = 0; int numladdersubdivisions = 0; //NOTE: only do gravitational subdivision BEFORE area merging!!!!!!! // because the bsp tree isn't refreshes like with ladder subdivision //=========================================================================== // NOTE: the original face is invalid after splitting // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_SplitFace(tmp_face_t *face, vec3_t normal, float dist, tmp_face_t **frontface, tmp_face_t **backface) { winding_t *frontw, *backw; // *frontface = *backface = NULL; ClipWindingEpsilon(face->winding, normal, dist, FACECLIP_EPSILON, &frontw, &backw); #ifdef DEBUG // if (frontw) { if (WindingIsTiny(frontw)) { Log_Write("AAS_SplitFace: tiny back face\r\n"); FreeWinding(frontw); frontw = NULL; } //end if } //end if if (backw) { if (WindingIsTiny(backw)) { Log_Write("AAS_SplitFace: tiny back face\r\n"); FreeWinding(backw); backw = NULL; } //end if } //end if #endif //DEBUG //if the winding was split if (frontw) { //check bounds (*frontface) = AAS_AllocTmpFace(); (*frontface)->planenum = face->planenum; (*frontface)->winding = frontw; (*frontface)->faceflags = face->faceflags; } //end if if (backw) { //check bounds (*backface) = AAS_AllocTmpFace(); (*backface)->planenum = face->planenum; (*backface)->winding = backw; (*backface)->faceflags = face->faceflags; } //end if } //end of the function AAS_SplitFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== winding_t *AAS_SplitWinding(tmp_area_t *tmparea, int planenum) { tmp_face_t *face; plane_t *plane; int side; winding_t *splitwinding; // plane = &mapplanes[planenum]; //create a split winding, first base winding for plane splitwinding = BaseWindingForPlane(plane->normal, plane->dist); //chop with all the faces of the area for (face = tmparea->tmpfaces; face && splitwinding; face = face->next[side]) { //side of the face the original area was on side = face->frontarea != tmparea; plane = &mapplanes[face->planenum ^ side]; ChopWindingInPlace(&splitwinding, plane->normal, plane->dist, 0); // PLANESIDE_EPSILON); } //end for return splitwinding; } //end of the function AAS_SplitWinding //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_TestSplitPlane(tmp_area_t *tmparea, vec3_t normal, float dist, int *facesplits, int *groundsplits, int *epsilonfaces) { int j, side, front, back, planenum; float d, d_front, d_back; tmp_face_t *face; winding_t *w; *facesplits = *groundsplits = *epsilonfaces = 0; planenum = FindFloatPlane(normal, dist); w = AAS_SplitWinding(tmparea, planenum); if (!w) return false; FreeWinding(w); // for (face = tmparea->tmpfaces; face; face = face->next[side]) { //side of the face the area is on side = face->frontarea != tmparea; if ((face->planenum & ~1) == (planenum & ~1)) { Log_Print("AAS_TestSplitPlane: tried face plane as splitter\n"); return false; } //end if w = face->winding; //reset distance at front and back side of plane d_front = d_back = 0; //reset front and back flags front = back = 0; for (j = 0; j < w->numpoints; j++) { d = DotProduct(w->p[j], normal) - dist; if (d > d_front) d_front = d; if (d < d_back) d_back = d; if (d > 0.4) // PLANESIDE_EPSILON) front = 1; if (d < -0.4) // PLANESIDE_EPSILON) back = 1; } //end for //check for an epsilon face if ( (d_front > FACECLIP_EPSILON && d_front < FACE_EPSILON) || (d_back < -FACECLIP_EPSILON && d_back > -FACE_EPSILON) ) { (*epsilonfaces)++; } //end if //if the face has points at both sides of the plane if (front && back) { (*facesplits)++; if (face->faceflags & FACE_GROUND) { (*groundsplits)++; } //end if } //end if } //end for return true; } //end of the function AAS_TestSplitPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_SplitArea(tmp_area_t *tmparea, int planenum, tmp_area_t **frontarea, tmp_area_t **backarea) { int side; tmp_area_t *facefrontarea, *facebackarea, *faceotherarea; tmp_face_t *face, *frontface, *backface, *splitface, *nextface; winding_t *splitwinding; plane_t *splitplane; /* #ifdef AW_DEBUG int facesplits, groundsplits, epsilonface; Log_Print("\n----------------------\n"); Log_Print("splitting area %d\n", areanum); Log_Print("with normal = \'%f %f %f\', dist = %f\n", normal[0], normal[1], normal[2], dist); AAS_TestSplitPlane(areanum, normal, dist, &facesplits, &groundsplits, &epsilonface); Log_Print("face splits = %d\nground splits = %d\n", facesplits, groundsplits); if (epsilonface) Log_Print("aaahh epsilon face\n"); #endif //AW_DEBUG*/ //the original area AAS_FlipAreaFaces(tmparea); AAS_CheckArea(tmparea); // splitplane = &mapplanes[planenum]; /* //create a split winding, first base winding for plane splitwinding = BaseWindingForPlane(splitplane->normal, splitplane->dist); //chop with all the faces of the area for (face = tmparea->tmpfaces; face && splitwinding; face = face->next[side]) { //side of the face the original area was on side = face->frontarea != tmparea->areanum; plane = &mapplanes[face->planenum ^ side]; ChopWindingInPlace(&splitwinding, plane->normal, plane->dist, 0); // PLANESIDE_EPSILON); } //end for*/ splitwinding = AAS_SplitWinding(tmparea, planenum); if (!splitwinding) { /* #ifdef DEBUG AAS_TestSplitPlane(areanum, normal, dist, &facesplits, &groundsplits, &epsilonface); Log_Print("\nface splits = %d\nground splits = %d\n", facesplits, groundsplits); if (epsilonface) Log_Print("aaahh epsilon face\n"); #endif //DEBUG*/ Error("AAS_SplitArea: no split winding when splitting area %d\n", tmparea->areanum); } //end if //create a split face splitface = AAS_AllocTmpFace(); //get the map plane splitface->planenum = planenum; //store the split winding splitface->winding = splitwinding; //the new front area (*frontarea) = AAS_AllocTmpArea(); (*frontarea)->presencetype = tmparea->presencetype; (*frontarea)->contents = tmparea->contents; (*frontarea)->modelnum = tmparea->modelnum; (*frontarea)->tmpfaces = NULL; //the new back area (*backarea) = AAS_AllocTmpArea(); (*backarea)->presencetype = tmparea->presencetype; (*backarea)->contents = tmparea->contents; (*backarea)->modelnum = tmparea->modelnum; (*backarea)->tmpfaces = NULL; //add the split face to the new areas AAS_AddFaceSideToArea(splitface, 0, (*frontarea)); AAS_AddFaceSideToArea(splitface, 1, (*backarea)); //split all the faces of the original area for (face = tmparea->tmpfaces; face; face = nextface) { //side of the face the original area was on side = face->frontarea != tmparea; //next face of the original area nextface = face->next[side]; //front area of the face facefrontarea = face->frontarea; //back area of the face facebackarea = face->backarea; //remove the face from both the front and back areas if (facefrontarea) AAS_RemoveFaceFromArea(face, facefrontarea); if (facebackarea) AAS_RemoveFaceFromArea(face, facebackarea); //split the face AAS_SplitFace(face, splitplane->normal, splitplane->dist, &frontface, &backface); //free the original face AAS_FreeTmpFace(face); //get the number of the area at the other side of the face if (side) faceotherarea = facefrontarea; else faceotherarea = facebackarea; //if there is an area at the other side of the original face if (faceotherarea) { if (frontface) AAS_AddFaceSideToArea(frontface, !side, faceotherarea); if (backface) AAS_AddFaceSideToArea(backface, !side, faceotherarea); } //end if //add the front and back part left after splitting the original face to the new areas if (frontface) AAS_AddFaceSideToArea(frontface, side, (*frontarea)); if (backface) AAS_AddFaceSideToArea(backface, side, (*backarea)); } //end for if (!(*frontarea)->tmpfaces) Log_Print("AAS_SplitArea: front area without faces\n"); if (!(*backarea)->tmpfaces) Log_Print("AAS_SplitArea: back area without faces\n"); tmparea->invalid = true; /* #ifdef AW_DEBUG for (i = 0, face = frontarea->tmpfaces; face; face = face->next[side]) { side = face->frontarea != frontarea->areanum; i++; } //end for Log_Print("created front area %d with %d faces\n", frontarea->areanum, i); for (i = 0, face = backarea->tmpfaces; face; face = face->next[side]) { side = face->frontarea != backarea->areanum; i++; } //end for Log_Print("created back area %d with %d faces\n", backarea->areanum, i); #endif //AW_DEBUG*/ AAS_FlipAreaFaces((*frontarea)); AAS_FlipAreaFaces((*backarea)); // AAS_CheckArea((*frontarea)); AAS_CheckArea((*backarea)); } //end of the function AAS_SplitArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_FindBestAreaSplitPlane(tmp_area_t *tmparea, vec3_t normal, float *dist) { int side1, side2; int foundsplitter, facesplits, groundsplits, epsilonfaces, bestepsilonfaces; float bestvalue, value; tmp_face_t *face1, *face2; vec3_t tmpnormal, invgravity; float tmpdist; //get inverse of gravity direction VectorCopy(cfg.phys_gravitydirection, invgravity); VectorInverse(invgravity); foundsplitter = false; bestvalue = -999999; bestepsilonfaces = 0; // #ifdef AW_DEBUG Log_Print("finding split plane for area %d\n", tmparea->areanum); #endif //AW_DEBUG for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) { //side of the face the area is on side1 = face1->frontarea != tmparea; // if (WindingIsTiny(face1->winding)) { Log_Write("gsubdiv: area %d has a tiny winding\r\n", tmparea->areanum); continue; } //end if //if the face isn't a gap or ground there's no split edge if (!(face1->faceflags & FACE_GROUND) && !AAS_GapFace(face1, side1)) continue; // for (face2 = face1->next[side1]; face2; face2 = face2->next[side2]) { //side of the face the area is on side2 = face2->frontarea != tmparea; // if (WindingIsTiny(face1->winding)) { Log_Write("gsubdiv: area %d has a tiny winding\r\n", tmparea->areanum); continue; } //end if //if the face isn't a gap or ground there's no split edge if (!(face2->faceflags & FACE_GROUND) && !AAS_GapFace(face2, side2)) continue; //only split between gaps and ground if (!(((face1->faceflags & FACE_GROUND) && AAS_GapFace(face2, side2)) || ((face2->faceflags & FACE_GROUND) && AAS_GapFace(face1, side1)))) continue; //find a plane seperating the windings of the faces if (!FindPlaneSeperatingWindings(face1->winding, face2->winding, invgravity, tmpnormal, &tmpdist)) continue; #ifdef AW_DEBUG Log_Print("normal = \'%f %f %f\', dist = %f\n", tmpnormal[0], tmpnormal[1], tmpnormal[2], tmpdist); #endif //AW_DEBUG //get metrics for this vertical plane if (!AAS_TestSplitPlane(tmparea, tmpnormal, tmpdist, &facesplits, &groundsplits, &epsilonfaces)) { continue; } //end if #ifdef AW_DEBUG Log_Print("face splits = %d\nground splits = %d\n", facesplits, groundsplits); #endif //AW_DEBUG value = 100 - facesplits - 2 * groundsplits; //avoid epsilon faces value += epsilonfaces * -1000; if (value > bestvalue) { VectorCopy(tmpnormal, normal); *dist = tmpdist; bestvalue = value; bestepsilonfaces = epsilonfaces; foundsplitter = true; } //end if } //end for } //end for if (bestepsilonfaces) { Log_Write("found %d epsilon faces trying to split area %d\r\n", epsilonfaces, tmparea->areanum); } //end else return foundsplitter; } //end of the function AAS_FindBestAreaSplitPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_node_t *AAS_SubdivideArea_r(tmp_node_t *tmpnode) { int planenum; tmp_area_t *frontarea, *backarea; tmp_node_t *tmpnode1, *tmpnode2; vec3_t normal; float dist; if (AAS_FindBestAreaSplitPlane(tmpnode->tmparea, normal, &dist)) { qprintf("\r%6d", ++numgravitationalsubdivisions); // planenum = FindFloatPlane(normal, dist); //split the area AAS_SplitArea(tmpnode->tmparea, planenum, &frontarea, &backarea); // tmpnode->tmparea = NULL; tmpnode->planenum = FindFloatPlane(normal, dist); // tmpnode1 = AAS_AllocTmpNode(); tmpnode1->planenum = 0; tmpnode1->tmparea = frontarea; // tmpnode2 = AAS_AllocTmpNode(); tmpnode2->planenum = 0; tmpnode2->tmparea = backarea; //subdivide the areas created by splitting recursively tmpnode->children[0] = AAS_SubdivideArea_r(tmpnode1); tmpnode->children[1] = AAS_SubdivideArea_r(tmpnode2); } //end if return tmpnode; } //end of the function AAS_SubdivideArea_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_node_t *AAS_GravitationalSubdivision_r(tmp_node_t *tmpnode) { //if this is a solid leaf if (!tmpnode) return NULL; //negative so it's an area if (tmpnode->tmparea) return AAS_SubdivideArea_r(tmpnode); //do the children recursively tmpnode->children[0] = AAS_GravitationalSubdivision_r(tmpnode->children[0]); tmpnode->children[1] = AAS_GravitationalSubdivision_r(tmpnode->children[1]); return tmpnode; } //end of the function AAS_GravitationalSubdivision_r //=========================================================================== // NOTE: merge faces and melt edges first // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_GravitationalSubdivision(void) { Log_Write("AAS_GravitationalSubdivision\r\n"); numgravitationalsubdivisions = 0; qprintf("%6i gravitational subdivisions", numgravitationalsubdivisions); //start with the head node AAS_GravitationalSubdivision_r(tmpaasworld.nodes); qprintf("\n"); Log_Write("%6i gravitational subdivisions\r\n", numgravitationalsubdivisions); } //end of the function AAS_GravitationalSubdivision //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_node_t *AAS_RefreshLadderSubdividedTree_r(tmp_node_t *tmpnode, tmp_area_t *tmparea, tmp_node_t *tmpnode1, tmp_node_t *tmpnode2, int planenum) { //if this is a solid leaf if (!tmpnode) return NULL; //negative so it's an area if (tmpnode->tmparea) { if (tmpnode->tmparea == tmparea) { tmpnode->tmparea = NULL; tmpnode->planenum = planenum; tmpnode->children[0] = tmpnode1; tmpnode->children[1] = tmpnode2; } //end if return tmpnode; } //end if //do the children recursively tmpnode->children[0] = AAS_RefreshLadderSubdividedTree_r(tmpnode->children[0], tmparea, tmpnode1, tmpnode2, planenum); tmpnode->children[1] = AAS_RefreshLadderSubdividedTree_r(tmpnode->children[1], tmparea, tmpnode1, tmpnode2, planenum); return tmpnode; } //end of the function AAS_RefreshLadderSubdividedTree_r //=========================================================================== // find an area with ladder faces and ground faces that are not connected // split the area with a horizontal plane at the lowest vertex of all // ladder faces in the area // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_node_t *AAS_LadderSubdivideArea_r(tmp_node_t *tmpnode) { int side1, i, planenum; int foundladderface, foundgroundface; float dist; tmp_area_t *tmparea, *frontarea, *backarea; tmp_face_t *face1; tmp_node_t *tmpnode1, *tmpnode2; vec3_t lowestpoint, normal = {0, 0, 1}; plane_t *plane; winding_t *w; tmparea = tmpnode->tmparea; //skip areas with a liquid if (tmparea->contents & (AREACONTENTS_WATER | AREACONTENTS_LAVA | AREACONTENTS_SLIME)) return tmpnode; //must be possible to stand in the area if (!(tmparea->presencetype & PRESENCE_NORMAL)) return tmpnode; // foundladderface = false; foundgroundface = false; lowestpoint[2] = 99999; // for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) { //side of the face the area is on side1 = face1->frontarea != tmparea; //if the face is a ladder face if (face1->faceflags & FACE_LADDER) { plane = &mapplanes[face1->planenum]; //the ladder face plane should be pretty much vertical if (DotProduct(plane->normal, normal) > -0.1) { foundladderface = true; //find lowest point for (i = 0; i < face1->winding->numpoints; i++) { if (face1->winding->p[i][2] < lowestpoint[2]) { VectorCopy(face1->winding->p[i], lowestpoint); } //end if } //end for } //end if } //end if else if (face1->faceflags & FACE_GROUND) { foundgroundface = true; } //end else if } //end for // if ((!foundladderface) || (!foundgroundface)) return tmpnode; // for (face1 = tmparea->tmpfaces; face1; face1 = face1->next[side1]) { //side of the face the area is on side1 = face1->frontarea != tmparea; //if the face isn't a ground face if (!(face1->faceflags & FACE_GROUND)) continue; //the ground plane plane = &mapplanes[face1->planenum]; //get the difference between the ground plane and the lowest point dist = DotProduct(plane->normal, lowestpoint) - plane->dist; //if the lowest point is very near one of the ground planes if (dist > -1 && dist < 1) { return tmpnode; } //end if } //end for // dist = DotProduct(normal, lowestpoint); planenum = FindFloatPlane(normal, dist); // w = AAS_SplitWinding(tmparea, planenum); if (!w) return tmpnode; FreeWinding(w); //split the area with a horizontal plane through the lowest point qprintf("\r%6d", ++numladdersubdivisions); // AAS_SplitArea(tmparea, planenum, &frontarea, &backarea); // tmpnode->tmparea = NULL; tmpnode->planenum = planenum; // tmpnode1 = AAS_AllocTmpNode(); tmpnode1->planenum = 0; tmpnode1->tmparea = frontarea; // tmpnode2 = AAS_AllocTmpNode(); tmpnode2->planenum = 0; tmpnode2->tmparea = backarea; //subdivide the areas created by splitting recursively tmpnode->children[0] = AAS_LadderSubdivideArea_r(tmpnode1); tmpnode->children[1] = AAS_LadderSubdivideArea_r(tmpnode2); //refresh the tree AAS_RefreshLadderSubdividedTree_r(tmpaasworld.nodes, tmparea, tmpnode1, tmpnode2, planenum); // return tmpnode; } //end of the function AAS_LadderSubdivideArea_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_node_t *AAS_LadderSubdivision_r(tmp_node_t *tmpnode) { //if this is a solid leaf if (!tmpnode) return 0; //negative so it's an area if (tmpnode->tmparea) return AAS_LadderSubdivideArea_r(tmpnode); //do the children recursively tmpnode->children[0] = AAS_LadderSubdivision_r(tmpnode->children[0]); tmpnode->children[1] = AAS_LadderSubdivision_r(tmpnode->children[1]); return tmpnode; } //end of the function AAS_LadderSubdivision_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_LadderSubdivision(void) { Log_Write("AAS_LadderSubdivision\r\n"); numladdersubdivisions = 0; qprintf("%6i ladder subdivisions", numladdersubdivisions); //start with the head node AAS_LadderSubdivision_r(tmpaasworld.nodes); // qprintf("\n"); Log_Write("%6i ladder subdivisions\r\n", numladdersubdivisions); } //end of the function AAS_LadderSubdivision ================================================ FILE: code/bspc/aas_gsubdiv.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ //works with the global tmpaasworld void AAS_GravitationalSubdivision(void); void AAS_LadderSubdivision(void); ================================================ FILE: code/bspc/aas_map.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "l_mem.h" #include "../botlib/aasfile.h" //aas_bbox_t #include "aas_store.h" //AAS_MAX_BBOXES #include "aas_cfg.h" #include "../game/surfaceflags.h" #define SPAWNFLAG_NOT_EASY 0x00000100 #define SPAWNFLAG_NOT_MEDIUM 0x00000200 #define SPAWNFLAG_NOT_HARD 0x00000400 #define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 #define SPAWNFLAG_NOT_COOP 0x00001000 #define STATE_TOP 0 #define STATE_BOTTOM 1 #define STATE_UP 2 #define STATE_DOWN 3 #define DOOR_START_OPEN 1 #define DOOR_REVERSE 2 #define DOOR_CRUSHER 4 #define DOOR_NOMONSTER 8 #define DOOR_TOGGLE 32 #define DOOR_X_AXIS 64 #define DOOR_Y_AXIS 128 #define BBOX_NORMAL_EPSILON 0.0001 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== vec_t BoxOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs, int side) { vec3_t v1, v2; int i; if (side) { for (i = 0; i < 3; i++) { if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = mins[i]; else v1[i] = 0; } //end for } //end if else { for (i = 0; i < 3; i++) { if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = mins[i]; else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; else v1[i] = 0; } //end for } //end else VectorCopy(normal, v2); VectorInverse(v2); return DotProduct(v1, v2); } //end of the function BoxOriginDistanceFromPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== vec_t CapsuleOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs) { float offset_up, offset_down, width, radius; width = maxs[0] - mins[0]; // if the box is less high then it is wide if (maxs[2] - mins[2] < width) { width = maxs[2] - mins[2]; } radius = width * 0.5; // offset to upper and lower sphere offset_up = maxs[2] - radius; offset_down = -mins[2] - radius; // if normal points upward if ( normal[2] > 0 ) { // touches lower sphere first return normal[2] * offset_down + radius; } else { // touched upper sphere first return -normal[2] * offset_up + radius; } } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ExpandMapBrush(mapbrush_t *brush, vec3_t mins, vec3_t maxs) { int sn; float dist; side_t *s; plane_t *plane; for (sn = 0; sn < brush->numsides; sn++) { s = brush->original_sides + sn; plane = &mapplanes[s->planenum]; dist = plane->dist; if (capsule_collision) { dist += CapsuleOriginDistanceFromPlane(plane->normal, mins, maxs); } else { dist += BoxOriginDistanceFromPlane(plane->normal, mins, maxs, 0); } s->planenum = FindFloatPlane(plane->normal, dist); //the side isn't a bevel after expanding s->flags &= ~SFL_BEVEL; //don't skip the surface s->surf &= ~SURF_SKIP; //make sure the texinfo is not TEXINFO_NODE //when player clip contents brushes are read from the bsp tree //they have the texinfo field set to TEXINFO_NODE //s->texinfo = 0; } //end for } //end of the function AAS_ExpandMapBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_SetTexinfo(mapbrush_t *brush) { int n; side_t *side; if (brush->contents & (CONTENTS_LADDER | CONTENTS_AREAPORTAL | CONTENTS_CLUSTERPORTAL | CONTENTS_TELEPORTER | CONTENTS_JUMPPAD | CONTENTS_DONOTENTER | CONTENTS_WATER | CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WINDOW | CONTENTS_PLAYERCLIP)) { //we just set texinfo to 0 because these brush sides MUST be used as //bsp splitters textured or not textured for (n = 0; n < brush->numsides; n++) { side = brush->original_sides + n; //side->flags |= SFL_TEXTURED|SFL_VISIBLE; side->texinfo = 0; } //end for } //end if else { //only use brush sides as splitters if they are textured //texinfo of non-textured sides will be set to TEXINFO_NODE for (n = 0; n < brush->numsides; n++) { side = brush->original_sides + n; //don't use side as splitter (set texinfo to TEXINFO_NODE) if not textured if (side->flags & (SFL_TEXTURED|SFL_BEVEL)) side->texinfo = 0; else side->texinfo = TEXINFO_NODE; } //end for } //end else } //end of the function AAS_SetTexinfo //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeBrushWindings(mapbrush_t *brush) { int n; side_t *side; // for (n = 0; n < brush->numsides; n++) { side = brush->original_sides + n; // if (side->winding) FreeWinding(side->winding); } //end for } //end of the function FreeBrushWindings //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_AddMapBrushSide(mapbrush_t *brush, int planenum) { side_t *side; // if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) Error ("MAX_MAPFILE_BRUSHSIDES"); // side = brush->original_sides + brush->numsides; side->original = NULL; side->winding = NULL; side->contents = brush->contents; side->flags &= ~(SFL_BEVEL|SFL_VISIBLE); side->surf = 0; side->planenum = planenum; side->texinfo = 0; // nummapbrushsides++; brush->numsides++; } //end of the function AAS_AddMapBrushSide //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FixMapBrush(mapbrush_t *brush) { int i, j, planenum; float dist; winding_t *w; plane_t *plane, *plane1, *plane2; side_t *side; vec3_t normal; //calculate the brush bounds ClearBounds(brush->mins, brush->maxs); for (i = 0; i < brush->numsides; i++) { plane = &mapplanes[brush->original_sides[i].planenum]; w = BaseWindingForPlane(plane->normal, plane->dist); for (j = 0; j < brush->numsides && w; j++) { if (i == j) continue; //there are no brush bevels marked but who cares :) if (brush->original_sides[j].flags & SFL_BEVEL) continue; plane = &mapplanes[brush->original_sides[j].planenum^1]; ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); } //end for side = &brush->original_sides[i]; side->winding = w; if (w) { for (j = 0; j < w->numpoints; j++) { AddPointToBounds(w->p[j], brush->mins, brush->maxs); } //end for } //end if } //end for // for (i = 0; i < brush->numsides; i++) { for (j = 0; j < brush->numsides; j++) { if (i == j) continue; plane1 = &mapplanes[brush->original_sides[i].planenum]; plane2 = &mapplanes[brush->original_sides[j].planenum]; if (WindingsNonConvex(brush->original_sides[i].winding, brush->original_sides[j].winding, plane1->normal, plane2->normal, plane1->dist, plane2->dist)) { Log_Print("non convex brush"); } //end if } //end for } //end for //NOW close the fucking brush!! for (i = 0; i < 3; i++) { if (brush->mins[i] < -MAX_MAP_BOUNDS) { VectorClear(normal); normal[i] = -1; dist = MAX_MAP_BOUNDS - 10; planenum = FindFloatPlane(normal, dist); // Log_Print("mins out of range: added extra brush side\n"); AAS_AddMapBrushSide(brush, planenum); } //end if if (brush->maxs[i] > MAX_MAP_BOUNDS) { VectorClear(normal); normal[i] = 1; dist = MAX_MAP_BOUNDS - 10; planenum = FindFloatPlane(normal, dist); // Log_Print("maxs out of range: added extra brush side\n"); AAS_AddMapBrushSide(brush, planenum); } //end if if (brush->mins[i] > MAX_MAP_BOUNDS || brush->maxs[i] < -MAX_MAP_BOUNDS) { Log_Print("entity %i, brush %i: no visible sides on brush\n", brush->entitynum, brush->brushnum); } //end if } //end for //free all the windings FreeBrushWindings(brush); } //end of the function AAS_FixMapBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_MakeBrushWindings(mapbrush_t *ob) { int i, j; winding_t *w; side_t *side; plane_t *plane, *plane1, *plane2; ClearBounds (ob->mins, ob->maxs); for (i = 0; i < ob->numsides; i++) { plane = &mapplanes[ob->original_sides[i].planenum]; w = BaseWindingForPlane(plane->normal, plane->dist); for (j = 0; j numsides && w; j++) { if (i == j) continue; if (ob->original_sides[j].flags & SFL_BEVEL) continue; plane = &mapplanes[ob->original_sides[j].planenum^1]; ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); } side = &ob->original_sides[i]; side->winding = w; if (w) { side->flags |= SFL_VISIBLE; for (j = 0; j < w->numpoints; j++) AddPointToBounds (w->p[j], ob->mins, ob->maxs); } } //check if the brush is convex for (i = 0; i < ob->numsides; i++) { for (j = 0; j < ob->numsides; j++) { if (i == j) continue; plane1 = &mapplanes[ob->original_sides[i].planenum]; plane2 = &mapplanes[ob->original_sides[j].planenum]; if (WindingsNonConvex(ob->original_sides[i].winding, ob->original_sides[j].winding, plane1->normal, plane2->normal, plane1->dist, plane2->dist)) { Log_Print("non convex brush"); } //end if } //end for } //end for //check for out of bound brushes for (i = 0; i < 3; i++) { //IDBUG: all the indexes into the mins and maxs were zero (not using i) if (ob->mins[i] < -MAX_MAP_BOUNDS || ob->maxs[i] > MAX_MAP_BOUNDS) { Log_Print("entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum); Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, ob->mins[i], i, ob->maxs[i]); ob->numsides = 0; //remove the brush break; } //end if if (ob->mins[i] > MAX_MAP_BOUNDS || ob->maxs[i] < -MAX_MAP_BOUNDS) { Log_Print("entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum); Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, ob->mins[i], i, ob->maxs[i]); ob->numsides = 0; //remove the brush break; } //end if } //end for return true; } //end of the function AAS_MakeBrushWindings //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== mapbrush_t *AAS_CopyMapBrush(mapbrush_t *brush, entity_t *mapent) { int n; mapbrush_t *newbrush; side_t *side, *newside; if (nummapbrushes >= MAX_MAPFILE_BRUSHES) Error ("MAX_MAPFILE_BRUSHES"); newbrush = &mapbrushes[nummapbrushes]; newbrush->original_sides = &brushsides[nummapbrushsides]; newbrush->entitynum = brush->entitynum; newbrush->brushnum = nummapbrushes - mapent->firstbrush; newbrush->numsides = brush->numsides; newbrush->contents = brush->contents; //copy the sides for (n = 0; n < brush->numsides; n++) { if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) Error ("MAX_MAPFILE_BRUSHSIDES"); side = brush->original_sides + n; newside = newbrush->original_sides + n; newside->original = NULL; newside->winding = NULL; newside->contents = side->contents; newside->flags = side->flags; newside->surf = side->surf; newside->planenum = side->planenum; newside->texinfo = side->texinfo; nummapbrushsides++; } //end for // nummapbrushes++; mapent->numbrushes++; return newbrush; } //end of the function AAS_CopyMapBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int mark_entities[MAX_MAP_ENTITIES]; int AAS_AlwaysTriggered_r(char *targetname) { int i; if (!strlen(targetname)) { return false; } // for (i = 0; i < num_entities; i++) { // if the entity will activate the given targetname if ( !strcmp(targetname, ValueForKey(&entities[i], "target")) ) { // if this activator is present in deathmatch if (!(atoi(ValueForKey(&entities[i], "spawnflags")) & SPAWNFLAG_NOT_DEATHMATCH)) { // if it is a trigger_always entity if (!strcmp("trigger_always", ValueForKey(&entities[i], "classname"))) { return true; } // check for possible trigger_always entities activating this entity if ( mark_entities[i] ) { Warning( "entity %d, classname %s has recursive targetname %s\n", i, ValueForKey(&entities[i], "classname"), targetname ); return false; } mark_entities[i] = true; if ( AAS_AlwaysTriggered_r(ValueForKey(&entities[i], "targetname")) ) { return true; } } } } return false; } int AAS_AlwaysTriggered(char *targetname) { memset( mark_entities, 0, sizeof(mark_entities) ); return AAS_AlwaysTriggered_r( targetname ); } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_ValidEntity(entity_t *mapent) { int i; char target[1024]; //all world brushes are used for AAS if (mapent == &entities[0]) { return true; } //end if //some of the func_wall brushes are also used for AAS else if (!strcmp("func_wall", ValueForKey(mapent, "classname"))) { //Log_Print("found func_wall entity %d\n", mapent - entities); //if the func wall is used in deathmatch if (!(atoi(ValueForKey(mapent, "spawnflags")) & SPAWNFLAG_NOT_DEATHMATCH)) { //Log_Print("func_wall USED in deathmatch mode %d\n", atoi(ValueForKey(mapent, "spawnflags"))); return true; } //end if } //end else if else if (!strcmp("func_door_rotating", ValueForKey(mapent, "classname"))) { //if the func_door_rotating is present in deathmatch if (!(atoi(ValueForKey(mapent, "spawnflags")) & SPAWNFLAG_NOT_DEATHMATCH)) { //if the func_door_rotating is always activated in deathmatch if (AAS_AlwaysTriggered(ValueForKey(mapent, "targetname"))) { //Log_Print("found func_door_rotating in deathmatch\ntargetname %s\n", ValueForKey(mapent, "targetname")); return true; } //end if } //end if } //end else if else if (!strcmp("trigger_hurt", ValueForKey(mapent, "classname"))) { //"dmg" is the damage, for instance: "dmg" "666" return true; } //end else if else if (!strcmp("trigger_push", ValueForKey(mapent, "classname"))) { return true; } //end else if else if (!strcmp("trigger_multiple", ValueForKey(mapent, "classname"))) { //find out if the trigger_multiple is pointing to a target_teleporter strcpy(target, ValueForKey(mapent, "target")); for (i = 0; i < num_entities; i++) { //if the entity will activate the given targetname if (!strcmp(target, ValueForKey(&entities[i], "targetname"))) { if (!strcmp("target_teleporter", ValueForKey(&entities[i], "classname"))) { return true; } //end if } //end if } //end for } //end else if else if (!strcmp("trigger_teleport", ValueForKey(mapent, "classname"))) { return true; } //end else if else if (!strcmp("func_static", ValueForKey(mapent, "classname"))) { //FIXME: easy/medium/hard/deathmatch specific? return true; } //end else if else if (!strcmp("func_door", ValueForKey(mapent, "classname"))) { return true; } //end else if return false; } //end of the function AAS_ValidEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_TransformPlane(int planenum, vec3_t origin, vec3_t angles) { float newdist, matrix[3][3]; vec3_t normal; //rotate the node plane VectorCopy(mapplanes[planenum].normal, normal); CreateRotationMatrix(angles, matrix); RotatePoint(normal, matrix); newdist = mapplanes[planenum].dist + DotProduct(normal, origin); return FindFloatPlane(normal, newdist); } //end of the function AAS_TransformPlane //=========================================================================== // this function sets the func_rotating_door in it's final position // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_PositionFuncRotatingBrush(entity_t *mapent, mapbrush_t *brush) { int spawnflags, i; float distance; vec3_t movedir, angles, pos1, pos2; side_t *s; spawnflags = FloatForKey(mapent, "spawnflags"); VectorClear(movedir); if (spawnflags & DOOR_X_AXIS) movedir[2] = 1.0; //roll else if (spawnflags & DOOR_Y_AXIS) movedir[0] = 1.0; //pitch else // Z_AXIS movedir[1] = 1.0; //yaw // check for reverse rotation if (spawnflags & DOOR_REVERSE) VectorInverse(movedir); distance = FloatForKey(mapent, "distance"); if (!distance) distance = 90; GetVectorForKey(mapent, "angles", angles); VectorCopy(angles, pos1); VectorMA(angles, -distance, movedir, pos2); // if it starts open, switch the positions if (spawnflags & DOOR_START_OPEN) { VectorCopy(pos2, angles); VectorCopy(pos1, pos2); VectorCopy(angles, pos1); VectorInverse(movedir); } //end if // for (i = 0; i < brush->numsides; i++) { s = &brush->original_sides[i]; s->planenum = AAS_TransformPlane(s->planenum, mapent->origin, pos2); } //end for // FreeBrushWindings(brush); AAS_MakeBrushWindings(brush); AddBrushBevels(brush); FreeBrushWindings(brush); } //end of the function AAS_PositionFuncRotatingBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_PositionBrush(entity_t *mapent, mapbrush_t *brush) { side_t *s; float newdist; int i, notteam; char *model; if (!strcmp(ValueForKey(mapent, "classname"), "func_door_rotating")) { AAS_PositionFuncRotatingBrush(mapent, brush); } //end if else { if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) { for (i = 0; i < brush->numsides; i++) { s = &brush->original_sides[i]; newdist = mapplanes[s->planenum].dist + DotProduct(mapplanes[s->planenum].normal, mapent->origin); s->planenum = FindFloatPlane(mapplanes[s->planenum].normal, newdist); } //end for } //end if //if it's a trigger hurt if (!strcmp("trigger_hurt", ValueForKey(mapent, "classname"))) { notteam = FloatForKey(mapent, "bot_notteam"); if ( notteam == 1 ) { brush->contents |= CONTENTS_NOTTEAM1; } else if ( notteam == 2 ) { brush->contents |= CONTENTS_NOTTEAM2; } else { // always avoid so set lava contents brush->contents |= CONTENTS_LAVA; } } //end if // else if (!strcmp("trigger_push", ValueForKey(mapent, "classname"))) { //set the jumppad contents brush->contents = CONTENTS_JUMPPAD; //Log_Print("found trigger_push brush\n"); } //end if // else if (!strcmp("trigger_multiple", ValueForKey(mapent, "classname"))) { //set teleporter contents brush->contents = CONTENTS_TELEPORTER; //Log_Print("found trigger_multiple teleporter brush\n"); } //end if // else if (!strcmp("trigger_teleport", ValueForKey(mapent, "classname"))) { //set teleporter contents brush->contents = CONTENTS_TELEPORTER; //Log_Print("found trigger_teleport teleporter brush\n"); } //end if else if (!strcmp("func_door", ValueForKey(mapent, "classname"))) { //set mover contents brush->contents = CONTENTS_MOVER; //get the model number model = ValueForKey(mapent, "model"); brush->modelnum = atoi(model+1); } //end if } //end else } //end of the function AAS_PositionBrush //=========================================================================== // uses the global cfg_t cfg // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CreateMapBrushes(mapbrush_t *brush, entity_t *mapent, int addbevels) { int i; //side_t *s; mapbrush_t *bboxbrushes[16]; //if the brushes are not from an entity used for AAS if (!AAS_ValidEntity(mapent)) { nummapbrushsides -= brush->numsides; brush->numsides = 0; return; } //end if // AAS_PositionBrush(mapent, brush); //from all normal solid brushes only the textured brush sides will //be used as bsp splitters, so set the right texinfo reference here AAS_SetTexinfo(brush); //remove contents detail flag, otherwise player clip contents won't be //bsped correctly for AAS! brush->contents &= ~CONTENTS_DETAIL; //if the brush has contents area portal it should be the only contents if (brush->contents & (CONTENTS_AREAPORTAL|CONTENTS_CLUSTERPORTAL)) { brush->contents = CONTENTS_CLUSTERPORTAL; brush->leafnum = -1; } //end if //window and playerclip are used for player clipping, make them solid if (brush->contents & (CONTENTS_WINDOW | CONTENTS_PLAYERCLIP)) { // brush->contents &= ~(CONTENTS_WINDOW | CONTENTS_PLAYERCLIP); brush->contents |= CONTENTS_SOLID; brush->leafnum = -1; } //end if // if (brush->contents & CONTENTS_BOTCLIP) { brush->contents = CONTENTS_SOLID; brush->leafnum = -1; } //end if // //Log_Write("brush %d contents = ", brush->brushnum); //PrintContents(brush->contents); //Log_Write("\r\n"); //if not one of the following brushes then the brush is NOT used for AAS if (!(brush->contents & (CONTENTS_SOLID | CONTENTS_LADDER | CONTENTS_CLUSTERPORTAL | CONTENTS_DONOTENTER | CONTENTS_TELEPORTER | CONTENTS_JUMPPAD | CONTENTS_WATER | CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_MOVER ))) { nummapbrushsides -= brush->numsides; brush->numsides = 0; return; } //end if //fix the map brush //AAS_FixMapBrush(brush); //if brush bevels should be added (for real map brushes, not bsp map brushes) if (addbevels) { //NOTE: we first have to get the mins and maxs of the brush before // creating the brush bevels... the mins and maxs are used to // create them. so we call MakeBrushWindings to get the mins // and maxs and then after creating the bevels we free the // windings because they are created for all sides (including // bevels) a little later AAS_MakeBrushWindings(brush); AddBrushBevels(brush); FreeBrushWindings(brush); } //end if //NOTE: add the brush to the WORLD entity!!! mapent = &entities[0]; //there's at least one new brush for now nummapbrushes++; mapent->numbrushes++; //liquid brushes are expanded for the maximum possible bounding box if (brush->contents & (CONTENTS_WATER | CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_TELEPORTER | CONTENTS_JUMPPAD | CONTENTS_DONOTENTER | CONTENTS_MOVER )) { brush->expansionbbox = 0; //NOTE: the first bounding box is the max //FIXME: use max bounding box created from all bboxes AAS_ExpandMapBrush(brush, cfg.bboxes[0].mins, cfg.bboxes[0].maxs); AAS_MakeBrushWindings(brush); } //end if //area portal brushes are NOT expanded else if (brush->contents & CONTENTS_CLUSTERPORTAL) { brush->expansionbbox = 0; //NOTE: the first bounding box is the max //FIXME: use max bounding box created from all bboxes AAS_ExpandMapBrush(brush, cfg.bboxes[0].mins, cfg.bboxes[0].maxs); AAS_MakeBrushWindings(brush); } //end if //all solid brushes are expanded for all bounding boxes else if (brush->contents & (CONTENTS_SOLID | CONTENTS_LADDER )) { //brush for the first bounding box bboxbrushes[0] = brush; //make a copy for the other bounding boxes for (i = 1; i < cfg.numbboxes; i++) { bboxbrushes[i] = AAS_CopyMapBrush(brush, mapent); } //end for //expand every brush for it's bounding box and create windings for (i = 0; i < cfg.numbboxes; i++) { AAS_ExpandMapBrush(bboxbrushes[i], cfg.bboxes[i].mins, cfg.bboxes[i].maxs); bboxbrushes[i]->expansionbbox = cfg.bboxes[i].presencetype; AAS_MakeBrushWindings(bboxbrushes[i]); } //end for } //end else } //end of the function AAS_CreateMapBrushes ================================================ FILE: code/bspc/aas_map.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ void AAS_CreateMapBrushes(mapbrush_t *brush, entity_t *mapent, int addbevels); ================================================ FILE: code/bspc/aas_prunenodes.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "../botlib/aasfile.h" #include "aas_create.h" int c_numprunes; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tmp_node_t *AAS_PruneNodes_r(tmp_node_t *tmpnode) { tmp_area_t *tmparea1, *tmparea2; //if it is a solid leaf if (!tmpnode) return NULL; // if (tmpnode->tmparea) return tmpnode; //process the children first tmpnode->children[0] = AAS_PruneNodes_r(tmpnode->children[0]); tmpnode->children[1] = AAS_PruneNodes_r(tmpnode->children[1]); //if both children are areas if (tmpnode->children[0] && tmpnode->children[1] && tmpnode->children[0]->tmparea && tmpnode->children[1]->tmparea) { tmparea1 = tmpnode->children[0]->tmparea; while(tmparea1->mergedarea) tmparea1 = tmparea1->mergedarea; tmparea2 = tmpnode->children[1]->tmparea; while(tmparea2->mergedarea) tmparea2 = tmparea2->mergedarea; if (tmparea1 == tmparea2) { c_numprunes++; tmpnode->tmparea = tmparea1; tmpnode->planenum = 0; AAS_FreeTmpNode(tmpnode->children[0]); AAS_FreeTmpNode(tmpnode->children[1]); tmpnode->children[0] = NULL; tmpnode->children[1] = NULL; return tmpnode; } //end if } //end if //if both solid leafs if (!tmpnode->children[0] && !tmpnode->children[1]) { c_numprunes++; AAS_FreeTmpNode(tmpnode); return NULL; } //end if // return tmpnode; } //end of the function AAS_PruneNodes_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_PruneNodes(void) { Log_Write("AAS_PruneNodes\r\n"); AAS_PruneNodes_r(tmpaasworld.nodes); Log_Print("%6d nodes pruned\r\n", c_numprunes); } //end of the function AAS_PruneNodes ================================================ FILE: code/bspc/aas_prunenodes.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ void AAS_PruneNodes(void); ================================================ FILE: code/bspc/aas_store.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "../botlib/aasfile.h" #include "aas_file.h" #include "aas_store.h" #include "aas_create.h" #include "aas_cfg.h" //#define NOTHREEVERTEXFACES #define STOREPLANESDOUBLE #define VERTEX_EPSILON 0.1 //NOTE: changed from 0.5 #define DIST_EPSILON 0.05 //NOTE: changed from 0.9 #define NORMAL_EPSILON 0.0001 //NOTE: changed from 0.005 #define INTEGRAL_EPSILON 0.01 #define VERTEX_HASHING #define VERTEX_HASH_SHIFT 7 #define VERTEX_HASH_SIZE ((MAX_MAP_BOUNDS>>(VERTEX_HASH_SHIFT-1))+1) //was 64 // #define PLANE_HASHING #define PLANE_HASH_SIZE 1024 //must be power of 2 // #define EDGE_HASHING #define EDGE_HASH_SIZE 1024 //must be power of 2 aas_t aasworld; //vertex hash int *aas_vertexchain; // the next vertex in a hash chain int aas_hashverts[VERTEX_HASH_SIZE*VERTEX_HASH_SIZE]; // a vertex number, or 0 for no verts //plane hash int *aas_planechain; int aas_hashplanes[PLANE_HASH_SIZE]; //edge hash int *aas_edgechain; int aas_hashedges[EDGE_HASH_SIZE]; int allocatedaasmem = 0; int groundfacesonly = false;//true; // typedef struct max_aas_s { int max_bboxes; int max_vertexes; int max_planes; int max_edges; int max_edgeindexsize; int max_faces; int max_faceindexsize; int max_areas; int max_areasettings; int max_reachabilitysize; int max_nodes; int max_portals; int max_portalindexsize; int max_clusters; } max_aas_t; //maximums of everything max_aas_t max_aas; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_CountTmpNodes(tmp_node_t *tmpnode) { if (!tmpnode) return 0; return AAS_CountTmpNodes(tmpnode->children[0]) + AAS_CountTmpNodes(tmpnode->children[1]) + 1; } //end of the function AAS_CountTmpNodes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitMaxAAS(void) { int numfaces, numpoints, numareas; tmp_face_t *f; tmp_area_t *a; numpoints = 0; numfaces = 0; for (f = tmpaasworld.faces; f; f = f->l_next) { numfaces++; if (f->winding) numpoints += f->winding->numpoints; } //end for // numareas = 0; for (a = tmpaasworld.areas; a; a = a->l_next) { numareas++; } //end for max_aas.max_bboxes = AAS_MAX_BBOXES; max_aas.max_vertexes = numpoints + 1; max_aas.max_planes = nummapplanes; max_aas.max_edges = numpoints + 1; max_aas.max_edgeindexsize = (numpoints + 1) * 3; max_aas.max_faces = numfaces + 10; max_aas.max_faceindexsize = (numfaces + 10) * 2; max_aas.max_areas = numareas + 10; max_aas.max_areasettings = numareas + 10; max_aas.max_reachabilitysize = 0; max_aas.max_nodes = AAS_CountTmpNodes(tmpaasworld.nodes) + 10; max_aas.max_portals = 0; max_aas.max_portalindexsize = 0; max_aas.max_clusters = 0; } //end of the function AAS_InitMaxAAS //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_AllocMaxAAS(void) { int i; AAS_InitMaxAAS(); //bounding boxes aasworld.numbboxes = 0; aasworld.bboxes = (aas_bbox_t *) GetClearedMemory(max_aas.max_bboxes * sizeof(aas_bbox_t)); allocatedaasmem += max_aas.max_bboxes * sizeof(aas_bbox_t); //vertexes aasworld.numvertexes = 0; aasworld.vertexes = (aas_vertex_t *) GetClearedMemory(max_aas.max_vertexes * sizeof(aas_vertex_t)); allocatedaasmem += max_aas.max_vertexes * sizeof(aas_vertex_t); //planes aasworld.numplanes = 0; aasworld.planes = (aas_plane_t *) GetClearedMemory(max_aas.max_planes * sizeof(aas_plane_t)); allocatedaasmem += max_aas.max_planes * sizeof(aas_plane_t); //edges aasworld.numedges = 0; aasworld.edges = (aas_edge_t *) GetClearedMemory(max_aas.max_edges * sizeof(aas_edge_t)); allocatedaasmem += max_aas.max_edges * sizeof(aas_edge_t); //edge index aasworld.edgeindexsize = 0; aasworld.edgeindex = (aas_edgeindex_t *) GetClearedMemory(max_aas.max_edgeindexsize * sizeof(aas_edgeindex_t)); allocatedaasmem += max_aas.max_edgeindexsize * sizeof(aas_edgeindex_t); //faces aasworld.numfaces = 0; aasworld.faces = (aas_face_t *) GetClearedMemory(max_aas.max_faces * sizeof(aas_face_t)); allocatedaasmem += max_aas.max_faces * sizeof(aas_face_t); //face index aasworld.faceindexsize = 0; aasworld.faceindex = (aas_faceindex_t *) GetClearedMemory(max_aas.max_faceindexsize * sizeof(aas_faceindex_t)); allocatedaasmem += max_aas.max_faceindexsize * sizeof(aas_faceindex_t); //convex areas aasworld.numareas = 0; aasworld.areas = (aas_area_t *) GetClearedMemory(max_aas.max_areas * sizeof(aas_area_t)); allocatedaasmem += max_aas.max_areas * sizeof(aas_area_t); //convex area settings aasworld.numareasettings = 0; aasworld.areasettings = (aas_areasettings_t *) GetClearedMemory(max_aas.max_areasettings * sizeof(aas_areasettings_t)); allocatedaasmem += max_aas.max_areasettings * sizeof(aas_areasettings_t); //reachablity list aasworld.reachabilitysize = 0; aasworld.reachability = (aas_reachability_t *) GetClearedMemory(max_aas.max_reachabilitysize * sizeof(aas_reachability_t)); allocatedaasmem += max_aas.max_reachabilitysize * sizeof(aas_reachability_t); //nodes of the bsp tree aasworld.numnodes = 0; aasworld.nodes = (aas_node_t *) GetClearedMemory(max_aas.max_nodes * sizeof(aas_node_t)); allocatedaasmem += max_aas.max_nodes * sizeof(aas_node_t); //cluster portals aasworld.numportals = 0; aasworld.portals = (aas_portal_t *) GetClearedMemory(max_aas.max_portals * sizeof(aas_portal_t)); allocatedaasmem += max_aas.max_portals * sizeof(aas_portal_t); //cluster portal index aasworld.portalindexsize = 0; aasworld.portalindex = (aas_portalindex_t *) GetClearedMemory(max_aas.max_portalindexsize * sizeof(aas_portalindex_t)); allocatedaasmem += max_aas.max_portalindexsize * sizeof(aas_portalindex_t); //cluster aasworld.numclusters = 0; aasworld.clusters = (aas_cluster_t *) GetClearedMemory(max_aas.max_clusters * sizeof(aas_cluster_t)); allocatedaasmem += max_aas.max_clusters * sizeof(aas_cluster_t); // Log_Print("allocated "); PrintMemorySize(allocatedaasmem); Log_Print(" of AAS memory\n"); //reset the has stuff aas_vertexchain = (int *) GetClearedMemory(max_aas.max_vertexes * sizeof(int)); aas_planechain = (int *) GetClearedMemory(max_aas.max_planes * sizeof(int)); aas_edgechain = (int *) GetClearedMemory(max_aas.max_edges * sizeof(int)); // for (i = 0; i < max_aas.max_vertexes; i++) aas_vertexchain[i] = -1; for (i = 0; i < VERTEX_HASH_SIZE * VERTEX_HASH_SIZE; i++) aas_hashverts[i] = -1; // for (i = 0; i < max_aas.max_planes; i++) aas_planechain[i] = -1; for (i = 0; i < PLANE_HASH_SIZE; i++) aas_hashplanes[i] = -1; // for (i = 0; i < max_aas.max_edges; i++) aas_edgechain[i] = -1; for (i = 0; i < EDGE_HASH_SIZE; i++) aas_hashedges[i] = -1; } //end of the function AAS_AllocMaxAAS //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_FreeMaxAAS(void) { //bounding boxes if (aasworld.bboxes) FreeMemory(aasworld.bboxes); aasworld.bboxes = NULL; aasworld.numbboxes = 0; //vertexes if (aasworld.vertexes) FreeMemory(aasworld.vertexes); aasworld.vertexes = NULL; aasworld.numvertexes = 0; //planes if (aasworld.planes) FreeMemory(aasworld.planes); aasworld.planes = NULL; aasworld.numplanes = 0; //edges if (aasworld.edges) FreeMemory(aasworld.edges); aasworld.edges = NULL; aasworld.numedges = 0; //edge index if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); aasworld.edgeindex = NULL; aasworld.edgeindexsize = 0; //faces if (aasworld.faces) FreeMemory(aasworld.faces); aasworld.faces = NULL; aasworld.numfaces = 0; //face index if (aasworld.faceindex) FreeMemory(aasworld.faceindex); aasworld.faceindex = NULL; aasworld.faceindexsize = 0; //convex areas if (aasworld.areas) FreeMemory(aasworld.areas); aasworld.areas = NULL; aasworld.numareas = 0; //convex area settings if (aasworld.areasettings) FreeMemory(aasworld.areasettings); aasworld.areasettings = NULL; aasworld.numareasettings = 0; //reachablity list if (aasworld.reachability) FreeMemory(aasworld.reachability); aasworld.reachability = NULL; aasworld.reachabilitysize = 0; //nodes of the bsp tree if (aasworld.nodes) FreeMemory(aasworld.nodes); aasworld.nodes = NULL; aasworld.numnodes = 0; //cluster portals if (aasworld.portals) FreeMemory(aasworld.portals); aasworld.portals = NULL; aasworld.numportals = 0; //cluster portal index if (aasworld.portalindex) FreeMemory(aasworld.portalindex); aasworld.portalindex = NULL; aasworld.portalindexsize = 0; //clusters if (aasworld.clusters) FreeMemory(aasworld.clusters); aasworld.clusters = NULL; aasworld.numclusters = 0; Log_Print("freed "); PrintMemorySize(allocatedaasmem); Log_Print(" of AAS memory\n"); allocatedaasmem = 0; // if (aas_vertexchain) FreeMemory(aas_vertexchain); aas_vertexchain = NULL; if (aas_planechain) FreeMemory(aas_planechain); aas_planechain = NULL; if (aas_edgechain) FreeMemory(aas_edgechain); aas_edgechain = NULL; } //end of the function AAS_FreeMaxAAS //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== unsigned AAS_HashVec(vec3_t vec) { int x, y; x = (MAX_MAP_BOUNDS + (int)(vec[0]+0.5)) >> VERTEX_HASH_SHIFT; y = (MAX_MAP_BOUNDS + (int)(vec[1]+0.5)) >> VERTEX_HASH_SHIFT; if (x < 0 || x >= VERTEX_HASH_SIZE || y < 0 || y >= VERTEX_HASH_SIZE) { Log_Print("WARNING! HashVec: point %f %f %f outside valid range\n", vec[0], vec[1], vec[2]); Log_Print("This should never happen!\n"); return -1; } //end if return y*VERTEX_HASH_SIZE + x; } //end of the function AAS_HashVec //=========================================================================== // returns true if the vertex was found in the list // stores the vertex number in *vnum // stores a new vertex if not stored already // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_GetVertex(vec3_t v, int *vnum) { int i; #ifndef VERTEX_HASHING float diff; #endif //VERTEX_HASHING #ifdef VERTEX_HASHING int h, vn; vec3_t vert; for (i = 0; i < 3; i++) { if ( fabs(v[i] - Q_rint(v[i])) < INTEGRAL_EPSILON) vert[i] = Q_rint(v[i]); else vert[i] = v[i]; } //end for h = AAS_HashVec(vert); //if the vertex was outside the valid range if (h == -1) { *vnum = -1; return true; } //end if for (vn = aas_hashverts[h]; vn >= 0; vn = aas_vertexchain[vn]) { if (fabs(aasworld.vertexes[vn][0] - vert[0]) < VERTEX_EPSILON && fabs(aasworld.vertexes[vn][1] - vert[1]) < VERTEX_EPSILON && fabs(aasworld.vertexes[vn][2] - vert[2]) < VERTEX_EPSILON) { *vnum = vn; return true; } //end if } //end for #else //VERTEX_HASHING //check if the vertex is already stored //stupid linear search for (i = 0; i < aasworld.numvertexes; i++) { diff = vert[0] - aasworld.vertexes[i][0]; if (diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON) { diff = vert[1] - aasworld.vertexes[i][1]; if (diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON) { diff = vert[2] - aasworld.vertexes[i][2]; if (diff < VERTEX_EPSILON && diff > -VERTEX_EPSILON) { *vnum = i; return true; } //end if } //end if } //end if } //end for #endif //VERTEX_HASHING if (aasworld.numvertexes >= max_aas.max_vertexes) { Error("AAS_MAX_VERTEXES = %d", max_aas.max_vertexes); } //end if VectorCopy(vert, aasworld.vertexes[aasworld.numvertexes]); *vnum = aasworld.numvertexes; #ifdef VERTEX_HASHING aas_vertexchain[aasworld.numvertexes] = aas_hashverts[h]; aas_hashverts[h] = aasworld.numvertexes; #endif //VERTEX_HASHING aasworld.numvertexes++; return false; } //end of the function AAS_GetVertex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== unsigned AAS_HashEdge(int v1, int v2) { int vnum1, vnum2; // if (v1 < v2) { vnum1 = v1; vnum2 = v2; } //end if else { vnum1 = v2; vnum2 = v1; } //end else return (vnum1 + vnum2) & (EDGE_HASH_SIZE-1); } //end of the function AAS_HashVec //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_AddEdgeToHash(int edgenum) { int hash; aas_edge_t *edge; edge = &aasworld.edges[edgenum]; hash = AAS_HashEdge(edge->v[0], edge->v[1]); aas_edgechain[edgenum] = aas_hashedges[hash]; aas_hashedges[hash] = edgenum; } //end of the function AAS_AddEdgeToHash //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_FindHashedEdge(int v1num, int v2num, int *edgenum) { int e, hash; aas_edge_t *edge; hash = AAS_HashEdge(v1num, v2num); for (e = aas_hashedges[hash]; e >= 0; e = aas_edgechain[e]) { edge = &aasworld.edges[e]; if (edge->v[0] == v1num) { if (edge->v[1] == v2num) { *edgenum = e; return true; } //end if } //end if else if (edge->v[1] == v1num) { if (edge->v[0] == v2num) { //negative for a reversed edge *edgenum = -e; return true; } //end if } //end else } //end for return false; } //end of the function AAS_FindHashedPlane //=========================================================================== // returns true if the edge was found // stores the edge number in *edgenum (negative if reversed edge) // stores new edge if not stored already // returns zero when the edge is degenerate // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_GetEdge(vec3_t v1, vec3_t v2, int *edgenum) { int v1num, v2num; qboolean found; //the first edge is a dummy if (aasworld.numedges == 0) aasworld.numedges = 1; found = AAS_GetVertex(v1, &v1num); found &= AAS_GetVertex(v2, &v2num); //if one of the vertexes was outside the valid range if (v1num == -1 || v2num == -1) { *edgenum = 0; return true; } //end if //if both vertexes are the same or snapped onto each other if (v1num == v2num) { *edgenum = 0; return true; } //end if //if both vertexes where already stored if (found) { #ifdef EDGE_HASHING if (AAS_FindHashedEdge(v1num, v2num, edgenum)) return true; #else int i; for (i = 1; i < aasworld.numedges; i++) { if (aasworld.edges[i].v[0] == v1num) { if (aasworld.edges[i].v[1] == v2num) { *edgenum = i; return true; } //end if } //end if else if (aasworld.edges[i].v[1] == v1num) { if (aasworld.edges[i].v[0] == v2num) { //negative for a reversed edge *edgenum = -i; return true; } //end if } //end else } //end for #endif //EDGE_HASHING } //end if if (aasworld.numedges >= max_aas.max_edges) { Error("AAS_MAX_EDGES = %d", max_aas.max_edges); } //end if aasworld.edges[aasworld.numedges].v[0] = v1num; aasworld.edges[aasworld.numedges].v[1] = v2num; *edgenum = aasworld.numedges; #ifdef EDGE_HASHING AAS_AddEdgeToHash(*edgenum); #endif //EDGE_HASHING aasworld.numedges++; return false; } //end of the function AAS_GetEdge //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_PlaneTypeForNormal(vec3_t normal) { vec_t ax, ay, az; //NOTE: epsilon used if ( (normal[0] >= 1.0 -NORMAL_EPSILON) || (normal[0] <= -1.0 + NORMAL_EPSILON)) return PLANE_X; if ( (normal[1] >= 1.0 -NORMAL_EPSILON) || (normal[1] <= -1.0 + NORMAL_EPSILON)) return PLANE_Y; if ( (normal[2] >= 1.0 -NORMAL_EPSILON) || (normal[2] <= -1.0 + NORMAL_EPSILON)) return PLANE_Z; ax = fabs(normal[0]); ay = fabs(normal[1]); az = fabs(normal[2]); if (ax >= ay && ax >= az) return PLANE_ANYX; if (ay >= ax && ay >= az) return PLANE_ANYY; return PLANE_ANYZ; } //end of the function AAS_PlaneTypeForNormal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_AddPlaneToHash(int planenum) { int hash; aas_plane_t *plane; plane = &aasworld.planes[planenum]; hash = (int)fabs(plane->dist) / 8; hash &= (PLANE_HASH_SIZE-1); aas_planechain[planenum] = aas_hashplanes[hash]; aas_hashplanes[hash] = planenum; } //end of the function AAS_AddPlaneToHash //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_PlaneEqual(vec3_t normal, float dist, int planenum) { float diff; diff = dist - aasworld.planes[planenum].dist; if (diff > -DIST_EPSILON && diff < DIST_EPSILON) { diff = normal[0] - aasworld.planes[planenum].normal[0]; if (diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON) { diff = normal[1] - aasworld.planes[planenum].normal[1]; if (diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON) { diff = normal[2] - aasworld.planes[planenum].normal[2]; if (diff > -NORMAL_EPSILON && diff < NORMAL_EPSILON) { return true; } //end if } //end if } //end if } //end if return false; } //end of the function AAS_PlaneEqual //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_FindPlane(vec3_t normal, float dist, int *planenum) { int i; for (i = 0; i < aasworld.numplanes; i++) { if (AAS_PlaneEqual(normal, dist, i)) { *planenum = i; return true; } //end if } //end for return false; } //end of the function AAS_FindPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_FindHashedPlane(vec3_t normal, float dist, int *planenum) { int i, p; aas_plane_t *plane; int hash, h; hash = (int)fabs(dist) / 8; hash &= (PLANE_HASH_SIZE-1); //search the border bins as well for (i = -1; i <= 1; i++) { h = (hash+i)&(PLANE_HASH_SIZE-1); for (p = aas_hashplanes[h]; p >= 0; p = aas_planechain[p]) { plane = &aasworld.planes[p]; if (AAS_PlaneEqual(normal, dist, p)) { *planenum = p; return true; } //end if } //end for } //end for return false; } //end of the function AAS_FindHashedPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_GetPlane(vec3_t normal, vec_t dist, int *planenum) { aas_plane_t *plane, temp; //if (AAS_FindPlane(normal, dist, planenum)) return true; if (AAS_FindHashedPlane(normal, dist, planenum)) return true; if (aasworld.numplanes >= max_aas.max_planes-1) { Error("AAS_MAX_PLANES = %d", max_aas.max_planes); } //end if #ifdef STOREPLANESDOUBLE plane = &aasworld.planes[aasworld.numplanes]; VectorCopy(normal, plane->normal); plane->dist = dist; plane->type = (plane+1)->type = PlaneTypeForNormal(plane->normal); VectorCopy(normal, (plane+1)->normal); VectorNegate((plane+1)->normal, (plane+1)->normal); (plane+1)->dist = -dist; aasworld.numplanes += 2; //allways put axial planes facing positive first if (plane->type < 3) { if (plane->normal[0] < 0 || plane->normal[1] < 0 || plane->normal[2] < 0) { // flip order temp = *plane; *plane = *(plane+1); *(plane+1) = temp; *planenum = aasworld.numplanes - 1; return false; } //end if } //end if *planenum = aasworld.numplanes - 2; //add the planes to the hash AAS_AddPlaneToHash(aasworld.numplanes - 1); AAS_AddPlaneToHash(aasworld.numplanes - 2); return false; #else plane = &aasworld.planes[aasworld.numplanes]; VectorCopy(normal, plane->normal); plane->dist = dist; plane->type = AAS_PlaneTypeForNormal(normal); *planenum = aasworld.numplanes; aasworld.numplanes++; //add the plane to the hash AAS_AddPlaneToHash(aasworld.numplanes - 1); return false; #endif //STOREPLANESDOUBLE } //end of the function AAS_GetPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean AAS_GetFace(winding_t *w, plane_t *p, int side, int *facenum) { int edgenum, i, j; aas_face_t *face; //face zero is a dummy, because of the face index with negative numbers if (aasworld.numfaces == 0) aasworld.numfaces = 1; if (aasworld.numfaces >= max_aas.max_faces) { Error("AAS_MAX_FACES = %d", max_aas.max_faces); } //end if face = &aasworld.faces[aasworld.numfaces]; AAS_GetPlane(p->normal, p->dist, &face->planenum); face->faceflags = 0; face->firstedge = aasworld.edgeindexsize; face->frontarea = 0; face->backarea = 0; face->numedges = 0; for (i = 0; i < w->numpoints; i++) { if (aasworld.edgeindexsize >= max_aas.max_edgeindexsize) { Error("AAS_MAX_EDGEINDEXSIZE = %d", max_aas.max_edgeindexsize); } //end if j = (i+1) % w->numpoints; AAS_GetEdge(w->p[i], w->p[j], &edgenum); //if the edge wasn't degenerate if (edgenum) { aasworld.edgeindex[aasworld.edgeindexsize++] = edgenum; face->numedges++; } //end if else if (verbose) { Log_Write("AAS_GetFace: face %d had degenerate edge %d-%d\r\n", aasworld.numfaces, i, j); } //end else } //end for if (face->numedges < 1 #ifdef NOTHREEVERTEXFACES || face->numedges < 3 #endif //NOTHREEVERTEXFACES ) { memset(&aasworld.faces[aasworld.numfaces], 0, sizeof(aas_face_t)); Log_Write("AAS_GetFace: face %d was tiny\r\n", aasworld.numfaces); return false; } //end if *facenum = aasworld.numfaces; aasworld.numfaces++; return true; } //end of the function AAS_GetFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== /* qboolean AAS_GetFace(winding_t *w, plane_t *p, int side, int *facenum) { aas_edgeindex_t edges[1024]; int planenum, numedges, i; int j, edgenum; qboolean foundplane, foundedges; aas_face_t *face; //face zero is a dummy, because of the face index with negative numbers if (aasworld.numfaces == 0) aasworld.numfaces = 1; foundplane = AAS_GetPlane(p->normal, p->dist, &planenum); foundedges = true; numedges = w->numpoints; for (i = 0; i < w->numpoints; i++) { if (i >= 1024) Error("AAS_GetFace: more than %d edges\n", 1024); foundedges &= AAS_GetEdge(w->p[i], w->p[(i+1 >= w->numpoints ? 0 : i+1)], &edges[i]); } //end for //FIXME: use portal number instead of a search //if the plane and all edges already existed if (foundplane && foundedges) { for (i = 0; i < aasworld.numfaces; i++) { face = &aasworld.faces[i]; if (planenum == face->planenum) { if (numedges == face->numedges) { for (j = 0; j < numedges; j++) { edgenum = abs(aasworld.edgeindex[face->firstedge + j]); if (abs(edges[i]) != edgenum) break; } //end for if (j == numedges) { //jippy found the face *facenum = -i; return true; } //end if } //end if } //end if } //end for } //end if if (aasworld.numfaces >= max_aas.max_faces) { Error("AAS_MAX_FACES = %d", max_aas.max_faces); } //end if face = &aasworld.faces[aasworld.numfaces]; face->planenum = planenum; face->faceflags = 0; face->numedges = numedges; face->firstedge = aasworld.edgeindexsize; face->frontarea = 0; face->backarea = 0; for (i = 0; i < numedges; i++) { if (aasworld.edgeindexsize >= max_aas.max_edgeindexsize) { Error("AAS_MAX_EDGEINDEXSIZE = %d", max_aas.max_edgeindexsize); } //end if aasworld.edgeindex[aasworld.edgeindexsize++] = edges[i]; } //end for *facenum = aasworld.numfaces; aasworld.numfaces++; return false; } //end of the function AAS_GetFace*/ //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_StoreAreaSettings(tmp_areasettings_t *tmpareasettings) { aas_areasettings_t *areasettings; if (aasworld.numareasettings == 0) aasworld.numareasettings = 1; areasettings = &aasworld.areasettings[aasworld.numareasettings++]; areasettings->areaflags = tmpareasettings->areaflags; areasettings->presencetype = tmpareasettings->presencetype; areasettings->contents = tmpareasettings->contents; if (tmpareasettings->modelnum > AREACONTENTS_MAXMODELNUM) Log_Print("WARNING: more than %d mover models\n", AREACONTENTS_MAXMODELNUM); areasettings->contents |= (tmpareasettings->modelnum & AREACONTENTS_MAXMODELNUM) << AREACONTENTS_MODELNUMSHIFT; } //end of the function AAS_StoreAreaSettings //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_StoreArea(tmp_area_t *tmparea) { int side, edgenum, i; plane_t *plane; tmp_face_t *tmpface; aas_area_t *aasarea; aas_edge_t *edge; aas_face_t *aasface; aas_faceindex_t aasfacenum; vec3_t facecenter; winding_t *w; //when the area is merged go to the merged area //FIXME: this isn't necessary anymore because the tree // is refreshed after area merging while(tmparea->mergedarea) tmparea = tmparea->mergedarea; // if (tmparea->invalid) Error("AAS_StoreArea: tried to store invalid area"); //if there is an aas area already stored for this tmp area if (tmparea->aasareanum) return -tmparea->aasareanum; // if (aasworld.numareas >= max_aas.max_areas) { Error("AAS_MAX_AREAS = %d", max_aas.max_areas); } //end if //area zero is a dummy if (aasworld.numareas == 0) aasworld.numareas = 1; //create an area from this leaf aasarea = &aasworld.areas[aasworld.numareas]; aasarea->areanum = aasworld.numareas; aasarea->numfaces = 0; aasarea->firstface = aasworld.faceindexsize; ClearBounds(aasarea->mins, aasarea->maxs); VectorClear(aasarea->center); // // Log_Write("tmparea %d became aasarea %d\r\n", tmparea->areanum, aasarea->areanum); //store the aas area number at the tmp area tmparea->aasareanum = aasarea->areanum; // for (tmpface = tmparea->tmpfaces; tmpface; tmpface = tmpface->next[side]) { side = tmpface->frontarea != tmparea; //if there's an aas face created for the tmp face already if (tmpface->aasfacenum) { //we're at the back of the face so use a negative index aasfacenum = -tmpface->aasfacenum; #ifdef DEBUG if (tmpface->aasfacenum < 0 || tmpface->aasfacenum > max_aas.max_faces) { Error("AAS_CreateTree_r: face number out of range"); } //end if #endif //DEBUG aasface = &aasworld.faces[tmpface->aasfacenum]; aasface->backarea = aasarea->areanum; } //end if else { plane = &mapplanes[tmpface->planenum ^ side]; if (side) { w = tmpface->winding; tmpface->winding = ReverseWinding(tmpface->winding); } //end if if (!AAS_GetFace(tmpface->winding, plane, 0, &aasfacenum)) continue; if (side) { FreeWinding(tmpface->winding); tmpface->winding = w; } //end if aasface = &aasworld.faces[aasfacenum]; aasface->frontarea = aasarea->areanum; aasface->backarea = 0; aasface->faceflags = tmpface->faceflags; //set the face number at the tmp face tmpface->aasfacenum = aasfacenum; } //end else //add face points to the area bounds and //calculate the face 'center' VectorClear(facecenter); for (edgenum = 0; edgenum < aasface->numedges; edgenum++) { edge = &aasworld.edges[abs(aasworld.edgeindex[aasface->firstedge + edgenum])]; for (i = 0; i < 2; i++) { AddPointToBounds(aasworld.vertexes[edge->v[i]], aasarea->mins, aasarea->maxs); VectorAdd(aasworld.vertexes[edge->v[i]], facecenter, facecenter); } //end for } //end for VectorScale(facecenter, 1.0 / (aasface->numedges * 2.0), facecenter); //add the face 'center' to the area 'center' VectorAdd(aasarea->center, facecenter, aasarea->center); // if (aasworld.faceindexsize >= max_aas.max_faceindexsize) { Error("AAS_MAX_FACEINDEXSIZE = %d", max_aas.max_faceindexsize); } //end if aasworld.faceindex[aasworld.faceindexsize++] = aasfacenum; aasarea->numfaces++; } //end for //if the area has no faces at all (return 0, = solid leaf) if (!aasarea->numfaces) return 0; // VectorScale(aasarea->center, 1.0 / aasarea->numfaces, aasarea->center); //Log_Write("area %d center %f %f %f\r\n", aasworld.numareas, // aasarea->center[0], aasarea->center[1], aasarea->center[2]); //store the area settings AAS_StoreAreaSettings(tmparea->settings); // //Log_Write("tmp area %d became aas area %d\r\n", tmpareanum, aasarea->areanum); qprintf("\r%6d", aasarea->areanum); // aasworld.numareas++; return -(aasworld.numareas - 1); } //end of the function AAS_StoreArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int AAS_StoreTree_r(tmp_node_t *tmpnode) { int aasnodenum; plane_t *plane; aas_node_t *aasnode; //if it is a solid leaf if (!tmpnode) return 0; //negative so it's an area if (tmpnode->tmparea) return AAS_StoreArea(tmpnode->tmparea); //it's another node //the first node is a dummy if (aasworld.numnodes == 0) aasworld.numnodes = 1; if (aasworld.numnodes >= max_aas.max_nodes) { Error("AAS_MAX_NODES = %d", max_aas.max_nodes); } //end if aasnodenum = aasworld.numnodes; aasnode = &aasworld.nodes[aasworld.numnodes++]; plane = &mapplanes[tmpnode->planenum]; AAS_GetPlane(plane->normal, plane->dist, &aasnode->planenum); aasnode->children[0] = AAS_StoreTree_r(tmpnode->children[0]); aasnode->children[1] = AAS_StoreTree_r(tmpnode->children[1]); return aasnodenum; } //end of the function AAS_StoreTree_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_StoreBoundingBoxes(void) { if (cfg.numbboxes > max_aas.max_bboxes) { Error("more than %d bounding boxes", max_aas.max_bboxes); } //end if aasworld.numbboxes = cfg.numbboxes; memcpy(aasworld.bboxes, cfg.bboxes, cfg.numbboxes * sizeof(aas_bbox_t)); } //end of the function AAS_StoreBoundingBoxes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_StoreFile(char *filename) { AAS_AllocMaxAAS(); Log_Write("AAS_StoreFile\r\n"); // AAS_StoreBoundingBoxes(); // qprintf("%6d areas stored", 0); //start with node 1 because node zero is a dummy AAS_StoreTree_r(tmpaasworld.nodes); qprintf("\n"); Log_Write("%6d areas stored\r\n", aasworld.numareas); aasworld.loaded = true; } //end of the function AAS_StoreFile ================================================ FILE: code/bspc/aas_store.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #define AAS_MAX_BBOXES 5 #define AAS_MAX_VERTEXES 512000 #define AAS_MAX_PLANES 65536 #define AAS_MAX_EDGES 512000 #define AAS_MAX_EDGEINDEXSIZE 512000 #define AAS_MAX_FACES 512000 #define AAS_MAX_FACEINDEXSIZE 512000 #define AAS_MAX_AREAS 65536 #define AAS_MAX_AREASETTINGS 65536 #define AAS_MAX_REACHABILITYSIZE 65536 #define AAS_MAX_NODES 256000 #define AAS_MAX_PORTALS 65536 #define AAS_MAX_PORTALINDEXSIZE 65536 #define AAS_MAX_CLUSTERS 65536 #define BSPCINCLUDE #include "../game/be_aas.h" #include "../botlib/be_aas_def.h" /* typedef struct bspc_aas_s { int loaded; int initialized; //true when AAS has been initialized int savefile; //set true when file should be saved //bounding boxes int numbboxes; aas_bbox_t *bboxes; //vertexes int numvertexes; aas_vertex_t *vertexes; //planes int numplanes; aas_plane_t *planes; //edges int numedges; aas_edge_t *edges; //edge index int edgeindexsize; aas_edgeindex_t *edgeindex; //faces int numfaces; aas_face_t *faces; //face index int faceindexsize; aas_faceindex_t *faceindex; //convex areas int numareas; aas_area_t *areas; //convex area settings int numareasettings; aas_areasettings_t *areasettings; //reachablity list int reachabilitysize; aas_reachability_t *reachability; //nodes of the bsp tree int numnodes; aas_node_t *nodes; //cluster portals int numportals; aas_portal_t *portals; //cluster portal index int portalindexsize; aas_portalindex_t *portalindex; //clusters int numclusters; aas_cluster_t *clusters; // int numreachabilityareas; float reachabilitytime; } bspc_aas_t; extern bspc_aas_t aasworld; //*/ extern aas_t aasworld; //stores the AAS file from the temporary AAS void AAS_StoreFile(char *filename); //returns a number of the given plane qboolean AAS_FindPlane(vec3_t normal, float dist, int *planenum); //allocates the maximum AAS memory for storage void AAS_AllocMaxAAS(void); //frees the maximum AAS memory for storage void AAS_FreeMaxAAS(void); ================================================ FILE: code/bspc/aasfile.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ //NOTE: int = default signed // default long #define AASID (('S'<<24)+('A'<<16)+('A'<<8)+'E') #define AASVERSION_OLD 4 #define AASVERSION 5 //presence types #define PRESENCE_NONE 1 #define PRESENCE_NORMAL 2 #define PRESENCE_CROUCH 4 //travel types #define MAX_TRAVELTYPES 32 #define TRAVEL_INVALID 1 //temporary not possible #define TRAVEL_WALK 2 //walking #define TRAVEL_CROUCH 3 //crouching #define TRAVEL_BARRIERJUMP 4 //jumping onto a barrier #define TRAVEL_JUMP 5 //jumping #define TRAVEL_LADDER 6 //climbing a ladder #define TRAVEL_WALKOFFLEDGE 7 //walking of a ledge #define TRAVEL_SWIM 8 //swimming #define TRAVEL_WATERJUMP 9 //jump out of the water #define TRAVEL_TELEPORT 10 //teleportation #define TRAVEL_ELEVATOR 11 //travel by elevator #define TRAVEL_ROCKETJUMP 12 //rocket jumping required for travel #define TRAVEL_BFGJUMP 13 //bfg jumping required for travel #define TRAVEL_GRAPPLEHOOK 14 //grappling hook required for travel #define TRAVEL_DOUBLEJUMP 15 //double jump #define TRAVEL_RAMPJUMP 16 //ramp jump #define TRAVEL_STRAFEJUMP 17 //strafe jump #define TRAVEL_JUMPPAD 18 //jump pad #define TRAVEL_FUNCBOB 19 //func bob //face flags #define FACE_SOLID 1 //just solid at the other side #define FACE_LADDER 2 //ladder #define FACE_GROUND 4 //standing on ground when in this face #define FACE_GAP 8 //gap in the ground #define FACE_LIQUID 16 #define FACE_LIQUIDSURFACE 32 //area contents #define AREACONTENTS_WATER 1 #define AREACONTENTS_LAVA 2 #define AREACONTENTS_SLIME 4 #define AREACONTENTS_CLUSTERPORTAL 8 #define AREACONTENTS_TELEPORTAL 16 #define AREACONTENTS_ROUTEPORTAL 32 #define AREACONTENTS_TELEPORTER 64 #define AREACONTENTS_JUMPPAD 128 #define AREACONTENTS_DONOTENTER 256 #define AREACONTENTS_VIEWPORTAL 512 //area flags #define AREA_GROUNDED 1 //bot can stand on the ground #define AREA_LADDER 2 //area contains one or more ladder faces #define AREA_LIQUID 4 //area contains a liquid //aas file header lumps #define AAS_LUMPS 14 #define AASLUMP_BBOXES 0 #define AASLUMP_VERTEXES 1 #define AASLUMP_PLANES 2 #define AASLUMP_EDGES 3 #define AASLUMP_EDGEINDEX 4 #define AASLUMP_FACES 5 #define AASLUMP_FACEINDEX 6 #define AASLUMP_AREAS 7 #define AASLUMP_AREASETTINGS 8 #define AASLUMP_REACHABILITY 9 #define AASLUMP_NODES 10 #define AASLUMP_PORTALS 11 #define AASLUMP_PORTALINDEX 12 #define AASLUMP_CLUSTERS 13 //========== bounding box ========= //bounding box typedef struct aas_bbox_s { int presencetype; int flags; vec3_t mins, maxs; } aas_bbox_t; //============ settings =========== //reachability to another area typedef struct aas_reachability_s { int areanum; //number of the reachable area int facenum; //number of the face towards the other area int edgenum; //number of the edge towards the other area vec3_t start; //start point of inter area movement vec3_t end; //end point of inter area movement int traveltype; //type of travel required to get to the area unsigned short int traveltime;//travel time of the inter area movement } aas_reachability_t; //area settings typedef struct aas_areasettings_s { //could also add all kind of statistic fields int contents; //contents of the convex area int areaflags; //several area flags int presencetype; //how a bot can be present in this convex area int cluster; //cluster the area belongs to, if negative it's a portal int clusterareanum; //number of the area in the cluster int numreachableareas; //number of reachable areas from this one int firstreachablearea; //first reachable area in the reachable area index } aas_areasettings_t; //cluster portal typedef struct aas_portal_s { int areanum; //area that is the actual portal int frontcluster; //cluster at front of portal int backcluster; //cluster at back of portal int clusterareanum[2]; //number of the area in the front and back cluster } aas_portal_t; //cluster portal index typedef int aas_portalindex_t; //cluster typedef struct aas_cluster_s { int numareas; //number of areas in the cluster int numreachabilityareas; //number of areas with reachabilities int numportals; //number of cluster portals int firstportal; //first cluster portal in the index } aas_cluster_t; //============ 3d definition ============ typedef vec3_t aas_vertex_t; //just a plane in the third dimension typedef struct aas_plane_s { vec3_t normal; //normal vector of the plane float dist; //distance of the plane (normal vector * distance = point in plane) int type; } aas_plane_t; //edge typedef struct aas_edge_s { int v[2]; //numbers of the vertexes of this edge } aas_edge_t; //edge index, negative if vertexes are reversed typedef int aas_edgeindex_t; //a face bounds a convex area, often it will also seperate two convex areas typedef struct aas_face_s { int planenum; //number of the plane this face is in int faceflags; //face flags (no use to create face settings for just this field) int numedges; //number of edges in the boundary of the face int firstedge; //first edge in the edge index int frontarea; //convex area at the front of this face int backarea; //convex area at the back of this face } aas_face_t; //face index, stores a negative index if backside of face typedef int aas_faceindex_t; //convex area with a boundary of faces typedef struct aas_area_s { int areanum; //number of this area //3d definition int numfaces; //number of faces used for the boundary of the convex area int firstface; //first face in the face index used for the boundary of the convex area vec3_t mins; //mins of the convex area vec3_t maxs; //maxs of the convex area vec3_t center; //'center' of the convex area } aas_area_t; //nodes of the bsp tree typedef struct aas_node_s { int planenum; int children[2]; //child nodes of this node, or convex areas as leaves when negative //when a child is zero it's a solid leaf } aas_node_t; //=========== aas file =============== //header lump typedef struct { int fileofs; int filelen; } aas_lump_t; //aas file header typedef struct aas_header_s { int ident; int version; int bspchecksum; //data entries aas_lump_t lumps[AAS_LUMPS]; } aas_header_t; //====== additional information ====== /* - when a node child is a solid leaf the node child number is zero - two adjacent areas (sharing a plane at opposite sides) share a face this face is a portal between the areas - when an area uses a face from the faceindex with a positive index then the face plane normal points into the area - the face edges are stored counter clockwise using the edgeindex - two adjacent convex areas (sharing a face) only share One face this is a simple result of the areas being convex - the convex areas can't have a mixture of ground and gap faces other mixtures of faces in one area are allowed - areas with the AREACONTENTS_CLUSTERPORTAL in the settings have cluster number zero - edge zero is a dummy - face zero is a dummy - area zero is a dummy - node zero is a dummy */ ================================================ FILE: code/bspc/be_aas_bspc.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "../game/q_shared.h" #include "../bspc/l_log.h" #include "../bspc/l_qfiles.h" #include "../botlib/l_memory.h" #include "../botlib/l_script.h" #include "../botlib/l_precomp.h" #include "../botlib/l_struct.h" #include "../botlib/aasfile.h" #include "../game/botlib.h" #include "../game/be_aas.h" #include "../botlib/be_aas_def.h" #include "../qcommon/cm_public.h" //#define BSPC extern botlib_import_t botimport; extern qboolean capsule_collision; botlib_import_t botimport; clipHandle_t worldmodel; void Error (char *error, ...); //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_Error(char *fmt, ...) { va_list argptr; char text[1024]; va_start(argptr, fmt); vsprintf(text, fmt, argptr); va_end(argptr); Error(text); } //end of the function AAS_Error //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Sys_MilliSeconds(void) { return clock() * 1000 / CLOCKS_PER_SEC; } //end of the function Sys_MilliSeconds //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_DebugLine(vec3_t start, vec3_t end, int color) { } //end of the function AAS_DebugLine //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ClearShownDebugLines(void) { } //end of the function AAS_ClearShownDebugLines //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *BotImport_BSPEntityData(void) { return CM_EntityString(); } //end of the function AAS_GetEntityData //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotImport_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { trace_t result; CM_BoxTrace(&result, start, end, mins, maxs, worldmodel, contentmask, capsule_collision); bsptrace->allsolid = result.allsolid; bsptrace->contents = result.contents; VectorCopy(result.endpos, bsptrace->endpos); bsptrace->ent = result.entityNum; bsptrace->fraction = result.fraction; bsptrace->exp_dist = 0; bsptrace->plane.dist = result.plane.dist; VectorCopy(result.plane.normal, bsptrace->plane.normal); bsptrace->plane.signbits = result.plane.signbits; bsptrace->plane.type = result.plane.type; bsptrace->sidenum = 0; bsptrace->startsolid = result.startsolid; bsptrace->surface.flags = result.surfaceFlags; } //end of the function BotImport_Trace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BotImport_PointContents(vec3_t p) { return CM_PointContents(p, worldmodel); } //end of the function BotImport_PointContents //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void *BotImport_GetMemory(int size) { return GetMemory(size); } //end of the function BotImport_GetMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotImport_Print(int type, char *fmt, ...) { va_list argptr; char buf[1024]; va_start(argptr, fmt); vsprintf(buf, fmt, argptr); printf(buf); if (buf[0] != '\r') Log_Write(buf); va_end(argptr); } //end of the function BotImport_Print //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BotImport_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t outmins, vec3_t outmaxs, vec3_t origin) { clipHandle_t h; vec3_t mins, maxs; float max; int i; h = CM_InlineModel(modelnum); CM_ModelBounds(h, mins, maxs); //if the model is rotated if ((angles[0] || angles[1] || angles[2])) { // expand for rotation max = RadiusFromBounds(mins, maxs); for (i = 0; i < 3; i++) { mins[i] = (mins[i] + maxs[i]) * 0.5 - max; maxs[i] = (mins[i] + maxs[i]) * 0.5 + max; } //end for } //end if if (outmins) VectorCopy(mins, outmins); if (outmaxs) VectorCopy(maxs, outmaxs); if (origin) VectorClear(origin); } //end of the function BotImport_BSPModelMinsMaxsOrigin //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Com_DPrintf(char *fmt, ...) { va_list argptr; char buf[1024]; va_start(argptr, fmt); vsprintf(buf, fmt, argptr); printf(buf); if (buf[0] != '\r') Log_Write(buf); va_end(argptr); } //end of the function Com_DPrintf //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int COM_Compress( char *data_p ) { return strlen(data_p); } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Com_Memset (void* dest, const int val, const size_t count) { memset(dest, val, count); } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Com_Memcpy (void* dest, const void* src, const size_t count) { memcpy(dest, src, count); } //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_InitBotImport(void) { botimport.BSPEntityData = BotImport_BSPEntityData; botimport.GetMemory = BotImport_GetMemory; botimport.FreeMemory = FreeMemory; botimport.Trace = BotImport_Trace; botimport.PointContents = BotImport_PointContents; botimport.Print = BotImport_Print; botimport.BSPModelMinsMaxsOrigin = BotImport_BSPModelMinsMaxsOrigin; } //end of the function AAS_InitBotImport //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_CalcReachAndClusters(struct quakefile_s *qf) { float time; Log_Print("loading collision map...\n"); // if (!qf->pakfile[0]) strcpy(qf->pakfile, qf->filename); //load the map CM_LoadMap((char *) qf, qfalse, &aasworld.bspchecksum); //get a handle to the world model worldmodel = CM_InlineModel(0); // 0 = world, 1 + are bmodels //initialize bot import structure AAS_InitBotImport(); //load the BSP entity string AAS_LoadBSPFile(); //init physics settings AAS_InitSettings(); //initialize AAS link heap AAS_InitAASLinkHeap(); //initialize the AAS linked entities for the new map AAS_InitAASLinkedEntities(); //reset all reachabilities and clusters aasworld.reachabilitysize = 0; aasworld.numclusters = 0; //set all view portals as cluster portals in case we re-calculate the reachabilities and clusters (with -reach) AAS_SetViewPortalsAsClusterPortals(); //calculate reachabilities AAS_InitReachability(); time = 0; while(AAS_ContinueInitReachability(time)) time++; //calculate clusters AAS_InitClustering(); } //end of the function AAS_CalcReachAndClusters ================================================ FILE: code/bspc/be_aas_bspc.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ void AAS_CalcReachAndClusters(struct quakefile_s *qf); ================================================ FILE: code/bspc/brushbsp.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "l_mem.h" #include "../botlib/aasfile.h" #include "aas_store.h" #include "aas_cfg.h" #include /* each side has a count of the other sides it splits the best split will be the one that minimizes the total split counts of all remaining sides precalc side on plane table evaluate split side { cost = 0 for all sides for all sides get if side splits side and splitside is on same child cost++; } */ int c_nodes; int c_nonvis; int c_active_brushes; int c_solidleafnodes; int c_totalsides; int c_brushmemory; int c_peak_brushmemory; int c_nodememory; int c_peak_totalbspmemory; // if a brush just barely pokes onto the other side, // let it slide by without chopping #define PLANESIDE_EPSILON 0.001 //0.1 //#ifdef DEBUG typedef struct cname_s { int value; char *name; } cname_t; cname_t contentnames[] = { {CONTENTS_SOLID,"CONTENTS_SOLID"}, {CONTENTS_WINDOW,"CONTENTS_WINDOW"}, {CONTENTS_AUX,"CONTENTS_AUX"}, {CONTENTS_LAVA,"CONTENTS_LAVA"}, {CONTENTS_SLIME,"CONTENTS_SLIME"}, {CONTENTS_WATER,"CONTENTS_WATER"}, {CONTENTS_MIST,"CONTENTS_MIST"}, {LAST_VISIBLE_CONTENTS,"LAST_VISIBLE_CONTENTS"}, {CONTENTS_AREAPORTAL,"CONTENTS_AREAPORTAL"}, {CONTENTS_PLAYERCLIP,"CONTENTS_PLAYERCLIP"}, {CONTENTS_MONSTERCLIP,"CONTENTS_MONSTERCLIP"}, {CONTENTS_CURRENT_0,"CONTENTS_CURRENT_0"}, {CONTENTS_CURRENT_90,"CONTENTS_CURRENT_90"}, {CONTENTS_CURRENT_180,"CONTENTS_CURRENT_180"}, {CONTENTS_CURRENT_270,"CONTENTS_CURRENT_270"}, {CONTENTS_CURRENT_UP,"CONTENTS_CURRENT_UP"}, {CONTENTS_CURRENT_DOWN,"CONTENTS_CURRENT_DOWN"}, {CONTENTS_ORIGIN,"CONTENTS_ORIGIN"}, {CONTENTS_MONSTER,"CONTENTS_MONSTER"}, {CONTENTS_DEADMONSTER,"CONTENTS_DEADMONSTER"}, {CONTENTS_DETAIL,"CONTENTS_DETAIL"}, {CONTENTS_Q2TRANSLUCENT,"CONTENTS_TRANSLUCENT"}, {CONTENTS_LADDER,"CONTENTS_LADDER"}, {0, 0} }; void PrintContents(int contents) { int i; for (i = 0; contentnames[i].value; i++) { if (contents & contentnames[i].value) { Log_Write("%s,", contentnames[i].name); } //end if } //end for } //end of the function PrintContents //#endif DEBUG //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ResetBrushBSP(void) { c_nodes = 0; c_nonvis = 0; c_active_brushes = 0; c_solidleafnodes = 0; c_totalsides = 0; c_brushmemory = 0; c_peak_brushmemory = 0; c_nodememory = 0; c_peak_totalbspmemory = 0; } //end of the function ResetBrushBSP //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FindBrushInTree (node_t *node, int brushnum) { bspbrush_t *b; if (node->planenum == PLANENUM_LEAF) { for (b=node->brushlist ; b ; b=b->next) if (b->original->brushnum == brushnum) Log_Print ("here\n"); return; } FindBrushInTree(node->children[0], brushnum); FindBrushInTree(node->children[1], brushnum); } //end of the function FindBrushInTree //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void DrawBrushList (bspbrush_t *brush, node_t *node) { int i; side_t *s; GLS_BeginScene (); for ( ; brush ; brush=brush->next) { for (i=0 ; inumsides ; i++) { s = &brush->sides[i]; if (!s->winding) continue; if (s->texinfo == TEXINFO_NODE) GLS_Winding (s->winding, 1); else if (!(s->flags & SFL_VISIBLE)) GLS_Winding (s->winding, 2); else GLS_Winding (s->winding, 0); } } GLS_EndScene (); } //end of the function DrawBrushList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void WriteBrushList (char *name, bspbrush_t *brush, qboolean onlyvis) { int i; side_t *s; FILE *f; qprintf ("writing %s\n", name); f = SafeOpenWrite (name); for ( ; brush ; brush=brush->next) { for (i=0 ; inumsides ; i++) { s = &brush->sides[i]; if (!s->winding) continue; if (onlyvis && !(s->flags & SFL_VISIBLE)) continue; OutputWinding (brush->sides[i].winding, f); } } fclose (f); } //end of the function WriteBrushList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintBrush (bspbrush_t *brush) { int i; printf ("brush: %p\n", brush); for (i=0;inumsides ; i++) { pw(brush->sides[i].winding); printf ("\n"); } //end for } //end of the function PrintBrush //=========================================================================== // Sets the mins/maxs based on the windings // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BoundBrush (bspbrush_t *brush) { int i, j; winding_t *w; ClearBounds (brush->mins, brush->maxs); for (i=0 ; inumsides ; i++) { w = brush->sides[i].winding; if (!w) continue; for (j=0 ; jnumpoints ; j++) AddPointToBounds (w->p[j], brush->mins, brush->maxs); } } //end of the function BoundBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void CreateBrushWindings (bspbrush_t *brush) { int i, j; winding_t *w; side_t *side; plane_t *plane; for (i=0 ; inumsides ; i++) { side = &brush->sides[i]; plane = &mapplanes[side->planenum]; w = BaseWindingForPlane (plane->normal, plane->dist); for (j=0 ; jnumsides && w; j++) { if (i == j) continue; if (brush->sides[j].flags & SFL_BEVEL) continue; plane = &mapplanes[brush->sides[j].planenum^1]; ChopWindingInPlace (&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); } side->winding = w; } BoundBrush (brush); } //end of the function CreateBrushWindings //=========================================================================== // Creates a new axial brush // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *BrushFromBounds (vec3_t mins, vec3_t maxs) { bspbrush_t *b; int i; vec3_t normal; vec_t dist; b = AllocBrush (6); b->numsides = 6; for (i=0 ; i<3 ; i++) { VectorClear (normal); normal[i] = 1; dist = maxs[i]; b->sides[i].planenum = FindFloatPlane (normal, dist); normal[i] = -1; dist = -mins[i]; b->sides[3+i].planenum = FindFloatPlane (normal, dist); } CreateBrushWindings (b); return b; } //end of the function BrushFromBounds //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BrushOutOfBounds(bspbrush_t *brush, vec3_t mins, vec3_t maxs, float epsilon) { int i, j, n; winding_t *w; side_t *side; for (i = 0; i < brush->numsides; i++) { side = &brush->sides[i]; w = side->winding; for (j = 0; j < w->numpoints; j++) { for (n = 0; n < 3; n++) { if (w->p[j][n] < (mins[n] + epsilon) || w->p[j][n] > (maxs[n] - epsilon)) return true; } //end for } //end for } //end for return false; } //end of the function BrushOutOfBounds //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== vec_t BrushVolume (bspbrush_t *brush) { int i; winding_t *w; vec3_t corner; vec_t d, area, volume; plane_t *plane; if (!brush) return 0; // grab the first valid point as the corner w = NULL; for (i = 0; i < brush->numsides; i++) { w = brush->sides[i].winding; if (w) break; } //end for if (!w) return 0; VectorCopy (w->p[0], corner); // make tetrahedrons to all other faces volume = 0; for ( ; i < brush->numsides; i++) { w = brush->sides[i].winding; if (!w) continue; plane = &mapplanes[brush->sides[i].planenum]; d = -(DotProduct (corner, plane->normal) - plane->dist); area = WindingArea(w); volume += d * area; } //end for volume /= 3; return volume; } //end of the function BrushVolume //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int CountBrushList (bspbrush_t *brushes) { int c; c = 0; for ( ; brushes; brushes = brushes->next) c++; return c; } //end of the function CountBrushList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== node_t *AllocNode (void) { node_t *node; node = GetMemory(sizeof(*node)); memset (node, 0, sizeof(*node)); if (numthreads == 1) { c_nodememory += MemorySize(node); } //end if return node; } //end of the function AllocNode //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *AllocBrush (int numsides) { bspbrush_t *bb; int c; c = (int)&(((bspbrush_t *)0)->sides[numsides]); bb = GetMemory(c); memset (bb, 0, c); if (numthreads == 1) { c_active_brushes++; c_brushmemory += MemorySize(bb); if (c_brushmemory > c_peak_brushmemory) c_peak_brushmemory = c_brushmemory; } //end if return bb; } //end of the function AllocBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeBrush (bspbrush_t *brushes) { int i; for (i=0 ; inumsides ; i++) if (brushes->sides[i].winding) FreeWinding(brushes->sides[i].winding); if (numthreads == 1) { c_active_brushes--; c_brushmemory -= MemorySize(brushes); if (c_brushmemory < 0) c_brushmemory = 0; } //end if FreeMemory(brushes); } //end of the function FreeBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeBrushList (bspbrush_t *brushes) { bspbrush_t *next; for ( ; brushes; brushes = next) { next = brushes->next; FreeBrush(brushes); } //end for } //end of the function FreeBrushList //=========================================================================== // Duplicates the brush, the sides, and the windings // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *CopyBrush (bspbrush_t *brush) { bspbrush_t *newbrush; int size; int i; size = (int)&(((bspbrush_t *)0)->sides[brush->numsides]); newbrush = AllocBrush (brush->numsides); memcpy (newbrush, brush, size); for (i=0 ; inumsides ; i++) { if (brush->sides[i].winding) newbrush->sides[i].winding = CopyWinding (brush->sides[i].winding); } return newbrush; } //end of the function CopyBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== node_t *PointInLeaf (node_t *node, vec3_t point) { vec_t d; plane_t *plane; while (node->planenum != PLANENUM_LEAF) { plane = &mapplanes[node->planenum]; d = DotProduct (point, plane->normal) - plane->dist; if (d > 0) node = node->children[0]; else node = node->children[1]; } return node; } //end of the function PointInLeaf //=========================================================================== // Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #if 0 int BoxOnPlaneSide (vec3_t mins, vec3_t maxs, plane_t *plane) { int side; int i; vec3_t corners[2]; vec_t dist1, dist2; // axial planes are easy if (plane->type < 3) { side = 0; if (maxs[plane->type] > plane->dist+PLANESIDE_EPSILON) side |= PSIDE_FRONT; if (mins[plane->type] < plane->dist-PLANESIDE_EPSILON) side |= PSIDE_BACK; return side; } // create the proper leading and trailing verts for the box for (i=0 ; i<3 ; i++) { if (plane->normal[i] < 0) { corners[0][i] = mins[i]; corners[1][i] = maxs[i]; } else { corners[1][i] = mins[i]; corners[0][i] = maxs[i]; } } dist1 = DotProduct (plane->normal, corners[0]) - plane->dist; dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; side = 0; if (dist1 >= PLANESIDE_EPSILON) side = PSIDE_FRONT; if (dist2 < PLANESIDE_EPSILON) side |= PSIDE_BACK; return side; } #else int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, plane_t *p) { float dist1, dist2; int sides; // axial planes are easy if (p->type < 3) { sides = 0; if (emaxs[p->type] > p->dist+PLANESIDE_EPSILON) sides |= PSIDE_FRONT; if (emins[p->type] < p->dist-PLANESIDE_EPSILON) sides |= PSIDE_BACK; return sides; } //end if // general case switch (p->signbits) { case 0: dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; break; case 1: dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; break; case 2: dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; break; case 3: dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; break; case 4: dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; break; case 5: dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; break; case 6: dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; break; case 7: dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; break; default: dist1 = dist2 = 0; // shut up compiler // assert( 0 ); break; } sides = 0; if (dist1 - p->dist >= PLANESIDE_EPSILON) sides = PSIDE_FRONT; if (dist2 - p->dist < PLANESIDE_EPSILON) sides |= PSIDE_BACK; // assert(sides != 0); return sides; } #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int QuickTestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits) { int i, num; plane_t *plane; int s; *numsplits = 0; plane = &mapplanes[planenum]; #ifdef ME //fast axial cases if (plane->type < 3) { if (plane->dist + PLANESIDE_EPSILON < brush->mins[plane->type]) return PSIDE_FRONT; if (plane->dist - PLANESIDE_EPSILON > brush->maxs[plane->type]) return PSIDE_BACK; } //end if #endif //ME*/ // if the brush actually uses the planenum, // we can tell the side for sure for (i = 0; i < brush->numsides; i++) { num = brush->sides[i].planenum; if (num >= MAX_MAPFILE_PLANES) Error ("bad planenum"); if (num == planenum) return PSIDE_BACK|PSIDE_FACING; if (num == (planenum ^ 1) ) return PSIDE_FRONT|PSIDE_FACING; } // box on plane side s = BoxOnPlaneSide (brush->mins, brush->maxs, plane); // if both sides, count the visible faces split if (s == PSIDE_BOTH) { *numsplits += 3; } return s; } //end of the function QuickTestBrushToPlanenum //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits, qboolean *hintsplit, int *epsilonbrush) { int i, j, num; plane_t *plane; int s = 0; winding_t *w; vec_t d, d_front, d_back; int front, back; int type; float dist; *numsplits = 0; *hintsplit = false; plane = &mapplanes[planenum]; #ifdef ME //fast axial cases type = plane->type; if (type < 3) { dist = plane->dist; if (dist + PLANESIDE_EPSILON < brush->mins[type]) return PSIDE_FRONT; if (dist - PLANESIDE_EPSILON > brush->maxs[type]) return PSIDE_BACK; if (brush->mins[type] < dist - PLANESIDE_EPSILON && brush->maxs[type] > dist + PLANESIDE_EPSILON) s = PSIDE_BOTH; } //end if if (s != PSIDE_BOTH) #endif //ME { // if the brush actually uses the planenum, // we can tell the side for sure for (i = 0; i < brush->numsides; i++) { num = brush->sides[i].planenum; if (num >= MAX_MAPFILE_PLANES) Error ("bad planenum"); if (num == planenum) { //we don't need to test this side plane again brush->sides[i].flags |= SFL_TESTED; return PSIDE_BACK|PSIDE_FACING; } //end if if (num == (planenum ^ 1) ) { //we don't need to test this side plane again brush->sides[i].flags |= SFL_TESTED; return PSIDE_FRONT|PSIDE_FACING; } //end if } //end for // box on plane side s = BoxOnPlaneSide (brush->mins, brush->maxs, plane); if (s != PSIDE_BOTH) return s; } //end if // if both sides, count the visible faces split d_front = d_back = 0; for (i = 0; i < brush->numsides; i++) { if (brush->sides[i].texinfo == TEXINFO_NODE) continue; // on node, don't worry about splits if (!(brush->sides[i].flags & SFL_VISIBLE)) continue; // we don't care about non-visible w = brush->sides[i].winding; if (!w) continue; front = back = 0; for (j = 0; j < w->numpoints; j++) { d = DotProduct(w->p[j], plane->normal) - plane->dist; if (d > d_front) d_front = d; if (d < d_back) d_back = d; if (d > 0.1) // PLANESIDE_EPSILON) front = 1; if (d < -0.1) // PLANESIDE_EPSILON) back = 1; } //end for if (front && back) { if ( !(brush->sides[i].surf & SURF_SKIP) ) { (*numsplits)++; if (brush->sides[i].surf & SURF_HINT) { *hintsplit = true; } //end if } //end if } //end if } //end for if ( (d_front > 0.0 && d_front < 1.0) || (d_back < 0.0 && d_back > -1.0) ) (*epsilonbrush)++; #if 0 if (*numsplits == 0) { // didn't really need to be split if (front) s = PSIDE_FRONT; else if (back) s = PSIDE_BACK; else s = 0; } #endif return s; } //end of the function TestBrushToPlanenum //=========================================================================== // Returns true if the winding would be crunched out of // existance by the vertex snapping. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #define EDGE_LENGTH 0.2 qboolean WindingIsTiny (winding_t *w) { #if 0 if (WindingArea (w) < 1) return true; return false; #else int i, j; vec_t len; vec3_t delta; int edges; edges = 0; for (i=0 ; inumpoints ; i++) { j = i == w->numpoints - 1 ? 0 : i+1; VectorSubtract (w->p[j], w->p[i], delta); len = VectorLength (delta); if (len > EDGE_LENGTH) { if (++edges == 3) return false; } } return true; #endif } //end of the function WindingIsTiny //=========================================================================== // Returns true if the winding still has one of the points // from basewinding for plane // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WindingIsHuge (winding_t *w) { int i, j; for (i=0 ; inumpoints ; i++) { for (j=0 ; j<3 ; j++) if (w->p[i][j] < -BOGUS_RANGE+1 || w->p[i][j] > BOGUS_RANGE-1) return true; } return false; } //end of the function WindingIsHuge //=========================================================================== // creates a leaf out of the given nodes with the given brushes // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void LeafNode(node_t *node, bspbrush_t *brushes) { bspbrush_t *b; int i; node->side = NULL; node->planenum = PLANENUM_LEAF; node->contents = 0; for (b = brushes; b; b = b->next) { // if the brush is solid and all of its sides are on nodes, // it eats everything if (b->original->contents & CONTENTS_SOLID) { for (i=0 ; inumsides ; i++) if (b->sides[i].texinfo != TEXINFO_NODE) break; if (i == b->numsides) { node->contents = CONTENTS_SOLID; break; } //end if } //end if node->contents |= b->original->contents; } //end for if (create_aas) { node->expansionbboxes = 0; node->contents = 0; for (b = brushes; b; b = b->next) { node->expansionbboxes |= b->original->expansionbbox; node->contents |= b->original->contents; if (b->original->modelnum) node->modelnum = b->original->modelnum; } //end for if (node->contents & CONTENTS_SOLID) { if (node->expansionbboxes != cfg.allpresencetypes) { node->contents &= ~CONTENTS_SOLID; } //end if } //end if } //end if node->brushlist = brushes; } //end of the function LeafNode //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void CheckPlaneAgainstParents (int pnum, node_t *node) { node_t *p; for (p = node->parent; p; p = p->parent) { if (p->planenum == pnum) Error("Tried parent"); } //end for } //end of the function CheckPlaneAgainstParants //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean CheckPlaneAgainstVolume (int pnum, node_t *node) { bspbrush_t *front, *back; qboolean good; SplitBrush (node->volume, pnum, &front, &back); good = (front && back); if (front) FreeBrush (front); if (back) FreeBrush (back); return good; } //end of the function CheckPlaneAgaintsVolume //=========================================================================== // Using a hueristic, choses one of the sides out of the brushlist // to partition the brushes with. // Returns NULL if there are no valid planes to split with.. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== side_t *SelectSplitSide (bspbrush_t *brushes, node_t *node) { int value, bestvalue; bspbrush_t *brush, *test; side_t *side, *bestside; int i, pass, numpasses; int pnum; int s; int front, back, both, facing, splits; int bsplits; int bestsplits; int epsilonbrush; qboolean hintsplit = false; bestside = NULL; bestvalue = -99999; bestsplits = 0; // the search order goes: visible-structural, visible-detail, // nonvisible-structural, nonvisible-detail. // If any valid plane is available in a pass, no further // passes will be tried. numpasses = 2; for (pass = 0; pass < numpasses; pass++) { for (brush = brushes; brush; brush = brush->next) { // only check detail the second pass // if ( (pass & 1) && !(brush->original->contents & CONTENTS_DETAIL) ) // continue; // if ( !(pass & 1) && (brush->original->contents & CONTENTS_DETAIL) ) // continue; for (i = 0; i < brush->numsides; i++) { side = brush->sides + i; // if (side->flags & SFL_BEVEL) // continue; // never use a bevel as a spliter if (!side->winding) continue; // nothing visible, so it can't split if (side->texinfo == TEXINFO_NODE) continue; // allready a node splitter if (side->flags & SFL_TESTED) continue; // we allready have metrics for this plane // if (side->surf & SURF_SKIP) // continue; // skip surfaces are never chosen // if (!(side->flags & SFL_VISIBLE) && (pass < 2)) // continue; // only check visible faces on first pass if ((side->flags & SFL_CURVE) && (pass < 1)) continue; // only check curves the second pass pnum = side->planenum; pnum &= ~1; // allways use positive facing plane CheckPlaneAgainstParents (pnum, node); if (!CheckPlaneAgainstVolume (pnum, node)) continue; // would produce a tiny volume front = 0; back = 0; both = 0; facing = 0; splits = 0; epsilonbrush = 0; //inner loop: optimize for (test = brushes; test; test = test->next) { s = TestBrushToPlanenum (test, pnum, &bsplits, &hintsplit, &epsilonbrush); splits += bsplits; // if (bsplits && (s&PSIDE_FACING) ) // Error ("PSIDE_FACING with splits"); test->testside = s; // if (s & PSIDE_FACING) facing++; if (s & PSIDE_FRONT) front++; if (s & PSIDE_BACK) back++; if (s == PSIDE_BOTH) both++; } //end for // give a value estimate for using this plane value = 5*facing - 5*splits - abs(front-back); // value = -5*splits; // value = 5*facing - 5*splits; if (mapplanes[pnum].type < 3) value+=5; // axial is better value -= epsilonbrush * 1000; // avoid! // never split a hint side except with another hint if (hintsplit && !(side->surf & SURF_HINT) ) value = -9999999; // save off the side test so we don't need // to recalculate it when we actually seperate // the brushes if (value > bestvalue) { bestvalue = value; bestside = side; bestsplits = splits; for (test = brushes; test ; test = test->next) test->side = test->testside; } //end if } //end for } //end for (brush = brushes; // if we found a good plane, don't bother trying any // other passes if (bestside) { if (pass > 1) { if (numthreads == 1) c_nonvis++; } if (pass > 0) node->detail_seperator = true; // not needed for vis break; } //end if } //end for (pass = 0; // // clear all the tested flags we set // for (brush = brushes ; brush ; brush=brush->next) { for (i = 0; i < brush->numsides; i++) { brush->sides[i].flags &= ~SFL_TESTED; } //end for } //end for return bestside; } //end of the function SelectSplitSide //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BrushMostlyOnSide (bspbrush_t *brush, plane_t *plane) { int i, j; winding_t *w; vec_t d, max; int side; max = 0; side = PSIDE_FRONT; for (i=0 ; inumsides ; i++) { w = brush->sides[i].winding; if (!w) continue; for (j=0 ; jnumpoints ; j++) { d = DotProduct (w->p[j], plane->normal) - plane->dist; if (d > max) { max = d; side = PSIDE_FRONT; } if (-d > max) { max = -d; side = PSIDE_BACK; } } } return side; } //end of the function BrushMostlyOnSide //=========================================================================== // Generates two new brushes, leaving the original // unchanged // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SplitBrush (bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back) { bspbrush_t *b[2]; int i, j; winding_t *w, *cw[2], *midwinding; plane_t *plane, *plane2; side_t *s, *cs; float d, d_front, d_back; *front = *back = NULL; plane = &mapplanes[planenum]; // check all points d_front = d_back = 0; for (i=0 ; inumsides ; i++) { w = brush->sides[i].winding; if (!w) continue; for (j=0 ; jnumpoints ; j++) { d = DotProduct (w->p[j], plane->normal) - plane->dist; if (d > 0 && d > d_front) d_front = d; if (d < 0 && d < d_back) d_back = d; } } if (d_front < 0.2) // PLANESIDE_EPSILON) { // only on back *back = CopyBrush (brush); return; } if (d_back > -0.2) // PLANESIDE_EPSILON) { // only on front *front = CopyBrush (brush); return; } // create a new winding from the split plane w = BaseWindingForPlane (plane->normal, plane->dist); for (i=0 ; inumsides && w ; i++) { plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; ChopWindingInPlace (&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON); } if (!w || WindingIsTiny(w)) { // the brush isn't really split int side; side = BrushMostlyOnSide (brush, plane); if (side == PSIDE_FRONT) *front = CopyBrush (brush); if (side == PSIDE_BACK) *back = CopyBrush (brush); //free a possible winding if (w) FreeWinding(w); return; } if (WindingIsHuge (w)) { Log_Write("WARNING: huge winding\n"); } midwinding = w; // split it for real for (i=0 ; i<2 ; i++) { b[i] = AllocBrush (brush->numsides+1); b[i]->original = brush->original; } // split all the current windings for (i=0 ; inumsides ; i++) { s = &brush->sides[i]; w = s->winding; if (!w) continue; ClipWindingEpsilon (w, plane->normal, plane->dist, 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); for (j=0 ; j<2 ; j++) { if (!cw[j]) continue; #if 0 if (WindingIsTiny (cw[j])) { FreeWinding (cw[j]); continue; } #endif cs = &b[j]->sides[b[j]->numsides]; b[j]->numsides++; *cs = *s; // cs->planenum = s->planenum; // cs->texinfo = s->texinfo; // cs->original = s->original; cs->winding = cw[j]; cs->flags &= ~SFL_TESTED; } } // see if we have valid polygons on both sides for (i=0 ; i<2 ; i++) { BoundBrush (b[i]); for (j=0 ; j<3 ; j++) { if (b[i]->mins[j] < -MAX_MAP_BOUNDS || b[i]->maxs[j] > MAX_MAP_BOUNDS) { Log_Write("bogus brush after clip"); break; } } if (b[i]->numsides < 3 || j < 3) { FreeBrush (b[i]); b[i] = NULL; } } if ( !(b[0] && b[1]) ) { if (!b[0] && !b[1]) Log_Write("split removed brush\r\n"); else Log_Write("split not on both sides\r\n"); if (b[0]) { FreeBrush (b[0]); *front = CopyBrush (brush); } if (b[1]) { FreeBrush (b[1]); *back = CopyBrush (brush); } return; } // add the midwinding to both sides for (i=0 ; i<2 ; i++) { cs = &b[i]->sides[b[i]->numsides]; b[i]->numsides++; cs->planenum = planenum^i^1; cs->texinfo = TEXINFO_NODE; //never use these sides as splitters cs->flags &= ~SFL_VISIBLE; cs->flags &= ~SFL_TESTED; if (i==0) cs->winding = CopyWinding (midwinding); else cs->winding = midwinding; } { vec_t v1; int i; for (i = 0; i < 2; i++) { v1 = BrushVolume (b[i]); if (v1 < 1.0) { FreeBrush(b[i]); b[i] = NULL; //Log_Write("tiny volume after clip"); } } if (!b[0] && !b[1]) { Log_Write("two tiny brushes\r\n"); } //end if } *front = b[0]; *back = b[1]; } //end of the function SplitBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SplitBrushList (bspbrush_t *brushes, node_t *node, bspbrush_t **front, bspbrush_t **back) { bspbrush_t *brush, *newbrush, *newbrush2; side_t *side; int sides; int i; *front = *back = NULL; for (brush = brushes; brush; brush = brush->next) { sides = brush->side; if (sides == PSIDE_BOTH) { // split into two brushes SplitBrush (brush, node->planenum, &newbrush, &newbrush2); if (newbrush) { newbrush->next = *front; *front = newbrush; } //end if if (newbrush2) { newbrush2->next = *back; *back = newbrush2; } //end if continue; } //end if newbrush = CopyBrush (brush); // if the planenum is actualy a part of the brush // find the plane and flag it as used so it won't be tried // as a splitter again if (sides & PSIDE_FACING) { for (i=0 ; inumsides ; i++) { side = newbrush->sides + i; if ( (side->planenum& ~1) == node->planenum) side->texinfo = TEXINFO_NODE; } //end for } //end if if (sides & PSIDE_FRONT) { newbrush->next = *front; *front = newbrush; continue; } //end if if (sides & PSIDE_BACK) { newbrush->next = *back; *back = newbrush; continue; } //end if } //end for } //end of the function SplitBrushList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void CheckBrushLists(bspbrush_t *brushlist1, bspbrush_t *brushlist2) { bspbrush_t *brush1, *brush2; for (brush1 = brushlist1; brush1; brush1 = brush1->next) { for (brush2 = brushlist2; brush2; brush2 = brush2->next) { assert(brush1 != brush2); } //end for } //end for } //end of the function CheckBrushLists //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int numrecurse = 0; node_t *BuildTree_r (node_t *node, bspbrush_t *brushes) { node_t *newnode; side_t *bestside; int i, totalmem; bspbrush_t *children[2]; qprintf("\r%6d", numrecurse); numrecurse++; if (numthreads == 1) { totalmem = WindingMemory() + c_nodememory + c_brushmemory; if (totalmem > c_peak_totalbspmemory) c_peak_totalbspmemory = totalmem; c_nodes++; } //endif if (drawflag) DrawBrushList(brushes, node); // find the best plane to use as a splitter bestside = SelectSplitSide (brushes, node); if (!bestside) { // leaf node node->side = NULL; node->planenum = -1; LeafNode(node, brushes); if (node->contents & CONTENTS_SOLID) c_solidleafnodes++; if (create_aas) { //free up memory!!! FreeBrushList(node->brushlist); node->brushlist = NULL; //free the node volume brush if (node->volume) { FreeBrush(node->volume); node->volume = NULL; } //end if } //end if return node; } //end if // this is a splitplane node node->side = bestside; node->planenum = bestside->planenum & ~1; // always use front facing //split the brush list in two for both children SplitBrushList (brushes, node, &children[0], &children[1]); //free the old brush list FreeBrushList (brushes); // allocate children before recursing for (i = 0; i < 2; i++) { newnode = AllocNode (); newnode->parent = node; node->children[i] = newnode; } //end for //split the volume brush of the node for the children SplitBrush (node->volume, node->planenum, &node->children[0]->volume, &node->children[1]->volume); if (create_aas) { //free the volume brush if (node->volume) { FreeBrush(node->volume); node->volume = NULL; } //end if } //end if // recursively process children for (i = 0; i < 2; i++) { node->children[i] = BuildTree_r(node->children[i], children[i]); } //end for return node; } //end of the function BuildTree_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== node_t *firstnode; //first node in the list node_t *lastnode; //last node in the list int nodelistsize; //number of nodes in the list int use_nodequeue = 0; //use nodequeue, otherwise a node stack is used int numwaiting = 0; void (*AddNodeToList)(node_t *node); //add the node to the front of the node list //(effectively using a node stack) void AddNodeToStack(node_t *node) { ThreadLock(); node->next = firstnode; firstnode = node; if (!lastnode) lastnode = node; nodelistsize++; ThreadUnlock(); // ThreadSemaphoreIncrease(1); } //end of the function AddNodeToStack //add the node to the end of the node list //(effectively using a node queue) void AddNodeToQueue(node_t *node) { ThreadLock(); node->next = NULL; if (lastnode) lastnode->next = node; else firstnode = node; lastnode = node; nodelistsize++; ThreadUnlock(); // ThreadSemaphoreIncrease(1); } //end of the function AddNodeToQueue //get the first node from the front of the node list node_t *NextNodeFromList(void) { node_t *node; ThreadLock(); numwaiting++; if (!firstnode) { if (numwaiting >= GetNumThreads()) ThreadSemaphoreIncrease(GetNumThreads()); } //end if ThreadUnlock(); ThreadSemaphoreWait(); ThreadLock(); numwaiting--; node = firstnode; if (firstnode) { firstnode = firstnode->next; nodelistsize--; } //end if if (!firstnode) lastnode = NULL; ThreadUnlock(); return node; } //end of the function NextNodeFromList //returns the size of the node list int NodeListSize(void) { int size; ThreadLock(); size = nodelistsize; ThreadUnlock(); return size; } //end of the function NodeListSize // void IncreaseNodeCounter(void) { ThreadLock(); //if (verbose) printf("\r%6d", numrecurse++); qprintf("\r%6d", numrecurse++); //qprintf("\r%6d %d, %5d ", numrecurse++, GetNumThreads(), nodelistsize); ThreadUnlock(); } //end of the function IncreaseNodeCounter //thread function, gets nodes from the nodelist and processes them void BuildTreeThread(int threadid) { node_t *newnode, *node; side_t *bestside; int i, totalmem; bspbrush_t *brushes; for (node = NextNodeFromList(); node; ) { //if the nodelist isn't empty try to add another thread //if (NodeListSize() > 10) AddThread(BuildTreeThread); //display the number of nodes processed so far if (numthreads == 1) IncreaseNodeCounter(); brushes = node->brushlist; if (numthreads == 1) { totalmem = WindingMemory() + c_nodememory + c_brushmemory; if (totalmem > c_peak_totalbspmemory) { c_peak_totalbspmemory = totalmem; } //end if c_nodes++; } //endif if (drawflag) { DrawBrushList(brushes, node); } //end if if (cancelconversion) { bestside = NULL; } //end if else { // find the best plane to use as a splitter bestside = SelectSplitSide(brushes, node); } //end else //if there's no split side left if (!bestside) { //create a leaf out of the node LeafNode(node, brushes); if (node->contents & CONTENTS_SOLID) c_solidleafnodes++; if (create_aas) { //free up memory!!! FreeBrushList(node->brushlist); node->brushlist = NULL; } //end if //free the node volume brush (it is not used anymore) if (node->volume) { FreeBrush(node->volume); node->volume = NULL; } //end if node = NextNodeFromList(); continue; } //end if // this is a splitplane node node->side = bestside; node->planenum = bestside->planenum & ~1; //always use front facing //allocate children for (i = 0; i < 2; i++) { newnode = AllocNode(); newnode->parent = node; node->children[i] = newnode; } //end for //split the brush list in two for both children SplitBrushList(brushes, node, &node->children[0]->brushlist, &node->children[1]->brushlist); CheckBrushLists(node->children[0]->brushlist, node->children[1]->brushlist); //free the old brush list FreeBrushList(brushes); node->brushlist = NULL; //split the volume brush of the node for the children SplitBrush(node->volume, node->planenum, &node->children[0]->volume, &node->children[1]->volume); if (!node->children[0]->volume || !node->children[1]->volume) { Error("child without volume brush"); } //end if //free the volume brush if (node->volume) { FreeBrush(node->volume); node->volume = NULL; } //end if //add both children to the node list //AddNodeToList(node->children[0]); AddNodeToList(node->children[1]); node = node->children[0]; } //end while RemoveThread(threadid); } //end of the function BuildTreeThread //=========================================================================== // build the bsp tree using a node list // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BuildTree(tree_t *tree) { int i; firstnode = NULL; lastnode = NULL; //use a node queue or node stack if (use_nodequeue) AddNodeToList = AddNodeToQueue; else AddNodeToList = AddNodeToStack; //setup thread locking ThreadSetupLock(); ThreadSetupSemaphore(); numwaiting = 0; // Log_Print("%6d threads max\n", numthreads); if (use_nodequeue) Log_Print("breadth first bsp building\n"); else Log_Print("depth first bsp building\n"); qprintf("%6d splits", 0); //add the first node to the list AddNodeToList(tree->headnode); //start the threads for (i = 0; i < numthreads; i++) AddThread(BuildTreeThread); //wait for all added threads to be finished WaitForAllThreadsFinished(); //shutdown the thread locking ThreadShutdownLock(); ThreadShutdownSemaphore(); } //end of the function BuildTree //=========================================================================== // The incoming brush list will be freed before exiting // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tree_t *BrushBSP(bspbrush_t *brushlist, vec3_t mins, vec3_t maxs) { int i, c_faces, c_nonvisfaces, c_brushes; bspbrush_t *b; node_t *node; tree_t *tree; vec_t volume; // vec3_t point; Log_Print("-------- Brush BSP ---------\n"); tree = Tree_Alloc(); c_faces = 0; c_nonvisfaces = 0; c_brushes = 0; c_totalsides = 0; for (b = brushlist; b; b = b->next) { c_brushes++; volume = BrushVolume(b); if (volume < microvolume) { Log_Print("WARNING: entity %i, brush %i: microbrush\n", b->original->entitynum, b->original->brushnum); } //end if for (i=0 ; inumsides ; i++) { if (b->sides[i].flags & SFL_BEVEL) continue; if (!b->sides[i].winding) continue; if (b->sides[i].texinfo == TEXINFO_NODE) continue; if (b->sides[i].flags & SFL_VISIBLE) { c_faces++; } //end if else { c_nonvisfaces++; //if (create_aas) b->sides[i].texinfo = TEXINFO_NODE; } //end if } //end for c_totalsides += b->numsides; AddPointToBounds (b->mins, tree->mins, tree->maxs); AddPointToBounds (b->maxs, tree->mins, tree->maxs); } //end for Log_Print("%6i brushes\n", c_brushes); Log_Print("%6i visible faces\n", c_faces); Log_Print("%6i nonvisible faces\n", c_nonvisfaces); Log_Print("%6i total sides\n", c_totalsides); c_active_brushes = c_brushes; c_nodememory = 0; c_brushmemory = 0; c_peak_brushmemory = 0; c_nodes = 0; c_nonvis = 0; node = AllocNode (); //volume of first node (head node) node->volume = BrushFromBounds (mins, maxs); // tree->headnode = node; //just get some statistics and the mins/maxs of the node numrecurse = 0; // qprintf("%6d splits", numrecurse); tree->headnode->brushlist = brushlist; BuildTree(tree); //build the bsp tree with the start node from the brushlist // node = BuildTree_r(node, brushlist); //if the conversion is cancelled if (cancelconversion) return tree; qprintf("\n"); Log_Write("%6d splits\r\n", numrecurse); // Log_Print("%6i visible nodes\n", c_nodes/2 - c_nonvis); // Log_Print("%6i nonvis nodes\n", c_nonvis); // Log_Print("%6i leaves\n", (c_nodes+1)/2); // Log_Print("%6i solid leaf nodes\n", c_solidleafnodes); // Log_Print("%6i active brushes\n", c_active_brushes); if (numthreads == 1) { // Log_Print("%6i KB of node memory\n", c_nodememory >> 10); // Log_Print("%6i KB of brush memory\n", c_brushmemory >> 10); // Log_Print("%6i KB of peak brush memory\n", c_peak_brushmemory >> 10); // Log_Print("%6i KB of winding memory\n", WindingMemory() >> 10); // Log_Print("%6i KB of peak winding memory\n", WindingPeakMemory() >> 10); Log_Print("%6i KB of peak total bsp memory\n", c_peak_totalbspmemory >> 10); } //end if /* point[0] = 1485; point[1] = 956.125; point[2] = 352.125; node = PointInLeaf(tree->headnode, point); if (node->planenum != PLANENUM_LEAF) { Log_Print("node not a leaf\n"); } //end if Log_Print("at %f %f %f:\n", point[0], point[1], point[2]); PrintContents(node->contents); Log_Print("node->expansionbboxes = %d\n", node->expansionbboxes); //*/ return tree; } //end of the function BrushBSP ================================================ FILE: code/bspc/bspc.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #if defined(WIN32) || defined(_WIN32) #include #include #include #include #else #include #include #include #include #endif #include "qbsp.h" #include "l_mem.h" #include "../botlib/aasfile.h" #include "../botlib/be_aas_cluster.h" #include "../botlib/be_aas_optimize.h" #include "aas_create.h" #include "aas_store.h" #include "aas_file.h" #include "aas_cfg.h" #include "be_aas_bspc.h" extern int use_nodequeue; //brushbsp.c extern int calcgrapplereach; //be_aas_reach.c float subdivide_size = 240; char source[1024]; char name[1024]; vec_t microvolume = 1.0; char outbase[32]; int entity_num; aas_settings_t aassettings; qboolean noprune; //don't prune nodes (bspc.c) qboolean glview; //create a gl view qboolean nodetail; //don't use detail brushes (map.c) qboolean fulldetail; //use but don't mark detail brushes (map.c) qboolean onlyents; //only process the entities (bspc.c) qboolean nomerge; //don't merge bsp node faces (faces.c) qboolean nowater; //don't use the water brushes (map.c) qboolean nocsg; //don't carve intersecting brushes (bspc.c) qboolean noweld; //use unique face vertexes (faces.c) qboolean noshare; //don't share bsp edges (faces.c) qboolean nosubdiv; //don't subdivide bsp node faces (faces.c) qboolean notjunc; //don't create tjunctions (edge melting) (faces.c) qboolean optimize; //enable optimisation qboolean leaktest; //perform a leak test qboolean verboseentities; qboolean freetree; //free the bsp tree when not needed anymore qboolean create_aas; //create an .AAS file qboolean nobrushmerge; //don't merge brushes qboolean lessbrushes; //create less brushes instead of correct texture placement qboolean cancelconversion; //true if the conversion is being cancelled qboolean noliquids; //no liquids when writing map file qboolean forcesidesvisible; //force all brush sides to be visible when loaded from bsp qboolean capsule_collision = 0; /* //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ProcessWorldModel (void) { entity_t *e; tree_t *tree; qboolean leaked; int brush_start, brush_end; e = &entities[entity_num]; brush_start = e->firstbrush; brush_end = brush_start + e->numbrushes; leaked = false; //process the whole world in one time tree = ProcessWorldBrushes(brush_start, brush_end); //create the bsp tree portals MakeTreePortals(tree); //mark all leafs that can be reached by entities if (FloodEntities(tree)) { FillOutside(tree->headnode); } //end if else { Log_Print("**** leaked ****\n"); leaked = true; LeakFile(tree); if (leaktest) { Log_Print("--- MAP LEAKED ---\n"); exit(0); } //end if } //end else MarkVisibleSides (tree, brush_start, brush_end); FloodAreas (tree); #ifndef ME if (glview) WriteGLView(tree, source); #endif MakeFaces(tree->headnode); FixTjuncs(tree->headnode); //NOTE: Never prune the nodes because the portals // are screwed when prunning is done and as // a result portal writing will crash //if (!noprune) PruneNodes(tree->headnode); WriteBSP(tree->headnode); if (!leaked) WritePortalFile(tree); Tree_Free(tree); } //end of the function ProcessWorldModel //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ProcessSubModel (void) { entity_t *e; int start, end; tree_t *tree; bspbrush_t *list; vec3_t mins, maxs; e = &entities[entity_num]; start = e->firstbrush; end = start + e->numbrushes; mins[0] = mins[1] = mins[2] = -4096; maxs[0] = maxs[1] = maxs[2] = 4096; list = MakeBspBrushList(start, end, mins, maxs); if (!nocsg) list = ChopBrushes (list); tree = BrushBSP (list, mins, maxs); MakeTreePortals (tree); MarkVisibleSides (tree, start, end); MakeFaces (tree->headnode); FixTjuncs (tree->headnode); WriteBSP (tree->headnode); Tree_Free(tree); } //end of the function ProcessSubModel //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ProcessModels (void) { BeginBSPFile(); for (entity_num = 0; entity_num < num_entities; entity_num++) { if (!entities[entity_num].numbrushes) continue; Log_Print("############### model %i ###############\n", nummodels); BeginModel(); if (entity_num == 0) ProcessWorldModel(); else ProcessSubModel(); EndModel(); if (!verboseentities) verbose = false; // don't bother printing submodels } //end for EndBSPFile(); } //end of the function ProcessModels //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Win_Map2Bsp(char *bspfilename) { double start, end; char path[1024]; start = I_FloatTime(); ThreadSetDefault(); //yeah sure Carmack //numthreads = 1; // multiple threads aren't helping... strcpy(source, ExpandArg(bspfilename)); StripExtension(source); //delete portal and line files sprintf(path, "%s.prt", source); remove(path); sprintf(path, "%s.lin", source); remove(path); strcpy(name, ExpandArg(bspfilename)); DefaultExtension(name, ".map"); // might be .reg Q2_AllocMaxBSP(); // SetModelNumbers(); SetLightStyles(); ProcessModels(); //write the BSP Q2_WriteBSPFile(bspfilename); Q2_FreeMaxBSP(); end = I_FloatTime(); Log_Print("%5.0f seconds elapsed\n", end-start); } //end of the function Win_Map2Bsp //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Map2Bsp(char *mapfilename, char *outputfilename) { double start, end; char path[1024]; start = I_FloatTime (); ThreadSetDefault (); //yeah sure Carmack //numthreads = 1; //multiple threads aren't helping... //SetQdirFromPath(bspfilename); strcpy(source, ExpandArg(mapfilename)); StripExtension(source); // delete portal and line files sprintf(path, "%s.prt", source); remove(path); sprintf(path, "%s.lin", source); remove(path); strcpy(name, ExpandArg(mapfilename)); DefaultExtension(name, ".map"); // might be .reg // // if onlyents, just grab the entites and resave // if (onlyents) { char out[1024]; Q2_AllocMaxBSP(); sprintf (out, "%s.bsp", source); Q2_LoadBSPFile(out, 0, 0); num_entities = 0; Q2_LoadMapFile(name); SetModelNumbers(); SetLightStyles(); Q2_UnparseEntities(); Q2_WriteBSPFile(out); // Q2_FreeMaxBSP(); } //end if else { // // start from scratch // Q2_AllocMaxBSP(); //load the map Q2_LoadMapFile(name); //create the .bsp file SetModelNumbers(); SetLightStyles(); ProcessModels(); //write the BSP Q2_WriteBSPFile(outputfilename); // Q2_FreeMaxBSP(); } //end else end = I_FloatTime(); Log_Print("%5.0f seconds elapsed\n", end-start); } //end of the function Map2Bsp */ //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AASOuputFile(quakefile_t *qf, char *outputpath, char *filename) { char ext[MAX_PATH]; // if (strlen(outputpath)) { strcpy(filename, outputpath); //append the bsp file base AppendPathSeperator(filename, MAX_PATH); ExtractFileBase(qf->origname, &filename[strlen(filename)]); //append .aas strcat(filename, ".aas"); return; } //end if // ExtractFileExtension(qf->filename, ext); if (!stricmp(ext, "pk3") || !stricmp(ext, "pak") || !stricmp(ext, "sin")) { strcpy(filename, qf->filename); while(strlen(filename) && filename[strlen(filename)-1] != '\\' && filename[strlen(filename)-1] != '/') { filename[strlen(filename)-1] = '\0'; } //end while strcat(filename, "maps"); if (access(filename, 0x04)) CreatePath(filename); //append the bsp file base AppendPathSeperator(filename, MAX_PATH); ExtractFileBase(qf->origname, &filename[strlen(filename)]); //append .aas strcat(filename, ".aas"); } //end if else { strcpy(filename, qf->filename); while(strlen(filename) && filename[strlen(filename)-1] != '.') { filename[strlen(filename)-1] = '\0'; } //end while strcat(filename, "aas"); } //end else } //end of the function AASOutputFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void CreateAASFilesForAllBSPFiles(char *quakepath) { #if defined(WIN32)|defined(_WIN32) WIN32_FIND_DATA filedata; HWND handle; struct _stat statbuf; #else glob_t globbuf; struct stat statbuf; int j; #endif int done; char filter[_MAX_PATH], bspfilter[_MAX_PATH], aasfilter[_MAX_PATH]; char aasfile[_MAX_PATH], buf[_MAX_PATH], foldername[_MAX_PATH]; quakefile_t *qf, *qf2, *files, *bspfiles, *aasfiles; strcpy(filter, quakepath); AppendPathSeperator(filter, sizeof(filter)); strcat(filter, "*"); #if defined(WIN32)|defined(_WIN32) handle = FindFirstFile(filter, &filedata); done = (handle == INVALID_HANDLE_VALUE); while(!done) { _splitpath(filter, foldername, NULL, NULL, NULL); _splitpath(filter, NULL, &foldername[strlen(foldername)], NULL, NULL); AppendPathSeperator(foldername, _MAX_PATH); strcat(foldername, filedata.cFileName); _stat(foldername, &statbuf); #else glob(filter, 0, NULL, &globbuf); for (j = 0; j < globbuf.gl_pathc; j++) { strcpy(foldername, globbuf.gl_pathv[j]); stat(foldername, &statbuf); #endif //if it is a folder if (statbuf.st_mode & S_IFDIR) { // AppendPathSeperator(foldername, sizeof(foldername)); //get all the bsp files strcpy(bspfilter, foldername); strcat(bspfilter, "maps/*.bsp"); files = FindQuakeFiles(bspfilter); strcpy(bspfilter, foldername); strcat(bspfilter, "*.pk3/maps/*.bsp"); bspfiles = FindQuakeFiles(bspfilter); for (qf = bspfiles; qf; qf = qf->next) if (!qf->next) break; if (qf) qf->next = files; else bspfiles = files; //get all the aas files strcpy(aasfilter, foldername); strcat(aasfilter, "maps/*.aas"); files = FindQuakeFiles(aasfilter); strcpy(aasfilter, foldername); strcat(aasfilter, "*.pk3/maps/*.aas"); aasfiles = FindQuakeFiles(aasfilter); for (qf = aasfiles; qf; qf = qf->next) if (!qf->next) break; if (qf) qf->next = files; else aasfiles = files; // for (qf = bspfiles; qf; qf = qf->next) { sprintf(aasfile, "%s/%s", qf->pakfile, qf->origname); Log_Print("found %s\n", aasfile); strcpy(&aasfile[strlen(aasfile)-strlen(".bsp")], ".aas"); for (qf2 = aasfiles; qf2; qf2 = qf2->next) { sprintf(buf, "%s/%s", qf2->pakfile, qf2->origname); if (!stricmp(aasfile, buf)) { Log_Print("found %s\n", buf); break; } //end if } //end for } //end for } //end if #if defined(WIN32)|defined(_WIN32) //find the next file done = !FindNextFile(handle, &filedata); } //end while #else } //end for globfree(&globbuf); #endif } //end of the function CreateAASFilesForAllBSPFiles //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== quakefile_t *GetArgumentFiles(int argc, char *argv[], int *i, char *ext) { quakefile_t *qfiles, *lastqf, *qf; int j; char buf[1024]; qfiles = NULL; lastqf = NULL; for (; (*i)+1 < argc && argv[(*i)+1][0] != '-'; (*i)++) { strcpy(buf, argv[(*i)+1]); for (j = strlen(buf)-1; j >= strlen(buf)-4; j--) if (buf[j] == '.') break; if (j >= strlen(buf)-4) strcpy(&buf[j+1], ext); qf = FindQuakeFiles(buf); if (!qf) continue; if (lastqf) lastqf->next = qf; else qfiles = qf; lastqf = qf; while(lastqf->next) lastqf = lastqf->next; } //end for return qfiles; } //end of the function GetArgumentFiles //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #define COMP_BSP2MAP 1 #define COMP_BSP2AAS 2 #define COMP_REACH 3 #define COMP_CLUSTER 4 #define COMP_AASOPTIMIZE 5 #define COMP_AASINFO 6 int main (int argc, char **argv) { int i, comp = 0; char outputpath[MAX_PATH] = ""; char filename[MAX_PATH] = "unknown"; quakefile_t *qfiles, *qf; double start_time; myargc = argc; myargv = argv; start_time = I_FloatTime(); Log_Open("bspc.log"); //open a log file Log_Print("BSPC version "BSPC_VERSION", %s %s\n", __DATE__, __TIME__); DefaultCfg(); for (i = 1; i < argc; i++) { if (!stricmp(argv[i],"-threads")) { if (i + 1 >= argc) {i = 0; break;} numthreads = atoi(argv[++i]); Log_Print("threads = %d\n", numthreads); } //end if else if (!stricmp(argv[i], "-noverbose")) { Log_Print("verbose = false\n"); verbose = false; } //end else if else if (!stricmp(argv[i], "-nocsg")) { Log_Print("nocsg = true\n"); nocsg = true; } //end else if else if (!stricmp(argv[i], "-optimize")) { Log_Print("optimize = true\n"); optimize = true; } //end else if /* else if (!stricmp(argv[i],"-glview")) { glview = true; } //end else if else if (!stricmp(argv[i], "-draw")) { Log_Print("drawflag = true\n"); drawflag = true; } //end else if else if (!stricmp(argv[i], "-noweld")) { Log_Print("noweld = true\n"); noweld = true; } //end else if else if (!stricmp(argv[i], "-noshare")) { Log_Print("noshare = true\n"); noshare = true; } //end else if else if (!stricmp(argv[i], "-notjunc")) { Log_Print("notjunc = true\n"); notjunc = true; } //end else if else if (!stricmp(argv[i], "-nowater")) { Log_Print("nowater = true\n"); nowater = true; } //end else if else if (!stricmp(argv[i], "-noprune")) { Log_Print("noprune = true\n"); noprune = true; } //end else if else if (!stricmp(argv[i], "-nomerge")) { Log_Print("nomerge = true\n"); nomerge = true; } //end else if else if (!stricmp(argv[i], "-nosubdiv")) { Log_Print("nosubdiv = true\n"); nosubdiv = true; } //end else if else if (!stricmp(argv[i], "-nodetail")) { Log_Print("nodetail = true\n"); nodetail = true; } //end else if else if (!stricmp(argv[i], "-fulldetail")) { Log_Print("fulldetail = true\n"); fulldetail = true; } //end else if else if (!stricmp(argv[i], "-onlyents")) { Log_Print("onlyents = true\n"); onlyents = true; } //end else if else if (!stricmp(argv[i], "-micro")) { if (i + 1 >= argc) {i = 0; break;} microvolume = atof(argv[++i]); Log_Print("microvolume = %f\n", microvolume); } //end else if else if (!stricmp(argv[i], "-leaktest")) { Log_Print("leaktest = true\n"); leaktest = true; } //end else if else if (!stricmp(argv[i], "-verboseentities")) { Log_Print("verboseentities = true\n"); verboseentities = true; } //end else if else if (!stricmp(argv[i], "-chop")) { if (i + 1 >= argc) {i = 0; break;} subdivide_size = atof(argv[++i]); Log_Print("subdivide_size = %f\n", subdivide_size); } //end else if else if (!stricmp (argv[i], "-tmpout")) { strcpy (outbase, "/tmp"); Log_Print("temp output\n"); } //end else if */ #ifdef ME else if (!stricmp(argv[i], "-freetree")) { freetree = true; Log_Print("freetree = true\n"); } //end else if else if (!stricmp(argv[i], "-grapplereach")) { calcgrapplereach = true; Log_Print("grapplereach = true\n"); } //end else if else if (!stricmp(argv[i], "-nobrushmerge")) { nobrushmerge = true; Log_Print("nobrushmerge = true\n"); } //end else if else if (!stricmp(argv[i], "-noliquids")) { noliquids = true; Log_Print("noliquids = true\n"); } //end else if else if (!stricmp(argv[i], "-forcesidesvisible")) { forcesidesvisible = true; Log_Print("forcesidesvisible = true\n"); } //end else if else if (!stricmp(argv[i], "-output")) { if (i + 1 >= argc) {i = 0; break;} if (access(argv[i+1], 0x04)) Warning("the folder %s does not exist", argv[i+1]); strcpy(outputpath, argv[++i]); } //end else if else if (!stricmp(argv[i], "-breadthfirst")) { use_nodequeue = true; Log_Print("breadthfirst = true\n"); } //end else if else if (!stricmp(argv[i], "-capsule")) { capsule_collision = true; Log_Print("capsule_collision = true\n"); } //end else if else if (!stricmp(argv[i], "-cfg")) { if (i + 1 >= argc) {i = 0; break;} if (!LoadCfgFile(argv[++i])) exit(0); } //end else if else if (!stricmp(argv[i], "-bsp2map")) { if (i + 1 >= argc) {i = 0; break;} comp = COMP_BSP2MAP; qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); } //end else if else if (!stricmp(argv[i], "-bsp2aas")) { if (i + 1 >= argc) {i = 0; break;} comp = COMP_BSP2AAS; qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); } //end else if else if (!stricmp(argv[i], "-aasall")) { if (i + 1 >= argc) {i = 0; break;} CreateAASFilesForAllBSPFiles(argv[++i]); } //end else if else if (!stricmp(argv[i], "-reach")) { if (i + 1 >= argc) {i = 0; break;} comp = COMP_REACH; qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); } //end else if else if (!stricmp(argv[i], "-cluster")) { if (i + 1 >= argc) {i = 0; break;} comp = COMP_CLUSTER; qfiles = GetArgumentFiles(argc, argv, &i, "bsp"); } //end else if else if (!stricmp(argv[i], "-aasinfo")) { if (i + 1 >= argc) {i = 0; break;} comp = COMP_AASINFO; qfiles = GetArgumentFiles(argc, argv, &i, "aas"); } //end else if else if (!stricmp(argv[i], "-aasopt")) { if (i + 1 >= argc) {i = 0; break;} comp = COMP_AASOPTIMIZE; qfiles = GetArgumentFiles(argc, argv, &i, "aas"); } //end else if #endif //ME else { Log_Print("unknown parameter %s\n", argv[i]); break; } //end else } //end for //if there are parameters and there's no mismatch in one of the parameters if (argc > 1 && i == argc) { switch(comp) { case COMP_BSP2MAP: { if (!qfiles) Log_Print("no files found\n"); for (qf = qfiles; qf; qf = qf->next) { //copy the output path strcpy(filename, outputpath); //append the bsp file base AppendPathSeperator(filename, MAX_PATH); ExtractFileBase(qf->origname, &filename[strlen(filename)]); //append .map strcat(filename, ".map"); // Log_Print("bsp2map: %s to %s\n", qf->origname, filename); if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); // LoadMapFromBSP(qf); //write the map file WriteMapFile(filename); } //end for break; } //end case case COMP_BSP2AAS: { if (!qfiles) Log_Print("no files found\n"); for (qf = qfiles; qf; qf = qf->next) { AASOuputFile(qf, outputpath, filename); // Log_Print("bsp2aas: %s to %s\n", qf->origname, filename); if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); //set before map loading create_aas = 1; LoadMapFromBSP(qf); //create the AAS file AAS_Create(filename); //if it's a Quake3 map calculate the reachabilities and clusters if (loadedmaptype == MAPTYPE_QUAKE3) AAS_CalcReachAndClusters(qf); // if (optimize) AAS_Optimize(); // //write out the stored AAS file if (!AAS_WriteAASFile(filename)) { Error("error writing %s\n", filename); } //end if //deallocate memory AAS_FreeMaxAAS(); } //end for break; } //end case case COMP_REACH: { if (!qfiles) Log_Print("no files found\n"); for (qf = qfiles; qf; qf = qf->next) { AASOuputFile(qf, outputpath, filename); // Log_Print("reach: %s to %s\n", qf->origname, filename); if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); //if the AAS file exists in the output directory if (!access(filename, 0x04)) { if (!AAS_LoadAASFile(filename, 0, 0)) { Error("error loading aas file %s\n", filename); } //end if //assume it's a Quake3 BSP file loadedmaptype = MAPTYPE_QUAKE3; } //end if else { Warning("AAS file %s not found in output folder\n", filename); Log_Print("creating %s...\n", filename); //set before map loading create_aas = 1; LoadMapFromBSP(qf); //create the AAS file AAS_Create(filename); } //end else //if it's a Quake3 map calculate the reachabilities and clusters if (loadedmaptype == MAPTYPE_QUAKE3) { AAS_CalcReachAndClusters(qf); } //end if // if (optimize) AAS_Optimize(); //write out the stored AAS file if (!AAS_WriteAASFile(filename)) { Error("error writing %s\n", filename); } //end if //deallocate memory AAS_FreeMaxAAS(); } //end for break; } //end case case COMP_CLUSTER: { if (!qfiles) Log_Print("no files found\n"); for (qf = qfiles; qf; qf = qf->next) { AASOuputFile(qf, outputpath, filename); // Log_Print("cluster: %s to %s\n", qf->origname, filename); if (qf->type != QFILETYPE_BSP) Warning("%s is probably not a BSP file\n", qf->origname); //if the AAS file exists in the output directory if (!access(filename, 0x04)) { if (!AAS_LoadAASFile(filename, 0, 0)) { Error("error loading aas file %s\n", filename); } //end if //assume it's a Quake3 BSP file loadedmaptype = MAPTYPE_QUAKE3; //if it's a Quake3 map calculate the clusters if (loadedmaptype == MAPTYPE_QUAKE3) { aasworld.numclusters = 0; AAS_InitBotImport(); AAS_InitClustering(); } //end if } //end if else { Warning("AAS file %s not found in output folder\n", filename); Log_Print("creating %s...\n", filename); //set before map loading create_aas = 1; LoadMapFromBSP(qf); //create the AAS file AAS_Create(filename); //if it's a Quake3 map calculate the reachabilities and clusters if (loadedmaptype == MAPTYPE_QUAKE3) AAS_CalcReachAndClusters(qf); } //end else // if (optimize) AAS_Optimize(); //write out the stored AAS file if (!AAS_WriteAASFile(filename)) { Error("error writing %s\n", filename); } //end if //deallocate memory AAS_FreeMaxAAS(); } //end for break; } //end case case COMP_AASOPTIMIZE: { if (!qfiles) Log_Print("no files found\n"); for (qf = qfiles; qf; qf = qf->next) { AASOuputFile(qf, outputpath, filename); // Log_Print("optimizing: %s to %s\n", qf->origname, filename); if (qf->type != QFILETYPE_AAS) Warning("%s is probably not a AAS file\n", qf->origname); // AAS_InitBotImport(); // if (!AAS_LoadAASFile(qf->filename, qf->offset, qf->length)) { Error("error loading aas file %s\n", qf->filename); } //end if AAS_Optimize(); //write out the stored AAS file if (!AAS_WriteAASFile(filename)) { Error("error writing %s\n", filename); } //end if //deallocate memory AAS_FreeMaxAAS(); } //end for break; } //end case case COMP_AASINFO: { if (!qfiles) Log_Print("no files found\n"); for (qf = qfiles; qf; qf = qf->next) { AASOuputFile(qf, outputpath, filename); // Log_Print("aas info for: %s\n", filename); if (qf->type != QFILETYPE_AAS) Warning("%s is probably not a AAS file\n", qf->origname); // AAS_InitBotImport(); // if (!AAS_LoadAASFile(qf->filename, qf->offset, qf->length)) { Error("error loading aas file %s\n", qf->filename); } //end if AAS_ShowTotals(); } //end for } //end case default: { Log_Print("don't know what to do\n"); break; } //end default } //end switch } //end if else { Log_Print("Usage: bspc [- [- ...]]\n" #if defined(WIN32) || defined(_WIN32) "Example 1: bspc -bsp2aas d:\\quake3\\baseq3\\maps\\mymap?.bsp\n" "Example 2: bspc -bsp2aas d:\\quake3\\baseq3\\pak0.pk3\\maps/q3dm*.bsp\n" #else "Example 1: bspc -bsp2aas /quake3/baseq3/maps/mymap?.bsp\n" "Example 2: bspc -bsp2aas /quake3/baseq3/pak0.pk3/maps/q3dm*.bsp\n" #endif "\n" "Switches:\n" //" bsp2map <[pakfilter/]filter.bsp> = convert BSP to MAP\n" //" aasall = create AAS files for all BSPs\n" " bsp2aas <[pakfilter/]filter.bsp> = convert BSP to AAS\n" " reach = compute reachability & clusters\n" " cluster = compute clusters\n" " aasopt = optimize aas file\n" " aasinfo = show AAS file info\n" " output = set output path\n" " threads = set number of threads to X\n" " cfg = use this cfg file\n" " optimize = enable optimization\n" " noverbose = disable verbose output\n" " breadthfirst = breadth first bsp building\n" " nobrushmerge = don't merge brushes\n" " noliquids = don't write liquids to map\n" " freetree = free the bsp tree\n" " nocsg = disables brush chopping\n" " forcesidesvisible = force all sides to be visible\n" " grapplereach = calculate grapple reachabilities\n" /* " glview = output a GL view\n" " draw = enables drawing\n" " noweld = disables weld\n" " noshare = disables sharing\n" " notjunc = disables juncs\n" " nowater = disables water brushes\n" " noprune = disables node prunes\n" " nomerge = disables face merging\n" " nosubdiv = disables subdeviding\n" " nodetail = disables detail brushes\n" " fulldetail = enables full detail\n" " onlyents = only compile entities with bsp\n" " micro \n" " = sets the micro volume to the given float\n" " leaktest = perform a leak test\n" " verboseentities\n" " = enable entity verbose mode\n" " chop \n" " = sets the subdivide size to the given float\n"*/ "\n"); } //end else Log_Print("BSPC run time is %5.0f seconds\n", I_FloatTime() - start_time); Log_Close(); //close the log file return 0; } //end of the function main ================================================ FILE: code/bspc/bspc.sln ================================================ Microsoft Visual Studio Solution File, Format Version 8.00 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bspc", "bspc.vcproj", "{4E4EBC16-F345-4667-84E1-86633BAFDAE6}" ProjectSection(ProjectDependencies) = postProject EndProjectSection EndProject Global GlobalSection(SourceCodeControl) = preSolution SccNumberOfProjects = 1 SccProjectUniqueName0 = bspc.vcproj SccProjectName0 = \u0022$/source/code/bspc\u0022,\u0020IGAAAAAA SccLocalPath0 = . SccProvider0 = MSSCCI:Perforce\u0020SCM EndGlobalSection GlobalSection(SolutionConfiguration) = preSolution Debug = Debug Release = Release EndGlobalSection GlobalSection(ProjectConfiguration) = postSolution {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Debug.ActiveCfg = Debug|Win32 {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Debug.Build.0 = Debug|Win32 {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Release.ActiveCfg = Release|Win32 {4E4EBC16-F345-4667-84E1-86633BAFDAE6}.Release.Build.0 = Release|Win32 EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution EndGlobalSection GlobalSection(ExtensibilityAddIns) = postSolution EndGlobalSection EndGlobal ================================================ FILE: code/bspc/bspc.vcproj ================================================ ================================================ FILE: code/bspc/cfgq3.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ //=========================================================================== // BSPC configuration file // Quake3 //=========================================================================== #define PRESENCE_NONE 1 #define PRESENCE_NORMAL 2 #define PRESENCE_CROUCH 4 bbox //30x30x56 { presencetype PRESENCE_NORMAL flags 0x0000 mins {-15, -15, -24} maxs {15, 15, 32} } //end bbox bbox //30x30x40 { presencetype PRESENCE_CROUCH flags 0x0001 mins {-15, -15, -24} maxs {15, 15, 16} } //end bbox settings { phys_gravitydirection {0, 0, -1} phys_friction 6 phys_stopspeed 100 phys_gravity 800 phys_waterfriction 1 phys_watergravity 400 phys_maxvelocity 320 phys_maxwalkvelocity 320 phys_maxcrouchvelocity 100 phys_maxswimvelocity 150 phys_maxacceleration 2200 phys_airaccelerate 0 phys_maxstep 18 phys_maxsteepness 0.7 phys_maxwaterjump 19 phys_maxbarrier 33 phys_jumpvel 270 phys_falldelta5 40 phys_falldelta10 60 rs_waterjump 400 rs_teleport 50 rs_barrierjump 100 rs_startcrouch 300 rs_startgrapple 500 rs_startwalkoffledge 70 rs_startjump 300 rs_rocketjump 500 rs_bfgjump 500 rs_jumppad 250 rs_aircontrolledjumppad 300 rs_funcbob 300 rs_startelevator 50 rs_falldamage5 300 rs_falldamage10 500 rs_maxjumpfallheight 450 } //end settings ================================================ FILE: code/bspc/csg.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" /* tag all brushes with original contents brushes may contain multiple contents there will be no brush overlap after csg phase */ int minplanenums[3]; int maxplanenums[3]; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void CheckBSPBrush(bspbrush_t *brush) { int i, j; plane_t *plane1, *plane2; //check if the brush is convex... flipped planes make a brush non-convex for (i = 0; i < brush->numsides; i++) { for (j = 0; j < brush->numsides; j++) { if (i == j) continue; plane1 = &mapplanes[brush->sides[i].planenum]; plane2 = &mapplanes[brush->sides[j].planenum]; // if (WindingsNonConvex(brush->sides[i].winding, brush->sides[j].winding, plane1->normal, plane2->normal, plane1->dist, plane2->dist)) { Log_Print("non convex brush"); break; } //end if } //end for } //end for BoundBrush(brush); //check for out of bound brushes for (i = 0; i < 3; i++) { if (brush->mins[i] < -MAX_MAP_BOUNDS || brush->maxs[i] > MAX_MAP_BOUNDS) { Log_Print("brush: bounds out of range\n"); Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, brush->mins[i], i, brush->maxs[i]); break; } //end if if (brush->mins[i] > MAX_MAP_BOUNDS || brush->maxs[i] < -MAX_MAP_BOUNDS) { Log_Print("brush: no visible sides on brush\n"); Log_Print("ob->mins[%d] = %f, ob->maxs[%d] = %f\n", i, brush->mins[i], i, brush->maxs[i]); break; } //end if } //end for } //end of the function CheckBSPBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void BSPBrushWindings(bspbrush_t *brush) { int i, j; winding_t *w; plane_t *plane; for (i = 0; i < brush->numsides; i++) { plane = &mapplanes[brush->sides[i].planenum]; w = BaseWindingForPlane(plane->normal, plane->dist); for (j = 0; j < brush->numsides && w; j++) { if (i == j) continue; plane = &mapplanes[brush->sides[j].planenum^1]; ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); } //end for brush->sides[i].winding = w; } //end for } //end of the function BSPBrushWindings //=========================================================================== // NOTE: can't keep brush->original intact // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *TryMergeBrushes(bspbrush_t *brush1, bspbrush_t *brush2) { int i, j, k, n, shared; side_t *side1, *side2, *cs; plane_t *plane1, *plane2; bspbrush_t *newbrush; //check for bounding box overlapp for (i = 0; i < 3; i++) { if (brush1->mins[i] > brush2->maxs[i] + 2 || brush1->maxs[i] < brush2->mins[i] - 2) { return NULL; } //end if } //end for // shared = 0; //check if the brush is convex... flipped planes make a brush non-convex for (i = 0; i < brush1->numsides; i++) { side1 = &brush1->sides[i]; //don't check the "shared" sides for (k = 0; k < brush2->numsides; k++) { side2 = &brush2->sides[k]; if (side1->planenum == (side2->planenum^1)) { shared++; //there may only be ONE shared side if (shared > 1) return NULL; break; } //end if } //end for if (k < brush2->numsides) continue; // for (j = 0; j < brush2->numsides; j++) { side2 = &brush2->sides[j]; //don't check the "shared" sides for (n = 0; n < brush1->numsides; n++) { side1 = &brush1->sides[n]; if (side1->planenum == (side2->planenum^1)) break; } //end for if (n < brush1->numsides) continue; // side1 = &brush1->sides[i]; //if the side is in the same plane //* if (side1->planenum == side2->planenum) { if (side1->texinfo != TEXINFO_NODE && side2->texinfo != TEXINFO_NODE && side1->texinfo != side2->texinfo) return NULL; continue; } //end if // plane1 = &mapplanes[side1->planenum]; plane2 = &mapplanes[side2->planenum]; // if (WindingsNonConvex(side1->winding, side2->winding, plane1->normal, plane2->normal, plane1->dist, plane2->dist)) { return NULL; } //end if } //end for } //end for newbrush = AllocBrush(brush1->numsides + brush2->numsides); newbrush->original = brush1->original; newbrush->numsides = 0; //newbrush->side = brush1->side; //brush contents //fix texinfos for sides lying in the same plane for (i = 0; i < brush1->numsides; i++) { side1 = &brush1->sides[i]; // for (n = 0; n < brush2->numsides; n++) { side2 = &brush2->sides[n]; //if both sides are in the same plane get the texinfo right if (side1->planenum == side2->planenum) { if (side1->texinfo == TEXINFO_NODE) side1->texinfo = side2->texinfo; if (side2->texinfo == TEXINFO_NODE) side2->texinfo = side1->texinfo; } //end if } //end for } //end for // for (i = 0; i < brush1->numsides; i++) { side1 = &brush1->sides[i]; //don't add the "shared" sides for (n = 0; n < brush2->numsides; n++) { side2 = &brush2->sides[n]; if (side1->planenum == (side2->planenum ^ 1)) break; } //end for if (n < brush2->numsides) continue; // for (n = 0; n < newbrush->numsides; n++) { cs = &newbrush->sides[n]; if (cs->planenum == side1->planenum) { Log_Print("brush duplicate plane\n"); break; } //end if } //end if if (n < newbrush->numsides) continue; //add this side cs = &newbrush->sides[newbrush->numsides]; newbrush->numsides++; *cs = *side1; } //end for for (j = 0; j < brush2->numsides; j++) { side2 = &brush2->sides[j]; for (n = 0; n < brush1->numsides; n++) { side1 = &brush1->sides[n]; //if the side is in the same plane if (side2->planenum == side1->planenum) break; //don't add the "shared" sides if (side2->planenum == (side1->planenum ^ 1)) break; } //end for if (n < brush1->numsides) continue; // for (n = 0; n < newbrush->numsides; n++) { cs = &newbrush->sides[n]; if (cs->planenum == side2->planenum) { Log_Print("brush duplicate plane\n"); break; } //end if } //end if if (n < newbrush->numsides) continue; //add this side cs = &newbrush->sides[newbrush->numsides]; newbrush->numsides++; *cs = *side2; } //end for BSPBrushWindings(newbrush); BoundBrush(newbrush); CheckBSPBrush(newbrush); return newbrush; } //end of the function TryMergeBrushes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *MergeBrushes(bspbrush_t *brushlist) { int nummerges, merged; bspbrush_t *b1, *b2, *tail, *newbrush, *newbrushlist; bspbrush_t *lastb2; if (!brushlist) return NULL; qprintf("%5d brushes merged", nummerges = 0); do { for (tail = brushlist; tail; tail = tail->next) { if (!tail->next) break; } //end for merged = 0; newbrushlist = NULL; for (b1 = brushlist; b1; b1 = brushlist) { lastb2 = b1; for (b2 = b1->next; b2; b2 = b2->next) { //if the brushes don't have the same contents if (b1->original->contents != b2->original->contents || b1->original->expansionbbox != b2->original->expansionbbox) newbrush = NULL; else newbrush = TryMergeBrushes(b1, b2); if (newbrush) { tail->next = newbrush; lastb2->next = b2->next; brushlist = brushlist->next; FreeBrush(b1); FreeBrush(b2); for (tail = brushlist; tail; tail = tail->next) { if (!tail->next) break; } //end for merged++; qprintf("\r%5d", nummerges++); break; } //end if lastb2 = b2; } //end for //if b1 can't be merged with any of the other brushes if (!b2) { brushlist = brushlist->next; //keep b1 b1->next = newbrushlist; newbrushlist = b1; } //end else } //end for brushlist = newbrushlist; } while(merged); qprintf("\n"); return newbrushlist; } //end of the function MergeBrushes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SplitBrush2 (bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back) { SplitBrush (brush, planenum, front, back); #if 0 if (*front && (*front)->sides[(*front)->numsides-1].texinfo == -1) (*front)->sides[(*front)->numsides-1].texinfo = (*front)->sides[0].texinfo; // not -1 if (*back && (*back)->sides[(*back)->numsides-1].texinfo == -1) (*back)->sides[(*back)->numsides-1].texinfo = (*back)->sides[0].texinfo; // not -1 #endif } //end of the function SplitBrush2 //=========================================================================== // Returns a list of brushes that remain after B is subtracted from A. // May by empty if A is contained inside B. // The originals are undisturbed. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *SubtractBrush (bspbrush_t *a, bspbrush_t *b) { // a - b = out (list) int i; bspbrush_t *front, *back; bspbrush_t *out, *in; in = a; out = NULL; for (i = 0; i < b->numsides && in; i++) { SplitBrush2(in, b->sides[i].planenum, &front, &back); if (in != a) FreeBrush(in); if (front) { // add to list front->next = out; out = front; } //end if in = back; } //end for if (in) { FreeBrush (in); } //end if else { // didn't really intersect FreeBrushList (out); return a; } //end else return out; } //end of the function SubtractBrush //=========================================================================== // Returns a single brush made up by the intersection of the // two provided brushes, or NULL if they are disjoint. // // The originals are undisturbed. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b) { int i; bspbrush_t *front, *back; bspbrush_t *in; in = a; for (i=0 ; inumsides && in ; i++) { SplitBrush2(in, b->sides[i].planenum, &front, &back); if (in != a) FreeBrush(in); if (front) FreeBrush(front); in = back; } //end for if (in == a) return NULL; in->next = NULL; return in; } //end of the function IntersectBrush //=========================================================================== // Returns true if the two brushes definately do not intersect. // There will be false negatives for some non-axial combinations. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean BrushesDisjoint (bspbrush_t *a, bspbrush_t *b) { int i, j; // check bounding boxes for (i=0 ; i<3 ; i++) if (a->mins[i] >= b->maxs[i] || a->maxs[i] <= b->mins[i]) return true; // bounding boxes don't overlap // check for opposing planes for (i=0 ; inumsides ; i++) { for (j=0 ; jnumsides ; j++) { if (a->sides[i].planenum == (b->sides[j].planenum^1) ) return true; // opposite planes, so not touching } } return false; // might intersect } //end of the function BrushesDisjoint //=========================================================================== // Returns a content word for the intersection of two brushes. // Some combinations will generate a combination (water + clip), // but most will be the stronger of the two contents. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int IntersectionContents (int c1, int c2) { int out; out = c1 | c2; if (out & CONTENTS_SOLID) out = CONTENTS_SOLID; return out; } //end of the function IntersectionContents //=========================================================================== // Any planes shared with the box edge will be set to no texinfo // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *ClipBrushToBox(bspbrush_t *brush, vec3_t clipmins, vec3_t clipmaxs) { int i, j; bspbrush_t *front, *back; int p; for (j=0 ; j<2 ; j++) { if (brush->maxs[j] > clipmaxs[j]) { SplitBrush (brush, maxplanenums[j], &front, &back); if (front) FreeBrush (front); brush = back; if (!brush) return NULL; } if (brush->mins[j] < clipmins[j]) { SplitBrush (brush, minplanenums[j], &front, &back); if (back) FreeBrush (back); brush = front; if (!brush) return NULL; } } // remove any colinear faces for (i=0 ; inumsides ; i++) { p = brush->sides[i].planenum & ~1; if (p == maxplanenums[0] || p == maxplanenums[1] || p == minplanenums[0] || p == minplanenums[1]) { brush->sides[i].texinfo = TEXINFO_NODE; brush->sides[i].flags &= ~SFL_VISIBLE; } } return brush; } //end of the function ClipBrushToBox //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *MakeBspBrushList(int startbrush, int endbrush, vec3_t clipmins, vec3_t clipmaxs) { mapbrush_t *mb; bspbrush_t *brushlist, *newbrush; int i, j; int c_faces; int c_brushes; int numsides; int vis; vec3_t normal; float dist; for (i=0 ; i<2 ; i++) { VectorClear (normal); normal[i] = 1; dist = clipmaxs[i]; maxplanenums[i] = FindFloatPlane(normal, dist); dist = clipmins[i]; minplanenums[i] = FindFloatPlane(normal, dist); } brushlist = NULL; c_faces = 0; c_brushes = 0; for (i=startbrush ; inumsides; if (!numsides) continue; // make sure the brush has at least one face showing vis = 0; for (j=0 ; joriginal_sides[j].flags & SFL_VISIBLE) && mb->original_sides[j].winding) vis++; #if 0 if (!vis) continue; // no faces at all #endif // if the brush is outside the clip area, skip it for (j=0 ; j<3 ; j++) if (mb->mins[j] >= clipmaxs[j] || mb->maxs[j] <= clipmins[j]) break; if (j != 3) continue; // // make a copy of the brush // newbrush = AllocBrush (mb->numsides); newbrush->original = mb; newbrush->numsides = mb->numsides; memcpy (newbrush->sides, mb->original_sides, numsides*sizeof(side_t)); for (j=0 ; jsides[j].winding) newbrush->sides[j].winding = CopyWinding (newbrush->sides[j].winding); if (newbrush->sides[j].surf & SURF_HINT) newbrush->sides[j].flags |= SFL_VISIBLE; // hints are always visible } VectorCopy (mb->mins, newbrush->mins); VectorCopy (mb->maxs, newbrush->maxs); // // carve off anything outside the clip box // newbrush = ClipBrushToBox (newbrush, clipmins, clipmaxs); if (!newbrush) continue; c_faces += vis; c_brushes++; newbrush->next = brushlist; brushlist = newbrush; } return brushlist; } //end of the function MakeBspBrushList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *AddBrushListToTail (bspbrush_t *list, bspbrush_t *tail) { bspbrush_t *walk, *next; for (walk=list ; walk ; walk=next) { // add to end of list next = walk->next; walk->next = NULL; tail->next = walk; tail = walk; } //end for return tail; } //end of the function AddBrushListToTail //=========================================================================== // Builds a new list that doesn't hold the given brush // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *CullList(bspbrush_t *list, bspbrush_t *skip1) { bspbrush_t *newlist; bspbrush_t *next; newlist = NULL; for ( ; list ; list = next) { next = list->next; if (list == skip1) { FreeBrush (list); continue; } list->next = newlist; newlist = list; } return newlist; } //end of the function CullList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== /* void WriteBrushMap(char *name, bspbrush_t *list) { FILE *f; side_t *s; int i; winding_t *w; Log_Print("writing %s\n", name); f = fopen (name, "wb"); if (!f) Error ("Can't write %s\b", name); fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); for ( ; list ; list=list->next ) { fprintf (f, "{\n"); for (i=0,s=list->sides ; inumsides ; i++,s++) { w = BaseWindingForPlane (mapplanes[s->planenum].normal, mapplanes[s->planenum].dist); fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); fprintf (f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture); FreeWinding (w); } fprintf (f, "}\n"); } fprintf (f, "}\n"); fclose (f); } //end of the function WriteBrushMap */ //=========================================================================== // Returns true if b1 is allowed to bite b2 // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean BrushGE (bspbrush_t *b1, bspbrush_t *b2) { #ifdef ME if (create_aas) { if (b1->original->expansionbbox != b2->original->expansionbbox) { return false; } //end if //never have something else bite a ladder brush //never have a ladder brush bite something else if ( (b1->original->contents & CONTENTS_LADDER) && !(b2->original->contents & CONTENTS_LADDER)) { return false; } //end if } //end if #endif //ME // detail brushes never bite structural brushes if ( (b1->original->contents & CONTENTS_DETAIL) && !(b2->original->contents & CONTENTS_DETAIL) ) { return false; } //end if if (b1->original->contents & CONTENTS_SOLID) { return true; } //end if return false; } //end of the function BrushGE //=========================================================================== // Carves any intersecting solid brushes into the minimum number // of non-intersecting brushes. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *ChopBrushes (bspbrush_t *head) { bspbrush_t *b1, *b2, *next; bspbrush_t *tail; bspbrush_t *keep; bspbrush_t *sub, *sub2; int c1, c2; int num_csg_iterations; Log_Print("-------- Brush CSG ---------\n"); Log_Print("%6d original brushes\n", CountBrushList (head)); num_csg_iterations = 0; qprintf("%6d output brushes", num_csg_iterations); #if 0 if (startbrush == 0) WriteBrushList ("before.gl", head, false); #endif keep = NULL; newlist: // find tail if (!head) return NULL; for (tail = head; tail->next; tail = tail->next) ; for (b1=head ; b1 ; b1=next) { next = b1->next; //if the conversion is cancelled if (cancelconversion) { b1->next = keep; keep = b1; continue; } //end if for (b2 = b1->next; b2; b2 = b2->next) { if (BrushesDisjoint (b1, b2)) continue; sub = NULL; sub2 = NULL; c1 = 999999; c2 = 999999; if (BrushGE (b2, b1)) { sub = SubtractBrush (b1, b2); if (sub == b1) { continue; // didn't really intersect } //end if if (!sub) { // b1 is swallowed by b2 head = CullList (b1, b1); goto newlist; } c1 = CountBrushList (sub); } if ( BrushGE (b1, b2) ) { sub2 = SubtractBrush (b2, b1); if (sub2 == b2) continue; // didn't really intersect if (!sub2) { // b2 is swallowed by b1 FreeBrushList (sub); head = CullList (b1, b2); goto newlist; } c2 = CountBrushList (sub2); } if (!sub && !sub2) continue; // neither one can bite // only accept if it didn't fragment // (commenting this out allows full fragmentation) if (c1 > 1 && c2 > 1) { if (sub2) FreeBrushList (sub2); if (sub) FreeBrushList (sub); continue; } if (c1 < c2) { if (sub2) FreeBrushList (sub2); tail = AddBrushListToTail (sub, tail); head = CullList (b1, b1); goto newlist; } //end if else { if (sub) FreeBrushList (sub); tail = AddBrushListToTail (sub2, tail); head = CullList (b1, b2); goto newlist; } //end else } //end for if (!b2) { // b1 is no longer intersecting anything, so keep it b1->next = keep; keep = b1; } //end if num_csg_iterations++; qprintf("\r%6d", num_csg_iterations); } //end for if (cancelconversion) return keep; // qprintf("\n"); Log_Write("%6d output brushes\r\n", num_csg_iterations); #if 0 { WriteBrushList ("after.gl", keep, false); WriteBrushMap ("after.map", keep); } #endif return keep; } //end of the function ChopBrushes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *InitialBrushList (bspbrush_t *list) { bspbrush_t *b; bspbrush_t *out, *newb; int i; // only return brushes that have visible faces out = NULL; for (b=list ; b ; b=b->next) { #if 0 for (i=0 ; inumsides ; i++) if (b->sides[i].flags & SFL_VISIBLE) break; if (i == b->numsides) continue; #endif newb = CopyBrush (b); newb->next = out; out = newb; // clear visible, so it must be set by MarkVisibleFaces_r // to be used in the optimized list for (i=0 ; inumsides ; i++) { newb->sides[i].original = &b->sides[i]; // newb->sides[i].visible = true; b->sides[i].flags &= ~SFL_VISIBLE; } } return out; } //end of the function InitialBrushList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *OptimizedBrushList (bspbrush_t *list) { bspbrush_t *b; bspbrush_t *out, *newb; int i; // only return brushes that have visible faces out = NULL; for (b=list ; b ; b=b->next) { for (i=0 ; inumsides ; i++) if (b->sides[i].flags & SFL_VISIBLE) break; if (i == b->numsides) continue; newb = CopyBrush (b); newb->next = out; out = newb; } //end for // WriteBrushList ("vis.gl", out, true); return out; } //end of the function OptimizeBrushList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tree_t *ProcessWorldBrushes(int brush_start, int brush_end) { bspbrush_t *brushes; tree_t *tree; node_t *node; vec3_t mins, maxs; //take the whole world mins[0] = map_mins[0] - 8; mins[1] = map_mins[1] - 8; mins[2] = map_mins[2] - 8; maxs[0] = map_maxs[0] + 8; maxs[1] = map_maxs[1] + 8; maxs[2] = map_maxs[2] + 8; //reset the brush bsp ResetBrushBSP(); // the makelist and chopbrushes could be cached between the passes... //create a list with brushes that are within the given mins/maxs //some brushes will be cut and only the part that falls within the //mins/maxs will be in the bush list brushes = MakeBspBrushList(brush_start, brush_end, mins, maxs); // if (!brushes) { node = AllocNode (); node->planenum = PLANENUM_LEAF; node->contents = CONTENTS_SOLID; tree = Tree_Alloc(); tree->headnode = node; VectorCopy(mins, tree->mins); VectorCopy(maxs, tree->maxs); } //end if else { //Carves any intersecting solid brushes into the minimum number //of non-intersecting brushes. if (!nocsg) { brushes = ChopBrushes(brushes); /* if (create_aas) { brushes = MergeBrushes(brushes); } //end if*/ } //end if //if the conversion is cancelled if (cancelconversion) { FreeBrushList(brushes); return NULL; } //end if //create the actual bsp tree tree = BrushBSP(brushes, mins, maxs); } //end else //return the tree return tree; } //end of the function ProcessWorldBrushes ================================================ FILE: code/bspc/faces.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // faces.c #include "qbsp.h" #include "l_mem.h" /* some faces will be removed before saving, but still form nodes: the insides of sky volumes meeting planes of different water current volumes */ // undefine for dumb linear searches #define USE_HASHING #define INTEGRAL_EPSILON 0.01 #define POINT_EPSILON 0.5 #define OFF_EPSILON 0.5 int c_merge; int c_subdivide; int c_totalverts; int c_uniqueverts; int c_degenerate; int c_tjunctions; int c_faceoverflows; int c_facecollapse; int c_badstartverts; #define MAX_SUPERVERTS 512 int superverts[MAX_SUPERVERTS]; int numsuperverts; face_t *edgefaces[MAX_MAP_EDGES][2]; int firstmodeledge = 1; int firstmodelface; int c_tryedges; vec3_t edge_dir; vec3_t edge_start; vec_t edge_len; int num_edge_verts; int edge_verts[MAX_MAP_VERTS]; face_t *NewFaceFromFace (face_t *f); //=========================================================================== typedef struct hashvert_s { struct hashvert_s *next; int num; } hashvert_t; #define HASH_SIZE 64 int vertexchain[MAX_MAP_VERTS]; // the next vertex in a hash chain int hashverts[HASH_SIZE*HASH_SIZE]; // a vertex number, or 0 for no verts face_t *edgefaces[MAX_MAP_EDGES][2]; //============================================================================ unsigned HashVec (vec3_t vec) { int x, y; x = (4096 + (int)(vec[0]+0.5)) >> 7; y = (4096 + (int)(vec[1]+0.5)) >> 7; if ( x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE ) Error ("HashVec: point outside valid range"); return y*HASH_SIZE + x; } #ifdef USE_HASHING /* ============= GetVertex Uses hashing ============= */ int GetVertexnum (vec3_t in) { int h; int i; float *p; vec3_t vert; int vnum; c_totalverts++; for (i=0 ; i<3 ; i++) { if ( fabs(in[i] - Q_rint(in[i])) < INTEGRAL_EPSILON) vert[i] = Q_rint(in[i]); else vert[i] = in[i]; } h = HashVec (vert); for (vnum=hashverts[h] ; vnum ; vnum=vertexchain[vnum]) { p = dvertexes[vnum].point; if ( fabs(p[0]-vert[0]) 4096) Error ("GetVertexnum: outside +/- 4096"); } // search for an existing vertex match for (i=0, dv=dvertexes ; ipoint[j]; if ( d > POINT_EPSILON || d < -POINT_EPSILON) break; } if (j == 3) return i; // a match } // new point if (numvertexes == MAX_MAP_VERTS) Error ("MAX_MAP_VERTS"); VectorCopy (v, dv->point); numvertexes++; c_uniqueverts++; return numvertexes-1; } #endif /* ================== FaceFromSuperverts The faces vertexes have been added to the superverts[] array, and there may be more there than can be held in a face (MAXEDGES). If less, the faces vertexnums[] will be filled in, otherwise face will reference a tree of split[] faces until all of the vertexnums can be added. superverts[base] will become face->vertexnums[0], and the others will be circularly filled in. ================== */ void FaceFromSuperverts (node_t *node, face_t *f, int base) { face_t *newf; int remaining; int i; remaining = numsuperverts; while (remaining > MAXEDGES) { // must split into two faces, because of vertex overload c_faceoverflows++; newf = f->split[0] = NewFaceFromFace (f); newf = f->split[0]; newf->next = node->faces; node->faces = newf; newf->numpoints = MAXEDGES; for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; f->split[1] = NewFaceFromFace (f); f = f->split[1]; f->next = node->faces; node->faces = f; remaining -= (MAXEDGES-2); base = (base+MAXEDGES-1)%numsuperverts; } // copy the vertexes back to the face f->numpoints = remaining; for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; } /* ================== EmitFaceVertexes ================== */ void EmitFaceVertexes (node_t *node, face_t *f) { winding_t *w; int i; if (f->merged || f->split[0] || f->split[1]) return; w = f->w; for (i=0 ; inumpoints ; i++) { if (noweld) { // make every point unique if (numvertexes == MAX_MAP_VERTS) Error ("MAX_MAP_VERTS"); superverts[i] = numvertexes; VectorCopy (w->p[i], dvertexes[numvertexes].point); numvertexes++; c_uniqueverts++; c_totalverts++; } else superverts[i] = GetVertexnum (w->p[i]); } numsuperverts = w->numpoints; // this may fragment the face if > MAXEDGES FaceFromSuperverts (node, f, 0); } /* ================== EmitVertexes_r ================== */ void EmitVertexes_r (node_t *node) { int i; face_t *f; if (node->planenum == PLANENUM_LEAF) return; for (f=node->faces ; f ; f=f->next) { EmitFaceVertexes (node, f); } for (i=0 ; i<2 ; i++) EmitVertexes_r (node->children[i]); } #ifdef USE_HASHING /* ========== FindEdgeVerts Uses the hash tables to cut down to a small number ========== */ void FindEdgeVerts (vec3_t v1, vec3_t v2) { int x1, x2, y1, y2, t; int x, y; int vnum; #if 0 { int i; num_edge_verts = numvertexes-1; for (i=0 ; i> 7; y1 = (4096 + (int)(v1[1]+0.5)) >> 7; x2 = (4096 + (int)(v2[0]+0.5)) >> 7; y2 = (4096 + (int)(v2[1]+0.5)) >> 7; if (x1 > x2) { t = x1; x1 = x2; x2 = t; } if (y1 > y2) { t = y1; y1 = y2; y2 = t; } #if 0 x1--; x2++; y1--; y2++; if (x1 < 0) x1 = 0; if (x2 >= HASH_SIZE) x2 = HASH_SIZE; if (y1 < 0) y1 = 0; if (y2 >= HASH_SIZE) y2 = HASH_SIZE; #endif num_edge_verts = 0; for (x=x1 ; x <= x2 ; x++) { for (y=y1 ; y <= y2 ; y++) { for (vnum=hashverts[y*HASH_SIZE+x] ; vnum ; vnum=vertexchain[vnum]) { edge_verts[num_edge_verts++] = vnum; } } } } #else /* ========== FindEdgeVerts Forced a dumb check of everything ========== */ void FindEdgeVerts (vec3_t v1, vec3_t v2) { int i; num_edge_verts = numvertexes-1; for (i=0 ; i= end) continue; // off an end VectorMA (edge_start, dist, edge_dir, exact); VectorSubtract (p, exact, off); error = VectorLength (off); if (fabs(error) > OFF_EPSILON) continue; // not on the edge // break the edge c_tjunctions++; TestEdge (start, dist, p1, j, k+1); TestEdge (dist, end, j, p2, k+1); return; } // the edge p1 to p2 is now free of tjunctions if (numsuperverts >= MAX_SUPERVERTS) Error ("MAX_SUPERVERTS"); superverts[numsuperverts] = p1; numsuperverts++; } /* ================== FixFaceEdges ================== */ void FixFaceEdges (node_t *node, face_t *f) { int p1, p2; int i; vec3_t e2; vec_t len; int count[MAX_SUPERVERTS], start[MAX_SUPERVERTS]; int base; if (f->merged || f->split[0] || f->split[1]) return; numsuperverts = 0; for (i=0 ; inumpoints ; i++) { p1 = f->vertexnums[i]; p2 = f->vertexnums[(i+1)%f->numpoints]; VectorCopy (dvertexes[p1].point, edge_start); VectorCopy (dvertexes[p2].point, e2); FindEdgeVerts (edge_start, e2); VectorSubtract (e2, edge_start, edge_dir); len = VectorNormalize(edge_dir); start[i] = numsuperverts; TestEdge (0, len, p1, p2, 0); count[i] = numsuperverts - start[i]; } if (numsuperverts < 3) { // entire face collapsed f->numpoints = 0; c_facecollapse++; return; } // we want to pick a vertex that doesn't have tjunctions // on either side, which can cause artifacts on trifans, // especially underwater for (i=0 ; inumpoints ; i++) { if (count[i] == 1 && count[(i+f->numpoints-1)%f->numpoints] == 1) break; } if (i == f->numpoints) { f->badstartvert = true; c_badstartverts++; base = 0; } else { // rotate the vertex order base = start[i]; } // this may fragment the face if > MAXEDGES FaceFromSuperverts (node, f, base); } /* ================== FixEdges_r ================== */ void FixEdges_r (node_t *node) { int i; face_t *f; if (node->planenum == PLANENUM_LEAF) return; for (f=node->faces ; f ; f=f->next) FixFaceEdges (node, f); for (i=0 ; i<2 ; i++) FixEdges_r (node->children[i]); } /* =========== FixTjuncs =========== */ void FixTjuncs (node_t *headnode) { // snap and merge all vertexes qprintf ("---- snap verts ----\n"); memset (hashverts, 0, sizeof(hashverts)); c_totalverts = 0; c_uniqueverts = 0; c_faceoverflows = 0; EmitVertexes_r (headnode); qprintf ("%i unique from %i\n", c_uniqueverts, c_totalverts); // break edges on tjunctions qprintf ("---- tjunc ----\n"); c_tryedges = 0; c_degenerate = 0; c_facecollapse = 0; c_tjunctions = 0; if (!notjunc) FixEdges_r (headnode); qprintf ("%5i edges degenerated\n", c_degenerate); qprintf ("%5i faces degenerated\n", c_facecollapse); qprintf ("%5i edges added by tjunctions\n", c_tjunctions); qprintf ("%5i faces added by tjunctions\n", c_faceoverflows); qprintf ("%5i bad start verts\n", c_badstartverts); } //======================================================== int c_faces; face_t *AllocFace (void) { face_t *f; f = GetMemory(sizeof(*f)); memset (f, 0, sizeof(*f)); c_faces++; return f; } face_t *NewFaceFromFace (face_t *f) { face_t *newf; newf = AllocFace (); *newf = *f; newf->merged = NULL; newf->split[0] = newf->split[1] = NULL; newf->w = NULL; return newf; } void FreeFace (face_t *f) { if (f->w) FreeWinding (f->w); FreeMemory(f); c_faces--; } //======================================================== /* ================== GetEdge Called by writebsp. Don't allow four way edges ================== */ int GetEdge2 (int v1, int v2, face_t *f) { dedge_t *edge; int i; c_tryedges++; if (!noshare) { for (i=firstmodeledge ; i < numedges ; i++) { edge = &dedges[i]; if (v1 == edge->v[1] && v2 == edge->v[0] && edgefaces[i][0]->contents == f->contents) { if (edgefaces[i][1]) // printf ("WARNING: multiple backward edge\n"); continue; edgefaces[i][1] = f; return -i; } #if 0 if (v1 == edge->v[0] && v2 == edge->v[1]) { printf ("WARNING: multiple forward edge\n"); return i; } #endif } } // emit an edge if (numedges >= MAX_MAP_EDGES) Error ("numedges == MAX_MAP_EDGES"); edge = &dedges[numedges]; numedges++; edge->v[0] = v1; edge->v[1] = v2; edgefaces[numedges-1][0] = f; return numedges-1; } /* =========================================================================== FACE MERGING =========================================================================== */ /* ============= TryMerge If two polygons share a common edge and the edges that meet at the common points are both inside the other polygons, merge them Returns NULL if the faces couldn't be merged, or the new face. The originals will NOT be freed. ============= */ face_t *TryMerge (face_t *f1, face_t *f2, vec3_t planenormal) { face_t *newf; winding_t *nw; if (!f1->w || !f2->w) return NULL; if (f1->texinfo != f2->texinfo) return NULL; if (f1->planenum != f2->planenum) // on front and back sides return NULL; if (f1->contents != f2->contents) return NULL; nw = TryMergeWinding (f1->w, f2->w, planenormal); if (!nw) return NULL; c_merge++; newf = NewFaceFromFace (f1); newf->w = nw; f1->merged = newf; f2->merged = newf; return newf; } /* =============== MergeNodeFaces =============== */ void MergeNodeFaces (node_t *node) { face_t *f1, *f2, *end; face_t *merged; plane_t *plane; plane = &mapplanes[node->planenum]; merged = NULL; for (f1 = node->faces ; f1 ; f1 = f1->next) { if (f1->merged || f1->split[0] || f1->split[1]) continue; for (f2 = node->faces ; f2 != f1 ; f2=f2->next) { if (f2->merged || f2->split[0] || f2->split[1]) continue; //IDBUG: always passes the face's node's normal to TryMerge() //regardless of which side the face is on. Approximately 50% of //the time the face will be on the other side of node, and thus //the result of the convex/concave test in TryMergeWinding(), //which depends on the normal, is flipped. This causes faces //that shouldn't be merged to be merged and faces that //should be merged to not be merged. //the following added line fixes this bug //thanks to: Alexander Malmberg plane = &mapplanes[f1->planenum]; // merged = TryMerge (f1, f2, plane->normal); if (!merged) continue; // add merged to the end of the node face list // so it will be checked against all the faces again for (end = node->faces ; end->next ; end = end->next) ; merged->next = NULL; end->next = merged; break; } } } //===================================================================== /* =============== SubdivideFace Chop up faces that are larger than we want in the surface cache =============== */ void SubdivideFace (node_t *node, face_t *f) { float mins, maxs; vec_t v; int axis, i; texinfo_t *tex; vec3_t temp; vec_t dist; winding_t *w, *frontw, *backw; if (f->merged) return; // special (non-surface cached) faces don't need subdivision tex = &texinfo[f->texinfo]; if ( tex->flags & (SURF_WARP|SURF_SKY) ) { return; } for (axis = 0 ; axis < 2 ; axis++) { while (1) { mins = 999999; maxs = -999999; VectorCopy (tex->vecs[axis], temp); w = f->w; for (i=0 ; inumpoints ; i++) { v = DotProduct (w->p[i], temp); if (v < mins) mins = v; if (v > maxs) maxs = v; } #if 0 if (maxs - mins <= 0) Error ("zero extents"); #endif if (axis == 2) { // allow double high walls if (maxs - mins <= subdivide_size/* *2 */) break; } else if (maxs - mins <= subdivide_size) break; // split it c_subdivide++; v = VectorNormalize (temp); dist = (mins + subdivide_size - 16)/v; ClipWindingEpsilon (w, temp, dist, ON_EPSILON, &frontw, &backw); if (!frontw || !backw) Error ("SubdivideFace: didn't split the polygon"); f->split[0] = NewFaceFromFace (f); f->split[0]->w = frontw; f->split[0]->next = node->faces; node->faces = f->split[0]; f->split[1] = NewFaceFromFace (f); f->split[1]->w = backw; f->split[1]->next = node->faces; node->faces = f->split[1]; SubdivideFace (node, f->split[0]); SubdivideFace (node, f->split[1]); return; } } } void SubdivideNodeFaces (node_t *node) { face_t *f; for (f = node->faces ; f ; f=f->next) { SubdivideFace (node, f); } } //=========================================================================== int c_nodefaces; /* ============ FaceFromPortal ============ */ face_t *FaceFromPortal (portal_t *p, int pside) { face_t *f; side_t *side; side = p->side; if (!side) return NULL; // portal does not bridge different visible contents f = AllocFace (); f->texinfo = side->texinfo; f->planenum = (side->planenum & ~1) | pside; f->portal = p; if ( (p->nodes[pside]->contents & CONTENTS_WINDOW) && VisibleContents(p->nodes[!pside]->contents^p->nodes[pside]->contents) == CONTENTS_WINDOW ) return NULL; // don't show insides of windows if (pside) { f->w = ReverseWinding(p->winding); f->contents = p->nodes[1]->contents; } else { f->w = CopyWinding(p->winding); f->contents = p->nodes[0]->contents; } return f; } /* =============== MakeFaces_r If a portal will make a visible face, mark the side that originally created it solid / empty : solid solid / water : solid water / empty : water water / water : none =============== */ void MakeFaces_r (node_t *node) { portal_t *p; int s; // recurse down to leafs if (node->planenum != PLANENUM_LEAF) { MakeFaces_r (node->children[0]); MakeFaces_r (node->children[1]); // merge together all visible faces on the node if (!nomerge) MergeNodeFaces (node); if (!nosubdiv) SubdivideNodeFaces (node); return; } // solid leafs never have visible faces if (node->contents & CONTENTS_SOLID) return; // see which portals are valid for (p=node->portals ; p ; p = p->next[s]) { s = (p->nodes[1] == node); p->face[s] = FaceFromPortal (p, s); if (p->face[s]) { c_nodefaces++; p->face[s]->next = p->onnode->faces; p->onnode->faces = p->face[s]; } } } /* ============ MakeFaces ============ */ void MakeFaces (node_t *node) { qprintf ("--- MakeFaces ---\n"); c_merge = 0; c_subdivide = 0; c_nodefaces = 0; MakeFaces_r (node); qprintf ("%5i makefaces\n", c_nodefaces); qprintf ("%5i merged\n", c_merge); qprintf ("%5i subdivided\n", c_subdivide); } ================================================ FILE: code/bspc/gldraw.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include #include #include #include #include "qbsp.h" // can't use the glvertex3fv functions, because the vec3_t fields // could be either floats or doubles, depending on DOUBLEVEC_T qboolean drawflag; vec3_t draw_mins, draw_maxs; #define WIN_SIZE 512 void InitWindow (void) { auxInitDisplayMode (AUX_SINGLE | AUX_RGB); auxInitPosition (0, 0, WIN_SIZE, WIN_SIZE); auxInitWindow ("qcsg"); } void Draw_ClearWindow (void) { static int init; int w, h, g; vec_t mx, my; if (!drawflag) return; if (!init) { init = true; InitWindow (); } glClearColor (1,0.8,0.8,0); glClear (GL_COLOR_BUFFER_BIT); w = (draw_maxs[0] - draw_mins[0]); h = (draw_maxs[1] - draw_mins[1]); mx = draw_mins[0] + w/2; my = draw_mins[1] + h/2; g = w > h ? w : h; glLoadIdentity (); gluPerspective (90, 1, 2, 16384); gluLookAt (mx, my, draw_maxs[2] + g/2, mx , my, draw_maxs[2], 0, 1, 0); glColor3f (0,0,0); // glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); glDisable (GL_DEPTH_TEST); glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); #if 0 glColor4f (1,0,0,0.5); glBegin (GL_POLYGON); glVertex3f (0, 500, 0); glVertex3f (0, 900, 0); glVertex3f (0, 900, 100); glVertex3f (0, 500, 100); glEnd (); #endif glFlush (); } void Draw_SetRed (void) { if (!drawflag) return; glColor3f (1,0,0); } void Draw_SetGrey (void) { if (!drawflag) return; glColor3f (0.5,0.5,0.5); } void Draw_SetBlack (void) { if (!drawflag) return; glColor3f (0,0,0); } void DrawWinding (winding_t *w) { int i; if (!drawflag) return; glColor4f (0,0,0,0.5); glBegin (GL_LINE_LOOP); for (i=0 ; inumpoints ; i++) glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); glEnd (); glColor4f (0,1,0,0.3); glBegin (GL_POLYGON); for (i=0 ; inumpoints ; i++) glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); glEnd (); glFlush (); } void DrawAuxWinding (winding_t *w) { int i; if (!drawflag) return; glColor4f (0,0,0,0.5); glBegin (GL_LINE_LOOP); for (i=0 ; inumpoints ; i++) glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); glEnd (); glColor4f (1,0,0,0.3); glBegin (GL_POLYGON); for (i=0 ; inumpoints ; i++) glVertex3f (w->p[i][0],w->p[i][1],w->p[i][2] ); glEnd (); glFlush (); } //============================================================ #define GLSERV_PORT 25001 qboolean wins_init; int draw_socket; void GLS_BeginScene (void) { WSADATA winsockdata; WORD wVersionRequested; struct sockaddr_in address; int r; if (!wins_init) { wins_init = true; wVersionRequested = MAKEWORD(1, 1); r = WSAStartup (MAKEWORD(1, 1), &winsockdata); if (r) Error ("Winsock initialization failed."); } // connect a socket to the server draw_socket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP); if (draw_socket == -1) Error ("draw_socket failed"); address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); address.sin_port = GLSERV_PORT; r = connect (draw_socket, (struct sockaddr *)&address, sizeof(address)); if (r == -1) { closesocket (draw_socket); draw_socket = 0; } } void GLS_Winding (winding_t *w, int code) { byte buf[1024]; int i, j; if (!draw_socket) return; ((int *)buf)[0] = w->numpoints; ((int *)buf)[1] = code; for (i=0 ; inumpoints ; i++) for (j=0 ; j<3 ; j++) ((float *)buf)[2+i*3+j] = w->p[i][j]; send (draw_socket, buf, w->numpoints*12+8, 0); } void GLS_EndScene (void) { closesocket (draw_socket); draw_socket = 0; } ================================================ FILE: code/bspc/glfile.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" int c_glfaces; int PortalVisibleSides (portal_t *p) { int fcon, bcon; if (!p->onnode) return 0; // outside fcon = p->nodes[0]->contents; bcon = p->nodes[1]->contents; // same contents never create a face if (fcon == bcon) return 0; // FIXME: is this correct now? if (!fcon) return 1; if (!bcon) return 2; return 0; } void OutputWinding (winding_t *w, FILE *glview) { static int level = 128; vec_t light; int i; fprintf (glview, "%i\n", w->numpoints); level+=28; light = (level&255)/255.0; for (i=0 ; inumpoints ; i++) { fprintf (glview, "%6.3f %6.3f %6.3f %6.3f %6.3f %6.3f\n", w->p[i][0], w->p[i][1], w->p[i][2], light, light, light); } fprintf (glview, "\n"); } /* ============= OutputPortal ============= */ void OutputPortal (portal_t *p, FILE *glview) { winding_t *w; int sides; sides = PortalVisibleSides (p); if (!sides) return; c_glfaces++; w = p->winding; if (sides == 2) // back side w = ReverseWinding (w); OutputWinding (w, glview); if (sides == 2) FreeWinding(w); } /* ============= WriteGLView_r ============= */ void WriteGLView_r (node_t *node, FILE *glview) { portal_t *p, *nextp; if (node->planenum != PLANENUM_LEAF) { WriteGLView_r (node->children[0], glview); WriteGLView_r (node->children[1], glview); return; } // write all the portals for (p=node->portals ; p ; p=nextp) { if (p->nodes[0] == node) { OutputPortal (p, glview); nextp = p->next[0]; } else nextp = p->next[1]; } } /* ============= WriteGLView ============= */ void WriteGLView (tree_t *tree, char *source) { char name[1024]; FILE *glview; c_glfaces = 0; sprintf (name, "%s%s.gl",outbase, source); printf ("Writing %s\n", name); glview = fopen (name, "w"); if (!glview) Error ("Couldn't open %s", name); WriteGLView_r (tree->headnode, glview); fclose (glview); printf ("%5i c_glfaces\n", c_glfaces); } ================================================ FILE: code/bspc/l_bsp_ent.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "l_cmd.h" #include "l_math.h" #include "l_mem.h" #include "l_log.h" #include "../botlib/l_script.h" #include "l_bsp_ent.h" #define MAX_KEY 32 #define MAX_VALUE 1024 int num_entities; entity_t entities[MAX_MAP_ENTITIES]; void StripTrailing(char *e) { char *s; s = e + strlen(e)-1; while (s >= e && *s <= 32) { *s = 0; s--; } } /* ================= ParseEpair ================= */ epair_t *ParseEpair(script_t *script) { epair_t *e; token_t token; e = GetMemory(sizeof(epair_t)); memset (e, 0, sizeof(epair_t)); PS_ExpectAnyToken(script, &token); StripDoubleQuotes(token.string); if (strlen(token.string) >= MAX_KEY-1) Error ("ParseEpair: token %s too long", token.string); e->key = copystring(token.string); PS_ExpectAnyToken(script, &token); StripDoubleQuotes(token.string); if (strlen(token.string) >= MAX_VALUE-1) Error ("ParseEpair: token %s too long", token.string); e->value = copystring(token.string); // strip trailing spaces StripTrailing(e->key); StripTrailing(e->value); return e; } //end of the function ParseEpair /* ================ ParseEntity ================ */ qboolean ParseEntity(script_t *script) { epair_t *e; entity_t *mapent; token_t token; if (!PS_ReadToken(script, &token)) return false; if (strcmp(token.string, "{")) Error ("ParseEntity: { not found"); if (num_entities == MAX_MAP_ENTITIES) Error ("num_entities == MAX_MAP_ENTITIES"); mapent = &entities[num_entities]; num_entities++; do { if (!PS_ReadToken(script, &token)) Error ("ParseEntity: EOF without closing brace"); if (!strcmp(token.string, "}") ) break; PS_UnreadLastToken(script); e = ParseEpair(script); e->next = mapent->epairs; mapent->epairs = e; } while (1); return true; } //end of the function ParseEntity void PrintEntity (entity_t *ent) { epair_t *ep; printf ("------- entity %p -------\n", ent); for (ep=ent->epairs ; ep ; ep=ep->next) { printf ("%s = %s\n", ep->key, ep->value); } } void SetKeyValue (entity_t *ent, char *key, char *value) { epair_t *ep; for (ep=ent->epairs ; ep ; ep=ep->next) if (!strcmp (ep->key, key) ) { FreeMemory(ep->value); ep->value = copystring(value); return; } ep = GetMemory(sizeof(*ep)); ep->next = ent->epairs; ent->epairs = ep; ep->key = copystring(key); ep->value = copystring(value); } char *ValueForKey (entity_t *ent, char *key) { epair_t *ep; for (ep=ent->epairs ; ep ; ep=ep->next) if (!strcmp (ep->key, key) ) return ep->value; return ""; } vec_t FloatForKey (entity_t *ent, char *key) { char *k; k = ValueForKey (ent, key); return atof(k); } void GetVectorForKey (entity_t *ent, char *key, vec3_t vec) { char *k; double v1, v2, v3; k = ValueForKey (ent, key); // scanf into doubles, then assign, so it is vec_t size independent v1 = v2 = v3 = 0; sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); vec[0] = v1; vec[1] = v2; vec[2] = v3; } ================================================ FILE: code/bspc/l_bsp_ent.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #ifndef MAX_MAP_ENTITIES #define MAX_MAP_ENTITIES 2048 #endif typedef struct epair_s { struct epair_s *next; char *key; char *value; } epair_t; typedef struct { vec3_t origin; int firstbrush; int numbrushes; epair_t *epairs; // only valid for func_areaportals int areaportalnum; int portalareas[2]; int modelnum; //for bsp 2 map conversion qboolean wasdetail; //for SIN } entity_t; extern int num_entities; extern entity_t entities[MAX_MAP_ENTITIES]; void StripTrailing(char *e); void SetKeyValue(entity_t *ent, char *key, char *value); char *ValueForKey(entity_t *ent, char *key); // will return "" if not present vec_t FloatForKey(entity_t *ent, char *key); void GetVectorForKey(entity_t *ent, char *key, vec3_t vec); qboolean ParseEntity(script_t *script); epair_t *ParseEpair(script_t *script); void PrintEntity(entity_t *ent); ================================================ FILE: code/bspc/l_bsp_hl.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "l_cmd.h" #include "l_math.h" #include "l_mem.h" #include "l_log.h" #include "../botlib/l_script.h" #include "l_bsp_hl.h" #include "l_bsp_ent.h" //============================================================================= int hl_nummodels; hl_dmodel_t *hl_dmodels;//[HL_MAX_MAP_MODELS]; int hl_dmodels_checksum; int hl_visdatasize; byte *hl_dvisdata;//[HL_MAX_MAP_VISIBILITY]; int hl_dvisdata_checksum; int hl_lightdatasize; byte *hl_dlightdata;//[HL_MAX_MAP_LIGHTING]; int hl_dlightdata_checksum; int hl_texdatasize; byte *hl_dtexdata;//[HL_MAX_MAP_MIPTEX]; // (dmiptexlump_t) int hl_dtexdata_checksum; int hl_entdatasize; char *hl_dentdata;//[HL_MAX_MAP_ENTSTRING]; int hl_dentdata_checksum; int hl_numleafs; hl_dleaf_t *hl_dleafs;//[HL_MAX_MAP_LEAFS]; int hl_dleafs_checksum; int hl_numplanes; hl_dplane_t *hl_dplanes;//[HL_MAX_MAP_PLANES]; int hl_dplanes_checksum; int hl_numvertexes; hl_dvertex_t *hl_dvertexes;//[HL_MAX_MAP_VERTS]; int hl_dvertexes_checksum; int hl_numnodes; hl_dnode_t *hl_dnodes;//[HL_MAX_MAP_NODES]; int hl_dnodes_checksum; int hl_numtexinfo; hl_texinfo_t *hl_texinfo;//[HL_MAX_MAP_TEXINFO]; int hl_texinfo_checksum; int hl_numfaces; hl_dface_t *hl_dfaces;//[HL_MAX_MAP_FACES]; int hl_dfaces_checksum; int hl_numclipnodes; hl_dclipnode_t *hl_dclipnodes;//[HL_MAX_MAP_CLIPNODES]; int hl_dclipnodes_checksum; int hl_numedges; hl_dedge_t *hl_dedges;//[HL_MAX_MAP_EDGES]; int hl_dedges_checksum; int hl_nummarksurfaces; unsigned short *hl_dmarksurfaces;//[HL_MAX_MAP_MARKSURFACES]; int hl_dmarksurfaces_checksum; int hl_numsurfedges; int *hl_dsurfedges;//[HL_MAX_MAP_SURFEDGES]; int hl_dsurfedges_checksum; //int num_entities; //entity_t entities[HL_MAX_MAP_ENTITIES]; //#ifdef //ME int hl_bspallocated = false; int hl_allocatedbspmem = 0; void HL_AllocMaxBSP(void) { //models hl_nummodels = 0; hl_dmodels = (hl_dmodel_t *) GetMemory(HL_MAX_MAP_MODELS * sizeof(hl_dmodel_t)); hl_allocatedbspmem = HL_MAX_MAP_MODELS * sizeof(hl_dmodel_t); //visibility hl_visdatasize = 0; hl_dvisdata = (byte *) GetMemory(HL_MAX_MAP_VISIBILITY * sizeof(byte)); hl_allocatedbspmem += HL_MAX_MAP_VISIBILITY * sizeof(byte); //light data hl_lightdatasize = 0; hl_dlightdata = (byte *) GetMemory(HL_MAX_MAP_LIGHTING * sizeof(byte)); hl_allocatedbspmem += HL_MAX_MAP_LIGHTING * sizeof(byte); //texture data hl_texdatasize = 0; hl_dtexdata = (byte *) GetMemory(HL_MAX_MAP_MIPTEX * sizeof(byte)); // (dmiptexlump_t) hl_allocatedbspmem += HL_MAX_MAP_MIPTEX * sizeof(byte); //entities hl_entdatasize = 0; hl_dentdata = (char *) GetMemory(HL_MAX_MAP_ENTSTRING * sizeof(char)); hl_allocatedbspmem += HL_MAX_MAP_ENTSTRING * sizeof(char); //leaves hl_numleafs = 0; hl_dleafs = (hl_dleaf_t *) GetMemory(HL_MAX_MAP_LEAFS * sizeof(hl_dleaf_t)); hl_allocatedbspmem += HL_MAX_MAP_LEAFS * sizeof(hl_dleaf_t); //planes hl_numplanes = 0; hl_dplanes = (hl_dplane_t *) GetMemory(HL_MAX_MAP_PLANES * sizeof(hl_dplane_t)); hl_allocatedbspmem += HL_MAX_MAP_PLANES * sizeof(hl_dplane_t); //vertexes hl_numvertexes = 0; hl_dvertexes = (hl_dvertex_t *) GetMemory(HL_MAX_MAP_VERTS * sizeof(hl_dvertex_t)); hl_allocatedbspmem += HL_MAX_MAP_VERTS * sizeof(hl_dvertex_t); //nodes hl_numnodes = 0; hl_dnodes = (hl_dnode_t *) GetMemory(HL_MAX_MAP_NODES * sizeof(hl_dnode_t)); hl_allocatedbspmem += HL_MAX_MAP_NODES * sizeof(hl_dnode_t); //texture info hl_numtexinfo = 0; hl_texinfo = (hl_texinfo_t *) GetMemory(HL_MAX_MAP_TEXINFO * sizeof(hl_texinfo_t)); hl_allocatedbspmem += HL_MAX_MAP_TEXINFO * sizeof(hl_texinfo_t); //faces hl_numfaces = 0; hl_dfaces = (hl_dface_t *) GetMemory(HL_MAX_MAP_FACES * sizeof(hl_dface_t)); hl_allocatedbspmem += HL_MAX_MAP_FACES * sizeof(hl_dface_t); //clip nodes hl_numclipnodes = 0; hl_dclipnodes = (hl_dclipnode_t *) GetMemory(HL_MAX_MAP_CLIPNODES * sizeof(hl_dclipnode_t)); hl_allocatedbspmem += HL_MAX_MAP_CLIPNODES * sizeof(hl_dclipnode_t); //edges hl_numedges = 0; hl_dedges = (hl_dedge_t *) GetMemory(HL_MAX_MAP_EDGES * sizeof(hl_dedge_t)); hl_allocatedbspmem += HL_MAX_MAP_EDGES, sizeof(hl_dedge_t); //mark surfaces hl_nummarksurfaces = 0; hl_dmarksurfaces = (unsigned short *) GetMemory(HL_MAX_MAP_MARKSURFACES * sizeof(unsigned short)); hl_allocatedbspmem += HL_MAX_MAP_MARKSURFACES * sizeof(unsigned short); //surface edges hl_numsurfedges = 0; hl_dsurfedges = (int *) GetMemory(HL_MAX_MAP_SURFEDGES * sizeof(int)); hl_allocatedbspmem += HL_MAX_MAP_SURFEDGES * sizeof(int); //print allocated memory Log_Print("allocated "); PrintMemorySize(hl_allocatedbspmem); Log_Print(" of BSP memory\n"); } //end of the function HL_AllocMaxBSP void HL_FreeMaxBSP(void) { //models hl_nummodels = 0; FreeMemory(hl_dmodels); hl_dmodels = NULL; //visibility hl_visdatasize = 0; FreeMemory(hl_dvisdata); hl_dvisdata = NULL; //light data hl_lightdatasize = 0; FreeMemory(hl_dlightdata); hl_dlightdata = NULL; //texture data hl_texdatasize = 0; FreeMemory(hl_dtexdata); hl_dtexdata = NULL; //entities hl_entdatasize = 0; FreeMemory(hl_dentdata); hl_dentdata = NULL; //leaves hl_numleafs = 0; FreeMemory(hl_dleafs); hl_dleafs = NULL; //planes hl_numplanes = 0; FreeMemory(hl_dplanes); hl_dplanes = NULL; //vertexes hl_numvertexes = 0; FreeMemory(hl_dvertexes); hl_dvertexes = NULL; //nodes hl_numnodes = 0; FreeMemory(hl_dnodes); hl_dnodes = NULL; //texture info hl_numtexinfo = 0; FreeMemory(hl_texinfo); hl_texinfo = NULL; //faces hl_numfaces = 0; FreeMemory(hl_dfaces); hl_dfaces = NULL; //clip nodes hl_numclipnodes = 0; FreeMemory(hl_dclipnodes); hl_dclipnodes = NULL; //edges hl_numedges = 0; FreeMemory(hl_dedges); hl_dedges = NULL; //mark surfaces hl_nummarksurfaces = 0; FreeMemory(hl_dmarksurfaces); hl_dmarksurfaces = NULL; //surface edges hl_numsurfedges = 0; FreeMemory(hl_dsurfedges); hl_dsurfedges = NULL; // Log_Print("freed "); PrintMemorySize(hl_allocatedbspmem); Log_Print(" of BSP memory\n"); hl_allocatedbspmem = 0; } //end of the function HL_FreeMaxBSP //#endif //ME /* =============== FastChecksum =============== */ int FastChecksum(void *buffer, int bytes) { int checksum = 0; while( bytes-- ) checksum = (checksum << 4) ^ *((char *)buffer)++; return checksum; } /* =============== HL_CompressVis =============== */ int HL_CompressVis(byte *vis, byte *dest) { int j; int rep; int visrow; byte *dest_p; dest_p = dest; visrow = (hl_numleafs + 7)>>3; for (j=0 ; j>3; out = decompressed; do { if (*in) { *out++ = *in++; continue; } c = in[1]; in += 2; while (c) { *out++ = 0; c--; } } while (out - decompressed < row); } //============================================================================= /* ============= HL_SwapBSPFile Byte swaps all data in a bsp file. ============= */ void HL_SwapBSPFile (qboolean todisk) { int i, j, c; hl_dmodel_t *d; hl_dmiptexlump_t *mtl; // models for (i = 0; i < hl_nummodels; i++) { d = &hl_dmodels[i]; for (j = 0; j < HL_MAX_MAP_HULLS; j++) d->headnode[j] = LittleLong(d->headnode[j]); d->visleafs = LittleLong(d->visleafs); d->firstface = LittleLong(d->firstface); d->numfaces = LittleLong(d->numfaces); for (j = 0; j < 3; j++) { d->mins[j] = LittleFloat(d->mins[j]); d->maxs[j] = LittleFloat(d->maxs[j]); d->origin[j] = LittleFloat(d->origin[j]); } } // // vertexes // for (i = 0; i < hl_numvertexes; i++) { for (j = 0; j < 3; j++) hl_dvertexes[i].point[j] = LittleFloat (hl_dvertexes[i].point[j]); } // // planes // for (i=0 ; inummiptex; else c = LittleLong(mtl->nummiptex); mtl->nummiptex = LittleLong (mtl->nummiptex); for (i=0 ; idataofs[i] = LittleLong(mtl->dataofs[i]); } // // marksurfaces // for (i=0 ; ilumps[lump].filelen; ofs = hl_header->lumps[lump].fileofs; if (length % size) { Error ("LoadBSPFile: odd lump size"); } // somehow things got out of range if ((length/size) > maxsize) { printf("WARNING: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize); length = maxsize * size; } if ( ofs + length > hl_fileLength ) { printf("WARNING: exceeded file length for lump %d\n", lump); length = hl_fileLength - ofs; if ( length <= 0 ) { return 0; } } memcpy (dest, (byte *)hl_header + ofs, length); return length / size; } /* ============= HL_LoadBSPFile ============= */ void HL_LoadBSPFile (char *filename, int offset, int length) { int i; // // load the file header // hl_fileLength = LoadFile (filename, (void **)&hl_header, offset, length); // swap the header for (i=0 ; i< sizeof(hl_dheader_t)/4 ; i++) ((int *)hl_header)[i] = LittleLong ( ((int *)hl_header)[i]); if (hl_header->version != HL_BSPVERSION) Error ("%s is version %i, not %i", filename, hl_header->version, HL_BSPVERSION); hl_nummodels = HL_CopyLump (HL_LUMP_MODELS, hl_dmodels, sizeof(hl_dmodel_t), HL_MAX_MAP_MODELS ); hl_numvertexes = HL_CopyLump (HL_LUMP_VERTEXES, hl_dvertexes, sizeof(hl_dvertex_t), HL_MAX_MAP_VERTS ); hl_numplanes = HL_CopyLump (HL_LUMP_PLANES, hl_dplanes, sizeof(hl_dplane_t), HL_MAX_MAP_PLANES ); hl_numleafs = HL_CopyLump (HL_LUMP_LEAFS, hl_dleafs, sizeof(hl_dleaf_t), HL_MAX_MAP_LEAFS ); hl_numnodes = HL_CopyLump (HL_LUMP_NODES, hl_dnodes, sizeof(hl_dnode_t), HL_MAX_MAP_NODES ); hl_numtexinfo = HL_CopyLump (HL_LUMP_TEXINFO, hl_texinfo, sizeof(hl_texinfo_t), HL_MAX_MAP_TEXINFO ); hl_numclipnodes = HL_CopyLump (HL_LUMP_CLIPNODES, hl_dclipnodes, sizeof(hl_dclipnode_t), HL_MAX_MAP_CLIPNODES ); hl_numfaces = HL_CopyLump (HL_LUMP_FACES, hl_dfaces, sizeof(hl_dface_t), HL_MAX_MAP_FACES ); hl_nummarksurfaces = HL_CopyLump (HL_LUMP_MARKSURFACES, hl_dmarksurfaces, sizeof(hl_dmarksurfaces[0]), HL_MAX_MAP_MARKSURFACES ); hl_numsurfedges = HL_CopyLump (HL_LUMP_SURFEDGES, hl_dsurfedges, sizeof(hl_dsurfedges[0]), HL_MAX_MAP_SURFEDGES ); hl_numedges = HL_CopyLump (HL_LUMP_EDGES, hl_dedges, sizeof(hl_dedge_t), HL_MAX_MAP_EDGES ); hl_texdatasize = HL_CopyLump (HL_LUMP_TEXTURES, hl_dtexdata, 1, HL_MAX_MAP_MIPTEX ); hl_visdatasize = HL_CopyLump (HL_LUMP_VISIBILITY, hl_dvisdata, 1, HL_MAX_MAP_VISIBILITY ); hl_lightdatasize = HL_CopyLump (HL_LUMP_LIGHTING, hl_dlightdata, 1, HL_MAX_MAP_LIGHTING ); hl_entdatasize = HL_CopyLump (HL_LUMP_ENTITIES, hl_dentdata, 1, HL_MAX_MAP_ENTSTRING ); FreeMemory(hl_header); // everything has been copied out // // swap everything // HL_SwapBSPFile (false); hl_dmodels_checksum = FastChecksum( hl_dmodels, hl_nummodels*sizeof(hl_dmodels[0]) ); hl_dvertexes_checksum = FastChecksum( hl_dvertexes, hl_numvertexes*sizeof(hl_dvertexes[0]) ); hl_dplanes_checksum = FastChecksum( hl_dplanes, hl_numplanes*sizeof(hl_dplanes[0]) ); hl_dleafs_checksum = FastChecksum( hl_dleafs, hl_numleafs*sizeof(hl_dleafs[0]) ); hl_dnodes_checksum = FastChecksum( hl_dnodes, hl_numnodes*sizeof(hl_dnodes[0]) ); hl_texinfo_checksum = FastChecksum( hl_texinfo, hl_numtexinfo*sizeof(hl_texinfo[0]) ); hl_dclipnodes_checksum = FastChecksum( hl_dclipnodes, hl_numclipnodes*sizeof(hl_dclipnodes[0]) ); hl_dfaces_checksum = FastChecksum( hl_dfaces, hl_numfaces*sizeof(hl_dfaces[0]) ); hl_dmarksurfaces_checksum = FastChecksum( hl_dmarksurfaces, hl_nummarksurfaces*sizeof(hl_dmarksurfaces[0]) ); hl_dsurfedges_checksum = FastChecksum( hl_dsurfedges, hl_numsurfedges*sizeof(hl_dsurfedges[0]) ); hl_dedges_checksum = FastChecksum( hl_dedges, hl_numedges*sizeof(hl_dedges[0]) ); hl_dtexdata_checksum = FastChecksum( hl_dtexdata, hl_numedges*sizeof(hl_dtexdata[0]) ); hl_dvisdata_checksum = FastChecksum( hl_dvisdata, hl_visdatasize*sizeof(hl_dvisdata[0]) ); hl_dlightdata_checksum = FastChecksum( hl_dlightdata, hl_lightdatasize*sizeof(hl_dlightdata[0]) ); hl_dentdata_checksum = FastChecksum( hl_dentdata, hl_entdatasize*sizeof(hl_dentdata[0]) ); } //============================================================================ FILE *wadfile; hl_dheader_t outheader; void HL_AddLump (int lumpnum, void *data, int len) { hl_lump_t *lump; lump = &hl_header->lumps[lumpnum]; lump->fileofs = LittleLong( ftell(wadfile) ); lump->filelen = LittleLong(len); SafeWrite (wadfile, data, (len+3)&~3); } /* ============= HL_WriteBSPFile Swaps the bsp file in place, so it should not be referenced again ============= */ void HL_WriteBSPFile (char *filename) { hl_header = &outheader; memset (hl_header, 0, sizeof(hl_dheader_t)); HL_SwapBSPFile (true); hl_header->version = LittleLong (HL_BSPVERSION); wadfile = SafeOpenWrite (filename); SafeWrite (wadfile, hl_header, sizeof(hl_dheader_t)); // overwritten later HL_AddLump (HL_LUMP_PLANES, hl_dplanes, hl_numplanes*sizeof(hl_dplane_t)); HL_AddLump (HL_LUMP_LEAFS, hl_dleafs, hl_numleafs*sizeof(hl_dleaf_t)); HL_AddLump (HL_LUMP_VERTEXES, hl_dvertexes, hl_numvertexes*sizeof(hl_dvertex_t)); HL_AddLump (HL_LUMP_NODES, hl_dnodes, hl_numnodes*sizeof(hl_dnode_t)); HL_AddLump (HL_LUMP_TEXINFO, hl_texinfo, hl_numtexinfo*sizeof(hl_texinfo_t)); HL_AddLump (HL_LUMP_FACES, hl_dfaces, hl_numfaces*sizeof(hl_dface_t)); HL_AddLump (HL_LUMP_CLIPNODES, hl_dclipnodes, hl_numclipnodes*sizeof(hl_dclipnode_t)); HL_AddLump (HL_LUMP_MARKSURFACES, hl_dmarksurfaces, hl_nummarksurfaces*sizeof(hl_dmarksurfaces[0])); HL_AddLump (HL_LUMP_SURFEDGES, hl_dsurfedges, hl_numsurfedges*sizeof(hl_dsurfedges[0])); HL_AddLump (HL_LUMP_EDGES, hl_dedges, hl_numedges*sizeof(hl_dedge_t)); HL_AddLump (HL_LUMP_MODELS, hl_dmodels, hl_nummodels*sizeof(hl_dmodel_t)); HL_AddLump (HL_LUMP_LIGHTING, hl_dlightdata, hl_lightdatasize); HL_AddLump (HL_LUMP_VISIBILITY, hl_dvisdata, hl_visdatasize); HL_AddLump (HL_LUMP_ENTITIES, hl_dentdata, hl_entdatasize); HL_AddLump (HL_LUMP_TEXTURES, hl_dtexdata, hl_texdatasize); fseek (wadfile, 0, SEEK_SET); SafeWrite (wadfile, hl_header, sizeof(hl_dheader_t)); fclose (wadfile); } //============================================================================ #define ENTRIES(a) (sizeof(a)/sizeof(*(a))) #define ENTRYSIZE(a) (sizeof(*(a))) ArrayUsage( char *szItem, int items, int maxitems, int itemsize ) { float percentage = maxitems ? items * 100.0 / maxitems : 0.0; qprintf("%-12s %7i/%-7i %7i/%-7i (%4.1f%%)", szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); if ( percentage > 80.0 ) qprintf( "VERY FULL!\n" ); else if ( percentage > 95.0 ) qprintf( "SIZE DANGER!\n" ); else if ( percentage > 99.9 ) qprintf( "SIZE OVERFLOW!!!\n" ); else qprintf( "\n" ); return items * itemsize; } GlobUsage( char *szItem, int itemstorage, int maxstorage ) { float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; qprintf("%-12s [variable] %7i/%-7i (%4.1f%%)", szItem, itemstorage, maxstorage, percentage ); if ( percentage > 80.0 ) qprintf( "VERY FULL!\n" ); else if ( percentage > 95.0 ) qprintf( "SIZE DANGER!\n" ); else if ( percentage > 99.9 ) qprintf( "SIZE OVERFLOW!!!\n" ); else qprintf( "\n" ); return itemstorage; } /* ============= HL_PrintBSPFileSizes Dumps info about current file ============= */ void HL_PrintBSPFileSizes(void) { int numtextures = hl_texdatasize ? ((hl_dmiptexlump_t*)hl_dtexdata)->nummiptex : 0; int totalmemory = 0; qprintf("\n"); qprintf("Object names Objects/Maxobjs Memory / Maxmem Fullness\n" ); qprintf("------------ --------------- --------------- --------\n" ); totalmemory += ArrayUsage( "models", hl_nummodels, ENTRIES(hl_dmodels), ENTRYSIZE(hl_dmodels) ); totalmemory += ArrayUsage( "planes", hl_numplanes, ENTRIES(hl_dplanes), ENTRYSIZE(hl_dplanes) ); totalmemory += ArrayUsage( "vertexes", hl_numvertexes, ENTRIES(hl_dvertexes), ENTRYSIZE(hl_dvertexes) ); totalmemory += ArrayUsage( "nodes", hl_numnodes, ENTRIES(hl_dnodes), ENTRYSIZE(hl_dnodes) ); totalmemory += ArrayUsage( "texinfos", hl_numtexinfo, ENTRIES(hl_texinfo), ENTRYSIZE(hl_texinfo) ); totalmemory += ArrayUsage( "faces", hl_numfaces, ENTRIES(hl_dfaces), ENTRYSIZE(hl_dfaces) ); totalmemory += ArrayUsage( "clipnodes", hl_numclipnodes, ENTRIES(hl_dclipnodes), ENTRYSIZE(hl_dclipnodes) ); totalmemory += ArrayUsage( "leaves", hl_numleafs, ENTRIES(hl_dleafs), ENTRYSIZE(hl_dleafs) ); totalmemory += ArrayUsage( "marksurfaces",hl_nummarksurfaces,ENTRIES(hl_dmarksurfaces),ENTRYSIZE(hl_dmarksurfaces) ); totalmemory += ArrayUsage( "surfedges", hl_numsurfedges, ENTRIES(hl_dsurfedges), ENTRYSIZE(hl_dsurfedges) ); totalmemory += ArrayUsage( "edges", hl_numedges, ENTRIES(hl_dedges), ENTRYSIZE(hl_dedges) ); totalmemory += GlobUsage( "texdata", hl_texdatasize, sizeof(hl_dtexdata) ); totalmemory += GlobUsage( "lightdata", hl_lightdatasize, sizeof(hl_dlightdata) ); totalmemory += GlobUsage( "visdata", hl_visdatasize, sizeof(hl_dvisdata) ); totalmemory += GlobUsage( "entdata", hl_entdatasize, sizeof(hl_dentdata) ); qprintf( "=== Total BSP file data space used: %d bytes ===\n\n", totalmemory ); } /* ================= ParseEpair ================= * / epair_t *ParseEpair (void) { epair_t *e; e = malloc (sizeof(epair_t)); memset (e, 0, sizeof(epair_t)); if (strlen(token) >= MAX_KEY-1) Error ("ParseEpar: token too long"); e->key = copystring(token); GetToken (false); if (strlen(token) >= MAX_VALUE-1) Error ("ParseEpar: token too long"); e->value = copystring(token); return e; } //*/ /* ================ ParseEntity ================ * / qboolean ParseEntity (void) { epair_t *e; entity_t *mapent; if (!GetToken (true)) return false; if (strcmp (token, "{") ) Error ("ParseEntity: { not found"); if (num_entities == HL_MAX_MAP_ENTITIES) Error ("num_entities == HL_MAX_MAP_ENTITIES"); mapent = &entities[num_entities]; num_entities++; do { if (!GetToken (true)) Error ("ParseEntity: EOF without closing brace"); if (!strcmp (token, "}") ) break; e = ParseEpair (); e->next = mapent->epairs; mapent->epairs = e; } while (1); return true; } //*/ /* ================ ParseEntities Parses the dentdata string into entities ================ */ void HL_ParseEntities (void) { script_t *script; num_entities = 0; script = LoadScriptMemory(hl_dentdata, hl_entdatasize, "*Half-Life bsp file"); SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | SCFL_NOSTRINGESCAPECHARS); while(ParseEntity(script)) { } //end while FreeScript(script); } //end of the function HL_ParseEntities /* ================ UnparseEntities Generates the dentdata string from all the entities ================ */ void HL_UnparseEntities (void) { char *buf, *end; epair_t *ep; char line[2048]; int i; buf = hl_dentdata; end = buf; *end = 0; for (i=0 ; inext) { sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); strcat (end, line); end += strlen(line); } strcat (end,"}\n"); end += 2; if (end > buf + HL_MAX_MAP_ENTSTRING) Error ("Entity text too long"); } hl_entdatasize = end - buf + 1; } //end of the function HL_UnparseEntities /* void SetKeyValue (entity_t *ent, char *key, char *value) { epair_t *ep; for (ep=ent->epairs ; ep ; ep=ep->next) if (!strcmp (ep->key, key) ) { free (ep->value); ep->value = copystring(value); return; } ep = malloc (sizeof(*ep)); ep->next = ent->epairs; ent->epairs = ep; ep->key = copystring(key); ep->value = copystring(value); } char *ValueForKey (entity_t *ent, char *key) { epair_t *ep; for (ep=ent->epairs ; ep ; ep=ep->next) if (!strcmp (ep->key, key) ) return ep->value; return ""; } vec_t FloatForKey (entity_t *ent, char *key) { char *k; k = ValueForKey (ent, key); return atof(k); } void GetVectorForKey (entity_t *ent, char *key, vec3_t vec) { char *k; double v1, v2, v3; k = ValueForKey (ent, key); // scanf into doubles, then assign, so it is vec_t size independent v1 = v2 = v3 = 0; sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); vec[0] = v1; vec[1] = v2; vec[2] = v3; } //*/ ================================================ FILE: code/bspc/l_bsp_hl.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // upper design bounds #define HL_MAX_MAP_HULLS 4 #define HL_MAX_MAP_MODELS 400 #define HL_MAX_MAP_BRUSHES 4096 #define HL_MAX_MAP_ENTITIES 1024 #define HL_MAX_MAP_ENTSTRING (128*1024) #define HL_MAX_MAP_PLANES 32767 #define HL_MAX_MAP_NODES 32767 // because negative shorts are contents #define HL_MAX_MAP_CLIPNODES 32767 // #define HL_MAX_MAP_LEAFS 8192 #define HL_MAX_MAP_VERTS 65535 #define HL_MAX_MAP_FACES 65535 #define HL_MAX_MAP_MARKSURFACES 65535 #define HL_MAX_MAP_TEXINFO 8192 #define HL_MAX_MAP_EDGES 256000 #define HL_MAX_MAP_SURFEDGES 512000 #define HL_MAX_MAP_TEXTURES 512 #define HL_MAX_MAP_MIPTEX 0x200000 #define HL_MAX_MAP_LIGHTING 0x200000 #define HL_MAX_MAP_VISIBILITY 0x200000 #define HL_MAX_MAP_PORTALS 65536 // key / value pair sizes #define MAX_KEY 32 #define MAX_VALUE 1024 //============================================================================= #define HL_BSPVERSION 30 #define HL_TOOLVERSION 2 typedef struct { int fileofs, filelen; } hl_lump_t; #define HL_LUMP_ENTITIES 0 #define HL_LUMP_PLANES 1 #define HL_LUMP_TEXTURES 2 #define HL_LUMP_VERTEXES 3 #define HL_LUMP_VISIBILITY 4 #define HL_LUMP_NODES 5 #define HL_LUMP_TEXINFO 6 #define HL_LUMP_FACES 7 #define HL_LUMP_LIGHTING 8 #define HL_LUMP_CLIPNODES 9 #define HL_LUMP_LEAFS 10 #define HL_LUMP_MARKSURFACES 11 #define HL_LUMP_EDGES 12 #define HL_LUMP_SURFEDGES 13 #define HL_LUMP_MODELS 14 #define HL_HEADER_LUMPS 15 typedef struct { float mins[3], maxs[3]; float origin[3]; int headnode[HL_MAX_MAP_HULLS]; int visleafs; // not including the solid leaf 0 int firstface, numfaces; } hl_dmodel_t; typedef struct { int version; hl_lump_t lumps[HL_HEADER_LUMPS]; } hl_dheader_t; typedef struct { int nummiptex; int dataofs[4]; // [nummiptex] } hl_dmiptexlump_t; #define MIPLEVELS 4 typedef struct hl_miptex_s { char name[16]; unsigned width, height; unsigned offsets[MIPLEVELS]; // four mip maps stored } hl_miptex_t; typedef struct { float point[3]; } hl_dvertex_t; // 0-2 are axial planes #define PLANE_X 0 #define PLANE_Y 1 #define PLANE_Z 2 // 3-5 are non-axial planes snapped to the nearest #define PLANE_ANYX 3 #define PLANE_ANYY 4 #define PLANE_ANYZ 5 typedef struct { float normal[3]; float dist; int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate } hl_dplane_t; #define HL_CONTENTS_EMPTY -1 #define HL_CONTENTS_SOLID -2 #define HL_CONTENTS_WATER -3 #define HL_CONTENTS_SLIME -4 #define HL_CONTENTS_LAVA -5 #define HL_CONTENTS_SKY -6 #define HL_CONTENTS_ORIGIN -7 // removed at csg time #define HL_CONTENTS_CLIP -8 // changed to contents_solid #define HL_CONTENTS_CURRENT_0 -9 #define HL_CONTENTS_CURRENT_90 -10 #define HL_CONTENTS_CURRENT_180 -11 #define HL_CONTENTS_CURRENT_270 -12 #define HL_CONTENTS_CURRENT_UP -13 #define HL_CONTENTS_CURRENT_DOWN -14 #define HL_CONTENTS_TRANSLUCENT -15 // !!! if this is changed, it must be changed in asm_i386.h too !!! typedef struct { int planenum; short children[2]; // negative numbers are -(leafs+1), not nodes short mins[3]; // for sphere culling short maxs[3]; unsigned short firstface; unsigned short numfaces; // counting both sides } hl_dnode_t; typedef struct { int planenum; short children[2]; // negative numbers are contents } hl_dclipnode_t; typedef struct hl_texinfo_s { float vecs[2][4]; // [s/t][xyz offset] int miptex; int flags; } hl_texinfo_t; #define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision // note that edge 0 is never used, because negative edge nums are used for // counterclockwise use of the edge in a face typedef struct { unsigned short v[2]; // vertex numbers } hl_dedge_t; #define MAXLIGHTMAPS 4 typedef struct { short planenum; short side; int firstedge; // we must support > 64k edges short numedges; short texinfo; // lighting info byte styles[MAXLIGHTMAPS]; int lightofs; // start of [numstyles*surfsize] samples } hl_dface_t; #define AMBIENT_WATER 0 #define AMBIENT_SKY 1 #define AMBIENT_SLIME 2 #define AMBIENT_LAVA 3 #define NUM_AMBIENTS 4 // automatic ambient sounds // leaf 0 is the generic HL_CONTENTS_SOLID leaf, used for all solid areas // all other leafs need visibility info typedef struct { int contents; int visofs; // -1 = no visibility info short mins[3]; // for frustum culling short maxs[3]; unsigned short firstmarksurface; unsigned short nummarksurfaces; byte ambient_level[NUM_AMBIENTS]; } hl_dleaf_t; //============================================================================ #ifndef QUAKE_GAME #define ANGLE_UP -1 #define ANGLE_DOWN -2 // the utilities get to be lazy and just use large static arrays extern int hl_nummodels; extern hl_dmodel_t *hl_dmodels;//[MAX_MAP_MODELS]; extern int hl_dmodels_checksum; extern int hl_visdatasize; extern byte *hl_dvisdata;//[MAX_MAP_VISIBILITY]; extern int hl_dvisdata_checksum; extern int hl_lightdatasize; extern byte *hl_dlightdata;//[MAX_MAP_LIGHTING]; extern int hl_dlightdata_checksum; extern int hl_texdatasize; extern byte *hl_dtexdata;//[MAX_MAP_MIPTEX]; // (dmiptexlump_t) extern int hl_dtexdata_checksum; extern int hl_entdatasize; extern char *hl_dentdata;//[MAX_MAP_ENTSTRING]; extern int hl_dentdata_checksum; extern int hl_numleafs; extern hl_dleaf_t *hl_dleafs;//[MAX_MAP_LEAFS]; extern int hl_dleafs_checksum; extern int hl_numplanes; extern hl_dplane_t *hl_dplanes;//[MAX_MAP_PLANES]; extern int hl_dplanes_checksum; extern int hl_numvertexes; extern hl_dvertex_t *hl_dvertexes;//[MAX_MAP_VERTS]; extern int hl_dvertexes_checksum; extern int hl_numnodes; extern hl_dnode_t *hl_dnodes;//[MAX_MAP_NODES]; extern int hl_dnodes_checksum; extern int hl_numtexinfo; extern hl_texinfo_t *hl_texinfo;//[MAX_MAP_TEXINFO]; extern int hl_texinfo_checksum; extern int hl_numfaces; extern hl_dface_t *hl_dfaces;//[MAX_MAP_FACES]; extern int hl_dfaces_checksum; extern int hl_numclipnodes; extern hl_dclipnode_t *hl_dclipnodes;//[MAX_MAP_CLIPNODES]; extern int hl_dclipnodes_checksum; extern int hl_numedges; extern hl_dedge_t *hl_dedges;//[MAX_MAP_EDGES]; extern int hl_dedges_checksum; extern int hl_nummarksurfaces; extern unsigned short *hl_dmarksurfaces;//[MAX_MAP_MARKSURFACES]; extern int hl_dmarksurfaces_checksum; extern int hl_numsurfedges; extern int *hl_dsurfedges;//[MAX_MAP_SURFEDGES]; extern int hl_dsurfedges_checksum; int FastChecksum(void *buffer, int bytes); void HL_AllocMaxBSP(void); void HL_FreeMaxBSP(void); void HL_DecompressVis(byte *in, byte *decompressed); int HL_CompressVis(byte *vis, byte *dest); void HL_LoadBSPFile(char *filename, int offset, int length); void HL_WriteBSPFile(char *filename); void HL_PrintBSPFileSizes(void); void HL_PrintBSPFileSizes(void); void HL_ParseEntities(void); void HL_UnparseEntities(void); #endif ================================================ FILE: code/bspc/l_bsp_q1.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "l_cmd.h" #include "l_math.h" #include "l_mem.h" #include "l_log.h" #include "../botlib/l_script.h" #include "l_bsp_q1.h" #include "l_bsp_ent.h" //============================================================================= int q1_nummodels; q1_dmodel_t *q1_dmodels;//[MAX_MAP_MODELS]; int q1_visdatasize; byte *q1_dvisdata;//[MAX_MAP_VISIBILITY]; int q1_lightdatasize; byte *q1_dlightdata;//[MAX_MAP_LIGHTING]; int q1_texdatasize; byte *q1_dtexdata;//[MAX_MAP_MIPTEX]; // (dmiptexlump_t) int q1_entdatasize; char *q1_dentdata;//[MAX_MAP_ENTSTRING]; int q1_numleafs; q1_dleaf_t *q1_dleafs;//[MAX_MAP_LEAFS]; int q1_numplanes; q1_dplane_t *q1_dplanes;//[MAX_MAP_PLANES]; int q1_numvertexes; q1_dvertex_t *q1_dvertexes;//[MAX_MAP_VERTS]; int q1_numnodes; q1_dnode_t *q1_dnodes;//[MAX_MAP_NODES]; int q1_numtexinfo; q1_texinfo_t *q1_texinfo;//[MAX_MAP_TEXINFO]; int q1_numfaces; q1_dface_t *q1_dfaces;//[MAX_MAP_FACES]; int q1_numclipnodes; q1_dclipnode_t *q1_dclipnodes;//[MAX_MAP_CLIPNODES]; int q1_numedges; q1_dedge_t *q1_dedges;//[MAX_MAP_EDGES]; int q1_nummarksurfaces; unsigned short *q1_dmarksurfaces;//[MAX_MAP_MARKSURFACES]; int q1_numsurfedges; int *q1_dsurfedges;//[MAX_MAP_SURFEDGES]; //============================================================================= int q1_bspallocated = false; int q1_allocatedbspmem = 0; void Q1_AllocMaxBSP(void) { //models q1_nummodels = 0; q1_dmodels = (q1_dmodel_t *) GetMemory(Q1_MAX_MAP_MODELS * sizeof(q1_dmodel_t)); q1_allocatedbspmem = Q1_MAX_MAP_MODELS * sizeof(q1_dmodel_t); //visibility q1_visdatasize = 0; q1_dvisdata = (byte *) GetMemory(Q1_MAX_MAP_VISIBILITY * sizeof(byte)); q1_allocatedbspmem += Q1_MAX_MAP_VISIBILITY * sizeof(byte); //light data q1_lightdatasize = 0; q1_dlightdata = (byte *) GetMemory(Q1_MAX_MAP_LIGHTING * sizeof(byte)); q1_allocatedbspmem += Q1_MAX_MAP_LIGHTING * sizeof(byte); //texture data q1_texdatasize = 0; q1_dtexdata = (byte *) GetMemory(Q1_MAX_MAP_MIPTEX * sizeof(byte)); // (dmiptexlump_t) q1_allocatedbspmem += Q1_MAX_MAP_MIPTEX * sizeof(byte); //entities q1_entdatasize = 0; q1_dentdata = (char *) GetMemory(Q1_MAX_MAP_ENTSTRING * sizeof(char)); q1_allocatedbspmem += Q1_MAX_MAP_ENTSTRING * sizeof(char); //leaves q1_numleafs = 0; q1_dleafs = (q1_dleaf_t *) GetMemory(Q1_MAX_MAP_LEAFS * sizeof(q1_dleaf_t)); q1_allocatedbspmem += Q1_MAX_MAP_LEAFS * sizeof(q1_dleaf_t); //planes q1_numplanes = 0; q1_dplanes = (q1_dplane_t *) GetMemory(Q1_MAX_MAP_PLANES * sizeof(q1_dplane_t)); q1_allocatedbspmem += Q1_MAX_MAP_PLANES * sizeof(q1_dplane_t); //vertexes q1_numvertexes = 0; q1_dvertexes = (q1_dvertex_t *) GetMemory(Q1_MAX_MAP_VERTS * sizeof(q1_dvertex_t)); q1_allocatedbspmem += Q1_MAX_MAP_VERTS * sizeof(q1_dvertex_t); //nodes q1_numnodes = 0; q1_dnodes = (q1_dnode_t *) GetMemory(Q1_MAX_MAP_NODES * sizeof(q1_dnode_t)); q1_allocatedbspmem += Q1_MAX_MAP_NODES * sizeof(q1_dnode_t); //texture info q1_numtexinfo = 0; q1_texinfo = (q1_texinfo_t *) GetMemory(Q1_MAX_MAP_TEXINFO * sizeof(q1_texinfo_t)); q1_allocatedbspmem += Q1_MAX_MAP_TEXINFO * sizeof(q1_texinfo_t); //faces q1_numfaces = 0; q1_dfaces = (q1_dface_t *) GetMemory(Q1_MAX_MAP_FACES * sizeof(q1_dface_t)); q1_allocatedbspmem += Q1_MAX_MAP_FACES * sizeof(q1_dface_t); //clip nodes q1_numclipnodes = 0; q1_dclipnodes = (q1_dclipnode_t *) GetMemory(Q1_MAX_MAP_CLIPNODES * sizeof(q1_dclipnode_t)); q1_allocatedbspmem += Q1_MAX_MAP_CLIPNODES * sizeof(q1_dclipnode_t); //edges q1_numedges = 0; q1_dedges = (q1_dedge_t *) GetMemory(Q1_MAX_MAP_EDGES * sizeof(q1_dedge_t)); q1_allocatedbspmem += Q1_MAX_MAP_EDGES, sizeof(q1_dedge_t); //mark surfaces q1_nummarksurfaces = 0; q1_dmarksurfaces = (unsigned short *) GetMemory(Q1_MAX_MAP_MARKSURFACES * sizeof(unsigned short)); q1_allocatedbspmem += Q1_MAX_MAP_MARKSURFACES * sizeof(unsigned short); //surface edges q1_numsurfedges = 0; q1_dsurfedges = (int *) GetMemory(Q1_MAX_MAP_SURFEDGES * sizeof(int)); q1_allocatedbspmem += Q1_MAX_MAP_SURFEDGES * sizeof(int); //print allocated memory Log_Print("allocated "); PrintMemorySize(q1_allocatedbspmem); Log_Print(" of BSP memory\n"); } //end of the function Q1_AllocMaxBSP void Q1_FreeMaxBSP(void) { //models q1_nummodels = 0; FreeMemory(q1_dmodels); q1_dmodels = NULL; //visibility q1_visdatasize = 0; FreeMemory(q1_dvisdata); q1_dvisdata = NULL; //light data q1_lightdatasize = 0; FreeMemory(q1_dlightdata); q1_dlightdata = NULL; //texture data q1_texdatasize = 0; FreeMemory(q1_dtexdata); q1_dtexdata = NULL; //entities q1_entdatasize = 0; FreeMemory(q1_dentdata); q1_dentdata = NULL; //leaves q1_numleafs = 0; FreeMemory(q1_dleafs); q1_dleafs = NULL; //planes q1_numplanes = 0; FreeMemory(q1_dplanes); q1_dplanes = NULL; //vertexes q1_numvertexes = 0; FreeMemory(q1_dvertexes); q1_dvertexes = NULL; //nodes q1_numnodes = 0; FreeMemory(q1_dnodes); q1_dnodes = NULL; //texture info q1_numtexinfo = 0; FreeMemory(q1_texinfo); q1_texinfo = NULL; //faces q1_numfaces = 0; FreeMemory(q1_dfaces); q1_dfaces = NULL; //clip nodes q1_numclipnodes = 0; FreeMemory(q1_dclipnodes); q1_dclipnodes = NULL; //edges q1_numedges = 0; FreeMemory(q1_dedges); q1_dedges = NULL; //mark surfaces q1_nummarksurfaces = 0; FreeMemory(q1_dmarksurfaces); q1_dmarksurfaces = NULL; //surface edges q1_numsurfedges = 0; FreeMemory(q1_dsurfedges); q1_dsurfedges = NULL; // Log_Print("freed "); PrintMemorySize(q1_allocatedbspmem); Log_Print(" of BSP memory\n"); q1_allocatedbspmem = 0; } //end of the function Q1_FreeMaxBSP //#endif //ME /* ============= Q1_SwapBSPFile Byte swaps all data in a bsp file. ============= */ void Q1_SwapBSPFile (qboolean todisk) { int i, j, c; q1_dmodel_t *d; q1_dmiptexlump_t *mtl; // models for (i=0 ; iheadnode[j] = LittleLong (d->headnode[j]); d->visleafs = LittleLong (d->visleafs); d->firstface = LittleLong (d->firstface); d->numfaces = LittleLong (d->numfaces); for (j=0 ; j<3 ; j++) { d->mins[j] = LittleFloat(d->mins[j]); d->maxs[j] = LittleFloat(d->maxs[j]); d->origin[j] = LittleFloat(d->origin[j]); } } // // vertexes // for (i=0 ; inummiptex; else c = LittleLong(mtl->nummiptex); mtl->nummiptex = LittleLong (mtl->nummiptex); for (i=0 ; idataofs[i] = LittleLong(mtl->dataofs[i]); } // // marksurfaces // for (i=0 ; ilumps[lump].filelen; ofs = q1_header->lumps[lump].fileofs; if (length % size) { Error ("LoadBSPFile: odd lump size"); } // somehow things got out of range if ((length/size) > maxsize) { printf("WARNING: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize); length = maxsize * size; } if ( ofs + length > q1_fileLength ) { printf("WARNING: exceeded file length for lump %d\n", lump); length = q1_fileLength - ofs; if ( length <= 0 ) { return 0; } } memcpy (dest, (byte *)q1_header + ofs, length); return length / size; } /* ============= Q1_LoadBSPFile ============= */ void Q1_LoadBSPFile(char *filename, int offset, int length) { int i; // // load the file header // q1_fileLength = LoadFile(filename, (void **)&q1_header, offset, length); // swap the header for (i=0 ; i< sizeof(q1_dheader_t)/4 ; i++) ((int *)q1_header)[i] = LittleLong ( ((int *)q1_header)[i]); if (q1_header->version != Q1_BSPVERSION) Error ("%s is version %i, not %i", filename, i, Q1_BSPVERSION); q1_nummodels = Q1_CopyLump (Q1_LUMP_MODELS, q1_dmodels, sizeof(q1_dmodel_t), Q1_MAX_MAP_MODELS ); q1_numvertexes = Q1_CopyLump (Q1_LUMP_VERTEXES, q1_dvertexes, sizeof(q1_dvertex_t), Q1_MAX_MAP_VERTS ); q1_numplanes = Q1_CopyLump (Q1_LUMP_PLANES, q1_dplanes, sizeof(q1_dplane_t), Q1_MAX_MAP_PLANES ); q1_numleafs = Q1_CopyLump (Q1_LUMP_LEAFS, q1_dleafs, sizeof(q1_dleaf_t), Q1_MAX_MAP_LEAFS ); q1_numnodes = Q1_CopyLump (Q1_LUMP_NODES, q1_dnodes, sizeof(q1_dnode_t), Q1_MAX_MAP_NODES ); q1_numtexinfo = Q1_CopyLump (Q1_LUMP_TEXINFO, q1_texinfo, sizeof(q1_texinfo_t), Q1_MAX_MAP_TEXINFO ); q1_numclipnodes = Q1_CopyLump (Q1_LUMP_CLIPNODES, q1_dclipnodes, sizeof(q1_dclipnode_t), Q1_MAX_MAP_CLIPNODES ); q1_numfaces = Q1_CopyLump (Q1_LUMP_FACES, q1_dfaces, sizeof(q1_dface_t), Q1_MAX_MAP_FACES ); q1_nummarksurfaces = Q1_CopyLump (Q1_LUMP_MARKSURFACES, q1_dmarksurfaces, sizeof(q1_dmarksurfaces[0]), Q1_MAX_MAP_MARKSURFACES ); q1_numsurfedges = Q1_CopyLump (Q1_LUMP_SURFEDGES, q1_dsurfedges, sizeof(q1_dsurfedges[0]), Q1_MAX_MAP_SURFEDGES ); q1_numedges = Q1_CopyLump (Q1_LUMP_EDGES, q1_dedges, sizeof(q1_dedge_t), Q1_MAX_MAP_EDGES ); q1_texdatasize = Q1_CopyLump (Q1_LUMP_TEXTURES, q1_dtexdata, 1, Q1_MAX_MAP_MIPTEX ); q1_visdatasize = Q1_CopyLump (Q1_LUMP_VISIBILITY, q1_dvisdata, 1, Q1_MAX_MAP_VISIBILITY ); q1_lightdatasize = Q1_CopyLump (Q1_LUMP_LIGHTING, q1_dlightdata, 1, Q1_MAX_MAP_LIGHTING ); q1_entdatasize = Q1_CopyLump (Q1_LUMP_ENTITIES, q1_dentdata, 1, Q1_MAX_MAP_ENTSTRING ); FreeMemory(q1_header); // everything has been copied out // // swap everything // Q1_SwapBSPFile (false); } //============================================================================ FILE *q1_wadfile; q1_dheader_t q1_outheader; void Q1_AddLump (int lumpnum, void *data, int len) { q1_lump_t *lump; lump = &q1_header->lumps[lumpnum]; lump->fileofs = LittleLong(ftell(q1_wadfile)); lump->filelen = LittleLong(len); SafeWrite(q1_wadfile, data, (len+3)&~3); } /* ============= Q1_WriteBSPFile Swaps the bsp file in place, so it should not be referenced again ============= */ void Q1_WriteBSPFile (char *filename) { q1_header = &q1_outheader; memset (q1_header, 0, sizeof(q1_dheader_t)); Q1_SwapBSPFile (true); q1_header->version = LittleLong (Q1_BSPVERSION); q1_wadfile = SafeOpenWrite (filename); SafeWrite (q1_wadfile, q1_header, sizeof(q1_dheader_t)); // overwritten later Q1_AddLump (Q1_LUMP_PLANES, q1_dplanes, q1_numplanes*sizeof(q1_dplane_t)); Q1_AddLump (Q1_LUMP_LEAFS, q1_dleafs, q1_numleafs*sizeof(q1_dleaf_t)); Q1_AddLump (Q1_LUMP_VERTEXES, q1_dvertexes, q1_numvertexes*sizeof(q1_dvertex_t)); Q1_AddLump (Q1_LUMP_NODES, q1_dnodes, q1_numnodes*sizeof(q1_dnode_t)); Q1_AddLump (Q1_LUMP_TEXINFO, q1_texinfo, q1_numtexinfo*sizeof(q1_texinfo_t)); Q1_AddLump (Q1_LUMP_FACES, q1_dfaces, q1_numfaces*sizeof(q1_dface_t)); Q1_AddLump (Q1_LUMP_CLIPNODES, q1_dclipnodes, q1_numclipnodes*sizeof(q1_dclipnode_t)); Q1_AddLump (Q1_LUMP_MARKSURFACES, q1_dmarksurfaces, q1_nummarksurfaces*sizeof(q1_dmarksurfaces[0])); Q1_AddLump (Q1_LUMP_SURFEDGES, q1_dsurfedges, q1_numsurfedges*sizeof(q1_dsurfedges[0])); Q1_AddLump (Q1_LUMP_EDGES, q1_dedges, q1_numedges*sizeof(q1_dedge_t)); Q1_AddLump (Q1_LUMP_MODELS, q1_dmodels, q1_nummodels*sizeof(q1_dmodel_t)); Q1_AddLump (Q1_LUMP_LIGHTING, q1_dlightdata, q1_lightdatasize); Q1_AddLump (Q1_LUMP_VISIBILITY, q1_dvisdata, q1_visdatasize); Q1_AddLump (Q1_LUMP_ENTITIES, q1_dentdata, q1_entdatasize); Q1_AddLump (Q1_LUMP_TEXTURES, q1_dtexdata, q1_texdatasize); fseek (q1_wadfile, 0, SEEK_SET); SafeWrite (q1_wadfile, q1_header, sizeof(q1_dheader_t)); fclose (q1_wadfile); } //============================================================================ /* ============= Q1_PrintBSPFileSizes Dumps info about current file ============= */ void Q1_PrintBSPFileSizes (void) { printf ("%5i planes %6i\n" ,q1_numplanes, (int)(q1_numplanes*sizeof(q1_dplane_t))); printf ("%5i vertexes %6i\n" ,q1_numvertexes, (int)(q1_numvertexes*sizeof(q1_dvertex_t))); printf ("%5i nodes %6i\n" ,q1_numnodes, (int)(q1_numnodes*sizeof(q1_dnode_t))); printf ("%5i texinfo %6i\n" ,q1_numtexinfo, (int)(q1_numtexinfo*sizeof(q1_texinfo_t))); printf ("%5i faces %6i\n" ,q1_numfaces, (int)(q1_numfaces*sizeof(q1_dface_t))); printf ("%5i clipnodes %6i\n" ,q1_numclipnodes, (int)(q1_numclipnodes*sizeof(q1_dclipnode_t))); printf ("%5i leafs %6i\n" ,q1_numleafs, (int)(q1_numleafs*sizeof(q1_dleaf_t))); printf ("%5i marksurfaces %6i\n" ,q1_nummarksurfaces, (int)(q1_nummarksurfaces*sizeof(q1_dmarksurfaces[0]))); printf ("%5i surfedges %6i\n" ,q1_numsurfedges, (int)(q1_numsurfedges*sizeof(q1_dmarksurfaces[0]))); printf ("%5i edges %6i\n" ,q1_numedges, (int)(q1_numedges*sizeof(q1_dedge_t))); if (!q1_texdatasize) printf (" 0 textures 0\n"); else printf ("%5i textures %6i\n",((q1_dmiptexlump_t*)q1_dtexdata)->nummiptex, q1_texdatasize); printf (" lightdata %6i\n", q1_lightdatasize); printf (" visdata %6i\n", q1_visdatasize); printf (" entdata %6i\n", q1_entdatasize); } //end of the function Q1_PrintBSPFileSizes /* ================ Q1_ParseEntities Parses the dentdata string into entities ================ */ void Q1_ParseEntities (void) { script_t *script; num_entities = 0; script = LoadScriptMemory(q1_dentdata, q1_entdatasize, "*Quake1 bsp file"); SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | SCFL_NOSTRINGESCAPECHARS); while(ParseEntity(script)) { } //end while FreeScript(script); } //end of the function Q1_ParseEntities /* ================ Q1_UnparseEntities Generates the dentdata string from all the entities ================ */ void Q1_UnparseEntities (void) { char *buf, *end; epair_t *ep; char line[2048]; int i; buf = q1_dentdata; end = buf; *end = 0; for (i=0 ; inext) { sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); strcat (end, line); end += strlen(line); } strcat (end,"}\n"); end += 2; if (end > buf + Q1_MAX_MAP_ENTSTRING) Error ("Entity text too long"); } q1_entdatasize = end - buf + 1; } //end of the function Q1_UnparseEntities ================================================ FILE: code/bspc/l_bsp_q1.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // upper design bounds #define Q1_MAX_MAP_HULLS 4 #define Q1_MAX_MAP_MODELS 256 #define Q1_MAX_MAP_BRUSHES 4096 #define Q1_MAX_MAP_ENTITIES 1024 #define Q1_MAX_MAP_ENTSTRING 65536 #define Q1_MAX_MAP_PLANES 8192 #define Q1_MAX_MAP_NODES 32767 // because negative shorts are contents #define Q1_MAX_MAP_CLIPNODES 32767 // #define Q1_MAX_MAP_LEAFS 32767 // #define Q1_MAX_MAP_VERTS 65535 #define Q1_MAX_MAP_FACES 65535 #define Q1_MAX_MAP_MARKSURFACES 65535 #define Q1_MAX_MAP_TEXINFO 4096 #define Q1_MAX_MAP_EDGES 256000 #define Q1_MAX_MAP_SURFEDGES 512000 #define Q1_MAX_MAP_MIPTEX 0x200000 #define Q1_MAX_MAP_LIGHTING 0x100000 #define Q1_MAX_MAP_VISIBILITY 0x100000 // key / value pair sizes #define MAX_KEY 32 #define MAX_VALUE 1024 //============================================================================= #define Q1_BSPVERSION 29 typedef struct { int fileofs, filelen; } q1_lump_t; #define Q1_LUMP_ENTITIES 0 #define Q1_LUMP_PLANES 1 #define Q1_LUMP_TEXTURES 2 #define Q1_LUMP_VERTEXES 3 #define Q1_LUMP_VISIBILITY 4 #define Q1_LUMP_NODES 5 #define Q1_LUMP_TEXINFO 6 #define Q1_LUMP_FACES 7 #define Q1_LUMP_LIGHTING 8 #define Q1_LUMP_CLIPNODES 9 #define Q1_LUMP_LEAFS 10 #define Q1_LUMP_MARKSURFACES 11 #define Q1_LUMP_EDGES 12 #define Q1_LUMP_SURFEDGES 13 #define Q1_LUMP_MODELS 14 #define Q1_HEADER_LUMPS 15 typedef struct { float mins[3], maxs[3]; float origin[3]; int headnode[Q1_MAX_MAP_HULLS]; int visleafs; // not including the solid leaf 0 int firstface, numfaces; } q1_dmodel_t; typedef struct { int version; q1_lump_t lumps[Q1_HEADER_LUMPS]; } q1_dheader_t; typedef struct { int nummiptex; int dataofs[4]; // [nummiptex] } q1_dmiptexlump_t; #define MIPLEVELS 4 typedef struct q1_miptex_s { char name[16]; unsigned width, height; unsigned offsets[MIPLEVELS]; // four mip maps stored } q1_miptex_t; typedef struct { float point[3]; } q1_dvertex_t; // 0-2 are axial planes #define PLANE_X 0 #define PLANE_Y 1 #define PLANE_Z 2 // 3-5 are non-axial planes snapped to the nearest #define PLANE_ANYX 3 #define PLANE_ANYY 4 #define PLANE_ANYZ 5 typedef struct { float normal[3]; float dist; int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate } q1_dplane_t; #define Q1_CONTENTS_EMPTY -1 #define Q1_CONTENTS_SOLID -2 #define Q1_CONTENTS_WATER -3 #define Q1_CONTENTS_SLIME -4 #define Q1_CONTENTS_LAVA -5 #define Q1_CONTENTS_SKY -6 // !!! if this is changed, it must be changed in asm_i386.h too !!! typedef struct { int planenum; short children[2]; // negative numbers are -(leafs+1), not nodes short mins[3]; // for sphere culling short maxs[3]; unsigned short firstface; unsigned short numfaces; // counting both sides } q1_dnode_t; typedef struct { int planenum; short children[2]; // negative numbers are contents } q1_dclipnode_t; typedef struct q1_texinfo_s { float vecs[2][4]; // [s/t][xyz offset] int miptex; int flags; } q1_texinfo_t; #define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision // note that edge 0 is never used, because negative edge nums are used for // counterclockwise use of the edge in a face typedef struct { unsigned short v[2]; // vertex numbers } q1_dedge_t; #define MAXLIGHTMAPS 4 typedef struct { short planenum; short side; int firstedge; // we must support > 64k edges short numedges; short texinfo; // lighting info byte styles[MAXLIGHTMAPS]; int lightofs; // start of [numstyles*surfsize] samples } q1_dface_t; #define AMBIENT_WATER 0 #define AMBIENT_SKY 1 #define AMBIENT_SLIME 2 #define AMBIENT_LAVA 3 #define NUM_AMBIENTS 4 // automatic ambient sounds // leaf 0 is the generic Q1_CONTENTS_SOLID leaf, used for all solid areas // all other leafs need visibility info typedef struct { int contents; int visofs; // -1 = no visibility info short mins[3]; // for frustum culling short maxs[3]; unsigned short firstmarksurface; unsigned short nummarksurfaces; byte ambient_level[NUM_AMBIENTS]; } q1_dleaf_t; //============================================================================ #ifndef QUAKE_GAME // the utilities get to be lazy and just use large static arrays extern int q1_nummodels; extern q1_dmodel_t *q1_dmodels;//[MAX_MAP_MODELS]; extern int q1_visdatasize; extern byte *q1_dvisdata;//[MAX_MAP_VISIBILITY]; extern int q1_lightdatasize; extern byte *q1_dlightdata;//[MAX_MAP_LIGHTING]; extern int q1_texdatasize; extern byte *q1_dtexdata;//[MAX_MAP_MIPTEX]; // (dmiptexlump_t) extern int q1_entdatasize; extern char *q1_dentdata;//[MAX_MAP_ENTSTRING]; extern int q1_numleafs; extern q1_dleaf_t *q1_dleafs;//[MAX_MAP_LEAFS]; extern int q1_numplanes; extern q1_dplane_t *q1_dplanes;//[MAX_MAP_PLANES]; extern int q1_numvertexes; extern q1_dvertex_t *q1_dvertexes;//[MAX_MAP_VERTS]; extern int q1_numnodes; extern q1_dnode_t *q1_dnodes;//[MAX_MAP_NODES]; extern int q1_numtexinfo; extern q1_texinfo_t *q1_texinfo;//[MAX_MAP_TEXINFO]; extern int q1_numfaces; extern q1_dface_t *q1_dfaces;//[MAX_MAP_FACES]; extern int q1_numclipnodes; extern q1_dclipnode_t *q1_dclipnodes;//[MAX_MAP_CLIPNODES]; extern int q1_numedges; extern q1_dedge_t *q1_dedges;//[MAX_MAP_EDGES]; extern int q1_nummarksurfaces; extern unsigned short *q1_dmarksurfaces;//[MAX_MAP_MARKSURFACES]; extern int q1_numsurfedges; extern int *q1_dsurfedges;//[MAX_MAP_SURFEDGES]; void Q1_AllocMaxBSP(void); void Q1_FreeMaxBSP(void); void Q1_LoadBSPFile(char *filename, int offset, int length); void Q1_WriteBSPFile(char *filename); void Q1_PrintBSPFileSizes(void); void Q1_ParseEntities(void); void Q1_UnparseEntities(void); #endif ================================================ FILE: code/bspc/l_bsp_q2.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "l_cmd.h" #include "l_math.h" #include "l_mem.h" #include "l_log.h" #include "l_poly.h" #include "../botlib/l_script.h" #include "q2files.h" #include "l_bsp_q2.h" #include "l_bsp_ent.h" #define q2_dmodel_t dmodel_t #define q2_lump_t lump_t #define q2_dheader_t dheader_t #define q2_dmodel_t dmodel_t #define q2_dvertex_t dvertex_t #define q2_dplane_t dplane_t #define q2_dnode_t dnode_t #define q2_texinfo_t texinfo_t #define q2_dedge_t dedge_t #define q2_dface_t dface_t #define q2_dleaf_t dleaf_t #define q2_dbrushside_t dbrushside_t #define q2_dbrush_t dbrush_t #define q2_dvis_t dvis_t #define q2_dareaportal_t dareaportal_t #define q2_darea_t darea_t #define q2_nummodels nummodels #define q2_dmodels dmodels #define q2_numleafs numleafs #define q2_dleafs dleafs #define q2_numplanes numplanes #define q2_dplanes dplanes #define q2_numvertexes numvertexes #define q2_dvertexes dvertexes #define q2_numnodes numnodes #define q2_dnodes dnodes #define q2_numtexinfo numtexinfo #define q2_texinfo texinfo #define q2_numfaces numfaces #define q2_dfaces dfaces #define q2_numedges numedges #define q2_dedges dedges #define q2_numleaffaces numleaffaces #define q2_dleaffaces dleaffaces #define q2_numleafbrushes numleafbrushes #define q2_dleafbrushes dleafbrushes #define q2_dsurfedges dsurfedges #define q2_numbrushes numbrushes #define q2_dbrushes dbrushes #define q2_numbrushsides numbrushsides #define q2_dbrushsides dbrushsides #define q2_numareas numareas #define q2_dareas dareas #define q2_numareaportals numareaportals #define q2_dareaportals dareaportals void GetLeafNums (void); //============================================================================= int nummodels; dmodel_t *dmodels;//[MAX_MAP_MODELS]; int visdatasize; byte *dvisdata;//[MAX_MAP_VISIBILITY]; dvis_t *dvis;// = (dvis_t *)dvisdata; int lightdatasize; byte *dlightdata;//[MAX_MAP_LIGHTING]; int entdatasize; char *dentdata;//[MAX_MAP_ENTSTRING]; int numleafs; dleaf_t *dleafs;//[MAX_MAP_LEAFS]; int numplanes; dplane_t *dplanes;//[MAX_MAP_PLANES]; int numvertexes; dvertex_t *dvertexes;//[MAX_MAP_VERTS]; int numnodes; dnode_t *dnodes;//[MAX_MAP_NODES]; //NOTE: must be static for q2 .map to q2 .bsp int numtexinfo; texinfo_t texinfo[MAX_MAP_TEXINFO]; int numfaces; dface_t *dfaces;//[MAX_MAP_FACES]; int numedges; dedge_t *dedges;//[MAX_MAP_EDGES]; int numleaffaces; unsigned short *dleaffaces;//[MAX_MAP_LEAFFACES]; int numleafbrushes; unsigned short *dleafbrushes;//[MAX_MAP_LEAFBRUSHES]; int numsurfedges; int *dsurfedges;//[MAX_MAP_SURFEDGES]; int numbrushes; dbrush_t *dbrushes;//[MAX_MAP_BRUSHES]; int numbrushsides; dbrushside_t *dbrushsides;//[MAX_MAP_BRUSHSIDES]; int numareas; darea_t *dareas;//[MAX_MAP_AREAS]; int numareaportals; dareaportal_t *dareaportals;//[MAX_MAP_AREAPORTALS]; #define MAX_MAP_DPOP 256 byte dpop[MAX_MAP_DPOP]; // char brushsidetextured[MAX_MAP_BRUSHSIDES]; //#ifdef ME int bspallocated = false; int allocatedbspmem = 0; void Q2_AllocMaxBSP(void) { //models nummodels = 0; dmodels = (dmodel_t *) GetClearedMemory(MAX_MAP_MODELS * sizeof(dmodel_t)); allocatedbspmem += MAX_MAP_MODELS * sizeof(dmodel_t); //vis data visdatasize = 0; dvisdata = (byte *) GetClearedMemory(MAX_MAP_VISIBILITY * sizeof(byte)); dvis = (dvis_t *) dvisdata; allocatedbspmem += MAX_MAP_VISIBILITY * sizeof(byte); //light data lightdatasize = 0; dlightdata = (byte *) GetClearedMemory(MAX_MAP_LIGHTING * sizeof(byte)); allocatedbspmem += MAX_MAP_LIGHTING * sizeof(byte); //entity data entdatasize = 0; dentdata = (char *) GetClearedMemory(MAX_MAP_ENTSTRING * sizeof(char)); allocatedbspmem += MAX_MAP_ENTSTRING * sizeof(char); //leafs numleafs = 0; dleafs = (dleaf_t *) GetClearedMemory(MAX_MAP_LEAFS * sizeof(dleaf_t)); allocatedbspmem += MAX_MAP_LEAFS * sizeof(dleaf_t); //planes numplanes = 0; dplanes = (dplane_t *) GetClearedMemory(MAX_MAP_PLANES * sizeof(dplane_t)); allocatedbspmem += MAX_MAP_PLANES * sizeof(dplane_t); //vertexes numvertexes = 0; dvertexes = (dvertex_t *) GetClearedMemory(MAX_MAP_VERTS * sizeof(dvertex_t)); allocatedbspmem += MAX_MAP_VERTS * sizeof(dvertex_t); //nodes numnodes = 0; dnodes = (dnode_t *) GetClearedMemory(MAX_MAP_NODES * sizeof(dnode_t)); allocatedbspmem += MAX_MAP_NODES * sizeof(dnode_t); /* //texture info numtexinfo = 0; texinfo = (texinfo_t *) GetClearedMemory(MAX_MAP_TEXINFO * sizeof(texinfo_t)); allocatedbspmem += MAX_MAP_TEXINFO * sizeof(texinfo_t); //*/ //faces numfaces = 0; dfaces = (dface_t *) GetClearedMemory(MAX_MAP_FACES * sizeof(dface_t)); allocatedbspmem += MAX_MAP_FACES * sizeof(dface_t); //edges numedges = 0; dedges = (dedge_t *) GetClearedMemory(MAX_MAP_EDGES * sizeof(dedge_t)); allocatedbspmem += MAX_MAP_EDGES * sizeof(dedge_t); //leaf faces numleaffaces = 0; dleaffaces = (unsigned short *) GetClearedMemory(MAX_MAP_LEAFFACES * sizeof(unsigned short)); allocatedbspmem += MAX_MAP_LEAFFACES * sizeof(unsigned short); //leaf brushes numleafbrushes = 0; dleafbrushes = (unsigned short *) GetClearedMemory(MAX_MAP_LEAFBRUSHES * sizeof(unsigned short)); allocatedbspmem += MAX_MAP_LEAFBRUSHES * sizeof(unsigned short); //surface edges numsurfedges = 0; dsurfedges = (int *) GetClearedMemory(MAX_MAP_SURFEDGES * sizeof(int)); allocatedbspmem += MAX_MAP_SURFEDGES * sizeof(int); //brushes numbrushes = 0; dbrushes = (dbrush_t *) GetClearedMemory(MAX_MAP_BRUSHES * sizeof(dbrush_t)); allocatedbspmem += MAX_MAP_BRUSHES * sizeof(dbrush_t); //brushsides numbrushsides = 0; dbrushsides = (dbrushside_t *) GetClearedMemory(MAX_MAP_BRUSHSIDES * sizeof(dbrushside_t)); allocatedbspmem += MAX_MAP_BRUSHSIDES * sizeof(dbrushside_t); //areas numareas = 0; dareas = (darea_t *) GetClearedMemory(MAX_MAP_AREAS * sizeof(darea_t)); allocatedbspmem += MAX_MAP_AREAS * sizeof(darea_t); //area portals numareaportals = 0; dareaportals = (dareaportal_t *) GetClearedMemory(MAX_MAP_AREAPORTALS * sizeof(dareaportal_t)); allocatedbspmem += MAX_MAP_AREAPORTALS * sizeof(dareaportal_t); //print allocated memory Log_Print("allocated "); PrintMemorySize(allocatedbspmem); Log_Print(" of BSP memory\n"); } //end of the function Q2_AllocMaxBSP void Q2_FreeMaxBSP(void) { //models nummodels = 0; FreeMemory(dmodels); dmodels = NULL; //vis data visdatasize = 0; FreeMemory(dvisdata); dvisdata = NULL; dvis = NULL; //light data lightdatasize = 0; FreeMemory(dlightdata); dlightdata = NULL; //entity data entdatasize = 0; FreeMemory(dentdata); dentdata = NULL; //leafs numleafs = 0; FreeMemory(dleafs); dleafs = NULL; //planes numplanes = 0; FreeMemory(dplanes); dplanes = NULL; //vertexes numvertexes = 0; FreeMemory(dvertexes); dvertexes = NULL; //nodes numnodes = 0; FreeMemory(dnodes); dnodes = NULL; /* //texture info numtexinfo = 0; FreeMemory(texinfo); texinfo = NULL; //*/ //faces numfaces = 0; FreeMemory(dfaces); dfaces = NULL; //edges numedges = 0; FreeMemory(dedges); dedges = NULL; //leaf faces numleaffaces = 0; FreeMemory(dleaffaces); dleaffaces = NULL; //leaf brushes numleafbrushes = 0; FreeMemory(dleafbrushes); dleafbrushes = NULL; //surface edges numsurfedges = 0; FreeMemory(dsurfedges); dsurfedges = NULL; //brushes numbrushes = 0; FreeMemory(dbrushes); dbrushes = NULL; //brushsides numbrushsides = 0; FreeMemory(dbrushsides); dbrushsides = NULL; //areas numareas = 0; FreeMemory(dareas); dareas = NULL; //area portals numareaportals = 0; FreeMemory(dareaportals); dareaportals = NULL; // Log_Print("freed "); PrintMemorySize(allocatedbspmem); Log_Print(" of BSP memory\n"); allocatedbspmem = 0; } //end of the function Q2_FreeMaxBSP #define WCONVEX_EPSILON 0.5 int InsideWinding(winding_t *w, vec3_t point, int planenum) { int i; float dist; vec_t *v1, *v2; vec3_t normal, edgevec; dplane_t *plane; for (i = 1; i <= w->numpoints; i++) { v1 = w->p[i % w->numpoints]; v2 = w->p[(i + 1) % w->numpoints]; VectorSubtract(v2, v1, edgevec); plane = &dplanes[planenum]; CrossProduct(plane->normal, edgevec, normal); VectorNormalize(normal); dist = DotProduct(normal, v1); // if (DotProduct(normal, point) - dist > WCONVEX_EPSILON) return false; } //end for return true; } //end of the function InsideWinding int InsideFace(dface_t *face, vec3_t point) { int i, edgenum, side; float dist; vec_t *v1, *v2; vec3_t normal, edgevec; dplane_t *plane; for (i = 0; i < face->numedges; i++) { //get the first and second vertex of the edge edgenum = dsurfedges[face->firstedge + i]; side = edgenum < 0; v1 = dvertexes[dedges[abs(edgenum)].v[side]].point; v2 = dvertexes[dedges[abs(edgenum)].v[!side]].point; //create a plane through the edge vector, orthogonal to the face plane //and with the normal vector pointing out of the face VectorSubtract(v1, v2, edgevec); plane = &dplanes[face->planenum]; CrossProduct(plane->normal, edgevec, normal); VectorNormalize(normal); dist = DotProduct(normal, v1); // if (DotProduct(normal, point) - dist > WCONVEX_EPSILON) return false; } //end for return true; } //end of the function InsideFace //=========================================================================== // returns the amount the face and the winding overlap // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float Q2_FaceOnWinding(q2_dface_t *face, winding_t *winding) { int i, edgenum, side; float dist, area; q2_dplane_t plane; vec_t *v1, *v2; vec3_t normal, edgevec; winding_t *w; // w = CopyWinding(winding); memcpy(&plane, &q2_dplanes[face->planenum], sizeof(q2_dplane_t)); //check on which side of the plane the face is if (face->side) { VectorNegate(plane.normal, plane.normal); plane.dist = -plane.dist; } //end if for (i = 0; i < face->numedges && w; i++) { //get the first and second vertex of the edge edgenum = q2_dsurfedges[face->firstedge + i]; side = edgenum > 0; //if the face plane is flipped v1 = q2_dvertexes[q2_dedges[abs(edgenum)].v[side]].point; v2 = q2_dvertexes[q2_dedges[abs(edgenum)].v[!side]].point; //create a plane through the edge vector, orthogonal to the face plane //and with the normal vector pointing inward VectorSubtract(v1, v2, edgevec); CrossProduct(edgevec, plane.normal, normal); VectorNormalize(normal); dist = DotProduct(normal, v1); // ChopWindingInPlace(&w, normal, dist, -0.1); //CLIP_EPSILON } //end for if (w) { area = WindingArea(w); FreeWinding(w); return area; } //end if return 0; } //end of the function Q2_FaceOnWinding //=========================================================================== // creates a winding for the given brush side on the given brush // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== winding_t *Q2_BrushSideWinding(dbrush_t *brush, dbrushside_t *baseside) { int i; dplane_t *baseplane, *plane; winding_t *w; dbrushside_t *side; //create a winding for the brush side with the given planenumber baseplane = &dplanes[baseside->planenum]; w = BaseWindingForPlane(baseplane->normal, baseplane->dist); for (i = 0; i < brush->numsides && w; i++) { side = &dbrushsides[brush->firstside + i]; //don't chop with the base plane if (side->planenum == baseside->planenum) continue; //also don't use planes that are almost equal plane = &dplanes[side->planenum]; if (DotProduct(baseplane->normal, plane->normal) > 0.999 && fabs(baseplane->dist - plane->dist) < 0.01) continue; // plane = &dplanes[side->planenum^1]; ChopWindingInPlace(&w, plane->normal, plane->dist, -0.1); //CLIP_EPSILON); } //end for return w; } //end of the function Q2_BrushSideWinding //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Q2_HintSkipBrush(dbrush_t *brush) { int j; dbrushside_t *brushside; for (j = 0; j < brush->numsides; j++) { brushside = &dbrushsides[brush->firstside + j]; if (brushside->texinfo > 0) { if (texinfo[brushside->texinfo].flags & (SURF_SKIP|SURF_HINT)) { return true; } //end if } //end if } //end for return false; } //end of the function Q2_HintSkipBrush //=========================================================================== // fix screwed brush texture references // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WindingIsTiny(winding_t *w); void Q2_FixTextureReferences(void) { int i, j, k, we; dbrushside_t *brushside; dbrush_t *brush; dface_t *face; winding_t *w; memset(brushsidetextured, false, MAX_MAP_BRUSHSIDES); //go over all the brushes for (i = 0; i < numbrushes; i++) { brush = &dbrushes[i]; //hint brushes are not textured if (Q2_HintSkipBrush(brush)) continue; //go over all the sides of the brush for (j = 0; j < brush->numsides; j++) { brushside = &dbrushsides[brush->firstside + j]; // w = Q2_BrushSideWinding(brush, brushside); if (!w) { brushsidetextured[brush->firstside + j] = true; continue; } //end if else { //RemoveEqualPoints(w, 0.2); if (WindingIsTiny(w)) { FreeWinding(w); brushsidetextured[brush->firstside + j] = true; continue; } //end if else { we = WindingError(w); if (we == WE_NOTENOUGHPOINTS || we == WE_SMALLAREA || we == WE_POINTBOGUSRANGE // || we == WE_NONCONVEX ) { FreeWinding(w); brushsidetextured[brush->firstside + j] = true; continue; } //end if } //end else } //end else if (WindingArea(w) < 20) { brushsidetextured[brush->firstside + j] = true; } //end if //find a face for texturing this brush for (k = 0; k < numfaces; k++) { face = &dfaces[k]; //if the face is in the same plane as the brush side if ((face->planenum&~1) != (brushside->planenum&~1)) continue; //if the face is partly or totally on the brush side if (Q2_FaceOnWinding(face, w)) { brushside->texinfo = face->texinfo; brushsidetextured[brush->firstside + j] = true; break; } //end if } //end for FreeWinding(w); } //end for } //end for } //end of the function Q2_FixTextureReferences*/ //#endif //ME /* =============== CompressVis =============== */ int Q2_CompressVis (byte *vis, byte *dest) { int j; int rep; int visrow; byte *dest_p; dest_p = dest; // visrow = (r_numvisleafs + 7)>>3; visrow = (dvis->numclusters + 7)>>3; for (j=0 ; j>3; row = (dvis->numclusters+7)>>3; out = decompressed; do { if (*in) { *out++ = *in++; continue; } c = in[1]; if (!c) Error ("DecompressVis: 0 repeat"); in += 2; while (c) { *out++ = 0; c--; } } while (out - decompressed < row); } //============================================================================= /* ============= SwapBSPFile Byte swaps all data in a bsp file. ============= */ void Q2_SwapBSPFile (qboolean todisk) { int i, j; dmodel_t *d; // models for (i=0 ; ifirstface = LittleLong (d->firstface); d->numfaces = LittleLong (d->numfaces); d->headnode = LittleLong (d->headnode); for (j=0 ; j<3 ; j++) { d->mins[j] = LittleFloat(d->mins[j]); d->maxs[j] = LittleFloat(d->maxs[j]); d->origin[j] = LittleFloat(d->origin[j]); } } // // vertexes // for (i=0 ; inumclusters; else j = LittleLong(dvis->numclusters); dvis->numclusters = LittleLong (dvis->numclusters); for (i=0 ; ibitofs[i][0] = LittleLong (dvis->bitofs[i][0]); dvis->bitofs[i][1] = LittleLong (dvis->bitofs[i][1]); } } //end of the function Q2_SwapBSPFile dheader_t *header; int Q2_CopyLump (int lump, void *dest, int size, int maxsize) { int length, ofs; length = header->lumps[lump].filelen; ofs = header->lumps[lump].fileofs; if (length % size) Error ("LoadBSPFile: odd lump size"); if ((length/size) > maxsize) Error ("Q2_LoadBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize); memcpy (dest, (byte *)header + ofs, length); return length / size; } //end of the function Q2_CopyLump /* ============= LoadBSPFile ============= */ void Q2_LoadBSPFile(char *filename, int offset, int length) { int i; // // load the file header // LoadFile (filename, (void **)&header, offset, length); // swap the header for (i=0 ; i< sizeof(dheader_t)/4 ; i++) ((int *)header)[i] = LittleLong ( ((int *)header)[i]); if (header->ident != IDBSPHEADER) Error ("%s is not a IBSP file", filename); if (header->version != BSPVERSION) Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); nummodels = Q2_CopyLump (LUMP_MODELS, dmodels, sizeof(dmodel_t), MAX_MAP_MODELS); numvertexes = Q2_CopyLump (LUMP_VERTEXES, dvertexes, sizeof(dvertex_t), MAX_MAP_VERTS); numplanes = Q2_CopyLump (LUMP_PLANES, dplanes, sizeof(dplane_t), MAX_MAP_PLANES); numleafs = Q2_CopyLump (LUMP_LEAFS, dleafs, sizeof(dleaf_t), MAX_MAP_LEAFS); numnodes = Q2_CopyLump (LUMP_NODES, dnodes, sizeof(dnode_t), MAX_MAP_NODES); numtexinfo = Q2_CopyLump (LUMP_TEXINFO, texinfo, sizeof(texinfo_t), MAX_MAP_TEXINFO); numfaces = Q2_CopyLump (LUMP_FACES, dfaces, sizeof(dface_t), MAX_MAP_FACES); numleaffaces = Q2_CopyLump (LUMP_LEAFFACES, dleaffaces, sizeof(dleaffaces[0]), MAX_MAP_LEAFFACES); numleafbrushes = Q2_CopyLump (LUMP_LEAFBRUSHES, dleafbrushes, sizeof(dleafbrushes[0]), MAX_MAP_LEAFBRUSHES); numsurfedges = Q2_CopyLump (LUMP_SURFEDGES, dsurfedges, sizeof(dsurfedges[0]), MAX_MAP_SURFEDGES); numedges = Q2_CopyLump (LUMP_EDGES, dedges, sizeof(dedge_t), MAX_MAP_EDGES); numbrushes = Q2_CopyLump (LUMP_BRUSHES, dbrushes, sizeof(dbrush_t), MAX_MAP_BRUSHES); numbrushsides = Q2_CopyLump (LUMP_BRUSHSIDES, dbrushsides, sizeof(dbrushside_t), MAX_MAP_BRUSHSIDES); numareas = Q2_CopyLump (LUMP_AREAS, dareas, sizeof(darea_t), MAX_MAP_AREAS); numareaportals = Q2_CopyLump (LUMP_AREAPORTALS, dareaportals, sizeof(dareaportal_t), MAX_MAP_AREAPORTALS); visdatasize = Q2_CopyLump (LUMP_VISIBILITY, dvisdata, 1, MAX_MAP_VISIBILITY); lightdatasize = Q2_CopyLump (LUMP_LIGHTING, dlightdata, 1, MAX_MAP_LIGHTING); entdatasize = Q2_CopyLump (LUMP_ENTITIES, dentdata, 1, MAX_MAP_ENTSTRING); Q2_CopyLump (LUMP_POP, dpop, 1, MAX_MAP_DPOP); FreeMemory(header); // everything has been copied out // // swap everything // Q2_SwapBSPFile (false); Q2_FixTextureReferences(); } //end of the function Q2_LoadBSPFile /* ============= LoadBSPFileTexinfo Only loads the texinfo lump, so qdata can scan for textures ============= */ void Q2_LoadBSPFileTexinfo (char *filename) { int i; FILE *f; int length, ofs; header = GetMemory(sizeof(dheader_t)); f = fopen (filename, "rb"); fread (header, sizeof(dheader_t), 1, f); // swap the header for (i=0 ; i< sizeof(dheader_t)/4 ; i++) ((int *)header)[i] = LittleLong ( ((int *)header)[i]); if (header->ident != IDBSPHEADER) Error ("%s is not a IBSP file", filename); if (header->version != BSPVERSION) Error ("%s is version %i, not %i", filename, header->version, BSPVERSION); length = header->lumps[LUMP_TEXINFO].filelen; ofs = header->lumps[LUMP_TEXINFO].fileofs; fseek (f, ofs, SEEK_SET); fread (texinfo, length, 1, f); fclose (f); numtexinfo = length / sizeof(texinfo_t); FreeMemory(header); // everything has been copied out Q2_SwapBSPFile (false); } //end of the function Q2_LoadBSPFileTexinfo //============================================================================ FILE *wadfile; dheader_t outheader; void Q2_AddLump (int lumpnum, void *data, int len) { lump_t *lump; lump = &header->lumps[lumpnum]; lump->fileofs = LittleLong( ftell(wadfile) ); lump->filelen = LittleLong(len); SafeWrite (wadfile, data, (len+3)&~3); } //end of the function Q2_AddLump /* ============= WriteBSPFile Swaps the bsp file in place, so it should not be referenced again ============= */ void Q2_WriteBSPFile (char *filename) { header = &outheader; memset (header, 0, sizeof(dheader_t)); Q2_SwapBSPFile (true); header->ident = LittleLong (IDBSPHEADER); header->version = LittleLong (BSPVERSION); wadfile = SafeOpenWrite (filename); SafeWrite (wadfile, header, sizeof(dheader_t)); // overwritten later Q2_AddLump (LUMP_PLANES, dplanes, numplanes*sizeof(dplane_t)); Q2_AddLump (LUMP_LEAFS, dleafs, numleafs*sizeof(dleaf_t)); Q2_AddLump (LUMP_VERTEXES, dvertexes, numvertexes*sizeof(dvertex_t)); Q2_AddLump (LUMP_NODES, dnodes, numnodes*sizeof(dnode_t)); Q2_AddLump (LUMP_TEXINFO, texinfo, numtexinfo*sizeof(texinfo_t)); Q2_AddLump (LUMP_FACES, dfaces, numfaces*sizeof(dface_t)); Q2_AddLump (LUMP_BRUSHES, dbrushes, numbrushes*sizeof(dbrush_t)); Q2_AddLump (LUMP_BRUSHSIDES, dbrushsides, numbrushsides*sizeof(dbrushside_t)); Q2_AddLump (LUMP_LEAFFACES, dleaffaces, numleaffaces*sizeof(dleaffaces[0])); Q2_AddLump (LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes*sizeof(dleafbrushes[0])); Q2_AddLump (LUMP_SURFEDGES, dsurfedges, numsurfedges*sizeof(dsurfedges[0])); Q2_AddLump (LUMP_EDGES, dedges, numedges*sizeof(dedge_t)); Q2_AddLump (LUMP_MODELS, dmodels, nummodels*sizeof(dmodel_t)); Q2_AddLump (LUMP_AREAS, dareas, numareas*sizeof(darea_t)); Q2_AddLump (LUMP_AREAPORTALS, dareaportals, numareaportals*sizeof(dareaportal_t)); Q2_AddLump (LUMP_LIGHTING, dlightdata, lightdatasize); Q2_AddLump (LUMP_VISIBILITY, dvisdata, visdatasize); Q2_AddLump (LUMP_ENTITIES, dentdata, entdatasize); Q2_AddLump (LUMP_POP, dpop, sizeof(dpop)); fseek (wadfile, 0, SEEK_SET); SafeWrite (wadfile, header, sizeof(dheader_t)); fclose (wadfile); } //end of the function Q2_WriteBSPFile //============================================================================ /* ============= PrintBSPFileSizes Dumps info about current file ============= */ void Q2_PrintBSPFileSizes (void) { if (!num_entities) Q2_ParseEntities(); printf ("%6i models %7i\n" ,nummodels, (int)(nummodels*sizeof(dmodel_t))); printf ("%6i brushes %7i\n" ,numbrushes, (int)(numbrushes*sizeof(dbrush_t))); printf ("%6i brushsides %7i\n" ,numbrushsides, (int)(numbrushsides*sizeof(dbrushside_t))); printf ("%6i planes %7i\n" ,numplanes, (int)(numplanes*sizeof(dplane_t))); printf ("%6i texinfo %7i\n" ,numtexinfo, (int)(numtexinfo*sizeof(texinfo_t))); printf ("%6i entdata %7i\n", num_entities, entdatasize); printf ("\n"); printf ("%6i vertexes %7i\n" ,numvertexes, (int)(numvertexes*sizeof(dvertex_t))); printf ("%6i nodes %7i\n" ,numnodes, (int)(numnodes*sizeof(dnode_t))); printf ("%6i faces %7i\n" ,numfaces, (int)(numfaces*sizeof(dface_t))); printf ("%6i leafs %7i\n" ,numleafs, (int)(numleafs*sizeof(dleaf_t))); printf ("%6i leaffaces %7i\n" ,numleaffaces, (int)(numleaffaces*sizeof(dleaffaces[0]))); printf ("%6i leafbrushes %7i\n" ,numleafbrushes, (int)(numleafbrushes*sizeof(dleafbrushes[0]))); printf ("%6i surfedges %7i\n" ,numsurfedges, (int)(numsurfedges*sizeof(dsurfedges[0]))); printf ("%6i edges %7i\n" ,numedges, (int)(numedges*sizeof(dedge_t))); //NEW printf ("%6i areas %7i\n" ,numareas, (int)(numareas*sizeof(darea_t))); printf ("%6i areaportals %7i\n" ,numareaportals, (int)(numareaportals*sizeof(dareaportal_t))); //ENDNEW printf (" lightdata %7i\n", lightdatasize); printf (" visdata %7i\n", visdatasize); } //end of the function Q2_PrintBSPFileSizes /* ================ ParseEntities Parses the dentdata string into entities ================ */ void Q2_ParseEntities (void) { script_t *script; num_entities = 0; script = LoadScriptMemory(dentdata, entdatasize, "*Quake2 bsp file"); SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | SCFL_NOSTRINGESCAPECHARS); while(ParseEntity(script)) { } //end while FreeScript(script); } //end of the function Q2_ParseEntities /* ================ UnparseEntities Generates the dentdata string from all the entities ================ */ void Q2_UnparseEntities (void) { char *buf, *end; epair_t *ep; char line[2048]; int i; char key[1024], value[1024]; buf = dentdata; end = buf; *end = 0; for (i=0 ; inext) { strcpy (key, ep->key); StripTrailing (key); strcpy (value, ep->value); StripTrailing (value); sprintf (line, "\"%s\" \"%s\"\n", key, value); strcat (end, line); end += strlen(line); } strcat (end,"}\n"); end += 2; if (end > buf + MAX_MAP_ENTSTRING) Error ("Entity text too long"); } entdatasize = end - buf + 1; } //end of the function Q2_UnparseEntities ================================================ FILE: code/bspc/l_bsp_q2.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #ifndef ME #define ME #endif //ME extern int nummodels; extern dmodel_t *dmodels;//[MAX_MAP_MODELS]; extern int visdatasize; extern byte *dvisdata;//[MAX_MAP_VISIBILITY]; extern dvis_t *dvis; extern int lightdatasize; extern byte *dlightdata;//[MAX_MAP_LIGHTING]; extern int entdatasize; extern char *dentdata;//[MAX_MAP_ENTSTRING]; extern int numleafs; extern dleaf_t *dleafs;//[MAX_MAP_LEAFS]; extern int numplanes; extern dplane_t *dplanes;//[MAX_MAP_PLANES]; extern int numvertexes; extern dvertex_t *dvertexes;//[MAX_MAP_VERTS]; extern int numnodes; extern dnode_t *dnodes;//[MAX_MAP_NODES]; extern int numtexinfo; extern texinfo_t texinfo[MAX_MAP_TEXINFO]; extern int numfaces; extern dface_t *dfaces;//[MAX_MAP_FACES]; extern int numedges; extern dedge_t *dedges;//[MAX_MAP_EDGES]; extern int numleaffaces; extern unsigned short *dleaffaces;//[MAX_MAP_LEAFFACES]; extern int numleafbrushes; extern unsigned short *dleafbrushes;//[MAX_MAP_LEAFBRUSHES]; extern int numsurfedges; extern int *dsurfedges;//[MAX_MAP_SURFEDGES]; extern int numareas; extern darea_t *dareas;//[MAX_MAP_AREAS]; extern int numareaportals; extern dareaportal_t *dareaportals;//[MAX_MAP_AREAPORTALS]; extern int numbrushes; extern dbrush_t *dbrushes;//[MAX_MAP_BRUSHES]; extern int numbrushsides; extern dbrushside_t *dbrushsides;//[MAX_MAP_BRUSHSIDES]; extern byte dpop[256]; extern char brushsidetextured[MAX_MAP_BRUSHSIDES]; void Q2_AllocMaxBSP(void); void Q2_FreeMaxBSP(void); void Q2_DecompressVis(byte *in, byte *decompressed); int Q2_CompressVis(byte *vis, byte *dest); void Q2_LoadBSPFile(char *filename, int offset, int length); void Q2_LoadBSPFileTexinfo(char *filename); // just for qdata void Q2_WriteBSPFile(char *filename); void Q2_PrintBSPFileSizes(void); void Q2_ParseEntities(void); void Q2_UnparseEntities(void); ================================================ FILE: code/bspc/l_bsp_q3.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "l_cmd.h" #include "l_math.h" #include "l_mem.h" #include "l_log.h" #include "l_poly.h" #include "../botlib/l_script.h" #include "l_qfiles.h" #include "l_bsp_q3.h" #include "l_bsp_ent.h" void Q3_ParseEntities (void); void Q3_PrintBSPFileSizes(void); void GetLeafNums (void); //============================================================================= #define WCONVEX_EPSILON 0.5 int q3_nummodels; q3_dmodel_t *q3_dmodels;//[MAX_MAP_MODELS]; int q3_numShaders; q3_dshader_t *q3_dshaders;//[Q3_MAX_MAP_SHADERS]; int q3_entdatasize; char *q3_dentdata;//[Q3_MAX_MAP_ENTSTRING]; int q3_numleafs; q3_dleaf_t *q3_dleafs;//[Q3_MAX_MAP_LEAFS]; int q3_numplanes; q3_dplane_t *q3_dplanes;//[Q3_MAX_MAP_PLANES]; int q3_numnodes; q3_dnode_t *q3_dnodes;//[Q3_MAX_MAP_NODES]; int q3_numleafsurfaces; int *q3_dleafsurfaces;//[Q3_MAX_MAP_LEAFFACES]; int q3_numleafbrushes; int *q3_dleafbrushes;//[Q3_MAX_MAP_LEAFBRUSHES]; int q3_numbrushes; q3_dbrush_t *q3_dbrushes;//[Q3_MAX_MAP_BRUSHES]; int q3_numbrushsides; q3_dbrushside_t *q3_dbrushsides;//[Q3_MAX_MAP_BRUSHSIDES]; int q3_numLightBytes; byte *q3_lightBytes;//[Q3_MAX_MAP_LIGHTING]; int q3_numGridPoints; byte *q3_gridData;//[Q3_MAX_MAP_LIGHTGRID]; int q3_numVisBytes; byte *q3_visBytes;//[Q3_MAX_MAP_VISIBILITY]; int q3_numDrawVerts; q3_drawVert_t *q3_drawVerts;//[Q3_MAX_MAP_DRAW_VERTS]; int q3_numDrawIndexes; int *q3_drawIndexes;//[Q3_MAX_MAP_DRAW_INDEXES]; int q3_numDrawSurfaces; q3_dsurface_t *q3_drawSurfaces;//[Q3_MAX_MAP_DRAW_SURFS]; int q3_numFogs; q3_dfog_t *q3_dfogs;//[Q3_MAX_MAP_FOGS]; char q3_dbrushsidetextured[Q3_MAX_MAP_BRUSHSIDES]; extern qboolean forcesidesvisible; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q3_FreeMaxBSP(void) { if (q3_dmodels) FreeMemory(q3_dmodels); q3_dmodels = NULL; q3_nummodels = 0; if (q3_dshaders) FreeMemory(q3_dshaders); q3_dshaders = NULL; q3_numShaders = 0; if (q3_dentdata) FreeMemory(q3_dentdata); q3_dentdata = NULL; q3_entdatasize = 0; if (q3_dleafs) FreeMemory(q3_dleafs); q3_dleafs = NULL; q3_numleafs = 0; if (q3_dplanes) FreeMemory(q3_dplanes); q3_dplanes = NULL; q3_numplanes = 0; if (q3_dnodes) FreeMemory(q3_dnodes); q3_dnodes = NULL; q3_numnodes = 0; if (q3_dleafsurfaces) FreeMemory(q3_dleafsurfaces); q3_dleafsurfaces = NULL; q3_numleafsurfaces = 0; if (q3_dleafbrushes) FreeMemory(q3_dleafbrushes); q3_dleafbrushes = NULL; q3_numleafbrushes = 0; if (q3_dbrushes) FreeMemory(q3_dbrushes); q3_dbrushes = NULL; q3_numbrushes = 0; if (q3_dbrushsides) FreeMemory(q3_dbrushsides); q3_dbrushsides = NULL; q3_numbrushsides = 0; if (q3_lightBytes) FreeMemory(q3_lightBytes); q3_lightBytes = NULL; q3_numLightBytes = 0; if (q3_gridData) FreeMemory(q3_gridData); q3_gridData = NULL; q3_numGridPoints = 0; if (q3_visBytes) FreeMemory(q3_visBytes); q3_visBytes = NULL; q3_numVisBytes = 0; if (q3_drawVerts) FreeMemory(q3_drawVerts); q3_drawVerts = NULL; q3_numDrawVerts = 0; if (q3_drawIndexes) FreeMemory(q3_drawIndexes); q3_drawIndexes = NULL; q3_numDrawIndexes = 0; if (q3_drawSurfaces) FreeMemory(q3_drawSurfaces); q3_drawSurfaces = NULL; q3_numDrawSurfaces = 0; if (q3_dfogs) FreeMemory(q3_dfogs); q3_dfogs = NULL; q3_numFogs = 0; } //end of the function Q3_FreeMaxBSP //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q3_PlaneFromPoints(vec3_t p0, vec3_t p1, vec3_t p2, vec3_t normal, float *dist) { vec3_t t1, t2; VectorSubtract(p0, p1, t1); VectorSubtract(p2, p1, t2); CrossProduct(t1, t2, normal); VectorNormalize(normal); *dist = DotProduct(p0, normal); } //end of the function PlaneFromPoints //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q3_SurfacePlane(q3_dsurface_t *surface, vec3_t normal, float *dist) { int i; float *p0, *p1, *p2; vec3_t t1, t2; p0 = q3_drawVerts[surface->firstVert].xyz; for (i = 1; i < surface->numVerts-1; i++) { p1 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; p2 = q3_drawVerts[surface->firstVert + ((i+1) % surface->numVerts)].xyz; VectorSubtract(p0, p1, t1); VectorSubtract(p2, p1, t2); CrossProduct(t1, t2, normal); VectorNormalize(normal); if (VectorLength(normal)) break; } //end for*/ /* float dot; for (i = 0; i < surface->numVerts; i++) { p0 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; p1 = q3_drawVerts[surface->firstVert + ((i+1) % surface->numVerts)].xyz; p2 = q3_drawVerts[surface->firstVert + ((i+2) % surface->numVerts)].xyz; VectorSubtract(p0, p1, t1); VectorSubtract(p2, p1, t2); VectorNormalize(t1); VectorNormalize(t2); dot = DotProduct(t1, t2); if (dot > -0.9 && dot < 0.9 && VectorLength(t1) > 0.1 && VectorLength(t2) > 0.1) break; } //end for CrossProduct(t1, t2, normal); VectorNormalize(normal); */ if (VectorLength(normal) < 0.9) { printf("surface %d bogus normal vector %f %f %f\n", surface - q3_drawSurfaces, normal[0], normal[1], normal[2]); printf("t1 = %f %f %f, t2 = %f %f %f\n", t1[0], t1[1], t1[2], t2[0], t2[1], t2[2]); for (i = 0; i < surface->numVerts; i++) { p1 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; Log_Print("p%d = %f %f %f\n", i, p1[0], p1[1], p1[2]); } //end for } //end if *dist = DotProduct(p0, normal); } //end of the function Q3_SurfacePlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== q3_dplane_t *q3_surfaceplanes; void Q3_CreatePlanarSurfacePlanes(void) { int i; q3_dsurface_t *surface; Log_Print("creating planar surface planes...\n"); q3_surfaceplanes = (q3_dplane_t *) GetClearedMemory(q3_numDrawSurfaces * sizeof(q3_dplane_t)); for (i = 0; i < q3_numDrawSurfaces; i++) { surface = &q3_drawSurfaces[i]; if (surface->surfaceType != MST_PLANAR) continue; Q3_SurfacePlane(surface, q3_surfaceplanes[i].normal, &q3_surfaceplanes[i].dist); //Log_Print("normal = %f %f %f, dist = %f\n", q3_surfaceplanes[i].normal[0], // q3_surfaceplanes[i].normal[1], // q3_surfaceplanes[i].normal[2], q3_surfaceplanes[i].dist); } //end for } //end of the function Q3_CreatePlanarSurfacePlanes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== /* void Q3_SurfacePlane(q3_dsurface_t *surface, vec3_t normal, float *dist) { //take the plane information from the lightmap vector //VectorCopy(surface->lightmapVecs[2], normal); //calculate plane dist with first surface vertex //*dist = DotProduct(q3_drawVerts[surface->firstVert].xyz, normal); Q3_PlaneFromPoints(q3_drawVerts[surface->firstVert].xyz, q3_drawVerts[surface->firstVert+1].xyz, q3_drawVerts[surface->firstVert+2].xyz, normal, dist); } //end of the function Q3_SurfacePlane*/ //=========================================================================== // returns the amount the face and the winding overlap // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float Q3_FaceOnWinding(q3_dsurface_t *surface, winding_t *winding) { int i; float dist, area; q3_dplane_t plane; vec_t *v1, *v2; vec3_t normal, edgevec; winding_t *w; //copy the winding before chopping w = CopyWinding(winding); //retrieve the surface plane Q3_SurfacePlane(surface, plane.normal, &plane.dist); //chop the winding with the surface edge planes for (i = 0; i < surface->numVerts && w; i++) { v1 = q3_drawVerts[surface->firstVert + ((i) % surface->numVerts)].xyz; v2 = q3_drawVerts[surface->firstVert + ((i+1) % surface->numVerts)].xyz; //create a plane through the edge from v1 to v2, orthogonal to the //surface plane and with the normal vector pointing inward VectorSubtract(v2, v1, edgevec); CrossProduct(edgevec, plane.normal, normal); VectorNormalize(normal); dist = DotProduct(normal, v1); // ChopWindingInPlace(&w, normal, dist, -0.1); //CLIP_EPSILON } //end for if (w) { area = WindingArea(w); FreeWinding(w); return area; } //end if return 0; } //end of the function Q3_FaceOnWinding //=========================================================================== // creates a winding for the given brush side on the given brush // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== winding_t *Q3_BrushSideWinding(q3_dbrush_t *brush, q3_dbrushside_t *baseside) { int i; q3_dplane_t *baseplane, *plane; winding_t *w; q3_dbrushside_t *side; //create a winding for the brush side with the given planenumber baseplane = &q3_dplanes[baseside->planeNum]; w = BaseWindingForPlane(baseplane->normal, baseplane->dist); for (i = 0; i < brush->numSides && w; i++) { side = &q3_dbrushsides[brush->firstSide + i]; //don't chop with the base plane if (side->planeNum == baseside->planeNum) continue; //also don't use planes that are almost equal plane = &q3_dplanes[side->planeNum]; if (DotProduct(baseplane->normal, plane->normal) > 0.999 && fabs(baseplane->dist - plane->dist) < 0.01) continue; // plane = &q3_dplanes[side->planeNum^1]; ChopWindingInPlace(&w, plane->normal, plane->dist, -0.1); //CLIP_EPSILON); } //end for return w; } //end of the function Q3_BrushSideWinding //=========================================================================== // fix screwed brush texture references // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WindingIsTiny(winding_t *w); void Q3_FindVisibleBrushSides(void) { int i, j, k, we, numtextured, numsides; float dot; q3_dplane_t *plane; q3_dbrushside_t *brushside; q3_dbrush_t *brush; q3_dsurface_t *surface; winding_t *w; memset(q3_dbrushsidetextured, false, Q3_MAX_MAP_BRUSHSIDES); // numsides = 0; //create planes for the planar surfaces Q3_CreatePlanarSurfacePlanes(); Log_Print("searching visible brush sides...\n"); Log_Print("%6d brush sides", numsides); //go over all the brushes for (i = 0; i < q3_numbrushes; i++) { brush = &q3_dbrushes[i]; //go over all the sides of the brush for (j = 0; j < brush->numSides; j++) { qprintf("\r%6d", numsides++); brushside = &q3_dbrushsides[brush->firstSide + j]; // w = Q3_BrushSideWinding(brush, brushside); if (!w) { q3_dbrushsidetextured[brush->firstSide + j] = true; continue; } //end if else { //RemoveEqualPoints(w, 0.2); if (WindingIsTiny(w)) { FreeWinding(w); q3_dbrushsidetextured[brush->firstSide + j] = true; continue; } //end if else { we = WindingError(w); if (we == WE_NOTENOUGHPOINTS || we == WE_SMALLAREA || we == WE_POINTBOGUSRANGE // || we == WE_NONCONVEX ) { FreeWinding(w); q3_dbrushsidetextured[brush->firstSide + j] = true; continue; } //end if } //end else } //end else if (WindingArea(w) < 20) { q3_dbrushsidetextured[brush->firstSide + j] = true; continue; } //end if //find a face for texturing this brush for (k = 0; k < q3_numDrawSurfaces; k++) { surface = &q3_drawSurfaces[k]; if (surface->surfaceType != MST_PLANAR) continue; // //Q3_SurfacePlane(surface, plane.normal, &plane.dist); plane = &q3_surfaceplanes[k]; //the surface plane and the brush side plane should be pretty much the same if (fabs(fabs(plane->dist) - fabs(q3_dplanes[brushside->planeNum].dist)) > 5) continue; dot = DotProduct(plane->normal, q3_dplanes[brushside->planeNum].normal); if (dot > -0.9 && dot < 0.9) continue; //if the face is partly or totally on the brush side if (Q3_FaceOnWinding(surface, w)) { q3_dbrushsidetextured[brush->firstSide + j] = true; //Log_Write("Q3_FaceOnWinding"); break; } //end if } //end for FreeWinding(w); } //end for } //end for qprintf("\r%6d brush sides\n", numsides); numtextured = 0; for (i = 0; i < q3_numbrushsides; i++) { if (forcesidesvisible) q3_dbrushsidetextured[i] = true; if (q3_dbrushsidetextured[i]) numtextured++; } //end for Log_Print("%d brush sides textured out of %d\n", numtextured, q3_numbrushsides); } //end of the function Q3_FindVisibleBrushSides /* ============= Q3_SwapBlock If all values are 32 bits, this can be used to swap everything ============= */ void Q3_SwapBlock( int *block, int sizeOfBlock ) { int i; sizeOfBlock >>= 2; for ( i = 0 ; i < sizeOfBlock ; i++ ) { block[i] = LittleLong( block[i] ); } } //end of the function Q3_SwapBlock /* ============= Q3_SwapBSPFile Byte swaps all data in a bsp file. ============= */ void Q3_SwapBSPFile( void ) { int i; // models Q3_SwapBlock( (int *)q3_dmodels, q3_nummodels * sizeof( q3_dmodels[0] ) ); // shaders (don't swap the name) for ( i = 0 ; i < q3_numShaders ; i++ ) { q3_dshaders[i].contentFlags = LittleLong( q3_dshaders[i].contentFlags ); q3_dshaders[i].surfaceFlags = LittleLong( q3_dshaders[i].surfaceFlags ); } // planes Q3_SwapBlock( (int *)q3_dplanes, q3_numplanes * sizeof( q3_dplanes[0] ) ); // nodes Q3_SwapBlock( (int *)q3_dnodes, q3_numnodes * sizeof( q3_dnodes[0] ) ); // leafs Q3_SwapBlock( (int *)q3_dleafs, q3_numleafs * sizeof( q3_dleafs[0] ) ); // leaffaces Q3_SwapBlock( (int *)q3_dleafsurfaces, q3_numleafsurfaces * sizeof( q3_dleafsurfaces[0] ) ); // leafbrushes Q3_SwapBlock( (int *)q3_dleafbrushes, q3_numleafbrushes * sizeof( q3_dleafbrushes[0] ) ); // brushes Q3_SwapBlock( (int *)q3_dbrushes, q3_numbrushes * sizeof( q3_dbrushes[0] ) ); // brushsides Q3_SwapBlock( (int *)q3_dbrushsides, q3_numbrushsides * sizeof( q3_dbrushsides[0] ) ); // vis ((int *)&q3_visBytes)[0] = LittleLong( ((int *)&q3_visBytes)[0] ); ((int *)&q3_visBytes)[1] = LittleLong( ((int *)&q3_visBytes)[1] ); // drawverts (don't swap colors ) for ( i = 0 ; i < q3_numDrawVerts ; i++ ) { q3_drawVerts[i].lightmap[0] = LittleFloat( q3_drawVerts[i].lightmap[0] ); q3_drawVerts[i].lightmap[1] = LittleFloat( q3_drawVerts[i].lightmap[1] ); q3_drawVerts[i].st[0] = LittleFloat( q3_drawVerts[i].st[0] ); q3_drawVerts[i].st[1] = LittleFloat( q3_drawVerts[i].st[1] ); q3_drawVerts[i].xyz[0] = LittleFloat( q3_drawVerts[i].xyz[0] ); q3_drawVerts[i].xyz[1] = LittleFloat( q3_drawVerts[i].xyz[1] ); q3_drawVerts[i].xyz[2] = LittleFloat( q3_drawVerts[i].xyz[2] ); q3_drawVerts[i].normal[0] = LittleFloat( q3_drawVerts[i].normal[0] ); q3_drawVerts[i].normal[1] = LittleFloat( q3_drawVerts[i].normal[1] ); q3_drawVerts[i].normal[2] = LittleFloat( q3_drawVerts[i].normal[2] ); } // drawindexes Q3_SwapBlock( (int *)q3_drawIndexes, q3_numDrawIndexes * sizeof( q3_drawIndexes[0] ) ); // drawsurfs Q3_SwapBlock( (int *)q3_drawSurfaces, q3_numDrawSurfaces * sizeof( q3_drawSurfaces[0] ) ); // fogs for ( i = 0 ; i < q3_numFogs ; i++ ) { q3_dfogs[i].brushNum = LittleLong( q3_dfogs[i].brushNum ); } } /* ============= Q3_CopyLump ============= */ int Q3_CopyLump( q3_dheader_t *header, int lump, void **dest, int size ) { int length, ofs; length = header->lumps[lump].filelen; ofs = header->lumps[lump].fileofs; if ( length % size ) { Error ("Q3_LoadBSPFile: odd lump size"); } *dest = GetMemory(length); memcpy( *dest, (byte *)header + ofs, length ); return length / size; } /* ============= CountTriangles ============= */ void CountTriangles( void ) { int i, numTris, numPatchTris; q3_dsurface_t *surface; numTris = numPatchTris = 0; for ( i = 0; i < q3_numDrawSurfaces; i++ ) { surface = &q3_drawSurfaces[i]; numTris += surface->numIndexes / 3; if ( surface->patchWidth ) { numPatchTris += surface->patchWidth * surface->patchHeight * 2; } } Log_Print( "%6d triangles\n", numTris ); Log_Print( "%6d patch tris\n", numPatchTris ); } /* ============= Q3_LoadBSPFile ============= */ void Q3_LoadBSPFile(struct quakefile_s *qf) { q3_dheader_t *header; // load the file header //LoadFile(filename, (void **)&header, offset, length); // LoadQuakeFile(qf, (void **)&header); // swap the header Q3_SwapBlock( (int *)header, sizeof(*header) ); if ( header->ident != Q3_BSP_IDENT ) { Error( "%s is not a IBSP file", qf->filename ); } if ( header->version != Q3_BSP_VERSION ) { Error( "%s is version %i, not %i", qf->filename, header->version, Q3_BSP_VERSION ); } q3_numShaders = Q3_CopyLump( header, Q3_LUMP_SHADERS, (void *) &q3_dshaders, sizeof(q3_dshader_t) ); q3_nummodels = Q3_CopyLump( header, Q3_LUMP_MODELS, (void *) &q3_dmodels, sizeof(q3_dmodel_t) ); q3_numplanes = Q3_CopyLump( header, Q3_LUMP_PLANES, (void *) &q3_dplanes, sizeof(q3_dplane_t) ); q3_numleafs = Q3_CopyLump( header, Q3_LUMP_LEAFS, (void *) &q3_dleafs, sizeof(q3_dleaf_t) ); q3_numnodes = Q3_CopyLump( header, Q3_LUMP_NODES, (void *) &q3_dnodes, sizeof(q3_dnode_t) ); q3_numleafsurfaces = Q3_CopyLump( header, Q3_LUMP_LEAFSURFACES, (void *) &q3_dleafsurfaces, sizeof(q3_dleafsurfaces[0]) ); q3_numleafbrushes = Q3_CopyLump( header, Q3_LUMP_LEAFBRUSHES, (void *) &q3_dleafbrushes, sizeof(q3_dleafbrushes[0]) ); q3_numbrushes = Q3_CopyLump( header, Q3_LUMP_BRUSHES, (void *) &q3_dbrushes, sizeof(q3_dbrush_t) ); q3_numbrushsides = Q3_CopyLump( header, Q3_LUMP_BRUSHSIDES, (void *) &q3_dbrushsides, sizeof(q3_dbrushside_t) ); q3_numDrawVerts = Q3_CopyLump( header, Q3_LUMP_DRAWVERTS, (void *) &q3_drawVerts, sizeof(q3_drawVert_t) ); q3_numDrawSurfaces = Q3_CopyLump( header, Q3_LUMP_SURFACES, (void *) &q3_drawSurfaces, sizeof(q3_dsurface_t) ); q3_numFogs = Q3_CopyLump( header, Q3_LUMP_FOGS, (void *) &q3_dfogs, sizeof(q3_dfog_t) ); q3_numDrawIndexes = Q3_CopyLump( header, Q3_LUMP_DRAWINDEXES, (void *) &q3_drawIndexes, sizeof(q3_drawIndexes[0]) ); q3_numVisBytes = Q3_CopyLump( header, Q3_LUMP_VISIBILITY, (void *) &q3_visBytes, 1 ); q3_numLightBytes = Q3_CopyLump( header, Q3_LUMP_LIGHTMAPS, (void *) &q3_lightBytes, 1 ); q3_entdatasize = Q3_CopyLump( header, Q3_LUMP_ENTITIES, (void *) &q3_dentdata, 1); q3_numGridPoints = Q3_CopyLump( header, Q3_LUMP_LIGHTGRID, (void *) &q3_gridData, 8 ); CountTriangles(); FreeMemory( header ); // everything has been copied out // swap everything Q3_SwapBSPFile(); Q3_FindVisibleBrushSides(); //Q3_PrintBSPFileSizes(); } //============================================================================ /* ============= Q3_AddLump ============= */ void Q3_AddLump( FILE *bspfile, q3_dheader_t *header, int lumpnum, void *data, int len ) { q3_lump_t *lump; lump = &header->lumps[lumpnum]; lump->fileofs = LittleLong( ftell(bspfile) ); lump->filelen = LittleLong( len ); SafeWrite( bspfile, data, (len+3)&~3 ); } /* ============= Q3_WriteBSPFile Swaps the bsp file in place, so it should not be referenced again ============= */ void Q3_WriteBSPFile( char *filename ) { q3_dheader_t outheader, *header; FILE *bspfile; header = &outheader; memset( header, 0, sizeof(q3_dheader_t) ); Q3_SwapBSPFile(); header->ident = LittleLong( Q3_BSP_IDENT ); header->version = LittleLong( Q3_BSP_VERSION ); bspfile = SafeOpenWrite( filename ); SafeWrite( bspfile, header, sizeof(q3_dheader_t) ); // overwritten later Q3_AddLump( bspfile, header, Q3_LUMP_SHADERS, q3_dshaders, q3_numShaders*sizeof(q3_dshader_t) ); Q3_AddLump( bspfile, header, Q3_LUMP_PLANES, q3_dplanes, q3_numplanes*sizeof(q3_dplane_t) ); Q3_AddLump( bspfile, header, Q3_LUMP_LEAFS, q3_dleafs, q3_numleafs*sizeof(q3_dleaf_t) ); Q3_AddLump( bspfile, header, Q3_LUMP_NODES, q3_dnodes, q3_numnodes*sizeof(q3_dnode_t) ); Q3_AddLump( bspfile, header, Q3_LUMP_BRUSHES, q3_dbrushes, q3_numbrushes*sizeof(q3_dbrush_t) ); Q3_AddLump( bspfile, header, Q3_LUMP_BRUSHSIDES, q3_dbrushsides, q3_numbrushsides*sizeof(q3_dbrushside_t) ); Q3_AddLump( bspfile, header, Q3_LUMP_LEAFSURFACES, q3_dleafsurfaces, q3_numleafsurfaces*sizeof(q3_dleafsurfaces[0]) ); Q3_AddLump( bspfile, header, Q3_LUMP_LEAFBRUSHES, q3_dleafbrushes, q3_numleafbrushes*sizeof(q3_dleafbrushes[0]) ); Q3_AddLump( bspfile, header, Q3_LUMP_MODELS, q3_dmodels, q3_nummodels*sizeof(q3_dmodel_t) ); Q3_AddLump( bspfile, header, Q3_LUMP_DRAWVERTS, q3_drawVerts, q3_numDrawVerts*sizeof(q3_drawVert_t) ); Q3_AddLump( bspfile, header, Q3_LUMP_SURFACES, q3_drawSurfaces, q3_numDrawSurfaces*sizeof(q3_dsurface_t) ); Q3_AddLump( bspfile, header, Q3_LUMP_VISIBILITY, q3_visBytes, q3_numVisBytes ); Q3_AddLump( bspfile, header, Q3_LUMP_LIGHTMAPS, q3_lightBytes, q3_numLightBytes ); Q3_AddLump( bspfile, header, Q3_LUMP_LIGHTGRID, q3_gridData, 8 * q3_numGridPoints ); Q3_AddLump( bspfile, header, Q3_LUMP_ENTITIES, q3_dentdata, q3_entdatasize ); Q3_AddLump( bspfile, header, Q3_LUMP_FOGS, q3_dfogs, q3_numFogs * sizeof(q3_dfog_t) ); Q3_AddLump( bspfile, header, Q3_LUMP_DRAWINDEXES, q3_drawIndexes, q3_numDrawIndexes * sizeof(q3_drawIndexes[0]) ); fseek (bspfile, 0, SEEK_SET); SafeWrite (bspfile, header, sizeof(q3_dheader_t)); fclose (bspfile); } //============================================================================ /* ============= Q3_PrintBSPFileSizes Dumps info about current file ============= */ void Q3_PrintBSPFileSizes( void ) { if ( !num_entities ) { Q3_ParseEntities(); } Log_Print ("%6i models %7i\n" ,q3_nummodels, (int)(q3_nummodels*sizeof(q3_dmodel_t))); Log_Print ("%6i shaders %7i\n" ,q3_numShaders, (int)(q3_numShaders*sizeof(q3_dshader_t))); Log_Print ("%6i brushes %7i\n" ,q3_numbrushes, (int)(q3_numbrushes*sizeof(q3_dbrush_t))); Log_Print ("%6i brushsides %7i\n" ,q3_numbrushsides, (int)(q3_numbrushsides*sizeof(q3_dbrushside_t))); Log_Print ("%6i fogs %7i\n" ,q3_numFogs, (int)(q3_numFogs*sizeof(q3_dfog_t))); Log_Print ("%6i planes %7i\n" ,q3_numplanes, (int)(q3_numplanes*sizeof(q3_dplane_t))); Log_Print ("%6i entdata %7i\n", num_entities, q3_entdatasize); Log_Print ("\n"); Log_Print ("%6i nodes %7i\n" ,q3_numnodes, (int)(q3_numnodes*sizeof(q3_dnode_t))); Log_Print ("%6i leafs %7i\n" ,q3_numleafs, (int)(q3_numleafs*sizeof(q3_dleaf_t))); Log_Print ("%6i leafsurfaces %7i\n" ,q3_numleafsurfaces, (int)(q3_numleafsurfaces*sizeof(q3_dleafsurfaces[0]))); Log_Print ("%6i leafbrushes %7i\n" ,q3_numleafbrushes, (int)(q3_numleafbrushes*sizeof(q3_dleafbrushes[0]))); Log_Print ("%6i drawverts %7i\n" ,q3_numDrawVerts, (int)(q3_numDrawVerts*sizeof(q3_drawVerts[0]))); Log_Print ("%6i drawindexes %7i\n" ,q3_numDrawIndexes, (int)(q3_numDrawIndexes*sizeof(q3_drawIndexes[0]))); Log_Print ("%6i drawsurfaces %7i\n" ,q3_numDrawSurfaces, (int)(q3_numDrawSurfaces*sizeof(q3_drawSurfaces[0]))); Log_Print ("%6i lightmaps %7i\n" ,q3_numLightBytes / (LIGHTMAP_WIDTH*LIGHTMAP_HEIGHT*3), q3_numLightBytes ); Log_Print (" visibility %7i\n" , q3_numVisBytes ); } /* ================ Q3_ParseEntities Parses the q3_dentdata string into entities ================ */ void Q3_ParseEntities (void) { script_t *script; num_entities = 0; script = LoadScriptMemory(q3_dentdata, q3_entdatasize, "*Quake3 bsp file"); SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | SCFL_NOSTRINGESCAPECHARS); while(ParseEntity(script)) { } //end while FreeScript(script); } //end of the function Q3_ParseEntities /* ================ Q3_UnparseEntities Generates the q3_dentdata string from all the entities ================ */ void Q3_UnparseEntities (void) { char *buf, *end; epair_t *ep; char line[2048]; int i; buf = q3_dentdata; end = buf; *end = 0; for (i=0 ; inext) { sprintf (line, "\"%s\" \"%s\"\n", ep->key, ep->value); strcat (end, line); end += strlen(line); } strcat (end,"}\n"); end += 2; if (end > buf + Q3_MAX_MAP_ENTSTRING) Error ("Entity text too long"); } q3_entdatasize = end - buf + 1; } //end of the function Q3_UnparseEntities ================================================ FILE: code/bspc/l_bsp_q3.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "q3files.h" //#include "surfaceflags.h" extern int q3_nummodels; extern q3_dmodel_t *q3_dmodels;//[MAX_MAP_MODELS]; extern int q3_numShaders; extern q3_dshader_t *q3_dshaders;//[Q3_MAX_MAP_SHADERS]; extern int q3_entdatasize; extern char *q3_dentdata;//[Q3_MAX_MAP_ENTSTRING]; extern int q3_numleafs; extern q3_dleaf_t *q3_dleafs;//[Q3_MAX_MAP_LEAFS]; extern int q3_numplanes; extern q3_dplane_t *q3_dplanes;//[Q3_MAX_MAP_PLANES]; extern int q3_numnodes; extern q3_dnode_t *q3_dnodes;//[Q3_MAX_MAP_NODES]; extern int q3_numleafsurfaces; extern int *q3_dleafsurfaces;//[Q3_MAX_MAP_LEAFFACES]; extern int q3_numleafbrushes; extern int *q3_dleafbrushes;//[Q3_MAX_MAP_LEAFBRUSHES]; extern int q3_numbrushes; extern q3_dbrush_t *q3_dbrushes;//[Q3_MAX_MAP_BRUSHES]; extern int q3_numbrushsides; extern q3_dbrushside_t *q3_dbrushsides;//[Q3_MAX_MAP_BRUSHSIDES]; extern int q3_numLightBytes; extern byte *q3_lightBytes;//[Q3_MAX_MAP_LIGHTING]; extern int q3_numGridPoints; extern byte *q3_gridData;//[Q3_MAX_MAP_LIGHTGRID]; extern int q3_numVisBytes; extern byte *q3_visBytes;//[Q3_MAX_MAP_VISIBILITY]; extern int q3_numDrawVerts; extern q3_drawVert_t *q3_drawVerts;//[Q3_MAX_MAP_DRAW_VERTS]; extern int q3_numDrawIndexes; extern int *q3_drawIndexes;//[Q3_MAX_MAP_DRAW_INDEXES]; extern int q3_numDrawSurfaces; extern q3_dsurface_t *q3_drawSurfaces;//[Q3_MAX_MAP_DRAW_SURFS]; extern int q3_numFogs; extern q3_dfog_t *q3_dfogs;//[Q3_MAX_MAP_FOGS]; extern char q3_dbrushsidetextured[Q3_MAX_MAP_BRUSHSIDES]; void Q3_LoadBSPFile(struct quakefile_s *qf); void Q3_FreeMaxBSP(void); void Q3_ParseEntities (void); ================================================ FILE: code/bspc/l_bsp_sin.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "l_cmd.h" #include "l_math.h" #include "l_mem.h" #include "l_log.h" #include "l_poly.h" #include "../botlib/l_script.h" #include "l_bsp_ent.h" #include "l_bsp_sin.h" void GetLeafNums (void); //============================================================================= int sin_nummodels; sin_dmodel_t *sin_dmodels;//[SIN_MAX_MAP_MODELS]; int sin_visdatasize; byte *sin_dvisdata;//[SIN_MAX_MAP_VISIBILITY]; sin_dvis_t *sin_dvis;// = (sin_dvis_t *)sin_sin_dvisdata; int sin_lightdatasize; byte *sin_dlightdata;//[SIN_MAX_MAP_LIGHTING]; int sin_entdatasize; char *sin_dentdata;//[SIN_MAX_MAP_ENTSTRING]; int sin_numleafs; sin_dleaf_t *sin_dleafs;//[SIN_MAX_MAP_LEAFS]; int sin_numplanes; sin_dplane_t *sin_dplanes;//[SIN_MAX_MAP_PLANES]; int sin_numvertexes; sin_dvertex_t *sin_dvertexes;//[SIN_MAX_MAP_VERTS]; int sin_numnodes; sin_dnode_t *sin_dnodes;//[SIN_MAX_MAP_NODES]; int sin_numtexinfo; sin_texinfo_t *sin_texinfo;//[SIN_MAX_MAP_sin_texinfo]; int sin_numfaces; sin_dface_t *sin_dfaces;//[SIN_MAX_MAP_FACES]; int sin_numedges; sin_dedge_t *sin_dedges;//[SIN_MAX_MAP_EDGES]; int sin_numleaffaces; unsigned short *sin_dleaffaces;//[SIN_MAX_MAP_LEAFFACES]; int sin_numleafbrushes; unsigned short *sin_dleafbrushes;//[SIN_MAX_MAP_LEAFBRUSHES]; int sin_numsurfedges; int *sin_dsurfedges;//[SIN_MAX_MAP_SURFEDGES]; int sin_numbrushes; sin_dbrush_t *sin_dbrushes;//[SIN_MAX_MAP_BRUSHES]; int sin_numbrushsides; sin_dbrushside_t *sin_dbrushsides;//[SIN_MAX_MAP_BRUSHSIDES]; int sin_numareas; sin_darea_t *sin_dareas;//[SIN_MAX_MAP_AREAS]; int sin_numareaportals; sin_dareaportal_t *sin_dareaportals;//[SIN_MAX_MAP_AREAPORTALS]; int sin_numlightinfo; sin_lightvalue_t *sin_lightinfo;//[SIN_MAX_MAP_LIGHTINFO]; byte sin_dpop[256]; char sin_dbrushsidetextured[SIN_MAX_MAP_BRUSHSIDES]; int sin_bspallocated = false; int sin_allocatedbspmem = 0; void Sin_AllocMaxBSP(void) { //models sin_nummodels = 0; sin_dmodels = (sin_dmodel_t *) GetClearedMemory(SIN_MAX_MAP_MODELS * sizeof(sin_dmodel_t)); sin_allocatedbspmem += SIN_MAX_MAP_MODELS * sizeof(sin_dmodel_t); //vis data sin_visdatasize = 0; sin_dvisdata = (byte *) GetClearedMemory(SIN_MAX_MAP_VISIBILITY * sizeof(byte)); sin_dvis = (sin_dvis_t *) sin_dvisdata; sin_allocatedbspmem += SIN_MAX_MAP_VISIBILITY * sizeof(byte); //light data sin_lightdatasize = 0; sin_dlightdata = (byte *) GetClearedMemory(SIN_MAX_MAP_LIGHTING * sizeof(byte)); sin_allocatedbspmem += SIN_MAX_MAP_LIGHTING * sizeof(byte); //entity data sin_entdatasize = 0; sin_dentdata = (char *) GetClearedMemory(SIN_MAX_MAP_ENTSTRING * sizeof(char)); sin_allocatedbspmem += SIN_MAX_MAP_ENTSTRING * sizeof(char); //leafs sin_numleafs = 0; sin_dleafs = (sin_dleaf_t *) GetClearedMemory(SIN_MAX_MAP_LEAFS * sizeof(sin_dleaf_t)); sin_allocatedbspmem += SIN_MAX_MAP_LEAFS * sizeof(sin_dleaf_t); //planes sin_numplanes = 0; sin_dplanes = (sin_dplane_t *) GetClearedMemory(SIN_MAX_MAP_PLANES * sizeof(sin_dplane_t)); sin_allocatedbspmem += SIN_MAX_MAP_PLANES * sizeof(sin_dplane_t); //vertexes sin_numvertexes = 0; sin_dvertexes = (sin_dvertex_t *) GetClearedMemory(SIN_MAX_MAP_VERTS * sizeof(sin_dvertex_t)); sin_allocatedbspmem += SIN_MAX_MAP_VERTS * sizeof(sin_dvertex_t); //nodes sin_numnodes = 0; sin_dnodes = (sin_dnode_t *) GetClearedMemory(SIN_MAX_MAP_NODES * sizeof(sin_dnode_t)); sin_allocatedbspmem += SIN_MAX_MAP_NODES * sizeof(sin_dnode_t); //texture info sin_numtexinfo = 0; sin_texinfo = (sin_texinfo_t *) GetClearedMemory(SIN_MAX_MAP_TEXINFO * sizeof(sin_texinfo_t)); sin_allocatedbspmem += SIN_MAX_MAP_TEXINFO * sizeof(sin_texinfo_t); //faces sin_numfaces = 0; sin_dfaces = (sin_dface_t *) GetClearedMemory(SIN_MAX_MAP_FACES * sizeof(sin_dface_t)); sin_allocatedbspmem += SIN_MAX_MAP_FACES * sizeof(sin_dface_t); //edges sin_numedges = 0; sin_dedges = (sin_dedge_t *) GetClearedMemory(SIN_MAX_MAP_EDGES * sizeof(sin_dedge_t)); sin_allocatedbspmem += SIN_MAX_MAP_EDGES * sizeof(sin_dedge_t); //leaf faces sin_numleaffaces = 0; sin_dleaffaces = (unsigned short *) GetClearedMemory(SIN_MAX_MAP_LEAFFACES * sizeof(unsigned short)); sin_allocatedbspmem += SIN_MAX_MAP_LEAFFACES * sizeof(unsigned short); //leaf brushes sin_numleafbrushes = 0; sin_dleafbrushes = (unsigned short *) GetClearedMemory(SIN_MAX_MAP_LEAFBRUSHES * sizeof(unsigned short)); sin_allocatedbspmem += SIN_MAX_MAP_LEAFBRUSHES * sizeof(unsigned short); //surface edges sin_numsurfedges = 0; sin_dsurfedges = (int *) GetClearedMemory(SIN_MAX_MAP_SURFEDGES * sizeof(int)); sin_allocatedbspmem += SIN_MAX_MAP_SURFEDGES * sizeof(int); //brushes sin_numbrushes = 0; sin_dbrushes = (sin_dbrush_t *) GetClearedMemory(SIN_MAX_MAP_BRUSHES * sizeof(sin_dbrush_t)); sin_allocatedbspmem += SIN_MAX_MAP_BRUSHES * sizeof(sin_dbrush_t); //brushsides sin_numbrushsides = 0; sin_dbrushsides = (sin_dbrushside_t *) GetClearedMemory(SIN_MAX_MAP_BRUSHSIDES * sizeof(sin_dbrushside_t)); sin_allocatedbspmem += SIN_MAX_MAP_BRUSHSIDES * sizeof(sin_dbrushside_t); //areas sin_numareas = 0; sin_dareas = (sin_darea_t *) GetClearedMemory(SIN_MAX_MAP_AREAS * sizeof(sin_darea_t)); sin_allocatedbspmem += SIN_MAX_MAP_AREAS * sizeof(sin_darea_t); //area portals sin_numareaportals = 0; sin_dareaportals = (sin_dareaportal_t *) GetClearedMemory(SIN_MAX_MAP_AREAPORTALS * sizeof(sin_dareaportal_t)); sin_allocatedbspmem += SIN_MAX_MAP_AREAPORTALS * sizeof(sin_dareaportal_t); //light info sin_numlightinfo = 0; sin_lightinfo = (sin_lightvalue_t *) GetClearedMemory(SIN_MAX_MAP_LIGHTINFO * sizeof(sin_lightvalue_t)); sin_allocatedbspmem += SIN_MAX_MAP_LIGHTINFO * sizeof(sin_lightvalue_t); //print allocated memory Log_Print("allocated "); PrintMemorySize(sin_allocatedbspmem); Log_Print(" of BSP memory\n"); } //end of the function Sin_AllocMaxBSP void Sin_FreeMaxBSP(void) { //models sin_nummodels = 0; FreeMemory(sin_dmodels); sin_dmodels = NULL; //vis data sin_visdatasize = 0; FreeMemory(sin_dvisdata); sin_dvisdata = NULL; sin_dvis = NULL; //light data sin_lightdatasize = 0; FreeMemory(sin_dlightdata); sin_dlightdata = NULL; //entity data sin_entdatasize = 0; FreeMemory(sin_dentdata); sin_dentdata = NULL; //leafs sin_numleafs = 0; FreeMemory(sin_dleafs); sin_dleafs = NULL; //planes sin_numplanes = 0; FreeMemory(sin_dplanes); sin_dplanes = NULL; //vertexes sin_numvertexes = 0; FreeMemory(sin_dvertexes); sin_dvertexes = NULL; //nodes sin_numnodes = 0; FreeMemory(sin_dnodes); sin_dnodes = NULL; //texture info sin_numtexinfo = 0; FreeMemory(sin_texinfo); sin_texinfo = NULL; //faces sin_numfaces = 0; FreeMemory(sin_dfaces); sin_dfaces = NULL; //edges sin_numedges = 0; FreeMemory(sin_dedges); sin_dedges = NULL; //leaf faces sin_numleaffaces = 0; FreeMemory(sin_dleaffaces); sin_dleaffaces = NULL; //leaf brushes sin_numleafbrushes = 0; FreeMemory(sin_dleafbrushes); sin_dleafbrushes = NULL; //surface edges sin_numsurfedges = 0; FreeMemory(sin_dsurfedges); sin_dsurfedges = NULL; //brushes sin_numbrushes = 0; FreeMemory(sin_dbrushes); sin_dbrushes = NULL; //brushsides sin_numbrushsides = 0; FreeMemory(sin_dbrushsides); sin_dbrushsides = NULL; //areas sin_numareas = 0; FreeMemory(sin_dareas); sin_dareas = NULL; //area portals sin_numareaportals = 0; FreeMemory(sin_dareaportals); sin_dareaportals = NULL; //light info sin_numlightinfo = 0; FreeMemory(sin_lightinfo); sin_lightinfo = NULL; // Log_Print("freed "); PrintMemorySize(sin_allocatedbspmem); Log_Print(" of BSP memory\n"); sin_allocatedbspmem = 0; } //end of the function Sin_FreeMaxBSP #define WCONVEX_EPSILON 0.5 //=========================================================================== // returns the amount the face and the winding overlap // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float Sin_FaceOnWinding(sin_dface_t *face, winding_t *winding) { int i, edgenum, side; float dist, area; sin_dplane_t plane; vec_t *v1, *v2; vec3_t normal, edgevec; winding_t *w; // w = CopyWinding(winding); memcpy(&plane, &sin_dplanes[face->planenum], sizeof(sin_dplane_t)); //check on which side of the plane the face is if (face->side) { VectorNegate(plane.normal, plane.normal); plane.dist = -plane.dist; } //end if for (i = 0; i < face->numedges && w; i++) { //get the first and second vertex of the edge edgenum = sin_dsurfedges[face->firstedge + i]; side = edgenum > 0; //if the face plane is flipped v1 = sin_dvertexes[sin_dedges[abs(edgenum)].v[side]].point; v2 = sin_dvertexes[sin_dedges[abs(edgenum)].v[!side]].point; //create a plane through the edge vector, orthogonal to the face plane //and with the normal vector pointing out of the face VectorSubtract(v1, v2, edgevec); CrossProduct(edgevec, plane.normal, normal); VectorNormalize(normal); dist = DotProduct(normal, v1); // ChopWindingInPlace(&w, normal, dist, 0.9); //CLIP_EPSILON } //end for if (w) { area = WindingArea(w); FreeWinding(w); return area; } //end if return 0; } //end of the function Sin_FaceOnWinding //=========================================================================== // creates a winding for the given brush side on the given brush // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== winding_t *Sin_BrushSideWinding(sin_dbrush_t *brush, sin_dbrushside_t *baseside) { int i; sin_dplane_t *baseplane, *plane; sin_dbrushside_t *side; winding_t *w; //create a winding for the brush side with the given planenumber baseplane = &sin_dplanes[baseside->planenum]; w = BaseWindingForPlane(baseplane->normal, baseplane->dist); for (i = 0; i < brush->numsides && w; i++) { side = &sin_dbrushsides[brush->firstside + i]; //don't chop with the base plane if (side->planenum == baseside->planenum) continue; //also don't use planes that are almost equal plane = &sin_dplanes[side->planenum]; if (DotProduct(baseplane->normal, plane->normal) > 0.999 && fabs(baseplane->dist - plane->dist) < 0.01) continue; // plane = &sin_dplanes[side->planenum^1]; ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); } //end for return w; } //end of the function Sin_BrushSideWinding //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Sin_HintSkipBrush(sin_dbrush_t *brush) { int j; sin_dbrushside_t *brushside; for (j = 0; j < brush->numsides; j++) { brushside = &sin_dbrushsides[brush->firstside + j]; if (brushside->texinfo > 0) { if (sin_texinfo[brushside->texinfo].flags & (SURF_SKIP|SURF_HINT)) { return true; } //end if } //end if } //end for return false; } //end of the function Sin_HintSkipBrush //=========================================================================== // fix screwed brush texture references // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WindingIsTiny(winding_t *w); void Sin_FixTextureReferences(void) { int i, j, k, we; sin_dbrushside_t *brushside; sin_dbrush_t *brush; sin_dface_t *face; winding_t *w; memset(sin_dbrushsidetextured, false, SIN_MAX_MAP_BRUSHSIDES); //go over all the brushes for (i = 0; i < sin_numbrushes; i++) { brush = &sin_dbrushes[i]; //hint brushes are not textured if (Sin_HintSkipBrush(brush)) continue; //go over all the sides of the brush for (j = 0; j < brush->numsides; j++) { brushside = &sin_dbrushsides[brush->firstside + j]; // w = Sin_BrushSideWinding(brush, brushside); if (!w) { sin_dbrushsidetextured[brush->firstside + j] = true; continue; } //end if else { //RemoveEqualPoints(w, 0.2); if (WindingIsTiny(w)) { FreeWinding(w); sin_dbrushsidetextured[brush->firstside + j] = true; continue; } //end if else { we = WindingError(w); if (we == WE_NOTENOUGHPOINTS || we == WE_SMALLAREA || we == WE_POINTBOGUSRANGE // || we == WE_NONCONVEX ) { FreeWinding(w); sin_dbrushsidetextured[brush->firstside + j] = true; continue; } //end if } //end else } //end else if (WindingArea(w) < 20) { sin_dbrushsidetextured[brush->firstside + j] = true; } //end if //find a face for texturing this brush for (k = 0; k < sin_numfaces; k++) { face = &sin_dfaces[k]; //if the face is in the same plane as the brush side if ((face->planenum&~1) != (brushside->planenum&~1)) continue; //if the face is partly or totally on the brush side if (Sin_FaceOnWinding(face, w)) { brushside->texinfo = face->texinfo; sin_dbrushsidetextured[brush->firstside + j] = true; break; } //end if } //end for FreeWinding(w); } //end for } //end for } //end of the function Sin_FixTextureReferences*/ /* =============== CompressVis =============== */ int Sin_CompressVis (byte *vis, byte *dest) { int j; int rep; int visrow; byte *dest_p; dest_p = dest; // visrow = (r_numvisleafs + 7)>>3; visrow = (sin_dvis->numclusters + 7)>>3; for (j=0 ; j>3; row = (sin_dvis->numclusters+7)>>3; out = decompressed; do { if (*in) { *out++ = *in++; continue; } c = in[1]; if (!c) Error ("DecompressVis: 0 repeat"); in += 2; while (c) { *out++ = 0; c--; } } while (out - decompressed < row); } //end of the function Sin_DecompressVis //============================================================================= /* ============= Sin_SwapBSPFile Byte swaps all data in a bsp file. ============= */ void Sin_SwapBSPFile (qboolean todisk) { int i, j; sin_dmodel_t *d; // models for (i=0 ; ifirstface = LittleLong (d->firstface); d->numfaces = LittleLong (d->numfaces); d->headnode = LittleLong (d->headnode); for (j=0 ; j<3 ; j++) { d->mins[j] = LittleFloat(d->mins[j]); d->maxs[j] = LittleFloat(d->maxs[j]); d->origin[j] = LittleFloat(d->origin[j]); } } // // vertexes // for (i=0 ; inumclusters; else j = LittleLong(sin_dvis->numclusters); sin_dvis->numclusters = LittleLong (sin_dvis->numclusters); for (i=0 ; ibitofs[i][0] = LittleLong (sin_dvis->bitofs[i][0]); sin_dvis->bitofs[i][1] = LittleLong (sin_dvis->bitofs[i][1]); } } //end of the function Sin_SwapBSPFile sin_dheader_t *header; #ifdef SIN int Sin_CopyLump (int lump, void *dest, int size, int maxsize) { int length, ofs; length = header->lumps[lump].filelen; ofs = header->lumps[lump].fileofs; if (length % size) Error ("Sin_LoadBSPFile: odd lump size"); if ((length/size) > maxsize) Error ("Sin_LoadBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lump, (length/size), maxsize ); memcpy (dest, (byte *)header + ofs, length); return length / size; } #else int Sin_CopyLump (int lump, void *dest, int size) { int length, ofs; length = header->lumps[lump].filelen; ofs = header->lumps[lump].fileofs; if (length % size) Error ("Sin_LoadBSPFile: odd lump size"); memcpy (dest, (byte *)header + ofs, length); return length / size; } #endif /* ============= Sin_LoadBSPFile ============= */ void Sin_LoadBSPFile(char *filename, int offset, int length) { int i; // // load the file header // LoadFile (filename, (void **)&header, offset, length); // swap the header for (i=0 ; i< sizeof(sin_dheader_t)/4 ; i++) ((int *)header)[i] = LittleLong ( ((int *)header)[i]); if (header->ident != SIN_BSPHEADER && header->ident != SINGAME_BSPHEADER) Error ("%s is not a IBSP file", filename); if (header->version != SIN_BSPVERSION && header->version != SINGAME_BSPVERSION) Error ("%s is version %i, not %i", filename, header->version, SIN_BSPVERSION); #ifdef SIN sin_nummodels = Sin_CopyLump (SIN_LUMP_MODELS, sin_dmodels, sizeof(sin_dmodel_t), SIN_MAX_MAP_MODELS); sin_numvertexes = Sin_CopyLump (SIN_LUMP_VERTEXES, sin_dvertexes, sizeof(sin_dvertex_t), SIN_MAX_MAP_VERTS); sin_numplanes = Sin_CopyLump (SIN_LUMP_PLANES, sin_dplanes, sizeof(sin_dplane_t), SIN_MAX_MAP_PLANES); sin_numleafs = Sin_CopyLump (SIN_LUMP_LEAFS, sin_dleafs, sizeof(sin_dleaf_t), SIN_MAX_MAP_LEAFS); sin_numnodes = Sin_CopyLump (SIN_LUMP_NODES, sin_dnodes, sizeof(sin_dnode_t), SIN_MAX_MAP_NODES); sin_numtexinfo = Sin_CopyLump (SIN_LUMP_TEXINFO, sin_texinfo, sizeof(sin_texinfo_t), SIN_MAX_MAP_TEXINFO); sin_numfaces = Sin_CopyLump (SIN_LUMP_FACES, sin_dfaces, sizeof(sin_dface_t), SIN_MAX_MAP_FACES); sin_numleaffaces = Sin_CopyLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sizeof(sin_dleaffaces[0]), SIN_MAX_MAP_LEAFFACES); sin_numleafbrushes = Sin_CopyLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sizeof(sin_dleafbrushes[0]), SIN_MAX_MAP_LEAFBRUSHES); sin_numsurfedges = Sin_CopyLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sizeof(sin_dsurfedges[0]), SIN_MAX_MAP_SURFEDGES); sin_numedges = Sin_CopyLump (SIN_LUMP_EDGES, sin_dedges, sizeof(sin_dedge_t), SIN_MAX_MAP_EDGES); sin_numbrushes = Sin_CopyLump (SIN_LUMP_BRUSHES, sin_dbrushes, sizeof(sin_dbrush_t), SIN_MAX_MAP_BRUSHES); sin_numbrushsides = Sin_CopyLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sizeof(sin_dbrushside_t), SIN_MAX_MAP_BRUSHSIDES); sin_numareas = Sin_CopyLump (SIN_LUMP_AREAS, sin_dareas, sizeof(sin_darea_t), SIN_MAX_MAP_AREAS); sin_numareaportals = Sin_CopyLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sizeof(sin_dareaportal_t), SIN_MAX_MAP_AREAPORTALS); sin_numlightinfo = Sin_CopyLump (SIN_LUMP_LIGHTINFO, sin_lightinfo, sizeof(sin_lightvalue_t), SIN_MAX_MAP_LIGHTINFO); sin_visdatasize = Sin_CopyLump (SIN_LUMP_VISIBILITY, sin_dvisdata, 1, SIN_MAX_MAP_VISIBILITY); sin_lightdatasize = Sin_CopyLump (SIN_LUMP_LIGHTING, sin_dlightdata, 1, SIN_MAX_MAP_LIGHTING); sin_entdatasize = Sin_CopyLump (SIN_LUMP_ENTITIES, sin_dentdata, 1, SIN_MAX_MAP_ENTSTRING); Sin_CopyLump (SIN_LUMP_POP, sin_dpop, 1, sizeof(sin_dpop)); #else sin_nummodels = Sin_CopyLump (SIN_LUMP_MODELS, sin_dmodels, sizeof(sin_dmodel_t)); sin_numvertexes = Sin_CopyLump (SIN_LUMP_VERTEXES, sin_dvertexes, sizeof(sin_dvertex_t)); sin_numplanes = Sin_CopyLump (SIN_LUMP_PLANES, sin_dplanes, sizeof(sin_dplane_t)); sin_numleafs = Sin_CopyLump (SIN_LUMP_LEAFS, sin_dleafs, sizeof(sin_dleaf_t)); sin_numnodes = Sin_CopyLump (SIN_LUMP_NODES, sin_dnodes, sizeof(sin_dnode_t)); sin_numtexinfo = Sin_CopyLump (SIN_LUMP_TEXINFO, sin_texinfo, sizeof(sin_texinfo_t)); sin_numfaces = Sin_CopyLump (SIN_LUMP_FACES, sin_dfaces, sizeof(sin_dface_t)); sin_numleaffaces = Sin_CopyLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sizeof(sin_dleaffaces[0])); sin_numleafbrushes = Sin_CopyLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sizeof(sin_dleafbrushes[0])); sin_numsurfedges = Sin_CopyLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sizeof(sin_dsurfedges[0])); sin_numedges = Sin_CopyLump (SIN_LUMP_EDGES, sin_dedges, sizeof(sin_dedge_t)); sin_numbrushes = Sin_CopyLump (SIN_LUMP_BRUSHES, sin_dbrushes, sizeof(sin_dbrush_t)); sin_numbrushsides = Sin_CopyLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sizeof(sin_dbrushside_t)); sin_numareas = Sin_CopyLump (SIN_LUMP_AREAS, sin_dareas, sizeof(sin_darea_t)); sin_numareaportals = Sin_CopyLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sizeof(sin_dareaportal_t)); sin_visdatasize = Sin_CopyLump (SIN_LUMP_VISIBILITY, sin_dvisdata, 1); sin_lightdatasize = Sin_CopyLump (SIN_LUMP_LIGHTING, sin_dlightdata, 1); sin_entdatasize = Sin_CopyLump (SIN_LUMP_ENTITIES, sin_dentdata, 1); Sin_CopyLump (SIN_LUMP_POP, sin_dpop, 1); #endif FreeMemory(header); // everything has been copied out // // swap everything // Sin_SwapBSPFile (false); } //end of the function Sin_LoadBSPFile /* ============= Sin_LoadBSPFilesTexinfo Only loads the sin_texinfo lump, so qdata can scan for textures ============= */ void Sin_LoadBSPFileTexinfo (char *filename) { int i; FILE *f; int length, ofs; header = GetMemory(sizeof(sin_dheader_t)); f = fopen (filename, "rb"); fread (header, sizeof(sin_dheader_t), 1, f); // swap the header for (i=0 ; i< sizeof(sin_dheader_t)/4 ; i++) ((int *)header)[i] = LittleLong ( ((int *)header)[i]); if (header->ident != SIN_BSPHEADER && header->ident != SINGAME_BSPHEADER) Error ("%s is not a IBSP file", filename); if (header->version != SIN_BSPVERSION && header->version != SINGAME_BSPVERSION) Error ("%s is version %i, not %i", filename, header->version, SIN_BSPVERSION); length = header->lumps[SIN_LUMP_TEXINFO].filelen; ofs = header->lumps[SIN_LUMP_TEXINFO].fileofs; fseek (f, ofs, SEEK_SET); fread (sin_texinfo, length, 1, f); fclose (f); sin_numtexinfo = length / sizeof(sin_texinfo_t); FreeMemory(header); // everything has been copied out Sin_SwapBSPFile (false); } //end of the function Sin_LoadBSPFilesTexinfo //============================================================================ FILE *wadfile; sin_dheader_t outheader; #ifdef SIN void Sin_AddLump (int lumpnum, void *data, int len, int size, int maxsize) { sin_lump_t *lump; int totallength; totallength = len*size; if (len > maxsize) Error ("Sin_WriteBSPFile: exceeded max size for lump %d size %d > maxsize %d\n", lumpnum, len, maxsize ); lump = &header->lumps[lumpnum]; lump->fileofs = LittleLong( ftell(wadfile) ); lump->filelen = LittleLong(totallength); SafeWrite (wadfile, data, (totallength+3)&~3); } #else void Sin_AddLump (int lumpnum, void *data, int len) { sin_lump_t *lump; lump = &header->lumps[lumpnum]; lump->fileofs = LittleLong( ftell(wadfile) ); lump->filelen = LittleLong(len); SafeWrite (wadfile, data, (len+3)&~3); } #endif /* ============= Sin_WriteBSPFile Swaps the bsp file in place, so it should not be referenced again ============= */ void Sin_WriteBSPFile (char *filename) { header = &outheader; memset (header, 0, sizeof(sin_dheader_t)); Sin_SwapBSPFile (true); header->ident = LittleLong (SIN_BSPHEADER); header->version = LittleLong (SIN_BSPVERSION); wadfile = SafeOpenWrite (filename); SafeWrite (wadfile, header, sizeof(sin_dheader_t)); // overwritten later #ifdef SIN Sin_AddLump (SIN_LUMP_PLANES, sin_dplanes, sin_numplanes, sizeof(sin_dplane_t), SIN_MAX_MAP_PLANES); Sin_AddLump (SIN_LUMP_LEAFS, sin_dleafs, sin_numleafs, sizeof(sin_dleaf_t), SIN_MAX_MAP_LEAFS); Sin_AddLump (SIN_LUMP_VERTEXES, sin_dvertexes, sin_numvertexes, sizeof(sin_dvertex_t), SIN_MAX_MAP_VERTS); Sin_AddLump (SIN_LUMP_NODES, sin_dnodes, sin_numnodes, sizeof(sin_dnode_t), SIN_MAX_MAP_NODES); Sin_AddLump (SIN_LUMP_TEXINFO, sin_texinfo, sin_numtexinfo, sizeof(sin_texinfo_t), SIN_MAX_MAP_TEXINFO); Sin_AddLump (SIN_LUMP_FACES, sin_dfaces, sin_numfaces, sizeof(sin_dface_t), SIN_MAX_MAP_FACES); Sin_AddLump (SIN_LUMP_BRUSHES, sin_dbrushes, sin_numbrushes, sizeof(sin_dbrush_t), SIN_MAX_MAP_BRUSHES); Sin_AddLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sin_numbrushsides, sizeof(sin_dbrushside_t), SIN_MAX_MAP_BRUSHSIDES); Sin_AddLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sin_numleaffaces, sizeof(sin_dleaffaces[0]), SIN_MAX_MAP_LEAFFACES); Sin_AddLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sin_numleafbrushes, sizeof(sin_dleafbrushes[0]), SIN_MAX_MAP_LEAFBRUSHES); Sin_AddLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sin_numsurfedges, sizeof(sin_dsurfedges[0]), SIN_MAX_MAP_SURFEDGES); Sin_AddLump (SIN_LUMP_EDGES, sin_dedges, sin_numedges, sizeof(sin_dedge_t), SIN_MAX_MAP_EDGES); Sin_AddLump (SIN_LUMP_MODELS, sin_dmodels, sin_nummodels, sizeof(sin_dmodel_t), SIN_MAX_MAP_MODELS); Sin_AddLump (SIN_LUMP_AREAS, sin_dareas, sin_numareas, sizeof(sin_darea_t), SIN_MAX_MAP_AREAS); Sin_AddLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sin_numareaportals, sizeof(sin_dareaportal_t), SIN_MAX_MAP_AREAPORTALS); Sin_AddLump (SIN_LUMP_LIGHTINFO, sin_lightinfo, sin_numlightinfo, sizeof(sin_lightvalue_t), SIN_MAX_MAP_LIGHTINFO); Sin_AddLump (SIN_LUMP_LIGHTING, sin_dlightdata, sin_lightdatasize, 1, SIN_MAX_MAP_LIGHTING); Sin_AddLump (SIN_LUMP_VISIBILITY, sin_dvisdata, sin_visdatasize, 1, SIN_MAX_MAP_VISIBILITY); Sin_AddLump (SIN_LUMP_ENTITIES, sin_dentdata, sin_entdatasize, 1, SIN_MAX_MAP_ENTSTRING); Sin_AddLump (SIN_LUMP_POP, sin_dpop, sizeof(sin_dpop), 1, sizeof(sin_dpop)); #else Sin_AddLump (SIN_LUMP_PLANES, sin_dplanes, sin_numplanes*sizeof(sin_dplane_t)); Sin_AddLump (SIN_LUMP_LEAFS, sin_dleafs, sin_numleafs*sizeof(sin_dleaf_t)); Sin_AddLump (SIN_LUMP_VERTEXES, sin_dvertexes, sin_numvertexes*sizeof(sin_dvertex_t)); Sin_AddLump (SIN_LUMP_NODES, sin_dnodes, sin_numnodes*sizeof(sin_dnode_t)); Sin_AddLump (SIN_LUMP_TEXINFO, sin_texinfo, sin_numtexinfo*sizeof(sin_texinfo_t)); Sin_AddLump (SIN_LUMP_FACES, sin_dfaces, sin_numfaces*sizeof(sin_dface_t)); Sin_AddLump (SIN_LUMP_BRUSHES, sin_dbrushes, sin_numbrushes*sizeof(sin_dbrush_t)); Sin_AddLump (SIN_LUMP_BRUSHSIDES, sin_dbrushsides, sin_numbrushsides*sizeof(sin_dbrushside_t)); Sin_AddLump (SIN_LUMP_LEAFFACES, sin_dleaffaces, sin_numleaffaces*sizeof(sin_dleaffaces[0])); Sin_AddLump (SIN_LUMP_LEAFBRUSHES, sin_dleafbrushes, sin_numleafbrushes*sizeof(sin_dleafbrushes[0])); Sin_AddLump (SIN_LUMP_SURFEDGES, sin_dsurfedges, sin_numsurfedges*sizeof(sin_dsurfedges[0])); Sin_AddLump (SIN_LUMP_EDGES, sin_dedges, sin_numedges*sizeof(sin_dedge_t)); Sin_AddLump (SIN_LUMP_MODELS, sin_dmodels, sin_nummodels*sizeof(sin_dmodel_t)); Sin_AddLump (SIN_LUMP_AREAS, sin_dareas, sin_numareas*sizeof(sin_darea_t)); Sin_AddLump (SIN_LUMP_AREAPORTALS, sin_dareaportals, sin_numareaportals*sizeof(sin_dareaportal_t)); Sin_AddLump (SIN_LUMP_LIGHTING, sin_dlightdata, sin_lightdatasize); Sin_AddLump (SIN_LUMP_VISIBILITY, sin_dvisdata, sin_visdatasize); Sin_AddLump (SIN_LUMP_ENTITIES, sin_dentdata, sin_entdatasize); Sin_AddLump (SIN_LUMP_POP, sin_dpop, sizeof(sin_dpop)); #endif fseek (wadfile, 0, SEEK_SET); SafeWrite (wadfile, header, sizeof(sin_dheader_t)); fclose (wadfile); } //============================================================================ //============================================ /* ================ ParseEntities Parses the sin_dentdata string into entities ================ */ void Sin_ParseEntities (void) { script_t *script; num_entities = 0; script = LoadScriptMemory(sin_dentdata, sin_entdatasize, "*sin bsp file"); SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | SCFL_NOSTRINGESCAPECHARS); while(ParseEntity(script)) { } //end while FreeScript(script); } //end of the function Sin_ParseEntities /* ================ UnparseEntities Generates the sin_dentdata string from all the entities ================ */ void Sin_UnparseEntities (void) { char *buf, *end; epair_t *ep; char line[2048]; int i; char key[1024], value[1024]; buf = sin_dentdata; end = buf; *end = 0; for (i=0 ; inext) { strcpy (key, ep->key); StripTrailing (key); strcpy (value, ep->value); StripTrailing (value); sprintf (line, "\"%s\" \"%s\"\n", key, value); strcat (end, line); end += strlen(line); } strcat (end,"}\n"); end += 2; if (end > buf + SIN_MAX_MAP_ENTSTRING) Error ("Entity text too long"); } sin_entdatasize = end - buf + 1; } //end of the function Sin_UnparseEntities #ifdef SIN void FreeValueKeys(entity_t *ent) { epair_t *ep,*next; for (ep=ent->epairs ; ep ; ep=next) { next = ep->next; FreeMemory(ep->value); FreeMemory(ep->key); FreeMemory(ep); } ent->epairs = NULL; } #endif /* ============= Sin_PrintBSPFileSizes Dumps info about current file ============= */ void Sin_PrintBSPFileSizes (void) { if (!num_entities) Sin_ParseEntities (); Log_Print("%6i models %7i\n" ,sin_nummodels, (int)(sin_nummodels*sizeof(sin_dmodel_t))); Log_Print("%6i brushes %7i\n" ,sin_numbrushes, (int)(sin_numbrushes*sizeof(sin_dbrush_t))); Log_Print("%6i brushsides %7i\n" ,sin_numbrushsides, (int)(sin_numbrushsides*sizeof(sin_dbrushside_t))); Log_Print("%6i planes %7i\n" ,sin_numplanes, (int)(sin_numplanes*sizeof(sin_dplane_t))); Log_Print("%6i texinfo %7i\n" ,sin_numtexinfo, (int)(sin_numtexinfo*sizeof(sin_texinfo_t))); #ifdef SIN Log_Print("%6i lightinfo %7i\n" ,sin_numlightinfo, (int)(sin_numlightinfo*sizeof(sin_lightvalue_t))); #endif Log_Print("%6i entdata %7i\n", num_entities, sin_entdatasize); Log_Print("\n"); Log_Print("%6i vertexes %7i\n" ,sin_numvertexes, (int)(sin_numvertexes*sizeof(sin_dvertex_t))); Log_Print("%6i nodes %7i\n" ,sin_numnodes, (int)(sin_numnodes*sizeof(sin_dnode_t))); Log_Print("%6i faces %7i\n" ,sin_numfaces, (int)(sin_numfaces*sizeof(sin_dface_t))); Log_Print("%6i leafs %7i\n" ,sin_numleafs, (int)(sin_numleafs*sizeof(sin_dleaf_t))); Log_Print("%6i leaffaces %7i\n" ,sin_numleaffaces, (int)(sin_numleaffaces*sizeof(sin_dleaffaces[0]))); Log_Print("%6i leafbrushes %7i\n" ,sin_numleafbrushes, (int)(sin_numleafbrushes*sizeof(sin_dleafbrushes[0]))); Log_Print("%6i surfedges %7i\n" ,sin_numsurfedges, (int)(sin_numsurfedges*sizeof(sin_dsurfedges[0]))); Log_Print("%6i edges %7i\n" ,sin_numedges, (int)(sin_numedges*sizeof(sin_dedge_t))); Log_Print(" lightdata %7i\n", sin_lightdatasize); Log_Print(" visdata %7i\n", sin_visdatasize); } ================================================ FILE: code/bspc/l_bsp_sin.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "sinfiles.h" #define SINGAME_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'R') //RBSP #define SINGAME_BSPVERSION 1 #define SIN_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') //IBSP #define SIN_BSPVERSION 41 extern int sin_nummodels; extern sin_dmodel_t *sin_dmodels;//[MAX_MAP_MODELS]; extern int sin_visdatasize; extern byte *sin_dvisdata;//[MAX_MAP_VISIBILITY]; extern sin_dvis_t *sin_dvis;// = (dvis_t *)sin_sin_dvisdata; extern int sin_lightdatasize; extern byte *sin_dlightdata;//[MAX_MAP_LIGHTING]; extern int sin_entdatasize; extern char *sin_dentdata;//[MAX_MAP_ENTSTRING]; extern int sin_numleafs; extern sin_dleaf_t *sin_dleafs;//[MAX_MAP_LEAFS]; extern int sin_numplanes; extern sin_dplane_t *sin_dplanes;//[MAX_MAP_PLANES]; extern int sin_numvertexes; extern sin_dvertex_t *sin_dvertexes;//[MAX_MAP_VERTS]; extern int sin_numnodes; extern sin_dnode_t *sin_dnodes;//[MAX_MAP_NODES]; extern int sin_numtexinfo; extern sin_texinfo_t *sin_texinfo;//[MAX_MAP_sin_texinfo]; extern int sin_numfaces; extern sin_dface_t *sin_dfaces;//[MAX_MAP_FACES]; extern int sin_numedges; extern sin_dedge_t *sin_dedges;//[MAX_MAP_EDGES]; extern int sin_numleaffaces; extern unsigned short *sin_dleaffaces;//[MAX_MAP_LEAFFACES]; extern int sin_numleafbrushes; extern unsigned short *sin_dleafbrushes;//[MAX_MAP_LEAFBRUSHES]; extern int sin_numsurfedges; extern int *sin_dsurfedges;//[MAX_MAP_SURFEDGES]; extern int sin_numbrushes; extern sin_dbrush_t *sin_dbrushes;//[MAX_MAP_BRUSHES]; extern int sin_numbrushsides; extern sin_dbrushside_t *sin_dbrushsides;//[MAX_MAP_BRUSHSIDES]; extern int sin_numareas; extern sin_darea_t *sin_dareas;//[MAX_MAP_AREAS]; extern int sin_numareaportals; extern sin_dareaportal_t *sin_dareaportals;//[MAX_MAP_AREAPORTALS]; extern int sin_numlightinfo; extern sin_lightvalue_t *sin_lightinfo;//[MAX_MAP_LIGHTINFO]; extern byte sin_dpop[256]; extern char sin_dbrushsidetextured[SIN_MAX_MAP_BRUSHSIDES]; void Sin_AllocMaxBSP(void); void Sin_FreeMaxBSP(void); void Sin_DecompressVis(byte *in, byte *decompressed); int Sin_CompressVis(byte *vis, byte *dest); void Sin_LoadBSPFile (char *filename, int offset, int length); void Sin_LoadBSPFileTexinfo (char *filename); // just for qdata void Sin_WriteBSPFile (char *filename); void Sin_PrintBSPFileSizes (void); void Sin_ParseEntities(void); void Sin_UnparseEntities(void); ================================================ FILE: code/bspc/l_cmd.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // cmdlib.c #include "l_cmd.h" #include "l_log.h" #include "l_mem.h" #include #include #ifndef SIN #define SIN #endif //SIN #if defined(WIN32) || defined(_WIN32) #include #else #include #endif #ifdef NeXT #include #endif #define BASEDIRNAME "quake2" #define PATHSEPERATOR '/' // set these before calling CheckParm int myargc; char **myargv; char com_token[1024]; qboolean com_eof; qboolean archive; char archivedir[1024]; /* =================== ExpandWildcards Mimic unix command line expansion =================== */ #define MAX_EX_ARGC 1024 int ex_argc; char *ex_argv[MAX_EX_ARGC]; #ifdef _WIN32 #include "io.h" void ExpandWildcards (int *argc, char ***argv) { struct _finddata_t fileinfo; int handle; int i; char filename[1024]; char filebase[1024]; char *path; ex_argc = 0; for (i=0 ; i<*argc ; i++) { path = (*argv)[i]; if ( path[0] == '-' || ( !strstr(path, "*") && !strstr(path, "?") ) ) { ex_argv[ex_argc++] = path; continue; } handle = _findfirst (path, &fileinfo); if (handle == -1) return; ExtractFilePath (path, filebase); do { sprintf (filename, "%s%s", filebase, fileinfo.name); ex_argv[ex_argc++] = copystring (filename); } while (_findnext( handle, &fileinfo ) != -1); _findclose (handle); } *argc = ex_argc; *argv = ex_argv; } #else void ExpandWildcards (int *argc, char ***argv) { } #endif #ifdef WINBSPC #include HWND program_hwnd; void SetProgramHandle(HWND hwnd) { program_hwnd = hwnd; } //end of the function SetProgramHandle /* ================= Error For abnormal program terminations in windowed apps ================= */ void Error (char *error, ...) { va_list argptr; char text[1024]; char text2[1024]; int err; err = GetLastError (); va_start(argptr, error); vsprintf(text, error, argptr); va_end(argptr); sprintf(text2, "%s\nGetLastError() = %i", text, err); MessageBox(program_hwnd, text2, "Error", 0 /* MB_OK */ ); Log_Write(text); Log_Close(); exit(1); } //end of the function Error void Warning(char *szFormat, ...) { char szBuffer[256]; va_list argptr; va_start (argptr, szFormat); vsprintf(szBuffer, szFormat, argptr); va_end (argptr); MessageBox(program_hwnd, szBuffer, "Warning", MB_OK); Log_Write(szBuffer); } //end of the function Warning #else /* ================= Error For abnormal program terminations in console apps ================= */ void Error (char *error, ...) { va_list argptr; char text[1024]; va_start(argptr, error); vsprintf(text, error, argptr); va_end(argptr); printf("ERROR: %s\n", text); Log_Write(text); Log_Close(); exit (1); } //end of the function Error void Warning(char *warning, ...) { va_list argptr; char text[1024]; va_start(argptr, warning); vsprintf(text, warning, argptr); va_end(argptr); printf("WARNING: %s\n", text); Log_Write(text); } //end of the function Warning #endif //only printf if in verbose mode qboolean verbose = true; void qprintf(char *format, ...) { va_list argptr; #ifdef WINBSPC char buf[2048]; #endif //WINBSPC if (!verbose) return; va_start(argptr,format); #ifdef WINBSPC vsprintf(buf, format, argptr); WinBSPCPrint(buf); #else vprintf(format, argptr); #endif //WINBSPC va_end(argptr); } //end of the function qprintf void Com_Error(int level, char *error, ...) { va_list argptr; char text[1024]; va_start(argptr, error); vsprintf(text, error, argptr); va_end(argptr); Error(text); } //end of the funcion Com_Error void Com_Printf( const char *fmt, ... ) { va_list argptr; char text[1024]; va_start(argptr, fmt); vsprintf(text, fmt, argptr); va_end(argptr); Log_Print(text); } //end of the funcion Com_Printf /* qdir will hold the path up to the quake directory, including the slash f:\quake\ /raid/quake/ gamedir will hold qdir + the game directory (id1, id2, etc) */ char qdir[1024]; char gamedir[1024]; void SetQdirFromPath (char *path) { char temp[1024]; char *c; int len; if (!(path[0] == '/' || path[0] == '\\' || path[1] == ':')) { // path is partial Q_getwd (temp); strcat (temp, path); path = temp; } // search for "quake2" in path len = strlen(BASEDIRNAME); for (c=path+strlen(path)-1 ; c != path ; c--) if (!Q_strncasecmp (c, BASEDIRNAME, len)) { strncpy (qdir, path, c+len+1-path); qprintf ("qdir: %s\n", qdir); c += len+1; while (*c) { if (*c == '/' || *c == '\\') { strncpy (gamedir, path, c+1-path); qprintf ("gamedir: %s\n", gamedir); return; } c++; } Error ("No gamedir in %s", path); return; } Error ("SetQdirFromPath: no '%s' in %s", BASEDIRNAME, path); } char *ExpandArg (char *path) { static char full[1024]; if (path[0] != '/' && path[0] != '\\' && path[1] != ':') { Q_getwd (full); strcat (full, path); } else strcpy (full, path); return full; } char *ExpandPath (char *path) { static char full[1024]; if (!qdir) Error ("ExpandPath called without qdir set"); if (path[0] == '/' || path[0] == '\\' || path[1] == ':') return path; sprintf (full, "%s%s", qdir, path); return full; } char *ExpandPathAndArchive (char *path) { char *expanded; char archivename[1024]; expanded = ExpandPath (path); if (archive) { sprintf (archivename, "%s/%s", archivedir, path); QCopyFile (expanded, archivename); } return expanded; } char *copystring(char *s) { char *b; b = GetMemory(strlen(s)+1); strcpy (b, s); return b; } /* ================ I_FloatTime ================ */ double I_FloatTime (void) { time_t t; time (&t); return t; #if 0 // more precise, less portable struct timeval tp; struct timezone tzp; static int secbase; gettimeofday(&tp, &tzp); if (!secbase) { secbase = tp.tv_sec; return tp.tv_usec/1000000.0; } return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; #endif } void Q_getwd (char *out) { #if defined(WIN32) || defined(_WIN32) getcwd (out, 256); strcat (out, "\\"); #else getwd(out); strcat(out, "/"); #endif } void Q_mkdir (char *path) { #ifdef WIN32 if (_mkdir (path) != -1) return; #else if (mkdir (path, 0777) != -1) return; #endif if (errno != EEXIST) Error ("mkdir %s: %s",path, strerror(errno)); } /* ============ FileTime returns -1 if not present ============ */ int FileTime (char *path) { struct stat buf; if (stat (path,&buf) == -1) return -1; return buf.st_mtime; } /* ============== COM_Parse Parse a token out of a string ============== */ char *COM_Parse (char *data) { int c; int len; len = 0; com_token[0] = 0; if (!data) return NULL; // skip whitespace skipwhite: while ( (c = *data) <= ' ') { if (c == 0) { com_eof = true; return NULL; // end of file; } data++; } // skip // comments if (c=='/' && data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } // handle quoted strings specially if (c == '\"') { data++; do { c = *data++; if (c=='\"') { com_token[len] = 0; return data; } com_token[len] = c; len++; } while (1); } // parse single characters if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') { com_token[len] = c; len++; com_token[len] = 0; return data+1; } // parse a regular word do { com_token[len] = c; data++; len++; c = *data; if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') break; } while (c>32); com_token[len] = 0; return data; } int Q_strncasecmp (char *s1, char *s2, int n) { int c1, c2; do { c1 = *s1++; c2 = *s2++; if (!n--) return 0; // strings are equal until end point if (c1 != c2) { if (c1 >= 'a' && c1 <= 'z') c1 -= ('a' - 'A'); if (c2 >= 'a' && c2 <= 'z') c2 -= ('a' - 'A'); if (c1 != c2) return -1; // strings not equal } } while (c1); return 0; // strings are equal } int Q_strcasecmp (char *s1, char *s2) { return Q_strncasecmp (s1, s2, 99999); } int Q_stricmp (char *s1, char *s2) { return Q_strncasecmp (s1, s2, 99999); } void Q_strncpyz( char *dest, const char *src, int destsize ) { strncpy( dest, src, destsize-1 ); dest[destsize-1] = 0; } char *strupr (char *start) { char *in; in = start; while (*in) { *in = toupper(*in); in++; } return start; } char *strlower (char *start) { char *in; in = start; while (*in) { *in = tolower(*in); in++; } return start; } /* ============================================================================= MISC FUNCTIONS ============================================================================= */ /* ================= CheckParm Checks for the given parameter in the program's command line arguments Returns the argument number (1 to argc-1) or 0 if not present ================= */ int CheckParm (char *check) { int i; for (i = 1;i 0 && path[length] != PATHSEPERATOR) length--; path[length] = 0; } void StripExtension (char *path) { int length; length = strlen(path)-1; while (length > 0 && path[length] != '.') { length--; if (path[length] == '/') return; // no extension } if (length) path[length] = 0; } /* ==================== Extract file parts ==================== */ // FIXME: should include the slash, otherwise // backing to an empty path will be wrong when appending a slash void ExtractFilePath (char *path, char *dest) { char *src; src = path + strlen(path) - 1; // // back up until a \ or the start // while (src != path && *(src-1) != '\\' && *(src-1) != '/') src--; memcpy (dest, path, src-path); dest[src-path] = 0; } void ExtractFileBase (char *path, char *dest) { char *src; src = path + strlen(path) - 1; // // back up until a \ or the start // while (src != path && *(src-1) != '\\' && *(src-1) != '/') src--; while (*src && *src != '.') { *dest++ = *src++; } *dest = 0; } void ExtractFileExtension (char *path, char *dest) { char *src; src = path + strlen(path) - 1; // // back up until a . or the start // while (src != path && *(src-1) != '.') src--; if (src == path) { *dest = 0; // no extension return; } strcpy (dest,src); } /* ============== ParseNum / ParseHex ============== */ int ParseHex (char *hex) { char *str; int num; num = 0; str = hex; while (*str) { num <<= 4; if (*str >= '0' && *str <= '9') num += *str-'0'; else if (*str >= 'a' && *str <= 'f') num += 10 + *str-'a'; else if (*str >= 'A' && *str <= 'F') num += 10 + *str-'A'; else Error ("Bad hex number: %s",hex); str++; } return num; } int ParseNum (char *str) { if (str[0] == '$') return ParseHex (str+1); if (str[0] == '0' && str[1] == 'x') return ParseHex (str+2); return atol (str); } /* ============================================================================ BYTE ORDER FUNCTIONS ============================================================================ */ #ifdef _SGI_SOURCE #define __BIG_ENDIAN__ #endif #ifdef __BIG_ENDIAN__ short LittleShort (short l) { byte b1,b2; b1 = l&255; b2 = (l>>8)&255; return (b1<<8) + b2; } short BigShort (short l) { return l; } int LittleLong (int l) { byte b1,b2,b3,b4; b1 = l&255; b2 = (l>>8)&255; b3 = (l>>16)&255; b4 = (l>>24)&255; return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; } int BigLong (int l) { return l; } float LittleFloat (float l) { union {byte b[4]; float f;} in, out; in.f = l; out.b[0] = in.b[3]; out.b[1] = in.b[2]; out.b[2] = in.b[1]; out.b[3] = in.b[0]; return out.f; } float BigFloat (float l) { return l; } #ifdef SIN unsigned short LittleUnsignedShort (unsigned short l) { byte b1,b2; b1 = l&255; b2 = (l>>8)&255; return (b1<<8) + b2; } unsigned short BigUnsignedShort (unsigned short l) { return l; } unsigned LittleUnsigned (unsigned l) { byte b1,b2,b3,b4; b1 = l&255; b2 = (l>>8)&255; b3 = (l>>16)&255; b4 = (l>>24)&255; return ((unsigned)b1<<24) + ((unsigned)b2<<16) + ((unsigned)b3<<8) + b4; } unsigned BigUnsigned (unsigned l) { return l; } #endif #else short BigShort (short l) { byte b1,b2; b1 = l&255; b2 = (l>>8)&255; return (b1<<8) + b2; } short LittleShort (short l) { return l; } int BigLong (int l) { byte b1,b2,b3,b4; b1 = l&255; b2 = (l>>8)&255; b3 = (l>>16)&255; b4 = (l>>24)&255; return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; } int LittleLong (int l) { return l; } float BigFloat (float l) { union {byte b[4]; float f;} in, out; in.f = l; out.b[0] = in.b[3]; out.b[1] = in.b[2]; out.b[2] = in.b[1]; out.b[3] = in.b[0]; return out.f; } float LittleFloat (float l) { return l; } #ifdef SIN unsigned short BigUnsignedShort (unsigned short l) { byte b1,b2; b1 = l&255; b2 = (l>>8)&255; return (b1<<8) + b2; } unsigned short LittleUnsignedShort (unsigned short l) { return l; } unsigned BigUnsigned (unsigned l) { byte b1,b2,b3,b4; b1 = l&255; b2 = (l>>8)&255; b3 = (l>>16)&255; b4 = (l>>24)&255; return ((unsigned)b1<<24) + ((unsigned)b2<<16) + ((unsigned)b3<<8) + b4; } unsigned LittleUnsigned (unsigned l) { return l; } #endif #endif //======================================================= // FIXME: byte swap? // this is a 16 bit, non-reflected CRC using the polynomial 0x1021 // and the initial and final xor values shown below... in other words, the // CCITT standard CRC used by XMODEM #define CRC_INIT_VALUE 0xffff #define CRC_XOR_VALUE 0x0000 static unsigned short crctable[256] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; void CRC_Init(unsigned short *crcvalue) { *crcvalue = CRC_INIT_VALUE; } void CRC_ProcessByte(unsigned short *crcvalue, byte data) { *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; } unsigned short CRC_Value(unsigned short crcvalue) { return crcvalue ^ CRC_XOR_VALUE; } //============================================================================= /* ============ CreatePath ============ */ void CreatePath (char *path) { char *ofs, c; if (path[1] == ':') path += 2; for (ofs = path+1 ; *ofs ; ofs++) { c = *ofs; if (c == '/' || c == '\\') { // create the directory *ofs = 0; Q_mkdir (path); *ofs = c; } } } /* ============ QCopyFile Used to archive source files ============ */ void QCopyFile (char *from, char *to) { void *buffer; int length; length = LoadFile (from, &buffer, 0, 0); CreatePath (to); SaveFile (to, buffer, length); FreeMemory(buffer); } void FS_FreeFile(void *buf) { FreeMemory(buf); } //end of the function FS_FreeFile int FS_ReadFileAndCache(const char *qpath, void **buffer) { return LoadFile((char *) qpath, buffer, 0, 0); } //end of the function FS_ReadFileAndCache int FS_FOpenFileRead( const char *filename, FILE **file, qboolean uniqueFILE ) { *file = fopen(filename, "rb"); return (*file != NULL); } //end of the function FS_FOpenFileRead ================================================ FILE: code/bspc/l_cmd.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // cmdlib.h #ifndef SIN #define SIN #endif //SIN #ifndef __CMDLIB__ #define __CMDLIB__ #ifdef _WIN32 #pragma warning(disable : 4244) // MIPS #pragma warning(disable : 4136) // X86 #pragma warning(disable : 4051) // ALPHA #pragma warning(disable : 4018) // signed/unsigned mismatch #pragma warning(disable : 4305) // truncate from double to float #endif #include #include #include #include #include #include #include #ifndef __BYTEBOOL__ #define __BYTEBOOL__ typedef enum {false, true} qboolean; typedef unsigned char byte; #endif // the dec offsetof macro doesnt work very well... #define myoffsetof(type,identifier) ((size_t)&((type *)0)->identifier) // set these before calling CheckParm extern int myargc; extern char **myargv; char *strupr (char *in); char *strlower (char *in); int Q_strncasecmp (char *s1, char *s2, int n); int Q_strcasecmp (char *s1, char *s2); void Q_getwd (char *out); int Q_filelength (FILE *f); int FileTime (char *path); void Q_mkdir (char *path); extern char qdir[1024]; extern char gamedir[1024]; void SetQdirFromPath (char *path); char *ExpandArg (char *path); // from cmd line char *ExpandPath (char *path); // from scripts char *ExpandPathAndArchive (char *path); double I_FloatTime (void); void Error(char *error, ...); void Warning(char *warning, ...); int CheckParm (char *check); FILE *SafeOpenWrite (char *filename); FILE *SafeOpenRead (char *filename); void SafeRead (FILE *f, void *buffer, int count); void SafeWrite (FILE *f, void *buffer, int count); int LoadFile (char *filename, void **bufferptr, int offset, int length); int TryLoadFile (char *filename, void **bufferptr); void SaveFile (char *filename, void *buffer, int count); qboolean FileExists (char *filename); void DefaultExtension (char *path, char *extension); void DefaultPath (char *path, char *basepath); void StripFilename (char *path); void StripExtension (char *path); void ExtractFilePath (char *path, char *dest); void ExtractFileBase (char *path, char *dest); void ExtractFileExtension (char *path, char *dest); int ParseNum (char *str); short BigShort (short l); short LittleShort (short l); int BigLong (int l); int LittleLong (int l); float BigFloat (float l); float LittleFloat (float l); #ifdef SIN unsigned short BigUnsignedShort (unsigned short l); unsigned short LittleUnsignedShort (unsigned short l); unsigned BigUnsigned (unsigned l); unsigned LittleUnsigned (unsigned l); #endif char *COM_Parse (char *data); extern char com_token[1024]; extern qboolean com_eof; char *copystring(char *s); void CRC_Init(unsigned short *crcvalue); void CRC_ProcessByte(unsigned short *crcvalue, byte data); unsigned short CRC_Value(unsigned short crcvalue); void CreatePath (char *path); void QCopyFile (char *from, char *to); extern qboolean archive; extern char archivedir[1024]; extern qboolean verbose; void qprintf (char *format, ...); void ExpandWildcards (int *argc, char ***argv); // for compression routines typedef struct { byte *data; int count; } cblock_t; #endif ================================================ FILE: code/bspc/l_log.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include #include #include #include "qbsp.h" #define MAX_LOGFILENAMESIZE 1024 typedef struct logfile_s { char filename[MAX_LOGFILENAMESIZE]; FILE *fp; int numwrites; } logfile_t; logfile_t logfile; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_Open(char *filename) { if (!filename || !strlen(filename)) { printf("openlog \n"); return; } //end if if (logfile.fp) { printf("log file %s is already opened\n", logfile.filename); return; } //end if logfile.fp = fopen(filename, "wb"); if (!logfile.fp) { printf("can't open the log file %s\n", filename); return; } //end if strncpy(logfile.filename, filename, MAX_LOGFILENAMESIZE); printf("Opened log %s\n", logfile.filename); } //end of the function Log_Create //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_Close(void) { if (!logfile.fp) { printf("no log file to close\n"); return; } //end if if (fclose(logfile.fp)) { printf("can't close log file %s\n", logfile.filename); return; } //end if logfile.fp = NULL; printf("Closed log %s\n", logfile.filename); } //end of the function Log_Close //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_Shutdown(void) { if (logfile.fp) Log_Close(); } //end of the function Log_Shutdown //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_UnifyEndOfLine(char *buf) { int i; for (i = 0; buf[i]; i++) { if (buf[i] == '\n') { if (i <= 0 || buf[i-1] != '\r') { memmove(&buf[i+1], &buf[i], strlen(&buf[i])+1); buf[i] = '\r'; i++; } //end if } //end if } //end for } //end of the function Log_UnifyEndOfLine //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_Print(char *fmt, ...) { va_list ap; char buf[2048]; va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); if (verbose) { #ifdef WINBSPC WinBSPCPrint(buf); #else printf("%s", buf); #endif //WINBSPS } //end if if (logfile.fp) { Log_UnifyEndOfLine(buf); fprintf(logfile.fp, "%s", buf); fflush(logfile.fp); } //end if } //end of the function Log_Print //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_Write(char *fmt, ...) { va_list ap; char buf[2048]; if (!logfile.fp) return; va_start(ap, fmt); vsprintf(buf, fmt, ap); va_end(ap); Log_UnifyEndOfLine(buf); fprintf(logfile.fp, "%s", buf); fflush(logfile.fp); } //end of the function Log_Write //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_WriteTimeStamped(char *fmt, ...) { va_list ap; if (!logfile.fp) return; /* fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", logfile.numwrites, (int) (botlibglobals.time / 60 / 60), (int) (botlibglobals.time / 60), (int) (botlibglobals.time), (int) ((int) (botlibglobals.time * 100)) - ((int) botlibglobals.time) * 100);*/ va_start(ap, fmt); vfprintf(logfile.fp, fmt, ap); va_end(ap); logfile.numwrites++; fflush(logfile.fp); } //end of the function Log_Write //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== FILE *Log_FileStruct(void) { return logfile.fp; } //end of the function Log_FileStruct //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Log_Flush(void) { if (logfile.fp) fflush(logfile.fp); } //end of the function Log_Flush ================================================ FILE: code/bspc/l_log.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ //open a log file void Log_Open(char *filename); //close the current log file void Log_Close(void); //close log file if present void Log_Shutdown(void); //print on stdout and write to the current opened log file void Log_Print(char *fmt, ...); //write to the current opened log file void Log_Write(char *fmt, ...); //write to the current opened log file with a time stamp void Log_WriteTimeStamped(char *fmt, ...); //returns the log file structure FILE *Log_FileStruct(void); //flush log file void Log_Flush(void); #ifdef WINBSPC void WinBSPCPrint(char *str); #endif //WINBSPC ================================================ FILE: code/bspc/l_math.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // mathlib.c -- math primitives #include "l_cmd.h" #include "l_math.h" vec3_t vec3_origin = {0,0,0}; void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) { float angle; static float sr, sp, sy, cr, cp, cy; // static to help MS compiler fp bugs angle = angles[YAW] * (M_PI*2 / 360); sy = sin(angle); cy = cos(angle); angle = angles[PITCH] * (M_PI*2 / 360); sp = sin(angle); cp = cos(angle); angle = angles[ROLL] * (M_PI*2 / 360); sr = sin(angle); cr = cos(angle); if (forward) { forward[0] = cp*cy; forward[1] = cp*sy; forward[2] = -sp; } if (right) { right[0] = (-1*sr*sp*cy+-1*cr*-sy); right[1] = (-1*sr*sp*sy+-1*cr*cy); right[2] = -1*sr*cp; } if (up) { up[0] = (cr*sp*cy+-sr*-sy); up[1] = (cr*sp*sy+-sr*cy); up[2] = cr*cp; } } /* ================= RadiusFromBounds ================= */ float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) { int i; vec3_t corner; float a, b; for (i=0 ; i<3 ; i++) { a = fabs( mins[i] ); b = fabs( maxs[i] ); corner[i] = a > b ? a : b; } return VectorLength (corner); } /* ================ R_ConcatRotations ================ */ void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]) { out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0]; out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1]; out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2]; out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0]; out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1]; out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2]; out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0]; out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1]; out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2]; } void AxisClear( vec3_t axis[3] ) { axis[0][0] = 1; axis[0][1] = 0; axis[0][2] = 0; axis[1][0] = 0; axis[1][1] = 1; axis[1][2] = 0; axis[2][0] = 0; axis[2][1] = 0; axis[2][2] = 1; } float VectorLengthSquared(vec3_t v) { return DotProduct(v, v); } double VectorLength(vec3_t v) { int i; double length; length = 0; for (i=0 ; i< 3 ; i++) length += v[i]*v[i]; length = sqrt (length); // FIXME return length; } qboolean VectorCompare (vec3_t v1, vec3_t v2) { int i; for (i=0 ; i<3 ; i++) if (fabs(v1[i]-v2[i]) > EQUAL_EPSILON) return false; return true; } vec_t Q_rint (vec_t in) { return floor(in + 0.5); } void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross) { cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; } void _VectorMA (vec3_t va, double scale, vec3_t vb, vec3_t vc) { vc[0] = va[0] + scale*vb[0]; vc[1] = va[1] + scale*vb[1]; vc[2] = va[2] + scale*vb[2]; } vec_t _DotProduct (vec3_t v1, vec3_t v2) { return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; } void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out) { out[0] = va[0]-vb[0]; out[1] = va[1]-vb[1]; out[2] = va[2]-vb[2]; } void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out) { out[0] = va[0]+vb[0]; out[1] = va[1]+vb[1]; out[2] = va[2]+vb[2]; } void _VectorCopy (vec3_t in, vec3_t out) { out[0] = in[0]; out[1] = in[1]; out[2] = in[2]; } void _VectorScale (vec3_t v, vec_t scale, vec3_t out) { out[0] = v[0] * scale; out[1] = v[1] * scale; out[2] = v[2] * scale; } vec_t VectorNormalize(vec3_t inout) { vec_t length, ilength; length = sqrt (inout[0]*inout[0] + inout[1]*inout[1] + inout[2]*inout[2]); if (length == 0) { VectorClear (inout); return 0; } ilength = 1.0/length; inout[0] = inout[0]*ilength; inout[1] = inout[1]*ilength; inout[2] = inout[2]*ilength; return length; } vec_t VectorNormalize2(const vec3_t in, vec3_t out) { vec_t length, ilength; length = sqrt (in[0]*in[0] + in[1]*in[1] + in[2]*in[2]); if (length == 0) { VectorClear (out); return 0; } ilength = 1.0/length; out[0] = in[0]*ilength; out[1] = in[1]*ilength; out[2] = in[2]*ilength; return length; } vec_t ColorNormalize (vec3_t in, vec3_t out) { float max, scale; max = in[0]; if (in[1] > max) max = in[1]; if (in[2] > max) max = in[2]; if (max == 0) return 0; scale = 1.0 / max; VectorScale (in, scale, out); return max; } void VectorInverse (vec3_t v) { v[0] = -v[0]; v[1] = -v[1]; v[2] = -v[2]; } void ClearBounds(vec3_t mins, vec3_t maxs) { mins[0] = mins[1] = mins[2] = 99999; maxs[0] = maxs[1] = maxs[2] = -99999; } void AddPointToBounds(const vec3_t v, vec3_t mins, vec3_t maxs) { int i; vec_t val; for (i=0 ; i<3 ; i++) { val = v[i]; if (val < mins[i]) mins[i] = val; if (val > maxs[i]) maxs[i] = val; } } ================================================ FILE: code/bspc/l_math.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #ifndef __MATHLIB__ #define __MATHLIB__ // mathlib.h #include #ifdef DOUBLEVEC_T typedef double vec_t; #else typedef float vec_t; #endif typedef vec_t vec3_t[3]; typedef vec_t vec4_t[4]; #define SIDE_FRONT 0 #define SIDE_ON 2 #define SIDE_BACK 1 #define SIDE_CROSS -2 #define PITCH 0 #define YAW 1 #define ROLL 2 #define Q_PI 3.14159265358979323846 #define DEG2RAD( a ) ( a * M_PI ) / 180.0F #ifndef M_PI #define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h #endif extern vec3_t vec3_origin; #define EQUAL_EPSILON 0.001 qboolean VectorCompare (vec3_t v1, vec3_t v2); #define DotProduct(x,y) (x[0]*y[0]+x[1]*y[1]+x[2]*y[2]) #define VectorSubtract(a,b,c) {c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];} #define VectorAdd(a,b,c) {c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];} #define VectorCopy(a,b) {b[0]=a[0];b[1]=a[1];b[2]=a[2];} #define Vector4Copy(a,b) {b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];} #define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) #define VectorClear(x) {x[0] = x[1] = x[2] = 0;} #define VectorNegate(x, y) {y[0]=-x[0];y[1]=-x[1];y[2]=-x[2];} #define VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s)) vec_t Q_rint (vec_t in); vec_t _DotProduct (vec3_t v1, vec3_t v2); void _VectorSubtract (vec3_t va, vec3_t vb, vec3_t out); void _VectorAdd (vec3_t va, vec3_t vb, vec3_t out); void _VectorCopy (vec3_t in, vec3_t out); void _VectorScale (vec3_t v, vec_t scale, vec3_t out); void _VectorMA(vec3_t va, double scale, vec3_t vb, vec3_t vc); double VectorLength(vec3_t v); void CrossProduct(const vec3_t v1, const vec3_t v2, vec3_t cross); vec_t VectorNormalize(vec3_t inout); vec_t ColorNormalize(vec3_t in, vec3_t out); vec_t VectorNormalize2(const vec3_t v, vec3_t out); void VectorInverse (vec3_t v); void ClearBounds (vec3_t mins, vec3_t maxs); void AddPointToBounds (const vec3_t v, vec3_t mins, vec3_t maxs); void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); void RotatePoint(vec3_t point, float matrix[3][3]); void CreateRotationMatrix(vec3_t angles, float matrix[3][3]); #endif ================================================ FILE: code/bspc/l_mem.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "l_log.h" int allocedmemory; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintMemorySize(unsigned long size) { unsigned long number1, number2, number3; number1 = size >> 20; number2 = (size & 0xFFFFF) >> 10; number3 = (size & 0x3FF); if (number1) Log_Print("%ld MB", number1); if (number1 && number2) Log_Print(" and "); if (number2) Log_Print("%ld KB", number2); if (number2 && number3) Log_Print(" and "); if (number3) Log_Print("%ld bytes", number3); } //end of the function PrintFileSize #ifndef MEMDEBUG //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int MemorySize(void *ptr) { #if defined(WIN32) || defined(_WIN32) #ifdef __WATCOMC__ //Intel 32 bits memory addressing, 16 bytes aligned return (_msize(ptr) + 15) >> 4 << 4; #else return _msize(ptr); #endif #else return 0; #endif } //end of the function MemorySize //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void *GetClearedMemory(int size) { void *ptr; ptr = (void *) malloc(size); if (!ptr) Error("out of memory"); memset(ptr, 0, size); allocedmemory += MemorySize(ptr); return ptr; } //end of the function GetClearedMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void *GetMemory(unsigned long size) { void *ptr; ptr = malloc(size); if (!ptr) Error("out of memory"); allocedmemory += MemorySize(ptr); return ptr; } //end of the function GetMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeMemory(void *ptr) { allocedmemory -= MemorySize(ptr); free(ptr); } //end of the function FreeMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TotalAllocatedMemory(void) { return allocedmemory; } //end of the function TotalAllocatedMemory #else #define MEM_ID 0x12345678l int totalmemorysize; int numblocks; typedef struct memoryblock_s { unsigned long int id; void *ptr; int size; #ifdef MEMDEBUG char *label; char *file; int line; #endif //MEMDEBUG struct memoryblock_s *prev, *next; } memoryblock_t; memoryblock_t *memory; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void LinkMemoryBlock(memoryblock_t *block) { block->prev = NULL; block->next = memory; if (memory) memory->prev = block; memory = block; } //end of the function LinkMemoryBlock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void UnlinkMemoryBlock(memoryblock_t *block) { if (block->prev) block->prev->next = block->next; else memory = block->next; if (block->next) block->next->prev = block->prev; } //end of the function UnlinkMemoryBlock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef MEMDEBUG void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) #else void *GetMemory(unsigned long size) #endif //MEMDEBUG { void *ptr; memoryblock_t *block; ptr = malloc(size + sizeof(memoryblock_t)); block = (memoryblock_t *) ptr; block->id = MEM_ID; block->ptr = (char *) ptr + sizeof(memoryblock_t); block->size = size + sizeof(memoryblock_t); #ifdef MEMDEBUG block->label = label; block->file = file; block->line = line; #endif //MEMDEBUG LinkMemoryBlock(block); totalmemorysize += block->size; numblocks++; return block->ptr; } //end of the function GetMemoryDebug //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef MEMDEBUG void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) #else void *GetClearedMemory(unsigned long size) #endif //MEMDEBUG { void *ptr; #ifdef MEMDEBUG ptr = GetMemoryDebug(size, label, file, line); #else ptr = GetMemory(size); #endif //MEMDEBUG memset(ptr, 0, size); return ptr; } //end of the function GetClearedMemoryLabelled //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void *GetClearedHunkMemory(unsigned long size) { return GetClearedMemory(size); } //end of the function GetClearedHunkMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void *GetHunkMemory(unsigned long size) { return GetMemory(size); } //end of the function GetHunkMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== memoryblock_t *BlockFromPointer(void *ptr, char *str) { memoryblock_t *block; if (!ptr) { #ifdef MEMDEBUG //char *crash = (char *) NULL; //crash[0] = 1; Error("%s: NULL pointer\n", str); #endif MEMDEBUG return NULL; } //end if block = (memoryblock_t *) ((char *) ptr - sizeof(memoryblock_t)); if (block->id != MEM_ID) { Error("%s: invalid memory block\n", str); } //end if if (block->ptr != ptr) { Error("%s: memory block pointer invalid\n", str); } //end if return block; } //end of the function BlockFromPointer //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreeMemory(void *ptr) { memoryblock_t *block; block = BlockFromPointer(ptr, "FreeMemory"); if (!block) return; UnlinkMemoryBlock(block); totalmemorysize -= block->size; numblocks--; // free(block); } //end of the function FreeMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int MemoryByteSize(void *ptr) { memoryblock_t *block; block = BlockFromPointer(ptr, "MemoryByteSize"); if (!block) return 0; return block->size; } //end of the function MemoryByteSize //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int MemorySize(void *ptr) { return MemoryByteSize(ptr); } //end of the function MemorySize //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintUsedMemorySize(void) { printf("total botlib memory: %d KB\n", totalmemorysize >> 10); printf("total memory blocks: %d\n", numblocks); } //end of the function PrintUsedMemorySize //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintMemoryLabels(void) { memoryblock_t *block; int i; PrintUsedMemorySize(); i = 0; for (block = memory; block; block = block->next) { #ifdef MEMDEBUG Log_Write("%6d, %p, %8d: %24s line %6d: %s", i, block->ptr, block->size, block->file, block->line, block->label); #endif //MEMDEBUG i++; } //end for } //end of the function PrintMemoryLabels //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void DumpMemory(void) { memoryblock_t *block; for (block = memory; block; block = memory) { FreeMemory(block->ptr); } //end for totalmemorysize = 0; } //end of the function DumpMemory //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TotalAllocatedMemory(void) { return totalmemorysize; } //end of the function TotalAllocatedMemory #endif //=========================================================================== // Q3 Hunk and Z_ memory management //=========================================================================== typedef struct memhunk_s { void *ptr; struct memhunk_s *next; } memhunk_t; memhunk_t *memhunk_high; memhunk_t *memhunk_low; int memhunk_high_size = 16 * 1024 * 1024; int memhunk_low_size = 0; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Hunk_ClearHigh(void) { memhunk_t *h, *nexth; for (h = memhunk_high; h; h = nexth) { nexth = h->next; FreeMemory(h); } //end for memhunk_high = NULL; memhunk_high_size = 16 * 1024 * 1024; } //end of the function Hunk_ClearHigh //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void *Hunk_Alloc(int size) { memhunk_t *h; if (!size) return (void *) memhunk_high_size; // h = GetClearedMemory(size + sizeof(memhunk_t)); h->ptr = (char *) h + sizeof(memhunk_t); h->next = memhunk_high; memhunk_high = h; memhunk_high_size -= size; return h->ptr; } //end of the function Hunk_Alloc //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void *Z_Malloc(int size) { return GetClearedMemory(size); } //end of the function Z_Malloc //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Z_Free (void *ptr) { FreeMemory(ptr); } //end of the function Z_Free ================================================ FILE: code/bspc/l_mem.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ //============================================================================= // memory.h //#define MEMDEBUG #undef MEMDEBUG #ifndef MEMDEBUG void *GetClearedMemory(int size); void *GetMemory(unsigned long size); #else #define GetMemory(size) GetMemoryDebug(size, #size, __FILE__, __LINE__); #define GetClearedMemory(size) GetClearedMemoryDebug(size, #size, __FILE__, __LINE__); //allocate a memory block of the given size void *GetMemoryDebug(unsigned long size, char *label, char *file, int line); //allocate a memory block of the given size and clear it void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line); // void PrintMemoryLabels(void); #endif //MEMDEBUG void FreeMemory(void *ptr); int MemorySize(void *ptr); void PrintMemorySize(unsigned long size); int TotalAllocatedMemory(void); ================================================ FILE: code/bspc/l_poly.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include #include "l_cmd.h" #include "l_math.h" #include "l_poly.h" #include "l_log.h" #include "l_mem.h" #define BOGUS_RANGE 65535 extern int numthreads; // counters are only bumped when running single threaded, // because they are an awefull coherence problem int c_active_windings; int c_peak_windings; int c_winding_allocs; int c_winding_points; int c_windingmemory; int c_peak_windingmemory; char windingerror[1024]; void pw(winding_t *w) { int i; for (i=0 ; inumpoints ; i++) printf ("(%5.3f, %5.3f, %5.3f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); } void ResetWindings(void) { c_active_windings = 0; c_peak_windings = 0; c_winding_allocs = 0; c_winding_points = 0; c_windingmemory = 0; c_peak_windingmemory = 0; strcpy(windingerror, ""); } //end of the function ResetWindings /* ============= AllocWinding ============= */ winding_t *AllocWinding (int points) { winding_t *w; int s; s = sizeof(vec_t)*3*points + sizeof(int); w = GetMemory(s); memset(w, 0, s); if (numthreads == 1) { c_winding_allocs++; c_winding_points += points; c_active_windings++; if (c_active_windings > c_peak_windings) c_peak_windings = c_active_windings; c_windingmemory += MemorySize(w); if (c_windingmemory > c_peak_windingmemory) c_peak_windingmemory = c_windingmemory; } //end if return w; } //end of the function AllocWinding void FreeWinding (winding_t *w) { if (*(unsigned *)w == 0xdeaddead) Error ("FreeWinding: freed a freed winding"); if (numthreads == 1) { c_active_windings--; c_windingmemory -= MemorySize(w); } //end if *(unsigned *)w = 0xdeaddead; FreeMemory(w); } //end of the function FreeWinding int WindingMemory(void) { return c_windingmemory; } //end of the function WindingMemory int WindingPeakMemory(void) { return c_peak_windingmemory; } //end of the function WindingPeakMemory int ActiveWindings(void) { return c_active_windings; } //end of the function ActiveWindings /* ============ RemoveColinearPoints ============ */ int c_removed; void RemoveColinearPoints (winding_t *w) { int i, j, k; vec3_t v1, v2; int nump; vec3_t p[MAX_POINTS_ON_WINDING]; nump = 0; for (i=0 ; inumpoints ; i++) { j = (i+1)%w->numpoints; k = (i+w->numpoints-1)%w->numpoints; VectorSubtract (w->p[j], w->p[i], v1); VectorSubtract (w->p[i], w->p[k], v2); VectorNormalize(v1); VectorNormalize(v2); if (DotProduct(v1, v2) < 0.999) { if (nump >= MAX_POINTS_ON_WINDING) Error("RemoveColinearPoints: MAX_POINTS_ON_WINDING"); VectorCopy (w->p[i], p[nump]); nump++; } } if (nump == w->numpoints) return; if (numthreads == 1) c_removed += w->numpoints - nump; w->numpoints = nump; memcpy (w->p, p, nump*sizeof(p[0])); } /* ============ WindingPlane ============ */ void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) { vec3_t v1, v2; int i; //find two vectors each longer than 0.5 units for (i = 0; i < w->numpoints; i++) { VectorSubtract(w->p[(i+1) % w->numpoints], w->p[i], v1); VectorSubtract(w->p[(i+2) % w->numpoints], w->p[i], v2); if (VectorLength(v1) > 0.5 && VectorLength(v2) > 0.5) break; } //end for CrossProduct(v2, v1, normal); VectorNormalize(normal); *dist = DotProduct(w->p[0], normal); } //end of the function WindingPlane /* ============= WindingArea ============= */ vec_t WindingArea (winding_t *w) { int i; vec3_t d1, d2, cross; vec_t total; total = 0; for (i=2 ; inumpoints ; i++) { VectorSubtract (w->p[i-1], w->p[0], d1); VectorSubtract (w->p[i], w->p[0], d2); CrossProduct (d1, d2, cross); total += 0.5 * VectorLength ( cross ); } return total; } void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) { vec_t v; int i,j; mins[0] = mins[1] = mins[2] = 99999; maxs[0] = maxs[1] = maxs[2] = -99999; for (i=0 ; inumpoints ; i++) { for (j=0 ; j<3 ; j++) { v = w->p[i][j]; if (v < mins[j]) mins[j] = v; if (v > maxs[j]) maxs[j] = v; } } } /* ============= WindingCenter ============= */ void WindingCenter (winding_t *w, vec3_t center) { int i; float scale; VectorCopy (vec3_origin, center); for (i=0 ; inumpoints ; i++) VectorAdd (w->p[i], center, center); scale = 1.0/w->numpoints; VectorScale (center, scale, center); } /* ================= BaseWindingForPlane ================= */ winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist) { int i, x; vec_t max, v; vec3_t org, vright, vup; winding_t *w; // find the major axis max = -BOGUS_RANGE; x = -1; for (i=0 ; i<3; i++) { v = fabs(normal[i]); if (v > max) { x = i; max = v; } } if (x==-1) Error ("BaseWindingForPlane: no axis found"); VectorCopy (vec3_origin, vup); switch (x) { case 0: case 1: vup[2] = 1; break; case 2: vup[0] = 1; break; } v = DotProduct (vup, normal); VectorMA (vup, -v, normal, vup); VectorNormalize (vup); VectorScale (normal, dist, org); CrossProduct (vup, normal, vright); VectorScale (vup, BOGUS_RANGE, vup); VectorScale (vright, BOGUS_RANGE, vright); // project a really big axis aligned box onto the plane w = AllocWinding (4); VectorSubtract (org, vright, w->p[0]); VectorAdd (w->p[0], vup, w->p[0]); VectorAdd (org, vright, w->p[1]); VectorAdd (w->p[1], vup, w->p[1]); VectorAdd (org, vright, w->p[2]); VectorSubtract (w->p[2], vup, w->p[2]); VectorSubtract (org, vright, w->p[3]); VectorSubtract (w->p[3], vup, w->p[3]); w->numpoints = 4; return w; } /* ================== CopyWinding ================== */ winding_t *CopyWinding (winding_t *w) { int size; winding_t *c; c = AllocWinding (w->numpoints); size = (int)((winding_t *)0)->p[w->numpoints]; memcpy (c, w, size); return c; } /* ================== ReverseWinding ================== */ winding_t *ReverseWinding (winding_t *w) { int i; winding_t *c; c = AllocWinding (w->numpoints); for (i=0 ; inumpoints ; i++) { VectorCopy (w->p[w->numpoints-1-i], c->p[i]); } c->numpoints = w->numpoints; return c; } /* ============= ClipWindingEpsilon ============= */ void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back) { vec_t dists[MAX_POINTS_ON_WINDING+4]; int sides[MAX_POINTS_ON_WINDING+4]; int counts[3]; //MrElusive: DOH can't use statics when unsing multithreading!!! vec_t dot; // VC 4.2 optimizer bug if not static int i, j; vec_t *p1, *p2; vec3_t mid; winding_t *f, *b; int maxpts; counts[0] = counts[1] = counts[2] = 0; // determine sides for each point for (i=0 ; inumpoints ; i++) { dot = DotProduct (in->p[i], normal); dot -= dist; dists[i] = dot; if (dot > epsilon) sides[i] = SIDE_FRONT; else if (dot < -epsilon) sides[i] = SIDE_BACK; else { sides[i] = SIDE_ON; } counts[sides[i]]++; } sides[i] = sides[0]; dists[i] = dists[0]; *front = *back = NULL; if (!counts[0]) { *back = CopyWinding (in); return; } if (!counts[1]) { *front = CopyWinding (in); return; } maxpts = in->numpoints+4; // cant use counts[0]+2 because // of fp grouping errors *front = f = AllocWinding (maxpts); *back = b = AllocWinding (maxpts); for (i=0 ; inumpoints ; i++) { p1 = in->p[i]; if (sides[i] == SIDE_ON) { VectorCopy (p1, f->p[f->numpoints]); f->numpoints++; VectorCopy (p1, b->p[b->numpoints]); b->numpoints++; continue; } if (sides[i] == SIDE_FRONT) { VectorCopy (p1, f->p[f->numpoints]); f->numpoints++; } if (sides[i] == SIDE_BACK) { VectorCopy (p1, b->p[b->numpoints]); b->numpoints++; } if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) continue; // generate a split point p2 = in->p[(i+1)%in->numpoints]; dot = dists[i] / (dists[i]-dists[i+1]); for (j=0 ; j<3 ; j++) { // avoid round off error when possible if (normal[j] == 1) mid[j] = dist; else if (normal[j] == -1) mid[j] = -dist; else mid[j] = p1[j] + dot*(p2[j]-p1[j]); } VectorCopy (mid, f->p[f->numpoints]); f->numpoints++; VectorCopy (mid, b->p[b->numpoints]); b->numpoints++; } if (f->numpoints > maxpts || b->numpoints > maxpts) Error ("ClipWinding: points exceeded estimate"); if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) Error ("ClipWinding: MAX_POINTS_ON_WINDING"); } /* ============= ChopWindingInPlace ============= */ void ChopWindingInPlace (winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon) { winding_t *in; vec_t dists[MAX_POINTS_ON_WINDING+4]; int sides[MAX_POINTS_ON_WINDING+4]; int counts[3]; //MrElusive: DOH can't use statics when unsing multithreading!!! vec_t dot; // VC 4.2 optimizer bug if not static int i, j; vec_t *p1, *p2; vec3_t mid; winding_t *f; int maxpts; in = *inout; counts[0] = counts[1] = counts[2] = 0; // determine sides for each point for (i=0 ; inumpoints ; i++) { dot = DotProduct (in->p[i], normal); dot -= dist; dists[i] = dot; if (dot > epsilon) sides[i] = SIDE_FRONT; else if (dot < -epsilon) sides[i] = SIDE_BACK; else { sides[i] = SIDE_ON; } counts[sides[i]]++; } sides[i] = sides[0]; dists[i] = dists[0]; if (!counts[0]) { FreeWinding (in); *inout = NULL; return; } if (!counts[1]) return; // inout stays the same maxpts = in->numpoints+4; // cant use counts[0]+2 because // of fp grouping errors f = AllocWinding (maxpts); for (i=0 ; inumpoints ; i++) { p1 = in->p[i]; if (sides[i] == SIDE_ON) { VectorCopy (p1, f->p[f->numpoints]); f->numpoints++; continue; } if (sides[i] == SIDE_FRONT) { VectorCopy (p1, f->p[f->numpoints]); f->numpoints++; } if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) continue; // generate a split point p2 = in->p[(i+1)%in->numpoints]; dot = dists[i] / (dists[i]-dists[i+1]); for (j=0 ; j<3 ; j++) { // avoid round off error when possible if (normal[j] == 1) mid[j] = dist; else if (normal[j] == -1) mid[j] = -dist; else mid[j] = p1[j] + dot*(p2[j]-p1[j]); } VectorCopy (mid, f->p[f->numpoints]); f->numpoints++; } if (f->numpoints > maxpts) Error ("ClipWinding: points exceeded estimate"); if (f->numpoints > MAX_POINTS_ON_WINDING) Error ("ClipWinding: MAX_POINTS_ON_WINDING"); FreeWinding (in); *inout = f; } /* ================= ChopWinding Returns the fragment of in that is on the front side of the cliping plane. The original is freed. ================= */ winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist) { winding_t *f, *b; ClipWindingEpsilon (in, normal, dist, ON_EPSILON, &f, &b); FreeWinding (in); if (b) FreeWinding (b); return f; } /* ================= CheckWinding ================= */ void CheckWinding (winding_t *w) { int i, j; vec_t *p1, *p2; vec_t d, edgedist; vec3_t dir, edgenormal, facenormal; vec_t area; vec_t facedist; if (w->numpoints < 3) Error ("CheckWinding: %i points",w->numpoints); area = WindingArea(w); if (area < 1) Error ("CheckWinding: %f area", area); WindingPlane (w, facenormal, &facedist); for (i=0 ; inumpoints ; i++) { p1 = w->p[i]; for (j=0 ; j<3 ; j++) if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) Error ("CheckWinding: BUGUS_RANGE: %f",p1[j]); j = i+1 == w->numpoints ? 0 : i+1; // check the point is on the face plane d = DotProduct (p1, facenormal) - facedist; if (d < -ON_EPSILON || d > ON_EPSILON) Error ("CheckWinding: point off plane"); // check the edge isnt degenerate p2 = w->p[j]; VectorSubtract (p2, p1, dir); if (VectorLength (dir) < ON_EPSILON) Error ("CheckWinding: degenerate edge"); CrossProduct (facenormal, dir, edgenormal); VectorNormalize (edgenormal); edgedist = DotProduct (p1, edgenormal); edgedist += ON_EPSILON; // all other points must be on front side for (j=0 ; jnumpoints ; j++) { if (j == i) continue; d = DotProduct (w->p[j], edgenormal); if (d > edgedist) Error ("CheckWinding: non-convex"); } } } /* ============ WindingOnPlaneSide ============ */ int WindingOnPlaneSide (winding_t *w, vec3_t normal, vec_t dist) { qboolean front, back; int i; vec_t d; front = false; back = false; for (i=0 ; inumpoints ; i++) { d = DotProduct (w->p[i], normal) - dist; if (d < -ON_EPSILON) { if (front) return SIDE_CROSS; back = true; continue; } if (d > ON_EPSILON) { if (back) return SIDE_CROSS; front = true; continue; } } if (back) return SIDE_BACK; if (front) return SIDE_FRONT; return SIDE_ON; } //#ifdef ME #define CONTINUOUS_EPSILON 0.005 //#else // #define CONTINUOUS_EPSILON 0.001 //#endif /* ============= TryMergeWinding If two polygons share a common edge and the edges that meet at the common points are both inside the other polygons, merge them Returns NULL if the faces couldn't be merged, or the new face. The originals will NOT be freed. ============= */ winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, vec3_t planenormal) { vec_t *p1, *p2, *p3, *p4, *back; winding_t *newf; int i, j, k, l; vec3_t normal, delta; vec_t dot; qboolean keep1, keep2; // // find a common edge // p1 = p2 = NULL; // stop compiler warning j = 0; // for (i = 0; i < f1->numpoints; i++) { p1 = f1->p[i]; p2 = f1->p[(i+1) % f1->numpoints]; for (j = 0; j < f2->numpoints; j++) { p3 = f2->p[j]; p4 = f2->p[(j+1) % f2->numpoints]; for (k = 0; k < 3; k++) { if (fabs(p1[k] - p4[k]) > 0.1)//EQUAL_EPSILON) //ME break; if (fabs(p2[k] - p3[k]) > 0.1)//EQUAL_EPSILON) //ME break; } //end for if (k==3) break; } //end for if (j < f2->numpoints) break; } //end for if (i == f1->numpoints) return NULL; // no matching edges // // check slope of connected lines // if the slopes are colinear, the point can be removed // back = f1->p[(i+f1->numpoints-1)%f1->numpoints]; VectorSubtract (p1, back, delta); CrossProduct (planenormal, delta, normal); VectorNormalize (normal); back = f2->p[(j+2)%f2->numpoints]; VectorSubtract (back, p1, delta); dot = DotProduct (delta, normal); if (dot > CONTINUOUS_EPSILON) return NULL; // not a convex polygon keep1 = (qboolean)(dot < -CONTINUOUS_EPSILON); back = f1->p[(i+2)%f1->numpoints]; VectorSubtract (back, p2, delta); CrossProduct (planenormal, delta, normal); VectorNormalize (normal); back = f2->p[(j+f2->numpoints-1)%f2->numpoints]; VectorSubtract (back, p2, delta); dot = DotProduct (delta, normal); if (dot > CONTINUOUS_EPSILON) return NULL; // not a convex polygon keep2 = (qboolean)(dot < -CONTINUOUS_EPSILON); // // build the new polygon // newf = AllocWinding (f1->numpoints + f2->numpoints); // copy first polygon for (k=(i+1)%f1->numpoints ; k != i ; k=(k+1)%f1->numpoints) { if (k==(i+1)%f1->numpoints && !keep2) continue; VectorCopy (f1->p[k], newf->p[newf->numpoints]); newf->numpoints++; } // copy second polygon for (l= (j+1)%f2->numpoints ; l != j ; l=(l+1)%f2->numpoints) { if (l==(j+1)%f2->numpoints && !keep1) continue; VectorCopy (f2->p[l], newf->p[newf->numpoints]); newf->numpoints++; } return newf; } //#ifdef ME //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== winding_t *MergeWindings(winding_t *w1, winding_t *w2, vec3_t planenormal) { winding_t *neww; float dist; int i, j, n, found, insertafter; int sides[MAX_POINTS_ON_WINDING+4]; vec3_t newp[MAX_POINTS_ON_WINDING+4]; int numpoints; vec3_t edgevec, sepnormal, v; RemoveEqualPoints(w1, 0.2); numpoints = w1->numpoints; memcpy(newp, w1->p, w1->numpoints * sizeof(vec3_t)); // for (i = 0; i < w2->numpoints; i++) { VectorCopy(w2->p[i], v); for (j = 0; j < numpoints; j++) { VectorSubtract(newp[(j+1)%numpoints], newp[(j)%numpoints], edgevec); CrossProduct(edgevec, planenormal, sepnormal); VectorNormalize(sepnormal); if (VectorLength(sepnormal) < 0.9) { //remove the point from the new winding for (n = j; n < numpoints-1; n++) { VectorCopy(newp[n+1], newp[n]); sides[n] = sides[n+1]; } //end for numpoints--; j--; Log_Print("MergeWindings: degenerate edge on winding %f %f %f\n", sepnormal[0], sepnormal[1], sepnormal[2]); continue; } //end if dist = DotProduct(newp[(j)%numpoints], sepnormal); if (DotProduct(v, sepnormal) - dist < -0.1) sides[j] = SIDE_BACK; else sides[j] = SIDE_FRONT; } //end for //remove all unnecesary points for (j = 0; j < numpoints;) { if (sides[j] == SIDE_BACK && sides[(j+1)%numpoints] == SIDE_BACK) { //remove the point from the new winding for (n = (j+1)%numpoints; n < numpoints-1; n++) { VectorCopy(newp[n+1], newp[n]); sides[n] = sides[n+1]; } //end for numpoints--; } //end if else { j++; } //end else } //end for // found = false; for (j = 0; j < numpoints; j++) { if (sides[j] == SIDE_FRONT && sides[(j+1)%numpoints] == SIDE_BACK) { if (found) Log_Print("Warning: MergeWindings: front to back found twice\n"); found = true; } //end if } //end for // for (j = 0; j < numpoints; j++) { if (sides[j] == SIDE_FRONT && sides[(j+1)%numpoints] == SIDE_BACK) { insertafter = (j+1)%numpoints; //insert the new point after j+1 for (n = numpoints-1; n > insertafter; n--) { VectorCopy(newp[n], newp[n+1]); } //end for numpoints++; VectorCopy(v, newp[(insertafter+1)%numpoints]); break; } //end if } //end for } //end for neww = AllocWinding(numpoints); neww->numpoints = numpoints; memcpy(neww->p, newp, numpoints * sizeof(vec3_t)); RemoveColinearPoints(neww); return neww; } //end of the function MergeWindings //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *WindingErrorString(void) { return windingerror; } //end of the function WindingErrorString //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int WindingError(winding_t *w) { int i, j; vec_t *p1, *p2; vec_t d, edgedist; vec3_t dir, edgenormal, facenormal; vec_t area; vec_t facedist; if (w->numpoints < 3) { sprintf(windingerror, "winding %i points", w->numpoints); return WE_NOTENOUGHPOINTS; } //end if area = WindingArea(w); if (area < 1) { sprintf(windingerror, "winding %f area", area); return WE_SMALLAREA; } //end if WindingPlane (w, facenormal, &facedist); for (i=0 ; inumpoints ; i++) { p1 = w->p[i]; for (j=0 ; j<3 ; j++) { if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) { sprintf(windingerror, "winding point %d BUGUS_RANGE \'%f %f %f\'", j, p1[0], p1[1], p1[2]); return WE_POINTBOGUSRANGE; } //end if } //end for j = i+1 == w->numpoints ? 0 : i+1; // check the point is on the face plane d = DotProduct (p1, facenormal) - facedist; if (d < -ON_EPSILON || d > ON_EPSILON) { sprintf(windingerror, "winding point %d off plane", i); return WE_POINTOFFPLANE; } //end if // check the edge isnt degenerate p2 = w->p[j]; VectorSubtract (p2, p1, dir); if (VectorLength (dir) < ON_EPSILON) { sprintf(windingerror, "winding degenerate edge %d-%d", i, j); return WE_DEGENERATEEDGE; } //end if CrossProduct (facenormal, dir, edgenormal); VectorNormalize (edgenormal); edgedist = DotProduct (p1, edgenormal); edgedist += ON_EPSILON; // all other points must be on front side for (j=0 ; jnumpoints ; j++) { if (j == i) continue; d = DotProduct (w->p[j], edgenormal); if (d > edgedist) { sprintf(windingerror, "winding non-convex"); return WE_NONCONVEX; } //end if } //end for } //end for return WE_NONE; } //end of the function WindingError //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RemoveEqualPoints(winding_t *w, float epsilon) { int i, nump; vec3_t v; vec3_t p[MAX_POINTS_ON_WINDING]; VectorCopy(w->p[0], p[0]); nump = 1; for (i = 1; i < w->numpoints; i++) { VectorSubtract(w->p[i], p[nump-1], v); if (VectorLength(v) > epsilon) { if (nump >= MAX_POINTS_ON_WINDING) Error("RemoveColinearPoints: MAX_POINTS_ON_WINDING"); VectorCopy (w->p[i], p[nump]); nump++; } //end if } //end for if (nump == w->numpoints) return; w->numpoints = nump; memcpy(w->p, p, nump * sizeof(p[0])); } //end of the function RemoveEqualPoints //=========================================================================== // adds the given point to a winding at the given spot // (for instance when spot is zero then the point is added at position zero) // the original winding is NOT freed // // Parameter: - // Returns: the new winding with the added point // Changes Globals: - //=========================================================================== winding_t *AddWindingPoint(winding_t *w, vec3_t point, int spot) { int i, j; winding_t *neww; if (spot > w->numpoints) { Error("AddWindingPoint: num > w->numpoints"); } //end if if (spot < 0) { Error("AddWindingPoint: num < 0"); } //end if neww = AllocWinding(w->numpoints + 1); neww->numpoints = w->numpoints + 1; for (i = 0, j = 0; i < neww->numpoints; i++) { if (i == spot) { VectorCopy(point, neww->p[i]); } //end if else { VectorCopy(w->p[j], neww->p[i]); j++; } //end else } //end for return neww; } //end of the function AddWindingPoint //=========================================================================== // the position where the new point should be added in the winding is // stored in *spot // // Parameter: - // Returns: true if the point is on the winding // Changes Globals: - //=========================================================================== #define MELT_ON_EPSILON 0.2 int PointOnWinding(winding_t *w, vec3_t normal, float dist, vec3_t point, int *spot) { int i, j; vec3_t v1, v2; vec3_t edgenormal, edgevec; float edgedist, dot; *spot = 0; //the point must be on the winding plane dot = DotProduct(point, normal) - dist; if (dot < -MELT_ON_EPSILON || dot > MELT_ON_EPSILON) return false; // for (i = 0; i < w->numpoints; i++) { j = (i+1) % w->numpoints; //get a plane orthogonal to the winding plane through the edge VectorSubtract(w->p[j], w->p[i], edgevec); CrossProduct(normal, edgevec, edgenormal); VectorNormalize(edgenormal); edgedist = DotProduct(edgenormal, w->p[i]); //point must be not too far from the plane dot = DotProduct(point, edgenormal) - edgedist; if (dot < -MELT_ON_EPSILON || dot > MELT_ON_EPSILON) continue; //vector from first point of winding to the point to test VectorSubtract(point, w->p[i], v1); //vector from second point of winding to the point to test VectorSubtract(point, w->p[j], v2); //if the length of the vector is not larger than 0.5 units then //the point is assumend to be the same as one of the winding points if (VectorNormalize(v1) < 0.5) return false; if (VectorNormalize(v2) < 0.5) return false; //point must be between the two winding points //(the two vectors must be directed towards each other, and on the //same straight line) if (DotProduct(v1, v2) < -0.99) { *spot = i + 1; return true; } //end if } //end for return false; } //end of the function PointOnWinding //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int FindPlaneSeperatingWindings(winding_t *w1, winding_t *w2, vec3_t dir, vec3_t normal, float *dist) { int i, i2, j, j2, n; int sides1[3], sides2[3]; float dist1, dist2, dot, diff; vec3_t normal1, normal2; vec3_t v1, v2; for (i = 0; i < w1->numpoints; i++) { i2 = (i+1) % w1->numpoints; // VectorSubtract(w1->p[i2], w1->p[i], v1); if (VectorLength(v1) < 0.1) { //Log_Write("FindPlaneSeperatingWindings: winding1 with degenerate edge\r\n"); continue; } //end if CrossProduct(v1, dir, normal1); VectorNormalize(normal1); dist1 = DotProduct(normal1, w1->p[i]); // for (j = 0; j < w2->numpoints; j++) { j2 = (j+1) % w2->numpoints; // VectorSubtract(w2->p[j2], w2->p[j], v2); if (VectorLength(v2) < 0.1) { //Log_Write("FindPlaneSeperatingWindings: winding2 with degenerate edge\r\n"); continue; } //end if CrossProduct(v2, dir, normal2); VectorNormalize(normal2); dist2 = DotProduct(normal2, w2->p[j]); // diff = dist1 - dist2; if (diff < -0.1 || diff > 0.1) { dist2 = -dist2; VectorNegate(normal2, normal2); diff = dist1 - dist2; if (diff < -0.1 || diff > 0.1) continue; } //end if //check if the normal vectors are equal for (n = 0; n < 3; n++) { diff = normal1[n] - normal2[n]; if (diff < -0.0001 || diff > 0.0001) break; } //end for if (n != 3) continue; //check on which side of the seperating plane the points of //the first winding are sides1[0] = sides1[1] = sides1[2] = 0; for (n = 0; n < w1->numpoints; n++) { dot = DotProduct(w1->p[n], normal1) - dist1; if (dot > 0.1) sides1[0]++; else if (dot < -0.1) sides1[1]++; else sides1[2]++; } //end for //check on which side of the seperating plane the points of //the second winding are sides2[0] = sides2[1] = sides2[2] = 0; for (n = 0; n < w2->numpoints; n++) { //used normal1 and dist1 (they are equal to normal2 and dist2) dot = DotProduct(w2->p[n], normal1) - dist1; if (dot > 0.1) sides2[0]++; else if (dot < -0.1) sides2[1]++; else sides2[2]++; } //end for //if the first winding has points at both sides if (sides1[0] && sides1[1]) { Log_Write("FindPlaneSeperatingWindings: winding1 non-convex\r\n"); continue; } //end if //if the second winding has points at both sides if (sides2[0] && sides2[1]) { Log_Write("FindPlaneSeperatingWindings: winding2 non-convex\r\n"); continue; } //end if // if ((!sides1[0] && !sides1[1]) || (!sides2[0] && !sides2[1])) { //don't use one of the winding planes as the seperating plane continue; } //end if //the windings must be at different sides of the seperating plane if ((!sides1[0] && !sides2[1]) || (!sides1[1] && !sides2[0])) { VectorCopy(normal1, normal); *dist = dist1; return true; } //end if } //end for } //end for return false; } //end of the function FindPlaneSeperatingWindings //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #define WCONVEX_EPSILON 0.2 int WindingsNonConvex(winding_t *w1, winding_t *w2, vec3_t normal1, vec3_t normal2, float dist1, float dist2) { int i; if (!w1 || !w2) return false; //check if one of the points of face1 is at the back of the plane of face2 for (i = 0; i < w1->numpoints; i++) { if (DotProduct(normal2, w1->p[i]) - dist2 > WCONVEX_EPSILON) return true; } //end for //check if one of the points of face2 is at the back of the plane of face1 for (i = 0; i < w2->numpoints; i++) { if (DotProduct(normal1, w2->p[i]) - dist1 > WCONVEX_EPSILON) return true; } //end for return false; } //end of the function WindingsNonConvex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== /* #define VERTEX_EPSILON 0.5 qboolean EqualVertexes(vec3_t v1, vec3_t v2) { float diff; diff = v1[0] - v2[0]; if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) { diff = v1[1] - v2[1]; if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) { diff = v1[2] - v2[2]; if (diff > -VERTEX_EPSILON && diff < VERTEX_EPSILON) { return true; } //end if } //end if } //end if return false; } //end of the function EqualVertexes #define CONTINUOUS_EPSILON 0.001 winding_t *AAS_MergeWindings(winding_t *w1, winding_t *w2, vec3_t windingnormal) { int n, i, k; vec3_t normal, delta; winding_t *winding, *neww; float dist, dot; int p1, p2; int points[2][64]; int numpoints[2] = {0, 0}; int newnumpoints; int keep[2]; if (!FindPlaneSeperatingWindings(w1, w2, windingnormal, normal, &dist)) return NULL; //for both windings for (n = 0; n < 2; n++) { if (n == 0) winding = w1; else winding = w2; //get the points of the winding which are on the seperating plane for (i = 0; i < winding->numpoints; i++) { dot = DotProduct(winding->p[i], normal) - dist; if (dot > -ON_EPSILON && dot < ON_EPSILON) { //don't allow more than 64 points on the seperating plane if (numpoints[n] >= 64) Error("AAS_MergeWindings: more than 64 points on seperating plane\n"); points[n][numpoints[n]++] = i; } //end if } //end for //there must be at least two points of each winding on the seperating plane if (numpoints[n] < 2) return NULL; } //end for //if the first point of winding1 (which is on the seperating plane) is unequal //to the last point of winding2 (which is on the seperating plane) if (!EqualVertexes(w1->p[points[0][0]], w2->p[points[1][numpoints[1]-1]])) { return NULL; } //end if //if the last point of winding1 (which is on the seperating plane) is unequal //to the first point of winding2 (which is on the seperating plane) if (!EqualVertexes(w1->p[points[0][numpoints[0]-1]], w2->p[points[1][0]])) { return NULL; } //end if // // check slope of connected lines // if the slopes are colinear, the point can be removed // //first point of winding1 which is on the seperating plane p1 = points[0][0]; //point before p1 p2 = (p1 + w1->numpoints - 1) % w1->numpoints; VectorSubtract(w1->p[p1], w1->p[p2], delta); CrossProduct(windingnormal, delta, normal); VectorNormalize(normal, normal); //last point of winding2 which is on the seperating plane p1 = points[1][numpoints[1]-1]; //point after p1 p2 = (p1 + 1) % w2->numpoints; VectorSubtract(w2->p[p2], w2->p[p1], delta); dot = DotProduct(delta, normal); if (dot > CONTINUOUS_EPSILON) return NULL; //merging would create a non-convex polygon keep[0] = (qboolean)(dot < -CONTINUOUS_EPSILON); //first point of winding2 which is on the seperating plane p1 = points[1][0]; //point before p1 p2 = (p1 + w2->numpoints - 1) % w2->numpoints; VectorSubtract(w2->p[p1], w2->p[p2], delta); CrossProduct(windingnormal, delta, normal); VectorNormalize(normal, normal); //last point of winding1 which is on the seperating plane p1 = points[0][numpoints[0]-1]; //point after p1 p2 = (p1 + 1) % w1->numpoints; VectorSubtract(w1->p[p2], w1->p[p1], delta); dot = DotProduct(delta, normal); if (dot > CONTINUOUS_EPSILON) return NULL; //merging would create a non-convex polygon keep[1] = (qboolean)(dot < -CONTINUOUS_EPSILON); //number of points on the new winding newnumpoints = w1->numpoints - numpoints[0] + w2->numpoints - numpoints[1] + 2; //allocate the winding neww = AllocWinding(newnumpoints); neww->numpoints = newnumpoints; //copy all the points k = 0; //for both windings for (n = 0; n < 2; n++) { if (n == 0) winding = w1; else winding = w2; //copy the points of the winding starting with the last point on the //seperating plane and ending before the first point on the seperating plane for (i = points[n][numpoints[n]-1]; i != points[n][0]; i = (i+1)%winding->numpoints) { if (k >= newnumpoints) { Log_Print("numpoints[0] = %d\n", numpoints[0]); Log_Print("numpoints[1] = %d\n", numpoints[1]); Error("AAS_MergeWindings: k = %d >= newnumpoints = %d\n", k, newnumpoints); } //end if VectorCopy(winding->p[i], neww->p[k]); k++; } //end for } //end for RemoveEqualPoints(neww); if (!WindingIsOk(neww, 1)) { Log_Print("AAS_MergeWindings: winding not ok after merging\n"); FreeWinding(neww); return NULL; } //end if return neww; } //end of the function AAS_MergeWindings*/ //#endif //ME ================================================ FILE: code/bspc/l_poly.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ //a winding gives the bounding points of a convex polygon typedef struct { int numpoints; vec3_t p[4]; //variable sized } winding_t; #define MAX_POINTS_ON_WINDING 96 //you can define on_epsilon in the makefile as tighter #ifndef ON_EPSILON #define ON_EPSILON 0.1 #endif //winding errors #define WE_NONE 0 #define WE_NOTENOUGHPOINTS 1 #define WE_SMALLAREA 2 #define WE_POINTBOGUSRANGE 3 #define WE_POINTOFFPLANE 4 #define WE_DEGENERATEEDGE 5 #define WE_NONCONVEX 6 //allocates a winding winding_t *AllocWinding (int points); //returns the area of the winding vec_t WindingArea (winding_t *w); //gives the center of the winding void WindingCenter (winding_t *w, vec3_t center); //clips the given winding to the given plane and gives the front //and back part of the clipped winding void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back); //returns the fragment of the given winding that is on the front //side of the cliping plane. The original is freed. winding_t *ChopWinding (winding_t *in, vec3_t normal, vec_t dist); //returns a copy of the given winding winding_t *CopyWinding (winding_t *w); //returns the reversed winding of the given one winding_t *ReverseWinding (winding_t *w); //returns a base winding for the given plane winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist); //checks the winding for errors void CheckWinding (winding_t *w); //returns the plane normal and dist the winding is in void WindingPlane(winding_t *w, vec3_t normal, vec_t *dist); //removes colinear points from the winding void RemoveColinearPoints(winding_t *w); //returns on which side of the plane the winding is situated int WindingOnPlaneSide(winding_t *w, vec3_t normal, vec_t dist); //frees the winding void FreeWinding(winding_t *w); //gets the bounds of the winding void WindingBounds(winding_t *w, vec3_t mins, vec3_t maxs); //chops the winding with the given plane, the original winding is freed if clipped void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); //prints the winding points on STDOUT void pw(winding_t *w); //try to merge the two windings which are in the given plane //the original windings are undisturbed //the merged winding is returned when merging was possible //NULL is returned otherwise winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, vec3_t planenormal); //brute force winding merging... creates a convex winding out of //the two whatsoever winding_t *MergeWindings(winding_t *w1, winding_t *w2, vec3_t planenormal); //#ifdef ME void ResetWindings(void); //returns the amount of winding memory int WindingMemory(void); int WindingPeakMemory(void); int ActiveWindings(void); //returns the winding error string char *WindingErrorString(void); //returns one of the WE_ flags when the winding has errors int WindingError(winding_t *w); //removes equal points from the winding void RemoveEqualPoints(winding_t *w, float epsilon); //returns a winding with a point added at the given spot to the //given winding, original winding is NOT freed winding_t *AddWindingPoint(winding_t *w, vec3_t point, int spot); //returns true if the point is on one of the winding 'edges' //when the point is on one of the edged the number of the first //point of the edge is stored in 'spot' int PointOnWinding(winding_t *w, vec3_t normal, float dist, vec3_t point, int *spot); //find a plane seperating the two windings //true is returned when the windings area adjacent //the seperating plane normal and distance area stored in 'normal' and 'dist' //this plane will contain both the piece of common edge of the two windings //and the vector 'dir' int FindPlaneSeperatingWindings(winding_t *w1, winding_t *w2, vec3_t dir, vec3_t normal, float *dist); // int WindingsNonConvex(winding_t *w1, winding_t *w2, vec3_t normal1, vec3_t normal2, float dist1, float dist2); //#endif //ME ================================================ FILE: code/bspc/l_qfiles.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #if defined(WIN32)|defined(_WIN32) #include #include #include #else #include #include #include #endif #include "qbsp.h" //file extensions with their type typedef struct qfile_exttype_s { char *extension; int type; } qfile_exttyp_t; qfile_exttyp_t quakefiletypes[] = { {QFILEEXT_UNKNOWN, QFILETYPE_UNKNOWN}, {QFILEEXT_PAK, QFILETYPE_PAK}, {QFILEEXT_PK3, QFILETYPE_PK3}, {QFILEEXT_SIN, QFILETYPE_PAK}, {QFILEEXT_BSP, QFILETYPE_BSP}, {QFILEEXT_MAP, QFILETYPE_MAP}, {QFILEEXT_MDL, QFILETYPE_MDL}, {QFILEEXT_MD2, QFILETYPE_MD2}, {QFILEEXT_MD3, QFILETYPE_MD3}, {QFILEEXT_WAL, QFILETYPE_WAL}, {QFILEEXT_WAV, QFILETYPE_WAV}, {QFILEEXT_AAS, QFILETYPE_AAS}, {NULL, 0} }; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int QuakeFileExtensionType(char *extension) { int i; for (i = 0; quakefiletypes[i].extension; i++) { if (!stricmp(extension, quakefiletypes[i].extension)) { return quakefiletypes[i].type; } //end if } //end for return QFILETYPE_UNKNOWN; } //end of the function QuakeFileExtensionType //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *QuakeFileTypeExtension(int type) { int i; for (i = 0; quakefiletypes[i].extension; i++) { if (quakefiletypes[i].type == type) { return quakefiletypes[i].extension; } //end if } //end for return QFILEEXT_UNKNOWN; } //end of the function QuakeFileExtension //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int QuakeFileType(char *filename) { char ext[_MAX_PATH] = "."; ExtractFileExtension(filename, ext+1); return QuakeFileExtensionType(ext); } //end of the function QuakeFileTypeFromFileName //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== char *StringContains(char *str1, char *str2, int casesensitive) { int len, i, j; len = strlen(str1) - strlen(str2); for (i = 0; i <= len; i++, str1++) { for (j = 0; str2[j]; j++) { if (casesensitive) { if (str1[j] != str2[j]) break; } //end if else { if (toupper(str1[j]) != toupper(str2[j])) break; } //end else } //end for if (!str2[j]) return str1; } //end for return NULL; } //end of the function StringContains //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int FileFilter(char *filter, char *filename, int casesensitive) { char buf[1024]; char *ptr; int i, found; while(*filter) { if (*filter == '*') { filter++; for (i = 0; *filter; i++) { if (*filter == '*' || *filter == '?') break; buf[i] = *filter; filter++; } //end for buf[i] = '\0'; if (strlen(buf)) { ptr = StringContains(filename, buf, casesensitive); if (!ptr) return false; filename = ptr + strlen(buf); } //end if } //end if else if (*filter == '?') { filter++; filename++; } //end else if else if (*filter == '[' && *(filter+1) == '[') { filter++; } //end if else if (*filter == '[') { filter++; found = false; while(*filter && !found) { if (*filter == ']' && *(filter+1) != ']') break; if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) { if (casesensitive) { if (*filename >= *filter && *filename <= *(filter+2)) found = true; } //end if else { if (toupper(*filename) >= toupper(*filter) && toupper(*filename) <= toupper(*(filter+2))) found = true; } //end else filter += 3; } //end if else { if (casesensitive) { if (*filter == *filename) found = true; } //end if else { if (toupper(*filter) == toupper(*filename)) found = true; } //end else filter++; } //end else } //end while if (!found) return false; while(*filter) { if (*filter == ']' && *(filter+1) != ']') break; filter++; } //end while filter++; filename++; } //end else if else { if (casesensitive) { if (*filter != *filename) return false; } //end if else { if (toupper(*filter) != toupper(*filename)) return false; } //end else filter++; filename++; } //end else } //end while return true; } //end of the function FileFilter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== quakefile_t *FindQuakeFilesInZip(char *zipfile, char *filter) { unzFile uf; int err; unz_global_info gi; char filename_inzip[MAX_PATH]; unz_file_info file_info; int i; quakefile_t *qfiles, *lastqf, *qf; uf = unzOpen(zipfile); err = unzGetGlobalInfo(uf, &gi); if (err != UNZ_OK) return NULL; unzGoToFirstFile(uf); qfiles = NULL; lastqf = NULL; for (i = 0; i < gi.number_entry; i++) { err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL,0,NULL,0); if (err != UNZ_OK) break; ConvertPath(filename_inzip); if (FileFilter(filter, filename_inzip, false)) { qf = malloc(sizeof(quakefile_t)); if (!qf) Error("out of memory"); memset(qf, 0, sizeof(quakefile_t)); strcpy(qf->pakfile, zipfile); strcpy(qf->filename, zipfile); strcpy(qf->origname, filename_inzip); qf->zipfile = true; //memcpy( &buildBuffer[i].zipfileinfo, (unz_s*)uf, sizeof(unz_s)); memcpy(&qf->zipinfo, (unz_s*)uf, sizeof(unz_s)); qf->offset = 0; qf->length = file_info.uncompressed_size; qf->type = QuakeFileType(filename_inzip); //add the file ot the list qf->next = NULL; if (lastqf) lastqf->next = qf; else qfiles = qf; lastqf = qf; } //end if unzGoToNextFile(uf); } //end for unzClose(uf); return qfiles; } //end of the function FindQuakeFilesInZip //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== quakefile_t *FindQuakeFilesInPak(char *pakfile, char *filter) { FILE *fp; dpackheader_t packheader; dsinpackfile_t *packfiles; dpackfile_t *idpackfiles; quakefile_t *qfiles, *lastqf, *qf; int numpackdirs, i; qfiles = NULL; lastqf = NULL; //open the pak file fp = fopen(pakfile, "rb"); if (!fp) { Warning("can't open pak file %s", pakfile); return NULL; } //end if //read pak header, check for valid pak id and seek to the dir entries if ((fread(&packheader, 1, sizeof(dpackheader_t), fp) != sizeof(dpackheader_t)) || (packheader.ident != IDPAKHEADER && packheader.ident != SINPAKHEADER) || (fseek(fp, LittleLong(packheader.dirofs), SEEK_SET)) ) { fclose(fp); Warning("invalid pak file %s", pakfile); return NULL; } //end if //if it is a pak file from id software if (packheader.ident == IDPAKHEADER) { //number of dir entries in the pak file numpackdirs = LittleLong(packheader.dirlen) / sizeof(dpackfile_t); idpackfiles = (dpackfile_t *) malloc(numpackdirs * sizeof(dpackfile_t)); if (!idpackfiles) Error("out of memory"); //read the dir entry if (fread(idpackfiles, sizeof(dpackfile_t), numpackdirs, fp) != numpackdirs) { fclose(fp); free(idpackfiles); Warning("can't read the Quake pak file dir entries from %s", pakfile); return NULL; } //end if fclose(fp); //convert to sin pack files packfiles = (dsinpackfile_t *) malloc(numpackdirs * sizeof(dsinpackfile_t)); if (!packfiles) Error("out of memory"); for (i = 0; i < numpackdirs; i++) { strcpy(packfiles[i].name, idpackfiles[i].name); packfiles[i].filepos = LittleLong(idpackfiles[i].filepos); packfiles[i].filelen = LittleLong(idpackfiles[i].filelen); } //end for free(idpackfiles); } //end if else //its a Sin pack file { //number of dir entries in the pak file numpackdirs = LittleLong(packheader.dirlen) / sizeof(dsinpackfile_t); packfiles = (dsinpackfile_t *) malloc(numpackdirs * sizeof(dsinpackfile_t)); if (!packfiles) Error("out of memory"); //read the dir entry if (fread(packfiles, sizeof(dsinpackfile_t), numpackdirs, fp) != numpackdirs) { fclose(fp); free(packfiles); Warning("can't read the Sin pak file dir entries from %s", pakfile); return NULL; } //end if fclose(fp); for (i = 0; i < numpackdirs; i++) { packfiles[i].filepos = LittleLong(packfiles[i].filepos); packfiles[i].filelen = LittleLong(packfiles[i].filelen); } //end for } //end else // for (i = 0; i < numpackdirs; i++) { ConvertPath(packfiles[i].name); if (FileFilter(filter, packfiles[i].name, false)) { qf = malloc(sizeof(quakefile_t)); if (!qf) Error("out of memory"); memset(qf, 0, sizeof(quakefile_t)); strcpy(qf->pakfile, pakfile); strcpy(qf->filename, pakfile); strcpy(qf->origname, packfiles[i].name); qf->zipfile = false; qf->offset = packfiles[i].filepos; qf->length = packfiles[i].filelen; qf->type = QuakeFileType(packfiles[i].name); //add the file ot the list qf->next = NULL; if (lastqf) lastqf->next = qf; else qfiles = qf; lastqf = qf; } //end if } //end for free(packfiles); return qfiles; } //end of the function FindQuakeFilesInPak //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== quakefile_t *FindQuakeFilesWithPakFilter(char *pakfilter, char *filter) { #if defined(WIN32)|defined(_WIN32) WIN32_FIND_DATA filedata; HWND handle; struct _stat statbuf; #else glob_t globbuf; struct stat statbuf; int j; #endif quakefile_t *qfiles, *lastqf, *qf; char pakfile[_MAX_PATH], filename[_MAX_PATH], *str; int done; qfiles = NULL; lastqf = NULL; if (pakfilter && strlen(pakfilter)) { #if defined(WIN32)|defined(_WIN32) handle = FindFirstFile(pakfilter, &filedata); done = (handle == INVALID_HANDLE_VALUE); while(!done) { _splitpath(pakfilter, pakfile, NULL, NULL, NULL); _splitpath(pakfilter, NULL, &pakfile[strlen(pakfile)], NULL, NULL); AppendPathSeperator(pakfile, _MAX_PATH); strcat(pakfile, filedata.cFileName); _stat(pakfile, &statbuf); #else glob(pakfilter, 0, NULL, &globbuf); for (j = 0; j < globbuf.gl_pathc; j++) { strcpy(pakfile, globbuf.gl_pathv[j]); stat(pakfile, &statbuf); #endif //if the file with .pak or .pk3 is a folder if (statbuf.st_mode & S_IFDIR) { strcpy(filename, pakfilter); AppendPathSeperator(filename, _MAX_PATH); strcat(filename, filter); qf = FindQuakeFilesWithPakFilter(NULL, filename); if (lastqf) lastqf->next = qf; else qfiles = qf; lastqf = qf; while(lastqf->next) lastqf = lastqf->next; } //end if else { #if defined(WIN32)|defined(_WIN32) str = StringContains(pakfile, ".pk3", false); #else str = StringContains(pakfile, ".pk3", true); #endif if (str && str == pakfile + strlen(pakfile) - strlen(".pk3")) { qf = FindQuakeFilesInZip(pakfile, filter); } //end if else { qf = FindQuakeFilesInPak(pakfile, filter); } //end else // if (qf) { if (lastqf) lastqf->next = qf; else qfiles = qf; lastqf = qf; while(lastqf->next) lastqf = lastqf->next; } //end if } //end else // #if defined(WIN32)|defined(_WIN32) //find the next file done = !FindNextFile(handle, &filedata); } //end while #else } //end for globfree(&globbuf); #endif } //end if else { #if defined(WIN32)|defined(_WIN32) handle = FindFirstFile(filter, &filedata); done = (handle == INVALID_HANDLE_VALUE); while(!done) { _splitpath(filter, filename, NULL, NULL, NULL); _splitpath(filter, NULL, &filename[strlen(filename)], NULL, NULL); AppendPathSeperator(filename, _MAX_PATH); strcat(filename, filedata.cFileName); #else glob(filter, 0, NULL, &globbuf); for (j = 0; j < globbuf.gl_pathc; j++) { strcpy(filename, globbuf.gl_pathv[j]); #endif // qf = malloc(sizeof(quakefile_t)); if (!qf) Error("out of memory"); memset(qf, 0, sizeof(quakefile_t)); strcpy(qf->pakfile, ""); strcpy(qf->filename, filename); strcpy(qf->origname, filename); qf->offset = 0; qf->length = 0; qf->type = QuakeFileType(filename); //add the file ot the list qf->next = NULL; if (lastqf) lastqf->next = qf; else qfiles = qf; lastqf = qf; #if defined(WIN32)|defined(_WIN32) //find the next file done = !FindNextFile(handle, &filedata); } //end while #else } //end for globfree(&globbuf); #endif } //end else return qfiles; } //end of the function FindQuakeFilesWithPakFilter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== quakefile_t *FindQuakeFiles(char *filter) { char *str; char newfilter[_MAX_PATH]; char pakfilter[_MAX_PATH]; char filefilter[_MAX_PATH]; strcpy(newfilter, filter); ConvertPath(newfilter); strcpy(pakfilter, newfilter); str = StringContains(pakfilter, ".pak", false); if (!str) str = StringContains(pakfilter, ".pk3", false); if (str) { str += strlen(".pak"); if (*str) { *str++ = '\0'; while(*str == '\\' || *str == '/') str++; strcpy(filefilter, str); return FindQuakeFilesWithPakFilter(pakfilter, filefilter); } //end if } //end else return FindQuakeFilesWithPakFilter(NULL, newfilter); } //end of the function FindQuakeFiles //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int LoadQuakeFile(quakefile_t *qf, void **bufferptr) { FILE *fp; void *buffer; int length; unzFile zf; if (qf->zipfile) { //open the zip file zf = unzOpen(qf->pakfile); //set the file pointer qf->zipinfo.file = ((unz_s *) zf)->file; //open the Quake file in the zip file unzOpenCurrentFile(&qf->zipinfo); //allocate memory for the buffer length = qf->length; buffer = GetMemory(length+1); //read the Quake file from the zip file length = unzReadCurrentFile(&qf->zipinfo, buffer, length); //close the Quake file in the zip file unzCloseCurrentFile(&qf->zipinfo); //close the zip file unzClose(zf); *bufferptr = buffer; return length; } //end if else { fp = SafeOpenRead(qf->filename); if (qf->offset) fseek(fp, qf->offset, SEEK_SET); length = qf->length; if (!length) length = Q_filelength(fp); buffer = GetMemory(length+1); ((char *)buffer)[length] = 0; SafeRead(fp, buffer, length); fclose(fp); *bufferptr = buffer; return length; } //end else } //end of the function LoadQuakeFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int ReadQuakeFile(quakefile_t *qf, void *buffer, int offset, int length) { FILE *fp; int read; unzFile zf; char tmpbuf[1024]; if (qf->zipfile) { //open the zip file zf = unzOpen(qf->pakfile); //set the file pointer qf->zipinfo.file = ((unz_s *) zf)->file; //open the Quake file in the zip file unzOpenCurrentFile(&qf->zipinfo); // while(offset > 0) { read = offset; if (read > sizeof(tmpbuf)) read = sizeof(tmpbuf); unzReadCurrentFile(&qf->zipinfo, tmpbuf, read); offset -= read; } //end while //read the Quake file from the zip file length = unzReadCurrentFile(&qf->zipinfo, buffer, length); //close the Quake file in the zip file unzCloseCurrentFile(&qf->zipinfo); //close the zip file unzClose(zf); return length; } //end if else { fp = SafeOpenRead(qf->filename); if (qf->offset) fseek(fp, qf->offset, SEEK_SET); if (offset) fseek(fp, offset, SEEK_CUR); SafeRead(fp, buffer, length); fclose(fp); return length; } //end else } //end of the function ReadQuakeFile ================================================ FILE: code/bspc/l_qfiles.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "../qcommon/unzip.h" #define QFILETYPE_UNKNOWN 0x8000 #define QFILETYPE_PAK 0x0001 #define QFILETYPE_PK3 0x0002 #define QFILETYPE_BSP 0x0004 #define QFILETYPE_MAP 0x0008 #define QFILETYPE_MDL 0x0010 #define QFILETYPE_MD2 0x0020 #define QFILETYPE_MD3 0x0040 #define QFILETYPE_WAL 0x0080 #define QFILETYPE_WAV 0x0100 #define QFILETYPE_AAS 0x4000 #define QFILEEXT_UNKNOWN "" #define QFILEEXT_PAK ".PAK" #define QFILEEXT_PK3 ".PK3" #define QFILEEXT_SIN ".SIN" #define QFILEEXT_BSP ".BSP" #define QFILEEXT_MAP ".MAP" #define QFILEEXT_MDL ".MDL" #define QFILEEXT_MD2 ".MD2" #define QFILEEXT_MD3 ".MD3" #define QFILEEXT_WAL ".WAL" #define QFILEEXT_WAV ".WAV" #define QFILEEXT_AAS ".AAS" //maximum path length #ifndef _MAX_PATH #define _MAX_PATH 1024 #endif //for Sin packs #define MAX_PAK_FILENAME_LENGTH 120 #define SINPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'S') typedef struct { char name[MAX_PAK_FILENAME_LENGTH]; int filepos, filelen; } dsinpackfile_t; typedef struct quakefile_s { char pakfile[_MAX_PATH]; char filename[_MAX_PATH]; char origname[_MAX_PATH]; int zipfile; int type; int offset; int length; unz_s zipinfo; struct quakefile_s *next; } quakefile_t; //returns the file extension for the given type char *QuakeFileTypeExtension(int type); //returns the file type for the given extension int QuakeFileExtensionType(char *extension); //return the Quake file type for the given file int QuakeFileType(char *filename); //returns true if the filename complies to the filter int FileFilter(char *filter, char *filename, int casesensitive); //find Quake files using the given filter quakefile_t *FindQuakeFiles(char *filter); //load the given Quake file, returns the length of the file int LoadQuakeFile(quakefile_t *qf, void **bufferptr); //read part of a Quake file into the buffer int ReadQuakeFile(quakefile_t *qf, void *buffer, int offset, int length); ================================================ FILE: code/bspc/l_threads.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "l_cmd.h" #include "l_threads.h" #include "l_log.h" #include "l_mem.h" #define MAX_THREADS 64 //#define THREAD_DEBUG int dispatch; int workcount; int oldf; qboolean pacifier; qboolean threaded; void (*workfunction) (int); //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int GetThreadWork(void) { int r; int f; ThreadLock(); if (dispatch == workcount) { ThreadUnlock (); return -1; } f = 10*dispatch / workcount; if (f != oldf) { oldf = f; if (pacifier) printf ("%i...", f); } //end if r = dispatch; dispatch++; ThreadUnlock (); return r; } //end of the function GetThreadWork //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadWorkerFunction(int threadnum) { int work; while(1) { work = GetThreadWork (); if (work == -1) break; //printf ("thread %i, work %i\n", threadnum, work); workfunction(work); } //end while } //end of the function ThreadWorkerFunction //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)) { if (numthreads == -1) ThreadSetDefault (); workfunction = func; RunThreadsOn (workcnt, showpacifier, ThreadWorkerFunction); } //end of the function RunThreadsOnIndividual //=================================================================== // // WIN32 // //=================================================================== #if defined(WIN32) || defined(_WIN32) #define USED #include typedef struct thread_s { HANDLE handle; int threadid; int id; struct thread_s *next; } thread_t; thread_t *firstthread; thread_t *lastthread; int currentnumthreads; int currentthreadid; int numthreads = 1; CRITICAL_SECTION crit; HANDLE semaphore; static int enter; static int numwaitingthreads = 0; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetDefault(void) { SYSTEM_INFO info; if (numthreads == -1) // not set manually { GetSystemInfo (&info); numthreads = info.dwNumberOfProcessors; if (numthreads < 1 || numthreads > 32) numthreads = 1; } //end if qprintf ("%i threads\n", numthreads); } //end of the function ThreadSetDefault //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadLock(void) { if (!threaded) { Error("ThreadLock: !threaded"); return; } //end if EnterCriticalSection(&crit); if (enter) Error("Recursive ThreadLock\n"); enter = 1; } //end of the function ThreadLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadUnlock (void) { if (!threaded) { Error("ThreadUnlock: !threaded"); return; } //end if if (!enter) Error("ThreadUnlock without lock\n"); enter = 0; LeaveCriticalSection(&crit); } //end of the function ThreadUnlock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetupLock(void) { Log_Print("Win32 multi-threading\n"); InitializeCriticalSection(&crit); threaded = true; //Stupid me... forgot this!!! currentnumthreads = 0; currentthreadid = 0; } //end of the function ThreadInitLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadShutdownLock(void) { DeleteCriticalSection(&crit); threaded = false; //Stupid me... forgot this!!! } //end of the function ThreadShutdownLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetupSemaphore(void) { semaphore = CreateSemaphore(NULL, 0, 99999999, "bspc"); } //end of the function ThreadSetupSemaphore //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadShutdownSemaphore(void) { } //end of the function ThreadShutdownSemaphore //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSemaphoreWait(void) { WaitForSingleObject(semaphore, INFINITE); } //end of the function ThreadSemaphoreWait //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSemaphoreIncrease(int count) { ReleaseSemaphore(semaphore, count, NULL); } //end of the function ThreadSemaphoreIncrease //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) { int threadid[MAX_THREADS]; HANDLE threadhandle[MAX_THREADS]; int i; int start, end; Log_Print("Win32 multi-threading\n"); start = I_FloatTime (); dispatch = 0; workcount = workcnt; oldf = -1; pacifier = showpacifier; threaded = true; if (numthreads == -1) ThreadSetDefault (); if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; // // run threads in parallel // InitializeCriticalSection (&crit); numwaitingthreads = 0; if (numthreads == 1) { // use same thread func (0); } //end if else { // printf("starting %d threads\n", numthreads); for (i = 0; i < numthreads; i++) { threadhandle[i] = CreateThread( NULL, // LPSECURITY_ATTRIBUTES lpsa, 0, // DWORD cbStack, (LPTHREAD_START_ROUTINE)func, // LPTHREAD_START_ROUTINE lpStartAddr, (LPVOID)i, // LPVOID lpvThreadParm, 0, // DWORD fdwCreate, &threadid[i]); // printf("started thread %d\n", i); } //end for for (i = 0; i < numthreads; i++) WaitForSingleObject (threadhandle[i], INFINITE); } //end else DeleteCriticalSection (&crit); threaded = false; end = I_FloatTime (); if (pacifier) printf (" (%i)\n", end-start); } //end of the function RunThreadsOn //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AddThread(void (*func)(int)) { thread_t *thread; if (numthreads == 1) { if (currentnumthreads >= numthreads) return; currentnumthreads++; func(-1); currentnumthreads--; } //end if else { ThreadLock(); if (currentnumthreads >= numthreads) { ThreadUnlock(); return; } //end if //allocate new thread thread = GetMemory(sizeof(thread_t)); if (!thread) Error("can't allocate memory for thread\n"); // thread->threadid = currentthreadid; thread->handle = CreateThread( NULL, // LPSECURITY_ATTRIBUTES lpsa, 0, // DWORD cbStack, (LPTHREAD_START_ROUTINE)func, // LPTHREAD_START_ROUTINE lpStartAddr, (LPVOID) thread->threadid, // LPVOID lpvThreadParm, 0, // DWORD fdwCreate, &thread->id); //add the thread to the end of the list thread->next = NULL; if (lastthread) lastthread->next = thread; else firstthread = thread; lastthread = thread; // #ifdef THREAD_DEBUG qprintf("added thread with id %d\n", thread->threadid); #endif //THREAD_DEBUG // currentnumthreads++; currentthreadid++; // ThreadUnlock(); } //end else } //end of the function AddThread //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RemoveThread(int threadid) { thread_t *thread, *last; //if a single thread if (threadid == -1) return; // ThreadLock(); last = NULL; for (thread = firstthread; thread; thread = thread->next) { if (thread->threadid == threadid) { if (last) last->next = thread->next; else firstthread = thread->next; if (!thread->next) lastthread = last; // FreeMemory(thread); currentnumthreads--; #ifdef THREAD_DEBUG qprintf("removed thread with id %d\n", threadid); #endif //THREAD_DEBUG break; } //end if last = thread; } //end if if (!thread) Error("couldn't find thread with id %d", threadid); ThreadUnlock(); } //end of the function RemoveThread //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void WaitForAllThreadsFinished(void) { HANDLE handle; ThreadLock(); while(firstthread) { handle = firstthread->handle; ThreadUnlock(); WaitForSingleObject(handle, INFINITE); ThreadLock(); } //end while ThreadUnlock(); } //end of the function WaitForAllThreadsFinished //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int GetNumThreads(void) { return currentnumthreads; } //end of the function GetNumThreads #endif //=================================================================== // // OSF1 // //=================================================================== #if defined(__osf__) #define USED #include typedef struct thread_s { pthread_t thread; int threadid; int id; struct thread_s *next; } thread_t; thread_t *firstthread; thread_t *lastthread; int currentnumthreads; int currentthreadid; int numthreads = 1; pthread_mutex_t my_mutex; pthread_attr_t attrib; static int enter; static int numwaitingthreads = 0; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetDefault(void) { if (numthreads == -1) // not set manually { numthreads = 1; } //end if qprintf("%i threads\n", numthreads); } //end of the function ThreadSetDefault //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadLock(void) { if (!threaded) { Error("ThreadLock: !threaded"); return; } //end if if (my_mutex) { pthread_mutex_lock(my_mutex); } //end if if (enter) Error("Recursive ThreadLock\n"); enter = 1; } //end of the function ThreadLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadUnlock(void) { if (!threaded) { Error("ThreadUnlock: !threaded"); return; } //end if if (!enter) Error("ThreadUnlock without lock\n"); enter = 0; if (my_mutex) { pthread_mutex_unlock(my_mutex); } //end if } //end of the function ThreadUnlock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetupLock(void) { pthread_mutexattr_t mattrib; Log_Print("pthread multi-threading\n"); if (!my_mutex) { my_mutex = GetMemory(sizeof(*my_mutex)); if (pthread_mutexattr_create (&mattrib) == -1) Error ("pthread_mutex_attr_create failed"); if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) Error ("pthread_mutexattr_setkind_np failed"); if (pthread_mutex_init (my_mutex, mattrib) == -1) Error ("pthread_mutex_init failed"); } if (pthread_attr_create (&attrib) == -1) Error ("pthread_attr_create failed"); if (pthread_attr_setstacksize (&attrib, 0x100000) == -1) Error ("pthread_attr_setstacksize failed"); threaded = true; currentnumthreads = 0; currentthreadid = 0; } //end of the function ThreadInitLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadShutdownLock(void) { threaded = false; } //end of the function ThreadShutdownLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) { int i; pthread_t work_threads[MAX_THREADS]; pthread_addr_t status; pthread_attr_t attrib; pthread_mutexattr_t mattrib; int start, end; Log_Print("pthread multi-threading\n"); start = I_FloatTime (); dispatch = 0; workcount = workcnt; oldf = -1; pacifier = showpacifier; threaded = true; if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; if (pacifier) setbuf (stdout, NULL); if (!my_mutex) { my_mutex = GetMemory(sizeof(*my_mutex)); if (pthread_mutexattr_create (&mattrib) == -1) Error ("pthread_mutex_attr_create failed"); if (pthread_mutexattr_setkind_np (&mattrib, MUTEX_FAST_NP) == -1) Error ("pthread_mutexattr_setkind_np failed"); if (pthread_mutex_init (my_mutex, mattrib) == -1) Error ("pthread_mutex_init failed"); } if (pthread_attr_create (&attrib) == -1) Error ("pthread_attr_create failed"); if (pthread_attr_setstacksize (&attrib, 0x100000) == -1) Error ("pthread_attr_setstacksize failed"); for (i=0 ; i= numthreads) return; currentnumthreads++; func(-1); currentnumthreads--; } //end if else { ThreadLock(); if (currentnumthreads >= numthreads) { ThreadUnlock(); return; } //end if //allocate new thread thread = GetMemory(sizeof(thread_t)); if (!thread) Error("can't allocate memory for thread\n"); // thread->threadid = currentthreadid; if (pthread_create(&thread->thread, attrib, (pthread_startroutine_t)func, (pthread_addr_t)thread->threadid) == -1) Error ("pthread_create failed"); //add the thread to the end of the list thread->next = NULL; if (lastthread) lastthread->next = thread; else firstthread = thread; lastthread = thread; // #ifdef THREAD_DEBUG qprintf("added thread with id %d\n", thread->threadid); #endif //THREAD_DEBUG // currentnumthreads++; currentthreadid++; // ThreadUnlock(); } //end else } //end of the function AddThread //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RemoveThread(int threadid) { thread_t *thread, *last; //if a single thread if (threadid == -1) return; // ThreadLock(); last = NULL; for (thread = firstthread; thread; thread = thread->next) { if (thread->threadid == threadid) { if (last) last->next = thread->next; else firstthread = thread->next; if (!thread->next) lastthread = last; // FreeMemory(thread); currentnumthreads--; #ifdef THREAD_DEBUG qprintf("removed thread with id %d\n", threadid); #endif //THREAD_DEBUG break; } //end if last = thread; } //end if if (!thread) Error("couldn't find thread with id %d", threadid); ThreadUnlock(); } //end of the function RemoveThread //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void WaitForAllThreadsFinished(void) { pthread_t *thread; pthread_addr_t status; ThreadLock(); while(firstthread) { thread = &firstthread->thread; ThreadUnlock(); if (pthread_join(*thread, &status) == -1) Error("pthread_join failed"); ThreadLock(); } //end while ThreadUnlock(); } //end of the function WaitForAllThreadsFinished //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int GetNumThreads(void) { return currentnumthreads; } //end of the function GetNumThreads #endif //=================================================================== // // LINUX // //=================================================================== #if defined(LINUX) #define USED #include #include typedef struct thread_s { pthread_t thread; int threadid; int id; struct thread_s *next; } thread_t; thread_t *firstthread; thread_t *lastthread; int currentnumthreads; int currentthreadid; int numthreads = 1; pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_attr_t attrib; sem_t semaphore; static int enter; static int numwaitingthreads = 0; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetDefault(void) { if (numthreads == -1) // not set manually { numthreads = 1; } //end if qprintf("%i threads\n", numthreads); } //end of the function ThreadSetDefault //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadLock(void) { if (!threaded) { Error("ThreadLock: !threaded"); return; } //end if pthread_mutex_lock(&my_mutex); if (enter) Error("Recursive ThreadLock\n"); enter = 1; } //end of the function ThreadLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadUnlock(void) { if (!threaded) { Error("ThreadUnlock: !threaded"); return; } //end if if (!enter) Error("ThreadUnlock without lock\n"); enter = 0; pthread_mutex_unlock(&my_mutex); } //end of the function ThreadUnlock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetupLock(void) { pthread_mutexattr_t mattrib; Log_Print("pthread multi-threading\n"); threaded = true; currentnumthreads = 0; currentthreadid = 0; } //end of the function ThreadInitLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadShutdownLock(void) { threaded = false; } //end of the function ThreadShutdownLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetupSemaphore(void) { sem_init(&semaphore, 0, 0); } //end of the function ThreadSetupSemaphore //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadShutdownSemaphore(void) { sem_destroy(&semaphore); } //end of the function ThreadShutdownSemaphore //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSemaphoreWait(void) { sem_wait(&semaphore); } //end of the function ThreadSemaphoreWait //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSemaphoreIncrease(int count) { int i; for (i = 0; i < count; i++) { sem_post(&semaphore); } //end for } //end of the function ThreadSemaphoreIncrease //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) { int i; pthread_t work_threads[MAX_THREADS]; void *pthread_return; pthread_attr_t attrib; pthread_mutexattr_t mattrib; int start, end; Log_Print("pthread multi-threading\n"); start = I_FloatTime (); dispatch = 0; workcount = workcnt; oldf = -1; pacifier = showpacifier; threaded = true; if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; if (pacifier) setbuf (stdout, NULL); for (i=0 ; i= numthreads) return; currentnumthreads++; func(-1); currentnumthreads--; } //end if else { ThreadLock(); if (currentnumthreads >= numthreads) { ThreadUnlock(); return; } //end if //allocate new thread thread = GetMemory(sizeof(thread_t)); if (!thread) Error("can't allocate memory for thread\n"); // thread->threadid = currentthreadid; if (pthread_create(&thread->thread, NULL, (void *)func, (void *)thread->threadid) == -1) Error ("pthread_create failed"); //add the thread to the end of the list thread->next = NULL; if (lastthread) lastthread->next = thread; else firstthread = thread; lastthread = thread; // #ifdef THREAD_DEBUG qprintf("added thread with id %d\n", thread->threadid); #endif //THREAD_DEBUG // currentnumthreads++; currentthreadid++; // ThreadUnlock(); } //end else } //end of the function AddThread //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RemoveThread(int threadid) { thread_t *thread, *last; //if a single thread if (threadid == -1) return; // ThreadLock(); last = NULL; for (thread = firstthread; thread; thread = thread->next) { if (thread->threadid == threadid) { if (last) last->next = thread->next; else firstthread = thread->next; if (!thread->next) lastthread = last; // FreeMemory(thread); currentnumthreads--; #ifdef THREAD_DEBUG qprintf("removed thread with id %d\n", threadid); #endif //THREAD_DEBUG break; } //end if last = thread; } //end if if (!thread) Error("couldn't find thread with id %d", threadid); ThreadUnlock(); } //end of the function RemoveThread //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void WaitForAllThreadsFinished(void) { pthread_t *thread; void *pthread_return; ThreadLock(); while(firstthread) { thread = &firstthread->thread; ThreadUnlock(); if (pthread_join(*thread, &pthread_return) == -1) Error("pthread_join failed"); ThreadLock(); } //end while ThreadUnlock(); } //end of the function WaitForAllThreadsFinished //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int GetNumThreads(void) { return currentnumthreads; } //end of the function GetNumThreads #endif //LINUX //=================================================================== // // IRIX // //=================================================================== #ifdef _MIPS_ISA #define USED #include #include #include #include typedef struct thread_s { int threadid; int id; struct thread_s *next; } thread_t; thread_t *firstthread; thread_t *lastthread; int currentnumthreads; int currentthreadid; int numthreads = 1; static int enter; static int numwaitingthreads = 0; abilock_t lck; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetDefault (void) { if (numthreads == -1) numthreads = prctl(PR_MAXPPROCS); printf ("%i threads\n", numthreads); //@@ usconfig (CONF_INITUSERS, numthreads); } //end of the function ThreadSetDefault //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadLock (void) { spin_lock (&lck); } //end of the function ThreadLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadUnlock (void) { release_lock(&lck); } //end of the function ThreadUnlock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetupLock(void) { init_lock (&lck); Log_Print("IRIX multi-threading\n"); threaded = true; currentnumthreads = 0; currentthreadid = 0; } //end of the function ThreadInitLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadShutdownLock(void) { threaded = false; } //end of the function ThreadShutdownLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)) { int i; int pid[MAX_THREADS]; int start, end; start = I_FloatTime (); dispatch = 0; workcount = workcnt; oldf = -1; pacifier = showpacifier; threaded = true; if (numthreads < 1 || numthreads > MAX_THREADS) numthreads = 1; if (pacifier) setbuf (stdout, NULL); init_lock (&lck); for (i=0 ; i= numthreads) return; currentnumthreads++; func(-1); currentnumthreads--; } //end if else { ThreadLock(); if (currentnumthreads >= numthreads) { ThreadUnlock(); return; } //end if //allocate new thread thread = GetMemory(sizeof(thread_t)); if (!thread) Error("can't allocate memory for thread\n"); // thread->threadid = currentthreadid; thread->id = sprocsp ( (void (*)(void *, size_t))func, PR_SALL, (void *)thread->threadid, NULL, 0x100000); if (thread->id == -1) { perror ("sproc"); Error ("sproc failed"); } //add the thread to the end of the list thread->next = NULL; if (lastthread) lastthread->next = thread; else firstthread = thread; lastthread = thread; // #ifdef THREAD_DEBUG qprintf("added thread with id %d\n", thread->threadid); #endif //THREAD_DEBUG // currentnumthreads++; currentthreadid++; // ThreadUnlock(); } //end else } //end of the function AddThread //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RemoveThread(int threadid) { thread_t *thread, *last; //if a single thread if (threadid == -1) return; // ThreadLock(); last = NULL; for (thread = firstthread; thread; thread = thread->next) { if (thread->threadid == threadid) { if (last) last->next = thread->next; else firstthread = thread->next; if (!thread->next) lastthread = last; // FreeMemory(thread); currentnumthreads--; #ifdef THREAD_DEBUG qprintf("removed thread with id %d\n", threadid); #endif //THREAD_DEBUG break; } //end if last = thread; } //end if if (!thread) Error("couldn't find thread with id %d", threadid); ThreadUnlock(); } //end of the function RemoveThread //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void WaitForAllThreadsFinished(void) { ThreadLock(); while(firstthread) { ThreadUnlock(); //wait (NULL); ThreadLock(); } //end while ThreadUnlock(); } //end of the function WaitForAllThreadsFinished //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int GetNumThreads(void) { return currentnumthreads; } //end of the function GetNumThreads #endif //_MIPS_ISA //======================================================================= // // SINGLE THREAD // //======================================================================= #ifndef USED int numthreads = 1; int currentnumthreads = 0; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetDefault(void) { numthreads = 1; } //end of the function ThreadSetDefault //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadLock(void) { } //end of the function ThreadLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadUnlock(void) { } //end of the function ThreadUnlock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetupLock(void) { Log_Print("no multi-threading\n"); } //end of the function ThreadInitLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadShutdownLock(void) { } //end of the function ThreadShutdownLock //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSetupSemaphore(void) { } //end of the function ThreadSetupSemaphore //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadShutdownSemaphore(void) { } //end of the function ThreadShutdownSemaphore //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSemaphoreWait(void) { } //end of the function ThreadSemaphoreWait //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ThreadSemaphoreIncrease(int count) { } //end of the function ThreadSemaphoreIncrease //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RunThreadsOn(int workcnt, qboolean showpacifier, void(*func)(int)) { int start, end; Log_Print("no multi-threading\n"); dispatch = 0; workcount = workcnt; oldf = -1; pacifier = showpacifier; start = I_FloatTime (); #ifdef NeXT if (pacifier) setbuf (stdout, NULL); #endif func(0); end = I_FloatTime (); if (pacifier) printf (" (%i)\n", end-start); } //end of the function RunThreadsOn //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AddThread(void (*func)(int)) { if (currentnumthreads >= numthreads) return; currentnumthreads++; func(-1); currentnumthreads--; } //end of the function AddThread //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RemoveThread(int threadid) { } //end of the function RemoveThread //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void WaitForAllThreadsFinished(void) { } //end of the function WaitForAllThreadsFinished //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int GetNumThreads(void) { return currentnumthreads; } //end of the function GetNumThreads #endif //USED ================================================ FILE: code/bspc/l_threads.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ extern int numthreads; void ThreadSetDefault (void); int GetThreadWork (void); void RunThreadsOnIndividual (int workcnt, qboolean showpacifier, void(*func)(int)); void RunThreadsOn (int workcnt, qboolean showpacifier, void(*func)(int)); //mutex void ThreadSetupLock(void); void ThreadShutdownLock(void); void ThreadLock (void); void ThreadUnlock (void); //semaphore void ThreadSetupSemaphore(void); void ThreadShutdownSemaphore(void); void ThreadSemaphoreWait(void); void ThreadSemaphoreIncrease(int count); //add/remove threads void AddThread(void (*func)(int)); void RemoveThread(int threadid); void WaitForAllThreadsFinished(void); int GetNumThreads(void); ================================================ FILE: code/bspc/l_utils.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ //#ifndef BOTLIB //#define BOTLIB //#endif //BOTLIB #ifdef BOTLIB #include "q_shared.h" #include "qfiles.h" #include "botlib.h" #include "l_log.h" #include "l_libvar.h" #include "l_memory.h" //#include "l_utils.h" #include "be_interface.h" #else //BOTLIB #include "qbsp.h" #include "l_mem.h" #endif //BOTLIB #ifdef BOTLIB //======================================================================== // // Parameter: - // Returns: - // Changes Globals: - //======================================================================== void Vector2Angles(vec3_t value1, vec3_t angles) { float forward; float yaw, pitch; if (value1[1] == 0 && value1[0] == 0) { yaw = 0; if (value1[2] > 0) pitch = 90; else pitch = 270; } //end if else { yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); if (yaw < 0) yaw += 360; forward = sqrt (value1[0]*value1[0] + value1[1]*value1[1]); pitch = (int) (atan2(value1[2], forward) * 180 / M_PI); if (pitch < 0) pitch += 360; } //end else angles[PITCH] = -pitch; angles[YAW] = yaw; angles[ROLL] = 0; } //end of the function Vector2Angles #endif //BOTLIB //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ConvertPath(char *path) { while(*path) { if (*path == '/' || *path == '\\') *path = PATHSEPERATOR_CHAR; path++; } //end while } //end of the function ConvertPath //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AppendPathSeperator(char *path, int length) { int pathlen = strlen(path); if (strlen(path) && length-pathlen > 1 && path[pathlen-1] != '/' && path[pathlen-1] != '\\') { path[pathlen] = PATHSEPERATOR_CHAR; path[pathlen+1] = '\0'; } //end if } //end of the function AppenPathSeperator #if 0 //=========================================================================== // returns pointer to file handle // sets offset to and length of 'filename' in the pak file // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean FindFileInPak(char *pakfile, char *filename, foundfile_t *file) { FILE *fp; dpackheader_t packheader; dpackfile_t *packfiles; int numdirs, i; char path[MAX_PATH]; //open the pak file fp = fopen(pakfile, "rb"); if (!fp) { return false; } //end if //read pak header, check for valid pak id and seek to the dir entries if ((fread(&packheader, 1, sizeof(dpackheader_t), fp) != sizeof(dpackheader_t)) || (packheader.ident != IDPAKHEADER) || (fseek(fp, LittleLong(packheader.dirofs), SEEK_SET)) ) { fclose(fp); return false; } //end if //number of dir entries in the pak file numdirs = LittleLong(packheader.dirlen) / sizeof(dpackfile_t); packfiles = (dpackfile_t *) GetMemory(numdirs * sizeof(dpackfile_t)); //read the dir entry if (fread(packfiles, sizeof(dpackfile_t), numdirs, fp) != numdirs) { fclose(fp); FreeMemory(packfiles); return false; } //end if fclose(fp); // strcpy(path, filename); ConvertPath(path); //find the dir entry in the pak file for (i = 0; i < numdirs; i++) { //convert the dir entry name ConvertPath(packfiles[i].name); //compare the dir entry name with the filename if (Q_strcasecmp(packfiles[i].name, path) == 0) { strcpy(file->filename, pakfile); file->offset = LittleLong(packfiles[i].filepos); file->length = LittleLong(packfiles[i].filelen); FreeMemory(packfiles); return true; } //end if } //end for FreeMemory(packfiles); return false; } //end of the function FindFileInPak //=========================================================================== // find a Quake2 file // returns full path in 'filename' // sets offset and length of the file // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean FindQuakeFile2(char *basedir, char *gamedir, char *filename, foundfile_t *file) { int dir, i; //NOTE: 3 is necessary (LCC bug???) char gamedirs[3][MAX_PATH] = {"","",""}; char filedir[MAX_PATH] = ""; // if (gamedir) strncpy(gamedirs[0], gamedir, MAX_PATH); strncpy(gamedirs[1], "baseq2", MAX_PATH); // //find the file in the two game directories for (dir = 0; dir < 2; dir++) { //check if the file is in a directory filedir[0] = 0; if (basedir && strlen(basedir)) { strncpy(filedir, basedir, MAX_PATH); AppendPathSeperator(filedir, MAX_PATH); } //end if if (strlen(gamedirs[dir])) { strncat(filedir, gamedirs[dir], MAX_PATH - strlen(filedir)); AppendPathSeperator(filedir, MAX_PATH); } //end if strncat(filedir, filename, MAX_PATH - strlen(filedir)); ConvertPath(filedir); Log_Write("accessing %s", filedir); if (!access(filedir, 0x04)) { strcpy(file->filename, filedir); file->length = 0; file->offset = 0; return true; } //end if //check if the file is in a pak?.pak for (i = 0; i < 10; i++) { filedir[0] = 0; if (basedir && strlen(basedir)) { strncpy(filedir, basedir, MAX_PATH); AppendPathSeperator(filedir, MAX_PATH); } //end if if (strlen(gamedirs[dir])) { strncat(filedir, gamedirs[dir], MAX_PATH - strlen(filedir)); AppendPathSeperator(filedir, MAX_PATH); } //end if sprintf(&filedir[strlen(filedir)], "pak%d.pak\0", i); if (!access(filedir, 0x04)) { Log_Write("searching %s in %s", filename, filedir); if (FindFileInPak(filedir, filename, file)) return true; } //end if } //end for } //end for file->offset = 0; file->length = 0; return false; } //end of the function FindQuakeFile2 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef BOTLIB qboolean FindQuakeFile(char *filename, foundfile_t *file) { return FindQuakeFile2(LibVarGetString("basedir"), LibVarGetString("gamedir"), filename, file); } //end of the function FindQuakeFile #else //BOTLIB qboolean FindQuakeFile(char *basedir, char *gamedir, char *filename, foundfile_t *file) { return FindQuakeFile2(basedir, gamedir, filename, file); } //end of the function FindQuakeFile #endif //BOTLIB #endif ================================================ FILE: code/bspc/l_utils.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #ifndef MAX_PATH #define MAX_PATH 64 #endif #ifndef PATH_SEPERATORSTR #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) #define PATHSEPERATOR_STR "\\" #else #define PATHSEPERATOR_STR "/" #endif #endif #ifndef PATH_SEPERATORCHAR #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) #define PATHSEPERATOR_CHAR '\\' #else #define PATHSEPERATOR_CHAR '/' #endif #endif //random in the range [0, 1] #define random() ((rand () & 0x7fff) / ((float)0x7fff)) //random in the range [-1, 1] #define crandom() (2.0 * (random() - 0.5)) //min and max #define Maximum(x,y) (x > y ? x : y) #define Minimum(x,y) (x < y ? x : y) //absolute value #define FloatAbs(x) (*(float *) &((* (int *) &(x)) & 0x7FFFFFFF)) #define IntAbs(x) (~(x)) //coordinates #define _X 0 #define _Y 1 #define _Z 2 typedef struct foundfile_s { int offset; int length; char filename[MAX_PATH]; //screw LCC, array must be at end of struct } foundfile_t; void Vector2Angles(vec3_t value1, vec3_t angles); //set the correct path seperators void ConvertPath(char *path); //append a path seperator to the given path not exceeding the length void AppendPathSeperator(char *path, int length); //find a file in a pak file qboolean FindFileInPak(char *pakfile, char *filename, foundfile_t *file); //find a quake file #ifdef BOTLIB qboolean FindQuakeFile(char *filename, foundfile_t *file); #else //BOTLIB qboolean FindQuakeFile(char *basedir, char *gamedir, char *filename, foundfile_t *file); #endif //BOTLIB ================================================ FILE: code/bspc/lcc.mak ================================================ # # Makefile for the BSPC tool for the Gladiator Bot # Intended for LCC-Win32 # CC=lcc CFLAGS=-DC_ONLY -o OBJS= _files.obj\ aas_areamerging.obj\ aas_cfg.obj\ aas_create.obj\ aas_edgemelting.obj\ aas_facemerging.obj\ aas_file.obj\ aas_gsubdiv.obj\ aas_map.obj\ aas_prunenodes.obj\ aas_store.obj\ brushbsp.obj\ bspc.obj\ csg.obj\ faces.obj\ glfile.obj\ l_bsp_hl.obj\ l_bsp_q1.obj\ l_bsp_q2.obj\ l_bsp_sin.obj\ l_cmd.obj\ l_log.obj\ l_math.obj\ l_mem.obj\ l_poly.obj\ l_qfiles.obj\ l_script.obj\ l_threads.obj\ l_utils.obj\ leakfile.obj\ map.obj\ map_hl.obj\ map_q1.obj\ map_q2.obj\ map_q2_new.obj\ map_sin.obj\ nodraw.obj\ portals.obj\ prtfile.obj\ textures.obj\ tree.obj\ writebsp.obj all: bspc.exe bspc.exe: $(OBJS) lcclnk clean: del *.obj bspc.exe %.obj: %.c $(CC) $(CFLAGS) $< ================================================ FILE: code/bspc/leakfile.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" /* ============================================================================== LEAF FILE GENERATION Save out name.line for qe3 to read ============================================================================== */ /* ============= LeakFile Finds the shortest possible chain of portals that leads from the outside leaf to a specifically occupied leaf ============= */ void LeakFile (tree_t *tree) { vec3_t mid; FILE *linefile; char filename[1024]; node_t *node; int count; if (!tree->outside_node.occupied) return; qprintf ("--- LeakFile ---\n"); // // write the points to the file // sprintf (filename, "%s.lin", source); qprintf ("%s\n", filename); linefile = fopen (filename, "w"); if (!linefile) Error ("Couldn't open %s\n", filename); count = 0; node = &tree->outside_node; while (node->occupied > 1) { int next; portal_t *p, *nextportal; node_t *nextnode; int s; // find the best portal exit next = node->occupied; for (p=node->portals ; p ; p = p->next[!s]) { s = (p->nodes[0] == node); if (p->nodes[s]->occupied && p->nodes[s]->occupied < next) { nextportal = p; nextnode = p->nodes[s]; next = nextnode->occupied; } } node = nextnode; WindingCenter (nextportal->winding, mid); fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); count++; } // add the occupant center GetVectorForKey (node->occupant, "origin", mid); fprintf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); qprintf ("%5i point linefile\n", count+1); fclose (linefile); } ================================================ FILE: code/bspc/linux-i386.mak ================================================ # # Makefile for the BSPC tool for the Gladiator Bot # Intended for gcc/Linux # ARCH=i386 CC=gcc BASE_CFLAGS=-Dstricmp=strcasecmp #use these cflags to optimize it CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \ -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ -malign-jumps=2 -malign-functions=2 -DLINUX -DBSPC #use these when debugging #CFLAGS=$(BASE_CFLAGS) -g LDFLAGS=-ldl -lm -lpthread DO_CC=$(CC) $(CFLAGS) -o $@ -c $< ############################################################################# # SETUP AND BUILD BSPC ############################################################################# .c.o: $(DO_CC) GAME_OBJS = \ _files.o\ aas_areamerging.o\ aas_cfg.o\ aas_create.o\ aas_edgemelting.o\ aas_facemerging.o\ aas_file.o\ aas_gsubdiv.o\ aas_map.o\ aas_prunenodes.o\ aas_store.o\ be_aas_bspc.o\ ../botlib/be_aas_bspq3.o\ ../botlib/be_aas_cluster.o\ ../botlib/be_aas_move.o\ ../botlib/be_aas_optimize.o\ ../botlib/be_aas_reach.o\ ../botlib/be_aas_sample.o\ brushbsp.o\ bspc.o\ ../qcommon/cm_load.o\ ../qcommon/cm_patch.o\ ../qcommon/cm_test.o\ ../qcommon/cm_trace.o\ csg.o\ glfile.o\ l_bsp_ent.o\ l_bsp_hl.o\ l_bsp_q1.o\ l_bsp_q2.o\ l_bsp_q3.o\ l_bsp_sin.o\ l_cmd.o\ ../botlib/l_libvar.o\ l_log.o\ l_math.o\ l_mem.o\ l_poly.o\ ../botlib/l_precomp.o\ l_qfiles.o\ ../botlib/l_script.o\ ../botlib/l_struct.o\ l_threads.o\ l_utils.o\ leakfile.o\ map.o\ map_hl.o\ map_q1.o\ map_q2.o\ map_q3.o\ map_sin.o\ ../qcommon/md4.o\ nodraw.o\ portals.o\ tetrahedron.o\ textures.o\ tree.o\ ../qcommon/unzip.o bspc$(ARCH) : $(GAME_OBJS) $(CC) $(CFLAGS) -o $@ $(GAME_OBJS) $(LDFLAGS) ############################################################################# # MISC ############################################################################# clean: -rm -f $(GAME_OBJS) depend: gcc -MM $(GAME_OBJS:.o=.c) install: cp bspci386 .. # # From "make depend" # ================================================ FILE: code/bspc/map.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "l_bsp_hl.h" #include "l_bsp_q1.h" #include "l_bsp_q2.h" #include "l_bsp_q3.h" #include "l_bsp_sin.h" #include "l_mem.h" #include "../botlib/aasfile.h" //aas_bbox_t #include "aas_store.h" //AAS_MAX_BBOXES #include "aas_cfg.h" #define Sign(x) (x < 0 ? 1 : 0) int nummapbrushes; mapbrush_t mapbrushes[MAX_MAPFILE_BRUSHES]; int nummapbrushsides; side_t brushsides[MAX_MAPFILE_BRUSHSIDES]; brush_texture_t side_brushtextures[MAX_MAPFILE_BRUSHSIDES]; int nummapplanes; plane_t mapplanes[MAX_MAPFILE_PLANES]; int mapplaneusers[MAX_MAPFILE_PLANES]; #define PLANE_HASHES 1024 plane_t *planehash[PLANE_HASHES]; vec3_t map_mins, map_maxs; #ifdef SIN textureref_t side_newrefs[MAX_MAPFILE_BRUSHSIDES]; #endif map_texinfo_t map_texinfo[MAX_MAPFILE_TEXINFO]; int map_numtexinfo; int loadedmaptype; //loaded map type // undefine to make plane finding use linear sort #define USE_HASHING int c_boxbevels; int c_edgebevels; int c_areaportals; int c_clipbrushes; int c_squattbrushes; int c_writtenbrushes; /* ============================================================================= PLANE FINDING ============================================================================= */ //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int PlaneSignBits(vec3_t normal) { int i, signbits; signbits = 0; for (i = 2; i >= 0; i--) { signbits = (signbits << 1) + Sign(normal[i]); } //end for return signbits; } //end of the function PlaneSignBits //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int PlaneTypeForNormal(vec3_t normal) { vec_t ax, ay, az; // NOTE: should these have an epsilon around 1.0? if (normal[0] == 1.0 || normal[0] == -1.0) return PLANE_X; if (normal[1] == 1.0 || normal[1] == -1.0) return PLANE_Y; if (normal[2] == 1.0 || normal[2] == -1.0) return PLANE_Z; ax = fabs(normal[0]); ay = fabs(normal[1]); az = fabs(normal[2]); if (ax >= ay && ax >= az) return PLANE_ANYX; if (ay >= ax && ay >= az) return PLANE_ANYY; return PLANE_ANYZ; } //end of the function PlaneTypeForNormal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== //ME NOTE: changed from 0.00001 #define NORMAL_EPSILON 0.0001 //ME NOTE: changed from 0.01 #define DIST_EPSILON 0.02 qboolean PlaneEqual(plane_t *p, vec3_t normal, vec_t dist) { #if 1 if ( fabs(p->normal[0] - normal[0]) < NORMAL_EPSILON && fabs(p->normal[1] - normal[1]) < NORMAL_EPSILON && fabs(p->normal[2] - normal[2]) < NORMAL_EPSILON && fabs(p->dist - dist) < DIST_EPSILON ) return true; #else if (p->normal[0] == normal[0] && p->normal[1] == normal[1] && p->normal[2] == normal[2] && p->dist == dist) return true; #endif return false; } //end of the function PlaneEqual //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AddPlaneToHash(plane_t *p) { int hash; hash = (int)fabs(p->dist) / 8; hash &= (PLANE_HASHES-1); p->hash_chain = planehash[hash]; planehash[hash] = p; } //end of the function AddPlaneToHash //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int CreateNewFloatPlane (vec3_t normal, vec_t dist) { plane_t *p, temp; if (VectorLength(normal) < 0.5) Error ("FloatPlane: bad normal"); // create a new plane if (nummapplanes+2 > MAX_MAPFILE_PLANES) Error ("MAX_MAPFILE_PLANES"); p = &mapplanes[nummapplanes]; VectorCopy (normal, p->normal); p->dist = dist; p->type = (p+1)->type = PlaneTypeForNormal (p->normal); p->signbits = PlaneSignBits(p->normal); VectorSubtract (vec3_origin, normal, (p+1)->normal); (p+1)->dist = -dist; (p+1)->signbits = PlaneSignBits((p+1)->normal); nummapplanes += 2; // allways put axial planes facing positive first if (p->type < 3) { if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) { // flip order temp = *p; *p = *(p+1); *(p+1) = temp; AddPlaneToHash (p); AddPlaneToHash (p+1); return nummapplanes - 1; } } AddPlaneToHash (p); AddPlaneToHash (p+1); return nummapplanes - 2; } //end of the function CreateNewFloatPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SnapVector(vec3_t normal) { int i; for (i=0 ; i<3 ; i++) { if ( fabs(normal[i] - 1) < NORMAL_EPSILON ) { VectorClear (normal); normal[i] = 1; break; } if ( fabs(normal[i] - -1) < NORMAL_EPSILON ) { VectorClear (normal); normal[i] = -1; break; } } } //end of the function SnapVector //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SnapPlane(vec3_t normal, vec_t *dist) { SnapVector(normal); if (fabs(*dist-Q_rint(*dist)) < DIST_EPSILON) *dist = Q_rint(*dist); } //end of the function SnapPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifndef USE_HASHING int FindFloatPlane(vec3_t normal, vec_t dist) { int i; plane_t *p; SnapPlane(normal, &dist); for (i = 0, p = mapplanes; i < nummapplanes; i++, p++) { if (PlaneEqual (p, normal, dist)) { mapplaneusers[i]++; return i; } //end if } //end for i = CreateNewFloatPlane (normal, dist); mapplaneusers[i]++; return i; } //end of the function FindFloatPlane #else int FindFloatPlane (vec3_t normal, vec_t dist) { int i; plane_t *p; int hash, h; SnapPlane (normal, &dist); hash = (int)fabs(dist) / 8; hash &= (PLANE_HASHES-1); // search the border bins as well for (i = -1; i <= 1; i++) { h = (hash+i)&(PLANE_HASHES-1); for (p = planehash[h]; p; p = p->hash_chain) { if (PlaneEqual(p, normal, dist)) { mapplaneusers[p-mapplanes]++; return p - mapplanes; } //end if } //end for } //end for i = CreateNewFloatPlane (normal, dist); mapplaneusers[i]++; return i; } //end of the function FindFloatPlane #endif //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int PlaneFromPoints (int *p0, int *p1, int *p2) { vec3_t t1, t2, normal; vec_t dist; VectorSubtract (p0, p1, t1); VectorSubtract (p2, p1, t2); CrossProduct (t1, t2, normal); VectorNormalize (normal); dist = DotProduct (p0, normal); return FindFloatPlane (normal, dist); } //end of the function PlaneFromPoints //=========================================================================== // Adds any additional planes necessary to allow the brush to be expanded // against axial bounding boxes // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AddBrushBevels (mapbrush_t *b) { int axis, dir; int i, j, k, l, order; side_t sidetemp; brush_texture_t tdtemp; #ifdef SIN textureref_t trtemp; #endif side_t *s, *s2; vec3_t normal; float dist; winding_t *w, *w2; vec3_t vec, vec2; float d; // // add the axial planes // order = 0; for (axis=0 ; axis <3 ; axis++) { for (dir=-1 ; dir <= 1 ; dir+=2, order++) { // see if the plane is allready present for (i=0, s=b->original_sides ; inumsides ; i++,s++) { if (mapplanes[s->planenum].normal[axis] == dir) break; } if (i == b->numsides) { // add a new side if (nummapbrushsides == MAX_MAP_BRUSHSIDES) Error ("MAX_MAP_BRUSHSIDES"); nummapbrushsides++; b->numsides++; VectorClear (normal); normal[axis] = dir; if (dir == 1) dist = b->maxs[axis]; else dist = -b->mins[axis]; s->planenum = FindFloatPlane (normal, dist); s->texinfo = b->original_sides[0].texinfo; #ifdef SIN s->lightinfo = b->original_sides[0].lightinfo; #endif s->contents = b->original_sides[0].contents; s->flags |= SFL_BEVEL; c_boxbevels++; } // if the plane is not in it canonical order, swap it if (i != order) { sidetemp = b->original_sides[order]; b->original_sides[order] = b->original_sides[i]; b->original_sides[i] = sidetemp; j = b->original_sides - brushsides; tdtemp = side_brushtextures[j+order]; side_brushtextures[j+order] = side_brushtextures[j+i]; side_brushtextures[j+i] = tdtemp; #ifdef SIN trtemp = side_newrefs[j+order]; side_newrefs[j+order] = side_newrefs[j+i]; side_newrefs[j+i] = trtemp; #endif } } } // // add the edge bevels // if (b->numsides == 6) return; // pure axial // test the non-axial plane edges for (i=6 ; inumsides ; i++) { s = b->original_sides + i; w = s->winding; if (!w) continue; for (j=0 ; jnumpoints ; j++) { k = (j+1)%w->numpoints; VectorSubtract (w->p[j], w->p[k], vec); if (VectorNormalize (vec) < 0.5) continue; SnapVector (vec); for (k=0 ; k<3 ; k++) if ( vec[k] == -1 || vec[k] == 1) break; // axial if (k != 3) continue; // only test non-axial edges // try the six possible slanted axials from this edge for (axis=0 ; axis <3 ; axis++) { for (dir=-1 ; dir <= 1 ; dir+=2) { // construct a plane VectorClear (vec2); vec2[axis] = dir; CrossProduct (vec, vec2, normal); if (VectorNormalize (normal) < 0.5) continue; dist = DotProduct (w->p[j], normal); // if all the points on all the sides are // behind this plane, it is a proper edge bevel for (k=0 ; knumsides ; k++) { // if this plane has allready been used, skip it if (PlaneEqual (&mapplanes[b->original_sides[k].planenum] , normal, dist) ) break; w2 = b->original_sides[k].winding; if (!w2) continue; for (l=0 ; lnumpoints ; l++) { d = DotProduct (w2->p[l], normal) - dist; if (d > 0.1) break; // point in front } if (l != w2->numpoints) break; } if (k != b->numsides) continue; // wasn't part of the outer hull // add this plane if (nummapbrushsides == MAX_MAP_BRUSHSIDES) Error ("MAX_MAP_BRUSHSIDES"); nummapbrushsides++; s2 = &b->original_sides[b->numsides]; s2->planenum = FindFloatPlane (normal, dist); s2->texinfo = b->original_sides[0].texinfo; #ifdef SIN s2->lightinfo = b->original_sides[0].lightinfo; #endif s2->contents = b->original_sides[0].contents; s2->flags |= SFL_BEVEL; c_edgebevels++; b->numsides++; } } } } } //end of the function AddBrushBevels //=========================================================================== // creates windigs for sides and mins / maxs for the brush // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean MakeBrushWindings(mapbrush_t *ob) { int i, j; winding_t *w; side_t *side; plane_t *plane; ClearBounds (ob->mins, ob->maxs); for (i = 0; i < ob->numsides; i++) { plane = &mapplanes[ob->original_sides[i].planenum]; w = BaseWindingForPlane(plane->normal, plane->dist); for (j = 0; j numsides && w; j++) { if (i == j) continue; if (ob->original_sides[j].flags & SFL_BEVEL) continue; plane = &mapplanes[ob->original_sides[j].planenum^1]; ChopWindingInPlace(&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); } side = &ob->original_sides[i]; side->winding = w; if (w) { side->flags |= SFL_VISIBLE; for (j = 0; j < w->numpoints; j++) AddPointToBounds (w->p[j], ob->mins, ob->maxs); } } for (i = 0; i < 3; i++) { //IDBUG: all the indexes into the mins and maxs were zero (not using i) if (ob->mins[i] < -MAX_MAP_BOUNDS || ob->maxs[i] > MAX_MAP_BOUNDS) { Log_Print("entity %i, brush %i: bounds out of range\n", ob->entitynum, ob->brushnum); ob->numsides = 0; //remove the brush break; } //end if if (ob->mins[i] > MAX_MAP_BOUNDS || ob->maxs[i] < -MAX_MAP_BOUNDS) { Log_Print("entity %i, brush %i: no visible sides on brush\n", ob->entitynum, ob->brushnum); ob->numsides = 0; //remove the brush break; } //end if } //end for return true; } //end of the function MakeBrushWindings //=========================================================================== // FIXME: currently doesn't mark all bevels // NOTE: when one brush bevel is found the remaining sides of the brush // are bevels as well (when the brush isn't expanded for AAS :)) // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void MarkBrushBevels(mapbrush_t *brush) { int i; int we; side_t *s; //check all the sides of the brush for (i = 0; i < brush->numsides; i++) { s = brush->original_sides + i; //if the side has no winding if (!s->winding) { Log_Write("MarkBrushBevels: brush %d no winding", brush->brushnum); s->flags |= SFL_BEVEL; } //end if //if the winding is tiny else if (WindingIsTiny(s->winding)) { s->flags |= SFL_BEVEL; Log_Write("MarkBrushBevels: brush %d tiny winding", brush->brushnum); } //end else if //if the winding has errors else { we = WindingError(s->winding); if (we == WE_NOTENOUGHPOINTS || we == WE_SMALLAREA || we == WE_POINTBOGUSRANGE // || we == WE_NONCONVEX ) { Log_Write("MarkBrushBevels: brush %d %s", brush->brushnum, WindingErrorString()); s->flags |= SFL_BEVEL; } //end else if } //end else if (s->flags & SFL_BEVEL) { s->flags &= ~SFL_VISIBLE; //if the side has a valid plane if (s->planenum > 0 && s->planenum < nummapplanes) { //if it is an axial plane if (mapplanes[s->planenum].type < 3) c_boxbevels++; else c_edgebevels++; } //end if } //end if } //end for } //end of the function MarkBrushBevels //=========================================================================== // returns true if the map brush already exists // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int BrushExists(mapbrush_t *brush) { int i, s1, s2; side_t *side1, *side2; mapbrush_t *brush1, *brush2; for (i = 0; i < nummapbrushes; i++) { brush1 = brush; brush2 = &mapbrushes[i]; //compare the brushes if (brush1->entitynum != brush2->entitynum) continue; //if (brush1->contents != brush2->contents) continue; if (brush1->numsides != brush2->numsides) continue; for (s1 = 0; s1 < brush1->numsides; s1++) { side1 = brush1->original_sides + s1; // for (s2 = 0; s2 < brush2->numsides; s2++) { side2 = brush2->original_sides + s2; // if ((side1->planenum & ~1) == (side2->planenum & ~1) // && side1->texinfo == side2->texinfo // && side1->contents == side2->contents // && side1->surf == side2->surf ) break; } //end if if (s2 >= brush2->numsides) break; } //end for if (s1 >= brush1->numsides) return true; } //end for return false; } //end of the function BrushExists //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WriteMapBrush(FILE *fp, mapbrush_t *brush, vec3_t origin) { int sn, rotate, shift[2], sv, tv, planenum, p1, i, j; float scale[2], originshift[2], ang1, ang2, newdist; vec3_t vecs[2], axis[2]; map_texinfo_t *ti; winding_t *w; side_t *s; plane_t *plane; if (noliquids) { if (brush->contents & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { return true; } //end if } //end if //if the brush has no contents if (!brush->contents) return true; //print the leading { if (fprintf(fp, " { //brush %d\n", brush->brushnum) < 0) return false; //write brush sides for (sn = 0; sn < brush->numsides; sn++) { s = brush->original_sides + sn; //don't write out bevels if (!(s->flags & SFL_BEVEL)) { //if the entity has an origin set if (origin[0] || origin[1] || origin[2]) { newdist = mapplanes[s->planenum].dist + DotProduct(mapplanes[s->planenum].normal, origin); planenum = FindFloatPlane(mapplanes[s->planenum].normal, newdist); } //end if else { planenum = s->planenum; } //end else //always take the first plane, then flip the points if necesary plane = &mapplanes[planenum & ~1]; w = BaseWindingForPlane(plane->normal, plane->dist); // for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { if (fabs(w->p[i][j]) < 0.2) w->p[i][j] = 0; else if (fabs((int)w->p[i][j] - w->p[i][j]) < 0.3) w->p[i][j] = (int) w->p[i][j]; //w->p[i][j] = (int) (w->p[i][j] + 0.2); } //end for } //end for //three non-colinear points to define the plane if (planenum & 1) p1 = 1; else p1 = 0; if (fprintf(fp," ( %5i %5i %5i ) ", (int)w->p[p1][0], (int)w->p[p1][1], (int)w->p[p1][2]) < 0) return false; if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[!p1][0], (int)w->p[!p1][1], (int)w->p[!p1][2]) < 0) return false; if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]) < 0) return false; //free the winding FreeWinding(w); // if (s->texinfo == TEXINFO_NODE) { if (brush->contents & CONTENTS_PLAYERCLIP) { //player clip if (loadedmaptype == MAPTYPE_SIN) { if (fprintf(fp, "generic/misc/clip 0 0 0 1 1") < 0) return false; } //end if else if (loadedmaptype == MAPTYPE_QUAKE2) { //FIXME: don't always use e1u1 if (fprintf(fp, "e1u1/clip 0 0 0 1 1") < 0) return false; } //end else else if (loadedmaptype == MAPTYPE_QUAKE3) { if (fprintf(fp, "e1u1/clip 0 0 0 1 1") < 0) return false; } //end else if else { if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false; } //end else } //end if else if (brush->contents == CONTENTS_MONSTERCLIP) { //monster clip if (loadedmaptype == MAPTYPE_SIN) { if (fprintf(fp, "generic/misc/monster 0 0 0 1 1") < 0) return false; } //end if else if (loadedmaptype == MAPTYPE_QUAKE2) { if (fprintf(fp, "e1u1/clip_mon 0 0 0 1 1") < 0) return false; } //end else else { if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false; } //end else } //end else else { if (fprintf(fp, "clip 0 0 0 1 1") < 0) return false; Log_Write("brush->contents = %d\n", brush->contents); } //end else } //end if else if (loadedmaptype == MAPTYPE_SIN && s->texinfo == 0) { if (brush->contents & CONTENTS_DUMMYFENCE) { if (fprintf(fp, "generic/misc/fence 0 0 0 1 1") < 0) return false; } //end if else if (brush->contents & CONTENTS_MIST) { if (fprintf(fp, "generic/misc/volumetric_base 0 0 0 1 1") < 0) return false; } //end if else //unknown so far { if (fprintf(fp, "generic/misc/red 0 0 0 1 1") < 0) return false; } //end else } //end if else if (loadedmaptype == MAPTYPE_QUAKE3) { //always use the same texture if (fprintf(fp, "e2u3/floor1_2 0 0 0 1 1 1 0 0") < 0) return false; } //end else if else { //* ti = &map_texinfo[s->texinfo]; //the scaling of the texture scale[0] = 1 / VectorNormalize2(ti->vecs[0], vecs[0]); scale[1] = 1 / VectorNormalize2(ti->vecs[1], vecs[1]); // TextureAxisFromPlane(plane, axis[0], axis[1]); //calculate texture shift done by entity origin originshift[0] = DotProduct(origin, axis[0]); originshift[1] = DotProduct(origin, axis[1]); //the texture shift without origin shift shift[0] = ti->vecs[0][3] - originshift[0]; shift[1] = ti->vecs[1][3] - originshift[1]; // if (axis[0][0]) sv = 0; else if (axis[0][1]) sv = 1; else sv = 2; if (axis[1][0]) tv = 0; else if (axis[1][1]) tv = 1; else tv = 2; //calculate rotation of texture if (vecs[0][tv] == 0) ang1 = vecs[0][sv] > 0 ? 90.0 : -90.0; else ang1 = atan2(vecs[0][sv], vecs[0][tv]) * 180 / Q_PI; if (ang1 < 0) ang1 += 360; if (ang1 >= 360) ang1 -= 360; if (axis[0][tv] == 0) ang2 = axis[0][sv] > 0 ? 90.0 : -90.0; else ang2 = atan2(axis[0][sv], axis[0][tv]) * 180 / Q_PI; if (ang2 < 0) ang2 += 360; if (ang2 >= 360) ang2 -= 360; rotate = ang2 - ang1; if (rotate < 0) rotate += 360; if (rotate >= 360) rotate -= 360; //write the texture info if (fprintf(fp, "%s %d %d %d", ti->texture, shift[0], shift[1], rotate) < 0) return false; if (fabs(scale[0] - ((int) scale[0])) < 0.001) { if (fprintf(fp, " %d", (int) scale[0]) < 0) return false; } //end if else { if (fprintf(fp, " %4f", scale[0]) < 0) return false; } //end if if (fabs(scale[1] - ((int) scale[1])) < 0.001) { if (fprintf(fp, " %d", (int) scale[1]) < 0) return false; } //end if else { if (fprintf(fp, " %4f", scale[1]) < 0) return false; } //end else //write the extra brush side info if (loadedmaptype == MAPTYPE_QUAKE2) { if (fprintf(fp, " %ld %ld %ld", s->contents, ti->flags, ti->value) < 0) return false; } //end if //*/ } //end else if (fprintf(fp, "\n") < 0) return false; } //end if } //end if if (fprintf(fp, " }\n") < 0) return false; c_writtenbrushes++; return true; } //end of the function WriteMapBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WriteOriginBrush(FILE *fp, vec3_t origin) { vec3_t normal; float dist; int i, s; winding_t *w; if (fprintf(fp, " {\n") < 0) return false; // for (i = 0; i < 3; i++) { for (s = -1; s <= 1; s += 2) { // VectorClear(normal); normal[i] = s; dist = origin[i] * s + 16; // w = BaseWindingForPlane(normal, dist); //three non-colinear points to define the plane if (fprintf(fp," ( %5i %5i %5i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]) < 0) return false; if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]) < 0) return false; if (fprintf(fp,"( %5i %5i %5i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]) < 0) return false; //free the winding FreeWinding(w); //write origin texture: // CONTENTS_ORIGIN = 16777216 // SURF_NODRAW = 128 if (loadedmaptype == MAPTYPE_SIN) { if (fprintf(fp, "generic/misc/origin 0 0 0 1 1") < 0) return false; } //end if else if (loadedmaptype == MAPTYPE_HALFLIFE) { if (fprintf(fp, "origin 0 0 0 1 1") < 0) return false; } //end if else { if (fprintf(fp, "e1u1/origin 0 0 0 1 1") < 0) return false; } //end else //Quake2 extra brush side info if (loadedmaptype == MAPTYPE_QUAKE2) { //if (fprintf(fp, " 16777216 128 0") < 0) return false; } //end if if (fprintf(fp, "\n") < 0) return false; } //end for } //end for if (fprintf(fp, " }\n") < 0) return false; c_writtenbrushes++; return true; } //end of the function WriteOriginBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== mapbrush_t *GetAreaPortalBrush(entity_t *mapent) { int portalnum, bn; mapbrush_t *brush; //the area portal number portalnum = mapent->areaportalnum; //find the area portal brush in the world brushes for (bn = 0; bn < nummapbrushes && portalnum; bn++) { brush = &mapbrushes[bn]; //must be in world entity if (brush->entitynum == 0) { if (brush->contents & CONTENTS_AREAPORTAL) { portalnum--; } //end if } //end if } //end for if (bn < nummapbrushes) { return brush; } //end if else { Log_Print("area portal %d brush not found\n", mapent->areaportalnum); return NULL; } //end else } //end of the function GetAreaPortalBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WriteMapFileSafe(FILE *fp) { char key[1024], value[1024]; int i, bn, entitybrushes; epair_t *ep; mapbrush_t *brush; entity_t *mapent; //vec3_t vec_origin = {0, 0, 0}; // if (fprintf(fp,"//=====================================================\n" "//\n" "// map file created with BSPC "BSPC_VERSION"\n" "//\n" "// BSPC is designed to decompile material in which you own the copyright\n" "// or have obtained permission to decompile from the copyright owner. Unless\n" "// you own the copyright or have permission to decompile from the copyright\n" "// owner, you may be violating copyright law and be subject to payment of\n" "// damages and other remedies. If you are uncertain about your rights, contact\n" "// your legal advisor.\n" "//\n") < 0) return false; if (loadedmaptype == MAPTYPE_SIN) { if (fprintf(fp, "// generic/misc/red is used for unknown textures\n") < 0) return false; } //end if if (fprintf(fp,"//\n" "//=====================================================\n") < 0) return false; //write out all the entities for (i = 0; i < num_entities; i++) { mapent = &entities[i]; if (!mapent->epairs) { continue; } //end if if (fprintf(fp, "{\n") < 0) return false; // if (loadedmaptype == MAPTYPE_QUAKE3) { if (!stricmp(ValueForKey(mapent, "classname"), "light")) { SetKeyValue(mapent, "light", "10000"); } //end if } //end if //write epairs for (ep = mapent->epairs; ep; ep = ep->next) { strcpy(key, ep->key); StripTrailing (key); strcpy(value, ep->value); StripTrailing(value); // if (loadedmaptype == MAPTYPE_QUAKE2 || loadedmaptype == MAPTYPE_SIN) { //don't write an origin for BSP models if (mapent->modelnum >= 0 && !strcmp(key, "origin")) continue; } //end if //don't write BSP model numbers if (mapent->modelnum >= 0 && !strcmp(key, "model") && value[0] == '*') continue; // if (fprintf(fp, " \"%s\" \"%s\"\n", key, value) < 0) return false; } //end for // if (ValueForKey(mapent, "origin")) GetVectorForKey(mapent, "origin", mapent->origin); else mapent->origin[0] = mapent->origin[1] = mapent->origin[2] = 0; //if this is an area portal entity if (!strcmp("func_areaportal", ValueForKey(mapent, "classname"))) { brush = GetAreaPortalBrush(mapent); if (!brush) return false; if (!WriteMapBrush(fp, brush, mapent->origin)) return false; } //end if else { entitybrushes = false; //write brushes for (bn = 0; bn < nummapbrushes; bn++) { brush = &mapbrushes[bn]; //if the brush is part of this entity if (brush->entitynum == i) { //don't write out area portal brushes in the world if (!((brush->contents & CONTENTS_AREAPORTAL) && brush->entitynum == 0)) { /* if (!strcmp("func_door_rotating", ValueForKey(mapent, "classname"))) { AAS_PositionFuncRotatingBrush(mapent, brush); if (!WriteMapBrush(fp, brush, vec_origin)) return false; } //end if else //*/ { if (!WriteMapBrush(fp, brush, mapent->origin)) return false; } //end else entitybrushes = true; } //end if } //end if } //end for //if the entity had brushes if (entitybrushes) { //if the entity has an origin set if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) { if (!WriteOriginBrush(fp, mapent->origin)) return false; } //end if } //end if } //end else if (fprintf(fp, "}\n") < 0) return false; } //end for if (fprintf(fp, "//total of %d brushes\n", c_writtenbrushes) < 0) return false; return true; } //end of the function WriteMapFileSafe //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void WriteMapFile(char *filename) { FILE *fp; double start_time; c_writtenbrushes = 0; //the time started start_time = I_FloatTime(); // Log_Print("writing %s\n", filename); fp = fopen(filename, "wb"); if (!fp) { Log_Print("can't open %s\n", filename); return; } //end if if (!WriteMapFileSafe(fp)) { fclose(fp); Log_Print("error writing map file %s\n", filename); return; } //end if fclose(fp); //display creation time Log_Print("written %d brushes\n", c_writtenbrushes); Log_Print("map file written in %5.0f seconds\n", I_FloatTime() - start_time); } //end of the function WriteMapFile //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintMapInfo(void) { Log_Print("\n"); Log_Print("%6i brushes\n", nummapbrushes); Log_Print("%6i brush sides\n", nummapbrushsides); // Log_Print("%6i clipbrushes\n", c_clipbrushes); // Log_Print("%6i total sides\n", nummapbrushsides); // Log_Print("%6i boxbevels\n", c_boxbevels); // Log_Print("%6i edgebevels\n", c_edgebevels); // Log_Print("%6i entities\n", num_entities); // Log_Print("%6i planes\n", nummapplanes); // Log_Print("%6i areaportals\n", c_areaportals); // Log_Print("%6i squatt brushes\n", c_squattbrushes); // Log_Print("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2], // map_maxs[0],map_maxs[1],map_maxs[2]); } //end of the function PrintMapInfo //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void ResetMapLoading(void) { int i; epair_t *ep, *nextep; Q2_ResetMapLoading(); Sin_ResetMapLoading(); //free all map brush side windings for (i = 0; i < nummapbrushsides; i++) { if (brushsides[i].winding) { FreeWinding(brushsides[i].winding); } //end for } //end for //reset regular stuff nummapbrushes = 0; memset(mapbrushes, 0, MAX_MAPFILE_BRUSHES * sizeof(mapbrush_t)); // nummapbrushsides = 0; memset(brushsides, 0, MAX_MAPFILE_BRUSHSIDES * sizeof(side_t)); memset(side_brushtextures, 0, MAX_MAPFILE_BRUSHSIDES * sizeof(brush_texture_t)); // nummapplanes = 0; memset(mapplanes, 0, MAX_MAPFILE_PLANES * sizeof(plane_t)); // memset(planehash, 0, PLANE_HASHES * sizeof(plane_t *)); // memset(map_texinfo, 0, MAX_MAPFILE_TEXINFO * sizeof(map_texinfo_t)); map_numtexinfo = 0; // VectorClear(map_mins); VectorClear(map_maxs); // c_boxbevels = 0; c_edgebevels = 0; c_areaportals = 0; c_clipbrushes = 0; c_writtenbrushes = 0; //clear the entities for (i = 0; i < num_entities; i++) { for (ep = entities[i].epairs; ep; ep = nextep) { nextep = ep->next; FreeMemory(ep->key); FreeMemory(ep->value); FreeMemory(ep); } //end for } //end for num_entities = 0; memset(entities, 0, MAX_MAP_ENTITIES * sizeof(entity_t)); } //end of the function ResetMapLoading //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifndef Q1_BSPVERSION #define Q1_BSPVERSION 29 #endif #ifndef HL_BSPVERSION #define HL_BSPVERSION 30 #endif #define Q2_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') //IBSP #define Q2_BSPVERSION 38 #define SINGAME_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'R') //RBSP #define SINGAME_BSPVERSION 1 #define SIN_BSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') //IBSP #define SIN_BSPVERSION 41 typedef struct { int ident; int version; } idheader_t; int LoadMapFromBSP(struct quakefile_s *qf) { idheader_t idheader; if (ReadQuakeFile(qf, &idheader, 0, sizeof(idheader_t)) != sizeof(idheader_t)) { return false; } //end if idheader.ident = LittleLong(idheader.ident); idheader.version = LittleLong(idheader.version); //Quake3 BSP file if (idheader.ident == Q3_BSP_IDENT && idheader.version == Q3_BSP_VERSION) { ResetMapLoading(); Q3_LoadMapFromBSP(qf); Q3_FreeMaxBSP(); } //end if //Quake2 BSP file else if (idheader.ident == Q2_BSPHEADER && idheader.version == Q2_BSPVERSION) { ResetMapLoading(); Q2_AllocMaxBSP(); Q2_LoadMapFromBSP(qf->filename, qf->offset, qf->length); Q2_FreeMaxBSP(); } //endif //Sin BSP file else if ((idheader.ident == SIN_BSPHEADER && idheader.version == SIN_BSPVERSION) || //the dorks gave the same format another ident and verions (idheader.ident == SINGAME_BSPHEADER && idheader.version == SINGAME_BSPVERSION)) { ResetMapLoading(); Sin_AllocMaxBSP(); Sin_LoadMapFromBSP(qf->filename, qf->offset, qf->length); Sin_FreeMaxBSP(); } //end if //the Quake1 bsp files don't have a ident only a version else if (idheader.ident == Q1_BSPVERSION) { ResetMapLoading(); Q1_AllocMaxBSP(); Q1_LoadMapFromBSP(qf->filename, qf->offset, qf->length); Q1_FreeMaxBSP(); } //end if //Half-Life also only uses a version number else if (idheader.ident == HL_BSPVERSION) { ResetMapLoading(); HL_AllocMaxBSP(); HL_LoadMapFromBSP(qf->filename, qf->offset, qf->length); HL_FreeMaxBSP(); } //end if else { Error("unknown BSP format %c%c%c%c, version %d\n", (idheader.ident & 0xFF), ((idheader.ident >> 8) & 0xFF), ((idheader.ident >> 16) & 0xFF), ((idheader.ident >> 24) & 0xFF), idheader.version); return false; } //end if // return true; } //end of the function LoadMapFromBSP ================================================ FILE: code/bspc/map_hl.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "l_bsp_hl.h" #include "aas_map.h" //AAS_CreateMapBrushes int hl_numbrushes; int hl_numclipbrushes; //#define HL_PRINT #define HLCONTENTS //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int HL_TextureContents(char *name) { if (!Q_strncasecmp (name, "sky",3)) return CONTENTS_SOLID; if (!Q_strncasecmp(name+1,"!lava",5)) return CONTENTS_LAVA; if (!Q_strncasecmp(name+1,"!slime",6)) return CONTENTS_SLIME; /* if (!Q_strncasecmp (name, "!cur_90",7)) return CONTENTS_CURRENT_90; if (!Q_strncasecmp (name, "!cur_0",6)) return CONTENTS_CURRENT_0; if (!Q_strncasecmp (name, "!cur_270",8)) return CONTENTS_CURRENT_270; if (!Q_strncasecmp (name, "!cur_180",8)) return CONTENTS_CURRENT_180; if (!Q_strncasecmp (name, "!cur_up",7)) return CONTENTS_CURRENT_UP; if (!Q_strncasecmp (name, "!cur_dwn",8)) return CONTENTS_CURRENT_DOWN; //*/ if (name[0] == '!') return CONTENTS_WATER; /* if (!Q_strncasecmp (name, "origin",6)) return CONTENTS_ORIGIN; if (!Q_strncasecmp (name, "clip",4)) return CONTENTS_CLIP; if( !Q_strncasecmp( name, "translucent", 11 ) ) return CONTENTS_TRANSLUCENT; if( name[0] == '@' ) return CONTENTS_TRANSLUCENT; //*/ return CONTENTS_SOLID; } //end of the function HL_TextureContents //=========================================================================== // Generates two new brushes, leaving the original // unchanged // // modified for Half-Life because there are quite a lot of tiny node leaves // in the Half-Life bsps // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void HL_SplitBrush(bspbrush_t *brush, int planenum, int nodenum, bspbrush_t **front, bspbrush_t **back) { bspbrush_t *b[2]; int i, j; winding_t *w, *cw[2], *midwinding; plane_t *plane, *plane2; side_t *s, *cs; float d, d_front, d_back; *front = *back = NULL; plane = &mapplanes[planenum]; // check all points d_front = d_back = 0; for (i=0 ; inumsides ; i++) { w = brush->sides[i].winding; if (!w) continue; for (j=0 ; jnumpoints ; j++) { d = DotProduct (w->p[j], plane->normal) - plane->dist; if (d > 0 && d > d_front) d_front = d; if (d < 0 && d < d_back) d_back = d; } //end for } //end for if (d_front < 0.1) // PLANESIDE_EPSILON) { // only on back *back = CopyBrush (brush); Log_Print("HL_SplitBrush: only on back\n"); return; } //end if if (d_back > -0.1) // PLANESIDE_EPSILON) { // only on front *front = CopyBrush (brush); Log_Print("HL_SplitBrush: only on front\n"); return; } //end if // create a new winding from the split plane w = BaseWindingForPlane (plane->normal, plane->dist); for (i = 0; i < brush->numsides && w; i++) { plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; ChopWindingInPlace(&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON); } //end for if (!w || WindingIsTiny(w)) { // the brush isn't really split int side; Log_Print("HL_SplitBrush: no split winding\n"); side = BrushMostlyOnSide (brush, plane); if (side == PSIDE_FRONT) *front = CopyBrush (brush); if (side == PSIDE_BACK) *back = CopyBrush (brush); return; } if (WindingIsHuge(w)) { Log_Print("HL_SplitBrush: WARNING huge split winding\n"); } //end of midwinding = w; // split it for real for (i = 0; i < 2; i++) { b[i] = AllocBrush (brush->numsides+1); b[i]->original = brush->original; } //end for // split all the current windings for (i=0 ; inumsides ; i++) { s = &brush->sides[i]; w = s->winding; if (!w) continue; ClipWindingEpsilon (w, plane->normal, plane->dist, 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); for (j=0 ; j<2 ; j++) { if (!cw[j]) continue; #if 0 if (WindingIsTiny (cw[j])) { FreeWinding (cw[j]); continue; } #endif cs = &b[j]->sides[b[j]->numsides]; b[j]->numsides++; *cs = *s; // cs->planenum = s->planenum; // cs->texinfo = s->texinfo; // cs->visible = s->visible; // cs->original = s->original; cs->winding = cw[j]; cs->flags &= ~SFL_TESTED; } //end for } //end for // see if we have valid polygons on both sides for (i=0 ; i<2 ; i++) { BoundBrush (b[i]); for (j=0 ; j<3 ; j++) { if (b[i]->mins[j] < -4096 || b[i]->maxs[j] > 4096) { Log_Print("HL_SplitBrush: bogus brush after clip\n"); break; } //end if } //end for if (b[i]->numsides < 3 || j < 3) { FreeBrush (b[i]); b[i] = NULL; Log_Print("HL_SplitBrush: numsides < 3\n"); } //end if } //end for if ( !(b[0] && b[1]) ) { if (!b[0] && !b[1]) Log_Print("HL_SplitBrush: split removed brush\n"); else Log_Print("HL_SplitBrush: split not on both sides\n"); if (b[0]) { FreeBrush (b[0]); *front = CopyBrush (brush); } //end if if (b[1]) { FreeBrush (b[1]); *back = CopyBrush (brush); } //end if return; } //end if // add the midwinding to both sides for (i = 0; i < 2; i++) { cs = &b[i]->sides[b[i]->numsides]; b[i]->numsides++; cs->planenum = planenum^i^1; cs->texinfo = 0; //store the node number in the surf to find the texinfo later on cs->surf = nodenum; // cs->flags &= ~SFL_VISIBLE; cs->flags &= ~SFL_TESTED; if (i==0) cs->winding = CopyWinding (midwinding); else cs->winding = midwinding; } //end for { vec_t v1; int i; for (i=0 ; i<2 ; i++) { v1 = BrushVolume (b[i]); if (v1 < 1) { FreeBrush (b[i]); b[i] = NULL; Log_Print("HL_SplitBrush: tiny volume after clip\n"); } //end if } //end for } //*/ *front = b[0]; *back = b[1]; } //end of the function HL_SplitBrush //=========================================================================== // returns true if the tree starting at nodenum has only solid leaves // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int HL_SolidTree_r(int nodenum) { if (nodenum < 0) { switch(hl_dleafs[(-nodenum) - 1].contents) { case HL_CONTENTS_EMPTY: { return false; } //end case case HL_CONTENTS_SOLID: #ifdef HLCONTENTS case HL_CONTENTS_CLIP: #endif //HLCONTENTS case HL_CONTENTS_SKY: #ifdef HLCONTENTS case HL_CONTENTS_TRANSLUCENT: #endif //HLCONTENTS { return true; } //end case case HL_CONTENTS_WATER: case HL_CONTENTS_SLIME: case HL_CONTENTS_LAVA: #ifdef HLCONTENTS //these contents should not be found in the BSP case HL_CONTENTS_ORIGIN: case HL_CONTENTS_CURRENT_0: case HL_CONTENTS_CURRENT_90: case HL_CONTENTS_CURRENT_180: case HL_CONTENTS_CURRENT_270: case HL_CONTENTS_CURRENT_UP: case HL_CONTENTS_CURRENT_DOWN: #endif //HLCONTENTS default: { return false; } //end default } //end switch return false; } //end if if (!HL_SolidTree_r(hl_dnodes[nodenum].children[0])) return false; if (!HL_SolidTree_r(hl_dnodes[nodenum].children[1])) return false; return true; } //end of the function HL_SolidTree_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *HL_CreateBrushes_r(bspbrush_t *brush, int nodenum) { int planenum; bspbrush_t *front, *back; hl_dleaf_t *leaf; //if it is a leaf if (nodenum < 0) { leaf = &hl_dleafs[(-nodenum) - 1]; if (leaf->contents != HL_CONTENTS_EMPTY) { #ifdef HL_PRINT qprintf("\r%5i", ++hl_numbrushes); #endif //HL_PRINT } //end if switch(leaf->contents) { case HL_CONTENTS_EMPTY: { FreeBrush(brush); return NULL; } //end case case HL_CONTENTS_SOLID: #ifdef HLCONTENTS case HL_CONTENTS_CLIP: #endif //HLCONTENTS case HL_CONTENTS_SKY: #ifdef HLCONTENTS case HL_CONTENTS_TRANSLUCENT: #endif //HLCONTENTS { brush->side = CONTENTS_SOLID; return brush; } //end case case HL_CONTENTS_WATER: { brush->side = CONTENTS_WATER; return brush; } //end case case HL_CONTENTS_SLIME: { brush->side = CONTENTS_SLIME; return brush; } //end case case HL_CONTENTS_LAVA: { brush->side = CONTENTS_LAVA; return brush; } //end case #ifdef HLCONTENTS //these contents should not be found in the BSP case HL_CONTENTS_ORIGIN: case HL_CONTENTS_CURRENT_0: case HL_CONTENTS_CURRENT_90: case HL_CONTENTS_CURRENT_180: case HL_CONTENTS_CURRENT_270: case HL_CONTENTS_CURRENT_UP: case HL_CONTENTS_CURRENT_DOWN: { Error("HL_CreateBrushes_r: found contents %d in Half-Life BSP", leaf->contents); return NULL; } //end case #endif //HLCONTENTS default: { Error("HL_CreateBrushes_r: unknown contents %d in Half-Life BSP", leaf->contents); return NULL; } //end default } //end switch return NULL; } //end if //if the rest of the tree is solid /*if (HL_SolidTree_r(nodenum)) { brush->side = CONTENTS_SOLID; return brush; } //end if*/ // planenum = hl_dnodes[nodenum].planenum; planenum = FindFloatPlane(hl_dplanes[planenum].normal, hl_dplanes[planenum].dist); //split the brush with the node plane HL_SplitBrush(brush, planenum, nodenum, &front, &back); //free the original brush FreeBrush(brush); //every node must split the brush in two if (!front || !back) { Log_Print("HL_CreateBrushes_r: WARNING node not splitting brush\n"); //return NULL; } //end if //create brushes recursively if (front) front = HL_CreateBrushes_r(front, hl_dnodes[nodenum].children[0]); if (back) back = HL_CreateBrushes_r(back, hl_dnodes[nodenum].children[1]); //link the brushes if possible and return them if (front) { for (brush = front; brush->next; brush = brush->next); brush->next = back; return front; } //end if else { return back; } //end else } //end of the function HL_CreateBrushes_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *HL_CreateBrushesFromBSP(int modelnum) { bspbrush_t *brushlist; bspbrush_t *brush; hl_dnode_t *headnode; vec3_t mins, maxs; int i; // headnode = &hl_dnodes[hl_dmodels[modelnum].headnode[0]]; //get the mins and maxs of the world VectorCopy(headnode->mins, mins); VectorCopy(headnode->maxs, maxs); //enlarge these mins and maxs for (i = 0; i < 3; i++) { mins[i] -= 8; maxs[i] += 8; } //end for //NOTE: have to add the BSP tree mins and maxs to the MAP mins and maxs AddPointToBounds(mins, map_mins, map_maxs); AddPointToBounds(maxs, map_mins, map_maxs); // if (!modelnum) { Log_Print("brush size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0], map_mins[1], map_mins[2], map_maxs[0], map_maxs[1], map_maxs[2]); } //end if //create one huge brush containing the whole world brush = BrushFromBounds(mins, maxs); VectorCopy(mins, brush->mins); VectorCopy(maxs, brush->maxs); // #ifdef HL_PRINT qprintf("creating Half-Life brushes\n"); qprintf("%5d brushes", hl_numbrushes = 0); #endif //HL_PRINT //create the brushes brushlist = HL_CreateBrushes_r(brush, hl_dmodels[modelnum].headnode[0]); // #ifdef HL_PRINT qprintf("\n"); #endif //HL_PRINT //now we've got a list with brushes! return brushlist; } //end of the function HL_CreateBrushesFromBSP //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *HL_MergeBrushes(bspbrush_t *brushlist, int modelnum) { int nummerges, merged; bspbrush_t *b1, *b2, *tail, *newbrush, *newbrushlist; bspbrush_t *lastb2; if (!brushlist) return NULL; if (!modelnum) qprintf("%5d brushes merged", nummerges = 0); do { for (tail = brushlist; tail; tail = tail->next) { if (!tail->next) break; } //end for merged = 0; newbrushlist = NULL; for (b1 = brushlist; b1; b1 = brushlist) { lastb2 = b1; for (b2 = b1->next; b2; b2 = b2->next) { //can't merge brushes with different contents if (b1->side != b2->side) newbrush = NULL; else newbrush = TryMergeBrushes(b1, b2); //if a merged brush is created if (newbrush) { //copy the brush contents newbrush->side = b1->side; //add the new brush to the end of the list tail->next = newbrush; //remove the second brush from the list lastb2->next = b2->next; //remove the first brush from the list brushlist = brushlist->next; //free the merged brushes FreeBrush(b1); FreeBrush(b2); //get a new tail brush for (tail = brushlist; tail; tail = tail->next) { if (!tail->next) break; } //end for merged++; if (!modelnum) qprintf("\r%5d", nummerges++); break; } //end if lastb2 = b2; } //end for //if b1 can't be merged with any of the other brushes if (!b2) { brushlist = brushlist->next; //keep b1 b1->next = newbrushlist; newbrushlist = b1; } //end else } //end for brushlist = newbrushlist; } while(merged); if (!modelnum) qprintf("\n"); return newbrushlist; } //end of the function HL_MergeBrushes //=========================================================================== // returns the amount the face and the winding have overlap // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float HL_FaceOnWinding(hl_dface_t *face, winding_t *winding) { int i, edgenum, side; float dist, area; hl_dplane_t plane; vec_t *v1, *v2; vec3_t normal, edgevec; winding_t *w; // w = CopyWinding(winding); memcpy(&plane, &hl_dplanes[face->planenum], sizeof(hl_dplane_t)); //check on which side of the plane the face is if (face->side) { VectorNegate(plane.normal, plane.normal); plane.dist = -plane.dist; } //end if for (i = 0; i < face->numedges && w; i++) { //get the first and second vertex of the edge edgenum = hl_dsurfedges[face->firstedge + i]; side = edgenum > 0; //if the face plane is flipped v1 = hl_dvertexes[hl_dedges[abs(edgenum)].v[side]].point; v2 = hl_dvertexes[hl_dedges[abs(edgenum)].v[!side]].point; //create a plane through the edge vector, orthogonal to the face plane //and with the normal vector pointing out of the face VectorSubtract(v1, v2, edgevec); CrossProduct(edgevec, plane.normal, normal); VectorNormalize(normal); dist = DotProduct(normal, v1); // ChopWindingInPlace(&w, normal, dist, 0.9); //CLIP_EPSILON } //end for if (w) { area = WindingArea(w); FreeWinding(w); return area; } //end if return 0; } //end of the function HL_FaceOnWinding //=========================================================================== // returns a list with brushes created by splitting the given brush with // planes that go through the face edges and are orthogonal to the face plane // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *HL_SplitBrushWithFace(bspbrush_t *brush, hl_dface_t *face) { int i, edgenum, side, planenum, splits; float dist; hl_dplane_t plane; vec_t *v1, *v2; vec3_t normal, edgevec; bspbrush_t *front, *back, *brushlist; memcpy(&plane, &hl_dplanes[face->planenum], sizeof(hl_dplane_t)); //check on which side of the plane the face is if (face->side) { VectorNegate(plane.normal, plane.normal); plane.dist = -plane.dist; } //end if splits = 0; brushlist = NULL; for (i = 0; i < face->numedges; i++) { //get the first and second vertex of the edge edgenum = hl_dsurfedges[face->firstedge + i]; side = edgenum > 0; //if the face plane is flipped v1 = hl_dvertexes[hl_dedges[abs(edgenum)].v[side]].point; v2 = hl_dvertexes[hl_dedges[abs(edgenum)].v[!side]].point; //create a plane through the edge vector, orthogonal to the face plane //and with the normal vector pointing out of the face VectorSubtract(v1, v2, edgevec); CrossProduct(edgevec, plane.normal, normal); VectorNormalize(normal); dist = DotProduct(normal, v1); // planenum = FindFloatPlane(normal, dist); //split the current brush SplitBrush(brush, planenum, &front, &back); //if there is a back brush just put it in the list if (back) { //copy the brush contents back->side = brush->side; // back->next = brushlist; brushlist = back; splits++; } //end if if (!front) { Log_Print("HL_SplitBrushWithFace: no new brush\n"); FreeBrushList(brushlist); return NULL; } //end if //copy the brush contents front->side = brush->side; //continue splitting the front brush brush = front; } //end for if (!splits) { FreeBrush(front); return NULL; } //end if front->next = brushlist; brushlist = front; return brushlist; } //end of the function HL_SplitBrushWithFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *HL_TextureBrushes(bspbrush_t *brushlist, int modelnum) { float area, largestarea; int i, n, texinfonum, sn, numbrushes, ofs; int bestfacenum, sidenodenum; side_t *side; hl_dmiptexlump_t *miptexlump; hl_miptex_t *miptex; bspbrush_t *brush, *nextbrush, *prevbrush, *newbrushes, *brushlistend; vec_t defaultvec[4] = {1, 0, 0, 0}; if (!modelnum) qprintf("texturing brushes\n"); if (!modelnum) qprintf("%5d brushes", numbrushes = 0); //get a pointer to the last brush in the list for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) { if (!brushlistend->next) break; } //end for //there's no previous brush when at the start of the list prevbrush = NULL; //go over the brush list for (brush = brushlist; brush; brush = nextbrush) { nextbrush = brush->next; //find a texinfo for every brush side for (sn = 0; sn < brush->numsides; sn++) { side = &brush->sides[sn]; // if (side->flags & SFL_TEXTURED) continue; //number of the node that created this brush side sidenodenum = side->surf; //see midwinding in HL_SplitBrush //no face found yet bestfacenum = -1; //minimum face size largestarea = 1; //if optimizing the texture placement and not going for the //least number of brushes if (!lessbrushes) { for (i = 0; i < hl_numfaces; i++) { //the face must be in the same plane as the node plane that created //this brush side if (hl_dfaces[i].planenum == hl_dnodes[sidenodenum].planenum) { //get the area the face and the brush side overlap area = HL_FaceOnWinding(&hl_dfaces[i], side->winding); //if this face overlaps the brush side winding more than previous faces if (area > largestarea) { //if there already was a face for texturing this brush side with //a different texture if (bestfacenum >= 0 && (hl_dfaces[bestfacenum].texinfo != hl_dfaces[i].texinfo)) { //split the brush to fit the texture newbrushes = HL_SplitBrushWithFace(brush, &hl_dfaces[i]); //if new brushes where created if (newbrushes) { //remove the current brush from the list if (prevbrush) prevbrush->next = brush->next; else brushlist = brush->next; if (brushlistend == brush) { brushlistend = prevbrush; nextbrush = newbrushes; } //end if //add the new brushes to the end of the list if (brushlistend) brushlistend->next = newbrushes; else brushlist = newbrushes; //free the current brush FreeBrush(brush); //don't forget about the prevbrush pointer at the bottom of //the outer loop brush = prevbrush; //find the end of the list for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) { if (!brushlistend->next) break; } //end for break; } //end if else { Log_Write("brush %d: no real texture split", numbrushes); } //end else } //end if else { //best face for texturing this brush side bestfacenum = i; } //end else } //end if } //end if } //end for //if the brush was split the original brush is removed //and we just continue with the next one in the list if (i < hl_numfaces) break; } //end if else { //find the face with the largest overlap with this brush side //for texturing the brush side for (i = 0; i < hl_numfaces; i++) { //the face must be in the same plane as the node plane that created //this brush side if (hl_dfaces[i].planenum == hl_dnodes[sidenodenum].planenum) { //get the area the face and the brush side overlap area = HL_FaceOnWinding(&hl_dfaces[i], side->winding); //if this face overlaps the brush side winding more than previous faces if (area > largestarea) { largestarea = area; bestfacenum = i; } //end if } //end if } //end for } //end else //if a face was found for texturing this brush side if (bestfacenum >= 0) { //set the MAP texinfo values texinfonum = hl_dfaces[bestfacenum].texinfo; for (n = 0; n < 4; n++) { map_texinfo[texinfonum].vecs[0][n] = hl_texinfo[texinfonum].vecs[0][n]; map_texinfo[texinfonum].vecs[1][n] = hl_texinfo[texinfonum].vecs[1][n]; } //end for //make sure the two vectors aren't of zero length otherwise use the default //vector to prevent a divide by zero in the map writing if (VectorLength(map_texinfo[texinfonum].vecs[0]) < 0.01) memcpy(map_texinfo[texinfonum].vecs[0], defaultvec, sizeof(defaultvec)); if (VectorLength(map_texinfo[texinfonum].vecs[1]) < 0.01) memcpy(map_texinfo[texinfonum].vecs[1], defaultvec, sizeof(defaultvec)); // map_texinfo[texinfonum].flags = hl_texinfo[texinfonum].flags; map_texinfo[texinfonum].value = 0; //HL_ and HL texinfos don't have a value //the mip texture miptexlump = (hl_dmiptexlump_t *) hl_dtexdata; ofs = miptexlump->dataofs[hl_texinfo[texinfonum].miptex]; if ( ofs > hl_texdatasize ) { ofs = miptexlump->dataofs[0]; } miptex = (hl_miptex_t *)((byte *)miptexlump + ofs ); //get the mip texture name strcpy(map_texinfo[texinfonum].texture, miptex->name); //no animations in Quake1 and Half-Life mip textures map_texinfo[texinfonum].nexttexinfo = -1; //store the texinfo number side->texinfo = texinfonum; // if (texinfonum > map_numtexinfo) map_numtexinfo = texinfonum; //this side is textured side->flags |= SFL_TEXTURED; } //end if else { //no texture for this side side->texinfo = TEXINFO_NODE; //this side is textured side->flags |= SFL_TEXTURED; } //end if } //end for // if (!modelnum && prevbrush != brush) qprintf("\r%5d", ++numbrushes); //previous brush in the list prevbrush = brush; } //end for if (!modelnum) qprintf("\n"); //return the new list with brushes return brushlist; } //end of the function HL_TextureBrushes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void HL_FixContentsTextures(bspbrush_t *brushlist) { int i, texinfonum; bspbrush_t *brush; for (brush = brushlist; brush; brush = brush->next) { //only fix the textures of water, slime and lava brushes if (brush->side != CONTENTS_WATER && brush->side != CONTENTS_SLIME && brush->side != CONTENTS_LAVA) continue; // for (i = 0; i < brush->numsides; i++) { texinfonum = brush->sides[i].texinfo; if (HL_TextureContents(map_texinfo[texinfonum].texture) == brush->side) break; } //end for //if no specific contents texture was found if (i >= brush->numsides) { texinfonum = -1; for (i = 0; i < map_numtexinfo; i++) { if (HL_TextureContents(map_texinfo[i].texture) == brush->side) { texinfonum = i; break; } //end if } //end for } //end if // if (texinfonum >= 0) { //give all the brush sides this contents texture for (i = 0; i < brush->numsides; i++) { brush->sides[i].texinfo = texinfonum; } //end for } //end if else Log_Print("brush contents %d with wrong textures\n", brush->side); // } //end for /* for (brush = brushlist; brush; brush = brush->next) { //give all the brush sides this contents texture for (i = 0; i < brush->numsides; i++) { if (HL_TextureContents(map_texinfo[texinfonum].texture) != brush->side) { Error("brush contents %d with wrong contents textures %s\n", brush->side, HL_TextureContents(map_texinfo[texinfonum].texture)); } //end if } //end for } //end for*/ } //end of the function HL_FixContentsTextures //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void HL_BSPBrushToMapBrush(bspbrush_t *bspbrush, entity_t *mapent) { mapbrush_t *mapbrush; side_t *side; int i, besttexinfo; if (nummapbrushes >= MAX_MAPFILE_BRUSHES) Error ("nummapbrushes == MAX_MAPFILE_BRUSHES"); mapbrush = &mapbrushes[nummapbrushes]; mapbrush->original_sides = &brushsides[nummapbrushsides]; mapbrush->entitynum = mapent - entities; mapbrush->brushnum = nummapbrushes - mapent->firstbrush; mapbrush->leafnum = -1; mapbrush->numsides = 0; besttexinfo = TEXINFO_NODE; for (i = 0; i < bspbrush->numsides; i++) { if (!bspbrush->sides[i].winding) continue; // if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) Error ("MAX_MAPFILE_BRUSHSIDES"); side = &brushsides[nummapbrushsides]; //the contents of the bsp brush is stored in the side variable side->contents = bspbrush->side; side->surf = 0; side->planenum = bspbrush->sides[i].planenum; side->texinfo = bspbrush->sides[i].texinfo; if (side->texinfo != TEXINFO_NODE) { //this brush side is textured side->flags |= SFL_TEXTURED; besttexinfo = side->texinfo; } //end if // nummapbrushsides++; mapbrush->numsides++; } //end for // if (besttexinfo == TEXINFO_NODE) { mapbrush->numsides = 0; hl_numclipbrushes++; return; } //end if //set the texinfo for all the brush sides without texture for (i = 0; i < mapbrush->numsides; i++) { if (mapbrush->original_sides[i].texinfo == TEXINFO_NODE) { mapbrush->original_sides[i].texinfo = besttexinfo; } //end if } //end for //contents of the brush mapbrush->contents = bspbrush->side; // if (create_aas) { //create the AAS brushes from this brush, add brush bevels AAS_CreateMapBrushes(mapbrush, mapent, true); return; } //end if //create windings for sides and bounds for brush MakeBrushWindings(mapbrush); //add brush bevels AddBrushBevels(mapbrush); //a new brush has been created nummapbrushes++; mapent->numbrushes++; } //end of the function HL_BSPBrushToMapBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void HL_CreateMapBrushes(entity_t *mapent, int modelnum) { bspbrush_t *brushlist, *brush, *nextbrush; int i; //create brushes from the model BSP tree brushlist = HL_CreateBrushesFromBSP(modelnum); //texture the brushes and split them when necesary brushlist = HL_TextureBrushes(brushlist, modelnum); //fix the contents textures of all brushes HL_FixContentsTextures(brushlist); // if (!nobrushmerge) { brushlist = HL_MergeBrushes(brushlist, modelnum); //brushlist = HL_MergeBrushes(brushlist, modelnum); } //end if // if (!modelnum) qprintf("converting brushes to map brushes\n"); if (!modelnum) qprintf("%5d brushes", i = 0); for (brush = brushlist; brush; brush = nextbrush) { nextbrush = brush->next; HL_BSPBrushToMapBrush(brush, mapent); brush->next = NULL; FreeBrush(brush); if (!modelnum) qprintf("\r%5d", ++i); } //end for if (!modelnum) qprintf("\n"); } //end of the function HL_CreateMapBrushes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void HL_ResetMapLoading(void) { } //end of the function HL_ResetMapLoading //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void HL_LoadMapFromBSP(char *filename, int offset, int length) { int i, modelnum; char *model, *classname; Log_Print("-- HL_LoadMapFromBSP --\n"); //loaded map type loadedmaptype = MAPTYPE_HALFLIFE; // qprintf("loading map from %s at %d\n", filename, offset); //load the Half-Life BSP file HL_LoadBSPFile(filename, offset, length); // hl_numclipbrushes = 0; //parse the entities from the BSP HL_ParseEntities(); //clear the map mins and maxs ClearBounds(map_mins, map_maxs); // qprintf("creating Half-Life brushes\n"); if (lessbrushes) qprintf("creating minimum number of brushes\n"); else qprintf("placing textures correctly\n"); // for (i = 0; i < num_entities; i++) { entities[i].firstbrush = nummapbrushes; entities[i].numbrushes = 0; // classname = ValueForKey(&entities[i], "classname"); if (classname && !strcmp(classname, "worldspawn")) { modelnum = 0; } //end if else { // model = ValueForKey(&entities[i], "model"); if (!model || *model != '*') continue; model++; modelnum = atoi(model); } //end else //create map brushes for the entity HL_CreateMapBrushes(&entities[i], modelnum); } //end for // qprintf("%5d map brushes\n", nummapbrushes); qprintf("%5d clip brushes\n", hl_numclipbrushes); } //end of the function HL_LoadMapFromBSP ================================================ FILE: code/bspc/map_q1.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "l_bsp_q1.h" #include "aas_map.h" //AAS_CreateMapBrushes int q1_numbrushes; int q1_numclipbrushes; //#define Q1_PRINT //=========================================================================== // water, slime and lava brush textures names always start with a * // followed by the type: "slime", "lava" or otherwise water // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Q1_TextureContents(char *name) { if (!Q_strcasecmp(name, "clip")) return CONTENTS_SOLID; if (name[0] == '*') { if (!Q_strncasecmp(name+1,"lava",4)) return CONTENTS_LAVA; else if (!Q_strncasecmp(name+1,"slime",5)) return CONTENTS_SLIME; else return CONTENTS_WATER; } //end if else if (!Q_strncasecmp(name, "sky", 3)) return CONTENTS_SOLID; else return CONTENTS_SOLID; } //end of the function Q1_TextureContents //=========================================================================== // Generates two new brushes, leaving the original // unchanged // // modified for Half-Life because there are quite a lot of tiny node leaves // in the Half-Life bsps // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q1_SplitBrush(bspbrush_t *brush, int planenum, int nodenum, bspbrush_t **front, bspbrush_t **back) { bspbrush_t *b[2]; int i, j; winding_t *w, *cw[2], *midwinding; plane_t *plane, *plane2; side_t *s, *cs; float d, d_front, d_back; *front = *back = NULL; plane = &mapplanes[planenum]; // check all points d_front = d_back = 0; for (i=0 ; inumsides ; i++) { w = brush->sides[i].winding; if (!w) continue; for (j=0 ; jnumpoints ; j++) { d = DotProduct (w->p[j], plane->normal) - plane->dist; if (d > 0 && d > d_front) d_front = d; if (d < 0 && d < d_back) d_back = d; } //end for } //end for if (d_front < 0.1) // PLANESIDE_EPSILON) { // only on back *back = CopyBrush (brush); Log_Print("Q1_SplitBrush: only on back\n"); return; } //end if if (d_back > -0.1) // PLANESIDE_EPSILON) { // only on front *front = CopyBrush (brush); Log_Print("Q1_SplitBrush: only on front\n"); return; } //end if // create a new winding from the split plane w = BaseWindingForPlane (plane->normal, plane->dist); for (i = 0; i < brush->numsides && w; i++) { plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; ChopWindingInPlace(&w, plane2->normal, plane2->dist, 0); // PLANESIDE_EPSILON); } //end for if (!w || WindingIsTiny(w)) { // the brush isn't really split int side; Log_Print("Q1_SplitBrush: no split winding\n"); side = BrushMostlyOnSide (brush, plane); if (side == PSIDE_FRONT) *front = CopyBrush (brush); if (side == PSIDE_BACK) *back = CopyBrush (brush); return; } if (WindingIsHuge(w)) { Log_Print("Q1_SplitBrush: WARNING huge split winding\n"); } //end of midwinding = w; // split it for real for (i = 0; i < 2; i++) { b[i] = AllocBrush (brush->numsides+1); b[i]->original = brush->original; } //end for // split all the current windings for (i=0 ; inumsides ; i++) { s = &brush->sides[i]; w = s->winding; if (!w) continue; ClipWindingEpsilon (w, plane->normal, plane->dist, 0 /*PLANESIDE_EPSILON*/, &cw[0], &cw[1]); for (j=0 ; j<2 ; j++) { if (!cw[j]) continue; #if 0 if (WindingIsTiny (cw[j])) { FreeWinding (cw[j]); continue; } #endif cs = &b[j]->sides[b[j]->numsides]; b[j]->numsides++; *cs = *s; // cs->planenum = s->planenum; // cs->texinfo = s->texinfo; // cs->visible = s->visible; // cs->original = s->original; cs->winding = cw[j]; cs->flags &= ~SFL_TESTED; } //end for } //end for // see if we have valid polygons on both sides for (i=0 ; i<2 ; i++) { BoundBrush (b[i]); for (j=0 ; j<3 ; j++) { if (b[i]->mins[j] < -4096 || b[i]->maxs[j] > 4096) { Log_Print("Q1_SplitBrush: bogus brush after clip\n"); break; } //end if } //end for if (b[i]->numsides < 3 || j < 3) { FreeBrush (b[i]); b[i] = NULL; Log_Print("Q1_SplitBrush: numsides < 3\n"); } //end if } //end for if ( !(b[0] && b[1]) ) { if (!b[0] && !b[1]) Log_Print("Q1_SplitBrush: split removed brush\n"); else Log_Print("Q1_SplitBrush: split not on both sides\n"); if (b[0]) { FreeBrush (b[0]); *front = CopyBrush (brush); } //end if if (b[1]) { FreeBrush (b[1]); *back = CopyBrush (brush); } //end if return; } //end if // add the midwinding to both sides for (i = 0; i < 2; i++) { cs = &b[i]->sides[b[i]->numsides]; b[i]->numsides++; cs->planenum = planenum^i^1; cs->texinfo = 0; //store the node number in the surf to find the texinfo later on cs->surf = nodenum; // cs->flags &= ~SFL_VISIBLE; cs->flags &= ~SFL_TESTED; cs->flags &= ~SFL_TEXTURED; if (i==0) cs->winding = CopyWinding (midwinding); else cs->winding = midwinding; } //end for { vec_t v1; int i; for (i=0 ; i<2 ; i++) { v1 = BrushVolume (b[i]); if (v1 < 1) { FreeBrush (b[i]); b[i] = NULL; Log_Print("Q1_SplitBrush: tiny volume after clip\n"); } //end if } //end for } //*/ *front = b[0]; *back = b[1]; } //end of the function Q1_SplitBrush //=========================================================================== // returns true if the tree starting at nodenum has only solid leaves // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Q1_SolidTree_r(int nodenum) { if (nodenum < 0) { switch(q1_dleafs[(-nodenum) - 1].contents) { case Q1_CONTENTS_EMPTY: { return false; } //end case case Q1_CONTENTS_SOLID: #ifdef HLCONTENTS case Q1_CONTENTS_CLIP: #endif HLCONTENTS case Q1_CONTENTS_SKY: #ifdef HLCONTENTS case Q1_CONTENTS_TRANSLUCENT: #endif HLCONTENTS { return true; } //end case case Q1_CONTENTS_WATER: case Q1_CONTENTS_SLIME: case Q1_CONTENTS_LAVA: #ifdef HLCONTENTS //these contents should not be found in the BSP case Q1_CONTENTS_ORIGIN: case Q1_CONTENTS_CURRENT_0: case Q1_CONTENTS_CURRENT_90: case Q1_CONTENTS_CURRENT_180: case Q1_CONTENTS_CURRENT_270: case Q1_CONTENTS_CURRENT_UP: case Q1_CONTENTS_CURRENT_DOWN: #endif HLCONTENTS default: { return false; } //end default } //end switch return false; } //end if if (!Q1_SolidTree_r(q1_dnodes[nodenum].children[0])) return false; if (!Q1_SolidTree_r(q1_dnodes[nodenum].children[1])) return false; return true; } //end of the function Q1_SolidTree_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *Q1_CreateBrushes_r(bspbrush_t *brush, int nodenum) { int planenum; bspbrush_t *front, *back; q1_dleaf_t *leaf; //if it is a leaf if (nodenum < 0) { leaf = &q1_dleafs[(-nodenum) - 1]; if (leaf->contents != Q1_CONTENTS_EMPTY) { #ifdef Q1_PRINT qprintf("\r%5i", ++q1_numbrushes); #endif //Q1_PRINT } //end if switch(leaf->contents) { case Q1_CONTENTS_EMPTY: { FreeBrush(brush); return NULL; } //end case case Q1_CONTENTS_SOLID: #ifdef HLCONTENTS case Q1_CONTENTS_CLIP: #endif HLCONTENTS case Q1_CONTENTS_SKY: #ifdef HLCONTENTS case Q1_CONTENTS_TRANSLUCENT: #endif HLCONTENTS { brush->side = CONTENTS_SOLID; return brush; } //end case case Q1_CONTENTS_WATER: { brush->side = CONTENTS_WATER; return brush; } //end case case Q1_CONTENTS_SLIME: { brush->side = CONTENTS_SLIME; return brush; } //end case case Q1_CONTENTS_LAVA: { brush->side = CONTENTS_LAVA; return brush; } //end case #ifdef HLCONTENTS //these contents should not be found in the BSP case Q1_CONTENTS_ORIGIN: case Q1_CONTENTS_CURRENT_0: case Q1_CONTENTS_CURRENT_90: case Q1_CONTENTS_CURRENT_180: case Q1_CONTENTS_CURRENT_270: case Q1_CONTENTS_CURRENT_UP: case Q1_CONTENTS_CURRENT_DOWN: { Error("Q1_CreateBrushes_r: found contents %d in Half-Life BSP", leaf->contents); return NULL; } //end case #endif HLCONTENTS default: { Error("Q1_CreateBrushes_r: unknown contents %d in Half-Life BSP", leaf->contents); return NULL; } //end default } //end switch return NULL; } //end if //if the rest of the tree is solid /*if (Q1_SolidTree_r(nodenum)) { brush->side = CONTENTS_SOLID; return brush; } //end if*/ // planenum = q1_dnodes[nodenum].planenum; planenum = FindFloatPlane(q1_dplanes[planenum].normal, q1_dplanes[planenum].dist); //split the brush with the node plane Q1_SplitBrush(brush, planenum, nodenum, &front, &back); //free the original brush FreeBrush(brush); //every node must split the brush in two if (!front || !back) { Log_Print("Q1_CreateBrushes_r: WARNING node not splitting brush\n"); //return NULL; } //end if //create brushes recursively if (front) front = Q1_CreateBrushes_r(front, q1_dnodes[nodenum].children[0]); if (back) back = Q1_CreateBrushes_r(back, q1_dnodes[nodenum].children[1]); //link the brushes if possible and return them if (front) { for (brush = front; brush->next; brush = brush->next); brush->next = back; return front; } //end if else { return back; } //end else } //end of the function Q1_CreateBrushes_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *Q1_CreateBrushesFromBSP(int modelnum) { bspbrush_t *brushlist; bspbrush_t *brush; q1_dnode_t *headnode; vec3_t mins, maxs; int i; // headnode = &q1_dnodes[q1_dmodels[modelnum].headnode[0]]; //get the mins and maxs of the world VectorCopy(headnode->mins, mins); VectorCopy(headnode->maxs, maxs); //enlarge these mins and maxs for (i = 0; i < 3; i++) { mins[i] -= 8; maxs[i] += 8; } //end for //NOTE: have to add the BSP tree mins and maxs to the MAP mins and maxs AddPointToBounds(mins, map_mins, map_maxs); AddPointToBounds(maxs, map_mins, map_maxs); // if (!modelnum) { Log_Print("brush size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0], map_mins[1], map_mins[2], map_maxs[0], map_maxs[1], map_maxs[2]); } //end if //create one huge brush containing the whole world brush = BrushFromBounds(mins, maxs); VectorCopy(mins, brush->mins); VectorCopy(maxs, brush->maxs); // #ifdef Q1_PRINT qprintf("creating Quake brushes\n"); qprintf("%5d brushes", q1_numbrushes = 0); #endif //Q1_PRINT //create the brushes brushlist = Q1_CreateBrushes_r(brush, q1_dmodels[modelnum].headnode[0]); // #ifdef Q1_PRINT qprintf("\n"); #endif //Q1_PRINT //now we've got a list with brushes! return brushlist; } //end of the function Q1_CreateBrushesFromBSP //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== q1_dleaf_t *Q1_PointInLeaf(int startnode, vec3_t point) { int nodenum; vec_t dist; q1_dnode_t *node; q1_dplane_t *plane; nodenum = startnode; while (nodenum >= 0) { node = &q1_dnodes[nodenum]; plane = &q1_dplanes[node->planenum]; dist = DotProduct(point, plane->normal) - plane->dist; if (dist > 0) nodenum = node->children[0]; else nodenum = node->children[1]; } //end while return &q1_dleafs[-nodenum - 1]; } //end of the function Q1_PointInLeaf //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float Q1_FaceArea(q1_dface_t *face) { int i; float total; vec_t *v; vec3_t d1, d2, cross; q1_dedge_t *edge; edge = &q1_dedges[face->firstedge]; v = q1_dvertexes[edge->v[0]].point; total = 0; for (i = 1; i < face->numedges - 1; i++) { edge = &q1_dedges[face->firstedge + i]; VectorSubtract(q1_dvertexes[edge->v[0]].point, v, d1); VectorSubtract(q1_dvertexes[edge->v[1]].point, v, d2); CrossProduct(d1, d2, cross); total += 0.5 * VectorLength(cross); } //end for return total; } //end of the function AAS_FaceArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q1_FacePlane(q1_dface_t *face, vec3_t normal, float *dist) { vec_t *v1, *v2, *v3; vec3_t vec1, vec2; int side, edgenum; edgenum = q1_dsurfedges[face->firstedge]; side = edgenum < 0; v1 = q1_dvertexes[q1_dedges[abs(edgenum)].v[side]].point; v2 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; edgenum = q1_dsurfedges[face->firstedge+1]; side = edgenum < 0; v3 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; // VectorSubtract(v2, v1, vec1); VectorSubtract(v3, v1, vec2); CrossProduct(vec1, vec2, normal); VectorNormalize(normal); *dist = DotProduct(v1, normal); } //end of the function Q1_FacePlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *Q1_MergeBrushes(bspbrush_t *brushlist, int modelnum) { int nummerges, merged; bspbrush_t *b1, *b2, *tail, *newbrush, *newbrushlist; bspbrush_t *lastb2; if (!brushlist) return NULL; if (!modelnum) qprintf("%5d brushes merged", nummerges = 0); do { for (tail = brushlist; tail; tail = tail->next) { if (!tail->next) break; } //end for merged = 0; newbrushlist = NULL; for (b1 = brushlist; b1; b1 = brushlist) { lastb2 = b1; for (b2 = b1->next; b2; b2 = b2->next) { //can't merge brushes with different contents if (b1->side != b2->side) newbrush = NULL; else newbrush = TryMergeBrushes(b1, b2); //if a merged brush is created if (newbrush) { //copy the brush contents newbrush->side = b1->side; //add the new brush to the end of the list tail->next = newbrush; //remove the second brush from the list lastb2->next = b2->next; //remove the first brush from the list brushlist = brushlist->next; //free the merged brushes FreeBrush(b1); FreeBrush(b2); //get a new tail brush for (tail = brushlist; tail; tail = tail->next) { if (!tail->next) break; } //end for merged++; if (!modelnum) qprintf("\r%5d", nummerges++); break; } //end if lastb2 = b2; } //end for //if b1 can't be merged with any of the other brushes if (!b2) { brushlist = brushlist->next; //keep b1 b1->next = newbrushlist; newbrushlist = b1; } //end else } //end for brushlist = newbrushlist; } while(merged); if (!modelnum) qprintf("\n"); return newbrushlist; } //end of the function Q1_MergeBrushes //=========================================================================== // returns the amount the face and the winding overlap // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float Q1_FaceOnWinding(q1_dface_t *face, winding_t *winding) { int i, edgenum, side; float dist, area; q1_dplane_t plane; vec_t *v1, *v2; vec3_t normal, edgevec; winding_t *w; // w = CopyWinding(winding); memcpy(&plane, &q1_dplanes[face->planenum], sizeof(q1_dplane_t)); //check on which side of the plane the face is if (face->side) { VectorNegate(plane.normal, plane.normal); plane.dist = -plane.dist; } //end if for (i = 0; i < face->numedges && w; i++) { //get the first and second vertex of the edge edgenum = q1_dsurfedges[face->firstedge + i]; side = edgenum > 0; //if the face plane is flipped v1 = q1_dvertexes[q1_dedges[abs(edgenum)].v[side]].point; v2 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; //create a plane through the edge vector, orthogonal to the face plane //and with the normal vector pointing out of the face VectorSubtract(v1, v2, edgevec); CrossProduct(edgevec, plane.normal, normal); VectorNormalize(normal); dist = DotProduct(normal, v1); // ChopWindingInPlace(&w, normal, dist, 0.9); //CLIP_EPSILON } //end for if (w) { area = WindingArea(w); FreeWinding(w); return area; } //end if return 0; } //end of the function Q1_FaceOnWinding //=========================================================================== // returns a list with brushes created by splitting the given brush with // planes that go through the face edges and are orthogonal to the face plane // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *Q1_SplitBrushWithFace(bspbrush_t *brush, q1_dface_t *face) { int i, edgenum, side, planenum, splits; float dist; q1_dplane_t plane; vec_t *v1, *v2; vec3_t normal, edgevec; bspbrush_t *front, *back, *brushlist; memcpy(&plane, &q1_dplanes[face->planenum], sizeof(q1_dplane_t)); //check on which side of the plane the face is if (face->side) { VectorNegate(plane.normal, plane.normal); plane.dist = -plane.dist; } //end if splits = 0; brushlist = NULL; for (i = 0; i < face->numedges; i++) { //get the first and second vertex of the edge edgenum = q1_dsurfedges[face->firstedge + i]; side = edgenum > 0; //if the face plane is flipped v1 = q1_dvertexes[q1_dedges[abs(edgenum)].v[side]].point; v2 = q1_dvertexes[q1_dedges[abs(edgenum)].v[!side]].point; //create a plane through the edge vector, orthogonal to the face plane //and with the normal vector pointing out of the face VectorSubtract(v1, v2, edgevec); CrossProduct(edgevec, plane.normal, normal); VectorNormalize(normal); dist = DotProduct(normal, v1); // planenum = FindFloatPlane(normal, dist); //split the current brush SplitBrush(brush, planenum, &front, &back); //if there is a back brush just put it in the list if (back) { //copy the brush contents back->side = brush->side; // back->next = brushlist; brushlist = back; splits++; } //end if if (!front) { Log_Print("Q1_SplitBrushWithFace: no new brush\n"); FreeBrushList(brushlist); return NULL; } //end if //copy the brush contents front->side = brush->side; //continue splitting the front brush brush = front; } //end for if (!splits) { FreeBrush(front); return NULL; } //end if front->next = brushlist; brushlist = front; return brushlist; } //end of the function Q1_SplitBrushWithFace //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== bspbrush_t *Q1_TextureBrushes(bspbrush_t *brushlist, int modelnum) { float area, largestarea; int i, n, texinfonum, sn, numbrushes, ofs; int bestfacenum, sidenodenum; side_t *side; q1_dmiptexlump_t *miptexlump; q1_miptex_t *miptex; bspbrush_t *brush, *nextbrush, *prevbrush, *newbrushes, *brushlistend; vec_t defaultvec[4] = {1, 0, 0, 0}; if (!modelnum) qprintf("texturing brushes\n"); if (!modelnum) qprintf("%5d brushes", numbrushes = 0); //get a pointer to the last brush in the list for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) { if (!brushlistend->next) break; } //end for //there's no previous brush when at the start of the list prevbrush = NULL; //go over the brush list for (brush = brushlist; brush; brush = nextbrush) { nextbrush = brush->next; //find a texinfo for every brush side for (sn = 0; sn < brush->numsides; sn++) { side = &brush->sides[sn]; // if (side->flags & SFL_TEXTURED) continue; //number of the node that created this brush side sidenodenum = side->surf; //see midwinding in Q1_SplitBrush //no face found yet bestfacenum = -1; //minimum face size largestarea = 1; //if optimizing the texture placement and not going for the //least number of brushes if (!lessbrushes) { for (i = 0; i < q1_numfaces; i++) { //the face must be in the same plane as the node plane that created //this brush side if (q1_dfaces[i].planenum == q1_dnodes[sidenodenum].planenum) { //get the area the face and the brush side overlap area = Q1_FaceOnWinding(&q1_dfaces[i], side->winding); //if this face overlaps the brush side winding more than previous faces if (area > largestarea) { //if there already was a face for texturing this brush side with //a different texture if (bestfacenum >= 0 && (q1_dfaces[bestfacenum].texinfo != q1_dfaces[i].texinfo)) { //split the brush to fit the texture newbrushes = Q1_SplitBrushWithFace(brush, &q1_dfaces[i]); //if new brushes where created if (newbrushes) { //remove the current brush from the list if (prevbrush) prevbrush->next = brush->next; else brushlist = brush->next; if (brushlistend == brush) { brushlistend = prevbrush; nextbrush = newbrushes; } //end if //add the new brushes to the end of the list if (brushlistend) brushlistend->next = newbrushes; else brushlist = newbrushes; //free the current brush FreeBrush(brush); //don't forget about the prevbrush pointer at the bottom of //the outer loop brush = prevbrush; //find the end of the list for (brushlistend = brushlist; brushlistend; brushlistend = brushlistend->next) { if (!brushlistend->next) break; } //end for break; } //end if else { Log_Write("brush %d: no real texture split", numbrushes); } //end else } //end if else { //best face for texturing this brush side bestfacenum = i; } //end else } //end if } //end if } //end for //if the brush was split the original brush is removed //and we just continue with the next one in the list if (i < q1_numfaces) break; } //end if else { //find the face with the largest overlap with this brush side //for texturing the brush side for (i = 0; i < q1_numfaces; i++) { //the face must be in the same plane as the node plane that created //this brush side if (q1_dfaces[i].planenum == q1_dnodes[sidenodenum].planenum) { //get the area the face and the brush side overlap area = Q1_FaceOnWinding(&q1_dfaces[i], side->winding); //if this face overlaps the brush side winding more than previous faces if (area > largestarea) { largestarea = area; bestfacenum = i; } //end if } //end if } //end for } //end else //if a face was found for texturing this brush side if (bestfacenum >= 0) { //set the MAP texinfo values texinfonum = q1_dfaces[bestfacenum].texinfo; for (n = 0; n < 4; n++) { map_texinfo[texinfonum].vecs[0][n] = q1_texinfo[texinfonum].vecs[0][n]; map_texinfo[texinfonum].vecs[1][n] = q1_texinfo[texinfonum].vecs[1][n]; } //end for //make sure the two vectors aren't of zero length otherwise use the default //vector to prevent a divide by zero in the map writing if (VectorLength(map_texinfo[texinfonum].vecs[0]) < 0.01) memcpy(map_texinfo[texinfonum].vecs[0], defaultvec, sizeof(defaultvec)); if (VectorLength(map_texinfo[texinfonum].vecs[1]) < 0.01) memcpy(map_texinfo[texinfonum].vecs[1], defaultvec, sizeof(defaultvec)); // map_texinfo[texinfonum].flags = q1_texinfo[texinfonum].flags; map_texinfo[texinfonum].value = 0; //Q1 and HL texinfos don't have a value //the mip texture miptexlump = (q1_dmiptexlump_t *) q1_dtexdata; ofs = miptexlump->dataofs[q1_texinfo[texinfonum].miptex]; if ( ofs > q1_texdatasize ) { ofs = miptexlump->dataofs[0]; } miptex = (q1_miptex_t *)((byte *)miptexlump + ofs); //get the mip texture name strcpy(map_texinfo[texinfonum].texture, miptex->name); //no animations in Quake1 and Half-Life mip textures map_texinfo[texinfonum].nexttexinfo = -1; //store the texinfo number side->texinfo = texinfonum; // if (texinfonum > map_numtexinfo) map_numtexinfo = texinfonum; //this side is textured side->flags |= SFL_TEXTURED; } //end if else { //no texture for this side side->texinfo = TEXINFO_NODE; //this side is textured side->flags |= SFL_TEXTURED; } //end if } //end for // if (!modelnum && prevbrush != brush) qprintf("\r%5d", ++numbrushes); //previous brush in the list prevbrush = brush; } //end for if (!modelnum) qprintf("\n"); //return the new list with brushes return brushlist; } //end of the function Q1_TextureBrushes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q1_FixContentsTextures(bspbrush_t *brushlist) { int i, texinfonum; bspbrush_t *brush; for (brush = brushlist; brush; brush = brush->next) { //only fix the textures of water, slime and lava brushes if (brush->side != CONTENTS_WATER && brush->side != CONTENTS_SLIME && brush->side != CONTENTS_LAVA) continue; // for (i = 0; i < brush->numsides; i++) { texinfonum = brush->sides[i].texinfo; if (Q1_TextureContents(map_texinfo[texinfonum].texture) == brush->side) break; } //end for //if no specific contents texture was found if (i >= brush->numsides) { texinfonum = -1; for (i = 0; i < map_numtexinfo; i++) { if (Q1_TextureContents(map_texinfo[i].texture) == brush->side) { texinfonum = i; break; } //end if } //end for } //end if // if (texinfonum >= 0) { //give all the brush sides this contents texture for (i = 0; i < brush->numsides; i++) { brush->sides[i].texinfo = texinfonum; } //end for } //end if else Log_Print("brush contents %d with wrong textures\n", brush->side); // } //end for /* for (brush = brushlist; brush; brush = brush->next) { //give all the brush sides this contents texture for (i = 0; i < brush->numsides; i++) { if (Q1_TextureContents(map_texinfo[texinfonum].texture) != brush->side) { Error("brush contents %d with wrong contents textures %s\n", brush->side, Q1_TextureContents(map_texinfo[texinfonum].texture)); } //end if } //end for } //end for*/ } //end of the function Q1_FixContentsTextures //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q1_BSPBrushToMapBrush(bspbrush_t *bspbrush, entity_t *mapent) { mapbrush_t *mapbrush; side_t *side; int i, besttexinfo; CheckBSPBrush(bspbrush); if (nummapbrushes >= MAX_MAPFILE_BRUSHES) Error ("nummapbrushes == MAX_MAPFILE_BRUSHES"); mapbrush = &mapbrushes[nummapbrushes]; mapbrush->original_sides = &brushsides[nummapbrushsides]; mapbrush->entitynum = mapent - entities; mapbrush->brushnum = nummapbrushes - mapent->firstbrush; mapbrush->leafnum = -1; mapbrush->numsides = 0; besttexinfo = TEXINFO_NODE; for (i = 0; i < bspbrush->numsides; i++) { if (!bspbrush->sides[i].winding) continue; // if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) Error ("MAX_MAPFILE_BRUSHSIDES"); side = &brushsides[nummapbrushsides]; //the contents of the bsp brush is stored in the side variable side->contents = bspbrush->side; side->surf = 0; side->planenum = bspbrush->sides[i].planenum; side->texinfo = bspbrush->sides[i].texinfo; if (side->texinfo != TEXINFO_NODE) { //this brush side is textured side->flags |= SFL_TEXTURED; besttexinfo = side->texinfo; } //end if // nummapbrushsides++; mapbrush->numsides++; } //end for // if (besttexinfo == TEXINFO_NODE) { mapbrush->numsides = 0; q1_numclipbrushes++; return; } //end if //set the texinfo for all the brush sides without texture for (i = 0; i < mapbrush->numsides; i++) { if (mapbrush->original_sides[i].texinfo == TEXINFO_NODE) { mapbrush->original_sides[i].texinfo = besttexinfo; } //end if } //end for //contents of the brush mapbrush->contents = bspbrush->side; // if (create_aas) { //create the AAS brushes from this brush, add brush bevels AAS_CreateMapBrushes(mapbrush, mapent, true); return; } //end if //create windings for sides and bounds for brush MakeBrushWindings(mapbrush); //add brush bevels AddBrushBevels(mapbrush); //a new brush has been created nummapbrushes++; mapent->numbrushes++; } //end of the function Q1_BSPBrushToMapBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q1_CreateMapBrushes(entity_t *mapent, int modelnum) { bspbrush_t *brushlist, *brush, *nextbrush; int i; //create brushes from the model BSP tree brushlist = Q1_CreateBrushesFromBSP(modelnum); //texture the brushes and split them when necesary brushlist = Q1_TextureBrushes(brushlist, modelnum); //fix the contents textures of all brushes Q1_FixContentsTextures(brushlist); // if (!nobrushmerge) { brushlist = Q1_MergeBrushes(brushlist, modelnum); //brushlist = Q1_MergeBrushes(brushlist, modelnum); } //end if // if (!modelnum) qprintf("converting brushes to map brushes\n"); if (!modelnum) qprintf("%5d brushes", i = 0); for (brush = brushlist; brush; brush = nextbrush) { nextbrush = brush->next; Q1_BSPBrushToMapBrush(brush, mapent); brush->next = NULL; FreeBrush(brush); if (!modelnum) qprintf("\r%5d", ++i); } //end for if (!modelnum) qprintf("\n"); } //end of the function Q1_CreateMapBrushes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q1_ResetMapLoading(void) { } //end of the function Q1_ResetMapLoading //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q1_LoadMapFromBSP(char *filename, int offset, int length) { int i, modelnum; char *model, *classname; Log_Print("-- Q1_LoadMapFromBSP --\n"); //the loaded map type loadedmaptype = MAPTYPE_QUAKE1; // qprintf("loading map from %s at %d\n", filename, offset); //load the Half-Life BSP file Q1_LoadBSPFile(filename, offset, length); // q1_numclipbrushes = 0; //CreatePath(path); //Q1_CreateQ2WALFiles(path); //parse the entities from the BSP Q1_ParseEntities(); //clear the map mins and maxs ClearBounds(map_mins, map_maxs); // qprintf("creating Quake1 brushes\n"); if (lessbrushes) qprintf("creating minimum number of brushes\n"); else qprintf("placing textures correctly\n"); // for (i = 0; i < num_entities; i++) { entities[i].firstbrush = nummapbrushes; entities[i].numbrushes = 0; // classname = ValueForKey(&entities[i], "classname"); if (classname && !strcmp(classname, "worldspawn")) { modelnum = 0; } //end if else { // model = ValueForKey(&entities[i], "model"); if (!model || *model != '*') continue; model++; modelnum = atoi(model); } //end else //create map brushes for the entity Q1_CreateMapBrushes(&entities[i], modelnum); } //end for // qprintf("%5d map brushes\n", nummapbrushes); qprintf("%5d clip brushes\n", q1_numclipbrushes); } //end of the function Q1_LoadMapFromBSP ================================================ FILE: code/bspc/map_q2.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ //=========================================================================== // ANSI, Area Navigational System Interface // AAS, Area Awareness System //=========================================================================== #include "qbsp.h" #include "l_mem.h" #include "../botlib/aasfile.h" //aas_bbox_t #include "aas_store.h" //AAS_MAX_BBOXES #include "aas_cfg.h" #include "aas_map.h" //AAS_CreateMapBrushes #include "l_bsp_q2.h" #ifdef ME #define NODESTACKSIZE 1024 int nodestack[NODESTACKSIZE]; int *nodestackptr; int nodestacksize = 0; int brushmodelnumbers[MAX_MAPFILE_BRUSHES]; int dbrushleafnums[MAX_MAPFILE_BRUSHES]; int dplanes2mapplanes[MAX_MAPFILE_PLANES]; #endif //ME //==================================================================== //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q2_CreateMapTexinfo(void) { int i; for (i = 0; i < numtexinfo; i++) { memcpy(map_texinfo[i].vecs, texinfo[i].vecs, sizeof(float) * 2 * 4); map_texinfo[i].flags = texinfo[i].flags; map_texinfo[i].value = texinfo[i].value; strcpy(map_texinfo[i].texture, texinfo[i].texture); map_texinfo[i].nexttexinfo = 0; } //end for } //end of the function Q2_CreateMapTexinfo /* =========== Q2_BrushContents =========== */ int Q2_BrushContents (mapbrush_t *b) { int contents; side_t *s; int i; int trans; s = &b->original_sides[0]; contents = s->contents; trans = texinfo[s->texinfo].flags; for (i = 1; i < b->numsides; i++, s++) { s = &b->original_sides[i]; trans |= texinfo[s->texinfo].flags; if (s->contents != contents) { Log_Print("Entity %i, Brush %i: mixed face contents\n" , b->entitynum, b->brushnum); Log_Print("texture name = %s\n", texinfo[s->texinfo].texture); break; } } // if any side is translucent, mark the contents // and change solid to window if ( trans & (SURF_TRANS33|SURF_TRANS66) ) { contents |= CONTENTS_Q2TRANSLUCENT; if (contents & CONTENTS_SOLID) { contents &= ~CONTENTS_SOLID; contents |= CONTENTS_WINDOW; } } return contents; } #ifdef ME #define BBOX_NORMAL_EPSILON 0.0001 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void MakeAreaPortalBrush(mapbrush_t *brush) { int sn; side_t *s; brush->contents = CONTENTS_AREAPORTAL; for (sn = 0; sn < brush->numsides; sn++) { s = brush->original_sides + sn; //make sure the surfaces are not hint or skip s->surf &= ~(SURF_HINT|SURF_SKIP); // s->texinfo = 0; s->contents = CONTENTS_AREAPORTAL; } //end for } //end of the function MakeAreaPortalBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void DPlanes2MapPlanes(void) { int i; for (i = 0; i < numplanes; i++) { dplanes2mapplanes[i] = FindFloatPlane(dplanes[i].normal, dplanes[i].dist); } //end for } //end of the function DPlanes2MapPlanes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void MarkVisibleBrushSides(mapbrush_t *brush) { int n, i, planenum; side_t *side; dface_t *face; // for (n = 0; n < brush->numsides; n++) { side = brush->original_sides + n; //if this side is a bevel or the leaf number of the brush is unknown if ((side->flags & SFL_BEVEL) || brush->leafnum < 0) { //this side is a valid splitter side->flags |= SFL_VISIBLE; continue; } //end if //assum this side will not be used as a splitter side->flags &= ~SFL_VISIBLE; //check if the side plane is used by a visible face for (i = 0; i < numfaces; i++) { face = &dfaces[i]; planenum = dplanes2mapplanes[face->planenum]; if ((planenum & ~1) == (side->planenum & ~1)) { //this side is a valid splitter side->flags |= SFL_VISIBLE; } //end if } //end for } //end for } //end of the function MarkVisibleBrushSides #endif //ME /* ================= Q2_ParseBrush ================= */ void Q2_ParseBrush (script_t *script, entity_t *mapent) { mapbrush_t *b; int i, j, k; int mt; side_t *side, *s2; int planenum; brush_texture_t td; int planepts[3][3]; token_t token; if (nummapbrushes >= MAX_MAPFILE_BRUSHES) Error ("nummapbrushes == MAX_MAPFILE_BRUSHES"); b = &mapbrushes[nummapbrushes]; b->original_sides = &brushsides[nummapbrushsides]; b->entitynum = num_entities-1; b->brushnum = nummapbrushes - mapent->firstbrush; b->leafnum = -1; do { if (!PS_ReadToken(script, &token)) break; if (!strcmp(token.string, "}") ) break; //IDBUG: mixed use of MAX_MAPFILE_? and MAX_MAP_? this could // lead to out of bound indexing of the arrays if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) Error ("MAX_MAPFILE_BRUSHSIDES"); side = &brushsides[nummapbrushsides]; //read the three point plane definition for (i = 0; i < 3; i++) { if (i != 0) PS_ExpectTokenString(script, "("); for (j = 0; j < 3; j++) { PS_ExpectAnyToken(script, &token); planepts[i][j] = atof(token.string); } //end for PS_ExpectTokenString(script, ")"); } //end for // //read the texturedef // PS_ExpectAnyToken(script, &token); strcpy(td.name, token.string); PS_ExpectAnyToken(script, &token); td.shift[0] = atol(token.string); PS_ExpectAnyToken(script, &token); td.shift[1] = atol(token.string); PS_ExpectAnyToken(script, &token); td.rotate = atol(token.string); PS_ExpectAnyToken(script, &token); td.scale[0] = atof(token.string); PS_ExpectAnyToken(script, &token); td.scale[1] = atof(token.string); //find default flags and values mt = FindMiptex (td.name); td.flags = textureref[mt].flags; td.value = textureref[mt].value; side->contents = textureref[mt].contents; side->surf = td.flags = textureref[mt].flags; //check if there's a number available if (PS_CheckTokenType(script, TT_NUMBER, 0, &token)) { side->contents = token.intvalue; PS_ExpectTokenType(script, TT_NUMBER, 0, &token); side->surf = td.flags = token.intvalue; PS_ExpectTokenType(script, TT_NUMBER, 0, &token); td.value = token.intvalue; } // translucent objects are automatically classified as detail if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) side->contents |= CONTENTS_DETAIL; if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) side->contents |= CONTENTS_DETAIL; if (fulldetail) side->contents &= ~CONTENTS_DETAIL; if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) side->contents |= CONTENTS_SOLID; // hints and skips are never detail, and have no content if (side->surf & (SURF_HINT|SURF_SKIP) ) { side->contents = 0; side->surf &= ~CONTENTS_DETAIL; } #ifdef ME //for creating AAS... this side is textured side->flags |= SFL_TEXTURED; #endif //ME // // find the plane number // planenum = PlaneFromPoints (planepts[0], planepts[1], planepts[2]); if (planenum == -1) { Log_Print("Entity %i, Brush %i: plane with no normal\n" , b->entitynum, b->brushnum); continue; } // // see if the plane has been used already // for (k=0 ; knumsides ; k++) { s2 = b->original_sides + k; if (s2->planenum == planenum) { Log_Print("Entity %i, Brush %i: duplicate plane\n" , b->entitynum, b->brushnum); break; } if ( s2->planenum == (planenum^1) ) { Log_Print("Entity %i, Brush %i: mirrored plane\n" , b->entitynum, b->brushnum); break; } } if (k != b->numsides) continue; // duplicated // // keep this side // side = b->original_sides + b->numsides; side->planenum = planenum; side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], &td, vec3_origin); // save the td off in case there is an origin brush and we // have to recalculate the texinfo side_brushtextures[nummapbrushsides] = td; nummapbrushsides++; b->numsides++; } while (1); // get the content for the entire brush b->contents = Q2_BrushContents (b); #ifdef ME if (BrushExists(b)) { c_squattbrushes++; b->numsides = 0; return; } //end if if (create_aas) { //create AAS brushes, and add brush bevels AAS_CreateMapBrushes(b, mapent, true); //NOTE: if we return here then duplicate plane errors occur for the non world entities return; } //end if #endif //ME // allow detail brushes to be removed if (nodetail && (b->contents & CONTENTS_DETAIL) ) { b->numsides = 0; return; } // allow water brushes to be removed if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) { b->numsides = 0; return; } // create windings for sides and bounds for brush MakeBrushWindings (b); // brushes that will not be visible at all will never be // used as bsp splitters if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) { c_clipbrushes++; for (i=0 ; inumsides ; i++) b->original_sides[i].texinfo = TEXINFO_NODE; } // // origin brushes are removed, but they set // the rotation origin for the rest of the brushes // in the entity. After the entire entity is parsed, // the planenums and texinfos will be adjusted for // the origin brush // if (b->contents & CONTENTS_ORIGIN) { char string[32]; vec3_t origin; if (num_entities == 1) { Error ("Entity %i, Brush %i: origin brushes not allowed in world" , b->entitynum, b->brushnum); return; } VectorAdd (b->mins, b->maxs, origin); VectorScale (origin, 0.5, origin); sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); SetKeyValue (&entities[b->entitynum], "origin", string); VectorCopy (origin, entities[b->entitynum].origin); // don't keep this brush b->numsides = 0; return; } AddBrushBevels(b); nummapbrushes++; mapent->numbrushes++; } /* ================ Q2_MoveBrushesToWorld Takes all of the brushes from the current entity and adds them to the world's brush list. Used by func_group and func_areaportal ================ */ void Q2_MoveBrushesToWorld (entity_t *mapent) { int newbrushes; int worldbrushes; mapbrush_t *temp; int i; // this is pretty gross, because the brushes are expected to be // in linear order for each entity newbrushes = mapent->numbrushes; worldbrushes = entities[0].numbrushes; temp = GetMemory(newbrushes*sizeof(mapbrush_t)); memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); #if 0 // let them keep their original brush numbers for (i=0 ; inumbrushes = 0; } /* ================ Q2_ParseMapEntity ================ */ qboolean Q2_ParseMapEntity(script_t *script) { entity_t *mapent; epair_t *e; side_t *s; int i, j; int startbrush, startsides; vec_t newdist; mapbrush_t *b; token_t token; if (!PS_ReadToken(script, &token)) return false; if (strcmp(token.string, "{") ) Error ("ParseEntity: { not found"); if (num_entities == MAX_MAP_ENTITIES) Error ("num_entities == MAX_MAP_ENTITIES"); startbrush = nummapbrushes; startsides = nummapbrushsides; mapent = &entities[num_entities]; num_entities++; memset (mapent, 0, sizeof(*mapent)); mapent->firstbrush = nummapbrushes; mapent->numbrushes = 0; // mapent->portalareas[0] = -1; // mapent->portalareas[1] = -1; do { if (!PS_ReadToken(script, &token)) { Error("ParseEntity: EOF without closing brace"); } //end if if (!strcmp(token.string, "}")) break; if (!strcmp(token.string, "{")) { Q2_ParseBrush(script, mapent); } //end if else { PS_UnreadLastToken(script); e = ParseEpair(script); e->next = mapent->epairs; mapent->epairs = e; } //end else } while(1); GetVectorForKey(mapent, "origin", mapent->origin); // // if there was an origin brush, offset all of the planes and texinfo // if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) { for (i=0 ; inumbrushes ; i++) { b = &mapbrushes[mapent->firstbrush + i]; for (j=0 ; jnumsides ; j++) { s = &b->original_sides[j]; newdist = mapplanes[s->planenum].dist - DotProduct (mapplanes[s->planenum].normal, mapent->origin); s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], &side_brushtextures[s-brushsides], mapent->origin); } MakeBrushWindings (b); } } // group entities are just for editor convenience // toss all brushes into the world entity if (!strcmp ("func_group", ValueForKey (mapent, "classname"))) { Q2_MoveBrushesToWorld (mapent); mapent->numbrushes = 0; return true; } // areaportal entities move their brushes, but don't eliminate // the entity if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) { char str[128]; if (mapent->numbrushes != 1) Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); b = &mapbrushes[nummapbrushes-1]; b->contents = CONTENTS_AREAPORTAL; c_areaportals++; mapent->areaportalnum = c_areaportals; // set the portal number as "style" sprintf (str, "%i", c_areaportals); SetKeyValue (mapent, "style", str); Q2_MoveBrushesToWorld (mapent); return true; } return true; } //=================================================================== /* ================ LoadMapFile ================ */ void Q2_LoadMapFile(char *filename) { int i; script_t *script; Log_Print("-- Q2_LoadMapFile --\n"); #ifdef ME //loaded map type loadedmaptype = MAPTYPE_QUAKE2; //reset the map loading ResetMapLoading(); #endif //ME script = LoadScriptFile(filename); if (!script) { Log_Print("couldn't open %s\n", filename); return; } //end if //white spaces and escape characters inside a string are not allowed SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES | SCFL_NOSTRINGESCAPECHARS | SCFL_PRIMITIVE); nummapbrushsides = 0; num_entities = 0; while (Q2_ParseMapEntity(script)) { } ClearBounds (map_mins, map_maxs); for (i=0 ; i 4096) continue; // no valid points AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); } //end for PrintMapInfo(); //free the script FreeScript(script); // TestExpandBrushes (); // Q2_CreateMapTexinfo(); } //end of the function Q2_LoadMapFile #ifdef ME //Begin MAP loading from BSP file //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q2_SetLeafBrushesModelNumbers(int leafnum, int modelnum) { int i, brushnum; dleaf_t *leaf; leaf = &dleafs[leafnum]; for (i = 0; i < leaf->numleafbrushes; i++) { brushnum = dleafbrushes[leaf->firstleafbrush + i]; brushmodelnumbers[brushnum] = modelnum; dbrushleafnums[brushnum] = leafnum; } //end for } //end of the function Q2_SetLeafBrushesModelNumbers //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q2_InitNodeStack(void) { nodestackptr = nodestack; nodestacksize = 0; } //end of the function Q2_InitNodeStack //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q2_PushNodeStack(int num) { *nodestackptr = num; nodestackptr++; nodestacksize++; // if (nodestackptr >= &nodestack[NODESTACKSIZE]) { Error("Q2_PushNodeStack: stack overflow\n"); } //end if } //end of the function Q2_PushNodeStack //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Q2_PopNodeStack(void) { //if the stack is empty if (nodestackptr <= nodestack) return -1; //decrease stack pointer nodestackptr--; nodestacksize--; //return the top value from the stack return *nodestackptr; } //end of the function Q2_PopNodeStack //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q2_SetBrushModelNumbers(entity_t *mapent) { int n, pn; int leafnum; // Q2_InitNodeStack(); //head node (root) of the bsp tree n = dmodels[mapent->modelnum].headnode; pn = 0; do { //if we are in a leaf (negative node number) if (n < 0) { //number of the leaf leafnum = (-n) - 1; //set the brush numbers Q2_SetLeafBrushesModelNumbers(leafnum, mapent->modelnum); //walk back into the tree to find a second child to continue with for (pn = Q2_PopNodeStack(); pn >= 0; n = pn, pn = Q2_PopNodeStack()) { //if we took the first child at the parent node if (dnodes[pn].children[0] == n) break; } //end for //if the stack wasn't empty (if not processed whole tree) if (pn >= 0) { //push the parent node again Q2_PushNodeStack(pn); //we proceed with the second child of the parent node n = dnodes[pn].children[1]; } //end if } //end if else { //push the current node onto the stack Q2_PushNodeStack(n); //walk forward into the tree to the first child n = dnodes[n].children[0]; } //end else } while(pn >= 0); } //end of the function Q2_SetBrushModelNumbers //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q2_BSPBrushToMapBrush(dbrush_t *bspbrush, entity_t *mapent) { mapbrush_t *b; int i, k, n; side_t *side, *s2; int planenum; dbrushside_t *bspbrushside; dplane_t *bspplane; if (nummapbrushes >= MAX_MAPFILE_BRUSHES) Error ("nummapbrushes >= MAX_MAPFILE_BRUSHES"); b = &mapbrushes[nummapbrushes]; b->original_sides = &brushsides[nummapbrushsides]; b->entitynum = mapent-entities; b->brushnum = nummapbrushes - mapent->firstbrush; b->leafnum = dbrushleafnums[bspbrush - dbrushes]; for (n = 0; n < bspbrush->numsides; n++) { //pointer to the bsp brush side bspbrushside = &dbrushsides[bspbrush->firstside + n]; if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) { Error ("MAX_MAPFILE_BRUSHSIDES"); } //end if //pointer to the map brush side side = &brushsides[nummapbrushsides]; //if the BSP brush side is textured if (brushsidetextured[bspbrush->firstside + n]) side->flags |= SFL_TEXTURED; else side->flags &= ~SFL_TEXTURED; //ME: can get side contents and surf directly from BSP file side->contents = bspbrush->contents; //if the texinfo is TEXINFO_NODE if (bspbrushside->texinfo < 0) side->surf = 0; else side->surf = texinfo[bspbrushside->texinfo].flags; // translucent objects are automatically classified as detail if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) side->contents |= CONTENTS_DETAIL; if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) side->contents |= CONTENTS_DETAIL; if (fulldetail) side->contents &= ~CONTENTS_DETAIL; if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) side->contents |= CONTENTS_SOLID; // hints and skips are never detail, and have no content if (side->surf & (SURF_HINT|SURF_SKIP) ) { side->contents = 0; side->surf &= ~CONTENTS_DETAIL; } //ME: get a plane for this side bspplane = &dplanes[bspbrushside->planenum]; planenum = FindFloatPlane(bspplane->normal, bspplane->dist); // // see if the plane has been used already // //ME: this really shouldn't happen!!! //ME: otherwise the bsp file is corrupted?? //ME: still it seems to happen, maybe Johny Boy's //ME: brush bevel adding is crappy ? for (k = 0; k < b->numsides; k++) { s2 = b->original_sides + k; // if (DotProduct (mapplanes[s2->planenum].normal, mapplanes[planenum].normal) > 0.999 // && fabs(mapplanes[s2->planenum].dist - mapplanes[planenum].dist) < 0.01 ) if (s2->planenum == planenum) { Log_Print("Entity %i, Brush %i: duplicate plane\n" , b->entitynum, b->brushnum); break; } if ( s2->planenum == (planenum^1) ) { Log_Print("Entity %i, Brush %i: mirrored plane\n" , b->entitynum, b->brushnum); break; } } if (k != b->numsides) continue; // duplicated // // keep this side // //ME: reset pointer to side, why? hell I dunno (pointer is set above already) side = b->original_sides + b->numsides; //ME: store the plane number side->planenum = planenum; //ME: texinfo is already stored when bsp is loaded //NOTE: check for TEXINFO_NODE, otherwise crash in Q2_BrushContents if (bspbrushside->texinfo < 0) side->texinfo = 0; else side->texinfo = bspbrushside->texinfo; // save the td off in case there is an origin brush and we // have to recalculate the texinfo // ME: don't need to recalculate because it's already done // (for non-world entities) in the BSP file // side_brushtextures[nummapbrushsides] = td; nummapbrushsides++; b->numsides++; } //end for // get the content for the entire brush b->contents = bspbrush->contents; Q2_BrushContents(b); if (BrushExists(b)) { c_squattbrushes++; b->numsides = 0; return; } //end if //if we're creating AAS if (create_aas) { //create the AAS brushes from this brush, don't add brush bevels AAS_CreateMapBrushes(b, mapent, false); return; } //end if // allow detail brushes to be removed if (nodetail && (b->contents & CONTENTS_DETAIL) ) { b->numsides = 0; return; } //end if // allow water brushes to be removed if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) { b->numsides = 0; return; } //end if // create windings for sides and bounds for brush MakeBrushWindings(b); //mark brushes without winding or with a tiny window as bevels MarkBrushBevels(b); // brushes that will not be visible at all will never be // used as bsp splitters if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) { c_clipbrushes++; for (i = 0; i < b->numsides; i++) b->original_sides[i].texinfo = TEXINFO_NODE; } //end for // // origin brushes are removed, but they set // the rotation origin for the rest of the brushes // in the entity. After the entire entity is parsed, // the planenums and texinfos will be adjusted for // the origin brush // //ME: not needed because the entities in the BSP file already // have an origin set // if (b->contents & CONTENTS_ORIGIN) // { // char string[32]; // vec3_t origin; // // if (num_entities == 1) // { // Error ("Entity %i, Brush %i: origin brushes not allowed in world" // , b->entitynum, b->brushnum); // return; // } // // VectorAdd (b->mins, b->maxs, origin); // VectorScale (origin, 0.5, origin); // // sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); // SetKeyValue (&entities[b->entitynum], "origin", string); // // VectorCopy (origin, entities[b->entitynum].origin); // // // don't keep this brush // b->numsides = 0; // // return; // } //ME: the bsp brushes already have bevels, so we won't try to // add them again (especially since Johny Boy's bevel adding might // be crappy) // AddBrushBevels(b); nummapbrushes++; mapent->numbrushes++; } //end of the function Q2_BSPBrushToMapBrush //=========================================================================== //=========================================================================== void Q2_ParseBSPBrushes(entity_t *mapent) { int i; //give all the brushes that belong to this entity the number of the //BSP model used by this entity Q2_SetBrushModelNumbers(mapent); //now parse all the brushes with the correct mapent->modelnum for (i = 0; i < numbrushes; i++) { if (brushmodelnumbers[i] == mapent->modelnum) { Q2_BSPBrushToMapBrush(&dbrushes[i], mapent); } //end if } //end for } //end of the function Q2_ParseBSPBrushes //=========================================================================== //=========================================================================== qboolean Q2_ParseBSPEntity(int entnum) { entity_t *mapent; char *model; int startbrush, startsides; startbrush = nummapbrushes; startsides = nummapbrushsides; mapent = &entities[entnum];//num_entities]; mapent->firstbrush = nummapbrushes; mapent->numbrushes = 0; mapent->modelnum = -1; //-1 = no model model = ValueForKey(mapent, "model"); if (model && strlen(model)) { if (*model != '*') { Error("Q2_ParseBSPEntity: model number without leading *"); } //end if //get the model number of this entity (skip the leading *) mapent->modelnum = atoi(&model[1]); } //end if GetVectorForKey(mapent, "origin", mapent->origin); //if this is the world entity it has model number zero //the world entity has no model key if (!strcmp("worldspawn", ValueForKey(mapent, "classname"))) { mapent->modelnum = 0; } //end if //if the map entity has a BSP model (a modelnum of -1 is used for //entities that aren't using a BSP model) if (mapent->modelnum >= 0) { //parse the bsp brushes Q2_ParseBSPBrushes(mapent); } //end if // //the origin of the entity is already taken into account // //func_group entities can't be in the bsp file // //check out the func_areaportal entities if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) { c_areaportals++; mapent->areaportalnum = c_areaportals; return true; } //end if return true; } //end of the function Q2_ParseBSPEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q2_LoadMapFromBSP(char *filename, int offset, int length) { int i; Log_Print("-- Q2_LoadMapFromBSP --\n"); //loaded map type loadedmaptype = MAPTYPE_QUAKE2; Log_Print("Loading map from %s...\n", filename); //load the bsp file Q2_LoadBSPFile(filename, offset, length); //create an index from bsp planes to map planes //DPlanes2MapPlanes(); //clear brush model numbers for (i = 0; i < MAX_MAPFILE_BRUSHES; i++) brushmodelnumbers[i] = -1; nummapbrushsides = 0; num_entities = 0; Q2_ParseEntities(); // for (i = 0; i < num_entities; i++) { Q2_ParseBSPEntity(i); } //end for //get the map mins and maxs from the world model ClearBounds(map_mins, map_maxs); for (i = 0; i < entities[0].numbrushes; i++) { if (mapbrushes[i].mins[0] > 4096) continue; //no valid points AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); } //end for PrintMapInfo(); // Q2_CreateMapTexinfo(); } //end of the function Q2_LoadMapFromBSP void Q2_ResetMapLoading(void) { //reset for map loading from bsp memset(nodestack, 0, NODESTACKSIZE * sizeof(int)); nodestackptr = NULL; nodestacksize = 0; memset(brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof(int)); } //end of the function Q2_ResetMapLoading //End MAP loading from BSP file #endif //ME //==================================================================== /* ================ TestExpandBrushes Expands all the brush planes and saves a new map out ================ */ void TestExpandBrushes (void) { FILE *f; side_t *s; int i, j, bn; winding_t *w; char *name = "expanded.map"; mapbrush_t *brush; vec_t dist; Log_Print("writing %s\n", name); f = fopen (name, "wb"); if (!f) Error ("Can't write %s\n", name); fprintf (f, "{\n\"classname\" \"worldspawn\"\n"); for (bn=0 ; bnnumsides ; i++) { s = brush->original_sides + i; dist = mapplanes[s->planenum].dist; for (j=0 ; j<3 ; j++) dist += fabs( 16 * mapplanes[s->planenum].normal[j] ); w = BaseWindingForPlane (mapplanes[s->planenum].normal, dist); fprintf (f,"( %i %i %i ) ", (int)w->p[0][0], (int)w->p[0][1], (int)w->p[0][2]); fprintf (f,"( %i %i %i ) ", (int)w->p[1][0], (int)w->p[1][1], (int)w->p[1][2]); fprintf (f,"( %i %i %i ) ", (int)w->p[2][0], (int)w->p[2][1], (int)w->p[2][2]); fprintf (f, "%s 0 0 0 1 1\n", texinfo[s->texinfo].texture); FreeWinding (w); } fprintf (f, "}\n"); } fprintf (f, "}\n"); fclose (f); Error ("can't proceed after expanding brushes"); } //end of the function TestExpandBrushes ================================================ FILE: code/bspc/map_q3.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "l_mem.h" #include "../botlib/aasfile.h" //aas_bbox_t #include "aas_store.h" //AAS_MAX_BBOXES #include "aas_cfg.h" #include "aas_map.h" //AAS_CreateMapBrushes #include "l_bsp_q3.h" #include "../qcommon/cm_patch.h" #include "../game/surfaceflags.h" #define NODESTACKSIZE 1024 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintContents(int contents); int Q3_BrushContents(mapbrush_t *b) { int contents, i, mixed, hint; side_t *s; s = &b->original_sides[0]; contents = s->contents; // mixed = false; hint = false; for (i = 1; i < b->numsides; i++) { s = &b->original_sides[i]; if (s->contents != contents) mixed = true; if (s->surf & (SURF_HINT|SURF_SKIP)) hint = true; contents |= s->contents; } //end for // if (hint) { if (contents) { Log_Write("WARNING: hint brush with contents: "); PrintContents(contents); Log_Write("\r\n"); // Log_Write("brush contents is: "); PrintContents(b->contents); Log_Write("\r\n"); } //end if return 0; } //end if //Log_Write("brush %d contents ", nummapbrushes); //PrintContents(contents); //Log_Write("\r\n"); //remove ladder and fog contents contents &= ~(CONTENTS_LADDER|CONTENTS_FOG); // if (mixed) { Log_Write("Entity %i, Brush %i: mixed face contents " , b->entitynum, b->brushnum); PrintContents(contents); Log_Write("\r\n"); // Log_Write("brush contents is: "); PrintContents(b->contents); Log_Write("\r\n"); // if (contents & CONTENTS_DONOTENTER) return CONTENTS_DONOTENTER;//Log_Print("mixed contents with donotenter\n"); /* Log_Print("contents:"); PrintContents(contents); Log_Print("\ncontents:"); PrintContents(s->contents); Log_Print("\n"); Log_Print("texture name = %s\n", texinfo[s->texinfo].texture); */ //if liquid brush if (contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) { return (contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)); } //end if if (contents & CONTENTS_PLAYERCLIP) return (contents & CONTENTS_PLAYERCLIP); return (contents & CONTENTS_SOLID); } //end if /* if (contents & CONTENTS_AREAPORTAL) { static int num; Log_Write("Entity %i, Brush %i: area portal %d\r\n", b->entitynum, b->brushnum, num++); } //end if*/ if (contents == (contents & CONTENTS_STRUCTURAL)) { //Log_Print("brush %i is only structural\n", b->brushnum); contents = 0; } //end if if (contents & CONTENTS_DONOTENTER) { Log_Print("brush %i is a donotenter brush, c = %X\n", b->brushnum, contents); } //end if return contents; } //end of the function Q3_BrushContents #define BBOX_NORMAL_EPSILON 0.0001 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q3_DPlanes2MapPlanes(void) { int i; for (i = 0; i < q3_numplanes; i++) { dplanes2mapplanes[i] = FindFloatPlane(q3_dplanes[i].normal, q3_dplanes[i].dist); } //end for } //end of the function Q3_DPlanes2MapPlanes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q3_BSPBrushToMapBrush(q3_dbrush_t *bspbrush, entity_t *mapent) { mapbrush_t *b; int i, k, n; side_t *side, *s2; int planenum; q3_dbrushside_t *bspbrushside; q3_dplane_t *bspplane; if (nummapbrushes >= MAX_MAPFILE_BRUSHES) Error ("nummapbrushes >= MAX_MAPFILE_BRUSHES"); b = &mapbrushes[nummapbrushes]; b->original_sides = &brushsides[nummapbrushsides]; b->entitynum = mapent-entities; b->brushnum = nummapbrushes - mapent->firstbrush; b->leafnum = dbrushleafnums[bspbrush - q3_dbrushes]; for (n = 0; n < bspbrush->numSides; n++) { //pointer to the bsp brush side bspbrushside = &q3_dbrushsides[bspbrush->firstSide + n]; if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) { Error ("MAX_MAPFILE_BRUSHSIDES"); } //end if //pointer to the map brush side side = &brushsides[nummapbrushsides]; //if the BSP brush side is textured if (q3_dbrushsidetextured[bspbrush->firstSide + n]) side->flags |= SFL_TEXTURED|SFL_VISIBLE; else side->flags &= ~SFL_TEXTURED; //NOTE: all Quake3 sides are assumed textured //side->flags |= SFL_TEXTURED|SFL_VISIBLE; // if (bspbrushside->shaderNum < 0) { side->contents = 0; side->surf = 0; } //end if else { side->contents = q3_dshaders[bspbrushside->shaderNum].contentFlags; side->surf = q3_dshaders[bspbrushside->shaderNum].surfaceFlags; if (strstr(q3_dshaders[bspbrushside->shaderNum].shader, "common/hint")) { //Log_Print("found hint side\n"); side->surf |= SURF_HINT; } //end if } //end else // if (side->surf & SURF_NODRAW) { side->flags |= SFL_TEXTURED|SFL_VISIBLE; } //end if /* if (side->contents & (CONTENTS_TRANSLUCENT|CONTENTS_STRUCTURAL)) { side->flags |= SFL_TEXTURED|SFL_VISIBLE; } //end if*/ // hints and skips are never detail, and have no content if (side->surf & (SURF_HINT|SURF_SKIP) ) { side->contents = 0; //Log_Print("found hint brush side\n"); } /* if ((side->surf & SURF_NODRAW) && (side->surf & SURF_NOIMPACT)) { side->contents = 0; side->surf &= ~CONTENTS_DETAIL; Log_Print("probably found hint brush in a BSP without hints being used\n"); } //end if*/ //ME: get a plane for this side bspplane = &q3_dplanes[bspbrushside->planeNum]; planenum = FindFloatPlane(bspplane->normal, bspplane->dist); // // see if the plane has been used already // //ME: this really shouldn't happen!!! //ME: otherwise the bsp file is corrupted?? //ME: still it seems to happen, maybe Johny Boy's //ME: brush bevel adding is crappy ? for (k = 0; k < b->numsides; k++) { s2 = b->original_sides + k; // if (DotProduct (mapplanes[s2->planenum].normal, mapplanes[planenum].normal) > 0.999 // && fabs(mapplanes[s2->planenum].dist - mapplanes[planenum].dist) < 0.01 ) if (s2->planenum == planenum) { Log_Print("Entity %i, Brush %i: duplicate plane\n" , b->entitynum, b->brushnum); break; } if ( s2->planenum == (planenum^1) ) { Log_Print("Entity %i, Brush %i: mirrored plane\n" , b->entitynum, b->brushnum); break; } } if (k != b->numsides) continue; // duplicated // // keep this side // //ME: reset pointer to side, why? hell I dunno (pointer is set above already) side = b->original_sides + b->numsides; //ME: store the plane number side->planenum = planenum; //ME: texinfo is already stored when bsp is loaded //NOTE: check for TEXINFO_NODE, otherwise crash in Q3_BrushContents //if (bspbrushside->texinfo < 0) side->texinfo = 0; //else side->texinfo = bspbrushside->texinfo; // save the td off in case there is an origin brush and we // have to recalculate the texinfo // ME: don't need to recalculate because it's already done // (for non-world entities) in the BSP file // side_brushtextures[nummapbrushsides] = td; nummapbrushsides++; b->numsides++; } //end for // get the content for the entire brush b->contents = q3_dshaders[bspbrush->shaderNum].contentFlags; b->contents &= ~(CONTENTS_LADDER|CONTENTS_FOG|CONTENTS_STRUCTURAL); // b->contents = Q3_BrushContents(b); // if (BrushExists(b)) { c_squattbrushes++; b->numsides = 0; return; } //end if //if we're creating AAS if (create_aas) { //create the AAS brushes from this brush, don't add brush bevels AAS_CreateMapBrushes(b, mapent, false); return; } //end if // allow detail brushes to be removed if (nodetail && (b->contents & CONTENTS_DETAIL) ) { b->numsides = 0; return; } //end if // allow water brushes to be removed if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) { b->numsides = 0; return; } //end if // create windings for sides and bounds for brush MakeBrushWindings(b); //mark brushes without winding or with a tiny window as bevels MarkBrushBevels(b); // brushes that will not be visible at all will never be // used as bsp splitters if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) { c_clipbrushes++; for (i = 0; i < b->numsides; i++) b->original_sides[i].texinfo = TEXINFO_NODE; } //end for // // origin brushes are removed, but they set // the rotation origin for the rest of the brushes // in the entity. After the entire entity is parsed, // the planenums and texinfos will be adjusted for // the origin brush // //ME: not needed because the entities in the BSP file already // have an origin set // if (b->contents & CONTENTS_ORIGIN) // { // char string[32]; // vec3_t origin; // // if (num_entities == 1) // { // Error ("Entity %i, Brush %i: origin brushes not allowed in world" // , b->entitynum, b->brushnum); // return; // } // // VectorAdd (b->mins, b->maxs, origin); // VectorScale (origin, 0.5, origin); // // sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); // SetKeyValue (&entities[b->entitynum], "origin", string); // // VectorCopy (origin, entities[b->entitynum].origin); // // // don't keep this brush // b->numsides = 0; // // return; // } //ME: the bsp brushes already have bevels, so we won't try to // add them again (especially since Johny Boy's bevel adding might // be crappy) // AddBrushBevels(b); nummapbrushes++; mapent->numbrushes++; } //end of the function Q3_BSPBrushToMapBrush //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q3_ParseBSPBrushes(entity_t *mapent) { int i; for (i = 0; i < q3_dmodels[mapent->modelnum].numBrushes; i++) { Q3_BSPBrushToMapBrush(&q3_dbrushes[q3_dmodels[mapent->modelnum].firstBrush + i], mapent); } //end for } //end of the function Q3_ParseBSPBrushes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean Q3_ParseBSPEntity(int entnum) { entity_t *mapent; char *model; int startbrush, startsides; startbrush = nummapbrushes; startsides = nummapbrushsides; mapent = &entities[entnum];//num_entities]; mapent->firstbrush = nummapbrushes; mapent->numbrushes = 0; mapent->modelnum = -1; //-1 = no BSP model model = ValueForKey(mapent, "model"); if (model && strlen(model)) { if (*model == '*') { //get the model number of this entity (skip the leading *) mapent->modelnum = atoi(&model[1]); } //end if } //end if GetVectorForKey(mapent, "origin", mapent->origin); //if this is the world entity it has model number zero //the world entity has no model key if (!strcmp("worldspawn", ValueForKey(mapent, "classname"))) { mapent->modelnum = 0; } //end if //if the map entity has a BSP model (a modelnum of -1 is used for //entities that aren't using a BSP model) if (mapent->modelnum >= 0) { //parse the bsp brushes Q3_ParseBSPBrushes(mapent); } //end if // //the origin of the entity is already taken into account // //func_group entities can't be in the bsp file // //check out the func_areaportal entities if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) { c_areaportals++; mapent->areaportalnum = c_areaportals; return true; } //end if return true; } //end of the function Q3_ParseBSPEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #define MAX_PATCH_VERTS 1024 void AAS_CreateCurveBrushes(void) { int i, j, n, planenum, numcurvebrushes = 0; q3_dsurface_t *surface; q3_drawVert_t *dv_p; vec3_t points[MAX_PATCH_VERTS]; int width, height, c; patchCollide_t *pc; facet_t *facet; mapbrush_t *brush; side_t *side; entity_t *mapent; winding_t *winding; qprintf("nummapbrushsides = %d\n", nummapbrushsides); mapent = &entities[0]; for (i = 0; i < q3_numDrawSurfaces; i++) { surface = &q3_drawSurfaces[i]; if ( ! surface->patchWidth ) continue; // if the curve is not solid if (!(q3_dshaders[surface->shaderNum].contentFlags & (CONTENTS_SOLID|CONTENTS_PLAYERCLIP))) { //Log_Print("skipped non-solid curve\n"); continue; } //end if // if this curve should not be used for AAS if ( q3_dshaders[surface->shaderNum].contentFlags & CONTENTS_NOBOTCLIP ) { continue; } // width = surface->patchWidth; height = surface->patchHeight; c = width * height; if (c > MAX_PATCH_VERTS) { Error("ParseMesh: MAX_PATCH_VERTS"); } //end if dv_p = q3_drawVerts + surface->firstVert; for ( j = 0 ; j < c ; j++, dv_p++ ) { points[j][0] = dv_p->xyz[0]; points[j][1] = dv_p->xyz[1]; points[j][2] = dv_p->xyz[2]; } //end for // create the internal facet structure pc = CM_GeneratePatchCollide(width, height, points); // for (j = 0; j < pc->numFacets; j++) { facet = &pc->facets[j]; // brush = &mapbrushes[nummapbrushes]; brush->original_sides = &brushsides[nummapbrushsides]; brush->entitynum = 0; brush->brushnum = nummapbrushes - mapent->firstbrush; // brush->numsides = facet->numBorders + 2; nummapbrushsides += brush->numsides; brush->contents = CONTENTS_SOLID; // //qprintf("\r%6d curve brushes", nummapbrushsides);//++numcurvebrushes); qprintf("\r%6d curve brushes", ++numcurvebrushes); // planenum = FindFloatPlane(pc->planes[facet->surfacePlane].plane, pc->planes[facet->surfacePlane].plane[3]); // side = &brush->original_sides[0]; side->planenum = planenum; side->contents = CONTENTS_SOLID; side->flags |= SFL_TEXTURED|SFL_VISIBLE|SFL_CURVE; side->surf = 0; // side = &brush->original_sides[1]; if (create_aas) { //the plane is expanded later so it's not a problem that //these first two opposite sides are coplanar side->planenum = planenum ^ 1; } //end if else { side->planenum = FindFloatPlane(mapplanes[planenum^1].normal, mapplanes[planenum^1].dist + 1); side->flags |= SFL_TEXTURED|SFL_VISIBLE; } //end else side->contents = CONTENTS_SOLID; side->flags |= SFL_CURVE; side->surf = 0; // winding = BaseWindingForPlane(mapplanes[side->planenum].normal, mapplanes[side->planenum].dist); for (n = 0; n < facet->numBorders; n++) { //never use the surface plane as a border if (facet->borderPlanes[n] == facet->surfacePlane) continue; // side = &brush->original_sides[2 + n]; side->planenum = FindFloatPlane(pc->planes[facet->borderPlanes[n]].plane, pc->planes[facet->borderPlanes[n]].plane[3]); if (facet->borderInward[n]) side->planenum ^= 1; side->contents = CONTENTS_SOLID; side->flags |= SFL_TEXTURED|SFL_CURVE; side->surf = 0; //chop the winding in place if (winding) ChopWindingInPlace(&winding, mapplanes[side->planenum^1].normal, mapplanes[side->planenum^1].dist, 0.1); //CLIP_EPSILON); } //end for //VectorCopy(pc->bounds[0], brush->mins); //VectorCopy(pc->bounds[1], brush->maxs); if (!winding) { Log_Print("WARNING: AAS_CreateCurveBrushes: no winding\n"); brush->numsides = 0; continue; } //end if brush->original_sides[0].winding = winding; WindingBounds(winding, brush->mins, brush->maxs); for (n = 0; n < 3; n++) { //IDBUG: all the indexes into the mins and maxs were zero (not using i) if (brush->mins[n] < -MAX_MAP_BOUNDS || brush->maxs[n] > MAX_MAP_BOUNDS) { Log_Print("entity %i, brush %i: bounds out of range\n", brush->entitynum, brush->brushnum); Log_Print("brush->mins[%d] = %f, brush->maxs[%d] = %f\n", n, brush->mins[n], n, brush->maxs[n]); brush->numsides = 0; //remove the brush break; } //end if if (brush->mins[n] > MAX_MAP_BOUNDS || brush->maxs[n] < -MAX_MAP_BOUNDS) { Log_Print("entity %i, brush %i: no visible sides on brush\n", brush->entitynum, brush->brushnum); Log_Print("brush->mins[%d] = %f, brush->maxs[%d] = %f\n", n, brush->mins[n], n, brush->maxs[n]); brush->numsides = 0; //remove the brush break; } //end if } //end for if (create_aas) { //NOTE: brush bevels now already added //AddBrushBevels(brush); AAS_CreateMapBrushes(brush, mapent, false); } //end if else { // create windings for sides and bounds for brush MakeBrushWindings(brush); AddBrushBevels(brush); nummapbrushes++; mapent->numbrushes++; } //end else } //end for } //end for //qprintf("\r%6d curve brushes", nummapbrushsides);//++numcurvebrushes); qprintf("\r%6d curve brushes\n", numcurvebrushes); } //end of the function AAS_CreateCurveBrushes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AAS_ExpandMapBrush(mapbrush_t *brush, vec3_t mins, vec3_t maxs); void Q3_LoadMapFromBSP(struct quakefile_s *qf) { int i; vec3_t mins = {-1,-1,-1}, maxs = {1, 1, 1}; Log_Print("-- Q3_LoadMapFromBSP --\n"); //loaded map type loadedmaptype = MAPTYPE_QUAKE3; Log_Print("Loading map from %s...\n", qf->filename); //load the bsp file Q3_LoadBSPFile(qf); //create an index from bsp planes to map planes //DPlanes2MapPlanes(); //clear brush model numbers for (i = 0; i < MAX_MAPFILE_BRUSHES; i++) brushmodelnumbers[i] = -1; nummapbrushsides = 0; num_entities = 0; Q3_ParseEntities(); // for (i = 0; i < num_entities; i++) { Q3_ParseBSPEntity(i); } //end for AAS_CreateCurveBrushes(); //get the map mins and maxs from the world model ClearBounds(map_mins, map_maxs); for (i = 0; i < entities[0].numbrushes; i++) { if (mapbrushes[i].numsides <= 0) continue; AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); } //end for /*/ for (i = 0; i < nummapbrushes; i++) { //if (!mapbrushes[i].original_sides) continue; //AddBrushBevels(&mapbrushes[i]); //AAS_ExpandMapBrush(&mapbrushes[i], mins, maxs); } //end for*/ /* for (i = 0; i < nummapbrushsides; i++) { Log_Write("side %d flags = %d", i, brushsides[i].flags); } //end for for (i = 0; i < nummapbrushes; i++) { Log_Write("brush contents: "); PrintContents(mapbrushes[i].contents); Log_Print("\n"); } //end for*/ } //end of the function Q3_LoadMapFromBSP //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Q3_ResetMapLoading(void) { //reset for map loading from bsp memset(nodestack, 0, NODESTACKSIZE * sizeof(int)); nodestackptr = NULL; nodestacksize = 0; memset(brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof(int)); } //end of the function Q3_ResetMapLoading ================================================ FILE: code/bspc/map_sin.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ //----------------------------------------------------------------------------- // // $Logfile:: /MissionPack/code/bspc/map_sin.c $ #include "qbsp.h" #include "l_bsp_sin.h" #include "aas_map.h" //AAS_CreateMapBrushes //==================================================================== /* =========== Sin_BrushContents =========== */ int Sin_BrushContents(mapbrush_t *b) { int contents; side_t *s; int i; #ifdef SIN float trans = 0; #else int trans; #endif s = &b->original_sides[0]; contents = s->contents; #ifdef SIN trans = sin_texinfo[s->texinfo].translucence; #else trans = texinfo[s->texinfo].flags; #endif for (i=1 ; inumsides ; i++, s++) { s = &b->original_sides[i]; #ifdef SIN trans += sin_texinfo[s->texinfo].translucence; #else trans |= texinfo[s->texinfo].flags; #endif if (s->contents != contents) { #ifdef SIN if ( ( s->contents & CONTENTS_DETAIL && !(contents & CONTENTS_DETAIL) ) || ( !(s->contents & CONTENTS_DETAIL) && contents & CONTENTS_DETAIL ) ) { s->contents |= CONTENTS_DETAIL; contents |= CONTENTS_DETAIL; continue; } #endif printf ("Entity %i, Brush %i: mixed face contents\n" , b->entitynum, b->brushnum); break; } } #ifdef SIN if (contents & CONTENTS_FENCE) { // contents |= CONTENTS_TRANSLUCENT; contents |= CONTENTS_DETAIL; contents |= CONTENTS_DUMMYFENCE; contents &= ~CONTENTS_SOLID; contents &= ~CONTENTS_FENCE; contents |= CONTENTS_WINDOW; } #endif // if any side is translucent, mark the contents // and change solid to window #ifdef SIN if ( trans > 0 ) #else if ( trans & (SURF_TRANS33|SURF_TRANS66) ) #endif { contents |= CONTENTS_Q2TRANSLUCENT; if (contents & CONTENTS_SOLID) { contents &= ~CONTENTS_SOLID; contents |= CONTENTS_WINDOW; } } return contents; } //*/ //============================================================================ /* ================= ParseBrush ================= * / void ParseBrush (entity_t *mapent) { mapbrush_t *b; int i,j, k; int mt; side_t *side, *s2; int planenum; brush_texture_t td; #ifdef SIN textureref_t newref; #endif int planepts[3][3]; if (nummapbrushes == MAX_MAP_BRUSHES) Error ("nummapbrushes == MAX_MAP_BRUSHES"); b = &mapbrushes[nummapbrushes]; b->original_sides = &brushsides[nummapbrushsides]; b->entitynum = num_entities-1; b->brushnum = nummapbrushes - mapent->firstbrush; do { if (!GetToken (true)) break; if (!strcmp (token, "}") ) break; if (nummapbrushsides == MAX_MAP_BRUSHSIDES) Error ("MAX_MAP_BRUSHSIDES"); side = &brushsides[nummapbrushsides]; // read the three point plane definition for (i=0 ; i<3 ; i++) { if (i != 0) GetToken (true); if (strcmp (token, "(") ) Error ("parsing brush"); for (j=0 ; j<3 ; j++) { GetToken (false); planepts[i][j] = atoi(token); } GetToken (false); if (strcmp (token, ")") ) Error ("parsing brush"); } // // read the texturedef // GetToken (false); strcpy (td.name, token); GetToken (false); td.shift[0] = atoi(token); GetToken (false); td.shift[1] = atoi(token); GetToken (false); #ifdef SIN td.rotate = atof(token); #else td.rotate = atoi(token); #endif GetToken (false); td.scale[0] = atof(token); GetToken (false); td.scale[1] = atof(token); // find default flags and values mt = FindMiptex (td.name); #ifdef SIN // clear out the masks on newref memset(&newref,0,sizeof(newref)); // copy over the name strcpy( newref.name, td.name ); ParseSurfaceInfo( &newref ); MergeRefs( &bsp_textureref[mt], &newref, &td.tref ); side->contents = td.tref.contents; side->surf = td.tref.flags; #else td.flags = textureref[mt].flags; td.value = textureref[mt].value; side->contents = textureref[mt].contents; side->surf = td.flags = textureref[mt].flags; if (TokenAvailable()) { GetToken (false); side->contents = atoi(token); GetToken (false); side->surf = td.flags = atoi(token); GetToken (false); td.value = atoi(token); } #endif // translucent objects are automatically classified as detail #ifdef SIN if ( td.tref.translucence > 0 ) #else if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) #endif side->contents |= CONTENTS_DETAIL; if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) side->contents |= CONTENTS_DETAIL; if (fulldetail) side->contents &= ~CONTENTS_DETAIL; if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) side->contents |= CONTENTS_SOLID; // hints and skips are never detail, and have no content if (side->surf & (SURF_HINT|SURF_SKIP) ) { side->contents = 0; #ifndef SIN // I think this is a bug of some kind side->surf &= ~CONTENTS_DETAIL; #endif } // // find the plane number // planenum = PlaneFromPoints (planepts[0], planepts[1], planepts[2]); if (planenum == -1) { printf ("Entity %i, Brush %i: plane with no normal\n" , b->entitynum, b->brushnum); continue; } // // see if the plane has been used already // for (k=0 ; knumsides ; k++) { s2 = b->original_sides + k; if (s2->planenum == planenum) { printf ("Entity %i, Brush %i: duplicate plane\n" , b->entitynum, b->brushnum); break; } if ( s2->planenum == (planenum^1) ) { printf ("Entity %i, Brush %i: mirrored plane\n" , b->entitynum, b->brushnum); break; } } if (k != b->numsides) continue; // duplicated // // keep this side // side = b->original_sides + b->numsides; side->planenum = planenum; #ifdef SIN side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], &td, vec3_origin, &newref); // // save off lightinfo // side->lightinfo = LightinfoForBrushTexture ( &td ); #else side->texinfo = TexinfoForBrushTexture (&mapplanes[planenum], &td, vec3_origin); #endif // save the td off in case there is an origin brush and we // have to recalculate the texinfo side_brushtextures[nummapbrushsides] = td; #ifdef SIN // save off the merged tref for animating textures side_newrefs[nummapbrushsides] = newref; #endif nummapbrushsides++; b->numsides++; } while (1); // get the content for the entire brush b->contents = Sin_BrushContents (b); // allow detail brushes to be removed if (nodetail && (b->contents & CONTENTS_DETAIL) ) { b->numsides = 0; return; } // allow water brushes to be removed if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) { b->numsides = 0; return; } // create windings for sides and bounds for brush MakeBrushWindings (b); // brushes that will not be visible at all will never be // used as bsp splitters if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) { c_clipbrushes++; for (i=0 ; inumsides ; i++) b->original_sides[i].texinfo = TEXINFO_NODE; } // // origin brushes are removed, but they set // the rotation origin for the rest of the brushes // in the entity. After the entire entity is parsed, // the planenums and texinfos will be adjusted for // the origin brush // if (b->contents & CONTENTS_ORIGIN) { char string[32]; vec3_t origin; if (num_entities == 1) { Error ("Entity %i, Brush %i: origin brushes not allowed in world" , b->entitynum, b->brushnum); return; } VectorAdd (b->mins, b->maxs, origin); VectorScale (origin, 0.5, origin); sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); SetKeyValue (&entities[b->entitynum], "origin", string); VectorCopy (origin, entities[b->entitynum].origin); // don't keep this brush b->numsides = 0; return; } AddBrushBevels (b); nummapbrushes++; mapent->numbrushes++; } //*/ /* ================ MoveBrushesToWorld Takes all of the brushes from the current entity and adds them to the world's brush list. Used by func_group and func_areaportal ================ * / void MoveBrushesToWorld (entity_t *mapent) { int newbrushes; int worldbrushes; mapbrush_t *temp; int i; // this is pretty gross, because the brushes are expected to be // in linear order for each entity newbrushes = mapent->numbrushes; worldbrushes = entities[0].numbrushes; temp = malloc(newbrushes*sizeof(mapbrush_t)); memcpy (temp, mapbrushes + mapent->firstbrush, newbrushes*sizeof(mapbrush_t)); #if 0 // let them keep their original brush numbers for (i=0 ; inumbrushes = 0; } //*/ /* ================ ParseMapEntity ================ * / qboolean Sin_ParseMapEntity (void) { entity_t *mapent; epair_t *e; side_t *s; int i, j; int startbrush, startsides; vec_t newdist; mapbrush_t *b; if (!GetToken (true)) return false; if (strcmp (token, "{") ) Error ("ParseEntity: { not found"); if (num_entities == MAX_MAP_ENTITIES) Error ("num_entities == MAX_MAP_ENTITIES"); startbrush = nummapbrushes; startsides = nummapbrushsides; mapent = &entities[num_entities]; num_entities++; memset (mapent, 0, sizeof(*mapent)); mapent->firstbrush = nummapbrushes; mapent->numbrushes = 0; // mapent->portalareas[0] = -1; // mapent->portalareas[1] = -1; do { if (!GetToken (true)) Error ("ParseEntity: EOF without closing brace"); if (!strcmp (token, "}") ) break; if (!strcmp (token, "{") ) ParseBrush (mapent); else { e = ParseEpair (); #ifdef SIN //HACK HACK HACK // MED Gotta do this here if ( !stricmp(e->key, "surfacefile") ) { if (!surfacefile[0]) { strcpy( surfacefile, e->value ); } printf ("--- ParseSurfaceFile ---\n"); printf ("Surface script: %s\n", surfacefile); if (!ParseSurfaceFile(surfacefile)) { Error ("Script file not found: %s\n", surfacefile); } } #endif e->next = mapent->epairs; mapent->epairs = e; } } while (1); #ifdef SIN if (!(strlen(ValueForKey(mapent, "origin"))) && ((num_entities-1) != 0)) { mapbrush_t *brush; vec3_t origin; char string[32]; vec3_t mins, maxs; int start, end; // Calculate bounds start = mapent->firstbrush; end = start + mapent->numbrushes; ClearBounds (mins, maxs); for (j=start ; jnumsides) continue; // not a real brush (origin brush) - shouldn't happen AddPointToBounds (brush->mins, mins, maxs); AddPointToBounds (brush->maxs, mins, maxs); } // Set the origin to be the centroid of the entity. VectorAdd ( mins, maxs, origin); VectorScale( origin, 0.5f, origin ); sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); SetKeyValue ( mapent, "origin", string); // qprintf("Setting origin to %s\n",string); } #endif GetVectorForKey (mapent, "origin", mapent->origin); #ifdef SIN if ( (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) || (!strcmp ("func_group", ValueForKey (mapent, "classname"))) || (!strcmp ("detail", ValueForKey (mapent, "classname")) && !entitydetails) ) { VectorClear( mapent->origin ); } #endif // // if there was an origin brush, offset all of the planes and texinfo // if (mapent->origin[0] || mapent->origin[1] || mapent->origin[2]) { for (i=0 ; inumbrushes ; i++) { b = &mapbrushes[mapent->firstbrush + i]; for (j=0 ; jnumsides ; j++) { s = &b->original_sides[j]; newdist = mapplanes[s->planenum].dist - DotProduct (mapplanes[s->planenum].normal, mapent->origin); s->planenum = FindFloatPlane (mapplanes[s->planenum].normal, newdist); #ifdef SIN s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], &side_brushtextures[s-brushsides], mapent->origin, &side_newrefs[s-brushsides]); // // save off lightinfo // s->lightinfo = LightinfoForBrushTexture ( &side_brushtextures[s-brushsides] ); #else s->texinfo = TexinfoForBrushTexture (&mapplanes[s->planenum], &side_brushtextures[s-brushsides], mapent->origin); #endif } MakeBrushWindings (b); } } // group entities are just for editor convenience // toss all brushes into the world entity if (!strcmp ("func_group", ValueForKey (mapent, "classname"))) { MoveBrushesToWorld (mapent); mapent->numbrushes = 0; mapent->wasdetail = true; FreeValueKeys( mapent ); return true; } #ifdef SIN // detail entities are just for editor convenience // toss all brushes into the world entity as detail brushes if (!strcmp ("detail", ValueForKey (mapent, "classname")) && !entitydetails) { for (i=0 ; inumbrushes ; i++) { int j; side_t * s; b = &mapbrushes[mapent->firstbrush + i]; if (nodetail) { b->numsides = 0; continue; } if (!fulldetail) { // set the contents for the entire brush b->contents |= CONTENTS_DETAIL; // set the contents in the sides as well for (j=0, s=b->original_sides ; jnumsides ; j++,s++) { s->contents |= CONTENTS_DETAIL; } } else { // set the contents for the entire brush b->contents |= CONTENTS_SOLID; // set the contents in the sides as well for (j=0, s=b->original_sides ; jnumsides ; j++,s++) { s->contents |= CONTENTS_SOLID; } } } MoveBrushesToWorld (mapent); mapent->wasdetail = true; FreeValueKeys( mapent ); // kill off the entity // num_entities--; return true; } #endif // areaportal entities move their brushes, but don't eliminate // the entity if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) { char str[128]; if (mapent->numbrushes != 1) Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); b = &mapbrushes[nummapbrushes-1]; b->contents = CONTENTS_AREAPORTAL; c_areaportals++; mapent->areaportalnum = c_areaportals; // set the portal number as "style" sprintf (str, "%i", c_areaportals); SetKeyValue (mapent, "style", str); MoveBrushesToWorld (mapent); return true; } return true; } //end of the function Sin_ParseMapEntity */ //=================================================================== /* ================ LoadMapFile ================ * / void Sin_LoadMapFile (char *filename) { int i; #ifdef SIN int num_detailsides=0; int num_detailbrushes=0; int num_worldsides=0; int num_worldbrushes=0; int j,k; #endif qprintf ("--- LoadMapFile ---\n"); LoadScriptFile (filename); nummapbrushsides = 0; num_entities = 0; while (ParseMapEntity ()) { } ClearBounds (map_mins, map_maxs); for (i=0 ; i 4096) continue; // no valid points AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); } #ifdef SIN for (j=0; jnumsides && b->contents & CONTENTS_DETAIL) num_detailbrushes++; else if (b->numsides) num_worldbrushes++; for (k=0, s=b->original_sides ; knumsides ; k++,s++) { if (s->contents & CONTENTS_DETAIL) num_detailsides++; else num_worldsides++; } } } #endif qprintf ("%5i brushes\n", nummapbrushes); qprintf ("%5i clipbrushes\n", c_clipbrushes); qprintf ("%5i total sides\n", nummapbrushsides); qprintf ("%5i boxbevels\n", c_boxbevels); qprintf ("%5i edgebevels\n", c_edgebevels); qprintf ("%5i entities\n", num_entities); qprintf ("%5i planes\n", nummapplanes); qprintf ("%5i areaportals\n", c_areaportals); qprintf ("size: %5.0f,%5.0f,%5.0f to %5.0f,%5.0f,%5.0f\n", map_mins[0],map_mins[1],map_mins[2], map_maxs[0],map_maxs[1],map_maxs[2]); #ifdef SIN qprintf ("%5i detailbrushes\n", num_detailbrushes); qprintf ("%5i worldbrushes\n", num_worldbrushes); qprintf ("%5i detailsides\n", num_detailsides); qprintf ("%5i worldsides\n", num_worldsides); #endif } //end of the function Sin_LoadMap */ #ifdef ME //Begin MAP loading from BSP file //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Sin_CreateMapTexinfo(void) { int i; vec_t defaultvec[4] = {1, 0, 0, 0}; memcpy(map_texinfo[0].vecs[0], defaultvec, sizeof(defaultvec)); memcpy(map_texinfo[0].vecs[1], defaultvec, sizeof(defaultvec)); map_texinfo[0].flags = 0; map_texinfo[0].value = 0; strcpy(map_texinfo[0].texture, "generic/misc/red"); //no texture map_texinfo[0].nexttexinfo = -1; for (i = 1; i < sin_numtexinfo; i++) { memcpy(map_texinfo[i].vecs, sin_texinfo[i].vecs, sizeof(float) * 2 * 4); map_texinfo[i].flags = sin_texinfo[i].flags; map_texinfo[i].value = 0; strcpy(map_texinfo[i].texture, sin_texinfo[i].texture); map_texinfo[i].nexttexinfo = -1; } //end for } //end of the function Sin_CreateMapTexinfo //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Sin_SetLeafBrushesModelNumbers(int leafnum, int modelnum) { int i, brushnum; sin_dleaf_t *leaf; leaf = &sin_dleafs[leafnum]; for (i = 0; i < leaf->numleafbrushes; i++) { brushnum = sin_dleafbrushes[leaf->firstleafbrush + i]; brushmodelnumbers[brushnum] = modelnum; dbrushleafnums[brushnum] = leafnum; } //end for } //end of the function Sin_SetLeafBrushesModelNumbers //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Sin_InitNodeStack(void) { nodestackptr = nodestack; nodestacksize = 0; } //end of the function Sin_InitNodeStack //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Sin_PushNodeStack(int num) { *nodestackptr = num; nodestackptr++; nodestacksize++; // if (nodestackptr >= &nodestack[NODESTACKSIZE]) { Error("Sin_PushNodeStack: stack overflow\n"); } //end if } //end of the function Sin_PushNodeStack //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int Sin_PopNodeStack(void) { //if the stack is empty if (nodestackptr <= nodestack) return -1; //decrease stack pointer nodestackptr--; nodestacksize--; //return the top value from the stack return *nodestackptr; } //end of the function Sin_PopNodeStack //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Sin_SetBrushModelNumbers(entity_t *mapent) { int n, pn; int leafnum; // Sin_InitNodeStack(); //head node (root) of the bsp tree n = sin_dmodels[mapent->modelnum].headnode; pn = 0; do { //if we are in a leaf (negative node number) if (n < 0) { //number of the leaf leafnum = (-n) - 1; //set the brush numbers Sin_SetLeafBrushesModelNumbers(leafnum, mapent->modelnum); //walk back into the tree to find a second child to continue with for (pn = Sin_PopNodeStack(); pn >= 0; n = pn, pn = Sin_PopNodeStack()) { //if we took the first child at the parent node if (sin_dnodes[pn].children[0] == n) break; } //end for //if the stack wasn't empty (if not processed whole tree) if (pn >= 0) { //push the parent node again Sin_PushNodeStack(pn); //we proceed with the second child of the parent node n = sin_dnodes[pn].children[1]; } //end if } //end if else { //push the current node onto the stack Sin_PushNodeStack(n); //walk forward into the tree to the first child n = sin_dnodes[n].children[0]; } //end else } while(pn >= 0); } //end of the function Sin_SetBrushModelNumbers //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Sin_BSPBrushToMapBrush(sin_dbrush_t *bspbrush, entity_t *mapent) { mapbrush_t *b; int i, k, n; side_t *side, *s2; int planenum; sin_dbrushside_t *bspbrushside; sin_dplane_t *bspplane; if (nummapbrushes >= MAX_MAPFILE_BRUSHES) Error ("nummapbrushes >= MAX_MAPFILE_BRUSHES"); b = &mapbrushes[nummapbrushes]; b->original_sides = &brushsides[nummapbrushsides]; b->entitynum = mapent-entities; b->brushnum = nummapbrushes - mapent->firstbrush; b->leafnum = dbrushleafnums[bspbrush - sin_dbrushes]; for (n = 0; n < bspbrush->numsides; n++) { //pointer to the bsp brush side bspbrushside = &sin_dbrushsides[bspbrush->firstside + n]; if (nummapbrushsides >= MAX_MAPFILE_BRUSHSIDES) { Error ("MAX_MAPFILE_BRUSHSIDES"); } //end if //pointer to the map brush side side = &brushsides[nummapbrushsides]; //if the BSP brush side is textured if (sin_dbrushsidetextured[bspbrush->firstside + n]) side->flags |= SFL_TEXTURED; else side->flags &= ~SFL_TEXTURED; //ME: can get side contents and surf directly from BSP file side->contents = bspbrush->contents; //if the texinfo is TEXINFO_NODE if (bspbrushside->texinfo < 0) side->surf = 0; else side->surf = sin_texinfo[bspbrushside->texinfo].flags; // translucent objects are automatically classified as detail if (side->surf & (SURF_TRANS33|SURF_TRANS66) ) side->contents |= CONTENTS_DETAIL; if (side->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) side->contents |= CONTENTS_DETAIL; if (fulldetail) side->contents &= ~CONTENTS_DETAIL; if (!(side->contents & ((LAST_VISIBLE_CONTENTS-1) | CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST) ) ) side->contents |= CONTENTS_SOLID; // hints and skips are never detail, and have no content if (side->surf & (SURF_HINT|SURF_SKIP) ) { side->contents = 0; side->surf &= ~CONTENTS_DETAIL; } //ME: get a plane for this side bspplane = &sin_dplanes[bspbrushside->planenum]; planenum = FindFloatPlane(bspplane->normal, bspplane->dist); // // see if the plane has been used already // //ME: this really shouldn't happen!!! //ME: otherwise the bsp file is corrupted?? //ME: still it seems to happen, maybe Johny Boy's //ME: brush bevel adding is crappy ? for (k = 0; k < b->numsides; k++) { s2 = b->original_sides + k; if (s2->planenum == planenum) { Log_Print("Entity %i, Brush %i: duplicate plane\n" , b->entitynum, b->brushnum); break; } if ( s2->planenum == (planenum^1) ) { Log_Print("Entity %i, Brush %i: mirrored plane\n" , b->entitynum, b->brushnum); break; } } if (k != b->numsides) continue; // duplicated // // keep this side // //ME: reset pointer to side, why? hell I dunno (pointer is set above already) side = b->original_sides + b->numsides; //ME: store the plane number side->planenum = planenum; //ME: texinfo is already stored when bsp is loaded //NOTE: check for TEXINFO_NODE, otherwise crash in Sin_BrushContents if (bspbrushside->texinfo < 0) side->texinfo = 0; else side->texinfo = bspbrushside->texinfo; // save the td off in case there is an origin brush and we // have to recalculate the texinfo // ME: don't need to recalculate because it's already done // (for non-world entities) in the BSP file // side_brushtextures[nummapbrushsides] = td; nummapbrushsides++; b->numsides++; } //end for // get the content for the entire brush b->contents = bspbrush->contents; Sin_BrushContents(b); if (BrushExists(b)) { c_squattbrushes++; b->numsides = 0; return; } //end if //if we're creating AAS if (create_aas) { //create the AAS brushes from this brush, don't add brush bevels AAS_CreateMapBrushes(b, mapent, false); return; } //end if // allow detail brushes to be removed if (nodetail && (b->contents & CONTENTS_DETAIL) ) { b->numsides = 0; return; } //end if // allow water brushes to be removed if (nowater && (b->contents & (CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER)) ) { b->numsides = 0; return; } //end if // create windings for sides and bounds for brush MakeBrushWindings(b); //mark brushes without winding or with a tiny window as bevels MarkBrushBevels(b); // brushes that will not be visible at all will never be // used as bsp splitters if (b->contents & (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) ) { c_clipbrushes++; for (i = 0; i < b->numsides; i++) b->original_sides[i].texinfo = TEXINFO_NODE; } //end for // // origin brushes are removed, but they set // the rotation origin for the rest of the brushes // in the entity. After the entire entity is parsed, // the planenums and texinfos will be adjusted for // the origin brush // //ME: not needed because the entities in the BSP file already // have an origin set // if (b->contents & CONTENTS_ORIGIN) // { // char string[32]; // vec3_t origin; // // if (num_entities == 1) // { // Error ("Entity %i, Brush %i: origin brushes not allowed in world" // , b->entitynum, b->brushnum); // return; // } // // VectorAdd (b->mins, b->maxs, origin); // VectorScale (origin, 0.5, origin); // // sprintf (string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]); // SetKeyValue (&entities[b->entitynum], "origin", string); // // VectorCopy (origin, entities[b->entitynum].origin); // // // don't keep this brush // b->numsides = 0; // // return; // } //ME: the bsp brushes already have bevels, so we won't try to // add them again (especially since Johny Boy's bevel adding might // be crappy) // AddBrushBevels(b); nummapbrushes++; mapent->numbrushes++; } //end of the function Sin_BSPBrushToMapBrush //=========================================================================== //=========================================================================== void Sin_ParseBSPBrushes(entity_t *mapent) { int i, testnum = 0; //give all the brushes that belong to this entity the number of the //BSP model used by this entity Sin_SetBrushModelNumbers(mapent); //now parse all the brushes with the correct mapent->modelnum for (i = 0; i < sin_numbrushes; i++) { if (brushmodelnumbers[i] == mapent->modelnum) { testnum++; Sin_BSPBrushToMapBrush(&sin_dbrushes[i], mapent); } //end if } //end for } //end of the function Sin_ParseBSPBrushes //=========================================================================== //=========================================================================== qboolean Sin_ParseBSPEntity(int entnum) { entity_t *mapent; char *model; int startbrush, startsides; startbrush = nummapbrushes; startsides = nummapbrushsides; mapent = &entities[entnum];//num_entities]; mapent->firstbrush = nummapbrushes; mapent->numbrushes = 0; mapent->modelnum = -1; //-1 = no model model = ValueForKey(mapent, "model"); if (model && *model == '*') { mapent->modelnum = atoi(&model[1]); //Log_Print("model = %s\n", model); //Log_Print("mapent->modelnum = %d\n", mapent->modelnum); } //end if GetVectorForKey(mapent, "origin", mapent->origin); //if this is the world entity it has model number zero //the world entity has no model key if (!strcmp("worldspawn", ValueForKey(mapent, "classname"))) { mapent->modelnum = 0; } //end if //if the map entity has a BSP model (a modelnum of -1 is used for //entities that aren't using a BSP model) if (mapent->modelnum >= 0) { //parse the bsp brushes Sin_ParseBSPBrushes(mapent); } //end if // //the origin of the entity is already taken into account // //func_group entities can't be in the bsp file // //check out the func_areaportal entities if (!strcmp ("func_areaportal", ValueForKey (mapent, "classname"))) { c_areaportals++; mapent->areaportalnum = c_areaportals; return true; } //end if return true; } //end of the function Sin_ParseBSPEntity //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Sin_LoadMapFromBSP(char *filename, int offset, int length) { int i; Log_Print("-- Sin_LoadMapFromBSP --\n"); //loaded map type loadedmaptype = MAPTYPE_SIN; Log_Print("Loading map from %s...\n", filename); //load the bsp file Sin_LoadBSPFile(filename, offset, length); //create an index from bsp planes to map planes //DPlanes2MapPlanes(); //clear brush model numbers for (i = 0; i < MAX_MAPFILE_BRUSHES; i++) brushmodelnumbers[i] = -1; nummapbrushsides = 0; num_entities = 0; Sin_ParseEntities(); // for (i = 0; i < num_entities; i++) { Sin_ParseBSPEntity(i); } //end for //get the map mins and maxs from the world model ClearBounds(map_mins, map_maxs); for (i = 0; i < entities[0].numbrushes; i++) { if (mapbrushes[i].mins[0] > 4096) continue; //no valid points AddPointToBounds (mapbrushes[i].mins, map_mins, map_maxs); AddPointToBounds (mapbrushes[i].maxs, map_mins, map_maxs); } //end for // Sin_CreateMapTexinfo(); } //end of the function Sin_LoadMapFromBSP void Sin_ResetMapLoading(void) { //reset for map loading from bsp memset(nodestack, 0, NODESTACKSIZE * sizeof(int)); nodestackptr = NULL; nodestacksize = 0; memset(brushmodelnumbers, 0, MAX_MAPFILE_BRUSHES * sizeof(int)); } //end of the function Sin_ResetMapLoading //End MAP loading from BSP file #endif //ME ================================================ FILE: code/bspc/nodraw.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" vec3_t draw_mins, draw_maxs; qboolean drawflag; void Draw_ClearWindow (void) { } //============================================================ #define GLSERV_PORT 25001 void GLS_BeginScene (void) { } void GLS_Winding (winding_t *w, int code) { } void GLS_EndScene (void) { } ================================================ FILE: code/bspc/portals.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "l_mem.h" int c_active_portals; int c_peak_portals; int c_boundary; int c_boundary_sides; int c_portalmemory; //portal_t *portallist = NULL; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== portal_t *AllocPortal (void) { portal_t *p; p = GetMemory(sizeof(portal_t)); memset (p, 0, sizeof(portal_t)); if (numthreads == 1) { c_active_portals++; if (c_active_portals > c_peak_portals) { c_peak_portals = c_active_portals; } //end if c_portalmemory += MemorySize(p); } //end if // p->nextportal = portallist; // portallist = p; return p; } //end of the function AllocPortal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FreePortal (portal_t *p) { if (p->winding) FreeWinding(p->winding); if (numthreads == 1) { c_active_portals--; c_portalmemory -= MemorySize(p); } //end if FreeMemory(p); } //end of the function FreePortal //=========================================================================== // Returns the single content bit of the // strongest visible content present // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int VisibleContents (int contents) { int i; for (i=1 ; i<=LAST_VISIBLE_CONTENTS ; i<<=1) if (contents & i ) return i; return 0; } //end of the function VisibleContents //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int ClusterContents (node_t *node) { int c1, c2, c; if (node->planenum == PLANENUM_LEAF) return node->contents; c1 = ClusterContents(node->children[0]); c2 = ClusterContents(node->children[1]); c = c1|c2; // a cluster may include some solid detail areas, but // still be seen into if ( ! (c1&CONTENTS_SOLID) || ! (c2&CONTENTS_SOLID) ) c &= ~CONTENTS_SOLID; return c; } //end of the function ClusterContents //=========================================================================== // Returns true if the portal is empty or translucent, allowing // the PVS calculation to see through it. // The nodes on either side of the portal may actually be clusters, // not leaves, so all contents should be ored together // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean Portal_VisFlood (portal_t *p) { int c1, c2; if (!p->onnode) return false; // to global outsideleaf c1 = ClusterContents(p->nodes[0]); c2 = ClusterContents(p->nodes[1]); if (!VisibleContents (c1^c2)) return true; if (c1 & (CONTENTS_Q2TRANSLUCENT|CONTENTS_DETAIL)) c1 = 0; if (c2 & (CONTENTS_Q2TRANSLUCENT|CONTENTS_DETAIL)) c2 = 0; if ( (c1|c2) & CONTENTS_SOLID ) return false; // can't see through solid if (! (c1 ^ c2)) return true; // identical on both sides if (!VisibleContents (c1^c2)) return true; return false; } //end of the function Portal_VisFlood //=========================================================================== // The entity flood determines which areas are // "outside" on the map, which are then filled in. // Flowing from side s to side !s // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean Portal_EntityFlood (portal_t *p, int s) { if (p->nodes[0]->planenum != PLANENUM_LEAF || p->nodes[1]->planenum != PLANENUM_LEAF) Error ("Portal_EntityFlood: not a leaf"); // can never cross to a solid if ( (p->nodes[0]->contents & CONTENTS_SOLID) || (p->nodes[1]->contents & CONTENTS_SOLID) ) return false; // can flood through everything else return true; } //============================================================================= int c_tinyportals; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void AddPortalToNodes (portal_t *p, node_t *front, node_t *back) { if (p->nodes[0] || p->nodes[1]) Error ("AddPortalToNode: allready included"); p->nodes[0] = front; p->next[0] = front->portals; front->portals = p; p->nodes[1] = back; p->next[1] = back->portals; back->portals = p; } //end of the function AddPortalToNodes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void RemovePortalFromNode (portal_t *portal, node_t *l) { portal_t **pp, *t; int s, i, n; portal_t *p; portal_t *portals[4096]; // remove reference to the current portal pp = &l->portals; while (1) { t = *pp; if (!t) Error ("RemovePortalFromNode: portal not in leaf"); if ( t == portal ) break; if (t->nodes[0] == l) pp = &t->next[0]; else if (t->nodes[1] == l) pp = &t->next[1]; else Error ("RemovePortalFromNode: portal not bounding leaf"); } if (portal->nodes[0] == l) { *pp = portal->next[0]; portal->nodes[0] = NULL; } //end if else if (portal->nodes[1] == l) { *pp = portal->next[1]; portal->nodes[1] = NULL; } //end else if else { Error("RemovePortalFromNode: mislinked portal"); } //end else //#ifdef ME n = 0; for (p = l->portals; p; p = p->next[s]) { for (i = 0; i < n; i++) { if (p == portals[i]) Error("RemovePortalFromNode: circular linked\n"); } //end for if (p->nodes[0] != l && p->nodes[1] != l) { Error("RemovePortalFromNodes: portal does not belong to node\n"); } //end if portals[n] = p; s = (p->nodes[1] == l); // if (++n >= 4096) Error("RemovePortalFromNode: more than 4096 portals\n"); } //end for //#endif } //end of the function RemovePortalFromNode //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void PrintPortal (portal_t *p) { int i; winding_t *w; w = p->winding; for (i=0 ; inumpoints ; i++) printf ("(%5.0f,%5.0f,%5.0f)\n",w->p[i][0] , w->p[i][1], w->p[i][2]); } //end of the function PrintPortal //=========================================================================== // The created portals will face the global outside_node // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #define SIDESPACE 8 void MakeHeadnodePortals (tree_t *tree) { vec3_t bounds[2]; int i, j, n; portal_t *p, *portals[6]; plane_t bplanes[6], *pl; node_t *node; node = tree->headnode; // pad with some space so there will never be null volume leaves for (i=0 ; i<3 ; i++) { bounds[0][i] = tree->mins[i] - SIDESPACE; bounds[1][i] = tree->maxs[i] + SIDESPACE; if ( bounds[0][i] > bounds[1][i] ) { Error("empty BSP tree"); } } tree->outside_node.planenum = PLANENUM_LEAF; tree->outside_node.brushlist = NULL; tree->outside_node.portals = NULL; tree->outside_node.contents = 0; for (i=0 ; i<3 ; i++) for (j=0 ; j<2 ; j++) { n = j*3 + i; p = AllocPortal (); portals[n] = p; pl = &bplanes[n]; memset (pl, 0, sizeof(*pl)); if (j) { pl->normal[i] = -1; pl->dist = -bounds[j][i]; } else { pl->normal[i] = 1; pl->dist = bounds[j][i]; } p->plane = *pl; p->winding = BaseWindingForPlane (pl->normal, pl->dist); AddPortalToNodes (p, node, &tree->outside_node); } // clip the basewindings by all the other planes for (i=0 ; i<6 ; i++) { for (j=0 ; j<6 ; j++) { if (j == i) continue; ChopWindingInPlace (&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON); } //end for } //end for } //end of the function MakeHeadNodePortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #define BASE_WINDING_EPSILON 0.001 #define SPLIT_WINDING_EPSILON 0.001 winding_t *BaseWindingForNode (node_t *node) { winding_t *w; node_t *n; plane_t *plane; vec3_t normal; vec_t dist; w = BaseWindingForPlane (mapplanes[node->planenum].normal , mapplanes[node->planenum].dist); // clip by all the parents for (n=node->parent ; n && w ; ) { plane = &mapplanes[n->planenum]; if (n->children[0] == node) { // take front ChopWindingInPlace (&w, plane->normal, plane->dist, BASE_WINDING_EPSILON); } else { // take back VectorSubtract (vec3_origin, plane->normal, normal); dist = -plane->dist; ChopWindingInPlace (&w, normal, dist, BASE_WINDING_EPSILON); } node = n; n = n->parent; } return w; } //end of the function BaseWindingForNode //=========================================================================== // create the new portal by taking the full plane winding for the cutting // plane and clipping it by all of parents of this node // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean WindingIsTiny (winding_t *w); void MakeNodePortal (node_t *node) { portal_t *new_portal, *p; winding_t *w; vec3_t normal; float dist; int side; w = BaseWindingForNode (node); // clip the portal by all the other portals in the node for (p = node->portals; p && w; p = p->next[side]) { if (p->nodes[0] == node) { side = 0; VectorCopy (p->plane.normal, normal); dist = p->plane.dist; } //end if else if (p->nodes[1] == node) { side = 1; VectorSubtract (vec3_origin, p->plane.normal, normal); dist = -p->plane.dist; } //end else if else { Error ("MakeNodePortal: mislinked portal"); } //end else ChopWindingInPlace (&w, normal, dist, 0.1); } //end for if (!w) { return; } //end if if (WindingIsTiny (w)) { c_tinyportals++; FreeWinding(w); return; } //end if #ifdef DEBUG /* //NOTE: don't use this winding ok check // all the invalid windings only have a degenerate edge if (WindingError(w)) { Log_Print("MakeNodePortal: %s\n", WindingErrorString()); FreeWinding(w); return; } //end if*/ #endif //DEBUG new_portal = AllocPortal(); new_portal->plane = mapplanes[node->planenum]; #ifdef ME new_portal->planenum = node->planenum; #endif //ME new_portal->onnode = node; new_portal->winding = w; AddPortalToNodes (new_portal, node->children[0], node->children[1]); } //end of the function MakeNodePortal //=========================================================================== // Move or split the portals that bound node so that the node's // children have portals instead of node. // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SplitNodePortals (node_t *node) { portal_t *p, *next_portal, *new_portal; node_t *f, *b, *other_node; int side; plane_t *plane; winding_t *frontwinding, *backwinding; plane = &mapplanes[node->planenum]; f = node->children[0]; b = node->children[1]; for (p = node->portals ; p ; p = next_portal) { if (p->nodes[0] == node) side = 0; else if (p->nodes[1] == node) side = 1; else Error ("SplitNodePortals: mislinked portal"); next_portal = p->next[side]; other_node = p->nodes[!side]; RemovePortalFromNode (p, p->nodes[0]); RemovePortalFromNode (p, p->nodes[1]); // // cut the portal into two portals, one on each side of the cut plane // ClipWindingEpsilon (p->winding, plane->normal, plane->dist, SPLIT_WINDING_EPSILON, &frontwinding, &backwinding); if (frontwinding && WindingIsTiny(frontwinding)) { FreeWinding (frontwinding); frontwinding = NULL; c_tinyportals++; } //end if if (backwinding && WindingIsTiny(backwinding)) { FreeWinding (backwinding); backwinding = NULL; c_tinyportals++; } //end if #ifdef DEBUG /* //NOTE: don't use this winding ok check // all the invalid windings only have a degenerate edge if (frontwinding && WindingError(frontwinding)) { Log_Print("SplitNodePortals: front %s\n", WindingErrorString()); FreeWinding(frontwinding); frontwinding = NULL; } //end if if (backwinding && WindingError(backwinding)) { Log_Print("SplitNodePortals: back %s\n", WindingErrorString()); FreeWinding(backwinding); backwinding = NULL; } //end if*/ #endif //DEBUG if (!frontwinding && !backwinding) { // tiny windings on both sides continue; } if (!frontwinding) { FreeWinding (backwinding); if (side == 0) AddPortalToNodes (p, b, other_node); else AddPortalToNodes (p, other_node, b); continue; } if (!backwinding) { FreeWinding (frontwinding); if (side == 0) AddPortalToNodes (p, f, other_node); else AddPortalToNodes (p, other_node, f); continue; } // the winding is split new_portal = AllocPortal(); *new_portal = *p; new_portal->winding = backwinding; FreeWinding (p->winding); p->winding = frontwinding; if (side == 0) { AddPortalToNodes (p, f, other_node); AddPortalToNodes (new_portal, b, other_node); } //end if else { AddPortalToNodes (p, other_node, f); AddPortalToNodes (new_portal, other_node, b); } //end else } node->portals = NULL; } //end of the function SplitNodePortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void CalcNodeBounds (node_t *node) { portal_t *p; int s; int i; // calc mins/maxs for both leaves and nodes ClearBounds (node->mins, node->maxs); for (p = node->portals ; p ; p = p->next[s]) { s = (p->nodes[1] == node); for (i=0 ; iwinding->numpoints ; i++) AddPointToBounds (p->winding->p[i], node->mins, node->maxs); } } //end of the function CalcNodeBounds //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int c_numportalizednodes; void MakeTreePortals_r (node_t *node) { int i; #ifdef ME qprintf("\r%6d", ++c_numportalizednodes); if (cancelconversion) return; #endif //ME CalcNodeBounds (node); if (node->mins[0] >= node->maxs[0]) { Log_Print("WARNING: node without a volume\n"); } for (i=0 ; i<3 ; i++) { if (node->mins[i] < -MAX_MAP_BOUNDS || node->maxs[i] > MAX_MAP_BOUNDS) { Log_Print("WARNING: node with unbounded volume\n"); break; } } if (node->planenum == PLANENUM_LEAF) return; MakeNodePortal (node); SplitNodePortals (node); MakeTreePortals_r (node->children[0]); MakeTreePortals_r (node->children[1]); } //end of the function MakeTreePortals_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void MakeTreePortals(tree_t *tree) { #ifdef ME Log_Print("---- Node Portalization ----\n"); c_numportalizednodes = 0; c_portalmemory = 0; qprintf("%6d nodes portalized", c_numportalizednodes); #endif //ME MakeHeadnodePortals(tree); MakeTreePortals_r(tree->headnode); #ifdef ME qprintf("\n"); Log_Write("%6d nodes portalized\r\n", c_numportalizednodes); Log_Print("%6d tiny portals\r\n", c_tinyportals); Log_Print("%6d KB of portal memory\r\n", c_portalmemory >> 10); Log_Print("%6i KB of winding memory\r\n", WindingMemory() >> 10); #endif //ME } //end of the function MakeTreePortals /* ========================================================= FLOOD ENTITIES ========================================================= */ //#define P_NODESTACK node_t *p_firstnode; node_t *p_lastnode; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== #ifdef P_NODESTACK void P_AddNodeToList(node_t *node) { node->next = p_firstnode; p_firstnode = node; if (!p_lastnode) p_lastnode = node; } //end of the function P_AddNodeToList #else //it's a node queue //add the node to the end of the node list void P_AddNodeToList(node_t *node) { node->next = NULL; if (p_lastnode) p_lastnode->next = node; else p_firstnode = node; p_lastnode = node; } //end of the function P_AddNodeToList #endif //P_NODESTACK //=========================================================================== // get the first node from the front of the node list // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== node_t *P_NextNodeFromList(void) { node_t *node; node = p_firstnode; if (p_firstnode) p_firstnode = p_firstnode->next; if (!p_firstnode) p_lastnode = NULL; return node; } //end of the function P_NextNodeFromList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FloodPortals(node_t *firstnode) { node_t *node; portal_t *p; int s; firstnode->occupied = 1; P_AddNodeToList(firstnode); for (node = P_NextNodeFromList(); node; node = P_NextNodeFromList()) { for (p = node->portals; p; p = p->next[s]) { s = (p->nodes[1] == node); //if the node at the other side of the portal is occupied already if (p->nodes[!s]->occupied) continue; //if it isn't possible to flood through this portal if (!Portal_EntityFlood(p, s)) continue; // p->nodes[!s]->occupied = node->occupied + 1; // P_AddNodeToList(p->nodes[!s]); } //end for } //end for } //end of the function FloodPortals //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int numrec; void FloodPortals_r (node_t *node, int dist) { portal_t *p; int s; // int i; Log_Print("\r%6d", ++numrec); if (node->occupied) Error("FloodPortals_r: node already occupied\n"); if (!node) { Error("FloodPortals_r: NULL node\n"); } //end if*/ node->occupied = dist; for (p = node->portals; p; p = p->next[s]) { s = (p->nodes[1] == node); //if the node at the other side of the portal is occupied already if (p->nodes[!s]->occupied) continue; //if it isn't possible to flood through this portal if (!Portal_EntityFlood(p, s)) continue; //flood recursively through the current portal FloodPortals_r(p->nodes[!s], dist+1); } //end for Log_Print("\r%6d", --numrec); } //end of the function FloodPortals_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean PlaceOccupant (node_t *headnode, vec3_t origin, entity_t *occupant) { node_t *node; vec_t d; plane_t *plane; //find the leaf to start in node = headnode; while(node->planenum != PLANENUM_LEAF) { if (node->planenum < 0 || node->planenum > nummapplanes) { Error("PlaceOccupant: invalid node->planenum\n"); } //end if plane = &mapplanes[node->planenum]; d = DotProduct(origin, plane->normal) - plane->dist; if (d >= 0) node = node->children[0]; else node = node->children[1]; if (!node) { Error("PlaceOccupant: invalid child %d\n", d < 0); } //end if } //end while //don't start in solid // if (node->contents == CONTENTS_SOLID) //ME: replaced because in LeafNode in brushbsp.c // some nodes have contents solid with other contents if (node->contents & CONTENTS_SOLID) return false; //if the node is already occupied if (node->occupied) return false; //place the occupant in the first leaf node->occupant = occupant; numrec = 0; // Log_Print("%6d recurses", numrec); // FloodPortals_r(node, 1); // Log_Print("\n"); FloodPortals(node); return true; } //end of the function PlaceOccupant //=========================================================================== // Marks all nodes that can be reached by entites // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean FloodEntities (tree_t *tree) { int i; int x, y; vec3_t origin; char *cl; qboolean inside; node_t *headnode; headnode = tree->headnode; Log_Print("------ FloodEntities -------\n"); inside = false; tree->outside_node.occupied = 0; //start at entity 1 not the world ( = 0) for (i = 1; i < num_entities; i++) { GetVectorForKey(&entities[i], "origin", origin); if (VectorCompare(origin, vec3_origin)) continue; cl = ValueForKey(&entities[i], "classname"); origin[2] += 1; //so objects on floor are ok // Log_Print("flooding from entity %d: %s\n", i, cl); //nudge playerstart around if needed so clipping hulls allways //have a valid point if (!strcmp(cl, "info_player_start")) { for (x = -16; x <= 16; x += 16) { for (y = -16; y <= 16; y += 16) { origin[0] += x; origin[1] += y; if (PlaceOccupant(headnode, origin, &entities[i])) { inside = true; x = 999; //stop for this info_player_start break; } //end if origin[0] -= x; origin[1] -= y; } //end for } //end for } //end if else { if (PlaceOccupant(headnode, origin, &entities[i])) { inside = true; } //end if } //end else } //end for if (!inside) { Log_Print("WARNING: no entities inside\n"); } //end if else if (tree->outside_node.occupied) { Log_Print("WARNING: entity reached from outside\n"); } //end else if return (qboolean)(inside && !tree->outside_node.occupied); } //end of the function FloodEntities /* ========================================================= FILL OUTSIDE ========================================================= */ int c_outside; int c_inside; int c_solid; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FillOutside_r (node_t *node) { if (node->planenum != PLANENUM_LEAF) { FillOutside_r (node->children[0]); FillOutside_r (node->children[1]); return; } //end if // anything not reachable by an entity // can be filled away (by setting solid contents) if (!node->occupied) { if (!(node->contents & CONTENTS_SOLID)) { c_outside++; node->contents |= CONTENTS_SOLID; } //end if else { c_solid++; } //end else } //end if else { c_inside++; } //end else } //end of the function FillOutside_r //=========================================================================== // Fill all nodes that can't be reached by entities // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FillOutside (node_t *headnode) { c_outside = 0; c_inside = 0; c_solid = 0; Log_Print("------- FillOutside --------\n"); FillOutside_r (headnode); Log_Print("%5i solid leaves\n", c_solid); Log_Print("%5i leaves filled\n", c_outside); Log_Print("%5i inside leaves\n", c_inside); } //end of the function FillOutside /* ========================================================= FLOOD AREAS ========================================================= */ int c_areas; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FloodAreas_r (node_t *node) { portal_t *p; int s; bspbrush_t *b; entity_t *e; if (node->contents == CONTENTS_AREAPORTAL) { // this node is part of an area portal b = node->brushlist; e = &entities[b->original->entitynum]; // if the current area has allready touched this // portal, we are done if (e->portalareas[0] == c_areas || e->portalareas[1] == c_areas) return; // note the current area as bounding the portal if (e->portalareas[1]) { Log_Print("WARNING: areaportal entity %i touches > 2 areas\n", b->original->entitynum); return; } if (e->portalareas[0]) e->portalareas[1] = c_areas; else e->portalareas[0] = c_areas; return; } //end if if (node->area) return; // allready got it node->area = c_areas; for (p=node->portals ; p ; p = p->next[s]) { s = (p->nodes[1] == node); #if 0 if (p->nodes[!s]->occupied) continue; #endif if (!Portal_EntityFlood (p, s)) continue; FloodAreas_r (p->nodes[!s]); } //end for } //end of the function FloodAreas_r //=========================================================================== // Just decend the tree, and for each node that hasn't had an // area set, flood fill out from there // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FindAreas_r (node_t *node) { if (node->planenum != PLANENUM_LEAF) { FindAreas_r (node->children[0]); FindAreas_r (node->children[1]); return; } if (node->area) return; // allready got it if (node->contents & CONTENTS_SOLID) return; if (!node->occupied) return; // not reachable by entities // area portals are allways only flooded into, never // out of if (node->contents == CONTENTS_AREAPORTAL) return; c_areas++; FloodAreas_r (node); } //end of the function FindAreas_r //=========================================================================== // Just decend the tree, and for each node that hasn't had an // area set, flood fill out from there // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void SetAreaPortalAreas_r (node_t *node) { bspbrush_t *b; entity_t *e; if (node->planenum != PLANENUM_LEAF) { SetAreaPortalAreas_r (node->children[0]); SetAreaPortalAreas_r (node->children[1]); return; } //end if if (node->contents == CONTENTS_AREAPORTAL) { if (node->area) return; // allready set b = node->brushlist; e = &entities[b->original->entitynum]; node->area = e->portalareas[0]; if (!e->portalareas[1]) { Log_Print("WARNING: areaportal entity %i doesn't touch two areas\n", b->original->entitynum); return; } //end if } //end if } //end of the function SetAreaPortalAreas_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== /* void EmitAreaPortals(node_t *headnode) { int i, j; entity_t *e; dareaportal_t *dp; if (c_areas > MAX_MAP_AREAS) Error ("MAX_MAP_AREAS"); numareas = c_areas+1; numareaportals = 1; // leave 0 as an error for (i=1 ; i<=c_areas ; i++) { dareas[i].firstareaportal = numareaportals; for (j=0 ; jareaportalnum) continue; dp = &dareaportals[numareaportals]; if (e->portalareas[0] == i) { dp->portalnum = e->areaportalnum; dp->otherarea = e->portalareas[1]; numareaportals++; } //end if else if (e->portalareas[1] == i) { dp->portalnum = e->areaportalnum; dp->otherarea = e->portalareas[0]; numareaportals++; } //end else if } //end for dareas[i].numareaportals = numareaportals - dareas[i].firstareaportal; } //end for Log_Print("%5i numareas\n", numareas); Log_Print("%5i numareaportals\n", numareaportals); } //end of the function EmitAreaPortals */ //=========================================================================== // Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FloodAreas (tree_t *tree) { Log_Print("--- FloodAreas ---\n"); FindAreas_r (tree->headnode); SetAreaPortalAreas_r (tree->headnode); Log_Print("%5i areas\n", c_areas); } //end of the function FloodAreas //=========================================================================== // Finds a brush side to use for texturing the given portal // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void FindPortalSide (portal_t *p) { int viscontents; bspbrush_t *bb; mapbrush_t *brush; node_t *n; int i,j; int planenum; side_t *side, *bestside; float dot, bestdot; plane_t *p1, *p2; // decide which content change is strongest // solid > lava > water, etc viscontents = VisibleContents (p->nodes[0]->contents ^ p->nodes[1]->contents); if (!viscontents) return; planenum = p->onnode->planenum; bestside = NULL; bestdot = 0; for (j=0 ; j<2 ; j++) { n = p->nodes[j]; p1 = &mapplanes[p->onnode->planenum]; for (bb=n->brushlist ; bb ; bb=bb->next) { brush = bb->original; if ( !(brush->contents & viscontents) ) continue; for (i=0 ; inumsides ; i++) { side = &brush->original_sides[i]; if (side->flags & SFL_BEVEL) continue; if (side->texinfo == TEXINFO_NODE) continue; // non-visible if ((side->planenum&~1) == planenum) { // exact match bestside = &brush->original_sides[i]; goto gotit; } //end if // see how close the match is p2 = &mapplanes[side->planenum&~1]; dot = DotProduct (p1->normal, p2->normal); if (dot > bestdot) { bestdot = dot; bestside = side; } //end if } //end for } //end for } //end for gotit: if (!bestside) Log_Print("WARNING: side not found for portal\n"); p->sidefound = true; p->side = bestside; } //end of the function FindPortalSide //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void MarkVisibleSides_r (node_t *node) { portal_t *p; int s; if (node->planenum != PLANENUM_LEAF) { MarkVisibleSides_r (node->children[0]); MarkVisibleSides_r (node->children[1]); return; } //end if // empty leaves are never boundary leaves if (!node->contents) return; // see if there is a visible face for (p=node->portals ; p ; p = p->next[!s]) { s = (p->nodes[0] == node); if (!p->onnode) continue; // edge of world if (!p->sidefound) FindPortalSide (p); if (p->side) p->side->flags |= SFL_VISIBLE; } //end for } //end of the function MarkVisibleSides_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void MarkVisibleSides(tree_t *tree, int startbrush, int endbrush) { int i, j; mapbrush_t *mb; int numsides; Log_Print("--- MarkVisibleSides ---\n"); // clear all the visible flags for (i=startbrush ; inumsides; for (j=0 ; joriginal_sides[j].flags &= ~SFL_VISIBLE; } // set visible flags on the sides that are used by portals MarkVisibleSides_r (tree->headnode); } //end of the function MarkVisibleSides ================================================ FILE: code/bspc/prtfile.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" /* ============================================================================== PORTAL FILE GENERATION Save out name.prt for qvis to read ============================================================================== */ #define PORTALFILE "PRT1" FILE *pf; int num_visclusters; // clusters the player can be in int num_visportals; void WriteFloat2 (FILE *f, vec_t v) { if ( fabs(v - Q_rint(v)) < 0.001 ) fprintf (f,"%i ",(int)Q_rint(v)); else fprintf (f,"%f ",v); } /* ================= WritePortalFile_r ================= */ void WritePortalFile_r (node_t *node) { int i, s; portal_t *p; winding_t *w; vec3_t normal; vec_t dist; // decision node if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) { WritePortalFile_r (node->children[0]); WritePortalFile_r (node->children[1]); return; } if (node->contents & CONTENTS_SOLID) return; for (p = node->portals ; p ; p=p->next[s]) { w = p->winding; s = (p->nodes[1] == node); if (w && p->nodes[0] == node) { if (!Portal_VisFlood (p)) continue; // write out to the file // sometimes planes get turned around when they are very near // the changeover point between different axis. interpret the // plane the same way vis will, and flip the side orders if needed // FIXME: is this still relevent? WindingPlane (w, normal, &dist); if ( DotProduct (p->plane.normal, normal) < 0.99 ) { // backwards... fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster); } else fprintf (pf,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster); for (i=0 ; inumpoints ; i++) { fprintf (pf,"("); WriteFloat2 (pf, w->p[i][0]); WriteFloat2 (pf, w->p[i][1]); WriteFloat2 (pf, w->p[i][2]); fprintf (pf,") "); } fprintf (pf,"\n"); } } } /* ================ FillLeafNumbers_r All of the leafs under node will have the same cluster ================ */ void FillLeafNumbers_r (node_t *node, int num) { if (node->planenum == PLANENUM_LEAF) { if (node->contents & CONTENTS_SOLID) node->cluster = -1; else node->cluster = num; return; } node->cluster = num; FillLeafNumbers_r (node->children[0], num); FillLeafNumbers_r (node->children[1], num); } /* ================ NumberLeafs_r ================ */ void NumberLeafs_r (node_t *node) { portal_t *p; if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) { // decision node node->cluster = -99; NumberLeafs_r (node->children[0]); NumberLeafs_r (node->children[1]); return; } // either a leaf or a detail cluster if ( node->contents & CONTENTS_SOLID ) { // solid block, viewpoint never inside node->cluster = -1; return; } FillLeafNumbers_r (node, num_visclusters); num_visclusters++; // count the portals for (p = node->portals ; p ; ) { if (p->nodes[0] == node) // only write out from first leaf { if (Portal_VisFlood (p)) num_visportals++; p = p->next[0]; } else p = p->next[1]; } } /* ================ CreateVisPortals_r ================ */ void CreateVisPortals_r (node_t *node) { // stop as soon as we get to a detail_seperator, which // means that everything below is in a single cluster if (node->planenum == PLANENUM_LEAF || node->detail_seperator ) return; MakeNodePortal (node); SplitNodePortals (node); CreateVisPortals_r (node->children[0]); CreateVisPortals_r (node->children[1]); } /* ================ FinishVisPortals_r ================ */ void FinishVisPortals2_r (node_t *node) { if (node->planenum == PLANENUM_LEAF) return; MakeNodePortal (node); SplitNodePortals (node); FinishVisPortals2_r (node->children[0]); FinishVisPortals2_r (node->children[1]); } void FinishVisPortals_r (node_t *node) { if (node->planenum == PLANENUM_LEAF) return; if (node->detail_seperator) { FinishVisPortals2_r (node); return; } FinishVisPortals_r (node->children[0]); FinishVisPortals_r (node->children[1]); } int clusterleaf; void SaveClusters_r (node_t *node) { if (node->planenum == PLANENUM_LEAF) { dleafs[clusterleaf++].cluster = node->cluster; return; } SaveClusters_r (node->children[0]); SaveClusters_r (node->children[1]); } /* ================ WritePortalFile ================ */ void WritePortalFile (tree_t *tree) { char filename[1024]; node_t *headnode; qprintf ("--- WritePortalFile ---\n"); headnode = tree->headnode; num_visclusters = 0; num_visportals = 0; Tree_FreePortals_r (headnode); MakeHeadnodePortals (tree); CreateVisPortals_r (headnode); // set the cluster field in every leaf and count the total number of portals NumberLeafs_r (headnode); // write the file sprintf (filename, "%s.prt", source); printf ("writing %s\n", filename); pf = fopen (filename, "w"); if (!pf) Error ("Error opening %s", filename); fprintf (pf, "%s\n", PORTALFILE); fprintf (pf, "%i\n", num_visclusters); fprintf (pf, "%i\n", num_visportals); qprintf ("%5i visclusters\n", num_visclusters); qprintf ("%5i visportals\n", num_visportals); WritePortalFile_r (headnode); fclose (pf); // we need to store the clusters out now because ordering // issues made us do this after writebsp... clusterleaf = 1; SaveClusters_r (headnode); } ================================================ FILE: code/bspc/q2files.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // qfiles.h: quake file formats // This file must be identical in the quake and utils directories // /* ======================================================================== The .pak files are just a linear collapse of a directory tree ======================================================================== */ #define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') typedef struct { char name[56]; int filepos, filelen; } dpackfile_t; typedef struct { int ident; // == IDPAKHEADER int dirofs; int dirlen; } dpackheader_t; #define MAX_FILES_IN_PACK 4096 /* ======================================================================== PCX files are used for as many images as possible ======================================================================== */ typedef struct { char manufacturer; char version; char encoding; char bits_per_pixel; unsigned short xmin,ymin,xmax,ymax; unsigned short hres,vres; unsigned char palette[48]; char reserved; char color_planes; unsigned short bytes_per_line; unsigned short palette_type; char filler[58]; unsigned char data; // unbounded } pcx_t; /* ======================================================================== .MD2 triangle model file format ======================================================================== */ #define IDALIASHEADER (('2'<<24)+('P'<<16)+('D'<<8)+'I') #define ALIAS_VERSION 8 #define MAX_TRIANGLES 4096 #define MAX_VERTS 2048 #define MAX_FRAMES 512 #define MAX_MD2SKINS 32 #define MAX_SKINNAME 64 typedef struct { short s; short t; } dstvert_t; typedef struct { short index_xyz[3]; short index_st[3]; } dtriangle_t; typedef struct { byte v[3]; // scaled byte to fit in frame mins/maxs byte lightnormalindex; } dtrivertx_t; #define DTRIVERTX_V0 0 #define DTRIVERTX_V1 1 #define DTRIVERTX_V2 2 #define DTRIVERTX_LNI 3 #define DTRIVERTX_SIZE 4 typedef struct { float scale[3]; // multiply byte verts by this float translate[3]; // then add this char name[16]; // frame name from grabbing dtrivertx_t verts[1]; // variable sized } daliasframe_t; // the glcmd format: // a positive integer starts a tristrip command, followed by that many // vertex structures. // a negative integer starts a trifan command, followed by -x vertexes // a zero indicates the end of the command list. // a vertex consists of a floating point s, a floating point t, // and an integer vertex index. typedef struct { int ident; int version; int skinwidth; int skinheight; int framesize; // byte size of each frame int num_skins; int num_xyz; int num_st; // greater than num_xyz for seams int num_tris; int num_glcmds; // dwords in strip/fan command list int num_frames; int ofs_skins; // each skin is a MAX_SKINNAME string int ofs_st; // byte offset from start for stverts int ofs_tris; // offset for dtriangles int ofs_frames; // offset for first frame int ofs_glcmds; int ofs_end; // end of file } dmdl_t; /* ======================================================================== .SP2 sprite file format ======================================================================== */ #define IDSPRITEHEADER (('2'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDS2" #define SPRITE_VERSION 2 typedef struct { int width, height; int origin_x, origin_y; // raster coordinates inside pic char name[MAX_SKINNAME]; // name of pcx file } dsprframe_t; typedef struct { int ident; int version; int numframes; dsprframe_t frames[1]; // variable sized } dsprite_t; /* ============================================================================== .WAL texture file format ============================================================================== */ #define MIPLEVELS 4 typedef struct miptex_s { char name[32]; unsigned width, height; unsigned offsets[MIPLEVELS]; // four mip maps stored char animname[32]; // next frame in animation chain int flags; int contents; int value; } miptex_t; /* ============================================================================== .BSP file format ============================================================================== */ #define IDBSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') // little-endian "IBSP" #define BSPVERSION 38 // upper design bounds // leaffaces, leafbrushes, planes, and verts are still bounded by // 16 bit short limits #define MAX_MAP_MODELS 1024 #define MAX_MAP_BRUSHES 8192 #define MAX_MAP_ENTITIES 2048 #define MAX_MAP_ENTSTRING 0x40000 #define MAX_MAP_TEXINFO 8192 #define MAX_MAP_AREAS 256 #define MAX_MAP_AREAPORTALS 1024 #define MAX_MAP_PLANES 65536 #define MAX_MAP_NODES 65536 #define MAX_MAP_BRUSHSIDES 65536 #define MAX_MAP_LEAFS 65536 #define MAX_MAP_VERTS 65536 #define MAX_MAP_FACES 65536 #define MAX_MAP_LEAFFACES 65536 #define MAX_MAP_LEAFBRUSHES 65536 #define MAX_MAP_PORTALS 65536 #define MAX_MAP_EDGES 128000 #define MAX_MAP_SURFEDGES 256000 #define MAX_MAP_LIGHTING 0x320000 #define MAX_MAP_VISIBILITY 0x280000 // key / value pair sizes #define MAX_KEY 32 #define MAX_VALUE 1024 //============================================================================= typedef struct { int fileofs, filelen; } lump_t; #define LUMP_ENTITIES 0 #define LUMP_PLANES 1 #define LUMP_VERTEXES 2 #define LUMP_VISIBILITY 3 #define LUMP_NODES 4 #define LUMP_TEXINFO 5 #define LUMP_FACES 6 #define LUMP_LIGHTING 7 #define LUMP_LEAFS 8 #define LUMP_LEAFFACES 9 #define LUMP_LEAFBRUSHES 10 #define LUMP_EDGES 11 #define LUMP_SURFEDGES 12 #define LUMP_MODELS 13 #define LUMP_BRUSHES 14 #define LUMP_BRUSHSIDES 15 #define LUMP_POP 16 #define LUMP_AREAS 17 #define LUMP_AREAPORTALS 18 #define HEADER_LUMPS 19 typedef struct { int ident; int version; lump_t lumps[HEADER_LUMPS]; } dheader_t; typedef struct { float mins[3], maxs[3]; float origin[3]; // for sounds or lights int headnode; int firstface, numfaces; // submodels just draw faces // without walking the bsp tree } dmodel_t; typedef struct { float point[3]; } dvertex_t; // 0-2 are axial planes #define PLANE_X 0 #define PLANE_Y 1 #define PLANE_Z 2 // 3-5 are non-axial planes snapped to the nearest #define PLANE_ANYX 3 #define PLANE_ANYY 4 #define PLANE_ANYZ 5 // planes (x&~1) and (x&~1)+1 are allways opposites typedef struct { float normal[3]; float dist; int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate } dplane_t; // contents flags are seperate bits // a given brush can contribute multiple content bits // multiple brushes can be in a single leaf // these definitions also need to be in q_shared.h! // lower bits are stronger, and will eat weaker brushes completely #define CONTENTS_SOLID 1 // an eye is never valid in a solid #define CONTENTS_WINDOW 2 // translucent, but not watery #define CONTENTS_AUX 4 #define CONTENTS_LAVA 8 #define CONTENTS_SLIME 16 #define CONTENTS_WATER 32 #define CONTENTS_MIST 64 #define LAST_VISIBLE_CONTENTS 64 // remaining contents are non-visible, and don't eat brushes #define CONTENTS_AREAPORTAL 0x8000 #define CONTENTS_PLAYERCLIP 0x10000 #define CONTENTS_MONSTERCLIP 0x20000 // currents can be added to any other contents, and may be mixed #define CONTENTS_CURRENT_0 0x40000 #define CONTENTS_CURRENT_90 0x80000 #define CONTENTS_CURRENT_180 0x100000 #define CONTENTS_CURRENT_270 0x200000 #define CONTENTS_CURRENT_UP 0x400000 #define CONTENTS_CURRENT_DOWN 0x800000 #define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity #define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game #define CONTENTS_DEADMONSTER 0x4000000 #define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs //renamed because it's in conflict with the Q3A translucent contents #define CONTENTS_Q2TRANSLUCENT 0x10000000 // auto set if any surface has trans #define CONTENTS_LADDER 0x20000000 #define SURF_LIGHT 0x1 // value will hold the light strength #define SURF_SLICK 0x2 // effects game physics #define SURF_SKY 0x4 // don't draw, but add to skybox #define SURF_WARP 0x8 // turbulent water warp #define SURF_TRANS33 0x10 #define SURF_TRANS66 0x20 #define SURF_FLOWING 0x40 // scroll towards angle #define SURF_NODRAW 0x80 // don't bother referencing the texture #define SURF_HINT 0x100 // make a primary bsp splitter #define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes typedef struct { int planenum; int children[2]; // negative numbers are -(leafs+1), not nodes short mins[3]; // for frustom culling short maxs[3]; unsigned short firstface; unsigned short numfaces; // counting both sides } dnode_t; typedef struct texinfo_s { float vecs[2][4]; // [s/t][xyz offset] int flags; // miptex flags + overrides int value; // light emission, etc char texture[32]; // texture name (textures/*.wal) int nexttexinfo; // for animations, -1 = end of chain } texinfo_t; // note that edge 0 is never used, because negative edge nums are used for // counterclockwise use of the edge in a face typedef struct { unsigned short v[2]; // vertex numbers } dedge_t; #define MAXLIGHTMAPS 4 typedef struct { unsigned short planenum; short side; int firstedge; // we must support > 64k edges short numedges; short texinfo; // lighting info byte styles[MAXLIGHTMAPS]; int lightofs; // start of [numstyles*surfsize] samples } dface_t; typedef struct { int contents; // OR of all brushes (not needed?) short cluster; short area; short mins[3]; // for frustum culling short maxs[3]; unsigned short firstleafface; unsigned short numleaffaces; unsigned short firstleafbrush; unsigned short numleafbrushes; } dleaf_t; typedef struct { unsigned short planenum; // facing out of the leaf short texinfo; } dbrushside_t; typedef struct { int firstside; int numsides; int contents; } dbrush_t; #define ANGLE_UP -1 #define ANGLE_DOWN -2 // the visibility lump consists of a header with a count, then // byte offsets for the PVS and PHS of each cluster, then the raw // compressed bit vectors #define DVIS_PVS 0 #define DVIS_PHS 1 typedef struct { int numclusters; int bitofs[8][2]; // bitofs[numclusters][2] } dvis_t; // each area has a list of portals that lead into other areas // when portals are closed, other areas may not be visible or // hearable even if the vis info says that it should be typedef struct { int portalnum; int otherarea; } dareaportal_t; typedef struct { int numareaportals; int firstareaportal; } darea_t; ================================================ FILE: code/bspc/q3files.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #ifndef __QFILES_H__ #define __QFILES_H__ // // qfiles.h: quake file formats // This file must be identical in the quake and utils directories // // surface geometry should not exceed these limits #define SHADER_MAX_VERTEXES 1000 #define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES) // the maximum size of game reletive pathnames #define MAX_QPATH 64 /* ======================================================================== PCX files are used for 8 bit images ======================================================================== * typedef struct { char manufacturer; char version; char encoding; char bits_per_pixel; unsigned short xmin,ymin,xmax,ymax; unsigned short hres,vres; unsigned char palette[48]; char reserved; char color_planes; unsigned short bytes_per_line; unsigned short palette_type; char filler[58]; unsigned char data; // unbounded } pcx_t; /* ======================================================================== TGA files are used for 24/32 bit images ======================================================================== * typedef struct _TargaHeader { unsigned char id_length, colormap_type, image_type; unsigned short colormap_index, colormap_length; unsigned char colormap_size; unsigned short x_origin, y_origin, width, height; unsigned char pixel_size, attributes; } TargaHeader; */ /* ======================================================================== .MD3 triangle model file format ======================================================================== */ #define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I') #define MD3_VERSION 15 // limits #define MD3_MAX_LODS 4 #define MD3_MAX_TRIANGLES 8192 // per surface #define MD3_MAX_VERTS 4096 // per surface #define MD3_MAX_SHADERS 256 // per surface #define MD3_MAX_FRAMES 1024 // per model #define MD3_MAX_SURFACES 32 // per model #define MD3_MAX_TAGS 16 // per frame // vertex scales #define MD3_XYZ_SCALE (1.0/64) typedef struct md3Frame_s { vec3_t bounds[2]; vec3_t localOrigin; float radius; char name[16]; } md3Frame_t; typedef struct md3Tag_s { char name[MAX_QPATH]; // tag name vec3_t origin; vec3_t axis[3]; } md3Tag_t; /* ** md3Surface_t ** ** CHUNK SIZE ** header sizeof( md3Surface_t ) ** shaders sizeof( md3Shader_t ) * numShaders ** triangles[0] sizeof( md3Triangle_t ) * numTriangles ** st sizeof( md3St_t ) * numVerts ** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames */ typedef struct { int ident; // char name[MAX_QPATH]; // polyset name int flags; int numFrames; // all surfaces in a model should have the same int numShaders; // all surfaces in a model should have the same int numVerts; int numTriangles; int ofsTriangles; int ofsShaders; // offset from start of md3Surface_t int ofsSt; // texture coords are common for all frames int ofsXyzNormals; // numVerts * numFrames int ofsEnd; // next surface follows } md3Surface_t; typedef struct { char name[MAX_QPATH]; int shaderIndex; // for in-game use } md3Shader_t; typedef struct { int indexes[3]; } md3Triangle_t; typedef struct { float st[2]; } md3St_t; typedef struct { short xyz[3]; short normal; } md3XyzNormal_t; typedef struct { int ident; int version; char name[MAX_QPATH]; // model name int flags; int numFrames; int numTags; int numSurfaces; int numSkins; int ofsFrames; // offset for first frame int ofsTags; // numFrames * numTags int ofsSurfaces; // first surface, others follow int ofsEnd; // end of file } md3Header_t; /* ============================================================================== .BSP file format ============================================================================== */ #define Q3_BSP_IDENT (('P'<<24)+('S'<<16)+('B'<<8)+'I') // little-endian "IBSP" #define Q3_BSP_VERSION 46 // there shouldn't be any problem with increasing these values at the // expense of more memory allocation in the utilities #define Q3_MAX_MAP_MODELS 0x400 #define Q3_MAX_MAP_BRUSHES 0x8000 #define Q3_MAX_MAP_ENTITIES 0x800 #define Q3_MAX_MAP_ENTSTRING 0x10000 #define Q3_MAX_MAP_SHADERS 0x400 #define Q3_MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! #define Q3_MAX_MAP_FOGS 0x100 #define Q3_MAX_MAP_PLANES 0x10000 #define Q3_MAX_MAP_NODES 0x10000 #define Q3_MAX_MAP_BRUSHSIDES 0x10000 #define Q3_MAX_MAP_LEAFS 0x10000 #define Q3_MAX_MAP_LEAFFACES 0x10000 #define Q3_MAX_MAP_LEAFBRUSHES 0x10000 #define Q3_MAX_MAP_PORTALS 0x10000 #define Q3_MAX_MAP_LIGHTING 0x400000 #define Q3_MAX_MAP_LIGHTGRID 0x400000 #define Q3_MAX_MAP_VISIBILITY 0x200000 #define Q3_MAX_MAP_DRAW_SURFS 0x20000 #define Q3_MAX_MAP_DRAW_VERTS 0x80000 #define Q3_MAX_MAP_DRAW_INDEXES 0x80000 // key / value pair sizes in the entities lump #define Q3_MAX_KEY 32 #define Q3_MAX_VALUE 1024 // the editor uses these predefined yaw angles to orient entities up or down #define ANGLE_UP -1 #define ANGLE_DOWN -2 #define LIGHTMAP_WIDTH 128 #define LIGHTMAP_HEIGHT 128 //============================================================================= typedef struct { int fileofs, filelen; } q3_lump_t; #define Q3_LUMP_ENTITIES 0 #define Q3_LUMP_SHADERS 1 #define Q3_LUMP_PLANES 2 #define Q3_LUMP_NODES 3 #define Q3_LUMP_LEAFS 4 #define Q3_LUMP_LEAFSURFACES 5 #define Q3_LUMP_LEAFBRUSHES 6 #define Q3_LUMP_MODELS 7 #define Q3_LUMP_BRUSHES 8 #define Q3_LUMP_BRUSHSIDES 9 #define Q3_LUMP_DRAWVERTS 10 #define Q3_LUMP_DRAWINDEXES 11 #define Q3_LUMP_FOGS 12 #define Q3_LUMP_SURFACES 13 #define Q3_LUMP_LIGHTMAPS 14 #define Q3_LUMP_LIGHTGRID 15 #define Q3_LUMP_VISIBILITY 16 #define Q3_HEADER_LUMPS 17 typedef struct { int ident; int version; q3_lump_t lumps[Q3_HEADER_LUMPS]; } q3_dheader_t; typedef struct { float mins[3], maxs[3]; int firstSurface, numSurfaces; int firstBrush, numBrushes; } q3_dmodel_t; typedef struct { char shader[MAX_QPATH]; int surfaceFlags; int contentFlags; } q3_dshader_t; // planes (x&~1) and (x&~1)+1 are allways opposites typedef struct { float normal[3]; float dist; } q3_dplane_t; typedef struct { int planeNum; int children[2]; // negative numbers are -(leafs+1), not nodes int mins[3]; // for frustom culling int maxs[3]; } q3_dnode_t; typedef struct { int cluster; // -1 = opaque cluster (do I still store these?) int area; int mins[3]; // for frustum culling int maxs[3]; int firstLeafSurface; int numLeafSurfaces; int firstLeafBrush; int numLeafBrushes; } q3_dleaf_t; typedef struct { int planeNum; // positive plane side faces out of the leaf int shaderNum; } q3_dbrushside_t; typedef struct { int firstSide; int numSides; int shaderNum; // the shader that determines the contents flags } q3_dbrush_t; typedef struct { char shader[MAX_QPATH]; int brushNum; int visibleSide; // the brush side that ray tests need to clip against (-1 == none) } q3_dfog_t; typedef struct { vec3_t xyz; float st[2]; float lightmap[2]; vec3_t normal; byte color[4]; } q3_drawVert_t; typedef enum { MST_BAD, MST_PLANAR, MST_PATCH, MST_TRIANGLE_SOUP, MST_FLARE } q3_mapSurfaceType_t; typedef struct { int shaderNum; int fogNum; int surfaceType; int firstVert; int numVerts; int firstIndex; int numIndexes; int lightmapNum; int lightmapX, lightmapY; int lightmapWidth, lightmapHeight; vec3_t lightmapOrigin; vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds int patchWidth; int patchHeight; } q3_dsurface_t; #endif ================================================ FILE: code/bspc/qbsp.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #if defined(WIN32) || defined(_WIN32) #include #endif #include #include "l_cmd.h" #include "l_math.h" #include "l_poly.h" #include "l_threads.h" #include "../botlib/l_script.h" #include "l_bsp_ent.h" #include "q2files.h" #include "l_mem.h" #include "l_utils.h" #include "l_log.h" #include "l_qfiles.h" #define BSPC_VERSION "2.1h" #define ME #define DEBUG #define NODELIST #define SIN #define MAX_BRUSH_SIDES 128 //maximum number of sides per brush #define CLIP_EPSILON 0.1 #define MAX_MAP_BOUNDS 65535 #define BOGUS_RANGE (MAX_MAP_BOUNDS+128) //somewhere outside the map #define TEXINFO_NODE -1 //side is allready on a node #define PLANENUM_LEAF -1 //used for leaf nodes #define MAXEDGES 20 //maximum number of face edges #define MAX_NODE_BRUSHES 8 //maximum brushes in a node //side flags #define SFL_TESTED 1 #define SFL_VISIBLE 2 #define SFL_BEVEL 4 #define SFL_TEXTURED 8 #define SFL_CURVE 16 //map plane typedef struct plane_s { vec3_t normal; vec_t dist; int type; int signbits; struct plane_s *hash_chain; } plane_t; //brush texture typedef struct { vec_t shift[2]; vec_t rotate; vec_t scale[2]; char name[32]; int flags; int value; } brush_texture_t; //brush side typedef struct side_s { int planenum; // map plane this side is in int texinfo; // texture reference winding_t *winding; // winding of this side struct side_s *original; // bspbrush_t sides will reference the mapbrush_t sides int lightinfo; // for SIN only int contents; // from miptex int surf; // from miptex unsigned short flags; // side flags } side_t; //sizeof(side_t) = 36 //map brush typedef struct mapbrush_s { int entitynum; int brushnum; int contents; #ifdef ME int expansionbbox; //bbox used for expansion of the brush int leafnum; int modelnum; #endif vec3_t mins, maxs; int numsides; side_t *original_sides; } mapbrush_t; //bsp face typedef struct face_s { struct face_s *next; // on node // the chain of faces off of a node can be merged or split, // but each face_t along the way will remain in the chain // until the entire tree is freed struct face_s *merged; // if set, this face isn't valid anymore struct face_s *split[2]; // if set, this face isn't valid anymore struct portal_s *portal; int texinfo; #ifdef SIN int lightinfo; #endif int planenum; int contents; // faces in different contents can't merge int outputnumber; winding_t *w; int numpoints; qboolean badstartvert; // tjunctions cannot be fixed without a midpoint vertex int vertexnums[MAXEDGES]; } face_t; //bsp brush typedef struct bspbrush_s { struct bspbrush_s *next; vec3_t mins, maxs; int side, testside; // side of node during construction mapbrush_t *original; int numsides; side_t sides[6]; // variably sized } bspbrush_t; //sizeof(bspbrush_t) = 44 + numsides * sizeof(side_t) //bsp node typedef struct node_s { //both leafs and nodes int planenum; // -1 = leaf node struct node_s *parent; vec3_t mins, maxs; // valid after portalization bspbrush_t *volume; // one for each leaf/node // nodes only qboolean detail_seperator; // a detail brush caused the split side_t *side; // the side that created the node struct node_s *children[2]; face_t *faces; // leafs only bspbrush_t *brushlist; // fragments of all brushes in this leaf int contents; // OR of all brush contents int occupied; // 1 or greater can reach entity entity_t *occupant; // for leak file testing int cluster; // for portalfile writing int area; // for areaportals struct portal_s *portals; // also on nodes during construction #ifdef NODELIST struct node_s *next; //next node in the nodelist #endif #ifdef ME int expansionbboxes; //OR of all bboxes used for expansion of the brushes int modelnum; #endif } node_t; //sizeof(node_t) = 80 bytes //bsp portal typedef struct portal_s { plane_t plane; node_t *onnode; // NULL = outside box node_t *nodes[2]; // [0] = front side of plane struct portal_s *next[2]; winding_t *winding; qboolean sidefound; // false if ->side hasn't been checked side_t *side; // NULL = non-visible face_t *face[2]; // output face in bsp file #ifdef ME struct tmp_face_s *tmpface; //pointer to the tmpface created for this portal int planenum; //number of the map plane used by the portal #endif } portal_t; //bsp tree typedef struct { node_t *headnode; node_t outside_node; vec3_t mins, maxs; } tree_t; //============================================================================= // bspc.c //============================================================================= extern qboolean noprune; extern qboolean nodetail; extern qboolean fulldetail; extern qboolean nomerge; extern qboolean nosubdiv; extern qboolean nowater; extern qboolean noweld; extern qboolean noshare; extern qboolean notjunc; extern qboolean onlyents; #ifdef ME extern qboolean nocsg; extern qboolean create_aas; extern qboolean freetree; extern qboolean lessbrushes; extern qboolean nobrushmerge; extern qboolean cancelconversion; extern qboolean noliquids; extern qboolean capsule_collision; #endif //ME extern float subdivide_size; extern vec_t microvolume; extern char outbase[32]; extern char source[1024]; //============================================================================= // map.c //============================================================================= #define MAX_MAPFILE_PLANES 256000 #define MAX_MAPFILE_BRUSHES 65535 #define MAX_MAPFILE_BRUSHSIDES (MAX_MAPFILE_BRUSHES*8) #define MAX_MAPFILE_TEXINFO 8192 extern int entity_num; extern plane_t mapplanes[MAX_MAPFILE_PLANES]; extern int nummapplanes; extern int mapplaneusers[MAX_MAPFILE_PLANES]; extern int nummapbrushes; extern mapbrush_t mapbrushes[MAX_MAPFILE_BRUSHES]; extern vec3_t map_mins, map_maxs; extern int nummapbrushsides; extern side_t brushsides[MAX_MAPFILE_BRUSHSIDES]; extern brush_texture_t side_brushtextures[MAX_MAPFILE_BRUSHSIDES]; #ifdef ME typedef struct { float vecs[2][4]; // [s/t][xyz offset] int flags; // miptex flags + overrides int value; char texture[64]; // texture name (textures/*.wal) int nexttexinfo; // for animations, -1 = end of chain } map_texinfo_t; extern map_texinfo_t map_texinfo[MAX_MAPFILE_TEXINFO]; extern int map_numtexinfo; #define NODESTACKSIZE 1024 #define MAPTYPE_QUAKE1 1 #define MAPTYPE_QUAKE2 2 #define MAPTYPE_QUAKE3 3 #define MAPTYPE_HALFLIFE 4 #define MAPTYPE_SIN 5 extern int nodestack[NODESTACKSIZE]; extern int *nodestackptr; extern int nodestacksize; extern int brushmodelnumbers[MAX_MAPFILE_BRUSHES]; extern int dbrushleafnums[MAX_MAPFILE_BRUSHES]; extern int dplanes2mapplanes[MAX_MAPFILE_PLANES]; extern int loadedmaptype; #endif //ME extern int c_boxbevels; extern int c_edgebevels; extern int c_areaportals; extern int c_clipbrushes; extern int c_squattbrushes; //finds a float plane for the given normal and distance int FindFloatPlane(vec3_t normal, vec_t dist); //returns the plane type for the given normal int PlaneTypeForNormal(vec3_t normal); //returns the plane defined by the three given points int PlaneFromPoints(int *p0, int *p1, int *p2); //add bevels to the map brush void AddBrushBevels(mapbrush_t *b); //makes brush side windings for the brush qboolean MakeBrushWindings(mapbrush_t *ob); //marks brush bevels of the brush as bevel void MarkBrushBevels(mapbrush_t *brush); //returns true if the map brush already exists int BrushExists(mapbrush_t *brush); //loads a map from a bsp file int LoadMapFromBSP(struct quakefile_s *qf); //resets map loading void ResetMapLoading(void); //print some map info void PrintMapInfo(void); //writes a map file (type depending on loaded map type) void WriteMapFile(char *filename); //============================================================================= // map_q2.c //============================================================================= void Q2_ResetMapLoading(void); //loads a Quake2 map file void Q2_LoadMapFile(char *filename); //loads a map from a Quake2 bsp file void Q2_LoadMapFromBSP(char *filename, int offset, int length); //============================================================================= // map_q1.c //============================================================================= void Q1_ResetMapLoading(void); //loads a Quake2 map file void Q1_LoadMapFile(char *filename); //loads a map from a Quake1 bsp file void Q1_LoadMapFromBSP(char *filename, int offset, int length); //============================================================================= // map_q3.c //============================================================================= void Q3_ResetMapLoading(void); //loads a map from a Quake3 bsp file void Q3_LoadMapFromBSP(struct quakefile_s *qf); //============================================================================= // map_sin.c //============================================================================= void Sin_ResetMapLoading(void); //loads a Sin map file void Sin_LoadMapFile(char *filename); //loads a map from a Sin bsp file void Sin_LoadMapFromBSP(char *filename, int offset, int length); //============================================================================= // map_hl.c //============================================================================= void HL_ResetMapLoading(void); //loads a Half-Life map file void HL_LoadMapFile(char *filename); //loads a map from a Half-Life bsp file void HL_LoadMapFromBSP(char *filename, int offset, int length); //============================================================================= // textures.c //============================================================================= typedef struct { char name[64]; int flags; int value; int contents; char animname[64]; } textureref_t; #define MAX_MAP_TEXTURES 1024 extern textureref_t textureref[MAX_MAP_TEXTURES]; int FindMiptex(char *name); int TexinfoForBrushTexture(plane_t *plane, brush_texture_t *bt, vec3_t origin); void TextureAxisFromPlane(plane_t *pln, vec3_t xv, vec3_t yv); //============================================================================= // csg //============================================================================= bspbrush_t *MakeBspBrushList(int startbrush, int endbrush, vec3_t clipmins, vec3_t clipmaxs); bspbrush_t *ChopBrushes(bspbrush_t *head); bspbrush_t *InitialBrushList(bspbrush_t *list); bspbrush_t *OptimizedBrushList(bspbrush_t *list); void WriteBrushMap(char *name, bspbrush_t *list); void CheckBSPBrush(bspbrush_t *brush); void BSPBrushWindings(bspbrush_t *brush); bspbrush_t *TryMergeBrushes(bspbrush_t *brush1, bspbrush_t *brush2); tree_t *ProcessWorldBrushes(int brush_start, int brush_end); //============================================================================= // brushbsp //============================================================================= #define PSIDE_FRONT 1 #define PSIDE_BACK 2 #define PSIDE_BOTH (PSIDE_FRONT|PSIDE_BACK) #define PSIDE_FACING 4 void WriteBrushList(char *name, bspbrush_t *brush, qboolean onlyvis); bspbrush_t *CopyBrush(bspbrush_t *brush); void SplitBrush(bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back); node_t *AllocNode(void); bspbrush_t *AllocBrush(int numsides); int CountBrushList(bspbrush_t *brushes); void FreeBrush(bspbrush_t *brushes); vec_t BrushVolume(bspbrush_t *brush); void BoundBrush(bspbrush_t *brush); void FreeBrushList(bspbrush_t *brushes); tree_t *BrushBSP(bspbrush_t *brushlist, vec3_t mins, vec3_t maxs); bspbrush_t *BrushFromBounds(vec3_t mins, vec3_t maxs); int BrushMostlyOnSide(bspbrush_t *brush, plane_t *plane); qboolean WindingIsHuge(winding_t *w); qboolean WindingIsTiny(winding_t *w); void ResetBrushBSP(void); //============================================================================= // portals.c //============================================================================= int VisibleContents (int contents); void MakeHeadnodePortals (tree_t *tree); void MakeNodePortal (node_t *node); void SplitNodePortals (node_t *node); qboolean Portal_VisFlood (portal_t *p); qboolean FloodEntities (tree_t *tree); void FillOutside (node_t *headnode); void FloodAreas (tree_t *tree); void MarkVisibleSides (tree_t *tree, int start, int end); void FreePortal (portal_t *p); void EmitAreaPortals (node_t *headnode); void MakeTreePortals (tree_t *tree); //============================================================================= // glfile.c //============================================================================= void OutputWinding(winding_t *w, FILE *glview); void WriteGLView(tree_t *tree, char *source); //============================================================================= // gldraw.c //============================================================================= extern vec3_t draw_mins, draw_maxs; extern qboolean drawflag; void Draw_ClearWindow (void); void DrawWinding (winding_t *w); void GLS_BeginScene (void); void GLS_Winding (winding_t *w, int code); void GLS_EndScene (void); //============================================================================= // leakfile.c //============================================================================= void LeakFile (tree_t *tree); //============================================================================= // tree.c //============================================================================= tree_t *Tree_Alloc(void); void Tree_Free(tree_t *tree); void Tree_Free_r(node_t *node); void Tree_Print_r(node_t *node, int depth); void Tree_FreePortals_r(node_t *node); void Tree_PruneNodes_r(node_t *node); void Tree_PruneNodes(node_t *node); ================================================ FILE: code/bspc/qfiles.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // qfiles.h: quake file formats // This file must be identical in the quake and utils directories // /* ======================================================================== The .pak files are just a linear collapse of a directory tree ======================================================================== */ #define IDPAKHEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') typedef struct { char name[56]; int filepos, filelen; } dpackfile_t; typedef struct { int ident; // == IDPAKHEADER int dirofs; int dirlen; } dpackheader_t; #define MAX_FILES_IN_PACK 4096 /* ======================================================================== PCX files are used for as many images as possible ======================================================================== */ typedef struct { char manufacturer; char version; char encoding; char bits_per_pixel; unsigned short xmin,ymin,xmax,ymax; unsigned short hres,vres; unsigned char palette[48]; char reserved; char color_planes; unsigned short bytes_per_line; unsigned short palette_type; char filler[58]; unsigned char data; // unbounded } pcx_t; /* ======================================================================== .MD2 triangle model file format ======================================================================== */ #define IDALIASHEADER (('2'<<24)+('P'<<16)+('D'<<8)+'I') #define ALIAS_VERSION 8 #define MAX_TRIANGLES 4096 #define MAX_VERTS 2048 #define MAX_FRAMES 512 #define MAX_MD2SKINS 32 #define MAX_SKINNAME 64 typedef struct { short s; short t; } dstvert_t; typedef struct { short index_xyz[3]; short index_st[3]; } dtriangle_t; typedef struct { byte v[3]; // scaled byte to fit in frame mins/maxs byte lightnormalindex; } dtrivertx_t; #define DTRIVERTX_V0 0 #define DTRIVERTX_V1 1 #define DTRIVERTX_V2 2 #define DTRIVERTX_LNI 3 #define DTRIVERTX_SIZE 4 typedef struct { float scale[3]; // multiply byte verts by this float translate[3]; // then add this char name[16]; // frame name from grabbing dtrivertx_t verts[1]; // variable sized } daliasframe_t; // the glcmd format: // a positive integer starts a tristrip command, followed by that many // vertex structures. // a negative integer starts a trifan command, followed by -x vertexes // a zero indicates the end of the command list. // a vertex consists of a floating point s, a floating point t, // and an integer vertex index. typedef struct { int ident; int version; int skinwidth; int skinheight; int framesize; // byte size of each frame int num_skins; int num_xyz; int num_st; // greater than num_xyz for seams int num_tris; int num_glcmds; // dwords in strip/fan command list int num_frames; int ofs_skins; // each skin is a MAX_SKINNAME string int ofs_st; // byte offset from start for stverts int ofs_tris; // offset for dtriangles int ofs_frames; // offset for first frame int ofs_glcmds; int ofs_end; // end of file } dmdl_t; /* ======================================================================== .SP2 sprite file format ======================================================================== */ #define IDSPRITEHEADER (('2'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDS2" #define SPRITE_VERSION 2 typedef struct { int width, height; int origin_x, origin_y; // raster coordinates inside pic char name[MAX_SKINNAME]; // name of pcx file } dsprframe_t; typedef struct { int ident; int version; int numframes; dsprframe_t frames[1]; // variable sized } dsprite_t; /* ============================================================================== .WAL texture file format ============================================================================== */ #define MIPLEVELS 4 typedef struct miptex_s { char name[32]; unsigned width, height; unsigned offsets[MIPLEVELS]; // four mip maps stored char animname[32]; // next frame in animation chain int flags; int contents; int value; } miptex_t; /* ============================================================================== .BSP file format ============================================================================== */ #define IDBSPHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'I') // little-endian "IBSP" #define BSPVERSION 38 // upper design bounds // leaffaces, leafbrushes, planes, and verts are still bounded by // 16 bit short limits #define MAX_MAP_MODELS 1024 #define MAX_MAP_BRUSHES 8192 #define MAX_MAP_ENTITIES 2048 #define MAX_MAP_ENTSTRING 0x40000 #define MAX_MAP_TEXINFO 8192 #define MAX_MAP_AREAS 256 #define MAX_MAP_AREAPORTALS 1024 #define MAX_MAP_PLANES 65536 #define MAX_MAP_NODES 65536 #define MAX_MAP_BRUSHSIDES 65536 #define MAX_MAP_LEAFS 65536 #define MAX_MAP_VERTS 65536 #define MAX_MAP_FACES 65536 #define MAX_MAP_LEAFFACES 65536 #define MAX_MAP_LEAFBRUSHES 65536 #define MAX_MAP_PORTALS 65536 #define MAX_MAP_EDGES 128000 #define MAX_MAP_SURFEDGES 256000 #define MAX_MAP_LIGHTING 0x320000 #define MAX_MAP_VISIBILITY 0x280000 // key / value pair sizes #define MAX_KEY 32 #define MAX_VALUE 1024 //============================================================================= typedef struct { int fileofs, filelen; } lump_t; #define LUMP_ENTITIES 0 #define LUMP_PLANES 1 #define LUMP_VERTEXES 2 #define LUMP_VISIBILITY 3 #define LUMP_NODES 4 #define LUMP_TEXINFO 5 #define LUMP_FACES 6 #define LUMP_LIGHTING 7 #define LUMP_LEAFS 8 #define LUMP_LEAFFACES 9 #define LUMP_LEAFBRUSHES 10 #define LUMP_EDGES 11 #define LUMP_SURFEDGES 12 #define LUMP_MODELS 13 #define LUMP_BRUSHES 14 #define LUMP_BRUSHSIDES 15 #define LUMP_POP 16 #define LUMP_AREAS 17 #define LUMP_AREAPORTALS 18 #define HEADER_LUMPS 19 typedef struct { int ident; int version; lump_t lumps[HEADER_LUMPS]; } dheader_t; typedef struct { float mins[3], maxs[3]; float origin[3]; // for sounds or lights int headnode; int firstface, numfaces; // submodels just draw faces // without walking the bsp tree } dmodel_t; typedef struct { float point[3]; } dvertex_t; // 0-2 are axial planes #define PLANE_X 0 #define PLANE_Y 1 #define PLANE_Z 2 // 3-5 are non-axial planes snapped to the nearest #define PLANE_ANYX 3 #define PLANE_ANYY 4 #define PLANE_ANYZ 5 // planes (x&~1) and (x&~1)+1 are allways opposites typedef struct { float normal[3]; float dist; int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate } dplane_t; // contents flags are seperate bits // a given brush can contribute multiple content bits // multiple brushes can be in a single leaf // these definitions also need to be in q_shared.h! // lower bits are stronger, and will eat weaker brushes completely #define CONTENTS_SOLID 1 // an eye is never valid in a solid #define CONTENTS_WINDOW 2 // translucent, but not watery #define CONTENTS_AUX 4 #define CONTENTS_LAVA 8 #define CONTENTS_SLIME 16 #define CONTENTS_WATER 32 #define CONTENTS_MIST 64 #define LAST_VISIBLE_CONTENTS 64 // remaining contents are non-visible, and don't eat brushes #define CONTENTS_AREAPORTAL 0x8000 #define CONTENTS_PLAYERCLIP 0x10000 #define CONTENTS_MONSTERCLIP 0x20000 // currents can be added to any other contents, and may be mixed #define CONTENTS_CURRENT_0 0x40000 #define CONTENTS_CURRENT_90 0x80000 #define CONTENTS_CURRENT_180 0x100000 #define CONTENTS_CURRENT_270 0x200000 #define CONTENTS_CURRENT_UP 0x400000 #define CONTENTS_CURRENT_DOWN 0x800000 #define CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity #define CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game #define CONTENTS_DEADMONSTER 0x4000000 #define CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs //renamed because it's in conflict with the Q3A translucent contents #define CONTENTS_Q2TRANSLUCENT 0x10000000 // auto set if any surface has trans #define CONTENTS_LADDER 0x20000000 #define SURF_LIGHT 0x1 // value will hold the light strength #define SURF_SLICK 0x2 // effects game physics #define SURF_SKY 0x4 // don't draw, but add to skybox #define SURF_WARP 0x8 // turbulent water warp #define SURF_TRANS33 0x10 #define SURF_TRANS66 0x20 #define SURF_FLOWING 0x40 // scroll towards angle #define SURF_NODRAW 0x80 // don't bother referencing the texture #define SURF_HINT 0x100 // make a primary bsp splitter #define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes typedef struct { int planenum; int children[2]; // negative numbers are -(leafs+1), not nodes short mins[3]; // for frustom culling short maxs[3]; unsigned short firstface; unsigned short numfaces; // counting both sides } dnode_t; typedef struct texinfo_s { float vecs[2][4]; // [s/t][xyz offset] int flags; // miptex flags + overrides int value; // light emission, etc char texture[32]; // texture name (textures/*.wal) int nexttexinfo; // for animations, -1 = end of chain } texinfo_t; // note that edge 0 is never used, because negative edge nums are used for // counterclockwise use of the edge in a face typedef struct { unsigned short v[2]; // vertex numbers } dedge_t; #define MAXLIGHTMAPS 4 typedef struct { unsigned short planenum; short side; int firstedge; // we must support > 64k edges short numedges; short texinfo; // lighting info byte styles[MAXLIGHTMAPS]; int lightofs; // start of [numstyles*surfsize] samples } dface_t; typedef struct { int contents; // OR of all brushes (not needed?) short cluster; short area; short mins[3]; // for frustum culling short maxs[3]; unsigned short firstleafface; unsigned short numleaffaces; unsigned short firstleafbrush; unsigned short numleafbrushes; } dleaf_t; typedef struct { unsigned short planenum; // facing out of the leaf short texinfo; } dbrushside_t; typedef struct { int firstside; int numsides; int contents; } dbrush_t; #define ANGLE_UP -1 #define ANGLE_DOWN -2 // the visibility lump consists of a header with a count, then // byte offsets for the PVS and PHS of each cluster, then the raw // compressed bit vectors #define DVIS_PVS 0 #define DVIS_PHS 1 typedef struct { int numclusters; int bitofs[8][2]; // bitofs[numclusters][2] } dvis_t; // each area has a list of portals that lead into other areas // when portals are closed, other areas may not be visible or // hearable even if the vis info says that it should be typedef struct { int portalnum; int otherarea; } dareaportal_t; typedef struct { int numareaportals; int firstareaportal; } darea_t; ================================================ FILE: code/bspc/sinfiles.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /* ============================================================================== .BSP file format ============================================================================== */ #define SIN #define SINBSPVERSION 41 // upper design bounds // leaffaces, leafbrushes, planes, and verts are still bounded by // 16 bit short limits #define SIN_MAX_MAP_MODELS 1024 #define SIN_MAX_MAP_BRUSHES 8192 #define SIN_MAX_MAP_ENTITIES 2048 #define SIN_MAX_MAP_ENTSTRING 0x40000 #define SIN_MAX_MAP_TEXINFO 8192 #define SIN_MAX_MAP_AREAS 256 #define SIN_MAX_MAP_AREAPORTALS 1024 #define SIN_MAX_MAP_PLANES 65536 #define SIN_MAX_MAP_NODES 65536 #define SIN_MAX_MAP_BRUSHSIDES 65536 #define SIN_MAX_MAP_LEAFS 65536 #define SIN_MAX_MAP_VERTS 65536 #define SIN_MAX_MAP_FACES 65536 #define SIN_MAX_MAP_LEAFFACES 65536 #define SIN_MAX_MAP_LEAFBRUSHES 65536 #define SIN_MAX_MAP_PORTALS 65536 #define SIN_MAX_MAP_EDGES 128000 #define SIN_MAX_MAP_SURFEDGES 256000 #define SIN_MAX_MAP_LIGHTING 0x320000 #define SIN_MAX_MAP_VISIBILITY 0x280000 #ifdef SIN #define SIN_MAX_MAP_LIGHTINFO 8192 #endif #ifdef SIN #undef SIN_MAX_MAP_LIGHTING //undef the Quake2 bsp version #define SIN_MAX_MAP_LIGHTING 0x300000 #endif #ifdef SIN #undef SIN_MAX_MAP_VISIBILITY //undef the Quake2 bsp version #define SIN_MAX_MAP_VISIBILITY 0x280000 #endif //============================================================================= typedef struct { int fileofs, filelen; } sin_lump_t; #define SIN_LUMP_ENTITIES 0 #define SIN_LUMP_PLANES 1 #define SIN_LUMP_VERTEXES 2 #define SIN_LUMP_VISIBILITY 3 #define SIN_LUMP_NODES 4 #define SIN_LUMP_TEXINFO 5 #define SIN_LUMP_FACES 6 #define SIN_LUMP_LIGHTING 7 #define SIN_LUMP_LEAFS 8 #define SIN_LUMP_LEAFFACES 9 #define SIN_LUMP_LEAFBRUSHES 10 #define SIN_LUMP_EDGES 11 #define SIN_LUMP_SURFEDGES 12 #define SIN_LUMP_MODELS 13 #define SIN_LUMP_BRUSHES 14 #define SIN_LUMP_BRUSHSIDES 15 #define SIN_LUMP_POP 16 #define SIN_LUMP_AREAS 17 #define SIN_LUMP_AREAPORTALS 18 #ifdef SIN #define SIN_LUMP_LIGHTINFO 19 #define SINHEADER_LUMPS 20 #endif typedef struct { int ident; int version; sin_lump_t lumps[SINHEADER_LUMPS]; } sin_dheader_t; typedef struct { float mins[3], maxs[3]; float origin[3]; // for sounds or lights int headnode; int firstface, numfaces; // submodels just draw faces // without walking the bsp tree } sin_dmodel_t; typedef struct { float point[3]; } sin_dvertex_t; // 0-2 are axial planes #define PLANE_X 0 #define PLANE_Y 1 #define PLANE_Z 2 // 3-5 are non-axial planes snapped to the nearest #define PLANE_ANYX 3 #define PLANE_ANYY 4 #define PLANE_ANYZ 5 // planes (x&~1) and (x&~1)+1 are allways opposites typedef struct { float normal[3]; float dist; int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate } sin_dplane_t; // contents flags are seperate bits // a given brush can contribute multiple content bits // multiple brushes can be in a single leaf // these definitions also need to be in q_shared.h! // lower bits are stronger, and will eat weaker brushes completely #ifdef SIN #define CONTENTS_FENCE 4 #endif // remaining contents are non-visible, and don't eat brushes #ifdef SIN #define CONTENTS_DUMMYFENCE 0x1000 #endif #ifdef SIN #define SURF_MASKED 0x2 // surface texture is masked #endif #define SURF_SKY 0x4 // don't draw, but add to skybox #define SURF_WARP 0x8 // turbulent water warp #ifdef SIN #define SURF_NONLIT 0x10 // surface is not lit #define SURF_NOFILTER 0x20 // surface is not bi-linear filtered #endif #define SURF_FLOWING 0x40 // scroll towards angle #define SURF_NODRAW 0x80 // don't bother referencing the texture #define SURF_HINT 0x100 // make a primary bsp splitter #define SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes #ifdef SIN #define SURF_CONVEYOR 0x40 // surface is not lit #endif #ifdef SIN #define SURF_WAVY 0x400 // surface has waves #define SURF_RICOCHET 0x800 // projectiles bounce literally bounce off this surface #define SURF_PRELIT 0x1000 // surface has intensity information for pre-lighting #define SURF_MIRROR 0x2000 // surface is a mirror #define SURF_CONSOLE 0x4000 // surface is a console #define SURF_USECOLOR 0x8000 // surface is lit with non-lit * color #define SURF_HARDWAREONLY 0x10000 // surface has been damaged #define SURF_DAMAGE 0x20000 // surface can be damaged #define SURF_WEAK 0x40000 // surface has weak hit points #define SURF_NORMAL 0x80000 // surface has normal hit points #define SURF_ADD 0x100000 // surface will be additive #define SURF_ENVMAPPED 0x200000 // surface is envmapped #define SURF_RANDOMANIMATE 0x400000 // surface start animating on a random frame #define SURF_ANIMATE 0x800000 // surface animates #define SURF_RNDTIME 0x1000000 // time between animations is random #define SURF_TRANSLATE 0x2000000 // surface translates #define SURF_NOMERGE 0x4000000 // surface is not merged in csg phase #define SURF_TYPE_BIT0 0x8000000 // 0 bit of surface type #define SURF_TYPE_BIT1 0x10000000 // 1 bit of surface type #define SURF_TYPE_BIT2 0x20000000 // 2 bit of surface type #define SURF_TYPE_BIT3 0x40000000 // 3 bit of surface type #define SURF_START_BIT 27 #define SURFACETYPE_FROM_FLAGS( x ) ( ( x >> (SURF_START_BIT) ) & 0xf ) #define SURF_TYPE_SHIFT(x) ( (x) << (SURF_START_BIT) ) // macro for getting proper bit mask #define SURF_TYPE_NONE SURF_TYPE_SHIFT(0) #define SURF_TYPE_WOOD SURF_TYPE_SHIFT(1) #define SURF_TYPE_METAL SURF_TYPE_SHIFT(2) #define SURF_TYPE_STONE SURF_TYPE_SHIFT(3) #define SURF_TYPE_CONCRETE SURF_TYPE_SHIFT(4) #define SURF_TYPE_DIRT SURF_TYPE_SHIFT(5) #define SURF_TYPE_FLESH SURF_TYPE_SHIFT(6) #define SURF_TYPE_GRILL SURF_TYPE_SHIFT(7) #define SURF_TYPE_GLASS SURF_TYPE_SHIFT(8) #define SURF_TYPE_FABRIC SURF_TYPE_SHIFT(9) #define SURF_TYPE_MONITOR SURF_TYPE_SHIFT(10) #define SURF_TYPE_GRAVEL SURF_TYPE_SHIFT(11) #define SURF_TYPE_VEGETATION SURF_TYPE_SHIFT(12) #define SURF_TYPE_PAPER SURF_TYPE_SHIFT(13) #define SURF_TYPE_DUCT SURF_TYPE_SHIFT(14) #define SURF_TYPE_WATER SURF_TYPE_SHIFT(15) #endif typedef struct { int planenum; int children[2]; // negative numbers are -(leafs+1), not nodes short mins[3]; // for frustom culling short maxs[3]; unsigned short firstface; unsigned short numfaces; // counting both sides } sin_dnode_t; #ifdef SIN typedef struct sin_lightvalue_s { int value; // light emission, etc vec3_t color; float direct; float directangle; float directstyle; char directstylename[32]; } sin_lightvalue_t; typedef struct sin_texinfo_s { float vecs[2][4]; // [s/t][xyz offset] int flags; // miptex flags + overrides char texture[64]; // texture name (textures/*.wal) int nexttexinfo; // for animations, -1 = end of chain float trans_mag; int trans_angle; int base_angle; float animtime; float nonlit; float translucence; float friction; float restitution; vec3_t color; char groupname[32]; } sin_texinfo_t; #endif //SIN // note that edge 0 is never used, because negative edge nums are used for // counterclockwise use of the edge in a face typedef struct { unsigned short v[2]; // vertex numbers } sin_dedge_t; #ifdef MAXLIGHTMAPS #undef MAXLIGHTMAPS #endif #define MAXLIGHTMAPS 16 typedef struct { unsigned short planenum; short side; int firstedge; // we must support > 64k edges short numedges; short texinfo; // lighting info byte styles[MAXLIGHTMAPS]; int lightofs; // start of [numstyles*surfsize] samples #ifdef SIN int lightinfo; #endif } sin_dface_t; typedef struct { int contents; // OR of all brushes (not needed?) short cluster; short area; short mins[3]; // for frustum culling short maxs[3]; unsigned short firstleafface; unsigned short numleaffaces; unsigned short firstleafbrush; unsigned short numleafbrushes; } sin_dleaf_t; typedef struct { unsigned short planenum; // facing out of the leaf short texinfo; #ifdef SIN int lightinfo; #endif } sin_dbrushside_t; typedef struct { int firstside; int numsides; int contents; } sin_dbrush_t; #define ANGLE_UP -1 #define ANGLE_DOWN -2 // the visibility lump consists of a header with a count, then // byte offsets for the PVS and PHS of each cluster, then the raw // compressed bit vectors #define DVIS_PVS 0 #define DVIS_PHS 1 typedef struct { int numclusters; int bitofs[8][2]; // bitofs[numclusters][2] } sin_dvis_t; // each area has a list of portals that lead into other areas // when portals are closed, other areas may not be visible or // hearable even if the vis info says that it should be typedef struct { int portalnum; int otherarea; } sin_dareaportal_t; typedef struct { int numareaportals; int firstareaportal; } sin_darea_t; ================================================ FILE: code/bspc/tetrahedron.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "l_mem.h" #include "../botlib/aasfile.h" #include "aas_store.h" #include "aas_cfg.h" #include "aas_file.h" // // creating tetrahedrons from a arbitrary world bounded by triangles // // a triangle has 3 corners and 3 edges // a tetrahedron is build out of 4 triangles // a tetrahedron has 6 edges // we start with a world bounded by triangles, a side of a triangle facing // towards the oudside of the world is marked as part of tetrahedron -1 // // a tetrahedron is defined by two non-coplanar triangles with a shared edge // // a tetrahedron is defined by one triangle and a vertex not in the triangle plane // // if all triangles using a specific vertex have tetrahedrons // at both sides then this vertex will never be part of a new tetrahedron // // if all triangles using a specific edge have tetrahedrons // at both sides then this vertex will never be part of a new tetrahedron // // each triangle can only be shared by two tetrahedrons // when all triangles have tetrahedrons at both sides then we're done // // if we cannot create any new tetrahedrons and there is at least one triangle // which has a tetrahedron only at one side then the world leaks // #define Sign(x) (x < 0 ? 1 : 0) #define MAX_TH_VERTEXES 128000 #define MAX_TH_PLANES 128000 #define MAX_TH_EDGES 512000 #define MAX_TH_TRIANGLES 51200 #define MAX_TH_TETRAHEDRONS 12800 #define PLANEHASH_SIZE 1024 #define EDGEHASH_SIZE 1024 #define TRIANGLEHASH_SIZE 1024 #define VERTEXHASH_SHIFT 7 #define VERTEXHASH_SIZE ((MAX_MAP_BOUNDS>>(VERTEXHASH_SHIFT-1))+1) //was 64 #define NORMAL_EPSILON 0.0001 #define DIST_EPSILON 0.1 #define VERTEX_EPSILON 0.01 #define INTEGRAL_EPSILON 0.01 //plane typedef struct th_plane_s { vec3_t normal; float dist; int type; int signbits; struct th_plane_s *hashnext; //next plane in hash } th_plane_t; //vertex typedef struct th_vertex_s { vec3_t v; int usercount; //2x the number of to be processed //triangles using this vertex struct th_vertex_s *hashnext; //next vertex in hash } th_vertex_t; //edge typedef struct th_edge_s { int v[2]; //vertex indexes int usercount; //number of to be processed //triangles using this edge struct th_edge_s *hashnext; //next edge in hash } th_edge_t; //triangle typedef struct th_triangle_s { int edges[3]; //negative if edge is flipped th_plane_t planes[3]; //triangle bounding planes int planenum; //plane the triangle is in int front; //tetrahedron at the front int back; //tetrahedron at the back vec3_t mins, maxs; //triangle bounding box struct th_triangle_s *prev, *next; //links in linked triangle lists struct th_triangle_s *hashnext; //next triangle in hash } th_triangle_t; //tetrahedron typedef struct th_tetrahedron_s { int triangles[4]; //negative if at backside of triangle float volume; //tetrahedron volume } th_tetrahedron_t; typedef struct th_s { //vertexes int numvertexes; th_vertex_t *vertexes; th_vertex_t *vertexhash[VERTEXHASH_SIZE * VERTEXHASH_SIZE]; //planes int numplanes; th_plane_t *planes; th_plane_t *planehash[PLANEHASH_SIZE]; //edges int numedges; th_edge_t *edges; th_edge_t *edgehash[EDGEHASH_SIZE]; //triangles int numtriangles; th_triangle_t *triangles; th_triangle_t *trianglehash[TRIANGLEHASH_SIZE]; //tetrahedrons int numtetrahedrons; th_tetrahedron_t *tetrahedrons; } th_t; th_t thworld; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_InitMaxTH(void) { //get memory for the tetrahedron data thworld.vertexes = (th_vertex_t *) GetClearedMemory(MAX_TH_VERTEXES * sizeof(th_vertex_t)); thworld.planes = (th_plane_t *) GetClearedMemory(MAX_TH_PLANES * sizeof(th_plane_t)); thworld.edges = (th_edge_t *) GetClearedMemory(MAX_TH_EDGES * sizeof(th_edge_t)); thworld.triangles = (th_triangle_t *) GetClearedMemory(MAX_TH_TRIANGLES * sizeof(th_triangle_t)); thworld.tetrahedrons = (th_tetrahedron_t *) GetClearedMemory(MAX_TH_TETRAHEDRONS * sizeof(th_tetrahedron_t)); //reset the hash tables memset(thworld.vertexhash, 0, VERTEXHASH_SIZE * sizeof(th_vertex_t *)); memset(thworld.planehash, 0, PLANEHASH_SIZE * sizeof(th_plane_t *)); memset(thworld.edgehash, 0, EDGEHASH_SIZE * sizeof(th_edge_t *)); memset(thworld.trianglehash, 0, TRIANGLEHASH_SIZE * sizeof(th_triangle_t *)); } //end of the function TH_InitMaxTH //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_FreeMaxTH(void) { if (thworld.vertexes) FreeMemory(thworld.vertexes); thworld.vertexes = NULL; thworld.numvertexes = 0; if (thworld.planes) FreeMemory(thworld.planes); thworld.planes = NULL; thworld.numplanes = 0; if (thworld.edges) FreeMemory(thworld.edges); thworld.edges = NULL; thworld.numedges = 0; if (thworld.triangles) FreeMemory(thworld.triangles); thworld.triangles = NULL; thworld.numtriangles = 0; if (thworld.tetrahedrons) FreeMemory(thworld.tetrahedrons); thworld.tetrahedrons = NULL; thworld.numtetrahedrons = 0; } //end of the function TH_FreeMaxTH //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float TH_TriangleArea(th_triangle_t *tri) { return 0; } //end of the function TH_TriangleArea //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== float TH_TetrahedronVolume(th_tetrahedron_t *tetrahedron) { int edgenum, verts[3], i, j, v2; float volume, d; th_triangle_t *tri, *tri2; th_plane_t *plane; tri = &thworld.triangles[abs(tetrahedron->triangles[0])]; for (i = 0; i < 3; i++) { edgenum = tri->edges[i]; if (edgenum < 0) verts[i] = thworld.edges[abs(edgenum)].v[1]; else verts[i] = thworld.edges[edgenum].v[0]; } //end for // tri2 = &thworld.triangles[abs(tetrahedron->triangles[1])]; for (j = 0; j < 3; j++) { edgenum = tri2->edges[i]; if (edgenum < 0) v2 = thworld.edges[abs(edgenum)].v[1]; else v2 = thworld.edges[edgenum].v[0]; if (v2 != verts[0] && v2 != verts[1] && v2 != verts[2]) break; } //end for plane = &thworld.planes[tri->planenum]; d = -(DotProduct (thworld.vertexes[v2].v, plane->normal) - plane->dist); volume = TH_TriangleArea(tri) * d / 3; return volume; } //end of the function TH_TetrahedronVolume //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_PlaneSignBits(vec3_t normal) { int i, signbits; signbits = 0; for (i = 2; i >= 0; i--) { signbits = (signbits << 1) + Sign(normal[i]); } //end for return signbits; } //end of the function TH_PlaneSignBits //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_PlaneTypeForNormal(vec3_t normal) { vec_t ax, ay, az; // NOTE: should these have an epsilon around 1.0? if (normal[0] == 1.0 || normal[0] == -1.0) return PLANE_X; if (normal[1] == 1.0 || normal[1] == -1.0) return PLANE_Y; if (normal[2] == 1.0 || normal[2] == -1.0) return PLANE_Z; ax = fabs(normal[0]); ay = fabs(normal[1]); az = fabs(normal[2]); if (ax >= ay && ax >= az) return PLANE_ANYX; if (ay >= ax && ay >= az) return PLANE_ANYY; return PLANE_ANYZ; } //end of the function TH_PlaneTypeForNormal //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== qboolean TH_PlaneEqual(th_plane_t *p, vec3_t normal, vec_t dist) { if ( fabs(p->normal[0] - normal[0]) < NORMAL_EPSILON && fabs(p->normal[1] - normal[1]) < NORMAL_EPSILON && fabs(p->normal[2] - normal[2]) < NORMAL_EPSILON && fabs(p->dist - dist) < DIST_EPSILON ) return true; return false; } //end of the function TH_PlaneEqual //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_AddPlaneToHash(th_plane_t *p) { int hash; hash = (int)fabs(p->dist) / 8; hash &= (PLANEHASH_SIZE-1); p->hashnext = thworld.planehash[hash]; thworld.planehash[hash] = p; } //end of the function TH_AddPlaneToHash //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_CreateFloatPlane(vec3_t normal, vec_t dist) { th_plane_t *p, temp; if (VectorLength(normal) < 0.5) Error ("FloatPlane: bad normal"); // create a new plane if (thworld.numplanes+2 > MAX_TH_PLANES) Error ("MAX_TH_PLANES"); p = &thworld.planes[thworld.numplanes]; VectorCopy (normal, p->normal); p->dist = dist; p->type = (p+1)->type = TH_PlaneTypeForNormal (p->normal); p->signbits = TH_PlaneSignBits(p->normal); VectorSubtract (vec3_origin, normal, (p+1)->normal); (p+1)->dist = -dist; (p+1)->signbits = TH_PlaneSignBits((p+1)->normal); thworld.numplanes += 2; // allways put axial planes facing positive first if (p->type < 3) { if (p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0) { // flip order temp = *p; *p = *(p+1); *(p+1) = temp; TH_AddPlaneToHash(p); TH_AddPlaneToHash(p+1); return thworld.numplanes - 1; } //end if } //end if TH_AddPlaneToHash(p); TH_AddPlaneToHash(p+1); return thworld.numplanes - 2; } //end of the function TH_CreateFloatPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_SnapVector(vec3_t normal) { int i; for (i = 0; i < 3; i++) { if ( fabs(normal[i] - 1) < NORMAL_EPSILON ) { VectorClear (normal); normal[i] = 1; break; } //end if if ( fabs(normal[i] - -1) < NORMAL_EPSILON ) { VectorClear (normal); normal[i] = -1; break; } //end if } //end for } //end of the function TH_SnapVector //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_SnapPlane(vec3_t normal, vec_t *dist) { TH_SnapVector(normal); if (fabs(*dist-Q_rint(*dist)) < DIST_EPSILON) *dist = Q_rint(*dist); } //end of the function TH_SnapPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_FindFloatPlane(vec3_t normal, vec_t dist) { int i; th_plane_t *p; int hash, h; TH_SnapPlane (normal, &dist); hash = (int)fabs(dist) / 8; hash &= (PLANEHASH_SIZE-1); // search the border bins as well for (i = -1; i <= 1; i++) { h = (hash+i)&(PLANEHASH_SIZE-1); for (p = thworld.planehash[h]; p; p = p->hashnext) { if (TH_PlaneEqual(p, normal, dist)) { return p - thworld.planes; } //end if } //end for } //end for return TH_CreateFloatPlane(normal, dist); } //end of the function TH_FindFloatPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_PlaneFromPoints(int v1, int v2, int v3) { vec3_t t1, t2, normal; vec_t dist; float *p0, *p1, *p2; p0 = thworld.vertexes[v1].v; p1 = thworld.vertexes[v2].v; p2 = thworld.vertexes[v3].v; VectorSubtract(p0, p1, t1); VectorSubtract(p2, p1, t2); CrossProduct(t1, t2, normal); VectorNormalize(normal); dist = DotProduct(p0, normal); return TH_FindFloatPlane(normal, dist); } //end of the function TH_PlaneFromPoints //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_AddEdgeUser(int edgenum) { th_edge_t *edge; edge = &thworld.edges[abs(edgenum)]; //increase edge user count edge->usercount++; //increase vertex user count as well thworld.vertexes[edge->v[0]].usercount++; thworld.vertexes[edge->v[1]].usercount++; } //end of the function TH_AddEdgeUser //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_RemoveEdgeUser(int edgenum) { th_edge_t *edge; edge = &thworld.edges[abs(edgenum)]; //decrease edge user count edge->usercount--; //decrease vertex user count as well thworld.vertexes[edge->v[0]].usercount--; thworld.vertexes[edge->v[1]].usercount--; } //end of the function TH_RemoveEdgeUser //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_FreeTriangleEdges(th_triangle_t *tri) { int i; for (i = 0; i < 3; i++) { TH_RemoveEdgeUser(abs(tri->edges[i])); } //end for } //end of the function TH_FreeTriangleEdges //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== unsigned TH_HashVec(vec3_t vec) { int x, y; x = (MAX_MAP_BOUNDS + (int)(vec[0]+0.5)) >> VERTEXHASH_SHIFT; y = (MAX_MAP_BOUNDS + (int)(vec[1]+0.5)) >> VERTEXHASH_SHIFT; if (x < 0 || x >= VERTEXHASH_SIZE || y < 0 || y >= VERTEXHASH_SIZE) Error("HashVec: point %f %f %f outside valid range", vec[0], vec[1], vec[2]); return y*VERTEXHASH_SIZE + x; } //end of the function TH_HashVec //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_FindVertex(vec3_t v) { int i, h; th_vertex_t *vertex; vec3_t vert; for (i = 0; i < 3; i++) { if ( fabs(v[i] - Q_rint(v[i])) < INTEGRAL_EPSILON) vert[i] = Q_rint(v[i]); else vert[i] = v[i]; } //end for h = TH_HashVec(vert); for (vertex = thworld.vertexhash[h]; vertex; vertex = vertex->hashnext) { if (fabs(vertex->v[0] - vert[0]) < VERTEX_EPSILON && fabs(vertex->v[1] - vert[1]) < VERTEX_EPSILON && fabs(vertex->v[2] - vert[2]) < VERTEX_EPSILON) { return vertex - thworld.vertexes; } //end if } //end for return 0; } //end of the function TH_FindVertex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_AddVertexToHash(th_vertex_t *vertex) { int hashvalue; hashvalue = TH_HashVec(vertex->v); vertex->hashnext = thworld.vertexhash[hashvalue]; thworld.vertexhash[hashvalue] = vertex; } //end of the function TH_AddVertexToHash //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_CreateVertex(vec3_t v) { if (thworld.numvertexes == 0) thworld.numvertexes = 1; if (thworld.numvertexes >= MAX_TH_VERTEXES) Error("MAX_TH_VERTEXES"); VectorCopy(v, thworld.vertexes[thworld.numvertexes].v); thworld.vertexes[thworld.numvertexes].usercount = 0; TH_AddVertexToHash(&thworld.vertexes[thworld.numvertexes]); thworld.numvertexes++; return thworld.numvertexes-1; } //end of the function TH_CreateVertex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_FindOrCreateVertex(vec3_t v) { int vertexnum; vertexnum = TH_FindVertex(v); if (!vertexnum) vertexnum = TH_CreateVertex(v); return vertexnum; } //end of the function TH_FindOrCreateVertex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_FindEdge(int v1, int v2) { int hashvalue; th_edge_t *edge; hashvalue = (v1 + v2) & (EDGEHASH_SIZE-1); for (edge = thworld.edgehash[hashvalue]; edge; edge = edge->hashnext) { if (edge->v[0] == v1 && edge->v[1] == v2) return edge - thworld.edges; if (edge->v[1] == v1 && edge->v[0] == v2) return -(edge - thworld.edges); } //end for return 0; } //end of the function TH_FindEdge //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_AddEdgeToHash(th_edge_t *edge) { int hashvalue; hashvalue = (edge->v[0] + edge->v[1]) & (EDGEHASH_SIZE-1); edge->hashnext = thworld.edgehash[hashvalue]; thworld.edgehash[hashvalue] = edge; } //end of the function TH_AddEdgeToHash //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_CreateEdge(int v1, int v2) { th_edge_t *edge; if (thworld.numedges == 0) thworld.numedges = 1; if (thworld.numedges >= MAX_TH_EDGES) Error("MAX_TH_EDGES"); edge = &thworld.edges[thworld.numedges++]; edge->v[0] = v1; edge->v[1] = v2; TH_AddEdgeToHash(edge); return thworld.numedges-1; } //end of the function TH_CreateEdge //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_FindOrCreateEdge(int v1, int v2) { int edgenum; edgenum = TH_FindEdge(v1, v2); if (!edgenum) edgenum = TH_CreateEdge(v1, v2); return edgenum; } //end of the function TH_FindOrCreateEdge //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_FindTriangle(int verts[3]) { int i, hashvalue, edges[3]; th_triangle_t *tri; for (i = 0; i < 3; i++) { edges[i] = TH_FindEdge(verts[i], verts[(i+1)%3]); if (!edges[i]) return false; } //end for hashvalue = (abs(edges[0]) + abs(edges[1]) + abs(edges[2])) & (TRIANGLEHASH_SIZE-1); for (tri = thworld.trianglehash[hashvalue]; tri; tri = tri->next) { for (i = 0; i < 3; i++) { if (abs(tri->edges[i]) != abs(edges[0]) && abs(tri->edges[i]) != abs(edges[1]) && abs(tri->edges[i]) != abs(edges[2])) break; } //end for if (i >= 3) return tri - thworld.triangles; } //end for return 0; } //end of the function TH_FindTriangle //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_AddTriangleToHash(th_triangle_t *tri) { int hashvalue; hashvalue = (abs(tri->edges[0]) + abs(tri->edges[1]) + abs(tri->edges[2])) & (TRIANGLEHASH_SIZE-1); tri->hashnext = thworld.trianglehash[hashvalue]; thworld.trianglehash[hashvalue] = tri; } //end of the function TH_AddTriangleToHash //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_CreateTrianglePlanes(int verts[3], th_plane_t *triplane, th_plane_t *planes) { int i; vec3_t dir; for (i = 0; i < 3; i++) { VectorSubtract(thworld.vertexes[verts[(i+1)%3]].v, thworld.vertexes[verts[i]].v, dir); CrossProduct(dir, triplane->normal, planes[i].normal); VectorNormalize(planes[i].normal); planes[i].dist = DotProduct(thworld.vertexes[verts[i]].v, planes[i].normal); } //end for } //end of the function TH_CreateTrianglePlanes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_CreateTriangle(int verts[3]) { th_triangle_t *tri; int i; if (thworld.numtriangles == 0) thworld.numtriangles = 1; if (thworld.numtriangles >= MAX_TH_TRIANGLES) Error("MAX_TH_TRIANGLES"); tri = &thworld.triangles[thworld.numtriangles++]; for (i = 0; i < 3; i++) { tri->edges[i] = TH_FindOrCreateEdge(verts[i], verts[(i+1)%3]); TH_AddEdgeUser(abs(tri->edges[i])); } //end for tri->front = 0; tri->back = 0; tri->planenum = TH_PlaneFromPoints(verts[0], verts[1], verts[2]); tri->prev = NULL; tri->next = NULL; tri->hashnext = NULL; TH_CreateTrianglePlanes(verts, &thworld.planes[tri->planenum], tri->planes); TH_AddTriangleToHash(tri); ClearBounds(tri->mins, tri->maxs); for (i = 0; i < 3; i++) { AddPointToBounds(thworld.vertexes[verts[i]].v, tri->mins, tri->maxs); } //end for return thworld.numtriangles-1; } //end of the function TH_CreateTriangle //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_CreateTetrahedron(int triangles[4]) { th_tetrahedron_t *tetrahedron; int i; if (thworld.numtetrahedrons == 0) thworld.numtetrahedrons = 1; if (thworld.numtetrahedrons >= MAX_TH_TETRAHEDRONS) Error("MAX_TH_TETRAHEDRONS"); tetrahedron = &thworld.tetrahedrons[thworld.numtetrahedrons++]; for (i = 0; i < 4; i++) { tetrahedron->triangles[i] = triangles[i]; if (thworld.triangles[abs(triangles[i])].front) { thworld.triangles[abs(triangles[i])].back = thworld.numtetrahedrons-1; } //end if else { thworld.triangles[abs(triangles[i])].front = thworld.numtetrahedrons-1; } //end else } //end for tetrahedron->volume = 0; return thworld.numtetrahedrons-1; } //end of the function TH_CreateTetrahedron //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_IntersectTrianglePlanes(int v1, int v2, th_plane_t *triplane, th_plane_t *planes) { float *p1, *p2, front, back, frac, d; int i, side, lastside; vec3_t mid; p1 = thworld.vertexes[v1].v; p2 = thworld.vertexes[v2].v; front = DotProduct(p1, triplane->normal) - triplane->dist; back = DotProduct(p2, triplane->normal) - triplane->dist; //if both points at the same side of the plane if (front < 0.1 && back < 0.1) return false; if (front > -0.1 && back > -0.1) return false; // frac = front/(front-back); mid[0] = p1[0] + (p2[0] - p1[0]) * frac; mid[1] = p1[1] + (p2[1] - p1[1]) * frac; mid[2] = p1[2] + (p2[2] - p1[2]) * frac; //if the mid point is at the same side of all the tri bounding planes lastside = 0; for (i = 0; i < 3; i++) { d = DotProduct(mid, planes[i].normal) - planes[i].dist; side = d < 0; if (i && side != lastside) return false; lastside = side; } //end for return true; } //end of the function TH_IntersectTrianglePlanes //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_OutsideBoundingBox(int v1, int v2, vec3_t mins, vec3_t maxs) { float *p1, *p2; int i; p1 = thworld.vertexes[v1].v; p2 = thworld.vertexes[v2].v; //if both points are at the outer side of one of the bounding box planes for (i = 0; i < 3; i++) { if (p1[i] < mins[i] && p2[i] < mins[i]) return true; if (p1[i] > maxs[i] && p2[i] > maxs[i]) return true; } //end for return false; } //end of the function TH_OutsideBoundingBox //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_TryEdge(int v1, int v2) { int i, j, v; th_plane_t *plane; th_triangle_t *tri; //if the edge already exists it must be valid if (TH_FindEdge(v1, v2)) return true; //test the edge with all existing triangles for (i = 1; i < thworld.numtriangles; i++) { tri = &thworld.triangles[i]; //if triangle is enclosed by two tetrahedrons we don't have to test it //because the edge always has to go through another triangle of those //tetrahedrons first to reach the enclosed triangle if (tri->front && tri->back) continue; //if the edges is totally outside the triangle bounding box if (TH_OutsideBoundingBox(v1, v2, tri->mins, tri->maxs)) continue; //if one of the edge vertexes is used by this triangle for (j = 0; j < 3; j++) { v = thworld.edges[abs(tri->edges[j])].v[tri->edges[j] < 0]; if (v == v1 || v == v2) break; } //end for if (j < 3) continue; //get the triangle plane plane = &thworld.planes[tri->planenum]; //if the edge intersects with a triangle then it's not valid if (TH_IntersectTrianglePlanes(v1, v2, plane, tri->planes)) return false; } //end for return true; } //end of the function TH_TryEdge //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_TryTriangle(int verts[3]) { th_plane_t planes[3], triplane; vec3_t t1, t2; float *p0, *p1, *p2; int i, j; p0 = thworld.vertexes[verts[0]].v; p1 = thworld.vertexes[verts[1]].v; p2 = thworld.vertexes[verts[2]].v; VectorSubtract(p0, p1, t1); VectorSubtract(p2, p1, t2); CrossProduct(t1, t2, triplane.normal); VectorNormalize(triplane.normal); triplane.dist = DotProduct(p0, triplane.normal); // TH_CreateTrianglePlanes(verts, &triplane, planes); //test if any existing edge intersects with this triangle for (i = 1; i < thworld.numedges; i++) { //if the edge is only used by triangles with tetrahedrons at both sides if (!thworld.edges[i].usercount) continue; //if one of the triangle vertexes is used by this edge for (j = 0; j < 3; j++) { if (verts[j] == thworld.edges[j].v[0] || verts[j] == thworld.edges[j].v[1]) break; } //end for if (j < 3) continue; //if this edge intersects with the triangle if (TH_IntersectTrianglePlanes(thworld.edges[i].v[0], thworld.edges[i].v[1], &triplane, planes)) return false; } //end for return true; } //end of the function TH_TryTriangle //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_AddTriangleToList(th_triangle_t **trianglelist, th_triangle_t *tri) { tri->prev = NULL; tri->next = *trianglelist; if (*trianglelist) (*trianglelist)->prev = tri; *trianglelist = tri; } //end of the function TH_AddTriangleToList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_RemoveTriangleFromList(th_triangle_t **trianglelist, th_triangle_t *tri) { if (tri->next) tri->next->prev = tri->prev; if (tri->prev) tri->prev->next = tri->next; else *trianglelist = tri->next; } //end of the function TH_RemoveTriangleFromList //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_FindTetrahedron1(th_triangle_t *tri, int *triangles) { int i, j, edgenum, side, v1, v2, v3, v4; int verts1[3], verts2[3]; th_triangle_t *tri2; //find another triangle with a shared edge for (tri2 = tri->next; tri2; tri2 = tri2->next) { //if the triangles are in the same plane if ((tri->planenum & ~1) == (tri2->planenum & ~1)) continue; //try to find a shared edge for (i = 0; i < 3; i++) { edgenum = abs(tri->edges[i]); for (j = 0; j < 3; j++) { if (edgenum == abs(tri2->edges[j])) break; } //end for if (j < 3) break; } //end for //if the triangles have a shared edge if (i < 3) { edgenum = tri->edges[(i+1)%3]; if (edgenum < 0) v1 = thworld.edges[abs(edgenum)].v[0]; else v1 = thworld.edges[edgenum].v[1]; edgenum = tri2->edges[(j+1)%3]; if (edgenum < 0) v2 = thworld.edges[abs(edgenum)].v[0]; else v2 = thworld.edges[edgenum].v[1]; //try the new edge if (TH_TryEdge(v1, v2)) { edgenum = tri->edges[i]; side = edgenum < 0; //get the vertexes of the shared edge v3 = thworld.edges[abs(edgenum)].v[side]; v4 = thworld.edges[abs(edgenum)].v[!side]; //try the two new triangles verts1[0] = v1; verts1[1] = v2; verts1[2] = v3; triangles[2] = TH_FindTriangle(verts1); if (triangles[2] || TH_TryTriangle(verts1)) { verts2[0] = v2; verts2[1] = v1; verts2[2] = v4; triangles[3] = TH_FindTriangle(verts2); if (triangles[3] || TH_TryTriangle(verts2)) { triangles[0] = tri - thworld.triangles; triangles[1] = tri2 - thworld.triangles; if (!triangles[2]) triangles[2] = TH_CreateTriangle(verts1); if (!triangles[3]) triangles[3] = TH_CreateTriangle(verts2); return true; } //end if } //end if } //end if } //end if } //end for return false; } //end of the function TH_FindTetrahedron //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_FindTetrahedron2(th_triangle_t *tri, int *triangles) { int i, edgenum, v1, verts[3], triverts[3]; float d; th_plane_t *plane; //get the verts of this triangle for (i = 0; i < 3; i++) { edgenum = tri->edges[i]; if (edgenum < 0) verts[i] = thworld.edges[abs(edgenum)].v[1]; else verts[i] = thworld.edges[edgenum].v[0]; } //end for // plane = &thworld.planes[tri->planenum]; for (v1 = 0; v1 < thworld.numvertexes; v1++) { //if the vertex is only used by triangles with tetrahedrons at both sides if (!thworld.vertexes[v1].usercount) continue; //check if the vertex is not coplanar with the triangle d = DotProduct(thworld.vertexes[v1].v, plane->normal) - plane->dist; if (fabs(d) < 1) continue; //check if we can create edges from the triangle towards this new vertex for (i = 0; i < 3; i++) { if (v1 == verts[i]) break; if (!TH_TryEdge(v1, verts[i])) break; } //end for if (i < 3) continue; //check if the triangles are valid for (i = 0; i < 3; i++) { triverts[0] = v1; triverts[1] = verts[i]; triverts[2] = verts[(i+1)%3]; //if the triangle already exists then it is valid triangles[i] = TH_FindTriangle(triverts); if (!triangles[i]) { if (!TH_TryTriangle(triverts)) break; } //end if } //end for if (i < 3) continue; //create the tetrahedron triangles using the new vertex for (i = 0; i < 3; i++) { if (!triangles[i]) { triverts[0] = v1; triverts[1] = verts[i]; triverts[2] = verts[(i+1)%3]; triangles[i] = TH_CreateTriangle(triverts); } //end if } //end for //add the existing triangle triangles[3] = tri - thworld.triangles; // return true; } //end for return false; } //end of the function TH_FindTetrahedron2 //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_TetrahedralDecomposition(th_triangle_t *triangles) { int i, thtriangles[4], numtriangles; th_triangle_t *donetriangles, *tri; donetriangles = NULL; /* numtriangles = 0; qprintf("%6d triangles", numtriangles); for (tri = triangles; tri; tri = triangles) { qprintf("\r%6d", numtriangles++); if (!TH_FindTetrahedron1(tri, thtriangles)) { // if (!TH_FindTetrahedron2(tri, thtriangles)) { // Error("triangle without tetrahedron"); TH_RemoveTriangleFromList(&triangles, tri); continue; } //end if } //end if //create a tetrahedron from the triangles TH_CreateTetrahedron(thtriangles); // for (i = 0; i < 4; i++) { if (thworld.triangles[abs(thtriangles[i])].front && thworld.triangles[abs(thtriangles[i])].back) { TH_RemoveTriangleFromList(&triangles, &thworld.triangles[abs(thtriangles[i])]); TH_AddTriangleToList(&donetriangles, &thworld.triangles[abs(thtriangles[i])]); TH_FreeTriangleEdges(&thworld.triangles[abs(thtriangles[i])]); } //end if else { TH_AddTriangleToList(&triangles, &thworld.triangles[abs(thtriangles[i])]); } //end else } //end for } //end for*/ qprintf("%6d tetrahedrons", thworld.numtetrahedrons); do { do { numtriangles = 0; for (i = 1; i < thworld.numtriangles; i++) { tri = &thworld.triangles[i]; if (tri->front && tri->back) continue; //qprintf("\r%6d", numtriangles++); if (!TH_FindTetrahedron1(tri, thtriangles)) { // if (!TH_FindTetrahedron2(tri, thtriangles)) { continue; } //end if } //end if numtriangles++; //create a tetrahedron from the triangles TH_CreateTetrahedron(thtriangles); qprintf("\r%6d", thworld.numtetrahedrons); } //end for } while(numtriangles); for (i = 1; i < thworld.numtriangles; i++) { tri = &thworld.triangles[i]; if (tri->front && tri->back) continue; //qprintf("\r%6d", numtriangles++); // if (!TH_FindTetrahedron1(tri, thtriangles)) { if (!TH_FindTetrahedron2(tri, thtriangles)) { continue; } //end if } //end if numtriangles++; //create a tetrahedron from the triangles TH_CreateTetrahedron(thtriangles); qprintf("\r%6d", thworld.numtetrahedrons); } //end for } while(numtriangles); // numtriangles = 0; for (i = 1; i < thworld.numtriangles; i++) { tri = &thworld.triangles[i]; if (!tri->front && !tri->back) numtriangles++; } //end for Log_Print("\r%6d triangles with front only\n", numtriangles); Log_Print("\r%6d tetrahedrons\n", thworld.numtetrahedrons-1); } //end of the function TH_TetrahedralDecomposition //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_AASFaceVertex(aas_face_t *face, int index, vec3_t vertex) { int edgenum, side; edgenum = aasworld.edgeindex[face->firstedge + index]; side = edgenum < 0; VectorCopy(aasworld.vertexes[aasworld.edges[abs(edgenum)].v[side]], vertex); } //end of the function TH_AASFaceVertex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TH_Colinear(float *v0, float *v1, float *v2) { vec3_t t1, t2, vcross; float d; VectorSubtract(v1, v0, t1); VectorSubtract(v2, v0, t2); CrossProduct (t1, t2, vcross); d = VectorLength( vcross ); // if cross product is zero point is colinear if (d < 10) { return true; } //end if return false; } //end of the function TH_Colinear //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_FaceCenter(aas_face_t *face, vec3_t center) { int i, edgenum, side; aas_edge_t *edge; VectorClear(center); for (i = 0; i < face->numedges; i++) { edgenum = abs(aasworld.edgeindex[face->firstedge + i]); side = edgenum < 0; edge = &aasworld.edges[abs(edgenum)]; VectorAdd(aasworld.vertexes[edge->v[side]], center, center); } //end for VectorScale(center, 1.0 / face->numedges, center); } //end of the function TH_FaceCenter //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== th_triangle_t *TH_CreateAASFaceTriangles(aas_face_t *face) { int i, first, verts[3], trinum; vec3_t p0, p1, p2, p3, p4, center; th_triangle_t *tri, *triangles; triangles = NULL; //find three points that are not colinear for (i = 0; i < face->numedges; i++) { TH_AASFaceVertex(face, (face->numedges + i-2)%face->numedges, p0); TH_AASFaceVertex(face, (face->numedges + i-1)%face->numedges, p1); TH_AASFaceVertex(face, (i )%face->numedges, p2); if (TH_Colinear(p2, p0, p1)) continue; TH_AASFaceVertex(face, (i+1)%face->numedges, p3); TH_AASFaceVertex(face, (i+2)%face->numedges, p4); if (TH_Colinear(p2, p3, p4)) continue; break; } //end for //if there are three points that are not colinear if (i < face->numedges) { //normal triangulation first = i; //left and right most point of three non-colinear points TH_AASFaceVertex(face, first, p0); verts[0] = TH_FindOrCreateVertex(p0); for (i = 1; i < face->numedges-1; i++) { TH_AASFaceVertex(face, (first+i )%face->numedges, p1); TH_AASFaceVertex(face, (first+i+1)%face->numedges, p2); verts[1] = TH_FindOrCreateVertex(p1); verts[2] = TH_FindOrCreateVertex(p2); trinum = TH_CreateTriangle(verts); tri = &thworld.triangles[trinum]; tri->front = -1; TH_AddTriangleToList(&triangles, tri); } //end for } //end if else { //fan triangulation TH_FaceCenter(face, center); // verts[0] = TH_FindOrCreateVertex(center); for (i = 0; i < face->numedges; i++) { TH_AASFaceVertex(face, (i )%face->numedges, p1); TH_AASFaceVertex(face, (i+1)%face->numedges, p2); if (TH_Colinear(center, p1, p2)) continue; verts[1] = TH_FindOrCreateVertex(p1); verts[2] = TH_FindOrCreateVertex(p2); trinum = TH_CreateTriangle(verts); tri = &thworld.triangles[trinum]; tri->front = -1; TH_AddTriangleToList(&triangles, tri); } //end for } //end else return triangles; } //end of the function TH_CreateAASFaceTriangles //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== th_triangle_t *TH_AASToTriangleMesh(void) { int i, j, facenum, otherareanum; aas_face_t *face; th_triangle_t *tri, *nexttri, *triangles; triangles = NULL; for (i = 1; i < aasworld.numareas; i++) { //if (!(aasworld.areasettings[i].presencetype & PRESENCE_NORMAL)) continue; for (j = 0; j < aasworld.areas[i].numfaces; j++) { facenum = abs(aasworld.faceindex[aasworld.areas[i].firstface + j]); face = &aasworld.faces[facenum]; //only convert solid faces into triangles if (!(face->faceflags & FACE_SOLID)) { /* if (face->frontarea == i) otherareanum = face->backarea; else otherareanum = face->frontarea; if (aasworld.areasettings[otherareanum].presencetype & PRESENCE_NORMAL) continue; */ continue; } //end if // tri = TH_CreateAASFaceTriangles(face); for (; tri; tri = nexttri) { nexttri = tri->next; TH_AddTriangleToList(&triangles, tri); } //end for } //end if } //end for return triangles; } //end of the function TH_AASToTriangleMesh //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void TH_AASToTetrahedrons(char *filename) { th_triangle_t *triangles, *tri, *lasttri; int cnt; if (!AAS_LoadAASFile(filename, 0, 0)) Error("couldn't load %s\n", filename); // TH_InitMaxTH(); //create a triangle mesh from the solid faces in the AAS file triangles = TH_AASToTriangleMesh(); // cnt = 0; lasttri = NULL; for (tri = triangles; tri; tri = tri->next) { cnt++; if (tri->prev != lasttri) Log_Print("BAH\n"); lasttri = tri; } //end for Log_Print("%6d triangles\n", cnt); //create a tetrahedral decomposition of the world bounded by triangles TH_TetrahedralDecomposition(triangles); // TH_FreeMaxTH(); } //end of the function TH_AASToTetrahedrons ================================================ FILE: code/bspc/tetrahedron.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ void TH_AASToTetrahedrons(char *filename); ================================================ FILE: code/bspc/textures.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" #include "l_bsp_q2.h" int nummiptex; textureref_t textureref[MAX_MAP_TEXTURES]; //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int FindMiptex (char *name) { int i; char path[1024]; miptex_t *mt; for (i = 0; i < nummiptex; i++) { if (!strcmp (name, textureref[i].name)) { return i; } //end if } //end for if (nummiptex == MAX_MAP_TEXTURES) Error ("MAX_MAP_TEXTURES"); strcpy (textureref[i].name, name); // load the miptex to get the flags and values sprintf (path, "%stextures/%s.wal", gamedir, name); if (TryLoadFile (path, (void **)&mt) != -1) { textureref[i].value = LittleLong (mt->value); textureref[i].flags = LittleLong (mt->flags); textureref[i].contents = LittleLong (mt->contents); strcpy (textureref[i].animname, mt->animname); FreeMemory(mt); } //end if nummiptex++; if (textureref[i].animname[0]) FindMiptex (textureref[i].animname); return i; } //end of the function FindMipTex //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== vec3_t baseaxis[18] = { {0,0,1}, {1,0,0}, {0,-1,0}, // floor {0,0,-1}, {1,0,0}, {0,-1,0}, // ceiling {1,0,0}, {0,1,0}, {0,0,-1}, // west wall {-1,0,0}, {0,1,0}, {0,0,-1}, // east wall {0,1,0}, {1,0,0}, {0,0,-1}, // south wall {0,-1,0}, {1,0,0}, {0,0,-1} // north wall }; void TextureAxisFromPlane(plane_t *pln, vec3_t xv, vec3_t yv) { int bestaxis; vec_t dot,best; int i; best = 0; bestaxis = 0; for (i=0 ; i<6 ; i++) { dot = DotProduct (pln->normal, baseaxis[i*3]); if (dot > best) { best = dot; bestaxis = i; } } VectorCopy (baseaxis[bestaxis*3+1], xv); VectorCopy (baseaxis[bestaxis*3+2], yv); } //end of the function TextureAxisFromPlane //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== int TexinfoForBrushTexture(plane_t *plane, brush_texture_t *bt, vec3_t origin) { vec3_t vecs[2]; int sv, tv; vec_t ang, sinv, cosv; vec_t ns, nt; texinfo_t tx, *tc; int i, j, k; float shift[2]; brush_texture_t anim; int mt; if (!bt->name[0]) return 0; memset (&tx, 0, sizeof(tx)); strcpy (tx.texture, bt->name); TextureAxisFromPlane(plane, vecs[0], vecs[1]); shift[0] = DotProduct (origin, vecs[0]); shift[1] = DotProduct (origin, vecs[1]); if (!bt->scale[0]) bt->scale[0] = 1; if (!bt->scale[1]) bt->scale[1] = 1; // rotate axis if (bt->rotate == 0) { sinv = 0 ; cosv = 1; } else if (bt->rotate == 90) { sinv = 1 ; cosv = 0; } else if (bt->rotate == 180) { sinv = 0 ; cosv = -1; } else if (bt->rotate == 270) { sinv = -1 ; cosv = 0; } else { ang = bt->rotate / 180 * Q_PI; sinv = sin(ang); cosv = cos(ang); } if (vecs[0][0]) sv = 0; else if (vecs[0][1]) sv = 1; else sv = 2; if (vecs[1][0]) tv = 0; else if (vecs[1][1]) tv = 1; else tv = 2; for (i=0 ; i<2 ; i++) { ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; vecs[i][sv] = ns; vecs[i][tv] = nt; } for (i=0 ; i<2 ; i++) for (j=0 ; j<3 ; j++) tx.vecs[i][j] = vecs[i][j] / bt->scale[i]; tx.vecs[0][3] = bt->shift[0] + shift[0]; tx.vecs[1][3] = bt->shift[1] + shift[1]; tx.flags = bt->flags; tx.value = bt->value; // // find the texinfo // tc = texinfo; for (i=0 ; iflags != tx.flags) continue; if (tc->value != tx.value) continue; for (j=0 ; j<2 ; j++) { if (strcmp (tc->texture, tx.texture)) goto skip; for (k=0 ; k<4 ; k++) { if (tc->vecs[j][k] != tx.vecs[j][k]) goto skip; } } return i; skip:; } *tc = tx; numtexinfo++; // load the next animation mt = FindMiptex (bt->name); if (textureref[mt].animname[0]) { anim = *bt; strcpy (anim.name, textureref[mt].animname); tc->nexttexinfo = TexinfoForBrushTexture (plane, &anim, origin); } else tc->nexttexinfo = -1; return i; } //end of the function TexinfoForBrushTexture ================================================ FILE: code/bspc/tree.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" extern int c_nodes; int c_pruned; int freedtreemem = 0; void RemovePortalFromNode (portal_t *portal, node_t *l); //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== node_t *NodeForPoint (node_t *node, vec3_t origin) { plane_t *plane; vec_t d; while (node->planenum != PLANENUM_LEAF) { plane = &mapplanes[node->planenum]; d = DotProduct (origin, plane->normal) - plane->dist; if (d >= 0) node = node->children[0]; else node = node->children[1]; } return node; } //end of the function NodeForPoint //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Tree_FreePortals_r (node_t *node) { portal_t *p, *nextp; int s; // free children if (node->planenum != PLANENUM_LEAF) { Tree_FreePortals_r(node->children[0]); Tree_FreePortals_r(node->children[1]); } // free portals for (p = node->portals; p; p = nextp) { s = (p->nodes[1] == node); nextp = p->next[s]; RemovePortalFromNode (p, p->nodes[!s]); #ifdef ME if (p->winding) freedtreemem += MemorySize(p->winding); freedtreemem += MemorySize(p); #endif //ME FreePortal(p); } node->portals = NULL; } //end of the function Tree_FreePortals_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Tree_Free_r (node_t *node) { // face_t *f, *nextf; bspbrush_t *brush, *nextbrush; //free children if (node->planenum != PLANENUM_LEAF) { Tree_Free_r (node->children[0]); Tree_Free_r (node->children[1]); } //end if //free bspbrushes // FreeBrushList (node->brushlist); for (brush = node->brushlist; brush; brush = nextbrush) { nextbrush = brush->next; #ifdef ME freedtreemem += MemorySize(brush); #endif //ME FreeBrush(brush); } //end for node->brushlist = NULL; /* NOTE: only used when creating Q2 bsp // free faces for (f = node->faces; f; f = nextf) { nextf = f->next; #ifdef ME if (f->w) freedtreemem += MemorySize(f->w); freedtreemem += sizeof(face_t); #endif //ME FreeFace(f); } //end for */ // free the node if (node->volume) { #ifdef ME freedtreemem += MemorySize(node->volume); #endif //ME FreeBrush (node->volume); } //end if if (numthreads == 1) c_nodes--; #ifdef ME freedtreemem += MemorySize(node); #endif //ME FreeMemory(node); } //end of the function Tree_Free_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Tree_Free(tree_t *tree) { //if no tree just return if (!tree) return; // freedtreemem = 0; // Tree_FreePortals_r(tree->headnode); Tree_Free_r(tree->headnode); #ifdef ME freedtreemem += MemorySize(tree); #endif //ME FreeMemory(tree); #ifdef ME Log_Print("freed "); PrintMemorySize(freedtreemem); Log_Print(" of tree memory\n"); #endif //ME } //end of the function Tree_Free //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== tree_t *Tree_Alloc(void) { tree_t *tree; tree = GetMemory(sizeof(*tree)); memset (tree, 0, sizeof(*tree)); ClearBounds (tree->mins, tree->maxs); return tree; } //end of the function Tree_Alloc //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Tree_Print_r (node_t *node, int depth) { int i; plane_t *plane; bspbrush_t *bb; for (i=0 ; iplanenum == PLANENUM_LEAF) { if (!node->brushlist) printf ("NULL\n"); else { for (bb=node->brushlist ; bb ; bb=bb->next) printf ("%i ", bb->original->brushnum); printf ("\n"); } return; } plane = &mapplanes[node->planenum]; printf ("#%i (%5.2f %5.2f %5.2f):%5.2f\n", node->planenum, plane->normal[0], plane->normal[1], plane->normal[2], plane->dist); Tree_Print_r (node->children[0], depth+1); Tree_Print_r (node->children[1], depth+1); } //end of the function Tree_Print_r //=========================================================================== // NODES THAT DON'T SEPERATE DIFFERENT CONTENTS CAN BE PRUNED // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Tree_PruneNodes_r (node_t *node) { bspbrush_t *b, *next; if (node->planenum == PLANENUM_LEAF) return; Tree_PruneNodes_r (node->children[0]); Tree_PruneNodes_r (node->children[1]); if (create_aas) { if ((node->children[0]->contents & CONTENTS_LADDER) || (node->children[1]->contents & CONTENTS_LADDER)) return; } if ((node->children[0]->contents & CONTENTS_SOLID) && (node->children[1]->contents & CONTENTS_SOLID)) { if (node->faces) Error ("node->faces seperating CONTENTS_SOLID"); if (node->children[0]->faces || node->children[1]->faces) Error ("!node->faces with children"); // FIXME: free stuff node->planenum = PLANENUM_LEAF; node->contents = CONTENTS_SOLID; node->detail_seperator = false; if (node->brushlist) Error ("PruneNodes: node->brushlist"); // combine brush lists node->brushlist = node->children[1]->brushlist; for (b = node->children[0]->brushlist; b; b = next) { next = b->next; b->next = node->brushlist; node->brushlist = b; } //end for //free the child nodes FreeMemory(node->children[0]); FreeMemory(node->children[1]); //two nodes are cut away c_pruned += 2; } //end if } //end of the function Tree_PruneNodes_r //=========================================================================== // // Parameter: - // Returns: - // Changes Globals: - //=========================================================================== void Tree_PruneNodes(node_t *node) { Log_Print("------- Prune Nodes --------\n"); c_pruned = 0; Tree_PruneNodes_r(node); Log_Print("%5i pruned nodes\n", c_pruned); } //end of the function Tree_PruneNodes ================================================ FILE: code/bspc/writebsp.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "qbsp.h" int c_nofaces; int c_facenodes; /* ========================================================= ONLY SAVE OUT PLANES THAT ARE ACTUALLY USED AS NODES ========================================================= */ int planeused[MAX_MAP_PLANES]; /* ============ EmitPlanes There is no oportunity to discard planes, because all of the original brushes will be saved in the map. ============ */ void EmitPlanes (void) { int i; dplane_t *dp; plane_t *mp; //ME: this causes a crash?? // int planetranslate[MAX_MAP_PLANES]; mp = mapplanes; for (i=0 ; inormal, dp->normal); dp->dist = mp->dist; dp->type = mp->type; numplanes++; if (numplanes >= MAX_MAP_PLANES) Error("MAX_MAP_PLANES"); } } //======================================================== void EmitMarkFace (dleaf_t *leaf_p, face_t *f) { int i; int facenum; while (f->merged) f = f->merged; if (f->split[0]) { EmitMarkFace (leaf_p, f->split[0]); EmitMarkFace (leaf_p, f->split[1]); return; } facenum = f->outputnumber; if (facenum == -1) return; // degenerate face if (facenum < 0 || facenum >= numfaces) Error ("Bad leafface"); for (i=leaf_p->firstleafface ; i= MAX_MAP_LEAFFACES) Error ("MAX_MAP_LEAFFACES"); dleaffaces[numleaffaces] = facenum; numleaffaces++; } } /* ================== EmitLeaf ================== */ void EmitLeaf (node_t *node) { dleaf_t *leaf_p; portal_t *p; int s; face_t *f; bspbrush_t *b; int i; int brushnum; // emit a leaf if (numleafs >= MAX_MAP_LEAFS) Error ("MAX_MAP_LEAFS"); leaf_p = &dleafs[numleafs]; numleafs++; leaf_p->contents = node->contents; leaf_p->cluster = node->cluster; leaf_p->area = node->area; // // write bounding box info // VectorCopy (node->mins, leaf_p->mins); VectorCopy (node->maxs, leaf_p->maxs); // // write the leafbrushes // leaf_p->firstleafbrush = numleafbrushes; for (b=node->brushlist ; b ; b=b->next) { if (numleafbrushes >= MAX_MAP_LEAFBRUSHES) Error ("MAX_MAP_LEAFBRUSHES"); brushnum = b->original - mapbrushes; for (i=leaf_p->firstleafbrush ; inumleafbrushes = numleafbrushes - leaf_p->firstleafbrush; // // write the leaffaces // if (leaf_p->contents & CONTENTS_SOLID) return; // no leaffaces in solids leaf_p->firstleafface = numleaffaces; for (p = node->portals ; p ; p = p->next[s]) { s = (p->nodes[1] == node); f = p->face[s]; if (!f) continue; // not a visible portal EmitMarkFace (leaf_p, f); } leaf_p->numleaffaces = numleaffaces - leaf_p->firstleafface; } /* ================== EmitFace ================== */ void EmitFace (face_t *f) { dface_t *df; int i; int e; f->outputnumber = -1; if (f->numpoints < 3) { return; // degenerated } if (f->merged || f->split[0] || f->split[1]) { return; // not a final face } // save output number so leaffaces can use f->outputnumber = numfaces; if (numfaces >= MAX_MAP_FACES) Error ("numfaces == MAX_MAP_FACES"); df = &dfaces[numfaces]; numfaces++; // planenum is used by qlight, but not quake df->planenum = f->planenum & (~1); df->side = f->planenum & 1; df->firstedge = numsurfedges; df->numedges = f->numpoints; df->texinfo = f->texinfo; for (i=0 ; inumpoints ; i++) { // e = GetEdge (f->pts[i], f->pts[(i+1)%f->numpoints], f); e = GetEdge2 (f->vertexnums[i], f->vertexnums[(i+1)%f->numpoints], f); if (numsurfedges >= MAX_MAP_SURFEDGES) Error ("numsurfedges == MAX_MAP_SURFEDGES"); dsurfedges[numsurfedges] = e; numsurfedges++; } } /* ============ EmitDrawingNode_r ============ */ int EmitDrawNode_r (node_t *node) { dnode_t *n; face_t *f; int i; if (node->planenum == PLANENUM_LEAF) { EmitLeaf (node); return -numleafs; } // emit a node if (numnodes == MAX_MAP_NODES) Error ("MAX_MAP_NODES"); n = &dnodes[numnodes]; numnodes++; VectorCopy (node->mins, n->mins); VectorCopy (node->maxs, n->maxs); planeused[node->planenum]++; planeused[node->planenum^1]++; if (node->planenum & 1) Error ("WriteDrawNodes_r: odd planenum"); n->planenum = node->planenum; n->firstface = numfaces; if (!node->faces) c_nofaces++; else c_facenodes++; for (f=node->faces ; f ; f=f->next) EmitFace (f); n->numfaces = numfaces - n->firstface; // // recursively output the other nodes // for (i=0 ; i<2 ; i++) { if (node->children[i]->planenum == PLANENUM_LEAF) { n->children[i] = -(numleafs + 1); EmitLeaf (node->children[i]); } else { n->children[i] = numnodes; EmitDrawNode_r (node->children[i]); } } return n - dnodes; } //========================================================= /* ============ WriteBSP ============ */ void WriteBSP (node_t *headnode) { int oldfaces; c_nofaces = 0; c_facenodes = 0; qprintf ("--- WriteBSP ---\n"); oldfaces = numfaces; dmodels[nummodels].headnode = EmitDrawNode_r (headnode); EmitAreaPortals (headnode); qprintf ("%5i nodes with faces\n", c_facenodes); qprintf ("%5i nodes without faces\n", c_nofaces); qprintf ("%5i faces\n", numfaces-oldfaces); } //=========================================================== /* ============ SetModelNumbers ============ */ void SetModelNumbers (void) { int i; int models; char value[10]; models = 1; for (i=1 ; icontents = b->contents; db->firstside = numbrushsides; db->numsides = b->numsides; for (j=0 ; jnumsides ; j++) { if (numbrushsides == MAX_MAP_BRUSHSIDES) Error ("MAX_MAP_BRUSHSIDES"); cp = &dbrushsides[numbrushsides]; numbrushsides++; cp->planenum = b->original_sides[j].planenum; cp->texinfo = b->original_sides[j].texinfo; } #ifdef ME //for collision detection, bounding boxes are axial :) //brushes are convex so just add dot or line touching planes on the sides of //the brush parallell to the axis planes #endif // add any axis planes not contained in the brush to bevel off corners for (x=0 ; x<3 ; x++) for (s=-1 ; s<=1 ; s+=2) { // add the plane VectorCopy (vec3_origin, normal); normal[x] = s; if (s == -1) dist = -b->mins[x]; else dist = b->maxs[x]; planenum = FindFloatPlane (normal, dist); for (i=0 ; inumsides ; i++) if (b->original_sides[i].planenum == planenum) break; if (i == b->numsides) { if (numbrushsides >= MAX_MAP_BRUSHSIDES) Error ("MAX_MAP_BRUSHSIDES"); dbrushsides[numbrushsides].planenum = planenum; dbrushsides[numbrushsides].texinfo = dbrushsides[numbrushsides-1].texinfo; numbrushsides++; db->numsides++; } } } } //=========================================================== /* ================== BeginBSPFile ================== */ void BeginBSPFile (void) { // these values may actually be initialized // if the file existed when loaded, so clear them explicitly nummodels = 0; numfaces = 0; numnodes = 0; numbrushsides = 0; numvertexes = 0; numleaffaces = 0; numleafbrushes = 0; numsurfedges = 0; // edge 0 is not used, because 0 can't be negated numedges = 1; // leave vertex 0 as an error numvertexes = 1; // leave leaf 0 as an error numleafs = 1; dleafs[0].contents = CONTENTS_SOLID; } /* ============ EndBSPFile ============ */ void EndBSPFile (void) { #if 0 char path[1024]; int len; byte *buf; #endif EmitBrushes (); EmitPlanes (); Q2_UnparseEntities (); // load the pop #if 0 sprintf (path, "%s/pics/pop.lmp", gamedir); len = LoadFile (path, &buf); memcpy (dpop, buf, sizeof(dpop)); FreeMemory(buf); #endif } /* ================== BeginModel ================== */ int firstmodleaf; extern int firstmodeledge; extern int firstmodelface; void BeginModel (void) { dmodel_t *mod; int start, end; mapbrush_t *b; int j; entity_t *e; vec3_t mins, maxs; if (nummodels == MAX_MAP_MODELS) Error ("MAX_MAP_MODELS"); mod = &dmodels[nummodels]; mod->firstface = numfaces; firstmodleaf = numleafs; firstmodeledge = numedges; firstmodelface = numfaces; // // bound the brushes // e = &entities[entity_num]; start = e->firstbrush; end = start + e->numbrushes; ClearBounds (mins, maxs); for (j=start ; jnumsides) continue; // not a real brush (origin brush) AddPointToBounds (b->mins, mins, maxs); AddPointToBounds (b->maxs, mins, maxs); } VectorCopy (mins, mod->mins); VectorCopy (maxs, mod->maxs); } /* ================== EndModel ================== */ void EndModel (void) { dmodel_t *mod; mod = &dmodels[nummodels]; mod->numfaces = numfaces - mod->firstface; nummodels++; } ================================================ FILE: code/cgame/Conscript ================================================ # cgame building # builds the cgame for vanilla Q3 and TA # there are slight differences between Q3 and TA build: # -DMISSIONPACK # TA has cg_newdraw.c and ../ui/ui_shared.c # the config is passed in the imported variable TARGET_DIR # qvm building against native: # only native has cg_syscalls.c # only qvm has ../game/bg_lib.c # qvm uses a custom cg_syscalls.asm with equ stubs Import qw( BASE_CFLAGS TARGET_DIR INSTALL_DIR NO_VM NO_SO CC CXX LINK ); $env = new cons( # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix CPPPATH => '#cgame:#game:#q3_ui', CC => $CC, CXX => $CXX, LINK => $LINK, ENV => { PATH => $ENV{PATH}, HOME => $ENV{HOME} }, CFLAGS => $BASE_CFLAGS . '-fPIC', LDFLAGS => '-shared -ldl -lm' ); # for TA, use -DMISSIONPACK %ta_env_hash = $env->copy( CPPPATH => '#cgame:#game:#ui' ); $ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $ta_env_hash{CFLAGS}; $ta_env = new cons(%ta_env_hash); # qvm building # we heavily customize the cons environment $vm_env = new cons( # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix CPPPATH => '#cgame:#game:#q3_ui', CC => 'q3lcc', CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', SUFOBJ => '.asm', LINK => 'q3asm', CFLAGS => '-DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g', # need to know where to find the compiler tools ENV => { PATH => $ENV{PATH} . ":./qvmtools", }, ); # TA qvm building %vm_ta_env_hash = $vm_env->copy( CPPPATH => '#cgame:#game:#ui' ); $vm_ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $vm_ta_env_hash{CFLAGS}; $vm_ta_env = new cons(%vm_ta_env_hash); # the file with vmMain function MUST be the first one of the list @FILES = qw( cg_main.c ../game/bg_misc.c ../game/bg_pmove.c ../game/bg_slidemove.c ../game/q_math.c ../game/q_shared.c cg_consolecmds.c cg_draw.c cg_drawtools.c cg_effects.c cg_ents.c cg_event.c cg_info.c cg_localents.c cg_marks.c cg_players.c cg_playerstate.c cg_predict.c cg_scoreboard.c cg_servercmds.c cg_snapshot.c cg_view.c cg_weapons.c ); $FILESREF = \@FILES; # only in .so # (VM uses a custom .asm with equ stubs) @SO_FILES = qw( cg_syscalls.c ); $SO_FILESREF = \@SO_FILES; # only for VM @VM_FILES = qw( ../game/bg_lib.c cg_syscalls.asm ); $VM_FILESREF = \@VM_FILES; # common additionals for TA @TA_FILES = qw( cg_newdraw.c ../ui/ui_shared.c ); $TA_FILESREF = \@TA_FILES; # FIXME CPU string if ($TARGET_DIR eq 'Q3') { if ($NO_SO eq 0) { Program $env 'cgamei386.so', @$FILESREF, @$SO_FILESREF; Install $env $INSTALL_DIR, 'cgamei386.so'; } if ($NO_VM eq 0) { Depends $vm_env 'cgame.qvm', '#qvmtools/q3lcc'; Depends $vm_env 'cgame.qvm', '#qvmtools/q3asm'; Program $vm_env 'cgame.qvm', @$FILESREF, @$VM_FILESREF; Install $vm_env $INSTALL_DIR . '/vm', 'cgame.qvm'; } } else { if ($NO_SO eq 0) { Program $ta_env 'cgamei386.so', @$FILESREF, @$SO_FILESREF, @$TA_FILESREF; Install $ta_env $INSTALL_DIR, 'cgamei386.so'; } if ($NO_VM eq 0) { Depends $vm_env 'cgame.qvm', '#qvmtools/q3lcc'; Depends $vm_env 'cgame.qvm', '#qvmtools/q3asm'; Program $vm_ta_env 'cgame.qvm', @$FILESREF, @$VM_FILESREF, @$TA_FILESREF; Install $vm_ta_env $INSTALL_DIR . '/vm', 'cgame.qvm'; } } ================================================ FILE: code/cgame/cg_consolecmds.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_consolecmds.c -- text commands typed in at the local console, or // executed by a key binding #include "cg_local.h" #include "../ui/ui_shared.h" #ifdef MISSIONPACK extern menuDef_t *menuScoreboard; #endif void CG_TargetCommand_f( void ) { int targetNum; char test[4]; targetNum = CG_CrosshairPlayer(); if (!targetNum ) { return; } trap_Argv( 1, test, 4 ); trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) ); } /* ================= CG_SizeUp_f Keybinding command ================= */ static void CG_SizeUp_f (void) { trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer+10))); } /* ================= CG_SizeDown_f Keybinding command ================= */ static void CG_SizeDown_f (void) { trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer-10))); } /* ============= CG_Viewpos_f Debugging command to print the current position ============= */ static void CG_Viewpos_f (void) { CG_Printf ("(%i %i %i) : %i\n", (int)cg.refdef.vieworg[0], (int)cg.refdef.vieworg[1], (int)cg.refdef.vieworg[2], (int)cg.refdefViewAngles[YAW]); } static void CG_ScoresDown_f( void ) { #ifdef MISSIONPACK CG_BuildSpectatorString(); #endif if ( cg.scoresRequestTime + 2000 < cg.time ) { // the scores are more than two seconds out of data, // so request new ones cg.scoresRequestTime = cg.time; trap_SendClientCommand( "score" ); // leave the current scores up if they were already // displayed, but if this is the first hit, clear them out if ( !cg.showScores ) { cg.showScores = qtrue; cg.numScores = 0; } } else { // show the cached contents even if they just pressed if it // is within two seconds cg.showScores = qtrue; } } static void CG_ScoresUp_f( void ) { if ( cg.showScores ) { cg.showScores = qfalse; cg.scoreFadeTime = cg.time; } } #ifdef MISSIONPACK extern menuDef_t *menuScoreboard; void Menu_Reset(); // FIXME: add to right include file static void CG_LoadHud_f( void) { char buff[1024]; const char *hudSet; memset(buff, 0, sizeof(buff)); String_Init(); Menu_Reset(); trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); hudSet = buff; if (hudSet[0] == '\0') { hudSet = "ui/hud.txt"; } CG_LoadMenus(hudSet); menuScoreboard = NULL; } static void CG_scrollScoresDown_f( void) { if (menuScoreboard && cg.scoreBoardShowing) { Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qtrue); Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qtrue); Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qtrue); } } static void CG_scrollScoresUp_f( void) { if (menuScoreboard && cg.scoreBoardShowing) { Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qfalse); Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qfalse); Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qfalse); } } static void CG_spWin_f( void) { trap_Cvar_Set("cg_cameraOrbit", "2"); trap_Cvar_Set("cg_cameraOrbitDelay", "35"); trap_Cvar_Set("cg_thirdPerson", "1"); trap_Cvar_Set("cg_thirdPersonAngle", "0"); trap_Cvar_Set("cg_thirdPersonRange", "100"); CG_AddBufferedSound(cgs.media.winnerSound); //trap_S_StartLocalSound(cgs.media.winnerSound, CHAN_ANNOUNCER); CG_CenterPrint("YOU WIN!", SCREEN_HEIGHT * .30, 0); } static void CG_spLose_f( void) { trap_Cvar_Set("cg_cameraOrbit", "2"); trap_Cvar_Set("cg_cameraOrbitDelay", "35"); trap_Cvar_Set("cg_thirdPerson", "1"); trap_Cvar_Set("cg_thirdPersonAngle", "0"); trap_Cvar_Set("cg_thirdPersonRange", "100"); CG_AddBufferedSound(cgs.media.loserSound); //trap_S_StartLocalSound(cgs.media.loserSound, CHAN_ANNOUNCER); CG_CenterPrint("YOU LOSE...", SCREEN_HEIGHT * .30, 0); } #endif static void CG_TellTarget_f( void ) { int clientNum; char command[128]; char message[128]; clientNum = CG_CrosshairPlayer(); if ( clientNum == -1 ) { return; } trap_Args( message, 128 ); Com_sprintf( command, 128, "tell %i %s", clientNum, message ); trap_SendClientCommand( command ); } static void CG_TellAttacker_f( void ) { int clientNum; char command[128]; char message[128]; clientNum = CG_LastAttacker(); if ( clientNum == -1 ) { return; } trap_Args( message, 128 ); Com_sprintf( command, 128, "tell %i %s", clientNum, message ); trap_SendClientCommand( command ); } static void CG_VoiceTellTarget_f( void ) { int clientNum; char command[128]; char message[128]; clientNum = CG_CrosshairPlayer(); if ( clientNum == -1 ) { return; } trap_Args( message, 128 ); Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); trap_SendClientCommand( command ); } static void CG_VoiceTellAttacker_f( void ) { int clientNum; char command[128]; char message[128]; clientNum = CG_LastAttacker(); if ( clientNum == -1 ) { return; } trap_Args( message, 128 ); Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); trap_SendClientCommand( command ); } #ifdef MISSIONPACK static void CG_NextTeamMember_f( void ) { CG_SelectNextPlayer(); } static void CG_PrevTeamMember_f( void ) { CG_SelectPrevPlayer(); } // ASS U ME's enumeration order as far as task specific orders, OFFENSE is zero, CAMP is last // static void CG_NextOrder_f( void ) { clientInfo_t *ci = cgs.clientinfo + cg.snap->ps.clientNum; if (ci) { if (!ci->teamLeader && sortedTeamPlayers[cg_currentSelectedPlayer.integer] != cg.snap->ps.clientNum) { return; } } if (cgs.currentOrder < TEAMTASK_CAMP) { cgs.currentOrder++; if (cgs.currentOrder == TEAMTASK_RETRIEVE) { if (!CG_OtherTeamHasFlag()) { cgs.currentOrder++; } } if (cgs.currentOrder == TEAMTASK_ESCORT) { if (!CG_YourTeamHasFlag()) { cgs.currentOrder++; } } } else { cgs.currentOrder = TEAMTASK_OFFENSE; } cgs.orderPending = qtrue; cgs.orderTime = cg.time + 3000; } static void CG_ConfirmOrder_f (void ) { trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_YES)); trap_SendConsoleCommand("+button5; wait; -button5"); if (cg.time < cgs.acceptOrderTime) { trap_SendClientCommand(va("teamtask %d\n", cgs.acceptTask)); cgs.acceptOrderTime = 0; } } static void CG_DenyOrder_f (void ) { trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_NO)); trap_SendConsoleCommand("+button6; wait; -button6"); if (cg.time < cgs.acceptOrderTime) { cgs.acceptOrderTime = 0; } } static void CG_TaskOffense_f (void ) { if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONGETFLAG)); } else { trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONOFFENSE)); } trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_OFFENSE)); } static void CG_TaskDefense_f (void ) { trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONDEFENSE)); trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_DEFENSE)); } static void CG_TaskPatrol_f (void ) { trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONPATROL)); trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_PATROL)); } static void CG_TaskCamp_f (void ) { trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONCAMPING)); trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_CAMP)); } static void CG_TaskFollow_f (void ) { trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOW)); trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_FOLLOW)); } static void CG_TaskRetrieve_f (void ) { trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONRETURNFLAG)); trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_RETRIEVE)); } static void CG_TaskEscort_f (void ) { trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOWCARRIER)); trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_ESCORT)); } static void CG_TaskOwnFlag_f (void ) { trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_IHAVEFLAG)); } static void CG_TauntKillInsult_f (void ) { trap_SendConsoleCommand("cmd vsay kill_insult\n"); } static void CG_TauntPraise_f (void ) { trap_SendConsoleCommand("cmd vsay praise\n"); } static void CG_TauntTaunt_f (void ) { trap_SendConsoleCommand("cmd vtaunt\n"); } static void CG_TauntDeathInsult_f (void ) { trap_SendConsoleCommand("cmd vsay death_insult\n"); } static void CG_TauntGauntlet_f (void ) { trap_SendConsoleCommand("cmd vsay kill_guantlet\n"); } static void CG_TaskSuicide_f (void ) { int clientNum; char command[128]; clientNum = CG_CrosshairPlayer(); if ( clientNum == -1 ) { return; } Com_sprintf( command, 128, "tell %i suicide", clientNum ); trap_SendClientCommand( command ); } /* ================== CG_TeamMenu_f ================== */ /* static void CG_TeamMenu_f( void ) { if (trap_Key_GetCatcher() & KEYCATCH_CGAME) { CG_EventHandling(CGAME_EVENT_NONE); trap_Key_SetCatcher(0); } else { CG_EventHandling(CGAME_EVENT_TEAMMENU); //trap_Key_SetCatcher(KEYCATCH_CGAME); } } */ /* ================== CG_EditHud_f ================== */ /* static void CG_EditHud_f( void ) { //cls.keyCatchers ^= KEYCATCH_CGAME; //VM_Call (cgvm, CG_EVENT_HANDLING, (cls.keyCatchers & KEYCATCH_CGAME) ? CGAME_EVENT_EDITHUD : CGAME_EVENT_NONE); } */ #endif /* ================== CG_StartOrbit_f ================== */ static void CG_StartOrbit_f( void ) { char var[MAX_TOKEN_CHARS]; trap_Cvar_VariableStringBuffer( "developer", var, sizeof( var ) ); if ( !atoi(var) ) { return; } if (cg_cameraOrbit.value != 0) { trap_Cvar_Set ("cg_cameraOrbit", "0"); trap_Cvar_Set("cg_thirdPerson", "0"); } else { trap_Cvar_Set("cg_cameraOrbit", "5"); trap_Cvar_Set("cg_thirdPerson", "1"); trap_Cvar_Set("cg_thirdPersonAngle", "0"); trap_Cvar_Set("cg_thirdPersonRange", "100"); } } /* static void CG_Camera_f( void ) { char name[1024]; trap_Argv( 1, name, sizeof(name)); if (trap_loadCamera(name)) { cg.cameraMode = qtrue; trap_startCamera(cg.time); } else { CG_Printf ("Unable to load camera %s\n",name); } } */ typedef struct { char *cmd; void (*function)(void); } consoleCommand_t; static consoleCommand_t commands[] = { { "testgun", CG_TestGun_f }, { "testmodel", CG_TestModel_f }, { "nextframe", CG_TestModelNextFrame_f }, { "prevframe", CG_TestModelPrevFrame_f }, { "nextskin", CG_TestModelNextSkin_f }, { "prevskin", CG_TestModelPrevSkin_f }, { "viewpos", CG_Viewpos_f }, { "+scores", CG_ScoresDown_f }, { "-scores", CG_ScoresUp_f }, { "+zoom", CG_ZoomDown_f }, { "-zoom", CG_ZoomUp_f }, { "sizeup", CG_SizeUp_f }, { "sizedown", CG_SizeDown_f }, { "weapnext", CG_NextWeapon_f }, { "weapprev", CG_PrevWeapon_f }, { "weapon", CG_Weapon_f }, { "tell_target", CG_TellTarget_f }, { "tell_attacker", CG_TellAttacker_f }, { "vtell_target", CG_VoiceTellTarget_f }, { "vtell_attacker", CG_VoiceTellAttacker_f }, { "tcmd", CG_TargetCommand_f }, #ifdef MISSIONPACK { "loadhud", CG_LoadHud_f }, { "nextTeamMember", CG_NextTeamMember_f }, { "prevTeamMember", CG_PrevTeamMember_f }, { "nextOrder", CG_NextOrder_f }, { "confirmOrder", CG_ConfirmOrder_f }, { "denyOrder", CG_DenyOrder_f }, { "taskOffense", CG_TaskOffense_f }, { "taskDefense", CG_TaskDefense_f }, { "taskPatrol", CG_TaskPatrol_f }, { "taskCamp", CG_TaskCamp_f }, { "taskFollow", CG_TaskFollow_f }, { "taskRetrieve", CG_TaskRetrieve_f }, { "taskEscort", CG_TaskEscort_f }, { "taskSuicide", CG_TaskSuicide_f }, { "taskOwnFlag", CG_TaskOwnFlag_f }, { "tauntKillInsult", CG_TauntKillInsult_f }, { "tauntPraise", CG_TauntPraise_f }, { "tauntTaunt", CG_TauntTaunt_f }, { "tauntDeathInsult", CG_TauntDeathInsult_f }, { "tauntGauntlet", CG_TauntGauntlet_f }, { "spWin", CG_spWin_f }, { "spLose", CG_spLose_f }, { "scoresDown", CG_scrollScoresDown_f }, { "scoresUp", CG_scrollScoresUp_f }, #endif { "startOrbit", CG_StartOrbit_f }, //{ "camera", CG_Camera_f }, { "loaddeferred", CG_LoadDeferredPlayers } }; /* ================= CG_ConsoleCommand The string has been tokenized and can be retrieved with Cmd_Argc() / Cmd_Argv() ================= */ qboolean CG_ConsoleCommand( void ) { const char *cmd; int i; cmd = CG_Argv(0); for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { if ( !Q_stricmp( cmd, commands[i].cmd ) ) { commands[i].function(); return qtrue; } } return qfalse; } /* ================= CG_InitConsoleCommands Let the client system know about all of our commands so it can perform tab completion ================= */ void CG_InitConsoleCommands( void ) { int i; for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { trap_AddCommand( commands[i].cmd ); } // // the game server will interpret these commands, which will be automatically // forwarded to the server after they are not recognized locally // trap_AddCommand ("kill"); trap_AddCommand ("say"); trap_AddCommand ("say_team"); trap_AddCommand ("tell"); trap_AddCommand ("vsay"); trap_AddCommand ("vsay_team"); trap_AddCommand ("vtell"); trap_AddCommand ("vtaunt"); trap_AddCommand ("vosay"); trap_AddCommand ("vosay_team"); trap_AddCommand ("votell"); trap_AddCommand ("give"); trap_AddCommand ("god"); trap_AddCommand ("notarget"); trap_AddCommand ("noclip"); trap_AddCommand ("team"); trap_AddCommand ("follow"); trap_AddCommand ("levelshot"); trap_AddCommand ("addbot"); trap_AddCommand ("setviewpos"); trap_AddCommand ("callvote"); trap_AddCommand ("vote"); trap_AddCommand ("callteamvote"); trap_AddCommand ("teamvote"); trap_AddCommand ("stats"); trap_AddCommand ("teamtask"); trap_AddCommand ("loaddefered"); // spelled wrong, but not changing for demo } ================================================ FILE: code/cgame/cg_draw.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_draw.c -- draw all of the graphical elements during // active (after loading) gameplay #include "cg_local.h" #ifdef MISSIONPACK #include "../ui/ui_shared.h" // used for scoreboard extern displayContextDef_t cgDC; menuDef_t *menuScoreboard = NULL; #else int drawTeamOverlayModificationCount = -1; #endif int sortedTeamPlayers[TEAM_MAXOVERLAY]; int numSortedTeamPlayers; char systemChat[256]; char teamChat1[256]; char teamChat2[256]; #ifdef MISSIONPACK int CG_Text_Width(const char *text, float scale, int limit) { int count,len; float out; glyphInfo_t *glyph; float useScale; // FIXME: see ui_main.c, same problem // const unsigned char *s = text; const char *s = text; fontInfo_t *font = &cgDC.Assets.textFont; if (scale <= cg_smallFont.value) { font = &cgDC.Assets.smallFont; } else if (scale > cg_bigFont.value) { font = &cgDC.Assets.bigFont; } useScale = scale * font->glyphScale; out = 0; if (text) { len = strlen(text); if (limit > 0 && len > limit) { len = limit; } count = 0; while (s && *s && count < len) { if ( Q_IsColorString(s) ) { s += 2; continue; } else { glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build out += glyph->xSkip; s++; count++; } } } return out * useScale; } int CG_Text_Height(const char *text, float scale, int limit) { int len, count; float max; glyphInfo_t *glyph; float useScale; // TTimo: FIXME // const unsigned char *s = text; const char *s = text; fontInfo_t *font = &cgDC.Assets.textFont; if (scale <= cg_smallFont.value) { font = &cgDC.Assets.smallFont; } else if (scale > cg_bigFont.value) { font = &cgDC.Assets.bigFont; } useScale = scale * font->glyphScale; max = 0; if (text) { len = strlen(text); if (limit > 0 && len > limit) { len = limit; } count = 0; while (s && *s && count < len) { if ( Q_IsColorString(s) ) { s += 2; continue; } else { glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build if (max < glyph->height) { max = glyph->height; } s++; count++; } } } return max * useScale; } void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader) { float w, h; w = width * scale; h = height * scale; CG_AdjustFrom640( &x, &y, &w, &h ); trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); } void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style) { int len, count; vec4_t newColor; glyphInfo_t *glyph; float useScale; fontInfo_t *font = &cgDC.Assets.textFont; if (scale <= cg_smallFont.value) { font = &cgDC.Assets.smallFont; } else if (scale > cg_bigFont.value) { font = &cgDC.Assets.bigFont; } useScale = scale * font->glyphScale; if (text) { // TTimo: FIXME // const unsigned char *s = text; const char *s = text; trap_R_SetColor( color ); memcpy(&newColor[0], &color[0], sizeof(vec4_t)); len = strlen(text); if (limit > 0 && len > limit) { len = limit; } count = 0; while (s && *s && count < len) { glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top; //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height); if ( Q_IsColorString( s ) ) { memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); newColor[3] = color[3]; trap_R_SetColor( newColor ); s += 2; continue; } else { float yadj = useScale * glyph->top; if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) { int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; colorBlack[3] = newColor[3]; trap_R_SetColor( colorBlack ); CG_Text_PaintChar(x + ofs, y - yadj + ofs, glyph->imageWidth, glyph->imageHeight, useScale, glyph->s, glyph->t, glyph->s2, glyph->t2, glyph->glyph); colorBlack[3] = 1.0; trap_R_SetColor( newColor ); } CG_Text_PaintChar(x, y - yadj, glyph->imageWidth, glyph->imageHeight, useScale, glyph->s, glyph->t, glyph->s2, glyph->t2, glyph->glyph); // CG_DrawPic(x, y - yadj, scale * cgDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * cgDC.Assets.textFont.glyphs[text[i]].imageHeight, cgDC.Assets.textFont.glyphs[text[i]].glyph); x += (glyph->xSkip * useScale) + adjust; s++; count++; } } trap_R_SetColor( NULL ); } } #endif /* ============== CG_DrawField Draws large numbers for status bar and powerups ============== */ #ifndef MISSIONPACK static void CG_DrawField (int x, int y, int width, int value) { char num[16], *ptr; int l; int frame; if ( width < 1 ) { return; } // draw number string if ( width > 5 ) { width = 5; } switch ( width ) { case 1: value = value > 9 ? 9 : value; value = value < 0 ? 0 : value; break; case 2: value = value > 99 ? 99 : value; value = value < -9 ? -9 : value; break; case 3: value = value > 999 ? 999 : value; value = value < -99 ? -99 : value; break; case 4: value = value > 9999 ? 9999 : value; value = value < -999 ? -999 : value; break; } Com_sprintf (num, sizeof(num), "%i", value); l = strlen(num); if (l > width) l = width; x += 2 + CHAR_WIDTH*(width - l); ptr = num; while (*ptr && l) { if (*ptr == '-') frame = STAT_MINUS; else frame = *ptr -'0'; CG_DrawPic( x,y, CHAR_WIDTH, CHAR_HEIGHT, cgs.media.numberShaders[frame] ); x += CHAR_WIDTH; ptr++; l--; } } #endif // MISSIONPACK /* ================ CG_Draw3DModel ================ */ void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ) { refdef_t refdef; refEntity_t ent; if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { return; } CG_AdjustFrom640( &x, &y, &w, &h ); memset( &refdef, 0, sizeof( refdef ) ); memset( &ent, 0, sizeof( ent ) ); AnglesToAxis( angles, ent.axis ); VectorCopy( origin, ent.origin ); ent.hModel = model; ent.customSkin = skin; ent.renderfx = RF_NOSHADOW; // no stencil shadows refdef.rdflags = RDF_NOWORLDMODEL; AxisClear( refdef.viewaxis ); refdef.fov_x = 30; refdef.fov_y = 30; refdef.x = x; refdef.y = y; refdef.width = w; refdef.height = h; refdef.time = cg.time; trap_R_ClearScene(); trap_R_AddRefEntityToScene( &ent ); trap_R_RenderScene( &refdef ); } /* ================ CG_DrawHead Used for both the status bar and the scoreboard ================ */ void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ) { clipHandle_t cm; clientInfo_t *ci; float len; vec3_t origin; vec3_t mins, maxs; ci = &cgs.clientinfo[ clientNum ]; if ( cg_draw3dIcons.integer ) { cm = ci->headModel; if ( !cm ) { return; } // offset the origin y and z to center the head trap_R_ModelBounds( cm, mins, maxs ); origin[2] = -0.5 * ( mins[2] + maxs[2] ); origin[1] = 0.5 * ( mins[1] + maxs[1] ); // calculate distance so the head nearly fills the box // assume heads are taller than wide len = 0.7 * ( maxs[2] - mins[2] ); origin[0] = len / 0.268; // len / tan( fov/2 ) // allow per-model tweaking VectorAdd( origin, ci->headOffset, origin ); CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, origin, headAngles ); } else if ( cg_drawIcons.integer ) { CG_DrawPic( x, y, w, h, ci->modelIcon ); } // if they are deferred, draw a cross out if ( ci->deferred ) { CG_DrawPic( x, y, w, h, cgs.media.deferShader ); } } /* ================ CG_DrawFlagModel Used for both the status bar and the scoreboard ================ */ void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ) { qhandle_t cm; float len; vec3_t origin, angles; vec3_t mins, maxs; qhandle_t handle; if ( !force2D && cg_draw3dIcons.integer ) { VectorClear( angles ); cm = cgs.media.redFlagModel; // offset the origin y and z to center the flag trap_R_ModelBounds( cm, mins, maxs ); origin[2] = -0.5 * ( mins[2] + maxs[2] ); origin[1] = 0.5 * ( mins[1] + maxs[1] ); // calculate distance so the flag nearly fills the box // assume heads are taller than wide len = 0.5 * ( maxs[2] - mins[2] ); origin[0] = len / 0.268; // len / tan( fov/2 ) angles[YAW] = 60 * sin( cg.time / 2000.0 );; if( team == TEAM_RED ) { handle = cgs.media.redFlagModel; } else if( team == TEAM_BLUE ) { handle = cgs.media.blueFlagModel; } else if( team == TEAM_FREE ) { handle = cgs.media.neutralFlagModel; } else { return; } CG_Draw3DModel( x, y, w, h, handle, 0, origin, angles ); } else if ( cg_drawIcons.integer ) { gitem_t *item; if( team == TEAM_RED ) { item = BG_FindItemForPowerup( PW_REDFLAG ); } else if( team == TEAM_BLUE ) { item = BG_FindItemForPowerup( PW_BLUEFLAG ); } else if( team == TEAM_FREE ) { item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); } else { return; } if (item) { CG_DrawPic( x, y, w, h, cg_items[ ITEM_INDEX(item) ].icon ); } } } /* ================ CG_DrawStatusBarHead ================ */ #ifndef MISSIONPACK static void CG_DrawStatusBarHead( float x ) { vec3_t angles; float size, stretch; float frac; VectorClear( angles ); if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; size = ICON_SIZE * 1.25 * ( 1.5 - frac * 0.5 ); stretch = size - ICON_SIZE * 1.25; // kick in the direction of damage x -= stretch * 0.5 + cg.damageX * stretch * 0.5; cg.headStartYaw = 180 + cg.damageX * 45; cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); cg.headEndPitch = 5 * cos( crandom()*M_PI ); cg.headStartTime = cg.time; cg.headEndTime = cg.time + 100 + random() * 2000; } else { if ( cg.time >= cg.headEndTime ) { // select a new head angle cg.headStartYaw = cg.headEndYaw; cg.headStartPitch = cg.headEndPitch; cg.headStartTime = cg.headEndTime; cg.headEndTime = cg.time + 100 + random() * 2000; cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); cg.headEndPitch = 5 * cos( crandom()*M_PI ); } size = ICON_SIZE * 1.25; } // if the server was frozen for a while we may have a bad head start time if ( cg.headStartTime > cg.time ) { cg.headStartTime = cg.time; } frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); frac = frac * frac * ( 3 - 2 * frac ); angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; CG_DrawHead( x, 480 - size, size, size, cg.snap->ps.clientNum, angles ); } #endif // MISSIONPACK /* ================ CG_DrawStatusBarFlag ================ */ #ifndef MISSIONPACK static void CG_DrawStatusBarFlag( float x, int team ) { CG_DrawFlagModel( x, 480 - ICON_SIZE, ICON_SIZE, ICON_SIZE, team, qfalse ); } #endif // MISSIONPACK /* ================ CG_DrawTeamBackground ================ */ void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ) { vec4_t hcolor; hcolor[3] = alpha; if ( team == TEAM_RED ) { hcolor[0] = 1; hcolor[1] = 0; hcolor[2] = 0; } else if ( team == TEAM_BLUE ) { hcolor[0] = 0; hcolor[1] = 0; hcolor[2] = 1; } else { return; } trap_R_SetColor( hcolor ); CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); trap_R_SetColor( NULL ); } /* ================ CG_DrawStatusBar ================ */ #ifndef MISSIONPACK static void CG_DrawStatusBar( void ) { int color; centity_t *cent; playerState_t *ps; int value; vec4_t hcolor; vec3_t angles; vec3_t origin; #ifdef MISSIONPACK qhandle_t handle; #endif static float colors[4][4] = { // { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; { 1.0f, 0.69f, 0.0f, 1.0f }, // normal { 1.0f, 0.2f, 0.2f, 1.0f }, // low health { 0.5f, 0.5f, 0.5f, 1.0f }, // weapon firing { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 if ( cg_drawStatus.integer == 0 ) { return; } // draw the team background CG_DrawTeamBackground( 0, 420, 640, 60, 0.33f, cg.snap->ps.persistant[PERS_TEAM] ); cent = &cg_entities[cg.snap->ps.clientNum]; ps = &cg.snap->ps; VectorClear( angles ); // draw any 3D icons first, so the changes back to 2D are minimized if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { origin[0] = 70; origin[1] = 0; origin[2] = 0; angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); CG_Draw3DModel( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); } CG_DrawStatusBarHead( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE ); if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_RED ); } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_BLUE ); } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_FREE ); } if ( ps->stats[ STAT_ARMOR ] ) { origin[0] = 90; origin[1] = 0; origin[2] = -10; angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; CG_Draw3DModel( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, cgs.media.armorModel, 0, origin, angles ); } #ifdef MISSIONPACK if( cgs.gametype == GT_HARVESTER ) { origin[0] = 90; origin[1] = 0; origin[2] = -10; angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { handle = cgs.media.redCubeModel; } else { handle = cgs.media.blueCubeModel; } CG_Draw3DModel( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 416, ICON_SIZE, ICON_SIZE, handle, 0, origin, angles ); } #endif // // ammo // if ( cent->currentState.weapon ) { value = ps->ammo[cent->currentState.weapon]; if ( value > -1 ) { if ( cg.predictedPlayerState.weaponstate == WEAPON_FIRING && cg.predictedPlayerState.weaponTime > 100 ) { // draw as dark grey when reloading color = 2; // dark grey } else { if ( value >= 0 ) { color = 0; // green } else { color = 1; // red } } trap_R_SetColor( colors[color] ); CG_DrawField (0, 432, 3, value); trap_R_SetColor( NULL ); // if we didn't draw a 3D icon, draw a 2D icon for ammo if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { qhandle_t icon; icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; if ( icon ) { CG_DrawPic( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, icon ); } } } } // // health // value = ps->stats[STAT_HEALTH]; if ( value > 100 ) { trap_R_SetColor( colors[3] ); // white } else if (value > 25) { trap_R_SetColor( colors[0] ); // green } else if (value > 0) { color = (cg.time >> 8) & 1; // flash trap_R_SetColor( colors[color] ); } else { trap_R_SetColor( colors[1] ); // red } // stretch the health up when taking damage CG_DrawField ( 185, 432, 3, value); CG_ColorForHealth( hcolor ); trap_R_SetColor( hcolor ); // // armor // value = ps->stats[STAT_ARMOR]; if (value > 0 ) { trap_R_SetColor( colors[0] ); CG_DrawField (370, 432, 3, value); trap_R_SetColor( NULL ); // if we didn't draw a 3D icon, draw a 2D icon for armor if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { CG_DrawPic( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, cgs.media.armorIcon ); } } #ifdef MISSIONPACK // // cubes // if( cgs.gametype == GT_HARVESTER ) { value = ps->generic1; if( value > 99 ) { value = 99; } trap_R_SetColor( colors[0] ); CG_DrawField (640 - (CHAR_WIDTH*2 + TEXT_ICON_SPACE + ICON_SIZE), 432, 2, value); trap_R_SetColor( NULL ); // if we didn't draw a 3D icon, draw a 2D icon for armor if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { handle = cgs.media.redCubeIcon; } else { handle = cgs.media.blueCubeIcon; } CG_DrawPic( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 432, ICON_SIZE, ICON_SIZE, handle ); } } #endif } #endif /* =========================================================================================== UPPER RIGHT CORNER =========================================================================================== */ /* ================ CG_DrawAttacker ================ */ static float CG_DrawAttacker( float y ) { int t; float size; vec3_t angles; const char *info; const char *name; int clientNum; if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { return y; } if ( !cg.attackerTime ) { return y; } clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER]; if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum ) { return y; } t = cg.time - cg.attackerTime; if ( t > ATTACKER_HEAD_TIME ) { cg.attackerTime = 0; return y; } size = ICON_SIZE * 1.25; angles[PITCH] = 0; angles[YAW] = 180; angles[ROLL] = 0; CG_DrawHead( 640 - size, y, size, size, clientNum, angles ); info = CG_ConfigString( CS_PLAYERS + clientNum ); name = Info_ValueForKey( info, "n" ); y += size; CG_DrawBigString( 640 - ( Q_PrintStrlen( name ) * BIGCHAR_WIDTH), y, name, 0.5 ); return y + BIGCHAR_HEIGHT + 2; } /* ================== CG_DrawSnapshot ================== */ static float CG_DrawSnapshot( float y ) { char *s; int w; s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime, cg.latestSnapshotNum, cgs.serverCommandSequence ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString( 635 - w, y + 2, s, 1.0F); return y + BIGCHAR_HEIGHT + 4; } /* ================== CG_DrawFPS ================== */ #define FPS_FRAMES 4 static float CG_DrawFPS( float y ) { char *s; int w; static int previousTimes[FPS_FRAMES]; static int index; int i, total; int fps; static int previous; int t, frameTime; // don't use serverTime, because that will be drifting to // correct for internet lag changes, timescales, timedemos, etc t = trap_Milliseconds(); frameTime = t - previous; previous = t; previousTimes[index % FPS_FRAMES] = frameTime; index++; if ( index > FPS_FRAMES ) { // average multiple frames together to smooth changes out a bit total = 0; for ( i = 0 ; i < FPS_FRAMES ; i++ ) { total += previousTimes[i]; } if ( !total ) { total = 1; } fps = 1000 * FPS_FRAMES / total; s = va( "%ifps", fps ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString( 635 - w, y + 2, s, 1.0F); } return y + BIGCHAR_HEIGHT + 4; } /* ================= CG_DrawTimer ================= */ static float CG_DrawTimer( float y ) { char *s; int w; int mins, seconds, tens; int msec; msec = cg.time - cgs.levelStartTime; seconds = msec / 1000; mins = seconds / 60; seconds -= mins * 60; tens = seconds / 10; seconds -= tens * 10; s = va( "%i:%i%i", mins, tens, seconds ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString( 635 - w, y + 2, s, 1.0F); return y + BIGCHAR_HEIGHT + 4; } /* ================= CG_DrawTeamOverlay ================= */ static float CG_DrawTeamOverlay( float y, qboolean right, qboolean upper ) { int x, w, h, xx; int i, j, len; const char *p; vec4_t hcolor; int pwidth, lwidth; int plyrs; char st[16]; clientInfo_t *ci; gitem_t *item; int ret_y, count; if ( !cg_drawTeamOverlay.integer ) { return y; } if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE ) { return y; // Not on any team } plyrs = 0; // max player name width pwidth = 0; count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; for (i = 0; i < count; i++) { ci = cgs.clientinfo + sortedTeamPlayers[i]; if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { plyrs++; len = CG_DrawStrlen(ci->name); if (len > pwidth) pwidth = len; } } if (!plyrs) return y; if (pwidth > TEAM_OVERLAY_MAXNAME_WIDTH) pwidth = TEAM_OVERLAY_MAXNAME_WIDTH; // max location name width lwidth = 0; for (i = 1; i < MAX_LOCATIONS; i++) { p = CG_ConfigString(CS_LOCATIONS + i); if (p && *p) { len = CG_DrawStrlen(p); if (len > lwidth) lwidth = len; } } if (lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH) lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH; w = (pwidth + lwidth + 4 + 7) * TINYCHAR_WIDTH; if ( right ) x = 640 - w; else x = 0; h = plyrs * TINYCHAR_HEIGHT; if ( upper ) { ret_y = y + h; } else { y -= h; ret_y = y; } if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { hcolor[0] = 1.0f; hcolor[1] = 0.0f; hcolor[2] = 0.0f; hcolor[3] = 0.33f; } else { // if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) hcolor[0] = 0.0f; hcolor[1] = 0.0f; hcolor[2] = 1.0f; hcolor[3] = 0.33f; } trap_R_SetColor( hcolor ); CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); trap_R_SetColor( NULL ); for (i = 0; i < count; i++) { ci = cgs.clientinfo + sortedTeamPlayers[i]; if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0; xx = x + TINYCHAR_WIDTH; CG_DrawStringExt( xx, y, ci->name, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH); if (lwidth) { p = CG_ConfigString(CS_LOCATIONS + ci->location); if (!p || !*p) p = "unknown"; len = CG_DrawStrlen(p); if (len > lwidth) len = lwidth; // xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth + // ((lwidth/2 - len/2) * TINYCHAR_WIDTH); xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth; CG_DrawStringExt( xx, y, p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXLOCATION_WIDTH); } CG_GetColorForHealth( ci->health, ci->armor, hcolor ); Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); xx = x + TINYCHAR_WIDTH * 3 + TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth; CG_DrawStringExt( xx, y, st, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); // draw weapon icon xx += TINYCHAR_WIDTH * 3; if ( cg_weapons[ci->curWeapon].weaponIcon ) { CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, cg_weapons[ci->curWeapon].weaponIcon ); } else { CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, cgs.media.deferShader ); } // Draw powerup icons if (right) { xx = x; } else { xx = x + w - TINYCHAR_WIDTH; } for (j = 0; j <= PW_NUM_POWERUPS; j++) { if (ci->powerups & (1 << j)) { item = BG_FindItemForPowerup( j ); if (item) { CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, trap_R_RegisterShader( item->icon ) ); if (right) { xx -= TINYCHAR_WIDTH; } else { xx += TINYCHAR_WIDTH; } } } } y += TINYCHAR_HEIGHT; } } return ret_y; //#endif } /* ===================== CG_DrawUpperRight ===================== */ static void CG_DrawUpperRight( void ) { float y; y = 0; if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1 ) { y = CG_DrawTeamOverlay( y, qtrue, qtrue ); } if ( cg_drawSnapshot.integer ) { y = CG_DrawSnapshot( y ); } if ( cg_drawFPS.integer ) { y = CG_DrawFPS( y ); } if ( cg_drawTimer.integer ) { y = CG_DrawTimer( y ); } if ( cg_drawAttacker.integer ) { y = CG_DrawAttacker( y ); } } /* =========================================================================================== LOWER RIGHT CORNER =========================================================================================== */ /* ================= CG_DrawScores Draw the small two score display ================= */ #ifndef MISSIONPACK static float CG_DrawScores( float y ) { const char *s; int s1, s2, score; int x, w; int v; vec4_t color; float y1; gitem_t *item; s1 = cgs.scores1; s2 = cgs.scores2; y -= BIGCHAR_HEIGHT + 8; y1 = y; // draw from the right side to left if ( cgs.gametype >= GT_TEAM ) { x = 640; color[0] = 0.0f; color[1] = 0.0f; color[2] = 1.0f; color[3] = 0.33f; s = va( "%2i", s2 ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; x -= w; CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); } CG_DrawBigString( x + 4, y, s, 1.0F); if ( cgs.gametype == GT_CTF ) { // Display flag status item = BG_FindItemForPowerup( PW_BLUEFLAG ); if (item) { y1 = y - BIGCHAR_HEIGHT - 8; if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.blueFlagShader[cgs.blueflag] ); } } } color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f; color[3] = 0.33f; s = va( "%2i", s1 ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; x -= w; CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); } CG_DrawBigString( x + 4, y, s, 1.0F); if ( cgs.gametype == GT_CTF ) { // Display flag status item = BG_FindItemForPowerup( PW_REDFLAG ); if (item) { y1 = y - BIGCHAR_HEIGHT - 8; if( cgs.redflag >= 0 && cgs.redflag <= 2 ) { CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.redFlagShader[cgs.redflag] ); } } } #ifdef MISSIONPACK if ( cgs.gametype == GT_1FCTF ) { // Display flag status item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); if (item) { y1 = y - BIGCHAR_HEIGHT - 8; if( cgs.flagStatus >= 0 && cgs.flagStatus <= 3 ) { CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.flagShader[cgs.flagStatus] ); } } } #endif if ( cgs.gametype >= GT_CTF ) { v = cgs.capturelimit; } else { v = cgs.fraglimit; } if ( v ) { s = va( "%2i", v ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; x -= w; CG_DrawBigString( x + 4, y, s, 1.0F); } } else { qboolean spectator; x = 640; score = cg.snap->ps.persistant[PERS_SCORE]; spectator = ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ); // always show your score in the second box if not in first place if ( s1 != score ) { s2 = score; } if ( s2 != SCORE_NOT_PRESENT ) { s = va( "%2i", s2 ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; x -= w; if ( !spectator && score == s2 && score != s1 ) { color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f; color[3] = 0.33f; CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); } else { color[0] = 0.5f; color[1] = 0.5f; color[2] = 0.5f; color[3] = 0.33f; CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); } CG_DrawBigString( x + 4, y, s, 1.0F); } // first place if ( s1 != SCORE_NOT_PRESENT ) { s = va( "%2i", s1 ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; x -= w; if ( !spectator && score == s1 ) { color[0] = 0.0f; color[1] = 0.0f; color[2] = 1.0f; color[3] = 0.33f; CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); } else { color[0] = 0.5f; color[1] = 0.5f; color[2] = 0.5f; color[3] = 0.33f; CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); } CG_DrawBigString( x + 4, y, s, 1.0F); } if ( cgs.fraglimit ) { s = va( "%2i", cgs.fraglimit ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; x -= w; CG_DrawBigString( x + 4, y, s, 1.0F); } } return y1 - 8; } #endif // MISSIONPACK /* ================ CG_DrawPowerups ================ */ #ifndef MISSIONPACK static float CG_DrawPowerups( float y ) { int sorted[MAX_POWERUPS]; int sortedTime[MAX_POWERUPS]; int i, j, k; int active; playerState_t *ps; int t; gitem_t *item; int x; int color; float size; float f; static float colors[2][4] = { { 0.2f, 1.0f, 0.2f, 1.0f } , { 1.0f, 0.2f, 0.2f, 1.0f } }; ps = &cg.snap->ps; if ( ps->stats[STAT_HEALTH] <= 0 ) { return y; } // sort the list by time remaining active = 0; for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { if ( !ps->powerups[ i ] ) { continue; } t = ps->powerups[ i ] - cg.time; // ZOID--don't draw if the power up has unlimited time (999 seconds) // This is true of the CTF flags if ( t < 0 || t > 999000) { continue; } // insert into the list for ( j = 0 ; j < active ; j++ ) { if ( sortedTime[j] >= t ) { for ( k = active - 1 ; k >= j ; k-- ) { sorted[k+1] = sorted[k]; sortedTime[k+1] = sortedTime[k]; } break; } } sorted[j] = i; sortedTime[j] = t; active++; } // draw the icons and timers x = 640 - ICON_SIZE - CHAR_WIDTH * 2; for ( i = 0 ; i < active ; i++ ) { item = BG_FindItemForPowerup( sorted[i] ); if (item) { color = 1; y -= ICON_SIZE; trap_R_SetColor( colors[color] ); CG_DrawField( x, y, 2, sortedTime[ i ] / 1000 ); t = ps->powerups[ sorted[i] ]; if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { trap_R_SetColor( NULL ); } else { vec4_t modulate; f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; f -= (int)f; modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; trap_R_SetColor( modulate ); } if ( cg.powerupActive == sorted[i] && cg.time - cg.powerupTime < PULSE_TIME ) { f = 1.0 - ( ( (float)cg.time - cg.powerupTime ) / PULSE_TIME ); size = ICON_SIZE * ( 1.0 + ( PULSE_SCALE - 1.0 ) * f ); } else { size = ICON_SIZE; } CG_DrawPic( 640 - size, y + ICON_SIZE / 2 - size / 2, size, size, trap_R_RegisterShader( item->icon ) ); } } trap_R_SetColor( NULL ); return y; } #endif // MISSIONPACK /* ===================== CG_DrawLowerRight ===================== */ #ifndef MISSIONPACK static void CG_DrawLowerRight( void ) { float y; y = 480 - ICON_SIZE; if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 2 ) { y = CG_DrawTeamOverlay( y, qtrue, qfalse ); } y = CG_DrawScores( y ); y = CG_DrawPowerups( y ); } #endif // MISSIONPACK /* =================== CG_DrawPickupItem =================== */ #ifndef MISSIONPACK static int CG_DrawPickupItem( int y ) { int value; float *fadeColor; if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { return y; } y -= ICON_SIZE; value = cg.itemPickup; if ( value ) { fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 ); if ( fadeColor ) { CG_RegisterItemVisuals( value ); trap_R_SetColor( fadeColor ); CG_DrawPic( 8, y, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); CG_DrawBigString( ICON_SIZE + 16, y + (ICON_SIZE/2 - BIGCHAR_HEIGHT/2), bg_itemlist[ value ].pickup_name, fadeColor[0] ); trap_R_SetColor( NULL ); } } return y; } #endif // MISSIONPACK /* ===================== CG_DrawLowerLeft ===================== */ #ifndef MISSIONPACK static void CG_DrawLowerLeft( void ) { float y; y = 480 - ICON_SIZE; if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 3 ) { y = CG_DrawTeamOverlay( y, qfalse, qfalse ); } y = CG_DrawPickupItem( y ); } #endif // MISSIONPACK //=========================================================================================== /* ================= CG_DrawTeamInfo ================= */ #ifndef MISSIONPACK static void CG_DrawTeamInfo( void ) { int w, h; int i, len; vec4_t hcolor; int chatHeight; #define CHATLOC_Y 420 // bottom end #define CHATLOC_X 0 if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) chatHeight = cg_teamChatHeight.integer; else chatHeight = TEAMCHAT_HEIGHT; if (chatHeight <= 0) return; // disabled if (cgs.teamLastChatPos != cgs.teamChatPos) { if (cg.time - cgs.teamChatMsgTimes[cgs.teamLastChatPos % chatHeight] > cg_teamChatTime.integer) { cgs.teamLastChatPos++; } h = (cgs.teamChatPos - cgs.teamLastChatPos) * TINYCHAR_HEIGHT; w = 0; for (i = cgs.teamLastChatPos; i < cgs.teamChatPos; i++) { len = CG_DrawStrlen(cgs.teamChatMsgs[i % chatHeight]); if (len > w) w = len; } w *= TINYCHAR_WIDTH; w += TINYCHAR_WIDTH * 2; if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { hcolor[0] = 1.0f; hcolor[1] = 0.0f; hcolor[2] = 0.0f; hcolor[3] = 0.33f; } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { hcolor[0] = 0.0f; hcolor[1] = 0.0f; hcolor[2] = 1.0f; hcolor[3] = 0.33f; } else { hcolor[0] = 0.0f; hcolor[1] = 1.0f; hcolor[2] = 0.0f; hcolor[3] = 0.33f; } trap_R_SetColor( hcolor ); CG_DrawPic( CHATLOC_X, CHATLOC_Y - h, 640, h, cgs.media.teamStatusBar ); trap_R_SetColor( NULL ); hcolor[0] = hcolor[1] = hcolor[2] = 1.0f; hcolor[3] = 1.0f; for (i = cgs.teamChatPos - 1; i >= cgs.teamLastChatPos; i--) { CG_DrawStringExt( CHATLOC_X + TINYCHAR_WIDTH, CHATLOC_Y - (cgs.teamChatPos - i)*TINYCHAR_HEIGHT, cgs.teamChatMsgs[i % chatHeight], hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); } } } #endif // MISSIONPACK /* =================== CG_DrawHoldableItem =================== */ #ifndef MISSIONPACK static void CG_DrawHoldableItem( void ) { int value; value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; if ( value ) { CG_RegisterItemVisuals( value ); CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); } } #endif // MISSIONPACK #ifdef MISSIONPACK /* =================== CG_DrawPersistantPowerup =================== */ #if 0 // sos001208 - DEAD static void CG_DrawPersistantPowerup( void ) { int value; value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; if ( value ) { CG_RegisterItemVisuals( value ); CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2 - ICON_SIZE, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); } } #endif #endif // MISSIONPACK /* =================== CG_DrawReward =================== */ static void CG_DrawReward( void ) { float *color; int i, count; float x, y; char buf[32]; if ( !cg_drawRewards.integer ) { return; } color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); if ( !color ) { if (cg.rewardStack > 0) { for(i = 0; i < cg.rewardStack; i++) { cg.rewardSound[i] = cg.rewardSound[i+1]; cg.rewardShader[i] = cg.rewardShader[i+1]; cg.rewardCount[i] = cg.rewardCount[i+1]; } cg.rewardTime = cg.time; cg.rewardStack--; color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); trap_S_StartLocalSound(cg.rewardSound[0], CHAN_ANNOUNCER); } else { return; } } trap_R_SetColor( color ); /* count = cg.rewardCount[0]/10; // number of big rewards to draw if (count) { y = 4; x = 320 - count * ICON_SIZE; for ( i = 0 ; i < count ; i++ ) { CG_DrawPic( x, y, (ICON_SIZE*2)-4, (ICON_SIZE*2)-4, cg.rewardShader[0] ); x += (ICON_SIZE*2); } } count = cg.rewardCount[0] - count*10; // number of small rewards to draw */ if ( cg.rewardCount[0] >= 10 ) { y = 56; x = 320 - ICON_SIZE/2; CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); Com_sprintf(buf, sizeof(buf), "%d", cg.rewardCount[0]); x = ( SCREEN_WIDTH - SMALLCHAR_WIDTH * CG_DrawStrlen( buf ) ) / 2; CG_DrawStringExt( x, y+ICON_SIZE, buf, color, qfalse, qtrue, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); } else { count = cg.rewardCount[0]; y = 56; x = 320 - count * ICON_SIZE/2; for ( i = 0 ; i < count ; i++ ) { CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); x += ICON_SIZE; } } trap_R_SetColor( NULL ); } /* =============================================================================== LAGOMETER =============================================================================== */ #define LAG_SAMPLES 128 typedef struct { int frameSamples[LAG_SAMPLES]; int frameCount; int snapshotFlags[LAG_SAMPLES]; int snapshotSamples[LAG_SAMPLES]; int snapshotCount; } lagometer_t; lagometer_t lagometer; /* ============== CG_AddLagometerFrameInfo Adds the current interpolate / extrapolate bar for this frame ============== */ void CG_AddLagometerFrameInfo( void ) { int offset; offset = cg.time - cg.latestSnapshotTime; lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1) ] = offset; lagometer.frameCount++; } /* ============== CG_AddLagometerSnapshotInfo Each time a snapshot is received, log its ping time and the number of snapshots that were dropped before it. Pass NULL for a dropped packet. ============== */ void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) { // dropped packet if ( !snap ) { lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = -1; lagometer.snapshotCount++; return; } // add this snapshot's info lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->ping; lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->snapFlags; lagometer.snapshotCount++; } /* ============== CG_DrawDisconnect Should we draw something differnet for long lag vs no packets? ============== */ static void CG_DrawDisconnect( void ) { float x, y; int cmdNum; usercmd_t cmd; const char *s; int w; // bk010215 - FIXME char message[1024]; // draw the phone jack if we are completely past our buffers cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1; trap_GetUserCmd( cmdNum, &cmd ); if ( cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time ) { // special check for map_restart // bk 0102165 - FIXME return; } // also add text in center of screen s = "Connection Interrupted"; // bk 010215 - FIXME w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString( 320 - w/2, 100, s, 1.0F); // blink the icon if ( ( cg.time >> 9 ) & 1 ) { return; } x = 640 - 48; y = 480 - 48; CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga" ) ); } #define MAX_LAGOMETER_PING 900 #define MAX_LAGOMETER_RANGE 300 /* ============== CG_DrawLagometer ============== */ static void CG_DrawLagometer( void ) { int a, x, y, i; float v; float ax, ay, aw, ah, mid, range; int color; float vscale; if ( !cg_lagometer.integer || cgs.localServer ) { CG_DrawDisconnect(); return; } // // draw the graph // #ifdef MISSIONPACK x = 640 - 48; y = 480 - 144; #else x = 640 - 48; y = 480 - 48; #endif trap_R_SetColor( NULL ); CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); ax = x; ay = y; aw = 48; ah = 48; CG_AdjustFrom640( &ax, &ay, &aw, &ah ); color = -1; range = ah / 3; mid = ay + range; vscale = range / MAX_LAGOMETER_RANGE; // draw the frame interpoalte / extrapolate graph for ( a = 0 ; a < aw ; a++ ) { i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1); v = lagometer.frameSamples[i]; v *= vscale; if ( v > 0 ) { if ( color != 1 ) { color = 1; trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); } if ( v > range ) { v = range; } trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); } else if ( v < 0 ) { if ( color != 2 ) { color = 2; trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] ); } v = -v; if ( v > range ) { v = range; } trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); } } // draw the snapshot latency / drop graph range = ah / 2; vscale = range / MAX_LAGOMETER_PING; for ( a = 0 ; a < aw ; a++ ) { i = ( lagometer.snapshotCount - 1 - a ) & (LAG_SAMPLES - 1); v = lagometer.snapshotSamples[i]; if ( v > 0 ) { if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) { if ( color != 5 ) { color = 5; // YELLOW for rate delay trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); } } else { if ( color != 3 ) { color = 3; trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] ); } } v = v * vscale; if ( v > range ) { v = range; } trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); } else if ( v < 0 ) { if ( color != 4 ) { color = 4; // RED for dropped snapshots trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] ); } trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); } } trap_R_SetColor( NULL ); if ( cg_nopredict.integer || cg_synchronousClients.integer ) { CG_DrawBigString( ax, ay, "snc", 1.0 ); } CG_DrawDisconnect(); } /* =============================================================================== CENTER PRINTING =============================================================================== */ /* ============== CG_CenterPrint Called for important messages that should stay in the center of the screen for a few moments ============== */ void CG_CenterPrint( const char *str, int y, int charWidth ) { char *s; Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) ); cg.centerPrintTime = cg.time; cg.centerPrintY = y; cg.centerPrintCharWidth = charWidth; // count the number of lines for centering cg.centerPrintLines = 1; s = cg.centerPrint; while( *s ) { if (*s == '\n') cg.centerPrintLines++; s++; } } /* =================== CG_DrawCenterString =================== */ static void CG_DrawCenterString( void ) { char *start; int l; int x, y, w; #ifdef MISSIONPACK // bk010221 - unused else int h; #endif float *color; if ( !cg.centerPrintTime ) { return; } color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); if ( !color ) { return; } trap_R_SetColor( color ); start = cg.centerPrint; y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2; while ( 1 ) { char linebuffer[1024]; for ( l = 0; l < 50; l++ ) { if ( !start[l] || start[l] == '\n' ) { break; } linebuffer[l] = start[l]; } linebuffer[l] = 0; #ifdef MISSIONPACK w = CG_Text_Width(linebuffer, 0.5, 0); h = CG_Text_Height(linebuffer, 0.5, 0); x = (SCREEN_WIDTH - w) / 2; CG_Text_Paint(x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); y += h + 6; #else w = cg.centerPrintCharWidth * CG_DrawStrlen( linebuffer ); x = ( SCREEN_WIDTH - w ) / 2; CG_DrawStringExt( x, y, linebuffer, color, qfalse, qtrue, cg.centerPrintCharWidth, (int)(cg.centerPrintCharWidth * 1.5), 0 ); y += cg.centerPrintCharWidth * 1.5; #endif while ( *start && ( *start != '\n' ) ) { start++; } if ( !*start ) { break; } start++; } trap_R_SetColor( NULL ); } /* ================================================================================ CROSSHAIR ================================================================================ */ /* ================= CG_DrawCrosshair ================= */ static void CG_DrawCrosshair(void) { float w, h; qhandle_t hShader; float f; float x, y; int ca; if ( !cg_drawCrosshair.integer ) { return; } if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR) { return; } if ( cg.renderingThirdPerson ) { return; } // set color based on health if ( cg_crosshairHealth.integer ) { vec4_t hcolor; CG_ColorForHealth( hcolor ); trap_R_SetColor( hcolor ); } else { trap_R_SetColor( NULL ); } w = h = cg_crosshairSize.value; // pulse the size of the crosshair when picking up items f = cg.time - cg.itemPickupBlendTime; if ( f > 0 && f < ITEM_BLOB_TIME ) { f /= ITEM_BLOB_TIME; w *= ( 1 + f ); h *= ( 1 + f ); } x = cg_crosshairX.integer; y = cg_crosshairY.integer; CG_AdjustFrom640( &x, &y, &w, &h ); ca = cg_drawCrosshair.integer; if (ca < 0) { ca = 0; } hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ]; trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w), y + cg.refdef.y + 0.5 * (cg.refdef.height - h), w, h, 0, 0, 1, 1, hShader ); } /* ================= CG_ScanForCrosshairEntity ================= */ static void CG_ScanForCrosshairEntity( void ) { trace_t trace; vec3_t start, end; int content; VectorCopy( cg.refdef.vieworg, start ); VectorMA( start, 131072, cg.refdef.viewaxis[0], end ); CG_Trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY ); if ( trace.entityNum >= MAX_CLIENTS ) { return; } // if the player is in fog, don't show it content = trap_CM_PointContents( trace.endpos, 0 ); if ( content & CONTENTS_FOG ) { return; } // if the player is invisible, don't show it if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) ) { return; } // update the fade timer cg.crosshairClientNum = trace.entityNum; cg.crosshairClientTime = cg.time; } /* ===================== CG_DrawCrosshairNames ===================== */ static void CG_DrawCrosshairNames( void ) { float *color; char *name; float w; if ( !cg_drawCrosshair.integer ) { return; } if ( !cg_drawCrosshairNames.integer ) { return; } if ( cg.renderingThirdPerson ) { return; } // scan the known entities to see if the crosshair is sighted on one CG_ScanForCrosshairEntity(); // draw the name of the player being looked at color = CG_FadeColor( cg.crosshairClientTime, 1000 ); if ( !color ) { trap_R_SetColor( NULL ); return; } name = cgs.clientinfo[ cg.crosshairClientNum ].name; #ifdef MISSIONPACK color[3] *= 0.5f; w = CG_Text_Width(name, 0.3f, 0); CG_Text_Paint( 320 - w / 2, 190, 0.3f, color, name, 0, 0, ITEM_TEXTSTYLE_SHADOWED); #else w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; CG_DrawBigString( 320 - w / 2, 170, name, color[3] * 0.5f ); #endif trap_R_SetColor( NULL ); } //============================================================================== /* ================= CG_DrawSpectator ================= */ static void CG_DrawSpectator(void) { CG_DrawBigString(320 - 9 * 8, 440, "SPECTATOR", 1.0F); if ( cgs.gametype == GT_TOURNAMENT ) { CG_DrawBigString(320 - 15 * 8, 460, "waiting to play", 1.0F); } else if ( cgs.gametype >= GT_TEAM ) { CG_DrawBigString(320 - 39 * 8, 460, "press ESC and use the JOIN menu to play", 1.0F); } } /* ================= CG_DrawVote ================= */ static void CG_DrawVote(void) { char *s; int sec; if ( !cgs.voteTime ) { return; } // play a talk beep whenever it is modified if ( cgs.voteModified ) { cgs.voteModified = qfalse; trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); } sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000; if ( sec < 0 ) { sec = 0; } #ifdef MISSIONPACK s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo); CG_DrawSmallString( 0, 58, s, 1.0F ); s = "or press ESC then click Vote"; CG_DrawSmallString( 0, 58 + SMALLCHAR_HEIGHT + 2, s, 1.0F ); #else s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo ); CG_DrawSmallString( 0, 58, s, 1.0F ); #endif } /* ================= CG_DrawTeamVote ================= */ static void CG_DrawTeamVote(void) { char *s; int sec, cs_offset; if ( cgs.clientinfo->team == TEAM_RED ) cs_offset = 0; else if ( cgs.clientinfo->team == TEAM_BLUE ) cs_offset = 1; else return; if ( !cgs.teamVoteTime[cs_offset] ) { return; } // play a talk beep whenever it is modified if ( cgs.teamVoteModified[cs_offset] ) { cgs.teamVoteModified[cs_offset] = qfalse; trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); } sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[cs_offset] ) ) / 1000; if ( sec < 0 ) { sec = 0; } s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); CG_DrawSmallString( 0, 90, s, 1.0F ); } static qboolean CG_DrawScoreboard() { #ifdef MISSIONPACK static qboolean firstTime = qtrue; float fade, *fadeColor; if (menuScoreboard) { menuScoreboard->window.flags &= ~WINDOW_FORCED; } if (cg_paused.integer) { cg.deferredPlayerLoading = 0; firstTime = qtrue; return qfalse; } // should never happen in Team Arena if (cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { cg.deferredPlayerLoading = 0; firstTime = qtrue; return qfalse; } // don't draw scoreboard during death while warmup up if ( cg.warmup && !cg.showScores ) { return qfalse; } if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { fade = 1.0; fadeColor = colorWhite; } else { fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); if ( !fadeColor ) { // next time scoreboard comes up, don't print killer cg.deferredPlayerLoading = 0; cg.killerName[0] = 0; firstTime = qtrue; return qfalse; } fade = *fadeColor; } if (menuScoreboard == NULL) { if ( cgs.gametype >= GT_TEAM ) { menuScoreboard = Menus_FindByName("teamscore_menu"); } else { menuScoreboard = Menus_FindByName("score_menu"); } } if (menuScoreboard) { if (firstTime) { CG_SetScoreSelection(menuScoreboard); firstTime = qfalse; } Menu_Paint(menuScoreboard, qtrue); } // load any models that have been deferred if ( ++cg.deferredPlayerLoading > 10 ) { CG_LoadDeferredPlayers(); } return qtrue; #else return CG_DrawOldScoreboard(); #endif } /* ================= CG_DrawIntermission ================= */ static void CG_DrawIntermission( void ) { // int key; #ifdef MISSIONPACK //if (cg_singlePlayer.integer) { // CG_DrawCenterString(); // return; //} #else if ( cgs.gametype == GT_SINGLE_PLAYER ) { CG_DrawCenterString(); return; } #endif cg.scoreFadeTime = cg.time; cg.scoreBoardShowing = CG_DrawScoreboard(); } /* ================= CG_DrawFollow ================= */ static qboolean CG_DrawFollow( void ) { float x; vec4_t color; const char *name; if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) ) { return qfalse; } color[0] = 1; color[1] = 1; color[2] = 1; color[3] = 1; CG_DrawBigString( 320 - 9 * 8, 24, "following", 1.0F ); name = cgs.clientinfo[ cg.snap->ps.clientNum ].name; x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( name ) ); CG_DrawStringExt( x, 40, name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); return qtrue; } /* ================= CG_DrawAmmoWarning ================= */ static void CG_DrawAmmoWarning( void ) { const char *s; int w; if ( cg_drawAmmoWarning.integer == 0 ) { return; } if ( !cg.lowAmmoWarning ) { return; } if ( cg.lowAmmoWarning == 2 ) { s = "OUT OF AMMO"; } else { s = "LOW AMMO WARNING"; } w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString(320 - w / 2, 64, s, 1.0F); } #ifdef MISSIONPACK /* ================= CG_DrawProxWarning ================= */ static void CG_DrawProxWarning( void ) { char s [32]; int w; static int proxTime; static int proxCounter; static int proxTick; if( !(cg.snap->ps.eFlags & EF_TICKING ) ) { proxTime = 0; return; } if (proxTime == 0) { proxTime = cg.time + 5000; proxCounter = 5; proxTick = 0; } if (cg.time > proxTime) { proxTick = proxCounter--; proxTime = cg.time + 1000; } if (proxTick != 0) { Com_sprintf(s, sizeof(s), "INTERNAL COMBUSTION IN: %i", proxTick); } else { Com_sprintf(s, sizeof(s), "YOU HAVE BEEN MINED"); } w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigStringColor( 320 - w / 2, 64 + BIGCHAR_HEIGHT, s, g_color_table[ColorIndex(COLOR_RED)] ); } #endif /* ================= CG_DrawWarmup ================= */ static void CG_DrawWarmup( void ) { int w; int sec; int i; float scale; clientInfo_t *ci1, *ci2; int cw; const char *s; sec = cg.warmup; if ( !sec ) { return; } if ( sec < 0 ) { s = "Waiting for players"; w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString(320 - w / 2, 24, s, 1.0F); cg.warmupCount = 0; return; } if (cgs.gametype == GT_TOURNAMENT) { // find the two active players ci1 = NULL; ci2 = NULL; for ( i = 0 ; i < cgs.maxclients ; i++ ) { if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE ) { if ( !ci1 ) { ci1 = &cgs.clientinfo[i]; } else { ci2 = &cgs.clientinfo[i]; } } } if ( ci1 && ci2 ) { s = va( "%s vs %s", ci1->name, ci2->name ); #ifdef MISSIONPACK w = CG_Text_Width(s, 0.6f, 0); CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); #else w = CG_DrawStrlen( s ); if ( w > 640 / GIANT_WIDTH ) { cw = 640 / w; } else { cw = GIANT_WIDTH; } CG_DrawStringExt( 320 - w * cw/2, 20,s, colorWhite, qfalse, qtrue, cw, (int)(cw * 1.5f), 0 ); #endif } } else { if ( cgs.gametype == GT_FFA ) { s = "Free For All"; } else if ( cgs.gametype == GT_TEAM ) { s = "Team Deathmatch"; } else if ( cgs.gametype == GT_CTF ) { s = "Capture the Flag"; #ifdef MISSIONPACK } else if ( cgs.gametype == GT_1FCTF ) { s = "One Flag CTF"; } else if ( cgs.gametype == GT_OBELISK ) { s = "Overload"; } else if ( cgs.gametype == GT_HARVESTER ) { s = "Harvester"; #endif } else { s = ""; } #ifdef MISSIONPACK w = CG_Text_Width(s, 0.6f, 0); CG_Text_Paint(320 - w / 2, 90, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); #else w = CG_DrawStrlen( s ); if ( w > 640 / GIANT_WIDTH ) { cw = 640 / w; } else { cw = GIANT_WIDTH; } CG_DrawStringExt( 320 - w * cw/2, 25,s, colorWhite, qfalse, qtrue, cw, (int)(cw * 1.1f), 0 ); #endif } sec = ( sec - cg.time ) / 1000; if ( sec < 0 ) { cg.warmup = 0; sec = 0; } s = va( "Starts in: %i", sec + 1 ); if ( sec != cg.warmupCount ) { cg.warmupCount = sec; switch ( sec ) { case 0: trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER ); break; case 1: trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER ); break; case 2: trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER ); break; default: break; } } scale = 0.45f; switch ( cg.warmupCount ) { case 0: cw = 28; scale = 0.54f; break; case 1: cw = 24; scale = 0.51f; break; case 2: cw = 20; scale = 0.48f; break; default: cw = 16; scale = 0.45f; break; } #ifdef MISSIONPACK w = CG_Text_Width(s, scale, 0); CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); #else w = CG_DrawStrlen( s ); CG_DrawStringExt( 320 - w * cw/2, 70, s, colorWhite, qfalse, qtrue, cw, (int)(cw * 1.5), 0 ); #endif } //================================================================================== #ifdef MISSIONPACK /* ================= CG_DrawTimedMenus ================= */ void CG_DrawTimedMenus() { if (cg.voiceTime) { int t = cg.time - cg.voiceTime; if ( t > 2500 ) { Menus_CloseByName("voiceMenu"); trap_Cvar_Set("cl_conXOffset", "0"); cg.voiceTime = 0; } } } #endif /* ================= CG_Draw2D ================= */ static void CG_Draw2D( void ) { #ifdef MISSIONPACK if (cgs.orderPending && cg.time > cgs.orderTime) { CG_CheckOrderPending(); } #endif // if we are taking a levelshot for the menu, don't draw anything if ( cg.levelShot ) { return; } if ( cg_draw2D.integer == 0 ) { return; } if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { CG_DrawIntermission(); return; } /* if (cg.cameraMode) { return; } */ if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { CG_DrawSpectator(); CG_DrawCrosshair(); CG_DrawCrosshairNames(); } else { // don't draw any status if dead or the scoreboard is being explicitly shown if ( !cg.showScores && cg.snap->ps.stats[STAT_HEALTH] > 0 ) { #ifdef MISSIONPACK if ( cg_drawStatus.integer ) { Menu_PaintAll(); CG_DrawTimedMenus(); } #else CG_DrawStatusBar(); #endif CG_DrawAmmoWarning(); #ifdef MISSIONPACK CG_DrawProxWarning(); #endif CG_DrawCrosshair(); CG_DrawCrosshairNames(); CG_DrawWeaponSelect(); #ifndef MISSIONPACK CG_DrawHoldableItem(); #else //CG_DrawPersistantPowerup(); #endif CG_DrawReward(); } if ( cgs.gametype >= GT_TEAM ) { #ifndef MISSIONPACK CG_DrawTeamInfo(); #endif } } CG_DrawVote(); CG_DrawTeamVote(); CG_DrawLagometer(); #ifdef MISSIONPACK if (!cg_paused.integer) { CG_DrawUpperRight(); } #else CG_DrawUpperRight(); #endif #ifndef MISSIONPACK CG_DrawLowerRight(); CG_DrawLowerLeft(); #endif if ( !CG_DrawFollow() ) { CG_DrawWarmup(); } // don't draw center string if scoreboard is up cg.scoreBoardShowing = CG_DrawScoreboard(); if ( !cg.scoreBoardShowing) { CG_DrawCenterString(); } } static void CG_DrawTourneyScoreboard() { #ifdef MISSIONPACK #else CG_DrawOldTourneyScoreboard(); #endif } /* ===================== CG_DrawActive Perform all drawing needed to completely fill the screen ===================== */ void CG_DrawActive( stereoFrame_t stereoView ) { float separation; vec3_t baseOrg; // optionally draw the info screen instead if ( !cg.snap ) { CG_DrawInformation(); return; } // optionally draw the tournement scoreboard instead if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && ( cg.snap->ps.pm_flags & PMF_SCOREBOARD ) ) { CG_DrawTourneyScoreboard(); return; } switch ( stereoView ) { case STEREO_CENTER: separation = 0; break; case STEREO_LEFT: separation = -cg_stereoSeparation.value / 2; break; case STEREO_RIGHT: separation = cg_stereoSeparation.value / 2; break; default: separation = 0; CG_Error( "CG_DrawActive: Undefined stereoView" ); } // clear around the rendered view if sized down CG_TileClear(); // offset vieworg appropriately if we're doing stereo separation VectorCopy( cg.refdef.vieworg, baseOrg ); if ( separation != 0 ) { VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg ); } // draw 3D view trap_R_RenderScene( &cg.refdef ); // restore original viewpoint if running stereo if ( separation != 0 ) { VectorCopy( baseOrg, cg.refdef.vieworg ); } // draw status bar and other floating elements CG_Draw2D(); } ================================================ FILE: code/cgame/cg_drawtools.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc #include "cg_local.h" /* ================ CG_AdjustFrom640 Adjusted for resolution and screen aspect ratio ================ */ void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) { #if 0 // adjust for wide screens if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) { *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) ); } #endif // scale for screen sizes *x *= cgs.screenXScale; *y *= cgs.screenYScale; *w *= cgs.screenXScale; *h *= cgs.screenYScale; } /* ================ CG_FillRect Coordinates are 640*480 virtual values ================= */ void CG_FillRect( float x, float y, float width, float height, const float *color ) { trap_R_SetColor( color ); CG_AdjustFrom640( &x, &y, &width, &height ); trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader ); trap_R_SetColor( NULL ); } /* ================ CG_DrawSides Coords are virtual 640x480 ================ */ void CG_DrawSides(float x, float y, float w, float h, float size) { CG_AdjustFrom640( &x, &y, &w, &h ); size *= cgs.screenXScale; trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); } void CG_DrawTopBottom(float x, float y, float w, float h, float size) { CG_AdjustFrom640( &x, &y, &w, &h ); size *= cgs.screenYScale; trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); } /* ================ UI_DrawRect Coordinates are 640*480 virtual values ================= */ void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) { trap_R_SetColor( color ); CG_DrawTopBottom(x, y, width, height, size); CG_DrawSides(x, y, width, height, size); trap_R_SetColor( NULL ); } /* ================ CG_DrawPic Coordinates are 640*480 virtual values ================= */ void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { CG_AdjustFrom640( &x, &y, &width, &height ); trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); } /* =============== CG_DrawChar Coordinates and size in 640*480 virtual screen size =============== */ void CG_DrawChar( int x, int y, int width, int height, int ch ) { int row, col; float frow, fcol; float size; float ax, ay, aw, ah; ch &= 255; if ( ch == ' ' ) { return; } ax = x; ay = y; aw = width; ah = height; CG_AdjustFrom640( &ax, &ay, &aw, &ah ); row = ch>>4; col = ch&15; frow = row*0.0625; fcol = col*0.0625; size = 0.0625; trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol + size, frow + size, cgs.media.charsetShader ); } /* ================== CG_DrawStringExt Draws a multi-colored string with a drop shadow, optionally forcing to a fixed color. Coordinates are at 640 by 480 virtual resolution ================== */ void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) { vec4_t color; const char *s; int xx; int cnt; if (maxChars <= 0) maxChars = 32767; // do them all! // draw the drop shadow if (shadow) { color[0] = color[1] = color[2] = 0; color[3] = setColor[3]; trap_R_SetColor( color ); s = string; xx = x; cnt = 0; while ( *s && cnt < maxChars) { if ( Q_IsColorString( s ) ) { s += 2; continue; } CG_DrawChar( xx + 2, y + 2, charWidth, charHeight, *s ); cnt++; xx += charWidth; s++; } } // draw the colored text s = string; xx = x; cnt = 0; trap_R_SetColor( setColor ); while ( *s && cnt < maxChars) { if ( Q_IsColorString( s ) ) { if ( !forceColor ) { memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); color[3] = setColor[3]; trap_R_SetColor( color ); } s += 2; continue; } CG_DrawChar( xx, y, charWidth, charHeight, *s ); xx += charWidth; cnt++; s++; } trap_R_SetColor( NULL ); } void CG_DrawBigString( int x, int y, const char *s, float alpha ) { float color[4]; color[0] = color[1] = color[2] = 1.0; color[3] = alpha; CG_DrawStringExt( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); } void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { CG_DrawStringExt( x, y, s, color, qtrue, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); } void CG_DrawSmallString( int x, int y, const char *s, float alpha ) { float color[4]; color[0] = color[1] = color[2] = 1.0; color[3] = alpha; CG_DrawStringExt( x, y, s, color, qfalse, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); } void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ) { CG_DrawStringExt( x, y, s, color, qtrue, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); } /* ================= CG_DrawStrlen Returns character count, skiping color escape codes ================= */ int CG_DrawStrlen( const char *str ) { const char *s = str; int count = 0; while ( *s ) { if ( Q_IsColorString( s ) ) { s += 2; } else { count++; s++; } } return count; } /* ============= CG_TileClearBox This repeats a 64*64 tile graphic to fill the screen around a sized down refresh window. ============= */ static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) { float s1, t1, s2, t2; s1 = x/64.0; t1 = y/64.0; s2 = (x+w)/64.0; t2 = (y+h)/64.0; trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); } /* ============== CG_TileClear Clear around a sized down screen ============== */ void CG_TileClear( void ) { int top, bottom, left, right; int w, h; w = cgs.glconfig.vidWidth; h = cgs.glconfig.vidHeight; if ( cg.refdef.x == 0 && cg.refdef.y == 0 && cg.refdef.width == w && cg.refdef.height == h ) { return; // full screen rendering } top = cg.refdef.y; bottom = top + cg.refdef.height-1; left = cg.refdef.x; right = left + cg.refdef.width-1; // clear above view screen CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); // clear below view screen CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); // clear left of view screen CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); // clear right of view screen CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); } /* ================ CG_FadeColor ================ */ float *CG_FadeColor( int startMsec, int totalMsec ) { static vec4_t color; int t; if ( startMsec == 0 ) { return NULL; } t = cg.time - startMsec; if ( t >= totalMsec ) { return NULL; } // fade out if ( totalMsec - t < FADE_TIME ) { color[3] = ( totalMsec - t ) * 1.0/FADE_TIME; } else { color[3] = 1.0; } color[0] = color[1] = color[2] = 1; return color; } /* ================ CG_TeamColor ================ */ float *CG_TeamColor( int team ) { static vec4_t red = {1, 0.2f, 0.2f, 1}; static vec4_t blue = {0.2f, 0.2f, 1, 1}; static vec4_t other = {1, 1, 1, 1}; static vec4_t spectator = {0.7f, 0.7f, 0.7f, 1}; switch ( team ) { case TEAM_RED: return red; case TEAM_BLUE: return blue; case TEAM_SPECTATOR: return spectator; default: return other; } } /* ================= CG_GetColorForHealth ================= */ void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ) { int count; int max; // calculate the total points of damage that can // be sustained at the current health / armor level if ( health <= 0 ) { VectorClear( hcolor ); // black hcolor[3] = 1; return; } count = armor; max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); if ( max < count ) { count = max; } health += count; // set the color based on health hcolor[0] = 1.0; hcolor[3] = 1.0; if ( health >= 100 ) { hcolor[2] = 1.0; } else if ( health < 66 ) { hcolor[2] = 0; } else { hcolor[2] = ( health - 66 ) / 33.0; } if ( health > 60 ) { hcolor[1] = 1.0; } else if ( health < 30 ) { hcolor[1] = 0; } else { hcolor[1] = ( health - 30 ) / 30.0; } } /* ================= CG_ColorForHealth ================= */ void CG_ColorForHealth( vec4_t hcolor ) { CG_GetColorForHealth( cg.snap->ps.stats[STAT_HEALTH], cg.snap->ps.stats[STAT_ARMOR], hcolor ); } // bk001205 - code below duplicated in q3_ui/ui-atoms.c // bk001205 - FIXME: does this belong in ui_shared.c? // bk001205 - FIXME: HARD_LINKED flags not visible here #ifndef Q3_STATIC // bk001205 - q_shared defines not visible here /* ================= UI_DrawProportionalString2 ================= */ static int propMap[128][3] = { {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, PROP_SPACE_WIDTH}, // SPACE {11, 122, 7}, // ! {154, 181, 14}, // " {55, 122, 17}, // # {79, 122, 18}, // $ {101, 122, 23}, // % {153, 122, 18}, // & {9, 93, 7}, // ' {207, 122, 8}, // ( {230, 122, 9}, // ) {177, 122, 18}, // * {30, 152, 18}, // + {85, 181, 7}, // , {34, 93, 11}, // - {110, 181, 6}, // . {130, 152, 14}, // / {22, 64, 17}, // 0 {41, 64, 12}, // 1 {58, 64, 17}, // 2 {78, 64, 18}, // 3 {98, 64, 19}, // 4 {120, 64, 18}, // 5 {141, 64, 18}, // 6 {204, 64, 16}, // 7 {162, 64, 17}, // 8 {182, 64, 18}, // 9 {59, 181, 7}, // : {35,181, 7}, // ; {203, 152, 14}, // < {56, 93, 14}, // = {228, 152, 14}, // > {177, 181, 18}, // ? {28, 122, 22}, // @ {5, 4, 18}, // A {27, 4, 18}, // B {48, 4, 18}, // C {69, 4, 17}, // D {90, 4, 13}, // E {106, 4, 13}, // F {121, 4, 18}, // G {143, 4, 17}, // H {164, 4, 8}, // I {175, 4, 16}, // J {195, 4, 18}, // K {216, 4, 12}, // L {230, 4, 23}, // M {6, 34, 18}, // N {27, 34, 18}, // O {48, 34, 18}, // P {68, 34, 18}, // Q {90, 34, 17}, // R {110, 34, 18}, // S {130, 34, 14}, // T {146, 34, 18}, // U {166, 34, 19}, // V {185, 34, 29}, // W {215, 34, 18}, // X {234, 34, 18}, // Y {5, 64, 14}, // Z {60, 152, 7}, // [ {106, 151, 13}, // '\' {83, 152, 7}, // ] {128, 122, 17}, // ^ {4, 152, 21}, // _ {134, 181, 5}, // ' {5, 4, 18}, // A {27, 4, 18}, // B {48, 4, 18}, // C {69, 4, 17}, // D {90, 4, 13}, // E {106, 4, 13}, // F {121, 4, 18}, // G {143, 4, 17}, // H {164, 4, 8}, // I {175, 4, 16}, // J {195, 4, 18}, // K {216, 4, 12}, // L {230, 4, 23}, // M {6, 34, 18}, // N {27, 34, 18}, // O {48, 34, 18}, // P {68, 34, 18}, // Q {90, 34, 17}, // R {110, 34, 18}, // S {130, 34, 14}, // T {146, 34, 18}, // U {166, 34, 19}, // V {185, 34, 29}, // W {215, 34, 18}, // X {234, 34, 18}, // Y {5, 64, 14}, // Z {153, 152, 13}, // { {11, 181, 5}, // | {180, 152, 13}, // } {79, 93, 17}, // ~ {0, 0, -1} // DEL }; static int propMapB[26][3] = { {11, 12, 33}, {49, 12, 31}, {85, 12, 31}, {120, 12, 30}, {156, 12, 21}, {183, 12, 21}, {207, 12, 32}, {13, 55, 30}, {49, 55, 13}, {66, 55, 29}, {101, 55, 31}, {135, 55, 21}, {158, 55, 40}, {204, 55, 32}, {12, 97, 31}, {48, 97, 31}, {82, 97, 30}, {118, 97, 30}, {153, 97, 30}, {185, 97, 25}, {213, 97, 30}, {11, 139, 32}, {42, 139, 51}, {93, 139, 32}, {126, 139, 31}, {158, 139, 25}, }; #define PROPB_GAP_WIDTH 4 #define PROPB_SPACE_WIDTH 12 #define PROPB_HEIGHT 36 /* ================= UI_DrawBannerString ================= */ static void UI_DrawBannerString2( int x, int y, const char* str, vec4_t color ) { const char* s; unsigned char ch; // bk001204 : array subscript float ax; float ay; float aw; float ah; float frow; float fcol; float fwidth; float fheight; // draw the colored text trap_R_SetColor( color ); ax = x * cgs.screenXScale + cgs.screenXBias; ay = y * cgs.screenXScale; s = str; while ( *s ) { ch = *s & 127; if ( ch == ' ' ) { ax += ((float)PROPB_SPACE_WIDTH + (float)PROPB_GAP_WIDTH)* cgs.screenXScale; } else if ( ch >= 'A' && ch <= 'Z' ) { ch -= 'A'; fcol = (float)propMapB[ch][0] / 256.0f; frow = (float)propMapB[ch][1] / 256.0f; fwidth = (float)propMapB[ch][2] / 256.0f; fheight = (float)PROPB_HEIGHT / 256.0f; aw = (float)propMapB[ch][2] * cgs.screenXScale; ah = (float)PROPB_HEIGHT * cgs.screenXScale; trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, cgs.media.charsetPropB ); ax += (aw + (float)PROPB_GAP_WIDTH * cgs.screenXScale); } s++; } trap_R_SetColor( NULL ); } void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ) { const char * s; int ch; int width; vec4_t drawcolor; // find the width of the drawn text s = str; width = 0; while ( *s ) { ch = *s; if ( ch == ' ' ) { width += PROPB_SPACE_WIDTH; } else if ( ch >= 'A' && ch <= 'Z' ) { width += propMapB[ch - 'A'][2] + PROPB_GAP_WIDTH; } s++; } width -= PROPB_GAP_WIDTH; switch( style & UI_FORMATMASK ) { case UI_CENTER: x -= width / 2; break; case UI_RIGHT: x -= width; break; case UI_LEFT: default: break; } if ( style & UI_DROPSHADOW ) { drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; drawcolor[3] = color[3]; UI_DrawBannerString2( x+2, y+2, str, drawcolor ); } UI_DrawBannerString2( x, y, str, color ); } int UI_ProportionalStringWidth( const char* str ) { const char * s; int ch; int charWidth; int width; s = str; width = 0; while ( *s ) { ch = *s & 127; charWidth = propMap[ch][2]; if ( charWidth != -1 ) { width += charWidth; width += PROP_GAP_WIDTH; } s++; } width -= PROP_GAP_WIDTH; return width; } static void UI_DrawProportionalString2( int x, int y, const char* str, vec4_t color, float sizeScale, qhandle_t charset ) { const char* s; unsigned char ch; // bk001204 - unsigned float ax; float ay; float aw; float ah; float frow; float fcol; float fwidth; float fheight; // draw the colored text trap_R_SetColor( color ); ax = x * cgs.screenXScale + cgs.screenXBias; ay = y * cgs.screenXScale; s = str; while ( *s ) { ch = *s & 127; if ( ch == ' ' ) { aw = (float)PROP_SPACE_WIDTH * cgs.screenXScale * sizeScale; } else if ( propMap[ch][2] != -1 ) { fcol = (float)propMap[ch][0] / 256.0f; frow = (float)propMap[ch][1] / 256.0f; fwidth = (float)propMap[ch][2] / 256.0f; fheight = (float)PROP_HEIGHT / 256.0f; aw = (float)propMap[ch][2] * cgs.screenXScale * sizeScale; ah = (float)PROP_HEIGHT * cgs.screenXScale * sizeScale; trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, charset ); } else { aw = 0; } ax += (aw + (float)PROP_GAP_WIDTH * cgs.screenXScale * sizeScale); s++; } trap_R_SetColor( NULL ); } /* ================= UI_ProportionalSizeScale ================= */ float UI_ProportionalSizeScale( int style ) { if( style & UI_SMALLFONT ) { return 0.75; } return 1.00; } /* ================= UI_DrawProportionalString ================= */ void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ) { vec4_t drawcolor; int width; float sizeScale; sizeScale = UI_ProportionalSizeScale( style ); switch( style & UI_FORMATMASK ) { case UI_CENTER: width = UI_ProportionalStringWidth( str ) * sizeScale; x -= width / 2; break; case UI_RIGHT: width = UI_ProportionalStringWidth( str ) * sizeScale; x -= width; break; case UI_LEFT: default: break; } if ( style & UI_DROPSHADOW ) { drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; drawcolor[3] = color[3]; UI_DrawProportionalString2( x+2, y+2, str, drawcolor, sizeScale, cgs.media.charsetProp ); } if ( style & UI_INVERSE ) { drawcolor[0] = color[0] * 0.8; drawcolor[1] = color[1] * 0.8; drawcolor[2] = color[2] * 0.8; drawcolor[3] = color[3]; UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetProp ); return; } if ( style & UI_PULSE ) { drawcolor[0] = color[0] * 0.8; drawcolor[1] = color[1] * 0.8; drawcolor[2] = color[2] * 0.8; drawcolor[3] = color[3]; UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); drawcolor[0] = color[0]; drawcolor[1] = color[1]; drawcolor[2] = color[2]; drawcolor[3] = 0.5 + 0.5 * sin( cg.time / PULSE_DIVISOR ); UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetPropGlow ); return; } UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); } #endif // Q3STATIC ================================================ FILE: code/cgame/cg_effects.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_effects.c -- these functions generate localentities, usually as a result // of event processing #include "cg_local.h" /* ================== CG_BubbleTrail Bullets shot underwater ================== */ void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ) { vec3_t move; vec3_t vec; float len; int i; if ( cg_noProjectileTrail.integer ) { return; } VectorCopy (start, move); VectorSubtract (end, start, vec); len = VectorNormalize (vec); // advance a random amount first i = rand() % (int)spacing; VectorMA( move, i, vec, move ); VectorScale (vec, spacing, vec); for ( ; i < len; i += spacing ) { localEntity_t *le; refEntity_t *re; le = CG_AllocLocalEntity(); le->leFlags = LEF_PUFF_DONT_SCALE; le->leType = LE_MOVE_SCALE_FADE; le->startTime = cg.time; le->endTime = cg.time + 1000 + random() * 250; le->lifeRate = 1.0 / ( le->endTime - le->startTime ); re = &le->refEntity; re->shaderTime = cg.time / 1000.0f; re->reType = RT_SPRITE; re->rotation = 0; re->radius = 3; re->customShader = cgs.media.waterBubbleShader; re->shaderRGBA[0] = 0xff; re->shaderRGBA[1] = 0xff; re->shaderRGBA[2] = 0xff; re->shaderRGBA[3] = 0xff; le->color[3] = 1.0; le->pos.trType = TR_LINEAR; le->pos.trTime = cg.time; VectorCopy( move, le->pos.trBase ); le->pos.trDelta[0] = crandom()*5; le->pos.trDelta[1] = crandom()*5; le->pos.trDelta[2] = crandom()*5 + 6; VectorAdd (move, vec, move); } } /* ===================== CG_SmokePuff Adds a smoke puff or blood trail localEntity. ===================== */ localEntity_t *CG_SmokePuff( const vec3_t p, const vec3_t vel, float radius, float r, float g, float b, float a, float duration, int startTime, int fadeInTime, int leFlags, qhandle_t hShader ) { static int seed = 0x92; localEntity_t *le; refEntity_t *re; // int fadeInTime = startTime + duration / 2; le = CG_AllocLocalEntity(); le->leFlags = leFlags; le->radius = radius; re = &le->refEntity; re->rotation = Q_random( &seed ) * 360; re->radius = radius; re->shaderTime = startTime / 1000.0f; le->leType = LE_MOVE_SCALE_FADE; le->startTime = startTime; le->fadeInTime = fadeInTime; le->endTime = startTime + duration; if ( fadeInTime > startTime ) { le->lifeRate = 1.0 / ( le->endTime - le->fadeInTime ); } else { le->lifeRate = 1.0 / ( le->endTime - le->startTime ); } le->color[0] = r; le->color[1] = g; le->color[2] = b; le->color[3] = a; le->pos.trType = TR_LINEAR; le->pos.trTime = startTime; VectorCopy( vel, le->pos.trDelta ); VectorCopy( p, le->pos.trBase ); VectorCopy( p, re->origin ); re->customShader = hShader; // rage pro can't alpha fade, so use a different shader if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { re->customShader = cgs.media.smokePuffRageProShader; re->shaderRGBA[0] = 0xff; re->shaderRGBA[1] = 0xff; re->shaderRGBA[2] = 0xff; re->shaderRGBA[3] = 0xff; } else { re->shaderRGBA[0] = le->color[0] * 0xff; re->shaderRGBA[1] = le->color[1] * 0xff; re->shaderRGBA[2] = le->color[2] * 0xff; re->shaderRGBA[3] = 0xff; } re->reType = RT_SPRITE; re->radius = le->radius; return le; } /* ================== CG_SpawnEffect Player teleporting in or out ================== */ void CG_SpawnEffect( vec3_t org ) { localEntity_t *le; refEntity_t *re; le = CG_AllocLocalEntity(); le->leFlags = 0; le->leType = LE_FADE_RGB; le->startTime = cg.time; le->endTime = cg.time + 500; le->lifeRate = 1.0 / ( le->endTime - le->startTime ); le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; re = &le->refEntity; re->reType = RT_MODEL; re->shaderTime = cg.time / 1000.0f; #ifndef MISSIONPACK re->customShader = cgs.media.teleportEffectShader; #endif re->hModel = cgs.media.teleportEffectModel; AxisClear( re->axis ); VectorCopy( org, re->origin ); #ifdef MISSIONPACK re->origin[2] += 16; #else re->origin[2] -= 24; #endif } #ifdef MISSIONPACK /* =============== CG_LightningBoltBeam =============== */ void CG_LightningBoltBeam( vec3_t start, vec3_t end ) { localEntity_t *le; refEntity_t *beam; le = CG_AllocLocalEntity(); le->leFlags = 0; le->leType = LE_SHOWREFENTITY; le->startTime = cg.time; le->endTime = cg.time + 50; beam = &le->refEntity; VectorCopy( start, beam->origin ); // this is the end point VectorCopy( end, beam->oldorigin ); beam->reType = RT_LIGHTNING; beam->customShader = cgs.media.lightningShader; } /* ================== CG_KamikazeEffect ================== */ void CG_KamikazeEffect( vec3_t org ) { localEntity_t *le; refEntity_t *re; le = CG_AllocLocalEntity(); le->leFlags = 0; le->leType = LE_KAMIKAZE; le->startTime = cg.time; le->endTime = cg.time + 3000;//2250; le->lifeRate = 1.0 / ( le->endTime - le->startTime ); le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; VectorClear(le->angles.trBase); re = &le->refEntity; re->reType = RT_MODEL; re->shaderTime = cg.time / 1000.0f; re->hModel = cgs.media.kamikazeEffectModel; VectorCopy( org, re->origin ); } /* ================== CG_ObeliskExplode ================== */ void CG_ObeliskExplode( vec3_t org, int entityNum ) { localEntity_t *le; vec3_t origin; // create an explosion VectorCopy( org, origin ); origin[2] += 64; le = CG_MakeExplosion( origin, vec3_origin, cgs.media.dishFlashModel, cgs.media.rocketExplosionShader, 600, qtrue ); le->light = 300; le->lightColor[0] = 1; le->lightColor[1] = 0.75; le->lightColor[2] = 0.0; } /* ================== CG_ObeliskPain ================== */ void CG_ObeliskPain( vec3_t org ) { float r; sfxHandle_t sfx; // hit sound r = rand() & 3; if ( r < 2 ) { sfx = cgs.media.obeliskHitSound1; } else if ( r == 2 ) { sfx = cgs.media.obeliskHitSound2; } else { sfx = cgs.media.obeliskHitSound3; } trap_S_StartSound ( org, ENTITYNUM_NONE, CHAN_BODY, sfx ); } /* ================== CG_InvulnerabilityImpact ================== */ void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ) { localEntity_t *le; refEntity_t *re; int r; sfxHandle_t sfx; le = CG_AllocLocalEntity(); le->leFlags = 0; le->leType = LE_INVULIMPACT; le->startTime = cg.time; le->endTime = cg.time + 1000; le->lifeRate = 1.0 / ( le->endTime - le->startTime ); le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; re = &le->refEntity; re->reType = RT_MODEL; re->shaderTime = cg.time / 1000.0f; re->hModel = cgs.media.invulnerabilityImpactModel; VectorCopy( org, re->origin ); AnglesToAxis( angles, re->axis ); r = rand() & 3; if ( r < 2 ) { sfx = cgs.media.invulnerabilityImpactSound1; } else if ( r == 2 ) { sfx = cgs.media.invulnerabilityImpactSound2; } else { sfx = cgs.media.invulnerabilityImpactSound3; } trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, sfx ); } /* ================== CG_InvulnerabilityJuiced ================== */ void CG_InvulnerabilityJuiced( vec3_t org ) { localEntity_t *le; refEntity_t *re; vec3_t angles; le = CG_AllocLocalEntity(); le->leFlags = 0; le->leType = LE_INVULJUICED; le->startTime = cg.time; le->endTime = cg.time + 10000; le->lifeRate = 1.0 / ( le->endTime - le->startTime ); le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; re = &le->refEntity; re->reType = RT_MODEL; re->shaderTime = cg.time / 1000.0f; re->hModel = cgs.media.invulnerabilityJuicedModel; VectorCopy( org, re->origin ); VectorClear(angles); AnglesToAxis( angles, re->axis ); trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, cgs.media.invulnerabilityJuicedSound ); } #endif /* ================== CG_ScorePlum ================== */ void CG_ScorePlum( int client, vec3_t org, int score ) { localEntity_t *le; refEntity_t *re; vec3_t angles; static vec3_t lastPos; // only visualize for the client that scored if (client != cg.predictedPlayerState.clientNum || cg_scorePlum.integer == 0) { return; } le = CG_AllocLocalEntity(); le->leFlags = 0; le->leType = LE_SCOREPLUM; le->startTime = cg.time; le->endTime = cg.time + 4000; le->lifeRate = 1.0 / ( le->endTime - le->startTime ); le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; le->radius = score; VectorCopy( org, le->pos.trBase ); if (org[2] >= lastPos[2] - 20 && org[2] <= lastPos[2] + 20) { le->pos.trBase[2] -= 20; } //CG_Printf( "Plum origin %i %i %i -- %i\n", (int)org[0], (int)org[1], (int)org[2], (int)Distance(org, lastPos)); VectorCopy(org, lastPos); re = &le->refEntity; re->reType = RT_SPRITE; re->radius = 16; VectorClear(angles); AnglesToAxis( angles, re->axis ); } /* ==================== CG_MakeExplosion ==================== */ localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, qhandle_t hModel, qhandle_t shader, int msec, qboolean isSprite ) { float ang; localEntity_t *ex; int offset; vec3_t tmpVec, newOrigin; if ( msec <= 0 ) { CG_Error( "CG_MakeExplosion: msec = %i", msec ); } // skew the time a bit so they aren't all in sync offset = rand() & 63; ex = CG_AllocLocalEntity(); if ( isSprite ) { ex->leType = LE_SPRITE_EXPLOSION; // randomly rotate sprite orientation ex->refEntity.rotation = rand() % 360; VectorScale( dir, 16, tmpVec ); VectorAdd( tmpVec, origin, newOrigin ); } else { ex->leType = LE_EXPLOSION; VectorCopy( origin, newOrigin ); // set axis with random rotate if ( !dir ) { AxisClear( ex->refEntity.axis ); } else { ang = rand() % 360; VectorCopy( dir, ex->refEntity.axis[0] ); RotateAroundDirection( ex->refEntity.axis, ang ); } } ex->startTime = cg.time - offset; ex->endTime = ex->startTime + msec; // bias the time so all shader effects start correctly ex->refEntity.shaderTime = ex->startTime / 1000.0f; ex->refEntity.hModel = hModel; ex->refEntity.customShader = shader; // set origin VectorCopy( newOrigin, ex->refEntity.origin ); VectorCopy( newOrigin, ex->refEntity.oldorigin ); ex->color[0] = ex->color[1] = ex->color[2] = 1.0; return ex; } /* ================= CG_Bleed This is the spurt of blood when a character gets hit ================= */ void CG_Bleed( vec3_t origin, int entityNum ) { localEntity_t *ex; if ( !cg_blood.integer ) { return; } ex = CG_AllocLocalEntity(); ex->leType = LE_EXPLOSION; ex->startTime = cg.time; ex->endTime = ex->startTime + 500; VectorCopy ( origin, ex->refEntity.origin); ex->refEntity.reType = RT_SPRITE; ex->refEntity.rotation = rand() % 360; ex->refEntity.radius = 24; ex->refEntity.customShader = cgs.media.bloodExplosionShader; // don't show player's own blood in view if ( entityNum == cg.snap->ps.clientNum ) { ex->refEntity.renderfx |= RF_THIRD_PERSON; } } /* ================== CG_LaunchGib ================== */ void CG_LaunchGib( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { localEntity_t *le; refEntity_t *re; le = CG_AllocLocalEntity(); re = &le->refEntity; le->leType = LE_FRAGMENT; le->startTime = cg.time; le->endTime = le->startTime + 5000 + random() * 3000; VectorCopy( origin, re->origin ); AxisCopy( axisDefault, re->axis ); re->hModel = hModel; le->pos.trType = TR_GRAVITY; VectorCopy( origin, le->pos.trBase ); VectorCopy( velocity, le->pos.trDelta ); le->pos.trTime = cg.time; le->bounceFactor = 0.6f; le->leBounceSoundType = LEBS_BLOOD; le->leMarkType = LEMT_BLOOD; } /* =================== CG_GibPlayer Generated a bunch of gibs launching out from the bodies location =================== */ #define GIB_VELOCITY 250 #define GIB_JUMP 250 void CG_GibPlayer( vec3_t playerOrigin ) { vec3_t origin, velocity; if ( !cg_blood.integer ) { return; } VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; if ( rand() & 1 ) { CG_LaunchGib( origin, velocity, cgs.media.gibSkull ); } else { CG_LaunchGib( origin, velocity, cgs.media.gibBrain ); } // allow gibs to be turned off for speed if ( !cg_gibs.integer ) { return; } VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibAbdomen ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibArm ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibChest ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibFist ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibFoot ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibForearm ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibIntestine ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*GIB_VELOCITY; velocity[1] = crandom()*GIB_VELOCITY; velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); } /* ================== CG_LaunchGib ================== */ void CG_LaunchExplode( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { localEntity_t *le; refEntity_t *re; le = CG_AllocLocalEntity(); re = &le->refEntity; le->leType = LE_FRAGMENT; le->startTime = cg.time; le->endTime = le->startTime + 10000 + random() * 6000; VectorCopy( origin, re->origin ); AxisCopy( axisDefault, re->axis ); re->hModel = hModel; le->pos.trType = TR_GRAVITY; VectorCopy( origin, le->pos.trBase ); VectorCopy( velocity, le->pos.trDelta ); le->pos.trTime = cg.time; le->bounceFactor = 0.1f; le->leBounceSoundType = LEBS_BRASS; le->leMarkType = LEMT_NONE; } #define EXP_VELOCITY 100 #define EXP_JUMP 150 /* =================== CG_GibPlayer Generated a bunch of gibs launching out from the bodies location =================== */ void CG_BigExplode( vec3_t playerOrigin ) { vec3_t origin, velocity; if ( !cg_blood.integer ) { return; } VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*EXP_VELOCITY; velocity[1] = crandom()*EXP_VELOCITY; velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*EXP_VELOCITY; velocity[1] = crandom()*EXP_VELOCITY; velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*EXP_VELOCITY*1.5; velocity[1] = crandom()*EXP_VELOCITY*1.5; velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*EXP_VELOCITY*2.0; velocity[1] = crandom()*EXP_VELOCITY*2.0; velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); VectorCopy( playerOrigin, origin ); velocity[0] = crandom()*EXP_VELOCITY*2.5; velocity[1] = crandom()*EXP_VELOCITY*2.5; velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); } ================================================ FILE: code/cgame/cg_ents.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_ents.c -- present snapshot entities, happens every single frame #include "cg_local.h" /* ====================== CG_PositionEntityOnTag Modifies the entities position and axis by the given tag location ====================== */ void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName ) { int i; orientation_t lerped; // lerp the tag trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, tagName ); // FIXME: allow origin offsets along tag? VectorCopy( parent->origin, entity->origin ); for ( i = 0 ; i < 3 ; i++ ) { VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); } // had to cast away the const to avoid compiler problems... MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis ); entity->backlerp = parent->backlerp; } /* ====================== CG_PositionRotatedEntityOnTag Modifies the entities position and axis by the given tag location ====================== */ void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName ) { int i; orientation_t lerped; vec3_t tempAxis[3]; //AxisClear( entity->axis ); // lerp the tag trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, 1.0 - parent->backlerp, tagName ); // FIXME: allow origin offsets along tag? VectorCopy( parent->origin, entity->origin ); for ( i = 0 ; i < 3 ; i++ ) { VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); } // had to cast away the const to avoid compiler problems... MatrixMultiply( entity->axis, lerped.axis, tempAxis ); MatrixMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis ); } /* ========================================================================== FUNCTIONS CALLED EACH FRAME ========================================================================== */ /* ====================== CG_SetEntitySoundPosition Also called by event processing code ====================== */ void CG_SetEntitySoundPosition( centity_t *cent ) { if ( cent->currentState.solid == SOLID_BMODEL ) { vec3_t origin; float *v; v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; VectorAdd( cent->lerpOrigin, v, origin ); trap_S_UpdateEntityPosition( cent->currentState.number, origin ); } else { trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); } } /* ================== CG_EntityEffects Add continuous entity effects, like local entity emission and lighting ================== */ static void CG_EntityEffects( centity_t *cent ) { // update sound origins CG_SetEntitySoundPosition( cent ); // add loop sound if ( cent->currentState.loopSound ) { if (cent->currentState.eType != ET_SPEAKER) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ] ); } else { trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.gameSounds[ cent->currentState.loopSound ] ); } } // constant light glow if ( cent->currentState.constantLight ) { int cl; int i, r, g, b; cl = cent->currentState.constantLight; r = cl & 255; g = ( cl >> 8 ) & 255; b = ( cl >> 16 ) & 255; i = ( ( cl >> 24 ) & 255 ) * 4; trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); } } /* ================== CG_General ================== */ static void CG_General( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // if set to invisible, skip if (!s1->modelindex) { return; } memset (&ent, 0, sizeof(ent)); // set frame ent.frame = s1->frame; ent.oldframe = ent.frame; ent.backlerp = 0; VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); ent.hModel = cgs.gameModels[s1->modelindex]; // player model if (s1->number == cg.snap->ps.clientNum) { ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors } // convert angles to axis AnglesToAxis( cent->lerpAngles, ent.axis ); // add to refresh list trap_R_AddRefEntityToScene (&ent); } /* ================== CG_Speaker Speaker entities can automatically play sounds ================== */ static void CG_Speaker( centity_t *cent ) { if ( ! cent->currentState.clientNum ) { // FIXME: use something other than clientNum... return; // not auto triggering } if ( cg.time < cent->miscTime ) { return; } trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] ); // ent->s.frame = ent->wait * 10; // ent->s.clientNum = ent->random * 10; cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); } /* ================== CG_Item ================== */ static void CG_Item( centity_t *cent ) { refEntity_t ent; entityState_t *es; gitem_t *item; int msec; float frac; float scale; weaponInfo_t *wi; es = ¢->currentState; if ( es->modelindex >= bg_numItems ) { CG_Error( "Bad item index %i on entity", es->modelindex ); } // if set to invisible, skip if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) { return; } item = &bg_itemlist[ es->modelindex ]; if ( cg_simpleItems.integer && item->giType != IT_TEAM ) { memset( &ent, 0, sizeof( ent ) ); ent.reType = RT_SPRITE; VectorCopy( cent->lerpOrigin, ent.origin ); ent.radius = 14; ent.customShader = cg_items[es->modelindex].icon; ent.shaderRGBA[0] = 255; ent.shaderRGBA[1] = 255; ent.shaderRGBA[2] = 255; ent.shaderRGBA[3] = 255; trap_R_AddRefEntityToScene(&ent); return; } // items bob up and down continuously scale = 0.005 + cent->currentState.number * 0.00001; cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; memset (&ent, 0, sizeof(ent)); // autorotate at one of two speeds if ( item->giType == IT_HEALTH ) { VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); AxisCopy( cg.autoAxisFast, ent.axis ); } else { VectorCopy( cg.autoAngles, cent->lerpAngles ); AxisCopy( cg.autoAxis, ent.axis ); } wi = NULL; // the weapons have their origin where they attatch to player // models, so we need to offset them or they will rotate // eccentricly if ( item->giType == IT_WEAPON ) { wi = &cg_weapons[item->giTag]; cent->lerpOrigin[0] -= wi->weaponMidpoint[0] * ent.axis[0][0] + wi->weaponMidpoint[1] * ent.axis[1][0] + wi->weaponMidpoint[2] * ent.axis[2][0]; cent->lerpOrigin[1] -= wi->weaponMidpoint[0] * ent.axis[0][1] + wi->weaponMidpoint[1] * ent.axis[1][1] + wi->weaponMidpoint[2] * ent.axis[2][1]; cent->lerpOrigin[2] -= wi->weaponMidpoint[0] * ent.axis[0][2] + wi->weaponMidpoint[1] * ent.axis[1][2] + wi->weaponMidpoint[2] * ent.axis[2][2]; cent->lerpOrigin[2] += 8; // an extra height boost } ent.hModel = cg_items[es->modelindex].models[0]; VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); ent.nonNormalizedAxes = qfalse; // if just respawned, slowly scale up msec = cg.time - cent->miscTime; if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) { frac = (float)msec / ITEM_SCALEUP_TIME; VectorScale( ent.axis[0], frac, ent.axis[0] ); VectorScale( ent.axis[1], frac, ent.axis[1] ); VectorScale( ent.axis[2], frac, ent.axis[2] ); ent.nonNormalizedAxes = qtrue; } else { frac = 1.0; } // items without glow textures need to keep a minimum light value // so they are always visible if ( ( item->giType == IT_WEAPON ) || ( item->giType == IT_ARMOR ) ) { ent.renderfx |= RF_MINLIGHT; } // increase the size of the weapons when they are presented as items if ( item->giType == IT_WEAPON ) { VectorScale( ent.axis[0], 1.5, ent.axis[0] ); VectorScale( ent.axis[1], 1.5, ent.axis[1] ); VectorScale( ent.axis[2], 1.5, ent.axis[2] ); ent.nonNormalizedAxes = qtrue; #ifdef MISSIONPACK trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound ); #endif } #ifdef MISSIONPACK if ( item->giType == IT_HOLDABLE && item->giTag == HI_KAMIKAZE ) { VectorScale( ent.axis[0], 2, ent.axis[0] ); VectorScale( ent.axis[1], 2, ent.axis[1] ); VectorScale( ent.axis[2], 2, ent.axis[2] ); ent.nonNormalizedAxes = qtrue; } #endif // add to refresh list trap_R_AddRefEntityToScene(&ent); #ifdef MISSIONPACK if ( item->giType == IT_WEAPON && wi->barrelModel ) { refEntity_t barrel; memset( &barrel, 0, sizeof( barrel ) ); barrel.hModel = wi->barrelModel; VectorCopy( ent.lightingOrigin, barrel.lightingOrigin ); barrel.shadowPlane = ent.shadowPlane; barrel.renderfx = ent.renderfx; CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); AxisCopy( ent.axis, barrel.axis ); barrel.nonNormalizedAxes = ent.nonNormalizedAxes; trap_R_AddRefEntityToScene( &barrel ); } #endif // accompanying rings / spheres for powerups if ( !cg_simpleItems.integer ) { vec3_t spinAngles; VectorClear( spinAngles ); if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP ) { if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 ) { if ( item->giType == IT_POWERUP ) { ent.origin[2] += 12; spinAngles[1] = ( cg.time & 1023 ) * 360 / -1024.0f; } AnglesToAxis( spinAngles, ent.axis ); // scale up if respawning if ( frac != 1.0 ) { VectorScale( ent.axis[0], frac, ent.axis[0] ); VectorScale( ent.axis[1], frac, ent.axis[1] ); VectorScale( ent.axis[2], frac, ent.axis[2] ); ent.nonNormalizedAxes = qtrue; } trap_R_AddRefEntityToScene( &ent ); } } } } //============================================================================ /* =============== CG_Missile =============== */ static void CG_Missile( centity_t *cent ) { refEntity_t ent; entityState_t *s1; const weaponInfo_t *weapon; // int col; s1 = ¢->currentState; if ( s1->weapon > WP_NUM_WEAPONS ) { s1->weapon = 0; } weapon = &cg_weapons[s1->weapon]; // calculate the axis VectorCopy( s1->angles, cent->lerpAngles); // add trails if ( weapon->missileTrailFunc ) { weapon->missileTrailFunc( cent, weapon ); } /* if ( cent->currentState.modelindex == TEAM_RED ) { col = 1; } else if ( cent->currentState.modelindex == TEAM_BLUE ) { col = 2; } else { col = 0; } // add dynamic light if ( weapon->missileDlight ) { trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, weapon->missileDlightColor[col][0], weapon->missileDlightColor[col][1], weapon->missileDlightColor[col][2] ); } */ // add dynamic light if ( weapon->missileDlight ) { trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); } // add missile sound if ( weapon->missileSound ) { vec3_t velocity; BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); } // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); if ( cent->currentState.weapon == WP_PLASMAGUN ) { ent.reType = RT_SPRITE; ent.radius = 16; ent.rotation = 0; ent.customShader = cgs.media.plasmaBallShader; trap_R_AddRefEntityToScene( &ent ); return; } // flicker between two skins ent.skinNum = cg.clientFrame & 1; ent.hModel = weapon->missileModel; ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; #ifdef MISSIONPACK if ( cent->currentState.weapon == WP_PROX_LAUNCHER ) { if (s1->generic1 == TEAM_BLUE) { ent.hModel = cgs.media.blueProxMine; } } #endif // convert direction of travel into axis if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { ent.axis[0][2] = 1; } // spin as it moves if ( s1->pos.trType != TR_STATIONARY ) { RotateAroundDirection( ent.axis, cg.time / 4 ); } else { #ifdef MISSIONPACK if ( s1->weapon == WP_PROX_LAUNCHER ) { AnglesToAxis( cent->lerpAngles, ent.axis ); } else #endif { RotateAroundDirection( ent.axis, s1->time ); } } // add to refresh list, possibly with quad glow CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); } /* =============== CG_Grapple This is called when the grapple is sitting up against the wall =============== */ static void CG_Grapple( centity_t *cent ) { refEntity_t ent; entityState_t *s1; const weaponInfo_t *weapon; s1 = ¢->currentState; if ( s1->weapon > WP_NUM_WEAPONS ) { s1->weapon = 0; } weapon = &cg_weapons[s1->weapon]; // calculate the axis VectorCopy( s1->angles, cent->lerpAngles); #if 0 // FIXME add grapple pull sound here..? // add missile sound if ( weapon->missileSound ) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound ); } #endif // Will draw cable if needed CG_GrappleTrail ( cent, weapon ); // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); // flicker between two skins ent.skinNum = cg.clientFrame & 1; ent.hModel = weapon->missileModel; ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; // convert direction of travel into axis if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { ent.axis[0][2] = 1; } trap_R_AddRefEntityToScene( &ent ); } /* =============== CG_Mover =============== */ static void CG_Mover( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( cent->lerpOrigin, ent.origin); VectorCopy( cent->lerpOrigin, ent.oldorigin); AnglesToAxis( cent->lerpAngles, ent.axis ); ent.renderfx = RF_NOSHADOW; // flicker between two skins (FIXME?) ent.skinNum = ( cg.time >> 6 ) & 1; // get the model, either as a bmodel or a modelindex if ( s1->solid == SOLID_BMODEL ) { ent.hModel = cgs.inlineDrawModel[s1->modelindex]; } else { ent.hModel = cgs.gameModels[s1->modelindex]; } // add to refresh list trap_R_AddRefEntityToScene(&ent); // add the secondary model if ( s1->modelindex2 ) { ent.skinNum = 0; ent.hModel = cgs.gameModels[s1->modelindex2]; trap_R_AddRefEntityToScene(&ent); } } /* =============== CG_Beam Also called as an event =============== */ void CG_Beam( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( s1->pos.trBase, ent.origin ); VectorCopy( s1->origin2, ent.oldorigin ); AxisClear( ent.axis ); ent.reType = RT_BEAM; ent.renderfx = RF_NOSHADOW; // add to refresh list trap_R_AddRefEntityToScene(&ent); } /* =============== CG_Portal =============== */ static void CG_Portal( centity_t *cent ) { refEntity_t ent; entityState_t *s1; s1 = ¢->currentState; // create the render entity memset (&ent, 0, sizeof(ent)); VectorCopy( cent->lerpOrigin, ent.origin ); VectorCopy( s1->origin2, ent.oldorigin ); ByteToDir( s1->eventParm, ent.axis[0] ); PerpendicularVector( ent.axis[1], ent.axis[0] ); // negating this tends to get the directions like they want // we really should have a camera roll value VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); ent.reType = RT_PORTALSURFACE; ent.oldframe = s1->powerups; ent.frame = s1->frame; // rotation speed ent.skinNum = s1->clientNum/256.0 * 360; // roll offset // add to refresh list trap_R_AddRefEntityToScene(&ent); } /* ========================= CG_AdjustPositionForMover Also called by client movement prediction code ========================= */ void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) { centity_t *cent; vec3_t oldOrigin, origin, deltaOrigin; vec3_t oldAngles, angles, deltaAngles; if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { VectorCopy( in, out ); return; } cent = &cg_entities[ moverNum ]; if ( cent->currentState.eType != ET_MOVER ) { VectorCopy( in, out ); return; } BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); VectorSubtract( origin, oldOrigin, deltaOrigin ); VectorSubtract( angles, oldAngles, deltaAngles ); VectorAdd( in, deltaOrigin, out ); // FIXME: origin change when on a rotating object } /* ============================= CG_InterpolateEntityPosition ============================= */ static void CG_InterpolateEntityPosition( centity_t *cent ) { vec3_t current, next; float f; // it would be an internal error to find an entity that interpolates without // a snapshot ahead of the current one if ( cg.nextSnap == NULL ) { CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); } f = cg.frameInterpolation; // this will linearize a sine or parabolic curve, but it is important // to not extrapolate player positions if more recent data is available BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); } /* =============== CG_CalcEntityLerpPositions =============== */ static void CG_CalcEntityLerpPositions( centity_t *cent ) { // if this player does not want to see extrapolated players if ( !cg_smoothClients.integer ) { // make sure the clients use TR_INTERPOLATE if ( cent->currentState.number < MAX_CLIENTS ) { cent->currentState.pos.trType = TR_INTERPOLATE; cent->nextState.pos.trType = TR_INTERPOLATE; } } if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { CG_InterpolateEntityPosition( cent ); return; } // first see if we can interpolate between two snaps for // linear extrapolated clients if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && cent->currentState.number < MAX_CLIENTS) { CG_InterpolateEntityPosition( cent ); return; } // just use the current frame and evaluate as best we can BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); // adjust for riding a mover if it wasn't rolled into the predicted // player state if ( cent != &cg.predictedPlayerEntity ) { CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, cg.snap->serverTime, cg.time, cent->lerpOrigin ); } } /* =============== CG_TeamBase =============== */ static void CG_TeamBase( centity_t *cent ) { refEntity_t model; #ifdef MISSIONPACK vec3_t angles; int t, h; float c; if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) { #else if ( cgs.gametype == GT_CTF) { #endif // show the flag base memset(&model, 0, sizeof(model)); model.reType = RT_MODEL; VectorCopy( cent->lerpOrigin, model.lightingOrigin ); VectorCopy( cent->lerpOrigin, model.origin ); AnglesToAxis( cent->currentState.angles, model.axis ); if ( cent->currentState.modelindex == TEAM_RED ) { model.hModel = cgs.media.redFlagBaseModel; } else if ( cent->currentState.modelindex == TEAM_BLUE ) { model.hModel = cgs.media.blueFlagBaseModel; } else { model.hModel = cgs.media.neutralFlagBaseModel; } trap_R_AddRefEntityToScene( &model ); } #ifdef MISSIONPACK else if ( cgs.gametype == GT_OBELISK ) { // show the obelisk memset(&model, 0, sizeof(model)); model.reType = RT_MODEL; VectorCopy( cent->lerpOrigin, model.lightingOrigin ); VectorCopy( cent->lerpOrigin, model.origin ); AnglesToAxis( cent->currentState.angles, model.axis ); model.hModel = cgs.media.overloadBaseModel; trap_R_AddRefEntityToScene( &model ); // if hit if ( cent->currentState.frame == 1) { // show hit model // modelindex2 is the health value of the obelisk c = cent->currentState.modelindex2; model.shaderRGBA[0] = 0xff; model.shaderRGBA[1] = c; model.shaderRGBA[2] = c; model.shaderRGBA[3] = 0xff; // model.hModel = cgs.media.overloadEnergyModel; trap_R_AddRefEntityToScene( &model ); } // if respawning if ( cent->currentState.frame == 2) { if ( !cent->miscTime ) { cent->miscTime = cg.time; } t = cg.time - cent->miscTime; h = (cg_obeliskRespawnDelay.integer - 5) * 1000; // if (t > h) { c = (float) (t - h) / h; if (c > 1) c = 1; } else { c = 0; } // show the lights AnglesToAxis( cent->currentState.angles, model.axis ); // model.shaderRGBA[0] = c * 0xff; model.shaderRGBA[1] = c * 0xff; model.shaderRGBA[2] = c * 0xff; model.shaderRGBA[3] = c * 0xff; model.hModel = cgs.media.overloadLightsModel; trap_R_AddRefEntityToScene( &model ); // show the target if (t > h) { if ( !cent->muzzleFlashTime ) { trap_S_StartSound (cent->lerpOrigin, ENTITYNUM_NONE, CHAN_BODY, cgs.media.obeliskRespawnSound); cent->muzzleFlashTime = 1; } VectorCopy(cent->currentState.angles, angles); angles[YAW] += (float) 16 * acos(1-c) * 180 / M_PI; AnglesToAxis( angles, model.axis ); VectorScale( model.axis[0], c, model.axis[0]); VectorScale( model.axis[1], c, model.axis[1]); VectorScale( model.axis[2], c, model.axis[2]); model.shaderRGBA[0] = 0xff; model.shaderRGBA[1] = 0xff; model.shaderRGBA[2] = 0xff; model.shaderRGBA[3] = 0xff; // model.origin[2] += 56; model.hModel = cgs.media.overloadTargetModel; trap_R_AddRefEntityToScene( &model ); } else { //FIXME: show animated smoke } } else { cent->miscTime = 0; cent->muzzleFlashTime = 0; // modelindex2 is the health value of the obelisk c = cent->currentState.modelindex2; model.shaderRGBA[0] = 0xff; model.shaderRGBA[1] = c; model.shaderRGBA[2] = c; model.shaderRGBA[3] = 0xff; // show the lights model.hModel = cgs.media.overloadLightsModel; trap_R_AddRefEntityToScene( &model ); // show the target model.origin[2] += 56; model.hModel = cgs.media.overloadTargetModel; trap_R_AddRefEntityToScene( &model ); } } else if ( cgs.gametype == GT_HARVESTER ) { // show harvester model memset(&model, 0, sizeof(model)); model.reType = RT_MODEL; VectorCopy( cent->lerpOrigin, model.lightingOrigin ); VectorCopy( cent->lerpOrigin, model.origin ); AnglesToAxis( cent->currentState.angles, model.axis ); if ( cent->currentState.modelindex == TEAM_RED ) { model.hModel = cgs.media.harvesterModel; model.customSkin = cgs.media.harvesterRedSkin; } else if ( cent->currentState.modelindex == TEAM_BLUE ) { model.hModel = cgs.media.harvesterModel; model.customSkin = cgs.media.harvesterBlueSkin; } else { model.hModel = cgs.media.harvesterNeutralModel; model.customSkin = 0; } trap_R_AddRefEntityToScene( &model ); } #endif } /* =============== CG_AddCEntity =============== */ static void CG_AddCEntity( centity_t *cent ) { // event-only entities will have been dealt with already if ( cent->currentState.eType >= ET_EVENTS ) { return; } // calculate the current origin CG_CalcEntityLerpPositions( cent ); // add automatic effects CG_EntityEffects( cent ); switch ( cent->currentState.eType ) { default: CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); break; case ET_INVISIBLE: case ET_PUSH_TRIGGER: case ET_TELEPORT_TRIGGER: break; case ET_GENERAL: CG_General( cent ); break; case ET_PLAYER: CG_Player( cent ); break; case ET_ITEM: CG_Item( cent ); break; case ET_MISSILE: CG_Missile( cent ); break; case ET_MOVER: CG_Mover( cent ); break; case ET_BEAM: CG_Beam( cent ); break; case ET_PORTAL: CG_Portal( cent ); break; case ET_SPEAKER: CG_Speaker( cent ); break; case ET_GRAPPLE: CG_Grapple( cent ); break; case ET_TEAM: CG_TeamBase( cent ); break; } } /* =============== CG_AddPacketEntities =============== */ void CG_AddPacketEntities( void ) { int num; centity_t *cent; playerState_t *ps; // set cg.frameInterpolation if ( cg.nextSnap ) { int delta; delta = (cg.nextSnap->serverTime - cg.snap->serverTime); if ( delta == 0 ) { cg.frameInterpolation = 0; } else { cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; } } else { cg.frameInterpolation = 0; // actually, it should never be used, because // no entities should be marked as interpolating } // the auto-rotating items will all have the same axis cg.autoAngles[0] = 0; cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0; cg.autoAngles[2] = 0; cg.autoAnglesFast[0] = 0; cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f; cg.autoAnglesFast[2] = 0; AnglesToAxis( cg.autoAngles, cg.autoAxis ); AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); // generate and add the entity from the playerstate ps = &cg.predictedPlayerState; BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); CG_AddCEntity( &cg.predictedPlayerEntity ); // lerp the non-predicted value for lightning gun origins CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); // add each entity sent over by the server for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { cent = &cg_entities[ cg.snap->entities[ num ].number ]; CG_AddCEntity( cent ); } } ================================================ FILE: code/cgame/cg_event.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_event.c -- handle entity events at snapshot or playerstate transitions #include "cg_local.h" // for the voice chats #ifdef MISSIONPACK // bk001205 #include "../../ui/menudef.h" #endif //========================================================================== /* =================== CG_PlaceString Also called by scoreboard drawing =================== */ const char *CG_PlaceString( int rank ) { static char str[64]; char *s, *t; if ( rank & RANK_TIED_FLAG ) { rank &= ~RANK_TIED_FLAG; t = "Tied for "; } else { t = ""; } if ( rank == 1 ) { s = S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue } else if ( rank == 2 ) { s = S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red } else if ( rank == 3 ) { s = S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow } else if ( rank == 11 ) { s = "11th"; } else if ( rank == 12 ) { s = "12th"; } else if ( rank == 13 ) { s = "13th"; } else if ( rank % 10 == 1 ) { s = va("%ist", rank); } else if ( rank % 10 == 2 ) { s = va("%ind", rank); } else if ( rank % 10 == 3 ) { s = va("%ird", rank); } else { s = va("%ith", rank); } Com_sprintf( str, sizeof( str ), "%s%s", t, s ); return str; } /* ============= CG_Obituary ============= */ static void CG_Obituary( entityState_t *ent ) { int mod; int target, attacker; char *message; char *message2; const char *targetInfo; const char *attackerInfo; char targetName[32]; char attackerName[32]; gender_t gender; clientInfo_t *ci; target = ent->otherEntityNum; attacker = ent->otherEntityNum2; mod = ent->eventParm; if ( target < 0 || target >= MAX_CLIENTS ) { CG_Error( "CG_Obituary: target out of range" ); } ci = &cgs.clientinfo[target]; if ( attacker < 0 || attacker >= MAX_CLIENTS ) { attacker = ENTITYNUM_WORLD; attackerInfo = NULL; } else { attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); } targetInfo = CG_ConfigString( CS_PLAYERS + target ); if ( !targetInfo ) { return; } Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof(targetName) - 2); strcat( targetName, S_COLOR_WHITE ); message2 = ""; // check for single client messages switch( mod ) { case MOD_SUICIDE: message = "suicides"; break; case MOD_FALLING: message = "cratered"; break; case MOD_CRUSH: message = "was squished"; break; case MOD_WATER: message = "sank like a rock"; break; case MOD_SLIME: message = "melted"; break; case MOD_LAVA: message = "does a back flip into the lava"; break; case MOD_TARGET_LASER: message = "saw the light"; break; case MOD_TRIGGER_HURT: message = "was in the wrong place"; break; default: message = NULL; break; } if (attacker == target) { gender = ci->gender; switch (mod) { #ifdef MISSIONPACK case MOD_KAMIKAZE: message = "goes out with a bang"; break; #endif case MOD_GRENADE_SPLASH: if ( gender == GENDER_FEMALE ) message = "tripped on her own grenade"; else if ( gender == GENDER_NEUTER ) message = "tripped on its own grenade"; else message = "tripped on his own grenade"; break; case MOD_ROCKET_SPLASH: if ( gender == GENDER_FEMALE ) message = "blew herself up"; else if ( gender == GENDER_NEUTER ) message = "blew itself up"; else message = "blew himself up"; break; case MOD_PLASMA_SPLASH: if ( gender == GENDER_FEMALE ) message = "melted herself"; else if ( gender == GENDER_NEUTER ) message = "melted itself"; else message = "melted himself"; break; case MOD_BFG_SPLASH: message = "should have used a smaller gun"; break; #ifdef MISSIONPACK case MOD_PROXIMITY_MINE: if( gender == GENDER_FEMALE ) { message = "found her prox mine"; } else if ( gender == GENDER_NEUTER ) { message = "found it's prox mine"; } else { message = "found his prox mine"; } break; #endif default: if ( gender == GENDER_FEMALE ) message = "killed herself"; else if ( gender == GENDER_NEUTER ) message = "killed itself"; else message = "killed himself"; break; } } if (message) { CG_Printf( "%s %s.\n", targetName, message); return; } // check for kill messages from the current clientNum if ( attacker == cg.snap->ps.clientNum ) { char *s; if ( cgs.gametype < GT_TEAM ) { s = va("You fragged %s\n%s place with %i", targetName, CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), cg.snap->ps.persistant[PERS_SCORE] ); } else { s = va("You fragged %s", targetName ); } #ifdef MISSIONPACK if (!(cg_singlePlayerActive.integer && cg_cameraOrbit.integer)) { CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); } #else CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); #endif // print the text message as well } // check for double client messages if ( !attackerInfo ) { attacker = ENTITYNUM_WORLD; strcpy( attackerName, "noname" ); } else { Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof(attackerName) - 2); strcat( attackerName, S_COLOR_WHITE ); // check for kill messages about the current clientNum if ( target == cg.snap->ps.clientNum ) { Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); } } if ( attacker != ENTITYNUM_WORLD ) { switch (mod) { case MOD_GRAPPLE: message = "was caught by"; break; case MOD_GAUNTLET: message = "was pummeled by"; break; case MOD_MACHINEGUN: message = "was machinegunned by"; break; case MOD_SHOTGUN: message = "was gunned down by"; break; case MOD_GRENADE: message = "ate"; message2 = "'s grenade"; break; case MOD_GRENADE_SPLASH: message = "was shredded by"; message2 = "'s shrapnel"; break; case MOD_ROCKET: message = "ate"; message2 = "'s rocket"; break; case MOD_ROCKET_SPLASH: message = "almost dodged"; message2 = "'s rocket"; break; case MOD_PLASMA: message = "was melted by"; message2 = "'s plasmagun"; break; case MOD_PLASMA_SPLASH: message = "was melted by"; message2 = "'s plasmagun"; break; case MOD_RAILGUN: message = "was railed by"; break; case MOD_LIGHTNING: message = "was electrocuted by"; break; case MOD_BFG: case MOD_BFG_SPLASH: message = "was blasted by"; message2 = "'s BFG"; break; #ifdef MISSIONPACK case MOD_NAIL: message = "was nailed by"; break; case MOD_CHAINGUN: message = "got lead poisoning from"; message2 = "'s Chaingun"; break; case MOD_PROXIMITY_MINE: message = "was too close to"; message2 = "'s Prox Mine"; break; case MOD_KAMIKAZE: message = "falls to"; message2 = "'s Kamikaze blast"; break; case MOD_JUICED: message = "was juiced by"; break; #endif case MOD_TELEFRAG: message = "tried to invade"; message2 = "'s personal space"; break; default: message = "was killed by"; break; } if (message) { CG_Printf( "%s %s %s%s\n", targetName, message, attackerName, message2); return; } } // we don't know what it was CG_Printf( "%s died.\n", targetName ); } //========================================================================== /* =============== CG_UseItem =============== */ static void CG_UseItem( centity_t *cent ) { clientInfo_t *ci; int itemNum, clientNum; gitem_t *item; entityState_t *es; es = ¢->currentState; itemNum = (es->event & ~EV_EVENT_BITS) - EV_USE_ITEM0; if ( itemNum < 0 || itemNum > HI_NUM_HOLDABLE ) { itemNum = 0; } // print a message if the local player if ( es->number == cg.snap->ps.clientNum ) { if ( !itemNum ) { CG_CenterPrint( "No item to use", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); } else { item = BG_FindItemForHoldable( itemNum ); CG_CenterPrint( va("Use %s", item->pickup_name), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); } } switch ( itemNum ) { default: case HI_NONE: trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); break; case HI_TELEPORTER: break; case HI_MEDKIT: clientNum = cent->currentState.clientNum; if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { ci = &cgs.clientinfo[ clientNum ]; ci->medkitUsageTime = cg.time; } trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.medkitSound ); break; #ifdef MISSIONPACK case HI_KAMIKAZE: break; case HI_PORTAL: break; case HI_INVULNERABILITY: trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useInvulnerabilitySound ); break; #endif } } /* ================ CG_ItemPickup A new item was picked up this frame ================ */ static void CG_ItemPickup( int itemNum ) { cg.itemPickup = itemNum; cg.itemPickupTime = cg.time; cg.itemPickupBlendTime = cg.time; // see if it should be the grabbed weapon if ( bg_itemlist[itemNum].giType == IT_WEAPON ) { // select it immediately if ( cg_autoswitch.integer && bg_itemlist[itemNum].giTag != WP_MACHINEGUN ) { cg.weaponSelectTime = cg.time; cg.weaponSelect = bg_itemlist[itemNum].giTag; } } } /* ================ CG_PainEvent Also called by playerstate transition ================ */ void CG_PainEvent( centity_t *cent, int health ) { char *snd; // don't do more than two pain sounds a second if ( cg.time - cent->pe.painTime < 500 ) { return; } if ( health < 25 ) { snd = "*pain25_1.wav"; } else if ( health < 50 ) { snd = "*pain50_1.wav"; } else if ( health < 75 ) { snd = "*pain75_1.wav"; } else { snd = "*pain100_1.wav"; } trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, CG_CustomSound( cent->currentState.number, snd ) ); // save pain time for programitic twitch animation cent->pe.painTime = cg.time; cent->pe.painDirection ^= 1; } /* ============== CG_EntityEvent An entity has an event value also called by CG_CheckPlayerstateEvents ============== */ #define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} void CG_EntityEvent( centity_t *cent, vec3_t position ) { entityState_t *es; int event; vec3_t dir; const char *s; int clientNum; clientInfo_t *ci; es = ¢->currentState; event = es->event & ~EV_EVENT_BITS; if ( cg_debugEvents.integer ) { CG_Printf( "ent:%3i event:%3i ", es->number, event ); } if ( !event ) { DEBUGNAME("ZEROEVENT"); return; } clientNum = es->clientNum; if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { clientNum = 0; } ci = &cgs.clientinfo[ clientNum ]; switch ( event ) { // // movement generated events // case EV_FOOTSTEP: DEBUGNAME("EV_FOOTSTEP"); if (cg_footsteps.integer) { trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ ci->footsteps ][rand()&3] ); } break; case EV_FOOTSTEP_METAL: DEBUGNAME("EV_FOOTSTEP_METAL"); if (cg_footsteps.integer) { trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_METAL ][rand()&3] ); } break; case EV_FOOTSPLASH: DEBUGNAME("EV_FOOTSPLASH"); if (cg_footsteps.integer) { trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); } break; case EV_FOOTWADE: DEBUGNAME("EV_FOOTWADE"); if (cg_footsteps.integer) { trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); } break; case EV_SWIM: DEBUGNAME("EV_SWIM"); if (cg_footsteps.integer) { trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); } break; case EV_FALL_SHORT: DEBUGNAME("EV_FALL_SHORT"); trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound ); if ( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes cg.landChange = -8; cg.landTime = cg.time; } break; case EV_FALL_MEDIUM: DEBUGNAME("EV_FALL_MEDIUM"); // use normal pain sound trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); if ( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes cg.landChange = -16; cg.landTime = cg.time; } break; case EV_FALL_FAR: DEBUGNAME("EV_FALL_FAR"); trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); cent->pe.painTime = cg.time; // don't play a pain sound right after this if ( clientNum == cg.predictedPlayerState.clientNum ) { // smooth landing z changes cg.landChange = -24; cg.landTime = cg.time; } break; case EV_STEP_4: case EV_STEP_8: case EV_STEP_12: case EV_STEP_16: // smooth out step up transitions DEBUGNAME("EV_STEP"); { float oldStep; int delta; int step; if ( clientNum != cg.predictedPlayerState.clientNum ) { break; } // if we are interpolating, we don't need to smooth steps if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) || cg_nopredict.integer || cg_synchronousClients.integer ) { break; } // check for stepping up before a previous step is completed delta = cg.time - cg.stepTime; if (delta < STEP_TIME) { oldStep = cg.stepChange * (STEP_TIME - delta) / STEP_TIME; } else { oldStep = 0; } // add this amount step = 4 * (event - EV_STEP_4 + 1 ); cg.stepChange = oldStep + step; if ( cg.stepChange > MAX_STEP_CHANGE ) { cg.stepChange = MAX_STEP_CHANGE; } cg.stepTime = cg.time; break; } case EV_JUMP_PAD: DEBUGNAME("EV_JUMP_PAD"); // CG_Printf( "EV_JUMP_PAD w/effect #%i\n", es->eventParm ); { localEntity_t *smoke; vec3_t up = {0, 0, 1}; smoke = CG_SmokePuff( cent->lerpOrigin, up, 32, 1, 1, 1, 0.33f, 1000, cg.time, 0, LEF_PUFF_DONT_SCALE, cgs.media.smokePuffShader ); } // boing sound at origin, jump sound on player trap_S_StartSound ( cent->lerpOrigin, -1, CHAN_VOICE, cgs.media.jumpPadSound ); trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); break; case EV_JUMP: DEBUGNAME("EV_JUMP"); trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); break; case EV_TAUNT: DEBUGNAME("EV_TAUNT"); trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); break; #ifdef MISSIONPACK case EV_TAUNT_YES: DEBUGNAME("EV_TAUNT_YES"); CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_YES); break; case EV_TAUNT_NO: DEBUGNAME("EV_TAUNT_NO"); CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_NO); break; case EV_TAUNT_FOLLOWME: DEBUGNAME("EV_TAUNT_FOLLOWME"); CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_FOLLOWME); break; case EV_TAUNT_GETFLAG: DEBUGNAME("EV_TAUNT_GETFLAG"); CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONGETFLAG); break; case EV_TAUNT_GUARDBASE: DEBUGNAME("EV_TAUNT_GUARDBASE"); CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONDEFENSE); break; case EV_TAUNT_PATROL: DEBUGNAME("EV_TAUNT_PATROL"); CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONPATROL); break; #endif case EV_WATER_TOUCH: DEBUGNAME("EV_WATER_TOUCH"); trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); break; case EV_WATER_LEAVE: DEBUGNAME("EV_WATER_LEAVE"); trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); break; case EV_WATER_UNDER: DEBUGNAME("EV_WATER_UNDER"); trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); break; case EV_WATER_CLEAR: DEBUGNAME("EV_WATER_CLEAR"); trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); break; case EV_ITEM_PICKUP: DEBUGNAME("EV_ITEM_PICKUP"); { gitem_t *item; int index; index = es->eventParm; // player predicted if ( index < 1 || index >= bg_numItems ) { break; } item = &bg_itemlist[ index ]; // powerups and team items will have a separate global sound, this one // will be played at prediction time if ( item->giType == IT_POWERUP || item->giType == IT_TEAM) { trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.n_healthSound ); } else if (item->giType == IT_PERSISTANT_POWERUP) { #ifdef MISSIONPACK switch (item->giTag ) { case PW_SCOUT: trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.scoutSound ); break; case PW_GUARD: trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.guardSound ); break; case PW_DOUBLER: trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.doublerSound ); break; case PW_AMMOREGEN: trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.ammoregenSound ); break; } #endif } else { trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); } // show icon and name on status bar if ( es->number == cg.snap->ps.clientNum ) { CG_ItemPickup( index ); } } break; case EV_GLOBAL_ITEM_PICKUP: DEBUGNAME("EV_GLOBAL_ITEM_PICKUP"); { gitem_t *item; int index; index = es->eventParm; // player predicted if ( index < 1 || index >= bg_numItems ) { break; } item = &bg_itemlist[ index ]; // powerup pickups are global if( item->pickup_sound ) { trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); } // show icon and name on status bar if ( es->number == cg.snap->ps.clientNum ) { CG_ItemPickup( index ); } } break; // // weapon events // case EV_NOAMMO: DEBUGNAME("EV_NOAMMO"); // trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound ); if ( es->number == cg.snap->ps.clientNum ) { CG_OutOfAmmoChange(); } break; case EV_CHANGE_WEAPON: DEBUGNAME("EV_CHANGE_WEAPON"); trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); break; case EV_FIRE_WEAPON: DEBUGNAME("EV_FIRE_WEAPON"); CG_FireWeapon( cent ); break; case EV_USE_ITEM0: DEBUGNAME("EV_USE_ITEM0"); CG_UseItem( cent ); break; case EV_USE_ITEM1: DEBUGNAME("EV_USE_ITEM1"); CG_UseItem( cent ); break; case EV_USE_ITEM2: DEBUGNAME("EV_USE_ITEM2"); CG_UseItem( cent ); break; case EV_USE_ITEM3: DEBUGNAME("EV_USE_ITEM3"); CG_UseItem( cent ); break; case EV_USE_ITEM4: DEBUGNAME("EV_USE_ITEM4"); CG_UseItem( cent ); break; case EV_USE_ITEM5: DEBUGNAME("EV_USE_ITEM5"); CG_UseItem( cent ); break; case EV_USE_ITEM6: DEBUGNAME("EV_USE_ITEM6"); CG_UseItem( cent ); break; case EV_USE_ITEM7: DEBUGNAME("EV_USE_ITEM7"); CG_UseItem( cent ); break; case EV_USE_ITEM8: DEBUGNAME("EV_USE_ITEM8"); CG_UseItem( cent ); break; case EV_USE_ITEM9: DEBUGNAME("EV_USE_ITEM9"); CG_UseItem( cent ); break; case EV_USE_ITEM10: DEBUGNAME("EV_USE_ITEM10"); CG_UseItem( cent ); break; case EV_USE_ITEM11: DEBUGNAME("EV_USE_ITEM11"); CG_UseItem( cent ); break; case EV_USE_ITEM12: DEBUGNAME("EV_USE_ITEM12"); CG_UseItem( cent ); break; case EV_USE_ITEM13: DEBUGNAME("EV_USE_ITEM13"); CG_UseItem( cent ); break; case EV_USE_ITEM14: DEBUGNAME("EV_USE_ITEM14"); CG_UseItem( cent ); break; //================================================================= // // other events // case EV_PLAYER_TELEPORT_IN: DEBUGNAME("EV_PLAYER_TELEPORT_IN"); trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); CG_SpawnEffect( position); break; case EV_PLAYER_TELEPORT_OUT: DEBUGNAME("EV_PLAYER_TELEPORT_OUT"); trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); CG_SpawnEffect( position); break; case EV_ITEM_POP: DEBUGNAME("EV_ITEM_POP"); trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); break; case EV_ITEM_RESPAWN: DEBUGNAME("EV_ITEM_RESPAWN"); cent->miscTime = cg.time; // scale up from this trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); break; case EV_GRENADE_BOUNCE: DEBUGNAME("EV_GRENADE_BOUNCE"); if ( rand() & 1 ) { trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb1aSound ); } else { trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb2aSound ); } break; #ifdef MISSIONPACK case EV_PROXIMITY_MINE_STICK: DEBUGNAME("EV_PROXIMITY_MINE_STICK"); if( es->eventParm & SURF_FLESH ) { trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimplSound ); } else if( es->eventParm & SURF_METALSTEPS ) { trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpmSound ); } else { trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpdSound ); } break; case EV_PROXIMITY_MINE_TRIGGER: DEBUGNAME("EV_PROXIMITY_MINE_TRIGGER"); trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbactvSound ); break; case EV_KAMIKAZE: DEBUGNAME("EV_KAMIKAZE"); CG_KamikazeEffect( cent->lerpOrigin ); break; case EV_OBELISKEXPLODE: DEBUGNAME("EV_OBELISKEXPLODE"); CG_ObeliskExplode( cent->lerpOrigin, es->eventParm ); break; case EV_OBELISKPAIN: DEBUGNAME("EV_OBELISKPAIN"); CG_ObeliskPain( cent->lerpOrigin ); break; case EV_INVUL_IMPACT: DEBUGNAME("EV_INVUL_IMPACT"); CG_InvulnerabilityImpact( cent->lerpOrigin, cent->currentState.angles ); break; case EV_JUICED: DEBUGNAME("EV_JUICED"); CG_InvulnerabilityJuiced( cent->lerpOrigin ); break; case EV_LIGHTNINGBOLT: DEBUGNAME("EV_LIGHTNINGBOLT"); CG_LightningBoltBeam(es->origin2, es->pos.trBase); break; #endif case EV_SCOREPLUM: DEBUGNAME("EV_SCOREPLUM"); CG_ScorePlum( cent->currentState.otherEntityNum, cent->lerpOrigin, cent->currentState.time ); break; // // missile impacts // case EV_MISSILE_HIT: DEBUGNAME("EV_MISSILE_HIT"); ByteToDir( es->eventParm, dir ); CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum ); break; case EV_MISSILE_MISS: DEBUGNAME("EV_MISSILE_MISS"); ByteToDir( es->eventParm, dir ); CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT ); break; case EV_MISSILE_MISS_METAL: DEBUGNAME("EV_MISSILE_MISS_METAL"); ByteToDir( es->eventParm, dir ); CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_METAL ); break; case EV_RAILTRAIL: DEBUGNAME("EV_RAILTRAIL"); cent->currentState.weapon = WP_RAILGUN; // if the end was on a nomark surface, don't make an explosion CG_RailTrail( ci, es->origin2, es->pos.trBase ); if ( es->eventParm != 255 ) { ByteToDir( es->eventParm, dir ); CG_MissileHitWall( es->weapon, es->clientNum, position, dir, IMPACTSOUND_DEFAULT ); } break; case EV_BULLET_HIT_WALL: DEBUGNAME("EV_BULLET_HIT_WALL"); ByteToDir( es->eventParm, dir ); CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD ); break; case EV_BULLET_HIT_FLESH: DEBUGNAME("EV_BULLET_HIT_FLESH"); CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm ); break; case EV_SHOTGUN: DEBUGNAME("EV_SHOTGUN"); CG_ShotgunFire( es ); break; case EV_GENERAL_SOUND: DEBUGNAME("EV_GENERAL_SOUND"); if ( cgs.gameSounds[ es->eventParm ] ) { trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); } else { s = CG_ConfigString( CS_SOUNDS + es->eventParm ); trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); } break; case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes DEBUGNAME("EV_GLOBAL_SOUND"); if ( cgs.gameSounds[ es->eventParm ] ) { trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); } else { s = CG_ConfigString( CS_SOUNDS + es->eventParm ); trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); } break; case EV_GLOBAL_TEAM_SOUND: // play from the player's head so it never diminishes { DEBUGNAME("EV_GLOBAL_TEAM_SOUND"); switch( es->eventParm ) { case GTS_RED_CAPTURE: // CTF: red team captured the blue flag, 1FCTF: red team captured the neutral flag if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) CG_AddBufferedSound( cgs.media.captureYourTeamSound ); else CG_AddBufferedSound( cgs.media.captureOpponentSound ); break; case GTS_BLUE_CAPTURE: // CTF: blue team captured the red flag, 1FCTF: blue team captured the neutral flag if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) CG_AddBufferedSound( cgs.media.captureYourTeamSound ); else CG_AddBufferedSound( cgs.media.captureOpponentSound ); break; case GTS_RED_RETURN: // CTF: blue flag returned, 1FCTF: never used if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) CG_AddBufferedSound( cgs.media.returnYourTeamSound ); else CG_AddBufferedSound( cgs.media.returnOpponentSound ); // CG_AddBufferedSound( cgs.media.blueFlagReturnedSound ); break; case GTS_BLUE_RETURN: // CTF red flag returned, 1FCTF: neutral flag returned if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) CG_AddBufferedSound( cgs.media.returnYourTeamSound ); else CG_AddBufferedSound( cgs.media.returnOpponentSound ); // CG_AddBufferedSound( cgs.media.redFlagReturnedSound ); break; case GTS_RED_TAKEN: // CTF: red team took blue flag, 1FCTF: blue team took the neutral flag // if this player picked up the flag then a sound is played in CG_CheckLocalSounds if (cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { } else { if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { #ifdef MISSIONPACK if (cgs.gametype == GT_1FCTF) CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); else #endif CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); } else if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { #ifdef MISSIONPACK if (cgs.gametype == GT_1FCTF) CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); else #endif CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); } } break; case GTS_BLUE_TAKEN: // CTF: blue team took the red flag, 1FCTF red team took the neutral flag // if this player picked up the flag then a sound is played in CG_CheckLocalSounds if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { } else { if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { #ifdef MISSIONPACK if (cgs.gametype == GT_1FCTF) CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); else #endif CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); } else if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { #ifdef MISSIONPACK if (cgs.gametype == GT_1FCTF) CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); else #endif CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); } } break; case GTS_REDOBELISK_ATTACKED: // Overload: red obelisk is being attacked if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); } break; case GTS_BLUEOBELISK_ATTACKED: // Overload: blue obelisk is being attacked if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); } break; case GTS_REDTEAM_SCORED: CG_AddBufferedSound(cgs.media.redScoredSound); break; case GTS_BLUETEAM_SCORED: CG_AddBufferedSound(cgs.media.blueScoredSound); break; case GTS_REDTEAM_TOOK_LEAD: CG_AddBufferedSound(cgs.media.redLeadsSound); break; case GTS_BLUETEAM_TOOK_LEAD: CG_AddBufferedSound(cgs.media.blueLeadsSound); break; case GTS_TEAMS_ARE_TIED: CG_AddBufferedSound( cgs.media.teamsTiedSound ); break; #ifdef MISSIONPACK case GTS_KAMIKAZE: trap_S_StartLocalSound(cgs.media.kamikazeFarSound, CHAN_ANNOUNCER); break; #endif default: break; } break; } case EV_PAIN: // local player sounds are triggered in CG_CheckLocalSounds, // so ignore events on the player DEBUGNAME("EV_PAIN"); if ( cent->currentState.number != cg.snap->ps.clientNum ) { CG_PainEvent( cent, es->eventParm ); } break; case EV_DEATH1: case EV_DEATH2: case EV_DEATH3: DEBUGNAME("EV_DEATHx"); trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, va("*death%i.wav", event - EV_DEATH1 + 1) ) ); break; case EV_OBITUARY: DEBUGNAME("EV_OBITUARY"); CG_Obituary( es ); break; // // powerup events // case EV_POWERUP_QUAD: DEBUGNAME("EV_POWERUP_QUAD"); if ( es->number == cg.snap->ps.clientNum ) { cg.powerupActive = PW_QUAD; cg.powerupTime = cg.time; } trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.quadSound ); break; case EV_POWERUP_BATTLESUIT: DEBUGNAME("EV_POWERUP_BATTLESUIT"); if ( es->number == cg.snap->ps.clientNum ) { cg.powerupActive = PW_BATTLESUIT; cg.powerupTime = cg.time; } trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.protectSound ); break; case EV_POWERUP_REGEN: DEBUGNAME("EV_POWERUP_REGEN"); if ( es->number == cg.snap->ps.clientNum ) { cg.powerupActive = PW_REGEN; cg.powerupTime = cg.time; } trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.regenSound ); break; case EV_GIB_PLAYER: DEBUGNAME("EV_GIB_PLAYER"); // don't play gib sound when using the kamikaze because it interferes // with the kamikaze sound, downside is that the gib sound will also // not be played when someone is gibbed while just carrying the kamikaze if ( !(es->eFlags & EF_KAMIKAZE) ) { trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); } CG_GibPlayer( cent->lerpOrigin ); break; case EV_STOPLOOPINGSOUND: DEBUGNAME("EV_STOPLOOPINGSOUND"); trap_S_StopLoopingSound( es->number ); es->loopSound = 0; break; case EV_DEBUG_LINE: DEBUGNAME("EV_DEBUG_LINE"); CG_Beam( cent ); break; default: DEBUGNAME("UNKNOWN"); CG_Error( "Unknown event: %i", event ); break; } } /* ============== CG_CheckEvents ============== */ void CG_CheckEvents( centity_t *cent ) { // check for event-only entities if ( cent->currentState.eType > ET_EVENTS ) { if ( cent->previousEvent ) { return; // already fired } // if this is a player event set the entity number of the client entity number if ( cent->currentState.eFlags & EF_PLAYER_EVENT ) { cent->currentState.number = cent->currentState.otherEntityNum; } cent->previousEvent = 1; cent->currentState.event = cent->currentState.eType - ET_EVENTS; } else { // check for events riding with another entity if ( cent->currentState.event == cent->previousEvent ) { return; } cent->previousEvent = cent->currentState.event; if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) { return; } } // calculate the position at exactly the frame time BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); CG_SetEntitySoundPosition( cent ); CG_EntityEvent( cent, cent->lerpOrigin ); } ================================================ FILE: code/cgame/cg_info.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_info.c -- display information while data is being loading #include "cg_local.h" #define MAX_LOADING_PLAYER_ICONS 16 #define MAX_LOADING_ITEM_ICONS 26 static int loadingPlayerIconCount; static int loadingItemIconCount; static qhandle_t loadingPlayerIcons[MAX_LOADING_PLAYER_ICONS]; static qhandle_t loadingItemIcons[MAX_LOADING_ITEM_ICONS]; /* =================== CG_DrawLoadingIcons =================== */ static void CG_DrawLoadingIcons( void ) { int n; int x, y; for( n = 0; n < loadingPlayerIconCount; n++ ) { x = 16 + n * 78; y = 324-40; CG_DrawPic( x, y, 64, 64, loadingPlayerIcons[n] ); } for( n = 0; n < loadingItemIconCount; n++ ) { y = 400-40; if( n >= 13 ) { y += 40; } x = 16 + n % 13 * 48; CG_DrawPic( x, y, 32, 32, loadingItemIcons[n] ); } } /* ====================== CG_LoadingString ====================== */ void CG_LoadingString( const char *s ) { Q_strncpyz( cg.infoScreenText, s, sizeof( cg.infoScreenText ) ); trap_UpdateScreen(); } /* =================== CG_LoadingItem =================== */ void CG_LoadingItem( int itemNum ) { gitem_t *item; item = &bg_itemlist[itemNum]; if ( item->icon && loadingItemIconCount < MAX_LOADING_ITEM_ICONS ) { loadingItemIcons[loadingItemIconCount++] = trap_R_RegisterShaderNoMip( item->icon ); } CG_LoadingString( item->pickup_name ); } /* =================== CG_LoadingClient =================== */ void CG_LoadingClient( int clientNum ) { const char *info; char *skin; char personality[MAX_QPATH]; char model[MAX_QPATH]; char iconName[MAX_QPATH]; info = CG_ConfigString( CS_PLAYERS + clientNum ); if ( loadingPlayerIconCount < MAX_LOADING_PLAYER_ICONS ) { Q_strncpyz( model, Info_ValueForKey( info, "model" ), sizeof( model ) ); skin = Q_strrchr( model, '/' ); if ( skin ) { *skin++ = '\0'; } else { skin = "default"; } Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", model, skin ); loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { Com_sprintf( iconName, MAX_QPATH, "models/players/characters/%s/icon_%s.tga", model, skin ); loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); } if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", DEFAULT_MODEL, "default" ); loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); } if ( loadingPlayerIcons[loadingPlayerIconCount] ) { loadingPlayerIconCount++; } } Q_strncpyz( personality, Info_ValueForKey( info, "n" ), sizeof(personality) ); Q_CleanStr( personality ); if( cgs.gametype == GT_SINGLE_PLAYER ) { trap_S_RegisterSound( va( "sound/player/announce/%s.wav", personality ), qtrue ); } CG_LoadingString( personality ); } /* ==================== CG_DrawInformation Draw all the status / pacifier stuff during level loading ==================== */ void CG_DrawInformation( void ) { const char *s; const char *info; const char *sysInfo; int y; int value; qhandle_t levelshot; qhandle_t detail; char buf[1024]; info = CG_ConfigString( CS_SERVERINFO ); sysInfo = CG_ConfigString( CS_SYSTEMINFO ); s = Info_ValueForKey( info, "mapname" ); levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) ); if ( !levelshot ) { levelshot = trap_R_RegisterShaderNoMip( "menu/art/unknownmap" ); } trap_R_SetColor( NULL ); CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, levelshot ); // blend a detail texture over it detail = trap_R_RegisterShader( "levelShotDetail" ); trap_R_DrawStretchPic( 0, 0, cgs.glconfig.vidWidth, cgs.glconfig.vidHeight, 0, 0, 2.5, 2, detail ); // draw the icons of things as they are loaded CG_DrawLoadingIcons(); // the first 150 rows are reserved for the client connection // screen to write into if ( cg.infoScreenText[0] ) { UI_DrawProportionalString( 320, 128-32, va("Loading... %s", cg.infoScreenText), UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); } else { UI_DrawProportionalString( 320, 128-32, "Awaiting snapshot...", UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); } // draw info string information y = 180-32; // don't print server lines if playing a local game trap_Cvar_VariableStringBuffer( "sv_running", buf, sizeof( buf ) ); if ( !atoi( buf ) ) { // server hostname Q_strncpyz(buf, Info_ValueForKey( info, "sv_hostname" ), 1024); Q_CleanStr(buf); UI_DrawProportionalString( 320, y, buf, UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); y += PROP_HEIGHT; // pure server s = Info_ValueForKey( sysInfo, "sv_pure" ); if ( s[0] == '1' ) { UI_DrawProportionalString( 320, y, "Pure Server", UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); y += PROP_HEIGHT; } // server-specific message of the day s = CG_ConfigString( CS_MOTD ); if ( s[0] ) { UI_DrawProportionalString( 320, y, s, UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); y += PROP_HEIGHT; } // some extra space after hostname and motd y += 10; } // map-specific message (long map name) s = CG_ConfigString( CS_MESSAGE ); if ( s[0] ) { UI_DrawProportionalString( 320, y, s, UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); y += PROP_HEIGHT; } // cheats warning s = Info_ValueForKey( sysInfo, "sv_cheats" ); if ( s[0] == '1' ) { UI_DrawProportionalString( 320, y, "CHEATS ARE ENABLED", UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); y += PROP_HEIGHT; } // game type switch ( cgs.gametype ) { case GT_FFA: s = "Free For All"; break; case GT_SINGLE_PLAYER: s = "Single Player"; break; case GT_TOURNAMENT: s = "Tournament"; break; case GT_TEAM: s = "Team Deathmatch"; break; case GT_CTF: s = "Capture The Flag"; break; #ifdef MISSIONPACK case GT_1FCTF: s = "One Flag CTF"; break; case GT_OBELISK: s = "Overload"; break; case GT_HARVESTER: s = "Harvester"; break; #endif default: s = "Unknown Gametype"; break; } UI_DrawProportionalString( 320, y, s, UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); y += PROP_HEIGHT; value = atoi( Info_ValueForKey( info, "timelimit" ) ); if ( value ) { UI_DrawProportionalString( 320, y, va( "timelimit %i", value ), UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); y += PROP_HEIGHT; } if (cgs.gametype < GT_CTF ) { value = atoi( Info_ValueForKey( info, "fraglimit" ) ); if ( value ) { UI_DrawProportionalString( 320, y, va( "fraglimit %i", value ), UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); y += PROP_HEIGHT; } } if (cgs.gametype >= GT_CTF) { value = atoi( Info_ValueForKey( info, "capturelimit" ) ); if ( value ) { UI_DrawProportionalString( 320, y, va( "capturelimit %i", value ), UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); y += PROP_HEIGHT; } } } ================================================ FILE: code/cgame/cg_local.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // #include "../game/q_shared.h" #include "tr_types.h" #include "../game/bg_public.h" #include "cg_public.h" // The entire cgame module is unloaded and reloaded on each level change, // so there is NO persistant data between levels on the client side. // If you absolutely need something stored, it can either be kept // by the server in the server stored userinfos, or stashed in a cvar. #ifdef MISSIONPACK #define CG_FONT_THRESHOLD 0.1 #endif #define POWERUP_BLINKS 5 #define POWERUP_BLINK_TIME 1000 #define FADE_TIME 200 #define PULSE_TIME 200 #define DAMAGE_DEFLECT_TIME 100 #define DAMAGE_RETURN_TIME 400 #define DAMAGE_TIME 500 #define LAND_DEFLECT_TIME 150 #define LAND_RETURN_TIME 300 #define STEP_TIME 200 #define DUCK_TIME 100 #define PAIN_TWITCH_TIME 200 #define WEAPON_SELECT_TIME 1400 #define ITEM_SCALEUP_TIME 1000 #define ZOOM_TIME 150 #define ITEM_BLOB_TIME 200 #define MUZZLE_FLASH_TIME 20 #define SINK_TIME 1000 // time for fragments to sink into ground before going away #define ATTACKER_HEAD_TIME 10000 #define REWARD_TIME 3000 #define PULSE_SCALE 1.5 // amount to scale up the icons when activating #define MAX_STEP_CHANGE 32 #define MAX_VERTS_ON_POLY 10 #define MAX_MARK_POLYS 256 #define STAT_MINUS 10 // num frame for '-' stats digit #define ICON_SIZE 48 #define CHAR_WIDTH 32 #define CHAR_HEIGHT 48 #define TEXT_ICON_SPACE 4 #define TEAMCHAT_WIDTH 80 #define TEAMCHAT_HEIGHT 8 // very large characters #define GIANT_WIDTH 32 #define GIANT_HEIGHT 48 #define NUM_CROSSHAIRS 10 #define TEAM_OVERLAY_MAXNAME_WIDTH 12 #define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 #define DEFAULT_MODEL "sarge" #ifdef MISSIONPACK #define DEFAULT_TEAM_MODEL "james" #define DEFAULT_TEAM_HEAD "*james" #else #define DEFAULT_TEAM_MODEL "sarge" #define DEFAULT_TEAM_HEAD "sarge" #endif #define DEFAULT_REDTEAM_NAME "Stroggs" #define DEFAULT_BLUETEAM_NAME "Pagans" typedef enum { FOOTSTEP_NORMAL, FOOTSTEP_BOOT, FOOTSTEP_FLESH, FOOTSTEP_MECH, FOOTSTEP_ENERGY, FOOTSTEP_METAL, FOOTSTEP_SPLASH, FOOTSTEP_TOTAL } footstep_t; typedef enum { IMPACTSOUND_DEFAULT, IMPACTSOUND_METAL, IMPACTSOUND_FLESH } impactSound_t; //================================================= // player entities need to track more information // than any other type of entity. // note that not every player entity is a client entity, // because corpses after respawn are outside the normal // client numbering range // when changing animation, set animationTime to frameTime + lerping time // The current lerp will finish out, then it will lerp to the new animation typedef struct { int oldFrame; int oldFrameTime; // time when ->oldFrame was exactly on int frame; int frameTime; // time when ->frame will be exactly on float backlerp; float yawAngle; qboolean yawing; float pitchAngle; qboolean pitching; int animationNumber; // may include ANIM_TOGGLEBIT animation_t *animation; int animationTime; // time when the first frame of the animation will be exact } lerpFrame_t; typedef struct { lerpFrame_t legs, torso, flag; int painTime; int painDirection; // flip from 0 to 1 int lightningFiring; // railgun trail spawning vec3_t railgunImpact; qboolean railgunFlash; // machinegun spinning float barrelAngle; int barrelTime; qboolean barrelSpinning; } playerEntity_t; //================================================= // centity_t have a direct corespondence with gentity_t in the game, but // only the entityState_t is directly communicated to the cgame typedef struct centity_s { entityState_t currentState; // from cg.frame entityState_t nextState; // from cg.nextFrame, if available qboolean interpolate; // true if next is valid to interpolate to qboolean currentValid; // true if cg.frame holds this entity int muzzleFlashTime; // move to playerEntity? int previousEvent; int teleportFlag; int trailTime; // so missile trails can handle dropped initial packets int dustTrailTime; int miscTime; int snapShotTime; // last time this entity was found in a snapshot playerEntity_t pe; int errorTime; // decay the error from this time vec3_t errorOrigin; vec3_t errorAngles; qboolean extrapolated; // false if origin / angles is an interpolation vec3_t rawOrigin; vec3_t rawAngles; vec3_t beamEnd; // exact interpolated position of entity on this frame vec3_t lerpOrigin; vec3_t lerpAngles; } centity_t; //====================================================================== // local entities are created as a result of events or predicted actions, // and live independantly from all server transmitted entities typedef struct markPoly_s { struct markPoly_s *prevMark, *nextMark; int time; qhandle_t markShader; qboolean alphaFade; // fade alpha instead of rgb float color[4]; poly_t poly; polyVert_t verts[MAX_VERTS_ON_POLY]; } markPoly_t; typedef enum { LE_MARK, LE_EXPLOSION, LE_SPRITE_EXPLOSION, LE_FRAGMENT, LE_MOVE_SCALE_FADE, LE_FALL_SCALE_FADE, LE_FADE_RGB, LE_SCALE_FADE, LE_SCOREPLUM, #ifdef MISSIONPACK LE_KAMIKAZE, LE_INVULIMPACT, LE_INVULJUICED, LE_SHOWREFENTITY #endif } leType_t; typedef enum { LEF_PUFF_DONT_SCALE = 0x0001, // do not scale size over time LEF_TUMBLE = 0x0002, // tumble over time, used for ejecting shells LEF_SOUND1 = 0x0004, // sound 1 for kamikaze LEF_SOUND2 = 0x0008 // sound 2 for kamikaze } leFlag_t; typedef enum { LEMT_NONE, LEMT_BURN, LEMT_BLOOD } leMarkType_t; // fragment local entities can leave marks on walls typedef enum { LEBS_NONE, LEBS_BLOOD, LEBS_BRASS } leBounceSoundType_t; // fragment local entities can make sounds on impacts typedef struct localEntity_s { struct localEntity_s *prev, *next; leType_t leType; int leFlags; int startTime; int endTime; int fadeInTime; float lifeRate; // 1.0 / (endTime - startTime) trajectory_t pos; trajectory_t angles; float bounceFactor; // 0.0 = no bounce, 1.0 = perfect float color[4]; float radius; float light; vec3_t lightColor; leMarkType_t leMarkType; // mark to leave on fragment impact leBounceSoundType_t leBounceSoundType; refEntity_t refEntity; } localEntity_t; //====================================================================== typedef struct { int client; int score; int ping; int time; int scoreFlags; int powerUps; int accuracy; int impressiveCount; int excellentCount; int guantletCount; int defendCount; int assistCount; int captures; qboolean perfect; int team; } score_t; // each client has an associated clientInfo_t // that contains media references necessary to present the // client model and other color coded effects // this is regenerated each time a client's configstring changes, // usually as a result of a userinfo (name, model, etc) change #define MAX_CUSTOM_SOUNDS 32 typedef struct { qboolean infoValid; char name[MAX_QPATH]; team_t team; int botSkill; // 0 = not bot, 1-5 = bot vec3_t color1; vec3_t color2; int score; // updated by score servercmds int location; // location index for team mode int health; // you only get this info about your teammates int armor; int curWeapon; int handicap; int wins, losses; // in tourney mode int teamTask; // task in teamplay (offence/defence) qboolean teamLeader; // true when this is a team leader int powerups; // so can display quad/flag status int medkitUsageTime; int invulnerabilityStartTime; int invulnerabilityStopTime; int breathPuffTime; // when clientinfo is changed, the loading of models/skins/sounds // can be deferred until you are dead, to prevent hitches in // gameplay char modelName[MAX_QPATH]; char skinName[MAX_QPATH]; char headModelName[MAX_QPATH]; char headSkinName[MAX_QPATH]; char redTeam[MAX_TEAMNAME]; char blueTeam[MAX_TEAMNAME]; qboolean deferred; qboolean newAnims; // true if using the new mission pack animations qboolean fixedlegs; // true if legs yaw is always the same as torso yaw qboolean fixedtorso; // true if torso never changes yaw vec3_t headOffset; // move head in icon views footstep_t footsteps; gender_t gender; // from model qhandle_t legsModel; qhandle_t legsSkin; qhandle_t torsoModel; qhandle_t torsoSkin; qhandle_t headModel; qhandle_t headSkin; qhandle_t modelIcon; animation_t animations[MAX_TOTALANIMATIONS]; sfxHandle_t sounds[MAX_CUSTOM_SOUNDS]; } clientInfo_t; // each WP_* weapon enum has an associated weaponInfo_t // that contains media references necessary to present the // weapon and its effects typedef struct weaponInfo_s { qboolean registered; gitem_t *item; qhandle_t handsModel; // the hands don't actually draw, they just position the weapon qhandle_t weaponModel; qhandle_t barrelModel; qhandle_t flashModel; vec3_t weaponMidpoint; // so it will rotate centered instead of by tag float flashDlight; vec3_t flashDlightColor; sfxHandle_t flashSound[4]; // fast firing weapons randomly choose qhandle_t weaponIcon; qhandle_t ammoIcon; qhandle_t ammoModel; qhandle_t missileModel; sfxHandle_t missileSound; void (*missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); float missileDlight; vec3_t missileDlightColor; int missileRenderfx; void (*ejectBrassFunc)( centity_t * ); float trailRadius; float wiTrailTime; sfxHandle_t readySound; sfxHandle_t firingSound; qboolean loopFireSound; } weaponInfo_t; // each IT_* item has an associated itemInfo_t // that constains media references necessary to present the // item and its effects typedef struct { qboolean registered; qhandle_t models[MAX_ITEM_MODELS]; qhandle_t icon; } itemInfo_t; typedef struct { int itemNum; } powerupInfo_t; #define MAX_SKULLTRAIL 10 typedef struct { vec3_t positions[MAX_SKULLTRAIL]; int numpositions; } skulltrail_t; #define MAX_REWARDSTACK 10 #define MAX_SOUNDBUFFER 20 //====================================================================== // all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action // occurs, and they will have visible effects for #define STEP_TIME or whatever msec after #define MAX_PREDICTED_EVENTS 16 typedef struct { int clientFrame; // incremented each frame int clientNum; qboolean demoPlayback; qboolean levelShot; // taking a level menu screenshot int deferredPlayerLoading; qboolean loading; // don't defer players at initial startup qboolean intermissionStarted; // don't play voice rewards, because game will end shortly // there are only one or two snapshot_t that are relevent at a time int latestSnapshotNum; // the number of snapshots the client system has received int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet snapshot_t *snap; // cg.snap->serverTime <= cg.time snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL snapshot_t activeSnapshots[2]; float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) / (cg.nextFrame->serverTime - cg.frame->serverTime) qboolean thisFrameTeleport; qboolean nextFrameTeleport; int frametime; // cg.time - cg.oldTime int time; // this is the time value that the client // is rendering at. int oldTime; // time at last frame, used for missile trails and prediction checking int physicsTime; // either cg.snap->time or cg.nextSnap->time int timelimitWarnings; // 5 min, 1 min, overtime int fraglimitWarnings; qboolean mapRestart; // set on a map restart to set back the weapon qboolean renderingThirdPerson; // during deaths, chasecams, etc // prediction state qboolean hyperspace; // true if prediction has hit a trigger_teleport playerState_t predictedPlayerState; centity_t predictedPlayerEntity; qboolean validPPS; // clear until the first call to CG_PredictPlayerState int predictedErrorTime; vec3_t predictedError; int eventSequence; int predictableEvents[MAX_PREDICTED_EVENTS]; float stepChange; // for stair up smoothing int stepTime; float duckChange; // for duck viewheight smoothing int duckTime; float landChange; // for landing hard int landTime; // input state sent to server int weaponSelect; // auto rotating items vec3_t autoAngles; vec3_t autoAxis[3]; vec3_t autoAnglesFast; vec3_t autoAxisFast[3]; // view rendering refdef_t refdef; vec3_t refdefViewAngles; // will be converted to refdef.viewaxis // zoom key qboolean zoomed; int zoomTime; float zoomSensitivity; // information screen text during loading char infoScreenText[MAX_STRING_CHARS]; // scoreboard int scoresRequestTime; int numScores; int selectedScore; int teamScores[2]; score_t scores[MAX_CLIENTS]; qboolean showScores; qboolean scoreBoardShowing; int scoreFadeTime; char killerName[MAX_NAME_LENGTH]; char spectatorList[MAX_STRING_CHARS]; // list of names int spectatorLen; // length of list float spectatorWidth; // width in device units int spectatorTime; // next time to offset int spectatorPaintX; // current paint x int spectatorPaintX2; // current paint x int spectatorOffset; // current offset from start int spectatorPaintLen; // current offset from start // skull trails skulltrail_t skulltrails[MAX_CLIENTS]; // centerprinting int centerPrintTime; int centerPrintCharWidth; int centerPrintY; char centerPrint[1024]; int centerPrintLines; // low ammo warning state int lowAmmoWarning; // 1 = low, 2 = empty // kill timers for carnage reward int lastKillTime; // crosshair client ID int crosshairClientNum; int crosshairClientTime; // powerup active flashing int powerupActive; int powerupTime; // attacking player int attackerTime; int voiceTime; // reward medals int rewardStack; int rewardTime; int rewardCount[MAX_REWARDSTACK]; qhandle_t rewardShader[MAX_REWARDSTACK]; qhandle_t rewardSound[MAX_REWARDSTACK]; // sound buffer mainly for announcer sounds int soundBufferIn; int soundBufferOut; int soundTime; qhandle_t soundBuffer[MAX_SOUNDBUFFER]; // for voice chat buffer int voiceChatTime; int voiceChatBufferIn; int voiceChatBufferOut; // warmup countdown int warmup; int warmupCount; //========================== int itemPickup; int itemPickupTime; int itemPickupBlendTime; // the pulse around the crosshair is timed seperately int weaponSelectTime; int weaponAnimation; int weaponAnimationTime; // blend blobs float damageTime; float damageX, damageY, damageValue; // status bar head float headYaw; float headEndPitch; float headEndYaw; int headEndTime; float headStartPitch; float headStartYaw; int headStartTime; // view movement float v_dmg_time; float v_dmg_pitch; float v_dmg_roll; vec3_t kick_angles; // weapon kicks vec3_t kick_origin; // temp working variables for player view float bobfracsin; int bobcycle; float xyspeed; int nextOrbitTime; //qboolean cameraMode; // if rendering from a loaded camera // development tool refEntity_t testModelEntity; char testModelName[MAX_QPATH]; qboolean testGun; } cg_t; // all of the model, shader, and sound references that are // loaded at gamestate time are stored in cgMedia_t // Other media that can be tied to clients, weapons, or items are // stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t typedef struct { qhandle_t charsetShader; qhandle_t charsetProp; qhandle_t charsetPropGlow; qhandle_t charsetPropB; qhandle_t whiteShader; qhandle_t redCubeModel; qhandle_t blueCubeModel; qhandle_t redCubeIcon; qhandle_t blueCubeIcon; qhandle_t redFlagModel; qhandle_t blueFlagModel; qhandle_t neutralFlagModel; qhandle_t redFlagShader[3]; qhandle_t blueFlagShader[3]; qhandle_t flagShader[4]; qhandle_t flagPoleModel; qhandle_t flagFlapModel; qhandle_t redFlagFlapSkin; qhandle_t blueFlagFlapSkin; qhandle_t neutralFlagFlapSkin; qhandle_t redFlagBaseModel; qhandle_t blueFlagBaseModel; qhandle_t neutralFlagBaseModel; #ifdef MISSIONPACK qhandle_t overloadBaseModel; qhandle_t overloadTargetModel; qhandle_t overloadLightsModel; qhandle_t overloadEnergyModel; qhandle_t harvesterModel; qhandle_t harvesterRedSkin; qhandle_t harvesterBlueSkin; qhandle_t harvesterNeutralModel; #endif qhandle_t armorModel; qhandle_t armorIcon; qhandle_t teamStatusBar; qhandle_t deferShader; // gib explosions qhandle_t gibAbdomen; qhandle_t gibArm; qhandle_t gibChest; qhandle_t gibFist; qhandle_t gibFoot; qhandle_t gibForearm; qhandle_t gibIntestine; qhandle_t gibLeg; qhandle_t gibSkull; qhandle_t gibBrain; qhandle_t smoke2; qhandle_t machinegunBrassModel; qhandle_t shotgunBrassModel; qhandle_t railRingsShader; qhandle_t railCoreShader; qhandle_t lightningShader; qhandle_t friendShader; qhandle_t balloonShader; qhandle_t connectionShader; qhandle_t selectShader; qhandle_t viewBloodShader; qhandle_t tracerShader; qhandle_t crosshairShader[NUM_CROSSHAIRS]; qhandle_t lagometerShader; qhandle_t backTileShader; qhandle_t noammoShader; qhandle_t smokePuffShader; qhandle_t smokePuffRageProShader; qhandle_t shotgunSmokePuffShader; qhandle_t plasmaBallShader; qhandle_t waterBubbleShader; qhandle_t bloodTrailShader; #ifdef MISSIONPACK qhandle_t nailPuffShader; qhandle_t blueProxMine; #endif qhandle_t numberShaders[11]; qhandle_t shadowMarkShader; qhandle_t botSkillShaders[5]; // wall mark shaders qhandle_t wakeMarkShader; qhandle_t bloodMarkShader; qhandle_t bulletMarkShader; qhandle_t burnMarkShader; qhandle_t holeMarkShader; qhandle_t energyMarkShader; // powerup shaders qhandle_t quadShader; qhandle_t redQuadShader; qhandle_t quadWeaponShader; qhandle_t invisShader; qhandle_t regenShader; qhandle_t battleSuitShader; qhandle_t battleWeaponShader; qhandle_t hastePuffShader; qhandle_t redKamikazeShader; qhandle_t blueKamikazeShader; // weapon effect models qhandle_t bulletFlashModel; qhandle_t ringFlashModel; qhandle_t dishFlashModel; qhandle_t lightningExplosionModel; // weapon effect shaders qhandle_t railExplosionShader; qhandle_t plasmaExplosionShader; qhandle_t bulletExplosionShader; qhandle_t rocketExplosionShader; qhandle_t grenadeExplosionShader; qhandle_t bfgExplosionShader; qhandle_t bloodExplosionShader; // special effects models qhandle_t teleportEffectModel; qhandle_t teleportEffectShader; #ifdef MISSIONPACK qhandle_t kamikazeEffectModel; qhandle_t kamikazeShockWave; qhandle_t kamikazeHeadModel; qhandle_t kamikazeHeadTrail; qhandle_t guardPowerupModel; qhandle_t scoutPowerupModel; qhandle_t doublerPowerupModel; qhandle_t ammoRegenPowerupModel; qhandle_t invulnerabilityImpactModel; qhandle_t invulnerabilityJuicedModel; qhandle_t medkitUsageModel; qhandle_t dustPuffShader; qhandle_t heartShader; #endif qhandle_t invulnerabilityPowerupModel; // scoreboard headers qhandle_t scoreboardName; qhandle_t scoreboardPing; qhandle_t scoreboardScore; qhandle_t scoreboardTime; // medals shown during gameplay qhandle_t medalImpressive; qhandle_t medalExcellent; qhandle_t medalGauntlet; qhandle_t medalDefend; qhandle_t medalAssist; qhandle_t medalCapture; // sounds sfxHandle_t quadSound; sfxHandle_t tracerSound; sfxHandle_t selectSound; sfxHandle_t useNothingSound; sfxHandle_t wearOffSound; sfxHandle_t footsteps[FOOTSTEP_TOTAL][4]; sfxHandle_t sfx_lghit1; sfxHandle_t sfx_lghit2; sfxHandle_t sfx_lghit3; sfxHandle_t sfx_ric1; sfxHandle_t sfx_ric2; sfxHandle_t sfx_ric3; sfxHandle_t sfx_railg; sfxHandle_t sfx_rockexp; sfxHandle_t sfx_plasmaexp; #ifdef MISSIONPACK sfxHandle_t sfx_proxexp; sfxHandle_t sfx_nghit; sfxHandle_t sfx_nghitflesh; sfxHandle_t sfx_nghitmetal; sfxHandle_t sfx_chghit; sfxHandle_t sfx_chghitflesh; sfxHandle_t sfx_chghitmetal; sfxHandle_t kamikazeExplodeSound; sfxHandle_t kamikazeImplodeSound; sfxHandle_t kamikazeFarSound; sfxHandle_t useInvulnerabilitySound; sfxHandle_t invulnerabilityImpactSound1; sfxHandle_t invulnerabilityImpactSound2; sfxHandle_t invulnerabilityImpactSound3; sfxHandle_t invulnerabilityJuicedSound; sfxHandle_t obeliskHitSound1; sfxHandle_t obeliskHitSound2; sfxHandle_t obeliskHitSound3; sfxHandle_t obeliskRespawnSound; sfxHandle_t winnerSound; sfxHandle_t loserSound; sfxHandle_t youSuckSound; #endif sfxHandle_t gibSound; sfxHandle_t gibBounce1Sound; sfxHandle_t gibBounce2Sound; sfxHandle_t gibBounce3Sound; sfxHandle_t teleInSound; sfxHandle_t teleOutSound; sfxHandle_t noAmmoSound; sfxHandle_t respawnSound; sfxHandle_t talkSound; sfxHandle_t landSound; sfxHandle_t fallSound; sfxHandle_t jumpPadSound; sfxHandle_t oneMinuteSound; sfxHandle_t fiveMinuteSound; sfxHandle_t suddenDeathSound; sfxHandle_t threeFragSound; sfxHandle_t twoFragSound; sfxHandle_t oneFragSound; sfxHandle_t hitSound; sfxHandle_t hitSoundHighArmor; sfxHandle_t hitSoundLowArmor; sfxHandle_t hitTeamSound; sfxHandle_t impressiveSound; sfxHandle_t excellentSound; sfxHandle_t deniedSound; sfxHandle_t humiliationSound; sfxHandle_t assistSound; sfxHandle_t defendSound; sfxHandle_t firstImpressiveSound; sfxHandle_t firstExcellentSound; sfxHandle_t firstHumiliationSound; sfxHandle_t takenLeadSound; sfxHandle_t tiedLeadSound; sfxHandle_t lostLeadSound; sfxHandle_t voteNow; sfxHandle_t votePassed; sfxHandle_t voteFailed; sfxHandle_t watrInSound; sfxHandle_t watrOutSound; sfxHandle_t watrUnSound; sfxHandle_t flightSound; sfxHandle_t medkitSound; sfxHandle_t weaponHoverSound; // teamplay sounds sfxHandle_t captureAwardSound; sfxHandle_t redScoredSound; sfxHandle_t blueScoredSound; sfxHandle_t redLeadsSound; sfxHandle_t blueLeadsSound; sfxHandle_t teamsTiedSound; sfxHandle_t captureYourTeamSound; sfxHandle_t captureOpponentSound; sfxHandle_t returnYourTeamSound; sfxHandle_t returnOpponentSound; sfxHandle_t takenYourTeamSound; sfxHandle_t takenOpponentSound; sfxHandle_t redFlagReturnedSound; sfxHandle_t blueFlagReturnedSound; sfxHandle_t neutralFlagReturnedSound; sfxHandle_t enemyTookYourFlagSound; sfxHandle_t enemyTookTheFlagSound; sfxHandle_t yourTeamTookEnemyFlagSound; sfxHandle_t yourTeamTookTheFlagSound; sfxHandle_t youHaveFlagSound; sfxHandle_t yourBaseIsUnderAttackSound; sfxHandle_t holyShitSound; // tournament sounds sfxHandle_t count3Sound; sfxHandle_t count2Sound; sfxHandle_t count1Sound; sfxHandle_t countFightSound; sfxHandle_t countPrepareSound; #ifdef MISSIONPACK // new stuff qhandle_t patrolShader; qhandle_t assaultShader; qhandle_t campShader; qhandle_t followShader; qhandle_t defendShader; qhandle_t teamLeaderShader; qhandle_t retrieveShader; qhandle_t escortShader; qhandle_t flagShaders[3]; sfxHandle_t countPrepareTeamSound; sfxHandle_t ammoregenSound; sfxHandle_t doublerSound; sfxHandle_t guardSound; sfxHandle_t scoutSound; #endif qhandle_t cursor; qhandle_t selectCursor; qhandle_t sizeCursor; sfxHandle_t regenSound; sfxHandle_t protectSound; sfxHandle_t n_healthSound; sfxHandle_t hgrenb1aSound; sfxHandle_t hgrenb2aSound; sfxHandle_t wstbimplSound; sfxHandle_t wstbimpmSound; sfxHandle_t wstbimpdSound; sfxHandle_t wstbactvSound; } cgMedia_t; // The client game static (cgs) structure hold everything // loaded or calculated from the gamestate. It will NOT // be cleared when a tournement restart is done, allowing // all clients to begin playing instantly typedef struct { gameState_t gameState; // gamestate from server glconfig_t glconfig; // rendering configuration float screenXScale; // derived from glconfig float screenYScale; float screenXBias; int serverCommandSequence; // reliable command stream counter int processedSnapshotNum;// the number of snapshots cgame has requested qboolean localServer; // detected on startup by checking sv_running // parsed from serverinfo gametype_t gametype; int dmflags; int teamflags; int fraglimit; int capturelimit; int timelimit; int maxclients; char mapname[MAX_QPATH]; char redTeam[MAX_QPATH]; char blueTeam[MAX_QPATH]; int voteTime; int voteYes; int voteNo; qboolean voteModified; // beep whenever changed char voteString[MAX_STRING_TOKENS]; int teamVoteTime[2]; int teamVoteYes[2]; int teamVoteNo[2]; qboolean teamVoteModified[2]; // beep whenever changed char teamVoteString[2][MAX_STRING_TOKENS]; int levelStartTime; int scores1, scores2; // from configstrings int redflag, blueflag; // flag status from configstrings int flagStatus; qboolean newHud; // // locally derived information from gamestate // qhandle_t gameModels[MAX_MODELS]; sfxHandle_t gameSounds[MAX_SOUNDS]; int numInlineModels; qhandle_t inlineDrawModel[MAX_MODELS]; vec3_t inlineModelMidpoints[MAX_MODELS]; clientInfo_t clientinfo[MAX_CLIENTS]; // teamchat width is *3 because of embedded color codes char teamChatMsgs[TEAMCHAT_HEIGHT][TEAMCHAT_WIDTH*3+1]; int teamChatMsgTimes[TEAMCHAT_HEIGHT]; int teamChatPos; int teamLastChatPos; int cursorX; int cursorY; qboolean eventHandling; qboolean mouseCaptured; qboolean sizingHud; void *capturedItem; qhandle_t activeCursor; // orders int currentOrder; qboolean orderPending; int orderTime; int currentVoiceClient; int acceptOrderTime; int acceptTask; int acceptLeader; char acceptVoice[MAX_NAME_LENGTH]; // media cgMedia_t media; } cgs_t; //============================================================================== extern cgs_t cgs; extern cg_t cg; extern centity_t cg_entities[MAX_GENTITIES]; extern weaponInfo_t cg_weapons[MAX_WEAPONS]; extern itemInfo_t cg_items[MAX_ITEMS]; extern markPoly_t cg_markPolys[MAX_MARK_POLYS]; extern vmCvar_t cg_centertime; extern vmCvar_t cg_runpitch; extern vmCvar_t cg_runroll; extern vmCvar_t cg_bobup; extern vmCvar_t cg_bobpitch; extern vmCvar_t cg_bobroll; extern vmCvar_t cg_swingSpeed; extern vmCvar_t cg_shadows; extern vmCvar_t cg_gibs; extern vmCvar_t cg_drawTimer; extern vmCvar_t cg_drawFPS; extern vmCvar_t cg_drawSnapshot; extern vmCvar_t cg_draw3dIcons; extern vmCvar_t cg_drawIcons; extern vmCvar_t cg_drawAmmoWarning; extern vmCvar_t cg_drawCrosshair; extern vmCvar_t cg_drawCrosshairNames; extern vmCvar_t cg_drawRewards; extern vmCvar_t cg_drawTeamOverlay; extern vmCvar_t cg_teamOverlayUserinfo; extern vmCvar_t cg_crosshairX; extern vmCvar_t cg_crosshairY; extern vmCvar_t cg_crosshairSize; extern vmCvar_t cg_crosshairHealth; extern vmCvar_t cg_drawStatus; extern vmCvar_t cg_draw2D; extern vmCvar_t cg_animSpeed; extern vmCvar_t cg_debugAnim; extern vmCvar_t cg_debugPosition; extern vmCvar_t cg_debugEvents; extern vmCvar_t cg_railTrailTime; extern vmCvar_t cg_errorDecay; extern vmCvar_t cg_nopredict; extern vmCvar_t cg_noPlayerAnims; extern vmCvar_t cg_showmiss; extern vmCvar_t cg_footsteps; extern vmCvar_t cg_addMarks; extern vmCvar_t cg_brassTime; extern vmCvar_t cg_gun_frame; extern vmCvar_t cg_gun_x; extern vmCvar_t cg_gun_y; extern vmCvar_t cg_gun_z; extern vmCvar_t cg_drawGun; extern vmCvar_t cg_viewsize; extern vmCvar_t cg_tracerChance; extern vmCvar_t cg_tracerWidth; extern vmCvar_t cg_tracerLength; extern vmCvar_t cg_autoswitch; extern vmCvar_t cg_ignore; extern vmCvar_t cg_simpleItems; extern vmCvar_t cg_fov; extern vmCvar_t cg_zoomFov; extern vmCvar_t cg_thirdPersonRange; extern vmCvar_t cg_thirdPersonAngle; extern vmCvar_t cg_thirdPerson; extern vmCvar_t cg_stereoSeparation; extern vmCvar_t cg_lagometer; extern vmCvar_t cg_drawAttacker; extern vmCvar_t cg_synchronousClients; extern vmCvar_t cg_teamChatTime; extern vmCvar_t cg_teamChatHeight; extern vmCvar_t cg_stats; extern vmCvar_t cg_forceModel; extern vmCvar_t cg_buildScript; extern vmCvar_t cg_paused; extern vmCvar_t cg_blood; extern vmCvar_t cg_predictItems; extern vmCvar_t cg_deferPlayers; extern vmCvar_t cg_drawFriend; extern vmCvar_t cg_teamChatsOnly; extern vmCvar_t cg_noVoiceChats; extern vmCvar_t cg_noVoiceText; extern vmCvar_t cg_scorePlum; extern vmCvar_t cg_smoothClients; extern vmCvar_t pmove_fixed; extern vmCvar_t pmove_msec; //extern vmCvar_t cg_pmove_fixed; extern vmCvar_t cg_cameraOrbit; extern vmCvar_t cg_cameraOrbitDelay; extern vmCvar_t cg_timescaleFadeEnd; extern vmCvar_t cg_timescaleFadeSpeed; extern vmCvar_t cg_timescale; extern vmCvar_t cg_cameraMode; extern vmCvar_t cg_smallFont; extern vmCvar_t cg_bigFont; extern vmCvar_t cg_noTaunt; extern vmCvar_t cg_noProjectileTrail; extern vmCvar_t cg_oldRail; extern vmCvar_t cg_oldRocket; extern vmCvar_t cg_oldPlasma; extern vmCvar_t cg_trueLightning; #ifdef MISSIONPACK extern vmCvar_t cg_redTeamName; extern vmCvar_t cg_blueTeamName; extern vmCvar_t cg_currentSelectedPlayer; extern vmCvar_t cg_currentSelectedPlayerName; extern vmCvar_t cg_singlePlayer; extern vmCvar_t cg_enableDust; extern vmCvar_t cg_enableBreath; extern vmCvar_t cg_singlePlayerActive; extern vmCvar_t cg_recordSPDemo; extern vmCvar_t cg_recordSPDemoName; extern vmCvar_t cg_obeliskRespawnDelay; #endif // // cg_main.c // const char *CG_ConfigString( int index ); const char *CG_Argv( int arg ); void QDECL CG_Printf( const char *msg, ... ); void QDECL CG_Error( const char *msg, ... ); void CG_StartMusic( void ); void CG_UpdateCvars( void ); int CG_CrosshairPlayer( void ); int CG_LastAttacker( void ); void CG_LoadMenus(const char *menuFile); void CG_KeyEvent(int key, qboolean down); void CG_MouseEvent(int x, int y); void CG_EventHandling(int type); void CG_RankRunFrame( void ); void CG_SetScoreSelection(void *menu); score_t *CG_GetSelectedScore(); void CG_BuildSpectatorString(); // // cg_view.c // void CG_TestModel_f (void); void CG_TestGun_f (void); void CG_TestModelNextFrame_f (void); void CG_TestModelPrevFrame_f (void); void CG_TestModelNextSkin_f (void); void CG_TestModelPrevSkin_f (void); void CG_ZoomDown_f( void ); void CG_ZoomUp_f( void ); void CG_AddBufferedSound( sfxHandle_t sfx); void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); // // cg_drawtools.c // void CG_AdjustFrom640( float *x, float *y, float *w, float *h ); void CG_FillRect( float x, float y, float width, float height, const float *color ); void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); void CG_DrawString( float x, float y, const char *string, float charWidth, float charHeight, const float *modulate ); void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ); void CG_DrawBigString( int x, int y, const char *s, float alpha ); void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); void CG_DrawSmallString( int x, int y, const char *s, float alpha ); void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ); int CG_DrawStrlen( const char *str ); float *CG_FadeColor( int startMsec, int totalMsec ); float *CG_TeamColor( int team ); void CG_TileClear( void ); void CG_ColorForHealth( vec4_t hcolor ); void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); void CG_DrawSides(float x, float y, float w, float h, float size); void CG_DrawTopBottom(float x, float y, float w, float h, float size); // // cg_draw.c, cg_newDraw.c // extern int sortedTeamPlayers[TEAM_MAXOVERLAY]; extern int numSortedTeamPlayers; extern int drawTeamOverlayModificationCount; extern char systemChat[256]; extern char teamChat1[256]; extern char teamChat2[256]; void CG_AddLagometerFrameInfo( void ); void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); void CG_CenterPrint( const char *str, int y, int charWidth ); void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ); void CG_DrawActive( stereoFrame_t stereoView ); void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ); void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ); void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle); void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style); int CG_Text_Width(const char *text, float scale, int limit); int CG_Text_Height(const char *text, float scale, int limit); void CG_SelectPrevPlayer(); void CG_SelectNextPlayer(); float CG_GetValue(int ownerDraw); qboolean CG_OwnerDrawVisible(int flags); void CG_RunMenuScript(char **args); void CG_ShowResponseHead(); void CG_SetPrintString(int type, const char *p); void CG_InitTeamChat(); void CG_GetTeamColor(vec4_t *color); const char *CG_GetGameStatusText(); const char *CG_GetKillerText(); void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ); void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader); void CG_CheckOrderPending(); const char *CG_GameTypeString(); qboolean CG_YourTeamHasFlag(); qboolean CG_OtherTeamHasFlag(); qhandle_t CG_StatusHandle(int task); // // cg_player.c // void CG_Player( centity_t *cent ); void CG_ResetPlayerEntity( centity_t *cent ); void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ); void CG_NewClientInfo( int clientNum ); sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); // // cg_predict.c // void CG_BuildSolidList( void ); int CG_PointContents( const vec3_t point, int passEntityNum ); void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int skipNumber, int mask ); void CG_PredictPlayerState( void ); void CG_LoadDeferredPlayers( void ); // // cg_events.c // void CG_CheckEvents( centity_t *cent ); const char *CG_PlaceString( int rank ); void CG_EntityEvent( centity_t *cent, vec3_t position ); void CG_PainEvent( centity_t *cent, int health ); // // cg_ents.c // void CG_SetEntitySoundPosition( centity_t *cent ); void CG_AddPacketEntities( void ); void CG_Beam( centity_t *cent ); void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ); void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName ); void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, qhandle_t parentModel, char *tagName ); // // cg_weapons.c // void CG_NextWeapon_f( void ); void CG_PrevWeapon_f( void ); void CG_Weapon_f( void ); void CG_RegisterWeapon( int weaponNum ); void CG_RegisterItemVisuals( int itemNum ); void CG_FireWeapon( centity_t *cent ); void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ); void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ); void CG_ShotgunFire( entityState_t *es ); void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ); void CG_RailTrail( clientInfo_t *ci, vec3_t start, vec3_t end ); void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ); void CG_AddViewWeapon (playerState_t *ps); void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ); void CG_DrawWeaponSelect( void ); void CG_OutOfAmmoChange( void ); // should this be in pmove? // // cg_marks.c // void CG_InitMarkPolys( void ); void CG_AddMarks( void ); void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, float orientation, float r, float g, float b, float a, qboolean alphaFade, float radius, qboolean temporary ); // // cg_localents.c // void CG_InitLocalEntities( void ); localEntity_t *CG_AllocLocalEntity( void ); void CG_AddLocalEntities( void ); // // cg_effects.c // localEntity_t *CG_SmokePuff( const vec3_t p, const vec3_t vel, float radius, float r, float g, float b, float a, float duration, int startTime, int fadeInTime, int leFlags, qhandle_t hShader ); void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ); void CG_SpawnEffect( vec3_t org ); #ifdef MISSIONPACK void CG_KamikazeEffect( vec3_t org ); void CG_ObeliskExplode( vec3_t org, int entityNum ); void CG_ObeliskPain( vec3_t org ); void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ); void CG_InvulnerabilityJuiced( vec3_t org ); void CG_LightningBoltBeam( vec3_t start, vec3_t end ); #endif void CG_ScorePlum( int client, vec3_t org, int score ); void CG_GibPlayer( vec3_t playerOrigin ); void CG_BigExplode( vec3_t playerOrigin ); void CG_Bleed( vec3_t origin, int entityNum ); localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, qhandle_t hModel, qhandle_t shader, int msec, qboolean isSprite ); // // cg_snapshot.c // void CG_ProcessSnapshots( void ); // // cg_info.c // void CG_LoadingString( const char *s ); void CG_LoadingItem( int itemNum ); void CG_LoadingClient( int clientNum ); void CG_DrawInformation( void ); // // cg_scoreboard.c // qboolean CG_DrawOldScoreboard( void ); void CG_DrawOldTourneyScoreboard( void ); // // cg_consolecmds.c // qboolean CG_ConsoleCommand( void ); void CG_InitConsoleCommands( void ); // // cg_servercmds.c // void CG_ExecuteNewServerCommands( int latestSequence ); void CG_ParseServerinfo( void ); void CG_SetConfigValues( void ); void CG_LoadVoiceChats( void ); void CG_ShaderStateChanged(void); void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ); void CG_PlayBufferedVoiceChats( void ); // // cg_playerstate.c // void CG_Respawn( void ); void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); void CG_CheckChangedPredictableEvents( playerState_t *ps ); //=============================================== // // system traps // These functions are how the cgame communicates with the main game system // // print message on the local console void trap_Print( const char *fmt ); // abort the game void trap_Error( const char *fmt ); // milliseconds should only be used for performance tuning, never // for anything game related. Get time from the CG_DrawActiveFrame parameter int trap_Milliseconds( void ); // console variable interaction void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); void trap_Cvar_Update( vmCvar_t *vmCvar ); void trap_Cvar_Set( const char *var_name, const char *value ); void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); // ServerCommand and ConsoleCommand parameter access int trap_Argc( void ); void trap_Argv( int n, char *buffer, int bufferLength ); void trap_Args( char *buffer, int bufferLength ); // filesystem access // returns length of file int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); void trap_FS_Read( void *buffer, int len, fileHandle_t f ); void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); void trap_FS_FCloseFile( fileHandle_t f ); int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t // add commands to the local console as if they were typed in // for map changing, etc. The command is not executed immediately, // but will be executed in order the next time console commands // are processed void trap_SendConsoleCommand( const char *text ); // register a command name so the console can perform command completion. // FIXME: replace this with a normal console command "defineCommand"? void trap_AddCommand( const char *cmdName ); // send a string to the server over the network void trap_SendClientCommand( const char *s ); // force a screen update, only used during gamestate load void trap_UpdateScreen( void ); // model collision void trap_CM_LoadMap( const char *mapname ); int trap_CM_NumInlineModels( void ); clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ); int trap_CM_PointContents( const vec3_t p, clipHandle_t model ); int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, clipHandle_t model, int brushmask ); void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, clipHandle_t model, int brushmask, const vec3_t origin, const vec3_t angles ); // Returns the projection of a polygon onto the solid brushes in the world int trap_CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ); // normal sounds will have their volume dynamically changed as their entity // moves and the listener moves void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); void trap_S_StopLoopingSound(int entnum); // a local sound is always played full volume void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); void trap_S_ClearLoopingSounds( qboolean killall ); void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); // respatialize recalculates the volumes of sound as they should be heard by the // given entityNum and position void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); // returns buzz if not found void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); // empty name stops music void trap_S_StopBackgroundTrack( void ); void trap_R_LoadWorldMap( const char *mapname ); // all media should be registered during level startup to prevent // hitches during gameplay qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found // a scene is built up by calls to R_ClearScene and the various R_Add functions. // Nothing is drawn until R_RenderScene is called. void trap_R_ClearScene( void ); void trap_R_AddRefEntityToScene( const refEntity_t *re ); // polys are intended for simple wall marks, not really for doing // significant construction void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); void trap_R_RenderScene( const refdef_t *fd ); void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ); void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ); void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); // The glconfig_t will not change during the life of a cgame. // If it needs to change, the entire cgame will be restarted, because // all the qhandle_t are then invalid. void trap_GetGlconfig( glconfig_t *glconfig ); // the gamestate should be grabbed at startup, and whenever a // configstring changes void trap_GetGameState( gameState_t *gamestate ); // cgame will poll each frame to see if a newer snapshot has arrived // that it is interested in. The time is returned seperately so that // snapshot latency can be calculated. void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); // a snapshot get can fail if the snapshot (or the entties it holds) is so // old that it has fallen out of the client system queue qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); // retrieve a text command from the server stream // the current snapshot will hold the number of the most recent command // qfalse can be returned if the client system handled the command // argc() / argv() can be used to examine the parameters of the command qboolean trap_GetServerCommand( int serverCommandNumber ); // returns the most recent command number that can be passed to GetUserCmd // this will always be at least one higher than the number in the current // snapshot, and it may be quite a few higher if it is a fast computer on // a lagged connection int trap_GetCurrentCmdNumber( void ); qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); // used for the weapon select and zoom void trap_SetUserCmdValue( int stateValue, float sensitivityScale ); // aids for VM testing void testPrintInt( char *string, int i ); void testPrintFloat( char *string, float f ); int trap_MemoryRemaining( void ); void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font); qboolean trap_Key_IsDown( int keynum ); int trap_Key_GetCatcher( void ); void trap_Key_SetCatcher( int catcher ); int trap_Key_GetKey( const char *binding ); typedef enum { SYSTEM_PRINT, CHAT_PRINT, TEAMCHAT_PRINT } q3print_t; // bk001201 - warning: useless keyword or type name in empty declaration int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); e_status trap_CIN_StopCinematic(int handle); e_status trap_CIN_RunCinematic (int handle); void trap_CIN_DrawCinematic (int handle); void trap_CIN_SetExtents (int handle, int x, int y, int w, int h); void trap_SnapVector( float *v ); qboolean trap_loadCamera(const char *name); void trap_startCamera(int time); qboolean trap_getCameraInfo(int time, vec3_t *origin, vec3_t *angles); qboolean trap_GetEntityToken( char *buffer, int bufferSize ); void CG_ClearParticles (void); void CG_AddParticles (void); void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum); void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent); void CG_AddParticleShrapnel (localEntity_t *le); void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent); void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration); void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed); void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir); void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha); void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd); extern qboolean initparticles; int CG_NewParticleArea ( int num ); ================================================ FILE: code/cgame/cg_localents.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_localents.c -- every frame, generate renderer commands for locally // processed entities, like smoke puffs, gibs, shells, etc. #include "cg_local.h" #define MAX_LOCAL_ENTITIES 512 localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES]; localEntity_t cg_activeLocalEntities; // double linked list localEntity_t *cg_freeLocalEntities; // single linked list /* =================== CG_InitLocalEntities This is called at startup and for tournement restarts =================== */ void CG_InitLocalEntities( void ) { int i; memset( cg_localEntities, 0, sizeof( cg_localEntities ) ); cg_activeLocalEntities.next = &cg_activeLocalEntities; cg_activeLocalEntities.prev = &cg_activeLocalEntities; cg_freeLocalEntities = cg_localEntities; for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) { cg_localEntities[i].next = &cg_localEntities[i+1]; } } /* ================== CG_FreeLocalEntity ================== */ void CG_FreeLocalEntity( localEntity_t *le ) { if ( !le->prev ) { CG_Error( "CG_FreeLocalEntity: not active" ); } // remove from the doubly linked active list le->prev->next = le->next; le->next->prev = le->prev; // the free list is only singly linked le->next = cg_freeLocalEntities; cg_freeLocalEntities = le; } /* =================== CG_AllocLocalEntity Will allways succeed, even if it requires freeing an old active entity =================== */ localEntity_t *CG_AllocLocalEntity( void ) { localEntity_t *le; if ( !cg_freeLocalEntities ) { // no free entities, so free the one at the end of the chain // remove the oldest active entity CG_FreeLocalEntity( cg_activeLocalEntities.prev ); } le = cg_freeLocalEntities; cg_freeLocalEntities = cg_freeLocalEntities->next; memset( le, 0, sizeof( *le ) ); // link into the active list le->next = cg_activeLocalEntities.next; le->prev = &cg_activeLocalEntities; cg_activeLocalEntities.next->prev = le; cg_activeLocalEntities.next = le; return le; } /* ==================================================================================== FRAGMENT PROCESSING A fragment localentity interacts with the environment in some way (hitting walls), or generates more localentities along a trail. ==================================================================================== */ /* ================ CG_BloodTrail Leave expanding blood puffs behind gibs ================ */ void CG_BloodTrail( localEntity_t *le ) { int t; int t2; int step; vec3_t newOrigin; localEntity_t *blood; step = 150; t = step * ( (cg.time - cg.frametime + step ) / step ); t2 = step * ( cg.time / step ); for ( ; t <= t2; t += step ) { BG_EvaluateTrajectory( &le->pos, t, newOrigin ); blood = CG_SmokePuff( newOrigin, vec3_origin, 20, // radius 1, 1, 1, 1, // color 2000, // trailTime t, // startTime 0, // fadeInTime 0, // flags cgs.media.bloodTrailShader ); // use the optimized version blood->leType = LE_FALL_SCALE_FADE; // drop a total of 40 units over its lifetime blood->pos.trDelta[2] = 40; } } /* ================ CG_FragmentBounceMark ================ */ void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { int radius; if ( le->leMarkType == LEMT_BLOOD ) { radius = 16 + (rand()&31); CG_ImpactMark( cgs.media.bloodMarkShader, trace->endpos, trace->plane.normal, random()*360, 1,1,1,1, qtrue, radius, qfalse ); } else if ( le->leMarkType == LEMT_BURN ) { radius = 8 + (rand()&15); CG_ImpactMark( cgs.media.burnMarkShader, trace->endpos, trace->plane.normal, random()*360, 1,1,1,1, qtrue, radius, qfalse ); } // don't allow a fragment to make multiple marks, or they // pile up while settling le->leMarkType = LEMT_NONE; } /* ================ CG_FragmentBounceSound ================ */ void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) { if ( le->leBounceSoundType == LEBS_BLOOD ) { // half the gibs will make splat sounds if ( rand() & 1 ) { int r = rand()&3; sfxHandle_t s; if ( r == 0 ) { s = cgs.media.gibBounce1Sound; } else if ( r == 1 ) { s = cgs.media.gibBounce2Sound; } else { s = cgs.media.gibBounce3Sound; } trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s ); } } else if ( le->leBounceSoundType == LEBS_BRASS ) { } // don't allow a fragment to make multiple bounce sounds, // or it gets too noisy as they settle le->leBounceSoundType = LEBS_NONE; } /* ================ CG_ReflectVelocity ================ */ void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { vec3_t velocity; float dot; int hitTime; // reflect the velocity on the trace plane hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction; BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity ); dot = DotProduct( velocity, trace->plane.normal ); VectorMA( velocity, -2*dot, trace->plane.normal, le->pos.trDelta ); VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta ); VectorCopy( trace->endpos, le->pos.trBase ); le->pos.trTime = cg.time; // check for stop, making sure that even on low FPS systems it doesn't bobble if ( trace->allsolid || ( trace->plane.normal[2] > 0 && ( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) { le->pos.trType = TR_STATIONARY; } else { } } /* ================ CG_AddFragment ================ */ void CG_AddFragment( localEntity_t *le ) { vec3_t newOrigin; trace_t trace; if ( le->pos.trType == TR_STATIONARY ) { // sink into the ground if near the removal time int t; float oldZ; t = le->endTime - cg.time; if ( t < SINK_TIME ) { // we must use an explicit lighting origin, otherwise the // lighting would be lost as soon as the origin went // into the ground VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin ); le->refEntity.renderfx |= RF_LIGHTING_ORIGIN; oldZ = le->refEntity.origin[2]; le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME ); trap_R_AddRefEntityToScene( &le->refEntity ); le->refEntity.origin[2] = oldZ; } else { trap_R_AddRefEntityToScene( &le->refEntity ); } return; } // calculate new position BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); // trace a line from previous position to new position CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); if ( trace.fraction == 1.0 ) { // still in free fall VectorCopy( newOrigin, le->refEntity.origin ); if ( le->leFlags & LEF_TUMBLE ) { vec3_t angles; BG_EvaluateTrajectory( &le->angles, cg.time, angles ); AnglesToAxis( angles, le->refEntity.axis ); } trap_R_AddRefEntityToScene( &le->refEntity ); // add a blood trail if ( le->leBounceSoundType == LEBS_BLOOD ) { CG_BloodTrail( le ); } return; } // if it is in a nodrop zone, remove it // this keeps gibs from waiting at the bottom of pits of death // and floating levels if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { CG_FreeLocalEntity( le ); return; } // leave a mark CG_FragmentBounceMark( le, &trace ); // do a bouncy sound CG_FragmentBounceSound( le, &trace ); // reflect the velocity on the trace plane CG_ReflectVelocity( le, &trace ); trap_R_AddRefEntityToScene( &le->refEntity ); } /* ===================================================================== TRIVIAL LOCAL ENTITIES These only do simple scaling or modulation before passing to the renderer ===================================================================== */ /* ==================== CG_AddFadeRGB ==================== */ void CG_AddFadeRGB( localEntity_t *le ) { refEntity_t *re; float c; re = &le->refEntity; c = ( le->endTime - cg.time ) * le->lifeRate; c *= 0xff; re->shaderRGBA[0] = le->color[0] * c; re->shaderRGBA[1] = le->color[1] * c; re->shaderRGBA[2] = le->color[2] * c; re->shaderRGBA[3] = le->color[3] * c; trap_R_AddRefEntityToScene( re ); } /* ================== CG_AddMoveScaleFade ================== */ static void CG_AddMoveScaleFade( localEntity_t *le ) { refEntity_t *re; float c; vec3_t delta; float len; re = &le->refEntity; if ( le->fadeInTime > le->startTime && cg.time < le->fadeInTime ) { // fade / grow time c = 1.0 - (float) ( le->fadeInTime - cg.time ) / ( le->fadeInTime - le->startTime ); } else { // fade / grow time c = ( le->endTime - cg.time ) * le->lifeRate; } re->shaderRGBA[3] = 0xff * c * le->color[3]; if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { re->radius = le->radius * ( 1.0 - c ) + 8; } BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); // if the view would be "inside" the sprite, kill the sprite // so it doesn't add too much overdraw VectorSubtract( re->origin, cg.refdef.vieworg, delta ); len = VectorLength( delta ); if ( len < le->radius ) { CG_FreeLocalEntity( le ); return; } trap_R_AddRefEntityToScene( re ); } /* =================== CG_AddScaleFade For rocket smokes that hang in place, fade out, and are removed if the view passes through them. There are often many of these, so it needs to be simple. =================== */ static void CG_AddScaleFade( localEntity_t *le ) { refEntity_t *re; float c; vec3_t delta; float len; re = &le->refEntity; // fade / grow time c = ( le->endTime - cg.time ) * le->lifeRate; re->shaderRGBA[3] = 0xff * c * le->color[3]; re->radius = le->radius * ( 1.0 - c ) + 8; // if the view would be "inside" the sprite, kill the sprite // so it doesn't add too much overdraw VectorSubtract( re->origin, cg.refdef.vieworg, delta ); len = VectorLength( delta ); if ( len < le->radius ) { CG_FreeLocalEntity( le ); return; } trap_R_AddRefEntityToScene( re ); } /* ================= CG_AddFallScaleFade This is just an optimized CG_AddMoveScaleFade For blood mists that drift down, fade out, and are removed if the view passes through them. There are often 100+ of these, so it needs to be simple. ================= */ static void CG_AddFallScaleFade( localEntity_t *le ) { refEntity_t *re; float c; vec3_t delta; float len; re = &le->refEntity; // fade time c = ( le->endTime - cg.time ) * le->lifeRate; re->shaderRGBA[3] = 0xff * c * le->color[3]; re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2]; re->radius = le->radius * ( 1.0 - c ) + 16; // if the view would be "inside" the sprite, kill the sprite // so it doesn't add too much overdraw VectorSubtract( re->origin, cg.refdef.vieworg, delta ); len = VectorLength( delta ); if ( len < le->radius ) { CG_FreeLocalEntity( le ); return; } trap_R_AddRefEntityToScene( re ); } /* ================ CG_AddExplosion ================ */ static void CG_AddExplosion( localEntity_t *ex ) { refEntity_t *ent; ent = &ex->refEntity; // add the entity trap_R_AddRefEntityToScene(ent); // add the dlight if ( ex->light ) { float light; light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime ); if ( light < 0.5 ) { light = 1.0; } else { light = 1.0 - ( light - 0.5 ) * 2; } light = ex->light * light; trap_R_AddLightToScene(ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2] ); } } /* ================ CG_AddSpriteExplosion ================ */ static void CG_AddSpriteExplosion( localEntity_t *le ) { refEntity_t re; float c; re = le->refEntity; c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime ); if ( c > 1 ) { c = 1.0; // can happen during connection problems } re.shaderRGBA[0] = 0xff; re.shaderRGBA[1] = 0xff; re.shaderRGBA[2] = 0xff; re.shaderRGBA[3] = 0xff * c * 0.33; re.reType = RT_SPRITE; re.radius = 42 * ( 1.0 - c ) + 30; trap_R_AddRefEntityToScene( &re ); // add the dlight if ( le->light ) { float light; light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); if ( light < 0.5 ) { light = 1.0; } else { light = 1.0 - ( light - 0.5 ) * 2; } light = le->light * light; trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] ); } } #ifdef MISSIONPACK /* ==================== CG_AddKamikaze ==================== */ void CG_AddKamikaze( localEntity_t *le ) { refEntity_t *re; refEntity_t shockwave; float c; vec3_t test, axis[3]; int t; re = &le->refEntity; t = cg.time - le->startTime; VectorClear( test ); AnglesToAxis( test, axis ); if (t > KAMI_SHOCKWAVE_STARTTIME && t < KAMI_SHOCKWAVE_ENDTIME) { if (!(le->leFlags & LEF_SOUND1)) { // trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); le->leFlags |= LEF_SOUND1; } // 1st kamikaze shockwave memset(&shockwave, 0, sizeof(shockwave)); shockwave.hModel = cgs.media.kamikazeShockWave; shockwave.reType = RT_MODEL; shockwave.shaderTime = re->shaderTime; VectorCopy(re->origin, shockwave.origin); c = (float)(t - KAMI_SHOCKWAVE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVE_STARTTIME); VectorScale( axis[0], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); VectorScale( axis[1], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); VectorScale( axis[2], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); shockwave.nonNormalizedAxes = qtrue; if (t > KAMI_SHOCKWAVEFADE_STARTTIME) { c = (float)(t - KAMI_SHOCKWAVEFADE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVEFADE_STARTTIME); } else { c = 0; } c *= 0xff; shockwave.shaderRGBA[0] = 0xff - c; shockwave.shaderRGBA[1] = 0xff - c; shockwave.shaderRGBA[2] = 0xff - c; shockwave.shaderRGBA[3] = 0xff - c; trap_R_AddRefEntityToScene( &shockwave ); } if (t > KAMI_EXPLODE_STARTTIME && t < KAMI_IMPLODE_ENDTIME) { // explosion and implosion c = ( le->endTime - cg.time ) * le->lifeRate; c *= 0xff; re->shaderRGBA[0] = le->color[0] * c; re->shaderRGBA[1] = le->color[1] * c; re->shaderRGBA[2] = le->color[2] * c; re->shaderRGBA[3] = le->color[3] * c; if( t < KAMI_IMPLODE_STARTTIME ) { c = (float)(t - KAMI_EXPLODE_STARTTIME) / (float)(KAMI_IMPLODE_STARTTIME - KAMI_EXPLODE_STARTTIME); } else { if (!(le->leFlags & LEF_SOUND2)) { // trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeImplodeSound ); trap_S_StartLocalSound(cgs.media.kamikazeImplodeSound, CHAN_AUTO); le->leFlags |= LEF_SOUND2; } c = (float)(KAMI_IMPLODE_ENDTIME - t) / (float) (KAMI_IMPLODE_ENDTIME - KAMI_IMPLODE_STARTTIME); } VectorScale( axis[0], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[0] ); VectorScale( axis[1], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[1] ); VectorScale( axis[2], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[2] ); re->nonNormalizedAxes = qtrue; trap_R_AddRefEntityToScene( re ); // add the dlight trap_R_AddLightToScene( re->origin, c * 1000.0, 1.0, 1.0, c ); } if (t > KAMI_SHOCKWAVE2_STARTTIME && t < KAMI_SHOCKWAVE2_ENDTIME) { // 2nd kamikaze shockwave if (le->angles.trBase[0] == 0 && le->angles.trBase[1] == 0 && le->angles.trBase[2] == 0) { le->angles.trBase[0] = random() * 360; le->angles.trBase[1] = random() * 360; le->angles.trBase[2] = random() * 360; } else { c = 0; } memset(&shockwave, 0, sizeof(shockwave)); shockwave.hModel = cgs.media.kamikazeShockWave; shockwave.reType = RT_MODEL; shockwave.shaderTime = re->shaderTime; VectorCopy(re->origin, shockwave.origin); test[0] = le->angles.trBase[0]; test[1] = le->angles.trBase[1]; test[2] = le->angles.trBase[2]; AnglesToAxis( test, axis ); c = (float)(t - KAMI_SHOCKWAVE2_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2_STARTTIME); VectorScale( axis[0], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); VectorScale( axis[1], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); VectorScale( axis[2], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); shockwave.nonNormalizedAxes = qtrue; if (t > KAMI_SHOCKWAVE2FADE_STARTTIME) { c = (float)(t - KAMI_SHOCKWAVE2FADE_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2FADE_STARTTIME); } else { c = 0; } c *= 0xff; shockwave.shaderRGBA[0] = 0xff - c; shockwave.shaderRGBA[1] = 0xff - c; shockwave.shaderRGBA[2] = 0xff - c; shockwave.shaderRGBA[3] = 0xff - c; trap_R_AddRefEntityToScene( &shockwave ); } } /* =================== CG_AddInvulnerabilityImpact =================== */ void CG_AddInvulnerabilityImpact( localEntity_t *le ) { trap_R_AddRefEntityToScene( &le->refEntity ); } /* =================== CG_AddInvulnerabilityJuiced =================== */ void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { int t; t = cg.time - le->startTime; if ( t > 3000 ) { le->refEntity.axis[0][0] = (float) 1.0 + 0.3 * (t - 3000) / 2000; le->refEntity.axis[1][1] = (float) 1.0 + 0.3 * (t - 3000) / 2000; le->refEntity.axis[2][2] = (float) 0.7 + 0.3 * (2000 - (t - 3000)) / 2000; } if ( t > 5000 ) { le->endTime = 0; CG_GibPlayer( le->refEntity.origin ); } else { trap_R_AddRefEntityToScene( &le->refEntity ); } } /* =================== CG_AddRefEntity =================== */ void CG_AddRefEntity( localEntity_t *le ) { if (le->endTime < cg.time) { CG_FreeLocalEntity( le ); return; } trap_R_AddRefEntityToScene( &le->refEntity ); } #endif /* =================== CG_AddScorePlum =================== */ #define NUMBER_SIZE 8 void CG_AddScorePlum( localEntity_t *le ) { refEntity_t *re; vec3_t origin, delta, dir, vec, up = {0, 0, 1}; float c, len; int i, score, digits[10], numdigits, negative; re = &le->refEntity; c = ( le->endTime - cg.time ) * le->lifeRate; score = le->radius; if (score < 0) { re->shaderRGBA[0] = 0xff; re->shaderRGBA[1] = 0x11; re->shaderRGBA[2] = 0x11; } else { re->shaderRGBA[0] = 0xff; re->shaderRGBA[1] = 0xff; re->shaderRGBA[2] = 0xff; if (score >= 50) { re->shaderRGBA[1] = 0; } else if (score >= 20) { re->shaderRGBA[0] = re->shaderRGBA[1] = 0; } else if (score >= 10) { re->shaderRGBA[2] = 0; } else if (score >= 2) { re->shaderRGBA[0] = re->shaderRGBA[2] = 0; } } if (c < 0.25) re->shaderRGBA[3] = 0xff * 4 * c; else re->shaderRGBA[3] = 0xff; re->radius = NUMBER_SIZE / 2; VectorCopy(le->pos.trBase, origin); origin[2] += 110 - c * 100; VectorSubtract(cg.refdef.vieworg, origin, dir); CrossProduct(dir, up, vec); VectorNormalize(vec); VectorMA(origin, -10 + 20 * sin(c * 2 * M_PI), vec, origin); // if the view would be "inside" the sprite, kill the sprite // so it doesn't add too much overdraw VectorSubtract( origin, cg.refdef.vieworg, delta ); len = VectorLength( delta ); if ( len < 20 ) { CG_FreeLocalEntity( le ); return; } negative = qfalse; if (score < 0) { negative = qtrue; score = -score; } for (numdigits = 0; !(numdigits && !score); numdigits++) { digits[numdigits] = score % 10; score = score / 10; } if (negative) { digits[numdigits] = 10; numdigits++; } for (i = 0; i < numdigits; i++) { VectorMA(origin, (float) (((float) numdigits / 2) - i) * NUMBER_SIZE, vec, re->origin); re->customShader = cgs.media.numberShaders[digits[numdigits-1-i]]; trap_R_AddRefEntityToScene( re ); } } //============================================================================== /* =================== CG_AddLocalEntities =================== */ void CG_AddLocalEntities( void ) { localEntity_t *le, *next; // walk the list backwards, so any new local entities generated // (trails, marks, etc) will be present this frame le = cg_activeLocalEntities.prev; for ( ; le != &cg_activeLocalEntities ; le = next ) { // grab next now, so if the local entity is freed we // still have it next = le->prev; if ( cg.time >= le->endTime ) { CG_FreeLocalEntity( le ); continue; } switch ( le->leType ) { default: CG_Error( "Bad leType: %i", le->leType ); break; case LE_MARK: break; case LE_SPRITE_EXPLOSION: CG_AddSpriteExplosion( le ); break; case LE_EXPLOSION: CG_AddExplosion( le ); break; case LE_FRAGMENT: // gibs and brass CG_AddFragment( le ); break; case LE_MOVE_SCALE_FADE: // water bubbles CG_AddMoveScaleFade( le ); break; case LE_FADE_RGB: // teleporters, railtrails CG_AddFadeRGB( le ); break; case LE_FALL_SCALE_FADE: // gib blood trails CG_AddFallScaleFade( le ); break; case LE_SCALE_FADE: // rocket trails CG_AddScaleFade( le ); break; case LE_SCOREPLUM: CG_AddScorePlum( le ); break; #ifdef MISSIONPACK case LE_KAMIKAZE: CG_AddKamikaze( le ); break; case LE_INVULIMPACT: CG_AddInvulnerabilityImpact( le ); break; case LE_INVULJUICED: CG_AddInvulnerabilityJuiced( le ); break; case LE_SHOWREFENTITY: CG_AddRefEntity( le ); break; #endif } } } ================================================ FILE: code/cgame/cg_main.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_main.c -- initialization and primary entry point for cgame #include "cg_local.h" #ifdef MISSIONPACK #include "../ui/ui_shared.h" // display context for new ui stuff displayContextDef_t cgDC; #endif int forceModelModificationCount = -1; void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); void CG_Shutdown( void ); /* ================ vmMain This is the only way control passes into the module. This must be the very first function compiled into the .q3vm file ================ */ int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) { switch ( command ) { case CG_INIT: CG_Init( arg0, arg1, arg2 ); return 0; case CG_SHUTDOWN: CG_Shutdown(); return 0; case CG_CONSOLE_COMMAND: return CG_ConsoleCommand(); case CG_DRAW_ACTIVE_FRAME: CG_DrawActiveFrame( arg0, arg1, arg2 ); return 0; case CG_CROSSHAIR_PLAYER: return CG_CrosshairPlayer(); case CG_LAST_ATTACKER: return CG_LastAttacker(); case CG_KEY_EVENT: CG_KeyEvent(arg0, arg1); return 0; case CG_MOUSE_EVENT: #ifdef MISSIONPACK cgDC.cursorx = cgs.cursorX; cgDC.cursory = cgs.cursorY; #endif CG_MouseEvent(arg0, arg1); return 0; case CG_EVENT_HANDLING: CG_EventHandling(arg0); return 0; default: CG_Error( "vmMain: unknown command %i", command ); break; } return -1; } cg_t cg; cgs_t cgs; centity_t cg_entities[MAX_GENTITIES]; weaponInfo_t cg_weapons[MAX_WEAPONS]; itemInfo_t cg_items[MAX_ITEMS]; vmCvar_t cg_railTrailTime; vmCvar_t cg_centertime; vmCvar_t cg_runpitch; vmCvar_t cg_runroll; vmCvar_t cg_bobup; vmCvar_t cg_bobpitch; vmCvar_t cg_bobroll; vmCvar_t cg_swingSpeed; vmCvar_t cg_shadows; vmCvar_t cg_gibs; vmCvar_t cg_drawTimer; vmCvar_t cg_drawFPS; vmCvar_t cg_drawSnapshot; vmCvar_t cg_draw3dIcons; vmCvar_t cg_drawIcons; vmCvar_t cg_drawAmmoWarning; vmCvar_t cg_drawCrosshair; vmCvar_t cg_drawCrosshairNames; vmCvar_t cg_drawRewards; vmCvar_t cg_crosshairSize; vmCvar_t cg_crosshairX; vmCvar_t cg_crosshairY; vmCvar_t cg_crosshairHealth; vmCvar_t cg_draw2D; vmCvar_t cg_drawStatus; vmCvar_t cg_animSpeed; vmCvar_t cg_debugAnim; vmCvar_t cg_debugPosition; vmCvar_t cg_debugEvents; vmCvar_t cg_errorDecay; vmCvar_t cg_nopredict; vmCvar_t cg_noPlayerAnims; vmCvar_t cg_showmiss; vmCvar_t cg_footsteps; vmCvar_t cg_addMarks; vmCvar_t cg_brassTime; vmCvar_t cg_viewsize; vmCvar_t cg_drawGun; vmCvar_t cg_gun_frame; vmCvar_t cg_gun_x; vmCvar_t cg_gun_y; vmCvar_t cg_gun_z; vmCvar_t cg_tracerChance; vmCvar_t cg_tracerWidth; vmCvar_t cg_tracerLength; vmCvar_t cg_autoswitch; vmCvar_t cg_ignore; vmCvar_t cg_simpleItems; vmCvar_t cg_fov; vmCvar_t cg_zoomFov; vmCvar_t cg_thirdPerson; vmCvar_t cg_thirdPersonRange; vmCvar_t cg_thirdPersonAngle; vmCvar_t cg_stereoSeparation; vmCvar_t cg_lagometer; vmCvar_t cg_drawAttacker; vmCvar_t cg_synchronousClients; vmCvar_t cg_teamChatTime; vmCvar_t cg_teamChatHeight; vmCvar_t cg_stats; vmCvar_t cg_buildScript; vmCvar_t cg_forceModel; vmCvar_t cg_paused; vmCvar_t cg_blood; vmCvar_t cg_predictItems; vmCvar_t cg_deferPlayers; vmCvar_t cg_drawTeamOverlay; vmCvar_t cg_teamOverlayUserinfo; vmCvar_t cg_drawFriend; vmCvar_t cg_teamChatsOnly; vmCvar_t cg_noVoiceChats; vmCvar_t cg_noVoiceText; vmCvar_t cg_hudFiles; vmCvar_t cg_scorePlum; vmCvar_t cg_smoothClients; vmCvar_t pmove_fixed; //vmCvar_t cg_pmove_fixed; vmCvar_t pmove_msec; vmCvar_t cg_pmove_msec; vmCvar_t cg_cameraMode; vmCvar_t cg_cameraOrbit; vmCvar_t cg_cameraOrbitDelay; vmCvar_t cg_timescaleFadeEnd; vmCvar_t cg_timescaleFadeSpeed; vmCvar_t cg_timescale; vmCvar_t cg_smallFont; vmCvar_t cg_bigFont; vmCvar_t cg_noTaunt; vmCvar_t cg_noProjectileTrail; vmCvar_t cg_oldRail; vmCvar_t cg_oldRocket; vmCvar_t cg_oldPlasma; vmCvar_t cg_trueLightning; #ifdef MISSIONPACK vmCvar_t cg_redTeamName; vmCvar_t cg_blueTeamName; vmCvar_t cg_currentSelectedPlayer; vmCvar_t cg_currentSelectedPlayerName; vmCvar_t cg_singlePlayer; vmCvar_t cg_enableDust; vmCvar_t cg_enableBreath; vmCvar_t cg_singlePlayerActive; vmCvar_t cg_recordSPDemo; vmCvar_t cg_recordSPDemoName; vmCvar_t cg_obeliskRespawnDelay; #endif typedef struct { vmCvar_t *vmCvar; char *cvarName; char *defaultString; int cvarFlags; } cvarTable_t; static cvarTable_t cvarTable[] = { // bk001129 { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE }, { &cg_fov, "cg_fov", "90", CVAR_ARCHIVE }, { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, { &cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE }, { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, { &cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE }, { &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE }, { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE }, { &cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE }, { &cg_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE }, { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, { &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE }, { &cg_crosshairHealth, "cg_crosshairHealth", "1", CVAR_ARCHIVE }, { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, { &cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, { &cg_lagometer, "cg_lagometer", "1", CVAR_ARCHIVE }, { &cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE }, { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT }, { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, { &cg_bobup , "cg_bobup", "0.005", CVAR_CHEAT }, { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, { &cg_errorDecay, "cg_errordecay", "100", 0 }, { &cg_nopredict, "cg_nopredict", "0", 0 }, { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, { &cg_showmiss, "cg_showmiss", "0", 0 }, { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT }, { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT }, { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT }, { &cg_thirdPersonRange, "cg_thirdPersonRange", "40", CVAR_CHEAT }, { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, { &cg_thirdPerson, "cg_thirdPerson", "0", 0 }, { &cg_teamChatTime, "cg_teamChatTime", "3000", CVAR_ARCHIVE }, { &cg_teamChatHeight, "cg_teamChatHeight", "0", CVAR_ARCHIVE }, { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, #ifdef MISSIONPACK { &cg_deferPlayers, "cg_deferPlayers", "0", CVAR_ARCHIVE }, #else { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, #endif { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE }, { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO }, { &cg_stats, "cg_stats", "0", 0 }, { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE }, { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE }, { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, // the following variables are created in other parts of the system, // but we also reference them here { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures { &cg_paused, "cl_paused", "0", CVAR_ROM }, { &cg_blood, "com_blood", "1", CVAR_ARCHIVE }, { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo #ifdef MISSIONPACK { &cg_redTeamName, "g_redteam", DEFAULT_REDTEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, { &cg_blueTeamName, "g_blueteam", DEFAULT_BLUETEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, { &cg_currentSelectedPlayer, "cg_currentSelectedPlayer", "0", CVAR_ARCHIVE}, { &cg_currentSelectedPlayerName, "cg_currentSelectedPlayerName", "", CVAR_ARCHIVE}, { &cg_singlePlayer, "ui_singlePlayerActive", "0", CVAR_USERINFO}, { &cg_enableDust, "g_enableDust", "0", CVAR_SERVERINFO}, { &cg_enableBreath, "g_enableBreath", "0", CVAR_SERVERINFO}, { &cg_singlePlayerActive, "ui_singlePlayerActive", "0", CVAR_USERINFO}, { &cg_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE}, { &cg_recordSPDemoName, "ui_recordSPDemoName", "", CVAR_ARCHIVE}, { &cg_obeliskRespawnDelay, "g_obeliskRespawnDelay", "10", CVAR_SERVERINFO}, { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, #endif { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT}, { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE}, { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0}, { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0}, { &cg_timescale, "timescale", "1", 0}, { &cg_scorePlum, "cg_scorePlums", "1", CVAR_USERINFO | CVAR_ARCHIVE}, { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, { &pmove_fixed, "pmove_fixed", "0", 0}, { &pmove_msec, "pmove_msec", "8", 0}, { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, { &cg_smallFont, "ui_smallFont", "0.25", CVAR_ARCHIVE}, { &cg_bigFont, "ui_bigFont", "0.4", CVAR_ARCHIVE}, { &cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE}, { &cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE}, { &cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE}, { &cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE} // { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE } }; static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); /* ================= CG_RegisterCvars ================= */ void CG_RegisterCvars( void ) { int i; cvarTable_t *cv; char var[MAX_TOKEN_CHARS]; for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { trap_Cvar_Register( cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags ); } // see if we are also running the server on this machine trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); cgs.localServer = atoi( var ); forceModelModificationCount = cg_forceModel.modificationCount; trap_Cvar_Register(NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); trap_Cvar_Register(NULL, "headmodel", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); trap_Cvar_Register(NULL, "team_model", DEFAULT_TEAM_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); trap_Cvar_Register(NULL, "team_headmodel", DEFAULT_TEAM_HEAD, CVAR_USERINFO | CVAR_ARCHIVE ); } /* =================== CG_ForceModelChange =================== */ static void CG_ForceModelChange( void ) { int i; for (i=0 ; ivmCvar ); } // check for modications here // If team overlay is on, ask for updates from the server. If its off, // let the server know so we don't receive it if ( drawTeamOverlayModificationCount != cg_drawTeamOverlay.modificationCount ) { drawTeamOverlayModificationCount = cg_drawTeamOverlay.modificationCount; if ( cg_drawTeamOverlay.integer > 0 ) { trap_Cvar_Set( "teamoverlay", "1" ); } else { trap_Cvar_Set( "teamoverlay", "0" ); } // FIXME E3 HACK trap_Cvar_Set( "teamoverlay", "1" ); } // if force model changed if ( forceModelModificationCount != cg_forceModel.modificationCount ) { forceModelModificationCount = cg_forceModel.modificationCount; CG_ForceModelChange(); } } int CG_CrosshairPlayer( void ) { if ( cg.time > ( cg.crosshairClientTime + 1000 ) ) { return -1; } return cg.crosshairClientNum; } int CG_LastAttacker( void ) { if ( !cg.attackerTime ) { return -1; } return cg.snap->ps.persistant[PERS_ATTACKER]; } void QDECL CG_Printf( const char *msg, ... ) { va_list argptr; char text[1024]; va_start (argptr, msg); vsprintf (text, msg, argptr); va_end (argptr); trap_Print( text ); } void QDECL CG_Error( const char *msg, ... ) { va_list argptr; char text[1024]; va_start (argptr, msg); vsprintf (text, msg, argptr); va_end (argptr); trap_Error( text ); } #ifndef CGAME_HARD_LINKED // this is only here so the functions in q_shared.c and bg_*.c can link (FIXME) void QDECL Com_Error( int level, const char *error, ... ) { va_list argptr; char text[1024]; va_start (argptr, error); vsprintf (text, error, argptr); va_end (argptr); CG_Error( "%s", text); } void QDECL Com_Printf( const char *msg, ... ) { va_list argptr; char text[1024]; va_start (argptr, msg); vsprintf (text, msg, argptr); va_end (argptr); CG_Printf ("%s", text); } #endif /* ================ CG_Argv ================ */ const char *CG_Argv( int arg ) { static char buffer[MAX_STRING_CHARS]; trap_Argv( arg, buffer, sizeof( buffer ) ); return buffer; } //======================================================================== /* ================= CG_RegisterItemSounds The server says this item is used on this level ================= */ static void CG_RegisterItemSounds( int itemNum ) { gitem_t *item; char data[MAX_QPATH]; char *s, *start; int len; item = &bg_itemlist[ itemNum ]; if( item->pickup_sound ) { trap_S_RegisterSound( item->pickup_sound, qfalse ); } // parse the space seperated precache string for other media s = item->sounds; if (!s || !s[0]) return; while (*s) { start = s; while (*s && *s != ' ') { s++; } len = s-start; if (len >= MAX_QPATH || len < 5) { CG_Error( "PrecacheItem: %s has bad precache string", item->classname); return; } memcpy (data, start, len); data[len] = 0; if ( *s ) { s++; } if ( !strcmp(data+len-3, "wav" )) { trap_S_RegisterSound( data, qfalse ); } } } /* ================= CG_RegisterSounds called during a precache command ================= */ static void CG_RegisterSounds( void ) { int i; char items[MAX_ITEMS+1]; char name[MAX_QPATH]; const char *soundName; // voice commands #ifdef MISSIONPACK CG_LoadVoiceChats(); #endif cgs.media.oneMinuteSound = trap_S_RegisterSound( "sound/feedback/1_minute.wav", qtrue ); cgs.media.fiveMinuteSound = trap_S_RegisterSound( "sound/feedback/5_minute.wav", qtrue ); cgs.media.suddenDeathSound = trap_S_RegisterSound( "sound/feedback/sudden_death.wav", qtrue ); cgs.media.oneFragSound = trap_S_RegisterSound( "sound/feedback/1_frag.wav", qtrue ); cgs.media.twoFragSound = trap_S_RegisterSound( "sound/feedback/2_frags.wav", qtrue ); cgs.media.threeFragSound = trap_S_RegisterSound( "sound/feedback/3_frags.wav", qtrue ); cgs.media.count3Sound = trap_S_RegisterSound( "sound/feedback/three.wav", qtrue ); cgs.media.count2Sound = trap_S_RegisterSound( "sound/feedback/two.wav", qtrue ); cgs.media.count1Sound = trap_S_RegisterSound( "sound/feedback/one.wav", qtrue ); cgs.media.countFightSound = trap_S_RegisterSound( "sound/feedback/fight.wav", qtrue ); cgs.media.countPrepareSound = trap_S_RegisterSound( "sound/feedback/prepare.wav", qtrue ); #ifdef MISSIONPACK cgs.media.countPrepareTeamSound = trap_S_RegisterSound( "sound/feedback/prepare_team.wav", qtrue ); #endif if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { cgs.media.captureAwardSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); cgs.media.redLeadsSound = trap_S_RegisterSound( "sound/feedback/redleads.wav", qtrue ); cgs.media.blueLeadsSound = trap_S_RegisterSound( "sound/feedback/blueleads.wav", qtrue ); cgs.media.teamsTiedSound = trap_S_RegisterSound( "sound/feedback/teamstied.wav", qtrue ); cgs.media.hitTeamSound = trap_S_RegisterSound( "sound/feedback/hit_teammate.wav", qtrue ); cgs.media.redScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_red_scores.wav", qtrue ); cgs.media.blueScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_scores.wav", qtrue ); cgs.media.captureYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); cgs.media.captureOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_opponent.wav", qtrue ); cgs.media.returnYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_yourteam.wav", qtrue ); cgs.media.returnOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); cgs.media.takenYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_yourteam.wav", qtrue ); cgs.media.takenOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_opponent.wav", qtrue ); if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { cgs.media.redFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_red_returned.wav", qtrue ); cgs.media.blueFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_returned.wav", qtrue ); cgs.media.enemyTookYourFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_flag.wav", qtrue ); cgs.media.yourTeamTookEnemyFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_flag.wav", qtrue ); } #ifdef MISSIONPACK if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { // FIXME: get a replacement for this sound ? cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); } if ( cgs.gametype == GT_1FCTF || cgs.gametype == GT_CTF || cg_buildScript.integer ) { cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); } if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { cgs.media.yourBaseIsUnderAttackSound = trap_S_RegisterSound( "sound/teamplay/voc_base_attack.wav", qtrue ); } #else cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); #endif } cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/machinegun/buletby1.wav", qfalse ); cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse ); cgs.media.wearOffSound = trap_S_RegisterSound( "sound/items/wearoff.wav", qfalse ); cgs.media.useNothingSound = trap_S_RegisterSound( "sound/items/use_nothing.wav", qfalse ); cgs.media.gibSound = trap_S_RegisterSound( "sound/player/gibsplt1.wav", qfalse ); cgs.media.gibBounce1Sound = trap_S_RegisterSound( "sound/player/gibimp1.wav", qfalse ); cgs.media.gibBounce2Sound = trap_S_RegisterSound( "sound/player/gibimp2.wav", qfalse ); cgs.media.gibBounce3Sound = trap_S_RegisterSound( "sound/player/gibimp3.wav", qfalse ); #ifdef MISSIONPACK cgs.media.useInvulnerabilitySound = trap_S_RegisterSound( "sound/items/invul_activate.wav", qfalse ); cgs.media.invulnerabilityImpactSound1 = trap_S_RegisterSound( "sound/items/invul_impact_01.wav", qfalse ); cgs.media.invulnerabilityImpactSound2 = trap_S_RegisterSound( "sound/items/invul_impact_02.wav", qfalse ); cgs.media.invulnerabilityImpactSound3 = trap_S_RegisterSound( "sound/items/invul_impact_03.wav", qfalse ); cgs.media.invulnerabilityJuicedSound = trap_S_RegisterSound( "sound/items/invul_juiced.wav", qfalse ); cgs.media.obeliskHitSound1 = trap_S_RegisterSound( "sound/items/obelisk_hit_01.wav", qfalse ); cgs.media.obeliskHitSound2 = trap_S_RegisterSound( "sound/items/obelisk_hit_02.wav", qfalse ); cgs.media.obeliskHitSound3 = trap_S_RegisterSound( "sound/items/obelisk_hit_03.wav", qfalse ); cgs.media.obeliskRespawnSound = trap_S_RegisterSound( "sound/items/obelisk_respawn.wav", qfalse ); cgs.media.ammoregenSound = trap_S_RegisterSound("sound/items/cl_ammoregen.wav", qfalse); cgs.media.doublerSound = trap_S_RegisterSound("sound/items/cl_doubler.wav", qfalse); cgs.media.guardSound = trap_S_RegisterSound("sound/items/cl_guard.wav", qfalse); cgs.media.scoutSound = trap_S_RegisterSound("sound/items/cl_scout.wav", qfalse); #endif cgs.media.teleInSound = trap_S_RegisterSound( "sound/world/telein.wav", qfalse ); cgs.media.teleOutSound = trap_S_RegisterSound( "sound/world/teleout.wav", qfalse ); cgs.media.respawnSound = trap_S_RegisterSound( "sound/items/respawn1.wav", qfalse ); cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav", qfalse ); cgs.media.talkSound = trap_S_RegisterSound( "sound/player/talk.wav", qfalse ); cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav", qfalse); cgs.media.hitSound = trap_S_RegisterSound( "sound/feedback/hit.wav", qfalse ); #ifdef MISSIONPACK cgs.media.hitSoundHighArmor = trap_S_RegisterSound( "sound/feedback/hithi.wav", qfalse ); cgs.media.hitSoundLowArmor = trap_S_RegisterSound( "sound/feedback/hitlo.wav", qfalse ); #endif cgs.media.impressiveSound = trap_S_RegisterSound( "sound/feedback/impressive.wav", qtrue ); cgs.media.excellentSound = trap_S_RegisterSound( "sound/feedback/excellent.wav", qtrue ); cgs.media.deniedSound = trap_S_RegisterSound( "sound/feedback/denied.wav", qtrue ); cgs.media.humiliationSound = trap_S_RegisterSound( "sound/feedback/humiliation.wav", qtrue ); cgs.media.assistSound = trap_S_RegisterSound( "sound/feedback/assist.wav", qtrue ); cgs.media.defendSound = trap_S_RegisterSound( "sound/feedback/defense.wav", qtrue ); #ifdef MISSIONPACK cgs.media.firstImpressiveSound = trap_S_RegisterSound( "sound/feedback/first_impressive.wav", qtrue ); cgs.media.firstExcellentSound = trap_S_RegisterSound( "sound/feedback/first_excellent.wav", qtrue ); cgs.media.firstHumiliationSound = trap_S_RegisterSound( "sound/feedback/first_gauntlet.wav", qtrue ); #endif cgs.media.takenLeadSound = trap_S_RegisterSound( "sound/feedback/takenlead.wav", qtrue); cgs.media.tiedLeadSound = trap_S_RegisterSound( "sound/feedback/tiedlead.wav", qtrue); cgs.media.lostLeadSound = trap_S_RegisterSound( "sound/feedback/lostlead.wav", qtrue); #ifdef MISSIONPACK cgs.media.voteNow = trap_S_RegisterSound( "sound/feedback/vote_now.wav", qtrue); cgs.media.votePassed = trap_S_RegisterSound( "sound/feedback/vote_passed.wav", qtrue); cgs.media.voteFailed = trap_S_RegisterSound( "sound/feedback/vote_failed.wav", qtrue); #endif cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav", qfalse); cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav", qfalse); cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav", qfalse); cgs.media.jumpPadSound = trap_S_RegisterSound ("sound/world/jumppad.wav", qfalse ); for (i=0 ; i<4 ; i++) { Com_sprintf (name, sizeof(name), "sound/player/footsteps/step%i.wav", i+1); cgs.media.footsteps[FOOTSTEP_NORMAL][i] = trap_S_RegisterSound (name, qfalse); Com_sprintf (name, sizeof(name), "sound/player/footsteps/boot%i.wav", i+1); cgs.media.footsteps[FOOTSTEP_BOOT][i] = trap_S_RegisterSound (name, qfalse); Com_sprintf (name, sizeof(name), "sound/player/footsteps/flesh%i.wav", i+1); cgs.media.footsteps[FOOTSTEP_FLESH][i] = trap_S_RegisterSound (name, qfalse); Com_sprintf (name, sizeof(name), "sound/player/footsteps/mech%i.wav", i+1); cgs.media.footsteps[FOOTSTEP_MECH][i] = trap_S_RegisterSound (name, qfalse); Com_sprintf (name, sizeof(name), "sound/player/footsteps/energy%i.wav", i+1); cgs.media.footsteps[FOOTSTEP_ENERGY][i] = trap_S_RegisterSound (name, qfalse); Com_sprintf (name, sizeof(name), "sound/player/footsteps/splash%i.wav", i+1); cgs.media.footsteps[FOOTSTEP_SPLASH][i] = trap_S_RegisterSound (name, qfalse); Com_sprintf (name, sizeof(name), "sound/player/footsteps/clank%i.wav", i+1); cgs.media.footsteps[FOOTSTEP_METAL][i] = trap_S_RegisterSound (name, qfalse); } // only register the items that the server says we need strcpy( items, CG_ConfigString( CS_ITEMS ) ); for ( i = 1 ; i < bg_numItems ; i++ ) { // if ( items[ i ] == '1' || cg_buildScript.integer ) { CG_RegisterItemSounds( i ); // } } for ( i = 1 ; i < MAX_SOUNDS ; i++ ) { soundName = CG_ConfigString( CS_SOUNDS+i ); if ( !soundName[0] ) { break; } if ( soundName[0] == '*' ) { continue; // custom sound } cgs.gameSounds[i] = trap_S_RegisterSound( soundName, qfalse ); } // FIXME: only needed with item cgs.media.flightSound = trap_S_RegisterSound( "sound/items/flight.wav", qfalse ); cgs.media.medkitSound = trap_S_RegisterSound ("sound/items/use_medkit.wav", qfalse); cgs.media.quadSound = trap_S_RegisterSound("sound/items/damage3.wav", qfalse); cgs.media.sfx_ric1 = trap_S_RegisterSound ("sound/weapons/machinegun/ric1.wav", qfalse); cgs.media.sfx_ric2 = trap_S_RegisterSound ("sound/weapons/machinegun/ric2.wav", qfalse); cgs.media.sfx_ric3 = trap_S_RegisterSound ("sound/weapons/machinegun/ric3.wav", qfalse); cgs.media.sfx_railg = trap_S_RegisterSound ("sound/weapons/railgun/railgf1a.wav", qfalse); cgs.media.sfx_rockexp = trap_S_RegisterSound ("sound/weapons/rocket/rocklx1a.wav", qfalse); cgs.media.sfx_plasmaexp = trap_S_RegisterSound ("sound/weapons/plasma/plasmx1a.wav", qfalse); #ifdef MISSIONPACK cgs.media.sfx_proxexp = trap_S_RegisterSound( "sound/weapons/proxmine/wstbexpl.wav" , qfalse); cgs.media.sfx_nghit = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpd.wav" , qfalse); cgs.media.sfx_nghitflesh = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpl.wav" , qfalse); cgs.media.sfx_nghitmetal = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpm.wav", qfalse ); cgs.media.sfx_chghit = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpd.wav", qfalse ); cgs.media.sfx_chghitflesh = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpl.wav", qfalse ); cgs.media.sfx_chghitmetal = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpm.wav", qfalse ); cgs.media.weaponHoverSound = trap_S_RegisterSound( "sound/weapons/weapon_hover.wav", qfalse ); cgs.media.kamikazeExplodeSound = trap_S_RegisterSound( "sound/items/kam_explode.wav", qfalse ); cgs.media.kamikazeImplodeSound = trap_S_RegisterSound( "sound/items/kam_implode.wav", qfalse ); cgs.media.kamikazeFarSound = trap_S_RegisterSound( "sound/items/kam_explode_far.wav", qfalse ); cgs.media.winnerSound = trap_S_RegisterSound( "sound/feedback/voc_youwin.wav", qfalse ); cgs.media.loserSound = trap_S_RegisterSound( "sound/feedback/voc_youlose.wav", qfalse ); cgs.media.youSuckSound = trap_S_RegisterSound( "sound/misc/yousuck.wav", qfalse ); cgs.media.wstbimplSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpl.wav", qfalse); cgs.media.wstbimpmSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpm.wav", qfalse); cgs.media.wstbimpdSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpd.wav", qfalse); cgs.media.wstbactvSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbactv.wav", qfalse); #endif cgs.media.regenSound = trap_S_RegisterSound("sound/items/regen.wav", qfalse); cgs.media.protectSound = trap_S_RegisterSound("sound/items/protect3.wav", qfalse); cgs.media.n_healthSound = trap_S_RegisterSound("sound/items/n_health.wav", qfalse ); cgs.media.hgrenb1aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb1a.wav", qfalse); cgs.media.hgrenb2aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb2a.wav", qfalse); #ifdef MISSIONPACK trap_S_RegisterSound("sound/player/james/death1.wav", qfalse ); trap_S_RegisterSound("sound/player/james/death2.wav", qfalse ); trap_S_RegisterSound("sound/player/james/death3.wav", qfalse ); trap_S_RegisterSound("sound/player/james/jump1.wav", qfalse ); trap_S_RegisterSound("sound/player/james/pain25_1.wav", qfalse ); trap_S_RegisterSound("sound/player/james/pain75_1.wav", qfalse ); trap_S_RegisterSound("sound/player/james/pain100_1.wav", qfalse ); trap_S_RegisterSound("sound/player/james/falling1.wav", qfalse ); trap_S_RegisterSound("sound/player/james/gasp.wav", qfalse ); trap_S_RegisterSound("sound/player/james/drown.wav", qfalse ); trap_S_RegisterSound("sound/player/james/fall1.wav", qfalse ); trap_S_RegisterSound("sound/player/james/taunt.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/death1.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/death2.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/death3.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/jump1.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/pain25_1.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/pain75_1.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/pain100_1.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/falling1.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/gasp.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/drown.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/fall1.wav", qfalse ); trap_S_RegisterSound("sound/player/janet/taunt.wav", qfalse ); #endif } //=================================================================================== /* ================= CG_RegisterGraphics This function may execute for a couple of minutes with a slow disk. ================= */ static void CG_RegisterGraphics( void ) { int i; char items[MAX_ITEMS+1]; static char *sb_nums[11] = { "gfx/2d/numbers/zero_32b", "gfx/2d/numbers/one_32b", "gfx/2d/numbers/two_32b", "gfx/2d/numbers/three_32b", "gfx/2d/numbers/four_32b", "gfx/2d/numbers/five_32b", "gfx/2d/numbers/six_32b", "gfx/2d/numbers/seven_32b", "gfx/2d/numbers/eight_32b", "gfx/2d/numbers/nine_32b", "gfx/2d/numbers/minus_32b", }; // clear any references to old media memset( &cg.refdef, 0, sizeof( cg.refdef ) ); trap_R_ClearScene(); CG_LoadingString( cgs.mapname ); trap_R_LoadWorldMap( cgs.mapname ); // precache status bar pics CG_LoadingString( "game media" ); for ( i=0 ; i<11 ; i++) { cgs.media.numberShaders[i] = trap_R_RegisterShader( sb_nums[i] ); } cgs.media.botSkillShaders[0] = trap_R_RegisterShader( "menu/art/skill1.tga" ); cgs.media.botSkillShaders[1] = trap_R_RegisterShader( "menu/art/skill2.tga" ); cgs.media.botSkillShaders[2] = trap_R_RegisterShader( "menu/art/skill3.tga" ); cgs.media.botSkillShaders[3] = trap_R_RegisterShader( "menu/art/skill4.tga" ); cgs.media.botSkillShaders[4] = trap_R_RegisterShader( "menu/art/skill5.tga" ); cgs.media.viewBloodShader = trap_R_RegisterShader( "viewBloodBlend" ); cgs.media.deferShader = trap_R_RegisterShaderNoMip( "gfx/2d/defer.tga" ); cgs.media.scoreboardName = trap_R_RegisterShaderNoMip( "menu/tab/name.tga" ); cgs.media.scoreboardPing = trap_R_RegisterShaderNoMip( "menu/tab/ping.tga" ); cgs.media.scoreboardScore = trap_R_RegisterShaderNoMip( "menu/tab/score.tga" ); cgs.media.scoreboardTime = trap_R_RegisterShaderNoMip( "menu/tab/time.tga" ); cgs.media.smokePuffShader = trap_R_RegisterShader( "smokePuff" ); cgs.media.smokePuffRageProShader = trap_R_RegisterShader( "smokePuffRagePro" ); cgs.media.shotgunSmokePuffShader = trap_R_RegisterShader( "shotgunSmokePuff" ); #ifdef MISSIONPACK cgs.media.nailPuffShader = trap_R_RegisterShader( "nailtrail" ); cgs.media.blueProxMine = trap_R_RegisterModel( "models/weaphits/proxmineb.md3" ); #endif cgs.media.plasmaBallShader = trap_R_RegisterShader( "sprites/plasma1" ); cgs.media.bloodTrailShader = trap_R_RegisterShader( "bloodTrail" ); cgs.media.lagometerShader = trap_R_RegisterShader("lagometer" ); cgs.media.connectionShader = trap_R_RegisterShader( "disconnected" ); cgs.media.waterBubbleShader = trap_R_RegisterShader( "waterBubble" ); cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); cgs.media.selectShader = trap_R_RegisterShader( "gfx/2d/select" ); for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) { cgs.media.crosshairShader[i] = trap_R_RegisterShader( va("gfx/2d/crosshair%c", 'a'+i) ); } cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" ); cgs.media.noammoShader = trap_R_RegisterShader( "icons/noammo" ); // powerup shaders cgs.media.quadShader = trap_R_RegisterShader("powerups/quad" ); cgs.media.quadWeaponShader = trap_R_RegisterShader("powerups/quadWeapon" ); cgs.media.battleSuitShader = trap_R_RegisterShader("powerups/battleSuit" ); cgs.media.battleWeaponShader = trap_R_RegisterShader("powerups/battleWeapon" ); cgs.media.invisShader = trap_R_RegisterShader("powerups/invisibility" ); cgs.media.regenShader = trap_R_RegisterShader("powerups/regen" ); cgs.media.hastePuffShader = trap_R_RegisterShader("hasteSmokePuff" ); #ifdef MISSIONPACK if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { #else if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { #endif cgs.media.redCubeModel = trap_R_RegisterModel( "models/powerups/orb/r_orb.md3" ); cgs.media.blueCubeModel = trap_R_RegisterModel( "models/powerups/orb/b_orb.md3" ); cgs.media.redCubeIcon = trap_R_RegisterShader( "icons/skull_red" ); cgs.media.blueCubeIcon = trap_R_RegisterShader( "icons/skull_blue" ); } #ifdef MISSIONPACK if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { #else if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { #endif cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag.md3" ); cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag.md3" ); cgs.media.redFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_red1" ); cgs.media.redFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); cgs.media.redFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_red3" ); cgs.media.blueFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_blu1" ); cgs.media.blueFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); cgs.media.blueFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu3" ); #ifdef MISSIONPACK cgs.media.flagPoleModel = trap_R_RegisterModel( "models/flag2/flagpole.md3" ); cgs.media.flagFlapModel = trap_R_RegisterModel( "models/flag2/flagflap3.md3" ); cgs.media.redFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/red.skin" ); cgs.media.blueFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/blue.skin" ); cgs.media.neutralFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/white.skin" ); cgs.media.redFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/red_base.md3" ); cgs.media.blueFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/blue_base.md3" ); cgs.media.neutralFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/ntrl_base.md3" ); #endif } #ifdef MISSIONPACK if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { cgs.media.neutralFlagModel = trap_R_RegisterModel( "models/flags/n_flag.md3" ); cgs.media.flagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral1" ); cgs.media.flagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); cgs.media.flagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); cgs.media.flagShader[3] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral3" ); } if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { cgs.media.overloadBaseModel = trap_R_RegisterModel( "models/powerups/overload_base.md3" ); cgs.media.overloadTargetModel = trap_R_RegisterModel( "models/powerups/overload_target.md3" ); cgs.media.overloadLightsModel = trap_R_RegisterModel( "models/powerups/overload_lights.md3" ); cgs.media.overloadEnergyModel = trap_R_RegisterModel( "models/powerups/overload_energy.md3" ); } if ( cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { cgs.media.harvesterModel = trap_R_RegisterModel( "models/powerups/harvester/harvester.md3" ); cgs.media.harvesterRedSkin = trap_R_RegisterSkin( "models/powerups/harvester/red.skin" ); cgs.media.harvesterBlueSkin = trap_R_RegisterSkin( "models/powerups/harvester/blue.skin" ); cgs.media.harvesterNeutralModel = trap_R_RegisterModel( "models/powerups/obelisk/obelisk.md3" ); } cgs.media.redKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikred" ); cgs.media.dustPuffShader = trap_R_RegisterShader("hasteSmokePuff" ); #endif if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { cgs.media.friendShader = trap_R_RegisterShader( "sprites/foe" ); cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); #ifdef MISSIONPACK cgs.media.blueKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikblu" ); #endif } cgs.media.armorModel = trap_R_RegisterModel( "models/powerups/armor/armor_yel.md3" ); cgs.media.armorIcon = trap_R_RegisterShaderNoMip( "icons/iconr_yellow" ); cgs.media.machinegunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/m_shell.md3" ); cgs.media.shotgunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); cgs.media.gibAbdomen = trap_R_RegisterModel( "models/gibs/abdomen.md3" ); cgs.media.gibArm = trap_R_RegisterModel( "models/gibs/arm.md3" ); cgs.media.gibChest = trap_R_RegisterModel( "models/gibs/chest.md3" ); cgs.media.gibFist = trap_R_RegisterModel( "models/gibs/fist.md3" ); cgs.media.gibFoot = trap_R_RegisterModel( "models/gibs/foot.md3" ); cgs.media.gibForearm = trap_R_RegisterModel( "models/gibs/forearm.md3" ); cgs.media.gibIntestine = trap_R_RegisterModel( "models/gibs/intestine.md3" ); cgs.media.gibLeg = trap_R_RegisterModel( "models/gibs/leg.md3" ); cgs.media.gibSkull = trap_R_RegisterModel( "models/gibs/skull.md3" ); cgs.media.gibBrain = trap_R_RegisterModel( "models/gibs/brain.md3" ); cgs.media.smoke2 = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); cgs.media.balloonShader = trap_R_RegisterShader( "sprites/balloon3" ); cgs.media.bloodExplosionShader = trap_R_RegisterShader( "bloodExplosion" ); cgs.media.bulletFlashModel = trap_R_RegisterModel("models/weaphits/bullet.md3"); cgs.media.ringFlashModel = trap_R_RegisterModel("models/weaphits/ring02.md3"); cgs.media.dishFlashModel = trap_R_RegisterModel("models/weaphits/boom01.md3"); #ifdef MISSIONPACK cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/powerups/pop.md3" ); #else cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/misc/telep.md3" ); cgs.media.teleportEffectShader = trap_R_RegisterShader( "teleportEffect" ); #endif #ifdef MISSIONPACK cgs.media.kamikazeEffectModel = trap_R_RegisterModel( "models/weaphits/kamboom2.md3" ); cgs.media.kamikazeShockWave = trap_R_RegisterModel( "models/weaphits/kamwave.md3" ); cgs.media.kamikazeHeadModel = trap_R_RegisterModel( "models/powerups/kamikazi.md3" ); cgs.media.kamikazeHeadTrail = trap_R_RegisterModel( "models/powerups/trailtest.md3" ); cgs.media.guardPowerupModel = trap_R_RegisterModel( "models/powerups/guard_player.md3" ); cgs.media.scoutPowerupModel = trap_R_RegisterModel( "models/powerups/scout_player.md3" ); cgs.media.doublerPowerupModel = trap_R_RegisterModel( "models/powerups/doubler_player.md3" ); cgs.media.ammoRegenPowerupModel = trap_R_RegisterModel( "models/powerups/ammo_player.md3" ); cgs.media.invulnerabilityImpactModel = trap_R_RegisterModel( "models/powerups/shield/impact.md3" ); cgs.media.invulnerabilityJuicedModel = trap_R_RegisterModel( "models/powerups/shield/juicer.md3" ); cgs.media.medkitUsageModel = trap_R_RegisterModel( "models/powerups/regen.md3" ); cgs.media.heartShader = trap_R_RegisterShaderNoMip( "ui/assets/statusbar/selectedhealth.tga" ); #endif cgs.media.invulnerabilityPowerupModel = trap_R_RegisterModel( "models/powerups/shield/shield.md3" ); cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); cgs.media.medalExcellent = trap_R_RegisterShaderNoMip( "medal_excellent" ); cgs.media.medalGauntlet = trap_R_RegisterShaderNoMip( "medal_gauntlet" ); cgs.media.medalDefend = trap_R_RegisterShaderNoMip( "medal_defend" ); cgs.media.medalAssist = trap_R_RegisterShaderNoMip( "medal_assist" ); cgs.media.medalCapture = trap_R_RegisterShaderNoMip( "medal_capture" ); memset( cg_items, 0, sizeof( cg_items ) ); memset( cg_weapons, 0, sizeof( cg_weapons ) ); // only register the items that the server says we need strcpy( items, CG_ConfigString( CS_ITEMS) ); for ( i = 1 ; i < bg_numItems ; i++ ) { if ( items[ i ] == '1' || cg_buildScript.integer ) { CG_LoadingItem( i ); CG_RegisterItemVisuals( i ); } } // wall marks cgs.media.bulletMarkShader = trap_R_RegisterShader( "gfx/damage/bullet_mrk" ); cgs.media.burnMarkShader = trap_R_RegisterShader( "gfx/damage/burn_med_mrk" ); cgs.media.holeMarkShader = trap_R_RegisterShader( "gfx/damage/hole_lg_mrk" ); cgs.media.energyMarkShader = trap_R_RegisterShader( "gfx/damage/plasma_mrk" ); cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); cgs.media.bloodMarkShader = trap_R_RegisterShader( "bloodMark" ); // register the inline models cgs.numInlineModels = trap_CM_NumInlineModels(); for ( i = 1 ; i < cgs.numInlineModels ; i++ ) { char name[10]; vec3_t mins, maxs; int j; Com_sprintf( name, sizeof(name), "*%i", i ); cgs.inlineDrawModel[i] = trap_R_RegisterModel( name ); trap_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs ); for ( j = 0 ; j < 3 ; j++ ) { cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); } } // register all the server specified models for (i=1 ; i= MAX_CONFIGSTRINGS ) { CG_Error( "CG_ConfigString: bad index: %i", index ); } return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; } //================================================================== /* ====================== CG_StartMusic ====================== */ void CG_StartMusic( void ) { char *s; char parm1[MAX_QPATH], parm2[MAX_QPATH]; // start the background music s = (char *)CG_ConfigString( CS_MUSIC ); Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); trap_S_StartBackgroundTrack( parm1, parm2 ); } #ifdef MISSIONPACK char *CG_GetMenuBuffer(const char *filename) { int len; fileHandle_t f; static char buf[MAX_MENUFILE]; len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( !f ) { trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); return NULL; } if ( len >= MAX_MENUFILE ) { trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); trap_FS_FCloseFile( f ); return NULL; } trap_FS_Read( buf, len, f ); buf[len] = 0; trap_FS_FCloseFile( f ); return buf; } // // ============================== // new hud stuff ( mission pack ) // ============================== // qboolean CG_Asset_Parse(int handle) { pc_token_t token; const char *tempStr; if (!trap_PC_ReadToken(handle, &token)) return qfalse; if (Q_stricmp(token.string, "{") != 0) { return qfalse; } while ( 1 ) { if (!trap_PC_ReadToken(handle, &token)) return qfalse; if (Q_stricmp(token.string, "}") == 0) { return qtrue; } // font if (Q_stricmp(token.string, "font") == 0) { int pointSize; if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { return qfalse; } cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.textFont); continue; } // smallFont if (Q_stricmp(token.string, "smallFont") == 0) { int pointSize; if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { return qfalse; } cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.smallFont); continue; } // font if (Q_stricmp(token.string, "bigfont") == 0) { int pointSize; if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { return qfalse; } cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.bigFont); continue; } // gradientbar if (Q_stricmp(token.string, "gradientbar") == 0) { if (!PC_String_Parse(handle, &tempStr)) { return qfalse; } cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr); continue; } // enterMenuSound if (Q_stricmp(token.string, "menuEnterSound") == 0) { if (!PC_String_Parse(handle, &tempStr)) { return qfalse; } cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); continue; } // exitMenuSound if (Q_stricmp(token.string, "menuExitSound") == 0) { if (!PC_String_Parse(handle, &tempStr)) { return qfalse; } cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); continue; } // itemFocusSound if (Q_stricmp(token.string, "itemFocusSound") == 0) { if (!PC_String_Parse(handle, &tempStr)) { return qfalse; } cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); continue; } // menuBuzzSound if (Q_stricmp(token.string, "menuBuzzSound") == 0) { if (!PC_String_Parse(handle, &tempStr)) { return qfalse; } cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); continue; } if (Q_stricmp(token.string, "cursor") == 0) { if (!PC_String_Parse(handle, &cgDC.Assets.cursorStr)) { return qfalse; } cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr); continue; } if (Q_stricmp(token.string, "fadeClamp") == 0) { if (!PC_Float_Parse(handle, &cgDC.Assets.fadeClamp)) { return qfalse; } continue; } if (Q_stricmp(token.string, "fadeCycle") == 0) { if (!PC_Int_Parse(handle, &cgDC.Assets.fadeCycle)) { return qfalse; } continue; } if (Q_stricmp(token.string, "fadeAmount") == 0) { if (!PC_Float_Parse(handle, &cgDC.Assets.fadeAmount)) { return qfalse; } continue; } if (Q_stricmp(token.string, "shadowX") == 0) { if (!PC_Float_Parse(handle, &cgDC.Assets.shadowX)) { return qfalse; } continue; } if (Q_stricmp(token.string, "shadowY") == 0) { if (!PC_Float_Parse(handle, &cgDC.Assets.shadowY)) { return qfalse; } continue; } if (Q_stricmp(token.string, "shadowColor") == 0) { if (!PC_Color_Parse(handle, &cgDC.Assets.shadowColor)) { return qfalse; } cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[3]; continue; } } return qfalse; // bk001204 - why not? } void CG_ParseMenu(const char *menuFile) { pc_token_t token; int handle; handle = trap_PC_LoadSource(menuFile); if (!handle) handle = trap_PC_LoadSource("ui/testhud.menu"); if (!handle) return; while ( 1 ) { if (!trap_PC_ReadToken( handle, &token )) { break; } //if ( Q_stricmp( token, "{" ) ) { // Com_Printf( "Missing { in menu file\n" ); // break; //} //if ( menuCount == MAX_MENUS ) { // Com_Printf( "Too many menus!\n" ); // break; //} if ( token.string[0] == '}' ) { break; } if (Q_stricmp(token.string, "assetGlobalDef") == 0) { if (CG_Asset_Parse(handle)) { continue; } else { break; } } if (Q_stricmp(token.string, "menudef") == 0) { // start a new menu Menu_New(handle); } } trap_PC_FreeSource(handle); } qboolean CG_Load_Menu(char **p) { char *token; token = COM_ParseExt(p, qtrue); if (token[0] != '{') { return qfalse; } while ( 1 ) { token = COM_ParseExt(p, qtrue); if (Q_stricmp(token, "}") == 0) { return qtrue; } if ( !token || token[0] == 0 ) { return qfalse; } CG_ParseMenu(token); } return qfalse; } void CG_LoadMenus(const char *menuFile) { char *token; char *p; int len, start; fileHandle_t f; static char buf[MAX_MENUDEFFILE]; start = trap_Milliseconds(); len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); if ( !f ) { trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ ); if (!f) { trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n", menuFile ) ); } } if ( len >= MAX_MENUDEFFILE ) { trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", menuFile, len, MAX_MENUDEFFILE ) ); trap_FS_FCloseFile( f ); return; } trap_FS_Read( buf, len, f ); buf[len] = 0; trap_FS_FCloseFile( f ); COM_Compress(buf); Menu_Reset(); p = buf; while ( 1 ) { token = COM_ParseExt( &p, qtrue ); if( !token || token[0] == 0 || token[0] == '}') { break; } //if ( Q_stricmp( token, "{" ) ) { // Com_Printf( "Missing { in menu file\n" ); // break; //} //if ( menuCount == MAX_MENUS ) { // Com_Printf( "Too many menus!\n" ); // break; //} if ( Q_stricmp( token, "}" ) == 0 ) { break; } if (Q_stricmp(token, "loadmenu") == 0) { if (CG_Load_Menu(&p)) { continue; } else { break; } } } Com_Printf("UI menu load time = %d milli seconds\n", trap_Milliseconds() - start); } static qboolean CG_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) { return qfalse; } static int CG_FeederCount(float feederID) { int i, count; count = 0; if (feederID == FEEDER_REDTEAM_LIST) { for (i = 0; i < cg.numScores; i++) { if (cg.scores[i].team == TEAM_RED) { count++; } } } else if (feederID == FEEDER_BLUETEAM_LIST) { for (i = 0; i < cg.numScores; i++) { if (cg.scores[i].team == TEAM_BLUE) { count++; } } } else if (feederID == FEEDER_SCOREBOARD) { return cg.numScores; } return count; } void CG_SetScoreSelection(void *p) { menuDef_t *menu = (menuDef_t*)p; playerState_t *ps = &cg.snap->ps; int i, red, blue; red = blue = 0; for (i = 0; i < cg.numScores; i++) { if (cg.scores[i].team == TEAM_RED) { red++; } else if (cg.scores[i].team == TEAM_BLUE) { blue++; } if (ps->clientNum == cg.scores[i].client) { cg.selectedScore = i; } } if (menu == NULL) { // just interested in setting the selected score return; } if ( cgs.gametype >= GT_TEAM ) { int feeder = FEEDER_REDTEAM_LIST; i = red; if (cg.scores[cg.selectedScore].team == TEAM_BLUE) { feeder = FEEDER_BLUETEAM_LIST; i = blue; } Menu_SetFeederSelection(menu, feeder, i, NULL); } else { Menu_SetFeederSelection(menu, FEEDER_SCOREBOARD, cg.selectedScore, NULL); } } // FIXME: might need to cache this info static clientInfo_t * CG_InfoFromScoreIndex(int index, int team, int *scoreIndex) { int i, count; if ( cgs.gametype >= GT_TEAM ) { count = 0; for (i = 0; i < cg.numScores; i++) { if (cg.scores[i].team == team) { if (count == index) { *scoreIndex = i; return &cgs.clientinfo[cg.scores[i].client]; } count++; } } } *scoreIndex = index; return &cgs.clientinfo[ cg.scores[index].client ]; } static const char *CG_FeederItemText(float feederID, int index, int column, qhandle_t *handle) { gitem_t *item; int scoreIndex = 0; clientInfo_t *info = NULL; int team = -1; score_t *sp = NULL; *handle = -1; if (feederID == FEEDER_REDTEAM_LIST) { team = TEAM_RED; } else if (feederID == FEEDER_BLUETEAM_LIST) { team = TEAM_BLUE; } info = CG_InfoFromScoreIndex(index, team, &scoreIndex); sp = &cg.scores[scoreIndex]; if (info && info->infoValid) { switch (column) { case 0: if ( info->powerups & ( 1 << PW_NEUTRALFLAG ) ) { item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); *handle = cg_items[ ITEM_INDEX(item) ].icon; } else if ( info->powerups & ( 1 << PW_REDFLAG ) ) { item = BG_FindItemForPowerup( PW_REDFLAG ); *handle = cg_items[ ITEM_INDEX(item) ].icon; } else if ( info->powerups & ( 1 << PW_BLUEFLAG ) ) { item = BG_FindItemForPowerup( PW_BLUEFLAG ); *handle = cg_items[ ITEM_INDEX(item) ].icon; } else { if ( info->botSkill > 0 && info->botSkill <= 5 ) { *handle = cgs.media.botSkillShaders[ info->botSkill - 1 ]; } else if ( info->handicap < 100 ) { return va("%i", info->handicap ); } } break; case 1: if (team == -1) { return ""; } else { *handle = CG_StatusHandle(info->teamTask); } break; case 2: if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << sp->client ) ) { return "Ready"; } if (team == -1) { if (cgs.gametype == GT_TOURNAMENT) { return va("%i/%i", info->wins, info->losses); } else if (info->infoValid && info->team == TEAM_SPECTATOR ) { return "Spectator"; } else { return ""; } } else { if (info->teamLeader) { return "Leader"; } } break; case 3: return info->name; break; case 4: return va("%i", info->score); break; case 5: return va("%4i", sp->time); break; case 6: if ( sp->ping == -1 ) { return "connecting"; } return va("%4i", sp->ping); break; } } return ""; } static qhandle_t CG_FeederItemImage(float feederID, int index) { return 0; } static void CG_FeederSelection(float feederID, int index) { if ( cgs.gametype >= GT_TEAM ) { int i, count; int team = (feederID == FEEDER_REDTEAM_LIST) ? TEAM_RED : TEAM_BLUE; count = 0; for (i = 0; i < cg.numScores; i++) { if (cg.scores[i].team == team) { if (index == count) { cg.selectedScore = i; } count++; } } } else { cg.selectedScore = index; } } #endif #ifdef MISSIONPACK // bk001204 - only needed there static float CG_Cvar_Get(const char *cvar) { char buff[128]; memset(buff, 0, sizeof(buff)); trap_Cvar_VariableStringBuffer(cvar, buff, sizeof(buff)); return atof(buff); } #endif #ifdef MISSIONPACK void CG_Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) { CG_Text_Paint(x, y, scale, color, text, 0, limit, style); } static int CG_OwnerDrawWidth(int ownerDraw, float scale) { switch (ownerDraw) { case CG_GAME_TYPE: return CG_Text_Width(CG_GameTypeString(), scale, 0); case CG_GAME_STATUS: return CG_Text_Width(CG_GetGameStatusText(), scale, 0); break; case CG_KILLER: return CG_Text_Width(CG_GetKillerText(), scale, 0); break; case CG_RED_NAME: return CG_Text_Width(cg_redTeamName.string, scale, 0); break; case CG_BLUE_NAME: return CG_Text_Width(cg_blueTeamName.string, scale, 0); break; } return 0; } static int CG_PlayCinematic(const char *name, float x, float y, float w, float h) { return trap_CIN_PlayCinematic(name, x, y, w, h, CIN_loop); } static void CG_StopCinematic(int handle) { trap_CIN_StopCinematic(handle); } static void CG_DrawCinematic(int handle, float x, float y, float w, float h) { trap_CIN_SetExtents(handle, x, y, w, h); trap_CIN_DrawCinematic(handle); } static void CG_RunCinematicFrame(int handle) { trap_CIN_RunCinematic(handle); } /* ================= CG_LoadHudMenu(); ================= */ void CG_LoadHudMenu() { char buff[1024]; const char *hudSet; cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; cgDC.setColor = &trap_R_SetColor; cgDC.drawHandlePic = &CG_DrawPic; cgDC.drawStretchPic = &trap_R_DrawStretchPic; cgDC.drawText = &CG_Text_Paint; cgDC.textWidth = &CG_Text_Width; cgDC.textHeight = &CG_Text_Height; cgDC.registerModel = &trap_R_RegisterModel; cgDC.modelBounds = &trap_R_ModelBounds; cgDC.fillRect = &CG_FillRect; cgDC.drawRect = &CG_DrawRect; cgDC.drawSides = &CG_DrawSides; cgDC.drawTopBottom = &CG_DrawTopBottom; cgDC.clearScene = &trap_R_ClearScene; cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; cgDC.renderScene = &trap_R_RenderScene; cgDC.registerFont = &trap_R_RegisterFont; cgDC.ownerDrawItem = &CG_OwnerDraw; cgDC.getValue = &CG_GetValue; cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; cgDC.runScript = &CG_RunMenuScript; cgDC.getTeamColor = &CG_GetTeamColor; cgDC.setCVar = trap_Cvar_Set; cgDC.getCVarString = trap_Cvar_VariableStringBuffer; cgDC.getCVarValue = CG_Cvar_Get; cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor; //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; cgDC.startLocalSound = &trap_S_StartLocalSound; cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; cgDC.feederCount = &CG_FeederCount; cgDC.feederItemImage = &CG_FeederItemImage; cgDC.feederItemText = &CG_FeederItemText; cgDC.feederSelection = &CG_FeederSelection; //cgDC.setBinding = &trap_Key_SetBinding; //cgDC.getBindingBuf = &trap_Key_GetBindingBuf; //cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; //cgDC.executeText = &trap_Cmd_ExecuteText; cgDC.Error = &Com_Error; cgDC.Print = &Com_Printf; cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; //cgDC.Pause = &CG_Pause; cgDC.registerSound = &trap_S_RegisterSound; cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; cgDC.playCinematic = &CG_PlayCinematic; cgDC.stopCinematic = &CG_StopCinematic; cgDC.drawCinematic = &CG_DrawCinematic; cgDC.runCinematicFrame = &CG_RunCinematicFrame; Init_Display(&cgDC); Menu_Reset(); trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); hudSet = buff; if (hudSet[0] == '\0') { hudSet = "ui/hud.txt"; } CG_LoadMenus(hudSet); } void CG_AssetCache() { //if (Assets.textFont == NULL) { // trap_R_RegisterFont("fonts/arial.ttf", 72, &Assets.textFont); //} //Assets.background = trap_R_RegisterShaderNoMip( ASSET_BACKGROUND ); //Com_Printf("Menu Size: %i bytes\n", sizeof(Menus)); cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); cgDC.Assets.fxBasePic = trap_R_RegisterShaderNoMip( ART_FX_BASE ); cgDC.Assets.fxPic[0] = trap_R_RegisterShaderNoMip( ART_FX_RED ); cgDC.Assets.fxPic[1] = trap_R_RegisterShaderNoMip( ART_FX_YELLOW ); cgDC.Assets.fxPic[2] = trap_R_RegisterShaderNoMip( ART_FX_GREEN ); cgDC.Assets.fxPic[3] = trap_R_RegisterShaderNoMip( ART_FX_TEAL ); cgDC.Assets.fxPic[4] = trap_R_RegisterShaderNoMip( ART_FX_BLUE ); cgDC.Assets.fxPic[5] = trap_R_RegisterShaderNoMip( ART_FX_CYAN ); cgDC.Assets.fxPic[6] = trap_R_RegisterShaderNoMip( ART_FX_WHITE ); cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); } #endif /* ================= CG_Init Called after every level change or subsystem restart Will perform callbacks to make the loading info screen update. ================= */ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) { const char *s; // clear everything memset( &cgs, 0, sizeof( cgs ) ); memset( &cg, 0, sizeof( cg ) ); memset( cg_entities, 0, sizeof(cg_entities) ); memset( cg_weapons, 0, sizeof(cg_weapons) ); memset( cg_items, 0, sizeof(cg_items) ); cg.clientNum = clientNum; cgs.processedSnapshotNum = serverMessageNum; cgs.serverCommandSequence = serverCommandSequence; // load a few needed things before we do any screen updates cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" ); cgs.media.whiteShader = trap_R_RegisterShader( "white" ); cgs.media.charsetProp = trap_R_RegisterShaderNoMip( "menu/art/font1_prop.tga" ); cgs.media.charsetPropGlow = trap_R_RegisterShaderNoMip( "menu/art/font1_prop_glo.tga" ); cgs.media.charsetPropB = trap_R_RegisterShaderNoMip( "menu/art/font2_prop.tga" ); CG_RegisterCvars(); CG_InitConsoleCommands(); cg.weaponSelect = WP_MACHINEGUN; cgs.redflag = cgs.blueflag = -1; // For compatibily, default to unset for cgs.flagStatus = -1; // old servers // get the rendering configuration from the client system trap_GetGlconfig( &cgs.glconfig ); cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; // get the gamestate from the client system trap_GetGameState( &cgs.gameState ); // check version s = CG_ConfigString( CS_GAME_VERSION ); if ( strcmp( s, GAME_VERSION ) ) { CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); } s = CG_ConfigString( CS_LEVEL_START_TIME ); cgs.levelStartTime = atoi( s ); CG_ParseServerinfo(); // load the new map CG_LoadingString( "collision map" ); trap_CM_LoadMap( cgs.mapname ); #ifdef MISSIONPACK String_Init(); #endif cg.loading = qtrue; // force players to load instead of defer CG_LoadingString( "sounds" ); CG_RegisterSounds(); CG_LoadingString( "graphics" ); CG_RegisterGraphics(); CG_LoadingString( "clients" ); CG_RegisterClients(); // if low on memory, some clients will be deferred #ifdef MISSIONPACK CG_AssetCache(); CG_LoadHudMenu(); // load new hud stuff #endif cg.loading = qfalse; // future players will be deferred CG_InitLocalEntities(); CG_InitMarkPolys(); // remove the last loading update cg.infoScreenText[0] = 0; // Make sure we have update values (scores) CG_SetConfigValues(); CG_StartMusic(); CG_LoadingString( "" ); #ifdef MISSIONPACK CG_InitTeamChat(); #endif CG_ShaderStateChanged(); trap_S_ClearLoopingSounds( qtrue ); } /* ================= CG_Shutdown Called before every level change or subsystem restart ================= */ void CG_Shutdown( void ) { // some mods may need to do cleanup work here, // like closing files or archiving session data } /* ================== CG_EventHandling ================== type 0 - no event handling 1 - team menu 2 - hud editor */ #ifndef MISSIONPACK void CG_EventHandling(int type) { } void CG_KeyEvent(int key, qboolean down) { } void CG_MouseEvent(int x, int y) { } #endif ================================================ FILE: code/cgame/cg_marks.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_marks.c -- wall marks #include "cg_local.h" /* =================================================================== MARK POLYS =================================================================== */ markPoly_t cg_activeMarkPolys; // double linked list markPoly_t *cg_freeMarkPolys; // single linked list markPoly_t cg_markPolys[MAX_MARK_POLYS]; static int markTotal; /* =================== CG_InitMarkPolys This is called at startup and for tournement restarts =================== */ void CG_InitMarkPolys( void ) { int i; memset( cg_markPolys, 0, sizeof(cg_markPolys) ); cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; cg_freeMarkPolys = cg_markPolys; for ( i = 0 ; i < MAX_MARK_POLYS - 1 ; i++ ) { cg_markPolys[i].nextMark = &cg_markPolys[i+1]; } } /* ================== CG_FreeMarkPoly ================== */ void CG_FreeMarkPoly( markPoly_t *le ) { if ( !le->prevMark ) { CG_Error( "CG_FreeLocalEntity: not active" ); } // remove from the doubly linked active list le->prevMark->nextMark = le->nextMark; le->nextMark->prevMark = le->prevMark; // the free list is only singly linked le->nextMark = cg_freeMarkPolys; cg_freeMarkPolys = le; } /* =================== CG_AllocMark Will allways succeed, even if it requires freeing an old active mark =================== */ markPoly_t *CG_AllocMark( void ) { markPoly_t *le; int time; if ( !cg_freeMarkPolys ) { // no free entities, so free the one at the end of the chain // remove the oldest active entity time = cg_activeMarkPolys.prevMark->time; while (cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time) { CG_FreeMarkPoly( cg_activeMarkPolys.prevMark ); } } le = cg_freeMarkPolys; cg_freeMarkPolys = cg_freeMarkPolys->nextMark; memset( le, 0, sizeof( *le ) ); // link into the active list le->nextMark = cg_activeMarkPolys.nextMark; le->prevMark = &cg_activeMarkPolys; cg_activeMarkPolys.nextMark->prevMark = le; cg_activeMarkPolys.nextMark = le; return le; } /* ================= CG_ImpactMark origin should be a point within a unit of the plane dir should be the plane normal temporary marks will not be stored or randomly oriented, but immediately passed to the renderer. ================= */ #define MAX_MARK_FRAGMENTS 128 #define MAX_MARK_POINTS 384 void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, float orientation, float red, float green, float blue, float alpha, qboolean alphaFade, float radius, qboolean temporary ) { vec3_t axis[3]; float texCoordScale; vec3_t originalPoints[4]; byte colors[4]; int i, j; int numFragments; markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; vec3_t markPoints[MAX_MARK_POINTS]; vec3_t projection; if ( !cg_addMarks.integer ) { return; } if ( radius <= 0 ) { CG_Error( "CG_ImpactMark called with <= 0 radius" ); } //if ( markTotal >= MAX_MARK_POLYS ) { // return; //} // create the texture axis VectorNormalize2( dir, axis[0] ); PerpendicularVector( axis[1], axis[0] ); RotatePointAroundVector( axis[2], axis[0], axis[1], orientation ); CrossProduct( axis[0], axis[2], axis[1] ); texCoordScale = 0.5 * 1.0 / radius; // create the full polygon for ( i = 0 ; i < 3 ; i++ ) { originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; } // get the fragments VectorScale( dir, -20, projection ); numFragments = trap_CM_MarkFragments( 4, (void *)originalPoints, projection, MAX_MARK_POINTS, markPoints[0], MAX_MARK_FRAGMENTS, markFragments ); colors[0] = red * 255; colors[1] = green * 255; colors[2] = blue * 255; colors[3] = alpha * 255; for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) { polyVert_t *v; polyVert_t verts[MAX_VERTS_ON_POLY]; markPoly_t *mark; // we have an upper limit on the complexity of polygons // that we store persistantly if ( mf->numPoints > MAX_VERTS_ON_POLY ) { mf->numPoints = MAX_VERTS_ON_POLY; } for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) { vec3_t delta; VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); VectorSubtract( v->xyz, origin, delta ); v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale; v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale; *(int *)v->modulate = *(int *)colors; } // if it is a temporary (shadow) mark, add it immediately and forget about it if ( temporary ) { trap_R_AddPolyToScene( markShader, mf->numPoints, verts ); continue; } // otherwise save it persistantly mark = CG_AllocMark(); mark->time = cg.time; mark->alphaFade = alphaFade; mark->markShader = markShader; mark->poly.numVerts = mf->numPoints; mark->color[0] = red; mark->color[1] = green; mark->color[2] = blue; mark->color[3] = alpha; memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); markTotal++; } } /* =============== CG_AddMarks =============== */ #define MARK_TOTAL_TIME 10000 #define MARK_FADE_TIME 1000 void CG_AddMarks( void ) { int j; markPoly_t *mp, *next; int t; int fade; if ( !cg_addMarks.integer ) { return; } mp = cg_activeMarkPolys.nextMark; for ( ; mp != &cg_activeMarkPolys ; mp = next ) { // grab next now, so if the local entity is freed we // still have it next = mp->nextMark; // see if it is time to completely remove it if ( cg.time > mp->time + MARK_TOTAL_TIME ) { CG_FreeMarkPoly( mp ); continue; } // fade out the energy bursts if ( mp->markShader == cgs.media.energyMarkShader ) { fade = 450 - 450 * ( (cg.time - mp->time ) / 3000.0 ); if ( fade < 255 ) { if ( fade < 0 ) { fade = 0; } if ( mp->verts[0].modulate[0] != 0 ) { for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { mp->verts[j].modulate[0] = mp->color[0] * fade; mp->verts[j].modulate[1] = mp->color[1] * fade; mp->verts[j].modulate[2] = mp->color[2] * fade; } } } } // fade all marks out with time t = mp->time + MARK_TOTAL_TIME - cg.time; if ( t < MARK_FADE_TIME ) { fade = 255 * t / MARK_FADE_TIME; if ( mp->alphaFade ) { for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { mp->verts[j].modulate[3] = fade; } } else { for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { mp->verts[j].modulate[0] = mp->color[0] * fade; mp->verts[j].modulate[1] = mp->color[1] * fade; mp->verts[j].modulate[2] = mp->color[2] * fade; } } } trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); } } // cg_particles.c #define BLOODRED 2 #define EMISIVEFADE 3 #define GREY75 4 typedef struct particle_s { struct particle_s *next; float time; float endtime; vec3_t org; vec3_t vel; vec3_t accel; int color; float colorvel; float alpha; float alphavel; int type; qhandle_t pshader; float height; float width; float endheight; float endwidth; float start; float end; float startfade; qboolean rotate; int snum; qboolean link; // Ridah int shaderAnim; int roll; int accumroll; } cparticle_t; typedef enum { P_NONE, P_WEATHER, P_FLAT, P_SMOKE, P_ROTATE, P_WEATHER_TURBULENT, P_ANIM, // Ridah P_BAT, P_BLEED, P_FLAT_SCALEUP, P_FLAT_SCALEUP_FADE, P_WEATHER_FLURRY, P_SMOKE_IMPACT, P_BUBBLE, P_BUBBLE_TURBULENT, P_SPRITE } particle_type_t; #define MAX_SHADER_ANIMS 32 #define MAX_SHADER_ANIM_FRAMES 64 static char *shaderAnimNames[MAX_SHADER_ANIMS] = { "explode1", NULL }; static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; static int shaderAnimCounts[MAX_SHADER_ANIMS] = { 23 }; static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { 1.0f }; static int numShaderAnims; // done. #define PARTICLE_GRAVITY 40 #define MAX_PARTICLES 1024 cparticle_t *active_particles, *free_particles; cparticle_t particles[MAX_PARTICLES]; int cl_numparticles = MAX_PARTICLES; qboolean initparticles = qfalse; vec3_t pvforward, pvright, pvup; vec3_t rforward, rright, rup; float oldtime; /* =============== CL_ClearParticles =============== */ void CG_ClearParticles (void) { int i; memset( particles, 0, sizeof(particles) ); free_particles = &particles[0]; active_particles = NULL; for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) {// create a front facing polygon if (p->type != P_WEATHER_FLURRY) { if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) { if (org[2] > p->end) { p->time = cg.time; VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground p->org[2] = ( p->start + crandom () * 4 ); if (p->type == P_BUBBLE_TURBULENT) { p->vel[0] = crandom() * 4; p->vel[1] = crandom() * 4; } } } else { if (org[2] < p->end) { p->time = cg.time; VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground while (p->org[2] < p->end) { p->org[2] += (p->start - p->end); } if (p->type == P_WEATHER_TURBULENT) { p->vel[0] = crandom() * 16; p->vel[1] = crandom() * 16; } } } // Rafael snow pvs check if (!p->link) return; p->alpha = 1; } // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp if (Distance( cg.snap->ps.origin, org ) > 1024) { return; } // done. if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) { VectorMA (org, -p->height, pvup, point); VectorMA (point, -p->width, pvright, point); VectorCopy (point, verts[0].xyz); verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255 * p->alpha; VectorMA (org, -p->height, pvup, point); VectorMA (point, p->width, pvright, point); VectorCopy (point, verts[1].xyz); verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255 * p->alpha; VectorMA (org, p->height, pvup, point); VectorMA (point, p->width, pvright, point); VectorCopy (point, verts[2].xyz); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = 255 * p->alpha; VectorMA (org, p->height, pvup, point); VectorMA (point, -p->width, pvright, point); VectorCopy (point, verts[3].xyz); verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = 255 * p->alpha; } else { VectorMA (org, -p->height, pvup, point); VectorMA (point, -p->width, pvright, point); VectorCopy( point, TRIverts[0].xyz ); TRIverts[0].st[0] = 1; TRIverts[0].st[1] = 0; TRIverts[0].modulate[0] = 255; TRIverts[0].modulate[1] = 255; TRIverts[0].modulate[2] = 255; TRIverts[0].modulate[3] = 255 * p->alpha; VectorMA (org, p->height, pvup, point); VectorMA (point, -p->width, pvright, point); VectorCopy (point, TRIverts[1].xyz); TRIverts[1].st[0] = 0; TRIverts[1].st[1] = 0; TRIverts[1].modulate[0] = 255; TRIverts[1].modulate[1] = 255; TRIverts[1].modulate[2] = 255; TRIverts[1].modulate[3] = 255 * p->alpha; VectorMA (org, p->height, pvup, point); VectorMA (point, p->width, pvright, point); VectorCopy (point, TRIverts[2].xyz); TRIverts[2].st[0] = 0; TRIverts[2].st[1] = 1; TRIverts[2].modulate[0] = 255; TRIverts[2].modulate[1] = 255; TRIverts[2].modulate[2] = 255; TRIverts[2].modulate[3] = 255 * p->alpha; } } else if (p->type == P_SPRITE) { vec3_t rr, ru; vec3_t rotate_ang; VectorSet (color, 1.0, 1.0, 0.5); time = cg.time - p->time; time2 = p->endtime - p->time; ratio = time / time2; width = p->width + ( ratio * ( p->endwidth - p->width) ); height = p->height + ( ratio * ( p->endheight - p->height) ); if (p->roll) { vectoangles( cg.refdef.viewaxis[0], rotate_ang ); rotate_ang[ROLL] += p->roll; AngleVectors ( rotate_ang, NULL, rr, ru); } if (p->roll) { VectorMA (org, -height, ru, point); VectorMA (point, -width, rr, point); } else { VectorMA (org, -height, pvup, point); VectorMA (point, -width, pvright, point); } VectorCopy (point, verts[0].xyz); verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255; if (p->roll) { VectorMA (point, 2*height, ru, point); } else { VectorMA (point, 2*height, pvup, point); } VectorCopy (point, verts[1].xyz); verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255; if (p->roll) { VectorMA (point, 2*width, rr, point); } else { VectorMA (point, 2*width, pvright, point); } VectorCopy (point, verts[2].xyz); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = 255; if (p->roll) { VectorMA (point, -2*height, ru, point); } else { VectorMA (point, -2*height, pvup, point); } VectorCopy (point, verts[3].xyz); verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = 255; } else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) {// create a front rotating facing polygon if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { return; } if (p->color == BLOODRED) VectorSet (color, 0.22f, 0.0f, 0.0f); else if (p->color == GREY75) { float len; float greyit; float val; len = Distance (cg.snap->ps.origin, org); if (!len) len = 1; val = 4096/len; greyit = 0.25 * val; if (greyit > 0.5) greyit = 0.5; VectorSet (color, greyit, greyit, greyit); } else VectorSet (color, 1.0, 1.0, 1.0); time = cg.time - p->time; time2 = p->endtime - p->time; ratio = time / time2; if (cg.time > p->startfade) { invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); if (p->color == EMISIVEFADE) { float fval; fval = (invratio * invratio); if (fval < 0) fval = 0; VectorSet (color, fval , fval , fval ); } invratio *= p->alpha; } else invratio = 1 * p->alpha; if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) invratio = 1; if (invratio > 1) invratio = 1; width = p->width + ( ratio * ( p->endwidth - p->width) ); height = p->height + ( ratio * ( p->endheight - p->height) ); if (p->type != P_SMOKE_IMPACT) { vec3_t temp; vectoangles (rforward, temp); p->accumroll += p->roll; temp[ROLL] += p->accumroll * 0.1; AngleVectors ( temp, NULL, rright2, rup2); } else { VectorCopy (rright, rright2); VectorCopy (rup, rup2); } if (p->rotate) { VectorMA (org, -height, rup2, point); VectorMA (point, -width, rright2, point); } else { VectorMA (org, -p->height, pvup, point); VectorMA (point, -p->width, pvright, point); } VectorCopy (point, verts[0].xyz); verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255 * color[0]; verts[0].modulate[1] = 255 * color[1]; verts[0].modulate[2] = 255 * color[2]; verts[0].modulate[3] = 255 * invratio; if (p->rotate) { VectorMA (org, -height, rup2, point); VectorMA (point, width, rright2, point); } else { VectorMA (org, -p->height, pvup, point); VectorMA (point, p->width, pvright, point); } VectorCopy (point, verts[1].xyz); verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255 * color[0]; verts[1].modulate[1] = 255 * color[1]; verts[1].modulate[2] = 255 * color[2]; verts[1].modulate[3] = 255 * invratio; if (p->rotate) { VectorMA (org, height, rup2, point); VectorMA (point, width, rright2, point); } else { VectorMA (org, p->height, pvup, point); VectorMA (point, p->width, pvright, point); } VectorCopy (point, verts[2].xyz); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255 * color[0]; verts[2].modulate[1] = 255 * color[1]; verts[2].modulate[2] = 255 * color[2]; verts[2].modulate[3] = 255 * invratio; if (p->rotate) { VectorMA (org, height, rup2, point); VectorMA (point, -width, rright2, point); } else { VectorMA (org, p->height, pvup, point); VectorMA (point, -p->width, pvright, point); } VectorCopy (point, verts[3].xyz); verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255 * color[0]; verts[3].modulate[1] = 255 * color[1]; verts[3].modulate[2] = 255 * color[2]; verts[3].modulate[3] = 255 * invratio; } else if (p->type == P_BLEED) { vec3_t rr, ru; vec3_t rotate_ang; float alpha; alpha = p->alpha; if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) alpha = 1; if (p->roll) { vectoangles( cg.refdef.viewaxis[0], rotate_ang ); rotate_ang[ROLL] += p->roll; AngleVectors ( rotate_ang, NULL, rr, ru); } else { VectorCopy (pvup, ru); VectorCopy (pvright, rr); } VectorMA (org, -p->height, ru, point); VectorMA (point, -p->width, rr, point); VectorCopy (point, verts[0].xyz); verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 111; verts[0].modulate[1] = 19; verts[0].modulate[2] = 9; verts[0].modulate[3] = 255 * alpha; VectorMA (org, -p->height, ru, point); VectorMA (point, p->width, rr, point); VectorCopy (point, verts[1].xyz); verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 111; verts[1].modulate[1] = 19; verts[1].modulate[2] = 9; verts[1].modulate[3] = 255 * alpha; VectorMA (org, p->height, ru, point); VectorMA (point, p->width, rr, point); VectorCopy (point, verts[2].xyz); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 111; verts[2].modulate[1] = 19; verts[2].modulate[2] = 9; verts[2].modulate[3] = 255 * alpha; VectorMA (org, p->height, ru, point); VectorMA (point, -p->width, rr, point); VectorCopy (point, verts[3].xyz); verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 111; verts[3].modulate[1] = 19; verts[3].modulate[2] = 9; verts[3].modulate[3] = 255 * alpha; } else if (p->type == P_FLAT_SCALEUP) { float width, height; float sinR, cosR; if (p->color == BLOODRED) VectorSet (color, 1, 1, 1); else VectorSet (color, 0.5, 0.5, 0.5); time = cg.time - p->time; time2 = p->endtime - p->time; ratio = time / time2; width = p->width + ( ratio * ( p->endwidth - p->width) ); height = p->height + ( ratio * ( p->endheight - p->height) ); if (width > p->endwidth) width = p->endwidth; if (height > p->endheight) height = p->endheight; sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); VectorCopy (org, verts[0].xyz); verts[0].xyz[0] -= sinR; verts[0].xyz[1] -= cosR; verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255 * color[0]; verts[0].modulate[1] = 255 * color[1]; verts[0].modulate[2] = 255 * color[2]; verts[0].modulate[3] = 255; VectorCopy (org, verts[1].xyz); verts[1].xyz[0] -= cosR; verts[1].xyz[1] += sinR; verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255 * color[0]; verts[1].modulate[1] = 255 * color[1]; verts[1].modulate[2] = 255 * color[2]; verts[1].modulate[3] = 255; VectorCopy (org, verts[2].xyz); verts[2].xyz[0] += sinR; verts[2].xyz[1] += cosR; verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255 * color[0]; verts[2].modulate[1] = 255 * color[1]; verts[2].modulate[2] = 255 * color[2]; verts[2].modulate[3] = 255; VectorCopy (org, verts[3].xyz); verts[3].xyz[0] += cosR; verts[3].xyz[1] -= sinR; verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255 * color[0]; verts[3].modulate[1] = 255 * color[1]; verts[3].modulate[2] = 255 * color[2]; verts[3].modulate[3] = 255; } else if (p->type == P_FLAT) { VectorCopy (org, verts[0].xyz); verts[0].xyz[0] -= p->height; verts[0].xyz[1] -= p->width; verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255; VectorCopy (org, verts[1].xyz); verts[1].xyz[0] -= p->height; verts[1].xyz[1] += p->width; verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255; VectorCopy (org, verts[2].xyz); verts[2].xyz[0] += p->height; verts[2].xyz[1] += p->width; verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = 255; VectorCopy (org, verts[3].xyz); verts[3].xyz[0] += p->height; verts[3].xyz[1] -= p->width; verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = 255; } // Ridah else if (p->type == P_ANIM) { vec3_t rr, ru; vec3_t rotate_ang; int i, j; time = cg.time - p->time; time2 = p->endtime - p->time; ratio = time / time2; if (ratio >= 1.0f) { ratio = 0.9999f; } width = p->width + ( ratio * ( p->endwidth - p->width) ); height = p->height + ( ratio * ( p->endheight - p->height) ); // if we are "inside" this sprite, don't draw if (Distance( cg.snap->ps.origin, org ) < width/1.5) { return; } i = p->shaderAnim; j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); p->pshader = shaderAnims[i][j]; if (p->roll) { vectoangles( cg.refdef.viewaxis[0], rotate_ang ); rotate_ang[ROLL] += p->roll; AngleVectors ( rotate_ang, NULL, rr, ru); } if (p->roll) { VectorMA (org, -height, ru, point); VectorMA (point, -width, rr, point); } else { VectorMA (org, -height, pvup, point); VectorMA (point, -width, pvright, point); } VectorCopy (point, verts[0].xyz); verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255; if (p->roll) { VectorMA (point, 2*height, ru, point); } else { VectorMA (point, 2*height, pvup, point); } VectorCopy (point, verts[1].xyz); verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255; if (p->roll) { VectorMA (point, 2*width, rr, point); } else { VectorMA (point, 2*width, pvright, point); } VectorCopy (point, verts[2].xyz); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = 255; if (p->roll) { VectorMA (point, -2*height, ru, point); } else { VectorMA (point, -2*height, pvup, point); } VectorCopy (point, verts[3].xyz); verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = 255; } // done. if (!p->pshader) { // (SA) temp commented out for DM // CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); return; } if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); else trap_R_AddPolyToScene( p->pshader, 4, verts ); } // Ridah, made this static so it doesn't interfere with other files static float roll = 0.0; /* =============== CG_AddParticles =============== */ void CG_AddParticles (void) { cparticle_t *p, *next; float alpha; float time, time2; vec3_t org; int color; cparticle_t *active, *tail; int type; vec3_t rotate_ang; if (!initparticles) CG_ClearParticles (); VectorCopy( cg.refdef.viewaxis[0], pvforward ); VectorCopy( cg.refdef.viewaxis[1], pvright ); VectorCopy( cg.refdef.viewaxis[2], pvup ); vectoangles( cg.refdef.viewaxis[0], rotate_ang ); roll += ((cg.time - oldtime) * 0.1) ; rotate_ang[ROLL] += (roll*0.9); AngleVectors ( rotate_ang, rforward, rright, rup); oldtime = cg.time; active = NULL; tail = NULL; for (p=active_particles ; p ; p=next) { next = p->next; time = (cg.time - p->time)*0.001; alpha = p->alpha + time*p->alphavel; if (alpha <= 0) { // faded out p->next = free_particles; free_particles = p; p->type = 0; p->color = 0; p->alpha = 0; continue; } if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) { if (cg.time > p->endtime) { p->next = free_particles; free_particles = p; p->type = 0; p->color = 0; p->alpha = 0; continue; } } if (p->type == P_WEATHER_FLURRY) { if (cg.time > p->endtime) { p->next = free_particles; free_particles = p; p->type = 0; p->color = 0; p->alpha = 0; continue; } } if (p->type == P_FLAT_SCALEUP_FADE) { if (cg.time > p->endtime) { p->next = free_particles; free_particles = p; p->type = 0; p->color = 0; p->alpha = 0; continue; } } if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { // temporary sprite CG_AddParticleToScene (p, p->org, alpha); p->next = free_particles; free_particles = p; p->type = 0; p->color = 0; p->alpha = 0; continue; } p->next = NULL; if (!tail) active = tail = p; else { tail->next = p; tail = p; } if (alpha > 1.0) alpha = 1; color = p->color; time2 = time*time; org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; type = p->type; CG_AddParticleToScene (p, org, alpha); } active_particles = active; } /* ====================== CG_AddParticles ====================== */ void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) { cparticle_t *p; qboolean turb = qtrue; if (!pshader) CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->color = 0; p->alpha = 0.90f; p->alphavel = 0; p->start = cent->currentState.origin2[0]; p->end = cent->currentState.origin2[1]; p->endtime = cg.time + cent->currentState.time; p->startfade = cg.time + cent->currentState.time2; p->pshader = pshader; if (rand()%100 > 90) { p->height = 32; p->width = 32; p->alpha = 0.10f; } else { p->height = 1; p->width = 1; } p->vel[2] = -20; p->type = P_WEATHER_FLURRY; if (turb) p->vel[2] = -10; VectorCopy(cent->currentState.origin, p->org); p->org[0] = p->org[0]; p->org[1] = p->org[1]; p->org[2] = p->org[2]; p->vel[0] = p->vel[1] = 0; p->accel[0] = p->accel[1] = p->accel[2] = 0; p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); p->vel[2] += cent->currentState.angles[2]; if (turb) { p->accel[0] = crandom () * 16; p->accel[1] = crandom () * 16; } } void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) { cparticle_t *p; if (!pshader) CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->color = 0; p->alpha = 0.40f; p->alphavel = 0; p->start = origin[2]; p->end = origin2[2]; p->pshader = pshader; p->height = 1; p->width = 1; p->vel[2] = -50; if (turb) { p->type = P_WEATHER_TURBULENT; p->vel[2] = -50 * 1.3; } else { p->type = P_WEATHER; } VectorCopy(origin, p->org); p->org[0] = p->org[0] + ( crandom() * range); p->org[1] = p->org[1] + ( crandom() * range); p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); p->vel[0] = p->vel[1] = 0; p->accel[0] = p->accel[1] = p->accel[2] = 0; if (turb) { p->vel[0] = crandom() * 16; p->vel[1] = crandom() * 16; } // Rafael snow pvs check p->snum = snum; p->link = qtrue; } void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) { cparticle_t *p; float randsize; if (!pshader) CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->color = 0; p->alpha = 0.40f; p->alphavel = 0; p->start = origin[2]; p->end = origin2[2]; p->pshader = pshader; randsize = 1 + (crandom() * 0.5); p->height = randsize; p->width = randsize; p->vel[2] = 50 + ( crandom() * 10 ); if (turb) { p->type = P_BUBBLE_TURBULENT; p->vel[2] = 50 * 1.3; } else { p->type = P_BUBBLE; } VectorCopy(origin, p->org); p->org[0] = p->org[0] + ( crandom() * range); p->org[1] = p->org[1] + ( crandom() * range); p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); p->vel[0] = p->vel[1] = 0; p->accel[0] = p->accel[1] = p->accel[2] = 0; if (turb) { p->vel[0] = crandom() * 4; p->vel[1] = crandom() * 4; } // Rafael snow pvs check p->snum = snum; p->link = qtrue; } void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) { // using cent->density = enttime // cent->frame = startfade cparticle_t *p; if (!pshader) CG_Printf ("CG_ParticleSmoke == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->endtime = cg.time + cent->currentState.time; p->startfade = cg.time + cent->currentState.time2; p->color = 0; p->alpha = 1.0; p->alphavel = 0; p->start = cent->currentState.origin[2]; p->end = cent->currentState.origin2[2]; p->pshader = pshader; p->rotate = qfalse; p->height = 8; p->width = 8; p->endheight = 32; p->endwidth = 32; p->type = P_SMOKE; VectorCopy(cent->currentState.origin, p->org); p->vel[0] = p->vel[1] = 0; p->accel[0] = p->accel[1] = p->accel[2] = 0; p->vel[2] = 5; if (cent->currentState.frame == 1)// reverse gravity p->vel[2] *= -1; p->roll = 8 + (crandom() * 4); } void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) { cparticle_t *p; if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->endtime = cg.time + duration; p->startfade = cg.time + duration/2; p->color = EMISIVEFADE; p->alpha = 1.0; p->alphavel = 0; p->height = 0.5; p->width = 0.5; p->endheight = 0.5; p->endwidth = 0.5; p->pshader = cgs.media.tracerShader; p->type = P_SMOKE; VectorCopy(org, p->org); p->vel[0] = vel[0]; p->vel[1] = vel[1]; p->vel[2] = vel[2]; p->accel[0] = p->accel[1] = p->accel[2] = 0; p->accel[2] = -60; p->vel[2] += -20; } /* ====================== CG_ParticleExplosion ====================== */ void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) { cparticle_t *p; int anim; if (animStr < (char *)10) CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); // find the animation string for (anim=0; shaderAnimNames[anim]; anim++) { if (!Q_stricmp( animStr, shaderAnimNames[anim] )) break; } if (!shaderAnimNames[anim]) { CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); return; } if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 0.5; p->alphavel = 0; if (duration < 0) { duration *= -1; p->roll = 0; } else { p->roll = crandom()*179; } p->shaderAnim = anim; p->width = sizeStart; p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction p->endheight = sizeEnd; p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; p->endtime = cg.time + duration; p->type = P_ANIM; VectorCopy( origin, p->org ); VectorCopy( vel, p->vel ); VectorClear( p->accel ); } // Rafael Shrapnel void CG_AddParticleShrapnel (localEntity_t *le) { return; } // done. int CG_NewParticleArea (int num) { // const char *str; char *str; char *token; int type; vec3_t origin, origin2; int i; float range = 0; int turb; int numparticles; int snum; str = (char *) CG_ConfigString (num); if (!str[0]) return (0); // returns type 128 64 or 32 token = COM_Parse (&str); type = atoi (token); if (type == 1) range = 128; else if (type == 2) range = 64; else if (type == 3) range = 32; else if (type == 0) range = 256; else if (type == 4) range = 8; else if (type == 5) range = 16; else if (type == 6) range = 32; else if (type == 7) range = 64; for (i=0; i<3; i++) { token = COM_Parse (&str); origin[i] = atof (token); } for (i=0; i<3; i++) { token = COM_Parse (&str); origin2[i] = atof (token); } token = COM_Parse (&str); numparticles = atoi (token); token = COM_Parse (&str); turb = atoi (token); token = COM_Parse (&str); snum = atoi (token); for (i=0; i= 4) CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); else CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); } return (1); } void CG_SnowLink (centity_t *cent, qboolean particleOn) { cparticle_t *p, *next; int id; id = cent->currentState.frame; for (p=active_particles ; p ; p=next) { next = p->next; if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) { if (p->snum == id) { if (particleOn) p->link = qtrue; else p->link = qfalse; } } } } void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) { cparticle_t *p; if (!pshader) CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 0.25; p->alphavel = 0; p->roll = crandom()*179; p->pshader = pshader; p->endtime = cg.time + 1000; p->startfade = cg.time + 100; p->width = rand()%4 + 8; p->height = rand()%4 + 8; p->endheight = p->height *2; p->endwidth = p->width * 2; p->endtime = cg.time + 500; p->type = P_SMOKE_IMPACT; VectorCopy( origin, p->org ); VectorSet(p->vel, 0, 0, 20); VectorSet(p->accel, 0, 0, 20); p->rotate = qtrue; } void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) { cparticle_t *p; if (!pshader) CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 1.0; p->alphavel = 0; p->roll = 0; p->pshader = pshader; p->endtime = cg.time + duration; if (fleshEntityNum) p->startfade = cg.time; else p->startfade = cg.time + 100; p->width = 4; p->height = 4; p->endheight = 4+rand()%3; p->endwidth = p->endheight; p->type = P_SMOKE; VectorCopy( start, p->org ); p->vel[0] = 0; p->vel[1] = 0; p->vel[2] = -20; VectorClear( p->accel ); p->rotate = qfalse; p->roll = rand()%179; p->color = BLOODRED; p->alpha = 0.75; } void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) { cparticle_t *p; int time; int time2; float ratio; float duration = 1500; time = cg.time; time2 = cg.time + cent->currentState.time; ratio =(float)1 - ((float)time / (float)time2); if (!pshader) CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 1.0; p->alphavel = 0; p->roll = 0; p->pshader = pshader; p->endtime = cg.time + duration; p->startfade = p->endtime; p->width = 1; p->height = 3; p->endheight = 3; p->endwidth = 1; p->type = P_SMOKE; VectorCopy(cent->currentState.origin, p->org ); p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); p->vel[2] = (cent->currentState.origin2[2]); p->snum = 1.0f; VectorClear( p->accel ); p->accel[2] = -20; p->rotate = qfalse; p->roll = rand()%179; p->alpha = 0.75; } void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) { cparticle_t *p; if (!pshader) CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; if (cent->currentState.angles2[2]) p->endtime = cg.time + cent->currentState.angles2[2]; else p->endtime = cg.time + 60000; p->startfade = p->endtime; p->alpha = 1.0; p->alphavel = 0; p->roll = 0; p->pshader = pshader; if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) { p->width = cent->currentState.angles2[0]; p->height = cent->currentState.angles2[0]; p->endheight = cent->currentState.angles2[1]; p->endwidth = cent->currentState.angles2[1]; } else { p->width = 8; p->height = 8; p->endheight = 16; p->endwidth = 16; } p->type = P_FLAT_SCALEUP; p->snum = 1.0; VectorCopy(cent->currentState.origin, p->org ); p->org[2]+= 0.55 + (crandom() * 0.5); p->vel[0] = 0; p->vel[1] = 0; p->vel[2] = 0; VectorClear( p->accel ); p->rotate = qfalse; p->roll = rand()%179; p->alpha = 0.75; } void CG_OilSlickRemove (centity_t *cent) { cparticle_t *p, *next; int id; id = 1.0f; if (!id) CG_Printf ("CG_OilSlickRevove NULL id\n"); for (p=active_particles ; p ; p=next) { next = p->next; if (p->type == P_FLAT_SCALEUP) { if (p->snum == id) { p->endtime = cg.time + 100; p->startfade = p->endtime; p->type = P_FLAT_SCALEUP_FADE; } } } } qboolean ValidBloodPool (vec3_t start) { #define EXTRUDE_DIST 0.5 vec3_t angles; vec3_t right, up; vec3_t this_pos, x_pos, center_pos, end_pos; float x, y; float fwidth, fheight; trace_t trace; vec3_t normal; fwidth = 16; fheight = 16; VectorSet (normal, 0, 0, 1); vectoangles (normal, angles); AngleVectors (angles, NULL, right, up); VectorMA (start, EXTRUDE_DIST, normal, center_pos); for (x= -fwidth/2; xendpos, start); legit = ValidBloodPool (start); if (!legit) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->endtime = cg.time + 3000; p->startfade = p->endtime; p->alpha = 1.0; p->alphavel = 0; p->roll = 0; p->pshader = pshader; rndSize = 0.4 + random()*0.6; p->width = 8*rndSize; p->height = 8*rndSize; p->endheight = 16*rndSize; p->endwidth = 16*rndSize; p->type = P_FLAT_SCALEUP; VectorCopy(start, p->org ); p->vel[0] = 0; p->vel[1] = 0; p->vel[2] = 0; VectorClear( p->accel ); p->rotate = qfalse; p->roll = rand()%179; p->alpha = 0.75; p->color = BLOODRED; } #define NORMALSIZE 16 #define LARGESIZE 32 void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) { float length; float dist; float crittersize; vec3_t angles, forward; vec3_t point; cparticle_t *p; int i; dist = 0; length = VectorLength (dir); vectoangles (dir, angles); AngleVectors (angles, forward, NULL, NULL); crittersize = LARGESIZE; if (length) dist = length / crittersize; if (dist < 1) dist = 1; VectorCopy (origin, point); for (i=0; inext; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 1.0; p->alphavel = 0; p->roll = 0; p->pshader = cgs.media.smokePuffShader; p->endtime = cg.time + 350 + (crandom() * 100); p->startfade = cg.time; p->width = LARGESIZE; p->height = LARGESIZE; p->endheight = LARGESIZE; p->endwidth = LARGESIZE; p->type = P_SMOKE; VectorCopy( origin, p->org ); p->vel[0] = 0; p->vel[1] = 0; p->vel[2] = -1; VectorClear( p->accel ); p->rotate = qfalse; p->roll = rand()%179; p->color = BLOODRED; p->alpha = 0.75; } } void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) { cparticle_t *p; if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->endtime = cg.time + duration; p->startfade = cg.time + duration/2; p->color = EMISIVEFADE; p->alpha = 0.4f; p->alphavel = 0; p->height = 0.5; p->width = 0.5; p->endheight = 0.5; p->endwidth = 0.5; p->pshader = cgs.media.tracerShader; p->type = P_SMOKE; VectorCopy(org, p->org); p->org[0] += (crandom() * x); p->org[1] += (crandom() * y); p->vel[0] = vel[0]; p->vel[1] = vel[1]; p->vel[2] = vel[2]; p->accel[0] = p->accel[1] = p->accel[2] = 0; p->vel[0] += (crandom() * 4); p->vel[1] += (crandom() * 4); p->vel[2] += (20 + (crandom() * 10)) * speed; p->accel[0] = crandom () * 4; p->accel[1] = crandom () * 4; } void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) { float length; float dist; float crittersize; vec3_t angles, forward; vec3_t point; cparticle_t *p; int i; dist = 0; VectorNegate (dir, dir); length = VectorLength (dir); vectoangles (dir, angles); AngleVectors (angles, forward, NULL, NULL); crittersize = LARGESIZE; if (length) dist = length / crittersize; if (dist < 1) dist = 1; VectorCopy (origin, point); for (i=0; inext; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 5.0; p->alphavel = 0; p->roll = 0; p->pshader = cgs.media.smokePuffShader; // RF, stay around for long enough to expand and dissipate naturally if (length) p->endtime = cg.time + 4500 + (crandom() * 3500); else p->endtime = cg.time + 750 + (crandom() * 500); p->startfade = cg.time; p->width = LARGESIZE; p->height = LARGESIZE; // RF, expand while falling p->endheight = LARGESIZE*3.0; p->endwidth = LARGESIZE*3.0; if (!length) { p->width *= 0.2f; p->height *= 0.2f; p->endheight = NORMALSIZE; p->endwidth = NORMALSIZE; } p->type = P_SMOKE; VectorCopy( point, p->org ); p->vel[0] = crandom()*6; p->vel[1] = crandom()*6; p->vel[2] = random()*20; // RF, add some gravity/randomness p->accel[0] = crandom()*3; p->accel[1] = crandom()*3; p->accel[2] = -PARTICLE_GRAVITY*0.4; VectorClear( p->accel ); p->rotate = qfalse; p->roll = rand()%179; p->alpha = 0.75; } } void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) { cparticle_t *p; if (!pshader) CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 1.0; p->alphavel = 0; p->roll = rand()%179; p->pshader = pshader; if (duration > 0) p->endtime = cg.time + duration; else p->endtime = duration; p->startfade = cg.time; p->width = size; p->height = size; p->endheight = size; p->endwidth = size; p->type = P_SPRITE; VectorCopy( origin, p->org ); p->rotate = qfalse; } ================================================ FILE: code/cgame/cg_newdraw.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #ifndef MISSIONPACK // bk001204 #error This file not be used for classic Q3A. #endif #include "cg_local.h" #include "../ui/ui_shared.h" extern displayContextDef_t cgDC; // set in CG_ParseTeamInfo //static int sortedTeamPlayers[TEAM_MAXOVERLAY]; //static int numSortedTeamPlayers; int drawTeamOverlayModificationCount = -1; //static char systemChat[256]; //static char teamChat1[256]; //static char teamChat2[256]; void CG_InitTeamChat() { memset(teamChat1, 0, sizeof(teamChat1)); memset(teamChat2, 0, sizeof(teamChat2)); memset(systemChat, 0, sizeof(systemChat)); } void CG_SetPrintString(int type, const char *p) { if (type == SYSTEM_PRINT) { strcpy(systemChat, p); } else { strcpy(teamChat2, teamChat1); strcpy(teamChat1, p); } } void CG_CheckOrderPending() { if (cgs.gametype < GT_CTF) { return; } if (cgs.orderPending) { //clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; const char *p1, *p2, *b; p1 = p2 = b = NULL; switch (cgs.currentOrder) { case TEAMTASK_OFFENSE: p1 = VOICECHAT_ONOFFENSE; p2 = VOICECHAT_OFFENSE; b = "+button7; wait; -button7"; break; case TEAMTASK_DEFENSE: p1 = VOICECHAT_ONDEFENSE; p2 = VOICECHAT_DEFEND; b = "+button8; wait; -button8"; break; case TEAMTASK_PATROL: p1 = VOICECHAT_ONPATROL; p2 = VOICECHAT_PATROL; b = "+button9; wait; -button9"; break; case TEAMTASK_FOLLOW: p1 = VOICECHAT_ONFOLLOW; p2 = VOICECHAT_FOLLOWME; b = "+button10; wait; -button10"; break; case TEAMTASK_CAMP: p1 = VOICECHAT_ONCAMPING; p2 = VOICECHAT_CAMP; break; case TEAMTASK_RETRIEVE: p1 = VOICECHAT_ONGETFLAG; p2 = VOICECHAT_RETURNFLAG; break; case TEAMTASK_ESCORT: p1 = VOICECHAT_ONFOLLOWCARRIER; p2 = VOICECHAT_FOLLOWFLAGCARRIER; break; } if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { // to everyone trap_SendConsoleCommand(va("cmd vsay_team %s\n", p2)); } else { // for the player self if (sortedTeamPlayers[cg_currentSelectedPlayer.integer] == cg.snap->ps.clientNum && p1) { trap_SendConsoleCommand(va("teamtask %i\n", cgs.currentOrder)); //trap_SendConsoleCommand(va("cmd say_team %s\n", p2)); trap_SendConsoleCommand(va("cmd vsay_team %s\n", p1)); } else if (p2) { //trap_SendConsoleCommand(va("cmd say_team %s, %s\n", ci->name,p)); trap_SendConsoleCommand(va("cmd vtell %d %s\n", sortedTeamPlayers[cg_currentSelectedPlayer.integer], p2)); } } if (b) { trap_SendConsoleCommand(b); } cgs.orderPending = qfalse; } } static void CG_SetSelectedPlayerName() { if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; if (ci) { trap_Cvar_Set("cg_selectedPlayerName", ci->name); trap_Cvar_Set("cg_selectedPlayer", va("%d", sortedTeamPlayers[cg_currentSelectedPlayer.integer])); cgs.currentOrder = ci->teamTask; } } else { trap_Cvar_Set("cg_selectedPlayerName", "Everyone"); } } int CG_GetSelectedPlayer() { if (cg_currentSelectedPlayer.integer < 0 || cg_currentSelectedPlayer.integer >= numSortedTeamPlayers) { cg_currentSelectedPlayer.integer = 0; } return cg_currentSelectedPlayer.integer; } void CG_SelectNextPlayer() { CG_CheckOrderPending(); if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { cg_currentSelectedPlayer.integer++; } else { cg_currentSelectedPlayer.integer = 0; } CG_SetSelectedPlayerName(); } void CG_SelectPrevPlayer() { CG_CheckOrderPending(); if (cg_currentSelectedPlayer.integer > 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { cg_currentSelectedPlayer.integer--; } else { cg_currentSelectedPlayer.integer = numSortedTeamPlayers; } CG_SetSelectedPlayerName(); } static void CG_DrawPlayerArmorIcon( rectDef_t *rect, qboolean draw2D ) { centity_t *cent; playerState_t *ps; vec3_t angles; vec3_t origin; if ( cg_drawStatus.integer == 0 ) { return; } cent = &cg_entities[cg.snap->ps.clientNum]; ps = &cg.snap->ps; if ( draw2D || ( !cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses CG_DrawPic( rect->x, rect->y + rect->h/2 + 1, rect->w, rect->h, cgs.media.armorIcon ); } else if (cg_draw3dIcons.integer) { VectorClear( angles ); origin[0] = 90; origin[1] = 0; origin[2] = -10; angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cgs.media.armorModel, 0, origin, angles ); } } static void CG_DrawPlayerArmorValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { char num[16]; int value; centity_t *cent; playerState_t *ps; cent = &cg_entities[cg.snap->ps.clientNum]; ps = &cg.snap->ps; value = ps->stats[STAT_ARMOR]; if (shader) { trap_R_SetColor( color ); CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); trap_R_SetColor( NULL ); } else { Com_sprintf (num, sizeof(num), "%i", value); value = CG_Text_Width(num, scale, 0); CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); } } #ifndef MISSIONPACK // bk001206 static float healthColors[4][4] = { // { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; // bk0101016 - float const { 1.0f, 0.69f, 0.0f, 1.0f } , // normal { 1.0f, 0.2f, 0.2f, 1.0f }, // low health { 0.5f, 0.5f, 0.5f, 1.0f}, // weapon firing { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 #endif static void CG_DrawPlayerAmmoIcon( rectDef_t *rect, qboolean draw2D ) { centity_t *cent; playerState_t *ps; vec3_t angles; vec3_t origin; cent = &cg_entities[cg.snap->ps.clientNum]; ps = &cg.snap->ps; if ( draw2D || (!cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses qhandle_t icon; icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; if ( icon ) { CG_DrawPic( rect->x, rect->y, rect->w, rect->h, icon ); } } else if (cg_draw3dIcons.integer) { if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { VectorClear( angles ); origin[0] = 70; origin[1] = 0; origin[2] = 0; angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); } } } static void CG_DrawPlayerAmmoValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { char num[16]; int value; centity_t *cent; playerState_t *ps; cent = &cg_entities[cg.snap->ps.clientNum]; ps = &cg.snap->ps; if ( cent->currentState.weapon ) { value = ps->ammo[cent->currentState.weapon]; if ( value > -1 ) { if (shader) { trap_R_SetColor( color ); CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); trap_R_SetColor( NULL ); } else { Com_sprintf (num, sizeof(num), "%i", value); value = CG_Text_Width(num, scale, 0); CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); } } } } static void CG_DrawPlayerHead(rectDef_t *rect, qboolean draw2D) { vec3_t angles; float size, stretch; float frac; float x = rect->x; VectorClear( angles ); if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; size = rect->w * 1.25 * ( 1.5 - frac * 0.5 ); stretch = size - rect->w * 1.25; // kick in the direction of damage x -= stretch * 0.5 + cg.damageX * stretch * 0.5; cg.headStartYaw = 180 + cg.damageX * 45; cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); cg.headEndPitch = 5 * cos( crandom()*M_PI ); cg.headStartTime = cg.time; cg.headEndTime = cg.time + 100 + random() * 2000; } else { if ( cg.time >= cg.headEndTime ) { // select a new head angle cg.headStartYaw = cg.headEndYaw; cg.headStartPitch = cg.headEndPitch; cg.headStartTime = cg.headEndTime; cg.headEndTime = cg.time + 100 + random() * 2000; cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); cg.headEndPitch = 5 * cos( crandom()*M_PI ); } size = rect->w * 1.25; } // if the server was frozen for a while we may have a bad head start time if ( cg.headStartTime > cg.time ) { cg.headStartTime = cg.time; } frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); frac = frac * frac * ( 3 - 2 * frac ); angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; CG_DrawHead( x, rect->y, rect->w, rect->h, cg.snap->ps.clientNum, angles ); } static void CG_DrawSelectedPlayerHealth( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { clientInfo_t *ci; int value; char num[16]; ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; if (ci) { if (shader) { trap_R_SetColor( color ); CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); trap_R_SetColor( NULL ); } else { Com_sprintf (num, sizeof(num), "%i", ci->health); value = CG_Text_Width(num, scale, 0); CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); } } } static void CG_DrawSelectedPlayerArmor( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { clientInfo_t *ci; int value; char num[16]; ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; if (ci) { if (ci->armor > 0) { if (shader) { trap_R_SetColor( color ); CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); trap_R_SetColor( NULL ); } else { Com_sprintf (num, sizeof(num), "%i", ci->armor); value = CG_Text_Width(num, scale, 0); CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); } } } } qhandle_t CG_StatusHandle(int task) { qhandle_t h = cgs.media.assaultShader; switch (task) { case TEAMTASK_OFFENSE : h = cgs.media.assaultShader; break; case TEAMTASK_DEFENSE : h = cgs.media.defendShader; break; case TEAMTASK_PATROL : h = cgs.media.patrolShader; break; case TEAMTASK_FOLLOW : h = cgs.media.followShader; break; case TEAMTASK_CAMP : h = cgs.media.campShader; break; case TEAMTASK_RETRIEVE : h = cgs.media.retrieveShader; break; case TEAMTASK_ESCORT : h = cgs.media.escortShader; break; default : h = cgs.media.assaultShader; break; } return h; } static void CG_DrawSelectedPlayerStatus( rectDef_t *rect ) { clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; if (ci) { qhandle_t h; if (cgs.orderPending) { // blink the icon if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { return; } h = CG_StatusHandle(cgs.currentOrder); } else { h = CG_StatusHandle(ci->teamTask); } CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h ); } } static void CG_DrawPlayerStatus( rectDef_t *rect ) { clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; if (ci) { qhandle_t h = CG_StatusHandle(ci->teamTask); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h); } } static void CG_DrawSelectedPlayerName( rectDef_t *rect, float scale, vec4_t color, qboolean voice, int textStyle) { clientInfo_t *ci; ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); if (ci) { CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, ci->name, 0, 0, textStyle); } } static void CG_DrawSelectedPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { clientInfo_t *ci; ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; if (ci) { const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); if (!p || !*p) { p = "unknown"; } CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); } } static void CG_DrawPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; if (ci) { const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); if (!p || !*p) { p = "unknown"; } CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); } } static void CG_DrawSelectedPlayerWeapon( rectDef_t *rect ) { clientInfo_t *ci; ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; if (ci) { if ( cg_weapons[ci->curWeapon].weaponIcon ) { CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ci->curWeapon].weaponIcon ); } else { CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader); } } } static void CG_DrawPlayerScore( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { char num[16]; int value = cg.snap->ps.persistant[PERS_SCORE]; if (shader) { trap_R_SetColor( color ); CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); trap_R_SetColor( NULL ); } else { Com_sprintf (num, sizeof(num), "%i", value); value = CG_Text_Width(num, scale, 0); CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); } } static void CG_DrawPlayerItem( rectDef_t *rect, float scale, qboolean draw2D) { int value; vec3_t origin, angles; value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; if ( value ) { CG_RegisterItemVisuals( value ); if (qtrue) { CG_RegisterItemVisuals( value ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); } else { VectorClear( angles ); origin[0] = 90; origin[1] = 0; origin[2] = -10; angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; CG_Draw3DModel(rect->x, rect->y, rect->w, rect->h, cg_items[ value ].models[0], 0, origin, angles ); } } } static void CG_DrawSelectedPlayerPowerup( rectDef_t *rect, qboolean draw2D ) { clientInfo_t *ci; int j; float x, y; ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; if (ci) { x = rect->x; y = rect->y; for (j = 0; j < PW_NUM_POWERUPS; j++) { if (ci->powerups & (1 << j)) { gitem_t *item; item = BG_FindItemForPowerup( j ); if (item) { CG_DrawPic( x, y, rect->w, rect->h, trap_R_RegisterShader( item->icon ) ); x += 3; y += 3; return; } } } } } static void CG_DrawSelectedPlayerHead( rectDef_t *rect, qboolean draw2D, qboolean voice ) { clipHandle_t cm; clientInfo_t *ci; float len; vec3_t origin; vec3_t mins, maxs, angles; ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); if (ci) { if ( cg_draw3dIcons.integer ) { cm = ci->headModel; if ( !cm ) { return; } // offset the origin y and z to center the head trap_R_ModelBounds( cm, mins, maxs ); origin[2] = -0.5 * ( mins[2] + maxs[2] ); origin[1] = 0.5 * ( mins[1] + maxs[1] ); // calculate distance so the head nearly fills the box // assume heads are taller than wide len = 0.7 * ( maxs[2] - mins[2] ); origin[0] = len / 0.268; // len / tan( fov/2 ) // allow per-model tweaking VectorAdd( origin, ci->headOffset, origin ); angles[PITCH] = 0; angles[YAW] = 180; angles[ROLL] = 0; CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, ci->headModel, ci->headSkin, origin, angles ); } else if ( cg_drawIcons.integer ) { CG_DrawPic( rect->x, rect->y, rect->w, rect->h, ci->modelIcon ); } // if they are deferred, draw a cross out if ( ci->deferred ) { CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader ); } } } static void CG_DrawPlayerHealth(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { playerState_t *ps; int value; char num[16]; ps = &cg.snap->ps; value = ps->stats[STAT_HEALTH]; if (shader) { trap_R_SetColor( color ); CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); trap_R_SetColor( NULL ); } else { Com_sprintf (num, sizeof(num), "%i", value); value = CG_Text_Width(num, scale, 0); CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); } } static void CG_DrawRedScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { int value; char num[16]; if ( cgs.scores1 == SCORE_NOT_PRESENT ) { Com_sprintf (num, sizeof(num), "-"); } else { Com_sprintf (num, sizeof(num), "%i", cgs.scores1); } value = CG_Text_Width(num, scale, 0); CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); } static void CG_DrawBlueScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { int value; char num[16]; if ( cgs.scores2 == SCORE_NOT_PRESENT ) { Com_sprintf (num, sizeof(num), "-"); } else { Com_sprintf (num, sizeof(num), "%i", cgs.scores2); } value = CG_Text_Width(num, scale, 0); CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); } // FIXME: team name support static void CG_DrawRedName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_redTeamName.string , 0, 0, textStyle); } static void CG_DrawBlueName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_blueTeamName.string, 0, 0, textStyle); } static void CG_DrawBlueFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { int i; for ( i = 0 ; i < cgs.maxclients ; i++ ) { if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); return; } } } static void CG_DrawBlueFlagStatus(rectDef_t *rect, qhandle_t shader) { if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { if (cgs.gametype == GT_HARVESTER) { vec4_t color = {0, 0, 1, 1}; trap_R_SetColor(color); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.blueCubeIcon ); trap_R_SetColor(NULL); } return; } if (shader) { CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); } else { gitem_t *item = BG_FindItemForPowerup( PW_BLUEFLAG ); if (item) { vec4_t color = {0, 0, 1, 1}; trap_R_SetColor(color); if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.blueflag] ); } else { CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); } trap_R_SetColor(NULL); } } } static void CG_DrawBlueFlagHead(rectDef_t *rect) { int i; for ( i = 0 ; i < cgs.maxclients ; i++ ) { if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { vec3_t angles; VectorClear( angles ); angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); return; } } } static void CG_DrawRedFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { int i; for ( i = 0 ; i < cgs.maxclients ; i++ ) { if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); return; } } } static void CG_DrawRedFlagStatus(rectDef_t *rect, qhandle_t shader) { if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { if (cgs.gametype == GT_HARVESTER) { vec4_t color = {1, 0, 0, 1}; trap_R_SetColor(color); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.redCubeIcon ); trap_R_SetColor(NULL); } return; } if (shader) { CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); } else { gitem_t *item = BG_FindItemForPowerup( PW_REDFLAG ); if (item) { vec4_t color = {1, 0, 0, 1}; trap_R_SetColor(color); if( cgs.redflag >= 0 && cgs.redflag <= 2) { CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.redflag] ); } else { CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); } trap_R_SetColor(NULL); } } } static void CG_DrawRedFlagHead(rectDef_t *rect) { int i; for ( i = 0 ; i < cgs.maxclients ; i++ ) { if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { vec3_t angles; VectorClear( angles ); angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); return; } } } static void CG_HarvesterSkulls(rectDef_t *rect, float scale, vec4_t color, qboolean force2D, int textStyle ) { char num[16]; vec3_t origin, angles; qhandle_t handle; int value = cg.snap->ps.generic1; if (cgs.gametype != GT_HARVESTER) { return; } if( value > 99 ) { value = 99; } Com_sprintf (num, sizeof(num), "%i", value); value = CG_Text_Width(num, scale, 0); CG_Text_Paint(rect->x + (rect->w - value), rect->y + rect->h, scale, color, num, 0, 0, textStyle); if (cg_drawIcons.integer) { if (!force2D && cg_draw3dIcons.integer) { VectorClear(angles); origin[0] = 90; origin[1] = 0; origin[2] = -10; angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { handle = cgs.media.redCubeModel; } else { handle = cgs.media.blueCubeModel; } CG_Draw3DModel( rect->x, rect->y, 35, 35, handle, 0, origin, angles ); } else { if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { handle = cgs.media.redCubeIcon; } else { handle = cgs.media.blueCubeIcon; } CG_DrawPic( rect->x + 3, rect->y + 16, 20, 20, handle ); } } } static void CG_OneFlagStatus(rectDef_t *rect) { if (cgs.gametype != GT_1FCTF) { return; } else { gitem_t *item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); if (item) { if( cgs.flagStatus >= 0 && cgs.flagStatus <= 4 ) { vec4_t color = {1, 1, 1, 1}; int index = 0; if (cgs.flagStatus == FLAG_TAKEN_RED) { color[1] = color[2] = 0; index = 1; } else if (cgs.flagStatus == FLAG_TAKEN_BLUE) { color[0] = color[1] = 0; index = 1; } else if (cgs.flagStatus == FLAG_DROPPED) { index = 2; } trap_R_SetColor(color); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[index] ); } } } } static void CG_DrawCTFPowerUp(rectDef_t *rect) { int value; if (cgs.gametype < GT_CTF) { return; } value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; if ( value ) { CG_RegisterItemVisuals( value ); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); } } static void CG_DrawTeamColor(rectDef_t *rect, vec4_t color) { CG_DrawTeamBackground(rect->x, rect->y, rect->w, rect->h, color[3], cg.snap->ps.persistant[PERS_TEAM]); } static void CG_DrawAreaPowerUp(rectDef_t *rect, int align, float special, float scale, vec4_t color) { char num[16]; int sorted[MAX_POWERUPS]; int sortedTime[MAX_POWERUPS]; int i, j, k; int active; playerState_t *ps; int t; gitem_t *item; float f; rectDef_t r2; float *inc; r2.x = rect->x; r2.y = rect->y; r2.w = rect->w; r2.h = rect->h; inc = (align == HUD_VERTICAL) ? &r2.y : &r2.x; ps = &cg.snap->ps; if ( ps->stats[STAT_HEALTH] <= 0 ) { return; } // sort the list by time remaining active = 0; for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { if ( !ps->powerups[ i ] ) { continue; } t = ps->powerups[ i ] - cg.time; // ZOID--don't draw if the power up has unlimited time (999 seconds) // This is true of the CTF flags if ( t <= 0 || t >= 999000) { continue; } // insert into the list for ( j = 0 ; j < active ; j++ ) { if ( sortedTime[j] >= t ) { for ( k = active - 1 ; k >= j ; k-- ) { sorted[k+1] = sorted[k]; sortedTime[k+1] = sortedTime[k]; } break; } } sorted[j] = i; sortedTime[j] = t; active++; } // draw the icons and timers for ( i = 0 ; i < active ; i++ ) { item = BG_FindItemForPowerup( sorted[i] ); if (item) { t = ps->powerups[ sorted[i] ]; if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { trap_R_SetColor( NULL ); } else { vec4_t modulate; f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; f -= (int)f; modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; trap_R_SetColor( modulate ); } CG_DrawPic( r2.x, r2.y, r2.w * .75, r2.h, trap_R_RegisterShader( item->icon ) ); Com_sprintf (num, sizeof(num), "%i", sortedTime[i] / 1000); CG_Text_Paint(r2.x + (r2.w * .75) + 3 , r2.y + r2.h, scale, color, num, 0, 0, 0); *inc += r2.w + special; } } trap_R_SetColor( NULL ); } float CG_GetValue(int ownerDraw) { centity_t *cent; clientInfo_t *ci; playerState_t *ps; cent = &cg_entities[cg.snap->ps.clientNum]; ps = &cg.snap->ps; switch (ownerDraw) { case CG_SELECTEDPLAYER_ARMOR: ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; return ci->armor; break; case CG_SELECTEDPLAYER_HEALTH: ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; return ci->health; break; case CG_PLAYER_ARMOR_VALUE: return ps->stats[STAT_ARMOR]; break; case CG_PLAYER_AMMO_VALUE: if ( cent->currentState.weapon ) { return ps->ammo[cent->currentState.weapon]; } break; case CG_PLAYER_SCORE: return cg.snap->ps.persistant[PERS_SCORE]; break; case CG_PLAYER_HEALTH: return ps->stats[STAT_HEALTH]; break; case CG_RED_SCORE: return cgs.scores1; break; case CG_BLUE_SCORE: return cgs.scores2; break; default: break; } return -1; } qboolean CG_OtherTeamHasFlag() { if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { int team = cg.snap->ps.persistant[PERS_TEAM]; if (cgs.gametype == GT_1FCTF) { if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_BLUE) { return qtrue; } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_RED) { return qtrue; } else { return qfalse; } } else { if (team == TEAM_RED && cgs.redflag == FLAG_TAKEN) { return qtrue; } else if (team == TEAM_BLUE && cgs.blueflag == FLAG_TAKEN) { return qtrue; } else { return qfalse; } } } return qfalse; } qboolean CG_YourTeamHasFlag() { if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { int team = cg.snap->ps.persistant[PERS_TEAM]; if (cgs.gametype == GT_1FCTF) { if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_RED) { return qtrue; } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_BLUE) { return qtrue; } else { return qfalse; } } else { if (team == TEAM_RED && cgs.blueflag == FLAG_TAKEN) { return qtrue; } else if (team == TEAM_BLUE && cgs.redflag == FLAG_TAKEN) { return qtrue; } else { return qfalse; } } } return qfalse; } // THINKABOUTME: should these be exclusive or inclusive.. // qboolean CG_OwnerDrawVisible(int flags) { if (flags & CG_SHOW_TEAMINFO) { return (cg_currentSelectedPlayer.integer == numSortedTeamPlayers); } if (flags & CG_SHOW_NOTEAMINFO) { return !(cg_currentSelectedPlayer.integer == numSortedTeamPlayers); } if (flags & CG_SHOW_OTHERTEAMHASFLAG) { return CG_OtherTeamHasFlag(); } if (flags & CG_SHOW_YOURTEAMHASENEMYFLAG) { return CG_YourTeamHasFlag(); } if (flags & (CG_SHOW_BLUE_TEAM_HAS_REDFLAG | CG_SHOW_RED_TEAM_HAS_BLUEFLAG)) { if (flags & CG_SHOW_BLUE_TEAM_HAS_REDFLAG && (cgs.redflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_RED)) { return qtrue; } else if (flags & CG_SHOW_RED_TEAM_HAS_BLUEFLAG && (cgs.blueflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_BLUE)) { return qtrue; } return qfalse; } if (flags & CG_SHOW_ANYTEAMGAME) { if( cgs.gametype >= GT_TEAM) { return qtrue; } } if (flags & CG_SHOW_ANYNONTEAMGAME) { if( cgs.gametype < GT_TEAM) { return qtrue; } } if (flags & CG_SHOW_HARVESTER) { if( cgs.gametype == GT_HARVESTER ) { return qtrue; } else { return qfalse; } } if (flags & CG_SHOW_ONEFLAG) { if( cgs.gametype == GT_1FCTF ) { return qtrue; } else { return qfalse; } } if (flags & CG_SHOW_CTF) { if( cgs.gametype == GT_CTF ) { return qtrue; } } if (flags & CG_SHOW_OBELISK) { if( cgs.gametype == GT_OBELISK ) { return qtrue; } else { return qfalse; } } if (flags & CG_SHOW_HEALTHCRITICAL) { if (cg.snap->ps.stats[STAT_HEALTH] < 25) { return qtrue; } } if (flags & CG_SHOW_HEALTHOK) { if (cg.snap->ps.stats[STAT_HEALTH] >= 25) { return qtrue; } } if (flags & CG_SHOW_SINGLEPLAYER) { if( cgs.gametype == GT_SINGLE_PLAYER ) { return qtrue; } } if (flags & CG_SHOW_TOURNAMENT) { if( cgs.gametype == GT_TOURNAMENT ) { return qtrue; } } if (flags & CG_SHOW_DURINGINCOMINGVOICE) { } if (flags & CG_SHOW_IF_PLAYER_HAS_FLAG) { if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { return qtrue; } } return qfalse; } static void CG_DrawPlayerHasFlag(rectDef_t *rect, qboolean force2D) { int adj = (force2D) ? 0 : 2; if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_RED, force2D); } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_BLUE, force2D); } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_FREE, force2D); } } static void CG_DrawAreaSystemChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, systemChat, 0, 0, 0); } static void CG_DrawAreaTeamChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { CG_Text_Paint(rect->x, rect->y + rect->h, scale, color,teamChat1, 0, 0, 0); } static void CG_DrawAreaChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, teamChat2, 0, 0, 0); } const char *CG_GetKillerText() { const char *s = ""; if ( cg.killerName[0] ) { s = va("Fragged by %s", cg.killerName ); } return s; } static void CG_DrawKiller(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { // fragged by ... line if ( cg.killerName[0] ) { int x = rect->x + rect->w / 2; CG_Text_Paint(x - CG_Text_Width(CG_GetKillerText(), scale, 0) / 2, rect->y + rect->h, scale, color, CG_GetKillerText(), 0, 0, textStyle); } } static void CG_DrawCapFragLimit(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { int limit = (cgs.gametype >= GT_CTF) ? cgs.capturelimit : cgs.fraglimit; CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", limit),0, 0, textStyle); } static void CG_Draw1stPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { if (cgs.scores1 != SCORE_NOT_PRESENT) { CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores1),0, 0, textStyle); } } static void CG_Draw2ndPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { if (cgs.scores2 != SCORE_NOT_PRESENT) { CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores2),0, 0, textStyle); } } const char *CG_GetGameStatusText() { const char *s = ""; if ( cgs.gametype < GT_TEAM) { if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { s = va("%s place with %i",CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),cg.snap->ps.persistant[PERS_SCORE] ); } } else { if ( cg.teamScores[0] == cg.teamScores[1] ) { s = va("Teams are tied at %i", cg.teamScores[0] ); } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { s = va("Red leads Blue, %i to %i", cg.teamScores[0], cg.teamScores[1] ); } else { s = va("Blue leads Red, %i to %i", cg.teamScores[1], cg.teamScores[0] ); } } return s; } static void CG_DrawGameStatus(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GetGameStatusText(), 0, 0, textStyle); } const char *CG_GameTypeString() { if ( cgs.gametype == GT_FFA ) { return "Free For All"; } else if ( cgs.gametype == GT_TEAM ) { return "Team Deathmatch"; } else if ( cgs.gametype == GT_CTF ) { return "Capture the Flag"; } else if ( cgs.gametype == GT_1FCTF ) { return "One Flag CTF"; } else if ( cgs.gametype == GT_OBELISK ) { return "Overload"; } else if ( cgs.gametype == GT_HARVESTER ) { return "Harvester"; } return ""; } static void CG_DrawGameType(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GameTypeString(), 0, 0, textStyle); } static void CG_Text_Paint_Limit(float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit) { int len, count; vec4_t newColor; glyphInfo_t *glyph; if (text) { // TTimo: FIXME // const unsigned char *s = text; // bk001206 - unsigned const char *s = text; float max = *maxX; float useScale; fontInfo_t *font = &cgDC.Assets.textFont; if (scale <= cg_smallFont.value) { font = &cgDC.Assets.smallFont; } else if (scale > cg_bigFont.value) { font = &cgDC.Assets.bigFont; } useScale = scale * font->glyphScale; trap_R_SetColor( color ); len = strlen(text); if (limit > 0 && len > limit) { len = limit; } count = 0; while (s && *s && count < len) { glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build if ( Q_IsColorString( s ) ) { memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); newColor[3] = color[3]; trap_R_SetColor( newColor ); s += 2; continue; } else { float yadj = useScale * glyph->top; if (CG_Text_Width(s, useScale, 1) + x > max) { *maxX = 0; break; } CG_Text_PaintChar(x, y - yadj, glyph->imageWidth, glyph->imageHeight, useScale, glyph->s, glyph->t, glyph->s2, glyph->t2, glyph->glyph); x += (glyph->xSkip * useScale) + adjust; *maxX = x; count++; s++; } } trap_R_SetColor( NULL ); } } #define PIC_WIDTH 12 void CG_DrawNewTeamInfo(rectDef_t *rect, float text_x, float text_y, float scale, vec4_t color, qhandle_t shader) { int xx; float y; int i, j, len, count; const char *p; vec4_t hcolor; float pwidth, lwidth, maxx, leftOver; clientInfo_t *ci; gitem_t *item; qhandle_t h; // max player name width pwidth = 0; count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; for (i = 0; i < count; i++) { ci = cgs.clientinfo + sortedTeamPlayers[i]; if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { len = CG_Text_Width( ci->name, scale, 0); if (len > pwidth) pwidth = len; } } // max location name width lwidth = 0; for (i = 1; i < MAX_LOCATIONS; i++) { p = CG_ConfigString(CS_LOCATIONS + i); if (p && *p) { len = CG_Text_Width(p, scale, 0); if (len > lwidth) lwidth = len; } } y = rect->y; for (i = 0; i < count; i++) { ci = cgs.clientinfo + sortedTeamPlayers[i]; if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { xx = rect->x + 1; for (j = 0; j <= PW_NUM_POWERUPS; j++) { if (ci->powerups & (1 << j)) { item = BG_FindItemForPowerup( j ); if (item) { CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, trap_R_RegisterShader( item->icon ) ); xx += PIC_WIDTH; } } } // FIXME: max of 3 powerups shown properly xx = rect->x + (PIC_WIDTH * 3) + 2; CG_GetColorForHealth( ci->health, ci->armor, hcolor ); trap_R_SetColor(hcolor); CG_DrawPic( xx, y + 1, PIC_WIDTH - 2, PIC_WIDTH - 2, cgs.media.heartShader ); //Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); //CG_Text_Paint(xx, y + text_y, scale, hcolor, st, 0, 0); // draw weapon icon xx += PIC_WIDTH + 1; // weapon used is not that useful, use the space for task #if 0 if ( cg_weapons[ci->curWeapon].weaponIcon ) { CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cg_weapons[ci->curWeapon].weaponIcon ); } else { CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cgs.media.deferShader ); } #endif trap_R_SetColor(NULL); if (cgs.orderPending) { // blink the icon if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { h = 0; } else { h = CG_StatusHandle(cgs.currentOrder); } } else { h = CG_StatusHandle(ci->teamTask); } if (h) { CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, h); } xx += PIC_WIDTH + 1; leftOver = rect->w - xx; maxx = xx + leftOver / 3; CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, ci->name, 0, 0); p = CG_ConfigString(CS_LOCATIONS + ci->location); if (!p || !*p) { p = "unknown"; } xx += leftOver / 3 + 2; maxx = rect->w - 4; CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, p, 0, 0); y += text_y + 2; if ( y + text_y + 2 > rect->y + rect->h ) { break; } } } } void CG_DrawTeamSpectators(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { if (cg.spectatorLen) { float maxX; if (cg.spectatorWidth == -1) { cg.spectatorWidth = 0; cg.spectatorPaintX = rect->x + 1; cg.spectatorPaintX2 = -1; } if (cg.spectatorOffset > cg.spectatorLen) { cg.spectatorOffset = 0; cg.spectatorPaintX = rect->x + 1; cg.spectatorPaintX2 = -1; } if (cg.time > cg.spectatorTime) { cg.spectatorTime = cg.time + 10; if (cg.spectatorPaintX <= rect->x + 2) { if (cg.spectatorOffset < cg.spectatorLen) { cg.spectatorPaintX += CG_Text_Width(&cg.spectatorList[cg.spectatorOffset], scale, 1) - 1; cg.spectatorOffset++; } else { cg.spectatorOffset = 0; if (cg.spectatorPaintX2 >= 0) { cg.spectatorPaintX = cg.spectatorPaintX2; } else { cg.spectatorPaintX = rect->x + rect->w - 2; } cg.spectatorPaintX2 = -1; } } else { cg.spectatorPaintX--; if (cg.spectatorPaintX2 >= 0) { cg.spectatorPaintX2--; } } } maxX = rect->x + rect->w - 2; CG_Text_Paint_Limit(&maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color, &cg.spectatorList[cg.spectatorOffset], 0, 0); if (cg.spectatorPaintX2 >= 0) { float maxX2 = rect->x + rect->w - 2; CG_Text_Paint_Limit(&maxX2, cg.spectatorPaintX2, rect->y + rect->h - 3, scale, color, cg.spectatorList, 0, cg.spectatorOffset); } if (cg.spectatorOffset && maxX > 0) { // if we have an offset ( we are skipping the first part of the string ) and we fit the string if (cg.spectatorPaintX2 == -1) { cg.spectatorPaintX2 = rect->x + rect->w - 2; } } else { cg.spectatorPaintX2 = -1; } } } void CG_DrawMedal(int ownerDraw, rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { score_t *score = &cg.scores[cg.selectedScore]; float value = 0; char *text = NULL; color[3] = 0.25; switch (ownerDraw) { case CG_ACCURACY: value = score->accuracy; break; case CG_ASSISTS: value = score->assistCount; break; case CG_DEFEND: value = score->defendCount; break; case CG_EXCELLENT: value = score->excellentCount; break; case CG_IMPRESSIVE: value = score->impressiveCount; break; case CG_PERFECT: value = score->perfect; break; case CG_GAUNTLET: value = score->guantletCount; break; case CG_CAPTURES: value = score->captures; break; } if (value > 0) { if (ownerDraw != CG_PERFECT) { if (ownerDraw == CG_ACCURACY) { text = va("%i%%", (int)value); if (value > 50) { color[3] = 1.0; } } else { text = va("%i", (int)value); color[3] = 1.0; } } else { if (value) { color[3] = 1.0; } text = "Wow"; } } trap_R_SetColor(color); CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); if (text) { color[3] = 1.0; value = CG_Text_Width(text, scale, 0); CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h + 10 , scale, color, text, 0, 0, 0); } trap_R_SetColor(NULL); } // void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle) { rectDef_t rect; if ( cg_drawStatus.integer == 0 ) { return; } //if (ownerDrawFlags != 0 && !CG_OwnerDrawVisible(ownerDrawFlags)) { // return; //} rect.x = x; rect.y = y; rect.w = w; rect.h = h; switch (ownerDraw) { case CG_PLAYER_ARMOR_ICON: CG_DrawPlayerArmorIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); break; case CG_PLAYER_ARMOR_ICON2D: CG_DrawPlayerArmorIcon(&rect, qtrue); break; case CG_PLAYER_ARMOR_VALUE: CG_DrawPlayerArmorValue(&rect, scale, color, shader, textStyle); break; case CG_PLAYER_AMMO_ICON: CG_DrawPlayerAmmoIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); break; case CG_PLAYER_AMMO_ICON2D: CG_DrawPlayerAmmoIcon(&rect, qtrue); break; case CG_PLAYER_AMMO_VALUE: CG_DrawPlayerAmmoValue(&rect, scale, color, shader, textStyle); break; case CG_SELECTEDPLAYER_HEAD: CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qfalse); break; case CG_VOICE_HEAD: CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qtrue); break; case CG_VOICE_NAME: CG_DrawSelectedPlayerName(&rect, scale, color, qtrue, textStyle); break; case CG_SELECTEDPLAYER_STATUS: CG_DrawSelectedPlayerStatus(&rect); break; case CG_SELECTEDPLAYER_ARMOR: CG_DrawSelectedPlayerArmor(&rect, scale, color, shader, textStyle); break; case CG_SELECTEDPLAYER_HEALTH: CG_DrawSelectedPlayerHealth(&rect, scale, color, shader, textStyle); break; case CG_SELECTEDPLAYER_NAME: CG_DrawSelectedPlayerName(&rect, scale, color, qfalse, textStyle); break; case CG_SELECTEDPLAYER_LOCATION: CG_DrawSelectedPlayerLocation(&rect, scale, color, textStyle); break; case CG_SELECTEDPLAYER_WEAPON: CG_DrawSelectedPlayerWeapon(&rect); break; case CG_SELECTEDPLAYER_POWERUP: CG_DrawSelectedPlayerPowerup(&rect, ownerDrawFlags & CG_SHOW_2DONLY); break; case CG_PLAYER_HEAD: CG_DrawPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY); break; case CG_PLAYER_ITEM: CG_DrawPlayerItem(&rect, scale, ownerDrawFlags & CG_SHOW_2DONLY); break; case CG_PLAYER_SCORE: CG_DrawPlayerScore(&rect, scale, color, shader, textStyle); break; case CG_PLAYER_HEALTH: CG_DrawPlayerHealth(&rect, scale, color, shader, textStyle); break; case CG_RED_SCORE: CG_DrawRedScore(&rect, scale, color, shader, textStyle); break; case CG_BLUE_SCORE: CG_DrawBlueScore(&rect, scale, color, shader, textStyle); break; case CG_RED_NAME: CG_DrawRedName(&rect, scale, color, textStyle); break; case CG_BLUE_NAME: CG_DrawBlueName(&rect, scale, color, textStyle); break; case CG_BLUE_FLAGHEAD: CG_DrawBlueFlagHead(&rect); break; case CG_BLUE_FLAGSTATUS: CG_DrawBlueFlagStatus(&rect, shader); break; case CG_BLUE_FLAGNAME: CG_DrawBlueFlagName(&rect, scale, color, textStyle); break; case CG_RED_FLAGHEAD: CG_DrawRedFlagHead(&rect); break; case CG_RED_FLAGSTATUS: CG_DrawRedFlagStatus(&rect, shader); break; case CG_RED_FLAGNAME: CG_DrawRedFlagName(&rect, scale, color, textStyle); break; case CG_HARVESTER_SKULLS: CG_HarvesterSkulls(&rect, scale, color, qfalse, textStyle); break; case CG_HARVESTER_SKULLS2D: CG_HarvesterSkulls(&rect, scale, color, qtrue, textStyle); break; case CG_ONEFLAG_STATUS: CG_OneFlagStatus(&rect); break; case CG_PLAYER_LOCATION: CG_DrawPlayerLocation(&rect, scale, color, textStyle); break; case CG_TEAM_COLOR: CG_DrawTeamColor(&rect, color); break; case CG_CTF_POWERUP: CG_DrawCTFPowerUp(&rect); break; case CG_AREA_POWERUP: CG_DrawAreaPowerUp(&rect, align, special, scale, color); break; case CG_PLAYER_STATUS: CG_DrawPlayerStatus(&rect); break; case CG_PLAYER_HASFLAG: CG_DrawPlayerHasFlag(&rect, qfalse); break; case CG_PLAYER_HASFLAG2D: CG_DrawPlayerHasFlag(&rect, qtrue); break; case CG_AREA_SYSTEMCHAT: CG_DrawAreaSystemChat(&rect, scale, color, shader); break; case CG_AREA_TEAMCHAT: CG_DrawAreaTeamChat(&rect, scale, color, shader); break; case CG_AREA_CHAT: CG_DrawAreaChat(&rect, scale, color, shader); break; case CG_GAME_TYPE: CG_DrawGameType(&rect, scale, color, shader, textStyle); break; case CG_GAME_STATUS: CG_DrawGameStatus(&rect, scale, color, shader, textStyle); break; case CG_KILLER: CG_DrawKiller(&rect, scale, color, shader, textStyle); break; case CG_ACCURACY: case CG_ASSISTS: case CG_DEFEND: case CG_EXCELLENT: case CG_IMPRESSIVE: case CG_PERFECT: case CG_GAUNTLET: case CG_CAPTURES: CG_DrawMedal(ownerDraw, &rect, scale, color, shader); break; case CG_SPECTATORS: CG_DrawTeamSpectators(&rect, scale, color, shader); break; case CG_TEAMINFO: if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { CG_DrawNewTeamInfo(&rect, text_x, text_y, scale, color, shader); } break; case CG_CAPFRAGLIMIT: CG_DrawCapFragLimit(&rect, scale, color, shader, textStyle); break; case CG_1STPLACE: CG_Draw1stPlace(&rect, scale, color, shader, textStyle); break; case CG_2NDPLACE: CG_Draw2ndPlace(&rect, scale, color, shader, textStyle); break; default: break; } } void CG_MouseEvent(int x, int y) { int n; if ( (cg.predictedPlayerState.pm_type == PM_NORMAL || cg.predictedPlayerState.pm_type == PM_SPECTATOR) && cg.showScores == qfalse) { trap_Key_SetCatcher(0); return; } cgs.cursorX+= x; if (cgs.cursorX < 0) cgs.cursorX = 0; else if (cgs.cursorX > 640) cgs.cursorX = 640; cgs.cursorY += y; if (cgs.cursorY < 0) cgs.cursorY = 0; else if (cgs.cursorY > 480) cgs.cursorY = 480; n = Display_CursorType(cgs.cursorX, cgs.cursorY); cgs.activeCursor = 0; if (n == CURSOR_ARROW) { cgs.activeCursor = cgs.media.selectCursor; } else if (n == CURSOR_SIZER) { cgs.activeCursor = cgs.media.sizeCursor; } if (cgs.capturedItem) { Display_MouseMove(cgs.capturedItem, x, y); } else { Display_MouseMove(NULL, cgs.cursorX, cgs.cursorY); } } /* ================== CG_HideTeamMenus ================== */ void CG_HideTeamMenu() { Menus_CloseByName("teamMenu"); Menus_CloseByName("getMenu"); } /* ================== CG_ShowTeamMenus ================== */ void CG_ShowTeamMenu() { Menus_OpenByName("teamMenu"); } /* ================== CG_EventHandling ================== type 0 - no event handling 1 - team menu 2 - hud editor */ void CG_EventHandling(int type) { cgs.eventHandling = type; if (type == CGAME_EVENT_NONE) { CG_HideTeamMenu(); } else if (type == CGAME_EVENT_TEAMMENU) { //CG_ShowTeamMenu(); } else if (type == CGAME_EVENT_SCOREBOARD) { } } void CG_KeyEvent(int key, qboolean down) { if (!down) { return; } if ( cg.predictedPlayerState.pm_type == PM_NORMAL || (cg.predictedPlayerState.pm_type == PM_SPECTATOR && cg.showScores == qfalse)) { CG_EventHandling(CGAME_EVENT_NONE); trap_Key_SetCatcher(0); return; } //if (key == trap_Key_GetKey("teamMenu") || !Display_CaptureItem(cgs.cursorX, cgs.cursorY)) { // if we see this then we should always be visible // CG_EventHandling(CGAME_EVENT_NONE); // trap_Key_SetCatcher(0); //} Display_HandleKey(key, down, cgs.cursorX, cgs.cursorY); if (cgs.capturedItem) { cgs.capturedItem = NULL; } else { if (key == K_MOUSE2 && down) { cgs.capturedItem = Display_CaptureItem(cgs.cursorX, cgs.cursorY); } } } int CG_ClientNumFromName(const char *p) { int i; for (i = 0; i < cgs.maxclients; i++) { if (cgs.clientinfo[i].infoValid && Q_stricmp(cgs.clientinfo[i].name, p) == 0) { return i; } } return -1; } void CG_ShowResponseHead() { Menus_OpenByName("voiceMenu"); trap_Cvar_Set("cl_conXOffset", "72"); cg.voiceTime = cg.time; } void CG_RunMenuScript(char **args) { } void CG_GetTeamColor(vec4_t *color) { if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) { (*color)[0] = 1.0f; (*color)[3] = 0.25f; (*color)[1] = (*color)[2] = 0.0f; } else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { (*color)[0] = (*color)[1] = 0.0f; (*color)[2] = 1.0f; (*color)[3] = 0.25f; } else { (*color)[0] = (*color)[2] = 0.0f; (*color)[1] = 0.17f; (*color)[3] = 0.25f; } } ================================================ FILE: code/cgame/cg_particles.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // Rafael particles // cg_particles.c #include "cg_local.h" #define BLOODRED 2 #define EMISIVEFADE 3 #define GREY75 4 typedef struct particle_s { struct particle_s *next; float time; float endtime; vec3_t org; vec3_t vel; vec3_t accel; int color; float colorvel; float alpha; float alphavel; int type; qhandle_t pshader; float height; float width; float endheight; float endwidth; float start; float end; float startfade; qboolean rotate; int snum; qboolean link; // Ridah int shaderAnim; int roll; int accumroll; } cparticle_t; typedef enum { P_NONE, P_WEATHER, P_FLAT, P_SMOKE, P_ROTATE, P_WEATHER_TURBULENT, P_ANIM, // Ridah P_BAT, P_BLEED, P_FLAT_SCALEUP, P_FLAT_SCALEUP_FADE, P_WEATHER_FLURRY, P_SMOKE_IMPACT, P_BUBBLE, P_BUBBLE_TURBULENT, P_SPRITE } particle_type_t; #define MAX_SHADER_ANIMS 32 #define MAX_SHADER_ANIM_FRAMES 64 static char *shaderAnimNames[MAX_SHADER_ANIMS] = { "explode1", "blacksmokeanim", "twiltb2", "expblue", "blacksmokeanimb", // uses 'explode1' sequence "blood", NULL }; static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; static int shaderAnimCounts[MAX_SHADER_ANIMS] = { 23, 25, 45, 25, 23, 5, }; static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { 1.405f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, }; static int numShaderAnims; // done. #define PARTICLE_GRAVITY 40 #define MAX_PARTICLES 1024 * 8 cparticle_t *active_particles, *free_particles; cparticle_t particles[MAX_PARTICLES]; int cl_numparticles = MAX_PARTICLES; qboolean initparticles = qfalse; vec3_t vforward, vright, vup; vec3_t rforward, rright, rup; float oldtime; /* =============== CL_ClearParticles =============== */ void CG_ClearParticles (void) { int i; memset( particles, 0, sizeof(particles) ); free_particles = &particles[0]; active_particles = NULL; for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) {// create a front facing polygon if (p->type != P_WEATHER_FLURRY) { if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) { if (org[2] > p->end) { p->time = cg.time; VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground p->org[2] = ( p->start + crandom () * 4 ); if (p->type == P_BUBBLE_TURBULENT) { p->vel[0] = crandom() * 4; p->vel[1] = crandom() * 4; } } } else { if (org[2] < p->end) { p->time = cg.time; VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground while (p->org[2] < p->end) { p->org[2] += (p->start - p->end); } if (p->type == P_WEATHER_TURBULENT) { p->vel[0] = crandom() * 16; p->vel[1] = crandom() * 16; } } } // Rafael snow pvs check if (!p->link) return; p->alpha = 1; } // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp if (Distance( cg.snap->ps.origin, org ) > 1024) { return; } // done. if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) { VectorMA (org, -p->height, vup, point); VectorMA (point, -p->width, vright, point); VectorCopy (point, verts[0].xyz); verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255 * p->alpha; VectorMA (org, -p->height, vup, point); VectorMA (point, p->width, vright, point); VectorCopy (point, verts[1].xyz); verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255 * p->alpha; VectorMA (org, p->height, vup, point); VectorMA (point, p->width, vright, point); VectorCopy (point, verts[2].xyz); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = 255 * p->alpha; VectorMA (org, p->height, vup, point); VectorMA (point, -p->width, vright, point); VectorCopy (point, verts[3].xyz); verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = 255 * p->alpha; } else { VectorMA (org, -p->height, vup, point); VectorMA (point, -p->width, vright, point); VectorCopy( point, TRIverts[0].xyz ); TRIverts[0].st[0] = 1; TRIverts[0].st[1] = 0; TRIverts[0].modulate[0] = 255; TRIverts[0].modulate[1] = 255; TRIverts[0].modulate[2] = 255; TRIverts[0].modulate[3] = 255 * p->alpha; VectorMA (org, p->height, vup, point); VectorMA (point, -p->width, vright, point); VectorCopy (point, TRIverts[1].xyz); TRIverts[1].st[0] = 0; TRIverts[1].st[1] = 0; TRIverts[1].modulate[0] = 255; TRIverts[1].modulate[1] = 255; TRIverts[1].modulate[2] = 255; TRIverts[1].modulate[3] = 255 * p->alpha; VectorMA (org, p->height, vup, point); VectorMA (point, p->width, vright, point); VectorCopy (point, TRIverts[2].xyz); TRIverts[2].st[0] = 0; TRIverts[2].st[1] = 1; TRIverts[2].modulate[0] = 255; TRIverts[2].modulate[1] = 255; TRIverts[2].modulate[2] = 255; TRIverts[2].modulate[3] = 255 * p->alpha; } } else if (p->type == P_SPRITE) { vec3_t rr, ru; vec3_t rotate_ang; VectorSet (color, 1.0, 1.0, 1.0); time = cg.time - p->time; time2 = p->endtime - p->time; ratio = time / time2; width = p->width + ( ratio * ( p->endwidth - p->width) ); height = p->height + ( ratio * ( p->endheight - p->height) ); if (p->roll) { vectoangles( cg.refdef.viewaxis[0], rotate_ang ); rotate_ang[ROLL] += p->roll; AngleVectors ( rotate_ang, NULL, rr, ru); } if (p->roll) { VectorMA (org, -height, ru, point); VectorMA (point, -width, rr, point); } else { VectorMA (org, -height, vup, point); VectorMA (point, -width, vright, point); } VectorCopy (point, verts[0].xyz); verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255; if (p->roll) { VectorMA (point, 2*height, ru, point); } else { VectorMA (point, 2*height, vup, point); } VectorCopy (point, verts[1].xyz); verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255; if (p->roll) { VectorMA (point, 2*width, rr, point); } else { VectorMA (point, 2*width, vright, point); } VectorCopy (point, verts[2].xyz); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = 255; if (p->roll) { VectorMA (point, -2*height, ru, point); } else { VectorMA (point, -2*height, vup, point); } VectorCopy (point, verts[3].xyz); verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = 255; } else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) {// create a front rotating facing polygon if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { return; } if (p->color == BLOODRED) VectorSet (color, 0.22f, 0.0f, 0.0f); else if (p->color == GREY75) { float len; float greyit; float val; len = Distance (cg.snap->ps.origin, org); if (!len) len = 1; val = 4096/len; greyit = 0.25 * val; if (greyit > 0.5) greyit = 0.5; VectorSet (color, greyit, greyit, greyit); } else VectorSet (color, 1.0, 1.0, 1.0); time = cg.time - p->time; time2 = p->endtime - p->time; ratio = time / time2; if (cg.time > p->startfade) { invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); if (p->color == EMISIVEFADE) { float fval; fval = (invratio * invratio); if (fval < 0) fval = 0; VectorSet (color, fval , fval , fval ); } invratio *= p->alpha; } else invratio = 1 * p->alpha; if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) invratio = 1; if (invratio > 1) invratio = 1; width = p->width + ( ratio * ( p->endwidth - p->width) ); height = p->height + ( ratio * ( p->endheight - p->height) ); if (p->type != P_SMOKE_IMPACT) { vec3_t temp; vectoangles (rforward, temp); p->accumroll += p->roll; temp[ROLL] += p->accumroll * 0.1; AngleVectors ( temp, NULL, rright2, rup2); } else { VectorCopy (rright, rright2); VectorCopy (rup, rup2); } if (p->rotate) { VectorMA (org, -height, rup2, point); VectorMA (point, -width, rright2, point); } else { VectorMA (org, -p->height, vup, point); VectorMA (point, -p->width, vright, point); } VectorCopy (point, verts[0].xyz); verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255 * color[0]; verts[0].modulate[1] = 255 * color[1]; verts[0].modulate[2] = 255 * color[2]; verts[0].modulate[3] = 255 * invratio; if (p->rotate) { VectorMA (org, -height, rup2, point); VectorMA (point, width, rright2, point); } else { VectorMA (org, -p->height, vup, point); VectorMA (point, p->width, vright, point); } VectorCopy (point, verts[1].xyz); verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255 * color[0]; verts[1].modulate[1] = 255 * color[1]; verts[1].modulate[2] = 255 * color[2]; verts[1].modulate[3] = 255 * invratio; if (p->rotate) { VectorMA (org, height, rup2, point); VectorMA (point, width, rright2, point); } else { VectorMA (org, p->height, vup, point); VectorMA (point, p->width, vright, point); } VectorCopy (point, verts[2].xyz); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255 * color[0]; verts[2].modulate[1] = 255 * color[1]; verts[2].modulate[2] = 255 * color[2]; verts[2].modulate[3] = 255 * invratio; if (p->rotate) { VectorMA (org, height, rup2, point); VectorMA (point, -width, rright2, point); } else { VectorMA (org, p->height, vup, point); VectorMA (point, -p->width, vright, point); } VectorCopy (point, verts[3].xyz); verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255 * color[0]; verts[3].modulate[1] = 255 * color[1]; verts[3].modulate[2] = 255 * color[2]; verts[3].modulate[3] = 255 * invratio; } else if (p->type == P_BLEED) { vec3_t rr, ru; vec3_t rotate_ang; float alpha; alpha = p->alpha; if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) alpha = 1; if (p->roll) { vectoangles( cg.refdef.viewaxis[0], rotate_ang ); rotate_ang[ROLL] += p->roll; AngleVectors ( rotate_ang, NULL, rr, ru); } else { VectorCopy (vup, ru); VectorCopy (vright, rr); } VectorMA (org, -p->height, ru, point); VectorMA (point, -p->width, rr, point); VectorCopy (point, verts[0].xyz); verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 111; verts[0].modulate[1] = 19; verts[0].modulate[2] = 9; verts[0].modulate[3] = 255 * alpha; VectorMA (org, -p->height, ru, point); VectorMA (point, p->width, rr, point); VectorCopy (point, verts[1].xyz); verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 111; verts[1].modulate[1] = 19; verts[1].modulate[2] = 9; verts[1].modulate[3] = 255 * alpha; VectorMA (org, p->height, ru, point); VectorMA (point, p->width, rr, point); VectorCopy (point, verts[2].xyz); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 111; verts[2].modulate[1] = 19; verts[2].modulate[2] = 9; verts[2].modulate[3] = 255 * alpha; VectorMA (org, p->height, ru, point); VectorMA (point, -p->width, rr, point); VectorCopy (point, verts[3].xyz); verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 111; verts[3].modulate[1] = 19; verts[3].modulate[2] = 9; verts[3].modulate[3] = 255 * alpha; } else if (p->type == P_FLAT_SCALEUP) { float width, height; float sinR, cosR; if (p->color == BLOODRED) VectorSet (color, 1, 1, 1); else VectorSet (color, 0.5, 0.5, 0.5); time = cg.time - p->time; time2 = p->endtime - p->time; ratio = time / time2; width = p->width + ( ratio * ( p->endwidth - p->width) ); height = p->height + ( ratio * ( p->endheight - p->height) ); if (width > p->endwidth) width = p->endwidth; if (height > p->endheight) height = p->endheight; sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); VectorCopy (org, verts[0].xyz); verts[0].xyz[0] -= sinR; verts[0].xyz[1] -= cosR; verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255 * color[0]; verts[0].modulate[1] = 255 * color[1]; verts[0].modulate[2] = 255 * color[2]; verts[0].modulate[3] = 255; VectorCopy (org, verts[1].xyz); verts[1].xyz[0] -= cosR; verts[1].xyz[1] += sinR; verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255 * color[0]; verts[1].modulate[1] = 255 * color[1]; verts[1].modulate[2] = 255 * color[2]; verts[1].modulate[3] = 255; VectorCopy (org, verts[2].xyz); verts[2].xyz[0] += sinR; verts[2].xyz[1] += cosR; verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255 * color[0]; verts[2].modulate[1] = 255 * color[1]; verts[2].modulate[2] = 255 * color[2]; verts[2].modulate[3] = 255; VectorCopy (org, verts[3].xyz); verts[3].xyz[0] += cosR; verts[3].xyz[1] -= sinR; verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255 * color[0]; verts[3].modulate[1] = 255 * color[1]; verts[3].modulate[2] = 255 * color[2]; verts[3].modulate[3] = 255; } else if (p->type == P_FLAT) { VectorCopy (org, verts[0].xyz); verts[0].xyz[0] -= p->height; verts[0].xyz[1] -= p->width; verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255; VectorCopy (org, verts[1].xyz); verts[1].xyz[0] -= p->height; verts[1].xyz[1] += p->width; verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255; VectorCopy (org, verts[2].xyz); verts[2].xyz[0] += p->height; verts[2].xyz[1] += p->width; verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = 255; VectorCopy (org, verts[3].xyz); verts[3].xyz[0] += p->height; verts[3].xyz[1] -= p->width; verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = 255; } // Ridah else if (p->type == P_ANIM) { vec3_t rr, ru; vec3_t rotate_ang; int i, j; time = cg.time - p->time; time2 = p->endtime - p->time; ratio = time / time2; if (ratio >= 1.0f) { ratio = 0.9999f; } width = p->width + ( ratio * ( p->endwidth - p->width) ); height = p->height + ( ratio * ( p->endheight - p->height) ); // if we are "inside" this sprite, don't draw if (Distance( cg.snap->ps.origin, org ) < width/1.5) { return; } i = p->shaderAnim; j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); p->pshader = shaderAnims[i][j]; if (p->roll) { vectoangles( cg.refdef.viewaxis[0], rotate_ang ); rotate_ang[ROLL] += p->roll; AngleVectors ( rotate_ang, NULL, rr, ru); } if (p->roll) { VectorMA (org, -height, ru, point); VectorMA (point, -width, rr, point); } else { VectorMA (org, -height, vup, point); VectorMA (point, -width, vright, point); } VectorCopy (point, verts[0].xyz); verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255; if (p->roll) { VectorMA (point, 2*height, ru, point); } else { VectorMA (point, 2*height, vup, point); } VectorCopy (point, verts[1].xyz); verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255; if (p->roll) { VectorMA (point, 2*width, rr, point); } else { VectorMA (point, 2*width, vright, point); } VectorCopy (point, verts[2].xyz); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = 255; if (p->roll) { VectorMA (point, -2*height, ru, point); } else { VectorMA (point, -2*height, vup, point); } VectorCopy (point, verts[3].xyz); verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = 255; } // done. if (!p->pshader) { // (SA) temp commented out for DM // CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); return; } if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); else trap_R_AddPolyToScene( p->pshader, 4, verts ); } // Ridah, made this static so it doesn't interfere with other files static float roll = 0.0; /* =============== CG_AddParticles =============== */ void CG_AddParticles (void) { cparticle_t *p, *next; float alpha; float time, time2; vec3_t org; int color; cparticle_t *active, *tail; int type; vec3_t rotate_ang; if (!initparticles) CG_ClearParticles (); VectorCopy( cg.refdef.viewaxis[0], vforward ); VectorCopy( cg.refdef.viewaxis[1], vright ); VectorCopy( cg.refdef.viewaxis[2], vup ); vectoangles( cg.refdef.viewaxis[0], rotate_ang ); roll += ((cg.time - oldtime) * 0.1) ; rotate_ang[ROLL] += (roll*0.9); AngleVectors ( rotate_ang, rforward, rright, rup); oldtime = cg.time; active = NULL; tail = NULL; for (p=active_particles ; p ; p=next) { next = p->next; time = (cg.time - p->time)*0.001; alpha = p->alpha + time*p->alphavel; if (alpha <= 0) { // faded out p->next = free_particles; free_particles = p; p->type = 0; p->color = 0; p->alpha = 0; continue; } if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) { if (cg.time > p->endtime) { p->next = free_particles; free_particles = p; p->type = 0; p->color = 0; p->alpha = 0; continue; } } if (p->type == P_WEATHER_FLURRY) { if (cg.time > p->endtime) { p->next = free_particles; free_particles = p; p->type = 0; p->color = 0; p->alpha = 0; continue; } } if (p->type == P_FLAT_SCALEUP_FADE) { if (cg.time > p->endtime) { p->next = free_particles; free_particles = p; p->type = 0; p->color = 0; p->alpha = 0; continue; } } if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { // temporary sprite CG_AddParticleToScene (p, p->org, alpha); p->next = free_particles; free_particles = p; p->type = 0; p->color = 0; p->alpha = 0; continue; } p->next = NULL; if (!tail) active = tail = p; else { tail->next = p; tail = p; } if (alpha > 1.0) alpha = 1; color = p->color; time2 = time*time; org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; type = p->type; CG_AddParticleToScene (p, org, alpha); } active_particles = active; } /* ====================== CG_AddParticles ====================== */ void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) { cparticle_t *p; qboolean turb = qtrue; if (!pshader) CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->color = 0; p->alpha = 0.90f; p->alphavel = 0; p->start = cent->currentState.origin2[0]; p->end = cent->currentState.origin2[1]; p->endtime = cg.time + cent->currentState.time; p->startfade = cg.time + cent->currentState.time2; p->pshader = pshader; if (rand()%100 > 90) { p->height = 32; p->width = 32; p->alpha = 0.10f; } else { p->height = 1; p->width = 1; } p->vel[2] = -20; p->type = P_WEATHER_FLURRY; if (turb) p->vel[2] = -10; VectorCopy(cent->currentState.origin, p->org); p->org[0] = p->org[0]; p->org[1] = p->org[1]; p->org[2] = p->org[2]; p->vel[0] = p->vel[1] = 0; p->accel[0] = p->accel[1] = p->accel[2] = 0; p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); p->vel[2] += cent->currentState.angles[2]; if (turb) { p->accel[0] = crandom () * 16; p->accel[1] = crandom () * 16; } } void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) { cparticle_t *p; if (!pshader) CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->color = 0; p->alpha = 0.40f; p->alphavel = 0; p->start = origin[2]; p->end = origin2[2]; p->pshader = pshader; p->height = 1; p->width = 1; p->vel[2] = -50; if (turb) { p->type = P_WEATHER_TURBULENT; p->vel[2] = -50 * 1.3; } else { p->type = P_WEATHER; } VectorCopy(origin, p->org); p->org[0] = p->org[0] + ( crandom() * range); p->org[1] = p->org[1] + ( crandom() * range); p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); p->vel[0] = p->vel[1] = 0; p->accel[0] = p->accel[1] = p->accel[2] = 0; if (turb) { p->vel[0] = crandom() * 16; p->vel[1] = crandom() * 16; } // Rafael snow pvs check p->snum = snum; p->link = qtrue; } void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) { cparticle_t *p; float randsize; if (!pshader) CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->color = 0; p->alpha = 0.40f; p->alphavel = 0; p->start = origin[2]; p->end = origin2[2]; p->pshader = pshader; randsize = 1 + (crandom() * 0.5); p->height = randsize; p->width = randsize; p->vel[2] = 50 + ( crandom() * 10 ); if (turb) { p->type = P_BUBBLE_TURBULENT; p->vel[2] = 50 * 1.3; } else { p->type = P_BUBBLE; } VectorCopy(origin, p->org); p->org[0] = p->org[0] + ( crandom() * range); p->org[1] = p->org[1] + ( crandom() * range); p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); p->vel[0] = p->vel[1] = 0; p->accel[0] = p->accel[1] = p->accel[2] = 0; if (turb) { p->vel[0] = crandom() * 4; p->vel[1] = crandom() * 4; } // Rafael snow pvs check p->snum = snum; p->link = qtrue; } void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) { // using cent->density = enttime // cent->frame = startfade cparticle_t *p; if (!pshader) CG_Printf ("CG_ParticleSmoke == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->endtime = cg.time + cent->currentState.time; p->startfade = cg.time + cent->currentState.time2; p->color = 0; p->alpha = 1.0; p->alphavel = 0; p->start = cent->currentState.origin[2]; p->end = cent->currentState.origin2[2]; p->pshader = pshader; p->rotate = qfalse; p->height = 8; p->width = 8; p->endheight = 32; p->endwidth = 32; p->type = P_SMOKE; VectorCopy(cent->currentState.origin, p->org); p->vel[0] = p->vel[1] = 0; p->accel[0] = p->accel[1] = p->accel[2] = 0; p->vel[2] = 5; if (cent->currentState.frame == 1)// reverse gravity p->vel[2] *= -1; p->roll = 8 + (crandom() * 4); } void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) { cparticle_t *p; if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->endtime = cg.time + duration; p->startfade = cg.time + duration/2; p->color = EMISIVEFADE; p->alpha = 1.0; p->alphavel = 0; p->height = 0.5; p->width = 0.5; p->endheight = 0.5; p->endwidth = 0.5; p->pshader = cgs.media.tracerShader; p->type = P_SMOKE; VectorCopy(org, p->org); p->vel[0] = vel[0]; p->vel[1] = vel[1]; p->vel[2] = vel[2]; p->accel[0] = p->accel[1] = p->accel[2] = 0; p->accel[2] = -60; p->vel[2] += -20; } /* ====================== CG_ParticleExplosion ====================== */ void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) { cparticle_t *p; int anim; if (animStr < (char *)10) CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); // find the animation string for (anim=0; shaderAnimNames[anim]; anim++) { if (!stricmp( animStr, shaderAnimNames[anim] )) break; } if (!shaderAnimNames[anim]) { CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); return; } if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 1.0; p->alphavel = 0; if (duration < 0) { duration *= -1; p->roll = 0; } else { p->roll = crandom()*179; } p->shaderAnim = anim; p->width = sizeStart; p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction p->endheight = sizeEnd; p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; p->endtime = cg.time + duration; p->type = P_ANIM; VectorCopy( origin, p->org ); VectorCopy( vel, p->vel ); VectorClear( p->accel ); } // Rafael Shrapnel void CG_AddParticleShrapnel (localEntity_t *le) { return; } // done. int CG_NewParticleArea (int num) { // const char *str; char *str; char *token; int type; vec3_t origin, origin2; int i; float range = 0; int turb; int numparticles; int snum; str = (char *) CG_ConfigString (num); if (!str[0]) return (0); // returns type 128 64 or 32 token = COM_Parse (&str); type = atoi (token); if (type == 1) range = 128; else if (type == 2) range = 64; else if (type == 3) range = 32; else if (type == 0) range = 256; else if (type == 4) range = 8; else if (type == 5) range = 16; else if (type == 6) range = 32; else if (type == 7) range = 64; for (i=0; i<3; i++) { token = COM_Parse (&str); origin[i] = atof (token); } for (i=0; i<3; i++) { token = COM_Parse (&str); origin2[i] = atof (token); } token = COM_Parse (&str); numparticles = atoi (token); token = COM_Parse (&str); turb = atoi (token); token = COM_Parse (&str); snum = atoi (token); for (i=0; i= 4) CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); else CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); } return (1); } void CG_SnowLink (centity_t *cent, qboolean particleOn) { cparticle_t *p, *next; int id; id = cent->currentState.frame; for (p=active_particles ; p ; p=next) { next = p->next; if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) { if (p->snum == id) { if (particleOn) p->link = qtrue; else p->link = qfalse; } } } } void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) { cparticle_t *p; if (!pshader) CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 0.25; p->alphavel = 0; p->roll = crandom()*179; p->pshader = pshader; p->endtime = cg.time + 1000; p->startfade = cg.time + 100; p->width = rand()%4 + 8; p->height = rand()%4 + 8; p->endheight = p->height *2; p->endwidth = p->width * 2; p->endtime = cg.time + 500; p->type = P_SMOKE_IMPACT; VectorCopy( origin, p->org ); VectorSet(p->vel, 0, 0, 20); VectorSet(p->accel, 0, 0, 20); p->rotate = qtrue; } void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) { cparticle_t *p; if (!pshader) CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 1.0; p->alphavel = 0; p->roll = 0; p->pshader = pshader; p->endtime = cg.time + duration; if (fleshEntityNum) p->startfade = cg.time; else p->startfade = cg.time + 100; p->width = 4; p->height = 4; p->endheight = 4+rand()%3; p->endwidth = p->endheight; p->type = P_SMOKE; VectorCopy( start, p->org ); p->vel[0] = 0; p->vel[1] = 0; p->vel[2] = -20; VectorClear( p->accel ); p->rotate = qfalse; p->roll = rand()%179; p->color = BLOODRED; p->alpha = 0.75; } void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) { cparticle_t *p; int time; int time2; float ratio; float duration = 1500; time = cg.time; time2 = cg.time + cent->currentState.time; ratio =(float)1 - ((float)time / (float)time2); if (!pshader) CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 1.0; p->alphavel = 0; p->roll = 0; p->pshader = pshader; p->endtime = cg.time + duration; p->startfade = p->endtime; p->width = 1; p->height = 3; p->endheight = 3; p->endwidth = 1; p->type = P_SMOKE; VectorCopy(cent->currentState.origin, p->org ); p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); p->vel[2] = (cent->currentState.origin2[2]); p->snum = 1.0f; VectorClear( p->accel ); p->accel[2] = -20; p->rotate = qfalse; p->roll = rand()%179; p->alpha = 0.75; } void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) { cparticle_t *p; if (!pshader) CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; if (cent->currentState.angles2[2]) p->endtime = cg.time + cent->currentState.angles2[2]; else p->endtime = cg.time + 60000; p->startfade = p->endtime; p->alpha = 1.0; p->alphavel = 0; p->roll = 0; p->pshader = pshader; if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) { p->width = cent->currentState.angles2[0]; p->height = cent->currentState.angles2[0]; p->endheight = cent->currentState.angles2[1]; p->endwidth = cent->currentState.angles2[1]; } else { p->width = 8; p->height = 8; p->endheight = 16; p->endwidth = 16; } p->type = P_FLAT_SCALEUP; p->snum = 1.0; VectorCopy(cent->currentState.origin, p->org ); p->org[2]+= 0.55 + (crandom() * 0.5); p->vel[0] = 0; p->vel[1] = 0; p->vel[2] = 0; VectorClear( p->accel ); p->rotate = qfalse; p->roll = rand()%179; p->alpha = 0.75; } void CG_OilSlickRemove (centity_t *cent) { cparticle_t *p, *next; int id; id = 1.0f; if (!id) CG_Printf ("CG_OilSlickRevove NULL id\n"); for (p=active_particles ; p ; p=next) { next = p->next; if (p->type == P_FLAT_SCALEUP) { if (p->snum == id) { p->endtime = cg.time + 100; p->startfade = p->endtime; p->type = P_FLAT_SCALEUP_FADE; } } } } qboolean ValidBloodPool (vec3_t start) { #define EXTRUDE_DIST 0.5 vec3_t angles; vec3_t right, up; vec3_t this_pos, x_pos, center_pos, end_pos; float x, y; float fwidth, fheight; trace_t trace; vec3_t normal; fwidth = 16; fheight = 16; VectorSet (normal, 0, 0, 1); vectoangles (normal, angles); AngleVectors (angles, NULL, right, up); VectorMA (start, EXTRUDE_DIST, normal, center_pos); for (x= -fwidth/2; xendpos, start); legit = ValidBloodPool (start); if (!legit) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->endtime = cg.time + 3000; p->startfade = p->endtime; p->alpha = 1.0; p->alphavel = 0; p->roll = 0; p->pshader = pshader; rndSize = 0.4 + random()*0.6; p->width = 8*rndSize; p->height = 8*rndSize; p->endheight = 16*rndSize; p->endwidth = 16*rndSize; p->type = P_FLAT_SCALEUP; VectorCopy(start, p->org ); p->vel[0] = 0; p->vel[1] = 0; p->vel[2] = 0; VectorClear( p->accel ); p->rotate = qfalse; p->roll = rand()%179; p->alpha = 0.75; p->color = BLOODRED; } #define NORMALSIZE 16 #define LARGESIZE 32 void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) { float length; float dist; float crittersize; vec3_t angles, forward; vec3_t point; cparticle_t *p; int i; dist = 0; length = VectorLength (dir); vectoangles (dir, angles); AngleVectors (angles, forward, NULL, NULL); crittersize = LARGESIZE; if (length) dist = length / crittersize; if (dist < 1) dist = 1; VectorCopy (origin, point); for (i=0; inext; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 1.0; p->alphavel = 0; p->roll = 0; p->pshader = cgs.media.smokePuffShader; p->endtime = cg.time + 350 + (crandom() * 100); p->startfade = cg.time; p->width = LARGESIZE; p->height = LARGESIZE; p->endheight = LARGESIZE; p->endwidth = LARGESIZE; p->type = P_SMOKE; VectorCopy( origin, p->org ); p->vel[0] = 0; p->vel[1] = 0; p->vel[2] = -1; VectorClear( p->accel ); p->rotate = qfalse; p->roll = rand()%179; p->color = BLOODRED; p->alpha = 0.75; } } void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) { cparticle_t *p; if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->endtime = cg.time + duration; p->startfade = cg.time + duration/2; p->color = EMISIVEFADE; p->alpha = 0.4f; p->alphavel = 0; p->height = 0.5; p->width = 0.5; p->endheight = 0.5; p->endwidth = 0.5; p->pshader = cgs.media.tracerShader; p->type = P_SMOKE; VectorCopy(org, p->org); p->org[0] += (crandom() * x); p->org[1] += (crandom() * y); p->vel[0] = vel[0]; p->vel[1] = vel[1]; p->vel[2] = vel[2]; p->accel[0] = p->accel[1] = p->accel[2] = 0; p->vel[0] += (crandom() * 4); p->vel[1] += (crandom() * 4); p->vel[2] += (20 + (crandom() * 10)) * speed; p->accel[0] = crandom () * 4; p->accel[1] = crandom () * 4; } void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) { float length; float dist; float crittersize; vec3_t angles, forward; vec3_t point; cparticle_t *p; int i; dist = 0; VectorNegate (dir, dir); length = VectorLength (dir); vectoangles (dir, angles); AngleVectors (angles, forward, NULL, NULL); crittersize = LARGESIZE; if (length) dist = length / crittersize; if (dist < 1) dist = 1; VectorCopy (origin, point); for (i=0; inext; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 5.0; p->alphavel = 0; p->roll = 0; p->pshader = cgs.media.smokePuffShader; // RF, stay around for long enough to expand and dissipate naturally if (length) p->endtime = cg.time + 4500 + (crandom() * 3500); else p->endtime = cg.time + 750 + (crandom() * 500); p->startfade = cg.time; p->width = LARGESIZE; p->height = LARGESIZE; // RF, expand while falling p->endheight = LARGESIZE*3.0; p->endwidth = LARGESIZE*3.0; if (!length) { p->width *= 0.2f; p->height *= 0.2f; p->endheight = NORMALSIZE; p->endwidth = NORMALSIZE; } p->type = P_SMOKE; VectorCopy( point, p->org ); p->vel[0] = crandom()*6; p->vel[1] = crandom()*6; p->vel[2] = random()*20; // RF, add some gravity/randomness p->accel[0] = crandom()*3; p->accel[1] = crandom()*3; p->accel[2] = -PARTICLE_GRAVITY*0.4; VectorClear( p->accel ); p->rotate = qfalse; p->roll = rand()%179; p->alpha = 0.75; } } void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) { cparticle_t *p; if (!pshader) CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); if (!free_particles) return; p = free_particles; free_particles = p->next; p->next = active_particles; active_particles = p; p->time = cg.time; p->alpha = 1.0; p->alphavel = 0; p->roll = rand()%179; p->pshader = pshader; if (duration > 0) p->endtime = cg.time + duration; else p->endtime = duration; p->startfade = cg.time; p->width = size; p->height = size; p->endheight = size; p->endwidth = size; p->type = P_SPRITE; VectorCopy( origin, p->org ); p->rotate = qfalse; } ================================================ FILE: code/cgame/cg_players.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_players.c -- handle the media and animation for player entities #include "cg_local.h" char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { "*death1.wav", "*death2.wav", "*death3.wav", "*jump1.wav", "*pain25_1.wav", "*pain50_1.wav", "*pain75_1.wav", "*pain100_1.wav", "*falling1.wav", "*gasp.wav", "*drown.wav", "*fall1.wav", "*taunt.wav" }; /* ================ CG_CustomSound ================ */ sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { clientInfo_t *ci; int i; if ( soundName[0] != '*' ) { return trap_S_RegisterSound( soundName, qfalse ); } if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { clientNum = 0; } ci = &cgs.clientinfo[ clientNum ]; for ( i = 0 ; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i] ; i++ ) { if ( !strcmp( soundName, cg_customSoundNames[i] ) ) { return ci->sounds[i]; } } CG_Error( "Unknown custom sound: %s", soundName ); return 0; } /* ============================================================================= CLIENT INFO ============================================================================= */ /* ====================== CG_ParseAnimationFile Read a configuration file containing animation coutns and rates models/players/visor/animation.cfg, etc ====================== */ static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { char *text_p, *prev; int len; int i; char *token; float fps; int skip; char text[20000]; fileHandle_t f; animation_t *animations; animations = ci->animations; // load the file len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) { return qfalse; } if ( len >= sizeof( text ) - 1 ) { CG_Printf( "File %s too long\n", filename ); return qfalse; } trap_FS_Read( text, len, f ); text[len] = 0; trap_FS_FCloseFile( f ); // parse the text text_p = text; skip = 0; // quite the compiler warning ci->footsteps = FOOTSTEP_NORMAL; VectorClear( ci->headOffset ); ci->gender = GENDER_MALE; ci->fixedlegs = qfalse; ci->fixedtorso = qfalse; // read optional parameters while ( 1 ) { prev = text_p; // so we can unget token = COM_Parse( &text_p ); if ( !token ) { break; } if ( !Q_stricmp( token, "footsteps" ) ) { token = COM_Parse( &text_p ); if ( !token ) { break; } if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { ci->footsteps = FOOTSTEP_NORMAL; } else if ( !Q_stricmp( token, "boot" ) ) { ci->footsteps = FOOTSTEP_BOOT; } else if ( !Q_stricmp( token, "flesh" ) ) { ci->footsteps = FOOTSTEP_FLESH; } else if ( !Q_stricmp( token, "mech" ) ) { ci->footsteps = FOOTSTEP_MECH; } else if ( !Q_stricmp( token, "energy" ) ) { ci->footsteps = FOOTSTEP_ENERGY; } else { CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); } continue; } else if ( !Q_stricmp( token, "headoffset" ) ) { for ( i = 0 ; i < 3 ; i++ ) { token = COM_Parse( &text_p ); if ( !token ) { break; } ci->headOffset[i] = atof( token ); } continue; } else if ( !Q_stricmp( token, "sex" ) ) { token = COM_Parse( &text_p ); if ( !token ) { break; } if ( token[0] == 'f' || token[0] == 'F' ) { ci->gender = GENDER_FEMALE; } else if ( token[0] == 'n' || token[0] == 'N' ) { ci->gender = GENDER_NEUTER; } else { ci->gender = GENDER_MALE; } continue; } else if ( !Q_stricmp( token, "fixedlegs" ) ) { ci->fixedlegs = qtrue; continue; } else if ( !Q_stricmp( token, "fixedtorso" ) ) { ci->fixedtorso = qtrue; continue; } // if it is a number, start parsing animations if ( token[0] >= '0' && token[0] <= '9' ) { text_p = prev; // unget the token break; } Com_Printf( "unknown token '%s' is %s\n", token, filename ); } // read information for each frame for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { token = COM_Parse( &text_p ); if ( !*token ) { if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) { animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame; animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp; animations[i].initialLerp = animations[TORSO_GESTURE].initialLerp; animations[i].loopFrames = animations[TORSO_GESTURE].loopFrames; animations[i].numFrames = animations[TORSO_GESTURE].numFrames; animations[i].reversed = qfalse; animations[i].flipflop = qfalse; continue; } break; } animations[i].firstFrame = atoi( token ); // leg only frames are adjusted to not count the upper body only frames if ( i == LEGS_WALKCR ) { skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; } if ( i >= LEGS_WALKCR && i0) { return qtrue; } return qfalse; } /* ========================== CG_FindClientModelFile ========================== */ static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *base, const char *ext ) { char *team, *charactersFolder; int i; if ( cgs.gametype >= GT_TEAM ) { switch ( ci->team ) { case TEAM_BLUE: { team = "blue"; break; } default: { team = "red"; break; } } } else { team = "default"; } charactersFolder = ""; while(1) { for ( i = 0; i < 2; i++ ) { if ( i == 0 && teamName && *teamName ) { // "models/players/characters/james/stroggs/lower_lily_red.skin" Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, team, ext ); } else { // "models/players/characters/james/lower_lily_red.skin" Com_sprintf( filename, length, "models/players/%s%s/%s_%s_%s.%s", charactersFolder, modelName, base, skinName, team, ext ); } if ( CG_FileExists( filename ) ) { return qtrue; } if ( cgs.gametype >= GT_TEAM ) { if ( i == 0 && teamName && *teamName ) { // "models/players/characters/james/stroggs/lower_red.skin" Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, team, ext ); } else { // "models/players/characters/james/lower_red.skin" Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, team, ext ); } } else { if ( i == 0 && teamName && *teamName ) { // "models/players/characters/james/stroggs/lower_lily.skin" Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, ext ); } else { // "models/players/characters/james/lower_lily.skin" Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext ); } } if ( CG_FileExists( filename ) ) { return qtrue; } if ( !teamName || !*teamName ) { break; } } // if tried the heads folder first if ( charactersFolder[0] ) { break; } charactersFolder = "characters/"; } return qfalse; } /* ========================== CG_FindClientHeadFile ========================== */ static qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { char *team, *headsFolder; int i; if ( cgs.gametype >= GT_TEAM ) { switch ( ci->team ) { case TEAM_BLUE: { team = "blue"; break; } default: { team = "red"; break; } } } else { team = "default"; } if ( headModelName[0] == '*' ) { headsFolder = "heads/"; headModelName++; } else { headsFolder = ""; } while(1) { for ( i = 0; i < 2; i++ ) { if ( i == 0 && teamName && *teamName ) { Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext ); } else { Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext ); } if ( CG_FileExists( filename ) ) { return qtrue; } if ( cgs.gametype >= GT_TEAM ) { if ( i == 0 && teamName && *teamName ) { Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, team, ext ); } else { Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, team, ext ); } } else { if ( i == 0 && teamName && *teamName ) { Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext ); } else { Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext ); } } if ( CG_FileExists( filename ) ) { return qtrue; } if ( !teamName || !*teamName ) { break; } } // if tried the heads folder first if ( headsFolder[0] ) { break; } headsFolder = "heads/"; } return qfalse; } /* ========================== CG_RegisterClientSkin ========================== */ static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName ) { char filename[MAX_QPATH]; /* Com_sprintf( filename, sizeof( filename ), "models/players/%s/%slower_%s.skin", modelName, teamName, skinName ); ci->legsSkin = trap_R_RegisterSkin( filename ); if (!ci->legsSkin) { Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%slower_%s.skin", modelName, teamName, skinName ); ci->legsSkin = trap_R_RegisterSkin( filename ); if (!ci->legsSkin) { Com_Printf( "Leg skin load failure: %s\n", filename ); } } Com_sprintf( filename, sizeof( filename ), "models/players/%s/%supper_%s.skin", modelName, teamName, skinName ); ci->torsoSkin = trap_R_RegisterSkin( filename ); if (!ci->torsoSkin) { Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%supper_%s.skin", modelName, teamName, skinName ); ci->torsoSkin = trap_R_RegisterSkin( filename ); if (!ci->torsoSkin) { Com_Printf( "Torso skin load failure: %s\n", filename ); } } */ if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "lower", "skin" ) ) { ci->legsSkin = trap_R_RegisterSkin( filename ); } if (!ci->legsSkin) { Com_Printf( "Leg skin load failure: %s\n", filename ); } if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "upper", "skin" ) ) { ci->torsoSkin = trap_R_RegisterSkin( filename ); } if (!ci->torsoSkin) { Com_Printf( "Torso skin load failure: %s\n", filename ); } if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headModelName, headSkinName, "head", "skin" ) ) { ci->headSkin = trap_R_RegisterSkin( filename ); } if (!ci->headSkin) { Com_Printf( "Head skin load failure: %s\n", filename ); } // if any skins failed to load if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) { return qfalse; } return qtrue; } /* ========================== CG_RegisterClientModelname ========================== */ static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName, const char *teamName ) { char filename[MAX_QPATH*2]; const char *headName; char newTeamName[MAX_QPATH*2]; if ( headModelName[0] == '\0' ) { headName = modelName; } else { headName = headModelName; } Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); ci->legsModel = trap_R_RegisterModel( filename ); if ( !ci->legsModel ) { Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); ci->legsModel = trap_R_RegisterModel( filename ); if ( !ci->legsModel ) { Com_Printf( "Failed to load model file %s\n", filename ); return qfalse; } } Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); ci->torsoModel = trap_R_RegisterModel( filename ); if ( !ci->torsoModel ) { Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); ci->torsoModel = trap_R_RegisterModel( filename ); if ( !ci->torsoModel ) { Com_Printf( "Failed to load model file %s\n", filename ); return qfalse; } } if( headName[0] == '*' ) { Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] ); } else { Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headName ); } ci->headModel = trap_R_RegisterModel( filename ); // if the head model could not be found and we didn't load from the heads folder try to load from there if ( !ci->headModel && headName[0] != '*' ) { Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); ci->headModel = trap_R_RegisterModel( filename ); } if ( !ci->headModel ) { Com_Printf( "Failed to load model file %s\n", filename ); return qfalse; } // if any skins failed to load, return failure if ( !CG_RegisterClientSkin( ci, teamName, modelName, skinName, headName, headSkinName ) ) { if ( teamName && *teamName) { Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", teamName, modelName, skinName, headName, headSkinName ); if( ci->team == TEAM_BLUE ) { Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_BLUETEAM_NAME); } else { Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_REDTEAM_NAME); } if ( !CG_RegisterClientSkin( ci, newTeamName, modelName, skinName, headName, headSkinName ) ) { Com_Printf( "Failed to load skin file: %s : %s : %s, %s : %s\n", newTeamName, modelName, skinName, headName, headSkinName ); return qfalse; } } else { Com_Printf( "Failed to load skin file: %s : %s, %s : %s\n", modelName, skinName, headName, headSkinName ); return qfalse; } } // load the animations Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); if ( !CG_ParseAnimationFile( filename, ci ) ) { Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); if ( !CG_ParseAnimationFile( filename, ci ) ) { Com_Printf( "Failed to load animation file %s\n", filename ); return qfalse; } } if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "skin" ) ) { ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); } else if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "tga" ) ) { ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); } if ( !ci->modelIcon ) { return qfalse; } return qtrue; } /* ==================== CG_ColorFromString ==================== */ static void CG_ColorFromString( const char *v, vec3_t color ) { int val; VectorClear( color ); val = atoi( v ); if ( val < 1 || val > 7 ) { VectorSet( color, 1, 1, 1 ); return; } if ( val & 1 ) { color[2] = 1.0f; } if ( val & 2 ) { color[1] = 1.0f; } if ( val & 4 ) { color[0] = 1.0f; } } /* =================== CG_LoadClientInfo Load it now, taking the disk hits. This will usually be deferred to a safe time =================== */ static void CG_LoadClientInfo( clientInfo_t *ci ) { const char *dir, *fallback; int i, modelloaded; const char *s; int clientNum; char teamname[MAX_QPATH]; teamname[0] = 0; #ifdef MISSIONPACK if( cgs.gametype >= GT_TEAM) { if( ci->team == TEAM_BLUE ) { Q_strncpyz(teamname, cg_blueTeamName.string, sizeof(teamname) ); } else { Q_strncpyz(teamname, cg_redTeamName.string, sizeof(teamname) ); } } if( teamname[0] ) { strcat( teamname, "/" ); } #endif modelloaded = qtrue; if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ) ) { if ( cg_buildScript.integer ) { CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); } // fall back to default team name if( cgs.gametype >= GT_TEAM) { // keep skin name if( ci->team == TEAM_BLUE ) { Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof(teamname) ); } else { Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof(teamname) ); } if ( !CG_RegisterClientModelname( ci, DEFAULT_TEAM_MODEL, ci->skinName, DEFAULT_TEAM_HEAD, ci->skinName, teamname ) ) { CG_Error( "DEFAULT_TEAM_MODEL / skin (%s/%s) failed to register", DEFAULT_TEAM_MODEL, ci->skinName ); } } else { if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", DEFAULT_MODEL, "default", teamname ) ) { CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); } } modelloaded = qfalse; } ci->newAnims = qfalse; if ( ci->torsoModel ) { orientation_t tag; // if the torso model has the "tag_flag" if ( trap_R_LerpTag( &tag, ci->torsoModel, 0, 0, 1, "tag_flag" ) ) { ci->newAnims = qtrue; } } // sounds dir = ci->modelName; fallback = (cgs.gametype >= GT_TEAM) ? DEFAULT_TEAM_MODEL : DEFAULT_MODEL; for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { s = cg_customSoundNames[i]; if ( !s ) { break; } ci->sounds[i] = 0; // if the model didn't load use the sounds of the default model if (modelloaded) { ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", dir, s + 1), qfalse ); } if ( !ci->sounds[i] ) { ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", fallback, s + 1), qfalse ); } } ci->deferred = qfalse; // reset any existing players and bodies, because they might be in bad // frames for this new model clientNum = ci - cgs.clientinfo; for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { if ( cg_entities[i].currentState.clientNum == clientNum && cg_entities[i].currentState.eType == ET_PLAYER ) { CG_ResetPlayerEntity( &cg_entities[i] ); } } } /* ====================== CG_CopyClientInfoModel ====================== */ static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) { VectorCopy( from->headOffset, to->headOffset ); to->footsteps = from->footsteps; to->gender = from->gender; to->legsModel = from->legsModel; to->legsSkin = from->legsSkin; to->torsoModel = from->torsoModel; to->torsoSkin = from->torsoSkin; to->headModel = from->headModel; to->headSkin = from->headSkin; to->modelIcon = from->modelIcon; to->newAnims = from->newAnims; memcpy( to->animations, from->animations, sizeof( to->animations ) ); memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); } /* ====================== CG_ScanForExistingClientInfo ====================== */ static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) { int i; clientInfo_t *match; for ( i = 0 ; i < cgs.maxclients ; i++ ) { match = &cgs.clientinfo[ i ]; if ( !match->infoValid ) { continue; } if ( match->deferred ) { continue; } if ( !Q_stricmp( ci->modelName, match->modelName ) && !Q_stricmp( ci->skinName, match->skinName ) && !Q_stricmp( ci->headModelName, match->headModelName ) && !Q_stricmp( ci->headSkinName, match->headSkinName ) && !Q_stricmp( ci->blueTeam, match->blueTeam ) && !Q_stricmp( ci->redTeam, match->redTeam ) && (cgs.gametype < GT_TEAM || ci->team == match->team) ) { // this clientinfo is identical, so use it's handles ci->deferred = qfalse; CG_CopyClientInfoModel( match, ci ); return qtrue; } } // nothing matches, so defer the load return qfalse; } /* ====================== CG_SetDeferredClientInfo We aren't going to load it now, so grab some other client's info to use until we have some spare time. ====================== */ static void CG_SetDeferredClientInfo( clientInfo_t *ci ) { int i; clientInfo_t *match; // if someone else is already the same models and skins we // can just load the client info for ( i = 0 ; i < cgs.maxclients ; i++ ) { match = &cgs.clientinfo[ i ]; if ( !match->infoValid || match->deferred ) { continue; } if ( Q_stricmp( ci->skinName, match->skinName ) || Q_stricmp( ci->modelName, match->modelName ) || // Q_stricmp( ci->headModelName, match->headModelName ) || // Q_stricmp( ci->headSkinName, match->headSkinName ) || (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { continue; } // just load the real info cause it uses the same models and skins CG_LoadClientInfo( ci ); return; } // if we are in teamplay, only grab a model if the skin is correct if ( cgs.gametype >= GT_TEAM ) { for ( i = 0 ; i < cgs.maxclients ; i++ ) { match = &cgs.clientinfo[ i ]; if ( !match->infoValid || match->deferred ) { continue; } if ( Q_stricmp( ci->skinName, match->skinName ) || (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { continue; } ci->deferred = qtrue; CG_CopyClientInfoModel( match, ci ); return; } // load the full model, because we don't ever want to show // an improper team skin. This will cause a hitch for the first // player, when the second enters. Combat shouldn't be going on // yet, so it shouldn't matter CG_LoadClientInfo( ci ); return; } // find the first valid clientinfo and grab its stuff for ( i = 0 ; i < cgs.maxclients ; i++ ) { match = &cgs.clientinfo[ i ]; if ( !match->infoValid ) { continue; } ci->deferred = qtrue; CG_CopyClientInfoModel( match, ci ); return; } // we should never get here... CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); CG_LoadClientInfo( ci ); } /* ====================== CG_NewClientInfo ====================== */ void CG_NewClientInfo( int clientNum ) { clientInfo_t *ci; clientInfo_t newInfo; const char *configstring; const char *v; char *slash; ci = &cgs.clientinfo[clientNum]; configstring = CG_ConfigString( clientNum + CS_PLAYERS ); if ( !configstring[0] ) { memset( ci, 0, sizeof( *ci ) ); return; // player just left } // build into a temp buffer so the defer checks can use // the old value memset( &newInfo, 0, sizeof( newInfo ) ); // isolate the player's name v = Info_ValueForKey(configstring, "n"); Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); // colors v = Info_ValueForKey( configstring, "c1" ); CG_ColorFromString( v, newInfo.color1 ); v = Info_ValueForKey( configstring, "c2" ); CG_ColorFromString( v, newInfo.color2 ); // bot skill v = Info_ValueForKey( configstring, "skill" ); newInfo.botSkill = atoi( v ); // handicap v = Info_ValueForKey( configstring, "hc" ); newInfo.handicap = atoi( v ); // wins v = Info_ValueForKey( configstring, "w" ); newInfo.wins = atoi( v ); // losses v = Info_ValueForKey( configstring, "l" ); newInfo.losses = atoi( v ); // team v = Info_ValueForKey( configstring, "t" ); newInfo.team = atoi( v ); // team task v = Info_ValueForKey( configstring, "tt" ); newInfo.teamTask = atoi(v); // team leader v = Info_ValueForKey( configstring, "tl" ); newInfo.teamLeader = atoi(v); v = Info_ValueForKey( configstring, "g_redteam" ); Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME); v = Info_ValueForKey( configstring, "g_blueteam" ); Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME); // model v = Info_ValueForKey( configstring, "model" ); if ( cg_forceModel.integer ) { // forcemodel makes everyone use a single model // to prevent load hitches char modelStr[MAX_QPATH]; char *skin; if( cgs.gametype >= GT_TEAM ) { Q_strncpyz( newInfo.modelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.modelName ) ); Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); } else { trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) ); if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { skin = "default"; } else { *skin++ = 0; } Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) ); } if ( cgs.gametype >= GT_TEAM ) { // keep skin name slash = strchr( v, '/' ); if ( slash ) { Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); } } } else { Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); slash = strchr( newInfo.modelName, '/' ); if ( !slash ) { // modelName didn not include a skin name Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); } else { Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); // truncate modelName *slash = 0; } } // head model v = Info_ValueForKey( configstring, "hmodel" ); if ( cg_forceModel.integer ) { // forcemodel makes everyone use a single model // to prevent load hitches char modelStr[MAX_QPATH]; char *skin; if( cgs.gametype >= GT_TEAM ) { Q_strncpyz( newInfo.headModelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.headModelName ) ); Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); } else { trap_Cvar_VariableStringBuffer( "headmodel", modelStr, sizeof( modelStr ) ); if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { skin = "default"; } else { *skin++ = 0; } Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) ); Q_strncpyz( newInfo.headModelName, modelStr, sizeof( newInfo.headModelName ) ); } if ( cgs.gametype >= GT_TEAM ) { // keep skin name slash = strchr( v, '/' ); if ( slash ) { Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); } } } else { Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) ); slash = strchr( newInfo.headModelName, '/' ); if ( !slash ) { // modelName didn not include a skin name Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); } else { Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); // truncate modelName *slash = 0; } } // scan for an existing clientinfo that matches this modelname // so we can avoid loading checks if possible if ( !CG_ScanForExistingClientInfo( &newInfo ) ) { qboolean forceDefer; forceDefer = trap_MemoryRemaining() < 4000000; // if we are defering loads, just have it pick the first valid if ( forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) { // keep whatever they had if it won't violate team skins CG_SetDeferredClientInfo( &newInfo ); // if we are low on memory, leave them with this model if ( forceDefer ) { CG_Printf( "Memory is low. Using deferred model.\n" ); newInfo.deferred = qfalse; } } else { CG_LoadClientInfo( &newInfo ); } } // replace whatever was there with the new one newInfo.infoValid = qtrue; *ci = newInfo; } /* ====================== CG_LoadDeferredPlayers Called each frame when a player is dead and the scoreboard is up so deferred players can be loaded ====================== */ void CG_LoadDeferredPlayers( void ) { int i; clientInfo_t *ci; // scan for a deferred player to load for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) { if ( ci->infoValid && ci->deferred ) { // if we are low on memory, leave it deferred if ( trap_MemoryRemaining() < 4000000 ) { CG_Printf( "Memory is low. Using deferred model.\n" ); ci->deferred = qfalse; continue; } CG_LoadClientInfo( ci ); // break; } } } /* ============================================================================= PLAYER ANIMATION ============================================================================= */ /* =============== CG_SetLerpFrameAnimation may include ANIM_TOGGLEBIT =============== */ static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { animation_t *anim; lf->animationNumber = newAnimation; newAnimation &= ~ANIM_TOGGLEBIT; if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) { CG_Error( "Bad animation number: %i", newAnimation ); } anim = &ci->animations[ newAnimation ]; lf->animation = anim; lf->animationTime = lf->frameTime + anim->initialLerp; if ( cg_debugAnim.integer ) { CG_Printf( "Anim: %i\n", newAnimation ); } } /* =============== CG_RunLerpFrame Sets cg.snap, cg.oldFrame, and cg.backlerp cg.time should be between oldFrameTime and frameTime after exit =============== */ static void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { int f, numFrames; animation_t *anim; // debugging tool to get no animations if ( cg_animSpeed.integer == 0 ) { lf->oldFrame = lf->frame = lf->backlerp = 0; return; } // see if the animation sequence is switching if ( newAnimation != lf->animationNumber || !lf->animation ) { CG_SetLerpFrameAnimation( ci, lf, newAnimation ); } // if we have passed the current frame, move it to // oldFrame and calculate a new frame if ( cg.time >= lf->frameTime ) { lf->oldFrame = lf->frame; lf->oldFrameTime = lf->frameTime; // get the next frame based on the animation anim = lf->animation; if ( !anim->frameLerp ) { return; // shouldn't happen } if ( cg.time < lf->animationTime ) { lf->frameTime = lf->animationTime; // initial lerp } else { lf->frameTime = lf->oldFrameTime + anim->frameLerp; } f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; f *= speedScale; // adjust for haste, etc numFrames = anim->numFrames; if (anim->flipflop) { numFrames *= 2; } if ( f >= numFrames ) { f -= numFrames; if ( anim->loopFrames ) { f %= anim->loopFrames; f += anim->numFrames - anim->loopFrames; } else { f = numFrames - 1; // the animation is stuck at the end, so it // can immediately transition to another sequence lf->frameTime = cg.time; } } if ( anim->reversed ) { lf->frame = anim->firstFrame + anim->numFrames - 1 - f; } else if (anim->flipflop && f>=anim->numFrames) { lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames); } else { lf->frame = anim->firstFrame + f; } if ( cg.time > lf->frameTime ) { lf->frameTime = cg.time; if ( cg_debugAnim.integer ) { CG_Printf( "Clamp lf->frameTime\n"); } } } if ( lf->frameTime > cg.time + 200 ) { lf->frameTime = cg.time; } if ( lf->oldFrameTime > cg.time ) { lf->oldFrameTime = cg.time; } // calculate current lerp value if ( lf->frameTime == lf->oldFrameTime ) { lf->backlerp = 0; } else { lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); } } /* =============== CG_ClearLerpFrame =============== */ static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) { lf->frameTime = lf->oldFrameTime = cg.time; CG_SetLerpFrameAnimation( ci, lf, animationNumber ); lf->oldFrame = lf->frame = lf->animation->firstFrame; } /* =============== CG_PlayerAnimation =============== */ static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, int *torsoOld, int *torso, float *torsoBackLerp ) { clientInfo_t *ci; int clientNum; float speedScale; clientNum = cent->currentState.clientNum; if ( cg_noPlayerAnims.integer ) { *legsOld = *legs = *torsoOld = *torso = 0; return; } if ( cent->currentState.powerups & ( 1 << PW_HASTE ) ) { speedScale = 1.5; } else { speedScale = 1; } ci = &cgs.clientinfo[ clientNum ]; // do the shuffle turn frames locally if ( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) { CG_RunLerpFrame( ci, ¢->pe.legs, LEGS_TURN, speedScale ); } else { CG_RunLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale ); } *legsOld = cent->pe.legs.oldFrame; *legs = cent->pe.legs.frame; *legsBackLerp = cent->pe.legs.backlerp; CG_RunLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale ); *torsoOld = cent->pe.torso.oldFrame; *torso = cent->pe.torso.frame; *torsoBackLerp = cent->pe.torso.backlerp; } /* ============================================================================= PLAYER ANGLES ============================================================================= */ /* ================== CG_SwingAngles ================== */ static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance, float speed, float *angle, qboolean *swinging ) { float swing; float move; float scale; if ( !*swinging ) { // see if a swing should be started swing = AngleSubtract( *angle, destination ); if ( swing > swingTolerance || swing < -swingTolerance ) { *swinging = qtrue; } } if ( !*swinging ) { return; } // modify the speed depending on the delta // so it doesn't seem so linear swing = AngleSubtract( destination, *angle ); scale = fabs( swing ); if ( scale < swingTolerance * 0.5 ) { scale = 0.5; } else if ( scale < swingTolerance ) { scale = 1.0; } else { scale = 2.0; } // swing towards the destination angle if ( swing >= 0 ) { move = cg.frametime * scale * speed; if ( move >= swing ) { move = swing; *swinging = qfalse; } *angle = AngleMod( *angle + move ); } else if ( swing < 0 ) { move = cg.frametime * scale * -speed; if ( move <= swing ) { move = swing; *swinging = qfalse; } *angle = AngleMod( *angle + move ); } // clamp to no more than tolerance swing = AngleSubtract( destination, *angle ); if ( swing > clampTolerance ) { *angle = AngleMod( destination - (clampTolerance - 1) ); } else if ( swing < -clampTolerance ) { *angle = AngleMod( destination + (clampTolerance - 1) ); } } /* ================= CG_AddPainTwitch ================= */ static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) { int t; float f; t = cg.time - cent->pe.painTime; if ( t >= PAIN_TWITCH_TIME ) { return; } f = 1.0 - (float)t / PAIN_TWITCH_TIME; if ( cent->pe.painDirection ) { torsoAngles[ROLL] += 20 * f; } else { torsoAngles[ROLL] -= 20 * f; } } /* =============== CG_PlayerAngles Handles seperate torso motion legs pivot based on direction of movement head always looks exactly at cent->lerpAngles if motion < 20 degrees, show in head only if < 45 degrees, also show in torso =============== */ static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { vec3_t legsAngles, torsoAngles, headAngles; float dest; static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; vec3_t velocity; float speed; int dir, clientNum; clientInfo_t *ci; VectorCopy( cent->lerpAngles, headAngles ); headAngles[YAW] = AngleMod( headAngles[YAW] ); VectorClear( legsAngles ); VectorClear( torsoAngles ); // --------- yaw ------------- // allow yaw to drift a bit if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) { // if not standing still, always point all in the same direction cent->pe.torso.yawing = qtrue; // always center cent->pe.torso.pitching = qtrue; // always center cent->pe.legs.yawing = qtrue; // always center } // adjust legs for movement dir if ( cent->currentState.eFlags & EF_DEAD ) { // don't let dead bodies twitch dir = 0; } else { dir = cent->currentState.angles2[YAW]; if ( dir < 0 || dir > 7 ) { CG_Error( "Bad player movement angle" ); } } legsAngles[YAW] = headAngles[YAW] + movementOffsets[ dir ]; torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ]; // torso CG_SwingAngles( torsoAngles[YAW], 25, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); CG_SwingAngles( legsAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); torsoAngles[YAW] = cent->pe.torso.yawAngle; legsAngles[YAW] = cent->pe.legs.yawAngle; // --------- pitch ------------- // only show a fraction of the pitch angle in the torso if ( headAngles[PITCH] > 180 ) { dest = (-360 + headAngles[PITCH]) * 0.75f; } else { dest = headAngles[PITCH] * 0.75f; } CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); torsoAngles[PITCH] = cent->pe.torso.pitchAngle; // clientNum = cent->currentState.clientNum; if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { ci = &cgs.clientinfo[ clientNum ]; if ( ci->fixedtorso ) { torsoAngles[PITCH] = 0.0f; } } // --------- roll ------------- // lean towards the direction of travel VectorCopy( cent->currentState.pos.trDelta, velocity ); speed = VectorNormalize( velocity ); if ( speed ) { vec3_t axis[3]; float side; speed *= 0.05f; AnglesToAxis( legsAngles, axis ); side = speed * DotProduct( velocity, axis[1] ); legsAngles[ROLL] -= side; side = speed * DotProduct( velocity, axis[0] ); legsAngles[PITCH] += side; } // clientNum = cent->currentState.clientNum; if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { ci = &cgs.clientinfo[ clientNum ]; if ( ci->fixedlegs ) { legsAngles[YAW] = torsoAngles[YAW]; legsAngles[PITCH] = 0.0f; legsAngles[ROLL] = 0.0f; } } // pain twitch CG_AddPainTwitch( cent, torsoAngles ); // pull the angles back out of the hierarchial chain AnglesSubtract( headAngles, torsoAngles, headAngles ); AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); AnglesToAxis( legsAngles, legs ); AnglesToAxis( torsoAngles, torso ); AnglesToAxis( headAngles, head ); } //========================================================================== /* =============== CG_HasteTrail =============== */ static void CG_HasteTrail( centity_t *cent ) { localEntity_t *smoke; vec3_t origin; int anim; if ( cent->trailTime > cg.time ) { return; } anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; if ( anim != LEGS_RUN && anim != LEGS_BACK ) { return; } cent->trailTime += 100; if ( cent->trailTime < cg.time ) { cent->trailTime = cg.time; } VectorCopy( cent->lerpOrigin, origin ); origin[2] -= 16; smoke = CG_SmokePuff( origin, vec3_origin, 8, 1, 1, 1, 1, 500, cg.time, 0, 0, cgs.media.hastePuffShader ); // use the optimized local entity add smoke->leType = LE_SCALE_FADE; } #ifdef MISSIONPACK /* =============== CG_BreathPuffs =============== */ static void CG_BreathPuffs( centity_t *cent, refEntity_t *head) { clientInfo_t *ci; vec3_t up, origin; int contents; ci = &cgs.clientinfo[ cent->currentState.number ]; if (!cg_enableBreath.integer) { return; } if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) { return; } if ( cent->currentState.eFlags & EF_DEAD ) { return; } contents = trap_CM_PointContents( head->origin, 0 ); if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { return; } if ( ci->breathPuffTime > cg.time ) { return; } VectorSet( up, 0, 0, 8 ); VectorMA(head->origin, 8, head->axis[0], origin); VectorMA(origin, -4, head->axis[2], origin); CG_SmokePuff( origin, up, 16, 1, 1, 1, 0.66f, 1500, cg.time, cg.time + 400, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); ci->breathPuffTime = cg.time + 2000; } /* =============== CG_DustTrail =============== */ static void CG_DustTrail( centity_t *cent ) { int anim; localEntity_t *dust; vec3_t end, vel; trace_t tr; if (!cg_enableDust.integer) return; if ( cent->dustTrailTime > cg.time ) { return; } anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; if ( anim != LEGS_LANDB && anim != LEGS_LAND ) { return; } cent->dustTrailTime += 40; if ( cent->dustTrailTime < cg.time ) { cent->dustTrailTime = cg.time; } VectorCopy(cent->currentState.pos.trBase, end); end[2] -= 64; CG_Trace( &tr, cent->currentState.pos.trBase, NULL, NULL, end, cent->currentState.number, MASK_PLAYERSOLID ); if ( !(tr.surfaceFlags & SURF_DUST) ) return; VectorCopy( cent->currentState.pos.trBase, end ); end[2] -= 16; VectorSet(vel, 0, 0, -30); dust = CG_SmokePuff( end, vel, 24, .8f, .8f, 0.7f, 0.33f, 500, cg.time, 0, 0, cgs.media.dustPuffShader ); } #endif /* =============== CG_TrailItem =============== */ static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { refEntity_t ent; vec3_t angles; vec3_t axis[3]; VectorCopy( cent->lerpAngles, angles ); angles[PITCH] = 0; angles[ROLL] = 0; AnglesToAxis( angles, axis ); memset( &ent, 0, sizeof( ent ) ); VectorMA( cent->lerpOrigin, -16, axis[0], ent.origin ); ent.origin[2] += 16; angles[YAW] += 90; AnglesToAxis( angles, ent.axis ); ent.hModel = hModel; trap_R_AddRefEntityToScene( &ent ); } /* =============== CG_PlayerFlag =============== */ static void CG_PlayerFlag( centity_t *cent, qhandle_t hSkin, refEntity_t *torso ) { clientInfo_t *ci; refEntity_t pole; refEntity_t flag; vec3_t angles, dir; int legsAnim, flagAnim, updateangles; float angle, d; // show the flag pole model memset( &pole, 0, sizeof(pole) ); pole.hModel = cgs.media.flagPoleModel; VectorCopy( torso->lightingOrigin, pole.lightingOrigin ); pole.shadowPlane = torso->shadowPlane; pole.renderfx = torso->renderfx; CG_PositionEntityOnTag( &pole, torso, torso->hModel, "tag_flag" ); trap_R_AddRefEntityToScene( &pole ); // show the flag model memset( &flag, 0, sizeof(flag) ); flag.hModel = cgs.media.flagFlapModel; flag.customSkin = hSkin; VectorCopy( torso->lightingOrigin, flag.lightingOrigin ); flag.shadowPlane = torso->shadowPlane; flag.renderfx = torso->renderfx; VectorClear(angles); updateangles = qfalse; legsAnim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; if( legsAnim == LEGS_IDLE || legsAnim == LEGS_IDLECR ) { flagAnim = FLAG_STAND; } else if ( legsAnim == LEGS_WALK || legsAnim == LEGS_WALKCR ) { flagAnim = FLAG_STAND; updateangles = qtrue; } else { flagAnim = FLAG_RUN; updateangles = qtrue; } if ( updateangles ) { VectorCopy( cent->currentState.pos.trDelta, dir ); // add gravity dir[2] += 100; VectorNormalize( dir ); d = DotProduct(pole.axis[2], dir); // if there is anough movement orthogonal to the flag pole if (fabs(d) < 0.9) { // d = DotProduct(pole.axis[0], dir); if (d > 1.0f) { d = 1.0f; } else if (d < -1.0f) { d = -1.0f; } angle = acos(d); d = DotProduct(pole.axis[1], dir); if (d < 0) { angles[YAW] = 360 - angle * 180 / M_PI; } else { angles[YAW] = angle * 180 / M_PI; } if (angles[YAW] < 0) angles[YAW] += 360; if (angles[YAW] > 360) angles[YAW] -= 360; //vectoangles( cent->currentState.pos.trDelta, tmpangles ); //angles[YAW] = tmpangles[YAW] + 45 - cent->pe.torso.yawAngle; // change the yaw angle CG_SwingAngles( angles[YAW], 25, 90, 0.15f, ¢->pe.flag.yawAngle, ¢->pe.flag.yawing ); } /* d = DotProduct(pole.axis[2], dir); angle = Q_acos(d); d = DotProduct(pole.axis[1], dir); if (d < 0) { angle = 360 - angle * 180 / M_PI; } else { angle = angle * 180 / M_PI; } if (angle > 340 && angle < 20) { flagAnim = FLAG_RUNUP; } if (angle > 160 && angle < 200) { flagAnim = FLAG_RUNDOWN; } */ } // set the yaw angle angles[YAW] = cent->pe.flag.yawAngle; // lerp the flag animation frames ci = &cgs.clientinfo[ cent->currentState.clientNum ]; CG_RunLerpFrame( ci, ¢->pe.flag, flagAnim, 1 ); flag.oldframe = cent->pe.flag.oldFrame; flag.frame = cent->pe.flag.frame; flag.backlerp = cent->pe.flag.backlerp; AnglesToAxis( angles, flag.axis ); CG_PositionRotatedEntityOnTag( &flag, &pole, pole.hModel, "tag_flag" ); trap_R_AddRefEntityToScene( &flag ); } #ifdef MISSIONPACK // bk001204 /* =============== CG_PlayerTokens =============== */ static void CG_PlayerTokens( centity_t *cent, int renderfx ) { int tokens, i, j; float angle; refEntity_t ent; vec3_t dir, origin; skulltrail_t *trail; trail = &cg.skulltrails[cent->currentState.number]; tokens = cent->currentState.generic1; if ( !tokens ) { trail->numpositions = 0; return; } if ( tokens > MAX_SKULLTRAIL ) { tokens = MAX_SKULLTRAIL; } // add skulls if there are more than last time for (i = 0; i < tokens - trail->numpositions; i++) { for (j = trail->numpositions; j > 0; j--) { VectorCopy(trail->positions[j-1], trail->positions[j]); } VectorCopy(cent->lerpOrigin, trail->positions[0]); } trail->numpositions = tokens; // move all the skulls along the trail VectorCopy(cent->lerpOrigin, origin); for (i = 0; i < trail->numpositions; i++) { VectorSubtract(trail->positions[i], origin, dir); if (VectorNormalize(dir) > 30) { VectorMA(origin, 30, dir, trail->positions[i]); } VectorCopy(trail->positions[i], origin); } memset( &ent, 0, sizeof( ent ) ); if( cgs.clientinfo[ cent->currentState.clientNum ].team == TEAM_BLUE ) { ent.hModel = cgs.media.redCubeModel; } else { ent.hModel = cgs.media.blueCubeModel; } ent.renderfx = renderfx; VectorCopy(cent->lerpOrigin, origin); for (i = 0; i < trail->numpositions; i++) { VectorSubtract(origin, trail->positions[i], ent.axis[0]); ent.axis[0][2] = 0; VectorNormalize(ent.axis[0]); VectorSet(ent.axis[2], 0, 0, 1); CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); VectorCopy(trail->positions[i], ent.origin); angle = (((cg.time + 500 * MAX_SKULLTRAIL - 500 * i) / 16) & 255) * (M_PI * 2) / 255; ent.origin[2] += sin(angle) * 10; trap_R_AddRefEntityToScene( &ent ); VectorCopy(trail->positions[i], origin); } } #endif /* =============== CG_PlayerPowerups =============== */ static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { int powerups; clientInfo_t *ci; powerups = cent->currentState.powerups; if ( !powerups ) { return; } // quad gives a dlight if ( powerups & ( 1 << PW_QUAD ) ) { trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 ); } // flight plays a looped sound if ( powerups & ( 1 << PW_FLIGHT ) ) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound ); } ci = &cgs.clientinfo[ cent->currentState.clientNum ]; // redflag if ( powerups & ( 1 << PW_REDFLAG ) ) { if (ci->newAnims) { CG_PlayerFlag( cent, cgs.media.redFlagFlapSkin, torso ); } else { CG_TrailItem( cent, cgs.media.redFlagModel ); } trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f ); } // blueflag if ( powerups & ( 1 << PW_BLUEFLAG ) ) { if (ci->newAnims){ CG_PlayerFlag( cent, cgs.media.blueFlagFlapSkin, torso ); } else { CG_TrailItem( cent, cgs.media.blueFlagModel ); } trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 ); } // neutralflag if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) { if (ci->newAnims) { CG_PlayerFlag( cent, cgs.media.neutralFlagFlapSkin, torso ); } else { CG_TrailItem( cent, cgs.media.neutralFlagModel ); } trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 ); } // haste leaves smoke trails if ( powerups & ( 1 << PW_HASTE ) ) { CG_HasteTrail( cent ); } } /* =============== CG_PlayerFloatSprite Float a sprite over the player's head =============== */ static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) { int rf; refEntity_t ent; if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { rf = RF_THIRD_PERSON; // only show in mirrors } else { rf = 0; } memset( &ent, 0, sizeof( ent ) ); VectorCopy( cent->lerpOrigin, ent.origin ); ent.origin[2] += 48; ent.reType = RT_SPRITE; ent.customShader = shader; ent.radius = 10; ent.renderfx = rf; ent.shaderRGBA[0] = 255; ent.shaderRGBA[1] = 255; ent.shaderRGBA[2] = 255; ent.shaderRGBA[3] = 255; trap_R_AddRefEntityToScene( &ent ); } /* =============== CG_PlayerSprites Float sprites over the player's head =============== */ static void CG_PlayerSprites( centity_t *cent ) { int team; if ( cent->currentState.eFlags & EF_CONNECTION ) { CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); return; } if ( cent->currentState.eFlags & EF_TALK ) { CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); return; } if ( cent->currentState.eFlags & EF_AWARD_IMPRESSIVE ) { CG_PlayerFloatSprite( cent, cgs.media.medalImpressive ); return; } if ( cent->currentState.eFlags & EF_AWARD_EXCELLENT ) { CG_PlayerFloatSprite( cent, cgs.media.medalExcellent ); return; } if ( cent->currentState.eFlags & EF_AWARD_GAUNTLET ) { CG_PlayerFloatSprite( cent, cgs.media.medalGauntlet ); return; } if ( cent->currentState.eFlags & EF_AWARD_DEFEND ) { CG_PlayerFloatSprite( cent, cgs.media.medalDefend ); return; } if ( cent->currentState.eFlags & EF_AWARD_ASSIST ) { CG_PlayerFloatSprite( cent, cgs.media.medalAssist ); return; } if ( cent->currentState.eFlags & EF_AWARD_CAP ) { CG_PlayerFloatSprite( cent, cgs.media.medalCapture ); return; } team = cgs.clientinfo[ cent->currentState.clientNum ].team; if ( !(cent->currentState.eFlags & EF_DEAD) && cg.snap->ps.persistant[PERS_TEAM] == team && cgs.gametype >= GT_TEAM) { if (cg_drawFriend.integer) { CG_PlayerFloatSprite( cent, cgs.media.friendShader ); } return; } } /* =============== CG_PlayerShadow Returns the Z component of the surface being shadowed should it return a full plane instead of a Z? =============== */ #define SHADOW_DISTANCE 128 static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2}; trace_t trace; float alpha; *shadowPlane = 0; if ( cg_shadows.integer == 0 ) { return qfalse; } // no shadows when invisible if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) { return qfalse; } // send a trace down from the player to the ground VectorCopy( cent->lerpOrigin, end ); end[2] -= SHADOW_DISTANCE; trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); // no shadow if too high if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) { return qfalse; } *shadowPlane = trace.endpos[2] + 1; if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows return qtrue; } // fade the shadow out with height alpha = 1.0 - trace.fraction; // bk0101022 - hack / FPE - bogus planes? //assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f ) // add the mark as a temporary, so it goes directly to the renderer // without taking a spot in the cg_marks array CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, 24, qtrue ); return qtrue; } /* =============== CG_PlayerSplash Draw a mark at the water surface =============== */ static void CG_PlayerSplash( centity_t *cent ) { vec3_t start, end; trace_t trace; int contents; polyVert_t verts[4]; if ( !cg_shadows.integer ) { return; } VectorCopy( cent->lerpOrigin, end ); end[2] -= 24; // if the feet aren't in liquid, don't make a mark // this won't handle moving water brushes, but they wouldn't draw right anyway... contents = trap_CM_PointContents( end, 0 ); if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { return; } VectorCopy( cent->lerpOrigin, start ); start[2] += 32; // if the head isn't out of liquid, don't make a mark contents = trap_CM_PointContents( start, 0 ); if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { return; } // trace down to find the surface trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); if ( trace.fraction == 1.0 ) { return; } // create a mark polygon VectorCopy( trace.endpos, verts[0].xyz ); verts[0].xyz[0] -= 32; verts[0].xyz[1] -= 32; verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255; VectorCopy( trace.endpos, verts[1].xyz ); verts[1].xyz[0] -= 32; verts[1].xyz[1] += 32; verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255; VectorCopy( trace.endpos, verts[2].xyz ); verts[2].xyz[0] += 32; verts[2].xyz[1] += 32; verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = 255; VectorCopy( trace.endpos, verts[3].xyz ); verts[3].xyz[0] += 32; verts[3].xyz[1] -= 32; verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = 255; trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts ); } /* =============== CG_AddRefEntityWithPowerups Adds a piece with modifications or duplications for powerups Also called by CG_Missile for quad rockets, but nobody can tell... =============== */ void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) { if ( state->powerups & ( 1 << PW_INVIS ) ) { ent->customShader = cgs.media.invisShader; trap_R_AddRefEntityToScene( ent ); } else { /* if ( state->eFlags & EF_KAMIKAZE ) { if (team == TEAM_BLUE) ent->customShader = cgs.media.blueKamikazeShader; else ent->customShader = cgs.media.redKamikazeShader; trap_R_AddRefEntityToScene( ent ); } else {*/ trap_R_AddRefEntityToScene( ent ); //} if ( state->powerups & ( 1 << PW_QUAD ) ) { if (team == TEAM_RED) ent->customShader = cgs.media.redQuadShader; else ent->customShader = cgs.media.quadShader; trap_R_AddRefEntityToScene( ent ); } if ( state->powerups & ( 1 << PW_REGEN ) ) { if ( ( ( cg.time / 100 ) % 10 ) == 1 ) { ent->customShader = cgs.media.regenShader; trap_R_AddRefEntityToScene( ent ); } } if ( state->powerups & ( 1 << PW_BATTLESUIT ) ) { ent->customShader = cgs.media.battleSuitShader; trap_R_AddRefEntityToScene( ent ); } } } /* ================= CG_LightVerts ================= */ int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) { int i, j; float incoming; vec3_t ambientLight; vec3_t lightDir; vec3_t directedLight; trap_R_LightForPoint( verts[0].xyz, ambientLight, directedLight, lightDir ); for (i = 0; i < numVerts; i++) { incoming = DotProduct (normal, lightDir); if ( incoming <= 0 ) { verts[i].modulate[0] = ambientLight[0]; verts[i].modulate[1] = ambientLight[1]; verts[i].modulate[2] = ambientLight[2]; verts[i].modulate[3] = 255; continue; } j = ( ambientLight[0] + incoming * directedLight[0] ); if ( j > 255 ) { j = 255; } verts[i].modulate[0] = j; j = ( ambientLight[1] + incoming * directedLight[1] ); if ( j > 255 ) { j = 255; } verts[i].modulate[1] = j; j = ( ambientLight[2] + incoming * directedLight[2] ); if ( j > 255 ) { j = 255; } verts[i].modulate[2] = j; verts[i].modulate[3] = 255; } return qtrue; } /* =============== CG_Player =============== */ void CG_Player( centity_t *cent ) { clientInfo_t *ci; refEntity_t legs; refEntity_t torso; refEntity_t head; int clientNum; int renderfx; qboolean shadow; float shadowPlane; #ifdef MISSIONPACK refEntity_t skull; refEntity_t powerup; int t; float c; float angle; vec3_t dir, angles; #endif // the client number is stored in clientNum. It can't be derived // from the entity number, because a single client may have // multiple corpses on the level using the same clientinfo clientNum = cent->currentState.clientNum; if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { CG_Error( "Bad clientNum on player entity"); } ci = &cgs.clientinfo[ clientNum ]; // it is possible to see corpses from disconnected players that may // not have valid clientinfo if ( !ci->infoValid ) { return; } // get the player model information renderfx = 0; if ( cent->currentState.number == cg.snap->ps.clientNum) { if (!cg.renderingThirdPerson) { renderfx = RF_THIRD_PERSON; // only draw in mirrors } else { if (cg_cameraMode.integer) { return; } } } memset( &legs, 0, sizeof(legs) ); memset( &torso, 0, sizeof(torso) ); memset( &head, 0, sizeof(head) ); // get the rotation information CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); // get the animation state (after rotation, to allow feet shuffle) CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, &torso.oldframe, &torso.frame, &torso.backlerp ); // add the talk baloon or disconnect icon CG_PlayerSprites( cent ); // add the shadow shadow = CG_PlayerShadow( cent, &shadowPlane ); // add a water splash if partially in and out of water CG_PlayerSplash( cent ); if ( cg_shadows.integer == 3 && shadow ) { renderfx |= RF_SHADOW_PLANE; } renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all #ifdef MISSIONPACK if( cgs.gametype == GT_HARVESTER ) { CG_PlayerTokens( cent, renderfx ); } #endif // // add the legs // legs.hModel = ci->legsModel; legs.customSkin = ci->legsSkin; VectorCopy( cent->lerpOrigin, legs.origin ); VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); legs.shadowPlane = shadowPlane; legs.renderfx = renderfx; VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all CG_AddRefEntityWithPowerups( &legs, ¢->currentState, ci->team ); // if the model failed, allow the default nullmodel to be displayed if (!legs.hModel) { return; } // // add the torso // torso.hModel = ci->torsoModel; if (!torso.hModel) { return; } torso.customSkin = ci->torsoSkin; VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso"); torso.shadowPlane = shadowPlane; torso.renderfx = renderfx; CG_AddRefEntityWithPowerups( &torso, ¢->currentState, ci->team ); #ifdef MISSIONPACK if ( cent->currentState.eFlags & EF_KAMIKAZE ) { memset( &skull, 0, sizeof(skull) ); VectorCopy( cent->lerpOrigin, skull.lightingOrigin ); skull.shadowPlane = shadowPlane; skull.renderfx = renderfx; if ( cent->currentState.eFlags & EF_DEAD ) { // one skull bobbing above the dead body angle = ((cg.time / 7) & 255) * (M_PI * 2) / 255; if (angle > M_PI * 2) angle -= (float)M_PI * 2; dir[0] = sin(angle) * 20; dir[1] = cos(angle) * 20; angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; dir[2] = 15 + sin(angle) * 8; VectorAdd(torso.origin, dir, skull.origin); dir[2] = 0; VectorCopy(dir, skull.axis[1]); VectorNormalize(skull.axis[1]); VectorSet(skull.axis[2], 0, 0, 1); CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); skull.hModel = cgs.media.kamikazeHeadModel; trap_R_AddRefEntityToScene( &skull ); skull.hModel = cgs.media.kamikazeHeadTrail; trap_R_AddRefEntityToScene( &skull ); } else { // three skulls spinning around the player angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; dir[0] = cos(angle) * 20; dir[1] = sin(angle) * 20; dir[2] = cos(angle) * 20; VectorAdd(torso.origin, dir, skull.origin); angles[0] = sin(angle) * 30; angles[1] = (angle * 180 / M_PI) + 90; if (angles[1] > 360) angles[1] -= 360; angles[2] = 0; AnglesToAxis( angles, skull.axis ); /* dir[2] = 0; VectorInverse(dir); VectorCopy(dir, skull.axis[1]); VectorNormalize(skull.axis[1]); VectorSet(skull.axis[2], 0, 0, 1); CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); */ skull.hModel = cgs.media.kamikazeHeadModel; trap_R_AddRefEntityToScene( &skull ); // flip the trail because this skull is spinning in the other direction VectorInverse(skull.axis[1]); skull.hModel = cgs.media.kamikazeHeadTrail; trap_R_AddRefEntityToScene( &skull ); angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255 + M_PI; if (angle > M_PI * 2) angle -= (float)M_PI * 2; dir[0] = sin(angle) * 20; dir[1] = cos(angle) * 20; dir[2] = cos(angle) * 20; VectorAdd(torso.origin, dir, skull.origin); angles[0] = cos(angle - 0.5 * M_PI) * 30; angles[1] = 360 - (angle * 180 / M_PI); if (angles[1] > 360) angles[1] -= 360; angles[2] = 0; AnglesToAxis( angles, skull.axis ); /* dir[2] = 0; VectorCopy(dir, skull.axis[1]); VectorNormalize(skull.axis[1]); VectorSet(skull.axis[2], 0, 0, 1); CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); */ skull.hModel = cgs.media.kamikazeHeadModel; trap_R_AddRefEntityToScene( &skull ); skull.hModel = cgs.media.kamikazeHeadTrail; trap_R_AddRefEntityToScene( &skull ); angle = ((cg.time / 3) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI; if (angle > M_PI * 2) angle -= (float)M_PI * 2; dir[0] = sin(angle) * 20; dir[1] = cos(angle) * 20; dir[2] = 0; VectorAdd(torso.origin, dir, skull.origin); VectorCopy(dir, skull.axis[1]); VectorNormalize(skull.axis[1]); VectorSet(skull.axis[2], 0, 0, 1); CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); skull.hModel = cgs.media.kamikazeHeadModel; trap_R_AddRefEntityToScene( &skull ); skull.hModel = cgs.media.kamikazeHeadTrail; trap_R_AddRefEntityToScene( &skull ); } } if ( cent->currentState.powerups & ( 1 << PW_GUARD ) ) { memcpy(&powerup, &torso, sizeof(torso)); powerup.hModel = cgs.media.guardPowerupModel; powerup.frame = 0; powerup.oldframe = 0; powerup.customSkin = 0; trap_R_AddRefEntityToScene( &powerup ); } if ( cent->currentState.powerups & ( 1 << PW_SCOUT ) ) { memcpy(&powerup, &torso, sizeof(torso)); powerup.hModel = cgs.media.scoutPowerupModel; powerup.frame = 0; powerup.oldframe = 0; powerup.customSkin = 0; trap_R_AddRefEntityToScene( &powerup ); } if ( cent->currentState.powerups & ( 1 << PW_DOUBLER ) ) { memcpy(&powerup, &torso, sizeof(torso)); powerup.hModel = cgs.media.doublerPowerupModel; powerup.frame = 0; powerup.oldframe = 0; powerup.customSkin = 0; trap_R_AddRefEntityToScene( &powerup ); } if ( cent->currentState.powerups & ( 1 << PW_AMMOREGEN ) ) { memcpy(&powerup, &torso, sizeof(torso)); powerup.hModel = cgs.media.ammoRegenPowerupModel; powerup.frame = 0; powerup.oldframe = 0; powerup.customSkin = 0; trap_R_AddRefEntityToScene( &powerup ); } if ( cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) { if ( !ci->invulnerabilityStartTime ) { ci->invulnerabilityStartTime = cg.time; } ci->invulnerabilityStopTime = cg.time; } else { ci->invulnerabilityStartTime = 0; } if ( (cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) || cg.time - ci->invulnerabilityStopTime < 250 ) { memcpy(&powerup, &torso, sizeof(torso)); powerup.hModel = cgs.media.invulnerabilityPowerupModel; powerup.customSkin = 0; // always draw powerup.renderfx &= ~RF_THIRD_PERSON; VectorCopy(cent->lerpOrigin, powerup.origin); if ( cg.time - ci->invulnerabilityStartTime < 250 ) { c = (float) (cg.time - ci->invulnerabilityStartTime) / 250; } else if (cg.time - ci->invulnerabilityStopTime < 250 ) { c = (float) (250 - (cg.time - ci->invulnerabilityStopTime)) / 250; } else { c = 1; } VectorSet( powerup.axis[0], c, 0, 0 ); VectorSet( powerup.axis[1], 0, c, 0 ); VectorSet( powerup.axis[2], 0, 0, c ); trap_R_AddRefEntityToScene( &powerup ); } t = cg.time - ci->medkitUsageTime; if ( ci->medkitUsageTime && t < 500 ) { memcpy(&powerup, &torso, sizeof(torso)); powerup.hModel = cgs.media.medkitUsageModel; powerup.customSkin = 0; // always draw powerup.renderfx &= ~RF_THIRD_PERSON; VectorClear(angles); AnglesToAxis(angles, powerup.axis); VectorCopy(cent->lerpOrigin, powerup.origin); powerup.origin[2] += -24 + (float) t * 80 / 500; if ( t > 400 ) { c = (float) (t - 1000) * 0xff / 100; powerup.shaderRGBA[0] = 0xff - c; powerup.shaderRGBA[1] = 0xff - c; powerup.shaderRGBA[2] = 0xff - c; powerup.shaderRGBA[3] = 0xff - c; } else { powerup.shaderRGBA[0] = 0xff; powerup.shaderRGBA[1] = 0xff; powerup.shaderRGBA[2] = 0xff; powerup.shaderRGBA[3] = 0xff; } trap_R_AddRefEntityToScene( &powerup ); } #endif // MISSIONPACK // // add the head // head.hModel = ci->headModel; if (!head.hModel) { return; } head.customSkin = ci->headSkin; VectorCopy( cent->lerpOrigin, head.lightingOrigin ); CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head"); head.shadowPlane = shadowPlane; head.renderfx = renderfx; CG_AddRefEntityWithPowerups( &head, ¢->currentState, ci->team ); #ifdef MISSIONPACK CG_BreathPuffs(cent, &head); CG_DustTrail(cent); #endif // // add the gun / barrel / flash // CG_AddPlayerWeapon( &torso, NULL, cent, ci->team ); // add powerups floating behind the player CG_PlayerPowerups( cent, &torso ); } //===================================================================== /* =============== CG_ResetPlayerEntity A player just came into view or teleported, so reset all animation info =============== */ void CG_ResetPlayerEntity( centity_t *cent ) { cent->errorTime = -99999; // guarantee no error decay added cent->extrapolated = qfalse; CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim ); CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim ); BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); VectorCopy( cent->lerpOrigin, cent->rawOrigin ); VectorCopy( cent->lerpAngles, cent->rawAngles ); memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); cent->pe.legs.yawAngle = cent->rawAngles[YAW]; cent->pe.legs.yawing = qfalse; cent->pe.legs.pitchAngle = 0; cent->pe.legs.pitching = qfalse; memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); cent->pe.torso.yawAngle = cent->rawAngles[YAW]; cent->pe.torso.yawing = qfalse; cent->pe.torso.pitchAngle = cent->rawAngles[PITCH]; cent->pe.torso.pitching = qfalse; if ( cg_debugPosition.integer ) { CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); } } ================================================ FILE: code/cgame/cg_playerstate.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_playerstate.c -- this file acts on changes in a new playerState_t // With normal play, this will be done after local prediction, but when // following another player or playing back a demo, it will be checked // when the snapshot transitions like all the other entities #include "cg_local.h" /* ============== CG_CheckAmmo If the ammo has gone low enough to generate the warning, play a sound ============== */ void CG_CheckAmmo( void ) { int i; int total; int previous; int weapons; // see about how many seconds of ammo we have remaining weapons = cg.snap->ps.stats[ STAT_WEAPONS ]; total = 0; for ( i = WP_MACHINEGUN ; i < WP_NUM_WEAPONS ; i++ ) { if ( ! ( weapons & ( 1 << i ) ) ) { continue; } switch ( i ) { case WP_ROCKET_LAUNCHER: case WP_GRENADE_LAUNCHER: case WP_RAILGUN: case WP_SHOTGUN: #ifdef MISSIONPACK case WP_PROX_LAUNCHER: #endif total += cg.snap->ps.ammo[i] * 1000; break; default: total += cg.snap->ps.ammo[i] * 200; break; } if ( total >= 5000 ) { cg.lowAmmoWarning = 0; return; } } previous = cg.lowAmmoWarning; if ( total == 0 ) { cg.lowAmmoWarning = 2; } else { cg.lowAmmoWarning = 1; } // play a sound on transitions if ( cg.lowAmmoWarning != previous ) { trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); } } /* ============== CG_DamageFeedback ============== */ void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) { float left, front, up; float kick; int health; float scale; vec3_t dir; vec3_t angles; float dist; float yaw, pitch; // show the attacking player's head and name in corner cg.attackerTime = cg.time; // the lower on health you are, the greater the view kick will be health = cg.snap->ps.stats[STAT_HEALTH]; if ( health < 40 ) { scale = 1; } else { scale = 40.0 / health; } kick = damage * scale; if (kick < 5) kick = 5; if (kick > 10) kick = 10; // if yaw and pitch are both 255, make the damage always centered (falling, etc) if ( yawByte == 255 && pitchByte == 255 ) { cg.damageX = 0; cg.damageY = 0; cg.v_dmg_roll = 0; cg.v_dmg_pitch = -kick; } else { // positional pitch = pitchByte / 255.0 * 360; yaw = yawByte / 255.0 * 360; angles[PITCH] = pitch; angles[YAW] = yaw; angles[ROLL] = 0; AngleVectors( angles, dir, NULL, NULL ); VectorSubtract( vec3_origin, dir, dir ); front = DotProduct (dir, cg.refdef.viewaxis[0] ); left = DotProduct (dir, cg.refdef.viewaxis[1] ); up = DotProduct (dir, cg.refdef.viewaxis[2] ); dir[0] = front; dir[1] = left; dir[2] = 0; dist = VectorLength( dir ); if ( dist < 0.1 ) { dist = 0.1f; } cg.v_dmg_roll = kick * left; cg.v_dmg_pitch = -kick * front; if ( front <= 0.1 ) { front = 0.1f; } cg.damageX = -left / front; cg.damageY = up / dist; } // clamp the position if ( cg.damageX > 1.0 ) { cg.damageX = 1.0; } if ( cg.damageX < - 1.0 ) { cg.damageX = -1.0; } if ( cg.damageY > 1.0 ) { cg.damageY = 1.0; } if ( cg.damageY < - 1.0 ) { cg.damageY = -1.0; } // don't let the screen flashes vary as much if ( kick > 10 ) { kick = 10; } cg.damageValue = kick; cg.v_dmg_time = cg.time + DAMAGE_TIME; cg.damageTime = cg.snap->serverTime; } /* ================ CG_Respawn A respawn happened this snapshot ================ */ void CG_Respawn( void ) { // no error decay on player movement cg.thisFrameTeleport = qtrue; // display weapons available cg.weaponSelectTime = cg.time; // select the weapon the server says we are using cg.weaponSelect = cg.snap->ps.weapon; } extern char *eventnames[]; /* ============== CG_CheckPlayerstateEvents ============== */ void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { int i; int event; centity_t *cent; if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) { cent = &cg_entities[ ps->clientNum ]; cent->currentState.event = ps->externalEvent; cent->currentState.eventParm = ps->externalEventParm; CG_EntityEvent( cent, cent->lerpOrigin ); } cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; // go through the predictable events buffer for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { // if we have a new predictable event if ( i >= ops->eventSequence // or the server told us to play another event instead of a predicted event we already issued // or something the server told us changed our prediction causing a different event || (i > ops->eventSequence - MAX_PS_EVENTS && ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]) ) { event = ps->events[ i & (MAX_PS_EVENTS-1) ]; cent->currentState.event = event; cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; CG_EntityEvent( cent, cent->lerpOrigin ); cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; cg.eventSequence++; } } } /* ================== CG_CheckChangedPredictableEvents ================== */ void CG_CheckChangedPredictableEvents( playerState_t *ps ) { int i; int event; centity_t *cent; cent = &cg.predictedPlayerEntity; for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { // if (i >= cg.eventSequence) { continue; } // if this event is not further back in than the maximum predictable events we remember if (i > cg.eventSequence - MAX_PREDICTED_EVENTS) { // if the new playerstate event is different from a previously predicted one if ( ps->events[i & (MAX_PS_EVENTS-1)] != cg.predictableEvents[i & (MAX_PREDICTED_EVENTS-1) ] ) { event = ps->events[ i & (MAX_PS_EVENTS-1) ]; cent->currentState.event = event; cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; CG_EntityEvent( cent, cent->lerpOrigin ); cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; if ( cg_showmiss.integer ) { CG_Printf("WARNING: changed predicted event\n"); } } } } } /* ================== pushReward ================== */ static void pushReward(sfxHandle_t sfx, qhandle_t shader, int rewardCount) { if (cg.rewardStack < (MAX_REWARDSTACK-1)) { cg.rewardStack++; cg.rewardSound[cg.rewardStack] = sfx; cg.rewardShader[cg.rewardStack] = shader; cg.rewardCount[cg.rewardStack] = rewardCount; } } /* ================== CG_CheckLocalSounds ================== */ void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { int highScore, health, armor, reward; sfxHandle_t sfx; // don't play the sounds if the player just changed teams if ( ps->persistant[PERS_TEAM] != ops->persistant[PERS_TEAM] ) { return; } // hit changes if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) { armor = ps->persistant[PERS_ATTACKEE_ARMOR] & 0xff; health = ps->persistant[PERS_ATTACKEE_ARMOR] >> 8; #ifdef MISSIONPACK if (armor > 50 ) { trap_S_StartLocalSound( cgs.media.hitSoundHighArmor, CHAN_LOCAL_SOUND ); } else if (armor || health > 100) { trap_S_StartLocalSound( cgs.media.hitSoundLowArmor, CHAN_LOCAL_SOUND ); } else { trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); } #else trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); #endif } else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) { trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND ); } // health changes of more than -1 should make pain sounds if ( ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1 ) { if ( ps->stats[STAT_HEALTH] > 0 ) { CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[STAT_HEALTH] ); } } // if we are going into the intermission, don't start any voices if ( cg.intermissionStarted ) { return; } // reward sounds reward = qfalse; if (ps->persistant[PERS_CAPTURES] != ops->persistant[PERS_CAPTURES]) { pushReward(cgs.media.captureAwardSound, cgs.media.medalCapture, ps->persistant[PERS_CAPTURES]); reward = qtrue; //Com_Printf("capture\n"); } if (ps->persistant[PERS_IMPRESSIVE_COUNT] != ops->persistant[PERS_IMPRESSIVE_COUNT]) { #ifdef MISSIONPACK if (ps->persistant[PERS_IMPRESSIVE_COUNT] == 1) { sfx = cgs.media.firstImpressiveSound; } else { sfx = cgs.media.impressiveSound; } #else sfx = cgs.media.impressiveSound; #endif pushReward(sfx, cgs.media.medalImpressive, ps->persistant[PERS_IMPRESSIVE_COUNT]); reward = qtrue; //Com_Printf("impressive\n"); } if (ps->persistant[PERS_EXCELLENT_COUNT] != ops->persistant[PERS_EXCELLENT_COUNT]) { #ifdef MISSIONPACK if (ps->persistant[PERS_EXCELLENT_COUNT] == 1) { sfx = cgs.media.firstExcellentSound; } else { sfx = cgs.media.excellentSound; } #else sfx = cgs.media.excellentSound; #endif pushReward(sfx, cgs.media.medalExcellent, ps->persistant[PERS_EXCELLENT_COUNT]); reward = qtrue; //Com_Printf("excellent\n"); } if (ps->persistant[PERS_GAUNTLET_FRAG_COUNT] != ops->persistant[PERS_GAUNTLET_FRAG_COUNT]) { #ifdef MISSIONPACK if (ops->persistant[PERS_GAUNTLET_FRAG_COUNT] == 1) { sfx = cgs.media.firstHumiliationSound; } else { sfx = cgs.media.humiliationSound; } #else sfx = cgs.media.humiliationSound; #endif pushReward(sfx, cgs.media.medalGauntlet, ps->persistant[PERS_GAUNTLET_FRAG_COUNT]); reward = qtrue; //Com_Printf("guantlet frag\n"); } if (ps->persistant[PERS_DEFEND_COUNT] != ops->persistant[PERS_DEFEND_COUNT]) { pushReward(cgs.media.defendSound, cgs.media.medalDefend, ps->persistant[PERS_DEFEND_COUNT]); reward = qtrue; //Com_Printf("defend\n"); } if (ps->persistant[PERS_ASSIST_COUNT] != ops->persistant[PERS_ASSIST_COUNT]) { pushReward(cgs.media.assistSound, cgs.media.medalAssist, ps->persistant[PERS_ASSIST_COUNT]); reward = qtrue; //Com_Printf("assist\n"); } // if any of the player event bits changed if (ps->persistant[PERS_PLAYEREVENTS] != ops->persistant[PERS_PLAYEREVENTS]) { if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD) != (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD)) { trap_S_StartLocalSound( cgs.media.deniedSound, CHAN_ANNOUNCER ); } else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD) != (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD)) { trap_S_StartLocalSound( cgs.media.humiliationSound, CHAN_ANNOUNCER ); } else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT) != (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT)) { trap_S_StartLocalSound( cgs.media.holyShitSound, CHAN_ANNOUNCER ); } reward = qtrue; } // check for flag pickup if ( cgs.gametype >= GT_TEAM ) { if ((ps->powerups[PW_REDFLAG] != ops->powerups[PW_REDFLAG] && ps->powerups[PW_REDFLAG]) || (ps->powerups[PW_BLUEFLAG] != ops->powerups[PW_BLUEFLAG] && ps->powerups[PW_BLUEFLAG]) || (ps->powerups[PW_NEUTRALFLAG] != ops->powerups[PW_NEUTRALFLAG] && ps->powerups[PW_NEUTRALFLAG]) ) { trap_S_StartLocalSound( cgs.media.youHaveFlagSound, CHAN_ANNOUNCER ); } } // lead changes if (!reward) { // if ( !cg.warmup ) { // never play lead changes during warmup if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) { if ( cgs.gametype < GT_TEAM) { if ( ps->persistant[PERS_RANK] == 0 ) { CG_AddBufferedSound(cgs.media.takenLeadSound); } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) { CG_AddBufferedSound(cgs.media.tiedLeadSound); } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) { CG_AddBufferedSound(cgs.media.lostLeadSound); } } } } } // timelimit warnings if ( cgs.timelimit > 0 ) { int msec; msec = cg.time - cgs.levelStartTime; if ( !( cg.timelimitWarnings & 4 ) && msec > ( cgs.timelimit * 60 + 2 ) * 1000 ) { cg.timelimitWarnings |= 1 | 2 | 4; trap_S_StartLocalSound( cgs.media.suddenDeathSound, CHAN_ANNOUNCER ); } else if ( !( cg.timelimitWarnings & 2 ) && msec > (cgs.timelimit - 1) * 60 * 1000 ) { cg.timelimitWarnings |= 1 | 2; trap_S_StartLocalSound( cgs.media.oneMinuteSound, CHAN_ANNOUNCER ); } else if ( cgs.timelimit > 5 && !( cg.timelimitWarnings & 1 ) && msec > (cgs.timelimit - 5) * 60 * 1000 ) { cg.timelimitWarnings |= 1; trap_S_StartLocalSound( cgs.media.fiveMinuteSound, CHAN_ANNOUNCER ); } } // fraglimit warnings if ( cgs.fraglimit > 0 && cgs.gametype < GT_CTF) { highScore = cgs.scores1; if ( !( cg.fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) { cg.fraglimitWarnings |= 1 | 2 | 4; CG_AddBufferedSound(cgs.media.oneFragSound); } else if ( cgs.fraglimit > 2 && !( cg.fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) { cg.fraglimitWarnings |= 1 | 2; CG_AddBufferedSound(cgs.media.twoFragSound); } else if ( cgs.fraglimit > 3 && !( cg.fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) { cg.fraglimitWarnings |= 1; CG_AddBufferedSound(cgs.media.threeFragSound); } } } /* =============== CG_TransitionPlayerState =============== */ void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) { // check for changing follow mode if ( ps->clientNum != ops->clientNum ) { cg.thisFrameTeleport = qtrue; // make sure we don't get any unwanted transition effects *ops = *ps; } // damage events (player is getting wounded) if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) { CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); } // respawning if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) { CG_Respawn(); } if ( cg.mapRestart ) { CG_Respawn(); cg.mapRestart = qfalse; } if ( cg.snap->ps.pm_type != PM_INTERMISSION && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) { CG_CheckLocalSounds( ps, ops ); } // check for going low on ammo CG_CheckAmmo(); // run events CG_CheckPlayerstateEvents( ps, ops ); // smooth the ducking viewheight change if ( ps->viewheight != ops->viewheight ) { cg.duckChange = ps->viewheight - ops->viewheight; cg.duckTime = cg.time; } } ================================================ FILE: code/cgame/cg_predict.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_predict.c -- this file generates cg.predictedPlayerState by either // interpolating between snapshots from the server or locally predicting // ahead the client's movement. // It also handles local physics interaction, like fragments bouncing off walls #include "cg_local.h" static pmove_t cg_pmove; static int cg_numSolidEntities; static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT]; static int cg_numTriggerEntities; static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; /* ==================== CG_BuildSolidList When a new cg.snap has been set, this function builds a sublist of the entities that are actually solid, to make for more efficient collision detection ==================== */ void CG_BuildSolidList( void ) { int i; centity_t *cent; snapshot_t *snap; entityState_t *ent; cg_numSolidEntities = 0; cg_numTriggerEntities = 0; if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { snap = cg.nextSnap; } else { snap = cg.snap; } for ( i = 0 ; i < snap->numEntities ; i++ ) { cent = &cg_entities[ snap->entities[ i ].number ]; ent = ¢->currentState; if ( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER ) { cg_triggerEntities[cg_numTriggerEntities] = cent; cg_numTriggerEntities++; continue; } if ( cent->nextState.solid ) { cg_solidEntities[cg_numSolidEntities] = cent; cg_numSolidEntities++; continue; } } } /* ==================== CG_ClipMoveToEntities ==================== */ static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int skipNumber, int mask, trace_t *tr ) { int i, x, zd, zu; trace_t trace; entityState_t *ent; clipHandle_t cmodel; vec3_t bmins, bmaxs; vec3_t origin, angles; centity_t *cent; for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { cent = cg_solidEntities[ i ]; ent = ¢->currentState; if ( ent->number == skipNumber ) { continue; } if ( ent->solid == SOLID_BMODEL ) { // special value for bmodel cmodel = trap_CM_InlineModel( ent->modelindex ); VectorCopy( cent->lerpAngles, angles ); BG_EvaluateTrajectory( ¢->currentState.pos, cg.physicsTime, origin ); } else { // encoded bbox x = (ent->solid & 255); zd = ((ent->solid>>8) & 255); zu = ((ent->solid>>16) & 255) - 32; bmins[0] = bmins[1] = -x; bmaxs[0] = bmaxs[1] = x; bmins[2] = -zd; bmaxs[2] = zu; cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); VectorCopy( vec3_origin, angles ); VectorCopy( cent->lerpOrigin, origin ); } trap_CM_TransformedBoxTrace ( &trace, start, end, mins, maxs, cmodel, mask, origin, angles); if (trace.allsolid || trace.fraction < tr->fraction) { trace.entityNum = ent->number; *tr = trace; } else if (trace.startsolid) { tr->startsolid = qtrue; } if ( tr->allsolid ) { return; } } } /* ================ CG_Trace ================ */ void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int skipNumber, int mask ) { trace_t t; trap_CM_BoxTrace ( &t, start, end, mins, maxs, 0, mask); t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; // check all other solid models CG_ClipMoveToEntities (start, mins, maxs, end, skipNumber, mask, &t); *result = t; } /* ================ CG_PointContents ================ */ int CG_PointContents( const vec3_t point, int passEntityNum ) { int i; entityState_t *ent; centity_t *cent; clipHandle_t cmodel; int contents; contents = trap_CM_PointContents (point, 0); for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { cent = cg_solidEntities[ i ]; ent = ¢->currentState; if ( ent->number == passEntityNum ) { continue; } if (ent->solid != SOLID_BMODEL) { // special value for bmodel continue; } cmodel = trap_CM_InlineModel( ent->modelindex ); if ( !cmodel ) { continue; } contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); } return contents; } /* ======================== CG_InterpolatePlayerState Generates cg.predictedPlayerState by interpolating between cg.snap->player_state and cg.nextFrame->player_state ======================== */ static void CG_InterpolatePlayerState( qboolean grabAngles ) { float f; int i; playerState_t *out; snapshot_t *prev, *next; out = &cg.predictedPlayerState; prev = cg.snap; next = cg.nextSnap; *out = cg.snap->ps; // if we are still allowing local input, short circuit the view angles if ( grabAngles ) { usercmd_t cmd; int cmdNum; cmdNum = trap_GetCurrentCmdNumber(); trap_GetUserCmd( cmdNum, &cmd ); PM_UpdateViewAngles( out, &cmd ); } // if the next frame is a teleport, we can't lerp to it if ( cg.nextFrameTeleport ) { return; } if ( !next || next->serverTime <= prev->serverTime ) { return; } f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); i = next->ps.bobCycle; if ( i < prev->ps.bobCycle ) { i += 256; // handle wraparound } out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); for ( i = 0 ; i < 3 ; i++ ) { out->origin[i] = prev->ps.origin[i] + f * (next->ps.origin[i] - prev->ps.origin[i] ); if ( !grabAngles ) { out->viewangles[i] = LerpAngle( prev->ps.viewangles[i], next->ps.viewangles[i], f ); } out->velocity[i] = prev->ps.velocity[i] + f * (next->ps.velocity[i] - prev->ps.velocity[i] ); } } /* =================== CG_TouchItem =================== */ static void CG_TouchItem( centity_t *cent ) { gitem_t *item; if ( !cg_predictItems.integer ) { return; } if ( !BG_PlayerTouchesItem( &cg.predictedPlayerState, ¢->currentState, cg.time ) ) { return; } // never pick an item up twice in a prediction if ( cent->miscTime == cg.time ) { return; } if ( !BG_CanItemBeGrabbed( cgs.gametype, ¢->currentState, &cg.predictedPlayerState ) ) { return; // can't hold it } item = &bg_itemlist[ cent->currentState.modelindex ]; // Special case for flags. // We don't predict touching our own flag #ifdef MISSIONPACK if( cgs.gametype == GT_1FCTF ) { if( item->giTag != PW_NEUTRALFLAG ) { return; } } if( cgs.gametype == GT_CTF || cgs.gametype == GT_HARVESTER ) { #else if( cgs.gametype == GT_CTF ) { #endif if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_RED && item->giTag == PW_REDFLAG) return; if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_BLUE && item->giTag == PW_BLUEFLAG) return; } // grab it BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.modelindex , &cg.predictedPlayerState); // remove it from the frame so it won't be drawn cent->currentState.eFlags |= EF_NODRAW; // don't touch it again this prediction cent->miscTime = cg.time; // if its a weapon, give them some predicted ammo so the autoswitch will work if ( item->giType == IT_WEAPON ) { cg.predictedPlayerState.stats[ STAT_WEAPONS ] |= 1 << item->giTag; if ( !cg.predictedPlayerState.ammo[ item->giTag ] ) { cg.predictedPlayerState.ammo[ item->giTag ] = 1; } } } /* ========================= CG_TouchTriggerPrediction Predict push triggers and items ========================= */ static void CG_TouchTriggerPrediction( void ) { int i; trace_t trace; entityState_t *ent; clipHandle_t cmodel; centity_t *cent; qboolean spectator; // dead clients don't activate triggers if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { return; } spectator = ( cg.predictedPlayerState.pm_type == PM_SPECTATOR ); if ( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator ) { return; } for ( i = 0 ; i < cg_numTriggerEntities ; i++ ) { cent = cg_triggerEntities[ i ]; ent = ¢->currentState; if ( ent->eType == ET_ITEM && !spectator ) { CG_TouchItem( cent ); continue; } if ( ent->solid != SOLID_BMODEL ) { continue; } cmodel = trap_CM_InlineModel( ent->modelindex ); if ( !cmodel ) { continue; } trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin, cg_pmove.mins, cg_pmove.maxs, cmodel, -1 ); if ( !trace.startsolid ) { continue; } if ( ent->eType == ET_TELEPORT_TRIGGER ) { cg.hyperspace = qtrue; } else if ( ent->eType == ET_PUSH_TRIGGER ) { BG_TouchJumpPad( &cg.predictedPlayerState, ent ); } } // if we didn't touch a jump pad this pmove frame if ( cg.predictedPlayerState.jumppad_frame != cg.predictedPlayerState.pmove_framecount ) { cg.predictedPlayerState.jumppad_frame = 0; cg.predictedPlayerState.jumppad_ent = 0; } } /* ================= CG_PredictPlayerState Generates cg.predictedPlayerState for the current cg.time cg.predictedPlayerState is guaranteed to be valid after exiting. For demo playback, this will be an interpolation between two valid playerState_t. For normal gameplay, it will be the result of predicted usercmd_t on top of the most recent playerState_t received from the server. Each new snapshot will usually have one or more new usercmd over the last, but we simulate all unacknowledged commands each time, not just the new ones. This means that on an internet connection, quite a few pmoves may be issued each frame. OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t differs from the predicted one. Would require saving all intermediate playerState_t during prediction. We detect prediction errors and allow them to be decayed off over several frames to ease the jerk. ================= */ void CG_PredictPlayerState( void ) { int cmdNum, current; playerState_t oldPlayerState; qboolean moved; usercmd_t oldestCmd; usercmd_t latestCmd; cg.hyperspace = qfalse; // will be set if touching a trigger_teleport // if this is the first frame we must guarantee // predictedPlayerState is valid even if there is some // other error condition if ( !cg.validPPS ) { cg.validPPS = qtrue; cg.predictedPlayerState = cg.snap->ps; } // demo playback just copies the moves if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) { CG_InterpolatePlayerState( qfalse ); return; } // non-predicting local movement will grab the latest angles if ( cg_nopredict.integer || cg_synchronousClients.integer ) { CG_InterpolatePlayerState( qtrue ); return; } // prepare for pmove cg_pmove.ps = &cg.predictedPlayerState; cg_pmove.trace = CG_Trace; cg_pmove.pointcontents = CG_PointContents; if ( cg_pmove.ps->pm_type == PM_DEAD ) { cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; } else { cg_pmove.tracemask = MASK_PLAYERSOLID; } if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies } cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; // save the state before the pmove so we can detect transitions oldPlayerState = cg.predictedPlayerState; current = trap_GetCurrentCmdNumber(); // if we don't have the commands right after the snapshot, we // can't accurately predict a current position, so just freeze at // the last good position we had cmdNum = current - CMD_BACKUP + 1; trap_GetUserCmd( cmdNum, &oldestCmd ); if ( oldestCmd.serverTime > cg.snap->ps.commandTime && oldestCmd.serverTime < cg.time ) { // special check for map_restart if ( cg_showmiss.integer ) { CG_Printf ("exceeded PACKET_BACKUP on commands\n"); } return; } // get the latest command so we can know which commands are from previous map_restarts trap_GetUserCmd( current, &latestCmd ); // get the most recent information we have, even if // the server time is beyond our current cg.time, // because predicted player positions are going to // be ahead of everything else anyway if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { cg.predictedPlayerState = cg.nextSnap->ps; cg.physicsTime = cg.nextSnap->serverTime; } else { cg.predictedPlayerState = cg.snap->ps; cg.physicsTime = cg.snap->serverTime; } if ( pmove_msec.integer < 8 ) { trap_Cvar_Set("pmove_msec", "8"); } else if (pmove_msec.integer > 33) { trap_Cvar_Set("pmove_msec", "33"); } cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; cg_pmove.pmove_msec = pmove_msec.integer; // run cmds moved = qfalse; for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { // get the command trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); if ( cg_pmove.pmove_fixed ) { PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); } // don't do anything if the time is before the snapshot player time if ( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) { continue; } // don't do anything if the command was from a previous map_restart if ( cg_pmove.cmd.serverTime > latestCmd.serverTime ) { continue; } // check for a prediction error from last frame // on a lan, this will often be the exact value // from the snapshot, but on a wan we will have // to predict several commands to get to the point // we want to compare if ( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime ) { vec3_t delta; float len; if ( cg.thisFrameTeleport ) { // a teleport will not cause an error decay VectorClear( cg.predictedError ); if ( cg_showmiss.integer ) { CG_Printf( "PredictionTeleport\n" ); } cg.thisFrameTeleport = qfalse; } else { vec3_t adjusted; CG_AdjustPositionForMover( cg.predictedPlayerState.origin, cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted ); if ( cg_showmiss.integer ) { if (!VectorCompare( oldPlayerState.origin, adjusted )) { CG_Printf("prediction error\n"); } } VectorSubtract( oldPlayerState.origin, adjusted, delta ); len = VectorLength( delta ); if ( len > 0.1 ) { if ( cg_showmiss.integer ) { CG_Printf("Prediction miss: %f\n", len); } if ( cg_errorDecay.integer ) { int t; float f; t = cg.time - cg.predictedErrorTime; f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; if ( f < 0 ) { f = 0; } if ( f > 0 && cg_showmiss.integer ) { CG_Printf("Double prediction decay: %f\n", f); } VectorScale( cg.predictedError, f, cg.predictedError ); } else { VectorClear( cg.predictedError ); } VectorAdd( delta, cg.predictedError, cg.predictedError ); cg.predictedErrorTime = cg.oldTime; } } } // don't predict gauntlet firing, which is only supposed to happen // when it actually inflicts damage cg_pmove.gauntletHit = qfalse; if ( cg_pmove.pmove_fixed ) { cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; } Pmove (&cg_pmove); moved = qtrue; // add push trigger movement effects CG_TouchTriggerPrediction(); // check for predictable events that changed from previous predictions //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState); } if ( cg_showmiss.integer > 1 ) { CG_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); } if ( !moved ) { if ( cg_showmiss.integer ) { CG_Printf( "not moved\n" ); } return; } // adjust for the movement of the groundentity CG_AdjustPositionForMover( cg.predictedPlayerState.origin, cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.time, cg.predictedPlayerState.origin ); if ( cg_showmiss.integer ) { if (cg.predictedPlayerState.eventSequence > oldPlayerState.eventSequence + MAX_PS_EVENTS) { CG_Printf("WARNING: dropped event\n"); } } // fire events and other transition triggered things CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState ); if ( cg_showmiss.integer ) { if (cg.eventSequence > cg.predictedPlayerState.eventSequence) { CG_Printf("WARNING: double event\n"); cg.eventSequence = cg.predictedPlayerState.eventSequence; } } } ================================================ FILE: code/cgame/cg_public.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // #define CMD_BACKUP 64 #define CMD_MASK (CMD_BACKUP - 1) // allow a lot of command backups for very fast systems // multiple commands may be combined into a single packet, so this // needs to be larger than PACKET_BACKUP #define MAX_ENTITIES_IN_SNAPSHOT 256 // snapshots are a view of the server at a given time // Snapshots are generated at regular time intervals by the server, // but they may not be sent if a client's rate level is exceeded, or // they may be dropped by the network. typedef struct { int snapFlags; // SNAPFLAG_RATE_DELAYED, etc int ping; int serverTime; // server time the message is valid for (in msec) byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits playerState_t ps; // complete information about the current player at this time int numEntities; // all of the entities that need to be presented entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot int numServerCommands; // text based server commands to execute when this int serverCommandSequence; // snapshot becomes current } snapshot_t; enum { CGAME_EVENT_NONE, CGAME_EVENT_TEAMMENU, CGAME_EVENT_SCOREBOARD, CGAME_EVENT_EDITHUD }; /* ================================================================== functions imported from the main executable ================================================================== */ #define CGAME_IMPORT_API_VERSION 4 typedef enum { CG_PRINT, CG_ERROR, CG_MILLISECONDS, CG_CVAR_REGISTER, CG_CVAR_UPDATE, CG_CVAR_SET, CG_CVAR_VARIABLESTRINGBUFFER, CG_ARGC, CG_ARGV, CG_ARGS, CG_FS_FOPENFILE, CG_FS_READ, CG_FS_WRITE, CG_FS_FCLOSEFILE, CG_SENDCONSOLECOMMAND, CG_ADDCOMMAND, CG_SENDCLIENTCOMMAND, CG_UPDATESCREEN, CG_CM_LOADMAP, CG_CM_NUMINLINEMODELS, CG_CM_INLINEMODEL, CG_CM_LOADMODEL, CG_CM_TEMPBOXMODEL, CG_CM_POINTCONTENTS, CG_CM_TRANSFORMEDPOINTCONTENTS, CG_CM_BOXTRACE, CG_CM_TRANSFORMEDBOXTRACE, CG_CM_MARKFRAGMENTS, CG_S_STARTSOUND, CG_S_STARTLOCALSOUND, CG_S_CLEARLOOPINGSOUNDS, CG_S_ADDLOOPINGSOUND, CG_S_UPDATEENTITYPOSITION, CG_S_RESPATIALIZE, CG_S_REGISTERSOUND, CG_S_STARTBACKGROUNDTRACK, CG_R_LOADWORLDMAP, CG_R_REGISTERMODEL, CG_R_REGISTERSKIN, CG_R_REGISTERSHADER, CG_R_CLEARSCENE, CG_R_ADDREFENTITYTOSCENE, CG_R_ADDPOLYTOSCENE, CG_R_ADDLIGHTTOSCENE, CG_R_RENDERSCENE, CG_R_SETCOLOR, CG_R_DRAWSTRETCHPIC, CG_R_MODELBOUNDS, CG_R_LERPTAG, CG_GETGLCONFIG, CG_GETGAMESTATE, CG_GETCURRENTSNAPSHOTNUMBER, CG_GETSNAPSHOT, CG_GETSERVERCOMMAND, CG_GETCURRENTCMDNUMBER, CG_GETUSERCMD, CG_SETUSERCMDVALUE, CG_R_REGISTERSHADERNOMIP, CG_MEMORY_REMAINING, CG_R_REGISTERFONT, CG_KEY_ISDOWN, CG_KEY_GETCATCHER, CG_KEY_SETCATCHER, CG_KEY_GETKEY, CG_PC_ADD_GLOBAL_DEFINE, CG_PC_LOAD_SOURCE, CG_PC_FREE_SOURCE, CG_PC_READ_TOKEN, CG_PC_SOURCE_FILE_AND_LINE, CG_S_STOPBACKGROUNDTRACK, CG_REAL_TIME, CG_SNAPVECTOR, CG_REMOVECOMMAND, CG_R_LIGHTFORPOINT, CG_CIN_PLAYCINEMATIC, CG_CIN_STOPCINEMATIC, CG_CIN_RUNCINEMATIC, CG_CIN_DRAWCINEMATIC, CG_CIN_SETEXTENTS, CG_R_REMAP_SHADER, CG_S_ADDREALLOOPINGSOUND, CG_S_STOPLOOPINGSOUND, CG_CM_TEMPCAPSULEMODEL, CG_CM_CAPSULETRACE, CG_CM_TRANSFORMEDCAPSULETRACE, CG_R_ADDADDITIVELIGHTTOSCENE, CG_GET_ENTITY_TOKEN, CG_R_ADDPOLYSTOSCENE, CG_R_INPVS, // 1.32 CG_FS_SEEK, /* CG_LOADCAMERA, CG_STARTCAMERA, CG_GETCAMERAINFO, */ CG_MEMSET = 100, CG_MEMCPY, CG_STRNCPY, CG_SIN, CG_COS, CG_ATAN2, CG_SQRT, CG_FLOOR, CG_CEIL, CG_TESTPRINTINT, CG_TESTPRINTFLOAT, CG_ACOS } cgameImport_t; /* ================================================================== functions exported to the main executable ================================================================== */ typedef enum { CG_INIT, // void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) // called when the level loads or when the renderer is restarted // all media should be registered at this time // cgame will display loading status by calling SCR_Update, which // will call CG_DrawInformation during the loading process // reliableCommandSequence will be 0 on fresh loads, but higher for // demos, tourney restarts, or vid_restarts CG_SHUTDOWN, // void (*CG_Shutdown)( void ); // oportunity to flush and close any open files CG_CONSOLE_COMMAND, // qboolean (*CG_ConsoleCommand)( void ); // a console command has been issued locally that is not recognized by the // main game system. // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the // command is not known to the game CG_DRAW_ACTIVE_FRAME, // void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); // Generates and draws a game scene and status information at the given time. // If demoPlayback is set, local movement prediction will not be enabled CG_CROSSHAIR_PLAYER, // int (*CG_CrosshairPlayer)( void ); CG_LAST_ATTACKER, // int (*CG_LastAttacker)( void ); CG_KEY_EVENT, // void (*CG_KeyEvent)( int key, qboolean down ); CG_MOUSE_EVENT, // void (*CG_MouseEvent)( int dx, int dy ); CG_EVENT_HANDLING // void (*CG_EventHandling)(int type); } cgameExport_t; //---------------------------------------------- ================================================ FILE: code/cgame/cg_scoreboard.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_scoreboard -- draw the scoreboard on top of the game screen #include "cg_local.h" #define SCOREBOARD_X (0) #define SB_HEADER 86 #define SB_TOP (SB_HEADER+32) // Where the status bar starts, so we don't overwrite it #define SB_STATUSBAR 420 #define SB_NORMAL_HEIGHT 40 #define SB_INTER_HEIGHT 16 // interleaved height #define SB_MAXCLIENTS_NORMAL ((SB_STATUSBAR - SB_TOP) / SB_NORMAL_HEIGHT) #define SB_MAXCLIENTS_INTER ((SB_STATUSBAR - SB_TOP) / SB_INTER_HEIGHT - 1) // Used when interleaved #define SB_LEFT_BOTICON_X (SCOREBOARD_X+0) #define SB_LEFT_HEAD_X (SCOREBOARD_X+32) #define SB_RIGHT_BOTICON_X (SCOREBOARD_X+64) #define SB_RIGHT_HEAD_X (SCOREBOARD_X+96) // Normal #define SB_BOTICON_X (SCOREBOARD_X+32) #define SB_HEAD_X (SCOREBOARD_X+64) #define SB_SCORELINE_X 112 #define SB_RATING_WIDTH (6 * BIGCHAR_WIDTH) // width 6 #define SB_SCORE_X (SB_SCORELINE_X + BIGCHAR_WIDTH) // width 6 #define SB_RATING_X (SB_SCORELINE_X + 6 * BIGCHAR_WIDTH) // width 6 #define SB_PING_X (SB_SCORELINE_X + 12 * BIGCHAR_WIDTH + 8) // width 5 #define SB_TIME_X (SB_SCORELINE_X + 17 * BIGCHAR_WIDTH + 8) // width 5 #define SB_NAME_X (SB_SCORELINE_X + 22 * BIGCHAR_WIDTH) // width 15 // The new and improved score board // // In cases where the number of clients is high, the score board heads are interleaved // here's the layout // // 0 32 80 112 144 240 320 400 <-- pixel position // bot head bot head score ping time name // // wins/losses are drawn on bot icon now static qboolean localClient; // true if local client has been displayed /* ================= CG_DrawScoreboard ================= */ static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, qboolean largeFormat ) { char string[1024]; vec3_t headAngles; clientInfo_t *ci; int iconx, headx; if ( score->client < 0 || score->client >= cgs.maxclients ) { Com_Printf( "Bad score->client: %i\n", score->client ); return; } ci = &cgs.clientinfo[score->client]; iconx = SB_BOTICON_X + (SB_RATING_WIDTH / 2); headx = SB_HEAD_X + (SB_RATING_WIDTH / 2); // draw the handicap or bot skill marker (unless player has flag) if ( ci->powerups & ( 1 << PW_NEUTRALFLAG ) ) { if( largeFormat ) { CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_FREE, qfalse ); } else { CG_DrawFlagModel( iconx, y, 16, 16, TEAM_FREE, qfalse ); } } else if ( ci->powerups & ( 1 << PW_REDFLAG ) ) { if( largeFormat ) { CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_RED, qfalse ); } else { CG_DrawFlagModel( iconx, y, 16, 16, TEAM_RED, qfalse ); } } else if ( ci->powerups & ( 1 << PW_BLUEFLAG ) ) { if( largeFormat ) { CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_BLUE, qfalse ); } else { CG_DrawFlagModel( iconx, y, 16, 16, TEAM_BLUE, qfalse ); } } else { if ( ci->botSkill > 0 && ci->botSkill <= 5 ) { if ( cg_drawIcons.integer ) { if( largeFormat ) { CG_DrawPic( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); } else { CG_DrawPic( iconx, y, 16, 16, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); } } } else if ( ci->handicap < 100 ) { Com_sprintf( string, sizeof( string ), "%i", ci->handicap ); if ( cgs.gametype == GT_TOURNAMENT ) CG_DrawSmallStringColor( iconx, y - SMALLCHAR_HEIGHT/2, string, color ); else CG_DrawSmallStringColor( iconx, y, string, color ); } // draw the wins / losses if ( cgs.gametype == GT_TOURNAMENT ) { Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses ); if( ci->handicap < 100 && !ci->botSkill ) { CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, string, color ); } else { CG_DrawSmallStringColor( iconx, y, string, color ); } } } // draw the face VectorClear( headAngles ); headAngles[YAW] = 180; if( largeFormat ) { CG_DrawHead( headx, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, score->client, headAngles ); } else { CG_DrawHead( headx, y, 16, 16, score->client, headAngles ); } #ifdef MISSIONPACK // draw the team task if ( ci->teamTask != TEAMTASK_NONE ) { if ( ci->teamTask == TEAMTASK_OFFENSE ) { CG_DrawPic( headx + 48, y, 16, 16, cgs.media.assaultShader ); } else if ( ci->teamTask == TEAMTASK_DEFENSE ) { CG_DrawPic( headx + 48, y, 16, 16, cgs.media.defendShader ); } } #endif // draw the score line if ( score->ping == -1 ) { Com_sprintf(string, sizeof(string), " connecting %s", ci->name); } else if ( ci->team == TEAM_SPECTATOR ) { Com_sprintf(string, sizeof(string), " SPECT %3i %4i %s", score->ping, score->time, ci->name); } else { Com_sprintf(string, sizeof(string), "%5i %4i %4i %s", score->score, score->ping, score->time, ci->name); } // highlight your position if ( score->client == cg.snap->ps.clientNum ) { float hcolor[4]; int rank; localClient = qtrue; if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || cgs.gametype >= GT_TEAM ) { rank = -1; } else { rank = cg.snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG; } if ( rank == 0 ) { hcolor[0] = 0; hcolor[1] = 0; hcolor[2] = 0.7f; } else if ( rank == 1 ) { hcolor[0] = 0.7f; hcolor[1] = 0; hcolor[2] = 0; } else if ( rank == 2 ) { hcolor[0] = 0.7f; hcolor[1] = 0.7f; hcolor[2] = 0; } else { hcolor[0] = 0.7f; hcolor[1] = 0.7f; hcolor[2] = 0.7f; } hcolor[3] = fade * 0.7; CG_FillRect( SB_SCORELINE_X + BIGCHAR_WIDTH + (SB_RATING_WIDTH / 2), y, 640 - SB_SCORELINE_X - BIGCHAR_WIDTH, BIGCHAR_HEIGHT+1, hcolor ); } CG_DrawBigString( SB_SCORELINE_X + (SB_RATING_WIDTH / 2), y, string, fade ); // add the "ready" marker for intermission exiting if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) { CG_DrawBigStringColor( iconx, y, "READY", color ); } } /* ================= CG_TeamScoreboard ================= */ static int CG_TeamScoreboard( int y, team_t team, float fade, int maxClients, int lineHeight ) { int i; score_t *score; float color[4]; int count; clientInfo_t *ci; color[0] = color[1] = color[2] = 1.0; color[3] = fade; count = 0; for ( i = 0 ; i < cg.numScores && count < maxClients ; i++ ) { score = &cg.scores[i]; ci = &cgs.clientinfo[ score->client ]; if ( team != ci->team ) { continue; } CG_DrawClientScore( y + lineHeight * count, score, color, fade, lineHeight == SB_NORMAL_HEIGHT ); count++; } return count; } /* ================= CG_DrawScoreboard Draw the normal in-game scoreboard ================= */ qboolean CG_DrawOldScoreboard( void ) { int x, y, w, i, n1, n2; float fade; float *fadeColor; char *s; int maxClients; int lineHeight; int topBorderSize, bottomBorderSize; // don't draw amuthing if the menu or console is up if ( cg_paused.integer ) { cg.deferredPlayerLoading = 0; return qfalse; } if ( cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { cg.deferredPlayerLoading = 0; return qfalse; } // don't draw scoreboard during death while warmup up if ( cg.warmup && !cg.showScores ) { return qfalse; } if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { fade = 1.0; fadeColor = colorWhite; } else { fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); if ( !fadeColor ) { // next time scoreboard comes up, don't print killer cg.deferredPlayerLoading = 0; cg.killerName[0] = 0; return qfalse; } fade = *fadeColor; } // fragged by ... line if ( cg.killerName[0] ) { s = va("Fragged by %s", cg.killerName ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; x = ( SCREEN_WIDTH - w ) / 2; y = 40; CG_DrawBigString( x, y, s, fade ); } // current rank if ( cgs.gametype < GT_TEAM) { if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { s = va("%s place with %i", CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), cg.snap->ps.persistant[PERS_SCORE] ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; x = ( SCREEN_WIDTH - w ) / 2; y = 60; CG_DrawBigString( x, y, s, fade ); } } else { if ( cg.teamScores[0] == cg.teamScores[1] ) { s = va("Teams are tied at %i", cg.teamScores[0] ); } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { s = va("Red leads %i to %i",cg.teamScores[0], cg.teamScores[1] ); } else { s = va("Blue leads %i to %i",cg.teamScores[1], cg.teamScores[0] ); } w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; x = ( SCREEN_WIDTH - w ) / 2; y = 60; CG_DrawBigString( x, y, s, fade ); } // scoreboard y = SB_HEADER; CG_DrawPic( SB_SCORE_X + (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardScore ); CG_DrawPic( SB_PING_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardPing ); CG_DrawPic( SB_TIME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardTime ); CG_DrawPic( SB_NAME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardName ); y = SB_TOP; // If there are more than SB_MAXCLIENTS_NORMAL, use the interleaved scores if ( cg.numScores > SB_MAXCLIENTS_NORMAL ) { maxClients = SB_MAXCLIENTS_INTER; lineHeight = SB_INTER_HEIGHT; topBorderSize = 8; bottomBorderSize = 16; } else { maxClients = SB_MAXCLIENTS_NORMAL; lineHeight = SB_NORMAL_HEIGHT; topBorderSize = 16; bottomBorderSize = 16; } localClient = qfalse; if ( cgs.gametype >= GT_TEAM ) { // // teamplay scoreboard // y += lineHeight/2; if ( cg.teamScores[0] >= cg.teamScores[1] ) { n1 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); y += (n1 * lineHeight) + BIGCHAR_HEIGHT; maxClients -= n1; n2 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); y += (n2 * lineHeight) + BIGCHAR_HEIGHT; maxClients -= n2; } else { n1 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); y += (n1 * lineHeight) + BIGCHAR_HEIGHT; maxClients -= n1; n2 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); y += (n2 * lineHeight) + BIGCHAR_HEIGHT; maxClients -= n2; } n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight ); y += (n1 * lineHeight) + BIGCHAR_HEIGHT; } else { // // free for all scoreboard // n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight ); y += (n1 * lineHeight) + BIGCHAR_HEIGHT; n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight ); y += (n2 * lineHeight) + BIGCHAR_HEIGHT; } if (!localClient) { // draw local client at the bottom for ( i = 0 ; i < cg.numScores ; i++ ) { if ( cg.scores[i].client == cg.snap->ps.clientNum ) { CG_DrawClientScore( y, &cg.scores[i], fadeColor, fade, lineHeight == SB_NORMAL_HEIGHT ); break; } } } // load any models that have been deferred if ( ++cg.deferredPlayerLoading > 10 ) { CG_LoadDeferredPlayers(); } return qtrue; } //================================================================================ /* ================ CG_CenterGiantLine ================ */ static void CG_CenterGiantLine( float y, const char *string ) { float x; vec4_t color; color[0] = 1; color[1] = 1; color[2] = 1; color[3] = 1; x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( string ) ); CG_DrawStringExt( x, y, string, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); } /* ================= CG_DrawTourneyScoreboard Draw the oversize scoreboard for tournements ================= */ void CG_DrawOldTourneyScoreboard( void ) { const char *s; vec4_t color; int min, tens, ones; clientInfo_t *ci; int y; int i; // request more scores regularly if ( cg.scoresRequestTime + 2000 < cg.time ) { cg.scoresRequestTime = cg.time; trap_SendClientCommand( "score" ); } color[0] = 1; color[1] = 1; color[2] = 1; color[3] = 1; // draw the dialog background color[0] = color[1] = color[2] = 0; color[3] = 1; CG_FillRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, color ); // print the mesage of the day s = CG_ConfigString( CS_MOTD ); if ( !s[0] ) { s = "Scoreboard"; } // print optional title CG_CenterGiantLine( 8, s ); // print server time ones = cg.time / 1000; min = ones / 60; ones %= 60; tens = ones / 10; ones %= 10; s = va("%i:%i%i", min, tens, ones ); CG_CenterGiantLine( 64, s ); // print the two scores y = 160; if ( cgs.gametype >= GT_TEAM ) { // // teamplay scoreboard // CG_DrawStringExt( 8, y, "Red Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); s = va("%i", cg.teamScores[0] ); CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); y += 64; CG_DrawStringExt( 8, y, "Blue Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); s = va("%i", cg.teamScores[1] ); CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); } else { // // free for all scoreboard // for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { ci = &cgs.clientinfo[i]; if ( !ci->infoValid ) { continue; } if ( ci->team != TEAM_FREE ) { continue; } CG_DrawStringExt( 8, y, ci->name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); s = va("%i", ci->score ); CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); y += 64; } } } ================================================ FILE: code/cgame/cg_servercmds.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_servercmds.c -- reliably sequenced text commands sent by the server // these are processed at snapshot transition time, so there will definately // be a valid snapshot this frame #include "cg_local.h" #include "../../ui/menudef.h" // bk001205 - for Q3_ui as well typedef struct { const char *order; int taskNum; } orderTask_t; static const orderTask_t validOrders[] = { { VOICECHAT_GETFLAG, TEAMTASK_OFFENSE }, { VOICECHAT_OFFENSE, TEAMTASK_OFFENSE }, { VOICECHAT_DEFEND, TEAMTASK_DEFENSE }, { VOICECHAT_DEFENDFLAG, TEAMTASK_DEFENSE }, { VOICECHAT_PATROL, TEAMTASK_PATROL }, { VOICECHAT_CAMP, TEAMTASK_CAMP }, { VOICECHAT_FOLLOWME, TEAMTASK_FOLLOW }, { VOICECHAT_RETURNFLAG, TEAMTASK_RETRIEVE }, { VOICECHAT_FOLLOWFLAGCARRIER, TEAMTASK_ESCORT } }; static const int numValidOrders = sizeof(validOrders) / sizeof(orderTask_t); #ifdef MISSIONPACK // bk001204 static int CG_ValidOrder(const char *p) { int i; for (i = 0; i < numValidOrders; i++) { if (Q_stricmp(p, validOrders[i].order) == 0) { return validOrders[i].taskNum; } } return -1; } #endif /* ================= CG_ParseScores ================= */ static void CG_ParseScores( void ) { int i, powerups; cg.numScores = atoi( CG_Argv( 1 ) ); if ( cg.numScores > MAX_CLIENTS ) { cg.numScores = MAX_CLIENTS; } cg.teamScores[0] = atoi( CG_Argv( 2 ) ); cg.teamScores[1] = atoi( CG_Argv( 3 ) ); memset( cg.scores, 0, sizeof( cg.scores ) ); for ( i = 0 ; i < cg.numScores ; i++ ) { // cg.scores[i].client = atoi( CG_Argv( i * 14 + 4 ) ); cg.scores[i].score = atoi( CG_Argv( i * 14 + 5 ) ); cg.scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) ); cg.scores[i].time = atoi( CG_Argv( i * 14 + 7 ) ); cg.scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) ); powerups = atoi( CG_Argv( i * 14 + 9 ) ); cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10)); cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11)); cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12)); cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13)); cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14)); cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15)); cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16)); cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17)); if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) { cg.scores[i].client = 0; } cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score; cgs.clientinfo[ cg.scores[i].client ].powerups = powerups; cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team; } #ifdef MISSIONPACK CG_SetScoreSelection(NULL); #endif } /* ================= CG_ParseTeamInfo ================= */ static void CG_ParseTeamInfo( void ) { int i; int client; numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) { client = atoi( CG_Argv( i * 6 + 2 ) ); sortedTeamPlayers[i] = client; cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) ); cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) ); cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) ); cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) ); cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) ); } } /* ================ CG_ParseServerinfo This is called explicitly when the gamestate is first received, and whenever the server updates any serverinfo flagged cvars ================ */ void CG_ParseServerinfo( void ) { const char *info; char *mapname; info = CG_ConfigString( CS_SERVERINFO ); cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) ); trap_Cvar_Set("g_gametype", va("%i", cgs.gametype)); cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) ); cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) ); cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) ); cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) ); cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); mapname = Info_ValueForKey( info, "mapname" ); Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) ); trap_Cvar_Set("g_redTeam", cgs.redTeam); Q_strncpyz( cgs.blueTeam, Info_ValueForKey( info, "g_blueTeam" ), sizeof(cgs.blueTeam) ); trap_Cvar_Set("g_blueTeam", cgs.blueTeam); } /* ================== CG_ParseWarmup ================== */ static void CG_ParseWarmup( void ) { const char *info; int warmup; info = CG_ConfigString( CS_WARMUP ); warmup = atoi( info ); cg.warmupCount = -1; if ( warmup == 0 && cg.warmup ) { } else if ( warmup > 0 && cg.warmup <= 0 ) { #ifdef MISSIONPACK if (cgs.gametype >= GT_CTF && cgs.gametype <= GT_HARVESTER) { trap_S_StartLocalSound( cgs.media.countPrepareTeamSound, CHAN_ANNOUNCER ); } else #endif { trap_S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER ); } } cg.warmup = warmup; } /* ================ CG_SetConfigValues Called on load to set the initial values from configure strings ================ */ void CG_SetConfigValues( void ) { const char *s; cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) ); cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) ); cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); if( cgs.gametype == GT_CTF ) { s = CG_ConfigString( CS_FLAGSTATUS ); cgs.redflag = s[0] - '0'; cgs.blueflag = s[1] - '0'; } #ifdef MISSIONPACK else if( cgs.gametype == GT_1FCTF ) { s = CG_ConfigString( CS_FLAGSTATUS ); cgs.flagStatus = s[0] - '0'; } #endif cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); } /* ===================== CG_ShaderStateChanged ===================== */ void CG_ShaderStateChanged(void) { char originalShader[MAX_QPATH]; char newShader[MAX_QPATH]; char timeOffset[16]; const char *o; char *n,*t; o = CG_ConfigString( CS_SHADERSTATE ); while (o && *o) { n = strstr(o, "="); if (n && *n) { strncpy(originalShader, o, n-o); originalShader[n-o] = 0; n++; t = strstr(n, ":"); if (t && *t) { strncpy(newShader, n, t-n); newShader[t-n] = 0; } else { break; } t++; o = strstr(t, "@"); if (o) { strncpy(timeOffset, t, o-t); timeOffset[o-t] = 0; o++; trap_R_RemapShader( originalShader, newShader, timeOffset ); } } else { break; } } } /* ================ CG_ConfigStringModified ================ */ static void CG_ConfigStringModified( void ) { const char *str; int num; num = atoi( CG_Argv( 1 ) ); // get the gamestate from the client system, which will have the // new configstring already integrated trap_GetGameState( &cgs.gameState ); // look up the individual string that was modified str = CG_ConfigString( num ); // do something with it if necessary if ( num == CS_MUSIC ) { CG_StartMusic(); } else if ( num == CS_SERVERINFO ) { CG_ParseServerinfo(); } else if ( num == CS_WARMUP ) { CG_ParseWarmup(); } else if ( num == CS_SCORES1 ) { cgs.scores1 = atoi( str ); } else if ( num == CS_SCORES2 ) { cgs.scores2 = atoi( str ); } else if ( num == CS_LEVEL_START_TIME ) { cgs.levelStartTime = atoi( str ); } else if ( num == CS_VOTE_TIME ) { cgs.voteTime = atoi( str ); cgs.voteModified = qtrue; } else if ( num == CS_VOTE_YES ) { cgs.voteYes = atoi( str ); cgs.voteModified = qtrue; } else if ( num == CS_VOTE_NO ) { cgs.voteNo = atoi( str ); cgs.voteModified = qtrue; } else if ( num == CS_VOTE_STRING ) { Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); #ifdef MISSIONPACK trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); #endif //MISSIONPACK } else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1) { cgs.teamVoteTime[num-CS_TEAMVOTE_TIME] = atoi( str ); cgs.teamVoteModified[num-CS_TEAMVOTE_TIME] = qtrue; } else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1) { cgs.teamVoteYes[num-CS_TEAMVOTE_YES] = atoi( str ); cgs.teamVoteModified[num-CS_TEAMVOTE_YES] = qtrue; } else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1) { cgs.teamVoteNo[num-CS_TEAMVOTE_NO] = atoi( str ); cgs.teamVoteModified[num-CS_TEAMVOTE_NO] = qtrue; } else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1) { Q_strncpyz( cgs.teamVoteString[num-CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) ); #ifdef MISSIONPACK trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); #endif } else if ( num == CS_INTERMISSION ) { cg.intermissionStarted = atoi( str ); } else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) { cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( str ); } else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_MODELS ) { if ( str[0] != '*' ) { // player specific sounds don't register here cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str, qfalse ); } } else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) { CG_NewClientInfo( num - CS_PLAYERS ); CG_BuildSpectatorString(); } else if ( num == CS_FLAGSTATUS ) { if( cgs.gametype == GT_CTF ) { // format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped cgs.redflag = str[0] - '0'; cgs.blueflag = str[1] - '0'; } #ifdef MISSIONPACK else if( cgs.gametype == GT_1FCTF ) { cgs.flagStatus = str[0] - '0'; } #endif } else if ( num == CS_SHADERSTATE ) { CG_ShaderStateChanged(); } } /* ======================= CG_AddToTeamChat ======================= */ static void CG_AddToTeamChat( const char *str ) { int len; char *p, *ls; int lastcolor; int chatHeight; if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) { chatHeight = cg_teamChatHeight.integer; } else { chatHeight = TEAMCHAT_HEIGHT; } if (chatHeight <= 0 || cg_teamChatTime.integer <= 0) { // team chat disabled, dump into normal chat cgs.teamChatPos = cgs.teamLastChatPos = 0; return; } len = 0; p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; *p = 0; lastcolor = '7'; ls = NULL; while (*str) { if (len > TEAMCHAT_WIDTH - 1) { if (ls) { str -= (p - ls); str++; p -= (p - ls); } *p = 0; cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; cgs.teamChatPos++; p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; *p = 0; *p++ = Q_COLOR_ESCAPE; *p++ = lastcolor; len = 0; ls = NULL; } if ( Q_IsColorString( str ) ) { *p++ = *str++; lastcolor = *str; *p++ = *str++; continue; } if (*str == ' ') { ls = p; } *p++ = *str++; len++; } *p = 0; cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; cgs.teamChatPos++; if (cgs.teamChatPos - cgs.teamLastChatPos > chatHeight) cgs.teamLastChatPos = cgs.teamChatPos - chatHeight; } /* =============== CG_MapRestart The server has issued a map_restart, so the next snapshot is completely new and should not be interpolated to. A tournement restart will clear everything, but doesn't require a reload of all the media =============== */ static void CG_MapRestart( void ) { if ( cg_showmiss.integer ) { CG_Printf( "CG_MapRestart\n" ); } CG_InitLocalEntities(); CG_InitMarkPolys(); CG_ClearParticles (); // make sure the "3 frags left" warnings play again cg.fraglimitWarnings = 0; cg.timelimitWarnings = 0; cg.intermissionStarted = qfalse; cgs.voteTime = 0; cg.mapRestart = qtrue; CG_StartMusic(); trap_S_ClearLoopingSounds(qtrue); // we really should clear more parts of cg here and stop sounds // play the "fight" sound if this is a restart without warmup if ( cg.warmup == 0 /* && cgs.gametype == GT_TOURNAMENT */) { trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH*2 ); } #ifdef MISSIONPACK if (cg_singlePlayerActive.integer) { trap_Cvar_Set("ui_matchStartTime", va("%i", cg.time)); if (cg_recordSPDemo.integer && cg_recordSPDemoName.string && *cg_recordSPDemoName.string) { trap_SendConsoleCommand(va("set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string)); } } #endif trap_Cvar_Set("cg_thirdPerson", "0"); } #define MAX_VOICEFILESIZE 16384 #define MAX_VOICEFILES 8 #define MAX_VOICECHATS 64 #define MAX_VOICESOUNDS 64 #define MAX_CHATSIZE 64 #define MAX_HEADMODELS 64 typedef struct voiceChat_s { char id[64]; int numSounds; sfxHandle_t sounds[MAX_VOICESOUNDS]; char chats[MAX_VOICESOUNDS][MAX_CHATSIZE]; } voiceChat_t; typedef struct voiceChatList_s { char name[64]; int gender; int numVoiceChats; voiceChat_t voiceChats[MAX_VOICECHATS]; } voiceChatList_t; typedef struct headModelVoiceChat_s { char headmodel[64]; int voiceChatNum; } headModelVoiceChat_t; voiceChatList_t voiceChatLists[MAX_VOICEFILES]; headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS]; /* ================= CG_ParseVoiceChats ================= */ int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats ) { int len, i; fileHandle_t f; char buf[MAX_VOICEFILESIZE]; char **p, *ptr; char *token; voiceChat_t *voiceChats; qboolean compress; sfxHandle_t sound; compress = qtrue; if (cg_buildScript.integer) { compress = qfalse; } len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( !f ) { trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) ); return qfalse; } if ( len >= MAX_VOICEFILESIZE ) { trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); trap_FS_FCloseFile( f ); return qfalse; } trap_FS_Read( buf, len, f ); buf[len] = 0; trap_FS_FCloseFile( f ); ptr = buf; p = &ptr; Com_sprintf(voiceChatList->name, sizeof(voiceChatList->name), "%s", filename); voiceChats = voiceChatList->voiceChats; for ( i = 0; i < maxVoiceChats; i++ ) { voiceChats[i].id[0] = 0; } token = COM_ParseExt(p, qtrue); if (!token || token[0] == 0) { return qtrue; } if (!Q_stricmp(token, "female")) { voiceChatList->gender = GENDER_FEMALE; } else if (!Q_stricmp(token, "male")) { voiceChatList->gender = GENDER_MALE; } else if (!Q_stricmp(token, "neuter")) { voiceChatList->gender = GENDER_NEUTER; } else { trap_Print( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) ); return qfalse; } voiceChatList->numVoiceChats = 0; while ( 1 ) { token = COM_ParseExt(p, qtrue); if (!token || token[0] == 0) { return qtrue; } Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token); token = COM_ParseExt(p, qtrue); if (Q_stricmp(token, "{")) { trap_Print( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) ); return qfalse; } voiceChats[voiceChatList->numVoiceChats].numSounds = 0; while(1) { token = COM_ParseExt(p, qtrue); if (!token || token[0] == 0) { return qtrue; } if (!Q_stricmp(token, "}")) break; sound = trap_S_RegisterSound( token, compress ); voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = sound; token = COM_ParseExt(p, qtrue); if (!token || token[0] == 0) { return qtrue; } Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[ voiceChats[voiceChatList->numVoiceChats].numSounds], MAX_CHATSIZE, "%s", token); if (sound) voiceChats[voiceChatList->numVoiceChats].numSounds++; if (voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS) break; } voiceChatList->numVoiceChats++; if (voiceChatList->numVoiceChats >= maxVoiceChats) return qtrue; } return qtrue; } /* ================= CG_LoadVoiceChats ================= */ void CG_LoadVoiceChats( void ) { int size; size = trap_MemoryRemaining(); CG_ParseVoiceChats( "scripts/female1.voice", &voiceChatLists[0], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/female2.voice", &voiceChatLists[1], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/female3.voice", &voiceChatLists[2], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[3], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/male2.voice", &voiceChatLists[4], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/male3.voice", &voiceChatLists[5], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/male4.voice", &voiceChatLists[6], MAX_VOICECHATS ); CG_ParseVoiceChats( "scripts/male5.voice", &voiceChatLists[7], MAX_VOICECHATS ); CG_Printf("voice chat memory size = %d\n", size - trap_MemoryRemaining()); } /* ================= CG_HeadModelVoiceChats ================= */ int CG_HeadModelVoiceChats( char *filename ) { int len, i; fileHandle_t f; char buf[MAX_VOICEFILESIZE]; char **p, *ptr; char *token; len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( !f ) { //trap_Print( va( "voice chat file not found: %s\n", filename ) ); return -1; } if ( len >= MAX_VOICEFILESIZE ) { trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); trap_FS_FCloseFile( f ); return -1; } trap_FS_Read( buf, len, f ); buf[len] = 0; trap_FS_FCloseFile( f ); ptr = buf; p = &ptr; token = COM_ParseExt(p, qtrue); if (!token || token[0] == 0) { return -1; } for ( i = 0; i < MAX_VOICEFILES; i++ ) { if ( !Q_stricmp(token, voiceChatLists[i].name) ) { return i; } } //FIXME: maybe try to load the .voice file which name is stored in token? return -1; } /* ================= CG_GetVoiceChat ================= */ int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, char **chat) { int i, rnd; for ( i = 0; i < voiceChatList->numVoiceChats; i++ ) { if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) ) { rnd = random() * voiceChatList->voiceChats[i].numSounds; *snd = voiceChatList->voiceChats[i].sounds[rnd]; *chat = voiceChatList->voiceChats[i].chats[rnd]; return qtrue; } } return qfalse; } /* ================= CG_VoiceChatListForClient ================= */ voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) { clientInfo_t *ci; int voiceChatNum, i, j, k, gender; char filename[MAX_QPATH], headModelName[MAX_QPATH]; if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { clientNum = 0; } ci = &cgs.clientinfo[ clientNum ]; for ( k = 0; k < 2; k++ ) { if ( k == 0 ) { if (ci->headModelName[0] == '*') { Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName+1, ci->headSkinName ); } else { Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName, ci->headSkinName ); } } else { if (ci->headModelName[0] == '*') { Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName+1 ); } else { Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName ); } } // find the voice file for the head model the client uses for ( i = 0; i < MAX_HEADMODELS; i++ ) { if (!Q_stricmp(headModelVoiceChat[i].headmodel, headModelName)) { break; } } if (i < MAX_HEADMODELS) { return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; } // find a .vc file for ( i = 0; i < MAX_HEADMODELS; i++ ) { if (!strlen(headModelVoiceChat[i].headmodel)) { Com_sprintf(filename, sizeof(filename), "scripts/%s.vc", headModelName); voiceChatNum = CG_HeadModelVoiceChats(filename); if (voiceChatNum == -1) break; Com_sprintf(headModelVoiceChat[i].headmodel, sizeof ( headModelVoiceChat[i].headmodel ), "%s", headModelName); headModelVoiceChat[i].voiceChatNum = voiceChatNum; return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; } } } gender = ci->gender; for (k = 0; k < 2; k++) { // just pick the first with the right gender for ( i = 0; i < MAX_VOICEFILES; i++ ) { if (strlen(voiceChatLists[i].name)) { if (voiceChatLists[i].gender == gender) { // store this head model with voice chat for future reference for ( j = 0; j < MAX_HEADMODELS; j++ ) { if (!strlen(headModelVoiceChat[j].headmodel)) { Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), "%s", headModelName); headModelVoiceChat[j].voiceChatNum = i; break; } } return &voiceChatLists[i]; } } } // fall back to male gender because we don't have neuter in the mission pack if (gender == GENDER_MALE) break; gender = GENDER_MALE; } // store this head model with voice chat for future reference for ( j = 0; j < MAX_HEADMODELS; j++ ) { if (!strlen(headModelVoiceChat[j].headmodel)) { Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), "%s", headModelName); headModelVoiceChat[j].voiceChatNum = 0; break; } } // just return the first voice chat list return &voiceChatLists[0]; } #define MAX_VOICECHATBUFFER 32 typedef struct bufferedVoiceChat_s { int clientNum; sfxHandle_t snd; int voiceOnly; char cmd[MAX_SAY_TEXT]; char message[MAX_SAY_TEXT]; } bufferedVoiceChat_t; bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER]; /* ================= CG_PlayVoiceChat ================= */ void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat ) { #ifdef MISSIONPACK // if we are going into the intermission, don't start any voices if ( cg.intermissionStarted ) { return; } if ( !cg_noVoiceChats.integer ) { trap_S_StartLocalSound( vchat->snd, CHAN_VOICE); if (vchat->clientNum != cg.snap->ps.clientNum) { int orderTask = CG_ValidOrder(vchat->cmd); if (orderTask > 0) { cgs.acceptOrderTime = cg.time + 5000; Q_strncpyz(cgs.acceptVoice, vchat->cmd, sizeof(cgs.acceptVoice)); cgs.acceptTask = orderTask; cgs.acceptLeader = vchat->clientNum; } // see if this was an order CG_ShowResponseHead(); } } if (!vchat->voiceOnly && !cg_noVoiceText.integer) { CG_AddToTeamChat( vchat->message ); CG_Printf( "%s\n", vchat->message ); } voiceChatBuffer[cg.voiceChatBufferOut].snd = 0; #endif } /* ===================== CG_PlayBufferedVoieChats ===================== */ void CG_PlayBufferedVoiceChats( void ) { #ifdef MISSIONPACK if ( cg.voiceChatTime < cg.time ) { if (cg.voiceChatBufferOut != cg.voiceChatBufferIn && voiceChatBuffer[cg.voiceChatBufferOut].snd) { // CG_PlayVoiceChat(&voiceChatBuffer[cg.voiceChatBufferOut]); // cg.voiceChatBufferOut = (cg.voiceChatBufferOut + 1) % MAX_VOICECHATBUFFER; cg.voiceChatTime = cg.time + 1000; } } #endif } /* ===================== CG_AddBufferedVoiceChat ===================== */ void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat ) { #ifdef MISSIONPACK // if we are going into the intermission, don't start any voices if ( cg.intermissionStarted ) { return; } memcpy(&voiceChatBuffer[cg.voiceChatBufferIn], vchat, sizeof(bufferedVoiceChat_t)); cg.voiceChatBufferIn = (cg.voiceChatBufferIn + 1) % MAX_VOICECHATBUFFER; if (cg.voiceChatBufferIn == cg.voiceChatBufferOut) { CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] ); cg.voiceChatBufferOut++; } #endif } /* ================= CG_VoiceChatLocal ================= */ void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ) { #ifdef MISSIONPACK char *chat; voiceChatList_t *voiceChatList; clientInfo_t *ci; sfxHandle_t snd; bufferedVoiceChat_t vchat; // if we are going into the intermission, don't start any voices if ( cg.intermissionStarted ) { return; } if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { clientNum = 0; } ci = &cgs.clientinfo[ clientNum ]; cgs.currentVoiceClient = clientNum; voiceChatList = CG_VoiceChatListForClient( clientNum ); if ( CG_GetVoiceChat( voiceChatList, cmd, &snd, &chat ) ) { // if ( mode == SAY_TEAM || !cg_teamChatsOnly.integer ) { vchat.clientNum = clientNum; vchat.snd = snd; vchat.voiceOnly = voiceOnly; Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd)); if ( mode == SAY_TELL ) { Com_sprintf(vchat.message, sizeof(vchat.message), "[%s]: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); } else if ( mode == SAY_TEAM ) { Com_sprintf(vchat.message, sizeof(vchat.message), "(%s): %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); } else { Com_sprintf(vchat.message, sizeof(vchat.message), "%s: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); } CG_AddBufferedVoiceChat(&vchat); } } #endif } /* ================= CG_VoiceChat ================= */ void CG_VoiceChat( int mode ) { #ifdef MISSIONPACK const char *cmd; int clientNum, color; qboolean voiceOnly; voiceOnly = atoi(CG_Argv(1)); clientNum = atoi(CG_Argv(2)); color = atoi(CG_Argv(3)); cmd = CG_Argv(4); if (cg_noTaunt.integer != 0) { if (!strcmp(cmd, VOICECHAT_KILLINSULT) || !strcmp(cmd, VOICECHAT_TAUNT) || \ !strcmp(cmd, VOICECHAT_DEATHINSULT) || !strcmp(cmd, VOICECHAT_KILLGAUNTLET) || \ !strcmp(cmd, VOICECHAT_PRAISE)) { return; } } CG_VoiceChatLocal( mode, voiceOnly, clientNum, color, cmd ); #endif } /* ================= CG_RemoveChatEscapeChar ================= */ static void CG_RemoveChatEscapeChar( char *text ) { int i, l; l = 0; for ( i = 0; text[i]; i++ ) { if (text[i] == '\x19') continue; text[l++] = text[i]; } text[l] = '\0'; } /* ================= CG_ServerCommand The string has been tokenized and can be retrieved with Cmd_Argc() / Cmd_Argv() ================= */ static void CG_ServerCommand( void ) { const char *cmd; char text[MAX_SAY_TEXT]; cmd = CG_Argv(0); if ( !cmd[0] ) { // server claimed the command return; } if ( !strcmp( cmd, "cp" ) ) { CG_CenterPrint( CG_Argv(1), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); return; } if ( !strcmp( cmd, "cs" ) ) { CG_ConfigStringModified(); return; } if ( !strcmp( cmd, "print" ) ) { CG_Printf( "%s", CG_Argv(1) ); #ifdef MISSIONPACK cmd = CG_Argv(1); // yes, this is obviously a hack, but so is the way we hear about // votes passing or failing if ( !Q_stricmpn( cmd, "vote failed", 11 ) || !Q_stricmpn( cmd, "team vote failed", 16 )) { trap_S_StartLocalSound( cgs.media.voteFailed, CHAN_ANNOUNCER ); } else if ( !Q_stricmpn( cmd, "vote passed", 11 ) || !Q_stricmpn( cmd, "team vote passed", 16 ) ) { trap_S_StartLocalSound( cgs.media.votePassed, CHAN_ANNOUNCER ); } #endif return; } if ( !strcmp( cmd, "chat" ) ) { if ( !cg_teamChatsOnly.integer ) { trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); CG_RemoveChatEscapeChar( text ); CG_Printf( "%s\n", text ); } return; } if ( !strcmp( cmd, "tchat" ) ) { trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); CG_RemoveChatEscapeChar( text ); CG_AddToTeamChat( text ); CG_Printf( "%s\n", text ); return; } if ( !strcmp( cmd, "vchat" ) ) { CG_VoiceChat( SAY_ALL ); return; } if ( !strcmp( cmd, "vtchat" ) ) { CG_VoiceChat( SAY_TEAM ); return; } if ( !strcmp( cmd, "vtell" ) ) { CG_VoiceChat( SAY_TELL ); return; } if ( !strcmp( cmd, "scores" ) ) { CG_ParseScores(); return; } if ( !strcmp( cmd, "tinfo" ) ) { CG_ParseTeamInfo(); return; } if ( !strcmp( cmd, "map_restart" ) ) { CG_MapRestart(); return; } if ( Q_stricmp (cmd, "remapShader") == 0 ) { if (trap_Argc() == 4) { trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3)); } } // loaddeferred can be both a servercmd and a consolecmd if ( !strcmp( cmd, "loaddefered" ) ) { // FIXME: spelled wrong, but not changing for demo CG_LoadDeferredPlayers(); return; } // clientLevelShot is sent before taking a special screenshot for // the menu system during development if ( !strcmp( cmd, "clientLevelShot" ) ) { cg.levelShot = qtrue; return; } CG_Printf( "Unknown client game command: %s\n", cmd ); } /* ==================== CG_ExecuteNewServerCommands Execute all of the server commands that were received along with this this snapshot. ==================== */ void CG_ExecuteNewServerCommands( int latestSequence ) { while ( cgs.serverCommandSequence < latestSequence ) { if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) { CG_ServerCommand(); } } } ================================================ FILE: code/cgame/cg_snapshot.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_snapshot.c -- things that happen on snapshot transition, // not necessarily every single rendered frame #include "cg_local.h" /* ================== CG_ResetEntity ================== */ static void CG_ResetEntity( centity_t *cent ) { // if the previous snapshot this entity was updated in is at least // an event window back in time then we can reset the previous event if ( cent->snapShotTime < cg.time - EVENT_VALID_MSEC ) { cent->previousEvent = 0; } cent->trailTime = cg.snap->serverTime; VectorCopy (cent->currentState.origin, cent->lerpOrigin); VectorCopy (cent->currentState.angles, cent->lerpAngles); if ( cent->currentState.eType == ET_PLAYER ) { CG_ResetPlayerEntity( cent ); } } /* =============== CG_TransitionEntity cent->nextState is moved to cent->currentState and events are fired =============== */ static void CG_TransitionEntity( centity_t *cent ) { cent->currentState = cent->nextState; cent->currentValid = qtrue; // reset if the entity wasn't in the last frame or was teleported if ( !cent->interpolate ) { CG_ResetEntity( cent ); } // clear the next state. if will be set by the next CG_SetNextSnap cent->interpolate = qfalse; // check for events CG_CheckEvents( cent ); } /* ================== CG_SetInitialSnapshot This will only happen on the very first snapshot, or on tourney restarts. All other times will use CG_TransitionSnapshot instead. FIXME: Also called by map_restart? ================== */ void CG_SetInitialSnapshot( snapshot_t *snap ) { int i; centity_t *cent; entityState_t *state; cg.snap = snap; BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); // sort out solid entities CG_BuildSolidList(); CG_ExecuteNewServerCommands( snap->serverCommandSequence ); // set our local weapon selection pointer to // what the server has indicated the current weapon is CG_Respawn(); for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { state = &cg.snap->entities[ i ]; cent = &cg_entities[ state->number ]; memcpy(¢->currentState, state, sizeof(entityState_t)); //cent->currentState = *state; cent->interpolate = qfalse; cent->currentValid = qtrue; CG_ResetEntity( cent ); // check for events CG_CheckEvents( cent ); } } /* =================== CG_TransitionSnapshot The transition point from snap to nextSnap has passed =================== */ static void CG_TransitionSnapshot( void ) { centity_t *cent; snapshot_t *oldFrame; int i; if ( !cg.snap ) { CG_Error( "CG_TransitionSnapshot: NULL cg.snap" ); } if ( !cg.nextSnap ) { CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" ); } // execute any server string commands before transitioning entities CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); // if we had a map_restart, set everthing with initial if ( !cg.snap ) { } // clear the currentValid flag for all entities in the existing snapshot for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { cent = &cg_entities[ cg.snap->entities[ i ].number ]; cent->currentValid = qfalse; } // move nextSnap to snap and do the transitions oldFrame = cg.snap; cg.snap = cg.nextSnap; BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse ); cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse; for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { cent = &cg_entities[ cg.snap->entities[ i ].number ]; CG_TransitionEntity( cent ); // remember time of snapshot this entity was last updated in cent->snapShotTime = cg.snap->serverTime; } cg.nextSnap = NULL; // check for playerstate transition events if ( oldFrame ) { playerState_t *ops, *ps; ops = &oldFrame->ps; ps = &cg.snap->ps; // teleporting checks are irrespective of prediction if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) { cg.thisFrameTeleport = qtrue; // will be cleared by prediction code } // if we are not doing client side movement prediction for any // reason, then the client events and view changes will be issued now if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) || cg_nopredict.integer || cg_synchronousClients.integer ) { CG_TransitionPlayerState( ps, ops ); } } } /* =================== CG_SetNextSnap A new snapshot has just been read in from the client system. =================== */ static void CG_SetNextSnap( snapshot_t *snap ) { int num; entityState_t *es; centity_t *cent; cg.nextSnap = snap; BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; // check for extrapolation errors for ( num = 0 ; num < snap->numEntities ; num++ ) { es = &snap->entities[num]; cent = &cg_entities[ es->number ]; memcpy(¢->nextState, es, sizeof(entityState_t)); //cent->nextState = *es; // if this frame is a teleport, or the entity wasn't in the // previous frame, don't interpolate if ( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) { cent->interpolate = qfalse; } else { cent->interpolate = qtrue; } } // if the next frame is a teleport for the playerstate, we // can't interpolate during demos if ( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) { cg.nextFrameTeleport = qtrue; } else { cg.nextFrameTeleport = qfalse; } // if changing follow mode, don't interpolate if ( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) { cg.nextFrameTeleport = qtrue; } // if changing server restarts, don't interpolate if ( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) { cg.nextFrameTeleport = qtrue; } // sort out solid entities CG_BuildSolidList(); } /* ======================== CG_ReadNextSnapshot This is the only place new snapshots are requested This may increment cgs.processedSnapshotNum multiple times if the client system fails to return a valid snapshot. ======================== */ static snapshot_t *CG_ReadNextSnapshot( void ) { qboolean r; snapshot_t *dest; if ( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", cg.latestSnapshotNum, cgs.processedSnapshotNum ); } while ( cgs.processedSnapshotNum < cg.latestSnapshotNum ) { // decide which of the two slots to load it into if ( cg.snap == &cg.activeSnapshots[0] ) { dest = &cg.activeSnapshots[1]; } else { dest = &cg.activeSnapshots[0]; } // try to read the snapshot from the client system cgs.processedSnapshotNum++; r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); // FIXME: why would trap_GetSnapshot return a snapshot with the same server time if ( cg.snap && r && dest->serverTime == cg.snap->serverTime ) { //continue; } // if it succeeded, return if ( r ) { CG_AddLagometerSnapshotInfo( dest ); return dest; } // a GetSnapshot will return failure if the snapshot // never arrived, or is so old that its entities // have been shoved off the end of the circular // buffer in the client system. // record as a dropped packet CG_AddLagometerSnapshotInfo( NULL ); // If there are additional snapshots, continue trying to // read them. } // nothing left to read return NULL; } /* ============ CG_ProcessSnapshots We are trying to set up a renderable view, so determine what the simulated time is, and try to get snapshots both before and after that time if available. If we don't have a valid cg.snap after exiting this function, then a 3D game view cannot be rendered. This should only happen right after the initial connection. After cg.snap has been valid once, it will never turn invalid. Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot hasn't arrived yet (it becomes an extrapolating situation instead of an interpolating one) ============ */ void CG_ProcessSnapshots( void ) { snapshot_t *snap; int n; // see what the latest snapshot the client system has is trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); if ( n != cg.latestSnapshotNum ) { if ( n < cg.latestSnapshotNum ) { // this should never happen CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); } cg.latestSnapshotNum = n; } // If we have yet to receive a snapshot, check for it. // Once we have gotten the first snapshot, cg.snap will // always have valid data for the rest of the game while ( !cg.snap ) { snap = CG_ReadNextSnapshot(); if ( !snap ) { // we can't continue until we get a snapshot return; } // set our weapon selection to what // the playerstate is currently using if ( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { CG_SetInitialSnapshot( snap ); } } // loop until we either have a valid nextSnap with a serverTime // greater than cg.time to interpolate towards, or we run // out of available snapshots do { // if we don't have a nextframe, try and read a new one in if ( !cg.nextSnap ) { snap = CG_ReadNextSnapshot(); // if we still don't have a nextframe, we will just have to // extrapolate if ( !snap ) { break; } CG_SetNextSnap( snap ); // if time went backwards, we have a level restart if ( cg.nextSnap->serverTime < cg.snap->serverTime ) { CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); } } // if our time is < nextFrame's, we have a nice interpolating state if ( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) { break; } // we have passed the transition from nextFrame to frame CG_TransitionSnapshot(); } while ( 1 ); // assert our valid conditions upon exiting if ( cg.snap == NULL ) { CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" ); } if ( cg.time < cg.snap->serverTime ) { // this can happen right after a vid_restart cg.time = cg.snap->serverTime; } if ( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) { CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); } } ================================================ FILE: code/cgame/cg_syscalls.asm ================================================ code equ trap_Print -1 equ trap_Error -2 equ trap_Milliseconds -3 equ trap_Cvar_Register -4 equ trap_Cvar_Update -5 equ trap_Cvar_Set -6 equ trap_Cvar_VariableStringBuffer -7 equ trap_Argc -8 equ trap_Argv -9 equ trap_Args -10 equ trap_FS_FOpenFile -11 equ trap_FS_Read -12 equ trap_FS_Write -13 equ trap_FS_FCloseFile -14 equ trap_SendConsoleCommand -15 equ trap_AddCommand -16 equ trap_SendClientCommand -17 equ trap_UpdateScreen -18 equ trap_CM_LoadMap -19 equ trap_CM_NumInlineModels -20 equ trap_CM_InlineModel -21 equ trap_CM_LoadModel -22 equ trap_CM_TempBoxModel -23 equ trap_CM_PointContents -24 equ trap_CM_TransformedPointContents -25 equ trap_CM_BoxTrace -26 equ trap_CM_TransformedBoxTrace -27 equ trap_CM_MarkFragments -28 equ trap_S_StartSound -29 equ trap_S_StartLocalSound -30 equ trap_S_ClearLoopingSounds -31 equ trap_S_AddLoopingSound -32 equ trap_S_UpdateEntityPosition -33 equ trap_S_Respatialize -34 equ trap_S_RegisterSound -35 equ trap_S_StartBackgroundTrack -36 equ trap_R_LoadWorldMap -37 equ trap_R_RegisterModel -38 equ trap_R_RegisterSkin -39 equ trap_R_RegisterShader -40 equ trap_R_ClearScene -41 equ trap_R_AddRefEntityToScene -42 equ trap_R_AddPolyToScene -43 equ trap_R_AddLightToScene -44 equ trap_R_RenderScene -45 equ trap_R_SetColor -46 equ trap_R_DrawStretchPic -47 equ trap_R_ModelBounds -48 equ trap_R_LerpTag -49 equ trap_GetGlconfig -50 equ trap_GetGameState -51 equ trap_GetCurrentSnapshotNumber -52 equ trap_GetSnapshot -53 equ trap_GetServerCommand -54 equ trap_GetCurrentCmdNumber -55 equ trap_GetUserCmd -56 equ trap_SetUserCmdValue -57 equ trap_R_RegisterShaderNoMip -58 equ trap_MemoryRemaining -59 equ trap_R_RegisterFont -60 equ trap_Key_IsDown -61 equ trap_Key_GetCatcher -62 equ trap_Key_SetCatcher -63 equ trap_Key_GetKey -64 equ trap_PC_AddGlobalDefine -65 equ trap_PC_LoadSource -66 equ trap_PC_FreeSource -67 equ trap_PC_ReadToken -68 equ trap_PC_SourceFileAndLine -69 equ trap_S_StopBackgroundTrack -70 equ trap_RealTime -71 equ trap_SnapVector -72 equ trap_RemoveCommand -73 equ trap_R_LightForPoint -74 equ trap_CIN_PlayCinematic -75 equ trap_CIN_StopCinematic -76 equ trap_CIN_RunCinematic -77 equ trap_CIN_DrawCinematic -78 equ trap_CIN_SetExtents -79 equ trap_R_RemapShader -80 equ trap_S_AddRealLoopingSound -81 equ trap_S_StopLoopingSound -82 equ trap_CM_TempCapsuleModel -83 equ trap_CM_CapsuleTrace -84 equ trap_CM_TransformedCapsuleTrace -85 equ trap_R_AddAdditiveLightToScene -86 equ trap_GetEntityToken -87 equ trap_R_AddPolysToScene -88 equ trap_R_inPVS -89 equ trap_FS_Seek -90 equ memset -101 equ memcpy -102 equ strncpy -103 equ sin -104 equ cos -105 equ atan2 -106 equ sqrt -107 equ floor -108 equ ceil -109 equ testPrintInt -110 equ testPrintFloat -111 equ acos -112 ================================================ FILE: code/cgame/cg_syscalls.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_syscalls.c -- this file is only included when building a dll // cg_syscalls.asm is included instead when building a qvm #ifdef Q3_VM #error "Do not use in VM build" #endif #include "cg_local.h" static int (QDECL *syscall)( int arg, ... ) = (int (QDECL *)( int, ...))-1; void dllEntry( int (QDECL *syscallptr)( int arg,... ) ) { syscall = syscallptr; } int PASSFLOAT( float x ) { float floatTemp; floatTemp = x; return *(int *)&floatTemp; } void trap_Print( const char *fmt ) { syscall( CG_PRINT, fmt ); } void trap_Error( const char *fmt ) { syscall( CG_ERROR, fmt ); } int trap_Milliseconds( void ) { return syscall( CG_MILLISECONDS ); } void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags ); } void trap_Cvar_Update( vmCvar_t *vmCvar ) { syscall( CG_CVAR_UPDATE, vmCvar ); } void trap_Cvar_Set( const char *var_name, const char *value ) { syscall( CG_CVAR_SET, var_name, value ); } void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); } int trap_Argc( void ) { return syscall( CG_ARGC ); } void trap_Argv( int n, char *buffer, int bufferLength ) { syscall( CG_ARGV, n, buffer, bufferLength ); } void trap_Args( char *buffer, int bufferLength ) { syscall( CG_ARGS, buffer, bufferLength ); } int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { return syscall( CG_FS_FOPENFILE, qpath, f, mode ); } void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { syscall( CG_FS_READ, buffer, len, f ); } void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { syscall( CG_FS_WRITE, buffer, len, f ); } void trap_FS_FCloseFile( fileHandle_t f ) { syscall( CG_FS_FCLOSEFILE, f ); } int trap_FS_Seek( fileHandle_t f, long offset, int origin ) { return syscall( CG_FS_SEEK, f, offset, origin ); } void trap_SendConsoleCommand( const char *text ) { syscall( CG_SENDCONSOLECOMMAND, text ); } void trap_AddCommand( const char *cmdName ) { syscall( CG_ADDCOMMAND, cmdName ); } void trap_RemoveCommand( const char *cmdName ) { syscall( CG_REMOVECOMMAND, cmdName ); } void trap_SendClientCommand( const char *s ) { syscall( CG_SENDCLIENTCOMMAND, s ); } void trap_UpdateScreen( void ) { syscall( CG_UPDATESCREEN ); } void trap_CM_LoadMap( const char *mapname ) { syscall( CG_CM_LOADMAP, mapname ); } int trap_CM_NumInlineModels( void ) { return syscall( CG_CM_NUMINLINEMODELS ); } clipHandle_t trap_CM_InlineModel( int index ) { return syscall( CG_CM_INLINEMODEL, index ); } clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) { return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); } clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ) { return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs ); } int trap_CM_PointContents( const vec3_t p, clipHandle_t model ) { return syscall( CG_CM_POINTCONTENTS, p, model ); } int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); } void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, clipHandle_t model, int brushmask ) { syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); } void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, clipHandle_t model, int brushmask ) { syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask ); } void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, clipHandle_t model, int brushmask, const vec3_t origin, const vec3_t angles ) { syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); } void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, clipHandle_t model, int brushmask, const vec3_t origin, const vec3_t angles ) { syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); } int trap_CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer ) { return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, pointBuffer, maxFragments, fragmentBuffer ); } void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) { syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx ); } void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); } void trap_S_ClearLoopingSounds( qboolean killall ) { syscall( CG_S_CLEARLOOPINGSOUNDS, killall ); } void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx ); } void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, sfx ); } void trap_S_StopLoopingSound( int entityNum ) { syscall( CG_S_STOPLOOPINGSOUND, entityNum ); } void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); } void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) { syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); } sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) { return syscall( CG_S_REGISTERSOUND, sample, compressed ); } void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) { syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); } void trap_R_LoadWorldMap( const char *mapname ) { syscall( CG_R_LOADWORLDMAP, mapname ); } qhandle_t trap_R_RegisterModel( const char *name ) { return syscall( CG_R_REGISTERMODEL, name ); } qhandle_t trap_R_RegisterSkin( const char *name ) { return syscall( CG_R_REGISTERSKIN, name ); } qhandle_t trap_R_RegisterShader( const char *name ) { return syscall( CG_R_REGISTERSHADER, name ); } qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { return syscall( CG_R_REGISTERSHADERNOMIP, name ); } void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) { syscall(CG_R_REGISTERFONT, fontName, pointSize, font ); } void trap_R_ClearScene( void ) { syscall( CG_R_CLEARSCENE ); } void trap_R_AddRefEntityToScene( const refEntity_t *re ) { syscall( CG_R_ADDREFENTITYTOSCENE, re ); } void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); } void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) { syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); } int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) { return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir ); } void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); } void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); } void trap_R_RenderScene( const refdef_t *fd ) { syscall( CG_R_RENDERSCENE, fd ); } void trap_R_SetColor( const float *rgba ) { syscall( CG_R_SETCOLOR, rgba ); } void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ) { syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); } void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { syscall( CG_R_MODELBOUNDS, model, mins, maxs ); } int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ) { return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); } void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); } void trap_GetGlconfig( glconfig_t *glconfig ) { syscall( CG_GETGLCONFIG, glconfig ); } void trap_GetGameState( gameState_t *gamestate ) { syscall( CG_GETGAMESTATE, gamestate ); } void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); } qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); } qboolean trap_GetServerCommand( int serverCommandNumber ) { return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); } int trap_GetCurrentCmdNumber( void ) { return syscall( CG_GETCURRENTCMDNUMBER ); } qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); } void trap_SetUserCmdValue( int stateValue, float sensitivityScale ) { syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale) ); } void testPrintInt( char *string, int i ) { syscall( CG_TESTPRINTINT, string, i ); } void testPrintFloat( char *string, float f ) { syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) ); } int trap_MemoryRemaining( void ) { return syscall( CG_MEMORY_REMAINING ); } qboolean trap_Key_IsDown( int keynum ) { return syscall( CG_KEY_ISDOWN, keynum ); } int trap_Key_GetCatcher( void ) { return syscall( CG_KEY_GETCATCHER ); } void trap_Key_SetCatcher( int catcher ) { syscall( CG_KEY_SETCATCHER, catcher ); } int trap_Key_GetKey( const char *binding ) { return syscall( CG_KEY_GETKEY, binding ); } int trap_PC_AddGlobalDefine( char *define ) { return syscall( CG_PC_ADD_GLOBAL_DEFINE, define ); } int trap_PC_LoadSource( const char *filename ) { return syscall( CG_PC_LOAD_SOURCE, filename ); } int trap_PC_FreeSource( int handle ) { return syscall( CG_PC_FREE_SOURCE, handle ); } int trap_PC_ReadToken( int handle, pc_token_t *pc_token ) { return syscall( CG_PC_READ_TOKEN, handle, pc_token ); } int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ) { return syscall( CG_PC_SOURCE_FILE_AND_LINE, handle, filename, line ); } void trap_S_StopBackgroundTrack( void ) { syscall( CG_S_STOPBACKGROUNDTRACK ); } int trap_RealTime(qtime_t *qtime) { return syscall( CG_REAL_TIME, qtime ); } void trap_SnapVector( float *v ) { syscall( CG_SNAPVECTOR, v ); } // this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) { return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); } // stops playing the cinematic and ends it. should always return FMV_EOF // cinematics must be stopped in reverse order of when they are started e_status trap_CIN_StopCinematic(int handle) { return syscall(CG_CIN_STOPCINEMATIC, handle); } // will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. e_status trap_CIN_RunCinematic (int handle) { return syscall(CG_CIN_RUNCINEMATIC, handle); } // draws the current frame void trap_CIN_DrawCinematic (int handle) { syscall(CG_CIN_DRAWCINEMATIC, handle); } // allows you to resize the animation dynamically void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) { syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h); } /* qboolean trap_loadCamera( const char *name ) { return syscall( CG_LOADCAMERA, name ); } void trap_startCamera(int time) { syscall(CG_STARTCAMERA, time); } qboolean trap_getCameraInfo( int time, vec3_t *origin, vec3_t *angles) { return syscall( CG_GETCAMERAINFO, time, origin, angles ); } */ qboolean trap_GetEntityToken( char *buffer, int bufferSize ) { return syscall( CG_GET_ENTITY_TOKEN, buffer, bufferSize ); } qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ) { return syscall( CG_R_INPVS, p1, p2 ); } ================================================ FILE: code/cgame/cg_view.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_view.c -- setup all the parameters (position, angle, etc) // for a 3D rendering #include "cg_local.h" /* ============================================================================= MODEL TESTING The viewthing and gun positioning tools from Q2 have been integrated and enhanced into a single model testing facility. Model viewing can begin with either "testmodel " or "testgun ". The names must be the full pathname after the basedir, like "models/weapons/v_launch/tris.md3" or "players/male/tris.md3" Testmodel will create a fake entity 100 units in front of the current view position, directly facing the viewer. It will remain immobile, so you can move around it to view it from different angles. Testgun will cause the model to follow the player around and supress the real view weapon model. The default frame 0 of most guns is completely off screen, so you will probably have to cycle a couple frames to see it. "nextframe", "prevframe", "nextskin", and "prevskin" commands will change the frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in q3default.cfg. If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let you adjust the positioning. Note that none of the model testing features update while the game is paused, so it may be convenient to test with deathmatch set to 1 so that bringing down the console doesn't pause the game. ============================================================================= */ /* ================= CG_TestModel_f Creates an entity in front of the current position, which can then be moved around ================= */ void CG_TestModel_f (void) { vec3_t angles; memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) ); if ( trap_Argc() < 2 ) { return; } Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); if ( trap_Argc() == 3 ) { cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); cg.testModelEntity.frame = 1; cg.testModelEntity.oldframe = 0; } if (! cg.testModelEntity.hModel ) { CG_Printf( "Can't register model\n" ); return; } VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin ); angles[PITCH] = 0; angles[YAW] = 180 + cg.refdefViewAngles[1]; angles[ROLL] = 0; AnglesToAxis( angles, cg.testModelEntity.axis ); cg.testGun = qfalse; } /* ================= CG_TestGun_f Replaces the current view weapon with the given model ================= */ void CG_TestGun_f (void) { CG_TestModel_f(); cg.testGun = qtrue; cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON; } void CG_TestModelNextFrame_f (void) { cg.testModelEntity.frame++; CG_Printf( "frame %i\n", cg.testModelEntity.frame ); } void CG_TestModelPrevFrame_f (void) { cg.testModelEntity.frame--; if ( cg.testModelEntity.frame < 0 ) { cg.testModelEntity.frame = 0; } CG_Printf( "frame %i\n", cg.testModelEntity.frame ); } void CG_TestModelNextSkin_f (void) { cg.testModelEntity.skinNum++; CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); } void CG_TestModelPrevSkin_f (void) { cg.testModelEntity.skinNum--; if ( cg.testModelEntity.skinNum < 0 ) { cg.testModelEntity.skinNum = 0; } CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); } static void CG_AddTestModel (void) { int i; // re-register the model, because the level may have changed cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); if (! cg.testModelEntity.hModel ) { CG_Printf ("Can't register model\n"); return; } // if testing a gun, set the origin reletive to the view origin if ( cg.testGun ) { VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); VectorCopy( cg.refdef.viewaxis[0], cg.testModelEntity.axis[0] ); VectorCopy( cg.refdef.viewaxis[1], cg.testModelEntity.axis[1] ); VectorCopy( cg.refdef.viewaxis[2], cg.testModelEntity.axis[2] ); // allow the position to be adjusted for (i=0 ; i<3 ; i++) { cg.testModelEntity.origin[i] += cg.refdef.viewaxis[0][i] * cg_gun_x.value; cg.testModelEntity.origin[i] += cg.refdef.viewaxis[1][i] * cg_gun_y.value; cg.testModelEntity.origin[i] += cg.refdef.viewaxis[2][i] * cg_gun_z.value; } } trap_R_AddRefEntityToScene( &cg.testModelEntity ); } //============================================================================ /* ================= CG_CalcVrect Sets the coordinates of the rendered window ================= */ static void CG_CalcVrect (void) { int size; // the intermission should allways be full screen if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { size = 100; } else { // bound normal viewsize if (cg_viewsize.integer < 30) { trap_Cvar_Set ("cg_viewsize","30"); size = 30; } else if (cg_viewsize.integer > 100) { trap_Cvar_Set ("cg_viewsize","100"); size = 100; } else { size = cg_viewsize.integer; } } cg.refdef.width = cgs.glconfig.vidWidth*size/100; cg.refdef.width &= ~1; cg.refdef.height = cgs.glconfig.vidHeight*size/100; cg.refdef.height &= ~1; cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width)/2; cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height)/2; } //============================================================================== /* =============== CG_OffsetThirdPersonView =============== */ #define FOCUS_DISTANCE 512 static void CG_OffsetThirdPersonView( void ) { vec3_t forward, right, up; vec3_t view; vec3_t focusAngles; trace_t trace; static vec3_t mins = { -4, -4, -4 }; static vec3_t maxs = { 4, 4, 4 }; vec3_t focusPoint; float focusDist; float forwardScale, sideScale; cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight; VectorCopy( cg.refdefViewAngles, focusAngles ); // if dead, look at killer if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; } if ( focusAngles[PITCH] > 45 ) { focusAngles[PITCH] = 45; // don't go too far overhead } AngleVectors( focusAngles, forward, NULL, NULL ); VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); VectorCopy( cg.refdef.vieworg, view ); view[2] += 8; cg.refdefViewAngles[PITCH] *= 0.5; AngleVectors( cg.refdefViewAngles, forward, right, up ); forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); // trace a ray from the origin to the viewpoint to make sure the view isn't // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything if (!cg_cameraMode.integer) { CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); if ( trace.fraction != 1.0 ) { VectorCopy( trace.endpos, view ); view[2] += (1.0 - trace.fraction) * 32; // try another trace to this position, because a tunnel may have the ceiling // close enogh that this is poking out CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); VectorCopy( trace.endpos, view ); } } VectorCopy( view, cg.refdef.vieworg ); // select pitch to look at focus point from vieword VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); if ( focusDist < 1 ) { focusDist = 1; // should never happen } cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); cg.refdefViewAngles[YAW] -= cg_thirdPersonAngle.value; } // this causes a compiler bug on mac MrC compiler static void CG_StepOffset( void ) { int timeDelta; // smooth out stair climbing timeDelta = cg.time - cg.stepTime; if ( timeDelta < STEP_TIME ) { cg.refdef.vieworg[2] -= cg.stepChange * (STEP_TIME - timeDelta) / STEP_TIME; } } /* =============== CG_OffsetFirstPersonView =============== */ static void CG_OffsetFirstPersonView( void ) { float *origin; float *angles; float bob; float ratio; float delta; float speed; float f; vec3_t predictedVelocity; int timeDelta; if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { return; } origin = cg.refdef.vieworg; angles = cg.refdefViewAngles; // if dead, fix the angle and don't add any kick if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { angles[ROLL] = 40; angles[PITCH] = -15; angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; origin[2] += cg.predictedPlayerState.viewheight; return; } // add angles based on weapon kick VectorAdd (angles, cg.kick_angles, angles); // add angles based on damage kick if ( cg.damageTime ) { ratio = cg.time - cg.damageTime; if ( ratio < DAMAGE_DEFLECT_TIME ) { ratio /= DAMAGE_DEFLECT_TIME; angles[PITCH] += ratio * cg.v_dmg_pitch; angles[ROLL] += ratio * cg.v_dmg_roll; } else { ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; if ( ratio > 0 ) { angles[PITCH] += ratio * cg.v_dmg_pitch; angles[ROLL] += ratio * cg.v_dmg_roll; } } } // add pitch based on fall kick #if 0 ratio = ( cg.time - cg.landTime) / FALL_TIME; if (ratio < 0) ratio = 0; angles[PITCH] += ratio * cg.fall_value; #endif // add angles based on velocity VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]); angles[PITCH] += delta * cg_runpitch.value; delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]); angles[ROLL] -= delta * cg_runroll.value; // add angles based on bob // make sure the bob is visible even at low speeds speed = cg.xyspeed > 200 ? cg.xyspeed : 200; delta = cg.bobfracsin * cg_bobpitch.value * speed; if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) delta *= 3; // crouching angles[PITCH] += delta; delta = cg.bobfracsin * cg_bobroll.value * speed; if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) delta *= 3; // crouching accentuates roll if (cg.bobcycle & 1) delta = -delta; angles[ROLL] += delta; //=================================== // add view height origin[2] += cg.predictedPlayerState.viewheight; // smooth out duck height changes timeDelta = cg.time - cg.duckTime; if ( timeDelta < DUCK_TIME) { cg.refdef.vieworg[2] -= cg.duckChange * (DUCK_TIME - timeDelta) / DUCK_TIME; } // add bob height bob = cg.bobfracsin * cg.xyspeed * cg_bobup.value; if (bob > 6) { bob = 6; } origin[2] += bob; // add fall height delta = cg.time - cg.landTime; if ( delta < LAND_DEFLECT_TIME ) { f = delta / LAND_DEFLECT_TIME; cg.refdef.vieworg[2] += cg.landChange * f; } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { delta -= LAND_DEFLECT_TIME; f = 1.0 - ( delta / LAND_RETURN_TIME ); cg.refdef.vieworg[2] += cg.landChange * f; } // add step offset CG_StepOffset(); // add kick offset VectorAdd (origin, cg.kick_origin, origin); // pivot the eye based on a neck length #if 0 { #define NECK_LENGTH 8 vec3_t forward, up; cg.refdef.vieworg[2] -= NECK_LENGTH; AngleVectors( cg.refdefViewAngles, forward, NULL, up ); VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg ); VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg ); } #endif } //====================================================================== void CG_ZoomDown_f( void ) { if ( cg.zoomed ) { return; } cg.zoomed = qtrue; cg.zoomTime = cg.time; } void CG_ZoomUp_f( void ) { if ( !cg.zoomed ) { return; } cg.zoomed = qfalse; cg.zoomTime = cg.time; } /* ==================== CG_CalcFov Fixed fov at intermissions, otherwise account for fov variable and zooms. ==================== */ #define WAVE_AMPLITUDE 1 #define WAVE_FREQUENCY 0.4 static int CG_CalcFov( void ) { float x; float phase; float v; int contents; float fov_x, fov_y; float zoomFov; float f; int inwater; if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { // if in intermission, use a fixed value fov_x = 90; } else { // user selectable if ( cgs.dmflags & DF_FIXED_FOV ) { // dmflag to prevent wide fov for all clients fov_x = 90; } else { fov_x = cg_fov.value; if ( fov_x < 1 ) { fov_x = 1; } else if ( fov_x > 160 ) { fov_x = 160; } } // account for zooms zoomFov = cg_zoomFov.value; if ( zoomFov < 1 ) { zoomFov = 1; } else if ( zoomFov > 160 ) { zoomFov = 160; } if ( cg.zoomed ) { f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; if ( f > 1.0 ) { fov_x = zoomFov; } else { fov_x = fov_x + f * ( zoomFov - fov_x ); } } else { f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; if ( f > 1.0 ) { fov_x = fov_x; } else { fov_x = zoomFov + f * ( fov_x - zoomFov ); } } } x = cg.refdef.width / tan( fov_x / 360 * M_PI ); fov_y = atan2( cg.refdef.height, x ); fov_y = fov_y * 360 / M_PI; // warp if underwater contents = CG_PointContents( cg.refdef.vieworg, -1 ); if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ){ phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; v = WAVE_AMPLITUDE * sin( phase ); fov_x += v; fov_y -= v; inwater = qtrue; } else { inwater = qfalse; } // set it cg.refdef.fov_x = fov_x; cg.refdef.fov_y = fov_y; if ( !cg.zoomed ) { cg.zoomSensitivity = 1; } else { cg.zoomSensitivity = cg.refdef.fov_y / 75.0; } return inwater; } /* =============== CG_DamageBlendBlob =============== */ static void CG_DamageBlendBlob( void ) { int t; int maxTime; refEntity_t ent; if ( !cg.damageValue ) { return; } //if (cg.cameraMode) { // return; //} // ragePro systems can't fade blends, so don't obscure the screen if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { return; } maxTime = DAMAGE_TIME; t = cg.time - cg.damageTime; if ( t <= 0 || t >= maxTime ) { return; } memset( &ent, 0, sizeof( ent ) ); ent.reType = RT_SPRITE; ent.renderfx = RF_FIRST_PERSON; VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin ); VectorMA( ent.origin, cg.damageX * -8, cg.refdef.viewaxis[1], ent.origin ); VectorMA( ent.origin, cg.damageY * 8, cg.refdef.viewaxis[2], ent.origin ); ent.radius = cg.damageValue * 3; ent.customShader = cgs.media.viewBloodShader; ent.shaderRGBA[0] = 255; ent.shaderRGBA[1] = 255; ent.shaderRGBA[2] = 255; ent.shaderRGBA[3] = 200 * ( 1.0 - ((float)t / maxTime) ); trap_R_AddRefEntityToScene( &ent ); } /* =============== CG_CalcViewValues Sets cg.refdef view values =============== */ static int CG_CalcViewValues( void ) { playerState_t *ps; memset( &cg.refdef, 0, sizeof( cg.refdef ) ); // strings for in game rendering // Q_strncpyz( cg.refdef.text[0], "Park Ranger", sizeof(cg.refdef.text[0]) ); // Q_strncpyz( cg.refdef.text[1], "19", sizeof(cg.refdef.text[1]) ); // calculate size of 3D view CG_CalcVrect(); ps = &cg.predictedPlayerState; /* if (cg.cameraMode) { vec3_t origin, angles; if (trap_getCameraInfo(cg.time, &origin, &angles)) { VectorCopy(origin, cg.refdef.vieworg); angles[ROLL] = 0; VectorCopy(angles, cg.refdefViewAngles); AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); return CG_CalcFov(); } else { cg.cameraMode = qfalse; } } */ // intermission view if ( ps->pm_type == PM_INTERMISSION ) { VectorCopy( ps->origin, cg.refdef.vieworg ); VectorCopy( ps->viewangles, cg.refdefViewAngles ); AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); return CG_CalcFov(); } cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + ps->velocity[1] * ps->velocity[1] ); VectorCopy( ps->origin, cg.refdef.vieworg ); VectorCopy( ps->viewangles, cg.refdefViewAngles ); if (cg_cameraOrbit.integer) { if (cg.time > cg.nextOrbitTime) { cg.nextOrbitTime = cg.time + cg_cameraOrbitDelay.integer; cg_thirdPersonAngle.value += cg_cameraOrbit.value; } } // add error decay if ( cg_errorDecay.value > 0 ) { int t; float f; t = cg.time - cg.predictedErrorTime; f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; if ( f > 0 && f < 1 ) { VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); } else { cg.predictedErrorTime = 0; } } if ( cg.renderingThirdPerson ) { // back away from character CG_OffsetThirdPersonView(); } else { // offset for local bobbing and kicks CG_OffsetFirstPersonView(); } // position eye reletive to origin AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); if ( cg.hyperspace ) { cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; } // field of view return CG_CalcFov(); } /* ===================== CG_PowerupTimerSounds ===================== */ static void CG_PowerupTimerSounds( void ) { int i; int t; // powerup timers going away for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { t = cg.snap->ps.powerups[i]; if ( t <= cg.time ) { continue; } if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { continue; } if ( ( t - cg.time ) / POWERUP_BLINK_TIME != ( t - cg.oldTime ) / POWERUP_BLINK_TIME ) { trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound ); } } } /* ===================== CG_AddBufferedSound ===================== */ void CG_AddBufferedSound( sfxHandle_t sfx ) { if ( !sfx ) return; cg.soundBuffer[cg.soundBufferIn] = sfx; cg.soundBufferIn = (cg.soundBufferIn + 1) % MAX_SOUNDBUFFER; if (cg.soundBufferIn == cg.soundBufferOut) { cg.soundBufferOut++; } } /* ===================== CG_PlayBufferedSounds ===================== */ static void CG_PlayBufferedSounds( void ) { if ( cg.soundTime < cg.time ) { if (cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[cg.soundBufferOut]) { trap_S_StartLocalSound(cg.soundBuffer[cg.soundBufferOut], CHAN_ANNOUNCER); cg.soundBuffer[cg.soundBufferOut] = 0; cg.soundBufferOut = (cg.soundBufferOut + 1) % MAX_SOUNDBUFFER; cg.soundTime = cg.time + 750; } } } //========================================================================= /* ================= CG_DrawActiveFrame Generates and draws a game scene and status information at the given time. ================= */ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) { int inwater; cg.time = serverTime; cg.demoPlayback = demoPlayback; // update cvars CG_UpdateCvars(); // if we are only updating the screen as a loading // pacifier, don't even try to read snapshots if ( cg.infoScreenText[0] != 0 ) { CG_DrawInformation(); return; } // any looped sounds will be respecified as entities // are added to the render list trap_S_ClearLoopingSounds(qfalse); // clear all the render lists trap_R_ClearScene(); // set up cg.snap and possibly cg.nextSnap CG_ProcessSnapshots(); // if we haven't received any snapshots yet, all // we can draw is the information screen if ( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { CG_DrawInformation(); return; } // let the client system know what our weapon and zoom settings are trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity ); // this counter will be bumped for every valid scene we generate cg.clientFrame++; // update cg.predictedPlayerState CG_PredictPlayerState(); // decide on third person view cg.renderingThirdPerson = cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); // build cg.refdef inwater = CG_CalcViewValues(); // first person blend blobs, done after AnglesToAxis if ( !cg.renderingThirdPerson ) { CG_DamageBlendBlob(); } // build the render lists if ( !cg.hyperspace ) { CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct CG_AddMarks(); CG_AddParticles (); CG_AddLocalEntities(); } CG_AddViewWeapon( &cg.predictedPlayerState ); // add buffered sounds CG_PlayBufferedSounds(); // play buffered voice chats CG_PlayBufferedVoiceChats(); // finish up the rest of the refdef if ( cg.testModelEntity.hModel ) { CG_AddTestModel(); } cg.refdef.time = cg.time; memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) ); // warning sounds when powerup is wearing off CG_PowerupTimerSounds(); // update audio positions trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); // make sure the lagometerSample and frame timing isn't done twice when in stereo if ( stereoView != STEREO_RIGHT ) { cg.frametime = cg.time - cg.oldTime; if ( cg.frametime < 0 ) { cg.frametime = 0; } cg.oldTime = cg.time; CG_AddLagometerFrameInfo(); } if (cg_timescale.value != cg_timescaleFadeEnd.value) { if (cg_timescale.value < cg_timescaleFadeEnd.value) { cg_timescale.value += cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; if (cg_timescale.value > cg_timescaleFadeEnd.value) cg_timescale.value = cg_timescaleFadeEnd.value; } else { cg_timescale.value -= cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; if (cg_timescale.value < cg_timescaleFadeEnd.value) cg_timescale.value = cg_timescaleFadeEnd.value; } if (cg_timescaleFadeSpeed.value) { trap_Cvar_Set("timescale", va("%f", cg_timescale.value)); } } // actually issue the rendering calls CG_DrawActive( stereoView ); if ( cg_stats.integer ) { CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); } } ================================================ FILE: code/cgame/cg_weapons.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // cg_weapons.c -- events and effects dealing with weapons #include "cg_local.h" /* ========================== CG_MachineGunEjectBrass ========================== */ static void CG_MachineGunEjectBrass( centity_t *cent ) { localEntity_t *le; refEntity_t *re; vec3_t velocity, xvelocity; vec3_t offset, xoffset; float waterScale = 1.0f; vec3_t v[3]; if ( cg_brassTime.integer <= 0 ) { return; } le = CG_AllocLocalEntity(); re = &le->refEntity; velocity[0] = 0; velocity[1] = -50 + 40 * crandom(); velocity[2] = 100 + 50 * crandom(); le->leType = LE_FRAGMENT; le->startTime = cg.time; le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random(); le->pos.trType = TR_GRAVITY; le->pos.trTime = cg.time - (rand()&15); AnglesToAxis( cent->lerpAngles, v ); offset[0] = 8; offset[1] = -4; offset[2] = 24; xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; VectorAdd( cent->lerpOrigin, xoffset, re->origin ); VectorCopy( re->origin, le->pos.trBase ); if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { waterScale = 0.10f; } xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; VectorScale( xvelocity, waterScale, le->pos.trDelta ); AxisCopy( axisDefault, re->axis ); re->hModel = cgs.media.machinegunBrassModel; le->bounceFactor = 0.4 * waterScale; le->angles.trType = TR_LINEAR; le->angles.trTime = cg.time; le->angles.trBase[0] = rand()&31; le->angles.trBase[1] = rand()&31; le->angles.trBase[2] = rand()&31; le->angles.trDelta[0] = 2; le->angles.trDelta[1] = 1; le->angles.trDelta[2] = 0; le->leFlags = LEF_TUMBLE; le->leBounceSoundType = LEBS_BRASS; le->leMarkType = LEMT_NONE; } /* ========================== CG_ShotgunEjectBrass ========================== */ static void CG_ShotgunEjectBrass( centity_t *cent ) { localEntity_t *le; refEntity_t *re; vec3_t velocity, xvelocity; vec3_t offset, xoffset; vec3_t v[3]; int i; if ( cg_brassTime.integer <= 0 ) { return; } for ( i = 0; i < 2; i++ ) { float waterScale = 1.0f; le = CG_AllocLocalEntity(); re = &le->refEntity; velocity[0] = 60 + 60 * crandom(); if ( i == 0 ) { velocity[1] = 40 + 10 * crandom(); } else { velocity[1] = -40 + 10 * crandom(); } velocity[2] = 100 + 50 * crandom(); le->leType = LE_FRAGMENT; le->startTime = cg.time; le->endTime = le->startTime + cg_brassTime.integer*3 + cg_brassTime.integer * random(); le->pos.trType = TR_GRAVITY; le->pos.trTime = cg.time; AnglesToAxis( cent->lerpAngles, v ); offset[0] = 8; offset[1] = 0; offset[2] = 24; xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; VectorAdd( cent->lerpOrigin, xoffset, re->origin ); VectorCopy( re->origin, le->pos.trBase ); if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { waterScale = 0.10f; } xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; VectorScale( xvelocity, waterScale, le->pos.trDelta ); AxisCopy( axisDefault, re->axis ); re->hModel = cgs.media.shotgunBrassModel; le->bounceFactor = 0.3f; le->angles.trType = TR_LINEAR; le->angles.trTime = cg.time; le->angles.trBase[0] = rand()&31; le->angles.trBase[1] = rand()&31; le->angles.trBase[2] = rand()&31; le->angles.trDelta[0] = 1; le->angles.trDelta[1] = 0.5; le->angles.trDelta[2] = 0; le->leFlags = LEF_TUMBLE; le->leBounceSoundType = LEBS_BRASS; le->leMarkType = LEMT_NONE; } } #ifdef MISSIONPACK /* ========================== CG_NailgunEjectBrass ========================== */ static void CG_NailgunEjectBrass( centity_t *cent ) { localEntity_t *smoke; vec3_t origin; vec3_t v[3]; vec3_t offset; vec3_t xoffset; vec3_t up; AnglesToAxis( cent->lerpAngles, v ); offset[0] = 0; offset[1] = -12; offset[2] = 24; xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; VectorAdd( cent->lerpOrigin, xoffset, origin ); VectorSet( up, 0, 0, 64 ); smoke = CG_SmokePuff( origin, up, 32, 1, 1, 1, 0.33f, 700, cg.time, 0, 0, cgs.media.smokePuffShader ); // use the optimized local entity add smoke->leType = LE_SCALE_FADE; } #endif /* ========================== CG_RailTrail ========================== */ void CG_RailTrail (clientInfo_t *ci, vec3_t start, vec3_t end) { vec3_t axis[36], move, move2, next_move, vec, temp; float len; int i, j, skip; localEntity_t *le; refEntity_t *re; #define RADIUS 4 #define ROTATION 1 #define SPACING 5 start[2] -= 4; VectorCopy (start, move); VectorSubtract (end, start, vec); len = VectorNormalize (vec); PerpendicularVector(temp, vec); for (i = 0 ; i < 36; i++) { RotatePointAroundVector(axis[i], vec, temp, i * 10);//banshee 2.4 was 10 } le = CG_AllocLocalEntity(); re = &le->refEntity; le->leType = LE_FADE_RGB; le->startTime = cg.time; le->endTime = cg.time + cg_railTrailTime.value; le->lifeRate = 1.0 / (le->endTime - le->startTime); re->shaderTime = cg.time / 1000.0f; re->reType = RT_RAIL_CORE; re->customShader = cgs.media.railCoreShader; VectorCopy(start, re->origin); VectorCopy(end, re->oldorigin); re->shaderRGBA[0] = ci->color1[0] * 255; re->shaderRGBA[1] = ci->color1[1] * 255; re->shaderRGBA[2] = ci->color1[2] * 255; re->shaderRGBA[3] = 255; le->color[0] = ci->color1[0] * 0.75; le->color[1] = ci->color1[1] * 0.75; le->color[2] = ci->color1[2] * 0.75; le->color[3] = 1.0f; AxisClear( re->axis ); VectorMA(move, 20, vec, move); VectorCopy(move, next_move); VectorScale (vec, SPACING, vec); if (cg_oldRail.integer != 0) { // nudge down a bit so it isn't exactly in center re->origin[2] -= 8; re->oldorigin[2] -= 8; return; } skip = -1; j = 18; for (i = 0; i < len; i += SPACING) { if (i != skip) { skip = i + SPACING; le = CG_AllocLocalEntity(); re = &le->refEntity; le->leFlags = LEF_PUFF_DONT_SCALE; le->leType = LE_MOVE_SCALE_FADE; le->startTime = cg.time; le->endTime = cg.time + (i>>1) + 600; le->lifeRate = 1.0 / (le->endTime - le->startTime); re->shaderTime = cg.time / 1000.0f; re->reType = RT_SPRITE; re->radius = 1.1f; re->customShader = cgs.media.railRingsShader; re->shaderRGBA[0] = ci->color2[0] * 255; re->shaderRGBA[1] = ci->color2[1] * 255; re->shaderRGBA[2] = ci->color2[2] * 255; re->shaderRGBA[3] = 255; le->color[0] = ci->color2[0] * 0.75; le->color[1] = ci->color2[1] * 0.75; le->color[2] = ci->color2[2] * 0.75; le->color[3] = 1.0f; le->pos.trType = TR_LINEAR; le->pos.trTime = cg.time; VectorCopy( move, move2); VectorMA(move2, RADIUS , axis[j], move2); VectorCopy(move2, le->pos.trBase); le->pos.trDelta[0] = axis[j][0]*6; le->pos.trDelta[1] = axis[j][1]*6; le->pos.trDelta[2] = axis[j][2]*6; } VectorAdd (move, vec, move); j = j + ROTATION < 36 ? j + ROTATION : (j + ROTATION) % 36; } } /* ========================== CG_RocketTrail ========================== */ static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { int step; vec3_t origin, lastPos; int t; int startTime, contents; int lastContents; entityState_t *es; vec3_t up; localEntity_t *smoke; if ( cg_noProjectileTrail.integer ) { return; } up[0] = 0; up[1] = 0; up[2] = 0; step = 50; es = &ent->currentState; startTime = ent->trailTime; t = step * ( (startTime + step) / step ); BG_EvaluateTrajectory( &es->pos, cg.time, origin ); contents = CG_PointContents( origin, -1 ); // if object (e.g. grenade) is stationary, don't toss up smoke if ( es->pos.trType == TR_STATIONARY ) { ent->trailTime = cg.time; return; } BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); lastContents = CG_PointContents( lastPos, -1 ); ent->trailTime = cg.time; if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { if ( contents & lastContents & CONTENTS_WATER ) { CG_BubbleTrail( lastPos, origin, 8 ); } return; } for ( ; t <= ent->trailTime ; t += step ) { BG_EvaluateTrajectory( &es->pos, t, lastPos ); smoke = CG_SmokePuff( lastPos, up, wi->trailRadius, 1, 1, 1, 0.33f, wi->wiTrailTime, t, 0, 0, cgs.media.smokePuffShader ); // use the optimized local entity add smoke->leType = LE_SCALE_FADE; } } #ifdef MISSIONPACK /* ========================== CG_NailTrail ========================== */ static void CG_NailTrail( centity_t *ent, const weaponInfo_t *wi ) { int step; vec3_t origin, lastPos; int t; int startTime, contents; int lastContents; entityState_t *es; vec3_t up; localEntity_t *smoke; if ( cg_noProjectileTrail.integer ) { return; } up[0] = 0; up[1] = 0; up[2] = 0; step = 50; es = &ent->currentState; startTime = ent->trailTime; t = step * ( (startTime + step) / step ); BG_EvaluateTrajectory( &es->pos, cg.time, origin ); contents = CG_PointContents( origin, -1 ); // if object (e.g. grenade) is stationary, don't toss up smoke if ( es->pos.trType == TR_STATIONARY ) { ent->trailTime = cg.time; return; } BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); lastContents = CG_PointContents( lastPos, -1 ); ent->trailTime = cg.time; if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { if ( contents & lastContents & CONTENTS_WATER ) { CG_BubbleTrail( lastPos, origin, 8 ); } return; } for ( ; t <= ent->trailTime ; t += step ) { BG_EvaluateTrajectory( &es->pos, t, lastPos ); smoke = CG_SmokePuff( lastPos, up, wi->trailRadius, 1, 1, 1, 0.33f, wi->wiTrailTime, t, 0, 0, cgs.media.nailPuffShader ); // use the optimized local entity add smoke->leType = LE_SCALE_FADE; } } #endif /* ========================== CG_NailTrail ========================== */ static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi ) { localEntity_t *le; refEntity_t *re; entityState_t *es; vec3_t velocity, xvelocity, origin; vec3_t offset, xoffset; vec3_t v[3]; int t, startTime, step; float waterScale = 1.0f; if ( cg_noProjectileTrail.integer || cg_oldPlasma.integer ) { return; } step = 50; es = ¢->currentState; startTime = cent->trailTime; t = step * ( (startTime + step) / step ); BG_EvaluateTrajectory( &es->pos, cg.time, origin ); le = CG_AllocLocalEntity(); re = &le->refEntity; velocity[0] = 60 - 120 * crandom(); velocity[1] = 40 - 80 * crandom(); velocity[2] = 100 - 200 * crandom(); le->leType = LE_MOVE_SCALE_FADE; le->leFlags = LEF_TUMBLE; le->leBounceSoundType = LEBS_NONE; le->leMarkType = LEMT_NONE; le->startTime = cg.time; le->endTime = le->startTime + 600; le->pos.trType = TR_GRAVITY; le->pos.trTime = cg.time; AnglesToAxis( cent->lerpAngles, v ); offset[0] = 2; offset[1] = 2; offset[2] = 2; xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; VectorAdd( origin, xoffset, re->origin ); VectorCopy( re->origin, le->pos.trBase ); if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { waterScale = 0.10f; } xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; VectorScale( xvelocity, waterScale, le->pos.trDelta ); AxisCopy( axisDefault, re->axis ); re->shaderTime = cg.time / 1000.0f; re->reType = RT_SPRITE; re->radius = 0.25f; re->customShader = cgs.media.railRingsShader; le->bounceFactor = 0.3f; re->shaderRGBA[0] = wi->flashDlightColor[0] * 63; re->shaderRGBA[1] = wi->flashDlightColor[1] * 63; re->shaderRGBA[2] = wi->flashDlightColor[2] * 63; re->shaderRGBA[3] = 63; le->color[0] = wi->flashDlightColor[0] * 0.2; le->color[1] = wi->flashDlightColor[1] * 0.2; le->color[2] = wi->flashDlightColor[2] * 0.2; le->color[3] = 0.25f; le->angles.trType = TR_LINEAR; le->angles.trTime = cg.time; le->angles.trBase[0] = rand()&31; le->angles.trBase[1] = rand()&31; le->angles.trBase[2] = rand()&31; le->angles.trDelta[0] = 1; le->angles.trDelta[1] = 0.5; le->angles.trDelta[2] = 0; } /* ========================== CG_GrappleTrail ========================== */ void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) { vec3_t origin; entityState_t *es; vec3_t forward, up; refEntity_t beam; es = &ent->currentState; BG_EvaluateTrajectory( &es->pos, cg.time, origin ); ent->trailTime = cg.time; memset( &beam, 0, sizeof( beam ) ); //FIXME adjust for muzzle position VectorCopy ( cg_entities[ ent->currentState.otherEntityNum ].lerpOrigin, beam.origin ); beam.origin[2] += 26; AngleVectors( cg_entities[ ent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up ); VectorMA( beam.origin, -6, up, beam.origin ); VectorCopy( origin, beam.oldorigin ); if (Distance( beam.origin, beam.oldorigin ) < 64 ) return; // Don't draw if close beam.reType = RT_LIGHTNING; beam.customShader = cgs.media.lightningShader; AxisClear( beam.axis ); beam.shaderRGBA[0] = 0xff; beam.shaderRGBA[1] = 0xff; beam.shaderRGBA[2] = 0xff; beam.shaderRGBA[3] = 0xff; trap_R_AddRefEntityToScene( &beam ); } /* ========================== CG_GrenadeTrail ========================== */ static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi ) { CG_RocketTrail( ent, wi ); } /* ================= CG_RegisterWeapon The server says this item is used on this level ================= */ void CG_RegisterWeapon( int weaponNum ) { weaponInfo_t *weaponInfo; gitem_t *item, *ammo; char path[MAX_QPATH]; vec3_t mins, maxs; int i; weaponInfo = &cg_weapons[weaponNum]; if ( weaponNum == 0 ) { return; } if ( weaponInfo->registered ) { return; } memset( weaponInfo, 0, sizeof( *weaponInfo ) ); weaponInfo->registered = qtrue; for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { weaponInfo->item = item; break; } } if ( !item->classname ) { CG_Error( "Couldn't find weapon %i", weaponNum ); } CG_RegisterItemVisuals( item - bg_itemlist ); // load cmodel before model so filecache works weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model[0] ); // calc midpoint for rotation trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); for ( i = 0 ; i < 3 ; i++ ) { weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); } weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); weaponInfo->ammoIcon = trap_R_RegisterShader( item->icon ); for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) { break; } } if ( ammo->classname && ammo->world_model[0] ) { weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] ); } strcpy( path, item->world_model[0] ); COM_StripExtension( path, path ); strcat( path, "_flash.md3" ); weaponInfo->flashModel = trap_R_RegisterModel( path ); strcpy( path, item->world_model[0] ); COM_StripExtension( path, path ); strcat( path, "_barrel.md3" ); weaponInfo->barrelModel = trap_R_RegisterModel( path ); strcpy( path, item->world_model[0] ); COM_StripExtension( path, path ); strcat( path, "_hand.md3" ); weaponInfo->handsModel = trap_R_RegisterModel( path ); if ( !weaponInfo->handsModel ) { weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); } weaponInfo->loopFireSound = qfalse; switch ( weaponNum ) { case WP_GAUNTLET: MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav", qfalse ); break; case WP_LIGHTNING: MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/lightning/lg_hum.wav", qfalse ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/lightning/lg_fire.wav", qfalse ); cgs.media.lightningShader = trap_R_RegisterShader( "lightningBoltNew"); cgs.media.lightningExplosionModel = trap_R_RegisterModel( "models/weaphits/crackle.md3" ); cgs.media.sfx_lghit1 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit.wav", qfalse ); cgs.media.sfx_lghit2 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit2.wav", qfalse ); cgs.media.sfx_lghit3 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit3.wav", qfalse ); break; case WP_GRAPPLING_HOOK: MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); weaponInfo->missileTrailFunc = CG_GrappleTrail; weaponInfo->missileDlight = 200; weaponInfo->wiTrailTime = 2000; weaponInfo->trailRadius = 64; MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); break; #ifdef MISSIONPACK case WP_CHAINGUN: weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/vulcan/wvulfire.wav", qfalse ); weaponInfo->loopFireSound = qtrue; MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf1b.wav", qfalse ); weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf2b.wav", qfalse ); weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf3b.wav", qfalse ); weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf4b.wav", qfalse ); weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); break; #endif case WP_MACHINEGUN: MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf1b.wav", qfalse ); weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf2b.wav", qfalse ); weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf3b.wav", qfalse ); weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf4b.wav", qfalse ); weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); break; case WP_SHOTGUN: MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse ); weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass; break; case WP_ROCKET_LAUNCHER: weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); weaponInfo->missileTrailFunc = CG_RocketTrail; weaponInfo->missileDlight = 200; weaponInfo->wiTrailTime = 2000; weaponInfo->trailRadius = 64; MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); cgs.media.rocketExplosionShader = trap_R_RegisterShader( "rocketExplosion" ); break; #ifdef MISSIONPACK case WP_PROX_LAUNCHER: weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/proxmine.md3" ); weaponInfo->missileTrailFunc = CG_GrenadeTrail; weaponInfo->wiTrailTime = 700; weaponInfo->trailRadius = 32; MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/proxmine/wstbfire.wav", qfalse ); cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); break; #endif case WP_GRENADE_LAUNCHER: weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" ); weaponInfo->missileTrailFunc = CG_GrenadeTrail; weaponInfo->wiTrailTime = 700; weaponInfo->trailRadius = 32; MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav", qfalse ); cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); break; #ifdef MISSIONPACK case WP_NAILGUN: weaponInfo->ejectBrassFunc = CG_NailgunEjectBrass; weaponInfo->missileTrailFunc = CG_NailTrail; // weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/nailgun/wnalflit.wav", qfalse ); weaponInfo->trailRadius = 16; weaponInfo->wiTrailTime = 250; weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/nail.md3" ); MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/nailgun/wnalfire.wav", qfalse ); break; #endif case WP_PLASMAGUN: // weaponInfo->missileModel = cgs.media.invulnerabilityPowerupModel; weaponInfo->missileTrailFunc = CG_PlasmaTrail; weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse ); MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav", qfalse ); cgs.media.plasmaExplosionShader = trap_R_RegisterShader( "plasmaExplosion" ); cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); break; case WP_RAILGUN: weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/railgun/rg_hum.wav", qfalse ); MAKERGB( weaponInfo->flashDlightColor, 1, 0.5f, 0 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/railgun/railgf1a.wav", qfalse ); cgs.media.railExplosionShader = trap_R_RegisterShader( "railExplosion" ); cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); cgs.media.railCoreShader = trap_R_RegisterShader( "railCore" ); break; case WP_BFG: weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/bfg/bfg_hum.wav", qfalse ); MAKERGB( weaponInfo->flashDlightColor, 1, 0.7f, 1 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bfg/bfg_fire.wav", qfalse ); cgs.media.bfgExplosionShader = trap_R_RegisterShader( "bfgExplosion" ); weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/bfg.md3" ); weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); break; default: MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); break; } } /* ================= CG_RegisterItemVisuals The server says this item is used on this level ================= */ void CG_RegisterItemVisuals( int itemNum ) { itemInfo_t *itemInfo; gitem_t *item; if ( itemNum < 0 || itemNum >= bg_numItems ) { CG_Error( "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 ); } itemInfo = &cg_items[ itemNum ]; if ( itemInfo->registered ) { return; } item = &bg_itemlist[ itemNum ]; memset( itemInfo, 0, sizeof( &itemInfo ) ); itemInfo->registered = qtrue; itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] ); itemInfo->icon = trap_R_RegisterShader( item->icon ); if ( item->giType == IT_WEAPON ) { CG_RegisterWeapon( item->giTag ); } // // powerups have an accompanying ring or sphere // if ( item->giType == IT_POWERUP || item->giType == IT_HEALTH || item->giType == IT_ARMOR || item->giType == IT_HOLDABLE ) { if ( item->world_model[1] ) { itemInfo->models[1] = trap_R_RegisterModel( item->world_model[1] ); } } } /* ======================================================================================== VIEW WEAPON ======================================================================================== */ /* ================= CG_MapTorsoToWeaponFrame ================= */ static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) { // change weapon if ( frame >= ci->animations[TORSO_DROP].firstFrame && frame < ci->animations[TORSO_DROP].firstFrame + 9 ) { return frame - ci->animations[TORSO_DROP].firstFrame + 6; } // stand attack if ( frame >= ci->animations[TORSO_ATTACK].firstFrame && frame < ci->animations[TORSO_ATTACK].firstFrame + 6 ) { return 1 + frame - ci->animations[TORSO_ATTACK].firstFrame; } // stand attack 2 if ( frame >= ci->animations[TORSO_ATTACK2].firstFrame && frame < ci->animations[TORSO_ATTACK2].firstFrame + 6 ) { return 1 + frame - ci->animations[TORSO_ATTACK2].firstFrame; } return 0; } /* ============== CG_CalculateWeaponPosition ============== */ static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { float scale; int delta; float fracsin; VectorCopy( cg.refdef.vieworg, origin ); VectorCopy( cg.refdefViewAngles, angles ); // on odd legs, invert some angles if ( cg.bobcycle & 1 ) { scale = -cg.xyspeed; } else { scale = cg.xyspeed; } // gun angles from bobbing angles[ROLL] += scale * cg.bobfracsin * 0.005; angles[YAW] += scale * cg.bobfracsin * 0.01; angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; // drop the weapon when landing delta = cg.time - cg.landTime; if ( delta < LAND_DEFLECT_TIME ) { origin[2] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { origin[2] += cg.landChange*0.25 * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; } #if 0 // drop the weapon when stair climbing delta = cg.time - cg.stepTime; if ( delta < STEP_TIME/2 ) { origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2); } else if ( delta < STEP_TIME ) { origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2); } #endif // idle drift scale = cg.xyspeed + 40; fracsin = sin( cg.time * 0.001 ); angles[ROLL] += scale * fracsin * 0.01; angles[YAW] += scale * fracsin * 0.01; angles[PITCH] += scale * fracsin * 0.01; } /* =============== CG_LightningBolt Origin will be the exact tag point, which is slightly different than the muzzle point used for determining hits. The cent should be the non-predicted cent if it is from the player, so the endpoint will reflect the simulated strike (lagging the predicted angle) =============== */ static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { trace_t trace; refEntity_t beam; vec3_t forward; vec3_t muzzlePoint, endPoint; if (cent->currentState.weapon != WP_LIGHTNING) { return; } memset( &beam, 0, sizeof( beam ) ); // CPMA "true" lightning if ((cent->currentState.number == cg.predictedPlayerState.clientNum) && (cg_trueLightning.value != 0)) { vec3_t angle; int i; for (i = 0; i < 3; i++) { float a = cent->lerpAngles[i] - cg.refdefViewAngles[i]; if (a > 180) { a -= 360; } if (a < -180) { a += 360; } angle[i] = cg.refdefViewAngles[i] + a * (1.0 - cg_trueLightning.value); if (angle[i] < 0) { angle[i] += 360; } if (angle[i] > 360) { angle[i] -= 360; } } AngleVectors(angle, forward, NULL, NULL ); VectorCopy(cent->lerpOrigin, muzzlePoint ); // VectorCopy(cg.refdef.vieworg, muzzlePoint ); } else { // !CPMA AngleVectors( cent->lerpAngles, forward, NULL, NULL ); VectorCopy(cent->lerpOrigin, muzzlePoint ); } // FIXME: crouch muzzlePoint[2] += DEFAULT_VIEWHEIGHT; VectorMA( muzzlePoint, 14, forward, muzzlePoint ); // project forward by the lightning range VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); // see if it hit a wall CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, cent->currentState.number, MASK_SHOT ); // this is the endpoint VectorCopy( trace.endpos, beam.oldorigin ); // use the provided origin, even though it may be slightly // different than the muzzle origin VectorCopy( origin, beam.origin ); beam.reType = RT_LIGHTNING; beam.customShader = cgs.media.lightningShader; trap_R_AddRefEntityToScene( &beam ); // add the impact flare if it hit something if ( trace.fraction < 1.0 ) { vec3_t angles; vec3_t dir; VectorSubtract( beam.oldorigin, beam.origin, dir ); VectorNormalize( dir ); memset( &beam, 0, sizeof( beam ) ); beam.hModel = cgs.media.lightningExplosionModel; VectorMA( trace.endpos, -16, dir, beam.origin ); // make a random orientation angles[0] = rand() % 360; angles[1] = rand() % 360; angles[2] = rand() % 360; AnglesToAxis( angles, beam.axis ); trap_R_AddRefEntityToScene( &beam ); } } /* static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { trace_t trace; refEntity_t beam; vec3_t forward; vec3_t muzzlePoint, endPoint; if ( cent->currentState.weapon != WP_LIGHTNING ) { return; } memset( &beam, 0, sizeof( beam ) ); // find muzzle point for this frame VectorCopy( cent->lerpOrigin, muzzlePoint ); AngleVectors( cent->lerpAngles, forward, NULL, NULL ); // FIXME: crouch muzzlePoint[2] += DEFAULT_VIEWHEIGHT; VectorMA( muzzlePoint, 14, forward, muzzlePoint ); // project forward by the lightning range VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); // see if it hit a wall CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, cent->currentState.number, MASK_SHOT ); // this is the endpoint VectorCopy( trace.endpos, beam.oldorigin ); // use the provided origin, even though it may be slightly // different than the muzzle origin VectorCopy( origin, beam.origin ); beam.reType = RT_LIGHTNING; beam.customShader = cgs.media.lightningShader; trap_R_AddRefEntityToScene( &beam ); // add the impact flare if it hit something if ( trace.fraction < 1.0 ) { vec3_t angles; vec3_t dir; VectorSubtract( beam.oldorigin, beam.origin, dir ); VectorNormalize( dir ); memset( &beam, 0, sizeof( beam ) ); beam.hModel = cgs.media.lightningExplosionModel; VectorMA( trace.endpos, -16, dir, beam.origin ); // make a random orientation angles[0] = rand() % 360; angles[1] = rand() % 360; angles[2] = rand() % 360; AnglesToAxis( angles, beam.axis ); trap_R_AddRefEntityToScene( &beam ); } } */ /* =============== CG_SpawnRailTrail Origin will be the exact tag point, which is slightly different than the muzzle point used for determining hits. =============== */ static void CG_SpawnRailTrail( centity_t *cent, vec3_t origin ) { clientInfo_t *ci; if ( cent->currentState.weapon != WP_RAILGUN ) { return; } if ( !cent->pe.railgunFlash ) { return; } cent->pe.railgunFlash = qtrue; ci = &cgs.clientinfo[ cent->currentState.clientNum ]; CG_RailTrail( ci, origin, cent->pe.railgunImpact ); } /* ====================== CG_MachinegunSpinAngle ====================== */ #define SPIN_SPEED 0.9 #define COAST_TIME 1000 static float CG_MachinegunSpinAngle( centity_t *cent ) { int delta; float angle; float speed; delta = cg.time - cent->pe.barrelTime; if ( cent->pe.barrelSpinning ) { angle = cent->pe.barrelAngle + delta * SPIN_SPEED; } else { if ( delta > COAST_TIME ) { delta = COAST_TIME; } speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); angle = cent->pe.barrelAngle + delta * speed; } if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) { cent->pe.barrelTime = cg.time; cent->pe.barrelAngle = AngleMod( angle ); cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); #ifdef MISSIONPACK if ( cent->currentState.weapon == WP_CHAINGUN && !cent->pe.barrelSpinning ) { trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, trap_S_RegisterSound( "sound/weapons/vulcan/wvulwind.wav", qfalse ) ); } #endif } return angle; } /* ======================== CG_AddWeaponWithPowerups ======================== */ static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { // add powerup effects if ( powerups & ( 1 << PW_INVIS ) ) { gun->customShader = cgs.media.invisShader; trap_R_AddRefEntityToScene( gun ); } else { trap_R_AddRefEntityToScene( gun ); if ( powerups & ( 1 << PW_BATTLESUIT ) ) { gun->customShader = cgs.media.battleWeaponShader; trap_R_AddRefEntityToScene( gun ); } if ( powerups & ( 1 << PW_QUAD ) ) { gun->customShader = cgs.media.quadWeaponShader; trap_R_AddRefEntityToScene( gun ); } } } /* ============= CG_AddPlayerWeapon Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) The main player will have this called for BOTH cases, so effects like light and sound should only be done on the world model case. ============= */ void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ) { refEntity_t gun; refEntity_t barrel; refEntity_t flash; vec3_t angles; weapon_t weaponNum; weaponInfo_t *weapon; centity_t *nonPredictedCent; // int col; weaponNum = cent->currentState.weapon; CG_RegisterWeapon( weaponNum ); weapon = &cg_weapons[weaponNum]; // add the weapon memset( &gun, 0, sizeof( gun ) ); VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); gun.shadowPlane = parent->shadowPlane; gun.renderfx = parent->renderfx; // set custom shading for railgun refire rate if ( ps ) { if ( cg.predictedPlayerState.weapon == WP_RAILGUN && cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) { float f; f = (float)cg.predictedPlayerState.weaponTime / 1500; gun.shaderRGBA[1] = 0; gun.shaderRGBA[0] = gun.shaderRGBA[2] = 255 * ( 1.0 - f ); } else { gun.shaderRGBA[0] = 255; gun.shaderRGBA[1] = 255; gun.shaderRGBA[2] = 255; gun.shaderRGBA[3] = 255; } } gun.hModel = weapon->weaponModel; if (!gun.hModel) { return; } if ( !ps ) { // add weapon ready sound cent->pe.lightningFiring = qfalse; if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) { // lightning gun and guantlet make a different sound when fire is held down trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); cent->pe.lightningFiring = qtrue; } else if ( weapon->readySound ) { trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); } } CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); // add the spinning barrel if ( weapon->barrelModel ) { memset( &barrel, 0, sizeof( barrel ) ); VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); barrel.shadowPlane = parent->shadowPlane; barrel.renderfx = parent->renderfx; barrel.hModel = weapon->barrelModel; angles[YAW] = 0; angles[PITCH] = 0; angles[ROLL] = CG_MachinegunSpinAngle( cent ); AnglesToAxis( angles, barrel.axis ); CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" ); CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); } // make sure we aren't looking at cg.predictedPlayerEntity for LG nonPredictedCent = &cg_entities[cent->currentState.clientNum]; // if the index of the nonPredictedCent is not the same as the clientNum // then this is a fake player (like on teh single player podiums), so // go ahead and use the cent if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { nonPredictedCent = cent; } // add the flash if ( ( weaponNum == WP_LIGHTNING || weaponNum == WP_GAUNTLET || weaponNum == WP_GRAPPLING_HOOK ) && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) { // continuous flash } else { // impulse flash if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME && !cent->pe.railgunFlash ) { return; } } memset( &flash, 0, sizeof( flash ) ); VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); flash.shadowPlane = parent->shadowPlane; flash.renderfx = parent->renderfx; flash.hModel = weapon->flashModel; if (!flash.hModel) { return; } angles[YAW] = 0; angles[PITCH] = 0; angles[ROLL] = crandom() * 10; AnglesToAxis( angles, flash.axis ); // colorize the railgun blast if ( weaponNum == WP_RAILGUN ) { clientInfo_t *ci; ci = &cgs.clientinfo[ cent->currentState.clientNum ]; flash.shaderRGBA[0] = 255 * ci->color1[0]; flash.shaderRGBA[1] = 255 * ci->color1[1]; flash.shaderRGBA[2] = 255 * ci->color1[2]; } CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash"); trap_R_AddRefEntityToScene( &flash ); if ( ps || cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum ) { // add lightning bolt CG_LightningBolt( nonPredictedCent, flash.origin ); // add rail trail CG_SpawnRailTrail( cent, flash.origin ); if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { trap_R_AddLightToScene( flash.origin, 300 + (rand()&31), weapon->flashDlightColor[0], weapon->flashDlightColor[1], weapon->flashDlightColor[2] ); } } } /* ============== CG_AddViewWeapon Add the weapon, and flash for the player's view ============== */ void CG_AddViewWeapon( playerState_t *ps ) { refEntity_t hand; centity_t *cent; clientInfo_t *ci; float fovOffset; vec3_t angles; weaponInfo_t *weapon; if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { return; } if ( ps->pm_type == PM_INTERMISSION ) { return; } // no gun if in third person view or a camera is active //if ( cg.renderingThirdPerson || cg.cameraMode) { if ( cg.renderingThirdPerson ) { return; } // allow the gun to be completely removed if ( !cg_drawGun.integer ) { vec3_t origin; if ( cg.predictedPlayerState.eFlags & EF_FIRING ) { // special hack for lightning gun... VectorCopy( cg.refdef.vieworg, origin ); VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); CG_LightningBolt( &cg_entities[ps->clientNum], origin ); } return; } // don't draw if testing a gun model if ( cg.testGun ) { return; } // drop gun lower at higher fov if ( cg_fov.integer > 90 ) { fovOffset = -0.2 * ( cg_fov.integer - 90 ); } else { fovOffset = 0; } cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; CG_RegisterWeapon( ps->weapon ); weapon = &cg_weapons[ ps->weapon ]; memset (&hand, 0, sizeof(hand)); // set up gun position CG_CalculateWeaponPosition( hand.origin, angles ); VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[0], hand.origin ); VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[1], hand.origin ); VectorMA( hand.origin, (cg_gun_z.value+fovOffset), cg.refdef.viewaxis[2], hand.origin ); AnglesToAxis( angles, hand.axis ); // map torso animations to weapon animations if ( cg_gun_frame.integer ) { // development tool hand.frame = hand.oldframe = cg_gun_frame.integer; hand.backlerp = 0; } else { // get clientinfo for animation map ci = &cgs.clientinfo[ cent->currentState.clientNum ]; hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame ); hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame ); hand.backlerp = cent->pe.torso.backlerp; } hand.hModel = weapon->handsModel; hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; // add everything onto the hand CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM] ); } /* ============================================================================== WEAPON SELECTION ============================================================================== */ /* =================== CG_DrawWeaponSelect =================== */ void CG_DrawWeaponSelect( void ) { int i; int bits; int count; int x, y, w; char *name; float *color; // don't display if dead if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { return; } color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); if ( !color ) { return; } trap_R_SetColor( color ); // showing weapon select clears pickup item display, but not the blend blob cg.itemPickupTime = 0; // count the number of weapons owned bits = cg.snap->ps.stats[ STAT_WEAPONS ]; count = 0; for ( i = 1 ; i < 16 ; i++ ) { if ( bits & ( 1 << i ) ) { count++; } } x = 320 - count * 20; y = 380; for ( i = 1 ; i < 16 ; i++ ) { if ( !( bits & ( 1 << i ) ) ) { continue; } CG_RegisterWeapon( i ); // draw weapon icon CG_DrawPic( x, y, 32, 32, cg_weapons[i].weaponIcon ); // draw selection marker if ( i == cg.weaponSelect ) { CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader ); } // no ammo cross on top if ( !cg.snap->ps.ammo[ i ] ) { CG_DrawPic( x, y, 32, 32, cgs.media.noammoShader ); } x += 40; } // draw the selected name if ( cg_weapons[ cg.weaponSelect ].item ) { name = cg_weapons[ cg.weaponSelect ].item->pickup_name; if ( name ) { w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; x = ( SCREEN_WIDTH - w ) / 2; CG_DrawBigStringColor(x, y - 22, name, color); } } trap_R_SetColor( NULL ); } /* =============== CG_WeaponSelectable =============== */ static qboolean CG_WeaponSelectable( int i ) { if ( !cg.snap->ps.ammo[i] ) { return qfalse; } if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { return qfalse; } return qtrue; } /* =============== CG_NextWeapon_f =============== */ void CG_NextWeapon_f( void ) { int i; int original; if ( !cg.snap ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } cg.weaponSelectTime = cg.time; original = cg.weaponSelect; for ( i = 0 ; i < 16 ; i++ ) { cg.weaponSelect++; if ( cg.weaponSelect == 16 ) { cg.weaponSelect = 0; } if ( cg.weaponSelect == WP_GAUNTLET ) { continue; // never cycle to gauntlet } if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } } if ( i == 16 ) { cg.weaponSelect = original; } } /* =============== CG_PrevWeapon_f =============== */ void CG_PrevWeapon_f( void ) { int i; int original; if ( !cg.snap ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } cg.weaponSelectTime = cg.time; original = cg.weaponSelect; for ( i = 0 ; i < 16 ; i++ ) { cg.weaponSelect--; if ( cg.weaponSelect == -1 ) { cg.weaponSelect = 15; } if ( cg.weaponSelect == WP_GAUNTLET ) { continue; // never cycle to gauntlet } if ( CG_WeaponSelectable( cg.weaponSelect ) ) { break; } } if ( i == 16 ) { cg.weaponSelect = original; } } /* =============== CG_Weapon_f =============== */ void CG_Weapon_f( void ) { int num; if ( !cg.snap ) { return; } if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { return; } num = atoi( CG_Argv( 1 ) ); if ( num < 1 || num > 15 ) { return; } cg.weaponSelectTime = cg.time; if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) { return; // don't have the weapon } cg.weaponSelect = num; } /* =================== CG_OutOfAmmoChange The current weapon has just run out of ammo =================== */ void CG_OutOfAmmoChange( void ) { int i; cg.weaponSelectTime = cg.time; for ( i = 15 ; i > 0 ; i-- ) { if ( CG_WeaponSelectable( i ) ) { cg.weaponSelect = i; break; } } } /* =================================================================================================== WEAPON EVENTS =================================================================================================== */ /* ================ CG_FireWeapon Caused by an EV_FIRE_WEAPON event ================ */ void CG_FireWeapon( centity_t *cent ) { entityState_t *ent; int c; weaponInfo_t *weap; ent = ¢->currentState; if ( ent->weapon == WP_NONE ) { return; } if ( ent->weapon >= WP_NUM_WEAPONS ) { CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); return; } weap = &cg_weapons[ ent->weapon ]; // mark the entity as muzzle flashing, so when it is added it will // append the flash to the weapon model cent->muzzleFlashTime = cg.time; // lightning gun only does this this on initial press if ( ent->weapon == WP_LIGHTNING ) { if ( cent->pe.lightningFiring ) { return; } } // play quad sound if needed if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); } // play a sound for ( c = 0 ; c < 4 ; c++ ) { if ( !weap->flashSound[c] ) { break; } } if ( c > 0 ) { c = rand() % c; if ( weap->flashSound[c] ) { trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] ); } } // do brass ejection if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) { weap->ejectBrassFunc( cent ); } } /* ================= CG_MissileHitWall Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing ================= */ void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ) { qhandle_t mod; qhandle_t mark; qhandle_t shader; sfxHandle_t sfx; float radius; float light; vec3_t lightColor; localEntity_t *le; int r; qboolean alphaFade; qboolean isSprite; int duration; vec3_t sprOrg; vec3_t sprVel; mark = 0; radius = 32; sfx = 0; mod = 0; shader = 0; light = 0; lightColor[0] = 1; lightColor[1] = 1; lightColor[2] = 0; // set defaults isSprite = qfalse; duration = 600; switch ( weapon ) { default: #ifdef MISSIONPACK case WP_NAILGUN: if( soundType == IMPACTSOUND_FLESH ) { sfx = cgs.media.sfx_nghitflesh; } else if( soundType == IMPACTSOUND_METAL ) { sfx = cgs.media.sfx_nghitmetal; } else { sfx = cgs.media.sfx_nghit; } mark = cgs.media.holeMarkShader; radius = 12; break; #endif case WP_LIGHTNING: // no explosion at LG impact, it is added with the beam r = rand() & 3; if ( r < 2 ) { sfx = cgs.media.sfx_lghit2; } else if ( r == 2 ) { sfx = cgs.media.sfx_lghit1; } else { sfx = cgs.media.sfx_lghit3; } mark = cgs.media.holeMarkShader; radius = 12; break; #ifdef MISSIONPACK case WP_PROX_LAUNCHER: mod = cgs.media.dishFlashModel; shader = cgs.media.grenadeExplosionShader; sfx = cgs.media.sfx_proxexp; mark = cgs.media.burnMarkShader; radius = 64; light = 300; isSprite = qtrue; break; #endif case WP_GRENADE_LAUNCHER: mod = cgs.media.dishFlashModel; shader = cgs.media.grenadeExplosionShader; sfx = cgs.media.sfx_rockexp; mark = cgs.media.burnMarkShader; radius = 64; light = 300; isSprite = qtrue; break; case WP_ROCKET_LAUNCHER: mod = cgs.media.dishFlashModel; shader = cgs.media.rocketExplosionShader; sfx = cgs.media.sfx_rockexp; mark = cgs.media.burnMarkShader; radius = 64; light = 300; isSprite = qtrue; duration = 1000; lightColor[0] = 1; lightColor[1] = 0.75; lightColor[2] = 0.0; if (cg_oldRocket.integer == 0) { // explosion sprite animation VectorMA( origin, 24, dir, sprOrg ); VectorScale( dir, 64, sprVel ); CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 20, 30 ); } break; case WP_RAILGUN: mod = cgs.media.ringFlashModel; shader = cgs.media.railExplosionShader; sfx = cgs.media.sfx_plasmaexp; mark = cgs.media.energyMarkShader; radius = 24; break; case WP_PLASMAGUN: mod = cgs.media.ringFlashModel; shader = cgs.media.plasmaExplosionShader; sfx = cgs.media.sfx_plasmaexp; mark = cgs.media.energyMarkShader; radius = 16; break; case WP_BFG: mod = cgs.media.dishFlashModel; shader = cgs.media.bfgExplosionShader; sfx = cgs.media.sfx_rockexp; mark = cgs.media.burnMarkShader; radius = 32; isSprite = qtrue; break; case WP_SHOTGUN: mod = cgs.media.bulletFlashModel; shader = cgs.media.bulletExplosionShader; mark = cgs.media.bulletMarkShader; sfx = 0; radius = 4; break; #ifdef MISSIONPACK case WP_CHAINGUN: mod = cgs.media.bulletFlashModel; if( soundType == IMPACTSOUND_FLESH ) { sfx = cgs.media.sfx_chghitflesh; } else if( soundType == IMPACTSOUND_METAL ) { sfx = cgs.media.sfx_chghitmetal; } else { sfx = cgs.media.sfx_chghit; } mark = cgs.media.bulletMarkShader; r = rand() & 3; if ( r < 2 ) { sfx = cgs.media.sfx_ric1; } else if ( r == 2 ) { sfx = cgs.media.sfx_ric2; } else { sfx = cgs.media.sfx_ric3; } radius = 8; break; #endif case WP_MACHINEGUN: mod = cgs.media.bulletFlashModel; shader = cgs.media.bulletExplosionShader; mark = cgs.media.bulletMarkShader; r = rand() & 3; if ( r == 0 ) { sfx = cgs.media.sfx_ric1; } else if ( r == 1 ) { sfx = cgs.media.sfx_ric2; } else { sfx = cgs.media.sfx_ric3; } radius = 8; break; } if ( sfx ) { trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); } // // create the explosion // if ( mod ) { le = CG_MakeExplosion( origin, dir, mod, shader, duration, isSprite ); le->light = light; VectorCopy( lightColor, le->lightColor ); if ( weapon == WP_RAILGUN ) { // colorize with client color VectorCopy( cgs.clientinfo[clientNum].color1, le->color ); } } // // impact mark // alphaFade = (mark == cgs.media.energyMarkShader); // plasma fades alpha, all others fade color if ( weapon == WP_RAILGUN ) { float *color; // colorize with client color color = cgs.clientinfo[clientNum].color2; CG_ImpactMark( mark, origin, dir, random()*360, color[0],color[1], color[2],1, alphaFade, radius, qfalse ); } else { CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse ); } } /* ================= CG_MissileHitPlayer ================= */ void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ) { CG_Bleed( origin, entityNum ); // some weapons will make an explosion with the blood, while // others will just make the blood switch ( weapon ) { case WP_GRENADE_LAUNCHER: case WP_ROCKET_LAUNCHER: #ifdef MISSIONPACK case WP_NAILGUN: case WP_CHAINGUN: case WP_PROX_LAUNCHER: #endif CG_MissileHitWall( weapon, 0, origin, dir, IMPACTSOUND_FLESH ); break; default: break; } } /* ============================================================================ SHOTGUN TRACING ============================================================================ */ /* ================ CG_ShotgunPellet ================ */ static void CG_ShotgunPellet( vec3_t start, vec3_t end, int skipNum ) { trace_t tr; int sourceContentType, destContentType; CG_Trace( &tr, start, NULL, NULL, end, skipNum, MASK_SHOT ); sourceContentType = trap_CM_PointContents( start, 0 ); destContentType = trap_CM_PointContents( tr.endpos, 0 ); // FIXME: should probably move this cruft into CG_BubbleTrail if ( sourceContentType == destContentType ) { if ( sourceContentType & CONTENTS_WATER ) { CG_BubbleTrail( start, tr.endpos, 32 ); } } else if ( sourceContentType & CONTENTS_WATER ) { trace_t trace; trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); CG_BubbleTrail( start, trace.endpos, 32 ); } else if ( destContentType & CONTENTS_WATER ) { trace_t trace; trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); CG_BubbleTrail( tr.endpos, trace.endpos, 32 ); } if ( tr.surfaceFlags & SURF_NOIMPACT ) { return; } if ( cg_entities[tr.entityNum].currentState.eType == ET_PLAYER ) { CG_MissileHitPlayer( WP_SHOTGUN, tr.endpos, tr.plane.normal, tr.entityNum ); } else { if ( tr.surfaceFlags & SURF_NOIMPACT ) { // SURF_NOIMPACT will not make a flame puff or a mark return; } if ( tr.surfaceFlags & SURF_METALSTEPS ) { CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL ); } else { CG_MissileHitWall( WP_SHOTGUN, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT ); } } } /* ================ CG_ShotgunPattern Perform the same traces the server did to locate the hit splashes ================ */ static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum ) { int i; float r, u; vec3_t end; vec3_t forward, right, up; // derive the right and up vectors from the forward vector, because // the client won't have any other information VectorNormalize2( origin2, forward ); PerpendicularVector( right, forward ); CrossProduct( forward, right, up ); // generate the "random" spread pattern for ( i = 0 ; i < DEFAULT_SHOTGUN_COUNT ; i++ ) { r = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; u = Q_crandom( &seed ) * DEFAULT_SHOTGUN_SPREAD * 16; VectorMA( origin, 8192 * 16, forward, end); VectorMA (end, r, right, end); VectorMA (end, u, up, end); CG_ShotgunPellet( origin, end, otherEntNum ); } } /* ============== CG_ShotgunFire ============== */ void CG_ShotgunFire( entityState_t *es ) { vec3_t v; int contents; VectorSubtract( es->origin2, es->pos.trBase, v ); VectorNormalize( v ); VectorScale( v, 32, v ); VectorAdd( es->pos.trBase, v, v ); if ( cgs.glconfig.hardwareType != GLHW_RAGEPRO ) { // ragepro can't alpha fade, so don't even bother with smoke vec3_t up; contents = trap_CM_PointContents( es->pos.trBase, 0 ); if ( !( contents & CONTENTS_WATER ) ) { VectorSet( up, 0, 0, 8 ); CG_SmokePuff( v, up, 32, 1, 1, 1, 0.33f, 900, cg.time, 0, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); } } CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum ); } /* ============================================================================ BULLETS ============================================================================ */ /* =============== CG_Tracer =============== */ void CG_Tracer( vec3_t source, vec3_t dest ) { vec3_t forward, right; polyVert_t verts[4]; vec3_t line; float len, begin, end; vec3_t start, finish; vec3_t midpoint; // tracer VectorSubtract( dest, source, forward ); len = VectorNormalize( forward ); // start at least a little ways from the muzzle if ( len < 100 ) { return; } begin = 50 + random() * (len - 60); end = begin + cg_tracerLength.value; if ( end > len ) { end = len; } VectorMA( source, begin, forward, start ); VectorMA( source, end, forward, finish ); line[0] = DotProduct( forward, cg.refdef.viewaxis[1] ); line[1] = DotProduct( forward, cg.refdef.viewaxis[2] ); VectorScale( cg.refdef.viewaxis[1], line[1], right ); VectorMA( right, -line[0], cg.refdef.viewaxis[2], right ); VectorNormalize( right ); VectorMA( finish, cg_tracerWidth.value, right, verts[0].xyz ); verts[0].st[0] = 0; verts[0].st[1] = 1; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = 255; VectorMA( finish, -cg_tracerWidth.value, right, verts[1].xyz ); verts[1].st[0] = 1; verts[1].st[1] = 0; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = 255; VectorMA( start, -cg_tracerWidth.value, right, verts[2].xyz ); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = 255; VectorMA( start, cg_tracerWidth.value, right, verts[3].xyz ); verts[3].st[0] = 0; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = 255; trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts ); midpoint[0] = ( start[0] + finish[0] ) * 0.5; midpoint[1] = ( start[1] + finish[1] ) * 0.5; midpoint[2] = ( start[2] + finish[2] ) * 0.5; // add the tracer sound trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound ); } /* ====================== CG_CalcMuzzlePoint ====================== */ static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { vec3_t forward; centity_t *cent; int anim; if ( entityNum == cg.snap->ps.clientNum ) { VectorCopy( cg.snap->ps.origin, muzzle ); muzzle[2] += cg.snap->ps.viewheight; AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); VectorMA( muzzle, 14, forward, muzzle ); return qtrue; } cent = &cg_entities[entityNum]; if ( !cent->currentValid ) { return qfalse; } VectorCopy( cent->currentState.pos.trBase, muzzle ); AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; if ( anim == LEGS_WALKCR || anim == LEGS_IDLECR ) { muzzle[2] += CROUCH_VIEWHEIGHT; } else { muzzle[2] += DEFAULT_VIEWHEIGHT; } VectorMA( muzzle, 14, forward, muzzle ); return qtrue; } /* ====================== CG_Bullet Renders bullet effects. ====================== */ void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ) { trace_t trace; int sourceContentType, destContentType; vec3_t start; // if the shooter is currently valid, calc a source point and possibly // do trail effects if ( sourceEntityNum >= 0 && cg_tracerChance.value > 0 ) { if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) { sourceContentType = trap_CM_PointContents( start, 0 ); destContentType = trap_CM_PointContents( end, 0 ); // do a complete bubble trail if necessary if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) { CG_BubbleTrail( start, end, 32 ); } // bubble trail from water into air else if ( ( sourceContentType & CONTENTS_WATER ) ) { trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); CG_BubbleTrail( start, trace.endpos, 32 ); } // bubble trail from air into water else if ( ( destContentType & CONTENTS_WATER ) ) { trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); CG_BubbleTrail( trace.endpos, end, 32 ); } // draw a tracer if ( random() < cg_tracerChance.value ) { CG_Tracer( start, end ); } } } // impact splash and mark if ( flesh ) { CG_Bleed( end, fleshEntityNum ); } else { CG_MissileHitWall( WP_MACHINEGUN, 0, end, normal, IMPACTSOUND_DEFAULT ); } } ================================================ FILE: code/cgame/cgame.bat ================================================ rem make sure we have a safe environement set LIBRARY= set INCLUDE= mkdir vm cd vm set cc=lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 %cc% ../../game/bg_misc.c @if errorlevel 1 goto quit %cc% ../../game/bg_pmove.c @if errorlevel 1 goto quit %cc% ../../game/bg_slidemove.c @if errorlevel 1 goto quit %cc% ../../game/bg_lib.c @if errorlevel 1 goto quit %cc% ../../game/q_math.c @if errorlevel 1 goto quit %cc% ../../game/q_shared.c @if errorlevel 1 goto quit %cc% ../cg_consolecmds.c @if errorlevel 1 goto quit %cc% ../cg_draw.c @if errorlevel 1 goto quit %cc% ../cg_drawtools.c @if errorlevel 1 goto quit %cc% ../cg_effects.c @if errorlevel 1 goto quit %cc% ../cg_ents.c @if errorlevel 1 goto quit %cc% ../cg_event.c @if errorlevel 1 goto quit %cc% ../cg_info.c @if errorlevel 1 goto quit %cc% ../cg_localents.c @if errorlevel 1 goto quit %cc% ../cg_main.c @if errorlevel 1 goto quit %cc% ../cg_marks.c @if errorlevel 1 goto quit %cc% ../cg_players.c @if errorlevel 1 goto quit %cc% ../cg_playerstate.c @if errorlevel 1 goto quit %cc% ../cg_predict.c @if errorlevel 1 goto quit %cc% ../cg_scoreboard.c @if errorlevel 1 goto quit %cc% ../cg_servercmds.c @if errorlevel 1 goto quit %cc% ../cg_snapshot.c @if errorlevel 1 goto quit %cc% ../cg_view.c @if errorlevel 1 goto quit %cc% ../cg_weapons.c @if errorlevel 1 goto quit q3asm -f ../cgame :quit cd .. ================================================ FILE: code/cgame/cgame.def ================================================ EXPORTS vmMain dllEntry ================================================ FILE: code/cgame/cgame.plg ================================================

Build Log

--------------------Configuration: cgame - Win32 Release--------------------

Command Lines

Creating temporary file "C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BE.tmp" with contents [ /nologo /G6 /ML /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /Fp"Release/cgame.pch" /YX /Fo"Release/" /Fd"Release/" /FD /c "D:\quake3\MissionPack\code\game\bg_misc.c" "D:\quake3\MissionPack\code\game\bg_pmove.c" "D:\quake3\MissionPack\code\game\bg_slidemove.c" "D:\quake3\MissionPack\code\cgame\cg_consolecmds.c" "D:\quake3\MissionPack\code\cgame\cg_draw.c" "D:\quake3\MissionPack\code\cgame\cg_drawtools.c" "D:\quake3\MissionPack\code\cgame\cg_effects.c" "D:\quake3\MissionPack\code\cgame\cg_ents.c" "D:\quake3\MissionPack\code\cgame\cg_event.c" "D:\quake3\MissionPack\code\cgame\cg_info.c" "D:\quake3\MissionPack\code\cgame\cg_localents.c" "D:\quake3\MissionPack\code\cgame\cg_main.c" "D:\quake3\MissionPack\code\cgame\cg_marks.c" "D:\quake3\MissionPack\code\cgame\cg_players.c" "D:\quake3\MissionPack\code\cgame\cg_playerstate.c" "D:\quake3\MissionPack\code\cgame\cg_predict.c" "D:\quake3\MissionPack\code\cgame\cg_rankings.c" "D:\quake3\MissionPack\code\cgame\cg_scoreboard.c" "D:\quake3\MissionPack\code\cgame\cg_servercmds.c" "D:\quake3\MissionPack\code\cgame\cg_snapshot.c" "D:\quake3\MissionPack\code\cgame\cg_syscalls.c" "D:\quake3\MissionPack\code\cgame\cg_view.c" "D:\quake3\MissionPack\code\cgame\cg_weapons.c" "D:\quake3\MissionPack\code\game\q_math.c" "D:\quake3\MissionPack\code\game\q_shared.c" "D:\quake3\MissionPack\code\ui\ui_shared.c" ] Creating command line "cl.exe @C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BE.tmp" Creating temporary file "C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BF.tmp" with contents [ /nologo /base:"0x30000000" /subsystem:windows /dll /incremental:no /pdb:"Release/cgamex86.pdb" /map:"Release/cgamex86.map" /machine:I386 /def:".\cgame.def" /out:"../Release/cgamex86.dll" /implib:"Release/cgamex86.lib" .\Release\bg_misc.obj .\Release\bg_pmove.obj .\Release\bg_slidemove.obj .\Release\cg_consolecmds.obj .\Release\cg_draw.obj .\Release\cg_drawtools.obj .\Release\cg_effects.obj .\Release\cg_ents.obj .\Release\cg_event.obj .\Release\cg_info.obj .\Release\cg_localents.obj .\Release\cg_main.obj .\Release\cg_marks.obj .\Release\cg_players.obj .\Release\cg_playerstate.obj .\Release\cg_predict.obj .\Release\cg_rankings.obj .\Release\cg_scoreboard.obj .\Release\cg_servercmds.obj .\Release\cg_snapshot.obj .\Release\cg_syscalls.obj .\Release\cg_view.obj .\Release\cg_weapons.obj .\Release\q_math.obj .\Release\q_shared.obj .\Release\ui_shared.obj ] Creating command line "link.exe @C:\WINNT\Profiles\ADMINI~1\LOCALS~1\Temp\RSP4BF.tmp"

Output Window

Compiling... bg_misc.c bg_pmove.c D:\quake3\MissionPack\code\game\bg_pmove.c(987) : warning C4189: 'shit' : local variable is initialized but not referenced D:\quake3\MissionPack\code\game\bg_pmove.c(2001) : warning C4505: 'PM_InvulnerabilityMove' : unreferenced local function has been removed D:\quake3\MissionPack\code\game\bg_pmove.c(519) : see declaration of 'PM_InvulnerabilityMove' bg_slidemove.c cg_consolecmds.c cg_draw.c cg_drawtools.c cg_effects.c cg_ents.c cg_event.c cg_info.c cg_localents.c cg_main.c D:\quake3\MissionPack\code\cgame\cg_main.c(1819) : warning C4505: 'CG_Cvar_Get' : unreferenced local function has been removed D:\quake3\MissionPack\code\cgame\cg_main.c(1513) : see declaration of 'CG_Cvar_Get' cg_marks.c cg_players.c D:\quake3\MissionPack\code\cgame\cg_players.c(2209) : warning C4505: 'CG_PlayerTokens' : unreferenced local function has been removed D:\quake3\MissionPack\code\cgame\cg_players.c(1371) : see declaration of 'CG_PlayerTokens' cg_playerstate.c cg_predict.c cg_rankings.c cg_scoreboard.c cg_servercmds.c cg_snapshot.c cg_syscalls.c cg_view.c cg_weapons.c q_math.c q_shared.c ui_shared.c D:\quake3\MissionPack\code\ui\ui_shared.c(2223) : warning C4189: 'parent' : local variable is initialized but not referenced D:\quake3\MissionPack\code\ui\ui_shared.c(3501) : warning C4189: 'collision' : local variable is initialized but not referenced D:\quake3\MissionPack\code\ui\ui_shared.c(4622) : warning C4505: 'Controls_SetDefaults' : unreferenced local function has been removed D:\quake3\MissionPack\code\ui\ui_shared.c(2595) : see declaration of 'Controls_SetDefaults' D:\quake3\MissionPack\code\ui\ui_shared.c(1540) : warning C4701: local variable 'value' may be used without having been initialized D:\quake3\MissionPack\code\ui\ui_shared.c(1566) : warning C4701: local variable 'value' may be used without having been initialized D:\quake3\MissionPack\code\ui\ui_shared.c(1912) : warning C4702: unreachable code D:\quake3\MissionPack\code\ui\ui_shared.c(4013) : warning C4702: unreachable code D:\quake3\MissionPack\code\ui\ui_shared.c(4056) : warning C4702: unreachable code Linking... Creating library Release/cgamex86.lib and object Release/cgamex86.exp

Results

cgamex86.dll - 0 error(s), 12 warning(s)
================================================ FILE: code/cgame/cgame.q3asm ================================================ -o "\quake3\baseq3\vm\cgame" cg_main ..\cg_syscalls cg_consolecmds cg_draw cg_drawtools cg_effects cg_ents cg_event cg_info cg_localents cg_marks cg_players cg_playerstate cg_predict cg_scoreboard cg_servercmds cg_snapshot cg_view cg_weapons bg_slidemove bg_pmove bg_lib bg_misc q_math q_shared ================================================ FILE: code/cgame/cgame.sh ================================================ #!/bin/sh mkdir -p vm cd vm CC="q3lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I../../cgame -I../../game -I../../q3_ui" $CC ../cg_syscalls.c $CC ../../game/bg_misc.c $CC ../../game/bg_pmove.c $CC ../../game/bg_slidemove.c $CC ../../game/bg_lib.c $CC ../../game/q_math.c $CC ../../game/q_shared.c $CC ../cg_consolecmds.c $CC ../cg_draw.c $CC ../cg_drawtools.c $CC ../cg_effects.c $CC ../cg_ents.c $CC ../cg_event.c $CC ../cg_info.c $CC ../cg_localents.c $CC ../cg_main.c $CC ../cg_marks.c $CC ../cg_players.c $CC ../cg_playerstate.c $CC ../cg_predict.c $CC ../cg_scoreboard.c $CC ../cg_servercmds.c $CC ../cg_snapshot.c $CC ../cg_view.c $CC ../cg_weapons.c q3asm -f ../cgame cd .. ================================================ FILE: code/cgame/cgame.vcproj ================================================ ================================================ FILE: code/cgame/cgame_ta.bat ================================================ rem make sure we have a safe environement set LIBRARY= set INCLUDE= mkdir vm cd vm set cc=lcc -DQ3_VM -DMISSIONPACK -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 %cc% ../../game/bg_misc.c @if errorlevel 1 goto quit %cc% ../../game/bg_pmove.c @if errorlevel 1 goto quit %cc% ../../game/bg_slidemove.c @if errorlevel 1 goto quit %cc% ../../game/bg_lib.c @if errorlevel 1 goto quit %cc% ../../game/q_math.c @if errorlevel 1 goto quit %cc% ../../game/q_shared.c @if errorlevel 1 goto quit %cc% ../cg_consolecmds.c @if errorlevel 1 goto quit %cc% ../cg_draw.c @if errorlevel 1 goto quit %cc% ../cg_drawtools.c @if errorlevel 1 goto quit %cc% ../cg_effects.c @if errorlevel 1 goto quit %cc% ../cg_ents.c @if errorlevel 1 goto quit %cc% ../cg_event.c @if errorlevel 1 goto quit %cc% ../cg_info.c @if errorlevel 1 goto quit %cc% ../cg_localents.c @if errorlevel 1 goto quit %cc% ../cg_main.c @if errorlevel 1 goto quit %cc% ../cg_marks.c @if errorlevel 1 goto quit %cc% ../cg_players.c @if errorlevel 1 goto quit %cc% ../cg_playerstate.c @if errorlevel 1 goto quit %cc% ../cg_predict.c @if errorlevel 1 goto quit %cc% ../cg_scoreboard.c @if errorlevel 1 goto quit %cc% ../cg_servercmds.c @if errorlevel 1 goto quit %cc% ../cg_snapshot.c @if errorlevel 1 goto quit %cc% ../cg_view.c @if errorlevel 1 goto quit %cc% ../cg_weapons.c @if errorlevel 1 goto quit %cc% ../../ui/ui_shared.c @if errorlevel 1 goto quit %cc% ../cg_newdraw.c @if errorlevel 1 goto quit q3asm -f ../cgame_ta :quit cd .. ================================================ FILE: code/cgame/cgame_ta.q3asm ================================================ -o "\quake3\missionpack\vm\cgame" cg_main ..\cg_syscalls cg_consolecmds cg_draw cg_drawtools cg_effects cg_ents cg_event cg_info cg_localents cg_marks cg_players cg_playerstate cg_predict cg_scoreboard cg_servercmds cg_snapshot cg_view cg_weapons bg_slidemove bg_pmove bg_lib bg_misc q_math q_shared ui_shared cg_newdraw ================================================ FILE: code/cgame/cgame_ta.sh ================================================ #!/bin/sh mkdir -p vm cd vm CC="q3lcc -DQ3_VM -DCGAME -DMISSIONPACK -S -Wf-target=bytecode -Wf-g -I../../cgame -I../../game -I../../ui" $CC ../cg_syscalls.c $CC ../../game/bg_misc.c $CC ../../game/bg_pmove.c $CC ../../game/bg_slidemove.c $CC ../../game/bg_lib.c $CC ../../game/q_math.c $CC ../../game/q_shared.c $CC ../cg_consolecmds.c $CC ../cg_draw.c $CC ../cg_drawtools.c $CC ../cg_effects.c $CC ../cg_ents.c $CC ../cg_event.c $CC ../cg_info.c $CC ../cg_localents.c $CC ../cg_main.c $CC ../cg_marks.c $CC ../cg_players.c $CC ../cg_playerstate.c $CC ../cg_predict.c $CC ../cg_scoreboard.c $CC ../cg_servercmds.c $CC ../cg_snapshot.c $CC ../cg_view.c $CC ../cg_weapons.c $CC ../../ui/ui_shared.c $CC ../cg_newdraw.c q3asm -f ../cgame_ta cd .. ================================================ FILE: code/cgame/tr_types.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // #ifndef __TR_TYPES_H #define __TR_TYPES_H #define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces #define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing // renderfx flags #define RF_MINLIGHT 1 // allways have some light (viewmodel, some items) #define RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites) #define RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob) #define RF_DEPTHHACK 8 // for view weapon Z crunching #define RF_NOSHADOW 64 // don't add stencil shadows #define RF_LIGHTING_ORIGIN 128 // use refEntity->lightingOrigin instead of refEntity->origin // for lighting. This allows entities to sink into the floor // with their origin going solid, and allows all parts of a // player to get the same lighting #define RF_SHADOW_PLANE 256 // use refEntity->shadowPlane #define RF_WRAP_FRAMES 512 // mod the model frames by the maxframes to allow continuous // animation without needing to know the frame count // refdef flags #define RDF_NOWORLDMODEL 1 // used for player configuration screen #define RDF_HYPERSPACE 4 // teleportation effect typedef struct { vec3_t xyz; float st[2]; byte modulate[4]; } polyVert_t; typedef struct poly_s { qhandle_t hShader; int numVerts; polyVert_t *verts; } poly_t; typedef enum { RT_MODEL, RT_POLY, RT_SPRITE, RT_BEAM, RT_RAIL_CORE, RT_RAIL_RINGS, RT_LIGHTNING, RT_PORTALSURFACE, // doesn't draw anything, just info for portals RT_MAX_REF_ENTITY_TYPE } refEntityType_t; typedef struct { refEntityType_t reType; int renderfx; qhandle_t hModel; // opaque type outside refresh // most recent data vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) float shadowPlane; // projection shadows go here, stencils go slightly lower vec3_t axis[3]; // rotation vectors qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale float origin[3]; // also used as MODEL_BEAM's "from" int frame; // also used as MODEL_BEAM's diameter // previous data for frame interpolation float oldorigin[3]; // also used as MODEL_BEAM's "to" int oldframe; float backlerp; // 0.0 = current, 1.0 = old // texturing int skinNum; // inline skin index qhandle_t customSkin; // NULL for default skin qhandle_t customShader; // use one image for the entire thing // misc byte shaderRGBA[4]; // colors used by rgbgen entity shaders float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers float shaderTime; // subtracted from refdef time to control effect start times // extra sprite information float radius; float rotation; } refEntity_t; #define MAX_RENDER_STRINGS 8 #define MAX_RENDER_STRING_LENGTH 32 typedef struct { int x, y, width, height; float fov_x, fov_y; vec3_t vieworg; vec3_t viewaxis[3]; // transformation matrix // time in milliseconds for shader effects and other time dependent rendering issues int time; int rdflags; // RDF_NOWORLDMODEL, etc // 1 bits will prevent the associated area from rendering at all byte areamask[MAX_MAP_AREA_BYTES]; // text messages for deform text shaders char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; } refdef_t; typedef enum { STEREO_CENTER, STEREO_LEFT, STEREO_RIGHT } stereoFrame_t; /* ** glconfig_t ** ** Contains variables specific to the OpenGL configuration ** being run right now. These are constant once the OpenGL ** subsystem is initialized. */ typedef enum { TC_NONE, TC_S3TC } textureCompression_t; typedef enum { GLDRV_ICD, // driver is integrated with window system // WARNING: there are tests that check for // > GLDRV_ICD for minidriverness, so this // should always be the lowest value in this // enum set GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver GLDRV_VOODOO // driver is a 3Dfx standalone driver } glDriverType_t; typedef enum { GLHW_GENERIC, // where everthing works the way it should GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is // the hardware type then there can NOT exist a secondary // display adapter GLHW_RIVA128, // where you can't interpolate alpha GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures GLHW_PERMEDIA2 // where you don't have src*dst } glHardwareType_t; typedef struct { char renderer_string[MAX_STRING_CHARS]; char vendor_string[MAX_STRING_CHARS]; char version_string[MAX_STRING_CHARS]; char extensions_string[BIG_INFO_STRING]; int maxTextureSize; // queried from GL int maxActiveTextures; // multitexture ability int colorBits, depthBits, stencilBits; glDriverType_t driverType; glHardwareType_t hardwareType; qboolean deviceSupportsGamma; textureCompression_t textureCompression; qboolean textureEnvAddAvailable; int vidWidth, vidHeight; // aspect is the screen's physical width / height, which may be different // than scrWidth / scrHeight if the pixels are non-square // normal screens should be 4/3, but wide aspect monitors may be 16/9 float windowAspect; int displayFrequency; // synonymous with "does rendering consume the entire screen?", therefore // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that // used CDS. qboolean isFullscreen; qboolean stereoEnabled; qboolean smpActive; // dual processor } glconfig_t; // FIXME: VM should be OS agnostic .. in theory /* #ifdef Q3_VM #define _3DFX_DRIVER_NAME "Voodoo" #define OPENGL_DRIVER_NAME "Default" #elif defined(_WIN32) */ #if defined(Q3_VM) || defined(_WIN32) #define _3DFX_DRIVER_NAME "3dfxvgl" #define OPENGL_DRIVER_NAME "opengl32" #else #define _3DFX_DRIVER_NAME "libMesaVoodooGL.so" // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524 #define OPENGL_DRIVER_NAME "libGL.so.1" #endif // !defined _WIN32 #endif // __TR_TYPES_H ================================================ FILE: code/cgame.lnt ================================================ opts.lnt game\bg_misc.c game\bg_pmove.c game\q_math.c game\q_shared.c cgame\cg_consolecmds.c cgame\cg_draw.c cgame\cg_drawtools.c cgame\cg_effects.c cgame\cg_ents.c cgame\cg_event.c cgame\cg_info.c cgame\cg_localents.c cgame\cg_main.c cgame\cg_marks.c cgame\cg_players.c cgame\cg_playerstate.c cgame\cg_predict.c cgame\cg_scoreboard.c cgame\cg_servercmds.c cgame\cg_snapshot.c cgame\cg_syscalls.c cgame\cg_view.c cgame\cg_weapons.c ================================================ FILE: code/clean.bat ================================================ rmdir debug /s /q rmdir release /s /q del quake3.ncb del quake3.opt del quake3.plg del quake3.stt rmdir cgame\debug /s /q rmdir cgame\release /s /q del cgame\cgame.ncb del cgame\cgame.opt del cgame\cgame.plg del cgame\cgame.stt rmdir game\debug /s /q rmdir game\release /s /q del game\game.ncb del game\game.opt del game\game.plg del game\game.stt rmdir ui\debug /s /q rmdir ui\release /s /q del ui\ui.ncb del ui\ui.opt del ui\ui.plg del ui\ui.stt rmdir renderer\debug /s /q rmdir renderer\release /s /q del renderer\renderer.ncb del renderer\renderer.opt del renderer\renderer.plg del renderer\renderer.stt rmdir botlib\debug /s /q rmdir botlib\release /s /q del botlib\botlib.ncb del botlib\botlib.opt del botlib\botlib.plg del botlib\botlib.stt rmdir botlai\debug /s /q rmdir botlai\release /s /q del botai\botai.dsp del botai\botai.plg rmdir bspc\debug /s /q rmdir bspc\release /s /q del bspc\bspc.exe del bspc\bspc.log del bspc\bspc.ncb del bspc\bspc.opt del bspc\bspc.pdb del bspc\bspc.plg rmdir unix\debugi386-glibc /s /q rmdir unix\releasei386-glibc /s /q rmdir "mac\MacQuake3 Data" /s /q rmdir macosx\Client\Q3Test.app /s /q rmdir macosx\Client\Q3Test.build /s /q del *.o /s del *.obj /s del *.lib /s del *.dll /s del *.ncb /s del *.plg /s del *.map /s del *.opt /s ================================================ FILE: code/client/cl_cgame.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // cl_cgame.c -- client system interaction with client game #include "client.h" #include "../game/botlib.h" extern botlib_export_t *botlib_export; extern qboolean loadCamera(const char *name); extern void startCamera(int time); extern qboolean getCameraInfo(int time, vec3_t *origin, vec3_t *angles); /* ==================== CL_GetGameState ==================== */ void CL_GetGameState( gameState_t *gs ) { *gs = cl.gameState; } /* ==================== CL_GetGlconfig ==================== */ void CL_GetGlconfig( glconfig_t *glconfig ) { *glconfig = cls.glconfig; } /* ==================== CL_GetUserCmd ==================== */ qboolean CL_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { // cmds[cmdNumber] is the last properly generated command // can't return anything that we haven't created yet if ( cmdNumber > cl.cmdNumber ) { Com_Error( ERR_DROP, "CL_GetUserCmd: %i >= %i", cmdNumber, cl.cmdNumber ); } // the usercmd has been overwritten in the wrapping // buffer because it is too far out of date if ( cmdNumber <= cl.cmdNumber - CMD_BACKUP ) { return qfalse; } *ucmd = cl.cmds[ cmdNumber & CMD_MASK ]; return qtrue; } int CL_GetCurrentCmdNumber( void ) { return cl.cmdNumber; } /* ==================== CL_GetParseEntityState ==================== */ qboolean CL_GetParseEntityState( int parseEntityNumber, entityState_t *state ) { // can't return anything that hasn't been parsed yet if ( parseEntityNumber >= cl.parseEntitiesNum ) { Com_Error( ERR_DROP, "CL_GetParseEntityState: %i >= %i", parseEntityNumber, cl.parseEntitiesNum ); } // can't return anything that has been overwritten in the circular buffer if ( parseEntityNumber <= cl.parseEntitiesNum - MAX_PARSE_ENTITIES ) { return qfalse; } *state = cl.parseEntities[ parseEntityNumber & ( MAX_PARSE_ENTITIES - 1 ) ]; return qtrue; } /* ==================== CL_GetCurrentSnapshotNumber ==================== */ void CL_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { *snapshotNumber = cl.snap.messageNum; *serverTime = cl.snap.serverTime; } /* ==================== CL_GetSnapshot ==================== */ qboolean CL_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { clSnapshot_t *clSnap; int i, count; if ( snapshotNumber > cl.snap.messageNum ) { Com_Error( ERR_DROP, "CL_GetSnapshot: snapshotNumber > cl.snapshot.messageNum" ); } // if the frame has fallen out of the circular buffer, we can't return it if ( cl.snap.messageNum - snapshotNumber >= PACKET_BACKUP ) { return qfalse; } // if the frame is not valid, we can't return it clSnap = &cl.snapshots[snapshotNumber & PACKET_MASK]; if ( !clSnap->valid ) { return qfalse; } // if the entities in the frame have fallen out of their // circular buffer, we can't return it if ( cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES ) { return qfalse; } // write the snapshot snapshot->snapFlags = clSnap->snapFlags; snapshot->serverCommandSequence = clSnap->serverCommandNum; snapshot->ping = clSnap->ping; snapshot->serverTime = clSnap->serverTime; Com_Memcpy( snapshot->areamask, clSnap->areamask, sizeof( snapshot->areamask ) ); snapshot->ps = clSnap->ps; count = clSnap->numEntities; if ( count > MAX_ENTITIES_IN_SNAPSHOT ) { Com_DPrintf( "CL_GetSnapshot: truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT ); count = MAX_ENTITIES_IN_SNAPSHOT; } snapshot->numEntities = count; for ( i = 0 ; i < count ; i++ ) { snapshot->entities[i] = cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ]; } // FIXME: configstring changes and server commands!!! return qtrue; } /* ===================== CL_SetUserCmdValue ===================== */ void CL_SetUserCmdValue( int userCmdValue, float sensitivityScale ) { cl.cgameUserCmdValue = userCmdValue; cl.cgameSensitivity = sensitivityScale; } /* ===================== CL_AddCgameCommand ===================== */ void CL_AddCgameCommand( const char *cmdName ) { Cmd_AddCommand( cmdName, NULL ); } /* ===================== CL_CgameError ===================== */ void CL_CgameError( const char *string ) { Com_Error( ERR_DROP, "%s", string ); } /* ===================== CL_ConfigstringModified ===================== */ void CL_ConfigstringModified( void ) { char *old, *s; int i, index; char *dup; gameState_t oldGs; int len; index = atoi( Cmd_Argv(1) ); if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); } // get everything after "cs " s = Cmd_ArgsFrom(2); old = cl.gameState.stringData + cl.gameState.stringOffsets[ index ]; if ( !strcmp( old, s ) ) { return; // unchanged } // build the new gameState_t oldGs = cl.gameState; Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); // leave the first 0 for uninitialized strings cl.gameState.dataCount = 1; for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { if ( i == index ) { dup = s; } else { dup = oldGs.stringData + oldGs.stringOffsets[ i ]; } if ( !dup[0] ) { continue; // leave with the default empty string } len = strlen( dup ); if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); } // append it to the gameState string buffer cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, dup, len + 1 ); cl.gameState.dataCount += len + 1; } if ( index == CS_SYSTEMINFO ) { // parse serverId and other cvars CL_SystemInfoChanged(); } } /* =================== CL_GetServerCommand Set up argc/argv for the given command =================== */ qboolean CL_GetServerCommand( int serverCommandNumber ) { char *s; char *cmd; static char bigConfigString[BIG_INFO_STRING]; int argc; // if we have irretrievably lost a reliable command, drop the connection if ( serverCommandNumber <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) { // when a demo record was started after the client got a whole bunch of // reliable commands then the client never got those first reliable commands if ( clc.demoplaying ) return qfalse; Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" ); return qfalse; } if ( serverCommandNumber > clc.serverCommandSequence ) { Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" ); return qfalse; } s = clc.serverCommands[ serverCommandNumber & ( MAX_RELIABLE_COMMANDS - 1 ) ]; clc.lastExecutedServerCommand = serverCommandNumber; Com_DPrintf( "serverCommand: %i : %s\n", serverCommandNumber, s ); rescan: Cmd_TokenizeString( s ); cmd = Cmd_Argv(0); argc = Cmd_Argc(); if ( !strcmp( cmd, "disconnect" ) ) { // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=552 // allow server to indicate why they were disconnected if ( argc >= 2 ) Com_Error (ERR_SERVERDISCONNECT, va( "Server Disconnected - %s", Cmd_Argv( 1 ) ) ); else Com_Error (ERR_SERVERDISCONNECT,"Server disconnected\n"); } if ( !strcmp( cmd, "bcs0" ) ) { Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2) ); return qfalse; } if ( !strcmp( cmd, "bcs1" ) ) { s = Cmd_Argv(2); if( strlen(bigConfigString) + strlen(s) >= BIG_INFO_STRING ) { Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); } strcat( bigConfigString, s ); return qfalse; } if ( !strcmp( cmd, "bcs2" ) ) { s = Cmd_Argv(2); if( strlen(bigConfigString) + strlen(s) + 1 >= BIG_INFO_STRING ) { Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); } strcat( bigConfigString, s ); strcat( bigConfigString, "\"" ); s = bigConfigString; goto rescan; } if ( !strcmp( cmd, "cs" ) ) { CL_ConfigstringModified(); // reparse the string, because CL_ConfigstringModified may have done another Cmd_TokenizeString() Cmd_TokenizeString( s ); return qtrue; } if ( !strcmp( cmd, "map_restart" ) ) { // clear notify lines and outgoing commands before passing // the restart to the cgame Con_ClearNotify(); Com_Memset( cl.cmds, 0, sizeof( cl.cmds ) ); return qtrue; } // the clientLevelShot command is used during development // to generate 128*128 screenshots from the intermission // point of levels for the menu system to use // we pass it along to the cgame to make apropriate adjustments, // but we also clear the console and notify lines here if ( !strcmp( cmd, "clientLevelShot" ) ) { // don't do it if we aren't running the server locally, // otherwise malicious remote servers could overwrite // the existing thumbnails if ( !com_sv_running->integer ) { return qfalse; } // close the console Con_Close(); // take a special screenshot next frame Cbuf_AddText( "wait ; wait ; wait ; wait ; screenshot levelshot\n" ); return qtrue; } // we may want to put a "connect to other server" command here // cgame can now act on the command return qtrue; } /* ==================== CL_CM_LoadMap Just adds default parameters that cgame doesn't need to know about ==================== */ void CL_CM_LoadMap( const char *mapname ) { int checksum; CM_LoadMap( mapname, qtrue, &checksum ); } /* ==================== CL_ShutdonwCGame ==================== */ void CL_ShutdownCGame( void ) { cls.keyCatchers &= ~KEYCATCH_CGAME; cls.cgameStarted = qfalse; if ( !cgvm ) { return; } VM_Call( cgvm, CG_SHUTDOWN ); VM_Free( cgvm ); cgvm = NULL; } static int FloatAsInt( float f ) { int temp; *(float *)&temp = f; return temp; } /* ==================== CL_CgameSystemCalls The cgame module is making a system call ==================== */ #define VMA(x) VM_ArgPtr(args[x]) #define VMF(x) ((float *)args)[x] int CL_CgameSystemCalls( int *args ) { switch( args[0] ) { case CG_PRINT: Com_Printf( "%s", VMA(1) ); return 0; case CG_ERROR: Com_Error( ERR_DROP, "%s", VMA(1) ); return 0; case CG_MILLISECONDS: return Sys_Milliseconds(); case CG_CVAR_REGISTER: Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); return 0; case CG_CVAR_UPDATE: Cvar_Update( VMA(1) ); return 0; case CG_CVAR_SET: Cvar_Set( VMA(1), VMA(2) ); return 0; case CG_CVAR_VARIABLESTRINGBUFFER: Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); return 0; case CG_ARGC: return Cmd_Argc(); case CG_ARGV: Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); return 0; case CG_ARGS: Cmd_ArgsBuffer( VMA(1), args[2] ); return 0; case CG_FS_FOPENFILE: return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); case CG_FS_READ: FS_Read2( VMA(1), args[2], args[3] ); return 0; case CG_FS_WRITE: FS_Write( VMA(1), args[2], args[3] ); return 0; case CG_FS_FCLOSEFILE: FS_FCloseFile( args[1] ); return 0; case CG_FS_SEEK: return FS_Seek( args[1], args[2], args[3] ); case CG_SENDCONSOLECOMMAND: Cbuf_AddText( VMA(1) ); return 0; case CG_ADDCOMMAND: CL_AddCgameCommand( VMA(1) ); return 0; case CG_REMOVECOMMAND: Cmd_RemoveCommand( VMA(1) ); return 0; case CG_SENDCLIENTCOMMAND: CL_AddReliableCommand( VMA(1) ); return 0; case CG_UPDATESCREEN: // this is used during lengthy level loading, so pump message loop // Com_EventLoop(); // FIXME: if a server restarts here, BAD THINGS HAPPEN! // We can't call Com_EventLoop here, a restart will crash and this _does_ happen // if there is a map change while we are downloading at pk3. // ZOID SCR_UpdateScreen(); return 0; case CG_CM_LOADMAP: CL_CM_LoadMap( VMA(1) ); return 0; case CG_CM_NUMINLINEMODELS: return CM_NumInlineModels(); case CG_CM_INLINEMODEL: return CM_InlineModel( args[1] ); case CG_CM_TEMPBOXMODEL: return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qfalse ); case CG_CM_TEMPCAPSULEMODEL: return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qtrue ); case CG_CM_POINTCONTENTS: return CM_PointContents( VMA(1), args[2] ); case CG_CM_TRANSFORMEDPOINTCONTENTS: return CM_TransformedPointContents( VMA(1), args[2], VMA(3), VMA(4) ); case CG_CM_BOXTRACE: CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qfalse ); return 0; case CG_CM_CAPSULETRACE: CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qtrue ); return 0; case CG_CM_TRANSFORMEDBOXTRACE: CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], VMA(8), VMA(9), /*int capsule*/ qfalse ); return 0; case CG_CM_TRANSFORMEDCAPSULETRACE: CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], VMA(8), VMA(9), /*int capsule*/ qtrue ); return 0; case CG_CM_MARKFRAGMENTS: return re.MarkFragments( args[1], VMA(2), VMA(3), args[4], VMA(5), args[6], VMA(7) ); case CG_S_STARTSOUND: S_StartSound( VMA(1), args[2], args[3], args[4] ); return 0; case CG_S_STARTLOCALSOUND: S_StartLocalSound( args[1], args[2] ); return 0; case CG_S_CLEARLOOPINGSOUNDS: S_ClearLoopingSounds(args[1]); return 0; case CG_S_ADDLOOPINGSOUND: S_AddLoopingSound( args[1], VMA(2), VMA(3), args[4] ); return 0; case CG_S_ADDREALLOOPINGSOUND: S_AddRealLoopingSound( args[1], VMA(2), VMA(3), args[4] ); return 0; case CG_S_STOPLOOPINGSOUND: S_StopLoopingSound( args[1] ); return 0; case CG_S_UPDATEENTITYPOSITION: S_UpdateEntityPosition( args[1], VMA(2) ); return 0; case CG_S_RESPATIALIZE: S_Respatialize( args[1], VMA(2), VMA(3), args[4] ); return 0; case CG_S_REGISTERSOUND: return S_RegisterSound( VMA(1), args[2] ); case CG_S_STARTBACKGROUNDTRACK: S_StartBackgroundTrack( VMA(1), VMA(2) ); return 0; case CG_R_LOADWORLDMAP: re.LoadWorld( VMA(1) ); return 0; case CG_R_REGISTERMODEL: return re.RegisterModel( VMA(1) ); case CG_R_REGISTERSKIN: return re.RegisterSkin( VMA(1) ); case CG_R_REGISTERSHADER: return re.RegisterShader( VMA(1) ); case CG_R_REGISTERSHADERNOMIP: return re.RegisterShaderNoMip( VMA(1) ); case CG_R_REGISTERFONT: re.RegisterFont( VMA(1), args[2], VMA(3)); case CG_R_CLEARSCENE: re.ClearScene(); return 0; case CG_R_ADDREFENTITYTOSCENE: re.AddRefEntityToScene( VMA(1) ); return 0; case CG_R_ADDPOLYTOSCENE: re.AddPolyToScene( args[1], args[2], VMA(3), 1 ); return 0; case CG_R_ADDPOLYSTOSCENE: re.AddPolyToScene( args[1], args[2], VMA(3), args[4] ); return 0; case CG_R_LIGHTFORPOINT: return re.LightForPoint( VMA(1), VMA(2), VMA(3), VMA(4) ); case CG_R_ADDLIGHTTOSCENE: re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); return 0; case CG_R_ADDADDITIVELIGHTTOSCENE: re.AddAdditiveLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); return 0; case CG_R_RENDERSCENE: re.RenderScene( VMA(1) ); return 0; case CG_R_SETCOLOR: re.SetColor( VMA(1) ); return 0; case CG_R_DRAWSTRETCHPIC: re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); return 0; case CG_R_MODELBOUNDS: re.ModelBounds( args[1], VMA(2), VMA(3) ); return 0; case CG_R_LERPTAG: return re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) ); case CG_GETGLCONFIG: CL_GetGlconfig( VMA(1) ); return 0; case CG_GETGAMESTATE: CL_GetGameState( VMA(1) ); return 0; case CG_GETCURRENTSNAPSHOTNUMBER: CL_GetCurrentSnapshotNumber( VMA(1), VMA(2) ); return 0; case CG_GETSNAPSHOT: return CL_GetSnapshot( args[1], VMA(2) ); case CG_GETSERVERCOMMAND: return CL_GetServerCommand( args[1] ); case CG_GETCURRENTCMDNUMBER: return CL_GetCurrentCmdNumber(); case CG_GETUSERCMD: return CL_GetUserCmd( args[1], VMA(2) ); case CG_SETUSERCMDVALUE: CL_SetUserCmdValue( args[1], VMF(2) ); return 0; case CG_MEMORY_REMAINING: return Hunk_MemoryRemaining(); case CG_KEY_ISDOWN: return Key_IsDown( args[1] ); case CG_KEY_GETCATCHER: return Key_GetCatcher(); case CG_KEY_SETCATCHER: Key_SetCatcher( args[1] ); return 0; case CG_KEY_GETKEY: return Key_GetKey( VMA(1) ); case CG_MEMSET: Com_Memset( VMA(1), args[2], args[3] ); return 0; case CG_MEMCPY: Com_Memcpy( VMA(1), VMA(2), args[3] ); return 0; case CG_STRNCPY: return (int)strncpy( VMA(1), VMA(2), args[3] ); case CG_SIN: return FloatAsInt( sin( VMF(1) ) ); case CG_COS: return FloatAsInt( cos( VMF(1) ) ); case CG_ATAN2: return FloatAsInt( atan2( VMF(1), VMF(2) ) ); case CG_SQRT: return FloatAsInt( sqrt( VMF(1) ) ); case CG_FLOOR: return FloatAsInt( floor( VMF(1) ) ); case CG_CEIL: return FloatAsInt( ceil( VMF(1) ) ); case CG_ACOS: return FloatAsInt( Q_acos( VMF(1) ) ); case CG_PC_ADD_GLOBAL_DEFINE: return botlib_export->PC_AddGlobalDefine( VMA(1) ); case CG_PC_LOAD_SOURCE: return botlib_export->PC_LoadSourceHandle( VMA(1) ); case CG_PC_FREE_SOURCE: return botlib_export->PC_FreeSourceHandle( args[1] ); case CG_PC_READ_TOKEN: return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) ); case CG_PC_SOURCE_FILE_AND_LINE: return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) ); case CG_S_STOPBACKGROUNDTRACK: S_StopBackgroundTrack(); return 0; case CG_REAL_TIME: return Com_RealTime( VMA(1) ); case CG_SNAPVECTOR: Sys_SnapVector( VMA(1) ); return 0; case CG_CIN_PLAYCINEMATIC: return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]); case CG_CIN_STOPCINEMATIC: return CIN_StopCinematic(args[1]); case CG_CIN_RUNCINEMATIC: return CIN_RunCinematic(args[1]); case CG_CIN_DRAWCINEMATIC: CIN_DrawCinematic(args[1]); return 0; case CG_CIN_SETEXTENTS: CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); return 0; case CG_R_REMAP_SHADER: re.RemapShader( VMA(1), VMA(2), VMA(3) ); return 0; /* case CG_LOADCAMERA: return loadCamera(VMA(1)); case CG_STARTCAMERA: startCamera(args[1]); return 0; case CG_GETCAMERAINFO: return getCameraInfo(args[1], VMA(2), VMA(3)); */ case CG_GET_ENTITY_TOKEN: return re.GetEntityToken( VMA(1), args[2] ); case CG_R_INPVS: return re.inPVS( VMA(1), VMA(2) ); default: assert(0); // bk010102 Com_Error( ERR_DROP, "Bad cgame system trap: %i", args[0] ); } return 0; } /* ==================== CL_InitCGame Should only be called by CL_StartHunkUsers ==================== */ void CL_InitCGame( void ) { const char *info; const char *mapname; int t1, t2; vmInterpret_t interpret; t1 = Sys_Milliseconds(); // put away the console Con_Close(); // find the current mapname info = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SERVERINFO ]; mapname = Info_ValueForKey( info, "mapname" ); Com_sprintf( cl.mapname, sizeof( cl.mapname ), "maps/%s.bsp", mapname ); // load the dll or bytecode if ( cl_connectedToPureServer != 0 ) { // if sv_pure is set we only allow qvms to be loaded interpret = VMI_COMPILED; } else { interpret = Cvar_VariableValue( "vm_cgame" ); } cgvm = VM_Create( "cgame", CL_CgameSystemCalls, interpret ); if ( !cgvm ) { Com_Error( ERR_DROP, "VM_Create on cgame failed" ); } cls.state = CA_LOADING; // init for this gamestate // use the lastExecutedServerCommand instead of the serverCommandSequence // otherwise server commands sent just before a gamestate are dropped VM_Call( cgvm, CG_INIT, clc.serverMessageSequence, clc.lastExecutedServerCommand, clc.clientNum ); // we will send a usercmd this frame, which // will cause the server to send us the first snapshot cls.state = CA_PRIMED; t2 = Sys_Milliseconds(); Com_Printf( "CL_InitCGame: %5.2f seconds\n", (t2-t1)/1000.0 ); // have the renderer touch all its images, so they are present // on the card even if the driver does deferred loading re.EndRegistration(); // make sure everything is paged in if (!Sys_LowPhysicalMemory()) { Com_TouchMemory(); } // clear anything that got printed Con_ClearNotify (); } /* ==================== CL_GameCommand See if the current console command is claimed by the cgame ==================== */ qboolean CL_GameCommand( void ) { if ( !cgvm ) { return qfalse; } return VM_Call( cgvm, CG_CONSOLE_COMMAND ); } /* ===================== CL_CGameRendering ===================== */ void CL_CGameRendering( stereoFrame_t stereo ) { VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME, cl.serverTime, stereo, clc.demoplaying ); VM_Debug( 0 ); } /* ================= CL_AdjustTimeDelta Adjust the clients view of server time. We attempt to have cl.serverTime exactly equal the server's view of time plus the timeNudge, but with variable latencies over the internet it will often need to drift a bit to match conditions. Our ideal time would be to have the adjusted time approach, but not pass, the very latest snapshot. Adjustments are only made when a new snapshot arrives with a rational latency, which keeps the adjustment process framerate independent and prevents massive overadjustment during times of significant packet loss or bursted delayed packets. ================= */ #define RESET_TIME 500 void CL_AdjustTimeDelta( void ) { int resetTime; int newDelta; int deltaDelta; cl.newSnapshots = qfalse; // the delta never drifts when replaying a demo if ( clc.demoplaying ) { return; } // if the current time is WAY off, just correct to the current value if ( com_sv_running->integer ) { resetTime = 100; } else { resetTime = RESET_TIME; } newDelta = cl.snap.serverTime - cls.realtime; deltaDelta = abs( newDelta - cl.serverTimeDelta ); if ( deltaDelta > RESET_TIME ) { cl.serverTimeDelta = newDelta; cl.oldServerTime = cl.snap.serverTime; // FIXME: is this a problem for cgame? cl.serverTime = cl.snap.serverTime; if ( cl_showTimeDelta->integer ) { Com_Printf( " " ); } } else if ( deltaDelta > 100 ) { // fast adjust, cut the difference in half if ( cl_showTimeDelta->integer ) { Com_Printf( " " ); } cl.serverTimeDelta = ( cl.serverTimeDelta + newDelta ) >> 1; } else { // slow drift adjust, only move 1 or 2 msec // if any of the frames between this and the previous snapshot // had to be extrapolated, nudge our sense of time back a little // the granularity of +1 / -2 is too high for timescale modified frametimes if ( com_timescale->value == 0 || com_timescale->value == 1 ) { if ( cl.extrapolatedSnapshot ) { cl.extrapolatedSnapshot = qfalse; cl.serverTimeDelta -= 2; } else { // otherwise, move our sense of time forward to minimize total latency cl.serverTimeDelta++; } } } if ( cl_showTimeDelta->integer ) { Com_Printf( "%i ", cl.serverTimeDelta ); } } /* ================== CL_FirstSnapshot ================== */ void CL_FirstSnapshot( void ) { // ignore snapshots that don't have entities if ( cl.snap.snapFlags & SNAPFLAG_NOT_ACTIVE ) { return; } cls.state = CA_ACTIVE; // set the timedelta so we are exactly on this first frame cl.serverTimeDelta = cl.snap.serverTime - cls.realtime; cl.oldServerTime = cl.snap.serverTime; clc.timeDemoBaseTime = cl.snap.serverTime; // if this is the first frame of active play, // execute the contents of activeAction now // this is to allow scripting a timedemo to start right // after loading if ( cl_activeAction->string[0] ) { Cbuf_AddText( cl_activeAction->string ); Cvar_Set( "activeAction", "" ); } Sys_BeginProfiling(); } /* ================== CL_SetCGameTime ================== */ void CL_SetCGameTime( void ) { // getting a valid frame message ends the connection process if ( cls.state != CA_ACTIVE ) { if ( cls.state != CA_PRIMED ) { return; } if ( clc.demoplaying ) { // we shouldn't get the first snapshot on the same frame // as the gamestate, because it causes a bad time skip if ( !clc.firstDemoFrameSkipped ) { clc.firstDemoFrameSkipped = qtrue; return; } CL_ReadDemoMessage(); } if ( cl.newSnapshots ) { cl.newSnapshots = qfalse; CL_FirstSnapshot(); } if ( cls.state != CA_ACTIVE ) { return; } } // if we have gotten to this point, cl.snap is guaranteed to be valid if ( !cl.snap.valid ) { Com_Error( ERR_DROP, "CL_SetCGameTime: !cl.snap.valid" ); } // allow pause in single player if ( sv_paused->integer && cl_paused->integer && com_sv_running->integer ) { // paused return; } if ( cl.snap.serverTime < cl.oldFrameServerTime ) { Com_Error( ERR_DROP, "cl.snap.serverTime < cl.oldFrameServerTime" ); } cl.oldFrameServerTime = cl.snap.serverTime; // get our current view of time if ( clc.demoplaying && cl_freezeDemo->integer ) { // cl_freezeDemo is used to lock a demo in place for single frame advances } else { // cl_timeNudge is a user adjustable cvar that allows more // or less latency to be added in the interest of better // smoothness or better responsiveness. int tn; tn = cl_timeNudge->integer; if (tn<-30) { tn = -30; } else if (tn>30) { tn = 30; } cl.serverTime = cls.realtime + cl.serverTimeDelta - tn; // guarantee that time will never flow backwards, even if // serverTimeDelta made an adjustment or cl_timeNudge was changed if ( cl.serverTime < cl.oldServerTime ) { cl.serverTime = cl.oldServerTime; } cl.oldServerTime = cl.serverTime; // note if we are almost past the latest frame (without timeNudge), // so we will try and adjust back a bit when the next snapshot arrives if ( cls.realtime + cl.serverTimeDelta >= cl.snap.serverTime - 5 ) { cl.extrapolatedSnapshot = qtrue; } } // if we have gotten new snapshots, drift serverTimeDelta // don't do this every frame, or a period of packet loss would // make a huge adjustment if ( cl.newSnapshots ) { CL_AdjustTimeDelta(); } if ( !clc.demoplaying ) { return; } // if we are playing a demo back, we can just keep reading // messages from the demo file until the cgame definately // has valid snapshots to interpolate between // a timedemo will always use a deterministic set of time samples // no matter what speed machine it is run on, // while a normal demo may have different time samples // each time it is played back if ( cl_timedemo->integer ) { if (!clc.timeDemoStart) { clc.timeDemoStart = Sys_Milliseconds(); } clc.timeDemoFrames++; cl.serverTime = clc.timeDemoBaseTime + clc.timeDemoFrames * 50; } while ( cl.serverTime >= cl.snap.serverTime ) { // feed another messag, which should change // the contents of cl.snap CL_ReadDemoMessage(); if ( cls.state != CA_ACTIVE ) { return; // end of demo } } } ================================================ FILE: code/client/cl_cin.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: cl_cin.c * * desc: video and cinematic playback * * $Archive: /MissionPack/code/client/cl_cin.c $ * * cl_glconfig.hwtype trtypes 3dfx/ragepro need 256x256 * *****************************************************************************/ #include "client.h" #include "snd_local.h" #define MAXSIZE 8 #define MINSIZE 4 #define DEFAULT_CIN_WIDTH 512 #define DEFAULT_CIN_HEIGHT 512 #define ROQ_QUAD 0x1000 #define ROQ_QUAD_INFO 0x1001 #define ROQ_CODEBOOK 0x1002 #define ROQ_QUAD_VQ 0x1011 #define ROQ_QUAD_JPEG 0x1012 #define ROQ_QUAD_HANG 0x1013 #define ROQ_PACKET 0x1030 #define ZA_SOUND_MONO 0x1020 #define ZA_SOUND_STEREO 0x1021 #define MAX_VIDEO_HANDLES 16 extern glconfig_t glConfig; extern int s_paintedtime; extern int s_rawend; static void RoQ_init( void ); /****************************************************************************** * * Class: trFMV * * Description: RoQ/RnR manipulation routines * not entirely complete for first run * ******************************************************************************/ static long ROQ_YY_tab[256]; static long ROQ_UB_tab[256]; static long ROQ_UG_tab[256]; static long ROQ_VG_tab[256]; static long ROQ_VR_tab[256]; static unsigned short vq2[256*16*4]; static unsigned short vq4[256*64*4]; static unsigned short vq8[256*256*4]; typedef struct { byte linbuf[DEFAULT_CIN_WIDTH*DEFAULT_CIN_HEIGHT*4*2]; byte file[65536]; short sqrTable[256]; unsigned int mcomp[256]; byte *qStatus[2][32768]; long oldXOff, oldYOff, oldysize, oldxsize; int currentHandle; } cinematics_t; typedef struct { char fileName[MAX_OSPATH]; int CIN_WIDTH, CIN_HEIGHT; int xpos, ypos, width, height; qboolean looping, holdAtEnd, dirty, alterGameState, silent, shader; fileHandle_t iFile; e_status status; unsigned int startTime; unsigned int lastTime; long tfps; long RoQPlayed; long ROQSize; unsigned int RoQFrameSize; long onQuad; long numQuads; long samplesPerLine; unsigned int roq_id; long screenDelta; void ( *VQ0)(byte *status, void *qdata ); void ( *VQ1)(byte *status, void *qdata ); void ( *VQNormal)(byte *status, void *qdata ); void ( *VQBuffer)(byte *status, void *qdata ); long samplesPerPixel; // defaults to 2 byte* gray; unsigned int xsize, ysize, maxsize, minsize; qboolean half, smootheddouble, inMemory; long normalBuffer0; long roq_flags; long roqF0; long roqF1; long t[2]; long roqFPS; int playonwalls; byte* buf; long drawX, drawY; } cin_cache; static cinematics_t cin; static cin_cache cinTable[MAX_VIDEO_HANDLES]; static int currentHandle = -1; static int CL_handle = -1; extern int s_soundtime; // sample PAIRS extern int s_paintedtime; // sample PAIRS void CIN_CloseAllVideos(void) { int i; for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { if (cinTable[i].fileName[0] != 0 ) { CIN_StopCinematic(i); } } } static int CIN_HandleForVideo(void) { int i; for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { if ( cinTable[i].fileName[0] == 0 ) { return i; } } Com_Error( ERR_DROP, "CIN_HandleForVideo: none free" ); return -1; } extern int CL_ScaledMilliseconds(void); //----------------------------------------------------------------------------- // RllSetupTable // // Allocates and initializes the square table. // // Parameters: None // // Returns: Nothing //----------------------------------------------------------------------------- static void RllSetupTable() { int z; for (z=0;z<128;z++) { cin.sqrTable[z] = (short)(z*z); cin.sqrTable[z+128] = (short)(-cin.sqrTable[z]); } } //----------------------------------------------------------------------------- // RllDecodeMonoToMono // // Decode mono source data into a mono buffer. // // Parameters: from -> buffer holding encoded data // to -> buffer to hold decoded data // size = number of bytes of input (= # of shorts of output) // signedOutput = 0 for unsigned output, non-zero for signed output // flag = flags from asset header // // Returns: Number of samples placed in output buffer //----------------------------------------------------------------------------- long RllDecodeMonoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput ,unsigned short flag) { unsigned int z; int prev; if (signedOutput) prev = flag - 0x8000; else prev = flag; for (z=0;z buffer holding encoded data // to -> buffer to hold decoded data // size = number of bytes of input (= 1/4 # of bytes of output) // signedOutput = 0 for unsigned output, non-zero for signed output // flag = flags from asset header // // Returns: Number of samples placed in output buffer //----------------------------------------------------------------------------- long RllDecodeMonoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput,unsigned short flag) { unsigned int z; int prev; if (signedOutput) prev = flag - 0x8000; else prev = flag; for (z = 0; z < size; z++) { prev = (short)(prev + cin.sqrTable[from[z]]); to[z*2+0] = to[z*2+1] = (short)(prev); } return size; // * 2 * sizeof(short)); } //----------------------------------------------------------------------------- // RllDecodeStereoToStereo // // Decode stereo source data into a stereo buffer. // // Parameters: from -> buffer holding encoded data // to -> buffer to hold decoded data // size = number of bytes of input (= 1/2 # of bytes of output) // signedOutput = 0 for unsigned output, non-zero for signed output // flag = flags from asset header // // Returns: Number of samples placed in output buffer //----------------------------------------------------------------------------- long RllDecodeStereoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) { unsigned int z; unsigned char *zz = from; int prevL, prevR; if (signedOutput) { prevL = (flag & 0xff00) - 0x8000; prevR = ((flag & 0x00ff) << 8) - 0x8000; } else { prevL = flag & 0xff00; prevR = (flag & 0x00ff) << 8; } for (z=0;z>1); //*sizeof(short)); } //----------------------------------------------------------------------------- // RllDecodeStereoToMono // // Decode stereo source data into a mono buffer. // // Parameters: from -> buffer holding encoded data // to -> buffer to hold decoded data // size = number of bytes of input (= # of bytes of output) // signedOutput = 0 for unsigned output, non-zero for signed output // flag = flags from asset header // // Returns: Number of samples placed in output buffer //----------------------------------------------------------------------------- long RllDecodeStereoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) { unsigned int z; int prevL,prevR; if (signedOutput) { prevL = (flag & 0xff00) - 0x8000; prevR = ((flag & 0x00ff) << 8) -0x8000; } else { prevL = flag & 0xff00; prevR = (flag & 0x00ff) << 8; } for (z=0;z>3; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += dspl; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += dspl; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += dspl; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += dspl; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += dspl; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += dspl; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += dspl; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void move4_32( byte *src, byte *dst, int spl ) { double *dsrc, *ddst; int dspl; dsrc = (double *)src; ddst = (double *)dst; dspl = spl>>3; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; dsrc += dspl; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; dsrc += dspl; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; dsrc += dspl; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void blit8_32( byte *src, byte *dst, int spl ) { double *dsrc, *ddst; int dspl; dsrc = (double *)src; ddst = (double *)dst; dspl = spl>>3; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += 4; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += 4; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += 4; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += 4; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += 4; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += 4; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; dsrc += 4; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; ddst[2] = dsrc[2]; ddst[3] = dsrc[3]; } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ #define movs double static void blit4_32( byte *src, byte *dst, int spl ) { movs *dsrc, *ddst; int dspl; dsrc = (movs *)src; ddst = (movs *)dst; dspl = spl>>3; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; dsrc += 2; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; dsrc += 2; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; dsrc += 2; ddst += dspl; ddst[0] = dsrc[0]; ddst[1] = dsrc[1]; } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void blit2_32( byte *src, byte *dst, int spl ) { double *dsrc, *ddst; int dspl; dsrc = (double *)src; ddst = (double *)dst; dspl = spl>>3; ddst[0] = dsrc[0]; ddst[dspl] = dsrc[1]; } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void blitVQQuad32fs( byte **status, unsigned char *data ) { unsigned short newd, celdata, code; unsigned int index, i; int spl; newd = 0; celdata = 0; index = 0; spl = cinTable[currentHandle].samplesPerLine; do { if (!newd) { newd = 7; celdata = data[0] + data[1]*256; data += 2; } else { newd--; } code = (unsigned short)(celdata&0xc000); celdata <<= 2; switch (code) { case 0x8000: // vq code blit8_32( (byte *)&vq8[(*data)*128], status[index], spl ); data++; index += 5; break; case 0xc000: // drop index++; // skip 8x8 for(i=0;i<4;i++) { if (!newd) { newd = 7; celdata = data[0] + data[1]*256; data += 2; } else { newd--; } code = (unsigned short)(celdata&0xc000); celdata <<= 2; switch (code) { // code in top two bits of code case 0x8000: // 4x4 vq code blit4_32( (byte *)&vq4[(*data)*32], status[index], spl ); data++; break; case 0xc000: // 2x2 vq code blit2_32( (byte *)&vq2[(*data)*8], status[index], spl ); data++; blit2_32( (byte *)&vq2[(*data)*8], status[index]+8, spl ); data++; blit2_32( (byte *)&vq2[(*data)*8], status[index]+spl*2, spl ); data++; blit2_32( (byte *)&vq2[(*data)*8], status[index]+spl*2+8, spl ); data++; break; case 0x4000: // motion compensation move4_32( status[index] + cin.mcomp[(*data)], status[index], spl ); data++; break; } index++; } break; case 0x4000: // motion compensation move8_32( status[index] + cin.mcomp[(*data)], status[index], spl ); data++; index += 5; break; case 0x0000: index += 5; break; } } while ( status[index] != NULL ); } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void ROQ_GenYUVTables( void ) { float t_ub,t_vr,t_ug,t_vg; long i; t_ub = (1.77200f/2.0f) * (float)(1<<6) + 0.5f; t_vr = (1.40200f/2.0f) * (float)(1<<6) + 0.5f; t_ug = (0.34414f/2.0f) * (float)(1<<6) + 0.5f; t_vg = (0.71414f/2.0f) * (float)(1<<6) + 0.5f; for(i=0;i<256;i++) { float x = (float)(2 * i - 255); ROQ_UB_tab[i] = (long)( ( t_ub * x) + (1<<5)); ROQ_VR_tab[i] = (long)( ( t_vr * x) + (1<<5)); ROQ_UG_tab[i] = (long)( (-t_ug * x) ); ROQ_VG_tab[i] = (long)( (-t_vg * x) + (1<<5)); ROQ_YY_tab[i] = (long)( (i << 6) | (i >> 2) ); } } #define VQ2TO4(a,b,c,d) { \ *c++ = a[0]; \ *d++ = a[0]; \ *d++ = a[0]; \ *c++ = a[1]; \ *d++ = a[1]; \ *d++ = a[1]; \ *c++ = b[0]; \ *d++ = b[0]; \ *d++ = b[0]; \ *c++ = b[1]; \ *d++ = b[1]; \ *d++ = b[1]; \ *d++ = a[0]; \ *d++ = a[0]; \ *d++ = a[1]; \ *d++ = a[1]; \ *d++ = b[0]; \ *d++ = b[0]; \ *d++ = b[1]; \ *d++ = b[1]; \ a += 2; b += 2; } #define VQ2TO2(a,b,c,d) { \ *c++ = *a; \ *d++ = *a; \ *d++ = *a; \ *c++ = *b; \ *d++ = *b; \ *d++ = *b; \ *d++ = *a; \ *d++ = *a; \ *d++ = *b; \ *d++ = *b; \ a++; b++; } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static unsigned short yuv_to_rgb( long y, long u, long v ) { long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); r = (YY + ROQ_VR_tab[v]) >> 9; g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 8; b = (YY + ROQ_UB_tab[u]) >> 9; if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; if (r > 31) r = 31; if (g > 63) g = 63; if (b > 31) b = 31; return (unsigned short)((r<<11)+(g<<5)+(b)); } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ #if defined(MACOS_X) static inline unsigned int yuv_to_rgb24( long y, long u, long v ) { long r,g,b,YY; YY = (long)(ROQ_YY_tab[(y)]); r = (YY + ROQ_VR_tab[v]) >> 6; g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; b = (YY + ROQ_UB_tab[u]) >> 6; if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; return ((r<<24)|(g<<16)|(b<<8))|(255); //+(255<<24)); } #else static unsigned int yuv_to_rgb24( long y, long u, long v ) { long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); r = (YY + ROQ_VR_tab[v]) >> 6; g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; b = (YY + ROQ_UB_tab[u]) >> 6; if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; return LittleLong ((r)|(g<<8)|(b<<16)|(255<<24)); } #endif /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void decodeCodeBook( byte *input, unsigned short roq_flags ) { long i, j, two, four; unsigned short *aptr, *bptr, *cptr, *dptr; long y0,y1,y2,y3,cr,cb; byte *bbptr, *baptr, *bcptr, *bdptr; unsigned int *iaptr, *ibptr, *icptr, *idptr; if (!roq_flags) { two = four = 256; } else { two = roq_flags>>8; if (!two) two = 256; four = roq_flags&0xff; } four *= 2; bptr = (unsigned short *)vq2; if (!cinTable[currentHandle].half) { if (!cinTable[currentHandle].smootheddouble) { // // normal height // if (cinTable[currentHandle].samplesPerPixel==2) { for(i=0;i cinTable[currentHandle].CIN_WIDTH) bigx = cinTable[currentHandle].CIN_WIDTH; if (bigy > cinTable[currentHandle].CIN_HEIGHT) bigy = cinTable[currentHandle].CIN_HEIGHT; if ( (startX >= lowx) && (startX+quadSize) <= (bigx) && (startY+quadSize) <= (bigy) && (startY >= lowy) && quadSize <= MAXSIZE) { useY = startY; scroff = cin.linbuf + (useY+((cinTable[currentHandle].CIN_HEIGHT-bigy)>>1)+yOff)*(cinTable[currentHandle].samplesPerLine) + (((startX+xOff))*cinTable[currentHandle].samplesPerPixel); cin.qStatus[0][cinTable[currentHandle].onQuad ] = scroff; cin.qStatus[1][cinTable[currentHandle].onQuad++] = scroff+offset; } if ( quadSize != MINSIZE ) { quadSize >>= 1; recurseQuad( startX, startY , quadSize, xOff, yOff ); recurseQuad( startX+quadSize, startY , quadSize, xOff, yOff ); recurseQuad( startX, startY+quadSize , quadSize, xOff, yOff ); recurseQuad( startX+quadSize, startY+quadSize , quadSize, xOff, yOff ); } } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void setupQuad( long xOff, long yOff ) { long numQuadCels, i,x,y; byte *temp; if (xOff == cin.oldXOff && yOff == cin.oldYOff && cinTable[currentHandle].ysize == cin.oldysize && cinTable[currentHandle].xsize == cin.oldxsize) { return; } cin.oldXOff = xOff; cin.oldYOff = yOff; cin.oldysize = cinTable[currentHandle].ysize; cin.oldxsize = cinTable[currentHandle].xsize; numQuadCels = (cinTable[currentHandle].CIN_WIDTH*cinTable[currentHandle].CIN_HEIGHT) / (16); numQuadCels += numQuadCels/4 + numQuadCels/16; numQuadCels += 64; // for overflow numQuadCels = (cinTable[currentHandle].xsize*cinTable[currentHandle].ysize) / (16); numQuadCels += numQuadCels/4; numQuadCels += 64; // for overflow cinTable[currentHandle].onQuad = 0; for(y=0;y<(long)cinTable[currentHandle].ysize;y+=16) for(x=0;x<(long)cinTable[currentHandle].xsize;x+=16) recurseQuad( x, y, 16, xOff, yOff ); temp = NULL; for(i=(numQuadCels-64);i256) { cinTable[currentHandle].drawX = 256; } if (cinTable[currentHandle].drawY>256) { cinTable[currentHandle].drawY = 256; } if (cinTable[currentHandle].CIN_WIDTH != 256 || cinTable[currentHandle].CIN_HEIGHT != 256) { Com_Printf("HACK: approxmimating cinematic for Rage Pro or Voodoo\n"); } } #if defined(MACOS_X) cinTable[currentHandle].drawX = 256; cinTable[currentHandle].drawX = 256; #endif } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void RoQPrepMcomp( long xoff, long yoff ) { long i, j, x, y, temp, temp2; i=cinTable[currentHandle].samplesPerLine; j=cinTable[currentHandle].samplesPerPixel; if ( cinTable[currentHandle].xsize == (cinTable[currentHandle].ysize*4) && !cinTable[currentHandle].half ) { j = j+j; i = i+i; } for(y=0;y<16;y++) { temp2 = (y+yoff-8)*i; for(x=0;x<16;x++) { temp = (x+xoff-8)*j; cin.mcomp[(x*16)+y] = cinTable[currentHandle].normalBuffer0-(temp2+temp); } } } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void initRoQ() { if (currentHandle < 0) return; cinTable[currentHandle].VQNormal = (void (*)(byte *, void *))blitVQQuad32fs; cinTable[currentHandle].VQBuffer = (void (*)(byte *, void *))blitVQQuad32fs; cinTable[currentHandle].samplesPerPixel = 4; ROQ_GenYUVTables(); RllSetupTable(); } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ /* static byte* RoQFetchInterlaced( byte *source ) { int x, *src, *dst; if (currentHandle < 0) return NULL; src = (int *)source; dst = (int *)cinTable[currentHandle].buf2; for(x=0;x<256*256;x++) { *dst = *src; dst++; src += 2; } return cinTable[currentHandle].buf2; } */ static void RoQReset() { if (currentHandle < 0) return; Sys_EndStreamedFile(cinTable[currentHandle].iFile); FS_FCloseFile( cinTable[currentHandle].iFile ); FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue); // let the background thread start reading ahead Sys_BeginStreamedFile( cinTable[currentHandle].iFile, 0x10000 ); Sys_StreamedRead (cin.file, 16, 1, cinTable[currentHandle].iFile); RoQ_init(); cinTable[currentHandle].status = FMV_LOOPED; } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void RoQInterrupt(void) { byte *framedata; short sbuf[32768]; int ssize; if (currentHandle < 0) return; Sys_StreamedRead( cin.file, cinTable[currentHandle].RoQFrameSize+8, 1, cinTable[currentHandle].iFile ); if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { if (cinTable[currentHandle].holdAtEnd==qfalse) { if (cinTable[currentHandle].looping) { RoQReset(); } else { cinTable[currentHandle].status = FMV_EOF; } } else { cinTable[currentHandle].status = FMV_IDLE; } return; } framedata = cin.file; // // new frame is ready // redump: switch(cinTable[currentHandle].roq_id) { case ROQ_QUAD_VQ: if ((cinTable[currentHandle].numQuads&1)) { cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[1]; RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); cinTable[currentHandle].VQ1( (byte *)cin.qStatus[1], framedata); cinTable[currentHandle].buf = cin.linbuf + cinTable[currentHandle].screenDelta; } else { cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[0]; RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); cinTable[currentHandle].VQ0( (byte *)cin.qStatus[0], framedata ); cinTable[currentHandle].buf = cin.linbuf; } if (cinTable[currentHandle].numQuads == 0) { // first frame Com_Memcpy(cin.linbuf+cinTable[currentHandle].screenDelta, cin.linbuf, cinTable[currentHandle].samplesPerLine*cinTable[currentHandle].ysize); } cinTable[currentHandle].numQuads++; cinTable[currentHandle].dirty = qtrue; break; case ROQ_CODEBOOK: decodeCodeBook( framedata, (unsigned short)cinTable[currentHandle].roq_flags ); break; case ZA_SOUND_MONO: if (!cinTable[currentHandle].silent) { ssize = RllDecodeMonoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); S_RawSamples( ssize, 22050, 2, 1, (byte *)sbuf, 1.0f ); } break; case ZA_SOUND_STEREO: if (!cinTable[currentHandle].silent) { if (cinTable[currentHandle].numQuads == -1) { S_Update(); s_rawend = s_soundtime; } ssize = RllDecodeStereoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); S_RawSamples( ssize, 22050, 2, 2, (byte *)sbuf, 1.0f ); } break; case ROQ_QUAD_INFO: if (cinTable[currentHandle].numQuads == -1) { readQuadInfo( framedata ); setupQuad( 0, 0 ); // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value; } if (cinTable[currentHandle].numQuads != 1) cinTable[currentHandle].numQuads = 0; break; case ROQ_PACKET: cinTable[currentHandle].inMemory = cinTable[currentHandle].roq_flags; cinTable[currentHandle].RoQFrameSize = 0; // for header break; case ROQ_QUAD_HANG: cinTable[currentHandle].RoQFrameSize = 0; break; case ROQ_QUAD_JPEG: break; default: cinTable[currentHandle].status = FMV_EOF; break; } // // read in next frame data // if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { if (cinTable[currentHandle].holdAtEnd==qfalse) { if (cinTable[currentHandle].looping) { RoQReset(); } else { cinTable[currentHandle].status = FMV_EOF; } } else { cinTable[currentHandle].status = FMV_IDLE; } return; } framedata += cinTable[currentHandle].RoQFrameSize; cinTable[currentHandle].roq_id = framedata[0] + framedata[1]*256; cinTable[currentHandle].RoQFrameSize = framedata[2] + framedata[3]*256 + framedata[4]*65536; cinTable[currentHandle].roq_flags = framedata[6] + framedata[7]*256; cinTable[currentHandle].roqF0 = (char)framedata[7]; cinTable[currentHandle].roqF1 = (char)framedata[6]; if (cinTable[currentHandle].RoQFrameSize>65536||cinTable[currentHandle].roq_id==0x1084) { Com_DPrintf("roq_size>65536||roq_id==0x1084\n"); cinTable[currentHandle].status = FMV_EOF; if (cinTable[currentHandle].looping) { RoQReset(); } return; } if (cinTable[currentHandle].inMemory && (cinTable[currentHandle].status != FMV_EOF)) { cinTable[currentHandle].inMemory--; framedata += 8; goto redump; } // // one more frame hits the dust // // assert(cinTable[currentHandle].RoQFrameSize <= 65536); // r = Sys_StreamedRead( cin.file, cinTable[currentHandle].RoQFrameSize+8, 1, cinTable[currentHandle].iFile ); cinTable[currentHandle].RoQPlayed += cinTable[currentHandle].RoQFrameSize+8; } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void RoQ_init( void ) { // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value; cinTable[currentHandle].RoQPlayed = 24; /* get frame rate */ cinTable[currentHandle].roqFPS = cin.file[ 6] + cin.file[ 7]*256; if (!cinTable[currentHandle].roqFPS) cinTable[currentHandle].roqFPS = 30; cinTable[currentHandle].numQuads = -1; cinTable[currentHandle].roq_id = cin.file[ 8] + cin.file[ 9]*256; cinTable[currentHandle].RoQFrameSize = cin.file[10] + cin.file[11]*256 + cin.file[12]*65536; cinTable[currentHandle].roq_flags = cin.file[14] + cin.file[15]*256; if (cinTable[currentHandle].RoQFrameSize > 65536 || !cinTable[currentHandle].RoQFrameSize) { return; } } /****************************************************************************** * * Function: * * Description: * ******************************************************************************/ static void RoQShutdown( void ) { const char *s; if (!cinTable[currentHandle].buf) { return; } if ( cinTable[currentHandle].status == FMV_IDLE ) { return; } Com_DPrintf("finished cinematic\n"); cinTable[currentHandle].status = FMV_IDLE; if (cinTable[currentHandle].iFile) { Sys_EndStreamedFile( cinTable[currentHandle].iFile ); FS_FCloseFile( cinTable[currentHandle].iFile ); cinTable[currentHandle].iFile = 0; } if (cinTable[currentHandle].alterGameState) { cls.state = CA_DISCONNECTED; // we can't just do a vstr nextmap, because // if we are aborting the intro cinematic with // a devmap command, nextmap would be valid by // the time it was referenced s = Cvar_VariableString( "nextmap" ); if ( s[0] ) { Cbuf_ExecuteText( EXEC_APPEND, va("%s\n", s) ); Cvar_Set( "nextmap", "" ); } CL_handle = -1; } cinTable[currentHandle].fileName[0] = 0; currentHandle = -1; } /* ================== SCR_StopCinematic ================== */ e_status CIN_StopCinematic(int handle) { if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; currentHandle = handle; Com_DPrintf("trFMV::stop(), closing %s\n", cinTable[currentHandle].fileName); if (!cinTable[currentHandle].buf) { return FMV_EOF; } if (cinTable[currentHandle].alterGameState) { if ( cls.state != CA_CINEMATIC ) { return cinTable[currentHandle].status; } } cinTable[currentHandle].status = FMV_EOF; RoQShutdown(); return FMV_EOF; } /* ================== SCR_RunCinematic Fetch and decompress the pending frame ================== */ e_status CIN_RunCinematic (int handle) { // bk001204 - init int start = 0; int thisTime = 0; if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; if (cin.currentHandle != handle) { currentHandle = handle; cin.currentHandle = currentHandle; cinTable[currentHandle].status = FMV_EOF; RoQReset(); } if (cinTable[handle].playonwalls < -1) { return cinTable[handle].status; } currentHandle = handle; if (cinTable[currentHandle].alterGameState) { if ( cls.state != CA_CINEMATIC ) { return cinTable[currentHandle].status; } } if (cinTable[currentHandle].status == FMV_IDLE) { return cinTable[currentHandle].status; } // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer thisTime = CL_ScaledMilliseconds()*com_timescale->value; if (cinTable[currentHandle].shader && (abs(thisTime - cinTable[currentHandle].lastTime))>100) { cinTable[currentHandle].startTime += thisTime - cinTable[currentHandle].lastTime; } // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) - cinTable[currentHandle].startTime)*3)/100); start = cinTable[currentHandle].startTime; while( (cinTable[currentHandle].tfps != cinTable[currentHandle].numQuads) && (cinTable[currentHandle].status == FMV_PLAY) ) { RoQInterrupt(); if (start != cinTable[currentHandle].startTime) { // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) - cinTable[currentHandle].startTime)*3)/100); start = cinTable[currentHandle].startTime; } } cinTable[currentHandle].lastTime = thisTime; if (cinTable[currentHandle].status == FMV_LOOPED) { cinTable[currentHandle].status = FMV_PLAY; } if (cinTable[currentHandle].status == FMV_EOF) { if (cinTable[currentHandle].looping) { RoQReset(); } else { RoQShutdown(); } } return cinTable[currentHandle].status; } /* ================== CL_PlayCinematic ================== */ int CIN_PlayCinematic( const char *arg, int x, int y, int w, int h, int systemBits ) { unsigned short RoQID; char name[MAX_OSPATH]; int i; if (strstr(arg, "/") == NULL && strstr(arg, "\\") == NULL) { Com_sprintf (name, sizeof(name), "video/%s", arg); } else { Com_sprintf (name, sizeof(name), "%s", arg); } if (!(systemBits & CIN_system)) { for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { if (!strcmp(cinTable[i].fileName, name) ) { return i; } } } Com_DPrintf("SCR_PlayCinematic( %s )\n", arg); Com_Memset(&cin, 0, sizeof(cinematics_t) ); currentHandle = CIN_HandleForVideo(); cin.currentHandle = currentHandle; strcpy(cinTable[currentHandle].fileName, name); cinTable[currentHandle].ROQSize = 0; cinTable[currentHandle].ROQSize = FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue); if (cinTable[currentHandle].ROQSize<=0) { Com_DPrintf("play(%s), ROQSize<=0\n", arg); cinTable[currentHandle].fileName[0] = 0; return -1; } CIN_SetExtents(currentHandle, x, y, w, h); CIN_SetLooping(currentHandle, (systemBits & CIN_loop)!=0); cinTable[currentHandle].CIN_HEIGHT = DEFAULT_CIN_HEIGHT; cinTable[currentHandle].CIN_WIDTH = DEFAULT_CIN_WIDTH; cinTable[currentHandle].holdAtEnd = (systemBits & CIN_hold) != 0; cinTable[currentHandle].alterGameState = (systemBits & CIN_system) != 0; cinTable[currentHandle].playonwalls = 1; cinTable[currentHandle].silent = (systemBits & CIN_silent) != 0; cinTable[currentHandle].shader = (systemBits & CIN_shader) != 0; if (cinTable[currentHandle].alterGameState) { // close the menu if ( uivm ) { VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); } } else { cinTable[currentHandle].playonwalls = cl_inGameVideo->integer; } initRoQ(); FS_Read (cin.file, 16, cinTable[currentHandle].iFile); RoQID = (unsigned short)(cin.file[0]) + (unsigned short)(cin.file[1])*256; if (RoQID == 0x1084) { RoQ_init(); // FS_Read (cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile); // let the background thread start reading ahead Sys_BeginStreamedFile( cinTable[currentHandle].iFile, 0x10000 ); cinTable[currentHandle].status = FMV_PLAY; Com_DPrintf("trFMV::play(), playing %s\n", arg); if (cinTable[currentHandle].alterGameState) { cls.state = CA_CINEMATIC; } Con_Close(); s_rawend = s_soundtime; return currentHandle; } Com_DPrintf("trFMV::play(), invalid RoQ ID\n"); RoQShutdown(); return -1; } void CIN_SetExtents (int handle, int x, int y, int w, int h) { if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; cinTable[handle].xpos = x; cinTable[handle].ypos = y; cinTable[handle].width = w; cinTable[handle].height = h; cinTable[handle].dirty = qtrue; } void CIN_SetLooping(int handle, qboolean loop) { if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; cinTable[handle].looping = loop; } /* ================== SCR_DrawCinematic ================== */ void CIN_DrawCinematic (int handle) { float x, y, w, h; byte *buf; if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; if (!cinTable[handle].buf) { return; } x = cinTable[handle].xpos; y = cinTable[handle].ypos; w = cinTable[handle].width; h = cinTable[handle].height; buf = cinTable[handle].buf; SCR_AdjustFrom640( &x, &y, &w, &h ); if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY)) { int ix, iy, *buf2, *buf3, xm, ym, ll; xm = cinTable[handle].CIN_WIDTH/256; ym = cinTable[handle].CIN_HEIGHT/256; ll = 8; if (cinTable[handle].CIN_WIDTH==512) { ll = 9; } buf3 = (int*)buf; buf2 = Hunk_AllocateTempMemory( 256*256*4 ); if (xm==2 && ym==2) { byte *bc2, *bc3; int ic, iiy; bc2 = (byte *)buf2; bc3 = (byte *)buf3; for (iy = 0; iy<256; iy++) { iiy = iy<<12; for (ix = 0; ix<2048; ix+=8) { for(ic = ix;ic<(ix+4);ic++) { *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic]+bc3[iiy+2048+ic]+bc3[iiy+2048+4+ic])>>2; bc2++; } } } } else if (xm==2 && ym==1) { byte *bc2, *bc3; int ic, iiy; bc2 = (byte *)buf2; bc3 = (byte *)buf3; for (iy = 0; iy<256; iy++) { iiy = iy<<11; for (ix = 0; ix<2048; ix+=8) { for(ic = ix;ic<(ix+4);ic++) { *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic])>>1; bc2++; } } } } else { for (iy = 0; iy<256; iy++) { for (ix = 0; ix<256; ix++) { buf2[(iy<<8)+ix] = buf3[((iy*ym)<= 0) { do { SCR_RunCinematic(); } while (cinTable[currentHandle].buf == NULL && cinTable[currentHandle].status == FMV_PLAY); // wait for first frame (load codebook and sound) } } void SCR_DrawCinematic (void) { if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { CIN_DrawCinematic(CL_handle); } } void SCR_RunCinematic (void) { if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { CIN_RunCinematic(CL_handle); } } void SCR_StopCinematic(void) { if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { CIN_StopCinematic(CL_handle); S_StopAllSounds (); CL_handle = -1; } } void CIN_UploadCinematic(int handle) { if (handle >= 0 && handle < MAX_VIDEO_HANDLES) { if (!cinTable[handle].buf) { return; } if (cinTable[handle].playonwalls <= 0 && cinTable[handle].dirty) { if (cinTable[handle].playonwalls == 0) { cinTable[handle].playonwalls = -1; } else { if (cinTable[handle].playonwalls == -1) { cinTable[handle].playonwalls = -2; } else { cinTable[handle].dirty = qfalse; } } } re.UploadCinematic( 256, 256, 256, 256, cinTable[handle].buf, handle, cinTable[handle].dirty); if (cl_inGameVideo->integer == 0 && cinTable[handle].playonwalls == 1) { cinTable[handle].playonwalls--; } } } ================================================ FILE: code/client/cl_console.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // console.c #include "client.h" int g_console_field_width = 78; #define NUM_CON_TIMES 4 #define CON_TEXTSIZE 32768 typedef struct { qboolean initialized; short text[CON_TEXTSIZE]; int current; // line where next message will be printed int x; // offset in current line for next print int display; // bottom of console displays this line int linewidth; // characters across screen int totallines; // total lines in console scrollback float xadjust; // for wide aspect screens float displayFrac; // aproaches finalFrac at scr_conspeed float finalFrac; // 0.0 to 1.0 lines of console to display int vislines; // in scanlines int times[NUM_CON_TIMES]; // cls.realtime time the line was generated // for transparent notify lines vec4_t color; } console_t; extern console_t con; console_t con; cvar_t *con_conspeed; cvar_t *con_notifytime; #define DEFAULT_CONSOLE_WIDTH 78 vec4_t console_color = {1.0, 1.0, 1.0, 1.0}; /* ================ Con_ToggleConsole_f ================ */ void Con_ToggleConsole_f (void) { // closing a full screen console restarts the demo loop if ( cls.state == CA_DISCONNECTED && cls.keyCatchers == KEYCATCH_CONSOLE ) { CL_StartDemoLoop(); return; } Field_Clear( &g_consoleField ); g_consoleField.widthInChars = g_console_field_width; Con_ClearNotify (); cls.keyCatchers ^= KEYCATCH_CONSOLE; } /* ================ Con_MessageMode_f ================ */ void Con_MessageMode_f (void) { chat_playerNum = -1; chat_team = qfalse; Field_Clear( &chatField ); chatField.widthInChars = 30; cls.keyCatchers ^= KEYCATCH_MESSAGE; } /* ================ Con_MessageMode2_f ================ */ void Con_MessageMode2_f (void) { chat_playerNum = -1; chat_team = qtrue; Field_Clear( &chatField ); chatField.widthInChars = 25; cls.keyCatchers ^= KEYCATCH_MESSAGE; } /* ================ Con_MessageMode3_f ================ */ void Con_MessageMode3_f (void) { chat_playerNum = VM_Call( cgvm, CG_CROSSHAIR_PLAYER ); if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { chat_playerNum = -1; return; } chat_team = qfalse; Field_Clear( &chatField ); chatField.widthInChars = 30; cls.keyCatchers ^= KEYCATCH_MESSAGE; } /* ================ Con_MessageMode4_f ================ */ void Con_MessageMode4_f (void) { chat_playerNum = VM_Call( cgvm, CG_LAST_ATTACKER ); if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { chat_playerNum = -1; return; } chat_team = qfalse; Field_Clear( &chatField ); chatField.widthInChars = 30; cls.keyCatchers ^= KEYCATCH_MESSAGE; } /* ================ Con_Clear_f ================ */ void Con_Clear_f (void) { int i; for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; } Con_Bottom(); // go to end } /* ================ Con_Dump_f Save the console contents out to a file ================ */ void Con_Dump_f (void) { int l, x, i; short *line; fileHandle_t f; char buffer[1024]; if (Cmd_Argc() != 2) { Com_Printf ("usage: condump \n"); return; } Com_Printf ("Dumped console text to %s.\n", Cmd_Argv(1) ); f = FS_FOpenFileWrite( Cmd_Argv( 1 ) ); if (!f) { Com_Printf ("ERROR: couldn't open.\n"); return; } // skip empty lines for (l = con.current - con.totallines + 1 ; l <= con.current ; l++) { line = con.text + (l%con.totallines)*con.linewidth; for (x=0 ; x=0 ; x--) { if (buffer[x] == ' ') buffer[x] = 0; else break; } strcat( buffer, "\n" ); FS_Write(buffer, strlen(buffer), f); } FS_FCloseFile( f ); } /* ================ Con_ClearNotify ================ */ void Con_ClearNotify( void ) { int i; for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) { con.times[i] = 0; } } /* ================ Con_CheckResize If the line width has changed, reformat the buffer. ================ */ void Con_CheckResize (void) { int i, j, width, oldwidth, oldtotallines, numlines, numchars; MAC_STATIC short tbuf[CON_TEXTSIZE]; width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2; if (width == con.linewidth) return; if (width < 1) // video hasn't been initialized yet { width = DEFAULT_CONSOLE_WIDTH; con.linewidth = width; con.totallines = CON_TEXTSIZE / con.linewidth; for(i=0; i= 0) { if (skipnotify) con.times[con.current % NUM_CON_TIMES] = 0; else con.times[con.current % NUM_CON_TIMES] = cls.realtime; } con.x = 0; if (con.display == con.current) con.display++; con.current++; for(i=0; iinteger ) { return; } if (!con.initialized) { con.color[0] = con.color[1] = con.color[2] = con.color[3] = 1.0f; con.linewidth = -1; Con_CheckResize (); con.initialized = qtrue; } color = ColorIndex(COLOR_WHITE); while ( (c = *txt) != 0 ) { if ( Q_IsColorString( txt ) ) { color = ColorIndex( *(txt+1) ); txt += 2; continue; } // count word length for (l=0 ; l< con.linewidth ; l++) { if ( txt[l] <= ' ') { break; } } // word wrap if (l != con.linewidth && (con.x + l >= con.linewidth) ) { Con_Linefeed(skipnotify); } txt++; switch (c) { case '\n': Con_Linefeed (skipnotify); break; case '\r': con.x = 0; break; default: // display character and advance y = con.current % con.totallines; con.text[y*con.linewidth+con.x] = (color << 8) | c; con.x++; if (con.x >= con.linewidth) { Con_Linefeed(skipnotify); con.x = 0; } break; } } // mark time for transparent overlay if (con.current >= 0) { // NERVE - SMF if ( skipnotify ) { prev = con.current % NUM_CON_TIMES - 1; if ( prev < 0 ) prev = NUM_CON_TIMES - 1; con.times[prev] = 0; } else // -NERVE - SMF con.times[con.current % NUM_CON_TIMES] = cls.realtime; } } /* ============================================================================== DRAWING ============================================================================== */ /* ================ Con_DrawInput Draw the editline after a ] prompt ================ */ void Con_DrawInput (void) { int y; if ( cls.state != CA_DISCONNECTED && !(cls.keyCatchers & KEYCATCH_CONSOLE ) ) { return; } y = con.vislines - ( SMALLCHAR_HEIGHT * 2 ); re.SetColor( con.color ); SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' ); Field_Draw( &g_consoleField, con.xadjust + 2 * SMALLCHAR_WIDTH, y, SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, qtrue ); } /* ================ Con_DrawNotify Draws the last few lines of output transparently over the game top ================ */ void Con_DrawNotify (void) { int x, v; short *text; int i; int time; int skip; int currentColor; currentColor = 7; re.SetColor( g_color_table[currentColor] ); v = 0; for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++) { if (i < 0) continue; time = con.times[i % NUM_CON_TIMES]; if (time == 0) continue; time = cls.realtime - time; if (time > con_notifytime->value*1000) continue; text = con.text + (i % con.totallines)*con.linewidth; if (cl.snap.ps.pm_type != PM_INTERMISSION && cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME) ) { continue; } for (x = 0 ; x < con.linewidth ; x++) { if ( ( text[x] & 0xff ) == ' ' ) { continue; } if ( ( (text[x]>>8)&7 ) != currentColor ) { currentColor = (text[x]>>8)&7; re.SetColor( g_color_table[currentColor] ); } SCR_DrawSmallChar( cl_conXOffset->integer + con.xadjust + (x+1)*SMALLCHAR_WIDTH, v, text[x] & 0xff ); } v += SMALLCHAR_HEIGHT; } re.SetColor( NULL ); if (cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME) ) { return; } // draw the chat line if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { if (chat_team) { SCR_DrawBigString (8, v, "say_team:", 1.0f ); skip = 11; } else { SCR_DrawBigString (8, v, "say:", 1.0f ); skip = 5; } Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, v, SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue ); v += BIGCHAR_HEIGHT; } } /* ================ Con_DrawSolidConsole Draws the console with the solid background ================ */ void Con_DrawSolidConsole( float frac ) { int i, x, y; int rows; short *text; int row; int lines; // qhandle_t conShader; int currentColor; vec4_t color; lines = cls.glconfig.vidHeight * frac; if (lines <= 0) return; if (lines > cls.glconfig.vidHeight ) lines = cls.glconfig.vidHeight; // on wide screens, we will center the text con.xadjust = 0; SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL ); // draw the background y = frac * SCREEN_HEIGHT - 2; if ( y < 1 ) { y = 0; } else { SCR_DrawPic( 0, 0, SCREEN_WIDTH, y, cls.consoleShader ); } color[0] = 1; color[1] = 0; color[2] = 0; color[3] = 1; SCR_FillRect( 0, y, SCREEN_WIDTH, 2, color ); // draw the version number re.SetColor( g_color_table[ColorIndex(COLOR_RED)] ); i = strlen( Q3_VERSION ); for (x=0 ; x= con.totallines) { // past scrollback wrap point continue; } text = con.text + (row % con.totallines)*con.linewidth; for (x=0 ; x>8)&7 ) != currentColor ) { currentColor = (text[x]>>8)&7; re.SetColor( g_color_table[currentColor] ); } SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff ); } } // draw the input prompt, user text, and cursor if desired Con_DrawInput (); re.SetColor( NULL ); } /* ================== Con_DrawConsole ================== */ void Con_DrawConsole( void ) { // check for console width changes from a vid mode change Con_CheckResize (); // if disconnected, render console full screen if ( cls.state == CA_DISCONNECTED ) { if ( !( cls.keyCatchers & (KEYCATCH_UI | KEYCATCH_CGAME)) ) { Con_DrawSolidConsole( 1.0 ); return; } } if ( con.displayFrac ) { Con_DrawSolidConsole( con.displayFrac ); } else { // draw notify lines if ( cls.state == CA_ACTIVE ) { Con_DrawNotify (); } } } //================================================================ /* ================== Con_RunConsole Scroll it up or down ================== */ void Con_RunConsole (void) { // decide on the destination height of the console if ( cls.keyCatchers & KEYCATCH_CONSOLE ) con.finalFrac = 0.5; // half screen else con.finalFrac = 0; // none visible // scroll towards the destination height if (con.finalFrac < con.displayFrac) { con.displayFrac -= con_conspeed->value*cls.realFrametime*0.001; if (con.finalFrac > con.displayFrac) con.displayFrac = con.finalFrac; } else if (con.finalFrac > con.displayFrac) { con.displayFrac += con_conspeed->value*cls.realFrametime*0.001; if (con.finalFrac < con.displayFrac) con.displayFrac = con.finalFrac; } } void Con_PageUp( void ) { con.display -= 2; if ( con.current - con.display >= con.totallines ) { con.display = con.current - con.totallines + 1; } } void Con_PageDown( void ) { con.display += 2; if (con.display > con.current) { con.display = con.current; } } void Con_Top( void ) { con.display = con.totallines; if ( con.current - con.display >= con.totallines ) { con.display = con.current - con.totallines + 1; } } void Con_Bottom( void ) { con.display = con.current; } void Con_Close( void ) { if ( !com_cl_running->integer ) { return; } Field_Clear( &g_consoleField ); Con_ClearNotify (); cls.keyCatchers &= ~KEYCATCH_CONSOLE; con.finalFrac = 0; // none visible con.displayFrac = 0; } ================================================ FILE: code/client/cl_input.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // cl.input.c -- builds an intended movement command to send to the server #include "client.h" unsigned frame_msec; int old_com_frameTime; /* =============================================================================== KEY BUTTONS Continuous button event tracking is complicated by the fact that two different input sources (say, mouse button 1 and the control key) can both press the same button, but the button should only be released when both of the pressing key have been released. When a key event issues a button command (+forward, +attack, etc), it appends its key number as argv(1) so it can be matched up with the release. argv(2) will be set to the time the event happened, which allows exact control even at low framerates when the down and up events may both get qued at the same time. =============================================================================== */ kbutton_t in_left, in_right, in_forward, in_back; kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; kbutton_t in_strafe, in_speed; kbutton_t in_up, in_down; kbutton_t in_buttons[16]; qboolean in_mlooking; void IN_MLookDown( void ) { in_mlooking = qtrue; } void IN_MLookUp( void ) { in_mlooking = qfalse; if ( !cl_freelook->integer ) { IN_CenterView (); } } void IN_KeyDown( kbutton_t *b ) { int k; char *c; c = Cmd_Argv(1); if ( c[0] ) { k = atoi(c); } else { k = -1; // typed manually at the console for continuous down } if ( k == b->down[0] || k == b->down[1] ) { return; // repeating key } if ( !b->down[0] ) { b->down[0] = k; } else if ( !b->down[1] ) { b->down[1] = k; } else { Com_Printf ("Three keys down for a button!\n"); return; } if ( b->active ) { return; // still down } // save timestamp for partial frame summing c = Cmd_Argv(2); b->downtime = atoi(c); b->active = qtrue; b->wasPressed = qtrue; } void IN_KeyUp( kbutton_t *b ) { int k; char *c; unsigned uptime; c = Cmd_Argv(1); if ( c[0] ) { k = atoi(c); } else { // typed manually at the console, assume for unsticking, so clear all b->down[0] = b->down[1] = 0; b->active = qfalse; return; } if ( b->down[0] == k ) { b->down[0] = 0; } else if ( b->down[1] == k ) { b->down[1] = 0; } else { return; // key up without coresponding down (menu pass through) } if ( b->down[0] || b->down[1] ) { return; // some other key is still holding it down } b->active = qfalse; // save timestamp for partial frame summing c = Cmd_Argv(2); uptime = atoi(c); if ( uptime ) { b->msec += uptime - b->downtime; } else { b->msec += frame_msec / 2; } b->active = qfalse; } /* =============== CL_KeyState Returns the fraction of the frame that the key was down =============== */ float CL_KeyState( kbutton_t *key ) { float val; int msec; msec = key->msec; key->msec = 0; if ( key->active ) { // still down if ( !key->downtime ) { msec = com_frameTime; } else { msec += com_frameTime - key->downtime; } key->downtime = com_frameTime; } #if 0 if (msec) { Com_Printf ("%i ", msec); } #endif val = (float)msec / frame_msec; if ( val < 0 ) { val = 0; } if ( val > 1 ) { val = 1; } return val; } void IN_UpDown(void) {IN_KeyDown(&in_up);} void IN_UpUp(void) {IN_KeyUp(&in_up);} void IN_DownDown(void) {IN_KeyDown(&in_down);} void IN_DownUp(void) {IN_KeyUp(&in_down);} void IN_LeftDown(void) {IN_KeyDown(&in_left);} void IN_LeftUp(void) {IN_KeyUp(&in_left);} void IN_RightDown(void) {IN_KeyDown(&in_right);} void IN_RightUp(void) {IN_KeyUp(&in_right);} void IN_ForwardDown(void) {IN_KeyDown(&in_forward);} void IN_ForwardUp(void) {IN_KeyUp(&in_forward);} void IN_BackDown(void) {IN_KeyDown(&in_back);} void IN_BackUp(void) {IN_KeyUp(&in_back);} void IN_LookupDown(void) {IN_KeyDown(&in_lookup);} void IN_LookupUp(void) {IN_KeyUp(&in_lookup);} void IN_LookdownDown(void) {IN_KeyDown(&in_lookdown);} void IN_LookdownUp(void) {IN_KeyUp(&in_lookdown);} void IN_MoveleftDown(void) {IN_KeyDown(&in_moveleft);} void IN_MoveleftUp(void) {IN_KeyUp(&in_moveleft);} void IN_MoverightDown(void) {IN_KeyDown(&in_moveright);} void IN_MoverightUp(void) {IN_KeyUp(&in_moveright);} void IN_SpeedDown(void) {IN_KeyDown(&in_speed);} void IN_SpeedUp(void) {IN_KeyUp(&in_speed);} void IN_StrafeDown(void) {IN_KeyDown(&in_strafe);} void IN_StrafeUp(void) {IN_KeyUp(&in_strafe);} void IN_Button0Down(void) {IN_KeyDown(&in_buttons[0]);} void IN_Button0Up(void) {IN_KeyUp(&in_buttons[0]);} void IN_Button1Down(void) {IN_KeyDown(&in_buttons[1]);} void IN_Button1Up(void) {IN_KeyUp(&in_buttons[1]);} void IN_Button2Down(void) {IN_KeyDown(&in_buttons[2]);} void IN_Button2Up(void) {IN_KeyUp(&in_buttons[2]);} void IN_Button3Down(void) {IN_KeyDown(&in_buttons[3]);} void IN_Button3Up(void) {IN_KeyUp(&in_buttons[3]);} void IN_Button4Down(void) {IN_KeyDown(&in_buttons[4]);} void IN_Button4Up(void) {IN_KeyUp(&in_buttons[4]);} void IN_Button5Down(void) {IN_KeyDown(&in_buttons[5]);} void IN_Button5Up(void) {IN_KeyUp(&in_buttons[5]);} void IN_Button6Down(void) {IN_KeyDown(&in_buttons[6]);} void IN_Button6Up(void) {IN_KeyUp(&in_buttons[6]);} void IN_Button7Down(void) {IN_KeyDown(&in_buttons[7]);} void IN_Button7Up(void) {IN_KeyUp(&in_buttons[7]);} void IN_Button8Down(void) {IN_KeyDown(&in_buttons[8]);} void IN_Button8Up(void) {IN_KeyUp(&in_buttons[8]);} void IN_Button9Down(void) {IN_KeyDown(&in_buttons[9]);} void IN_Button9Up(void) {IN_KeyUp(&in_buttons[9]);} void IN_Button10Down(void) {IN_KeyDown(&in_buttons[10]);} void IN_Button10Up(void) {IN_KeyUp(&in_buttons[10]);} void IN_Button11Down(void) {IN_KeyDown(&in_buttons[11]);} void IN_Button11Up(void) {IN_KeyUp(&in_buttons[11]);} void IN_Button12Down(void) {IN_KeyDown(&in_buttons[12]);} void IN_Button12Up(void) {IN_KeyUp(&in_buttons[12]);} void IN_Button13Down(void) {IN_KeyDown(&in_buttons[13]);} void IN_Button13Up(void) {IN_KeyUp(&in_buttons[13]);} void IN_Button14Down(void) {IN_KeyDown(&in_buttons[14]);} void IN_Button14Up(void) {IN_KeyUp(&in_buttons[14]);} void IN_Button15Down(void) {IN_KeyDown(&in_buttons[15]);} void IN_Button15Up(void) {IN_KeyUp(&in_buttons[15]);} void IN_ButtonDown (void) { IN_KeyDown(&in_buttons[1]);} void IN_ButtonUp (void) { IN_KeyUp(&in_buttons[1]);} void IN_CenterView (void) { cl.viewangles[PITCH] = -SHORT2ANGLE(cl.snap.ps.delta_angles[PITCH]); } //========================================================================== cvar_t *cl_upspeed; cvar_t *cl_forwardspeed; cvar_t *cl_sidespeed; cvar_t *cl_yawspeed; cvar_t *cl_pitchspeed; cvar_t *cl_run; cvar_t *cl_anglespeedkey; /* ================ CL_AdjustAngles Moves the local angle positions ================ */ void CL_AdjustAngles( void ) { float speed; if ( in_speed.active ) { speed = 0.001 * cls.frametime * cl_anglespeedkey->value; } else { speed = 0.001 * cls.frametime; } if ( !in_strafe.active ) { cl.viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); cl.viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); } cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_lookup); cl.viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_lookdown); } /* ================ CL_KeyMove Sets the usercmd_t based on key states ================ */ void CL_KeyMove( usercmd_t *cmd ) { int movespeed; int forward, side, up; // // adjust for speed key / running // the walking flag is to keep animations consistant // even during acceleration and develeration // if ( in_speed.active ^ cl_run->integer ) { movespeed = 127; cmd->buttons &= ~BUTTON_WALKING; } else { cmd->buttons |= BUTTON_WALKING; movespeed = 64; } forward = 0; side = 0; up = 0; if ( in_strafe.active ) { side += movespeed * CL_KeyState (&in_right); side -= movespeed * CL_KeyState (&in_left); } side += movespeed * CL_KeyState (&in_moveright); side -= movespeed * CL_KeyState (&in_moveleft); up += movespeed * CL_KeyState (&in_up); up -= movespeed * CL_KeyState (&in_down); forward += movespeed * CL_KeyState (&in_forward); forward -= movespeed * CL_KeyState (&in_back); cmd->forwardmove = ClampChar( forward ); cmd->rightmove = ClampChar( side ); cmd->upmove = ClampChar( up ); } /* ================= CL_MouseEvent ================= */ void CL_MouseEvent( int dx, int dy, int time ) { if ( cls.keyCatchers & KEYCATCH_UI ) { VM_Call( uivm, UI_MOUSE_EVENT, dx, dy ); } else if (cls.keyCatchers & KEYCATCH_CGAME) { VM_Call (cgvm, CG_MOUSE_EVENT, dx, dy); } else { cl.mouseDx[cl.mouseIndex] += dx; cl.mouseDy[cl.mouseIndex] += dy; } } /* ================= CL_JoystickEvent Joystick values stay set until changed ================= */ void CL_JoystickEvent( int axis, int value, int time ) { if ( axis < 0 || axis >= MAX_JOYSTICK_AXIS ) { Com_Error( ERR_DROP, "CL_JoystickEvent: bad axis %i", axis ); } cl.joystickAxis[axis] = value; } /* ================= CL_JoystickMove ================= */ void CL_JoystickMove( usercmd_t *cmd ) { int movespeed; float anglespeed; if ( in_speed.active ^ cl_run->integer ) { movespeed = 2; } else { movespeed = 1; cmd->buttons |= BUTTON_WALKING; } if ( in_speed.active ) { anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value; } else { anglespeed = 0.001 * cls.frametime; } if ( !in_strafe.active ) { cl.viewangles[YAW] += anglespeed * cl_yawspeed->value * cl.joystickAxis[AXIS_SIDE]; } else { cmd->rightmove = ClampChar( cmd->rightmove + cl.joystickAxis[AXIS_SIDE] ); } if ( in_mlooking ) { cl.viewangles[PITCH] += anglespeed * cl_pitchspeed->value * cl.joystickAxis[AXIS_FORWARD]; } else { cmd->forwardmove = ClampChar( cmd->forwardmove + cl.joystickAxis[AXIS_FORWARD] ); } cmd->upmove = ClampChar( cmd->upmove + cl.joystickAxis[AXIS_UP] ); } /* ================= CL_MouseMove ================= */ void CL_MouseMove( usercmd_t *cmd ) { float mx, my; float accelSensitivity; float rate; // allow mouse smoothing if ( m_filter->integer ) { mx = ( cl.mouseDx[0] + cl.mouseDx[1] ) * 0.5; my = ( cl.mouseDy[0] + cl.mouseDy[1] ) * 0.5; } else { mx = cl.mouseDx[cl.mouseIndex]; my = cl.mouseDy[cl.mouseIndex]; } cl.mouseIndex ^= 1; cl.mouseDx[cl.mouseIndex] = 0; cl.mouseDy[cl.mouseIndex] = 0; rate = sqrt( mx * mx + my * my ) / (float)frame_msec; accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value; // scale by FOV accelSensitivity *= cl.cgameSensitivity; if ( rate && cl_showMouseRate->integer ) { Com_Printf( "%f : %f\n", rate, accelSensitivity ); } mx *= accelSensitivity; my *= accelSensitivity; if (!mx && !my) { return; } // add mouse X/Y movement to cmd if ( in_strafe.active ) { cmd->rightmove = ClampChar( cmd->rightmove + m_side->value * mx ); } else { cl.viewangles[YAW] -= m_yaw->value * mx; } if ( (in_mlooking || cl_freelook->integer) && !in_strafe.active ) { cl.viewangles[PITCH] += m_pitch->value * my; } else { cmd->forwardmove = ClampChar( cmd->forwardmove - m_forward->value * my ); } } /* ============== CL_CmdButtons ============== */ void CL_CmdButtons( usercmd_t *cmd ) { int i; // // figure button bits // send a button bit even if the key was pressed and released in // less than a frame // for (i = 0 ; i < 15 ; i++) { if ( in_buttons[i].active || in_buttons[i].wasPressed ) { cmd->buttons |= 1 << i; } in_buttons[i].wasPressed = qfalse; } if ( cls.keyCatchers ) { cmd->buttons |= BUTTON_TALK; } // allow the game to know if any key at all is // currently pressed, even if it isn't bound to anything if ( anykeydown && !cls.keyCatchers ) { cmd->buttons |= BUTTON_ANY; } } /* ============== CL_FinishMove ============== */ void CL_FinishMove( usercmd_t *cmd ) { int i; // copy the state that the cgame is currently sending cmd->weapon = cl.cgameUserCmdValue; // send the current server time so the amount of movement // can be determined without allowing cheating cmd->serverTime = cl.serverTime; for (i=0 ; i<3 ; i++) { cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]); } } /* ================= CL_CreateCmd ================= */ usercmd_t CL_CreateCmd( void ) { usercmd_t cmd; vec3_t oldAngles; VectorCopy( cl.viewangles, oldAngles ); // keyboard angle adjustment CL_AdjustAngles (); Com_Memset( &cmd, 0, sizeof( cmd ) ); CL_CmdButtons( &cmd ); // get basic movement from keyboard CL_KeyMove( &cmd ); // get basic movement from mouse CL_MouseMove( &cmd ); // get basic movement from joystick CL_JoystickMove( &cmd ); // check to make sure the angles haven't wrapped if ( cl.viewangles[PITCH] - oldAngles[PITCH] > 90 ) { cl.viewangles[PITCH] = oldAngles[PITCH] + 90; } else if ( oldAngles[PITCH] - cl.viewangles[PITCH] > 90 ) { cl.viewangles[PITCH] = oldAngles[PITCH] - 90; } // store out the final values CL_FinishMove( &cmd ); // draw debug graphs of turning for mouse testing if ( cl_debugMove->integer ) { if ( cl_debugMove->integer == 1 ) { SCR_DebugGraph( abs(cl.viewangles[YAW] - oldAngles[YAW]), 0 ); } if ( cl_debugMove->integer == 2 ) { SCR_DebugGraph( abs(cl.viewangles[PITCH] - oldAngles[PITCH]), 0 ); } } return cmd; } /* ================= CL_CreateNewCommands Create a new usercmd_t structure for this frame ================= */ void CL_CreateNewCommands( void ) { usercmd_t *cmd; int cmdNum; // no need to create usercmds until we have a gamestate if ( cls.state < CA_PRIMED ) { return; } frame_msec = com_frameTime - old_com_frameTime; // if running less than 5fps, truncate the extra time to prevent // unexpected moves after a hitch if ( frame_msec > 200 ) { frame_msec = 200; } old_com_frameTime = com_frameTime; // generate a command for this frame cl.cmdNumber++; cmdNum = cl.cmdNumber & CMD_MASK; cl.cmds[cmdNum] = CL_CreateCmd (); cmd = &cl.cmds[cmdNum]; } /* ================= CL_ReadyToSendPacket Returns qfalse if we are over the maxpackets limit and should choke back the bandwidth a bit by not sending a packet this frame. All the commands will still get delivered in the next packet, but saving a header and getting more delta compression will reduce total bandwidth. ================= */ qboolean CL_ReadyToSendPacket( void ) { int oldPacketNum; int delta; // don't send anything if playing back a demo if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { return qfalse; } // If we are downloading, we send no less than 50ms between packets if ( *clc.downloadTempName && cls.realtime - clc.lastPacketSentTime < 50 ) { return qfalse; } // if we don't have a valid gamestate yet, only send // one packet a second if ( cls.state != CA_ACTIVE && cls.state != CA_PRIMED && !*clc.downloadTempName && cls.realtime - clc.lastPacketSentTime < 1000 ) { return qfalse; } // send every frame for loopbacks if ( clc.netchan.remoteAddress.type == NA_LOOPBACK ) { return qtrue; } // send every frame for LAN if ( Sys_IsLANAddress( clc.netchan.remoteAddress ) ) { return qtrue; } // check for exceeding cl_maxpackets if ( cl_maxpackets->integer < 15 ) { Cvar_Set( "cl_maxpackets", "15" ); } else if ( cl_maxpackets->integer > 125 ) { Cvar_Set( "cl_maxpackets", "125" ); } oldPacketNum = (clc.netchan.outgoingSequence - 1) & PACKET_MASK; delta = cls.realtime - cl.outPackets[ oldPacketNum ].p_realtime; if ( delta < 1000 / cl_maxpackets->integer ) { // the accumulated commands will go out in the next packet return qfalse; } return qtrue; } /* =================== CL_WritePacket Create and send the command packet to the server Including both the reliable commands and the usercmds During normal gameplay, a client packet will contain something like: 4 sequence number 2 qport 4 serverid 4 acknowledged sequence number 4 clc.serverCommandSequence 1 clc_move or clc_moveNoDelta 1 command count =================== */ void CL_WritePacket( void ) { msg_t buf; byte data[MAX_MSGLEN]; int i, j; usercmd_t *cmd, *oldcmd; usercmd_t nullcmd; int packetNum; int oldPacketNum; int count, key; // don't send anything if playing back a demo if ( clc.demoplaying || cls.state == CA_CINEMATIC ) { return; } Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); oldcmd = &nullcmd; MSG_Init( &buf, data, sizeof(data) ); MSG_Bitstream( &buf ); // write the current serverId so the server // can tell if this is from the current gameState MSG_WriteLong( &buf, cl.serverId ); // write the last message we received, which can // be used for delta compression, and is also used // to tell if we dropped a gamestate MSG_WriteLong( &buf, clc.serverMessageSequence ); // write the last reliable message we received MSG_WriteLong( &buf, clc.serverCommandSequence ); // write any unacknowledged clientCommands for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) { MSG_WriteByte( &buf, clc_clientCommand ); MSG_WriteLong( &buf, i ); MSG_WriteString( &buf, clc.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); } // we want to send all the usercmds that were generated in the last // few packet, so even if a couple packets are dropped in a row, // all the cmds will make it to the server if ( cl_packetdup->integer < 0 ) { Cvar_Set( "cl_packetdup", "0" ); } else if ( cl_packetdup->integer > 5 ) { Cvar_Set( "cl_packetdup", "5" ); } oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK; count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber; if ( count > MAX_PACKET_USERCMDS ) { count = MAX_PACKET_USERCMDS; Com_Printf("MAX_PACKET_USERCMDS\n"); } if ( count >= 1 ) { if ( cl_showSend->integer ) { Com_Printf( "(%i)", count ); } // begin a client move command if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting || clc.serverMessageSequence != cl.snap.messageNum ) { MSG_WriteByte (&buf, clc_moveNoDelta); } else { MSG_WriteByte (&buf, clc_move); } // write the command count MSG_WriteByte( &buf, count ); // use the checksum feed in the key key = clc.checksumFeed; // also use the message acknowledge key ^= clc.serverMessageSequence; // also use the last acknowledged server command in the key key ^= Com_HashKey(clc.serverCommands[ clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS-1) ], 32); // write all the commands, including the predicted command for ( i = 0 ; i < count ; i++ ) { j = (cl.cmdNumber - count + i + 1) & CMD_MASK; cmd = &cl.cmds[j]; MSG_WriteDeltaUsercmdKey (&buf, key, oldcmd, cmd); oldcmd = cmd; } } // // deliver the message // packetNum = clc.netchan.outgoingSequence & PACKET_MASK; cl.outPackets[ packetNum ].p_realtime = cls.realtime; cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber; clc.lastPacketSentTime = cls.realtime; if ( cl_showSend->integer ) { Com_Printf( "%i ", buf.cursize ); } CL_Netchan_Transmit (&clc.netchan, &buf); // clients never really should have messages large enough // to fragment, but in case they do, fire them all off // at once // TTimo: this causes a packet burst, which is bad karma for winsock // added a WARNING message, we'll see if there are legit situations where this happens while ( clc.netchan.unsentFragments ) { Com_DPrintf( "WARNING: #462 unsent fragments (not supposed to happen!)\n" ); CL_Netchan_TransmitNextFragment( &clc.netchan ); } } /* ================= CL_SendCmd Called every frame to builds and sends a command packet to the server. ================= */ void CL_SendCmd( void ) { // don't send any message if not connected if ( cls.state < CA_CONNECTED ) { return; } // don't send commands if paused if ( com_sv_running->integer && sv_paused->integer && cl_paused->integer ) { return; } // we create commands even if a demo is playing, CL_CreateNewCommands(); // don't send a packet if the last packet was sent too recently if ( !CL_ReadyToSendPacket() ) { if ( cl_showSend->integer ) { Com_Printf( ". " ); } return; } CL_WritePacket(); } /* ============ CL_InitInput ============ */ void CL_InitInput( void ) { Cmd_AddCommand ("centerview",IN_CenterView); Cmd_AddCommand ("+moveup",IN_UpDown); Cmd_AddCommand ("-moveup",IN_UpUp); Cmd_AddCommand ("+movedown",IN_DownDown); Cmd_AddCommand ("-movedown",IN_DownUp); Cmd_AddCommand ("+left",IN_LeftDown); Cmd_AddCommand ("-left",IN_LeftUp); Cmd_AddCommand ("+right",IN_RightDown); Cmd_AddCommand ("-right",IN_RightUp); Cmd_AddCommand ("+forward",IN_ForwardDown); Cmd_AddCommand ("-forward",IN_ForwardUp); Cmd_AddCommand ("+back",IN_BackDown); Cmd_AddCommand ("-back",IN_BackUp); Cmd_AddCommand ("+lookup", IN_LookupDown); Cmd_AddCommand ("-lookup", IN_LookupUp); Cmd_AddCommand ("+lookdown", IN_LookdownDown); Cmd_AddCommand ("-lookdown", IN_LookdownUp); Cmd_AddCommand ("+strafe", IN_StrafeDown); Cmd_AddCommand ("-strafe", IN_StrafeUp); Cmd_AddCommand ("+moveleft", IN_MoveleftDown); Cmd_AddCommand ("-moveleft", IN_MoveleftUp); Cmd_AddCommand ("+moveright", IN_MoverightDown); Cmd_AddCommand ("-moveright", IN_MoverightUp); Cmd_AddCommand ("+speed", IN_SpeedDown); Cmd_AddCommand ("-speed", IN_SpeedUp); Cmd_AddCommand ("+attack", IN_Button0Down); Cmd_AddCommand ("-attack", IN_Button0Up); Cmd_AddCommand ("+button0", IN_Button0Down); Cmd_AddCommand ("-button0", IN_Button0Up); Cmd_AddCommand ("+button1", IN_Button1Down); Cmd_AddCommand ("-button1", IN_Button1Up); Cmd_AddCommand ("+button2", IN_Button2Down); Cmd_AddCommand ("-button2", IN_Button2Up); Cmd_AddCommand ("+button3", IN_Button3Down); Cmd_AddCommand ("-button3", IN_Button3Up); Cmd_AddCommand ("+button4", IN_Button4Down); Cmd_AddCommand ("-button4", IN_Button4Up); Cmd_AddCommand ("+button5", IN_Button5Down); Cmd_AddCommand ("-button5", IN_Button5Up); Cmd_AddCommand ("+button6", IN_Button6Down); Cmd_AddCommand ("-button6", IN_Button6Up); Cmd_AddCommand ("+button7", IN_Button7Down); Cmd_AddCommand ("-button7", IN_Button7Up); Cmd_AddCommand ("+button8", IN_Button8Down); Cmd_AddCommand ("-button8", IN_Button8Up); Cmd_AddCommand ("+button9", IN_Button9Down); Cmd_AddCommand ("-button9", IN_Button9Up); Cmd_AddCommand ("+button10", IN_Button10Down); Cmd_AddCommand ("-button10", IN_Button10Up); Cmd_AddCommand ("+button11", IN_Button11Down); Cmd_AddCommand ("-button11", IN_Button11Up); Cmd_AddCommand ("+button12", IN_Button12Down); Cmd_AddCommand ("-button12", IN_Button12Up); Cmd_AddCommand ("+button13", IN_Button13Down); Cmd_AddCommand ("-button13", IN_Button13Up); Cmd_AddCommand ("+button14", IN_Button14Down); Cmd_AddCommand ("-button14", IN_Button14Up); Cmd_AddCommand ("+mlook", IN_MLookDown); Cmd_AddCommand ("-mlook", IN_MLookUp); cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0); cl_debugMove = Cvar_Get ("cl_debugMove", "0", 0); } ================================================ FILE: code/client/cl_keys.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "client.h" /* key up events are sent even if in console mode */ field_t historyEditLines[COMMAND_HISTORY]; int nextHistoryLine; // the last line in the history buffer, not masked int historyLine; // the line being displayed from history buffer // will be <= nextHistoryLine field_t g_consoleField; field_t chatField; qboolean chat_team; int chat_playerNum; qboolean key_overstrikeMode; qboolean anykeydown; qkey_t keys[MAX_KEYS]; typedef struct { char *name; int keynum; } keyname_t; // names not in this list can either be lowercase ascii, or '0xnn' hex sequences keyname_t keynames[] = { {"TAB", K_TAB}, {"ENTER", K_ENTER}, {"ESCAPE", K_ESCAPE}, {"SPACE", K_SPACE}, {"BACKSPACE", K_BACKSPACE}, {"UPARROW", K_UPARROW}, {"DOWNARROW", K_DOWNARROW}, {"LEFTARROW", K_LEFTARROW}, {"RIGHTARROW", K_RIGHTARROW}, {"ALT", K_ALT}, {"CTRL", K_CTRL}, {"SHIFT", K_SHIFT}, {"COMMAND", K_COMMAND}, {"CAPSLOCK", K_CAPSLOCK}, {"F1", K_F1}, {"F2", K_F2}, {"F3", K_F3}, {"F4", K_F4}, {"F5", K_F5}, {"F6", K_F6}, {"F7", K_F7}, {"F8", K_F8}, {"F9", K_F9}, {"F10", K_F10}, {"F11", K_F11}, {"F12", K_F12}, {"INS", K_INS}, {"DEL", K_DEL}, {"PGDN", K_PGDN}, {"PGUP", K_PGUP}, {"HOME", K_HOME}, {"END", K_END}, {"MOUSE1", K_MOUSE1}, {"MOUSE2", K_MOUSE2}, {"MOUSE3", K_MOUSE3}, {"MOUSE4", K_MOUSE4}, {"MOUSE5", K_MOUSE5}, {"MWHEELUP", K_MWHEELUP }, {"MWHEELDOWN", K_MWHEELDOWN }, {"JOY1", K_JOY1}, {"JOY2", K_JOY2}, {"JOY3", K_JOY3}, {"JOY4", K_JOY4}, {"JOY5", K_JOY5}, {"JOY6", K_JOY6}, {"JOY7", K_JOY7}, {"JOY8", K_JOY8}, {"JOY9", K_JOY9}, {"JOY10", K_JOY10}, {"JOY11", K_JOY11}, {"JOY12", K_JOY12}, {"JOY13", K_JOY13}, {"JOY14", K_JOY14}, {"JOY15", K_JOY15}, {"JOY16", K_JOY16}, {"JOY17", K_JOY17}, {"JOY18", K_JOY18}, {"JOY19", K_JOY19}, {"JOY20", K_JOY20}, {"JOY21", K_JOY21}, {"JOY22", K_JOY22}, {"JOY23", K_JOY23}, {"JOY24", K_JOY24}, {"JOY25", K_JOY25}, {"JOY26", K_JOY26}, {"JOY27", K_JOY27}, {"JOY28", K_JOY28}, {"JOY29", K_JOY29}, {"JOY30", K_JOY30}, {"JOY31", K_JOY31}, {"JOY32", K_JOY32}, {"AUX1", K_AUX1}, {"AUX2", K_AUX2}, {"AUX3", K_AUX3}, {"AUX4", K_AUX4}, {"AUX5", K_AUX5}, {"AUX6", K_AUX6}, {"AUX7", K_AUX7}, {"AUX8", K_AUX8}, {"AUX9", K_AUX9}, {"AUX10", K_AUX10}, {"AUX11", K_AUX11}, {"AUX12", K_AUX12}, {"AUX13", K_AUX13}, {"AUX14", K_AUX14}, {"AUX15", K_AUX15}, {"AUX16", K_AUX16}, {"KP_HOME", K_KP_HOME }, {"KP_UPARROW", K_KP_UPARROW }, {"KP_PGUP", K_KP_PGUP }, {"KP_LEFTARROW", K_KP_LEFTARROW }, {"KP_5", K_KP_5 }, {"KP_RIGHTARROW", K_KP_RIGHTARROW }, {"KP_END", K_KP_END }, {"KP_DOWNARROW", K_KP_DOWNARROW }, {"KP_PGDN", K_KP_PGDN }, {"KP_ENTER", K_KP_ENTER }, {"KP_INS", K_KP_INS }, {"KP_DEL", K_KP_DEL }, {"KP_SLASH", K_KP_SLASH }, {"KP_MINUS", K_KP_MINUS }, {"KP_PLUS", K_KP_PLUS }, {"KP_NUMLOCK", K_KP_NUMLOCK }, {"KP_STAR", K_KP_STAR }, {"KP_EQUALS", K_KP_EQUALS }, {"PAUSE", K_PAUSE}, {"SEMICOLON", ';'}, // because a raw semicolon seperates commands {NULL,0} }; /* ============================================================================= EDIT FIELDS ============================================================================= */ /* =================== Field_Draw Handles horizontal scrolling and cursor blinking x, y, amd width are in pixels =================== */ void Field_VariableSizeDraw( field_t *edit, int x, int y, int width, int size, qboolean showCursor ) { int len; int drawLen; int prestep; int cursorChar; char str[MAX_STRING_CHARS]; int i; drawLen = edit->widthInChars; len = strlen( edit->buffer ) + 1; // guarantee that cursor will be visible if ( len <= drawLen ) { prestep = 0; } else { if ( edit->scroll + drawLen > len ) { edit->scroll = len - drawLen; if ( edit->scroll < 0 ) { edit->scroll = 0; } } prestep = edit->scroll; /* if ( edit->cursor < len - drawLen ) { prestep = edit->cursor; // cursor at start } else { prestep = len - drawLen; } */ } if ( prestep + drawLen > len ) { drawLen = len - prestep; } // extract characters from the field at if ( drawLen >= MAX_STRING_CHARS ) { Com_Error( ERR_DROP, "drawLen >= MAX_STRING_CHARS" ); } Com_Memcpy( str, edit->buffer + prestep, drawLen ); str[ drawLen ] = 0; // draw it if ( size == SMALLCHAR_WIDTH ) { float color[4]; color[0] = color[1] = color[2] = color[3] = 1.0; SCR_DrawSmallStringExt( x, y, str, color, qfalse ); } else { // draw big string with drop shadow SCR_DrawBigString( x, y, str, 1.0 ); } // draw the cursor if ( !showCursor ) { return; } if ( (int)( cls.realtime >> 8 ) & 1 ) { return; // off blink } if ( key_overstrikeMode ) { cursorChar = 11; } else { cursorChar = 10; } i = drawLen - ( Q_PrintStrlen( str ) + 1 ); if ( size == SMALLCHAR_WIDTH ) { SCR_DrawSmallChar( x + ( edit->cursor - prestep - i ) * size, y, cursorChar ); } else { str[0] = cursorChar; str[1] = 0; SCR_DrawBigString( x + ( edit->cursor - prestep - i ) * size, y, str, 1.0 ); } } void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor ) { Field_VariableSizeDraw( edit, x, y, width, SMALLCHAR_WIDTH, showCursor ); } void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor ) { Field_VariableSizeDraw( edit, x, y, width, BIGCHAR_WIDTH, showCursor ); } /* ================ Field_Paste ================ */ void Field_Paste( field_t *edit ) { char *cbd; int pasteLen, i; cbd = Sys_GetClipboardData(); if ( !cbd ) { return; } // send as if typed, so insert / overstrike works properly pasteLen = strlen( cbd ); for ( i = 0 ; i < pasteLen ; i++ ) { Field_CharEvent( edit, cbd[i] ); } Z_Free( cbd ); } /* ================= Field_KeyDownEvent Performs the basic line editing functions for the console, in-game talk, and menu fields Key events are used for non-printable characters, others are gotten from char events. ================= */ void Field_KeyDownEvent( field_t *edit, int key ) { int len; // shift-insert is paste if ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keys[K_SHIFT].down ) { Field_Paste( edit ); return; } len = strlen( edit->buffer ); if ( key == K_DEL ) { if ( edit->cursor < len ) { memmove( edit->buffer + edit->cursor, edit->buffer + edit->cursor + 1, len - edit->cursor ); } return; } if ( key == K_RIGHTARROW ) { if ( edit->cursor < len ) { edit->cursor++; } if ( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) { edit->scroll++; } return; } if ( key == K_LEFTARROW ) { if ( edit->cursor > 0 ) { edit->cursor--; } if ( edit->cursor < edit->scroll ) { edit->scroll--; } return; } if ( key == K_HOME || ( tolower(key) == 'a' && keys[K_CTRL].down ) ) { edit->cursor = 0; return; } if ( key == K_END || ( tolower(key) == 'e' && keys[K_CTRL].down ) ) { edit->cursor = len; return; } if ( key == K_INS ) { key_overstrikeMode = !key_overstrikeMode; return; } } /* ================== Field_CharEvent ================== */ void Field_CharEvent( field_t *edit, int ch ) { int len; if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste Field_Paste( edit ); return; } if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field Field_Clear( edit ); return; } len = strlen( edit->buffer ); if ( ch == 'h' - 'a' + 1 ) { // ctrl-h is backspace if ( edit->cursor > 0 ) { memmove( edit->buffer + edit->cursor - 1, edit->buffer + edit->cursor, len + 1 - edit->cursor ); edit->cursor--; if ( edit->cursor < edit->scroll ) { edit->scroll--; } } return; } if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home edit->cursor = 0; edit->scroll = 0; return; } if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end edit->cursor = len; edit->scroll = edit->cursor - edit->widthInChars; return; } // // ignore any other non printable chars // if ( ch < 32 ) { return; } if ( key_overstrikeMode ) { if ( edit->cursor == MAX_EDIT_LINE - 1 ) return; edit->buffer[edit->cursor] = ch; edit->cursor++; } else { // insert mode if ( len == MAX_EDIT_LINE - 1 ) { return; // all full } memmove( edit->buffer + edit->cursor + 1, edit->buffer + edit->cursor, len + 1 - edit->cursor ); edit->buffer[edit->cursor] = ch; edit->cursor++; } if ( edit->cursor >= edit->widthInChars ) { edit->scroll++; } if ( edit->cursor == len + 1) { edit->buffer[edit->cursor] = 0; } } /* ============================================================================= CONSOLE LINE EDITING ============================================================================== */ /* ==================== Console_Key Handles history and console scrollback ==================== */ void Console_Key (int key) { // ctrl-L clears screen if ( key == 'l' && keys[K_CTRL].down ) { Cbuf_AddText ("clear\n"); return; } // enter finishes the line if ( key == K_ENTER || key == K_KP_ENTER ) { // if not in the game explicitly prepent a slash if needed if ( cls.state != CA_ACTIVE && g_consoleField.buffer[0] != '\\' && g_consoleField.buffer[0] != '/' ) { char temp[MAX_STRING_CHARS]; Q_strncpyz( temp, g_consoleField.buffer, sizeof( temp ) ); Com_sprintf( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\\%s", temp ); g_consoleField.cursor++; } Com_Printf ( "]%s\n", g_consoleField.buffer ); // leading slash is an explicit command if ( g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/' ) { Cbuf_AddText( g_consoleField.buffer+1 ); // valid command Cbuf_AddText ("\n"); } else { // other text will be chat messages if ( !g_consoleField.buffer[0] ) { return; // empty lines just scroll the console without adding to history } else { Cbuf_AddText ("cmd say "); Cbuf_AddText( g_consoleField.buffer ); Cbuf_AddText ("\n"); } } // copy line to history buffer historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField; nextHistoryLine++; historyLine = nextHistoryLine; Field_Clear( &g_consoleField ); g_consoleField.widthInChars = g_console_field_width; if ( cls.state == CA_DISCONNECTED ) { SCR_UpdateScreen (); // force an update, because the command } // may take some time return; } // command completion if (key == K_TAB) { Field_CompleteCommand(&g_consoleField); return; } // command history (ctrl-p ctrl-n for unix style) if ( (key == K_MWHEELUP && keys[K_SHIFT].down) || ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) || ( ( tolower(key) == 'p' ) && keys[K_CTRL].down ) ) { if ( nextHistoryLine - historyLine < COMMAND_HISTORY && historyLine > 0 ) { historyLine--; } g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; return; } if ( (key == K_MWHEELDOWN && keys[K_SHIFT].down) || ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) || ( ( tolower(key) == 'n' ) && keys[K_CTRL].down ) ) { if (historyLine == nextHistoryLine) return; historyLine++; g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; return; } // console scrolling if ( key == K_PGUP ) { Con_PageUp(); return; } if ( key == K_PGDN) { Con_PageDown(); return; } if ( key == K_MWHEELUP) { //----(SA) added some mousewheel functionality to the console Con_PageUp(); if(keys[K_CTRL].down) { // hold to accelerate scrolling Con_PageUp(); Con_PageUp(); } return; } if ( key == K_MWHEELDOWN) { //----(SA) added some mousewheel functionality to the console Con_PageDown(); if(keys[K_CTRL].down) { // hold to accelerate scrolling Con_PageDown(); Con_PageDown(); } return; } // ctrl-home = top of console if ( key == K_HOME && keys[K_CTRL].down ) { Con_Top(); return; } // ctrl-end = bottom of console if ( key == K_END && keys[K_CTRL].down ) { Con_Bottom(); return; } // pass to the normal editline routine Field_KeyDownEvent( &g_consoleField, key ); } //============================================================================ /* ================ Message_Key In game talk message ================ */ void Message_Key( int key ) { char buffer[MAX_STRING_CHARS]; if (key == K_ESCAPE) { cls.keyCatchers &= ~KEYCATCH_MESSAGE; Field_Clear( &chatField ); return; } if ( key == K_ENTER || key == K_KP_ENTER ) { if ( chatField.buffer[0] && cls.state == CA_ACTIVE ) { if (chat_playerNum != -1 ) Com_sprintf( buffer, sizeof( buffer ), "tell %i \"%s\"\n", chat_playerNum, chatField.buffer ); else if (chat_team) Com_sprintf( buffer, sizeof( buffer ), "say_team \"%s\"\n", chatField.buffer ); else Com_sprintf( buffer, sizeof( buffer ), "say \"%s\"\n", chatField.buffer ); CL_AddReliableCommand( buffer ); } cls.keyCatchers &= ~KEYCATCH_MESSAGE; Field_Clear( &chatField ); return; } Field_KeyDownEvent( &chatField, key ); } //============================================================================ qboolean Key_GetOverstrikeMode( void ) { return key_overstrikeMode; } void Key_SetOverstrikeMode( qboolean state ) { key_overstrikeMode = state; } /* =================== Key_IsDown =================== */ qboolean Key_IsDown( int keynum ) { if ( keynum == -1 ) { return qfalse; } return keys[keynum].down; } /* =================== Key_StringToKeynum Returns a key number to be used to index keys[] by looking at the given string. Single ascii characters return themselves, while the K_* names are matched up. 0x11 will be interpreted as raw hex, which will allow new controlers to be configured even if they don't have defined names. =================== */ int Key_StringToKeynum( char *str ) { keyname_t *kn; if ( !str || !str[0] ) { return -1; } if ( !str[1] ) { return str[0]; } // check for hex code if ( str[0] == '0' && str[1] == 'x' && strlen( str ) == 4) { int n1, n2; n1 = str[2]; if ( n1 >= '0' && n1 <= '9' ) { n1 -= '0'; } else if ( n1 >= 'a' && n1 <= 'f' ) { n1 = n1 - 'a' + 10; } else { n1 = 0; } n2 = str[3]; if ( n2 >= '0' && n2 <= '9' ) { n2 -= '0'; } else if ( n2 >= 'a' && n2 <= 'f' ) { n2 = n2 - 'a' + 10; } else { n2 = 0; } return n1 * 16 + n2; } // scan for a text match for ( kn=keynames ; kn->name ; kn++ ) { if ( !Q_stricmp( str,kn->name ) ) return kn->keynum; } return -1; } /* =================== Key_KeynumToString Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the given keynum. =================== */ char *Key_KeynumToString( int keynum ) { keyname_t *kn; static char tinystr[5]; int i, j; if ( keynum == -1 ) { return ""; } if ( keynum < 0 || keynum > 255 ) { return ""; } // check for printable ascii (don't use quote) if ( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' ) { tinystr[0] = keynum; tinystr[1] = 0; return tinystr; } // check for a key string for ( kn=keynames ; kn->name ; kn++ ) { if (keynum == kn->keynum) { return kn->name; } } // make a hex string i = keynum >> 4; j = keynum & 15; tinystr[0] = '0'; tinystr[1] = 'x'; tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0'; tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0'; tinystr[4] = 0; return tinystr; } /* =================== Key_SetBinding =================== */ void Key_SetBinding( int keynum, const char *binding ) { if ( keynum == -1 ) { return; } // free old bindings if ( keys[ keynum ].binding ) { Z_Free( keys[ keynum ].binding ); } // allocate memory for new binding keys[keynum].binding = CopyString( binding ); // consider this like modifying an archived cvar, so the // file write will be triggered at the next oportunity cvar_modifiedFlags |= CVAR_ARCHIVE; } /* =================== Key_GetBinding =================== */ char *Key_GetBinding( int keynum ) { if ( keynum == -1 ) { return ""; } return keys[ keynum ].binding; } /* =================== Key_GetKey =================== */ int Key_GetKey(const char *binding) { int i; if (binding) { for (i=0 ; i<256 ; i++) { if (keys[i].binding && Q_stricmp(binding, keys[i].binding) == 0) { return i; } } } return -1; } /* =================== Key_Unbind_f =================== */ void Key_Unbind_f (void) { int b; if (Cmd_Argc() != 2) { Com_Printf ("unbind : remove commands from a key\n"); return; } b = Key_StringToKeynum (Cmd_Argv(1)); if (b==-1) { Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); return; } Key_SetBinding (b, ""); } /* =================== Key_Unbindall_f =================== */ void Key_Unbindall_f (void) { int i; for (i=0 ; i<256 ; i++) if (keys[i].binding) Key_SetBinding (i, ""); } /* =================== Key_Bind_f =================== */ void Key_Bind_f (void) { int i, c, b; char cmd[1024]; c = Cmd_Argc(); if (c < 2) { Com_Printf ("bind [command] : attach a command to a key\n"); return; } b = Key_StringToKeynum (Cmd_Argv(1)); if (b==-1) { Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); return; } if (c == 2) { if (keys[b].binding) Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keys[b].binding ); else Com_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) ); return; } // copy the rest of the command line cmd[0] = 0; // start out with a null string for (i=2 ; i< c ; i++) { strcat (cmd, Cmd_Argv(i)); if (i != (c-1)) strcat (cmd, " "); } Key_SetBinding (b, cmd); } /* ============ Key_WriteBindings Writes lines containing "bind key value" ============ */ void Key_WriteBindings( fileHandle_t f ) { int i; FS_Printf (f, "unbindall\n" ); for (i=0 ; i<256 ; i++) { if (keys[i].binding && keys[i].binding[0] ) { FS_Printf (f, "bind %s \"%s\"\n", Key_KeynumToString(i), keys[i].binding); } } } /* ============ Key_Bindlist_f ============ */ void Key_Bindlist_f( void ) { int i; for ( i = 0 ; i < 256 ; i++ ) { if ( keys[i].binding && keys[i].binding[0] ) { Com_Printf( "%s \"%s\"\n", Key_KeynumToString(i), keys[i].binding ); } } } /* =================== CL_InitKeyCommands =================== */ void CL_InitKeyCommands( void ) { // register our functions Cmd_AddCommand ("bind",Key_Bind_f); Cmd_AddCommand ("unbind",Key_Unbind_f); Cmd_AddCommand ("unbindall",Key_Unbindall_f); Cmd_AddCommand ("bindlist",Key_Bindlist_f); } /* =================== CL_AddKeyUpCommands =================== */ void CL_AddKeyUpCommands( int key, char *kb ) { int i; char button[1024], *buttonPtr; char cmd[1024]; qboolean keyevent; if ( !kb ) { return; } keyevent = qfalse; buttonPtr = button; for ( i = 0; ; i++ ) { if ( kb[i] == ';' || !kb[i] ) { *buttonPtr = '\0'; if ( button[0] == '+') { // button commands add keynum and time as parms so that multiple // sources can be discriminated and subframe corrected Com_sprintf (cmd, sizeof(cmd), "-%s %i %i\n", button+1, key, time); Cbuf_AddText (cmd); keyevent = qtrue; } else { if (keyevent) { // down-only command Cbuf_AddText (button); Cbuf_AddText ("\n"); } } buttonPtr = button; while ( (kb[i] <= ' ' || kb[i] == ';') && kb[i] != 0 ) { i++; } } *buttonPtr++ = kb[i]; if ( !kb[i] ) { break; } } } /* =================== CL_KeyEvent Called by the system for both key up and key down events =================== */ void CL_KeyEvent (int key, qboolean down, unsigned time) { char *kb; char cmd[1024]; // update auto-repeat status and BUTTON_ANY status keys[key].down = down; if (down) { keys[key].repeats++; if ( keys[key].repeats == 1) { anykeydown++; } } else { keys[key].repeats = 0; anykeydown--; if (anykeydown < 0) { anykeydown = 0; } } #ifdef __linux__ if (key == K_ENTER) { if (down) { if (keys[K_ALT].down) { Key_ClearStates(); if (Cvar_VariableValue("r_fullscreen") == 0) { Com_Printf("Switching to fullscreen rendering\n"); Cvar_Set("r_fullscreen", "1"); } else { Com_Printf("Switching to windowed rendering\n"); Cvar_Set("r_fullscreen", "0"); } Cbuf_ExecuteText( EXEC_APPEND, "vid_restart\n"); return; } } } #endif // console key is hardcoded, so the user can never unbind it if (key == '`' || key == '~') { if (!down) { return; } Con_ToggleConsole_f (); return; } // keys can still be used for bound actions if ( down && ( key < 128 || key == K_MOUSE1 ) && ( clc.demoplaying || cls.state == CA_CINEMATIC ) && !cls.keyCatchers) { if (Cvar_VariableValue ("com_cameraMode") == 0) { Cvar_Set ("nextdemo",""); key = K_ESCAPE; } } // escape is always handled special if ( key == K_ESCAPE && down ) { if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { // clear message mode Message_Key( key ); return; } // escape always gets out of CGAME stuff if (cls.keyCatchers & KEYCATCH_CGAME) { cls.keyCatchers &= ~KEYCATCH_CGAME; VM_Call (cgvm, CG_EVENT_HANDLING, CGAME_EVENT_NONE); return; } if ( !( cls.keyCatchers & KEYCATCH_UI ) ) { if ( cls.state == CA_ACTIVE && !clc.demoplaying ) { VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME ); } else { CL_Disconnect_f(); S_StopAllSounds(); VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); } return; } VM_Call( uivm, UI_KEY_EVENT, key, down ); return; } // // key up events only perform actions if the game key binding is // a button command (leading + sign). These will be processed even in // console mode and menu mode, to keep the character from continuing // an action started before a mode switch. // if (!down) { kb = keys[key].binding; CL_AddKeyUpCommands( key, kb ); if ( cls.keyCatchers & KEYCATCH_UI && uivm ) { VM_Call( uivm, UI_KEY_EVENT, key, down ); } else if ( cls.keyCatchers & KEYCATCH_CGAME && cgvm ) { VM_Call( cgvm, CG_KEY_EVENT, key, down ); } return; } // distribute the key down event to the apropriate handler if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { Console_Key( key ); } else if ( cls.keyCatchers & KEYCATCH_UI ) { if ( uivm ) { VM_Call( uivm, UI_KEY_EVENT, key, down ); } } else if ( cls.keyCatchers & KEYCATCH_CGAME ) { if ( cgvm ) { VM_Call( cgvm, CG_KEY_EVENT, key, down ); } } else if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { Message_Key( key ); } else if ( cls.state == CA_DISCONNECTED ) { Console_Key( key ); } else { // send the bound action kb = keys[key].binding; if ( !kb ) { if (key >= 200) { Com_Printf ("%s is unbound, use controls menu to set.\n" , Key_KeynumToString( key ) ); } } else if (kb[0] == '+') { int i; char button[1024], *buttonPtr; buttonPtr = button; for ( i = 0; ; i++ ) { if ( kb[i] == ';' || !kb[i] ) { *buttonPtr = '\0'; if ( button[0] == '+') { // button commands add keynum and time as parms so that multiple // sources can be discriminated and subframe corrected Com_sprintf (cmd, sizeof(cmd), "%s %i %i\n", button, key, time); Cbuf_AddText (cmd); } else { // down-only command Cbuf_AddText (button); Cbuf_AddText ("\n"); } buttonPtr = button; while ( (kb[i] <= ' ' || kb[i] == ';') && kb[i] != 0 ) { i++; } } *buttonPtr++ = kb[i]; if ( !kb[i] ) { break; } } } else { // down-only command Cbuf_AddText (kb); Cbuf_AddText ("\n"); } } } /* =================== CL_CharEvent Normal keyboard characters, already shifted / capslocked / etc =================== */ void CL_CharEvent( int key ) { // the console key should never be used as a char if ( key == '`' || key == '~' ) { return; } // distribute the key down event to the apropriate handler if ( cls.keyCatchers & KEYCATCH_CONSOLE ) { Field_CharEvent( &g_consoleField, key ); } else if ( cls.keyCatchers & KEYCATCH_UI ) { VM_Call( uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue ); } else if ( cls.keyCatchers & KEYCATCH_MESSAGE ) { Field_CharEvent( &chatField, key ); } else if ( cls.state == CA_DISCONNECTED ) { Field_CharEvent( &g_consoleField, key ); } } /* =================== Key_ClearStates =================== */ void Key_ClearStates (void) { int i; anykeydown = qfalse; for ( i=0 ; i < MAX_KEYS ; i++ ) { if ( keys[i].down ) { CL_KeyEvent( i, qfalse, 0 ); } keys[i].down = 0; keys[i].repeats = 0; } } ================================================ FILE: code/client/cl_main.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // cl_main.c -- client main loop #include "client.h" #include cvar_t *cl_nodelta; cvar_t *cl_debugMove; cvar_t *cl_noprint; cvar_t *cl_motd; cvar_t *rcon_client_password; cvar_t *rconAddress; cvar_t *cl_timeout; cvar_t *cl_maxpackets; cvar_t *cl_packetdup; cvar_t *cl_timeNudge; cvar_t *cl_showTimeDelta; cvar_t *cl_freezeDemo; cvar_t *cl_shownet; cvar_t *cl_showSend; cvar_t *cl_timedemo; cvar_t *cl_avidemo; cvar_t *cl_forceavidemo; cvar_t *cl_freelook; cvar_t *cl_sensitivity; cvar_t *cl_mouseAccel; cvar_t *cl_showMouseRate; cvar_t *m_pitch; cvar_t *m_yaw; cvar_t *m_forward; cvar_t *m_side; cvar_t *m_filter; cvar_t *cl_activeAction; cvar_t *cl_motdString; cvar_t *cl_allowDownload; cvar_t *cl_conXOffset; cvar_t *cl_inGameVideo; cvar_t *cl_serverStatusResendTime; cvar_t *cl_trn; clientActive_t cl; clientConnection_t clc; clientStatic_t cls; vm_t *cgvm; // Structure containing functions exported from refresh DLL refexport_t re; ping_t cl_pinglist[MAX_PINGREQUESTS]; typedef struct serverStatus_s { char string[BIG_INFO_STRING]; netadr_t address; int time, startTime; qboolean pending; qboolean print; qboolean retrieved; } serverStatus_t; serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS]; int serverStatusCount; #if defined __USEA3D && defined __A3D_GEOM void hA3Dg_ExportRenderGeom (refexport_t *incoming_re); #endif extern void SV_BotFrame( int time ); void CL_CheckForResend( void ); void CL_ShowIP_f(void); void CL_ServerStatus_f(void); void CL_ServerStatusResponse( netadr_t from, msg_t *msg ); /* =============== CL_CDDialog Called by Com_Error when a cd is needed =============== */ void CL_CDDialog( void ) { cls.cddialog = qtrue; // start it next frame } /* ======================================================================= CLIENT RELIABLE COMMAND COMMUNICATION ======================================================================= */ /* ====================== CL_AddReliableCommand The given command will be transmitted to the server, and is gauranteed to not have future usercmd_t executed before it is executed ====================== */ void CL_AddReliableCommand( const char *cmd ) { int index; // if we would be losing an old command that hasn't been acknowledged, // we must drop the connection if ( clc.reliableSequence - clc.reliableAcknowledge > MAX_RELIABLE_COMMANDS ) { Com_Error( ERR_DROP, "Client command overflow" ); } clc.reliableSequence++; index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); Q_strncpyz( clc.reliableCommands[ index ], cmd, sizeof( clc.reliableCommands[ index ] ) ); } /* ====================== CL_ChangeReliableCommand ====================== */ void CL_ChangeReliableCommand( void ) { int r, index, l; r = clc.reliableSequence - (random() * 5); index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); l = strlen(clc.reliableCommands[ index ]); if ( l >= MAX_STRING_CHARS - 1 ) { l = MAX_STRING_CHARS - 2; } clc.reliableCommands[ index ][ l ] = '\n'; clc.reliableCommands[ index ][ l+1 ] = '\0'; } /* ======================================================================= CLIENT SIDE DEMO RECORDING ======================================================================= */ /* ==================== CL_WriteDemoMessage Dumps the current net message, prefixed by the length ==================== */ void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) { int len, swlen; // write the packet sequence len = clc.serverMessageSequence; swlen = LittleLong( len ); FS_Write (&swlen, 4, clc.demofile); // skip the packet sequencing information len = msg->cursize - headerBytes; swlen = LittleLong(len); FS_Write (&swlen, 4, clc.demofile); FS_Write ( msg->data + headerBytes, len, clc.demofile ); } /* ==================== CL_StopRecording_f stop recording a demo ==================== */ void CL_StopRecord_f( void ) { int len; if ( !clc.demorecording ) { Com_Printf ("Not recording a demo.\n"); return; } // finish up len = -1; FS_Write (&len, 4, clc.demofile); FS_Write (&len, 4, clc.demofile); FS_FCloseFile (clc.demofile); clc.demofile = 0; clc.demorecording = qfalse; clc.spDemoRecording = qfalse; Com_Printf ("Stopped demo.\n"); } /* ================== CL_DemoFilename ================== */ void CL_DemoFilename( int number, char *fileName ) { int a,b,c,d; if ( number < 0 || number > 9999 ) { Com_sprintf( fileName, MAX_OSPATH, "demo9999.tga" ); return; } a = number / 1000; number -= a*1000; b = number / 100; number -= b*100; c = number / 10; number -= c*10; d = number; Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i" , a, b, c, d ); } /* ==================== CL_Record_f record Begins recording a demo from the current position ==================== */ static char demoName[MAX_QPATH]; // compiler bug workaround void CL_Record_f( void ) { char name[MAX_OSPATH]; byte bufData[MAX_MSGLEN]; msg_t buf; int i; int len; entityState_t *ent; entityState_t nullstate; char *s; if ( Cmd_Argc() > 2 ) { Com_Printf ("record \n"); return; } if ( clc.demorecording ) { if (!clc.spDemoRecording) { Com_Printf ("Already recording.\n"); } return; } if ( cls.state != CA_ACTIVE ) { Com_Printf ("You must be in a level to record.\n"); return; } // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. if ( !Cvar_VariableValue( "g_synchronousClients" ) ) { Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); } if ( Cmd_Argc() == 2 ) { s = Cmd_Argv(1); Q_strncpyz( demoName, s, sizeof( demoName ) ); Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); } else { int number; // scan for a free demo name for ( number = 0 ; number <= 9999 ; number++ ) { CL_DemoFilename( number, demoName ); Com_sprintf (name, sizeof(name), "demos/%s.dm_%d", demoName, PROTOCOL_VERSION ); len = FS_ReadFile( name, NULL ); if ( len <= 0 ) { break; // file doesn't exist } } } // open the demo file Com_Printf ("recording to %s.\n", name); clc.demofile = FS_FOpenFileWrite( name ); if ( !clc.demofile ) { Com_Printf ("ERROR: couldn't open.\n"); return; } clc.demorecording = qtrue; if (Cvar_VariableValue("ui_recordSPDemo")) { clc.spDemoRecording = qtrue; } else { clc.spDemoRecording = qfalse; } Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); // don't start saving messages until a non-delta compressed message is received clc.demowaiting = qtrue; // write out the gamestate message MSG_Init (&buf, bufData, sizeof(bufData)); MSG_Bitstream(&buf); // NOTE, MRE: all server->client messages now acknowledge MSG_WriteLong( &buf, clc.reliableSequence ); MSG_WriteByte (&buf, svc_gamestate); MSG_WriteLong (&buf, clc.serverCommandSequence ); // configstrings for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { if ( !cl.gameState.stringOffsets[i] ) { continue; } s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; MSG_WriteByte (&buf, svc_configstring); MSG_WriteShort (&buf, i); MSG_WriteBigString (&buf, s); } // baselines Com_Memset (&nullstate, 0, sizeof(nullstate)); for ( i = 0; i < MAX_GENTITIES ; i++ ) { ent = &cl.entityBaselines[i]; if ( !ent->number ) { continue; } MSG_WriteByte (&buf, svc_baseline); MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue ); } MSG_WriteByte( &buf, svc_EOF ); // finished writing the gamestate stuff // write the client num MSG_WriteLong(&buf, clc.clientNum); // write the checksum feed MSG_WriteLong(&buf, clc.checksumFeed); // finished writing the client packet MSG_WriteByte( &buf, svc_EOF ); // write it to the demo file len = LittleLong( clc.serverMessageSequence - 1 ); FS_Write (&len, 4, clc.demofile); len = LittleLong (buf.cursize); FS_Write (&len, 4, clc.demofile); FS_Write (buf.data, buf.cursize, clc.demofile); // the rest of the demo file will be copied from net messages } /* ======================================================================= CLIENT SIDE DEMO PLAYBACK ======================================================================= */ /* ================= CL_DemoCompleted ================= */ void CL_DemoCompleted( void ) { if (cl_timedemo && cl_timedemo->integer) { int time; time = Sys_Milliseconds() - clc.timeDemoStart; if ( time > 0 ) { Com_Printf ("%i frames, %3.1f seconds: %3.1f fps\n", clc.timeDemoFrames, time/1000.0, clc.timeDemoFrames*1000.0 / time); } } CL_Disconnect( qtrue ); CL_NextDemo(); } /* ================= CL_ReadDemoMessage ================= */ void CL_ReadDemoMessage( void ) { int r; msg_t buf; byte bufData[ MAX_MSGLEN ]; int s; if ( !clc.demofile ) { CL_DemoCompleted (); return; } // get the sequence number r = FS_Read( &s, 4, clc.demofile); if ( r != 4 ) { CL_DemoCompleted (); return; } clc.serverMessageSequence = LittleLong( s ); // init the message MSG_Init( &buf, bufData, sizeof( bufData ) ); // get the length r = FS_Read (&buf.cursize, 4, clc.demofile); if ( r != 4 ) { CL_DemoCompleted (); return; } buf.cursize = LittleLong( buf.cursize ); if ( buf.cursize == -1 ) { CL_DemoCompleted (); return; } if ( buf.cursize > buf.maxsize ) { Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN"); } r = FS_Read( buf.data, buf.cursize, clc.demofile ); if ( r != buf.cursize ) { Com_Printf( "Demo file was truncated.\n"); CL_DemoCompleted (); return; } clc.lastPacketTime = cls.realtime; buf.readcount = 0; CL_ParseServerMessage( &buf ); } /* ==================== CL_WalkDemoExt ==================== */ static void CL_WalkDemoExt(char *arg, char *name, int *demofile) { int i = 0; *demofile = 0; while(demo_protocols[i]) { Com_sprintf (name, MAX_OSPATH, "demos/%s.dm_%d", arg, demo_protocols[i]); FS_FOpenFileRead( name, demofile, qtrue ); if (*demofile) { Com_Printf("Demo file: %s\n", name); break; } else Com_Printf("Not found: %s\n", name); i++; } } /* ==================== CL_PlayDemo_f demo ==================== */ void CL_PlayDemo_f( void ) { char name[MAX_OSPATH]; char *arg, *ext_test; int protocol, i; char retry[MAX_OSPATH]; if (Cmd_Argc() != 2) { Com_Printf ("playdemo \n"); return; } // make sure a local server is killed Cvar_Set( "sv_killserver", "1" ); CL_Disconnect( qtrue ); // open the demo file arg = Cmd_Argv(1); // check for an extension .dm_?? (?? is protocol) ext_test = arg + strlen(arg) - 6; if ((strlen(arg) > 6) && (ext_test[0] == '.') && ((ext_test[1] == 'd') || (ext_test[1] == 'D')) && ((ext_test[2] == 'm') || (ext_test[2] == 'M')) && (ext_test[3] == '_')) { protocol = atoi(ext_test+4); i=0; while(demo_protocols[i]) { if (demo_protocols[i] == protocol) break; i++; } if (demo_protocols[i]) { Com_sprintf (name, sizeof(name), "demos/%s", arg); FS_FOpenFileRead( name, &clc.demofile, qtrue ); } else { Com_Printf("Protocol %d not supported for demos\n", protocol); Q_strncpyz(retry, arg, sizeof(retry)); retry[strlen(retry)-6] = 0; CL_WalkDemoExt( retry, name, &clc.demofile ); } } else { CL_WalkDemoExt( arg, name, &clc.demofile ); } if (!clc.demofile) { Com_Error( ERR_DROP, "couldn't open %s", name); return; } Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) ); Con_Close(); cls.state = CA_CONNECTED; clc.demoplaying = qtrue; Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) ); // read demo messages until connected while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) { CL_ReadDemoMessage(); } // don't get the first snapshot this frame, to prevent the long // time from the gamestate load from messing causing a time skip clc.firstDemoFrameSkipped = qfalse; } /* ==================== CL_StartDemoLoop Closing the main menu will restart the demo loop ==================== */ void CL_StartDemoLoop( void ) { // start the demo loop again Cbuf_AddText ("d1\n"); cls.keyCatchers = 0; } /* ================== CL_NextDemo Called when a demo or cinematic finishes If the "nextdemo" cvar is set, that command will be issued ================== */ void CL_NextDemo( void ) { char v[MAX_STRING_CHARS]; Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) ); v[MAX_STRING_CHARS-1] = 0; Com_DPrintf("CL_NextDemo: %s\n", v ); if (!v[0]) { return; } Cvar_Set ("nextdemo",""); Cbuf_AddText (v); Cbuf_AddText ("\n"); Cbuf_Execute(); } //====================================================================== /* ===================== CL_ShutdownAll ===================== */ void CL_ShutdownAll(void) { // clear sounds S_DisableSounds(); // shutdown CGame CL_ShutdownCGame(); // shutdown UI CL_ShutdownUI(); // shutdown the renderer if ( re.Shutdown ) { re.Shutdown( qfalse ); // don't destroy window or context } cls.uiStarted = qfalse; cls.cgameStarted = qfalse; cls.rendererStarted = qfalse; cls.soundRegistered = qfalse; } /* ================= CL_FlushMemory Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only ways a client gets into a game Also called by Com_Error ================= */ void CL_FlushMemory( void ) { // shutdown all the client stuff CL_ShutdownAll(); // if not running a server clear the whole hunk if ( !com_sv_running->integer ) { // clear the whole hunk Hunk_Clear(); // clear collision map data CM_ClearMap(); } else { // clear all the client data on the hunk Hunk_ClearToMark(); } CL_StartHunkUsers(); } /* ===================== CL_MapLoading A local server is starting to load a map, so update the screen to let the user know about it, then dump all client memory on the hunk from cgame, ui, and renderer ===================== */ void CL_MapLoading( void ) { if ( !com_cl_running->integer ) { return; } Con_Close(); cls.keyCatchers = 0; // if we are already connected to the local host, stay connected if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) { cls.state = CA_CONNECTED; // so the connect screen is drawn Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) ); Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) ); Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); clc.lastPacketSentTime = -9999; SCR_UpdateScreen(); } else { // clear nextmap so the cinematic shutdown doesn't execute it Cvar_Set( "nextmap", "" ); CL_Disconnect( qtrue ); Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) ); cls.state = CA_CHALLENGING; // so the connect screen is drawn cls.keyCatchers = 0; SCR_UpdateScreen(); clc.connectTime = -RETRANSMIT_TIMEOUT; NET_StringToAdr( cls.servername, &clc.serverAddress); // we don't need a challenge on the localhost CL_CheckForResend(); } } /* ===================== CL_ClearState Called before parsing a gamestate ===================== */ void CL_ClearState (void) { // S_StopAllSounds(); Com_Memset( &cl, 0, sizeof( cl ) ); } /* ===================== CL_Disconnect Called when a connection, demo, or cinematic is being terminated. Goes from a connected state to either a menu state or a console state Sends a disconnect message to the server This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors ===================== */ void CL_Disconnect( qboolean showMainMenu ) { if ( !com_cl_running || !com_cl_running->integer ) { return; } // shutting down the client so enter full screen ui mode Cvar_Set("r_uiFullScreen", "1"); if ( clc.demorecording ) { CL_StopRecord_f (); } if (clc.download) { FS_FCloseFile( clc.download ); clc.download = 0; } *clc.downloadTempName = *clc.downloadName = 0; Cvar_Set( "cl_downloadName", "" ); if ( clc.demofile ) { FS_FCloseFile( clc.demofile ); clc.demofile = 0; } if ( uivm && showMainMenu ) { VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); } SCR_StopCinematic (); S_ClearSoundBuffer(); // send a disconnect message to the server // send it a few times in case one is dropped if ( cls.state >= CA_CONNECTED ) { CL_AddReliableCommand( "disconnect" ); CL_WritePacket(); CL_WritePacket(); CL_WritePacket(); } CL_ClearState (); // wipe the client connection Com_Memset( &clc, 0, sizeof( clc ) ); cls.state = CA_DISCONNECTED; // allow cheats locally Cvar_Set( "sv_cheats", "1" ); // not connected to a pure server anymore cl_connectedToPureServer = qfalse; } /* =================== CL_ForwardCommandToServer adds the current command line as a clientCommand things like godmode, noclip, etc, are commands directed to the server, so when they are typed in at the console, they will need to be forwarded. =================== */ void CL_ForwardCommandToServer( const char *string ) { char *cmd; cmd = Cmd_Argv(0); // ignore key up commands if ( cmd[0] == '-' ) { return; } if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) { Com_Printf ("Unknown command \"%s\"\n", cmd); return; } if ( Cmd_Argc() > 1 ) { CL_AddReliableCommand( string ); } else { CL_AddReliableCommand( cmd ); } } /* =================== CL_RequestMotd =================== */ void CL_RequestMotd( void ) { char info[MAX_INFO_STRING]; if ( !cl_motd->integer ) { return; } Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME ); if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer ) ) { Com_Printf( "Couldn't resolve address\n" ); return; } cls.updateServer.port = BigShort( PORT_UPDATE ); Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME, cls.updateServer.ip[0], cls.updateServer.ip[1], cls.updateServer.ip[2], cls.updateServer.ip[3], BigShort( cls.updateServer.port ) ); info[0] = 0; // NOTE TTimo xoring against Com_Milliseconds, otherwise we may not have a true randomization // only srand I could catch before here is tr_noise.c l:26 srand(1001) // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=382 // NOTE: the Com_Milliseconds xoring only affects the lower 16-bit word, // but I decided it was enough randomization Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds()); Info_SetValueForKey( info, "challenge", cls.updateChallenge ); Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string ); Info_SetValueForKey( info, "version", com_version->string ); NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd \"%s\"\n", info ); } /* =================== CL_RequestAuthorization Authorization server protocol ----------------------------- All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff). Whenever the client tries to get a challenge from the server it wants to connect to, it also blindly fires off a packet to the authorize server: getKeyAuthorize cdkey may be "demo" #OLD The authorize server returns a: #OLD #OLD keyAthorize #OLD #OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP #OLD address in the last 15 minutes. The server sends a: getIpAuthorize The authorize server returns a: ipAuthorize A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes. If no response is received from the authorize server after two tries, the client will be let in anyway. =================== */ void CL_RequestAuthorization( void ) { char nums[64]; int i, j, l; cvar_t *fs; if ( !cls.authorizeServer.port ) { Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer ) ) { Com_Printf( "Couldn't resolve address\n" ); return; } cls.authorizeServer.port = BigShort( PORT_AUTHORIZE ); Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, cls.authorizeServer.ip[0], cls.authorizeServer.ip[1], cls.authorizeServer.ip[2], cls.authorizeServer.ip[3], BigShort( cls.authorizeServer.port ) ); } if ( cls.authorizeServer.type == NA_BAD ) { return; } if ( Cvar_VariableValue( "fs_restrict" ) ) { Q_strncpyz( nums, "demota", sizeof( nums ) ); } else { // only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces j = 0; l = strlen( cl_cdkey ); if ( l > 32 ) { l = 32; } for ( i = 0 ; i < l ; i++ ) { if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' ) || ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' ) || ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' ) ) { nums[j] = cl_cdkey[i]; j++; } } nums[j] = 0; } fs = Cvar_Get ("cl_anonymous", "0", CVAR_INIT|CVAR_SYSTEMINFO ); NET_OutOfBandPrint(NS_CLIENT, cls.authorizeServer, va("getKeyAuthorize %i %s", fs->integer, nums) ); } /* ====================================================================== CONSOLE COMMANDS ====================================================================== */ /* ================== CL_ForwardToServer_f ================== */ void CL_ForwardToServer_f( void ) { if ( cls.state != CA_ACTIVE || clc.demoplaying ) { Com_Printf ("Not connected to a server.\n"); return; } // don't forward the first argument if ( Cmd_Argc() > 1 ) { CL_AddReliableCommand( Cmd_Args() ); } } /* ================== CL_Setenv_f Mostly for controlling voodoo environment variables ================== */ void CL_Setenv_f( void ) { int argc = Cmd_Argc(); if ( argc > 2 ) { char buffer[1024]; int i; strcpy( buffer, Cmd_Argv(1) ); strcat( buffer, "=" ); for ( i = 2; i < argc; i++ ) { strcat( buffer, Cmd_Argv( i ) ); strcat( buffer, " " ); } putenv( buffer ); } else if ( argc == 2 ) { char *env = getenv( Cmd_Argv(1) ); if ( env ) { Com_Printf( "%s=%s\n", Cmd_Argv(1), env ); } else { Com_Printf( "%s undefined\n", Cmd_Argv(1), env ); } } } /* ================== CL_Disconnect_f ================== */ void CL_Disconnect_f( void ) { SCR_StopCinematic(); Cvar_Set("ui_singlePlayerActive", "0"); if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) { Com_Error (ERR_DISCONNECT, "Disconnected from server"); } } /* ================ CL_Reconnect_f ================ */ void CL_Reconnect_f( void ) { if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) { Com_Printf( "Can't reconnect to localhost.\n" ); return; } Cvar_Set("ui_singlePlayerActive", "0"); Cbuf_AddText( va("connect %s\n", cls.servername ) ); } /* ================ CL_Connect_f ================ */ void CL_Connect_f( void ) { char *server; if ( Cmd_Argc() != 2 ) { Com_Printf( "usage: connect [server]\n"); return; } Cvar_Set("ui_singlePlayerActive", "0"); // fire a message off to the motd server CL_RequestMotd(); // clear any previous "server full" type messages clc.serverMessage[0] = 0; server = Cmd_Argv (1); if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) { // if running a local server, kill it SV_Shutdown( "Server quit\n" ); } // make sure a local server is killed Cvar_Set( "sv_killserver", "1" ); SV_Frame( 0 ); CL_Disconnect( qtrue ); Con_Close(); /* MrE: 2000-09-13: now called in CL_DownloadsComplete CL_FlushMemory( ); */ Q_strncpyz( cls.servername, server, sizeof(cls.servername) ); if (!NET_StringToAdr( cls.servername, &clc.serverAddress) ) { Com_Printf ("Bad server address\n"); cls.state = CA_DISCONNECTED; return; } if (clc.serverAddress.port == 0) { clc.serverAddress.port = BigShort( PORT_SERVER ); } Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", cls.servername, clc.serverAddress.ip[0], clc.serverAddress.ip[1], clc.serverAddress.ip[2], clc.serverAddress.ip[3], BigShort( clc.serverAddress.port ) ); // if we aren't playing on a lan, we need to authenticate // with the cd key if ( NET_IsLocalAddress( clc.serverAddress ) ) { cls.state = CA_CHALLENGING; } else { cls.state = CA_CONNECTING; } cls.keyCatchers = 0; clc.connectTime = -99999; // CL_CheckForResend() will fire immediately clc.connectPacketCount = 0; // server connection string Cvar_Set( "cl_currentServerAddress", server ); } /* ===================== CL_Rcon_f Send the rest of the command line over as an unconnected command. ===================== */ void CL_Rcon_f( void ) { char message[1024]; netadr_t to; if ( !rcon_client_password->string ) { Com_Printf ("You must set 'rconpassword' before\n" "issuing an rcon command.\n"); return; } message[0] = -1; message[1] = -1; message[2] = -1; message[3] = -1; message[4] = 0; strcat (message, "rcon "); strcat (message, rcon_client_password->string); strcat (message, " "); // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 strcat (message, Cmd_Cmd()+5); if ( cls.state >= CA_CONNECTED ) { to = clc.netchan.remoteAddress; } else { if (!strlen(rconAddress->string)) { Com_Printf ("You must either be connected,\n" "or set the 'rconAddress' cvar\n" "to issue rcon commands\n"); return; } NET_StringToAdr (rconAddress->string, &to); if (to.port == 0) { to.port = BigShort (PORT_SERVER); } } NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); } /* ================= CL_SendPureChecksums ================= */ void CL_SendPureChecksums( void ) { const char *pChecksums; char cMsg[MAX_INFO_VALUE]; int i; // if we are pure we need to send back a command with our referenced pk3 checksums pChecksums = FS_ReferencedPakPureChecksums(); // "cp" // "Yf" Com_sprintf(cMsg, sizeof(cMsg), "Yf "); Q_strcat(cMsg, sizeof(cMsg), va("%d ", cl.serverId) ); Q_strcat(cMsg, sizeof(cMsg), pChecksums); for (i = 0; i < 2; i++) { cMsg[i] += 10; } CL_AddReliableCommand( cMsg ); } /* ================= CL_ResetPureClientAtServer ================= */ void CL_ResetPureClientAtServer( void ) { CL_AddReliableCommand( va("vdr") ); } /* ================= CL_Vid_Restart_f Restart the video subsystem we also have to reload the UI and CGame because the renderer doesn't know what graphics to reload ================= */ void CL_Vid_Restart_f( void ) { // don't let them loop during the restart S_StopAllSounds(); // shutdown the UI CL_ShutdownUI(); // shutdown the CGame CL_ShutdownCGame(); // shutdown the renderer and clear the renderer interface CL_ShutdownRef(); // client is no longer pure untill new checksums are sent CL_ResetPureClientAtServer(); // clear pak references FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF ); // reinitialize the filesystem if the game directory or checksum has changed FS_ConditionalRestart( clc.checksumFeed ); cls.rendererStarted = qfalse; cls.uiStarted = qfalse; cls.cgameStarted = qfalse; cls.soundRegistered = qfalse; // unpause so the cgame definately gets a snapshot and renders a frame Cvar_Set( "cl_paused", "0" ); // if not running a server clear the whole hunk if ( !com_sv_running->integer ) { // clear the whole hunk Hunk_Clear(); } else { // clear all the client data on the hunk Hunk_ClearToMark(); } // initialize the renderer interface CL_InitRef(); // startup all the client stuff CL_StartHunkUsers(); // start the cgame if connected if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) { cls.cgameStarted = qtrue; CL_InitCGame(); // send pure checksums CL_SendPureChecksums(); } } /* ================= CL_Snd_Restart_f Restart the sound subsystem The cgame and game must also be forced to restart because handles will be invalid ================= */ void CL_Snd_Restart_f( void ) { S_Shutdown(); S_Init(); CL_Vid_Restart_f(); } /* ================== CL_PK3List_f ================== */ void CL_OpenedPK3List_f( void ) { Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames()); } /* ================== CL_PureList_f ================== */ void CL_ReferencedPK3List_f( void ) { Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames()); } /* ================== CL_Configstrings_f ================== */ void CL_Configstrings_f( void ) { int i; int ofs; if ( cls.state != CA_ACTIVE ) { Com_Printf( "Not connected to a server.\n"); return; } for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { ofs = cl.gameState.stringOffsets[ i ]; if ( !ofs ) { continue; } Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs ); } } /* ============== CL_Clientinfo_f ============== */ void CL_Clientinfo_f( void ) { Com_Printf( "--------- Client Information ---------\n" ); Com_Printf( "state: %i\n", cls.state ); Com_Printf( "Server: %s\n", cls.servername ); Com_Printf ("User info settings:\n"); Info_Print( Cvar_InfoString( CVAR_USERINFO ) ); Com_Printf( "--------------------------------------\n" ); } //==================================================================== /* ================= CL_DownloadsComplete Called when all downloading has been completed ================= */ void CL_DownloadsComplete( void ) { // if we downloaded files we need to restart the file system if (clc.downloadRestart) { clc.downloadRestart = qfalse; FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it // inform the server so we get new gamestate info CL_AddReliableCommand( "donedl" ); // by sending the donedl command we request a new gamestate // so we don't want to load stuff yet return; } // let the client game init and load data cls.state = CA_LOADING; // Pump the loop, this may change gamestate! Com_EventLoop(); // if the gamestate was changed by calling Com_EventLoop // then we loaded everything already and we don't want to do it again. if ( cls.state != CA_LOADING ) { return; } // starting to load a map so we get out of full screen ui mode Cvar_Set("r_uiFullScreen", "0"); // flush client memory and start loading stuff // this will also (re)load the UI // if this is a local client then only the client part of the hunk // will be cleared, note that this is done after the hunk mark has been set CL_FlushMemory(); // initialize the CGame cls.cgameStarted = qtrue; CL_InitCGame(); // set pure checksums CL_SendPureChecksums(); CL_WritePacket(); CL_WritePacket(); CL_WritePacket(); } /* ================= CL_BeginDownload Requests a file to download from the server. Stores it in the current game directory. ================= */ void CL_BeginDownload( const char *localName, const char *remoteName ) { Com_DPrintf("***** CL_BeginDownload *****\n" "Localname: %s\n" "Remotename: %s\n" "****************************\n", localName, remoteName); Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) ); Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName ); // Set so UI gets access to it Cvar_Set( "cl_downloadName", remoteName ); Cvar_Set( "cl_downloadSize", "0" ); Cvar_Set( "cl_downloadCount", "0" ); Cvar_SetValue( "cl_downloadTime", cls.realtime ); clc.downloadBlock = 0; // Starting new file clc.downloadCount = 0; CL_AddReliableCommand( va("download %s", remoteName) ); } /* ================= CL_NextDownload A download completed or failed ================= */ void CL_NextDownload(void) { char *s; char *remoteName, *localName; // We are looking to start a download here if (*clc.downloadList) { s = clc.downloadList; // format is: // @remotename@localname@remotename@localname, etc. if (*s == '@') s++; remoteName = s; if ( (s = strchr(s, '@')) == NULL ) { CL_DownloadsComplete(); return; } *s++ = 0; localName = s; if ( (s = strchr(s, '@')) != NULL ) *s++ = 0; else s = localName + strlen(localName); // point at the nul byte CL_BeginDownload( localName, remoteName ); clc.downloadRestart = qtrue; // move over the rest memmove( clc.downloadList, s, strlen(s) + 1); return; } CL_DownloadsComplete(); } /* ================= CL_InitDownloads After receiving a valid game state, we valid the cgame and local zip files here and determine if we need to download them ================= */ void CL_InitDownloads(void) { char missingfiles[1024]; if ( !cl_allowDownload->integer ) { // autodownload is disabled on the client // but it's possible that some referenced files on the server are missing if (FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) ) { // NOTE TTimo I would rather have that printed as a modal message box // but at this point while joining the game we don't know wether we will successfully join or not Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s" "You might not be able to join the game\n" "Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles ); } } else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) { Com_Printf("Need paks: %s\n", clc.downloadList ); if ( *clc.downloadList ) { // if autodownloading is not enabled on the server cls.state = CA_CONNECTED; CL_NextDownload(); return; } } CL_DownloadsComplete(); } /* ================= CL_CheckForResend Resend a connect message if the last one has timed out ================= */ void CL_CheckForResend( void ) { int port, i; char info[MAX_INFO_STRING]; char data[MAX_INFO_STRING]; // don't send anything if playing back a demo if ( clc.demoplaying ) { return; } // resend if we haven't gotten a reply yet if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) { return; } if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) { return; } clc.connectTime = cls.realtime; // for retransmit requests clc.connectPacketCount++; switch ( cls.state ) { case CA_CONNECTING: // requesting a challenge if ( !Sys_IsLANAddress( clc.serverAddress ) ) { CL_RequestAuthorization(); } NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "getchallenge"); break; case CA_CHALLENGING: // sending back the challenge port = Cvar_VariableValue ("net_qport"); Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) ); Info_SetValueForKey( info, "qport", va("%i", port ) ); Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) ); strcpy(data, "connect "); // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server // (Com_TokenizeString tokenizes around spaces) data[8] = '"'; for(i=0;iadr.type = NA_IP; server->adr.ip[0] = address->ip[0]; server->adr.ip[1] = address->ip[1]; server->adr.ip[2] = address->ip[2]; server->adr.ip[3] = address->ip[3]; server->adr.port = address->port; server->clients = 0; server->hostName[0] = '\0'; server->mapName[0] = '\0'; server->maxClients = 0; server->maxPing = 0; server->minPing = 0; server->ping = -1; server->game[0] = '\0'; server->gameType = 0; server->netType = 0; } #define MAX_SERVERSPERPACKET 256 /* =================== CL_ServersResponsePacket =================== */ void CL_ServersResponsePacket( netadr_t from, msg_t *msg ) { int i, count, max, total; serverAddress_t addresses[MAX_SERVERSPERPACKET]; int numservers; byte* buffptr; byte* buffend; Com_Printf("CL_ServersResponsePacket\n"); if (cls.numglobalservers == -1) { // state to detect lack of servers or lack of response cls.numglobalservers = 0; cls.numGlobalServerAddresses = 0; } if (cls.nummplayerservers == -1) { cls.nummplayerservers = 0; } // parse through server response string numservers = 0; buffptr = msg->data; buffend = buffptr + msg->cursize; while (buffptr+1 < buffend) { // advance to initial token do { if (*buffptr++ == '\\') break; } while (buffptr < buffend); if ( buffptr >= buffend - 6 ) { break; } // parse out ip addresses[numservers].ip[0] = *buffptr++; addresses[numservers].ip[1] = *buffptr++; addresses[numservers].ip[2] = *buffptr++; addresses[numservers].ip[3] = *buffptr++; // parse out port addresses[numservers].port = (*buffptr++)<<8; addresses[numservers].port += *buffptr++; addresses[numservers].port = BigShort( addresses[numservers].port ); // syntax check if (*buffptr != '\\') { break; } Com_DPrintf( "server: %d ip: %d.%d.%d.%d:%d\n",numservers, addresses[numservers].ip[0], addresses[numservers].ip[1], addresses[numservers].ip[2], addresses[numservers].ip[3], addresses[numservers].port ); numservers++; if (numservers >= MAX_SERVERSPERPACKET) { break; } // parse out EOT if (buffptr[1] == 'E' && buffptr[2] == 'O' && buffptr[3] == 'T') { break; } } if (cls.masterNum == 0) { count = cls.numglobalservers; max = MAX_GLOBAL_SERVERS; } else { count = cls.nummplayerservers; max = MAX_OTHER_SERVERS; } for (i = 0; i < numservers && count < max; i++) { // build net address serverInfo_t *server = (cls.masterNum == 0) ? &cls.globalServers[count] : &cls.mplayerServers[count]; CL_InitServerInfo( server, &addresses[i] ); // advance to next slot count++; } // if getting the global list if (cls.masterNum == 0) { if ( cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) { // if we couldn't store the servers in the main list anymore for (; i < numservers && count >= max; i++) { serverAddress_t *addr; // just store the addresses in an additional list addr = &cls.globalServerAddresses[cls.numGlobalServerAddresses++]; addr->ip[0] = addresses[i].ip[0]; addr->ip[1] = addresses[i].ip[1]; addr->ip[2] = addresses[i].ip[2]; addr->ip[3] = addresses[i].ip[3]; addr->port = addresses[i].port; } } } if (cls.masterNum == 0) { cls.numglobalservers = count; total = count + cls.numGlobalServerAddresses; } else { cls.nummplayerservers = count; total = count; } Com_Printf("%d servers parsed (total %d)\n", numservers, total); } /* ================= CL_ConnectionlessPacket Responses to broadcasts, etc ================= */ void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { char *s; char *c; MSG_BeginReadingOOB( msg ); MSG_ReadLong( msg ); // skip the -1 s = MSG_ReadStringLine( msg ); Cmd_TokenizeString( s ); c = Cmd_Argv(0); Com_DPrintf ("CL packet %s: %s\n", NET_AdrToString(from), c); // challenge from the server we are connecting to if ( !Q_stricmp(c, "challengeResponse") ) { if ( cls.state != CA_CONNECTING ) { Com_Printf( "Unwanted challenge response received. Ignored.\n" ); } else { // start sending challenge repsonse instead of challenge request packets clc.challenge = atoi(Cmd_Argv(1)); cls.state = CA_CHALLENGING; clc.connectPacketCount = 0; clc.connectTime = -99999; // take this address as the new server address. This allows // a server proxy to hand off connections to multiple servers clc.serverAddress = from; Com_DPrintf ("challengeResponse: %d\n", clc.challenge); } return; } // server connection if ( !Q_stricmp(c, "connectResponse") ) { if ( cls.state >= CA_CONNECTED ) { Com_Printf ("Dup connect received. Ignored.\n"); return; } if ( cls.state != CA_CHALLENGING ) { Com_Printf ("connectResponse packet while not connecting. Ignored.\n"); return; } if ( !NET_CompareBaseAdr( from, clc.serverAddress ) ) { Com_Printf( "connectResponse from a different address. Ignored.\n" ); Com_Printf( "%s should have been %s\n", NET_AdrToString( from ), NET_AdrToString( clc.serverAddress ) ); return; } Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) ); cls.state = CA_CONNECTED; clc.lastPacketSentTime = -9999; // send first packet immediately return; } // server responding to an info broadcast if ( !Q_stricmp(c, "infoResponse") ) { CL_ServerInfoPacket( from, msg ); return; } // server responding to a get playerlist if ( !Q_stricmp(c, "statusResponse") ) { CL_ServerStatusResponse( from, msg ); return; } // a disconnect message from the server, which will happen if the server // dropped the connection but it is still getting packets from us if (!Q_stricmp(c, "disconnect")) { CL_DisconnectPacket( from ); return; } // echo request from server if ( !Q_stricmp(c, "echo") ) { NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); return; } // cd check if ( !Q_stricmp(c, "keyAuthorize") ) { // we don't use these now, so dump them on the floor return; } // global MOTD from id if ( !Q_stricmp(c, "motd") ) { CL_MotdPacket( from ); return; } // echo request from server if ( !Q_stricmp(c, "print") ) { s = MSG_ReadString( msg ); Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); Com_Printf( "%s", s ); return; } // echo request from server if ( !Q_strncmp(c, "getserversResponse", 18) ) { CL_ServersResponsePacket( from, msg ); return; } Com_DPrintf ("Unknown connectionless packet command.\n"); } /* ================= CL_PacketEvent A packet has arrived from the main event loop ================= */ void CL_PacketEvent( netadr_t from, msg_t *msg ) { int headerBytes; clc.lastPacketTime = cls.realtime; if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { CL_ConnectionlessPacket( from, msg ); return; } if ( cls.state < CA_CONNECTED ) { return; // can't be a valid sequenced packet } if ( msg->cursize < 4 ) { Com_Printf ("%s: Runt packet\n",NET_AdrToString( from )); return; } // // packet from server // if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { Com_DPrintf ("%s:sequenced packet without connection\n" ,NET_AdrToString( from ) ); // FIXME: send a client disconnect? return; } if (!CL_Netchan_Process( &clc.netchan, msg) ) { return; // out of order, duplicated, etc } // the header is different lengths for reliable and unreliable messages headerBytes = msg->readcount; // track the last message received so it can be returned in // client messages, allowing the server to detect a dropped // gamestate clc.serverMessageSequence = LittleLong( *(int *)msg->data ); clc.lastPacketTime = cls.realtime; CL_ParseServerMessage( msg ); // // we don't know if it is ok to save a demo message until // after we have parsed the frame // if ( clc.demorecording && !clc.demowaiting ) { CL_WriteDemoMessage( msg, headerBytes ); } } /* ================== CL_CheckTimeout ================== */ void CL_CheckTimeout( void ) { // // check timeout // if ( ( !cl_paused->integer || !sv_paused->integer ) && cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC && cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) { if (++cl.timeoutcount > 5) { // timeoutcount saves debugger Com_Printf ("\nServer connection timed out.\n"); CL_Disconnect( qtrue ); return; } } else { cl.timeoutcount = 0; } } //============================================================================ /* ================== CL_CheckUserinfo ================== */ void CL_CheckUserinfo( void ) { // don't add reliable commands when not yet connected if ( cls.state < CA_CHALLENGING ) { return; } // don't overflow the reliable command buffer when paused if ( cl_paused->integer ) { return; } // send a reliable userinfo update if needed if ( cvar_modifiedFlags & CVAR_USERINFO ) { cvar_modifiedFlags &= ~CVAR_USERINFO; CL_AddReliableCommand( va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ) ); } } /* ================== CL_Frame ================== */ void CL_Frame ( int msec ) { if ( !com_cl_running->integer ) { return; } if ( cls.cddialog ) { // bring up the cd error dialog if needed cls.cddialog = qfalse; VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD ); } else if ( cls.state == CA_DISCONNECTED && !( cls.keyCatchers & KEYCATCH_UI ) && !com_sv_running->integer ) { // if disconnected, bring up the menu S_StopAllSounds(); VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); } // if recording an avi, lock to a fixed fps if ( cl_avidemo->integer && msec) { // save the current screen if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) { Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" ); } // fixed time for next frame' msec = (1000 / cl_avidemo->integer) * com_timescale->value; if (msec == 0) { msec = 1; } } // save the msec before checking pause cls.realFrametime = msec; // decide the simulation time cls.frametime = msec; cls.realtime += cls.frametime; if ( cl_timegraph->integer ) { SCR_DebugGraph ( cls.realFrametime * 0.25, 0 ); } // see if we need to update any userinfo CL_CheckUserinfo(); // if we haven't gotten a packet in a long time, // drop the connection CL_CheckTimeout(); // send intentions now CL_SendCmd(); // resend a connection request if necessary CL_CheckForResend(); // decide on the serverTime to render CL_SetCGameTime(); // update the screen SCR_UpdateScreen(); // update audio S_Update(); // advance local effects for next frame SCR_RunCinematic(); Con_RunConsole(); cls.framecount++; } //============================================================================ /* ================ CL_RefPrintf DLL glue ================ */ void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) { va_list argptr; char msg[MAXPRINTMSG]; va_start (argptr,fmt); Q_vsnprintf (msg, sizeof(msg), fmt, argptr); va_end (argptr); if ( print_level == PRINT_ALL ) { Com_Printf ("%s", msg); } else if ( print_level == PRINT_WARNING ) { Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow } else if ( print_level == PRINT_DEVELOPER ) { Com_DPrintf (S_COLOR_RED "%s", msg); // red } } /* ============ CL_ShutdownRef ============ */ void CL_ShutdownRef( void ) { if ( !re.Shutdown ) { return; } re.Shutdown( qtrue ); Com_Memset( &re, 0, sizeof( re ) ); } /* ============ CL_InitRenderer ============ */ void CL_InitRenderer( void ) { // this sets up the renderer and calls R_Init re.BeginRegistration( &cls.glconfig ); // load character sets cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" ); cls.whiteShader = re.RegisterShader( "white" ); cls.consoleShader = re.RegisterShader( "console" ); g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; g_consoleField.widthInChars = g_console_field_width; } /* ============================ CL_StartHunkUsers After the server has cleared the hunk, these will need to be restarted This is the only place that any of these functions are called from ============================ */ void CL_StartHunkUsers( void ) { if (!com_cl_running) { return; } if ( !com_cl_running->integer ) { return; } if ( !cls.rendererStarted ) { cls.rendererStarted = qtrue; CL_InitRenderer(); } if ( !cls.soundStarted ) { cls.soundStarted = qtrue; S_Init(); } if ( !cls.soundRegistered ) { cls.soundRegistered = qtrue; S_BeginRegistration(); } if ( !cls.uiStarted ) { cls.uiStarted = qtrue; CL_InitUI(); } } /* ============ CL_RefMalloc ============ */ void *CL_RefMalloc( int size ) { return Z_TagMalloc( size, TAG_RENDERER ); } int CL_ScaledMilliseconds(void) { return Sys_Milliseconds()*com_timescale->value; } /* ============ CL_InitRef ============ */ void CL_InitRef( void ) { refimport_t ri; refexport_t *ret; Com_Printf( "----- Initializing Renderer ----\n" ); ri.Cmd_AddCommand = Cmd_AddCommand; ri.Cmd_RemoveCommand = Cmd_RemoveCommand; ri.Cmd_Argc = Cmd_Argc; ri.Cmd_Argv = Cmd_Argv; ri.Cmd_ExecuteText = Cbuf_ExecuteText; ri.Printf = CL_RefPrintf; ri.Error = Com_Error; ri.Milliseconds = CL_ScaledMilliseconds; ri.Malloc = CL_RefMalloc; ri.Free = Z_Free; #ifdef HUNK_DEBUG ri.Hunk_AllocDebug = Hunk_AllocDebug; #else ri.Hunk_Alloc = Hunk_Alloc; #endif ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory; ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory; ri.CM_DrawDebugSurface = CM_DrawDebugSurface; ri.FS_ReadFile = FS_ReadFile; ri.FS_FreeFile = FS_FreeFile; ri.FS_WriteFile = FS_WriteFile; ri.FS_FreeFileList = FS_FreeFileList; ri.FS_ListFiles = FS_ListFiles; ri.FS_FileIsInPAK = FS_FileIsInPAK; ri.FS_FileExists = FS_FileExists; ri.Cvar_Get = Cvar_Get; ri.Cvar_Set = Cvar_Set; // cinematic stuff ri.CIN_UploadCinematic = CIN_UploadCinematic; ri.CIN_PlayCinematic = CIN_PlayCinematic; ri.CIN_RunCinematic = CIN_RunCinematic; ret = GetRefAPI( REF_API_VERSION, &ri ); #if defined __USEA3D && defined __A3D_GEOM hA3Dg_ExportRenderGeom (ret); #endif Com_Printf( "-------------------------------\n"); if ( !ret ) { Com_Error (ERR_FATAL, "Couldn't initialize refresh" ); } re = *ret; // unpause so the cgame definately gets a snapshot and renders a frame Cvar_Set( "cl_paused", "0" ); } //=========================================================================================== void CL_SetModel_f( void ) { char *arg; char name[256]; arg = Cmd_Argv( 1 ); if (arg[0]) { Cvar_Set( "model", arg ); Cvar_Set( "headmodel", arg ); } else { Cvar_VariableStringBuffer( "model", name, sizeof(name) ); Com_Printf("model is set to %s\n", name); } } /* ==================== CL_Init ==================== */ void CL_Init( void ) { Com_Printf( "----- Client Initialization -----\n" ); Con_Init (); CL_ClearState (); cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED cls.realtime = 0; CL_InitInput (); // // register our variables // cl_noprint = Cvar_Get( "cl_noprint", "0", 0 ); cl_motd = Cvar_Get ("cl_motd", "1", 0); cl_timeout = Cvar_Get ("cl_timeout", "200", 0); cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP ); cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP ); cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP ); cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP ); rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP ); cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP ); cl_timedemo = Cvar_Get ("timedemo", "0", 0); cl_avidemo = Cvar_Get ("cl_avidemo", "0", 0); cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0); rconAddress = Cvar_Get ("rconAddress", "", 0); cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE); cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE); cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0); cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE ); cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE ); cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE); cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE); cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE); cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0); cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE); cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0); #ifdef MACOS_X // In game video is REALLY slow in Mac OS X right now due to driver slowness cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE); #else cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE); #endif cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0); // init autoswitch so the ui will have it correctly even // if the cgame hasn't been started Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE); m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE); m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE); m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE); m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE); #ifdef MACOS_X // Input is jittery on OS X w/o this m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE); #else m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE); #endif cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM ); Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE ); // userinfo Cvar_Get ("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("rate", "3000", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("model", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("headmodel", "sarge", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("team_model", "james", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("team_headmodel", "*james", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("g_redTeam", "Stroggs", CVAR_SERVERINFO | CVAR_ARCHIVE); Cvar_Get ("g_blueTeam", "Pagans", CVAR_SERVERINFO | CVAR_ARCHIVE); Cvar_Get ("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("teamtask", "0", CVAR_USERINFO ); Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE ); Cvar_Get ("password", "", CVAR_USERINFO); Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE ); // cgame might not be initialized before menu is used Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE ); // // register our commands // Cmd_AddCommand ("cmd", CL_ForwardToServer_f); Cmd_AddCommand ("configstrings", CL_Configstrings_f); Cmd_AddCommand ("clientinfo", CL_Clientinfo_f); Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f); Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f); Cmd_AddCommand ("disconnect", CL_Disconnect_f); Cmd_AddCommand ("record", CL_Record_f); Cmd_AddCommand ("demo", CL_PlayDemo_f); Cmd_AddCommand ("cinematic", CL_PlayCinematic_f); Cmd_AddCommand ("stoprecord", CL_StopRecord_f); Cmd_AddCommand ("connect", CL_Connect_f); Cmd_AddCommand ("reconnect", CL_Reconnect_f); Cmd_AddCommand ("localservers", CL_LocalServers_f); Cmd_AddCommand ("globalservers", CL_GlobalServers_f); Cmd_AddCommand ("rcon", CL_Rcon_f); Cmd_AddCommand ("setenv", CL_Setenv_f ); Cmd_AddCommand ("ping", CL_Ping_f ); Cmd_AddCommand ("serverstatus", CL_ServerStatus_f ); Cmd_AddCommand ("showip", CL_ShowIP_f ); Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f ); Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f ); Cmd_AddCommand ("model", CL_SetModel_f ); CL_InitRef(); SCR_Init (); Cbuf_Execute (); Cvar_Set( "cl_running", "1" ); Com_Printf( "----- Client Initialization Complete -----\n" ); } /* =============== CL_Shutdown =============== */ void CL_Shutdown( void ) { static qboolean recursive = qfalse; Com_Printf( "----- CL_Shutdown -----\n" ); if ( recursive ) { printf ("recursive shutdown\n"); return; } recursive = qtrue; CL_Disconnect( qtrue ); S_Shutdown(); CL_ShutdownRef(); CL_ShutdownUI(); Cmd_RemoveCommand ("cmd"); Cmd_RemoveCommand ("configstrings"); Cmd_RemoveCommand ("userinfo"); Cmd_RemoveCommand ("snd_restart"); Cmd_RemoveCommand ("vid_restart"); Cmd_RemoveCommand ("disconnect"); Cmd_RemoveCommand ("record"); Cmd_RemoveCommand ("demo"); Cmd_RemoveCommand ("cinematic"); Cmd_RemoveCommand ("stoprecord"); Cmd_RemoveCommand ("connect"); Cmd_RemoveCommand ("localservers"); Cmd_RemoveCommand ("globalservers"); Cmd_RemoveCommand ("rcon"); Cmd_RemoveCommand ("setenv"); Cmd_RemoveCommand ("ping"); Cmd_RemoveCommand ("serverstatus"); Cmd_RemoveCommand ("showip"); Cmd_RemoveCommand ("model"); Cvar_Set( "cl_running", "0" ); recursive = qfalse; Com_Memset( &cls, 0, sizeof( cls ) ); Com_Printf( "-----------------------\n" ); } static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) { if (server) { if (info) { server->clients = atoi(Info_ValueForKey(info, "clients")); Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_NAME_LENGTH); Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH); server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); Q_strncpyz(server->game,Info_ValueForKey(info, "game"), MAX_NAME_LENGTH); server->gameType = atoi(Info_ValueForKey(info, "gametype")); server->netType = atoi(Info_ValueForKey(info, "nettype")); server->minPing = atoi(Info_ValueForKey(info, "minping")); server->maxPing = atoi(Info_ValueForKey(info, "maxping")); server->punkbuster = atoi(Info_ValueForKey(info, "punkbuster")); } server->ping = ping; } } static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) { int i; for (i = 0; i < MAX_OTHER_SERVERS; i++) { if (NET_CompareAdr(from, cls.localServers[i].adr)) { CL_SetServerInfo(&cls.localServers[i], info, ping); } } for (i = 0; i < MAX_OTHER_SERVERS; i++) { if (NET_CompareAdr(from, cls.mplayerServers[i].adr)) { CL_SetServerInfo(&cls.mplayerServers[i], info, ping); } } for (i = 0; i < MAX_GLOBAL_SERVERS; i++) { if (NET_CompareAdr(from, cls.globalServers[i].adr)) { CL_SetServerInfo(&cls.globalServers[i], info, ping); } } for (i = 0; i < MAX_OTHER_SERVERS; i++) { if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) { CL_SetServerInfo(&cls.favoriteServers[i], info, ping); } } } /* =================== CL_ServerInfoPacket =================== */ void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) { int i, type; char info[MAX_INFO_STRING]; char* str; char *infoString; int prot; infoString = MSG_ReadString( msg ); // if this isn't the correct protocol version, ignore it prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); if ( prot != PROTOCOL_VERSION ) { Com_DPrintf( "Different protocol info packet: %s\n", infoString ); return; } // iterate servers waiting for ping response for (i=0; iretrieved = qtrue; return qfalse; } // if this server status request has the same address if ( NET_CompareAdr( to, serverStatus->address) ) { // if we recieved an response for this server status request if (!serverStatus->pending) { Q_strncpyz(serverStatusString, serverStatus->string, maxLen); serverStatus->retrieved = qtrue; serverStatus->startTime = 0; return qtrue; } // resend the request regularly else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) { serverStatus->print = qfalse; serverStatus->pending = qtrue; serverStatus->retrieved = qfalse; serverStatus->time = 0; serverStatus->startTime = Com_Milliseconds(); NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); return qfalse; } } // if retrieved else if ( serverStatus->retrieved ) { serverStatus->address = to; serverStatus->print = qfalse; serverStatus->pending = qtrue; serverStatus->retrieved = qfalse; serverStatus->startTime = Com_Milliseconds(); serverStatus->time = 0; NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); return qfalse; } return qfalse; } /* =================== CL_ServerStatusResponse =================== */ void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) { char *s; char info[MAX_INFO_STRING]; int i, l, score, ping; int len; serverStatus_t *serverStatus; serverStatus = NULL; for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { serverStatus = &cl_serverStatusList[i]; break; } } // if we didn't request this server status if (!serverStatus) { return; } s = MSG_ReadStringLine( msg ); len = 0; Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s); if (serverStatus->print) { Com_Printf("Server settings:\n"); // print cvars while (*s) { for (i = 0; i < 2 && *s; i++) { if (*s == '\\') s++; l = 0; while (*s) { info[l++] = *s; if (l >= MAX_INFO_STRING-1) break; s++; if (*s == '\\') { break; } } info[l] = '\0'; if (i) { Com_Printf("%s\n", info); } else { Com_Printf("%-24s", info); } } } } len = strlen(serverStatus->string); Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); if (serverStatus->print) { Com_Printf("\nPlayers:\n"); Com_Printf("num: score: ping: name:\n"); } for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) { len = strlen(serverStatus->string); Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s); if (serverStatus->print) { score = ping = 0; sscanf(s, "%d %d", &score, &ping); s = strchr(s, ' '); if (s) s = strchr(s+1, ' '); if (s) s++; else s = "unknown"; Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s ); } } len = strlen(serverStatus->string); Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); serverStatus->time = Com_Milliseconds(); serverStatus->address = from; serverStatus->pending = qfalse; if (serverStatus->print) { serverStatus->retrieved = qtrue; } } /* ================== CL_LocalServers_f ================== */ void CL_LocalServers_f( void ) { char *message; int i, j; netadr_t to; Com_Printf( "Scanning for servers on the local network...\n"); // reset the list, waiting for response cls.numlocalservers = 0; cls.pingUpdateSource = AS_LOCAL; for (i = 0; i < MAX_OTHER_SERVERS; i++) { qboolean b = cls.localServers[i].visible; Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i])); cls.localServers[i].visible = b; } Com_Memset( &to, 0, sizeof( to ) ); // The 'xxx' in the message is a challenge that will be echoed back // by the server. We don't care about that here, but master servers // can use that to prevent spoofed server responses from invalid ip message = "\377\377\377\377getinfo xxx"; // send each message twice in case one is dropped for ( i = 0 ; i < 2 ; i++ ) { // send a broadcast packet on each server port // we support multiple server ports so a single machine // can nicely run multiple servers for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) { to.port = BigShort( (short)(PORT_SERVER + j) ); to.type = NA_BROADCAST; NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); to.type = NA_BROADCAST_IPX; NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); } } } /* ================== CL_GlobalServers_f ================== */ void CL_GlobalServers_f( void ) { netadr_t to; int i; int count; char *buffptr; char command[1024]; if ( Cmd_Argc() < 3) { Com_Printf( "usage: globalservers [keywords]\n"); return; } cls.masterNum = atoi( Cmd_Argv(1) ); Com_Printf( "Requesting servers from the master...\n"); // reset the list, waiting for response // -1 is used to distinguish a "no response" if( cls.masterNum == 1 ) { NET_StringToAdr( MASTER_SERVER_NAME, &to ); cls.nummplayerservers = -1; cls.pingUpdateSource = AS_MPLAYER; } else { NET_StringToAdr( MASTER_SERVER_NAME, &to ); cls.numglobalservers = -1; cls.pingUpdateSource = AS_GLOBAL; } to.type = NA_IP; to.port = BigShort(PORT_MASTER); sprintf( command, "getservers %s", Cmd_Argv(2) ); // tack on keywords buffptr = command + strlen( command ); count = Cmd_Argc(); for (i=3; i= MAX_PINGREQUESTS) return; cl_pinglist[n].adr.port = 0; } /* ================== CL_GetPingQueueCount ================== */ int CL_GetPingQueueCount( void ) { int i; int count; ping_t* pingptr; count = 0; pingptr = cl_pinglist; for (i=0; iadr.port) { count++; } } return (count); } /* ================== CL_GetFreePing ================== */ ping_t* CL_GetFreePing( void ) { ping_t* pingptr; ping_t* best; int oldest; int i; int time; pingptr = cl_pinglist; for (i=0; iadr.port) { if (!pingptr->time) { if (cls.realtime - pingptr->start < 500) { // still waiting for response continue; } } else if (pingptr->time < 500) { // results have not been queried continue; } } // clear it pingptr->adr.port = 0; return (pingptr); } // use oldest entry pingptr = cl_pinglist; best = cl_pinglist; oldest = INT_MIN; for (i=0; istart; if (time > oldest) { oldest = time; best = pingptr; } } return (best); } /* ================== CL_Ping_f ================== */ void CL_Ping_f( void ) { netadr_t to; ping_t* pingptr; char* server; if ( Cmd_Argc() != 2 ) { Com_Printf( "usage: ping [server]\n"); return; } Com_Memset( &to, 0, sizeof(netadr_t) ); server = Cmd_Argv(1); if ( !NET_StringToAdr( server, &to ) ) { return; } pingptr = CL_GetFreePing(); memcpy( &pingptr->adr, &to, sizeof (netadr_t) ); pingptr->start = cls.realtime; pingptr->time = 0; CL_SetServerInfoByAddress(pingptr->adr, NULL, 0); NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" ); } /* ================== CL_UpdateVisiblePings_f ================== */ qboolean CL_UpdateVisiblePings_f(int source) { int slots, i; char buff[MAX_STRING_CHARS]; int pingTime; int max; qboolean status = qfalse; if (source < 0 || source > AS_FAVORITES) { return qfalse; } cls.pingUpdateSource = source; slots = CL_GetPingQueueCount(); if (slots < MAX_PINGREQUESTS) { serverInfo_t *server = NULL; max = (source == AS_GLOBAL) ? MAX_GLOBAL_SERVERS : MAX_OTHER_SERVERS; switch (source) { case AS_LOCAL : server = &cls.localServers[0]; max = cls.numlocalservers; break; case AS_MPLAYER : server = &cls.mplayerServers[0]; max = cls.nummplayerservers; break; case AS_GLOBAL : server = &cls.globalServers[0]; max = cls.numglobalservers; break; case AS_FAVORITES : server = &cls.favoriteServers[0]; max = cls.numfavoriteservers; break; } for (i = 0; i < max; i++) { if (server[i].visible) { if (server[i].ping == -1) { int j; if (slots >= MAX_PINGREQUESTS) { break; } for (j = 0; j < MAX_PINGREQUESTS; j++) { if (!cl_pinglist[j].adr.port) { continue; } if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) { // already on the list break; } } if (j >= MAX_PINGREQUESTS) { status = qtrue; for (j = 0; j < MAX_PINGREQUESTS; j++) { if (!cl_pinglist[j].adr.port) { break; } } memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t)); cl_pinglist[j].start = cls.realtime; cl_pinglist[j].time = 0; NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" ); slots++; } } // if the server has a ping higher than cl_maxPing or // the ping packet got lost else if (server[i].ping == 0) { // if we are updating global servers if (source == AS_GLOBAL) { // if ( cls.numGlobalServerAddresses > 0 ) { // overwrite this server with one from the additional global servers cls.numGlobalServerAddresses--; CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]); // NOTE: the server[i].visible flag stays untouched } } } } } } if (slots) { status = qtrue; } for (i = 0; i < MAX_PINGREQUESTS; i++) { if (!cl_pinglist[i].adr.port) { continue; } CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime ); if (pingTime != 0) { CL_ClearPing(i); status = qtrue; } } return status; } /* ================== CL_ServerStatus_f ================== */ void CL_ServerStatus_f(void) { netadr_t to; char *server; serverStatus_t *serverStatus; Com_Memset( &to, 0, sizeof(netadr_t) ); if ( Cmd_Argc() != 2 ) { if ( cls.state != CA_ACTIVE || clc.demoplaying ) { Com_Printf ("Not connected to a server.\n"); Com_Printf( "Usage: serverstatus [server]\n"); return; } server = cls.servername; } else { server = Cmd_Argv(1); } if ( !NET_StringToAdr( server, &to ) ) { return; } NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); serverStatus = CL_GetServerStatus( to ); serverStatus->address = to; serverStatus->print = qtrue; serverStatus->pending = qtrue; } /* ================== CL_ShowIP_f ================== */ void CL_ShowIP_f(void) { Sys_ShowIP(); } /* ================= bool CL_CDKeyValidate ================= */ qboolean CL_CDKeyValidate( const char *key, const char *checksum ) { char ch; byte sum; char chs[3]; int i, len; len = strlen(key); if( len != CDKEY_LEN ) { return qfalse; } if( checksum && strlen( checksum ) != CDCHKSUM_LEN ) { return qfalse; } sum = 0; // for loop gets rid of conditional assignment warning for (i = 0; i < len; i++) { ch = *key++; if (ch>='a' && ch<='z') { ch -= 32; } switch( ch ) { case '2': case '3': case '7': case 'A': case 'B': case 'C': case 'D': case 'G': case 'H': case 'J': case 'L': case 'P': case 'R': case 'S': case 'T': case 'W': sum += ch; continue; default: return qfalse; } } sprintf(chs, "%02x", sum); if (checksum && !Q_stricmp(chs, checksum)) { return qtrue; } if (!checksum) { return qtrue; } return qfalse; } ================================================ FILE: code/client/cl_net_chan.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "../game/q_shared.h" #include "../qcommon/qcommon.h" #include "client.h" /* ============== CL_Netchan_Encode // first 12 bytes of the data are always: long serverId; long messageAcknowledge; long reliableAcknowledge; ============== */ static void CL_Netchan_Encode( msg_t *msg ) { int serverId, messageAcknowledge, reliableAcknowledge; int i, index, srdc, sbit, soob; byte key, *string; if ( msg->cursize <= CL_ENCODE_START ) { return; } srdc = msg->readcount; sbit = msg->bit; soob = msg->oob; msg->bit = 0; msg->readcount = 0; msg->oob = 0; serverId = MSG_ReadLong(msg); messageAcknowledge = MSG_ReadLong(msg); reliableAcknowledge = MSG_ReadLong(msg); msg->oob = soob; msg->bit = sbit; msg->readcount = srdc; string = (byte *)clc.serverCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; index = 0; // key = clc.challenge ^ serverId ^ messageAcknowledge; for (i = CL_ENCODE_START; i < msg->cursize; i++) { // modify the key with the last received now acknowledged server command if (!string[index]) index = 0; if (string[index] > 127 || string[index] == '%') { key ^= '.' << (i & 1); } else { key ^= string[index] << (i & 1); } index++; // encode the data with this key *(msg->data + i) = (*(msg->data + i)) ^ key; } } /* ============== CL_Netchan_Decode // first four bytes of the data are always: long reliableAcknowledge; ============== */ static void CL_Netchan_Decode( msg_t *msg ) { long reliableAcknowledge, i, index; byte key, *string; int srdc, sbit, soob; srdc = msg->readcount; sbit = msg->bit; soob = msg->oob; msg->oob = 0; reliableAcknowledge = MSG_ReadLong(msg); msg->oob = soob; msg->bit = sbit; msg->readcount = srdc; string = clc.reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; index = 0; // xor the client challenge with the netchan sequence number (need something that changes every message) key = clc.challenge ^ LittleLong( *(unsigned *)msg->data ); for (i = msg->readcount + CL_DECODE_START; i < msg->cursize; i++) { // modify the key with the last sent and with this message acknowledged client command if (!string[index]) index = 0; if (string[index] > 127 || string[index] == '%') { key ^= '.' << (i & 1); } else { key ^= string[index] << (i & 1); } index++; // decode the data with this key *(msg->data + i) = *(msg->data + i) ^ key; } } /* ================= CL_Netchan_TransmitNextFragment ================= */ void CL_Netchan_TransmitNextFragment( netchan_t *chan ) { Netchan_TransmitNextFragment( chan ); } /* =============== CL_Netchan_Transmit ================ */ void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) { MSG_WriteByte( msg, clc_EOF ); CL_Netchan_Encode( msg ); Netchan_Transmit( chan, msg->cursize, msg->data ); } extern int oldsize; int newsize = 0; /* ================= CL_Netchan_Process ================= */ qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) { int ret; ret = Netchan_Process( chan, msg ); if (!ret) return qfalse; CL_Netchan_Decode( msg ); newsize += msg->cursize; return qtrue; } ================================================ FILE: code/client/cl_parse.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // cl_parse.c -- parse a message received from the server #include "client.h" char *svc_strings[256] = { "svc_bad", "svc_nop", "svc_gamestate", "svc_configstring", "svc_baseline", "svc_serverCommand", "svc_download", "svc_snapshot" }; void SHOWNET( msg_t *msg, char *s) { if ( cl_shownet->integer >= 2) { Com_Printf ("%3i:%s\n", msg->readcount-1, s); } } /* ========================================================================= MESSAGE PARSING ========================================================================= */ /* ================== CL_DeltaEntity Parses deltas from the given base and adds the resulting entity to the current frame ================== */ void CL_DeltaEntity (msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old, qboolean unchanged) { entityState_t *state; // save the parsed entity state into the big circular buffer so // it can be used as the source for a later delta state = &cl.parseEntities[cl.parseEntitiesNum & (MAX_PARSE_ENTITIES-1)]; if ( unchanged ) { *state = *old; } else { MSG_ReadDeltaEntity( msg, old, state, newnum ); } if ( state->number == (MAX_GENTITIES-1) ) { return; // entity was delta removed } cl.parseEntitiesNum++; frame->numEntities++; } /* ================== CL_ParsePacketEntities ================== */ void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe) { int newnum; entityState_t *oldstate; int oldindex, oldnum; newframe->parseEntitiesNum = cl.parseEntitiesNum; newframe->numEntities = 0; // delta from the entities present in oldframe oldindex = 0; oldstate = NULL; if (!oldframe) { oldnum = 99999; } else { if ( oldindex >= oldframe->numEntities ) { oldnum = 99999; } else { oldstate = &cl.parseEntities[ (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; oldnum = oldstate->number; } } while ( 1 ) { // read the entity index number newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); if ( newnum == (MAX_GENTITIES-1) ) { break; } if ( msg->readcount > msg->cursize ) { Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message"); } while ( oldnum < newnum ) { // one or more entities from the old packet are unchanged if ( cl_shownet->integer == 3 ) { Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); } CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); oldindex++; if ( oldindex >= oldframe->numEntities ) { oldnum = 99999; } else { oldstate = &cl.parseEntities[ (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; oldnum = oldstate->number; } } if (oldnum == newnum) { // delta from previous state if ( cl_shownet->integer == 3 ) { Com_Printf ("%3i: delta: %i\n", msg->readcount, newnum); } CL_DeltaEntity( msg, newframe, newnum, oldstate, qfalse ); oldindex++; if ( oldindex >= oldframe->numEntities ) { oldnum = 99999; } else { oldstate = &cl.parseEntities[ (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; oldnum = oldstate->number; } continue; } if ( oldnum > newnum ) { // delta from baseline if ( cl_shownet->integer == 3 ) { Com_Printf ("%3i: baseline: %i\n", msg->readcount, newnum); } CL_DeltaEntity( msg, newframe, newnum, &cl.entityBaselines[newnum], qfalse ); continue; } } // any remaining entities in the old frame are copied over while ( oldnum != 99999 ) { // one or more entities from the old packet are unchanged if ( cl_shownet->integer == 3 ) { Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); } CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); oldindex++; if ( oldindex >= oldframe->numEntities ) { oldnum = 99999; } else { oldstate = &cl.parseEntities[ (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; oldnum = oldstate->number; } } } /* ================ CL_ParseSnapshot If the snapshot is parsed properly, it will be copied to cl.snap and saved in cl.snapshots[]. If the snapshot is invalid for any reason, no changes to the state will be made at all. ================ */ void CL_ParseSnapshot( msg_t *msg ) { int len; clSnapshot_t *old; clSnapshot_t newSnap; int deltaNum; int oldMessageNum; int i, packetNum; // get the reliable sequence acknowledge number // NOTE: now sent with all server to client messages //clc.reliableAcknowledge = MSG_ReadLong( msg ); // read in the new snapshot to a temporary buffer // we will only copy to cl.snap if it is valid Com_Memset (&newSnap, 0, sizeof(newSnap)); // we will have read any new server commands in this // message before we got to svc_snapshot newSnap.serverCommandNum = clc.serverCommandSequence; newSnap.serverTime = MSG_ReadLong( msg ); newSnap.messageNum = clc.serverMessageSequence; deltaNum = MSG_ReadByte( msg ); if ( !deltaNum ) { newSnap.deltaNum = -1; } else { newSnap.deltaNum = newSnap.messageNum - deltaNum; } newSnap.snapFlags = MSG_ReadByte( msg ); // If the frame is delta compressed from data that we // no longer have available, we must suck up the rest of // the frame, but not use it, then ask for a non-compressed // message if ( newSnap.deltaNum <= 0 ) { newSnap.valid = qtrue; // uncompressed frame old = NULL; clc.demowaiting = qfalse; // we can start recording now } else { old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK]; if ( !old->valid ) { // should never happen Com_Printf ("Delta from invalid frame (not supposed to happen!).\n"); } else if ( old->messageNum != newSnap.deltaNum ) { // The frame that the server did the delta from // is too old, so we can't reconstruct it properly. Com_Printf ("Delta frame too old.\n"); } else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES-128 ) { Com_Printf ("Delta parseEntitiesNum too old.\n"); } else { newSnap.valid = qtrue; // valid delta parse } } // read areamask len = MSG_ReadByte( msg ); MSG_ReadData( msg, &newSnap.areamask, len); // read playerinfo SHOWNET( msg, "playerstate" ); if ( old ) { MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps ); } else { MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps ); } // read packet entities SHOWNET( msg, "packet entities" ); CL_ParsePacketEntities( msg, old, &newSnap ); // if not valid, dump the entire thing now that it has // been properly read if ( !newSnap.valid ) { return; } // clear the valid flags of any snapshots between the last // received and this one, so if there was a dropped packet // it won't look like something valid to delta from next // time we wrap around in the buffer oldMessageNum = cl.snap.messageNum + 1; if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) { oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 ); } for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) { cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse; } // copy to the current good spot cl.snap = newSnap; cl.snap.ping = 999; // calculate ping time for ( i = 0 ; i < PACKET_BACKUP ; i++ ) { packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK; if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) { cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime; break; } } // save the frame off in the backup array for later delta comparisons cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap; if (cl_shownet->integer == 3) { Com_Printf( " snapshot:%i delta:%i ping:%i\n", cl.snap.messageNum, cl.snap.deltaNum, cl.snap.ping ); } cl.newSnapshots = qtrue; } //===================================================================== int cl_connectedToPureServer; /* ================== CL_SystemInfoChanged The systeminfo configstring has been changed, so parse new information out of it. This will happen at every gamestate, and possibly during gameplay. ================== */ void CL_SystemInfoChanged( void ) { char *systemInfo; const char *s, *t; char key[BIG_INFO_KEY]; char value[BIG_INFO_VALUE]; qboolean gameSet; systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ]; // NOTE TTimo: // when the serverId changes, any further messages we send to the server will use this new serverId // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 // in some cases, outdated cp commands might get sent with this news serverId cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) ); // don't set any vars when playing a demo if ( clc.demoplaying ) { return; } s = Info_ValueForKey( systemInfo, "sv_cheats" ); if ( atoi(s) == 0 ) { Cvar_SetCheatState(); } // check pure server string s = Info_ValueForKey( systemInfo, "sv_paks" ); t = Info_ValueForKey( systemInfo, "sv_pakNames" ); FS_PureServerSetLoadedPaks( s, t ); s = Info_ValueForKey( systemInfo, "sv_referencedPaks" ); t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" ); FS_PureServerSetReferencedPaks( s, t ); gameSet = qfalse; // scan through all the variables in the systeminfo and locally set cvars to match s = systemInfo; while ( s ) { Info_NextPair( &s, key, value ); if ( !key[0] ) { break; } // ehw! if ( !Q_stricmp( key, "fs_game" ) ) { gameSet = qtrue; } Cvar_Set( key, value ); } // if game folder should not be set and it is set at the client side if ( !gameSet && *Cvar_VariableString("fs_game") ) { Cvar_Set( "fs_game", "" ); } cl_connectedToPureServer = Cvar_VariableValue( "sv_pure" ); } /* ================== CL_ParseGamestate ================== */ void CL_ParseGamestate( msg_t *msg ) { int i; entityState_t *es; int newnum; entityState_t nullstate; int cmd; char *s; Con_Close(); clc.connectPacketCount = 0; // wipe local client state CL_ClearState(); // a gamestate always marks a server command sequence clc.serverCommandSequence = MSG_ReadLong( msg ); // parse all the configstrings and baselines cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings while ( 1 ) { cmd = MSG_ReadByte( msg ); if ( cmd == svc_EOF ) { break; } if ( cmd == svc_configstring ) { int len; i = MSG_ReadShort( msg ); if ( i < 0 || i >= MAX_CONFIGSTRINGS ) { Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); } s = MSG_ReadBigString( msg ); len = strlen( s ); if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); } // append it to the gameState string buffer cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 ); cl.gameState.dataCount += len + 1; } else if ( cmd == svc_baseline ) { newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); if ( newnum < 0 || newnum >= MAX_GENTITIES ) { Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum ); } Com_Memset (&nullstate, 0, sizeof(nullstate)); es = &cl.entityBaselines[ newnum ]; MSG_ReadDeltaEntity( msg, &nullstate, es, newnum ); } else { Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" ); } } clc.clientNum = MSG_ReadLong(msg); // read the checksum feed clc.checksumFeed = MSG_ReadLong( msg ); // parse serverId and other cvars CL_SystemInfoChanged(); // reinitialize the filesystem if the game directory has changed FS_ConditionalRestart( clc.checksumFeed ); // This used to call CL_StartHunkUsers, but now we enter the download state before loading the // cgame CL_InitDownloads(); // make sure the game starts Cvar_Set( "cl_paused", "0" ); } //===================================================================== /* ===================== CL_ParseDownload A download message has been received from the server ===================== */ void CL_ParseDownload ( msg_t *msg ) { int size; unsigned char data[MAX_MSGLEN]; int block; // read the data block = MSG_ReadShort ( msg ); if ( !block ) { // block zero is special, contains file size clc.downloadSize = MSG_ReadLong ( msg ); Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); if (clc.downloadSize < 0) { Com_Error(ERR_DROP, MSG_ReadString( msg ) ); return; } } size = MSG_ReadShort ( msg ); if (size > 0) MSG_ReadData( msg, data, size ); if (clc.downloadBlock != block) { Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", clc.downloadBlock, block); return; } // open the file if not opened yet if (!clc.download) { if (!*clc.downloadTempName) { Com_Printf("Server sending download, but no download was requested\n"); CL_AddReliableCommand( "stopdl" ); return; } clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName ); if (!clc.download) { Com_Printf( "Could not create %s\n", clc.downloadTempName ); CL_AddReliableCommand( "stopdl" ); CL_NextDownload(); return; } } if (size) FS_Write( data, size, clc.download ); CL_AddReliableCommand( va("nextdl %d", clc.downloadBlock) ); clc.downloadBlock++; clc.downloadCount += size; // So UI gets access to it Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); if (!size) { // A zero length block means EOF if (clc.download) { FS_FCloseFile( clc.download ); clc.download = 0; // rename the file FS_SV_Rename ( clc.downloadTempName, clc.downloadName ); } *clc.downloadTempName = *clc.downloadName = 0; Cvar_Set( "cl_downloadName", "" ); // send intentions now // We need this because without it, we would hold the last nextdl and then start // loading right away. If we take a while to load, the server is happily trying // to send us that last block over and over. // Write it twice to help make sure we acknowledge the download CL_WritePacket(); CL_WritePacket(); // get another file if needed CL_NextDownload (); } } /* ===================== CL_ParseCommandString Command strings are just saved off until cgame asks for them when it transitions a snapshot ===================== */ void CL_ParseCommandString( msg_t *msg ) { char *s; int seq; int index; seq = MSG_ReadLong( msg ); s = MSG_ReadString( msg ); // see if we have already executed stored it off if ( clc.serverCommandSequence >= seq ) { return; } clc.serverCommandSequence = seq; index = seq & (MAX_RELIABLE_COMMANDS-1); Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) ); } /* ===================== CL_ParseServerMessage ===================== */ void CL_ParseServerMessage( msg_t *msg ) { int cmd; if ( cl_shownet->integer == 1 ) { Com_Printf ("%i ",msg->cursize); } else if ( cl_shownet->integer >= 2 ) { Com_Printf ("------------------\n"); } MSG_Bitstream(msg); // get the reliable sequence acknowledge number clc.reliableAcknowledge = MSG_ReadLong( msg ); // if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { clc.reliableAcknowledge = clc.reliableSequence; } // // parse the message // while ( 1 ) { if ( msg->readcount > msg->cursize ) { Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message"); break; } cmd = MSG_ReadByte( msg ); if ( cmd == svc_EOF) { SHOWNET( msg, "END OF MESSAGE" ); break; } if ( cl_shownet->integer >= 2 ) { if ( !svc_strings[cmd] ) { Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd ); } else { SHOWNET( msg, svc_strings[cmd] ); } } // other commands switch ( cmd ) { default: Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message\n"); break; case svc_nop: break; case svc_serverCommand: CL_ParseCommandString( msg ); break; case svc_gamestate: CL_ParseGamestate( msg ); break; case svc_snapshot: CL_ParseSnapshot( msg ); break; case svc_download: CL_ParseDownload( msg ); break; } } } ================================================ FILE: code/client/cl_scrn.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc #include "client.h" qboolean scr_initialized; // ready to draw cvar_t *cl_timegraph; cvar_t *cl_debuggraph; cvar_t *cl_graphheight; cvar_t *cl_graphscale; cvar_t *cl_graphshift; /* ================ SCR_DrawNamedPic Coordinates are 640*480 virtual values ================= */ void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { qhandle_t hShader; assert( width != 0 ); hShader = re.RegisterShader( picname ); SCR_AdjustFrom640( &x, &y, &width, &height ); re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); } /* ================ SCR_AdjustFrom640 Adjusted for resolution and screen aspect ratio ================ */ void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ) { float xscale; float yscale; #if 0 // adjust for wide screens if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { *x += 0.5 * ( cls.glconfig.vidWidth - ( cls.glconfig.vidHeight * 640 / 480 ) ); } #endif // scale for screen sizes xscale = cls.glconfig.vidWidth / 640.0; yscale = cls.glconfig.vidHeight / 480.0; if ( x ) { *x *= xscale; } if ( y ) { *y *= yscale; } if ( w ) { *w *= xscale; } if ( h ) { *h *= yscale; } } /* ================ SCR_FillRect Coordinates are 640*480 virtual values ================= */ void SCR_FillRect( float x, float y, float width, float height, const float *color ) { re.SetColor( color ); SCR_AdjustFrom640( &x, &y, &width, &height ); re.DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cls.whiteShader ); re.SetColor( NULL ); } /* ================ SCR_DrawPic Coordinates are 640*480 virtual values ================= */ void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { SCR_AdjustFrom640( &x, &y, &width, &height ); re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); } /* ** SCR_DrawChar ** chars are drawn at 640*480 virtual screen size */ static void SCR_DrawChar( int x, int y, float size, int ch ) { int row, col; float frow, fcol; float ax, ay, aw, ah; ch &= 255; if ( ch == ' ' ) { return; } if ( y < -size ) { return; } ax = x; ay = y; aw = size; ah = size; SCR_AdjustFrom640( &ax, &ay, &aw, &ah ); row = ch>>4; col = ch&15; frow = row*0.0625; fcol = col*0.0625; size = 0.0625; re.DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol + size, frow + size, cls.charSetShader ); } /* ** SCR_DrawSmallChar ** small chars are drawn at native screen resolution */ void SCR_DrawSmallChar( int x, int y, int ch ) { int row, col; float frow, fcol; float size; ch &= 255; if ( ch == ' ' ) { return; } if ( y < -SMALLCHAR_HEIGHT ) { return; } row = ch>>4; col = ch&15; frow = row*0.0625; fcol = col*0.0625; size = 0.0625; re.DrawStretchPic( x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, fcol, frow, fcol + size, frow + size, cls.charSetShader ); } /* ================== SCR_DrawBigString[Color] Draws a multi-colored string with a drop shadow, optionally forcing to a fixed color. Coordinates are at 640 by 480 virtual resolution ================== */ void SCR_DrawStringExt( int x, int y, float size, const char *string, float *setColor, qboolean forceColor ) { vec4_t color; const char *s; int xx; // draw the drop shadow color[0] = color[1] = color[2] = 0; color[3] = setColor[3]; re.SetColor( color ); s = string; xx = x; while ( *s ) { if ( Q_IsColorString( s ) ) { s += 2; continue; } SCR_DrawChar( xx+2, y+2, size, *s ); xx += size; s++; } // draw the colored text s = string; xx = x; re.SetColor( setColor ); while ( *s ) { if ( Q_IsColorString( s ) ) { if ( !forceColor ) { Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); color[3] = setColor[3]; re.SetColor( color ); } s += 2; continue; } SCR_DrawChar( xx, y, size, *s ); xx += size; s++; } re.SetColor( NULL ); } void SCR_DrawBigString( int x, int y, const char *s, float alpha ) { float color[4]; color[0] = color[1] = color[2] = 1.0; color[3] = alpha; SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qfalse ); } void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qtrue ); } /* ================== SCR_DrawSmallString[Color] Draws a multi-colored string with a drop shadow, optionally forcing to a fixed color. Coordinates are at 640 by 480 virtual resolution ================== */ void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor ) { vec4_t color; const char *s; int xx; // draw the colored text s = string; xx = x; re.SetColor( setColor ); while ( *s ) { if ( Q_IsColorString( s ) ) { if ( !forceColor ) { Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); color[3] = setColor[3]; re.SetColor( color ); } s += 2; continue; } SCR_DrawSmallChar( xx, y, *s ); xx += SMALLCHAR_WIDTH; s++; } re.SetColor( NULL ); } /* ** SCR_Strlen -- skips color escape codes */ static int SCR_Strlen( const char *str ) { const char *s = str; int count = 0; while ( *s ) { if ( Q_IsColorString( s ) ) { s += 2; } else { count++; s++; } } return count; } /* ** SCR_GetBigStringWidth */ int SCR_GetBigStringWidth( const char *str ) { return SCR_Strlen( str ) * 16; } //=============================================================================== /* ================= SCR_DrawDemoRecording ================= */ void SCR_DrawDemoRecording( void ) { char string[1024]; int pos; if ( !clc.demorecording ) { return; } if ( clc.spDemoRecording ) { return; } pos = FS_FTell( clc.demofile ); sprintf( string, "RECORDING %s: %ik", clc.demoName, pos / 1024 ); SCR_DrawStringExt( 320 - strlen( string ) * 4, 20, 8, string, g_color_table[7], qtrue ); } /* =============================================================================== DEBUG GRAPH =============================================================================== */ typedef struct { float value; int color; } graphsamp_t; static int current; static graphsamp_t values[1024]; /* ============== SCR_DebugGraph ============== */ void SCR_DebugGraph (float value, int color) { values[current&1023].value = value; values[current&1023].color = color; current++; } /* ============== SCR_DrawDebugGraph ============== */ void SCR_DrawDebugGraph (void) { int a, x, y, w, i, h; float v; int color; // // draw the graph // w = cls.glconfig.vidWidth; x = 0; y = cls.glconfig.vidHeight; re.SetColor( g_color_table[0] ); re.DrawStretchPic(x, y - cl_graphheight->integer, w, cl_graphheight->integer, 0, 0, 0, 0, cls.whiteShader ); re.SetColor( NULL ); for (a=0 ; ainteger + cl_graphshift->integer; if (v < 0) v += cl_graphheight->integer * (1+(int)(-v / cl_graphheight->integer)); h = (int)v % cl_graphheight->integer; re.DrawStretchPic( x+w-1-a, y - h, 1, h, 0, 0, 0, 0, cls.whiteShader ); } } //============================================================================= /* ================== SCR_Init ================== */ void SCR_Init( void ) { cl_timegraph = Cvar_Get ("timegraph", "0", CVAR_CHEAT); cl_debuggraph = Cvar_Get ("debuggraph", "0", CVAR_CHEAT); cl_graphheight = Cvar_Get ("graphheight", "32", CVAR_CHEAT); cl_graphscale = Cvar_Get ("graphscale", "1", CVAR_CHEAT); cl_graphshift = Cvar_Get ("graphshift", "0", CVAR_CHEAT); scr_initialized = qtrue; } //======================================================= /* ================== SCR_DrawScreenField This will be called twice if rendering in stereo mode ================== */ void SCR_DrawScreenField( stereoFrame_t stereoFrame ) { re.BeginFrame( stereoFrame ); // wide aspect ratio screens need to have the sides cleared // unless they are displaying game renderings if ( cls.state != CA_ACTIVE ) { if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { re.SetColor( g_color_table[0] ); re.DrawStretchPic( 0, 0, cls.glconfig.vidWidth, cls.glconfig.vidHeight, 0, 0, 0, 0, cls.whiteShader ); re.SetColor( NULL ); } } if ( !uivm ) { Com_DPrintf("draw screen without UI loaded\n"); return; } // if the menu is going to cover the entire screen, we // don't need to render anything under it if ( !VM_Call( uivm, UI_IS_FULLSCREEN )) { switch( cls.state ) { default: Com_Error( ERR_FATAL, "SCR_DrawScreenField: bad cls.state" ); break; case CA_CINEMATIC: SCR_DrawCinematic(); break; case CA_DISCONNECTED: // force menu up S_StopAllSounds(); VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); break; case CA_CONNECTING: case CA_CHALLENGING: case CA_CONNECTED: // connecting clients will only show the connection dialog // refresh to update the time VM_Call( uivm, UI_REFRESH, cls.realtime ); VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse ); break; case CA_LOADING: case CA_PRIMED: // draw the game information screen and loading progress CL_CGameRendering( stereoFrame ); // also draw the connection information, so it doesn't // flash away too briefly on local or lan games // refresh to update the time VM_Call( uivm, UI_REFRESH, cls.realtime ); VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qtrue ); break; case CA_ACTIVE: CL_CGameRendering( stereoFrame ); SCR_DrawDemoRecording(); break; } } // the menu draws next if ( cls.keyCatchers & KEYCATCH_UI && uivm ) { VM_Call( uivm, UI_REFRESH, cls.realtime ); } // console draws next Con_DrawConsole (); // debug graph can be drawn on top of anything if ( cl_debuggraph->integer || cl_timegraph->integer || cl_debugMove->integer ) { SCR_DrawDebugGraph (); } } /* ================== SCR_UpdateScreen This is called every frame, and can also be called explicitly to flush text to the screen. ================== */ void SCR_UpdateScreen( void ) { static int recursive; if ( !scr_initialized ) { return; // not initialized yet } if ( ++recursive > 2 ) { Com_Error( ERR_FATAL, "SCR_UpdateScreen: recursively called" ); } recursive = 1; // if running in stereo, we need to draw the frame twice if ( cls.glconfig.stereoEnabled ) { SCR_DrawScreenField( STEREO_LEFT ); SCR_DrawScreenField( STEREO_RIGHT ); } else { SCR_DrawScreenField( STEREO_CENTER ); } if ( com_speeds->integer ) { re.EndFrame( &time_frontend, &time_backend ); } else { re.EndFrame( NULL, NULL ); } recursive = 0; } ================================================ FILE: code/client/cl_ui.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "client.h" #include "../game/botlib.h" extern botlib_export_t *botlib_export; vm_t *uivm; /* ==================== GetClientState ==================== */ static void GetClientState( uiClientState_t *state ) { state->connectPacketCount = clc.connectPacketCount; state->connState = cls.state; Q_strncpyz( state->servername, cls.servername, sizeof( state->servername ) ); Q_strncpyz( state->updateInfoString, cls.updateInfoString, sizeof( state->updateInfoString ) ); Q_strncpyz( state->messageString, clc.serverMessage, sizeof( state->messageString ) ); state->clientNum = cl.snap.ps.clientNum; } /* ==================== LAN_LoadCachedServers ==================== */ void LAN_LoadCachedServers( ) { int size; fileHandle_t fileIn; cls.numglobalservers = cls.nummplayerservers = cls.numfavoriteservers = 0; cls.numGlobalServerAddresses = 0; if (FS_SV_FOpenFileRead("servercache.dat", &fileIn)) { FS_Read(&cls.numglobalservers, sizeof(int), fileIn); FS_Read(&cls.nummplayerservers, sizeof(int), fileIn); FS_Read(&cls.numfavoriteservers, sizeof(int), fileIn); FS_Read(&size, sizeof(int), fileIn); if (size == sizeof(cls.globalServers) + sizeof(cls.favoriteServers) + sizeof(cls.mplayerServers)) { FS_Read(&cls.globalServers, sizeof(cls.globalServers), fileIn); FS_Read(&cls.mplayerServers, sizeof(cls.mplayerServers), fileIn); FS_Read(&cls.favoriteServers, sizeof(cls.favoriteServers), fileIn); } else { cls.numglobalservers = cls.nummplayerservers = cls.numfavoriteservers = 0; cls.numGlobalServerAddresses = 0; } FS_FCloseFile(fileIn); } } /* ==================== LAN_SaveServersToCache ==================== */ void LAN_SaveServersToCache( ) { int size; fileHandle_t fileOut = FS_SV_FOpenFileWrite("servercache.dat"); FS_Write(&cls.numglobalservers, sizeof(int), fileOut); FS_Write(&cls.nummplayerservers, sizeof(int), fileOut); FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut); size = sizeof(cls.globalServers) + sizeof(cls.favoriteServers) + sizeof(cls.mplayerServers); FS_Write(&size, sizeof(int), fileOut); FS_Write(&cls.globalServers, sizeof(cls.globalServers), fileOut); FS_Write(&cls.mplayerServers, sizeof(cls.mplayerServers), fileOut); FS_Write(&cls.favoriteServers, sizeof(cls.favoriteServers), fileOut); FS_FCloseFile(fileOut); } /* ==================== LAN_ResetPings ==================== */ static void LAN_ResetPings(int source) { int count,i; serverInfo_t *servers = NULL; count = 0; switch (source) { case AS_LOCAL : servers = &cls.localServers[0]; count = MAX_OTHER_SERVERS; break; case AS_MPLAYER : servers = &cls.mplayerServers[0]; count = MAX_OTHER_SERVERS; break; case AS_GLOBAL : servers = &cls.globalServers[0]; count = MAX_GLOBAL_SERVERS; break; case AS_FAVORITES : servers = &cls.favoriteServers[0]; count = MAX_OTHER_SERVERS; break; } if (servers) { for (i = 0; i < count; i++) { servers[i].ping = -1; } } } /* ==================== LAN_AddServer ==================== */ static int LAN_AddServer(int source, const char *name, const char *address) { int max, *count, i; netadr_t adr; serverInfo_t *servers = NULL; max = MAX_OTHER_SERVERS; count = 0; switch (source) { case AS_LOCAL : count = &cls.numlocalservers; servers = &cls.localServers[0]; break; case AS_MPLAYER : count = &cls.nummplayerservers; servers = &cls.mplayerServers[0]; break; case AS_GLOBAL : max = MAX_GLOBAL_SERVERS; count = &cls.numglobalservers; servers = &cls.globalServers[0]; break; case AS_FAVORITES : count = &cls.numfavoriteservers; servers = &cls.favoriteServers[0]; break; } if (servers && *count < max) { NET_StringToAdr( address, &adr ); for ( i = 0; i < *count; i++ ) { if (NET_CompareAdr(servers[i].adr, adr)) { break; } } if (i >= *count) { servers[*count].adr = adr; Q_strncpyz(servers[*count].hostName, name, sizeof(servers[*count].hostName)); servers[*count].visible = qtrue; (*count)++; return 1; } return 0; } return -1; } /* ==================== LAN_RemoveServer ==================== */ static void LAN_RemoveServer(int source, const char *addr) { int *count, i; serverInfo_t *servers = NULL; count = 0; switch (source) { case AS_LOCAL : count = &cls.numlocalservers; servers = &cls.localServers[0]; break; case AS_MPLAYER : count = &cls.nummplayerservers; servers = &cls.mplayerServers[0]; break; case AS_GLOBAL : count = &cls.numglobalservers; servers = &cls.globalServers[0]; break; case AS_FAVORITES : count = &cls.numfavoriteservers; servers = &cls.favoriteServers[0]; break; } if (servers) { netadr_t comp; NET_StringToAdr( addr, &comp ); for (i = 0; i < *count; i++) { if (NET_CompareAdr( comp, servers[i].adr)) { int j = i; while (j < *count - 1) { Com_Memcpy(&servers[j], &servers[j+1], sizeof(servers[j])); j++; } (*count)--; break; } } } } /* ==================== LAN_GetServerCount ==================== */ static int LAN_GetServerCount( int source ) { switch (source) { case AS_LOCAL : return cls.numlocalservers; break; case AS_MPLAYER : return cls.nummplayerservers; break; case AS_GLOBAL : return cls.numglobalservers; break; case AS_FAVORITES : return cls.numfavoriteservers; break; } return 0; } /* ==================== LAN_GetLocalServerAddressString ==================== */ static void LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) { switch (source) { case AS_LOCAL : if (n >= 0 && n < MAX_OTHER_SERVERS) { Q_strncpyz(buf, NET_AdrToString( cls.localServers[n].adr) , buflen ); return; } break; case AS_MPLAYER : if (n >= 0 && n < MAX_OTHER_SERVERS) { Q_strncpyz(buf, NET_AdrToString( cls.mplayerServers[n].adr) , buflen ); return; } break; case AS_GLOBAL : if (n >= 0 && n < MAX_GLOBAL_SERVERS) { Q_strncpyz(buf, NET_AdrToString( cls.globalServers[n].adr) , buflen ); return; } break; case AS_FAVORITES : if (n >= 0 && n < MAX_OTHER_SERVERS) { Q_strncpyz(buf, NET_AdrToString( cls.favoriteServers[n].adr) , buflen ); return; } break; } buf[0] = '\0'; } /* ==================== LAN_GetServerInfo ==================== */ static void LAN_GetServerInfo( int source, int n, char *buf, int buflen ) { char info[MAX_STRING_CHARS]; serverInfo_t *server = NULL; info[0] = '\0'; switch (source) { case AS_LOCAL : if (n >= 0 && n < MAX_OTHER_SERVERS) { server = &cls.localServers[n]; } break; case AS_MPLAYER : if (n >= 0 && n < MAX_OTHER_SERVERS) { server = &cls.mplayerServers[n]; } break; case AS_GLOBAL : if (n >= 0 && n < MAX_GLOBAL_SERVERS) { server = &cls.globalServers[n]; } break; case AS_FAVORITES : if (n >= 0 && n < MAX_OTHER_SERVERS) { server = &cls.favoriteServers[n]; } break; } if (server && buf) { buf[0] = '\0'; Info_SetValueForKey( info, "hostname", server->hostName); Info_SetValueForKey( info, "mapname", server->mapName); Info_SetValueForKey( info, "clients", va("%i",server->clients)); Info_SetValueForKey( info, "sv_maxclients", va("%i",server->maxClients)); Info_SetValueForKey( info, "ping", va("%i",server->ping)); Info_SetValueForKey( info, "minping", va("%i",server->minPing)); Info_SetValueForKey( info, "maxping", va("%i",server->maxPing)); Info_SetValueForKey( info, "game", server->game); Info_SetValueForKey( info, "gametype", va("%i",server->gameType)); Info_SetValueForKey( info, "nettype", va("%i",server->netType)); Info_SetValueForKey( info, "addr", NET_AdrToString(server->adr)); Info_SetValueForKey( info, "punkbuster", va("%i", server->punkbuster)); Q_strncpyz(buf, info, buflen); } else { if (buf) { buf[0] = '\0'; } } } /* ==================== LAN_GetServerPing ==================== */ static int LAN_GetServerPing( int source, int n ) { serverInfo_t *server = NULL; switch (source) { case AS_LOCAL : if (n >= 0 && n < MAX_OTHER_SERVERS) { server = &cls.localServers[n]; } break; case AS_MPLAYER : if (n >= 0 && n < MAX_OTHER_SERVERS) { server = &cls.mplayerServers[n]; } break; case AS_GLOBAL : if (n >= 0 && n < MAX_GLOBAL_SERVERS) { server = &cls.globalServers[n]; } break; case AS_FAVORITES : if (n >= 0 && n < MAX_OTHER_SERVERS) { server = &cls.favoriteServers[n]; } break; } if (server) { return server->ping; } return -1; } /* ==================== LAN_GetServerPtr ==================== */ static serverInfo_t *LAN_GetServerPtr( int source, int n ) { switch (source) { case AS_LOCAL : if (n >= 0 && n < MAX_OTHER_SERVERS) { return &cls.localServers[n]; } break; case AS_MPLAYER : if (n >= 0 && n < MAX_OTHER_SERVERS) { return &cls.mplayerServers[n]; } break; case AS_GLOBAL : if (n >= 0 && n < MAX_GLOBAL_SERVERS) { return &cls.globalServers[n]; } break; case AS_FAVORITES : if (n >= 0 && n < MAX_OTHER_SERVERS) { return &cls.favoriteServers[n]; } break; } return NULL; } /* ==================== LAN_CompareServers ==================== */ static int LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) { int res; serverInfo_t *server1, *server2; server1 = LAN_GetServerPtr(source, s1); server2 = LAN_GetServerPtr(source, s2); if (!server1 || !server2) { return 0; } res = 0; switch( sortKey ) { case SORT_HOST: res = Q_stricmp( server1->hostName, server2->hostName ); break; case SORT_MAP: res = Q_stricmp( server1->mapName, server2->mapName ); break; case SORT_CLIENTS: if (server1->clients < server2->clients) { res = -1; } else if (server1->clients > server2->clients) { res = 1; } else { res = 0; } break; case SORT_GAME: if (server1->gameType < server2->gameType) { res = -1; } else if (server1->gameType > server2->gameType) { res = 1; } else { res = 0; } break; case SORT_PING: if (server1->ping < server2->ping) { res = -1; } else if (server1->ping > server2->ping) { res = 1; } else { res = 0; } break; } if (sortDir) { if (res < 0) return 1; if (res > 0) return -1; return 0; } return res; } /* ==================== LAN_GetPingQueueCount ==================== */ static int LAN_GetPingQueueCount( void ) { return (CL_GetPingQueueCount()); } /* ==================== LAN_ClearPing ==================== */ static void LAN_ClearPing( int n ) { CL_ClearPing( n ); } /* ==================== LAN_GetPing ==================== */ static void LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) { CL_GetPing( n, buf, buflen, pingtime ); } /* ==================== LAN_GetPingInfo ==================== */ static void LAN_GetPingInfo( int n, char *buf, int buflen ) { CL_GetPingInfo( n, buf, buflen ); } /* ==================== LAN_MarkServerVisible ==================== */ static void LAN_MarkServerVisible(int source, int n, qboolean visible ) { if (n == -1) { int count = MAX_OTHER_SERVERS; serverInfo_t *server = NULL; switch (source) { case AS_LOCAL : server = &cls.localServers[0]; break; case AS_MPLAYER : server = &cls.mplayerServers[0]; break; case AS_GLOBAL : server = &cls.globalServers[0]; count = MAX_GLOBAL_SERVERS; break; case AS_FAVORITES : server = &cls.favoriteServers[0]; break; } if (server) { for (n = 0; n < count; n++) { server[n].visible = visible; } } } else { switch (source) { case AS_LOCAL : if (n >= 0 && n < MAX_OTHER_SERVERS) { cls.localServers[n].visible = visible; } break; case AS_MPLAYER : if (n >= 0 && n < MAX_OTHER_SERVERS) { cls.mplayerServers[n].visible = visible; } break; case AS_GLOBAL : if (n >= 0 && n < MAX_GLOBAL_SERVERS) { cls.globalServers[n].visible = visible; } break; case AS_FAVORITES : if (n >= 0 && n < MAX_OTHER_SERVERS) { cls.favoriteServers[n].visible = visible; } break; } } } /* ======================= LAN_ServerIsVisible ======================= */ static int LAN_ServerIsVisible(int source, int n ) { switch (source) { case AS_LOCAL : if (n >= 0 && n < MAX_OTHER_SERVERS) { return cls.localServers[n].visible; } break; case AS_MPLAYER : if (n >= 0 && n < MAX_OTHER_SERVERS) { return cls.mplayerServers[n].visible; } break; case AS_GLOBAL : if (n >= 0 && n < MAX_GLOBAL_SERVERS) { return cls.globalServers[n].visible; } break; case AS_FAVORITES : if (n >= 0 && n < MAX_OTHER_SERVERS) { return cls.favoriteServers[n].visible; } break; } return qfalse; } /* ======================= LAN_UpdateVisiblePings ======================= */ qboolean LAN_UpdateVisiblePings(int source ) { return CL_UpdateVisiblePings_f(source); } /* ==================== LAN_GetServerStatus ==================== */ int LAN_GetServerStatus( char *serverAddress, char *serverStatus, int maxLen ) { return CL_ServerStatus( serverAddress, serverStatus, maxLen ); } /* ==================== CL_GetGlConfig ==================== */ static void CL_GetGlconfig( glconfig_t *config ) { *config = cls.glconfig; } /* ==================== GetClipboardData ==================== */ static void GetClipboardData( char *buf, int buflen ) { char *cbd; cbd = Sys_GetClipboardData(); if ( !cbd ) { *buf = 0; return; } Q_strncpyz( buf, cbd, buflen ); Z_Free( cbd ); } /* ==================== Key_KeynumToStringBuf ==================== */ static void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { Q_strncpyz( buf, Key_KeynumToString( keynum ), buflen ); } /* ==================== Key_GetBindingBuf ==================== */ static void Key_GetBindingBuf( int keynum, char *buf, int buflen ) { char *value; value = Key_GetBinding( keynum ); if ( value ) { Q_strncpyz( buf, value, buflen ); } else { *buf = 0; } } /* ==================== Key_GetCatcher ==================== */ int Key_GetCatcher( void ) { return cls.keyCatchers; } /* ==================== Ket_SetCatcher ==================== */ void Key_SetCatcher( int catcher ) { cls.keyCatchers = catcher; } /* ==================== CLUI_GetCDKey ==================== */ static void CLUI_GetCDKey( char *buf, int buflen ) { cvar_t *fs; fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) { Com_Memcpy( buf, &cl_cdkey[16], 16); buf[16] = 0; } else { Com_Memcpy( buf, cl_cdkey, 16); buf[16] = 0; } } /* ==================== CLUI_SetCDKey ==================== */ static void CLUI_SetCDKey( char *buf ) { cvar_t *fs; fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) { Com_Memcpy( &cl_cdkey[16], buf, 16 ); cl_cdkey[32] = 0; // set the flag so the fle will be written at the next opportunity cvar_modifiedFlags |= CVAR_ARCHIVE; } else { Com_Memcpy( cl_cdkey, buf, 16 ); // set the flag so the fle will be written at the next opportunity cvar_modifiedFlags |= CVAR_ARCHIVE; } } /* ==================== GetConfigString ==================== */ static int GetConfigString(int index, char *buf, int size) { int offset; if (index < 0 || index >= MAX_CONFIGSTRINGS) return qfalse; offset = cl.gameState.stringOffsets[index]; if (!offset) { if( size ) { buf[0] = 0; } return qfalse; } Q_strncpyz( buf, cl.gameState.stringData+offset, size); return qtrue; } /* ==================== FloatAsInt ==================== */ static int FloatAsInt( float f ) { int temp; *(float *)&temp = f; return temp; } void *VM_ArgPtr( int intValue ); #define VMA(x) VM_ArgPtr(args[x]) #define VMF(x) ((float *)args)[x] /* ==================== CL_UISystemCalls The ui module is making a system call ==================== */ int CL_UISystemCalls( int *args ) { switch( args[0] ) { case UI_ERROR: Com_Error( ERR_DROP, "%s", VMA(1) ); return 0; case UI_PRINT: Com_Printf( "%s", VMA(1) ); return 0; case UI_MILLISECONDS: return Sys_Milliseconds(); case UI_CVAR_REGISTER: Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); return 0; case UI_CVAR_UPDATE: Cvar_Update( VMA(1) ); return 0; case UI_CVAR_SET: Cvar_Set( VMA(1), VMA(2) ); return 0; case UI_CVAR_VARIABLEVALUE: return FloatAsInt( Cvar_VariableValue( VMA(1) ) ); case UI_CVAR_VARIABLESTRINGBUFFER: Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); return 0; case UI_CVAR_SETVALUE: Cvar_SetValue( VMA(1), VMF(2) ); return 0; case UI_CVAR_RESET: Cvar_Reset( VMA(1) ); return 0; case UI_CVAR_CREATE: Cvar_Get( VMA(1), VMA(2), args[3] ); return 0; case UI_CVAR_INFOSTRINGBUFFER: Cvar_InfoStringBuffer( args[1], VMA(2), args[3] ); return 0; case UI_ARGC: return Cmd_Argc(); case UI_ARGV: Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); return 0; case UI_CMD_EXECUTETEXT: Cbuf_ExecuteText( args[1], VMA(2) ); return 0; case UI_FS_FOPENFILE: return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); case UI_FS_READ: FS_Read2( VMA(1), args[2], args[3] ); return 0; case UI_FS_WRITE: FS_Write( VMA(1), args[2], args[3] ); return 0; case UI_FS_FCLOSEFILE: FS_FCloseFile( args[1] ); return 0; case UI_FS_GETFILELIST: return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] ); case UI_FS_SEEK: return FS_Seek( args[1], args[2], args[3] ); case UI_R_REGISTERMODEL: return re.RegisterModel( VMA(1) ); case UI_R_REGISTERSKIN: return re.RegisterSkin( VMA(1) ); case UI_R_REGISTERSHADERNOMIP: return re.RegisterShaderNoMip( VMA(1) ); case UI_R_CLEARSCENE: re.ClearScene(); return 0; case UI_R_ADDREFENTITYTOSCENE: re.AddRefEntityToScene( VMA(1) ); return 0; case UI_R_ADDPOLYTOSCENE: re.AddPolyToScene( args[1], args[2], VMA(3), 1 ); return 0; case UI_R_ADDLIGHTTOSCENE: re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); return 0; case UI_R_RENDERSCENE: re.RenderScene( VMA(1) ); return 0; case UI_R_SETCOLOR: re.SetColor( VMA(1) ); return 0; case UI_R_DRAWSTRETCHPIC: re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); return 0; case UI_R_MODELBOUNDS: re.ModelBounds( args[1], VMA(2), VMA(3) ); return 0; case UI_UPDATESCREEN: SCR_UpdateScreen(); return 0; case UI_CM_LERPTAG: re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) ); return 0; case UI_S_REGISTERSOUND: return S_RegisterSound( VMA(1), args[2] ); case UI_S_STARTLOCALSOUND: S_StartLocalSound( args[1], args[2] ); return 0; case UI_KEY_KEYNUMTOSTRINGBUF: Key_KeynumToStringBuf( args[1], VMA(2), args[3] ); return 0; case UI_KEY_GETBINDINGBUF: Key_GetBindingBuf( args[1], VMA(2), args[3] ); return 0; case UI_KEY_SETBINDING: Key_SetBinding( args[1], VMA(2) ); return 0; case UI_KEY_ISDOWN: return Key_IsDown( args[1] ); case UI_KEY_GETOVERSTRIKEMODE: return Key_GetOverstrikeMode(); case UI_KEY_SETOVERSTRIKEMODE: Key_SetOverstrikeMode( args[1] ); return 0; case UI_KEY_CLEARSTATES: Key_ClearStates(); return 0; case UI_KEY_GETCATCHER: return Key_GetCatcher(); case UI_KEY_SETCATCHER: Key_SetCatcher( args[1] ); return 0; case UI_GETCLIPBOARDDATA: GetClipboardData( VMA(1), args[2] ); return 0; case UI_GETCLIENTSTATE: GetClientState( VMA(1) ); return 0; case UI_GETGLCONFIG: CL_GetGlconfig( VMA(1) ); return 0; case UI_GETCONFIGSTRING: return GetConfigString( args[1], VMA(2), args[3] ); case UI_LAN_LOADCACHEDSERVERS: LAN_LoadCachedServers(); return 0; case UI_LAN_SAVECACHEDSERVERS: LAN_SaveServersToCache(); return 0; case UI_LAN_ADDSERVER: return LAN_AddServer(args[1], VMA(2), VMA(3)); case UI_LAN_REMOVESERVER: LAN_RemoveServer(args[1], VMA(2)); return 0; case UI_LAN_GETPINGQUEUECOUNT: return LAN_GetPingQueueCount(); case UI_LAN_CLEARPING: LAN_ClearPing( args[1] ); return 0; case UI_LAN_GETPING: LAN_GetPing( args[1], VMA(2), args[3], VMA(4) ); return 0; case UI_LAN_GETPINGINFO: LAN_GetPingInfo( args[1], VMA(2), args[3] ); return 0; case UI_LAN_GETSERVERCOUNT: return LAN_GetServerCount(args[1]); case UI_LAN_GETSERVERADDRESSSTRING: LAN_GetServerAddressString( args[1], args[2], VMA(3), args[4] ); return 0; case UI_LAN_GETSERVERINFO: LAN_GetServerInfo( args[1], args[2], VMA(3), args[4] ); return 0; case UI_LAN_GETSERVERPING: return LAN_GetServerPing( args[1], args[2] ); case UI_LAN_MARKSERVERVISIBLE: LAN_MarkServerVisible( args[1], args[2], args[3] ); return 0; case UI_LAN_SERVERISVISIBLE: return LAN_ServerIsVisible( args[1], args[2] ); case UI_LAN_UPDATEVISIBLEPINGS: return LAN_UpdateVisiblePings( args[1] ); case UI_LAN_RESETPINGS: LAN_ResetPings( args[1] ); return 0; case UI_LAN_SERVERSTATUS: return LAN_GetServerStatus( VMA(1), VMA(2), args[3] ); case UI_LAN_COMPARESERVERS: return LAN_CompareServers( args[1], args[2], args[3], args[4], args[5] ); case UI_MEMORY_REMAINING: return Hunk_MemoryRemaining(); case UI_GET_CDKEY: CLUI_GetCDKey( VMA(1), args[2] ); return 0; case UI_SET_CDKEY: CLUI_SetCDKey( VMA(1) ); return 0; case UI_SET_PBCLSTATUS: return 0; case UI_R_REGISTERFONT: re.RegisterFont( VMA(1), args[2], VMA(3)); return 0; case UI_MEMSET: Com_Memset( VMA(1), args[2], args[3] ); return 0; case UI_MEMCPY: Com_Memcpy( VMA(1), VMA(2), args[3] ); return 0; case UI_STRNCPY: return (int)strncpy( VMA(1), VMA(2), args[3] ); case UI_SIN: return FloatAsInt( sin( VMF(1) ) ); case UI_COS: return FloatAsInt( cos( VMF(1) ) ); case UI_ATAN2: return FloatAsInt( atan2( VMF(1), VMF(2) ) ); case UI_SQRT: return FloatAsInt( sqrt( VMF(1) ) ); case UI_FLOOR: return FloatAsInt( floor( VMF(1) ) ); case UI_CEIL: return FloatAsInt( ceil( VMF(1) ) ); case UI_PC_ADD_GLOBAL_DEFINE: return botlib_export->PC_AddGlobalDefine( VMA(1) ); case UI_PC_LOAD_SOURCE: return botlib_export->PC_LoadSourceHandle( VMA(1) ); case UI_PC_FREE_SOURCE: return botlib_export->PC_FreeSourceHandle( args[1] ); case UI_PC_READ_TOKEN: return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) ); case UI_PC_SOURCE_FILE_AND_LINE: return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) ); case UI_S_STOPBACKGROUNDTRACK: S_StopBackgroundTrack(); return 0; case UI_S_STARTBACKGROUNDTRACK: S_StartBackgroundTrack( VMA(1), VMA(2)); return 0; case UI_REAL_TIME: return Com_RealTime( VMA(1) ); case UI_CIN_PLAYCINEMATIC: Com_DPrintf("UI_CIN_PlayCinematic\n"); return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]); case UI_CIN_STOPCINEMATIC: return CIN_StopCinematic(args[1]); case UI_CIN_RUNCINEMATIC: return CIN_RunCinematic(args[1]); case UI_CIN_DRAWCINEMATIC: CIN_DrawCinematic(args[1]); return 0; case UI_CIN_SETEXTENTS: CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); return 0; case UI_R_REMAP_SHADER: re.RemapShader( VMA(1), VMA(2), VMA(3) ); return 0; case UI_VERIFY_CDKEY: return CL_CDKeyValidate(VMA(1), VMA(2)); default: Com_Error( ERR_DROP, "Bad UI system trap: %i", args[0] ); } return 0; } /* ==================== CL_ShutdownUI ==================== */ void CL_ShutdownUI( void ) { cls.keyCatchers &= ~KEYCATCH_UI; cls.uiStarted = qfalse; if ( !uivm ) { return; } VM_Call( uivm, UI_SHUTDOWN ); VM_Free( uivm ); uivm = NULL; } /* ==================== CL_InitUI ==================== */ #define UI_OLD_API_VERSION 4 void CL_InitUI( void ) { int v; vmInterpret_t interpret; // load the dll or bytecode if ( cl_connectedToPureServer != 0 ) { // if sv_pure is set we only allow qvms to be loaded interpret = VMI_COMPILED; } else { interpret = Cvar_VariableValue( "vm_ui" ); } uivm = VM_Create( "ui", CL_UISystemCalls, interpret ); if ( !uivm ) { Com_Error( ERR_FATAL, "VM_Create on UI failed" ); } // sanity check v = VM_Call( uivm, UI_GETAPIVERSION ); if (v == UI_OLD_API_VERSION) { // Com_Printf(S_COLOR_YELLOW "WARNING: loading old Quake III Arena User Interface version %d\n", v ); // init for this gamestate VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE)); } else if (v != UI_API_VERSION) { Com_Error( ERR_DROP, "User Interface is version %d, expected %d", v, UI_API_VERSION ); cls.uiStarted = qfalse; } else { // init for this gamestate VM_Call( uivm, UI_INIT, (cls.state >= CA_AUTHORIZING && cls.state < CA_ACTIVE) ); } } qboolean UI_usesUniqueCDKey() { if (uivm) { return (VM_Call( uivm, UI_HASUNIQUECDKEY) == qtrue); } else { return qfalse; } } /* ==================== UI_GameCommand See if the current console command is claimed by the ui ==================== */ qboolean UI_GameCommand( void ) { if ( !uivm ) { return qfalse; } return VM_Call( uivm, UI_CONSOLE_COMMAND, cls.realtime ); } ================================================ FILE: code/client/client.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // client.h -- primary header for client #include "../game/q_shared.h" #include "../qcommon/qcommon.h" #include "../renderer/tr_public.h" #include "../ui/ui_public.h" #include "keys.h" #include "snd_public.h" #include "../cgame/cg_public.h" #include "../game/bg_public.h" #define RETRANSMIT_TIMEOUT 3000 // time between connection packet retransmits // snapshots are a view of the server at a given time typedef struct { qboolean valid; // cleared if delta parsing was invalid int snapFlags; // rate delayed and dropped commands int serverTime; // server time the message is valid for (in msec) int messageNum; // copied from netchan->incoming_sequence int deltaNum; // messageNum the delta is from int ping; // time from when cmdNum-1 was sent to time packet was reeceived byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits int cmdNum; // the next cmdNum the server is expecting playerState_t ps; // complete information about the current player at this time int numEntities; // all of the entities that need to be presented int parseEntitiesNum; // at the time of this snapshot int serverCommandNum; // execute all commands up to this before // making the snapshot current } clSnapshot_t; /* ============================================================================= the clientActive_t structure is wiped completely at every new gamestate_t, potentially several times during an established connection ============================================================================= */ typedef struct { int p_cmdNumber; // cl.cmdNumber when packet was sent int p_serverTime; // usercmd->serverTime when packet was sent int p_realtime; // cls.realtime when packet was sent } outPacket_t; // the parseEntities array must be large enough to hold PACKET_BACKUP frames of // entities, so that when a delta compressed message arives from the server // it can be un-deltad from the original #define MAX_PARSE_ENTITIES 2048 extern int g_console_field_width; typedef struct { int timeoutcount; // it requres several frames in a timeout condition // to disconnect, preventing debugging breaks from // causing immediate disconnects on continue clSnapshot_t snap; // latest received from server int serverTime; // may be paused during play int oldServerTime; // to prevent time from flowing bakcwards int oldFrameServerTime; // to check tournament restarts int serverTimeDelta; // cl.serverTime = cls.realtime + cl.serverTimeDelta // this value changes as net lag varies qboolean extrapolatedSnapshot; // set if any cgame frame has been forced to extrapolate // cleared when CL_AdjustTimeDelta looks at it qboolean newSnapshots; // set on parse of any valid packet gameState_t gameState; // configstrings char mapname[MAX_QPATH]; // extracted from CS_SERVERINFO int parseEntitiesNum; // index (not anded off) into cl_parse_entities[] int mouseDx[2], mouseDy[2]; // added to by mouse events int mouseIndex; int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events // cgame communicates a few values to the client system int cgameUserCmdValue; // current weapon to add to usercmd_t float cgameSensitivity; // cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last // properly generated command usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds int cmdNumber; // incremented each frame, because multiple // frames may need to be packed into a single packet outPacket_t outPackets[PACKET_BACKUP]; // information about each packet we have sent out // the client maintains its own idea of view angles, which are // sent to the server each frame. It is cleared to 0 upon entering each level. // the server sends a delta each frame which is added to the locally // tracked view angles to account for standing on rotating objects, // and teleport direction changes vec3_t viewangles; int serverId; // included in each client message so the server // can tell if it is for a prior map_restart // big stuff at end of structure so most offsets are 15 bits or less clSnapshot_t snapshots[PACKET_BACKUP]; entityState_t entityBaselines[MAX_GENTITIES]; // for delta compression when not in previous frame entityState_t parseEntities[MAX_PARSE_ENTITIES]; } clientActive_t; extern clientActive_t cl; /* ============================================================================= the clientConnection_t structure is wiped when disconnecting from a server, either to go to a full screen console, play a demo, or connect to a different server A connection can be to either a server through the network layer or a demo through a file. ============================================================================= */ typedef struct { int clientNum; int lastPacketSentTime; // for retransmits during connection int lastPacketTime; // for timeouts netadr_t serverAddress; int connectTime; // for connection retransmits int connectPacketCount; // for display on connection dialog char serverMessage[MAX_STRING_TOKENS]; // for display on connection dialog int challenge; // from the server to use for connecting int checksumFeed; // from the server for checksum calculations // these are our reliable messages that go to the server int reliableSequence; int reliableAcknowledge; // the last one the server has executed char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; // server message (unreliable) and command (reliable) sequence // numbers are NOT cleared at level changes, but continue to // increase as long as the connection is valid // message sequence is used by both the network layer and the // delta compression layer int serverMessageSequence; // reliable messages received from server int serverCommandSequence; int lastExecutedServerCommand; // last server command grabbed or executed with CL_GetServerCommand char serverCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; // file transfer from server fileHandle_t download; char downloadTempName[MAX_OSPATH]; char downloadName[MAX_OSPATH]; int downloadNumber; int downloadBlock; // block we are waiting for int downloadCount; // how many bytes we got int downloadSize; // how many bytes we got char downloadList[MAX_INFO_STRING]; // list of paks we need to download qboolean downloadRestart; // if true, we need to do another FS_Restart because we downloaded a pak // demo information char demoName[MAX_QPATH]; qboolean spDemoRecording; qboolean demorecording; qboolean demoplaying; qboolean demowaiting; // don't record until a non-delta message is received qboolean firstDemoFrameSkipped; fileHandle_t demofile; int timeDemoFrames; // counter of rendered frames int timeDemoStart; // cls.realtime before first frame int timeDemoBaseTime; // each frame will be at this time + frameNum * 50 // big stuff at end of structure so most offsets are 15 bits or less netchan_t netchan; } clientConnection_t; extern clientConnection_t clc; /* ================================================================== the clientStatic_t structure is never wiped, and is used even when no client connection is active at all ================================================================== */ typedef struct { netadr_t adr; int start; int time; char info[MAX_INFO_STRING]; } ping_t; typedef struct { netadr_t adr; char hostName[MAX_NAME_LENGTH]; char mapName[MAX_NAME_LENGTH]; char game[MAX_NAME_LENGTH]; int netType; int gameType; int clients; int maxClients; int minPing; int maxPing; int ping; qboolean visible; int punkbuster; } serverInfo_t; typedef struct { byte ip[4]; unsigned short port; } serverAddress_t; typedef struct { connstate_t state; // connection status int keyCatchers; // bit flags qboolean cddialog; // bring up the cd needed dialog next frame char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect) // when the server clears the hunk, all of these must be restarted qboolean rendererStarted; qboolean soundStarted; qboolean soundRegistered; qboolean uiStarted; qboolean cgameStarted; int framecount; int frametime; // msec since last frame int realtime; // ignores pause int realFrametime; // ignoring pause, so console always works int numlocalservers; serverInfo_t localServers[MAX_OTHER_SERVERS]; int numglobalservers; serverInfo_t globalServers[MAX_GLOBAL_SERVERS]; // additional global servers int numGlobalServerAddresses; serverAddress_t globalServerAddresses[MAX_GLOBAL_SERVERS]; int numfavoriteservers; serverInfo_t favoriteServers[MAX_OTHER_SERVERS]; int nummplayerservers; serverInfo_t mplayerServers[MAX_OTHER_SERVERS]; int pingUpdateSource; // source currently pinging or updating int masterNum; // update server info netadr_t updateServer; char updateChallenge[MAX_TOKEN_CHARS]; char updateInfoString[MAX_INFO_STRING]; netadr_t authorizeServer; // rendering info glconfig_t glconfig; qhandle_t charSetShader; qhandle_t whiteShader; qhandle_t consoleShader; } clientStatic_t; extern clientStatic_t cls; //============================================================================= extern vm_t *cgvm; // interface to cgame dll or vm extern vm_t *uivm; // interface to ui dll or vm extern refexport_t re; // interface to refresh .dll // // cvars // extern cvar_t *cl_nodelta; extern cvar_t *cl_debugMove; extern cvar_t *cl_noprint; extern cvar_t *cl_timegraph; extern cvar_t *cl_maxpackets; extern cvar_t *cl_packetdup; extern cvar_t *cl_shownet; extern cvar_t *cl_showSend; extern cvar_t *cl_timeNudge; extern cvar_t *cl_showTimeDelta; extern cvar_t *cl_freezeDemo; extern cvar_t *cl_yawspeed; extern cvar_t *cl_pitchspeed; extern cvar_t *cl_run; extern cvar_t *cl_anglespeedkey; extern cvar_t *cl_sensitivity; extern cvar_t *cl_freelook; extern cvar_t *cl_mouseAccel; extern cvar_t *cl_showMouseRate; extern cvar_t *m_pitch; extern cvar_t *m_yaw; extern cvar_t *m_forward; extern cvar_t *m_side; extern cvar_t *m_filter; extern cvar_t *cl_timedemo; extern cvar_t *cl_activeAction; extern cvar_t *cl_allowDownload; extern cvar_t *cl_conXOffset; extern cvar_t *cl_inGameVideo; //================================================= // // cl_main // void CL_Init (void); void CL_FlushMemory(void); void CL_ShutdownAll(void); void CL_AddReliableCommand( const char *cmd ); void CL_StartHunkUsers( void ); void CL_Disconnect_f (void); void CL_GetChallengePacket (void); void CL_Vid_Restart_f( void ); void CL_Snd_Restart_f (void); void CL_StartDemoLoop( void ); void CL_NextDemo( void ); void CL_ReadDemoMessage( void ); void CL_InitDownloads(void); void CL_NextDownload(void); void CL_GetPing( int n, char *buf, int buflen, int *pingtime ); void CL_GetPingInfo( int n, char *buf, int buflen ); void CL_ClearPing( int n ); int CL_GetPingQueueCount( void ); void CL_ShutdownRef( void ); void CL_InitRef( void ); qboolean CL_CDKeyValidate( const char *key, const char *checksum ); int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ); // // cl_input // typedef struct { int down[2]; // key nums holding it down unsigned downtime; // msec timestamp unsigned msec; // msec down this frame if both a down and up happened qboolean active; // current state qboolean wasPressed; // set when down, not cleared when up } kbutton_t; extern kbutton_t in_mlook, in_klook; extern kbutton_t in_strafe; extern kbutton_t in_speed; void CL_InitInput (void); void CL_SendCmd (void); void CL_ClearState (void); void CL_ReadPackets (void); void CL_WritePacket( void ); void IN_CenterView (void); void CL_VerifyCode( void ); float CL_KeyState (kbutton_t *key); char *Key_KeynumToString (int keynum); // // cl_parse.c // extern int cl_connectedToPureServer; void CL_SystemInfoChanged( void ); void CL_ParseServerMessage( msg_t *msg ); //==================================================================== void CL_ServerInfoPacket( netadr_t from, msg_t *msg ); void CL_LocalServers_f( void ); void CL_GlobalServers_f( void ); void CL_FavoriteServers_f( void ); void CL_Ping_f( void ); qboolean CL_UpdateVisiblePings_f( int source ); // // console // void Con_DrawCharacter (int cx, int line, int num); void Con_CheckResize (void); void Con_Init (void); void Con_Clear_f (void); void Con_ToggleConsole_f (void); void Con_DrawNotify (void); void Con_ClearNotify (void); void Con_RunConsole (void); void Con_DrawConsole (void); void Con_PageUp( void ); void Con_PageDown( void ); void Con_Top( void ); void Con_Bottom( void ); void Con_Close( void ); // // cl_scrn.c // void SCR_Init (void); void SCR_UpdateScreen (void); void SCR_DebugGraph (float value, int color); int SCR_GetBigStringWidth( const char *str ); // returns in virtual 640x480 coordinates void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ); void SCR_FillRect( float x, float y, float width, float height, const float *color ); void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ); void SCR_DrawBigString( int x, int y, const char *s, float alpha ); // draws a string with embedded color control characters with fade void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); // ignores embedded color control characters void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor ); void SCR_DrawSmallChar( int x, int y, int ch ); // // cl_cin.c // void CL_PlayCinematic_f( void ); void SCR_DrawCinematic (void); void SCR_RunCinematic (void); void SCR_StopCinematic (void); int CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); e_status CIN_StopCinematic(int handle); e_status CIN_RunCinematic (int handle); void CIN_DrawCinematic (int handle); void CIN_SetExtents (int handle, int x, int y, int w, int h); void CIN_SetLooping (int handle, qboolean loop); void CIN_UploadCinematic(int handle); void CIN_CloseAllVideos(void); // // cl_cgame.c // void CL_InitCGame( void ); void CL_ShutdownCGame( void ); qboolean CL_GameCommand( void ); void CL_CGameRendering( stereoFrame_t stereo ); void CL_SetCGameTime( void ); void CL_FirstSnapshot( void ); void CL_ShaderStateChanged(void); // // cl_ui.c // void CL_InitUI( void ); void CL_ShutdownUI( void ); int Key_GetCatcher( void ); void Key_SetCatcher( int catcher ); void LAN_LoadCachedServers(); void LAN_SaveServersToCache(); // // cl_net_chan.c // void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg); //int length, const byte *data ); void CL_Netchan_TransmitNextFragment( netchan_t *chan ); qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ); ================================================ FILE: code/client/keys.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "../ui/keycodes.h" #define MAX_KEYS 256 typedef struct { qboolean down; int repeats; // if > 1, it is autorepeating char *binding; } qkey_t; extern qboolean key_overstrikeMode; extern qkey_t keys[MAX_KEYS]; // NOTE TTimo the declaration of field_t and Field_Clear is now in qcommon/qcommon.h void Field_KeyDownEvent( field_t *edit, int key ); void Field_CharEvent( field_t *edit, int ch ); void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor ); void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor ); #define COMMAND_HISTORY 32 extern field_t historyEditLines[COMMAND_HISTORY]; extern field_t g_consoleField; extern field_t chatField; extern qboolean anykeydown; extern qboolean chat_team; extern int chat_playerNum; void Key_WriteBindings( fileHandle_t f ); void Key_SetBinding( int keynum, const char *binding ); char *Key_GetBinding( int keynum ); qboolean Key_IsDown( int keynum ); qboolean Key_GetOverstrikeMode( void ); void Key_SetOverstrikeMode( qboolean state ); void Key_ClearStates( void ); int Key_GetKey(const char *binding); ================================================ FILE: code/client/snd_adpcm.c ================================================ /*********************************************************** Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The Netherlands. All Rights Reserved Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the names of Stichting Mathematisch Centrum or CWI not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ******************************************************************/ /* ** Intel/DVI ADPCM coder/decoder. ** ** The algorithm for this coder was taken from the IMA Compatability Project ** proceedings, Vol 2, Number 2; May 1992. ** ** Version 1.2, 18-Dec-92. */ #include "snd_local.h" /* Intel ADPCM step variation table */ static int indexTable[16] = { -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8, }; static int stepsizeTable[89] = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 }; void S_AdpcmEncode( short indata[], char outdata[], int len, struct adpcm_state *state ) { short *inp; /* Input buffer pointer */ signed char *outp; /* output buffer pointer */ int val; /* Current input sample value */ int sign; /* Current adpcm sign bit */ int delta; /* Current adpcm output value */ int diff; /* Difference between val and sample */ int step; /* Stepsize */ int valpred; /* Predicted output value */ int vpdiff; /* Current change to valpred */ int index; /* Current step change index */ int outputbuffer; /* place to keep previous 4-bit value */ int bufferstep; /* toggle between outputbuffer/output */ outp = (signed char *)outdata; inp = indata; valpred = state->sample; index = state->index; step = stepsizeTable[index]; outputbuffer = 0; // quiet a compiler warning bufferstep = 1; for ( ; len > 0 ; len-- ) { val = *inp++; /* Step 1 - compute difference with previous value */ diff = val - valpred; sign = (diff < 0) ? 8 : 0; if ( sign ) diff = (-diff); /* Step 2 - Divide and clamp */ /* Note: ** This code *approximately* computes: ** delta = diff*4/step; ** vpdiff = (delta+0.5)*step/4; ** but in shift step bits are dropped. The net result of this is ** that even if you have fast mul/div hardware you cannot put it to ** good use since the fixup would be too expensive. */ delta = 0; vpdiff = (step >> 3); if ( diff >= step ) { delta = 4; diff -= step; vpdiff += step; } step >>= 1; if ( diff >= step ) { delta |= 2; diff -= step; vpdiff += step; } step >>= 1; if ( diff >= step ) { delta |= 1; vpdiff += step; } /* Step 3 - Update previous value */ if ( sign ) valpred -= vpdiff; else valpred += vpdiff; /* Step 4 - Clamp previous value to 16 bits */ if ( valpred > 32767 ) valpred = 32767; else if ( valpred < -32768 ) valpred = -32768; /* Step 5 - Assemble value, update index and step values */ delta |= sign; index += indexTable[delta]; if ( index < 0 ) index = 0; if ( index > 88 ) index = 88; step = stepsizeTable[index]; /* Step 6 - Output value */ if ( bufferstep ) { outputbuffer = (delta << 4) & 0xf0; } else { *outp++ = (delta & 0x0f) | outputbuffer; } bufferstep = !bufferstep; } /* Output last step, if needed */ if ( !bufferstep ) *outp++ = outputbuffer; state->sample = valpred; state->index = index; } /* static */ void S_AdpcmDecode( const char indata[], short *outdata, int len, struct adpcm_state *state ) { signed char *inp; /* Input buffer pointer */ int outp; /* output buffer pointer */ int sign; /* Current adpcm sign bit */ int delta; /* Current adpcm output value */ int step; /* Stepsize */ int valpred; /* Predicted value */ int vpdiff; /* Current change to valpred */ int index; /* Current step change index */ int inputbuffer; /* place to keep next 4-bit value */ int bufferstep; /* toggle between inputbuffer/input */ outp = 0; inp = (signed char *)indata; valpred = state->sample; index = state->index; step = stepsizeTable[index]; bufferstep = 0; inputbuffer = 0; // quiet a compiler warning for ( ; len > 0 ; len-- ) { /* Step 1 - get the delta value */ if ( bufferstep ) { delta = inputbuffer & 0xf; } else { inputbuffer = *inp++; delta = (inputbuffer >> 4) & 0xf; } bufferstep = !bufferstep; /* Step 2 - Find new index value (for later) */ index += indexTable[delta]; if ( index < 0 ) index = 0; if ( index > 88 ) index = 88; /* Step 3 - Separate sign and magnitude */ sign = delta & 8; delta = delta & 7; /* Step 4 - Compute difference and new predicted value */ /* ** Computes 'vpdiff = (delta+0.5)*step/4', but see comment ** in adpcm_coder. */ vpdiff = step >> 3; if ( delta & 4 ) vpdiff += step; if ( delta & 2 ) vpdiff += step>>1; if ( delta & 1 ) vpdiff += step>>2; if ( sign ) valpred -= vpdiff; else valpred += vpdiff; /* Step 5 - clamp output value */ if ( valpred > 32767 ) valpred = 32767; else if ( valpred < -32768 ) valpred = -32768; /* Step 6 - Update step value */ step = stepsizeTable[index]; /* Step 7 - Output value */ outdata[outp] = valpred; outp++; } state->sample = valpred; state->index = index; } /* ==================== S_AdpcmMemoryNeeded Returns the amount of memory (in bytes) needed to store the samples in out internal adpcm format ==================== */ int S_AdpcmMemoryNeeded( const wavinfo_t *info ) { float scale; int scaledSampleCount; int sampleMemory; int blockCount; int headerMemory; // determine scale to convert from input sampling rate to desired sampling rate scale = (float)info->rate / dma.speed; // calc number of samples at playback sampling rate scaledSampleCount = info->samples / scale; // calc memory need to store those samples using ADPCM at 4 bits per sample sampleMemory = scaledSampleCount / 2; // calc number of sample blocks needed of PAINTBUFFER_SIZE blockCount = scaledSampleCount / PAINTBUFFER_SIZE; if( scaledSampleCount % PAINTBUFFER_SIZE ) { blockCount++; } // calc memory needed to store the block headers headerMemory = blockCount * sizeof(adpcm_state_t); return sampleMemory + headerMemory; } /* ==================== S_AdpcmGetSamples ==================== */ void S_AdpcmGetSamples(sndBuffer *chunk, short *to) { adpcm_state_t state; byte *out; // get the starting state from the block header state.index = chunk->adpcm.index; state.sample = chunk->adpcm.sample; out = (byte *)chunk->sndChunk; // get samples S_AdpcmDecode( out, to, SND_CHUNK_SIZE_BYTE*2, &state ); } /* ==================== S_AdpcmEncodeSound ==================== */ void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ) { adpcm_state_t state; int inOffset; int count; int n; sndBuffer *newchunk, *chunk; byte *out; inOffset = 0; count = sfx->soundLength; state.index = 0; state.sample = samples[0]; chunk = NULL; while( count ) { n = count; if( n > SND_CHUNK_SIZE_BYTE*2 ) { n = SND_CHUNK_SIZE_BYTE*2; } newchunk = SND_malloc(); if (sfx->soundData == NULL) { sfx->soundData = newchunk; } else { chunk->next = newchunk; } chunk = newchunk; // output the header chunk->adpcm.index = state.index; chunk->adpcm.sample = state.sample; out = (byte *)chunk->sndChunk; // encode the samples S_AdpcmEncode( samples + inOffset, out, n, &state ); inOffset += n; count -= n; } } ================================================ FILE: code/client/snd_dma.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: snd_dma.c * * desc: main control for any streaming sound output device * * $Archive: /MissionPack/code/client/snd_dma.c $ * *****************************************************************************/ #include "snd_local.h" #include "client.h" void S_Play_f(void); void S_SoundList_f(void); void S_Music_f(void); void S_Update_(); void S_StopAllSounds(void); void S_UpdateBackgroundTrack( void ); static fileHandle_t s_backgroundFile; static wavinfo_t s_backgroundInfo; //int s_nextWavChunk; static int s_backgroundSamples; static char s_backgroundLoop[MAX_QPATH]; //static char s_backgroundMusic[MAX_QPATH]; //TTimo: unused // ======================================================================= // Internal sound data & structures // ======================================================================= // only begin attenuating sound volumes when outside the FULLVOLUME range #define SOUND_FULLVOLUME 80 #define SOUND_ATTENUATE 0.0008f channel_t s_channels[MAX_CHANNELS]; channel_t loop_channels[MAX_CHANNELS]; int numLoopChannels; static int s_soundStarted; static qboolean s_soundMuted; dma_t dma; static int listener_number; static vec3_t listener_origin; static vec3_t listener_axis[3]; int s_soundtime; // sample PAIRS int s_paintedtime; // sample PAIRS // MAX_SFX may be larger than MAX_SOUNDS because // of custom player sounds #define MAX_SFX 4096 sfx_t s_knownSfx[MAX_SFX]; int s_numSfx = 0; #define LOOP_HASH 128 static sfx_t *sfxHash[LOOP_HASH]; cvar_t *s_volume; cvar_t *s_testsound; cvar_t *s_khz; cvar_t *s_show; cvar_t *s_mixahead; cvar_t *s_mixPreStep; cvar_t *s_musicVolume; cvar_t *s_separation; cvar_t *s_doppler; static loopSound_t loopSounds[MAX_GENTITIES]; static channel_t *freelist = NULL; int s_rawend; portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; // ==================================================================== // User-setable variables // ==================================================================== void S_SoundInfo_f(void) { Com_Printf("----- Sound Info -----\n" ); if (!s_soundStarted) { Com_Printf ("sound system not started\n"); } else { if ( s_soundMuted ) { Com_Printf ("sound system is muted\n"); } Com_Printf("%5d stereo\n", dma.channels - 1); Com_Printf("%5d samples\n", dma.samples); Com_Printf("%5d samplebits\n", dma.samplebits); Com_Printf("%5d submission_chunk\n", dma.submission_chunk); Com_Printf("%5d speed\n", dma.speed); Com_Printf("0x%x dma buffer\n", dma.buffer); if ( s_backgroundFile ) { Com_Printf("Background file: %s\n", s_backgroundLoop ); } else { Com_Printf("No background file.\n" ); } } Com_Printf("----------------------\n" ); } /* ================ S_Init ================ */ void S_Init( void ) { cvar_t *cv; qboolean r; Com_Printf("\n------- sound initialization -------\n"); s_volume = Cvar_Get ("s_volume", "0.8", CVAR_ARCHIVE); s_musicVolume = Cvar_Get ("s_musicvolume", "0.25", CVAR_ARCHIVE); s_separation = Cvar_Get ("s_separation", "0.5", CVAR_ARCHIVE); s_doppler = Cvar_Get ("s_doppler", "1", CVAR_ARCHIVE); s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE); s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE); s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT); s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT); cv = Cvar_Get ("s_initsound", "1", 0); if ( !cv->integer ) { Com_Printf ("not initializing.\n"); Com_Printf("------------------------------------\n"); return; } Cmd_AddCommand("play", S_Play_f); Cmd_AddCommand("music", S_Music_f); Cmd_AddCommand("s_list", S_SoundList_f); Cmd_AddCommand("s_info", S_SoundInfo_f); Cmd_AddCommand("s_stop", S_StopAllSounds); r = SNDDMA_Init(); Com_Printf("------------------------------------\n"); if ( r ) { s_soundStarted = 1; s_soundMuted = 1; // s_numSfx = 0; Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); s_soundtime = 0; s_paintedtime = 0; S_StopAllSounds (); S_SoundInfo_f(); } } void S_ChannelFree(channel_t *v) { v->thesfx = NULL; *(channel_t **)v = freelist; freelist = (channel_t*)v; } channel_t* S_ChannelMalloc() { channel_t *v; if (freelist == NULL) { return NULL; } v = freelist; freelist = *(channel_t **)freelist; v->allocTime = Com_Milliseconds(); return v; } void S_ChannelSetup() { channel_t *p, *q; // clear all the sounds so they don't Com_Memset( s_channels, 0, sizeof( s_channels ) ); p = s_channels;; q = p + MAX_CHANNELS; while (--q > p) { *(channel_t **)q = q-1; } *(channel_t **)q = NULL; freelist = p + MAX_CHANNELS - 1; Com_DPrintf("Channel memory manager started\n"); } // ======================================================================= // Shutdown sound engine // ======================================================================= void S_Shutdown( void ) { if ( !s_soundStarted ) { return; } SNDDMA_Shutdown(); s_soundStarted = 0; Cmd_RemoveCommand("play"); Cmd_RemoveCommand("music"); Cmd_RemoveCommand("stopsound"); Cmd_RemoveCommand("soundlist"); Cmd_RemoveCommand("soundinfo"); } // ======================================================================= // Load a sound // ======================================================================= /* ================ return a hash value for the sfx name ================ */ static long S_HashSFXName(const char *name) { int i; long hash; char letter; hash = 0; i = 0; while (name[i] != '\0') { letter = tolower(name[i]); if (letter =='.') break; // don't include extension if (letter =='\\') letter = '/'; // damn path names hash+=(long)(letter)*(i+119); i++; } hash &= (LOOP_HASH-1); return hash; } /* ================== S_FindName Will allocate a new sfx if it isn't found ================== */ static sfx_t *S_FindName( const char *name ) { int i; int hash; sfx_t *sfx; if (!name) { Com_Error (ERR_FATAL, "S_FindName: NULL\n"); } if (!name[0]) { Com_Error (ERR_FATAL, "S_FindName: empty name\n"); } if (strlen(name) >= MAX_QPATH) { Com_Error (ERR_FATAL, "Sound name too long: %s", name); } hash = S_HashSFXName(name); sfx = sfxHash[hash]; // see if already loaded while (sfx) { if (!Q_stricmp(sfx->soundName, name) ) { return sfx; } sfx = sfx->next; } // find a free sfx for (i=0 ; i < s_numSfx ; i++) { if (!s_knownSfx[i].soundName[0]) { break; } } if (i == s_numSfx) { if (s_numSfx == MAX_SFX) { Com_Error (ERR_FATAL, "S_FindName: out of sfx_t"); } s_numSfx++; } sfx = &s_knownSfx[i]; Com_Memset (sfx, 0, sizeof(*sfx)); strcpy (sfx->soundName, name); sfx->next = sfxHash[hash]; sfxHash[hash] = sfx; return sfx; } /* ================= S_DefaultSound ================= */ void S_DefaultSound( sfx_t *sfx ) { int i; sfx->soundLength = 512; sfx->soundData = SND_malloc(); sfx->soundData->next = NULL; for ( i = 0 ; i < sfx->soundLength ; i++ ) { sfx->soundData->sndChunk[i] = i; } } /* =================== S_DisableSounds Disables sounds until the next S_BeginRegistration. This is called when the hunk is cleared and the sounds are no longer valid. =================== */ void S_DisableSounds( void ) { S_StopAllSounds(); s_soundMuted = qtrue; } /* ===================== S_BeginRegistration ===================== */ void S_BeginRegistration( void ) { s_soundMuted = qfalse; // we can play again if (s_numSfx == 0) { SND_setup(); s_numSfx = 0; Com_Memset( s_knownSfx, 0, sizeof( s_knownSfx ) ); Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); S_RegisterSound("sound/feedback/hit.wav", qfalse); // changed to a sound in baseq3 } } /* ================== S_RegisterSound Creates a default buzz sound if the file can't be loaded ================== */ sfxHandle_t S_RegisterSound( const char *name, qboolean compressed ) { sfx_t *sfx; compressed = qfalse; if (!s_soundStarted) { return 0; } if ( strlen( name ) >= MAX_QPATH ) { Com_Printf( "Sound name exceeds MAX_QPATH\n" ); return 0; } sfx = S_FindName( name ); if ( sfx->soundData ) { if ( sfx->defaultSound ) { Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); return 0; } return sfx - s_knownSfx; } sfx->inMemory = qfalse; sfx->soundCompressed = compressed; S_memoryLoad(sfx); if ( sfx->defaultSound ) { Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); return 0; } return sfx - s_knownSfx; } void S_memoryLoad(sfx_t *sfx) { // load the sound file if ( !S_LoadSound ( sfx ) ) { // Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->soundName ); sfx->defaultSound = qtrue; } sfx->inMemory = qtrue; } //============================================================================= /* ================= S_SpatializeOrigin Used for spatializing s_channels ================= */ void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *right_vol) { vec_t dot; vec_t dist; vec_t lscale, rscale, scale; vec3_t source_vec; vec3_t vec; const float dist_mult = SOUND_ATTENUATE; // calculate stereo seperation and distance attenuation VectorSubtract(origin, listener_origin, source_vec); dist = VectorNormalize(source_vec); dist -= SOUND_FULLVOLUME; if (dist < 0) dist = 0; // close enough to be at full volume dist *= dist_mult; // different attenuation levels VectorRotate( source_vec, listener_axis, vec ); dot = -vec[1]; if (dma.channels == 1) { // no attenuation = no spatialization rscale = 1.0; lscale = 1.0; } else { rscale = 0.5 * (1.0 + dot); lscale = 0.5 * (1.0 - dot); //rscale = s_separation->value + ( 1.0 - s_separation->value ) * dot; //lscale = s_separation->value - ( 1.0 - s_separation->value ) * dot; if ( rscale < 0 ) { rscale = 0; } if ( lscale < 0 ) { lscale = 0; } } // add in distance effect scale = (1.0 - dist) * rscale; *right_vol = (master_vol * scale); if (*right_vol < 0) *right_vol = 0; scale = (1.0 - dist) * lscale; *left_vol = (master_vol * scale); if (*left_vol < 0) *left_vol = 0; } // ======================================================================= // Start a sound effect // ======================================================================= /* ==================== S_StartSound Validates the parms and ques the sound up if pos is NULL, the sound will be dynamically sourced from the entity Entchannel 0 will never override a playing sound ==================== */ void S_StartSound(vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) { channel_t *ch; sfx_t *sfx; int i, oldest, chosen, time; int inplay, allowed; if ( !s_soundStarted || s_soundMuted ) { return; } if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) { Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum ); } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { Com_Printf( S_COLOR_YELLOW, "S_StartSound: handle %i out of range\n", sfxHandle ); return; } sfx = &s_knownSfx[ sfxHandle ]; if (sfx->inMemory == qfalse) { S_memoryLoad(sfx); } if ( s_show->integer == 1 ) { Com_Printf( "%i : %s\n", s_paintedtime, sfx->soundName ); } time = Com_Milliseconds(); // Com_Printf("playing %s\n", sfx->soundName); // pick a channel to play on allowed = 4; if (entityNum == listener_number) { allowed = 8; } ch = s_channels; inplay = 0; for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { if (ch[i].entnum == entityNum && ch[i].thesfx == sfx) { if (time - ch[i].allocTime < 50) { // if (Cvar_VariableValue( "cg_showmiss" )) { // Com_Printf("double sound start\n"); // } return; } inplay++; } } if (inplay>allowed) { return; } sfx->lastTimeUsed = time; ch = S_ChannelMalloc(); // entityNum, entchannel); if (!ch) { ch = s_channels; oldest = sfx->lastTimeUsed; chosen = -1; for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTimeentchannel != CHAN_ANNOUNCER) { oldest = ch->allocTime; chosen = i; } } if (chosen == -1) { ch = s_channels; for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { if (ch->entnum != listener_number && ch->allocTimeentchannel != CHAN_ANNOUNCER) { oldest = ch->allocTime; chosen = i; } } if (chosen == -1) { if (ch->entnum == listener_number) { for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { if (ch->allocTimeallocTime; chosen = i; } } } if (chosen == -1) { Com_Printf("dropping sound\n"); return; } } } ch = &s_channels[chosen]; ch->allocTime = sfx->lastTimeUsed; } if (origin) { VectorCopy (origin, ch->origin); ch->fixed_origin = qtrue; } else { ch->fixed_origin = qfalse; } ch->master_vol = 127; ch->entnum = entityNum; ch->thesfx = sfx; ch->startSample = START_SAMPLE_IMMEDIATE; ch->entchannel = entchannel; ch->leftvol = ch->master_vol; // these will get calced at next spatialize ch->rightvol = ch->master_vol; // unless the game isn't running ch->doppler = qfalse; } /* ================== S_StartLocalSound ================== */ void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { if ( !s_soundStarted || s_soundMuted ) { return; } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { Com_Printf( S_COLOR_YELLOW, "S_StartLocalSound: handle %i out of range\n", sfxHandle ); return; } S_StartSound (NULL, listener_number, channelNum, sfxHandle ); } /* ================== S_ClearSoundBuffer If we are about to perform file access, clear the buffer so sound doesn't stutter. ================== */ void S_ClearSoundBuffer( void ) { int clear; if (!s_soundStarted) return; // stop looping sounds Com_Memset(loopSounds, 0, MAX_GENTITIES*sizeof(loopSound_t)); Com_Memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t)); numLoopChannels = 0; S_ChannelSetup(); s_rawend = 0; if (dma.samplebits == 8) clear = 0x80; else clear = 0; SNDDMA_BeginPainting (); if (dma.buffer) // TTimo: due to a particular bug workaround in linux sound code, // have to optionally use a custom C implementation of Com_Memset // not affecting win32, we have #define Snd_Memset Com_Memset // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371 Snd_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8); SNDDMA_Submit (); } /* ================== S_StopAllSounds ================== */ void S_StopAllSounds(void) { if ( !s_soundStarted ) { return; } // stop the background music S_StopBackgroundTrack(); S_ClearSoundBuffer (); } /* ============================================================== continuous looping sounds are added each frame ============================================================== */ void S_StopLoopingSound(int entityNum) { loopSounds[entityNum].active = qfalse; // loopSounds[entityNum].sfx = 0; loopSounds[entityNum].kill = qfalse; } /* ================== S_ClearLoopingSounds ================== */ void S_ClearLoopingSounds( qboolean killall ) { int i; for ( i = 0 ; i < MAX_GENTITIES ; i++) { if (killall || loopSounds[i].kill == qtrue || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) { loopSounds[i].kill = qfalse; S_StopLoopingSound(i); } } numLoopChannels = 0; } /* ================== S_AddLoopingSound Called during entity generation for a frame Include velocity in case I get around to doing doppler... ================== */ void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { sfx_t *sfx; if ( !s_soundStarted || s_soundMuted ) { return; } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { Com_Printf( S_COLOR_YELLOW, "S_AddLoopingSound: handle %i out of range\n", sfxHandle ); return; } sfx = &s_knownSfx[ sfxHandle ]; if (sfx->inMemory == qfalse) { S_memoryLoad(sfx); } if ( !sfx->soundLength ) { Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); } VectorCopy( origin, loopSounds[entityNum].origin ); VectorCopy( velocity, loopSounds[entityNum].velocity ); loopSounds[entityNum].active = qtrue; loopSounds[entityNum].kill = qtrue; loopSounds[entityNum].doppler = qfalse; loopSounds[entityNum].oldDopplerScale = 1.0; loopSounds[entityNum].dopplerScale = 1.0; loopSounds[entityNum].sfx = sfx; if (s_doppler->integer && VectorLengthSquared(velocity)>0.0) { vec3_t out; float lena, lenb; loopSounds[entityNum].doppler = qtrue; lena = DistanceSquared(loopSounds[listener_number].origin, loopSounds[entityNum].origin); VectorAdd(loopSounds[entityNum].origin, loopSounds[entityNum].velocity, out); lenb = DistanceSquared(loopSounds[listener_number].origin, out); if ((loopSounds[entityNum].framenum+1) != cls.framecount) { loopSounds[entityNum].oldDopplerScale = 1.0; } else { loopSounds[entityNum].oldDopplerScale = loopSounds[entityNum].dopplerScale; } loopSounds[entityNum].dopplerScale = lenb/(lena*100); if (loopSounds[entityNum].dopplerScale<=1.0) { loopSounds[entityNum].doppler = qfalse; // don't bother doing the math } } loopSounds[entityNum].framenum = cls.framecount; } /* ================== S_AddLoopingSound Called during entity generation for a frame Include velocity in case I get around to doing doppler... ================== */ void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { sfx_t *sfx; if ( !s_soundStarted || s_soundMuted ) { return; } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { Com_Printf( S_COLOR_YELLOW, "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle ); return; } sfx = &s_knownSfx[ sfxHandle ]; if (sfx->inMemory == qfalse) { S_memoryLoad(sfx); } if ( !sfx->soundLength ) { Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); } VectorCopy( origin, loopSounds[entityNum].origin ); VectorCopy( velocity, loopSounds[entityNum].velocity ); loopSounds[entityNum].sfx = sfx; loopSounds[entityNum].active = qtrue; loopSounds[entityNum].kill = qfalse; loopSounds[entityNum].doppler = qfalse; } /* ================== S_AddLoopSounds Spatialize all of the looping sounds. All sounds are on the same cycle, so any duplicates can just sum up the channel multipliers. ================== */ void S_AddLoopSounds (void) { int i, j, time; int left_total, right_total, left, right; channel_t *ch; loopSound_t *loop, *loop2; static int loopFrame; numLoopChannels = 0; time = Com_Milliseconds(); loopFrame++; for ( i = 0 ; i < MAX_GENTITIES ; i++) { loop = &loopSounds[i]; if ( !loop->active || loop->mergeFrame == loopFrame ) { continue; // already merged into an earlier sound } if (loop->kill) { S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total); // 3d } else { S_SpatializeOrigin( loop->origin, 90, &left_total, &right_total); // sphere } loop->sfx->lastTimeUsed = time; for (j=(i+1); j< MAX_GENTITIES ; j++) { loop2 = &loopSounds[j]; if ( !loop2->active || loop2->doppler || loop2->sfx != loop->sfx) { continue; } loop2->mergeFrame = loopFrame; if (loop2->kill) { S_SpatializeOrigin( loop2->origin, 127, &left, &right); // 3d } else { S_SpatializeOrigin( loop2->origin, 90, &left, &right); // sphere } loop2->sfx->lastTimeUsed = time; left_total += left; right_total += right; } if (left_total == 0 && right_total == 0) { continue; // not audible } // allocate a channel ch = &loop_channels[numLoopChannels]; if (left_total > 255) { left_total = 255; } if (right_total > 255) { right_total = 255; } ch->master_vol = 127; ch->leftvol = left_total; ch->rightvol = right_total; ch->thesfx = loop->sfx; ch->doppler = loop->doppler; ch->dopplerScale = loop->dopplerScale; ch->oldDopplerScale = loop->oldDopplerScale; numLoopChannels++; if (numLoopChannels == MAX_CHANNELS) { return; } } } //============================================================================= /* ================= S_ByteSwapRawSamples If raw data has been loaded in little endien binary form, this must be done. If raw data was calculated, as with ADPCM, this should not be called. ================= */ void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { int i; if ( width != 2 ) { return; } if ( LittleShort( 256 ) == 256 ) { return; } if ( s_channels == 2 ) { samples <<= 1; } for ( i = 0 ; i < samples ; i++ ) { ((short *)data)[i] = LittleShort( ((short *)data)[i] ); } } portable_samplepair_t *S_GetRawSamplePointer() { return s_rawsamples; } /* ============ S_RawSamples Music streaming ============ */ void S_RawSamples( int samples, int rate, int width, int s_channels, const byte *data, float volume ) { int i; int src, dst; float scale; int intVolume; if ( !s_soundStarted || s_soundMuted ) { return; } intVolume = 256 * volume; if ( s_rawend < s_soundtime ) { Com_DPrintf( "S_RawSamples: resetting minimum: %i < %i\n", s_rawend, s_soundtime ); s_rawend = s_soundtime; } scale = (float)rate / dma.speed; //Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend); if (s_channels == 2 && width == 2) { if (scale == 1.0) { // optimized case for (i=0 ; i= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ((short *)data)[src*2] * intVolume; s_rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume; } } } else if (s_channels == 1 && width == 2) { for (i=0 ; ; i++) { src = i*scale; if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ((short *)data)[src] * intVolume; s_rawsamples[dst].right = ((short *)data)[src] * intVolume; } } else if (s_channels == 2 && width == 1) { intVolume *= 256; for (i=0 ; ; i++) { src = i*scale; if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ((char *)data)[src*2] * intVolume; s_rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume; } } else if (s_channels == 1 && width == 1) { intVolume *= 256; for (i=0 ; ; i++) { src = i*scale; if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume; s_rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume; } } if ( s_rawend > s_soundtime + MAX_RAW_SAMPLES ) { Com_DPrintf( "S_RawSamples: overflowed %i > %i\n", s_rawend, s_soundtime ); } } //============================================================================= /* ===================== S_UpdateEntityPosition let the sound system know where an entity currently is ====================== */ void S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { if ( entityNum < 0 || entityNum > MAX_GENTITIES ) { Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); } VectorCopy( origin, loopSounds[entityNum].origin ); } /* ============ S_Respatialize Change the volumes of all the playing sounds for changes in their positions ============ */ void S_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) { int i; channel_t *ch; vec3_t origin; if ( !s_soundStarted || s_soundMuted ) { return; } listener_number = entityNum; VectorCopy(head, listener_origin); VectorCopy(axis[0], listener_axis[0]); VectorCopy(axis[1], listener_axis[1]); VectorCopy(axis[2], listener_axis[2]); // update spatialization for dynamic sounds ch = s_channels; for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { if ( !ch->thesfx ) { continue; } // anything coming from the view entity will always be full volume if (ch->entnum == listener_number) { ch->leftvol = ch->master_vol; ch->rightvol = ch->master_vol; } else { if (ch->fixed_origin) { VectorCopy( ch->origin, origin ); } else { VectorCopy( loopSounds[ ch->entnum ].origin, origin ); } S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol); } } // add loopsounds S_AddLoopSounds (); } /* ======================== S_ScanChannelStarts Returns qtrue if any new sounds were started since the last mix ======================== */ qboolean S_ScanChannelStarts( void ) { channel_t *ch; int i; qboolean newSamples; newSamples = qfalse; ch = s_channels; for (i=0; ithesfx ) { continue; } // if this channel was just started this frame, // set the sample count to it begins mixing // into the very first sample if ( ch->startSample == START_SAMPLE_IMMEDIATE ) { ch->startSample = s_paintedtime; newSamples = qtrue; continue; } // if it is completely finished by now, clear it if ( ch->startSample + (ch->thesfx->soundLength) <= s_paintedtime ) { S_ChannelFree(ch); } } return newSamples; } /* ============ S_Update Called once each time through the main loop ============ */ void S_Update( void ) { int i; int total; channel_t *ch; if ( !s_soundStarted || s_soundMuted ) { Com_DPrintf ("not started or muted\n"); return; } // // debugging output // if ( s_show->integer == 2 ) { total = 0; ch = s_channels; for (i=0 ; ithesfx && (ch->leftvol || ch->rightvol) ) { Com_Printf ("%f %f %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName); total++; } } Com_Printf ("----(%i)---- painted: %i\n", total, s_paintedtime); } // add raw data from streamed samples S_UpdateBackgroundTrack(); // mix some sound S_Update_(); } void S_GetSoundtime(void) { int samplepos; static int buffers; static int oldsamplepos; int fullsamples; fullsamples = dma.samples / dma.channels; // it is possible to miscount buffers if it has wrapped twice between // calls to S_Update. Oh well. samplepos = SNDDMA_GetDMAPos(); if (samplepos < oldsamplepos) { buffers++; // buffer wrapped if (s_paintedtime > 0x40000000) { // time to chop things off to avoid 32 bit limits buffers = 0; s_paintedtime = fullsamples; S_StopAllSounds (); } } oldsamplepos = samplepos; s_soundtime = buffers*fullsamples + samplepos/dma.channels; #if 0 // check to make sure that we haven't overshot if (s_paintedtime < s_soundtime) { Com_DPrintf ("S_Update_ : overflow\n"); s_paintedtime = s_soundtime; } #endif if ( dma.submission_chunk < 256 ) { s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed; } else { s_paintedtime = s_soundtime + dma.submission_chunk; } } void S_Update_(void) { unsigned endtime; int samps; static float lastTime = 0.0f; float ma, op; float thisTime, sane; static int ot = -1; if ( !s_soundStarted || s_soundMuted ) { return; } thisTime = Com_Milliseconds(); // Updates s_soundtime S_GetSoundtime(); if (s_soundtime == ot) { return; } ot = s_soundtime; // clear any sound effects that end before the current time, // and start any new sounds S_ScanChannelStarts(); sane = thisTime - lastTime; if (sane<11) { sane = 11; // 85hz } ma = s_mixahead->value * dma.speed; op = s_mixPreStep->value + sane*dma.speed*0.01; if (op < ma) { ma = op; } // mix ahead of current position endtime = s_soundtime + ma; // mix to an even submission block size endtime = (endtime + dma.submission_chunk-1) & ~(dma.submission_chunk-1); // never mix more than the complete buffer samps = dma.samples >> (dma.channels-1); if (endtime - s_soundtime > samps) endtime = s_soundtime + samps; SNDDMA_BeginPainting (); S_PaintChannels (endtime); SNDDMA_Submit (); lastTime = thisTime; } /* =============================================================================== console functions =============================================================================== */ void S_Play_f( void ) { int i; sfxHandle_t h; char name[256]; i = 1; while ( i [loopfile]\n"); return; } } void S_SoundList_f( void ) { int i; sfx_t *sfx; int size, total; char type[4][16]; char mem[2][16]; strcpy(type[0], "16bit"); strcpy(type[1], "adpcm"); strcpy(type[2], "daub4"); strcpy(type[3], "mulaw"); strcpy(mem[0], "paged out"); strcpy(mem[1], "resident "); total = 0; for (sfx=s_knownSfx, i=0 ; isoundLength; total += size; Com_Printf("%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod], sfx->soundName, mem[sfx->inMemory] ); } Com_Printf ("Total resident: %i\n", total); S_DisplayFreeMemory(); } /* =============================================================================== background music functions =============================================================================== */ int FGetLittleLong( fileHandle_t f ) { int v; FS_Read( &v, sizeof(v), f ); return LittleLong( v); } int FGetLittleShort( fileHandle_t f ) { short v; FS_Read( &v, sizeof(v), f ); return LittleShort( v); } // returns the length of the data in the chunk, or 0 if not found int S_FindWavChunk( fileHandle_t f, char *chunk ) { char name[5]; int len; int r; name[4] = 0; len = 0; r = FS_Read( name, 4, f ); if ( r != 4 ) { return 0; } len = FGetLittleLong( f ); if ( len < 0 || len > 0xfffffff ) { len = 0; return 0; } len = (len + 1 ) & ~1; // pad to word boundary // s_nextWavChunk += len + 8; if ( strcmp( name, chunk ) ) { return 0; } return len; } /* ====================== S_StopBackgroundTrack ====================== */ void S_StopBackgroundTrack( void ) { if ( !s_backgroundFile ) { return; } Sys_EndStreamedFile( s_backgroundFile ); FS_FCloseFile( s_backgroundFile ); s_backgroundFile = 0; s_rawend = 0; } /* ====================== S_StartBackgroundTrack ====================== */ void S_StartBackgroundTrack( const char *intro, const char *loop ){ int len; char dump[16]; char name[MAX_QPATH]; if ( !intro ) { intro = ""; } if ( !loop || !loop[0] ) { loop = intro; } Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop ); Q_strncpyz( name, intro, sizeof( name ) - 4 ); COM_DefaultExtension( name, sizeof( name ), ".wav" ); if ( !intro[0] ) { return; } Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); // close the background track, but DON'T reset s_rawend // if restarting the same back ground track if ( s_backgroundFile ) { Sys_EndStreamedFile( s_backgroundFile ); FS_FCloseFile( s_backgroundFile ); s_backgroundFile = 0; } // // open up a wav file and get all the info // FS_FOpenFileRead( name, &s_backgroundFile, qtrue ); if ( !s_backgroundFile ) { Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", name ); return; } // skip the riff wav header FS_Read(dump, 12, s_backgroundFile); if ( !S_FindWavChunk( s_backgroundFile, "fmt " ) ) { Com_Printf( "No fmt chunk in %s\n", name ); FS_FCloseFile( s_backgroundFile ); s_backgroundFile = 0; return; } // save name for soundinfo s_backgroundInfo.format = FGetLittleShort( s_backgroundFile ); s_backgroundInfo.channels = FGetLittleShort( s_backgroundFile ); s_backgroundInfo.rate = FGetLittleLong( s_backgroundFile ); FGetLittleLong( s_backgroundFile ); FGetLittleShort( s_backgroundFile ); s_backgroundInfo.width = FGetLittleShort( s_backgroundFile ) / 8; if ( s_backgroundInfo.format != WAV_FORMAT_PCM ) { FS_FCloseFile( s_backgroundFile ); s_backgroundFile = 0; Com_Printf("Not a microsoft PCM format wav: %s\n", name); return; } if ( s_backgroundInfo.channels != 2 || s_backgroundInfo.rate != 22050 ) { Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", name ); } if ( ( len = S_FindWavChunk( s_backgroundFile, "data" ) ) == 0 ) { FS_FCloseFile( s_backgroundFile ); s_backgroundFile = 0; Com_Printf("No data chunk in %s\n", name); return; } s_backgroundInfo.samples = len / (s_backgroundInfo.width * s_backgroundInfo.channels); s_backgroundSamples = s_backgroundInfo.samples; // // start the background streaming // Sys_BeginStreamedFile( s_backgroundFile, 0x10000 ); } /* ====================== S_UpdateBackgroundTrack ====================== */ void S_UpdateBackgroundTrack( void ) { int bufferSamples; int fileSamples; byte raw[30000]; // just enough to fit in a mac stack frame int fileBytes; int r; static float musicVolume = 0.5f; if ( !s_backgroundFile ) { return; } // graeme see if this is OK musicVolume = (musicVolume + (s_musicVolume->value * 2))/4.0f; // don't bother playing anything if musicvolume is 0 if ( musicVolume <= 0 ) { return; } // see how many samples should be copied into the raw buffer if ( s_rawend < s_soundtime ) { s_rawend = s_soundtime; } while ( s_rawend < s_soundtime + MAX_RAW_SAMPLES ) { bufferSamples = MAX_RAW_SAMPLES - (s_rawend - s_soundtime); // decide how much data needs to be read from the file fileSamples = bufferSamples * s_backgroundInfo.rate / dma.speed; // don't try and read past the end of the file if ( fileSamples > s_backgroundSamples ) { fileSamples = s_backgroundSamples; } // our max buffer size fileBytes = fileSamples * (s_backgroundInfo.width * s_backgroundInfo.channels); if ( fileBytes > sizeof(raw) ) { fileBytes = sizeof(raw); fileSamples = fileBytes / (s_backgroundInfo.width * s_backgroundInfo.channels); } r = Sys_StreamedRead( raw, 1, fileBytes, s_backgroundFile ); if ( r != fileBytes ) { Com_Printf("StreamedRead failure on music track\n"); S_StopBackgroundTrack(); return; } // byte swap if needed S_ByteSwapRawSamples( fileSamples, s_backgroundInfo.width, s_backgroundInfo.channels, raw ); // add to raw buffer S_RawSamples( fileSamples, s_backgroundInfo.rate, s_backgroundInfo.width, s_backgroundInfo.channels, raw, musicVolume ); s_backgroundSamples -= fileSamples; if ( !s_backgroundSamples ) { // loop if (s_backgroundLoop[0]) { Sys_EndStreamedFile( s_backgroundFile ); FS_FCloseFile( s_backgroundFile ); s_backgroundFile = 0; S_StartBackgroundTrack( s_backgroundLoop, s_backgroundLoop ); if ( !s_backgroundFile ) { return; // loop failed to restart } } else { s_backgroundFile = 0; return; } } } } /* ====================== S_FreeOldestSound ====================== */ void S_FreeOldestSound() { int i, oldest, used; sfx_t *sfx; sndBuffer *buffer, *nbuffer; oldest = Com_Milliseconds(); used = 0; for (i=1 ; i < s_numSfx ; i++) { sfx = &s_knownSfx[i]; if (sfx->inMemory && sfx->lastTimeUsedlastTimeUsed; } } sfx = &s_knownSfx[used]; Com_DPrintf("S_FreeOldestSound: freeing sound %s\n", sfx->soundName); buffer = sfx->soundData; while(buffer != NULL) { nbuffer = buffer->next; SND_free(buffer); buffer = nbuffer; } sfx->inMemory = qfalse; sfx->soundData = NULL; } ================================================ FILE: code/client/snd_local.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // snd_local.h -- private sound definations #include "../game/q_shared.h" #include "../qcommon/qcommon.h" #include "snd_public.h" #define PAINTBUFFER_SIZE 4096 // this is in samples #define SND_CHUNK_SIZE 1024 // samples #define SND_CHUNK_SIZE_FLOAT (SND_CHUNK_SIZE/2) // floats #define SND_CHUNK_SIZE_BYTE (SND_CHUNK_SIZE*2) // floats typedef struct { int left; // the final values will be clamped to +/- 0x00ffff00 and shifted down int right; } portable_samplepair_t; typedef struct adpcm_state { short sample; /* Previous output value */ char index; /* Index into stepsize table */ } adpcm_state_t; typedef struct sndBuffer_s { short sndChunk[SND_CHUNK_SIZE]; struct sndBuffer_s *next; int size; adpcm_state_t adpcm; } sndBuffer; typedef struct sfx_s { sndBuffer *soundData; qboolean defaultSound; // couldn't be loaded, so use buzz qboolean inMemory; // not in Memory qboolean soundCompressed; // not in Memory int soundCompressionMethod; int soundLength; char soundName[MAX_QPATH]; int lastTimeUsed; struct sfx_s *next; } sfx_t; typedef struct { int channels; int samples; // mono samples in buffer int submission_chunk; // don't mix less than this # int samplebits; int speed; byte *buffer; } dma_t; #define START_SAMPLE_IMMEDIATE 0x7fffffff typedef struct loopSound_s { vec3_t origin; vec3_t velocity; sfx_t *sfx; int mergeFrame; qboolean active; qboolean kill; qboolean doppler; float dopplerScale; float oldDopplerScale; int framenum; } loopSound_t; typedef struct { int allocTime; int startSample; // START_SAMPLE_IMMEDIATE = set immediately on next mix int entnum; // to allow overriding a specific sound int entchannel; // to allow overriding a specific sound int leftvol; // 0-255 volume after spatialization int rightvol; // 0-255 volume after spatialization int master_vol; // 0-255 volume before spatialization float dopplerScale; float oldDopplerScale; vec3_t origin; // only use if fixed_origin is set qboolean fixed_origin; // use origin instead of fetching entnum's origin sfx_t *thesfx; // sfx structure qboolean doppler; } channel_t; #define WAV_FORMAT_PCM 1 typedef struct { int format; int rate; int width; int channels; int samples; int dataofs; // chunk starts this many bytes from file start } wavinfo_t; /* ==================================================================== SYSTEM SPECIFIC FUNCTIONS ==================================================================== */ // initializes cycling through a DMA buffer and returns information on it qboolean SNDDMA_Init(void); // gets the current DMA position int SNDDMA_GetDMAPos(void); // shutdown the DMA xfer. void SNDDMA_Shutdown(void); void SNDDMA_BeginPainting (void); void SNDDMA_Submit(void); //==================================================================== #define MAX_CHANNELS 96 extern channel_t s_channels[MAX_CHANNELS]; extern channel_t loop_channels[MAX_CHANNELS]; extern int numLoopChannels; extern int s_paintedtime; extern int s_rawend; extern vec3_t listener_forward; extern vec3_t listener_right; extern vec3_t listener_up; extern dma_t dma; #define MAX_RAW_SAMPLES 16384 extern portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; extern cvar_t *s_volume; extern cvar_t *s_nosound; extern cvar_t *s_khz; extern cvar_t *s_show; extern cvar_t *s_mixahead; extern cvar_t *s_testsound; extern cvar_t *s_separation; qboolean S_LoadSound( sfx_t *sfx ); void SND_free(sndBuffer *v); sndBuffer* SND_malloc(); void SND_setup(); void S_PaintChannels(int endtime); void S_memoryLoad(sfx_t *sfx); portable_samplepair_t *S_GetRawSamplePointer(); // spatializes a channel void S_Spatialize(channel_t *ch); // adpcm functions int S_AdpcmMemoryNeeded( const wavinfo_t *info ); void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ); void S_AdpcmGetSamples(sndBuffer *chunk, short *to); // wavelet function #define SENTINEL_MULAW_ZERO_RUN 127 #define SENTINEL_MULAW_FOUR_BIT_RUN 126 void S_FreeOldestSound(); #define NXStream byte void encodeWavelet(sfx_t *sfx, short *packets); void decodeWavelet( sndBuffer *stream, short *packets); void encodeMuLaw( sfx_t *sfx, short *packets); extern short mulawToShort[256]; extern short *sfxScratchBuffer; extern sfx_t *sfxScratchPointer; extern int sfxScratchIndex; ================================================ FILE: code/client/snd_mem.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ /***************************************************************************** * name: snd_mem.c * * desc: sound caching * * $Archive: /MissionPack/code/client/snd_mem.c $ * *****************************************************************************/ #include "snd_local.h" #define DEF_COMSOUNDMEGS "8" /* =============================================================================== memory management =============================================================================== */ static sndBuffer *buffer = NULL; static sndBuffer *freelist = NULL; static int inUse = 0; static int totalInUse = 0; short *sfxScratchBuffer = NULL; sfx_t *sfxScratchPointer = NULL; int sfxScratchIndex = 0; void SND_free(sndBuffer *v) { *(sndBuffer **)v = freelist; freelist = (sndBuffer*)v; inUse += sizeof(sndBuffer); } sndBuffer* SND_malloc() { sndBuffer *v; redo: if (freelist == NULL) { S_FreeOldestSound(); goto redo; } inUse -= sizeof(sndBuffer); totalInUse += sizeof(sndBuffer); v = freelist; freelist = *(sndBuffer **)freelist; v->next = NULL; return v; } void SND_setup() { sndBuffer *p, *q; cvar_t *cv; int scs; cv = Cvar_Get( "com_soundMegs", DEF_COMSOUNDMEGS, CVAR_LATCH | CVAR_ARCHIVE ); scs = (cv->integer*1536); buffer = malloc(scs*sizeof(sndBuffer) ); // allocate the stack based hunk allocator sfxScratchBuffer = malloc(SND_CHUNK_SIZE * sizeof(short) * 4); //Hunk_Alloc(SND_CHUNK_SIZE * sizeof(short) * 4); sfxScratchPointer = NULL; inUse = scs*sizeof(sndBuffer); p = buffer;; q = p + scs; while (--q > p) *(sndBuffer **)q = q-1; *(sndBuffer **)q = NULL; freelist = p + scs - 1; Com_Printf("Sound memory manager started\n"); } /* =============================================================================== WAV loading =============================================================================== */ static byte *data_p; static byte *iff_end; static byte *last_chunk; static byte *iff_data; static int iff_chunk_len; static short GetLittleShort(void) { short val = 0; val = *data_p; val = val + (*(data_p+1)<<8); data_p += 2; return val; } static int GetLittleLong(void) { int val = 0; val = *data_p; val = val + (*(data_p+1)<<8); val = val + (*(data_p+2)<<16); val = val + (*(data_p+3)<<24); data_p += 4; return val; } static void FindNextChunk(char *name) { while (1) { data_p=last_chunk; if (data_p >= iff_end) { // didn't find the chunk data_p = NULL; return; } data_p += 4; iff_chunk_len = GetLittleLong(); if (iff_chunk_len < 0) { data_p = NULL; return; } data_p -= 8; last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); if (!strncmp((char *)data_p, name, 4)) return; } } static void FindChunk(char *name) { last_chunk = iff_data; FindNextChunk (name); } /* ============ GetWavinfo ============ */ static wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength) { wavinfo_t info; Com_Memset (&info, 0, sizeof(info)); if (!wav) return info; iff_data = wav; iff_end = wav + wavlength; // find "RIFF" chunk FindChunk("RIFF"); if (!(data_p && !strncmp((char *)data_p+8, "WAVE", 4))) { Com_Printf("Missing RIFF/WAVE chunks\n"); return info; } // get "fmt " chunk iff_data = data_p + 12; // DumpChunks (); FindChunk("fmt "); if (!data_p) { Com_Printf("Missing fmt chunk\n"); return info; } data_p += 8; info.format = GetLittleShort(); info.channels = GetLittleShort(); info.rate = GetLittleLong(); data_p += 4+2; info.width = GetLittleShort() / 8; if (info.format != 1) { Com_Printf("Microsoft PCM format only\n"); return info; } // find data chunk FindChunk("data"); if (!data_p) { Com_Printf("Missing data chunk\n"); return info; } data_p += 4; info.samples = GetLittleLong () / info.width; info.dataofs = data_p - wav; return info; } /* ================ ResampleSfx resample / decimate to the current source rate ================ */ static void ResampleSfx( sfx_t *sfx, int inrate, int inwidth, byte *data, qboolean compressed ) { int outcount; int srcsample; float stepscale; int i; int sample, samplefrac, fracstep; int part; sndBuffer *chunk; stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 outcount = sfx->soundLength / stepscale; sfx->soundLength = outcount; samplefrac = 0; fracstep = stepscale * 256; chunk = sfx->soundData; for (i=0 ; i> 8; samplefrac += fracstep; if( inwidth == 2 ) { sample = LittleShort ( ((short *)data)[srcsample] ); } else { sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; } part = (i&(SND_CHUNK_SIZE-1)); if (part == 0) { sndBuffer *newchunk; newchunk = SND_malloc(); if (chunk == NULL) { sfx->soundData = newchunk; } else { chunk->next = newchunk; } chunk = newchunk; } chunk->sndChunk[part] = sample; } } /* ================ ResampleSfx resample / decimate to the current source rate ================ */ static int ResampleSfxRaw( short *sfx, int inrate, int inwidth, int samples, byte *data ) { int outcount; int srcsample; float stepscale; int i; int sample, samplefrac, fracstep; stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 outcount = samples / stepscale; samplefrac = 0; fracstep = stepscale * 256; for (i=0 ; i> 8; samplefrac += fracstep; if( inwidth == 2 ) { sample = LittleShort ( ((short *)data)[srcsample] ); } else { sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; } sfx[i] = sample; } return outcount; } //============================================================================= /* ============== S_LoadSound The filename may be different than sfx->name in the case of a forced fallback of a player specific sound ============== */ qboolean S_LoadSound( sfx_t *sfx ) { byte *data; short *samples; wavinfo_t info; int size; // player specific sounds are never directly loaded if ( sfx->soundName[0] == '*') { return qfalse; } // load it in size = FS_ReadFile( sfx->soundName, (void **)&data ); if ( !data ) { return qfalse; } info = GetWavinfo( sfx->soundName, data, size ); if ( info.channels != 1 ) { Com_Printf ("%s is a stereo wav file\n", sfx->soundName); FS_FreeFile (data); return qfalse; } if ( info.width == 1 ) { Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit wav file\n", sfx->soundName); } if ( info.rate != 22050 ) { Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sfx->soundName); } samples = Hunk_AllocateTempMemory(info.samples * sizeof(short) * 2); sfx->lastTimeUsed = Com_Milliseconds()+1; // each of these compression schemes works just fine // but the 16bit quality is much nicer and with a local // install assured we can rely upon the sound memory // manager to do the right thing for us and page // sound in as needed if( sfx->soundCompressed == qtrue) { sfx->soundCompressionMethod = 1; sfx->soundData = NULL; sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); S_AdpcmEncodeSound(sfx, samples); #if 0 } else if (info.samples>(SND_CHUNK_SIZE*16) && info.width >1) { sfx->soundCompressionMethod = 3; sfx->soundData = NULL; sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); encodeMuLaw( sfx, samples); } else if (info.samples>(SND_CHUNK_SIZE*6400) && info.width >1) { sfx->soundCompressionMethod = 2; sfx->soundData = NULL; sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); encodeWavelet( sfx, samples); #endif } else { sfx->soundCompressionMethod = 0; sfx->soundLength = info.samples; sfx->soundData = NULL; ResampleSfx( sfx, info.rate, info.width, data + info.dataofs, qfalse ); } Hunk_FreeTempMemory(samples); FS_FreeFile( data ); return qtrue; } void S_DisplayFreeMemory() { Com_Printf("%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse); } ================================================ FILE: code/client/snd_mix.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // snd_mix.c -- portable code to mix sounds for snd_dma.c #include "snd_local.h" static portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; static int snd_vol; // bk001119 - these not static, required by unix/snd_mixa.s int* snd_p; int snd_linear_count; short* snd_out; #if !( (defined __linux__ || defined __FreeBSD__ ) && (defined __i386__) ) // rb010123 #if !id386 void S_WriteLinearBlastStereo16 (void) { int i; int val; for (i=0 ; i>8; if (val > 0x7fff) snd_out[i] = 0x7fff; else if (val < -32768) snd_out[i] = -32768; else snd_out[i] = val; val = snd_p[i+1]>>8; if (val > 0x7fff) snd_out[i+1] = 0x7fff; else if (val < -32768) snd_out[i+1] = -32768; else snd_out[i+1] = val; } } #else __declspec( naked ) void S_WriteLinearBlastStereo16 (void) { __asm { push edi push ebx mov ecx,ds:dword ptr[snd_linear_count] mov ebx,ds:dword ptr[snd_p] mov edi,ds:dword ptr[snd_out] LWLBLoopTop: mov eax,ds:dword ptr[-8+ebx+ecx*4] sar eax,8 cmp eax,07FFFh jg LClampHigh cmp eax,0FFFF8000h jnl LClampDone mov eax,0FFFF8000h jmp LClampDone LClampHigh: mov eax,07FFFh LClampDone: mov edx,ds:dword ptr[-4+ebx+ecx*4] sar edx,8 cmp edx,07FFFh jg LClampHigh2 cmp edx,0FFFF8000h jnl LClampDone2 mov edx,0FFFF8000h jmp LClampDone2 LClampHigh2: mov edx,07FFFh LClampDone2: shl edx,16 and eax,0FFFFh or edx,eax mov ds:dword ptr[-4+edi+ecx*2],edx sub ecx,2 jnz LWLBLoopTop pop ebx pop edi ret } } #endif #else // forward declare, implementation somewhere else void S_WriteLinearBlastStereo16 (void); #endif void S_TransferStereo16 (unsigned long *pbuf, int endtime) { int lpos; int ls_paintedtime; snd_p = (int *) paintbuffer; ls_paintedtime = s_paintedtime; while (ls_paintedtime < endtime) { // handle recirculating buffer issues lpos = ls_paintedtime & ((dma.samples>>1)-1); snd_out = (short *) pbuf + (lpos<<1); snd_linear_count = (dma.samples>>1) - lpos; if (ls_paintedtime + snd_linear_count > endtime) snd_linear_count = endtime - ls_paintedtime; snd_linear_count <<= 1; // write a linear blast of samples S_WriteLinearBlastStereo16 (); snd_p += snd_linear_count; ls_paintedtime += (snd_linear_count>>1); } } /* =================== S_TransferPaintBuffer =================== */ void S_TransferPaintBuffer(int endtime) { int out_idx; int count; int out_mask; int *p; int step; int val; unsigned long *pbuf; pbuf = (unsigned long *)dma.buffer; if ( s_testsound->integer ) { int i; int count; // write a fixed sine wave count = (endtime - s_paintedtime); for (i=0 ; i> 8; p+= step; if (val > 0x7fff) val = 0x7fff; else if (val < -32768) val = -32768; out[out_idx] = val; out_idx = (out_idx + 1) & out_mask; } } else if (dma.samplebits == 8) { unsigned char *out = (unsigned char *) pbuf; while (count--) { val = *p >> 8; p+= step; if (val > 0x7fff) val = 0x7fff; else if (val < -32768) val = -32768; out[out_idx] = (val>>8) + 128; out_idx = (out_idx + 1) & out_mask; } } } } /* =============================================================================== CHANNEL MIXING =============================================================================== */ static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { int data, aoff, boff; int leftvol, rightvol; int i, j; portable_samplepair_t *samp; sndBuffer *chunk; short *samples; float ooff, fdata, fdiv, fleftvol, frightvol; samp = &paintbuffer[ bufferOffset ]; if (ch->doppler) { sampleOffset = sampleOffset*ch->oldDopplerScale; } chunk = sc->soundData; while (sampleOffset>=SND_CHUNK_SIZE) { chunk = chunk->next; sampleOffset -= SND_CHUNK_SIZE; if (!chunk) { chunk = sc->soundData; } } if (!ch->doppler || ch->dopplerScale==1.0f) { #if idppc_altivec vector signed short volume_vec; vector unsigned int volume_shift; int vectorCount, samplesLeft, chunkSamplesLeft; #endif leftvol = ch->leftvol*snd_vol; rightvol = ch->rightvol*snd_vol; samples = chunk->sndChunk; #if idppc_altivec ((short *)&volume_vec)[0] = leftvol; ((short *)&volume_vec)[1] = leftvol; ((short *)&volume_vec)[4] = leftvol; ((short *)&volume_vec)[5] = leftvol; ((short *)&volume_vec)[2] = rightvol; ((short *)&volume_vec)[3] = rightvol; ((short *)&volume_vec)[6] = rightvol; ((short *)&volume_vec)[7] = rightvol; volume_shift = vec_splat_u32(8); i = 0; while(i < count) { /* Try to align destination to 16-byte boundary */ while(i < count && (((unsigned long)&samp[i] & 0x1f) || ((count-i) < 8) || ((SND_CHUNK_SIZE - sampleOffset) < 8))) { data = samples[sampleOffset++]; samp[i].left += (data * leftvol)>>8; samp[i].right += (data * rightvol)>>8; if (sampleOffset == SND_CHUNK_SIZE) { chunk = chunk->next; samples = chunk->sndChunk; sampleOffset = 0; } i++; } /* Destination is now aligned. Process as many 8-sample chunks as we can before we run out of room from the current sound chunk. We do 8 per loop to avoid extra source data reads. */ samplesLeft = count - i; chunkSamplesLeft = SND_CHUNK_SIZE - sampleOffset; if(samplesLeft > chunkSamplesLeft) samplesLeft = chunkSamplesLeft; vectorCount = samplesLeft / 8; if(vectorCount) { vector unsigned char tmp; vector short s0, s1, sampleData0, sampleData1; vector short samples0, samples1; vector signed int left0, right0; vector signed int merge0, merge1; vector signed int d0, d1, d2, d3; vector unsigned char samplePermute0 = (vector unsigned char)(0, 1, 4, 5, 0, 1, 4, 5, 2, 3, 6, 7, 2, 3, 6, 7); vector unsigned char samplePermute1 = (vector unsigned char)(8, 9, 12, 13, 8, 9, 12, 13, 10, 11, 14, 15, 10, 11, 14, 15); vector unsigned char loadPermute0, loadPermute1; // Rather than permute the vectors after we load them to do the sample // replication and rearrangement, we permute the alignment vector so // we do everything in one step below and avoid data shuffling. tmp = vec_lvsl(0,&samples[sampleOffset]); loadPermute0 = vec_perm(tmp,tmp,samplePermute0); loadPermute1 = vec_perm(tmp,tmp,samplePermute1); s0 = *(vector short *)&samples[sampleOffset]; while(vectorCount) { /* Load up source (16-bit) sample data */ s1 = *(vector short *)&samples[sampleOffset+7]; /* Load up destination sample data */ d0 = *(vector signed int *)&samp[i]; d1 = *(vector signed int *)&samp[i+2]; d2 = *(vector signed int *)&samp[i+4]; d3 = *(vector signed int *)&samp[i+6]; sampleData0 = vec_perm(s0,s1,loadPermute0); sampleData1 = vec_perm(s0,s1,loadPermute1); merge0 = vec_mule(sampleData0,volume_vec); merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ merge1 = vec_mulo(sampleData0,volume_vec); merge1 = vec_sra(merge1,volume_shift); d0 = vec_add(merge0,d0); d1 = vec_add(merge1,d1); merge0 = vec_mule(sampleData1,volume_vec); merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ merge1 = vec_mulo(sampleData1,volume_vec); merge1 = vec_sra(merge1,volume_shift); d2 = vec_add(merge0,d2); d3 = vec_add(merge1,d3); /* Store destination sample data */ *(vector signed int *)&samp[i] = d0; *(vector signed int *)&samp[i+2] = d1; *(vector signed int *)&samp[i+4] = d2; *(vector signed int *)&samp[i+6] = d3; i += 8; vectorCount--; s0 = s1; sampleOffset += 8; } if (sampleOffset == SND_CHUNK_SIZE) { chunk = chunk->next; samples = chunk->sndChunk; sampleOffset = 0; } } } #else for ( i=0 ; i>8; samp[i].right += (data * rightvol)>>8; if (sampleOffset == SND_CHUNK_SIZE) { chunk = chunk->next; samples = chunk->sndChunk; sampleOffset = 0; } } #endif } else { fleftvol = ch->leftvol*snd_vol; frightvol = ch->rightvol*snd_vol; ooff = sampleOffset; samples = chunk->sndChunk; for ( i=0 ; idopplerScale; boff = ooff; fdata = 0; for (j=aoff; jnext; if (!chunk) { chunk = sc->soundData; } samples = chunk->sndChunk; ooff -= SND_CHUNK_SIZE; } fdata += samples[j&(SND_CHUNK_SIZE-1)]; } fdiv = 256 * (boff-aoff); samp[i].left += (fdata * fleftvol)/fdiv; samp[i].right += (fdata * frightvol)/fdiv; } } } void S_PaintChannelFromWavelet( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { int data; int leftvol, rightvol; int i; portable_samplepair_t *samp; sndBuffer *chunk; short *samples; leftvol = ch->leftvol*snd_vol; rightvol = ch->rightvol*snd_vol; i = 0; samp = &paintbuffer[ bufferOffset ]; chunk = sc->soundData; while (sampleOffset>=(SND_CHUNK_SIZE_FLOAT*4)) { chunk = chunk->next; sampleOffset -= (SND_CHUNK_SIZE_FLOAT*4); i++; } if (i!=sfxScratchIndex || sfxScratchPointer != sc) { S_AdpcmGetSamples( chunk, sfxScratchBuffer ); sfxScratchIndex = i; sfxScratchPointer = sc; } samples = sfxScratchBuffer; for ( i=0 ; i>8; samp[i].right += (data * rightvol)>>8; if (sampleOffset == SND_CHUNK_SIZE*2) { chunk = chunk->next; decodeWavelet(chunk, sfxScratchBuffer); sfxScratchIndex++; sampleOffset = 0; } } } void S_PaintChannelFromADPCM( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { int data; int leftvol, rightvol; int i; portable_samplepair_t *samp; sndBuffer *chunk; short *samples; leftvol = ch->leftvol*snd_vol; rightvol = ch->rightvol*snd_vol; i = 0; samp = &paintbuffer[ bufferOffset ]; chunk = sc->soundData; if (ch->doppler) { sampleOffset = sampleOffset*ch->oldDopplerScale; } while (sampleOffset>=(SND_CHUNK_SIZE*4)) { chunk = chunk->next; sampleOffset -= (SND_CHUNK_SIZE*4); i++; } if (i!=sfxScratchIndex || sfxScratchPointer != sc) { S_AdpcmGetSamples( chunk, sfxScratchBuffer ); sfxScratchIndex = i; sfxScratchPointer = sc; } samples = sfxScratchBuffer; for ( i=0 ; i>8; samp[i].right += (data * rightvol)>>8; if (sampleOffset == SND_CHUNK_SIZE*4) { chunk = chunk->next; S_AdpcmGetSamples( chunk, sfxScratchBuffer); sampleOffset = 0; sfxScratchIndex++; } } } void S_PaintChannelFromMuLaw( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { int data; int leftvol, rightvol; int i; portable_samplepair_t *samp; sndBuffer *chunk; byte *samples; float ooff; leftvol = ch->leftvol*snd_vol; rightvol = ch->rightvol*snd_vol; samp = &paintbuffer[ bufferOffset ]; chunk = sc->soundData; while (sampleOffset>=(SND_CHUNK_SIZE*2)) { chunk = chunk->next; sampleOffset -= (SND_CHUNK_SIZE*2); if (!chunk) { chunk = sc->soundData; } } if (!ch->doppler) { samples = (byte *)chunk->sndChunk + sampleOffset; for ( i=0 ; i>8; samp[i].right += (data * rightvol)>>8; samples++; if (samples == (byte *)chunk->sndChunk+(SND_CHUNK_SIZE*2)) { chunk = chunk->next; samples = (byte *)chunk->sndChunk; } } } else { ooff = sampleOffset; samples = (byte *)chunk->sndChunk; for ( i=0 ; idopplerScale; samp[i].left += (data * leftvol)>>8; samp[i].right += (data * rightvol)>>8; if (ooff >= SND_CHUNK_SIZE*2) { chunk = chunk->next; if (!chunk) { chunk = sc->soundData; } samples = (byte *)chunk->sndChunk; ooff = 0.0; } } } } /* =================== S_PaintChannels =================== */ void S_PaintChannels( int endtime ) { int i; int end; channel_t *ch; sfx_t *sc; int ltime, count; int sampleOffset; snd_vol = s_volume->value*255; //Com_Printf ("%i to %i\n", s_paintedtime, endtime); while ( s_paintedtime < endtime ) { // if paintbuffer is smaller than DMA buffer // we may need to fill it multiple times end = endtime; if ( endtime - s_paintedtime > PAINTBUFFER_SIZE ) { end = s_paintedtime + PAINTBUFFER_SIZE; } // clear the paint buffer to either music or zeros if ( s_rawend < s_paintedtime ) { if ( s_rawend ) { //Com_DPrintf ("background sound underrun\n"); } Com_Memset(paintbuffer, 0, (end - s_paintedtime) * sizeof(portable_samplepair_t)); } else { // copy from the streaming sound source int s; int stop; stop = (end < s_rawend) ? end : s_rawend; for ( i = s_paintedtime ; i < stop ; i++ ) { s = i&(MAX_RAW_SAMPLES-1); paintbuffer[i-s_paintedtime] = s_rawsamples[s]; } // if (i != end) // Com_Printf ("partial stream\n"); // else // Com_Printf ("full stream\n"); for ( ; i < end ; i++ ) { paintbuffer[i-s_paintedtime].left = paintbuffer[i-s_paintedtime].right = 0; } } // paint in the channels. ch = s_channels; for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { if ( !ch->thesfx || (ch->leftvol<0.25 && ch->rightvol<0.25 )) { continue; } ltime = s_paintedtime; sc = ch->thesfx; sampleOffset = ltime - ch->startSample; count = end - ltime; if ( sampleOffset + count > sc->soundLength ) { count = sc->soundLength - sampleOffset; } if ( count > 0 ) { if( sc->soundCompressionMethod == 1) { S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); } else if( sc->soundCompressionMethod == 2) { S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); } else if( sc->soundCompressionMethod == 3) { S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); } else { S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); } } } // paint in the looped channels. ch = loop_channels; for ( i = 0; i < numLoopChannels ; i++, ch++ ) { if ( !ch->thesfx || (!ch->leftvol && !ch->rightvol )) { continue; } ltime = s_paintedtime; sc = ch->thesfx; if (sc->soundData==NULL || sc->soundLength==0) { continue; } // we might have to make two passes if it // is a looping sound effect and the end of // the sample is hit do { sampleOffset = (ltime % sc->soundLength); count = end - ltime; if ( sampleOffset + count > sc->soundLength ) { count = sc->soundLength - sampleOffset; } if ( count > 0 ) { if( sc->soundCompressionMethod == 1) { S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); } else if( sc->soundCompressionMethod == 2) { S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); } else if( sc->soundCompressionMethod == 3) { S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); } else { S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); } ltime += count; } } while ( ltime < end); } // transfer out according to DMA format S_TransferPaintBuffer( end ); s_paintedtime = end; } } ================================================ FILE: code/client/snd_public.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ void S_Init( void ); void S_Shutdown( void ); // if origin is NULL, the sound will be dynamically sourced from the entity void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); void S_StartLocalSound( sfxHandle_t sfx, int channelNum ); void S_StartBackgroundTrack( const char *intro, const char *loop ); void S_StopBackgroundTrack( void ); // cinematics and voice-over-network will send raw samples // 1.0 volume will be direct output of source samples void S_RawSamples (int samples, int rate, int width, int channels, const byte *data, float volume); // stop all sounds and the background track void S_StopAllSounds( void ); // all continuous looping sounds must be added before calling S_Update void S_ClearLoopingSounds( qboolean killall ); void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); void S_StopLoopingSound(int entityNum ); // recompute the reletive volumes for all running sounds // reletive to the given entityNum / orientation void S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); // let the sound system know where an entity currently is void S_UpdateEntityPosition( int entityNum, const vec3_t origin ); void S_Update( void ); void S_DisableSounds( void ); void S_BeginRegistration( void ); // RegisterSound will allways return a valid sample, even if it // has to create a placeholder. This prevents continuous filesystem // checks for missing files sfxHandle_t S_RegisterSound( const char *sample, qboolean compressed ); void S_DisplayFreeMemory(void); void S_ClearSoundBuffer( void ); void SNDDMA_Activate( void ); void S_UpdateBackgroundTrack( void ); ================================================ FILE: code/client/snd_wavelet.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "snd_local.h" long myftol( float f ); #define C0 0.4829629131445341 #define C1 0.8365163037378079 #define C2 0.2241438680420134 #define C3 -0.1294095225512604 void daub4(float b[], unsigned long n, int isign) { float wksp[4097]; float *a=b-1; // numerical recipies so a[1] = b[0] unsigned long nh,nh1,i,j; if (n < 4) return; nh1=(nh=n >> 1)+1; if (isign >= 0) { for (i=1,j=1;j<=n-3;j+=2,i++) { wksp[i] = C0*a[j]+C1*a[j+1]+C2*a[j+2]+C3*a[j+3]; wksp[i+nh] = C3*a[j]-C2*a[j+1]+C1*a[j+2]-C0*a[j+3]; } wksp[i ] = C0*a[n-1]+C1*a[n]+C2*a[1]+C3*a[2]; wksp[i+nh] = C3*a[n-1]-C2*a[n]+C1*a[1]-C0*a[2]; } else { wksp[1] = C2*a[nh]+C1*a[n]+C0*a[1]+C3*a[nh1]; wksp[2] = C3*a[nh]-C0*a[n]+C1*a[1]-C2*a[nh1]; for (i=1,j=3;i= 0) { for (nn=n;nn>=inverseStartLength;nn>>=1) daub4(a,nn,isign); } else { for (nn=inverseStartLength;nn<=n;nn<<=1) daub4(a,nn,isign); } } /* The number of bits required by each value */ static unsigned char numBits[] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, }; byte MuLawEncode(short s) { unsigned long adjusted; byte sign, exponent, mantissa; sign = (s<0)?0:0x80; if (s<0) s=-s; adjusted = (long)s << (16-sizeof(short)*8); adjusted += 128L + 4L; if (adjusted > 32767) adjusted = 32767; exponent = numBits[(adjusted>>7)&0xff] - 1; mantissa = (adjusted>>(exponent+3))&0xf; return ~(sign | (exponent<<4) | mantissa); } short MuLawDecode(byte uLaw) { signed long adjusted; byte exponent, mantissa; uLaw = ~uLaw; exponent = (uLaw>>4) & 0x7; mantissa = (uLaw&0xf) + 16; adjusted = (mantissa << (exponent +3)) - 128 - 4; return (uLaw & 0x80)? adjusted : -adjusted; } short mulawToShort[256]; static qboolean madeTable = qfalse; static int NXStreamCount; void NXPutc(NXStream *stream, char out) { stream[NXStreamCount++] = out; } void encodeWavelet( sfx_t *sfx, short *packets) { float wksp[4097], temp; int i, samples, size; sndBuffer *newchunk, *chunk; byte *out; if (!madeTable) { for (i=0;i<256;i++) { mulawToShort[i] = (float)MuLawDecode((byte)i); } madeTable = qtrue; } chunk = NULL; samples = sfx->soundLength; while(samples>0) { size = samples; if (size>(SND_CHUNK_SIZE*2)) { size = (SND_CHUNK_SIZE*2); } if (size<4) { size = 4; } newchunk = SND_malloc(); if (sfx->soundData == NULL) { sfx->soundData = newchunk; } else { chunk->next = newchunk; } chunk = newchunk; for(i=0; isndChunk; for(i=0;i 32767) temp = 32767; else if (temp<-32768) temp = -32768; out[i] = MuLawEncode((short)temp); } chunk->size = size; samples -= size; } } void decodeWavelet(sndBuffer *chunk, short *to) { float wksp[4097]; int i; byte *out; int size = chunk->size; out = (byte *)chunk->sndChunk; for(i=0;isoundLength; grade = 0; while(samples>0) { size = samples; if (size>(SND_CHUNK_SIZE*2)) { size = (SND_CHUNK_SIZE*2); } newchunk = SND_malloc(); if (sfx->soundData == NULL) { sfx->soundData = newchunk; } else { chunk->next = newchunk; } chunk = newchunk; out = (byte *)chunk->sndChunk; for(i=0; i32767) { poop = 32767; } else if (poop<-32768) { poop = -32768; } out[i] = MuLawEncode((short)poop); grade = poop - mulawToShort[out[i]]; packets++; } chunk->size = size; samples -= size; } } void decodeMuLaw(sndBuffer *chunk, short *to) { int i; byte *out; int size = chunk->size; out = (byte *)chunk->sndChunk; for(i=0;i '#cgame:#game:#q3_ui', CC => $CC, CXX => $CXX, LINK => $LINK, ENV => { PATH => $ENV{PATH}, HOME => $ENV{HOME} }, CFLAGS => $BASE_CFLAGS . '-fPIC', LDFLAGS => '-shared -ldl -lm' ); # for TA, use -DMISSIONPACK %ta_env_hash = $env->copy( CPPPATH => '#cgame:#game:#ui' ); $ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $ta_env_hash{CFLAGS}; $ta_env = new cons(%ta_env_hash); # qvm building # we heavily customize the cons environment $vm_env = new cons( # the code has the very bad habit of doing things like #include "../ui/ui_shared.h" # this seems to confuse the dependency analysis, explicit toplevel includes seem to fix CPPPATH => '#cgame:#game:#q3_ui', CC => 'q3lcc', CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>', SUFOBJ => '.asm', LINK => 'q3asm', CFLAGS => '-DQ3_VM -S -Wf-target=bytecode -Wf-g', # need to know where to find the compiler tools ENV => { PATH => $ENV{PATH} . ":./qvmtools", }, ); # TA qvm building %vm_ta_env_hash = $vm_env->copy( CPPPATH => '#cgame:#game:#ui' ); $vm_ta_env_hash{CFLAGS} = '-DMISSIONPACK ' . $vm_ta_env_hash{CFLAGS}; $vm_ta_env = new cons(%vm_ta_env_hash); # the file with vmMain function MUST be the first one of the list @FILES = qw( g_main.c ai_chat.c ai_cmd.c ai_dmnet.c ai_dmq3.c ai_main.c ai_team.c ai_vcmd.c bg_misc.c bg_pmove.c bg_slidemove.c g_active.c g_arenas.c g_bot.c g_client.c g_cmds.c g_combat.c g_items.c g_mem.c g_misc.c g_missile.c g_mover.c g_session.c g_spawn.c g_svcmds.c g_target.c g_team.c g_trigger.c g_utils.c g_weapon.c q_math.c q_shared.c ); $FILESREF = \@FILES; # only in .so # (VM uses a custom .asm with equ stubs) @SO_FILES = qw( g_syscalls.c ); $SO_FILESREF = \@SO_FILES; # only for VM @VM_FILES = qw( bg_lib.c g_syscalls.asm ); $VM_FILESREF = \@VM_FILES; # FIXME CPU string? # NOTE: $env $ta_env and $vm_env $vm_ta_env may not be necessary # we could alter the $env and $ta_env based on $TARGET_DIR # doing it this way to ensure homogeneity with cgame building if ($TARGET_DIR eq 'Q3') { if ($NO_SO eq 0) { Program $env 'qagamei386.so', @$FILESREF, @$SO_FILESREF; Install $env $INSTALL_DIR, 'qagamei386.so'; } if ($NO_VM eq 0) { Depends $vm_env 'qagame.qvm', '#qvmtools/q3lcc'; Depends $vm_env 'qagame.qvm', '#qvmtools/q3asm'; Program $vm_env 'qagame.qvm', @$FILESREF, @$VM_FILESREF; Install $vm_env $INSTALL_DIR . '/vm', 'qagame.qvm'; } } else { if ($NO_SO eq 0) { Program $ta_env 'qagamei386.so', @$FILESREF, @$SO_FILESREF; Install $ta_env $INSTALL_DIR, 'qagamei386.so'; } if ($NO_VM eq 0) { Depends $vm_env 'qagame.qvm', '#qvmtools/q3lcc'; Depends $vm_env 'qagame.qvm', '#qvmtools/q3asm'; Program $vm_ta_env 'qagame.qvm', @$FILESREF, @$VM_FILESREF; Install $vm_ta_env $INSTALL_DIR . '/vm', 'qagame.qvm'; } } ================================================ FILE: code/game/ai_chat.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_chat.c * * desc: Quake3 bot AI * * $Archive: /MissionPack/code/game/ai_chat.c $ * *****************************************************************************/ #include "g_local.h" #include "botlib.h" #include "be_aas.h" #include "be_ea.h" #include "be_ai_char.h" #include "be_ai_chat.h" #include "be_ai_gen.h" #include "be_ai_goal.h" #include "be_ai_move.h" #include "be_ai_weap.h" // #include "ai_main.h" #include "ai_dmq3.h" #include "ai_chat.h" #include "ai_cmd.h" #include "ai_dmnet.h" // #include "chars.h" //characteristics #include "inv.h" //indexes into the inventory #include "syn.h" //synonyms #include "match.h" //string matching types and vars // for the voice chats #ifdef MISSIONPACK // bk001205 #include "../../ui/menudef.h" #endif #define TIME_BETWEENCHATTING 25 /* ================== BotNumActivePlayers ================== */ int BotNumActivePlayers(void) { int i, num; char buf[MAX_INFO_STRING]; static int maxclients; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); num = 0; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // num++; } return num; } /* ================== BotIsFirstInRankings ================== */ int BotIsFirstInRankings(bot_state_t *bs) { int i, score; char buf[MAX_INFO_STRING]; static int maxclients; playerState_t ps; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); score = bs->cur_ps.persistant[PERS_SCORE]; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // BotAI_GetClientState(i, &ps); if (score < ps.persistant[PERS_SCORE]) return qfalse; } return qtrue; } /* ================== BotIsLastInRankings ================== */ int BotIsLastInRankings(bot_state_t *bs) { int i, score; char buf[MAX_INFO_STRING]; static int maxclients; playerState_t ps; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); score = bs->cur_ps.persistant[PERS_SCORE]; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // BotAI_GetClientState(i, &ps); if (score > ps.persistant[PERS_SCORE]) return qfalse; } return qtrue; } /* ================== BotFirstClientInRankings ================== */ char *BotFirstClientInRankings(void) { int i, bestscore, bestclient; char buf[MAX_INFO_STRING]; static char name[32]; static int maxclients; playerState_t ps; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); bestscore = -999999; bestclient = 0; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // BotAI_GetClientState(i, &ps); if (ps.persistant[PERS_SCORE] > bestscore) { bestscore = ps.persistant[PERS_SCORE]; bestclient = i; } } EasyClientName(bestclient, name, 32); return name; } /* ================== BotLastClientInRankings ================== */ char *BotLastClientInRankings(void) { int i, worstscore, bestclient; char buf[MAX_INFO_STRING]; static char name[32]; static int maxclients; playerState_t ps; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); worstscore = 999999; bestclient = 0; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // BotAI_GetClientState(i, &ps); if (ps.persistant[PERS_SCORE] < worstscore) { worstscore = ps.persistant[PERS_SCORE]; bestclient = i; } } EasyClientName(bestclient, name, 32); return name; } /* ================== BotRandomOpponentName ================== */ char *BotRandomOpponentName(bot_state_t *bs) { int i, count; char buf[MAX_INFO_STRING]; int opponents[MAX_CLIENTS], numopponents; static int maxclients; static char name[32]; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); numopponents = 0; opponents[0] = 0; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (i == bs->client) continue; // trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; //skip team mates if (BotSameTeam(bs, i)) continue; // opponents[numopponents] = i; numopponents++; } count = random() * numopponents; for (i = 0; i < numopponents; i++) { count--; if (count <= 0) { EasyClientName(opponents[i], name, sizeof(name)); return name; } } EasyClientName(opponents[0], name, sizeof(name)); return name; } /* ================== BotMapTitle ================== */ char *BotMapTitle(void) { char info[1024]; static char mapname[128]; trap_GetServerinfo(info, sizeof(info)); strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); mapname[sizeof(mapname)-1] = '\0'; return mapname; } /* ================== BotWeaponNameForMeansOfDeath ================== */ char *BotWeaponNameForMeansOfDeath(int mod) { switch(mod) { case MOD_SHOTGUN: return "Shotgun"; case MOD_GAUNTLET: return "Gauntlet"; case MOD_MACHINEGUN: return "Machinegun"; case MOD_GRENADE: case MOD_GRENADE_SPLASH: return "Grenade Launcher"; case MOD_ROCKET: case MOD_ROCKET_SPLASH: return "Rocket Launcher"; case MOD_PLASMA: case MOD_PLASMA_SPLASH: return "Plasmagun"; case MOD_RAILGUN: return "Railgun"; case MOD_LIGHTNING: return "Lightning Gun"; case MOD_BFG: case MOD_BFG_SPLASH: return "BFG10K"; #ifdef MISSIONPACK case MOD_NAIL: return "Nailgun"; case MOD_CHAINGUN: return "Chaingun"; case MOD_PROXIMITY_MINE: return "Proximity Launcher"; case MOD_KAMIKAZE: return "Kamikaze"; case MOD_JUICED: return "Prox mine"; #endif case MOD_GRAPPLE: return "Grapple"; default: return "[unknown weapon]"; } } /* ================== BotRandomWeaponName ================== */ char *BotRandomWeaponName(void) { int rnd; #ifdef MISSIONPACK rnd = random() * 11.9; #else rnd = random() * 8.9; #endif switch(rnd) { case 0: return "Gauntlet"; case 1: return "Shotgun"; case 2: return "Machinegun"; case 3: return "Grenade Launcher"; case 4: return "Rocket Launcher"; case 5: return "Plasmagun"; case 6: return "Railgun"; case 7: return "Lightning Gun"; #ifdef MISSIONPACK case 8: return "Nailgun"; case 9: return "Chaingun"; case 10: return "Proximity Launcher"; #endif default: return "BFG10K"; } } /* ================== BotVisibleEnemies ================== */ int BotVisibleEnemies(bot_state_t *bs) { float vis; int i; aas_entityinfo_t entinfo; for (i = 0; i < MAX_CLIENTS; i++) { if (i == bs->client) continue; // BotEntityInfo(i, &entinfo); // if (!entinfo.valid) continue; //if the enemy isn't dead and the enemy isn't the bot self if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; //if the enemy is invisible and not shooting if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { continue; } //if on the same team if (BotSameTeam(bs, i)) continue; //check if the enemy is visible vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); if (vis > 0) return qtrue; } return qfalse; } /* ================== BotValidChatPosition ================== */ int BotValidChatPosition(bot_state_t *bs) { vec3_t point, start, end, mins, maxs; bsp_trace_t trace; //if the bot is dead all positions are valid if (BotIsDead(bs)) return qtrue; //never start chatting with a powerup if (bs->inventory[INVENTORY_QUAD] || bs->inventory[INVENTORY_HASTE] || bs->inventory[INVENTORY_INVISIBILITY] || bs->inventory[INVENTORY_REGEN] || bs->inventory[INVENTORY_FLIGHT]) return qfalse; //must be on the ground //if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) return qfalse; //do not chat if in lava or slime VectorCopy(bs->origin, point); point[2] -= 24; if (trap_PointContents(point,bs->entitynum) & (CONTENTS_LAVA|CONTENTS_SLIME)) return qfalse; //do not chat if under water VectorCopy(bs->origin, point); point[2] += 32; if (trap_PointContents(point,bs->entitynum) & MASK_WATER) return qfalse; //must be standing on the world entity VectorCopy(bs->origin, start); VectorCopy(bs->origin, end); start[2] += 1; end[2] -= 10; trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); BotAI_Trace(&trace, start, mins, maxs, end, bs->client, MASK_SOLID); if (trace.ent != ENTITYNUM_WORLD) return qfalse; //the bot is in a position where it can chat return qtrue; } /* ================== BotChat_EnterGame ================== */ int BotChat_EnterGame(bot_state_t *bs) { char name[32]; float rnd; if (bot_nochat.integer) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; //don't chat in teamplay if (TeamPlayIsOn()) return qfalse; // don't chat in tournament mode if (gametype == GT_TOURNAMENT) return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; } if (BotNumActivePlayers() <= 1) return qfalse; if (!BotValidChatPosition(bs)) return qfalse; BotAI_BotInitialChat(bs, "game_enter", EasyClientName(bs->client, name, 32), // 0 BotRandomOpponentName(bs), // 1 "[invalid var]", // 2 "[invalid var]", // 3 BotMapTitle(), // 4 NULL); bs->lastchat_time = FloatTime(); bs->chatto = CHAT_ALL; return qtrue; } /* ================== BotChat_ExitGame ================== */ int BotChat_ExitGame(bot_state_t *bs) { char name[32]; float rnd; if (bot_nochat.integer) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; //don't chat in teamplay if (TeamPlayIsOn()) return qfalse; // don't chat in tournament mode if (gametype == GT_TOURNAMENT) return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; } if (BotNumActivePlayers() <= 1) return qfalse; // BotAI_BotInitialChat(bs, "game_exit", EasyClientName(bs->client, name, 32), // 0 BotRandomOpponentName(bs), // 1 "[invalid var]", // 2 "[invalid var]", // 3 BotMapTitle(), // 4 NULL); bs->lastchat_time = FloatTime(); bs->chatto = CHAT_ALL; return qtrue; } /* ================== BotChat_StartLevel ================== */ int BotChat_StartLevel(bot_state_t *bs) { char name[32]; float rnd; if (bot_nochat.integer) return qfalse; if (BotIsObserver(bs)) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; //don't chat in teamplay if (TeamPlayIsOn()) { trap_EA_Command(bs->client, "vtaunt"); return qfalse; } // don't chat in tournament mode if (gametype == GT_TOURNAMENT) return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; } if (BotNumActivePlayers() <= 1) return qfalse; BotAI_BotInitialChat(bs, "level_start", EasyClientName(bs->client, name, 32), // 0 NULL); bs->lastchat_time = FloatTime(); bs->chatto = CHAT_ALL; return qtrue; } /* ================== BotChat_EndLevel ================== */ int BotChat_EndLevel(bot_state_t *bs) { char name[32]; float rnd; if (bot_nochat.integer) return qfalse; if (BotIsObserver(bs)) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; // teamplay if (TeamPlayIsOn()) { if (BotIsFirstInRankings(bs)) { trap_EA_Command(bs->client, "vtaunt"); } return qtrue; } // don't chat in tournament mode if (gametype == GT_TOURNAMENT) return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; } if (BotNumActivePlayers() <= 1) return qfalse; // if (BotIsFirstInRankings(bs)) { BotAI_BotInitialChat(bs, "level_end_victory", EasyClientName(bs->client, name, 32), // 0 BotRandomOpponentName(bs), // 1 "[invalid var]", // 2 BotLastClientInRankings(), // 3 BotMapTitle(), // 4 NULL); } else if (BotIsLastInRankings(bs)) { BotAI_BotInitialChat(bs, "level_end_lose", EasyClientName(bs->client, name, 32), // 0 BotRandomOpponentName(bs), // 1 BotFirstClientInRankings(), // 2 "[invalid var]", // 3 BotMapTitle(), // 4 NULL); } else { BotAI_BotInitialChat(bs, "level_end", EasyClientName(bs->client, name, 32), // 0 BotRandomOpponentName(bs), // 1 BotFirstClientInRankings(), // 2 BotLastClientInRankings(), // 3 BotMapTitle(), // 4 NULL); } bs->lastchat_time = FloatTime(); bs->chatto = CHAT_ALL; return qtrue; } /* ================== BotChat_Death ================== */ int BotChat_Death(bot_state_t *bs) { char name[32]; float rnd; if (bot_nochat.integer) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_DEATH, 0, 1); // don't chat in tournament mode if (gametype == GT_TOURNAMENT) return qfalse; //if fast chatting is off if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; } if (BotNumActivePlayers() <= 1) return qfalse; // if (bs->lastkilledby >= 0 && bs->lastkilledby < MAX_CLIENTS) EasyClientName(bs->lastkilledby, name, 32); else strcpy(name, "[world]"); // if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledby)) { if (bs->lastkilledby == bs->client) return qfalse; BotAI_BotInitialChat(bs, "death_teammate", name, NULL); bs->chatto = CHAT_TEAM; } else { //teamplay if (TeamPlayIsOn()) { trap_EA_Command(bs->client, "vtaunt"); return qtrue; } // if (bs->botdeathtype == MOD_WATER) BotAI_BotInitialChat(bs, "death_drown", BotRandomOpponentName(bs), NULL); else if (bs->botdeathtype == MOD_SLIME) BotAI_BotInitialChat(bs, "death_slime", BotRandomOpponentName(bs), NULL); else if (bs->botdeathtype == MOD_LAVA) BotAI_BotInitialChat(bs, "death_lava", BotRandomOpponentName(bs), NULL); else if (bs->botdeathtype == MOD_FALLING) BotAI_BotInitialChat(bs, "death_cratered", BotRandomOpponentName(bs), NULL); else if (bs->botsuicide || //all other suicides by own weapon bs->botdeathtype == MOD_CRUSH || bs->botdeathtype == MOD_SUICIDE || bs->botdeathtype == MOD_TARGET_LASER || bs->botdeathtype == MOD_TRIGGER_HURT || bs->botdeathtype == MOD_UNKNOWN) BotAI_BotInitialChat(bs, "death_suicide", BotRandomOpponentName(bs), NULL); else if (bs->botdeathtype == MOD_TELEFRAG) BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); #ifdef MISSIONPACK else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "death_kamikaze")) BotAI_BotInitialChat(bs, "death_kamikaze", name, NULL); #endif else { if ((bs->botdeathtype == MOD_GAUNTLET || bs->botdeathtype == MOD_RAILGUN || bs->botdeathtype == MOD_BFG || bs->botdeathtype == MOD_BFG_SPLASH) && random() < 0.5) { if (bs->botdeathtype == MOD_GAUNTLET) BotAI_BotInitialChat(bs, "death_gauntlet", name, // 0 BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 NULL); else if (bs->botdeathtype == MOD_RAILGUN) BotAI_BotInitialChat(bs, "death_rail", name, // 0 BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 NULL); else BotAI_BotInitialChat(bs, "death_bfg", name, // 0 BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 NULL); } //choose between insult and praise else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { BotAI_BotInitialChat(bs, "death_insult", name, // 0 BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 NULL); } else { BotAI_BotInitialChat(bs, "death_praise", name, // 0 BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 NULL); } } bs->chatto = CHAT_ALL; } bs->lastchat_time = FloatTime(); return qtrue; } /* ================== BotChat_Kill ================== */ int BotChat_Kill(bot_state_t *bs) { char name[32]; float rnd; if (bot_nochat.integer) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); // don't chat in tournament mode if (gametype == GT_TOURNAMENT) return qfalse; //if fast chat is off if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; } if (bs->lastkilledplayer == bs->client) return qfalse; if (BotNumActivePlayers() <= 1) return qfalse; if (!BotValidChatPosition(bs)) return qfalse; // if (BotVisibleEnemies(bs)) return qfalse; // EasyClientName(bs->lastkilledplayer, name, 32); // bs->chatto = CHAT_ALL; if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledplayer)) { BotAI_BotInitialChat(bs, "kill_teammate", name, NULL); bs->chatto = CHAT_TEAM; } else { //don't chat in teamplay if (TeamPlayIsOn()) { trap_EA_Command(bs->client, "vtaunt"); return qfalse; // don't wait } // if (bs->enemydeathtype == MOD_GAUNTLET) { BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); } else if (bs->enemydeathtype == MOD_RAILGUN) { BotAI_BotInitialChat(bs, "kill_rail", name, NULL); } else if (bs->enemydeathtype == MOD_TELEFRAG) { BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); } #ifdef MISSIONPACK else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "kill_kamikaze")) BotAI_BotInitialChat(bs, "kill_kamikaze", name, NULL); #endif //choose between insult and praise else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { BotAI_BotInitialChat(bs, "kill_insult", name, NULL); } else { BotAI_BotInitialChat(bs, "kill_praise", name, NULL); } } bs->lastchat_time = FloatTime(); return qtrue; } /* ================== BotChat_EnemySuicide ================== */ int BotChat_EnemySuicide(bot_state_t *bs) { char name[32]; float rnd; if (bot_nochat.integer) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; if (BotNumActivePlayers() <= 1) return qfalse; // rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); //don't chat in teamplay if (TeamPlayIsOn()) return qfalse; // don't chat in tournament mode if (gametype == GT_TOURNAMENT) return qfalse; //if fast chat is off if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; } if (!BotValidChatPosition(bs)) return qfalse; // if (BotVisibleEnemies(bs)) return qfalse; // if (bs->enemy >= 0) EasyClientName(bs->enemy, name, 32); else strcpy(name, ""); BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); bs->lastchat_time = FloatTime(); bs->chatto = CHAT_ALL; return qtrue; } /* ================== BotChat_HitTalking ================== */ int BotChat_HitTalking(bot_state_t *bs) { char name[32], *weap; int lasthurt_client; float rnd; if (bot_nochat.integer) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; if (BotNumActivePlayers() <= 1) return qfalse; lasthurt_client = g_entities[bs->client].client->lasthurt_client; if (!lasthurt_client) return qfalse; if (lasthurt_client == bs->client) return qfalse; // if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; // rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITTALKING, 0, 1); //don't chat in teamplay if (TeamPlayIsOn()) return qfalse; // don't chat in tournament mode if (gametype == GT_TOURNAMENT) return qfalse; //if fast chat is off if (!bot_fastchat.integer) { if (random() > rnd * 0.5) return qfalse; } if (!BotValidChatPosition(bs)) return qfalse; // ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); // BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); bs->lastchat_time = FloatTime(); bs->chatto = CHAT_ALL; return qtrue; } /* ================== BotChat_HitNoDeath ================== */ int BotChat_HitNoDeath(bot_state_t *bs) { char name[32], *weap; float rnd; int lasthurt_client; aas_entityinfo_t entinfo; lasthurt_client = g_entities[bs->client].client->lasthurt_client; if (!lasthurt_client) return qfalse; if (lasthurt_client == bs->client) return qfalse; // if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; // if (bot_nochat.integer) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; if (BotNumActivePlayers() <= 1) return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNODEATH, 0, 1); //don't chat in teamplay if (TeamPlayIsOn()) return qfalse; // don't chat in tournament mode if (gametype == GT_TOURNAMENT) return qfalse; //if fast chat is off if (!bot_fastchat.integer) { if (random() > rnd * 0.5) return qfalse; } if (!BotValidChatPosition(bs)) return qfalse; // if (BotVisibleEnemies(bs)) return qfalse; // BotEntityInfo(bs->enemy, &entinfo); if (EntityIsShooting(&entinfo)) return qfalse; // ClientName(lasthurt_client, name, sizeof(name)); weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_mod); // BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); bs->lastchat_time = FloatTime(); bs->chatto = CHAT_ALL; return qtrue; } /* ================== BotChat_HitNoKill ================== */ int BotChat_HitNoKill(bot_state_t *bs) { char name[32], *weap; float rnd; aas_entityinfo_t entinfo; if (bot_nochat.integer) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; if (BotNumActivePlayers() <= 1) return qfalse; rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNOKILL, 0, 1); //don't chat in teamplay if (TeamPlayIsOn()) return qfalse; // don't chat in tournament mode if (gametype == GT_TOURNAMENT) return qfalse; //if fast chat is off if (!bot_fastchat.integer) { if (random() > rnd * 0.5) return qfalse; } if (!BotValidChatPosition(bs)) return qfalse; // if (BotVisibleEnemies(bs)) return qfalse; // BotEntityInfo(bs->enemy, &entinfo); if (EntityIsShooting(&entinfo)) return qfalse; // ClientName(bs->enemy, name, sizeof(name)); weap = BotWeaponNameForMeansOfDeath(g_entities[bs->enemy].client->lasthurt_mod); // BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); bs->lastchat_time = FloatTime(); bs->chatto = CHAT_ALL; return qtrue; } /* ================== BotChat_Random ================== */ int BotChat_Random(bot_state_t *bs) { float rnd; char name[32]; if (bot_nochat.integer) return qfalse; if (BotIsObserver(bs)) return qfalse; if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; // don't chat in tournament mode if (gametype == GT_TOURNAMENT) return qfalse; //don't chat when doing something important :) if (bs->ltgtype == LTG_TEAMHELP || bs->ltgtype == LTG_TEAMACCOMPANY || bs->ltgtype == LTG_RUSHBASE) return qfalse; // rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_RANDOM, 0, 1); if (random() > bs->thinktime * 0.1) return qfalse; if (!bot_fastchat.integer) { if (random() > rnd) return qfalse; if (random() > 0.25) return qfalse; } if (BotNumActivePlayers() <= 1) return qfalse; // if (!BotValidChatPosition(bs)) return qfalse; // if (BotVisibleEnemies(bs)) return qfalse; // if (bs->lastkilledplayer == bs->client) { strcpy(name, BotRandomOpponentName(bs)); } else { EasyClientName(bs->lastkilledplayer, name, sizeof(name)); } if (TeamPlayIsOn()) { trap_EA_Command(bs->client, "vtaunt"); return qfalse; // don't wait } // if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_MISC, 0, 1)) { BotAI_BotInitialChat(bs, "random_misc", BotRandomOpponentName(bs), // 0 name, // 1 "[invalid var]", // 2 "[invalid var]", // 3 BotMapTitle(), // 4 BotRandomWeaponName(), // 5 NULL); } else { BotAI_BotInitialChat(bs, "random_insult", BotRandomOpponentName(bs), // 0 name, // 1 "[invalid var]", // 2 "[invalid var]", // 3 BotMapTitle(), // 4 BotRandomWeaponName(), // 5 NULL); } bs->lastchat_time = FloatTime(); bs->chatto = CHAT_ALL; return qtrue; } /* ================== BotChatTime ================== */ float BotChatTime(bot_state_t *bs) { int cpm; cpm = trap_Characteristic_BInteger(bs->character, CHARACTERISTIC_CHAT_CPM, 1, 4000); return 2.0; //(float) trap_BotChatLength(bs->cs) * 30 / cpm; } /* ================== BotChatTest ================== */ void BotChatTest(bot_state_t *bs) { char name[32]; char *weap; int num, i; num = trap_BotNumInitialChats(bs->cs, "game_enter"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "game_enter", EasyClientName(bs->client, name, 32), // 0 BotRandomOpponentName(bs), // 1 "[invalid var]", // 2 "[invalid var]", // 3 BotMapTitle(), // 4 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "game_exit"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "game_exit", EasyClientName(bs->client, name, 32), // 0 BotRandomOpponentName(bs), // 1 "[invalid var]", // 2 "[invalid var]", // 3 BotMapTitle(), // 4 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "level_start"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "level_start", EasyClientName(bs->client, name, 32), // 0 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "level_end_victory"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "level_end_victory", EasyClientName(bs->client, name, 32), // 0 BotRandomOpponentName(bs), // 1 BotFirstClientInRankings(), // 2 BotLastClientInRankings(), // 3 BotMapTitle(), // 4 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "level_end_lose"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "level_end_lose", EasyClientName(bs->client, name, 32), // 0 BotRandomOpponentName(bs), // 1 BotFirstClientInRankings(), // 2 BotLastClientInRankings(), // 3 BotMapTitle(), // 4 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "level_end"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "level_end", EasyClientName(bs->client, name, 32), // 0 BotRandomOpponentName(bs), // 1 BotFirstClientInRankings(), // 2 BotLastClientInRankings(), // 3 BotMapTitle(), // 4 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } EasyClientName(bs->lastkilledby, name, sizeof(name)); num = trap_BotNumInitialChats(bs->cs, "death_drown"); for (i = 0; i < num; i++) { // BotAI_BotInitialChat(bs, "death_drown", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "death_slime"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "death_slime", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "death_lava"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "death_lava", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "death_cratered"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "death_cratered", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "death_suicide"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "death_suicide", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "death_telefrag"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "death_gauntlet"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "death_gauntlet", name, // 0 BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "death_rail"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "death_rail", name, // 0 BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "death_bfg"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "death_bfg", name, // 0 BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "death_insult"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "death_insult", name, // 0 BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "death_praise"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "death_praise", name, // 0 BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } // EasyClientName(bs->lastkilledplayer, name, 32); // num = trap_BotNumInitialChats(bs->cs, "kill_gauntlet"); for (i = 0; i < num; i++) { // BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "kill_rail"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "kill_rail", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "kill_telefrag"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "kill_insult"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "kill_insult", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "kill_praise"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "kill_praise", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "enemy_suicide"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); num = trap_BotNumInitialChats(bs->cs, "hit_talking"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "hit_nodeath"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "hit_nokill"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } // if (bs->lastkilledplayer == bs->client) { strcpy(name, BotRandomOpponentName(bs)); } else { EasyClientName(bs->lastkilledplayer, name, sizeof(name)); } // num = trap_BotNumInitialChats(bs->cs, "random_misc"); for (i = 0; i < num; i++) { // BotAI_BotInitialChat(bs, "random_misc", BotRandomOpponentName(bs), // 0 name, // 1 "[invalid var]", // 2 "[invalid var]", // 3 BotMapTitle(), // 4 BotRandomWeaponName(), // 5 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } num = trap_BotNumInitialChats(bs->cs, "random_insult"); for (i = 0; i < num; i++) { BotAI_BotInitialChat(bs, "random_insult", BotRandomOpponentName(bs), // 0 name, // 1 "[invalid var]", // 2 "[invalid var]", // 3 BotMapTitle(), // 4 BotRandomWeaponName(), // 5 NULL); trap_BotEnterChat(bs->cs, 0, CHAT_ALL); } } ================================================ FILE: code/game/ai_chat.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_chat.h * * desc: Quake3 bot AI * * $Archive: /source/code/botai/ai_chat.c $ * *****************************************************************************/ // int BotChat_EnterGame(bot_state_t *bs); // int BotChat_ExitGame(bot_state_t *bs); // int BotChat_StartLevel(bot_state_t *bs); // int BotChat_EndLevel(bot_state_t *bs); // int BotChat_HitTalking(bot_state_t *bs); // int BotChat_HitNoDeath(bot_state_t *bs); // int BotChat_HitNoKill(bot_state_t *bs); // int BotChat_Death(bot_state_t *bs); // int BotChat_Kill(bot_state_t *bs); // int BotChat_EnemySuicide(bot_state_t *bs); // int BotChat_Random(bot_state_t *bs); // time the selected chat takes to type in float BotChatTime(bot_state_t *bs); // returns true if the bot can chat at the current position int BotValidChatPosition(bot_state_t *bs); // test the initial bot chats void BotChatTest(bot_state_t *bs); ================================================ FILE: code/game/ai_cmd.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_cmd.c * * desc: Quake3 bot AI * * $Archive: /MissionPack/code/game/ai_cmd.c $ * *****************************************************************************/ #include "g_local.h" #include "botlib.h" #include "be_aas.h" #include "be_ea.h" #include "be_ai_char.h" #include "be_ai_chat.h" #include "be_ai_gen.h" #include "be_ai_goal.h" #include "be_ai_move.h" #include "be_ai_weap.h" // #include "ai_main.h" #include "ai_dmq3.h" #include "ai_chat.h" #include "ai_cmd.h" #include "ai_dmnet.h" #include "ai_team.h" // #include "chars.h" //characteristics #include "inv.h" //indexes into the inventory #include "syn.h" //synonyms #include "match.h" //string matching types and vars // for the voice chats #include "../../ui/menudef.h" int notleader[MAX_CLIENTS]; #ifdef DEBUG /* ================== BotPrintTeamGoal ================== */ void BotPrintTeamGoal(bot_state_t *bs) { char netname[MAX_NETNAME]; float t; ClientName(bs->client, netname, sizeof(netname)); t = bs->teamgoal_time - FloatTime(); switch(bs->ltgtype) { case LTG_TEAMHELP: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t); break; } case LTG_TEAMACCOMPANY: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t); break; } case LTG_GETFLAG: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t); break; } case LTG_RUSHBASE: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t); break; } case LTG_RETURNFLAG: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t); break; } #ifdef MISSIONPACK case LTG_ATTACKENEMYBASE: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna attack the enemy base for %1.0f secs\n", netname, t); break; } case LTG_HARVEST: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna harvest for %1.0f secs\n", netname, t); break; } #endif case LTG_DEFENDKEYAREA: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t); break; } case LTG_GETITEM: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t); break; } case LTG_KILL: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t); break; } case LTG_CAMP: case LTG_CAMPORDER: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t); break; } case LTG_PATROL: { BotAI_Print(PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t); break; } default: { if (bs->ctfroam_time > FloatTime()) { t = bs->ctfroam_time - FloatTime(); BotAI_Print(PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t); } else { BotAI_Print(PRT_MESSAGE, "%s: I've got a regular goal\n", netname); } } } } #endif //DEBUG /* ================== BotGetItemTeamGoal FIXME: add stuff like "upper rocket launcher" "the rl near the railgun", "lower grenade launcher" etc. ================== */ int BotGetItemTeamGoal(char *goalname, bot_goal_t *goal) { int i; if (!strlen(goalname)) return qfalse; i = -1; do { i = trap_BotGetLevelItemGoal(i, goalname, goal); if (i > 0) { //do NOT defend dropped items if (goal->flags & GFL_DROPPED) continue; return qtrue; } } while(i > 0); return qfalse; } /* ================== BotGetMessageTeamGoal ================== */ int BotGetMessageTeamGoal(bot_state_t *bs, char *goalname, bot_goal_t *goal) { bot_waypoint_t *cp; if (BotGetItemTeamGoal(goalname, goal)) return qtrue; cp = BotFindWayPoint(bs->checkpoints, goalname); if (cp) { memcpy(goal, &cp->goal, sizeof(bot_goal_t)); return qtrue; } return qfalse; } /* ================== BotGetTime ================== */ float BotGetTime(bot_match_t *match) { bot_match_t timematch; char timestring[MAX_MESSAGE_SIZE]; float t; //if the matched string has a time if (match->subtype & ST_TIME) { //get the time string trap_BotMatchVariable(match, TIME, timestring, MAX_MESSAGE_SIZE); //match it to find out if the time is in seconds or minutes if (trap_BotFindMatch(timestring, &timematch, MTCONTEXT_TIME)) { if (timematch.type == MSG_FOREVER) { t = 99999999.0f; } else if (timematch.type == MSG_FORAWHILE) { t = 10 * 60; // 10 minutes } else if (timematch.type == MSG_FORALONGTIME) { t = 30 * 60; // 30 minutes } else { trap_BotMatchVariable(&timematch, TIME, timestring, MAX_MESSAGE_SIZE); if (timematch.type == MSG_MINUTES) t = atof(timestring) * 60; else if (timematch.type == MSG_SECONDS) t = atof(timestring); else t = 0; } //if there's a valid time if (t > 0) return FloatTime() + t; } } return 0; } /* ================== FindClientByName ================== */ int FindClientByName(char *name) { int i; char buf[MAX_INFO_STRING]; static int maxclients; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { ClientName(i, buf, sizeof(buf)); if (!Q_stricmp(buf, name)) return i; } for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { ClientName(i, buf, sizeof(buf)); if (stristr(buf, name)) return i; } return -1; } /* ================== FindEnemyByName ================== */ int FindEnemyByName(bot_state_t *bs, char *name) { int i; char buf[MAX_INFO_STRING]; static int maxclients; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (BotSameTeam(bs, i)) continue; ClientName(i, buf, sizeof(buf)); if (!Q_stricmp(buf, name)) return i; } for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (BotSameTeam(bs, i)) continue; ClientName(i, buf, sizeof(buf)); if (stristr(buf, name)) return i; } return -1; } /* ================== NumPlayersOnSameTeam ================== */ int NumPlayersOnSameTeam(bot_state_t *bs) { int i, num; char buf[MAX_INFO_STRING]; static int maxclients; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); num = 0; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, MAX_INFO_STRING); if (strlen(buf)) { if (BotSameTeam(bs, i+1)) num++; } } return num; } /* ================== TeamPlayIsOn ================== */ int BotGetPatrolWaypoints(bot_state_t *bs, bot_match_t *match) { char keyarea[MAX_MESSAGE_SIZE]; int patrolflags; bot_waypoint_t *wp, *newwp, *newpatrolpoints; bot_match_t keyareamatch; bot_goal_t goal; newpatrolpoints = NULL; patrolflags = 0; // trap_BotMatchVariable(match, KEYAREA, keyarea, MAX_MESSAGE_SIZE); // while(1) { if (!trap_BotFindMatch(keyarea, &keyareamatch, MTCONTEXT_PATROLKEYAREA)) { trap_EA_SayTeam(bs->client, "what do you say?"); BotFreeWaypoints(newpatrolpoints); bs->patrolpoints = NULL; return qfalse; } trap_BotMatchVariable(&keyareamatch, KEYAREA, keyarea, MAX_MESSAGE_SIZE); if (!BotGetMessageTeamGoal(bs, keyarea, &goal)) { //BotAI_BotInitialChat(bs, "cannotfind", keyarea, NULL); //trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotFreeWaypoints(newpatrolpoints); bs->patrolpoints = NULL; return qfalse; } //create a new waypoint newwp = BotCreateWayPoint(keyarea, goal.origin, goal.areanum); if (!newwp) break; //add the waypoint to the patrol points newwp->next = NULL; for (wp = newpatrolpoints; wp && wp->next; wp = wp->next); if (!wp) { newpatrolpoints = newwp; newwp->prev = NULL; } else { wp->next = newwp; newwp->prev = wp; } // if (keyareamatch.subtype & ST_BACK) { patrolflags = PATROL_LOOP; break; } else if (keyareamatch.subtype & ST_REVERSE) { patrolflags = PATROL_REVERSE; break; } else if (keyareamatch.subtype & ST_MORE) { trap_BotMatchVariable(&keyareamatch, MORE, keyarea, MAX_MESSAGE_SIZE); } else { break; } } // if (!newpatrolpoints || !newpatrolpoints->next) { trap_EA_SayTeam(bs->client, "I need more key points to patrol\n"); BotFreeWaypoints(newpatrolpoints); newpatrolpoints = NULL; return qfalse; } // BotFreeWaypoints(bs->patrolpoints); bs->patrolpoints = newpatrolpoints; // bs->curpatrolpoint = bs->patrolpoints; bs->patrolflags = patrolflags; // return qtrue; } /* ================== BotAddressedToBot ================== */ int BotAddressedToBot(bot_state_t *bs, bot_match_t *match) { char addressedto[MAX_MESSAGE_SIZE]; char netname[MAX_MESSAGE_SIZE]; char name[MAX_MESSAGE_SIZE]; char botname[128]; int client; bot_match_t addresseematch; trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientOnSameTeamFromName(bs, netname); if (client < 0) return qfalse; //if the message is addressed to someone if (match->subtype & ST_ADDRESSED) { trap_BotMatchVariable(match, ADDRESSEE, addressedto, sizeof(addressedto)); //the name of this bot ClientName(bs->client, botname, 128); // while(trap_BotFindMatch(addressedto, &addresseematch, MTCONTEXT_ADDRESSEE)) { if (addresseematch.type == MSG_EVERYONE) { return qtrue; } else if (addresseematch.type == MSG_MULTIPLENAMES) { trap_BotMatchVariable(&addresseematch, TEAMMATE, name, sizeof(name)); if (strlen(name)) { if (stristr(botname, name)) return qtrue; if (stristr(bs->subteam, name)) return qtrue; } trap_BotMatchVariable(&addresseematch, MORE, addressedto, MAX_MESSAGE_SIZE); } else { trap_BotMatchVariable(&addresseematch, TEAMMATE, name, MAX_MESSAGE_SIZE); if (strlen(name)) { if (stristr(botname, name)) return qtrue; if (stristr(bs->subteam, name)) return qtrue; } break; } } //Com_sprintf(buf, sizeof(buf), "not addressed to me but %s", addressedto); //trap_EA_Say(bs->client, buf); return qfalse; } else { bot_match_t tellmatch; tellmatch.type = 0; //if this message wasn't directed solely to this bot if (!trap_BotFindMatch(match->string, &tellmatch, MTCONTEXT_REPLYCHAT) || tellmatch.type != MSG_CHATTELL) { //make sure not everyone reacts to this message if (random() > (float ) 1.0 / (NumPlayersOnSameTeam(bs)-1)) return qfalse; } } return qtrue; } /* ================== BotGPSToPosition ================== */ int BotGPSToPosition(char *buf, vec3_t position) { int i, j = 0; int num, sign; for (i = 0; i < 3; i++) { num = 0; while(buf[j] == ' ') j++; if (buf[j] == '-') { j++; sign = -1; } else { sign = 1; } while (buf[j]) { if (buf[j] >= '0' && buf[j] <= '9') { num = num * 10 + buf[j] - '0'; j++; } else { j++; break; } } BotAI_Print(PRT_MESSAGE, "%d\n", sign * num); position[i] = (float) sign * num; } return qtrue; } /* ================== BotMatch_HelpAccompany ================== */ void BotMatch_HelpAccompany(bot_state_t *bs, bot_match_t *match) { int client, other, areanum; char teammate[MAX_MESSAGE_SIZE]; char netname[MAX_MESSAGE_SIZE]; char itemname[MAX_MESSAGE_SIZE]; bot_match_t teammatematch; aas_entityinfo_t entinfo; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; //get the team mate name trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); //get the client to help if (trap_BotFindMatch(teammate, &teammatematch, MTCONTEXT_TEAMMATE) && //if someone asks for him or herself teammatematch.type == MSG_ME) { //get the netname trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientFromName(netname); other = qfalse; } else { //asked for someone else client = FindClientByName(teammate); //if this is the bot self if (client == bs->client) { other = qfalse; } else if (!BotSameTeam(bs, client)) { //FIXME: say "I don't help the enemy" return; } else { other = qtrue; } } //if the bot doesn't know who to help (FindClientByName returned -1) if (client < 0) { if (other) BotAI_BotInitialChat(bs, "whois", teammate, NULL); else BotAI_BotInitialChat(bs, "whois", netname, NULL); client = ClientFromName(netname); trap_BotEnterChat(bs->cs, client, CHAT_TELL); return; } //don't help or accompany yourself if (client == bs->client) { return; } // bs->teamgoal.entitynum = -1; BotEntityInfo(client, &entinfo); //if info is valid (in PVS) if (entinfo.valid) { areanum = BotPointAreaNum(entinfo.origin); if (areanum) {// && trap_AAS_AreaReachability(areanum)) { bs->teamgoal.entitynum = client; bs->teamgoal.areanum = areanum; VectorCopy(entinfo.origin, bs->teamgoal.origin); VectorSet(bs->teamgoal.mins, -8, -8, -8); VectorSet(bs->teamgoal.maxs, 8, 8, 8); } } //if no teamgoal yet if (bs->teamgoal.entitynum < 0) { //if near an item if (match->subtype & ST_NEARITEM) { //get the match variable trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); // if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); return; } } } // if (bs->teamgoal.entitynum < 0) { if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); client = ClientFromName(netname); trap_BotEnterChat(bs->cs, client, CHAT_TEAM); return; } //the team mate bs->teammate = client; // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); // client = ClientFromName(netname); //the team mate who ordered bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //last time the team mate was assumed visible bs->teammatevisible_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //get the team goal time bs->teamgoal_time = BotGetTime(match); //set the ltg type if (match->type == MSG_HELP) { bs->ltgtype = LTG_TEAMHELP; if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_HELP_TIME; } else { bs->ltgtype = LTG_TEAMACCOMPANY; if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; bs->formation_dist = 3.5 * 32; //3.5 meter bs->arrive_time = 0; // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); } #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotMatch_DefendKeyArea ================== */ void BotMatch_DefendKeyArea(bot_state_t *bs, bot_match_t *match) { char itemname[MAX_MESSAGE_SIZE]; char netname[MAX_MESSAGE_SIZE]; int client; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; //get the match variable trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); // if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); return; } // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); // client = ClientFromName(netname); //the team mate who ordered bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_DEFENDKEYAREA; //get the team goal time bs->teamgoal_time = BotGetTime(match); //set the team goal time if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; //away from defending bs->defendaway_time = 0; // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotMatch_GetItem ================== */ void BotMatch_GetItem(bot_state_t *bs, bot_match_t *match) { char itemname[MAX_MESSAGE_SIZE]; char netname[MAX_MESSAGE_SIZE]; int client; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; //get the match variable trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); // if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); return; } trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientOnSameTeamFromName(bs, netname); // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_GETITEM; //set the team goal time bs->teamgoal_time = FloatTime() + TEAM_GETITEM_TIME; // BotSetTeamStatus(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotMatch_Camp ================== */ void BotMatch_Camp(bot_state_t *bs, bot_match_t *match) { int client, areanum; char netname[MAX_MESSAGE_SIZE]; char itemname[MAX_MESSAGE_SIZE]; aas_entityinfo_t entinfo; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); //asked for someone else client = FindClientByName(netname); //if there's no valid client with this name if (client < 0) { BotAI_BotInitialChat(bs, "whois", netname, NULL); trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); return; } //get the match variable trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); //in CTF it could be the base if (match->subtype & ST_THERE) { //camp at the spot the bot is currently standing bs->teamgoal.entitynum = bs->entitynum; bs->teamgoal.areanum = bs->areanum; VectorCopy(bs->origin, bs->teamgoal.origin); VectorSet(bs->teamgoal.mins, -8, -8, -8); VectorSet(bs->teamgoal.maxs, 8, 8, 8); } else if (match->subtype & ST_HERE) { //if this is the bot self if (client == bs->client) return; // bs->teamgoal.entitynum = -1; BotEntityInfo(client, &entinfo); //if info is valid (in PVS) if (entinfo.valid) { areanum = BotPointAreaNum(entinfo.origin); if (areanum) {// && trap_AAS_AreaReachability(areanum)) { //NOTE: just assume the bot knows where the person is //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { bs->teamgoal.entitynum = client; bs->teamgoal.areanum = areanum; VectorCopy(entinfo.origin, bs->teamgoal.origin); VectorSet(bs->teamgoal.mins, -8, -8, -8); VectorSet(bs->teamgoal.maxs, 8, 8, 8); //} } } //if the other is not visible if (bs->teamgoal.entitynum < 0) { BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); client = ClientFromName(netname); trap_BotEnterChat(bs->cs, client, CHAT_TELL); return; } } else if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); //client = ClientFromName(netname); //trap_BotEnterChat(bs->cs, client, CHAT_TELL); return; } // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_CAMPORDER; //get the team goal time bs->teamgoal_time = BotGetTime(match); //set the team goal time if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; //not arrived yet bs->arrive_time = 0; // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotMatch_Patrol ================== */ void BotMatch_Patrol(bot_state_t *bs, bot_match_t *match) { char netname[MAX_MESSAGE_SIZE]; int client; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; //get the patrol waypoints if (!BotGetPatrolWaypoints(bs, match)) return; // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); // client = FindClientByName(netname); // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_PATROL; //get the team goal time bs->teamgoal_time = BotGetTime(match); //set the team goal time if not set already if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_PATROL_TIME; // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotMatch_GetFlag ================== */ void BotMatch_GetFlag(bot_state_t *bs, bot_match_t *match) { char netname[MAX_MESSAGE_SIZE]; int client; if (gametype == GT_CTF) { if (!ctf_redflag.areanum || !ctf_blueflag.areanum) return; } #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) return; } #endif else { return; } //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); // client = FindClientByName(netname); // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_GETFLAG; //set the team goal time bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; // get an alternate route in ctf if (gametype == GT_CTF) { //get an alternative route goal towards the enemy base BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); } // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotMatch_AttackEnemyBase ================== */ void BotMatch_AttackEnemyBase(bot_state_t *bs, bot_match_t *match) { char netname[MAX_MESSAGE_SIZE]; int client; if (gametype == GT_CTF) { BotMatch_GetFlag(bs, match); } #ifdef MISSIONPACK else if (gametype == GT_1FCTF || gametype == GT_OBELISK || gametype == GT_HARVESTER) { if (!redobelisk.areanum || !blueobelisk.areanum) return; } #endif else { return; } //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); // client = FindClientByName(netname); // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_ATTACKENEMYBASE; //set the team goal time bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; bs->attackaway_time = 0; // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } #ifdef MISSIONPACK /* ================== BotMatch_Harvest ================== */ void BotMatch_Harvest(bot_state_t *bs, bot_match_t *match) { char netname[MAX_MESSAGE_SIZE]; int client; if (gametype == GT_HARVESTER) { if (!neutralobelisk.areanum || !redobelisk.areanum || !blueobelisk.areanum) return; } else { return; } //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); // client = FindClientByName(netname); // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_HARVEST; //set the team goal time bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; bs->harvestaway_time = 0; // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } #endif /* ================== BotMatch_RushBase ================== */ void BotMatch_RushBase(bot_state_t *bs, bot_match_t *match) { char netname[MAX_MESSAGE_SIZE]; int client; if (gametype == GT_CTF) { if (!ctf_redflag.areanum || !ctf_blueflag.areanum) return; } #ifdef MISSIONPACK else if (gametype == GT_1FCTF || gametype == GT_HARVESTER) { if (!redobelisk.areanum || !blueobelisk.areanum) return; } #endif else { return; } //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); // client = FindClientByName(netname); // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_RUSHBASE; //set the team goal time bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; bs->rushbaseaway_time = 0; // BotSetTeamStatus(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotMatch_TaskPreference ================== */ void BotMatch_TaskPreference(bot_state_t *bs, bot_match_t *match) { char netname[MAX_NETNAME]; char teammatename[MAX_MESSAGE_SIZE]; int teammate, preference; ClientName(bs->client, netname, sizeof(netname)); if (Q_stricmp(netname, bs->teamleader) != 0) return; trap_BotMatchVariable(match, NETNAME, teammatename, sizeof(teammatename)); teammate = ClientFromName(teammatename); if (teammate < 0) return; preference = BotGetTeamMateTaskPreference(bs, teammate); switch(match->subtype) { case ST_DEFENDER: { preference &= ~TEAMTP_ATTACKER; preference |= TEAMTP_DEFENDER; break; } case ST_ATTACKER: { preference &= ~TEAMTP_DEFENDER; preference |= TEAMTP_ATTACKER; break; } case ST_ROAMER: { preference &= ~(TEAMTP_ATTACKER|TEAMTP_DEFENDER); break; } } BotSetTeamMateTaskPreference(bs, teammate, preference); // EasyClientName(teammate, teammatename, sizeof(teammatename)); BotAI_BotInitialChat(bs, "keepinmind", teammatename, NULL); trap_BotEnterChat(bs->cs, teammate, CHAT_TELL); BotVoiceChatOnly(bs, teammate, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); } /* ================== BotMatch_ReturnFlag ================== */ void BotMatch_ReturnFlag(bot_state_t *bs, bot_match_t *match) { char netname[MAX_MESSAGE_SIZE]; int client; //if not in CTF mode if ( gametype != GT_CTF #ifdef MISSIONPACK && gametype != GT_1FCTF #endif ) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); // client = FindClientByName(netname); // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_RETURNFLAG; //set the team goal time bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; bs->rushbaseaway_time = 0; // BotSetTeamStatus(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotMatch_JoinSubteam ================== */ void BotMatch_JoinSubteam(bot_state_t *bs, bot_match_t *match) { char teammate[MAX_MESSAGE_SIZE]; char netname[MAX_MESSAGE_SIZE]; int client; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; //get the sub team name trap_BotMatchVariable(match, TEAMNAME, teammate, sizeof(teammate)); //set the sub team name strncpy(bs->subteam, teammate, 32); bs->subteam[31] = '\0'; // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); BotAI_BotInitialChat(bs, "joinedteam", teammate, NULL); client = ClientFromName(netname); trap_BotEnterChat(bs->cs, client, CHAT_TELL); } /* ================== BotMatch_LeaveSubteam ================== */ void BotMatch_LeaveSubteam(bot_state_t *bs, bot_match_t *match) { char netname[MAX_MESSAGE_SIZE]; int client; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // if (strlen(bs->subteam)) { BotAI_BotInitialChat(bs, "leftteam", bs->subteam, NULL); trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientFromName(netname); trap_BotEnterChat(bs->cs, client, CHAT_TELL); } //end if strcpy(bs->subteam, ""); } /* ================== BotMatch_LeaveSubteam ================== */ void BotMatch_WhichTeam(bot_state_t *bs, bot_match_t *match) { if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // if (strlen(bs->subteam)) { BotAI_BotInitialChat(bs, "inteam", bs->subteam, NULL); } else { BotAI_BotInitialChat(bs, "noteam", NULL); } trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); } /* ================== BotMatch_CheckPoint ================== */ void BotMatch_CheckPoint(bot_state_t *bs, bot_match_t *match) { int areanum, client; char buf[MAX_MESSAGE_SIZE]; char netname[MAX_MESSAGE_SIZE]; vec3_t position; bot_waypoint_t *cp; if (!TeamPlayIsOn()) return; // trap_BotMatchVariable(match, POSITION, buf, MAX_MESSAGE_SIZE); VectorClear(position); // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientFromName(netname); //BotGPSToPosition(buf, position); sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]); position[2] += 0.5; areanum = BotPointAreaNum(position); if (!areanum) { if (BotAddressedToBot(bs, match)) { BotAI_BotInitialChat(bs, "checkpoint_invalid", NULL); trap_BotEnterChat(bs->cs, client, CHAT_TELL); } return; } // trap_BotMatchVariable(match, NAME, buf, MAX_MESSAGE_SIZE); //check if there already exists a checkpoint with this name cp = BotFindWayPoint(bs->checkpoints, buf); if (cp) { if (cp->next) cp->next->prev = cp->prev; if (cp->prev) cp->prev->next = cp->next; else bs->checkpoints = cp->next; cp->inuse = qfalse; } //create a new check point cp = BotCreateWayPoint(buf, position, areanum); //add the check point to the bot's known chech points cp->next = bs->checkpoints; if (bs->checkpoints) bs->checkpoints->prev = cp; bs->checkpoints = cp; // if (BotAddressedToBot(bs, match)) { Com_sprintf(buf, sizeof(buf), "%1.0f %1.0f %1.0f", cp->goal.origin[0], cp->goal.origin[1], cp->goal.origin[2]); BotAI_BotInitialChat(bs, "checkpoint_confirm", cp->name, buf, NULL); trap_BotEnterChat(bs->cs, client, CHAT_TELL); } } /* ================== BotMatch_FormationSpace ================== */ void BotMatch_FormationSpace(bot_state_t *bs, bot_match_t *match) { char buf[MAX_MESSAGE_SIZE]; float space; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // trap_BotMatchVariable(match, NUMBER, buf, MAX_MESSAGE_SIZE); //if it's the distance in feet if (match->subtype & ST_FEET) space = 0.3048 * 32 * atof(buf); //else it's in meters else space = 32 * atof(buf); //check if the formation intervening space is valid if (space < 48 || space > 500) space = 100; bs->formation_dist = space; } /* ================== BotMatch_Dismiss ================== */ void BotMatch_Dismiss(bot_state_t *bs, bot_match_t *match) { char netname[MAX_MESSAGE_SIZE]; int client; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientFromName(netname); // bs->decisionmaker = client; // bs->ltgtype = 0; bs->lead_time = 0; bs->lastgoal_ltgtype = 0; // BotAI_BotInitialChat(bs, "dismissed", NULL); trap_BotEnterChat(bs->cs, client, CHAT_TELL); } /* ================== BotMatch_Suicide ================== */ void BotMatch_Suicide(bot_state_t *bs, bot_match_t *match) { char netname[MAX_MESSAGE_SIZE]; int client; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // trap_EA_Command(bs->client, "kill"); // trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientFromName(netname); // BotVoiceChat(bs, client, VOICECHAT_TAUNT); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); } /* ================== BotMatch_StartTeamLeaderShip ================== */ void BotMatch_StartTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { int client; char teammate[MAX_MESSAGE_SIZE]; if (!TeamPlayIsOn()) return; //if chats for him or herself if (match->subtype & ST_I) { //get the team mate that will be the team leader trap_BotMatchVariable(match, NETNAME, teammate, sizeof(teammate)); strncpy(bs->teamleader, teammate, sizeof(bs->teamleader)); bs->teamleader[sizeof(bs->teamleader)] = '\0'; } //chats for someone else else { //get the team mate that will be the team leader trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); client = FindClientByName(teammate); if (client >= 0) ClientName(client, bs->teamleader, sizeof(bs->teamleader)); } } /* ================== BotMatch_StopTeamLeaderShip ================== */ void BotMatch_StopTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { int client; char teammate[MAX_MESSAGE_SIZE]; char netname[MAX_MESSAGE_SIZE]; if (!TeamPlayIsOn()) return; //get the team mate that stops being the team leader trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); //if chats for him or herself if (match->subtype & ST_I) { trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = FindClientByName(netname); } //chats for someone else else { client = FindClientByName(teammate); } //end else if (client >= 0) { if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { bs->teamleader[0] = '\0'; notleader[client] = qtrue; } } } /* ================== BotMatch_WhoIsTeamLeader ================== */ void BotMatch_WhoIsTeamLeader(bot_state_t *bs, bot_match_t *match) { char netname[MAX_MESSAGE_SIZE]; if (!TeamPlayIsOn()) return; ClientName(bs->client, netname, sizeof(netname)); //if this bot IS the team leader if (!Q_stricmp(netname, bs->teamleader)) { trap_EA_SayTeam(bs->client, "I'm the team leader\n"); } } /* ================== BotMatch_WhatAreYouDoing ================== */ void BotMatch_WhatAreYouDoing(bot_state_t *bs, bot_match_t *match) { char netname[MAX_MESSAGE_SIZE]; char goalname[MAX_MESSAGE_SIZE]; int client; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; // switch(bs->ltgtype) { case LTG_TEAMHELP: { EasyClientName(bs->teammate, netname, sizeof(netname)); BotAI_BotInitialChat(bs, "helping", netname, NULL); break; } case LTG_TEAMACCOMPANY: { EasyClientName(bs->teammate, netname, sizeof(netname)); BotAI_BotInitialChat(bs, "accompanying", netname, NULL); break; } case LTG_DEFENDKEYAREA: { trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); BotAI_BotInitialChat(bs, "defending", goalname, NULL); break; } case LTG_GETITEM: { trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); BotAI_BotInitialChat(bs, "gettingitem", goalname, NULL); break; } case LTG_KILL: { ClientName(bs->teamgoal.entitynum, netname, sizeof(netname)); BotAI_BotInitialChat(bs, "killing", netname, NULL); break; } case LTG_CAMP: case LTG_CAMPORDER: { BotAI_BotInitialChat(bs, "camping", NULL); break; } case LTG_PATROL: { BotAI_BotInitialChat(bs, "patrolling", NULL); break; } case LTG_GETFLAG: { BotAI_BotInitialChat(bs, "capturingflag", NULL); break; } case LTG_RUSHBASE: { BotAI_BotInitialChat(bs, "rushingbase", NULL); break; } case LTG_RETURNFLAG: { BotAI_BotInitialChat(bs, "returningflag", NULL); break; } #ifdef MISSIONPACK case LTG_ATTACKENEMYBASE: { BotAI_BotInitialChat(bs, "attackingenemybase", NULL); break; } case LTG_HARVEST: { BotAI_BotInitialChat(bs, "harvesting", NULL); break; } #endif default: { BotAI_BotInitialChat(bs, "roaming", NULL); break; } } //chat what the bot is doing trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientFromName(netname); trap_BotEnterChat(bs->cs, client, CHAT_TELL); } /* ================== BotMatch_WhatIsMyCommand ================== */ void BotMatch_WhatIsMyCommand(bot_state_t *bs, bot_match_t *match) { char netname[MAX_NETNAME]; ClientName(bs->client, netname, sizeof(netname)); if (Q_stricmp(netname, bs->teamleader) != 0) return; bs->forceorders = qtrue; } /* ================== BotNearestVisibleItem ================== */ float BotNearestVisibleItem(bot_state_t *bs, char *itemname, bot_goal_t *goal) { int i; char name[64]; bot_goal_t tmpgoal; float dist, bestdist; vec3_t dir; bsp_trace_t trace; bestdist = 999999; i = -1; do { i = trap_BotGetLevelItemGoal(i, itemname, &tmpgoal); trap_BotGoalName(tmpgoal.number, name, sizeof(name)); if (Q_stricmp(itemname, name) != 0) continue; VectorSubtract(tmpgoal.origin, bs->origin, dir); dist = VectorLength(dir); if (dist < bestdist) { //trace from start to end BotAI_Trace(&trace, bs->eye, NULL, NULL, tmpgoal.origin, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); if (trace.fraction >= 1.0) { bestdist = dist; memcpy(goal, &tmpgoal, sizeof(bot_goal_t)); } } } while(i > 0); return bestdist; } /* ================== BotMatch_WhereAreYou ================== */ void BotMatch_WhereAreYou(bot_state_t *bs, bot_match_t *match) { float dist, bestdist; int i, bestitem, redtt, bluett, client; bot_goal_t goal; char netname[MAX_MESSAGE_SIZE]; char *nearbyitems[] = { "Shotgun", "Grenade Launcher", "Rocket Launcher", "Plasmagun", "Railgun", "Lightning Gun", "BFG10K", "Quad Damage", "Regeneration", "Battle Suit", "Speed", "Invisibility", "Flight", "Armor", "Heavy Armor", "Red Flag", "Blue Flag", #ifdef MISSIONPACK "Nailgun", "Prox Launcher", "Chaingun", "Scout", "Guard", "Doubler", "Ammo Regen", "Neutral Flag", "Red Obelisk", "Blue Obelisk", "Neutral Obelisk", #endif NULL }; // if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; bestitem = -1; bestdist = 999999; for (i = 0; nearbyitems[i]; i++) { dist = BotNearestVisibleItem(bs, nearbyitems[i], &goal); if (dist < bestdist) { bestdist = dist; bestitem = i; } } if (bestitem != -1) { if (gametype == GT_CTF #ifdef MISSIONPACK || gametype == GT_1FCTF #endif ) { redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_redflag.areanum, TFL_DEFAULT); bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_blueflag.areanum, TFL_DEFAULT); if (redtt < (redtt + bluett) * 0.4) { BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); } else if (bluett < (redtt + bluett) * 0.4) { BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); } else { BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); } } #ifdef MISSIONPACK else if (gametype == GT_OBELISK || gametype == GT_HARVESTER) { redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, redobelisk.areanum, TFL_DEFAULT); bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, blueobelisk.areanum, TFL_DEFAULT); if (redtt < (redtt + bluett) * 0.4) { BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); } else if (bluett < (redtt + bluett) * 0.4) { BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); } else { BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); } } #endif else { BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); } trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientFromName(netname); trap_BotEnterChat(bs->cs, client, CHAT_TELL); } } /* ================== BotMatch_LeadTheWay ================== */ void BotMatch_LeadTheWay(bot_state_t *bs, bot_match_t *match) { aas_entityinfo_t entinfo; char netname[MAX_MESSAGE_SIZE], teammate[MAX_MESSAGE_SIZE]; int client, areanum, other; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; //if someone asks for someone else if (match->subtype & ST_SOMEONE) { //get the team mate name trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); client = FindClientByName(teammate); //if this is the bot self if (client == bs->client) { other = qfalse; } else if (!BotSameTeam(bs, client)) { //FIXME: say "I don't help the enemy" return; } else { other = qtrue; } } else { //get the netname trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientFromName(netname); other = qfalse; } //if the bot doesn't know who to help (FindClientByName returned -1) if (client < 0) { BotAI_BotInitialChat(bs, "whois", netname, NULL); trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); return; } // bs->lead_teamgoal.entitynum = -1; BotEntityInfo(client, &entinfo); //if info is valid (in PVS) if (entinfo.valid) { areanum = BotPointAreaNum(entinfo.origin); if (areanum) { // && trap_AAS_AreaReachability(areanum)) { bs->lead_teamgoal.entitynum = client; bs->lead_teamgoal.areanum = areanum; VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); } } if (bs->teamgoal.entitynum < 0) { if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); return; } bs->lead_teammate = client; bs->lead_time = FloatTime() + TEAM_LEAD_TIME; bs->leadvisible_time = 0; bs->leadmessage_time = -(FloatTime() + 2 * random()); } /* ================== BotMatch_Kill ================== */ void BotMatch_Kill(bot_state_t *bs, bot_match_t *match) { char enemy[MAX_MESSAGE_SIZE]; char netname[MAX_MESSAGE_SIZE]; int client; if (!TeamPlayIsOn()) return; //if not addressed to this bot if (!BotAddressedToBot(bs, match)) return; trap_BotMatchVariable(match, ENEMY, enemy, sizeof(enemy)); // client = FindEnemyByName(bs, enemy); if (client < 0) { BotAI_BotInitialChat(bs, "whois", enemy, NULL); trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = ClientFromName(netname); trap_BotEnterChat(bs->cs, client, CHAT_TELL); return; } bs->teamgoal.entitynum = client; //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_KILL; //set the team goal time bs->teamgoal_time = FloatTime() + TEAM_KILL_SOMEONE; // BotSetTeamStatus(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotMatch_CTF ================== */ void BotMatch_CTF(bot_state_t *bs, bot_match_t *match) { char flag[128], netname[MAX_NETNAME]; if (gametype == GT_CTF) { trap_BotMatchVariable(match, FLAG, flag, sizeof(flag)); if (match->subtype & ST_GOTFLAG) { if (!Q_stricmp(flag, "red")) { bs->redflagstatus = 1; if (BotTeam(bs) == TEAM_BLUE) { trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); bs->flagcarrier = ClientFromName(netname); } } else { bs->blueflagstatus = 1; if (BotTeam(bs) == TEAM_RED) { trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); bs->flagcarrier = ClientFromName(netname); } } bs->flagstatuschanged = 1; bs->lastflagcapture_time = FloatTime(); } else if (match->subtype & ST_CAPTUREDFLAG) { bs->redflagstatus = 0; bs->blueflagstatus = 0; bs->flagcarrier = 0; bs->flagstatuschanged = 1; } else if (match->subtype & ST_RETURNEDFLAG) { if (!Q_stricmp(flag, "red")) bs->redflagstatus = 0; else bs->blueflagstatus = 0; bs->flagstatuschanged = 1; } } #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (match->subtype & ST_1FCTFGOTFLAG) { trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); bs->flagcarrier = ClientFromName(netname); } } #endif } void BotMatch_EnterGame(bot_state_t *bs, bot_match_t *match) { int client; char netname[MAX_NETNAME]; trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = FindClientByName(netname); if (client >= 0) { notleader[client] = qfalse; } //NOTE: eliza chats will catch this //Com_sprintf(buf, sizeof(buf), "heya %s", netname); //EA_Say(bs->client, buf); } void BotMatch_NewLeader(bot_state_t *bs, bot_match_t *match) { int client; char netname[MAX_NETNAME]; trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); client = FindClientByName(netname); if (!BotSameTeam(bs, client)) return; Q_strncpyz(bs->teamleader, netname, sizeof(bs->teamleader)); } /* ================== BotMatchMessage ================== */ int BotMatchMessage(bot_state_t *bs, char *message) { bot_match_t match; match.type = 0; //if it is an unknown message if (!trap_BotFindMatch(message, &match, MTCONTEXT_MISC |MTCONTEXT_INITIALTEAMCHAT |MTCONTEXT_CTF)) { return qfalse; } //react to the found message switch(match.type) { case MSG_HELP: //someone calling for help case MSG_ACCOMPANY: //someone calling for company { BotMatch_HelpAccompany(bs, &match); break; } case MSG_DEFENDKEYAREA: //teamplay defend a key area { BotMatch_DefendKeyArea(bs, &match); break; } case MSG_CAMP: //camp somewhere { BotMatch_Camp(bs, &match); break; } case MSG_PATROL: //patrol between several key areas { BotMatch_Patrol(bs, &match); break; } //CTF & 1FCTF case MSG_GETFLAG: //ctf get the enemy flag { BotMatch_GetFlag(bs, &match); break; } #ifdef MISSIONPACK //CTF & 1FCTF & Obelisk & Harvester case MSG_ATTACKENEMYBASE: { BotMatch_AttackEnemyBase(bs, &match); break; } //Harvester case MSG_HARVEST: { BotMatch_Harvest(bs, &match); break; } #endif //CTF & 1FCTF & Harvester case MSG_RUSHBASE: //ctf rush to the base { BotMatch_RushBase(bs, &match); break; } //CTF & 1FCTF case MSG_RETURNFLAG: { BotMatch_ReturnFlag(bs, &match); break; } //CTF & 1FCTF & Obelisk & Harvester case MSG_TASKPREFERENCE: { BotMatch_TaskPreference(bs, &match); break; } //CTF & 1FCTF case MSG_CTF: { BotMatch_CTF(bs, &match); break; } case MSG_GETITEM: { BotMatch_GetItem(bs, &match); break; } case MSG_JOINSUBTEAM: //join a sub team { BotMatch_JoinSubteam(bs, &match); break; } case MSG_LEAVESUBTEAM: //leave a sub team { BotMatch_LeaveSubteam(bs, &match); break; } case MSG_WHICHTEAM: { BotMatch_WhichTeam(bs, &match); break; } case MSG_CHECKPOINT: //remember a check point { BotMatch_CheckPoint(bs, &match); break; } case MSG_CREATENEWFORMATION: //start the creation of a new formation { trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); break; } case MSG_FORMATIONPOSITION: //tell someone his/her position in the formation { trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); break; } case MSG_FORMATIONSPACE: //set the formation space { BotMatch_FormationSpace(bs, &match); break; } case MSG_DOFORMATION: //form a certain formation { break; } case MSG_DISMISS: //dismiss someone { BotMatch_Dismiss(bs, &match); break; } case MSG_STARTTEAMLEADERSHIP: //someone will become the team leader { BotMatch_StartTeamLeaderShip(bs, &match); break; } case MSG_STOPTEAMLEADERSHIP: //someone will stop being the team leader { BotMatch_StopTeamLeaderShip(bs, &match); break; } case MSG_WHOISTEAMLAEDER: { BotMatch_WhoIsTeamLeader(bs, &match); break; } case MSG_WHATAREYOUDOING: //ask a bot what he/she is doing { BotMatch_WhatAreYouDoing(bs, &match); break; } case MSG_WHATISMYCOMMAND: { BotMatch_WhatIsMyCommand(bs, &match); break; } case MSG_WHEREAREYOU: { BotMatch_WhereAreYou(bs, &match); break; } case MSG_LEADTHEWAY: { BotMatch_LeadTheWay(bs, &match); break; } case MSG_KILL: { BotMatch_Kill(bs, &match); break; } case MSG_ENTERGAME: //someone entered the game { BotMatch_EnterGame(bs, &match); break; } case MSG_NEWLEADER: { BotMatch_NewLeader(bs, &match); break; } case MSG_WAIT: { break; } case MSG_SUICIDE: { BotMatch_Suicide(bs, &match); break; } default: { BotAI_Print(PRT_MESSAGE, "unknown match type\n"); break; } } return qtrue; } ================================================ FILE: code/game/ai_cmd.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_cmd.h * * desc: Quake3 bot AI * * $Archive: /source/code/botai/ai_chat.c $ * *****************************************************************************/ extern int notleader[MAX_CLIENTS]; int BotMatchMessage(bot_state_t *bs, char *message); void BotPrintTeamGoal(bot_state_t *bs); ================================================ FILE: code/game/ai_dmnet.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_dmnet.c * * desc: Quake3 bot AI * * $Archive: /MissionPack/code/game/ai_dmnet.c $ * *****************************************************************************/ #include "g_local.h" #include "botlib.h" #include "be_aas.h" #include "be_ea.h" #include "be_ai_char.h" #include "be_ai_chat.h" #include "be_ai_gen.h" #include "be_ai_goal.h" #include "be_ai_move.h" #include "be_ai_weap.h" // #include "ai_main.h" #include "ai_dmq3.h" #include "ai_chat.h" #include "ai_cmd.h" #include "ai_dmnet.h" #include "ai_team.h" //data file headers #include "chars.h" //characteristics #include "inv.h" //indexes into the inventory #include "syn.h" //synonyms #include "match.h" //string matching types and vars // for the voice chats #include "../../ui/menudef.h" //goal flag, see be_ai_goal.h for the other GFL_* #define GFL_AIR 128 int numnodeswitches; char nodeswitch[MAX_NODESWITCHES+1][144]; #define LOOKAHEAD_DISTANCE 300 /* ================== BotResetNodeSwitches ================== */ void BotResetNodeSwitches(void) { numnodeswitches = 0; } /* ================== BotDumpNodeSwitches ================== */ void BotDumpNodeSwitches(bot_state_t *bs) { int i; char netname[MAX_NETNAME]; ClientName(bs->client, netname, sizeof(netname)); BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, FloatTime(), MAX_NODESWITCHES); for (i = 0; i < numnodeswitches; i++) { BotAI_Print(PRT_MESSAGE, nodeswitch[i]); } BotAI_Print(PRT_FATAL, ""); } /* ================== BotRecordNodeSwitch ================== */ void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str, char *s) { char netname[MAX_NETNAME]; ClientName(bs->client, netname, sizeof(netname)); Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s from %s\n", netname, FloatTime(), node, str, s); #ifdef DEBUG if (0) { BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]); } #endif //DEBUG numnodeswitches++; } /* ================== BotGetAirGoal ================== */ int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) { bsp_trace_t bsptrace; vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2}; int areanum; //trace up until we hit solid VectorCopy(bs->origin, end); end[2] += 1000; BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); //trace down until we hit water VectorCopy(bsptrace.endpos, end); BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA); //if we found the water surface if (bsptrace.fraction > 0) { areanum = BotPointAreaNum(bsptrace.endpos); if (areanum) { VectorCopy(bsptrace.endpos, goal->origin); goal->origin[2] -= 2; goal->areanum = areanum; goal->mins[0] = -15; goal->mins[1] = -15; goal->mins[2] = -1; goal->maxs[0] = 15; goal->maxs[1] = 15; goal->maxs[2] = 1; goal->flags = GFL_AIR; goal->number = 0; goal->iteminfo = 0; goal->entitynum = 0; return qtrue; } } return qfalse; } /* ================== BotGoForAir ================== */ int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { bot_goal_t goal; //if the bot needs air if (bs->lastair_time < FloatTime() - 6) { // #ifdef DEBUG //BotAI_Print(PRT_MESSAGE, "going for air\n"); #endif //DEBUG //if we can find an air goal if (BotGetAirGoal(bs, &goal)) { trap_BotPushGoal(bs->gs, &goal); return qtrue; } else { //get a nearby goal outside the water while(trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range)) { trap_BotGetTopGoal(bs->gs, &goal); //if the goal is not in water if (!(trap_AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) { return qtrue; } trap_BotPopGoal(bs->gs); } trap_BotResetAvoidGoals(bs->gs); } } return qfalse; } /* ================== BotNearbyGoal ================== */ int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { int ret; //check if the bot should go for air if (BotGoForAir(bs, tfl, ltg, range)) return qtrue; //if the bot is carrying the enemy flag if (BotCTFCarryingFlag(bs)) { //if the bot is just a few secs away from the base if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->teamgoal.areanum, TFL_DEFAULT) < 300) { //make the range really small range = 50; } } // ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range); /* if (ret) { char buf[128]; //get the goal at the top of the stack trap_BotGetTopGoal(bs->gs, &goal); trap_BotGoalName(goal.number, buf, sizeof(buf)); BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", FloatTime(), buf); } */ return ret; } /* ================== BotReachedGoal ================== */ int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) { if (goal->flags & GFL_ITEM) { //if touching the goal if (trap_BotTouchingGoal(bs->origin, goal)) { if (!(goal->flags & GFL_DROPPED)) { trap_BotSetAvoidGoalTime(bs->gs, goal->number, -1); } return qtrue; } //if the goal isn't there if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { /* float avoidtime; int t; avoidtime = trap_BotAvoidGoalTime(bs->gs, goal->number); if (avoidtime > 0) { t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal->areanum, bs->tfl); if ((float) t * 0.009 < avoidtime) return qtrue; } */ return qtrue; } //if in the goal area and below or above the goal and not swimming if (bs->areanum == goal->areanum) { if (bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0]) { if (bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1]) { if (!trap_AAS_Swimming(bs->origin)) { return qtrue; } } } } } else if (goal->flags & GFL_AIR) { //if touching the goal if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; //if the bot got air if (bs->lastair_time > FloatTime() - 1) return qtrue; } else { //if touching the goal if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; } return qfalse; } /* ================== BotGetItemLongTermGoal ================== */ int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) { //if the bot has no goal if (!trap_BotGetTopGoal(bs->gs, goal)) { //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n"); bs->ltg_time = 0; } //if the bot touches the current goal else if (BotReachedGoal(bs, goal)) { BotChooseWeapon(bs); bs->ltg_time = 0; } //if it is time to find a new long term goal if (bs->ltg_time < FloatTime()) { //pop the current goal from the stack trap_BotPopGoal(bs->gs); //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname))); //choose a new goal //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", FloatTime(), bs->client); if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl)) { /* char buf[128]; //get the goal at the top of the stack trap_BotGetTopGoal(bs->gs, goal); trap_BotGoalName(goal->number, buf, sizeof(buf)); BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", FloatTime(), buf); */ bs->ltg_time = FloatTime() + 20; } else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though // #ifdef DEBUG char netname[128]; BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname))); #endif //trap_BotDumpAvoidGoals(bs->gs); //reset the avoid goals and the avoid reach trap_BotResetAvoidGoals(bs->gs); trap_BotResetAvoidReach(bs->ms); } //get the goal at the top of the stack return trap_BotGetTopGoal(bs->gs, goal); } return qtrue; } /* ================== BotGetLongTermGoal we could also create a seperate AI node for every long term goal type however this saves us a lot of code ================== */ int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { vec3_t target, dir, dir2; char netname[MAX_NETNAME]; char buf[MAX_MESSAGE_SIZE]; int areanum; float croucher; aas_entityinfo_t entinfo, botinfo; bot_waypoint_t *wp; if (bs->ltgtype == LTG_TEAMHELP && !retreat) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); bs->teammessage_time = 0; } //if trying to help the team mate for more than a minute if (bs->teamgoal_time < FloatTime()) bs->ltgtype = 0; //if the team mate IS visible for quite some time if (bs->teammatevisible_time < FloatTime() - 10) bs->ltgtype = 0; //get entity information of the companion BotEntityInfo(bs->teammate, &entinfo); //if the team mate is visible if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { //if close just stand still there VectorSubtract(entinfo.origin, bs->origin, dir); if (VectorLengthSquared(dir) < Square(100)) { trap_BotResetAvoidReach(bs->ms); return qfalse; } } else { //last time the bot was NOT visible bs->teammatevisible_time = FloatTime(); } //if the entity information is valid (entity in PVS) if (entinfo.valid) { areanum = BotPointAreaNum(entinfo.origin); if (areanum && trap_AAS_AreaReachability(areanum)) { //update team goal bs->teamgoal.entitynum = bs->teammate; bs->teamgoal.areanum = areanum; VectorCopy(entinfo.origin, bs->teamgoal.origin); VectorSet(bs->teamgoal.mins, -8, -8, -8); VectorSet(bs->teamgoal.maxs, 8, 8, 8); } } memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); return qtrue; } //if the bot accompanies someone if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); bs->teammessage_time = 0; } //if accompanying the companion for 3 minutes if (bs->teamgoal_time < FloatTime()) { BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); bs->ltgtype = 0; } //get entity information of the companion BotEntityInfo(bs->teammate, &entinfo); //if the companion is visible if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { //update visible time bs->teammatevisible_time = FloatTime(); VectorSubtract(entinfo.origin, bs->origin, dir); if (VectorLengthSquared(dir) < Square(bs->formation_dist)) { // // if the client being followed bumps into this bot then // the bot should back up BotEntityInfo(bs->entitynum, &botinfo); // if the followed client is not standing ontop of the bot if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2]) { // if the bounding boxes touch each other if (botinfo.origin[0] + botinfo.maxs[0] > entinfo.origin[0] + entinfo.mins[0] - 4&& botinfo.origin[0] + botinfo.mins[0] < entinfo.origin[0] + entinfo.maxs[0] + 4) { if (botinfo.origin[1] + botinfo.maxs[1] > entinfo.origin[1] + entinfo.mins[1] - 4 && botinfo.origin[1] + botinfo.mins[1] < entinfo.origin[1] + entinfo.maxs[1] + 4) { if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2] - 4 && botinfo.origin[2] + botinfo.mins[2] < entinfo.origin[2] + entinfo.maxs[2] + 4) { // if the followed client looks in the direction of this bot AngleVectors(entinfo.angles, dir, NULL, NULL); dir[2] = 0; VectorNormalize(dir); //VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); VectorSubtract(bs->origin, entinfo.origin, dir2); VectorNormalize(dir2); if (DotProduct(dir, dir2) > 0.7) { // back up BotSetupForMovement(bs); trap_BotMoveInDirection(bs->ms, dir2, 400, MOVE_WALK); } } } } } //check if the bot wants to crouch //don't crouch if crouched less than 5 seconds ago if (bs->attackcrouch_time < FloatTime() - 5) { croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); if (random() < bs->thinktime * croucher) { bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; } } //don't crouch when swimming if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; //if not arrived yet or arived some time ago if (bs->arrive_time < FloatTime() - 2) { //if not arrived yet if (!bs->arrive_time) { trap_EA_Gesture(bs->client); BotAI_BotInitialChat(bs, "accompany_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); bs->arrive_time = FloatTime(); } //if the bot wants to crouch else if (bs->attackcrouch_time > FloatTime()) { trap_EA_Crouch(bs->client); } //else do some model taunts else if (random() < bs->thinktime * 0.05) { //do a gesture :) trap_EA_Gesture(bs->client); } } //if just arrived look at the companion if (bs->arrive_time > FloatTime() - 2) { VectorSubtract(entinfo.origin, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); bs->ideal_viewangles[2] *= 0.5; } //else look strategically around for enemies else if (random() < bs->thinktime * 0.8) { BotRoamGoal(bs, target); VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); bs->ideal_viewangles[2] *= 0.5; } //check if the bot wants to go for air if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) { trap_BotResetLastAvoidReach(bs->ms); //get the goal at the top of the stack //trap_BotGetTopGoal(bs->gs, &tmpgoal); //trap_BotGoalName(tmpgoal.number, buf, 144); //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); //time the bot gets to pick up the nearby goal item bs->nbg_time = FloatTime() + 8; AIEnter_Seek_NBG(bs, "BotLongTermGoal: go for air"); return qfalse; } // trap_BotResetAvoidReach(bs->ms); return qfalse; } } //if the entity information is valid (entity in PVS) if (entinfo.valid) { areanum = BotPointAreaNum(entinfo.origin); if (areanum && trap_AAS_AreaReachability(areanum)) { //update team goal bs->teamgoal.entitynum = bs->teammate; bs->teamgoal.areanum = areanum; VectorCopy(entinfo.origin, bs->teamgoal.origin); VectorSet(bs->teamgoal.mins, -8, -8, -8); VectorSet(bs->teamgoal.maxs, 8, 8, 8); } } //the goal the bot should go for memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); //if the companion is NOT visible for too long if (bs->teammatevisible_time < FloatTime() - 60) { BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); bs->ltgtype = 0; // just to make sure the bot won't spam this message bs->teammatevisible_time = FloatTime(); } return qtrue; } // if (bs->ltgtype == LTG_DEFENDKEYAREA) { if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) { bs->defendaway_time = 0; } } //if defending a key area if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat && bs->defendaway_time < FloatTime()) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); BotAI_BotInitialChat(bs, "defend_start", buf, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONDEFENSE); bs->teammessage_time = 0; } //set the bot goal memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); //stop after 2 minutes if (bs->teamgoal_time < FloatTime()) { trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); BotAI_BotInitialChat(bs, "defend_stop", buf, NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); bs->ltgtype = 0; } //if very close... go away for some time VectorSubtract(goal->origin, bs->origin, dir); if (VectorLengthSquared(dir) < Square(70)) { trap_BotResetAvoidReach(bs->ms); bs->defendaway_time = FloatTime() + 3 + 3 * random(); if (BotHasPersistantPowerupAndWeapon(bs)) { bs->defendaway_range = 100; } else { bs->defendaway_range = 350; } } return qtrue; } //going to kill someone if (bs->ltgtype == LTG_KILL && !retreat) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); BotAI_BotInitialChat(bs, "kill_start", buf, NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); bs->teammessage_time = 0; } // if (bs->lastkilledplayer == bs->teamgoal.entitynum) { EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); BotAI_BotInitialChat(bs, "kill_done", buf, NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); bs->lastkilledplayer = -1; bs->ltgtype = 0; } // if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } //just roam around return BotGetItemLongTermGoal(bs, tfl, goal); } //get an item if (bs->ltgtype == LTG_GETITEM && !retreat) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); BotAI_BotInitialChat(bs, "getitem_start", buf, NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); bs->teammessage_time = 0; } //set the bot goal memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); //stop after some time if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } // if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); bs->ltgtype = 0; } else if (BotReachedGoal(bs, goal)) { trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); bs->ltgtype = 0; } return qtrue; } //if camping somewhere if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { if (bs->ltgtype == LTG_CAMPORDER) { BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); } bs->teammessage_time = 0; } //set the bot goal memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); // if (bs->teamgoal_time < FloatTime()) { if (bs->ltgtype == LTG_CAMPORDER) { BotAI_BotInitialChat(bs, "camp_stop", NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); } bs->ltgtype = 0; } //if really near the camp spot VectorSubtract(goal->origin, bs->origin, dir); if (VectorLengthSquared(dir) < Square(60)) { //if not arrived yet if (!bs->arrive_time) { if (bs->ltgtype == LTG_CAMPORDER) { BotAI_BotInitialChat(bs, "camp_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_INPOSITION); } bs->arrive_time = FloatTime(); } //look strategically around for enemies if (random() < bs->thinktime * 0.8) { BotRoamGoal(bs, target); VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); bs->ideal_viewangles[2] *= 0.5; } //check if the bot wants to crouch //don't crouch if crouched less than 5 seconds ago if (bs->attackcrouch_time < FloatTime() - 5) { croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); if (random() < bs->thinktime * croucher) { bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; } } //if the bot wants to crouch if (bs->attackcrouch_time > FloatTime()) { trap_EA_Crouch(bs->client); } //don't crouch when swimming if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; //make sure the bot is not gonna drown if (trap_PointContents(bs->eye,bs->entitynum) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { if (bs->ltgtype == LTG_CAMPORDER) { BotAI_BotInitialChat(bs, "camp_stop", NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); // if (bs->lastgoal_ltgtype == LTG_CAMPORDER) { bs->lastgoal_ltgtype = 0; } } bs->ltgtype = 0; } // if (bs->camp_range > 0) { //FIXME: move around a bit } // trap_BotResetAvoidReach(bs->ms); return qfalse; } return qtrue; } //patrolling along several waypoints if (bs->ltgtype == LTG_PATROL && !retreat) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { strcpy(buf, ""); for (wp = bs->patrolpoints; wp; wp = wp->next) { strcat(buf, wp->name); if (wp->next) strcat(buf, " to "); } BotAI_BotInitialChat(bs, "patrol_start", buf, NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); bs->teammessage_time = 0; } // if (!bs->curpatrolpoint) { bs->ltgtype = 0; return qfalse; } //if the bot touches the current goal if (trap_BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) { if (bs->patrolflags & PATROL_BACK) { if (bs->curpatrolpoint->prev) { bs->curpatrolpoint = bs->curpatrolpoint->prev; } else { bs->curpatrolpoint = bs->curpatrolpoint->next; bs->patrolflags &= ~PATROL_BACK; } } else { if (bs->curpatrolpoint->next) { bs->curpatrolpoint = bs->curpatrolpoint->next; } else { bs->curpatrolpoint = bs->curpatrolpoint->prev; bs->patrolflags |= PATROL_BACK; } } } //stop after 5 minutes if (bs->teamgoal_time < FloatTime()) { BotAI_BotInitialChat(bs, "patrol_stop", NULL); trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); bs->ltgtype = 0; } if (!bs->curpatrolpoint) { bs->ltgtype = 0; return qfalse; } memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t)); return qtrue; } #ifdef CTF if (gametype == GT_CTF) { //if going for enemy flag if (bs->ltgtype == LTG_GETFLAG) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "captureflag_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); bs->teammessage_time = 0; } // switch(BotTeam(bs)) { case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; default: bs->ltgtype = 0; return qfalse; } //if touching the flag if (trap_BotTouchingGoal(bs->origin, goal)) { // make sure the bot knows the flag isn't there anymore switch(BotTeam(bs)) { case TEAM_RED: bs->blueflagstatus = 1; break; case TEAM_BLUE: bs->redflagstatus = 1; break; } bs->ltgtype = 0; } //stop after 3 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } BotAlternateRoute(bs, goal); return qtrue; } //if rushing to the base if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < FloatTime()) { switch(BotTeam(bs)) { case TEAM_RED: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; default: bs->ltgtype = 0; return qfalse; } //if not carrying the flag anymore if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0; //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) bs->ltgtype = 0; //if touching the base flag the bot should loose the enemy flag if (trap_BotTouchingGoal(bs->origin, goal)) { //if the bot is still carrying the enemy flag then the //base flag is gone, now just walk near the base a bit if (BotCTFCarryingFlag(bs)) { trap_BotResetAvoidReach(bs->ms); bs->rushbaseaway_time = FloatTime() + 5 + 10 * random(); //FIXME: add chat to tell the others to get back the flag } else { bs->ltgtype = 0; } } BotAlternateRoute(bs, goal); return qtrue; } //returning flag if (bs->ltgtype == LTG_RETURNFLAG) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "returnflag_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); bs->teammessage_time = 0; } // switch(BotTeam(bs)) { case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; default: bs->ltgtype = 0; return qfalse; } //if touching the flag if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0; //stop after 3 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } BotAlternateRoute(bs, goal); return qtrue; } } #endif //CTF #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (bs->ltgtype == LTG_GETFLAG) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "captureflag_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); bs->teammessage_time = 0; } memcpy(goal, &ctf_neutralflag, sizeof(bot_goal_t)); //if touching the flag if (trap_BotTouchingGoal(bs->origin, goal)) { bs->ltgtype = 0; } //stop after 3 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } return qtrue; } //if rushing to the base if (bs->ltgtype == LTG_RUSHBASE) { switch(BotTeam(bs)) { case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; default: bs->ltgtype = 0; return qfalse; } //if not carrying the flag anymore if (!Bot1FCTFCarryingFlag(bs)) { bs->ltgtype = 0; } //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } //if touching the base flag the bot should loose the enemy flag if (trap_BotTouchingGoal(bs->origin, goal)) { bs->ltgtype = 0; } BotAlternateRoute(bs, goal); return qtrue; } //attack the enemy base if (bs->ltgtype == LTG_ATTACKENEMYBASE && bs->attackaway_time < FloatTime()) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); bs->teammessage_time = 0; } switch(BotTeam(bs)) { case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; default: bs->ltgtype = 0; return qfalse; } //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } //if touching the base flag the bot should loose the enemy flag if (trap_BotTouchingGoal(bs->origin, goal)) { bs->attackaway_time = FloatTime() + 2 + 5 * random(); } return qtrue; } //returning flag if (bs->ltgtype == LTG_RETURNFLAG) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "returnflag_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); bs->teammessage_time = 0; } // if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } //just roam around return BotGetItemLongTermGoal(bs, tfl, goal); } } else if (gametype == GT_OBELISK) { if (bs->ltgtype == LTG_ATTACKENEMYBASE && bs->attackaway_time < FloatTime()) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); bs->teammessage_time = 0; } switch(BotTeam(bs)) { case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; default: bs->ltgtype = 0; return qfalse; } //if the bot no longer wants to attack the obelisk if (BotFeelingBad(bs) > 50) { return BotGetItemLongTermGoal(bs, tfl, goal); } //if touching the obelisk if (trap_BotTouchingGoal(bs->origin, goal)) { bs->attackaway_time = FloatTime() + 3 + 5 * random(); } // or very close to the obelisk VectorSubtract(bs->origin, goal->origin, dir); if (VectorLengthSquared(dir) < Square(60)) { bs->attackaway_time = FloatTime() + 3 + 5 * random(); } //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } BotAlternateRoute(bs, goal); //just move towards the obelisk return qtrue; } } else if (gametype == GT_HARVESTER) { //if rushing to the base if (bs->ltgtype == LTG_RUSHBASE) { switch(BotTeam(bs)) { case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; default: BotGoHarvest(bs); return qfalse; } //if not carrying any cubes if (!BotHarvesterCarryingCubes(bs)) { BotGoHarvest(bs); return qfalse; } //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) { BotGoHarvest(bs); return qfalse; } //if touching the base flag the bot should loose the enemy flag if (trap_BotTouchingGoal(bs->origin, goal)) { BotGoHarvest(bs); return qfalse; } BotAlternateRoute(bs, goal); return qtrue; } //attack the enemy base if (bs->ltgtype == LTG_ATTACKENEMYBASE && bs->attackaway_time < FloatTime()) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); bs->teammessage_time = 0; } switch(BotTeam(bs)) { case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; default: bs->ltgtype = 0; return qfalse; } //quit rushing after 2 minutes if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } //if touching the base flag the bot should loose the enemy flag if (trap_BotTouchingGoal(bs->origin, goal)) { bs->attackaway_time = FloatTime() + 2 + 5 * random(); } return qtrue; } //harvest cubes if (bs->ltgtype == LTG_HARVEST && bs->harvestaway_time < FloatTime()) { //check for bot typing status message if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "harvest_start", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); bs->teammessage_time = 0; } memcpy(goal, &neutralobelisk, sizeof(bot_goal_t)); // if (bs->teamgoal_time < FloatTime()) { bs->ltgtype = 0; } // if (trap_BotTouchingGoal(bs->origin, goal)) { bs->harvestaway_time = FloatTime() + 4 + 3 * random(); } return qtrue; } } #endif //normal goal stuff return BotGetItemLongTermGoal(bs, tfl, goal); } /* ================== BotLongTermGoal ================== */ int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { aas_entityinfo_t entinfo; char teammate[MAX_MESSAGE_SIZE]; float squaredist; int areanum; vec3_t dir; //FIXME: also have air long term goals? // //if the bot is leading someone and not retreating if (bs->lead_time > 0 && !retreat) { if (bs->lead_time < FloatTime()) { BotAI_BotInitialChat(bs, "lead_stop", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); bs->lead_time = 0; return BotGetLongTermGoal(bs, tfl, retreat, goal); } // if (bs->leadmessage_time < 0 && -bs->leadmessage_time < FloatTime()) { BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); bs->leadmessage_time = FloatTime(); } //get entity information of the companion BotEntityInfo(bs->lead_teammate, &entinfo); // if (entinfo.valid) { areanum = BotPointAreaNum(entinfo.origin); if (areanum && trap_AAS_AreaReachability(areanum)) { //update team goal bs->lead_teamgoal.entitynum = bs->lead_teammate; bs->lead_teamgoal.areanum = areanum; VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); } } //if the team mate is visible if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) { bs->leadvisible_time = FloatTime(); } //if the team mate is not visible for 1 seconds if (bs->leadvisible_time < FloatTime() - 1) { bs->leadbackup_time = FloatTime() + 2; } //distance towards the team mate VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir); squaredist = VectorLengthSquared(dir); //if backing up towards the team mate if (bs->leadbackup_time > FloatTime()) { if (bs->leadmessage_time < FloatTime() - 20) { BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); bs->leadmessage_time = FloatTime(); } //if very close to the team mate if (squaredist < Square(100)) { bs->leadbackup_time = 0; } //the bot should go back to the team mate memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t)); return qtrue; } else { //if quite distant from the team mate if (squaredist > Square(500)) { if (bs->leadmessage_time < FloatTime() - 20) { BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); bs->leadmessage_time = FloatTime(); } //look at the team mate VectorSubtract(entinfo.origin, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); bs->ideal_viewangles[2] *= 0.5; //just wait for the team mate return qfalse; } } } return BotGetLongTermGoal(bs, tfl, retreat, goal); } /* ================== AIEnter_Intermission ================== */ void AIEnter_Intermission(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "intermission", "", s); //reset the bot state BotResetState(bs); //check for end level chat if (BotChat_EndLevel(bs)) { trap_BotEnterChat(bs->cs, 0, bs->chatto); } bs->ainode = AINode_Intermission; } /* ================== AINode_Intermission ================== */ int AINode_Intermission(bot_state_t *bs) { //if the intermission ended if (!BotIntermission(bs)) { if (BotChat_StartLevel(bs)) { bs->stand_time = FloatTime() + BotChatTime(bs); } else { bs->stand_time = FloatTime() + 2; } AIEnter_Stand(bs, "intermission: chat"); } return qtrue; } /* ================== AIEnter_Observer ================== */ void AIEnter_Observer(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "observer", "", s); //reset the bot state BotResetState(bs); bs->ainode = AINode_Observer; } /* ================== AINode_Observer ================== */ int AINode_Observer(bot_state_t *bs) { //if the bot left observer mode if (!BotIsObserver(bs)) { AIEnter_Stand(bs, "observer: left observer"); } return qtrue; } /* ================== AIEnter_Stand ================== */ void AIEnter_Stand(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "stand", "", s); bs->standfindenemy_time = FloatTime() + 1; bs->ainode = AINode_Stand; } /* ================== AINode_Stand ================== */ int AINode_Stand(bot_state_t *bs) { //if the bot's health decreased if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { if (BotChat_HitTalking(bs)) { bs->standfindenemy_time = FloatTime() + BotChatTime(bs) + 0.1; bs->stand_time = FloatTime() + BotChatTime(bs) + 0.1; } } if (bs->standfindenemy_time < FloatTime()) { if (BotFindEnemy(bs, -1)) { AIEnter_Battle_Fight(bs, "stand: found enemy"); return qfalse; } bs->standfindenemy_time = FloatTime() + 1; } // put up chat icon trap_EA_Talk(bs->client); // when done standing if (bs->stand_time < FloatTime()) { trap_BotEnterChat(bs->cs, 0, bs->chatto); AIEnter_Seek_LTG(bs, "stand: time out"); return qfalse; } // return qtrue; } /* ================== AIEnter_Respawn ================== */ void AIEnter_Respawn(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "respawn", "", s); //reset some states trap_BotResetMoveState(bs->ms); trap_BotResetGoalState(bs->gs); trap_BotResetAvoidGoals(bs->gs); trap_BotResetAvoidReach(bs->ms); //if the bot wants to chat if (BotChat_Death(bs)) { bs->respawn_time = FloatTime() + BotChatTime(bs); bs->respawnchat_time = FloatTime(); } else { bs->respawn_time = FloatTime() + 1 + random(); bs->respawnchat_time = 0; } //set respawn state bs->respawn_wait = qfalse; bs->ainode = AINode_Respawn; } /* ================== AINode_Respawn ================== */ int AINode_Respawn(bot_state_t *bs) { // if waiting for the actual respawn if (bs->respawn_wait) { if (!BotIsDead(bs)) { AIEnter_Seek_LTG(bs, "respawn: respawned"); } else { trap_EA_Respawn(bs->client); } } else if (bs->respawn_time < FloatTime()) { // wait until respawned bs->respawn_wait = qtrue; // elementary action respawn trap_EA_Respawn(bs->client); // if (bs->respawnchat_time) { trap_BotEnterChat(bs->cs, 0, bs->chatto); bs->enemy = -1; } } if (bs->respawnchat_time && bs->respawnchat_time < FloatTime() - 0.5) { trap_EA_Talk(bs->client); } // return qtrue; } /* ================== BotSelectActivateWeapon ================== */ int BotSelectActivateWeapon(bot_state_t *bs) { // if (bs->inventory[INVENTORY_MACHINEGUN] > 0 && bs->inventory[INVENTORY_BULLETS] > 0) return WEAPONINDEX_MACHINEGUN; else if (bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] > 0) return WEAPONINDEX_SHOTGUN; else if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) return WEAPONINDEX_PLASMAGUN; else if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_LIGHTNINGAMMO] > 0) return WEAPONINDEX_LIGHTNING; #ifdef MISSIONPACK else if (bs->inventory[INVENTORY_CHAINGUN] > 0 && bs->inventory[INVENTORY_BELT] > 0) return WEAPONINDEX_CHAINGUN; else if (bs->inventory[INVENTORY_NAILGUN] > 0 && bs->inventory[INVENTORY_NAILS] > 0) return WEAPONINDEX_NAILGUN; #endif else if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 0) return WEAPONINDEX_RAILGUN; else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) return WEAPONINDEX_ROCKET_LAUNCHER; else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) return WEAPONINDEX_BFG; else { return -1; } } /* ================== BotClearPath try to deactivate obstacles like proximity mines on the bot's path ================== */ void BotClearPath(bot_state_t *bs, bot_moveresult_t *moveresult) { int i, bestmine; float dist, bestdist; vec3_t target, dir; bsp_trace_t bsptrace; entityState_t state; // if there is a dead body wearing kamikze nearby if (bs->kamikazebody) { // if the bot's view angles and weapon are not used for movement if ( !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { // BotAI_GetEntityState(bs->kamikazebody, &state); VectorCopy(state.pos.trBase, target); target[2] += 8; VectorSubtract(target, bs->eye, dir); vectoangles(dir, moveresult->ideal_viewangles); // moveresult->weapon = BotSelectActivateWeapon(bs); if (moveresult->weapon == -1) { // FIXME: run away! moveresult->weapon = 0; } if (moveresult->weapon) { // moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; // if holding the right weapon if (bs->cur_ps.weapon == moveresult->weapon) { // if the bot is pretty close with it's aim if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { // BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); // if the mine is visible from the current position if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { // shoot at the mine trap_EA_Attack(bs->client); } } } } } } if (moveresult->flags & MOVERESULT_BLOCKEDBYAVOIDSPOT) { bs->blockedbyavoidspot_time = FloatTime() + 5; } // if blocked by an avoid spot and the view angles and weapon are used for movement if (bs->blockedbyavoidspot_time > FloatTime() && !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { bestdist = 300; bestmine = -1; for (i = 0; i < bs->numproxmines; i++) { BotAI_GetEntityState(bs->proxmines[i], &state); VectorSubtract(state.pos.trBase, bs->origin, dir); dist = VectorLength(dir); if (dist < bestdist) { bestdist = dist; bestmine = i; } } if (bestmine != -1) { // // state->generic1 == TEAM_RED || state->generic1 == TEAM_BLUE // // deactivate prox mines in the bot's path by shooting // rockets or plasma cells etc. at them BotAI_GetEntityState(bs->proxmines[bestmine], &state); VectorCopy(state.pos.trBase, target); target[2] += 2; VectorSubtract(target, bs->eye, dir); vectoangles(dir, moveresult->ideal_viewangles); // if the bot has a weapon that does splash damage if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) moveresult->weapon = WEAPONINDEX_PLASMAGUN; else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) moveresult->weapon = WEAPONINDEX_ROCKET_LAUNCHER; else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) moveresult->weapon = WEAPONINDEX_BFG; else { moveresult->weapon = 0; } if (moveresult->weapon) { // moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; // if holding the right weapon if (bs->cur_ps.weapon == moveresult->weapon) { // if the bot is pretty close with it's aim if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { // BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); // if the mine is visible from the current position if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { // shoot at the mine trap_EA_Attack(bs->client); } } } } } } } /* ================== AIEnter_Seek_ActivateEntity ================== */ void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "activate entity", "", s); bs->ainode = AINode_Seek_ActivateEntity; } /* ================== AINode_Seek_Activate_Entity ================== */ int AINode_Seek_ActivateEntity(bot_state_t *bs) { bot_goal_t *goal; vec3_t target, dir, ideal_viewangles; bot_moveresult_t moveresult; int targetvisible; bsp_trace_t bsptrace; aas_entityinfo_t entinfo; if (BotIsObserver(bs)) { BotClearActivateGoalStack(bs); AIEnter_Observer(bs, "active entity: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { BotClearActivateGoalStack(bs); AIEnter_Intermission(bs, "activate entity: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { BotClearActivateGoalStack(bs); AIEnter_Respawn(bs, "activate entity: bot dead"); return qfalse; } // bs->tfl = TFL_DEFAULT; if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; // if in lava or slime the bot should be able to get out if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; // map specific code BotMapScripts(bs); // no enemy bs->enemy = -1; // if the bot has no activate goal if (!bs->activatestack) { BotClearActivateGoalStack(bs); AIEnter_Seek_NBG(bs, "activate entity: no goal"); return qfalse; } // goal = &bs->activatestack->goal; // initialize target being visible to false targetvisible = qfalse; // if the bot has to shoot at a target to activate something if (bs->activatestack->shoot) { // BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->activatestack->target, bs->entitynum, MASK_SHOT); // if the shootable entity is visible from the current position if (bsptrace.fraction >= 1.0 || bsptrace.ent == goal->entitynum) { targetvisible = qtrue; // if holding the right weapon if (bs->cur_ps.weapon == bs->activatestack->weapon) { VectorSubtract(bs->activatestack->target, bs->eye, dir); vectoangles(dir, ideal_viewangles); // if the bot is pretty close with it's aim if (InFieldOfVision(bs->viewangles, 20, ideal_viewangles)) { trap_EA_Attack(bs->client); } } } } // if the shoot target is visible if (targetvisible) { // get the entity info of the entity the bot is shooting at BotEntityInfo(goal->entitynum, &entinfo); // if the entity the bot shoots at moved if (!VectorCompare(bs->activatestack->origin, entinfo.origin)) { #ifdef DEBUG BotAI_Print(PRT_MESSAGE, "hit shootable button or trigger\n"); #endif //DEBUG bs->activatestack->time = 0; } // if the activate goal has been activated or the bot takes too long if (bs->activatestack->time < FloatTime()) { BotPopFromActivateGoalStack(bs); // if there are more activate goals on the stack if (bs->activatestack) { bs->activatestack->time = FloatTime() + 10; return qfalse; } AIEnter_Seek_NBG(bs, "activate entity: time out"); return qfalse; } memset(&moveresult, 0, sizeof(bot_moveresult_t)); } else { // if the bot has no goal if (!goal) { bs->activatestack->time = 0; } // if the bot does not have a shoot goal else if (!bs->activatestack->shoot) { //if the bot touches the current goal if (trap_BotTouchingGoal(bs->origin, goal)) { #ifdef DEBUG BotAI_Print(PRT_MESSAGE, "touched button or trigger\n"); #endif //DEBUG bs->activatestack->time = 0; } } // if the activate goal has been activated or the bot takes too long if (bs->activatestack->time < FloatTime()) { BotPopFromActivateGoalStack(bs); // if there are more activate goals on the stack if (bs->activatestack) { bs->activatestack->time = FloatTime() + 10; return qfalse; } AIEnter_Seek_NBG(bs, "activate entity: activated"); return qfalse; } //predict obstacles if (BotAIPredictObstacles(bs, goal)) return qfalse; //initialize the movement state BotSetupForMovement(bs); //move towards the goal trap_BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl); //if the movement failed if (moveresult.failure) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach(bs->ms); // bs->activatestack->time = 0; } //check if the bot is blocked BotAIBlocked(bs, &moveresult, qtrue); } // BotClearPath(bs, &moveresult); // if the bot has to shoot to activate if (bs->activatestack->shoot) { // if the view angles aren't yet used for the movement if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEW)) { VectorSubtract(bs->activatestack->target, bs->eye, dir); vectoangles(dir, moveresult.ideal_viewangles); moveresult.flags |= MOVERESULT_MOVEMENTVIEW; } // if there's no weapon yet used for the movement if (!(moveresult.flags & MOVERESULT_MOVEMENTWEAPON)) { moveresult.flags |= MOVERESULT_MOVEMENTWEAPON; // bs->activatestack->weapon = BotSelectActivateWeapon(bs); if (bs->activatestack->weapon == -1) { //FIXME: find a decent weapon first bs->activatestack->weapon = 0; } moveresult.weapon = bs->activatestack->weapon; } } // if the ideal view angles are set for movement if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); } // if waiting for something else if (moveresult.flags & MOVERESULT_WAITING) { if (random() < bs->thinktime * 0.8) { BotRoamGoal(bs, target); VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); bs->ideal_viewangles[2] *= 0.5; } } else if (!(bs->flags & BFL_IDEALVIEWSET)) { if (trap_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) { VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); } else { vectoangles(moveresult.movedir, bs->ideal_viewangles); } bs->ideal_viewangles[2] *= 0.5; } // if the weapon is used for the bot movement if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; // if there is an enemy if (BotFindEnemy(bs, -1)) { if (BotWantsToRetreat(bs)) { //keep the current long term goal and retreat AIEnter_Battle_NBG(bs, "activate entity: found enemy"); } else { trap_BotResetLastAvoidReach(bs->ms); //empty the goal stack trap_BotEmptyGoalStack(bs->gs); //go fight AIEnter_Battle_Fight(bs, "activate entity: found enemy"); } BotClearActivateGoalStack(bs); } return qtrue; } /* ================== AIEnter_Seek_NBG ================== */ void AIEnter_Seek_NBG(bot_state_t *bs, char *s) { bot_goal_t goal; char buf[144]; if (trap_BotGetTopGoal(bs->gs, &goal)) { trap_BotGoalName(goal.number, buf, 144); BotRecordNodeSwitch(bs, "seek NBG", buf, s); } else { BotRecordNodeSwitch(bs, "seek NBG", "no goal", s); } bs->ainode = AINode_Seek_NBG; } /* ================== AINode_Seek_NBG ================== */ int AINode_Seek_NBG(bot_state_t *bs) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; if (BotIsObserver(bs)) { AIEnter_Observer(bs, "seek nbg: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "seek nbg: intermision"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "seek nbg: bot dead"); return qfalse; } // bs->tfl = TFL_DEFAULT; if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; //if in lava or slime the bot should be able to get out if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; // if (BotCanAndWantsToRocketJump(bs)) { bs->tfl |= TFL_ROCKETJUMP; } //map specific code BotMapScripts(bs); //no enemy bs->enemy = -1; //if the bot has no goal if (!trap_BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0; //if the bot touches the current goal else if (BotReachedGoal(bs, &goal)) { BotChooseWeapon(bs); bs->nbg_time = 0; } // if (bs->nbg_time < FloatTime()) { //pop the current goal from the stack trap_BotPopGoal(bs->gs); //check for new nearby items right away //NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches bs->check_time = FloatTime() + 0.05; //go back to seek ltg AIEnter_Seek_LTG(bs, "seek nbg: time out"); return qfalse; } //predict obstacles if (BotAIPredictObstacles(bs, &goal)) return qfalse; //initialize the movement state BotSetupForMovement(bs); //move towards the goal trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); //if the movement failed if (moveresult.failure) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach(bs->ms); bs->nbg_time = 0; } //check if the bot is blocked BotAIBlocked(bs, &moveresult, qtrue); // BotClearPath(bs, &moveresult); //if the viewangles are used for the movement if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); } //if waiting for something else if (moveresult.flags & MOVERESULT_WAITING) { if (random() < bs->thinktime * 0.8) { BotRoamGoal(bs, target); VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); bs->ideal_viewangles[2] *= 0.5; } } else if (!(bs->flags & BFL_IDEALVIEWSET)) { if (!trap_BotGetSecondGoal(bs->gs, &goal)) trap_BotGetTopGoal(bs->gs, &goal); if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); } //FIXME: look at cluster portals? else vectoangles(moveresult.movedir, bs->ideal_viewangles); bs->ideal_viewangles[2] *= 0.5; } //if the weapon is used for the bot movement if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; //if there is an enemy if (BotFindEnemy(bs, -1)) { if (BotWantsToRetreat(bs)) { //keep the current long term goal and retreat AIEnter_Battle_NBG(bs, "seek nbg: found enemy"); } else { trap_BotResetLastAvoidReach(bs->ms); //empty the goal stack trap_BotEmptyGoalStack(bs->gs); //go fight AIEnter_Battle_Fight(bs, "seek nbg: found enemy"); } } return qtrue; } /* ================== AIEnter_Seek_LTG ================== */ void AIEnter_Seek_LTG(bot_state_t *bs, char *s) { bot_goal_t goal; char buf[144]; if (trap_BotGetTopGoal(bs->gs, &goal)) { trap_BotGoalName(goal.number, buf, 144); BotRecordNodeSwitch(bs, "seek LTG", buf, s); } else { BotRecordNodeSwitch(bs, "seek LTG", "no goal", s); } bs->ainode = AINode_Seek_LTG; } /* ================== AINode_Seek_LTG ================== */ int AINode_Seek_LTG(bot_state_t *bs) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; int range; //char buf[128]; //bot_goal_t tmpgoal; if (BotIsObserver(bs)) { AIEnter_Observer(bs, "seek ltg: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "seek ltg: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "seek ltg: bot dead"); return qfalse; } // if (BotChat_Random(bs)) { bs->stand_time = FloatTime() + BotChatTime(bs); AIEnter_Stand(bs, "seek ltg: random chat"); return qfalse; } // bs->tfl = TFL_DEFAULT; if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; //if in lava or slime the bot should be able to get out if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; // if (BotCanAndWantsToRocketJump(bs)) { bs->tfl |= TFL_ROCKETJUMP; } //map specific code BotMapScripts(bs); //no enemy bs->enemy = -1; // if (bs->killedenemy_time > FloatTime() - 2) { if (random() < bs->thinktime * 1) { trap_EA_Gesture(bs->client); } } //if there is an enemy if (BotFindEnemy(bs, -1)) { if (BotWantsToRetreat(bs)) { //keep the current long term goal and retreat AIEnter_Battle_Retreat(bs, "seek ltg: found enemy"); return qfalse; } else { trap_BotResetLastAvoidReach(bs->ms); //empty the goal stack trap_BotEmptyGoalStack(bs->gs); //go fight AIEnter_Battle_Fight(bs, "seek ltg: found enemy"); return qfalse; } } // BotTeamGoals(bs, qfalse); //get the current long term goal if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) { return qtrue; } //check for nearby goals periodicly if (bs->check_time < FloatTime()) { bs->check_time = FloatTime() + 0.5; //check if the bot wants to camp BotWantsToCamp(bs); // if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400; else range = 150; // #ifdef CTF if (gametype == GT_CTF) { //if carrying a flag the bot shouldn't be distracted too much if (BotCTFCarryingFlag(bs)) range = 50; } #endif //CTF #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (Bot1FCTFCarryingFlag(bs)) range = 50; } else if (gametype == GT_HARVESTER) { if (BotHarvesterCarryingCubes(bs)) range = 80; } #endif // if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { trap_BotResetLastAvoidReach(bs->ms); //get the goal at the top of the stack //trap_BotGetTopGoal(bs->gs, &tmpgoal); //trap_BotGoalName(tmpgoal.number, buf, 144); //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); //time the bot gets to pick up the nearby goal item bs->nbg_time = FloatTime() + 4 + range * 0.01; AIEnter_Seek_NBG(bs, "ltg seek: nbg"); return qfalse; } } //predict obstacles if (BotAIPredictObstacles(bs, &goal)) return qfalse; //initialize the movement state BotSetupForMovement(bs); //move towards the goal trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); //if the movement failed if (moveresult.failure) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach(bs->ms); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); bs->ltg_time = 0; } // BotAIBlocked(bs, &moveresult, qtrue); // BotClearPath(bs, &moveresult); //if the viewangles are used for the movement if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); } //if waiting for something else if (moveresult.flags & MOVERESULT_WAITING) { if (random() < bs->thinktime * 0.8) { BotRoamGoal(bs, target); VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); bs->ideal_viewangles[2] *= 0.5; } } else if (!(bs->flags & BFL_IDEALVIEWSET)) { if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); } //FIXME: look at cluster portals? else if (VectorLengthSquared(moveresult.movedir)) { vectoangles(moveresult.movedir, bs->ideal_viewangles); } else if (random() < bs->thinktime * 0.8) { BotRoamGoal(bs, target); VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); bs->ideal_viewangles[2] *= 0.5; } bs->ideal_viewangles[2] *= 0.5; } //if the weapon is used for the bot movement if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; // return qtrue; } /* ================== AIEnter_Battle_Fight ================== */ void AIEnter_Battle_Fight(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "battle fight", "", s); trap_BotResetLastAvoidReach(bs->ms); bs->ainode = AINode_Battle_Fight; } /* ================== AIEnter_Battle_Fight ================== */ void AIEnter_Battle_SuicidalFight(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "battle fight", "", s); trap_BotResetLastAvoidReach(bs->ms); bs->ainode = AINode_Battle_Fight; bs->flags |= BFL_FIGHTSUICIDAL; } /* ================== AINode_Battle_Fight ================== */ int AINode_Battle_Fight(bot_state_t *bs) { int areanum; vec3_t target; aas_entityinfo_t entinfo; bot_moveresult_t moveresult; if (BotIsObserver(bs)) { AIEnter_Observer(bs, "battle fight: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "battle fight: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "battle fight: bot dead"); return qfalse; } //if there is another better enemy if (BotFindEnemy(bs, bs->enemy)) { #ifdef DEBUG BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); #endif } //if no enemy if (bs->enemy < 0) { AIEnter_Seek_LTG(bs, "battle fight: no enemy"); return qfalse; } // BotEntityInfo(bs->enemy, &entinfo); //if the enemy is dead if (bs->enemydeath_time) { if (bs->enemydeath_time < FloatTime() - 1.0) { bs->enemydeath_time = 0; if (bs->enemysuicide) { BotChat_EnemySuicide(bs); } if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) { bs->stand_time = FloatTime() + BotChatTime(bs); AIEnter_Stand(bs, "battle fight: enemy dead"); } else { bs->ltg_time = 0; AIEnter_Seek_LTG(bs, "battle fight: enemy dead"); } return qfalse; } } else { if (EntityIsDead(&entinfo)) { bs->enemydeath_time = FloatTime(); } } //if the enemy is invisible and not shooting the bot looses track easily if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { if (random() < 0.2) { AIEnter_Seek_LTG(bs, "battle fight: invisible"); return qfalse; } } // VectorCopy(entinfo.origin, target); // if not a player enemy if (bs->enemy >= MAX_CLIENTS) { #ifdef MISSIONPACK // if attacking an obelisk if ( bs->enemy == redobelisk.entitynum || bs->enemy == blueobelisk.entitynum ) { target[2] += 16; } #endif } //update the reachability area and origin if possible areanum = BotPointAreaNum(target); if (areanum && trap_AAS_AreaReachability(areanum)) { VectorCopy(target, bs->lastenemyorigin); bs->lastenemyareanum = areanum; } //update the attack inventory values BotUpdateBattleInventory(bs, bs->enemy); //if the bot's health decreased if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { if (BotChat_HitNoDeath(bs)) { bs->stand_time = FloatTime() + BotChatTime(bs); AIEnter_Stand(bs, "battle fight: chat health decreased"); return qfalse; } } //if the bot hit someone if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount) { if (BotChat_HitNoKill(bs)) { bs->stand_time = FloatTime() + BotChatTime(bs); AIEnter_Stand(bs, "battle fight: chat hit someone"); return qfalse; } } //if the enemy is not visible if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { if (BotWantsToChase(bs)) { AIEnter_Battle_Chase(bs, "battle fight: enemy out of sight"); return qfalse; } else { AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight"); return qfalse; } } //use holdable items BotBattleUseItems(bs); // bs->tfl = TFL_DEFAULT; if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; //if in lava or slime the bot should be able to get out if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; // if (BotCanAndWantsToRocketJump(bs)) { bs->tfl |= TFL_ROCKETJUMP; } //choose the best weapon to fight with BotChooseWeapon(bs); //do attack movements moveresult = BotAttackMove(bs, bs->tfl); //if the movement failed if (moveresult.failure) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach(bs->ms); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); bs->ltg_time = 0; } // BotAIBlocked(bs, &moveresult, qfalse); //aim at the enemy BotAimAtEnemy(bs); //attack the enemy if possible BotCheckAttack(bs); //if the bot wants to retreat if (!(bs->flags & BFL_FIGHTSUICIDAL)) { if (BotWantsToRetreat(bs)) { AIEnter_Battle_Retreat(bs, "battle fight: wants to retreat"); return qtrue; } } return qtrue; } /* ================== AIEnter_Battle_Chase ================== */ void AIEnter_Battle_Chase(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "battle chase", "", s); bs->chase_time = FloatTime(); bs->ainode = AINode_Battle_Chase; } /* ================== AINode_Battle_Chase ================== */ int AINode_Battle_Chase(bot_state_t *bs) { bot_goal_t goal; vec3_t target, dir; bot_moveresult_t moveresult; float range; if (BotIsObserver(bs)) { AIEnter_Observer(bs, "battle chase: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "battle chase: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "battle chase: bot dead"); return qfalse; } //if no enemy if (bs->enemy < 0) { AIEnter_Seek_LTG(bs, "battle chase: no enemy"); return qfalse; } //if the enemy is visible if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { AIEnter_Battle_Fight(bs, "battle chase"); return qfalse; } //if there is another enemy if (BotFindEnemy(bs, -1)) { AIEnter_Battle_Fight(bs, "battle chase: better enemy"); return qfalse; } //there is no last enemy area if (!bs->lastenemyareanum) { AIEnter_Seek_LTG(bs, "battle chase: no enemy area"); return qfalse; } // bs->tfl = TFL_DEFAULT; if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; //if in lava or slime the bot should be able to get out if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; // if (BotCanAndWantsToRocketJump(bs)) { bs->tfl |= TFL_ROCKETJUMP; } //map specific code BotMapScripts(bs); //create the chase goal goal.entitynum = bs->enemy; goal.areanum = bs->lastenemyareanum; VectorCopy(bs->lastenemyorigin, goal.origin); VectorSet(goal.mins, -8, -8, -8); VectorSet(goal.maxs, 8, 8, 8); //if the last seen enemy spot is reached the enemy could not be found if (trap_BotTouchingGoal(bs->origin, &goal)) bs->chase_time = 0; //if there's no chase time left if (!bs->chase_time || bs->chase_time < FloatTime() - 10) { AIEnter_Seek_LTG(bs, "battle chase: time out"); return qfalse; } //check for nearby goals periodicly if (bs->check_time < FloatTime()) { bs->check_time = FloatTime() + 1; range = 150; // if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { //the bot gets 5 seconds to pick up the nearby goal item bs->nbg_time = FloatTime() + 0.1 * range + 1; trap_BotResetLastAvoidReach(bs->ms); AIEnter_Battle_NBG(bs, "battle chase: nbg"); return qfalse; } } // BotUpdateBattleInventory(bs, bs->enemy); //initialize the movement state BotSetupForMovement(bs); //move towards the goal trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); //if the movement failed if (moveresult.failure) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach(bs->ms); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); bs->ltg_time = 0; } // BotAIBlocked(bs, &moveresult, qfalse); // if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); } else if (!(bs->flags & BFL_IDEALVIEWSET)) { if (bs->chase_time > FloatTime() - 2) { BotAimAtEnemy(bs); } else { if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); } else { vectoangles(moveresult.movedir, bs->ideal_viewangles); } } bs->ideal_viewangles[2] *= 0.5; } //if the weapon is used for the bot movement if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; //if the bot is in the area the enemy was last seen in if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0; //if the bot wants to retreat (the bot could have been damage during the chase) if (BotWantsToRetreat(bs)) { AIEnter_Battle_Retreat(bs, "battle chase: wants to retreat"); return qtrue; } return qtrue; } /* ================== AIEnter_Battle_Retreat ================== */ void AIEnter_Battle_Retreat(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "battle retreat", "", s); bs->ainode = AINode_Battle_Retreat; } /* ================== AINode_Battle_Retreat ================== */ int AINode_Battle_Retreat(bot_state_t *bs) { bot_goal_t goal; aas_entityinfo_t entinfo; bot_moveresult_t moveresult; vec3_t target, dir; float attack_skill, range; int areanum; if (BotIsObserver(bs)) { AIEnter_Observer(bs, "battle retreat: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "battle retreat: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "battle retreat: bot dead"); return qfalse; } //if no enemy if (bs->enemy < 0) { AIEnter_Seek_LTG(bs, "battle retreat: no enemy"); return qfalse; } // BotEntityInfo(bs->enemy, &entinfo); if (EntityIsDead(&entinfo)) { AIEnter_Seek_LTG(bs, "battle retreat: enemy dead"); return qfalse; } //if there is another better enemy if (BotFindEnemy(bs, bs->enemy)) { #ifdef DEBUG BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); #endif } // bs->tfl = TFL_DEFAULT; if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; //if in lava or slime the bot should be able to get out if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; //map specific code BotMapScripts(bs); //update the attack inventory values BotUpdateBattleInventory(bs, bs->enemy); //if the bot doesn't want to retreat anymore... probably picked up some nice items if (BotWantsToChase(bs)) { //empty the goal stack, when chasing, only the enemy is the goal trap_BotEmptyGoalStack(bs->gs); //go chase the enemy AIEnter_Battle_Chase(bs, "battle retreat: wants to chase"); return qfalse; } //update the last time the enemy was visible if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { bs->enemyvisible_time = FloatTime(); VectorCopy(entinfo.origin, target); // if not a player enemy if (bs->enemy >= MAX_CLIENTS) { #ifdef MISSIONPACK // if attacking an obelisk if ( bs->enemy == redobelisk.entitynum || bs->enemy == blueobelisk.entitynum ) { target[2] += 16; } #endif } //update the reachability area and origin if possible areanum = BotPointAreaNum(target); if (areanum && trap_AAS_AreaReachability(areanum)) { VectorCopy(target, bs->lastenemyorigin); bs->lastenemyareanum = areanum; } } //if the enemy is NOT visible for 4 seconds if (bs->enemyvisible_time < FloatTime() - 4) { AIEnter_Seek_LTG(bs, "battle retreat: lost enemy"); return qfalse; } //else if the enemy is NOT visible else if (bs->enemyvisible_time < FloatTime()) { //if there is another enemy if (BotFindEnemy(bs, -1)) { AIEnter_Battle_Fight(bs, "battle retreat: another enemy"); return qfalse; } } // BotTeamGoals(bs, qtrue); //use holdable items BotBattleUseItems(bs); //get the current long term goal while retreating if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) { AIEnter_Battle_SuicidalFight(bs, "battle retreat: no way out"); return qfalse; } //check for nearby goals periodicly if (bs->check_time < FloatTime()) { bs->check_time = FloatTime() + 1; range = 150; #ifdef CTF if (gametype == GT_CTF) { //if carrying a flag the bot shouldn't be distracted too much if (BotCTFCarryingFlag(bs)) range = 50; } #endif //CTF #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (Bot1FCTFCarryingFlag(bs)) range = 50; } else if (gametype == GT_HARVESTER) { if (BotHarvesterCarryingCubes(bs)) range = 80; } #endif // if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { trap_BotResetLastAvoidReach(bs->ms); //time the bot gets to pick up the nearby goal item bs->nbg_time = FloatTime() + range / 100 + 1; AIEnter_Battle_NBG(bs, "battle retreat: nbg"); return qfalse; } } //initialize the movement state BotSetupForMovement(bs); //move towards the goal trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); //if the movement failed if (moveresult.failure) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach(bs->ms); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); bs->ltg_time = 0; } // BotAIBlocked(bs, &moveresult, qfalse); //choose the best weapon to fight with BotChooseWeapon(bs); //if the view is fixed for the movement if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); } else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) && !(bs->flags & BFL_IDEALVIEWSET) ) { attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); //if the bot is skilled anough if (attack_skill > 0.3) { BotAimAtEnemy(bs); } else { if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); } else { vectoangles(moveresult.movedir, bs->ideal_viewangles); } bs->ideal_viewangles[2] *= 0.5; } } //if the weapon is used for the bot movement if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; //attack the enemy if possible BotCheckAttack(bs); // return qtrue; } /* ================== AIEnter_Battle_NBG ================== */ void AIEnter_Battle_NBG(bot_state_t *bs, char *s) { BotRecordNodeSwitch(bs, "battle NBG", "", s); bs->ainode = AINode_Battle_NBG; } /* ================== AINode_Battle_NBG ================== */ int AINode_Battle_NBG(bot_state_t *bs) { int areanum; bot_goal_t goal; aas_entityinfo_t entinfo; bot_moveresult_t moveresult; float attack_skill; vec3_t target, dir; if (BotIsObserver(bs)) { AIEnter_Observer(bs, "battle nbg: observer"); return qfalse; } //if in the intermission if (BotIntermission(bs)) { AIEnter_Intermission(bs, "battle nbg: intermission"); return qfalse; } //respawn if dead if (BotIsDead(bs)) { AIEnter_Respawn(bs, "battle nbg: bot dead"); return qfalse; } //if no enemy if (bs->enemy < 0) { AIEnter_Seek_NBG(bs, "battle nbg: no enemy"); return qfalse; } // BotEntityInfo(bs->enemy, &entinfo); if (EntityIsDead(&entinfo)) { AIEnter_Seek_NBG(bs, "battle nbg: enemy dead"); return qfalse; } // bs->tfl = TFL_DEFAULT; if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; //if in lava or slime the bot should be able to get out if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; // if (BotCanAndWantsToRocketJump(bs)) { bs->tfl |= TFL_ROCKETJUMP; } //map specific code BotMapScripts(bs); //update the last time the enemy was visible if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { bs->enemyvisible_time = FloatTime(); VectorCopy(entinfo.origin, target); // if not a player enemy if (bs->enemy >= MAX_CLIENTS) { #ifdef MISSIONPACK // if attacking an obelisk if ( bs->enemy == redobelisk.entitynum || bs->enemy == blueobelisk.entitynum ) { target[2] += 16; } #endif } //update the reachability area and origin if possible areanum = BotPointAreaNum(target); if (areanum && trap_AAS_AreaReachability(areanum)) { VectorCopy(target, bs->lastenemyorigin); bs->lastenemyareanum = areanum; } } //if the bot has no goal or touches the current goal if (!trap_BotGetTopGoal(bs->gs, &goal)) { bs->nbg_time = 0; } else if (BotReachedGoal(bs, &goal)) { bs->nbg_time = 0; } // if (bs->nbg_time < FloatTime()) { //pop the current goal from the stack trap_BotPopGoal(bs->gs); //if the bot still has a goal if (trap_BotGetTopGoal(bs->gs, &goal)) AIEnter_Battle_Retreat(bs, "battle nbg: time out"); else AIEnter_Battle_Fight(bs, "battle nbg: time out"); // return qfalse; } //initialize the movement state BotSetupForMovement(bs); //move towards the goal trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); //if the movement failed if (moveresult.failure) { //reset the avoid reach, otherwise bot is stuck in current area trap_BotResetAvoidReach(bs->ms); //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); bs->nbg_time = 0; } // BotAIBlocked(bs, &moveresult, qfalse); //update the attack inventory values BotUpdateBattleInventory(bs, bs->enemy); //choose the best weapon to fight with BotChooseWeapon(bs); //if the view is fixed for the movement if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); } else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) && !(bs->flags & BFL_IDEALVIEWSET)) { attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); //if the bot is skilled anough and the enemy is visible if (attack_skill > 0.3) { //&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy) BotAimAtEnemy(bs); } else { if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { VectorSubtract(target, bs->origin, dir); vectoangles(dir, bs->ideal_viewangles); } else { vectoangles(moveresult.movedir, bs->ideal_viewangles); } bs->ideal_viewangles[2] *= 0.5; } } //if the weapon is used for the bot movement if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; //attack the enemy if possible BotCheckAttack(bs); // return qtrue; } ================================================ FILE: code/game/ai_dmnet.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_dmnet.h * * desc: Quake3 bot AI * * $Archive: /source/code/botai/ai_chat.c $ * *****************************************************************************/ #define MAX_NODESWITCHES 50 void AIEnter_Intermission(bot_state_t *bs, char *s); void AIEnter_Observer(bot_state_t *bs, char *s); void AIEnter_Respawn(bot_state_t *bs, char *s); void AIEnter_Stand(bot_state_t *bs, char *s); void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s); void AIEnter_Seek_NBG(bot_state_t *bs, char *s); void AIEnter_Seek_LTG(bot_state_t *bs, char *s); void AIEnter_Seek_Camp(bot_state_t *bs, char *s); void AIEnter_Battle_Fight(bot_state_t *bs, char *s); void AIEnter_Battle_Chase(bot_state_t *bs, char *s); void AIEnter_Battle_Retreat(bot_state_t *bs, char *s); void AIEnter_Battle_NBG(bot_state_t *bs, char *s); int AINode_Intermission(bot_state_t *bs); int AINode_Observer(bot_state_t *bs); int AINode_Respawn(bot_state_t *bs); int AINode_Stand(bot_state_t *bs); int AINode_Seek_ActivateEntity(bot_state_t *bs); int AINode_Seek_NBG(bot_state_t *bs); int AINode_Seek_LTG(bot_state_t *bs); int AINode_Battle_Fight(bot_state_t *bs); int AINode_Battle_Chase(bot_state_t *bs); int AINode_Battle_Retreat(bot_state_t *bs); int AINode_Battle_NBG(bot_state_t *bs); void BotResetNodeSwitches(void); void BotDumpNodeSwitches(bot_state_t *bs); ================================================ FILE: code/game/ai_dmq3.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_dmq3.c * * desc: Quake3 bot AI * * $Archive: /MissionPack/code/game/ai_dmq3.c $ * *****************************************************************************/ #include "g_local.h" #include "botlib.h" #include "be_aas.h" #include "be_ea.h" #include "be_ai_char.h" #include "be_ai_chat.h" #include "be_ai_gen.h" #include "be_ai_goal.h" #include "be_ai_move.h" #include "be_ai_weap.h" // #include "ai_main.h" #include "ai_dmq3.h" #include "ai_chat.h" #include "ai_cmd.h" #include "ai_dmnet.h" #include "ai_team.h" // #include "chars.h" //characteristics #include "inv.h" //indexes into the inventory #include "syn.h" //synonyms #include "match.h" //string matching types and vars // for the voice chats #include "../../ui/menudef.h" // sos001205 - for q3_ui also // from aasfile.h #define AREACONTENTS_MOVER 1024 #define AREACONTENTS_MODELNUMSHIFT 24 #define AREACONTENTS_MAXMODELNUM 0xFF #define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) #define IDEAL_ATTACKDIST 140 #define MAX_WAYPOINTS 128 // bot_waypoint_t botai_waypoints[MAX_WAYPOINTS]; bot_waypoint_t *botai_freewaypoints; //NOTE: not using a cvars which can be updated because the game should be reloaded anyway int gametype; //game type int maxclients; //maximum number of clients vmCvar_t bot_grapple; vmCvar_t bot_rocketjump; vmCvar_t bot_fastchat; vmCvar_t bot_nochat; vmCvar_t bot_testrchat; vmCvar_t bot_challenge; vmCvar_t bot_predictobstacles; vmCvar_t g_spSkill; extern vmCvar_t bot_developer; vec3_t lastteleport_origin; //last teleport event origin float lastteleport_time; //last teleport event time int max_bspmodelindex; //maximum BSP model index //CTF flag goals bot_goal_t ctf_redflag; bot_goal_t ctf_blueflag; #ifdef MISSIONPACK bot_goal_t ctf_neutralflag; bot_goal_t redobelisk; bot_goal_t blueobelisk; bot_goal_t neutralobelisk; #endif #define MAX_ALTROUTEGOALS 32 int altroutegoals_setup; aas_altroutegoal_t red_altroutegoals[MAX_ALTROUTEGOALS]; int red_numaltroutegoals; aas_altroutegoal_t blue_altroutegoals[MAX_ALTROUTEGOALS]; int blue_numaltroutegoals; /* ================== BotSetUserInfo ================== */ void BotSetUserInfo(bot_state_t *bs, char *key, char *value) { char userinfo[MAX_INFO_STRING]; trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); Info_SetValueForKey(userinfo, key, value); trap_SetUserinfo(bs->client, userinfo); ClientUserinfoChanged( bs->client ); } /* ================== BotCTFCarryingFlag ================== */ int BotCTFCarryingFlag(bot_state_t *bs) { if (gametype != GT_CTF) return CTF_FLAG_NONE; if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED; else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE; return CTF_FLAG_NONE; } /* ================== BotTeam ================== */ int BotTeam(bot_state_t *bs) { char info[1024]; if (bs->client < 0 || bs->client >= MAX_CLIENTS) { //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n"); return qfalse; } trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info)); // if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return TEAM_RED; else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return TEAM_BLUE; return TEAM_FREE; } /* ================== BotOppositeTeam ================== */ int BotOppositeTeam(bot_state_t *bs) { switch(BotTeam(bs)) { case TEAM_RED: return TEAM_BLUE; case TEAM_BLUE: return TEAM_RED; default: return TEAM_FREE; } } /* ================== BotEnemyFlag ================== */ bot_goal_t *BotEnemyFlag(bot_state_t *bs) { if (BotTeam(bs) == TEAM_RED) { return &ctf_blueflag; } else { return &ctf_redflag; } } /* ================== BotTeamFlag ================== */ bot_goal_t *BotTeamFlag(bot_state_t *bs) { if (BotTeam(bs) == TEAM_RED) { return &ctf_redflag; } else { return &ctf_blueflag; } } /* ================== EntityIsDead ================== */ qboolean EntityIsDead(aas_entityinfo_t *entinfo) { playerState_t ps; if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) { //retrieve the current client state BotAI_GetClientState( entinfo->number, &ps ); if (ps.pm_type != PM_NORMAL) return qtrue; } return qfalse; } /* ================== EntityCarriesFlag ================== */ qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) { if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) return qtrue; if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) ) return qtrue; #ifdef MISSIONPACK if ( entinfo->powerups & ( 1 << PW_NEUTRALFLAG ) ) return qtrue; #endif return qfalse; } /* ================== EntityIsInvisible ================== */ qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) { // the flag is always visible if (EntityCarriesFlag(entinfo)) { return qfalse; } if (entinfo->powerups & (1 << PW_INVIS)) { return qtrue; } return qfalse; } /* ================== EntityIsShooting ================== */ qboolean EntityIsShooting(aas_entityinfo_t *entinfo) { if (entinfo->flags & EF_FIRING) { return qtrue; } return qfalse; } /* ================== EntityIsChatting ================== */ qboolean EntityIsChatting(aas_entityinfo_t *entinfo) { if (entinfo->flags & EF_TALK) { return qtrue; } return qfalse; } /* ================== EntityHasQuad ================== */ qboolean EntityHasQuad(aas_entityinfo_t *entinfo) { if (entinfo->powerups & (1 << PW_QUAD)) { return qtrue; } return qfalse; } #ifdef MISSIONPACK /* ================== EntityHasKamikze ================== */ qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo) { if (entinfo->flags & EF_KAMIKAZE) { return qtrue; } return qfalse; } /* ================== EntityCarriesCubes ================== */ qboolean EntityCarriesCubes(aas_entityinfo_t *entinfo) { entityState_t state; if (gametype != GT_HARVESTER) return qfalse; //FIXME: get this info from the aas_entityinfo_t ? BotAI_GetEntityState(entinfo->number, &state); if (state.generic1 > 0) return qtrue; return qfalse; } /* ================== Bot1FCTFCarryingFlag ================== */ int Bot1FCTFCarryingFlag(bot_state_t *bs) { if (gametype != GT_1FCTF) return qfalse; if (bs->inventory[INVENTORY_NEUTRALFLAG] > 0) return qtrue; return qfalse; } /* ================== BotHarvesterCarryingCubes ================== */ int BotHarvesterCarryingCubes(bot_state_t *bs) { if (gametype != GT_HARVESTER) return qfalse; if (bs->inventory[INVENTORY_REDCUBE] > 0) return qtrue; if (bs->inventory[INVENTORY_BLUECUBE] > 0) return qtrue; return qfalse; } #endif /* ================== BotRememberLastOrderedTask ================== */ void BotRememberLastOrderedTask(bot_state_t *bs) { if (!bs->ordered) { return; } bs->lastgoal_decisionmaker = bs->decisionmaker; bs->lastgoal_ltgtype = bs->ltgtype; memcpy(&bs->lastgoal_teamgoal, &bs->teamgoal, sizeof(bot_goal_t)); bs->lastgoal_teammate = bs->teammate; } /* ================== BotSetTeamStatus ================== */ void BotSetTeamStatus(bot_state_t *bs) { #ifdef MISSIONPACK int teamtask; aas_entityinfo_t entinfo; teamtask = TEAMTASK_PATROL; switch(bs->ltgtype) { case LTG_TEAMHELP: break; case LTG_TEAMACCOMPANY: BotEntityInfo(bs->teammate, &entinfo); if ( ( (gametype == GT_CTF || gametype == GT_1FCTF) && EntityCarriesFlag(&entinfo)) || ( gametype == GT_HARVESTER && EntityCarriesCubes(&entinfo)) ) { teamtask = TEAMTASK_ESCORT; } else { teamtask = TEAMTASK_FOLLOW; } break; case LTG_DEFENDKEYAREA: teamtask = TEAMTASK_DEFENSE; break; case LTG_GETFLAG: teamtask = TEAMTASK_OFFENSE; break; case LTG_RUSHBASE: teamtask = TEAMTASK_DEFENSE; break; case LTG_RETURNFLAG: teamtask = TEAMTASK_RETRIEVE; break; case LTG_CAMP: case LTG_CAMPORDER: teamtask = TEAMTASK_CAMP; break; case LTG_PATROL: teamtask = TEAMTASK_PATROL; break; case LTG_GETITEM: teamtask = TEAMTASK_PATROL; break; case LTG_KILL: teamtask = TEAMTASK_PATROL; break; case LTG_HARVEST: teamtask = TEAMTASK_OFFENSE; break; case LTG_ATTACKENEMYBASE: teamtask = TEAMTASK_OFFENSE; break; default: teamtask = TEAMTASK_PATROL; break; } BotSetUserInfo(bs, "teamtask", va("%d", teamtask)); #endif } /* ================== BotSetLastOrderedTask ================== */ int BotSetLastOrderedTask(bot_state_t *bs) { if (gametype == GT_CTF) { // don't go back to returning the flag if it's at the base if ( bs->lastgoal_ltgtype == LTG_RETURNFLAG ) { if ( BotTeam(bs) == TEAM_RED ) { if ( bs->redflagstatus == 0 ) { bs->lastgoal_ltgtype = 0; } } else { if ( bs->blueflagstatus == 0 ) { bs->lastgoal_ltgtype = 0; } } } } if ( bs->lastgoal_ltgtype ) { bs->decisionmaker = bs->lastgoal_decisionmaker; bs->ordered = qtrue; bs->ltgtype = bs->lastgoal_ltgtype; memcpy(&bs->teamgoal, &bs->lastgoal_teamgoal, sizeof(bot_goal_t)); bs->teammate = bs->lastgoal_teammate; bs->teamgoal_time = FloatTime() + 300; BotSetTeamStatus(bs); // if ( gametype == GT_CTF ) { if ( bs->ltgtype == LTG_GETFLAG ) { bot_goal_t *tb, *eb; int tt, et; tb = BotTeamFlag(bs); eb = BotEnemyFlag(bs); tt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, tb->areanum, TFL_DEFAULT); et = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, eb->areanum, TFL_DEFAULT); // if the travel time towards the enemy base is larger than towards our base if (et > tt) { //get an alternative route goal towards the enemy base BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); } } } return qtrue; } return qfalse; } /* ================== BotRefuseOrder ================== */ void BotRefuseOrder(bot_state_t *bs) { if (!bs->ordered) return; // if the bot was ordered to do something if ( bs->order_time && bs->order_time > FloatTime() - 10 ) { trap_EA_Action(bs->client, ACTION_NEGATIVE); BotVoiceChat(bs, bs->decisionmaker, VOICECHAT_NO); bs->order_time = 0; } } /* ================== BotCTFSeekGoals ================== */ void BotCTFSeekGoals(bot_state_t *bs) { float rnd, l1, l2; int flagstatus, c; vec3_t dir; aas_entityinfo_t entinfo; //when carrying a flag in ctf the bot should rush to the base if (BotCTFCarryingFlag(bs)) { //if not already rushing to the base if (bs->ltgtype != LTG_RUSHBASE) { BotRefuseOrder(bs); bs->ltgtype = LTG_RUSHBASE; bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; bs->rushbaseaway_time = 0; bs->decisionmaker = bs->client; bs->ordered = qfalse; // switch(BotTeam(bs)) { case TEAM_RED: VectorSubtract(bs->origin, ctf_blueflag.origin, dir); break; case TEAM_BLUE: VectorSubtract(bs->origin, ctf_redflag.origin, dir); break; default: VectorSet(dir, 999, 999, 999); break; } // if the bot picked up the flag very close to the enemy base if ( VectorLength(dir) < 128 ) { // get an alternative route goal through the enemy base BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); } else { // don't use any alt route goal, just get the hell out of the base bs->altroutegoal.areanum = 0; } BotSetUserInfo(bs, "teamtask", va("%d", TEAMTASK_OFFENSE)); BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); } else if (bs->rushbaseaway_time > FloatTime()) { if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus; else flagstatus = bs->blueflagstatus; //if the flag is back if (flagstatus == 0) { bs->rushbaseaway_time = 0; } } return; } // if the bot decided to follow someone if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { // if the team mate being accompanied no longer carries the flag BotEntityInfo(bs->teammate, &entinfo); if (!EntityCarriesFlag(&entinfo)) { bs->ltgtype = 0; } } // if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; //if our team has the enemy flag and our flag is at the base if (flagstatus == 1) { // if (bs->owndecision_time < FloatTime()) { //if Not defending the base already if (!(bs->ltgtype == LTG_DEFENDKEYAREA && (bs->teamgoal.number == ctf_redflag.number || bs->teamgoal.number == ctf_blueflag.number))) { //if there is a visible team mate flag carrier c = BotTeamFlagCarrierVisible(bs); if (c >= 0 && // and not already following the team mate flag carrier (bs->ltgtype != LTG_TEAMACCOMPANY || bs->teammate != c)) { // BotRefuseOrder(bs); //follow the flag carrier bs->decisionmaker = bs->client; bs->ordered = qfalse; //the team mate bs->teammate = c; //last time the team mate was visible bs->teammatevisible_time = FloatTime(); //no message bs->teammessage_time = 0; //no arrive message bs->arrive_time = 1; // BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); //get the team goal time bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; bs->ltgtype = LTG_TEAMACCOMPANY; bs->formation_dist = 3.5 * 32; //3.5 meter BotSetTeamStatus(bs); bs->owndecision_time = FloatTime() + 5; } } } return; } //if the enemy has our flag else if (flagstatus == 2) { // if (bs->owndecision_time < FloatTime()) { //if enemy flag carrier is visible c = BotEnemyFlagCarrierVisible(bs); if (c >= 0) { //FIXME: fight enemy flag carrier } //if not already doing something important if (bs->ltgtype != LTG_GETFLAG && bs->ltgtype != LTG_RETURNFLAG && bs->ltgtype != LTG_TEAMHELP && bs->ltgtype != LTG_TEAMACCOMPANY && bs->ltgtype != LTG_CAMPORDER && bs->ltgtype != LTG_PATROL && bs->ltgtype != LTG_GETITEM) { BotRefuseOrder(bs); bs->decisionmaker = bs->client; bs->ordered = qfalse; // if (random() < 0.5) { //go for the enemy flag bs->ltgtype = LTG_GETFLAG; } else { bs->ltgtype = LTG_RETURNFLAG; } //no team message bs->teammessage_time = 0; //set the time the bot will stop getting the flag bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; //get an alternative route goal towards the enemy base BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); // BotSetTeamStatus(bs); bs->owndecision_time = FloatTime() + 5; } } return; } //if both flags Not at their bases else if (flagstatus == 3) { // if (bs->owndecision_time < FloatTime()) { // if not trying to return the flag and not following the team flag carrier if ( bs->ltgtype != LTG_RETURNFLAG && bs->ltgtype != LTG_TEAMACCOMPANY ) { // c = BotTeamFlagCarrierVisible(bs); // if there is a visible team mate flag carrier if (c >= 0) { BotRefuseOrder(bs); //follow the flag carrier bs->decisionmaker = bs->client; bs->ordered = qfalse; //the team mate bs->teammate = c; //last time the team mate was visible bs->teammatevisible_time = FloatTime(); //no message bs->teammessage_time = 0; //no arrive message bs->arrive_time = 1; // BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); //get the team goal time bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; bs->ltgtype = LTG_TEAMACCOMPANY; bs->formation_dist = 3.5 * 32; //3.5 meter // BotSetTeamStatus(bs); bs->owndecision_time = FloatTime() + 5; } else { BotRefuseOrder(bs); bs->decisionmaker = bs->client; bs->ordered = qfalse; //get the enemy flag bs->teammessage_time = FloatTime() + 2 * random(); //get the flag bs->ltgtype = LTG_RETURNFLAG; //set the time the bot will stop getting the flag bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; //get an alternative route goal towards the enemy base BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); // BotSetTeamStatus(bs); bs->owndecision_time = FloatTime() + 5; } } } return; } // don't just do something wait for the bot team leader to give orders if (BotTeamLeader(bs)) { return; } // if the bot is ordered to do something if ( bs->lastgoal_ltgtype ) { bs->teamgoal_time += 60; } // if the bot decided to do something on it's own and has a last ordered goal if ( !bs->ordered && bs->lastgoal_ltgtype ) { bs->ltgtype = 0; } //if already a CTF or team goal if (bs->ltgtype == LTG_TEAMHELP || bs->ltgtype == LTG_TEAMACCOMPANY || bs->ltgtype == LTG_DEFENDKEYAREA || bs->ltgtype == LTG_GETFLAG || bs->ltgtype == LTG_RUSHBASE || bs->ltgtype == LTG_RETURNFLAG || bs->ltgtype == LTG_CAMPORDER || bs->ltgtype == LTG_PATROL || bs->ltgtype == LTG_GETITEM || bs->ltgtype == LTG_MAKELOVE_UNDER || bs->ltgtype == LTG_MAKELOVE_ONTOP) { return; } // if (BotSetLastOrderedTask(bs)) return; // if (bs->owndecision_time > FloatTime()) return;; //if the bot is roaming if (bs->ctfroam_time > FloatTime()) return; //if the bot has anough aggression to decide what to do if (BotAggression(bs) < 50) return; //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); // if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { if (bs->teamtaskpreference & TEAMTP_ATTACKER) { l1 = 0.7f; } else { l1 = 0.2f; } l2 = 0.9f; } else { l1 = 0.4f; l2 = 0.7f; } //get the flag or defend the base rnd = random(); if (rnd < l1 && ctf_redflag.areanum && ctf_blueflag.areanum) { bs->decisionmaker = bs->client; bs->ordered = qfalse; bs->ltgtype = LTG_GETFLAG; //set the time the bot will stop getting the flag bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; //get an alternative route goal towards the enemy base BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); BotSetTeamStatus(bs); } else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { bs->decisionmaker = bs->client; bs->ordered = qfalse; // if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); //set the ltg type bs->ltgtype = LTG_DEFENDKEYAREA; //set the time the bot stops defending the base bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; bs->defendaway_time = 0; BotSetTeamStatus(bs); } else { bs->ltgtype = 0; //set the time the bot will stop roaming bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; BotSetTeamStatus(bs); } bs->owndecision_time = FloatTime() + 5; #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotCTFRetreatGoals ================== */ void BotCTFRetreatGoals(bot_state_t *bs) { //when carrying a flag in ctf the bot should rush to the base if (BotCTFCarryingFlag(bs)) { //if not already rushing to the base if (bs->ltgtype != LTG_RUSHBASE) { BotRefuseOrder(bs); bs->ltgtype = LTG_RUSHBASE; bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; bs->rushbaseaway_time = 0; bs->decisionmaker = bs->client; bs->ordered = qfalse; BotSetTeamStatus(bs); } } } #ifdef MISSIONPACK /* ================== Bot1FCTFSeekGoals ================== */ void Bot1FCTFSeekGoals(bot_state_t *bs) { aas_entityinfo_t entinfo; float rnd, l1, l2; int c; //when carrying a flag in ctf the bot should rush to the base if (Bot1FCTFCarryingFlag(bs)) { //if not already rushing to the base if (bs->ltgtype != LTG_RUSHBASE) { BotRefuseOrder(bs); bs->ltgtype = LTG_RUSHBASE; bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; bs->rushbaseaway_time = 0; bs->decisionmaker = bs->client; bs->ordered = qfalse; //get an alternative route goal towards the enemy base BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); // BotSetTeamStatus(bs); BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); } return; } // if the bot decided to follow someone if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { // if the team mate being accompanied no longer carries the flag BotEntityInfo(bs->teammate, &entinfo); if (!EntityCarriesFlag(&entinfo)) { bs->ltgtype = 0; } } //our team has the flag if (bs->neutralflagstatus == 1) { if (bs->owndecision_time < FloatTime()) { // if not already following someone if (bs->ltgtype != LTG_TEAMACCOMPANY) { //if there is a visible team mate flag carrier c = BotTeamFlagCarrierVisible(bs); if (c >= 0) { BotRefuseOrder(bs); //follow the flag carrier bs->decisionmaker = bs->client; bs->ordered = qfalse; //the team mate bs->teammate = c; //last time the team mate was visible bs->teammatevisible_time = FloatTime(); //no message bs->teammessage_time = 0; //no arrive message bs->arrive_time = 1; // BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); //get the team goal time bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; bs->ltgtype = LTG_TEAMACCOMPANY; bs->formation_dist = 3.5 * 32; //3.5 meter BotSetTeamStatus(bs); bs->owndecision_time = FloatTime() + 5; return; } } //if already a CTF or team goal if (bs->ltgtype == LTG_TEAMHELP || bs->ltgtype == LTG_TEAMACCOMPANY || bs->ltgtype == LTG_DEFENDKEYAREA || bs->ltgtype == LTG_GETFLAG || bs->ltgtype == LTG_RUSHBASE || bs->ltgtype == LTG_CAMPORDER || bs->ltgtype == LTG_PATROL || bs->ltgtype == LTG_ATTACKENEMYBASE || bs->ltgtype == LTG_GETITEM || bs->ltgtype == LTG_MAKELOVE_UNDER || bs->ltgtype == LTG_MAKELOVE_ONTOP) { return; } //if not already attacking the enemy base if (bs->ltgtype != LTG_ATTACKENEMYBASE) { BotRefuseOrder(bs); bs->decisionmaker = bs->client; bs->ordered = qfalse; // if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); else memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); //set the ltg type bs->ltgtype = LTG_ATTACKENEMYBASE; //set the time the bot will stop getting the flag bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; BotSetTeamStatus(bs); bs->owndecision_time = FloatTime() + 5; } } return; } //enemy team has the flag else if (bs->neutralflagstatus == 2) { if (bs->owndecision_time < FloatTime()) { c = BotEnemyFlagCarrierVisible(bs); if (c >= 0) { //FIXME: attack enemy flag carrier } //if already a CTF or team goal if (bs->ltgtype == LTG_TEAMHELP || bs->ltgtype == LTG_TEAMACCOMPANY || bs->ltgtype == LTG_CAMPORDER || bs->ltgtype == LTG_PATROL || bs->ltgtype == LTG_GETITEM) { return; } // if not already defending the base if (bs->ltgtype != LTG_DEFENDKEYAREA) { BotRefuseOrder(bs); bs->decisionmaker = bs->client; bs->ordered = qfalse; // if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); //set the ltg type bs->ltgtype = LTG_DEFENDKEYAREA; //set the time the bot stops defending the base bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; bs->defendaway_time = 0; BotSetTeamStatus(bs); bs->owndecision_time = FloatTime() + 5; } } return; } // don't just do something wait for the bot team leader to give orders if (BotTeamLeader(bs)) { return; } // if the bot is ordered to do something if ( bs->lastgoal_ltgtype ) { bs->teamgoal_time += 60; } // if the bot decided to do something on it's own and has a last ordered goal if ( !bs->ordered && bs->lastgoal_ltgtype ) { bs->ltgtype = 0; } //if already a CTF or team goal if (bs->ltgtype == LTG_TEAMHELP || bs->ltgtype == LTG_TEAMACCOMPANY || bs->ltgtype == LTG_DEFENDKEYAREA || bs->ltgtype == LTG_GETFLAG || bs->ltgtype == LTG_RUSHBASE || bs->ltgtype == LTG_RETURNFLAG || bs->ltgtype == LTG_CAMPORDER || bs->ltgtype == LTG_PATROL || bs->ltgtype == LTG_ATTACKENEMYBASE || bs->ltgtype == LTG_GETITEM || bs->ltgtype == LTG_MAKELOVE_UNDER || bs->ltgtype == LTG_MAKELOVE_ONTOP) { return; } // if (BotSetLastOrderedTask(bs)) return; // if (bs->owndecision_time > FloatTime()) return;; //if the bot is roaming if (bs->ctfroam_time > FloatTime()) return; //if the bot has anough aggression to decide what to do if (BotAggression(bs) < 50) return; //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); // if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { if (bs->teamtaskpreference & TEAMTP_ATTACKER) { l1 = 0.7f; } else { l1 = 0.2f; } l2 = 0.9f; } else { l1 = 0.4f; l2 = 0.7f; } //get the flag or defend the base rnd = random(); if (rnd < l1 && ctf_neutralflag.areanum) { bs->decisionmaker = bs->client; bs->ordered = qfalse; bs->ltgtype = LTG_GETFLAG; //set the time the bot will stop getting the flag bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; BotSetTeamStatus(bs); } else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { bs->decisionmaker = bs->client; bs->ordered = qfalse; // if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); //set the ltg type bs->ltgtype = LTG_DEFENDKEYAREA; //set the time the bot stops defending the base bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; bs->defendaway_time = 0; BotSetTeamStatus(bs); } else { bs->ltgtype = 0; //set the time the bot will stop roaming bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; BotSetTeamStatus(bs); } bs->owndecision_time = FloatTime() + 5; #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== Bot1FCTFRetreatGoals ================== */ void Bot1FCTFRetreatGoals(bot_state_t *bs) { //when carrying a flag in ctf the bot should rush to the enemy base if (Bot1FCTFCarryingFlag(bs)) { //if not already rushing to the base if (bs->ltgtype != LTG_RUSHBASE) { BotRefuseOrder(bs); bs->ltgtype = LTG_RUSHBASE; bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; bs->rushbaseaway_time = 0; bs->decisionmaker = bs->client; bs->ordered = qfalse; //get an alternative route goal towards the enemy base BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); BotSetTeamStatus(bs); } } } /* ================== BotObeliskSeekGoals ================== */ void BotObeliskSeekGoals(bot_state_t *bs) { float rnd, l1, l2; // don't just do something wait for the bot team leader to give orders if (BotTeamLeader(bs)) { return; } // if the bot is ordered to do something if ( bs->lastgoal_ltgtype ) { bs->teamgoal_time += 60; } //if already a team goal if (bs->ltgtype == LTG_TEAMHELP || bs->ltgtype == LTG_TEAMACCOMPANY || bs->ltgtype == LTG_DEFENDKEYAREA || bs->ltgtype == LTG_GETFLAG || bs->ltgtype == LTG_RUSHBASE || bs->ltgtype == LTG_RETURNFLAG || bs->ltgtype == LTG_CAMPORDER || bs->ltgtype == LTG_PATROL || bs->ltgtype == LTG_ATTACKENEMYBASE || bs->ltgtype == LTG_GETITEM || bs->ltgtype == LTG_MAKELOVE_UNDER || bs->ltgtype == LTG_MAKELOVE_ONTOP) { return; } // if (BotSetLastOrderedTask(bs)) return; //if the bot is roaming if (bs->ctfroam_time > FloatTime()) return; //if the bot has anough aggression to decide what to do if (BotAggression(bs) < 50) return; //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); // if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { if (bs->teamtaskpreference & TEAMTP_ATTACKER) { l1 = 0.7f; } else { l1 = 0.2f; } l2 = 0.9f; } else { l1 = 0.4f; l2 = 0.7f; } //get the flag or defend the base rnd = random(); if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { bs->decisionmaker = bs->client; bs->ordered = qfalse; // if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); //set the ltg type bs->ltgtype = LTG_ATTACKENEMYBASE; //set the time the bot will stop attacking the enemy base bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; //get an alternate route goal towards the enemy base BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); BotSetTeamStatus(bs); } else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { bs->decisionmaker = bs->client; bs->ordered = qfalse; // if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); //set the ltg type bs->ltgtype = LTG_DEFENDKEYAREA; //set the time the bot stops defending the base bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; bs->defendaway_time = 0; BotSetTeamStatus(bs); } else { bs->ltgtype = 0; //set the time the bot will stop roaming bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; BotSetTeamStatus(bs); } } /* ================== BotGoHarvest ================== */ void BotGoHarvest(bot_state_t *bs) { // if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); //set the ltg type bs->ltgtype = LTG_HARVEST; //set the time the bot will stop harvesting bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; bs->harvestaway_time = 0; BotSetTeamStatus(bs); } /* ================== BotObeliskRetreatGoals ================== */ void BotObeliskRetreatGoals(bot_state_t *bs) { //nothing special } /* ================== BotHarvesterSeekGoals ================== */ void BotHarvesterSeekGoals(bot_state_t *bs) { aas_entityinfo_t entinfo; float rnd, l1, l2; int c; //when carrying cubes in harvester the bot should rush to the base if (BotHarvesterCarryingCubes(bs)) { //if not already rushing to the base if (bs->ltgtype != LTG_RUSHBASE) { BotRefuseOrder(bs); bs->ltgtype = LTG_RUSHBASE; bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; bs->rushbaseaway_time = 0; bs->decisionmaker = bs->client; bs->ordered = qfalse; //get an alternative route goal towards the enemy base BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); // BotSetTeamStatus(bs); } return; } // don't just do something wait for the bot team leader to give orders if (BotTeamLeader(bs)) { return; } // if the bot decided to follow someone if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { // if the team mate being accompanied no longer carries the flag BotEntityInfo(bs->teammate, &entinfo); if (!EntityCarriesCubes(&entinfo)) { bs->ltgtype = 0; } } // if the bot is ordered to do something if ( bs->lastgoal_ltgtype ) { bs->teamgoal_time += 60; } //if not yet doing something if (bs->ltgtype == LTG_TEAMHELP || bs->ltgtype == LTG_TEAMACCOMPANY || bs->ltgtype == LTG_DEFENDKEYAREA || bs->ltgtype == LTG_GETFLAG || bs->ltgtype == LTG_CAMPORDER || bs->ltgtype == LTG_PATROL || bs->ltgtype == LTG_ATTACKENEMYBASE || bs->ltgtype == LTG_HARVEST || bs->ltgtype == LTG_GETITEM || bs->ltgtype == LTG_MAKELOVE_UNDER || bs->ltgtype == LTG_MAKELOVE_ONTOP) { return; } // if (BotSetLastOrderedTask(bs)) return; //if the bot is roaming if (bs->ctfroam_time > FloatTime()) return; //if the bot has anough aggression to decide what to do if (BotAggression(bs) < 50) return; //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); // c = BotEnemyCubeCarrierVisible(bs); if (c >= 0) { //FIXME: attack enemy cube carrier } if (bs->ltgtype != LTG_TEAMACCOMPANY) { //if there is a visible team mate carrying cubes c = BotTeamCubeCarrierVisible(bs); if (c >= 0) { //follow the team mate carrying cubes bs->decisionmaker = bs->client; bs->ordered = qfalse; //the team mate bs->teammate = c; //last time the team mate was visible bs->teammatevisible_time = FloatTime(); //no message bs->teammessage_time = 0; //no arrive message bs->arrive_time = 1; // BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); //get the team goal time bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; bs->ltgtype = LTG_TEAMACCOMPANY; bs->formation_dist = 3.5 * 32; //3.5 meter BotSetTeamStatus(bs); return; } } // if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { if (bs->teamtaskpreference & TEAMTP_ATTACKER) { l1 = 0.7f; } else { l1 = 0.2f; } l2 = 0.9f; } else { l1 = 0.4f; l2 = 0.7f; } // rnd = random(); if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { bs->decisionmaker = bs->client; bs->ordered = qfalse; BotGoHarvest(bs); } else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { bs->decisionmaker = bs->client; bs->ordered = qfalse; // if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); //set the ltg type bs->ltgtype = LTG_DEFENDKEYAREA; //set the time the bot stops defending the base bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; bs->defendaway_time = 0; BotSetTeamStatus(bs); } else { bs->ltgtype = 0; //set the time the bot will stop roaming bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; BotSetTeamStatus(bs); } } /* ================== BotHarvesterRetreatGoals ================== */ void BotHarvesterRetreatGoals(bot_state_t *bs) { //when carrying cubes in harvester the bot should rush to the base if (BotHarvesterCarryingCubes(bs)) { //if not already rushing to the base if (bs->ltgtype != LTG_RUSHBASE) { BotRefuseOrder(bs); bs->ltgtype = LTG_RUSHBASE; bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; bs->rushbaseaway_time = 0; bs->decisionmaker = bs->client; bs->ordered = qfalse; BotSetTeamStatus(bs); } return; } } #endif /* ================== BotTeamGoals ================== */ void BotTeamGoals(bot_state_t *bs, int retreat) { if ( retreat ) { if (gametype == GT_CTF) { BotCTFRetreatGoals(bs); } #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { Bot1FCTFRetreatGoals(bs); } else if (gametype == GT_OBELISK) { BotObeliskRetreatGoals(bs); } else if (gametype == GT_HARVESTER) { BotHarvesterRetreatGoals(bs); } #endif } else { if (gametype == GT_CTF) { //decide what to do in CTF mode BotCTFSeekGoals(bs); } #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { Bot1FCTFSeekGoals(bs); } else if (gametype == GT_OBELISK) { BotObeliskSeekGoals(bs); } else if (gametype == GT_HARVESTER) { BotHarvesterSeekGoals(bs); } #endif } // reset the order time which is used to see if // we decided to refuse an order bs->order_time = 0; } /* ================== BotPointAreaNum ================== */ int BotPointAreaNum(vec3_t origin) { int areanum, numareas, areas[10]; vec3_t end; areanum = trap_AAS_PointAreaNum(origin); if (areanum) return areanum; VectorCopy(origin, end); end[2] += 10; numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10); if (numareas > 0) return areas[0]; return 0; } /* ================== ClientName ================== */ char *ClientName(int client, char *name, int size) { char buf[MAX_INFO_STRING]; if (client < 0 || client >= MAX_CLIENTS) { BotAI_Print(PRT_ERROR, "ClientName: client out of range\n"); return "[client out of range]"; } trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); strncpy(name, Info_ValueForKey(buf, "n"), size-1); name[size-1] = '\0'; Q_CleanStr( name ); return name; } /* ================== ClientSkin ================== */ char *ClientSkin(int client, char *skin, int size) { char buf[MAX_INFO_STRING]; if (client < 0 || client >= MAX_CLIENTS) { BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n"); return "[client out of range]"; } trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); strncpy(skin, Info_ValueForKey(buf, "model"), size-1); skin[size-1] = '\0'; return skin; } /* ================== ClientFromName ================== */ int ClientFromName(char *name) { int i; char buf[MAX_INFO_STRING]; static int maxclients; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); Q_CleanStr( buf ); if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; } return -1; } /* ================== ClientOnSameTeamFromName ================== */ int ClientOnSameTeamFromName(bot_state_t *bs, char *name) { int i; char buf[MAX_INFO_STRING]; static int maxclients; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (!BotSameTeam(bs, i)) continue; trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); Q_CleanStr( buf ); if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; } return -1; } /* ================== stristr ================== */ char *stristr(char *str, char *charset) { int i; while(*str) { for (i = 0; charset[i] && str[i]; i++) { if (toupper(charset[i]) != toupper(str[i])) break; } if (!charset[i]) return str; str++; } return NULL; } /* ================== EasyClientName ================== */ char *EasyClientName(int client, char *buf, int size) { int i; char *str1, *str2, *ptr, c; char name[128]; strcpy(name, ClientName(client, name, sizeof(name))); for (i = 0; name[i]; i++) name[i] &= 127; //remove all spaces for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) { memmove(ptr, ptr+1, strlen(ptr+1)+1); } //check for [x] and ]x[ clan names str1 = strstr(name, "["); str2 = strstr(name, "]"); if (str1 && str2) { if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1); else memmove(str2, str1+1, strlen(str1+1)+1); } //remove Mr prefix if ((name[0] == 'm' || name[0] == 'M') && (name[1] == 'r' || name[1] == 'R')) { memmove(name, name+2, strlen(name+2)+1); } //only allow lower case alphabet characters ptr = name; while(*ptr) { c = *ptr; if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_') { ptr++; } else if (c >= 'A' && c <= 'Z') { *ptr += 'a' - 'A'; ptr++; } else { memmove(ptr, ptr+1, strlen(ptr + 1)+1); } } strncpy(buf, name, size-1); buf[size-1] = '\0'; return buf; } /* ================== BotSynonymContext ================== */ int BotSynonymContext(bot_state_t *bs) { int context; context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES; // if (gametype == GT_CTF #ifdef MISSIONPACK || gametype == GT_1FCTF #endif ) { if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_CTFREDTEAM; else context |= CONTEXT_CTFBLUETEAM; } #ifdef MISSIONPACK else if (gametype == GT_OBELISK) { if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_OBELISKREDTEAM; else context |= CONTEXT_OBELISKBLUETEAM; } else if (gametype == GT_HARVESTER) { if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_HARVESTERREDTEAM; else context |= CONTEXT_HARVESTERBLUETEAM; } #endif return context; } /* ================== BotChooseWeapon ================== */ void BotChooseWeapon(bot_state_t *bs) { int newweaponnum; if (bs->cur_ps.weaponstate == WEAPON_RAISING || bs->cur_ps.weaponstate == WEAPON_DROPPING) { trap_EA_SelectWeapon(bs->client, bs->weaponnum); } else { newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory); if (bs->weaponnum != newweaponnum) bs->weaponchange_time = FloatTime(); bs->weaponnum = newweaponnum; //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum); trap_EA_SelectWeapon(bs->client, bs->weaponnum); } } /* ================== BotSetupForMovement ================== */ void BotSetupForMovement(bot_state_t *bs) { bot_initmove_t initmove; memset(&initmove, 0, sizeof(bot_initmove_t)); VectorCopy(bs->cur_ps.origin, initmove.origin); VectorCopy(bs->cur_ps.velocity, initmove.velocity); VectorClear(initmove.viewoffset); initmove.viewoffset[2] += bs->cur_ps.viewheight; initmove.entitynum = bs->entitynum; initmove.client = bs->client; initmove.thinktime = bs->thinktime; //set the onground flag if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND; //set the teleported flag if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) { initmove.or_moveflags |= MFL_TELEPORTED; } //set the waterjump flag if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) { initmove.or_moveflags |= MFL_WATERJUMP; } //set presence type if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH; else initmove.presencetype = PRESENCE_NORMAL; // if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK; // VectorCopy(bs->viewangles, initmove.viewangles); // trap_BotInitMoveState(bs->ms, &initmove); } /* ================== BotCheckItemPickup ================== */ void BotCheckItemPickup(bot_state_t *bs, int *oldinventory) { #ifdef MISSIONPACK int offence, leader; if (gametype <= GT_TEAM) return; offence = -1; // go into offence if picked up the kamikaze or invulnerability if (!oldinventory[INVENTORY_KAMIKAZE] && bs->inventory[INVENTORY_KAMIKAZE] >= 1) { offence = qtrue; } if (!oldinventory[INVENTORY_INVULNERABILITY] && bs->inventory[INVENTORY_INVULNERABILITY] >= 1) { offence = qtrue; } // if not already wearing the kamikaze or invulnerability if (!bs->inventory[INVENTORY_KAMIKAZE] && !bs->inventory[INVENTORY_INVULNERABILITY]) { if (!oldinventory[INVENTORY_SCOUT] && bs->inventory[INVENTORY_SCOUT] >= 1) { offence = qtrue; } if (!oldinventory[INVENTORY_GUARD] && bs->inventory[INVENTORY_GUARD] >= 1) { offence = qtrue; } if (!oldinventory[INVENTORY_DOUBLER] && bs->inventory[INVENTORY_DOUBLER] >= 1) { offence = qfalse; } if (!oldinventory[INVENTORY_AMMOREGEN] && bs->inventory[INVENTORY_AMMOREGEN] >= 1) { offence = qfalse; } } if (offence >= 0) { leader = ClientFromName(bs->teamleader); if (offence) { if (!(bs->teamtaskpreference & TEAMTP_ATTACKER)) { // if we have a bot team leader if (BotTeamLeader(bs)) { // tell the leader we want to be on offence BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); //BotAI_BotInitialChat(bs, "wantoffence", NULL); //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); } else if (g_spSkill.integer <= 3) { if ( bs->ltgtype != LTG_GETFLAG && bs->ltgtype != LTG_ATTACKENEMYBASE && bs->ltgtype != LTG_HARVEST ) { // if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { // tell the leader we want to be on offence BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); //BotAI_BotInitialChat(bs, "wantoffence", NULL); //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); } } bs->teamtaskpreference |= TEAMTP_ATTACKER; } } bs->teamtaskpreference &= ~TEAMTP_DEFENDER; } else { if (!(bs->teamtaskpreference & TEAMTP_DEFENDER)) { // if we have a bot team leader if (BotTeamLeader(bs)) { // tell the leader we want to be on defense BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); //BotAI_BotInitialChat(bs, "wantdefence", NULL); //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); } else if (g_spSkill.integer <= 3) { if ( bs->ltgtype != LTG_DEFENDKEYAREA ) { // if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { // tell the leader we want to be on defense BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); //BotAI_BotInitialChat(bs, "wantdefence", NULL); //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); } } } bs->teamtaskpreference |= TEAMTP_DEFENDER; } bs->teamtaskpreference &= ~TEAMTP_ATTACKER; } } #endif } /* ================== BotUpdateInventory ================== */ void BotUpdateInventory(bot_state_t *bs) { int oldinventory[MAX_ITEMS]; memcpy(oldinventory, bs->inventory, sizeof(oldinventory)); //armor bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; //weapons bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0; bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0; bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0; bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0; bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0; bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0; bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0; bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0; bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0; bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0; #ifdef MISSIONPACK bs->inventory[INVENTORY_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0;; bs->inventory[INVENTORY_PROXLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PROX_LAUNCHER)) != 0;; bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;; #endif //ammo bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN]; bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN]; bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN]; bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING]; bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER]; bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN]; bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG]; #ifdef MISSIONPACK bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN]; bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER]; bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN]; #endif //powerups bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER; bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT; #ifdef MISSIONPACK bs->inventory[INVENTORY_KAMIKAZE] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_KAMIKAZE; bs->inventory[INVENTORY_PORTAL] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_PORTAL; bs->inventory[INVENTORY_INVULNERABILITY] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_INVULNERABILITY; #endif bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0; bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0; bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0; bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0; #ifdef MISSIONPACK bs->inventory[INVENTORY_SCOUT] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_SCOUT; bs->inventory[INVENTORY_GUARD] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_GUARD; bs->inventory[INVENTORY_DOUBLER] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_DOUBLER; bs->inventory[INVENTORY_AMMOREGEN] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_AMMOREGEN; #endif bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0; #ifdef MISSIONPACK bs->inventory[INVENTORY_NEUTRALFLAG] = bs->cur_ps.powerups[PW_NEUTRALFLAG] != 0; if (BotTeam(bs) == TEAM_RED) { bs->inventory[INVENTORY_REDCUBE] = bs->cur_ps.generic1; bs->inventory[INVENTORY_BLUECUBE] = 0; } else { bs->inventory[INVENTORY_REDCUBE] = 0; bs->inventory[INVENTORY_BLUECUBE] = bs->cur_ps.generic1; } #endif BotCheckItemPickup(bs, oldinventory); } /* ================== BotUpdateBattleInventory ================== */ void BotUpdateBattleInventory(bot_state_t *bs, int enemy) { vec3_t dir; aas_entityinfo_t entinfo; BotEntityInfo(enemy, &entinfo); VectorSubtract(entinfo.origin, bs->origin, dir); bs->inventory[ENEMY_HEIGHT] = (int) dir[2]; dir[2] = 0; bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir); //FIXME: add num visible enemies and num visible team mates to the inventory } #ifdef MISSIONPACK /* ================== BotUseKamikaze ================== */ #define KAMIKAZE_DIST 1024 void BotUseKamikaze(bot_state_t *bs) { int c, teammates, enemies; aas_entityinfo_t entinfo; vec3_t dir, target; bot_goal_t *goal; bsp_trace_t trace; //if the bot has no kamikaze if (bs->inventory[INVENTORY_KAMIKAZE] <= 0) return; if (bs->kamikaze_time > FloatTime()) return; bs->kamikaze_time = FloatTime() + 0.2; if (gametype == GT_CTF) { //never use kamikaze if the team flag carrier is visible if (BotCTFCarryingFlag(bs)) return; c = BotTeamFlagCarrierVisible(bs); if (c >= 0) { BotEntityInfo(c, &entinfo); VectorSubtract(entinfo.origin, bs->origin, dir); if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) return; } c = BotEnemyFlagCarrierVisible(bs); if (c >= 0) { BotEntityInfo(c, &entinfo); VectorSubtract(entinfo.origin, bs->origin, dir); if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { trap_EA_Use(bs->client); return; } } } else if (gametype == GT_1FCTF) { //never use kamikaze if the team flag carrier is visible if (Bot1FCTFCarryingFlag(bs)) return; c = BotTeamFlagCarrierVisible(bs); if (c >= 0) { BotEntityInfo(c, &entinfo); VectorSubtract(entinfo.origin, bs->origin, dir); if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) return; } c = BotEnemyFlagCarrierVisible(bs); if (c >= 0) { BotEntityInfo(c, &entinfo); VectorSubtract(entinfo.origin, bs->origin, dir); if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { trap_EA_Use(bs->client); return; } } } else if (gametype == GT_OBELISK) { switch(BotTeam(bs)) { case TEAM_RED: goal = &blueobelisk; break; default: goal = &redobelisk; break; } //if the obelisk is visible VectorCopy(goal->origin, target); target[2] += 1; VectorSubtract(bs->origin, target, dir); if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST * 0.9)) { BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); if (trace.fraction >= 1 || trace.ent == goal->entitynum) { trap_EA_Use(bs->client); return; } } } else if (gametype == GT_HARVESTER) { // if (BotHarvesterCarryingCubes(bs)) return; //never use kamikaze if a team mate carrying cubes is visible c = BotTeamCubeCarrierVisible(bs); if (c >= 0) { BotEntityInfo(c, &entinfo); VectorSubtract(entinfo.origin, bs->origin, dir); if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) return; } c = BotEnemyCubeCarrierVisible(bs); if (c >= 0) { BotEntityInfo(c, &entinfo); VectorSubtract(entinfo.origin, bs->origin, dir); if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { trap_EA_Use(bs->client); return; } } } // BotVisibleTeamMatesAndEnemies(bs, &teammates, &enemies, KAMIKAZE_DIST); // if (enemies > 2 && enemies > teammates+1) { trap_EA_Use(bs->client); return; } } /* ================== BotUseInvulnerability ================== */ void BotUseInvulnerability(bot_state_t *bs) { int c; vec3_t dir, target; bot_goal_t *goal; bsp_trace_t trace; //if the bot has no invulnerability if (bs->inventory[INVENTORY_INVULNERABILITY] <= 0) return; if (bs->invulnerability_time > FloatTime()) return; bs->invulnerability_time = FloatTime() + 0.2; if (gametype == GT_CTF) { //never use kamikaze if the team flag carrier is visible if (BotCTFCarryingFlag(bs)) return; c = BotEnemyFlagCarrierVisible(bs); if (c >= 0) return; //if near enemy flag and the flag is visible switch(BotTeam(bs)) { case TEAM_RED: goal = &ctf_blueflag; break; default: goal = &ctf_redflag; break; } //if the obelisk is visible VectorCopy(goal->origin, target); target[2] += 1; VectorSubtract(bs->origin, target, dir); if (VectorLengthSquared(dir) < Square(200)) { BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); if (trace.fraction >= 1 || trace.ent == goal->entitynum) { trap_EA_Use(bs->client); return; } } } else if (gametype == GT_1FCTF) { //never use kamikaze if the team flag carrier is visible if (Bot1FCTFCarryingFlag(bs)) return; c = BotEnemyFlagCarrierVisible(bs); if (c >= 0) return; //if near enemy flag and the flag is visible switch(BotTeam(bs)) { case TEAM_RED: goal = &ctf_blueflag; break; default: goal = &ctf_redflag; break; } //if the obelisk is visible VectorCopy(goal->origin, target); target[2] += 1; VectorSubtract(bs->origin, target, dir); if (VectorLengthSquared(dir) < Square(200)) { BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); if (trace.fraction >= 1 || trace.ent == goal->entitynum) { trap_EA_Use(bs->client); return; } } } else if (gametype == GT_OBELISK) { switch(BotTeam(bs)) { case TEAM_RED: goal = &blueobelisk; break; default: goal = &redobelisk; break; } //if the obelisk is visible VectorCopy(goal->origin, target); target[2] += 1; VectorSubtract(bs->origin, target, dir); if (VectorLengthSquared(dir) < Square(300)) { BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); if (trace.fraction >= 1 || trace.ent == goal->entitynum) { trap_EA_Use(bs->client); return; } } } else if (gametype == GT_HARVESTER) { // if (BotHarvesterCarryingCubes(bs)) return; c = BotEnemyCubeCarrierVisible(bs); if (c >= 0) return; //if near enemy base and enemy base is visible switch(BotTeam(bs)) { case TEAM_RED: goal = &blueobelisk; break; default: goal = &redobelisk; break; } //if the obelisk is visible VectorCopy(goal->origin, target); target[2] += 1; VectorSubtract(bs->origin, target, dir); if (VectorLengthSquared(dir) < Square(200)) { BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); if (trace.fraction >= 1 || trace.ent == goal->entitynum) { trap_EA_Use(bs->client); return; } } } } #endif /* ================== BotBattleUseItems ================== */ void BotBattleUseItems(bot_state_t *bs) { if (bs->inventory[INVENTORY_HEALTH] < 40) { if (bs->inventory[INVENTORY_TELEPORTER] > 0) { if (!BotCTFCarryingFlag(bs) #ifdef MISSIONPACK && !Bot1FCTFCarryingFlag(bs) && !BotHarvesterCarryingCubes(bs) #endif ) { trap_EA_Use(bs->client); } } } if (bs->inventory[INVENTORY_HEALTH] < 60) { if (bs->inventory[INVENTORY_MEDKIT] > 0) { trap_EA_Use(bs->client); } } #ifdef MISSIONPACK BotUseKamikaze(bs); BotUseInvulnerability(bs); #endif } /* ================== BotSetTeleportTime ================== */ void BotSetTeleportTime(bot_state_t *bs) { if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) { bs->teleport_time = FloatTime(); } bs->last_eFlags = bs->cur_ps.eFlags; } /* ================== BotIsDead ================== */ qboolean BotIsDead(bot_state_t *bs) { return (bs->cur_ps.pm_type == PM_DEAD); } /* ================== BotIsObserver ================== */ qboolean BotIsObserver(bot_state_t *bs) { char buf[MAX_INFO_STRING]; if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue; trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf)); if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue; return qfalse; } /* ================== BotIntermission ================== */ qboolean BotIntermission(bot_state_t *bs) { //NOTE: we shouldn't be looking at the game code... if (level.intermissiontime) return qtrue; return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION); } /* ================== BotInLavaOrSlime ================== */ qboolean BotInLavaOrSlime(bot_state_t *bs) { vec3_t feet; VectorCopy(bs->origin, feet); feet[2] -= 23; return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME)); } /* ================== BotCreateWayPoint ================== */ bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) { bot_waypoint_t *wp; vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8}; wp = botai_freewaypoints; if ( !wp ) { BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" ); return NULL; } botai_freewaypoints = botai_freewaypoints->next; Q_strncpyz( wp->name, name, sizeof(wp->name) ); VectorCopy(origin, wp->goal.origin); VectorCopy(waypointmins, wp->goal.mins); VectorCopy(waypointmaxs, wp->goal.maxs); wp->goal.areanum = areanum; wp->next = NULL; wp->prev = NULL; return wp; } /* ================== BotFindWayPoint ================== */ bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) { bot_waypoint_t *wp; for (wp = waypoints; wp; wp = wp->next) { if (!Q_stricmp(wp->name, name)) return wp; } return NULL; } /* ================== BotFreeWaypoints ================== */ void BotFreeWaypoints(bot_waypoint_t *wp) { bot_waypoint_t *nextwp; for (; wp; wp = nextwp) { nextwp = wp->next; wp->next = botai_freewaypoints; botai_freewaypoints = wp; } } /* ================== BotInitWaypoints ================== */ void BotInitWaypoints(void) { int i; botai_freewaypoints = NULL; for (i = 0; i < MAX_WAYPOINTS; i++) { botai_waypoints[i].next = botai_freewaypoints; botai_freewaypoints = &botai_waypoints[i]; } } /* ================== TeamPlayIsOn ================== */ int TeamPlayIsOn(void) { return ( gametype >= GT_TEAM ); } /* ================== BotAggression ================== */ float BotAggression(bot_state_t *bs) { //if the bot has quad if (bs->inventory[INVENTORY_QUAD]) { //if the bot is not holding the gauntlet or the enemy is really nearby if (bs->weaponnum != WP_GAUNTLET || bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) { return 70; } } //if the enemy is located way higher than the bot if (bs->inventory[ENEMY_HEIGHT] > 200) return 0; //if the bot is very low on health if (bs->inventory[INVENTORY_HEALTH] < 60) return 0; //if the bot is low on health if (bs->inventory[INVENTORY_HEALTH] < 80) { //if the bot has insufficient armor if (bs->inventory[INVENTORY_ARMOR] < 40) return 0; } //if the bot can use the bfg if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 7) return 100; //if the bot can use the railgun if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 5) return 95; //if the bot can use the lightning gun if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return 90; //if the bot can use the rocketlauncher if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 5) return 90; //if the bot can use the plasmagun if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 40) return 85; //if the bot can use the grenade launcher if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && bs->inventory[INVENTORY_GRENADES] > 10) return 80; //if the bot can use the shotgun if (bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] > 10) return 50; //otherwise the bot is not feeling too good return 0; } /* ================== BotFeelingBad ================== */ float BotFeelingBad(bot_state_t *bs) { if (bs->weaponnum == WP_GAUNTLET) { return 100; } if (bs->inventory[INVENTORY_HEALTH] < 40) { return 100; } if (bs->weaponnum == WP_MACHINEGUN) { return 90; } if (bs->inventory[INVENTORY_HEALTH] < 60) { return 80; } return 0; } /* ================== BotWantsToRetreat ================== */ int BotWantsToRetreat(bot_state_t *bs) { aas_entityinfo_t entinfo; if (gametype == GT_CTF) { //always retreat when carrying a CTF flag if (BotCTFCarryingFlag(bs)) return qtrue; } #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { //if carrying the flag then always retreat if (Bot1FCTFCarryingFlag(bs)) return qtrue; } else if (gametype == GT_OBELISK) { //the bots should be dedicated to attacking the enemy obelisk if (bs->ltgtype == LTG_ATTACKENEMYBASE) { if (bs->enemy != redobelisk.entitynum || bs->enemy != blueobelisk.entitynum) { return qtrue; } } if (BotFeelingBad(bs) > 50) { return qtrue; } return qfalse; } else if (gametype == GT_HARVESTER) { //if carrying cubes then always retreat if (BotHarvesterCarryingCubes(bs)) return qtrue; } #endif // if (bs->enemy >= 0) { //if the enemy is carrying a flag BotEntityInfo(bs->enemy, &entinfo); if (EntityCarriesFlag(&entinfo)) return qfalse; } //if the bot is getting the flag if (bs->ltgtype == LTG_GETFLAG) return qtrue; // if (BotAggression(bs) < 50) return qtrue; return qfalse; } /* ================== BotWantsToChase ================== */ int BotWantsToChase(bot_state_t *bs) { aas_entityinfo_t entinfo; if (gametype == GT_CTF) { //never chase when carrying a CTF flag if (BotCTFCarryingFlag(bs)) return qfalse; //always chase if the enemy is carrying a flag BotEntityInfo(bs->enemy, &entinfo); if (EntityCarriesFlag(&entinfo)) return qtrue; } #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { //never chase if carrying the flag if (Bot1FCTFCarryingFlag(bs)) return qfalse; //always chase if the enemy is carrying a flag BotEntityInfo(bs->enemy, &entinfo); if (EntityCarriesFlag(&entinfo)) return qtrue; } else if (gametype == GT_OBELISK) { //the bots should be dedicated to attacking the enemy obelisk if (bs->ltgtype == LTG_ATTACKENEMYBASE) { if (bs->enemy != redobelisk.entitynum || bs->enemy != blueobelisk.entitynum) { return qfalse; } } } else if (gametype == GT_HARVESTER) { //never chase if carrying cubes if (BotHarvesterCarryingCubes(bs)) return qfalse; } #endif //if the bot is getting the flag if (bs->ltgtype == LTG_GETFLAG) return qfalse; // if (BotAggression(bs) > 50) return qtrue; return qfalse; } /* ================== BotWantsToHelp ================== */ int BotWantsToHelp(bot_state_t *bs) { return qtrue; } /* ================== BotCanAndWantsToRocketJump ================== */ int BotCanAndWantsToRocketJump(bot_state_t *bs) { float rocketjumper; //if rocket jumping is disabled if (!bot_rocketjump.integer) return qfalse; //if no rocket launcher if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse; //if low on rockets if (bs->inventory[INVENTORY_ROCKETS] < 3) return qfalse; //never rocket jump with the Quad if (bs->inventory[INVENTORY_QUAD]) return qfalse; //if low on health if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; //if not full health if (bs->inventory[INVENTORY_HEALTH] < 90) { //if the bot has insufficient armor if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; } rocketjumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1); if (rocketjumper < 0.5) return qfalse; return qtrue; } /* ================== BotHasPersistantPowerupAndWeapon ================== */ int BotHasPersistantPowerupAndWeapon(bot_state_t *bs) { #ifdef MISSIONPACK // if the bot does not have a persistant powerup if (!bs->inventory[INVENTORY_SCOUT] && !bs->inventory[INVENTORY_GUARD] && !bs->inventory[INVENTORY_DOUBLER] && !bs->inventory[INVENTORY_AMMOREGEN] ) { return qfalse; } #endif //if the bot is very low on health if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; //if the bot is low on health if (bs->inventory[INVENTORY_HEALTH] < 80) { //if the bot has insufficient armor if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; } //if the bot can use the bfg if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 7) return qtrue; //if the bot can use the railgun if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 5) return qtrue; //if the bot can use the lightning gun if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return qtrue; //if the bot can use the rocketlauncher if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 5) return qtrue; // if (bs->inventory[INVENTORY_NAILGUN] > 0 && bs->inventory[INVENTORY_NAILS] > 5) return qtrue; // if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 && bs->inventory[INVENTORY_MINES] > 5) return qtrue; // if (bs->inventory[INVENTORY_CHAINGUN] > 0 && bs->inventory[INVENTORY_BELT] > 40) return qtrue; //if the bot can use the plasmagun if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 20) return qtrue; return qfalse; } /* ================== BotGoCamp ================== */ void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) { float camper; bs->decisionmaker = bs->client; //set message time to zero so bot will NOT show any message bs->teammessage_time = 0; //set the ltg type bs->ltgtype = LTG_CAMP; //set the team goal memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t)); //get the team goal time camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); if (camper > 0.99) bs->teamgoal_time = FloatTime() + 99999; else bs->teamgoal_time = FloatTime() + 120 + 180 * camper + random() * 15; //set the last time the bot started camping bs->camp_time = FloatTime(); //the teammate that requested the camping bs->teammate = 0; //do NOT type arrive message bs->arrive_time = 1; } /* ================== BotWantsToCamp ================== */ int BotWantsToCamp(bot_state_t *bs) { float camper; int cs, traveltime, besttraveltime; bot_goal_t goal, bestgoal; camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); if (camper < 0.1) return qfalse; //if the bot has a team goal if (bs->ltgtype == LTG_TEAMHELP || bs->ltgtype == LTG_TEAMACCOMPANY || bs->ltgtype == LTG_DEFENDKEYAREA || bs->ltgtype == LTG_GETFLAG || bs->ltgtype == LTG_RUSHBASE || bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER || bs->ltgtype == LTG_PATROL) { return qfalse; } //if camped recently if (bs->camp_time > FloatTime() - 60 + 300 * (1-camper)) return qfalse; // if (random() > camper) { bs->camp_time = FloatTime(); return qfalse; } //if the bot isn't healthy anough if (BotAggression(bs) < 50) return qfalse; //the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10]) && (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) && (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)) { return qfalse; } //find the closest camp spot besttraveltime = 99999; for (cs = trap_BotGetNextCampSpotGoal(0, &goal); cs; cs = trap_BotGetNextCampSpotGoal(cs, &goal)) { traveltime = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT); if (traveltime && traveltime < besttraveltime) { besttraveltime = traveltime; memcpy(&bestgoal, &goal, sizeof(bot_goal_t)); } } if (besttraveltime > 150) return qfalse; //ok found a camp spot, go camp there BotGoCamp(bs, &bestgoal); bs->ordered = qfalse; // return qtrue; } /* ================== BotDontAvoid ================== */ void BotDontAvoid(bot_state_t *bs, char *itemname) { bot_goal_t goal; int num; num = trap_BotGetLevelItemGoal(-1, itemname, &goal); while(num >= 0) { trap_BotRemoveFromAvoidGoals(bs->gs, goal.number); num = trap_BotGetLevelItemGoal(num, itemname, &goal); } } /* ================== BotGoForPowerups ================== */ void BotGoForPowerups(bot_state_t *bs) { //don't avoid any of the powerups anymore BotDontAvoid(bs, "Quad Damage"); BotDontAvoid(bs, "Regeneration"); BotDontAvoid(bs, "Battle Suit"); BotDontAvoid(bs, "Speed"); BotDontAvoid(bs, "Invisibility"); //BotDontAvoid(bs, "Flight"); //reset the long term goal time so the bot will go for the powerup //NOTE: the long term goal type doesn't change bs->ltg_time = 0; } /* ================== BotRoamGoal ================== */ void BotRoamGoal(bot_state_t *bs, vec3_t goal) { int pc, i; float len, rnd; vec3_t dir, bestorg, belowbestorg; bsp_trace_t trace; for (i = 0; i < 10; i++) { //start at the bot origin VectorCopy(bs->origin, bestorg); rnd = random(); if (rnd > 0.25) { //add a random value to the x-coordinate if (random() < 0.5) bestorg[0] -= 800 * random() + 100; else bestorg[0] += 800 * random() + 100; } if (rnd < 0.75) { //add a random value to the y-coordinate if (random() < 0.5) bestorg[1] -= 800 * random() + 100; else bestorg[1] += 800 * random() + 100; } //add a random value to the z-coordinate (NOTE: 48 = maxjump?) bestorg[2] += 2 * 48 * crandom(); //trace a line from the origin to the roam target BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID); //direction and length towards the roam target VectorSubtract(trace.endpos, bs->origin, dir); len = VectorNormalize(dir); //if the roam target is far away anough if (len > 200) { //the roam target is in the given direction before walls VectorScale(dir, len * trace.fraction - 40, dir); VectorAdd(bs->origin, dir, bestorg); //get the coordinates of the floor below the roam target belowbestorg[0] = bestorg[0]; belowbestorg[1] = bestorg[1]; belowbestorg[2] = bestorg[2] - 800; BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID); // if (!trace.startsolid) { trace.endpos[2]++; pc = trap_PointContents(trace.endpos, bs->entitynum); if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) { VectorCopy(bestorg, goal); return; } } } } VectorCopy(bestorg, goal); } /* ================== BotAttackMove ================== */ bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) { int movetype, i, attackentity; float attack_skill, jumper, croucher, dist, strafechange_time; float attack_dist, attack_range; vec3_t forward, backward, sideward, hordir, up = {0, 0, 1}; aas_entityinfo_t entinfo; bot_moveresult_t moveresult; bot_goal_t goal; attackentity = bs->enemy; // if (bs->attackchase_time > FloatTime()) { //create the chase goal goal.entitynum = attackentity; goal.areanum = bs->lastenemyareanum; VectorCopy(bs->lastenemyorigin, goal.origin); VectorSet(goal.mins, -8, -8, -8); VectorSet(goal.maxs, 8, 8, 8); //initialize the movement state BotSetupForMovement(bs); //move towards the goal trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl); return moveresult; } // memset(&moveresult, 0, sizeof(bot_moveresult_t)); // attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); jumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1); croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); //if the bot is really stupid if (attack_skill < 0.2) return moveresult; //initialize the movement state BotSetupForMovement(bs); //get the enemy entity info BotEntityInfo(attackentity, &entinfo); //direction towards the enemy VectorSubtract(entinfo.origin, bs->origin, forward); //the distance towards the enemy dist = VectorNormalize(forward); VectorNegate(forward, backward); //walk, crouch or jump movetype = MOVE_WALK; // if (bs->attackcrouch_time < FloatTime() - 1) { if (random() < jumper) { movetype = MOVE_JUMP; } //wait at least one second before crouching again else if (bs->attackcrouch_time < FloatTime() - 1 && random() < croucher) { bs->attackcrouch_time = FloatTime() + croucher * 5; } } if (bs->attackcrouch_time > FloatTime()) movetype = MOVE_CROUCH; //if the bot should jump if (movetype == MOVE_JUMP) { //if jumped last frame if (bs->attackjump_time > FloatTime()) { movetype = MOVE_WALK; } else { bs->attackjump_time = FloatTime() + 1; } } if (bs->cur_ps.weapon == WP_GAUNTLET) { attack_dist = 0; attack_range = 0; } else { attack_dist = IDEAL_ATTACKDIST; attack_range = 40; } //if the bot is stupid if (attack_skill <= 0.4) { //just walk to or away from the enemy if (dist > attack_dist + attack_range) { if (trap_BotMoveInDirection(bs->ms, forward, 400, movetype)) return moveresult; } if (dist < attack_dist - attack_range) { if (trap_BotMoveInDirection(bs->ms, backward, 400, movetype)) return moveresult; } return moveresult; } //increase the strafe time bs->attackstrafe_time += bs->thinktime; //get the strafe change time strafechange_time = 0.4 + (1 - attack_skill) * 0.2; if (attack_skill > 0.7) strafechange_time += crandom() * 0.2; //if the strafe direction should be changed if (bs->attackstrafe_time > strafechange_time) { //some magic number :) if (random() > 0.935) { //flip the strafe direction bs->flags ^= BFL_STRAFERIGHT; bs->attackstrafe_time = 0; } } // for (i = 0; i < 2; i++) { hordir[0] = forward[0]; hordir[1] = forward[1]; hordir[2] = 0; VectorNormalize(hordir); //get the sideward vector CrossProduct(hordir, up, sideward); //reverse the vector depending on the strafe direction if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward); //randomly go back a little if (random() > 0.9) { VectorAdd(sideward, backward, sideward); } else { //walk forward or backward to get at the ideal attack distance if (dist > attack_dist + attack_range) { VectorAdd(sideward, forward, sideward); } else if (dist < attack_dist - attack_range) { VectorAdd(sideward, backward, sideward); } } //perform the movement if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) return moveresult; //movement failed, flip the strafe direction bs->flags ^= BFL_STRAFERIGHT; bs->attackstrafe_time = 0; } //bot couldn't do any usefull movement // bs->attackchase_time = AAS_Time() + 6; return moveresult; } /* ================== BotSameTeam ================== */ int BotSameTeam(bot_state_t *bs, int entnum) { char info1[1024], info2[1024]; if (bs->client < 0 || bs->client >= MAX_CLIENTS) { //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); return qfalse; } if (entnum < 0 || entnum >= MAX_CLIENTS) { //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); return qfalse; } if ( gametype >= GT_TEAM ) { trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1)); trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2)); // if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue; } return qfalse; } /* ================== InFieldOfVision ================== */ qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) { int i; float diff, angle; for (i = 0; i < 2; i++) { angle = AngleMod(viewangles[i]); angles[i] = AngleMod(angles[i]); diff = angles[i] - angle; if (angles[i] > angle) { if (diff > 180.0) diff -= 360.0; } else { if (diff < -180.0) diff += 360.0; } if (diff > 0) { if (diff > fov * 0.5) return qfalse; } else { if (diff < -fov * 0.5) return qfalse; } } return qtrue; } /* ================== BotEntityVisible returns visibility in the range [0, 1] taking fog and water surfaces into account ================== */ float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) { int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc; float squaredfogdist, waterfactor, vis, bestvis; bsp_trace_t trace; aas_entityinfo_t entinfo; vec3_t dir, entangles, start, end, middle; //calculate middle of bounding box BotEntityInfo(ent, &entinfo); VectorAdd(entinfo.mins, entinfo.maxs, middle); VectorScale(middle, 0.5, middle); VectorAdd(entinfo.origin, middle, middle); //check if entity is within field of vision VectorSubtract(middle, eye, dir); vectoangles(dir, entangles); if (!InFieldOfVision(viewangles, fov, entangles)) return 0; // pc = trap_AAS_PointContents(eye); infog = (pc & CONTENTS_FOG); inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)); // bestvis = 0; for (i = 0; i < 3; i++) { //if the point is not in potential visible sight //if (!AAS_inPVS(eye, middle)) continue; // contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP; passent = viewer; hitent = ent; VectorCopy(eye, start); VectorCopy(middle, end); //if the entity is in water, lava or slime if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); } //if eye is in water, lava or slime if (inwater) { if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) { passent = ent; hitent = viewer; VectorCopy(middle, start); VectorCopy(eye, end); } contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); } //trace from start to end BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask); //if water was hit waterfactor = 1.0; if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { //if the water surface is translucent if (1) { //trace through the water contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask); waterfactor = 0.5; } } //if a full trace or the hitent was hit if (trace.fraction >= 1 || trace.ent == hitent) { //check for fog, assuming there's only one fog brush where //either the viewer or the entity is in or both are in otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG); if (infog && otherinfog) { VectorSubtract(trace.endpos, eye, dir); squaredfogdist = VectorLengthSquared(dir); } else if (infog) { VectorCopy(trace.endpos, start); BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG); VectorSubtract(eye, trace.endpos, dir); squaredfogdist = VectorLengthSquared(dir); } else if (otherinfog) { VectorCopy(trace.endpos, end); BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG); VectorSubtract(end, trace.endpos, dir); squaredfogdist = VectorLengthSquared(dir); } else { //if the entity and the viewer are not in fog assume there's no fog in between squaredfogdist = 0; } //decrease visibility with the view distance through fog vis = 1 / ((squaredfogdist * 0.001) < 1 ? 1 : (squaredfogdist * 0.001)); //if entering water visibility is reduced vis *= waterfactor; // if (vis > bestvis) bestvis = vis; //if pretty much no fog if (bestvis >= 0.95) return bestvis; } //check bottom and top of bounding box as well if (i == 0) middle[2] += entinfo.mins[2]; else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2]; } return bestvis; } /* ================== BotFindEnemy ================== */ int BotFindEnemy(bot_state_t *bs, int curenemy) { int i, healthdecrease; float f, alertness, easyfragger, vis; float squaredist, cursquaredist; aas_entityinfo_t entinfo, curenemyinfo; vec3_t dir, angles; alertness = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1); easyfragger = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1); //check if the health decreased healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH]; //remember the current health value bs->lasthealth = bs->inventory[INVENTORY_HEALTH]; // if (curenemy >= 0) { BotEntityInfo(curenemy, &curenemyinfo); if (EntityCarriesFlag(&curenemyinfo)) return qfalse; VectorSubtract(curenemyinfo.origin, bs->origin, dir); cursquaredist = VectorLengthSquared(dir); } else { cursquaredist = 0; } #ifdef MISSIONPACK if (gametype == GT_OBELISK) { vec3_t target; bot_goal_t *goal; bsp_trace_t trace; if (BotTeam(bs) == TEAM_RED) goal = &blueobelisk; else goal = &redobelisk; //if the obelisk is visible VectorCopy(goal->origin, target); target[2] += 1; BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); if (trace.fraction >= 1 || trace.ent == goal->entitynum) { if (goal->entitynum == bs->enemy) { return qfalse; } bs->enemy = goal->entitynum; bs->enemysight_time = FloatTime(); bs->enemysuicide = qfalse; bs->enemydeath_time = 0; bs->enemyvisible_time = FloatTime(); return qtrue; } } #endif // for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (i == bs->client) continue; //if it's the current enemy if (i == curenemy) continue; // BotEntityInfo(i, &entinfo); // if (!entinfo.valid) continue; //if the enemy isn't dead and the enemy isn't the bot self if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; //if the enemy is invisible and not shooting if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { continue; } //if not an easy fragger don't shoot at chatting players if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue; // if (lastteleport_time > FloatTime() - 3) { VectorSubtract(entinfo.origin, lastteleport_origin, dir); if (VectorLengthSquared(dir) < Square(70)) continue; } //calculate the distance towards the enemy VectorSubtract(entinfo.origin, bs->origin, dir); squaredist = VectorLengthSquared(dir); //if this entity is not carrying a flag if (!EntityCarriesFlag(&entinfo)) { //if this enemy is further away than the current one if (curenemy >= 0 && squaredist > cursquaredist) continue; } //end if //if the bot has no if (squaredist > Square(900.0 + alertness * 4000.0)) continue; //if on the same team if (BotSameTeam(bs, i)) continue; //if the bot's health decreased or the enemy is shooting if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) f = 360; else f = 90 + 90 - (90 - (squaredist > Square(810) ? Square(810) : squaredist) / (810 * 9)); //check if the enemy is visible vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i); if (vis <= 0) continue; //if the enemy is quite far away, not shooting and the bot is not damaged if (curenemy < 0 && squaredist > Square(100) && !healthdecrease && !EntityIsShooting(&entinfo)) { //check if we can avoid this enemy VectorSubtract(bs->origin, entinfo.origin, dir); vectoangles(dir, angles); //if the bot isn't in the fov of the enemy if (!InFieldOfVision(entinfo.angles, 90, angles)) { //update some stuff for this enemy BotUpdateBattleInventory(bs, i); //if the bot doesn't really want to fight if (BotWantsToRetreat(bs)) continue; } } //found an enemy bs->enemy = entinfo.number; if (curenemy >= 0) bs->enemysight_time = FloatTime() - 2; else bs->enemysight_time = FloatTime(); bs->enemysuicide = qfalse; bs->enemydeath_time = 0; bs->enemyvisible_time = FloatTime(); return qtrue; } return qfalse; } /* ================== BotTeamFlagCarrierVisible ================== */ int BotTeamFlagCarrierVisible(bot_state_t *bs) { int i; float vis; aas_entityinfo_t entinfo; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (i == bs->client) continue; // BotEntityInfo(i, &entinfo); //if this player is active if (!entinfo.valid) continue; //if this player is carrying a flag if (!EntityCarriesFlag(&entinfo)) continue; //if the flag carrier is not on the same team if (!BotSameTeam(bs, i)) continue; //if the flag carrier is not visible vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); if (vis <= 0) continue; // return i; } return -1; } /* ================== BotTeamFlagCarrier ================== */ int BotTeamFlagCarrier(bot_state_t *bs) { int i; aas_entityinfo_t entinfo; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (i == bs->client) continue; // BotEntityInfo(i, &entinfo); //if this player is active if (!entinfo.valid) continue; //if this player is carrying a flag if (!EntityCarriesFlag(&entinfo)) continue; //if the flag carrier is not on the same team if (!BotSameTeam(bs, i)) continue; // return i; } return -1; } /* ================== BotEnemyFlagCarrierVisible ================== */ int BotEnemyFlagCarrierVisible(bot_state_t *bs) { int i; float vis; aas_entityinfo_t entinfo; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (i == bs->client) continue; // BotEntityInfo(i, &entinfo); //if this player is active if (!entinfo.valid) continue; //if this player is carrying a flag if (!EntityCarriesFlag(&entinfo)) continue; //if the flag carrier is on the same team if (BotSameTeam(bs, i)) continue; //if the flag carrier is not visible vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); if (vis <= 0) continue; // return i; } return -1; } /* ================== BotVisibleTeamMatesAndEnemies ================== */ void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range) { int i; float vis; aas_entityinfo_t entinfo; vec3_t dir; if (teammates) *teammates = 0; if (enemies) *enemies = 0; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (i == bs->client) continue; // BotEntityInfo(i, &entinfo); //if this player is active if (!entinfo.valid) continue; //if this player is carrying a flag if (!EntityCarriesFlag(&entinfo)) continue; //if not within range VectorSubtract(entinfo.origin, bs->origin, dir); if (VectorLengthSquared(dir) > Square(range)) continue; //if the flag carrier is not visible vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); if (vis <= 0) continue; //if the flag carrier is on the same team if (BotSameTeam(bs, i)) { if (teammates) (*teammates)++; } else { if (enemies) (*enemies)++; } } } #ifdef MISSIONPACK /* ================== BotTeamCubeCarrierVisible ================== */ int BotTeamCubeCarrierVisible(bot_state_t *bs) { int i; float vis; aas_entityinfo_t entinfo; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (i == bs->client) continue; // BotEntityInfo(i, &entinfo); //if this player is active if (!entinfo.valid) continue; //if this player is carrying a flag if (!EntityCarriesCubes(&entinfo)) continue; //if the flag carrier is not on the same team if (!BotSameTeam(bs, i)) continue; //if the flag carrier is not visible vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); if (vis <= 0) continue; // return i; } return -1; } /* ================== BotEnemyCubeCarrierVisible ================== */ int BotEnemyCubeCarrierVisible(bot_state_t *bs) { int i; float vis; aas_entityinfo_t entinfo; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (i == bs->client) continue; // BotEntityInfo(i, &entinfo); //if this player is active if (!entinfo.valid) continue; //if this player is carrying a flag if (!EntityCarriesCubes(&entinfo)) continue; //if the flag carrier is on the same team if (BotSameTeam(bs, i)) continue; //if the flag carrier is not visible vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); if (vis <= 0) continue; // return i; } return -1; } #endif /* ================== BotAimAtEnemy ================== */ void BotAimAtEnemy(bot_state_t *bs) { int i, enemyvisible; float dist, f, aim_skill, aim_accuracy, speed, reactiontime; vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity; vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; weaponinfo_t wi; aas_entityinfo_t entinfo; bot_goal_t goal; bsp_trace_t trace; vec3_t target; //if the bot has no enemy if (bs->enemy < 0) { return; } //get the enemy entity information BotEntityInfo(bs->enemy, &entinfo); //if this is not a player (should be an obelisk) if (bs->enemy >= MAX_CLIENTS) { //if the obelisk is visible VectorCopy(entinfo.origin, target); #ifdef MISSIONPACK // if attacking an obelisk if ( bs->enemy == redobelisk.entitynum || bs->enemy == blueobelisk.entitynum ) { target[2] += 32; } #endif //aim at the obelisk VectorSubtract(target, bs->eye, dir); vectoangles(dir, bs->ideal_viewangles); //set the aim target before trying to attack VectorCopy(target, bs->aimtarget); return; } // //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy); // aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1); aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); // if (aim_skill > 0.95) { //don't aim too early reactiontime = 0.5 * trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); if (bs->enemysight_time > FloatTime() - reactiontime) return; if (bs->teleport_time > FloatTime() - reactiontime) return; } //get the weapon information trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); //get the weapon specific aim accuracy and or aim skill if (wi.number == WP_MACHINEGUN) { aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1); } else if (wi.number == WP_SHOTGUN) { aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1); } else if (wi.number == WP_GRENADE_LAUNCHER) { aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1); aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1); } else if (wi.number == WP_ROCKET_LAUNCHER) { aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1); aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1); } else if (wi.number == WP_LIGHTNING) { aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1); } else if (wi.number == WP_RAILGUN) { aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1); } else if (wi.number == WP_PLASMAGUN) { aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1); aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1); } else if (wi.number == WP_BFG) { aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1); aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1); } // if (aim_accuracy <= 0) aim_accuracy = 0.0001f; //get the enemy entity information BotEntityInfo(bs->enemy, &entinfo); //if the enemy is invisible then shoot crappy most of the time if (EntityIsInvisible(&entinfo)) { if (random() > 0.1) aim_accuracy *= 0.4f; } // VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity); VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity); //enemy origin and velocity is remembered every 0.5 seconds if (bs->enemyposition_time < FloatTime()) { // bs->enemyposition_time = FloatTime() + 0.5; VectorCopy(enemyvelocity, bs->enemyvelocity); VectorCopy(entinfo.origin, bs->enemyorigin); } //if not extremely skilled if (aim_skill < 0.9) { VectorSubtract(entinfo.origin, bs->enemyorigin, dir); //if the enemy moved a bit if (VectorLengthSquared(dir) > Square(48)) { //if the enemy changed direction if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) { //aim accuracy should be worse now aim_accuracy *= 0.7f; } } } //check visibility of enemy enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy); //if the enemy is visible if (enemyvisible) { // VectorCopy(entinfo.origin, bestorigin); bestorigin[2] += 8; //get the start point shooting from //NOTE: the x and y projectile start offsets are ignored VectorCopy(bs->origin, start); start[2] += bs->cur_ps.viewheight; start[2] += wi.offset[2]; // BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT); //if the enemy is NOT hit if (trace.fraction <= 1 && trace.ent != entinfo.number) { bestorigin[2] += 16; } //if it is not an instant hit weapon the bot might want to predict the enemy if (wi.speed) { // VectorSubtract(bestorigin, bs->origin, dir); dist = VectorLength(dir); VectorSubtract(entinfo.origin, bs->enemyorigin, dir); //if the enemy is NOT pretty far away and strafing just small steps left and right if (!(dist > 100 && VectorLengthSquared(dir) < Square(32))) { //if skilled anough do exact prediction if (aim_skill > 0.8 && //if the weapon is ready to fire bs->cur_ps.weaponstate == WEAPON_READY) { aas_clientmove_t move; vec3_t origin; VectorSubtract(entinfo.origin, bs->origin, dir); //distance towards the enemy dist = VectorLength(dir); //direction the enemy is moving in VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); // VectorScale(dir, 1 / entinfo.update_time, dir); // VectorCopy(entinfo.origin, origin); origin[2] += 1; // VectorClear(cmdmove); //AAS_ClearShownDebugLines(); trap_AAS_PredictClientMovement(&move, bs->enemy, origin, PRESENCE_CROUCH, qfalse, dir, cmdmove, 0, dist * 10 / wi.speed, 0.1f, 0, 0, qfalse); VectorCopy(move.endpos, bestorigin); //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", FloatTime(), VectorLength(dir), dist * 10 / wi.speed); } //if not that skilled do linear prediction else if (aim_skill > 0.4) { VectorSubtract(entinfo.origin, bs->origin, dir); //distance towards the enemy dist = VectorLength(dir); //direction the enemy is moving in VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); dir[2] = 0; // speed = VectorNormalize(dir) / entinfo.update_time; //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed); //best spot to aim at VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin); } } } //if the projectile does radial damage if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) { //if the enemy isn't standing significantly higher than the bot if (entinfo.origin[2] < bs->origin[2] + 16) { //try to aim at the ground in front of the enemy VectorCopy(entinfo.origin, end); end[2] -= 64; BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT); // VectorCopy(bestorigin, groundtarget); if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16; else groundtarget[2] = trace.endpos[2] - 8; //trace a line from projectile start to ground target BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT); //if hitpoint is not vertically too far from the ground target if (fabs(trace.endpos[2] - groundtarget[2]) < 50) { VectorSubtract(trace.endpos, groundtarget, dir); //if the hitpoint is near anough the ground target if (VectorLengthSquared(dir) < Square(60)) { VectorSubtract(trace.endpos, start, dir); //if the hitpoint is far anough from the bot if (VectorLengthSquared(dir) > Square(100)) { //check if the bot is visible from the ground target trace.endpos[2] += 1; BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT); if (trace.fraction >= 1) { //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time()); VectorCopy(groundtarget, bestorigin); } } } } } } bestorigin[0] += 20 * crandom() * (1 - aim_accuracy); bestorigin[1] += 20 * crandom() * (1 - aim_accuracy); bestorigin[2] += 10 * crandom() * (1 - aim_accuracy); } else { // VectorCopy(bs->lastenemyorigin, bestorigin); bestorigin[2] += 8; //if the bot is skilled anough if (aim_skill > 0.5) { //do prediction shots around corners if (wi.number == WP_BFG || wi.number == WP_ROCKET_LAUNCHER || wi.number == WP_GRENADE_LAUNCHER) { //create the chase goal goal.entitynum = bs->client; goal.areanum = bs->areanum; VectorCopy(bs->eye, goal.origin); VectorSet(goal.mins, -8, -8, -8); VectorSet(goal.maxs, 8, 8, 8); // if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) { VectorSubtract(target, bs->eye, dir); if (VectorLengthSquared(dir) > Square(80)) { VectorCopy(target, bestorigin); bestorigin[2] -= 20; } } aim_accuracy = 1; } } } // if (enemyvisible) { BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT); VectorCopy(trace.endpos, bs->aimtarget); } else { VectorCopy(bestorigin, bs->aimtarget); } //get aim direction VectorSubtract(bestorigin, bs->eye, dir); // if (wi.number == WP_MACHINEGUN || wi.number == WP_SHOTGUN || wi.number == WP_LIGHTNING || wi.number == WP_RAILGUN) { //distance towards the enemy dist = VectorLength(dir); if (dist > 150) dist = 150; f = 0.6 + dist / 150 * 0.4; aim_accuracy *= f; } //add some random stuff to the aim direction depending on the aim accuracy if (aim_accuracy < 0.8) { VectorNormalize(dir); for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy); } //set the ideal view angles vectoangles(dir, bs->ideal_viewangles); //take the weapon spread into account for lower skilled bots bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy); bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy); bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); //if the bots should be really challenging if (bot_challenge.integer) { //if the bot is really accurate and has the enemy in view for some time if (aim_accuracy > 0.9 && bs->enemysight_time < FloatTime() - 1) { //set the view angles directly if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; VectorCopy(bs->ideal_viewangles, bs->viewangles); trap_EA_View(bs->client, bs->viewangles); } } } /* ================== BotCheckAttack ================== */ void BotCheckAttack(bot_state_t *bs) { float points, reactiontime, fov, firethrottle; int attackentity; bsp_trace_t bsptrace; //float selfpreservation; vec3_t forward, right, start, end, dir, angles; weaponinfo_t wi; bsp_trace_t trace; aas_entityinfo_t entinfo; vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; attackentity = bs->enemy; // BotEntityInfo(attackentity, &entinfo); // if not attacking a player if (attackentity >= MAX_CLIENTS) { #ifdef MISSIONPACK // if attacking an obelisk if ( entinfo.number == redobelisk.entitynum || entinfo.number == blueobelisk.entitynum ) { // if obelisk is respawning return if ( g_entities[entinfo.number].activator && g_entities[entinfo.number].activator->s.frame == 2 ) { return; } } #endif } // reactiontime = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); if (bs->enemysight_time > FloatTime() - reactiontime) return; if (bs->teleport_time > FloatTime() - reactiontime) return; //if changing weapons if (bs->weaponchange_time > FloatTime() - 0.1) return; //check fire throttle characteristic if (bs->firethrottlewait_time > FloatTime()) return; firethrottle = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1); if (bs->firethrottleshoot_time < FloatTime()) { if (random() > firethrottle) { bs->firethrottlewait_time = FloatTime() + firethrottle; bs->firethrottleshoot_time = 0; } else { bs->firethrottleshoot_time = FloatTime() + 1 - firethrottle; bs->firethrottlewait_time = 0; } } // // VectorSubtract(bs->aimtarget, bs->eye, dir); // if (bs->weaponnum == WP_GAUNTLET) { if (VectorLengthSquared(dir) > Square(60)) { return; } } if (VectorLengthSquared(dir) < Square(100)) fov = 120; else fov = 50; // vectoangles(dir, angles); if (!InFieldOfVision(bs->viewangles, fov, angles)) return; BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); if (bsptrace.fraction < 1 && bsptrace.ent != attackentity) return; //get the weapon info trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); //get the start point shooting from VectorCopy(bs->origin, start); start[2] += bs->cur_ps.viewheight; AngleVectors(bs->viewangles, forward, right, NULL); start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2]; //end point aiming at VectorMA(start, 1000, forward, end); //a little back to make sure not inside a very close enemy VectorMA(start, -12, forward, start); BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT); //if the entity is a client if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) { if (trace.ent != attackentity) { //if a teammate is hit if (BotSameTeam(bs, trace.ent)) return; } } //if won't hit the enemy or not attacking a player (obelisk) if (trace.ent != attackentity || attackentity >= MAX_CLIENTS) { //if the projectile does radial damage if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { if (trace.fraction * 1000 < wi.proj.radius) { points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5; if (points > 0) { return; } } //FIXME: check if a teammate gets radial damage } } //if fire has to be release to activate weapon if (wi.flags & WFL_FIRERELEASED) { if (bs->flags & BFL_ATTACKED) { trap_EA_Attack(bs->client); } } else { trap_EA_Attack(bs->client); } bs->flags ^= BFL_ATTACKED; } /* ================== BotMapScripts ================== */ void BotMapScripts(bot_state_t *bs) { char info[1024]; char mapname[128]; int i, shootbutton; float aim_accuracy; aas_entityinfo_t entinfo; vec3_t dir; trap_GetServerinfo(info, sizeof(info)); strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); mapname[sizeof(mapname)-1] = '\0'; if (!Q_stricmp(mapname, "q3tourney6")) { vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; vec3_t buttonorg = {304, 352, 920}; //NOTE: NEVER use the func_bobbing in q3tourney6 bs->tfl &= ~TFL_FUNCBOB; //if the bot is below the bounding box if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) { if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) { if (bs->origin[2] < mins[2]) { return; } } } shootbutton = qfalse; //if an enemy is below this bounding box then shoot the button for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { if (i == bs->client) continue; // BotEntityInfo(i, &entinfo); // if (!entinfo.valid) continue; //if the enemy isn't dead and the enemy isn't the bot self if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; // if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) { if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) { if (entinfo.origin[2] < mins[2]) { //if there's a team mate below the crusher if (BotSameTeam(bs, i)) { shootbutton = qfalse; break; } else { shootbutton = qtrue; } } } } } if (shootbutton) { bs->flags |= BFL_IDEALVIEWSET; VectorSubtract(buttonorg, bs->eye, dir); vectoangles(dir, bs->ideal_viewangles); aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy); bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy); bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); // if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) { trap_EA_Attack(bs->client); } } } else if (!Q_stricmp(mapname, "mpq3tourney6")) { //NOTE: NEVER use the func_bobbing in mpq3tourney6 bs->tfl &= ~TFL_FUNCBOB; } } /* ================== BotSetMovedir ================== */ // bk001205 - made these static static vec3_t VEC_UP = {0, -1, 0}; static vec3_t MOVEDIR_UP = {0, 0, 1}; static vec3_t VEC_DOWN = {0, -2, 0}; static vec3_t MOVEDIR_DOWN = {0, 0, -1}; void BotSetMovedir(vec3_t angles, vec3_t movedir) { if (VectorCompare(angles, VEC_UP)) { VectorCopy(MOVEDIR_UP, movedir); } else if (VectorCompare(angles, VEC_DOWN)) { VectorCopy(MOVEDIR_DOWN, movedir); } else { AngleVectors(angles, movedir, NULL, NULL); } } /* ================== BotModelMinsMaxs this is ugly ================== */ int BotModelMinsMaxs(int modelindex, int eType, int contents, vec3_t mins, vec3_t maxs) { gentity_t *ent; int i; ent = &g_entities[0]; for (i = 0; i < level.num_entities; i++, ent++) { if ( !ent->inuse ) { continue; } if ( eType && ent->s.eType != eType) { continue; } if ( contents && ent->r.contents != contents) { continue; } if (ent->s.modelindex == modelindex) { if (mins) VectorAdd(ent->r.currentOrigin, ent->r.mins, mins); if (maxs) VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs); return i; } } if (mins) VectorClear(mins); if (maxs) VectorClear(maxs); return 0; } /* ================== BotFuncButtonGoal ================== */ int BotFuncButtonActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { int i, areas[10], numareas, modelindex, entitynum; char model[128]; float lip, dist, health, angle; vec3_t size, start, end, mins, maxs, angles, points[10]; vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs; vec3_t extramins = {1, 1, 1}, extramaxs = {-1, -1, -1}; bsp_trace_t bsptrace; activategoal->shoot = qfalse; VectorClear(activategoal->target); //create a bot goal towards the button trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); if (!*model) return qfalse; modelindex = atoi(model+1); if (!modelindex) return qfalse; VectorClear(angles); entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); //get the lip of the button trap_AAS_FloatForBSPEpairKey(bspent, "lip", &lip); if (!lip) lip = 4; //get the move direction from the angle trap_AAS_FloatForBSPEpairKey(bspent, "angle", &angle); VectorSet(angles, 0, angle, 0); BotSetMovedir(angles, movedir); //button size VectorSubtract(maxs, mins, size); //button origin VectorAdd(mins, maxs, origin); VectorScale(origin, 0.5, origin); //touch distance of the button dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2]; dist *= 0.5; // trap_AAS_FloatForBSPEpairKey(bspent, "health", &health); //if the button is shootable if (health) { //calculate the shoot target VectorMA(origin, -dist, movedir, goalorigin); // VectorCopy(goalorigin, activategoal->target); activategoal->shoot = qtrue; // BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, goalorigin, bs->entitynum, MASK_SHOT); // if the button is visible from the current position if (bsptrace.fraction >= 1.0 || bsptrace.ent == entitynum) { // activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable button activategoal->goal.number = 0; activategoal->goal.flags = 0; VectorCopy(bs->origin, activategoal->goal.origin); activategoal->goal.areanum = bs->areanum; VectorSet(activategoal->goal.mins, -8, -8, -8); VectorSet(activategoal->goal.maxs, 8, 8, 8); // return qtrue; } else { //create a goal from where the button is visible and shoot at the button from there //add bounding box size to the dist trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); for (i = 0; i < 3; i++) { if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); else dist += fabs(movedir[i]) * fabs(bboxmins[i]); } //calculate the goal origin VectorMA(origin, -dist, movedir, goalorigin); // VectorCopy(goalorigin, start); start[2] += 24; VectorCopy(start, end); end[2] -= 512; numareas = trap_AAS_TraceAreas(start, end, areas, points, 10); // for (i = numareas-1; i >= 0; i--) { if (trap_AAS_AreaReachability(areas[i])) { break; } } if (i < 0) { // FIXME: trace forward and maybe in other directions to find a valid area } if (i >= 0) { // VectorCopy(points[i], activategoal->goal.origin); activategoal->goal.areanum = areas[i]; VectorSet(activategoal->goal.mins, 8, 8, 8); VectorSet(activategoal->goal.maxs, -8, -8, -8); // for (i = 0; i < 3; i++) { if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); } //end for // activategoal->goal.entitynum = entitynum; activategoal->goal.number = 0; activategoal->goal.flags = 0; return qtrue; } } return qfalse; } else { //add bounding box size to the dist trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); for (i = 0; i < 3; i++) { if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); else dist += fabs(movedir[i]) * fabs(bboxmins[i]); } //calculate the goal origin VectorMA(origin, -dist, movedir, goalorigin); // VectorCopy(goalorigin, start); start[2] += 24; VectorCopy(start, end); end[2] -= 100; numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); // for (i = 0; i < numareas; i++) { if (trap_AAS_AreaReachability(areas[i])) { break; } } if (i < numareas) { // VectorCopy(origin, activategoal->goal.origin); activategoal->goal.areanum = areas[i]; VectorSubtract(mins, origin, activategoal->goal.mins); VectorSubtract(maxs, origin, activategoal->goal.maxs); // for (i = 0; i < 3; i++) { if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); } //end for // activategoal->goal.entitynum = entitynum; activategoal->goal.number = 0; activategoal->goal.flags = 0; return qtrue; } } return qfalse; } /* ================== BotFuncDoorGoal ================== */ int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { int modelindex, entitynum; char model[MAX_INFO_STRING]; vec3_t mins, maxs, origin, angles; //shoot at the shootable door trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); if (!*model) return qfalse; modelindex = atoi(model+1); if (!modelindex) return qfalse; VectorClear(angles); entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); //door origin VectorAdd(mins, maxs, origin); VectorScale(origin, 0.5, origin); VectorCopy(origin, activategoal->target); activategoal->shoot = qtrue; // activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable door activategoal->goal.number = 0; activategoal->goal.flags = 0; VectorCopy(bs->origin, activategoal->goal.origin); activategoal->goal.areanum = bs->areanum; VectorSet(activategoal->goal.mins, -8, -8, -8); VectorSet(activategoal->goal.maxs, 8, 8, 8); return qtrue; } /* ================== BotTriggerMultipleGoal ================== */ int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { int i, areas[10], numareas, modelindex, entitynum; char model[128]; vec3_t start, end, mins, maxs, angles; vec3_t origin, goalorigin; activategoal->shoot = qfalse; VectorClear(activategoal->target); //create a bot goal towards the trigger trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); if (!*model) return qfalse; modelindex = atoi(model+1); if (!modelindex) return qfalse; VectorClear(angles); entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_TRIGGER, mins, maxs); //trigger origin VectorAdd(mins, maxs, origin); VectorScale(origin, 0.5, origin); VectorCopy(origin, goalorigin); // VectorCopy(goalorigin, start); start[2] += 24; VectorCopy(start, end); end[2] -= 100; numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); // for (i = 0; i < numareas; i++) { if (trap_AAS_AreaReachability(areas[i])) { break; } } if (i < numareas) { VectorCopy(origin, activategoal->goal.origin); activategoal->goal.areanum = areas[i]; VectorSubtract(mins, origin, activategoal->goal.mins); VectorSubtract(maxs, origin, activategoal->goal.maxs); // activategoal->goal.entitynum = entitynum; activategoal->goal.number = 0; activategoal->goal.flags = 0; return qtrue; } return qfalse; } /* ================== BotPopFromActivateGoalStack ================== */ int BotPopFromActivateGoalStack(bot_state_t *bs) { if (!bs->activatestack) return qfalse; BotEnableActivateGoalAreas(bs->activatestack, qtrue); bs->activatestack->inuse = qfalse; bs->activatestack->justused_time = FloatTime(); bs->activatestack = bs->activatestack->next; return qtrue; } /* ================== BotPushOntoActivateGoalStack ================== */ int BotPushOntoActivateGoalStack(bot_state_t *bs, bot_activategoal_t *activategoal) { int i, best; float besttime; best = -1; besttime = FloatTime() + 9999; // for (i = 0; i < MAX_ACTIVATESTACK; i++) { if (!bs->activategoalheap[i].inuse) { if (bs->activategoalheap[i].justused_time < besttime) { besttime = bs->activategoalheap[i].justused_time; best = i; } } } if (best != -1) { memcpy(&bs->activategoalheap[best], activategoal, sizeof(bot_activategoal_t)); bs->activategoalheap[best].inuse = qtrue; bs->activategoalheap[best].next = bs->activatestack; bs->activatestack = &bs->activategoalheap[best]; return qtrue; } return qfalse; } /* ================== BotClearActivateGoalStack ================== */ void BotClearActivateGoalStack(bot_state_t *bs) { while(bs->activatestack) BotPopFromActivateGoalStack(bs); } /* ================== BotEnableActivateGoalAreas ================== */ void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable) { int i; if (activategoal->areasdisabled == !enable) return; for (i = 0; i < activategoal->numareas; i++) trap_AAS_EnableRoutingArea( activategoal->areas[i], enable ); activategoal->areasdisabled = !enable; } /* ================== BotIsGoingToActivateEntity ================== */ int BotIsGoingToActivateEntity(bot_state_t *bs, int entitynum) { bot_activategoal_t *a; int i; for (a = bs->activatestack; a; a = a->next) { if (a->time < FloatTime()) continue; if (a->goal.entitynum == entitynum) return qtrue; } for (i = 0; i < MAX_ACTIVATESTACK; i++) { if (bs->activategoalheap[i].inuse) continue; // if (bs->activategoalheap[i].goal.entitynum == entitynum) { // if the bot went for this goal less than 2 seconds ago if (bs->activategoalheap[i].justused_time > FloatTime() - 2) return qtrue; } } return qfalse; } /* ================== BotGetActivateGoal returns the number of the bsp entity to activate goal->entitynum will be set to the game entity to activate ================== */ //#define OBSTACLEDEBUG int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activategoal) { int i, ent, cur_entities[10], spawnflags, modelindex, areas[MAX_ACTIVATEAREAS*2], numareas, t; char model[MAX_INFO_STRING], tmpmodel[128]; char target[128], classname[128]; float health; char targetname[10][128]; aas_entityinfo_t entinfo; aas_areainfo_t areainfo; vec3_t origin, angles, absmins, absmaxs; memset(activategoal, 0, sizeof(bot_activategoal_t)); BotEntityInfo(entitynum, &entinfo); Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex); for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue; if (!strcmp(model, tmpmodel)) break; } if (!ent) { BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity found with model %s\n", model); return 0; } trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); if (!classname) { BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model %s has no classname\n", model); return 0; } //if it is a door if (!strcmp(classname, "func_door")) { if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) { //if the door has health then the door must be shot to open if (health) { BotFuncDoorActivateGoal(bs, ent, activategoal); return ent; } } // trap_AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); // if the door starts open then just wait for the door to return if ( spawnflags & 1 ) return 0; //get the door origin if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) { VectorClear(origin); } //if the door is open or opening already if (!VectorCompare(origin, entinfo.origin)) return 0; // store all the areas the door is in trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); if (*model) { modelindex = atoi(model+1); if (modelindex) { VectorClear(angles); BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs); // numareas = trap_AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2); // store the areas with reachabilities first for (i = 0; i < numareas; i++) { if (activategoal->numareas >= MAX_ACTIVATEAREAS) break; if ( !trap_AAS_AreaReachability(areas[i]) ) { continue; } trap_AAS_AreaInfo(areas[i], &areainfo); if (areainfo.contents & AREACONTENTS_MOVER) { activategoal->areas[activategoal->numareas++] = areas[i]; } } // store any remaining areas for (i = 0; i < numareas; i++) { if (activategoal->numareas >= MAX_ACTIVATEAREAS) break; if ( trap_AAS_AreaReachability(areas[i]) ) { continue; } trap_AAS_AreaInfo(areas[i], &areainfo); if (areainfo.contents & AREACONTENTS_MOVER) { activategoal->areas[activategoal->numareas++] = areas[i]; } } } } } // if the bot is blocked by or standing on top of a button if (!strcmp(classname, "func_button")) { return 0; } // get the targetname so we can find an entity with a matching target if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) { if (bot_developer.integer) { BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model \"%s\" has no targetname\n", model); } return 0; } // allow tree-like activation cur_entities[0] = trap_AAS_NextBSPEntity(0); for (i = 0; i >= 0 && i < 10;) { for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) { if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue; if (!strcmp(targetname[i], target)) { cur_entities[i] = trap_AAS_NextBSPEntity(ent); break; } } if (!ent) { if (bot_developer.integer) { BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity with target \"%s\"\n", targetname[i]); } i--; continue; } if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) { if (bot_developer.integer) { BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with target \"%s\" has no classname\n", targetname[i]); } continue; } // BSP button model if (!strcmp(classname, "func_button")) { // if (!BotFuncButtonActivateGoal(bs, ent, activategoal)) continue; // if the bot tries to activate this button already if ( bs->activatestack && bs->activatestack->inuse && bs->activatestack->goal.entitynum == activategoal->goal.entitynum && bs->activatestack->time > FloatTime() && bs->activatestack->start_time < FloatTime() - 2) continue; // if the bot is in a reachability area if ( trap_AAS_AreaReachability(bs->areanum) ) { // disable all areas the blocking entity is in BotEnableActivateGoalAreas( activategoal, qfalse ); // t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); // if the button is not reachable if (!t) { continue; } activategoal->time = FloatTime() + t * 0.01 + 5; } return ent; } // invisible trigger multiple box else if (!strcmp(classname, "trigger_multiple")) { // if (!BotTriggerMultipleActivateGoal(bs, ent, activategoal)) continue; // if the bot tries to activate this trigger already if ( bs->activatestack && bs->activatestack->inuse && bs->activatestack->goal.entitynum == activategoal->goal.entitynum && bs->activatestack->time > FloatTime() && bs->activatestack->start_time < FloatTime() - 2) continue; // if the bot is in a reachability area if ( trap_AAS_AreaReachability(bs->areanum) ) { // disable all areas the blocking entity is in BotEnableActivateGoalAreas( activategoal, qfalse ); // t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); // if the trigger is not reachable if (!t) { continue; } activategoal->time = FloatTime() + t * 0.01 + 5; } return ent; } else if (!strcmp(classname, "func_timer")) { // just skip the func_timer continue; } // the actual button or trigger might be linked through a target_relay or target_delay else if (!strcmp(classname, "target_relay") || !strcmp(classname, "target_delay")) { if (trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[i+1], sizeof(targetname[0]))) { i++; cur_entities[i] = trap_AAS_NextBSPEntity(0); } } } #ifdef OBSTACLEDEBUG BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no valid activator for entity with target \"%s\"\n", targetname[0]); #endif return 0; } /* ================== BotGoForActivateGoal ================== */ int BotGoForActivateGoal(bot_state_t *bs, bot_activategoal_t *activategoal) { aas_entityinfo_t activateinfo; activategoal->inuse = qtrue; if (!activategoal->time) activategoal->time = FloatTime() + 10; activategoal->start_time = FloatTime(); BotEntityInfo(activategoal->goal.entitynum, &activateinfo); VectorCopy(activateinfo.origin, activategoal->origin); // if (BotPushOntoActivateGoalStack(bs, activategoal)) { // enter the activate entity AI node AIEnter_Seek_ActivateEntity(bs, "BotGoForActivateGoal"); return qtrue; } else { // enable any routing areas that were disabled BotEnableActivateGoalAreas(activategoal, qtrue); return qfalse; } } /* ================== BotPrintActivateGoalInfo ================== */ void BotPrintActivateGoalInfo(bot_state_t *bs, bot_activategoal_t *activategoal, int bspent) { char netname[MAX_NETNAME]; char classname[128]; char buf[128]; ClientName(bs->client, netname, sizeof(netname)); trap_AAS_ValueForBSPEpairKey(bspent, "classname", classname, sizeof(classname)); if (activategoal->shoot) { Com_sprintf(buf, sizeof(buf), "%s: I have to shoot at a %s from %1.1f %1.1f %1.1f in area %d\n", netname, classname, activategoal->goal.origin[0], activategoal->goal.origin[1], activategoal->goal.origin[2], activategoal->goal.areanum); } else { Com_sprintf(buf, sizeof(buf), "%s: I have to activate a %s at %1.1f %1.1f %1.1f in area %d\n", netname, classname, activategoal->goal.origin[0], activategoal->goal.origin[1], activategoal->goal.origin[2], activategoal->goal.areanum); } trap_EA_Say(bs->client, buf); } /* ================== BotRandomMove ================== */ void BotRandomMove(bot_state_t *bs, bot_moveresult_t *moveresult) { vec3_t dir, angles; angles[0] = 0; angles[1] = random() * 360; angles[2] = 0; AngleVectors(angles, dir, NULL, NULL); trap_BotMoveInDirection(bs->ms, dir, 400, MOVE_WALK); moveresult->failure = qfalse; VectorCopy(dir, moveresult->movedir); } /* ================== BotAIBlocked Very basic handling of bots being blocked by other entities. Check what kind of entity is blocking the bot and try to activate it. If that's not an option then try to walk around or over the entity. Before the bot ends in this part of the AI it should predict which doors to open, which buttons to activate etc. ================== */ void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { int movetype, bspent; vec3_t hordir, start, end, mins, maxs, sideward, angles, up = {0, 0, 1}; aas_entityinfo_t entinfo; bot_activategoal_t activategoal; // if the bot is not blocked by anything if (!moveresult->blocked) { bs->notblocked_time = FloatTime(); return; } // if stuck in a solid area if ( moveresult->type == RESULTTYPE_INSOLIDAREA ) { // move in a random direction in the hope to get out BotRandomMove(bs, moveresult); // return; } // get info for the entity that is blocking the bot BotEntityInfo(moveresult->blockentity, &entinfo); #ifdef OBSTACLEDEBUG ClientName(bs->client, netname, sizeof(netname)); BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex); #endif // OBSTACLEDEBUG // if blocked by a bsp model and the bot wants to activate it if (activate && entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex) { // find the bsp entity which should be activated in order to get the blocking entity out of the way bspent = BotGetActivateGoal(bs, entinfo.number, &activategoal); if (bspent) { // if (bs->activatestack && !bs->activatestack->inuse) bs->activatestack = NULL; // if not already trying to activate this entity if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { // BotGoForActivateGoal(bs, &activategoal); } // if ontop of an obstacle or // if the bot is not in a reachability area it'll still // need some dynamic obstacle avoidance, otherwise return if (!(moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) && trap_AAS_AreaReachability(bs->areanum)) return; } else { // enable any routing areas that were disabled BotEnableActivateGoalAreas(&activategoal, qtrue); } } // just some basic dynamic obstacle avoidance code hordir[0] = moveresult->movedir[0]; hordir[1] = moveresult->movedir[1]; hordir[2] = 0; // if no direction just take a random direction if (VectorNormalize(hordir) < 0.1) { VectorSet(angles, 0, 360 * random(), 0); AngleVectors(angles, hordir, NULL, NULL); } // //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP; //else movetype = MOVE_WALK; // if there's an obstacle at the bot's feet and head then // the bot might be able to crouch through VectorCopy(bs->origin, start); start[2] += 18; VectorMA(start, 5, hordir, end); VectorSet(mins, -16, -16, -24); VectorSet(maxs, 16, 16, 4); // //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID); //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH; // get the sideward vector CrossProduct(hordir, up, sideward); // if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward); // try to crouch straight forward? if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) { // perform the movement if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) { // flip the avoid direction flag bs->flags ^= BFL_AVOIDRIGHT; // flip the direction // VectorNegate(sideward, sideward); VectorMA(sideward, -1, hordir, sideward); // move in the other direction trap_BotMoveInDirection(bs->ms, sideward, 400, movetype); } } // if (bs->notblocked_time < FloatTime() - 0.4) { // just reset goals and hope the bot will go into another direction? // is this still needed?? if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; } } /* ================== BotAIPredictObstacles Predict the route towards the goal and check if the bot will be blocked by certain obstacles. When the bot has obstacles on it's path the bot should figure out if they can be removed by activating certain entities. ================== */ int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal) { int modelnum, entitynum, bspent; bot_activategoal_t activategoal; aas_predictroute_t route; if (!bot_predictobstacles.integer) return qfalse; // always predict when the goal change or at regular intervals if (bs->predictobstacles_goalareanum == goal->areanum && bs->predictobstacles_time > FloatTime() - 6) { return qfalse; } bs->predictobstacles_goalareanum = goal->areanum; bs->predictobstacles_time = FloatTime(); // predict at most 100 areas or 10 seconds ahead trap_AAS_PredictRoute(&route, bs->areanum, bs->origin, goal->areanum, bs->tfl, 100, 1000, RSE_USETRAVELTYPE|RSE_ENTERCONTENTS, AREACONTENTS_MOVER, TFL_BRIDGE, 0); // if bot has to travel through an area with a mover if (route.stopevent & RSE_ENTERCONTENTS) { // if the bot will run into a mover if (route.endcontents & AREACONTENTS_MOVER) { //NOTE: this only works with bspc 2.1 or higher modelnum = (route.endcontents & AREACONTENTS_MODELNUM) >> AREACONTENTS_MODELNUMSHIFT; if (modelnum) { // entitynum = BotModelMinsMaxs(modelnum, ET_MOVER, 0, NULL, NULL); if (entitynum) { //NOTE: BotGetActivateGoal already checks if the door is open or not bspent = BotGetActivateGoal(bs, entitynum, &activategoal); if (bspent) { // if (bs->activatestack && !bs->activatestack->inuse) bs->activatestack = NULL; // if not already trying to activate this entity if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { // //BotAI_Print(PRT_MESSAGE, "blocked by mover model %d, entity %d ?\n", modelnum, entitynum); // BotGoForActivateGoal(bs, &activategoal); return qtrue; } else { // enable any routing areas that were disabled BotEnableActivateGoalAreas(&activategoal, qtrue); } } } } } } else if (route.stopevent & RSE_USETRAVELTYPE) { if (route.endtravelflags & TFL_BRIDGE) { //FIXME: check if the bridge is available to travel over } } return qfalse; } /* ================== BotCheckConsoleMessages ================== */ void BotCheckConsoleMessages(bot_state_t *bs) { char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr; float chat_reply; int context, handle; bot_consolemessage_t m; bot_match_t match; //the name of this bot ClientName(bs->client, botname, sizeof(botname)); // while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) { //if the chat state is flooded with messages the bot will read them quickly if (trap_BotNumConsoleMessages(bs->cs) < 10) { //if it is a chat message the bot needs some time to read it if (m.type == CMS_CHAT && m.time > FloatTime() - (1 + random())) break; } // ptr = m.message; //if it is a chat message then don't unify white spaces and don't //replace synonyms in the netname if (m.type == CMS_CHAT) { // if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { ptr = m.message + match.variables[MESSAGE].offset; } } //unify the white spaces in the message trap_UnifyWhiteSpaces(ptr); //replace synonyms in the right context context = BotSynonymContext(bs); trap_BotReplaceSynonyms(ptr, context); //if there's no match if (!BotMatchMessage(bs, m.message)) { //if it is a chat message if (m.type == CMS_CHAT && !bot_nochat.integer) { // if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { trap_BotRemoveConsoleMessage(bs->cs, handle); continue; } //don't use eliza chats with team messages if (match.subtype & ST_TEAM) { trap_BotRemoveConsoleMessage(bs->cs, handle); continue; } // trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname)); trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message)); //if this is a message from the bot self if (bs->client == ClientFromName(netname)) { trap_BotRemoveConsoleMessage(bs->cs, handle); continue; } //unify the message trap_UnifyWhiteSpaces(message); // trap_Cvar_Update(&bot_testrchat); if (bot_testrchat.integer) { // trap_BotLibVarSet("bot_testrchat", "1"); //if bot replies with a chat message if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, NULL, NULL, NULL, NULL, NULL, NULL, botname, netname)) { BotAI_Print(PRT_MESSAGE, "------------------------\n"); } else { BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n"); } } //if at a valid chat position and not chatting already and not in teamplay else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs) && !TeamPlayIsOn()) { chat_reply = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1); if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) { //if bot replies with a chat message if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, NULL, NULL, NULL, NULL, NULL, NULL, botname, netname)) { //remove the console message trap_BotRemoveConsoleMessage(bs->cs, handle); bs->stand_time = FloatTime() + BotChatTime(bs); AIEnter_Stand(bs, "BotCheckConsoleMessages: reply chat"); //EA_Say(bs->client, bs->cs.chatmessage); break; } } } } } //remove the console message trap_BotRemoveConsoleMessage(bs->cs, handle); } } /* ================== BotCheckEvents ================== */ void BotCheckForGrenades(bot_state_t *bs, entityState_t *state) { // if this is not a grenade if (state->eType != ET_MISSILE || state->weapon != WP_GRENADE_LAUNCHER) return; // try to avoid the grenade trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); } #ifdef MISSIONPACK /* ================== BotCheckForProxMines ================== */ void BotCheckForProxMines(bot_state_t *bs, entityState_t *state) { // if this is not a prox mine if (state->eType != ET_MISSILE || state->weapon != WP_PROX_LAUNCHER) return; // if this prox mine is from someone on our own team if (state->generic1 == BotTeam(bs)) return; // if the bot doesn't have a weapon to deactivate the mine if (!(bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) && !(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) && !(bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) ) { return; } // try to avoid the prox mine trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); // if (bs->numproxmines >= MAX_PROXMINES) return; bs->proxmines[bs->numproxmines] = state->number; bs->numproxmines++; } /* ================== BotCheckForKamikazeBody ================== */ void BotCheckForKamikazeBody(bot_state_t *bs, entityState_t *state) { // if this entity is not wearing the kamikaze if (!(state->eFlags & EF_KAMIKAZE)) return; // if this entity isn't dead if (!(state->eFlags & EF_DEAD)) return; //remember this kamikaze body bs->kamikazebody = state->number; } #endif /* ================== BotCheckEvents ================== */ void BotCheckEvents(bot_state_t *bs, entityState_t *state) { int event; char buf[128]; #ifdef MISSIONPACK aas_entityinfo_t entinfo; #endif //NOTE: this sucks, we're accessing the gentity_t directly //but there's no other fast way to do it right now if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) { return; } bs->entityeventTime[state->number] = g_entities[state->number].eventTime; //if it's an event only entity if (state->eType > ET_EVENTS) { event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS; } else { event = state->event & ~EV_EVENT_BITS; } // switch(event) { //client obituary event case EV_OBITUARY: { int target, attacker, mod; target = state->otherEntityNum; attacker = state->otherEntityNum2; mod = state->eventParm; // if (target == bs->client) { bs->botdeathtype = mod; bs->lastkilledby = attacker; // if (target == attacker || target == ENTITYNUM_NONE || target == ENTITYNUM_WORLD) bs->botsuicide = qtrue; else bs->botsuicide = qfalse; // bs->num_deaths++; } //else if this client was killed by the bot else if (attacker == bs->client) { bs->enemydeathtype = mod; bs->lastkilledplayer = target; bs->killedenemy_time = FloatTime(); // bs->num_kills++; } else if (attacker == bs->enemy && target == attacker) { bs->enemysuicide = qtrue; } // #ifdef MISSIONPACK if (gametype == GT_1FCTF) { // BotEntityInfo(target, &entinfo); if ( entinfo.powerups & ( 1 << PW_NEUTRALFLAG ) ) { if (!BotSameTeam(bs, target)) { bs->neutralflagstatus = 3; //enemy dropped the flag bs->flagstatuschanged = qtrue; } } } #endif break; } case EV_GLOBAL_SOUND: { if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm); break; } trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); /* if (!strcmp(buf, "sound/teamplay/flagret_red.wav")) { //red flag is returned bs->redflagstatus = 0; bs->flagstatuschanged = qtrue; } else if (!strcmp(buf, "sound/teamplay/flagret_blu.wav")) { //blue flag is returned bs->blueflagstatus = 0; bs->flagstatuschanged = qtrue; } else*/ #ifdef MISSIONPACK if (!strcmp(buf, "sound/items/kamikazerespawn.wav" )) { //the kamikaze respawned so dont avoid it BotDontAvoid(bs, "Kamikaze"); } else #endif if (!strcmp(buf, "sound/items/poweruprespawn.wav")) { //powerup respawned... go get it BotGoForPowerups(bs); } break; } case EV_GLOBAL_TEAM_SOUND: { if (gametype == GT_CTF) { switch(state->eventParm) { case GTS_RED_CAPTURE: bs->blueflagstatus = 0; bs->redflagstatus = 0; bs->flagstatuschanged = qtrue; break; //see BotMatch_CTF case GTS_BLUE_CAPTURE: bs->blueflagstatus = 0; bs->redflagstatus = 0; bs->flagstatuschanged = qtrue; break; //see BotMatch_CTF case GTS_RED_RETURN: //blue flag is returned bs->blueflagstatus = 0; bs->flagstatuschanged = qtrue; break; case GTS_BLUE_RETURN: //red flag is returned bs->redflagstatus = 0; bs->flagstatuschanged = qtrue; break; case GTS_RED_TAKEN: //blue flag is taken bs->blueflagstatus = 1; bs->flagstatuschanged = qtrue; break; //see BotMatch_CTF case GTS_BLUE_TAKEN: //red flag is taken bs->redflagstatus = 1; bs->flagstatuschanged = qtrue; break; //see BotMatch_CTF } } #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { switch(state->eventParm) { case GTS_RED_CAPTURE: bs->neutralflagstatus = 0; bs->flagstatuschanged = qtrue; break; case GTS_BLUE_CAPTURE: bs->neutralflagstatus = 0; bs->flagstatuschanged = qtrue; break; case GTS_RED_RETURN: //flag has returned bs->neutralflagstatus = 0; bs->flagstatuschanged = qtrue; break; case GTS_BLUE_RETURN: //flag has returned bs->neutralflagstatus = 0; bs->flagstatuschanged = qtrue; break; case GTS_RED_TAKEN: bs->neutralflagstatus = BotTeam(bs) == TEAM_RED ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c bs->flagstatuschanged = qtrue; break; case GTS_BLUE_TAKEN: bs->neutralflagstatus = BotTeam(bs) == TEAM_BLUE ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c bs->flagstatuschanged = qtrue; break; } } #endif break; } case EV_PLAYER_TELEPORT_IN: { VectorCopy(state->origin, lastteleport_origin); lastteleport_time = FloatTime(); break; } case EV_GENERAL_SOUND: { //if this sound is played on the bot if (state->number == bs->client) { if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm); break; } //check out the sound trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); //if falling into a death pit if (!strcmp(buf, "*falling1.wav")) { //if the bot has a personal teleporter if (bs->inventory[INVENTORY_TELEPORTER] > 0) { //use the holdable item trap_EA_Use(bs->client); } } } break; } case EV_FOOTSTEP: case EV_FOOTSTEP_METAL: case EV_FOOTSPLASH: case EV_FOOTWADE: case EV_SWIM: case EV_FALL_SHORT: case EV_FALL_MEDIUM: case EV_FALL_FAR: case EV_STEP_4: case EV_STEP_8: case EV_STEP_12: case EV_STEP_16: case EV_JUMP_PAD: case EV_JUMP: case EV_TAUNT: case EV_WATER_TOUCH: case EV_WATER_LEAVE: case EV_WATER_UNDER: case EV_WATER_CLEAR: case EV_ITEM_PICKUP: case EV_GLOBAL_ITEM_PICKUP: case EV_NOAMMO: case EV_CHANGE_WEAPON: case EV_FIRE_WEAPON: //FIXME: either add to sound queue or mark player as someone making noise break; case EV_USE_ITEM0: case EV_USE_ITEM1: case EV_USE_ITEM2: case EV_USE_ITEM3: case EV_USE_ITEM4: case EV_USE_ITEM5: case EV_USE_ITEM6: case EV_USE_ITEM7: case EV_USE_ITEM8: case EV_USE_ITEM9: case EV_USE_ITEM10: case EV_USE_ITEM11: case EV_USE_ITEM12: case EV_USE_ITEM13: case EV_USE_ITEM14: break; } } /* ================== BotCheckSnapshot ================== */ void BotCheckSnapshot(bot_state_t *bs) { int ent; entityState_t state; //remove all avoid spots trap_BotAddAvoidSpot(bs->ms, vec3_origin, 0, AVOID_CLEAR); //reset kamikaze body bs->kamikazebody = 0; //reset number of proxmines bs->numproxmines = 0; // ent = 0; while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) { //check the entity state for events BotCheckEvents(bs, &state); //check for grenades the bot should avoid BotCheckForGrenades(bs, &state); // #ifdef MISSIONPACK //check for proximity mines which the bot should deactivate BotCheckForProxMines(bs, &state); //check for dead bodies with the kamikaze effect which should be gibbed BotCheckForKamikazeBody(bs, &state); #endif } //check the player state for events BotAI_GetEntityState(bs->client, &state); //copy the player state events to the entity state state.event = bs->cur_ps.externalEvent; state.eventParm = bs->cur_ps.externalEventParm; // BotCheckEvents(bs, &state); } /* ================== BotCheckAir ================== */ void BotCheckAir(bot_state_t *bs) { if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) { if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { return; } } bs->lastair_time = FloatTime(); } /* ================== BotAlternateRoute ================== */ bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal) { int t; // if the bot has an alternative route goal if (bs->altroutegoal.areanum) { // if (bs->reachedaltroutegoal_time) return goal; // travel time towards alternative route goal t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->altroutegoal.areanum, bs->tfl); if (t && t < 20) { //BotAI_Print(PRT_MESSAGE, "reached alternate route goal\n"); bs->reachedaltroutegoal_time = FloatTime(); } memcpy(goal, &bs->altroutegoal, sizeof(bot_goal_t)); return &bs->altroutegoal; } return goal; } /* ================== BotGetAlternateRouteGoal ================== */ int BotGetAlternateRouteGoal(bot_state_t *bs, int base) { aas_altroutegoal_t *altroutegoals; bot_goal_t *goal; int numaltroutegoals, rnd; if (base == TEAM_RED) { altroutegoals = red_altroutegoals; numaltroutegoals = red_numaltroutegoals; } else { altroutegoals = blue_altroutegoals; numaltroutegoals = blue_numaltroutegoals; } if (!numaltroutegoals) return qfalse; rnd = (float) random() * numaltroutegoals; if (rnd >= numaltroutegoals) rnd = numaltroutegoals-1; goal = &bs->altroutegoal; goal->areanum = altroutegoals[rnd].areanum; VectorCopy(altroutegoals[rnd].origin, goal->origin); VectorSet(goal->mins, -8, -8, -8); VectorSet(goal->maxs, 8, 8, 8); goal->entitynum = 0; goal->iteminfo = 0; goal->number = 0; goal->flags = 0; // bs->reachedaltroutegoal_time = 0; return qtrue; } /* ================== BotSetupAlternateRouteGoals ================== */ void BotSetupAlternativeRouteGoals(void) { if (altroutegoals_setup) return; #ifdef MISSIONPACK if (gametype == GT_CTF) { if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) BotAI_Print(PRT_WARNING, "no alt routes without Neutral Flag\n"); if (ctf_neutralflag.areanum) { // red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( ctf_neutralflag.origin, ctf_neutralflag.areanum, ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, red_altroutegoals, MAX_ALTROUTEGOALS, ALTROUTEGOAL_CLUSTERPORTALS| ALTROUTEGOAL_VIEWPORTALS); blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( ctf_neutralflag.origin, ctf_neutralflag.areanum, ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, blue_altroutegoals, MAX_ALTROUTEGOALS, ALTROUTEGOAL_CLUSTERPORTALS| ALTROUTEGOAL_VIEWPORTALS); } } else if (gametype == GT_1FCTF) { // red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( ctf_neutralflag.origin, ctf_neutralflag.areanum, ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, red_altroutegoals, MAX_ALTROUTEGOALS, ALTROUTEGOAL_CLUSTERPORTALS| ALTROUTEGOAL_VIEWPORTALS); blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( ctf_neutralflag.origin, ctf_neutralflag.areanum, ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, blue_altroutegoals, MAX_ALTROUTEGOALS, ALTROUTEGOAL_CLUSTERPORTALS| ALTROUTEGOAL_VIEWPORTALS); } else if (gametype == GT_OBELISK) { if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); // red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( neutralobelisk.origin, neutralobelisk.areanum, redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, red_altroutegoals, MAX_ALTROUTEGOALS, ALTROUTEGOAL_CLUSTERPORTALS| ALTROUTEGOAL_VIEWPORTALS); blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( neutralobelisk.origin, neutralobelisk.areanum, blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, blue_altroutegoals, MAX_ALTROUTEGOALS, ALTROUTEGOAL_CLUSTERPORTALS| ALTROUTEGOAL_VIEWPORTALS); } else if (gametype == GT_HARVESTER) { // red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( neutralobelisk.origin, neutralobelisk.areanum, redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, red_altroutegoals, MAX_ALTROUTEGOALS, ALTROUTEGOAL_CLUSTERPORTALS| ALTROUTEGOAL_VIEWPORTALS); blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( neutralobelisk.origin, neutralobelisk.areanum, blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, blue_altroutegoals, MAX_ALTROUTEGOALS, ALTROUTEGOAL_CLUSTERPORTALS| ALTROUTEGOAL_VIEWPORTALS); } #endif altroutegoals_setup = qtrue; } /* ================== BotDeathmatchAI ================== */ void BotDeathmatchAI(bot_state_t *bs, float thinktime) { char gender[144], name[144], buf[144]; char userinfo[MAX_INFO_STRING]; int i; //if the bot has just been setup if (bs->setupcount > 0) { bs->setupcount--; if (bs->setupcount > 0) return; //get the gender characteristic trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender)); //set the bot gender trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); Info_SetValueForKey(userinfo, "sex", gender); trap_SetUserinfo(bs->client, userinfo); //set the team if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT ) { Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); trap_EA_Command(bs->client, buf); } //set the chat gender if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); //set the chat name ClientName(bs->client, name, sizeof(name)); trap_BotSetChatName(bs->cs, name, bs->client); // bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; // bs->setupcount = 0; // BotSetupAlternativeRouteGoals(); } //no ideal view set bs->flags &= ~BFL_IDEALVIEWSET; // if (!BotIntermission(bs)) { //set the teleport time BotSetTeleportTime(bs); //update some inventory values BotUpdateInventory(bs); //check out the snapshot BotCheckSnapshot(bs); //check for air BotCheckAir(bs); } //check the console messages BotCheckConsoleMessages(bs); //if not in the intermission and not in observer mode if (!BotIntermission(bs) && !BotIsObserver(bs)) { //do team AI BotTeamAI(bs); } //if the bot has no ai node if (!bs->ainode) { AIEnter_Seek_LTG(bs, "BotDeathmatchAI: no ai node"); } //if the bot entered the game less than 8 seconds ago if (!bs->entergamechat && bs->entergame_time > FloatTime() - 8) { if (BotChat_EnterGame(bs)) { bs->stand_time = FloatTime() + BotChatTime(bs); AIEnter_Stand(bs, "BotDeathmatchAI: chat enter game"); } bs->entergamechat = qtrue; } //reset the node switches from the previous frame BotResetNodeSwitches(); //execute AI nodes for (i = 0; i < MAX_NODESWITCHES; i++) { if (bs->ainode(bs)) break; } //if the bot removed itself :) if (!bs->inuse) return; //if the bot executed too many AI nodes if (i >= MAX_NODESWITCHES) { trap_BotDumpGoalStack(bs->gs); trap_BotDumpAvoidGoals(bs->gs); BotDumpNodeSwitches(bs); ClientName(bs->client, name, sizeof(name)); BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, FloatTime(), MAX_NODESWITCHES); } // bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; } /* ================== BotSetEntityNumForGoalWithModel ================== */ void BotSetEntityNumForGoalWithModel(bot_goal_t *goal, int eType, char *modelname) { gentity_t *ent; int i, modelindex; vec3_t dir; modelindex = G_ModelIndex( modelname ); ent = &g_entities[0]; for (i = 0; i < level.num_entities; i++, ent++) { if ( !ent->inuse ) { continue; } if ( eType && ent->s.eType != eType) { continue; } if (ent->s.modelindex != modelindex) { continue; } VectorSubtract(goal->origin, ent->s.origin, dir); if (VectorLengthSquared(dir) < Square(10)) { goal->entitynum = i; return; } } } /* ================== BotSetEntityNumForGoal ================== */ void BotSetEntityNumForGoal(bot_goal_t *goal, char *classname) { gentity_t *ent; int i; vec3_t dir; ent = &g_entities[0]; for (i = 0; i < level.num_entities; i++, ent++) { if ( !ent->inuse ) { continue; } if ( !Q_stricmp(ent->classname, classname) ) { continue; } VectorSubtract(goal->origin, ent->s.origin, dir); if (VectorLengthSquared(dir) < Square(10)) { goal->entitynum = i; return; } } } /* ================== BotGoalForBSPEntity ================== */ int BotGoalForBSPEntity( char *classname, bot_goal_t *goal ) { char value[MAX_INFO_STRING]; vec3_t origin, start, end; int ent, numareas, areas[10]; memset(goal, 0, sizeof(bot_goal_t)); for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", value, sizeof(value))) continue; if (!strcmp(value, classname)) { if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) return qfalse; VectorCopy(origin, goal->origin); VectorCopy(origin, start); start[2] -= 32; VectorCopy(origin, end); end[2] += 32; numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); if (!numareas) return qfalse; goal->areanum = areas[0]; return qtrue; } } return qfalse; } /* ================== BotSetupDeathmatchAI ================== */ void BotSetupDeathmatchAI(void) { int ent, modelnum; char model[128]; gametype = trap_Cvar_VariableIntegerValue("g_gametype"); maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0); trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0); trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0); trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0); trap_Cvar_Register(&bot_predictobstacles, "bot_predictobstacles", "1", 0); trap_Cvar_Register(&g_spSkill, "g_spSkill", "2", 0); // if (gametype == GT_CTF) { if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); } #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) BotAI_Print(PRT_WARNING, "One Flag CTF without Neutral Flag\n"); if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); } else if (gametype == GT_OBELISK) { if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) BotAI_Print(PRT_WARNING, "Obelisk without red obelisk\n"); BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) BotAI_Print(PRT_WARNING, "Obelisk without blue obelisk\n"); BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); } else if (gametype == GT_HARVESTER) { if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) BotAI_Print(PRT_WARNING, "Harvester without red obelisk\n"); BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) BotAI_Print(PRT_WARNING, "Harvester without blue obelisk\n"); BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); BotSetEntityNumForGoal(&neutralobelisk, "team_neutralobelisk"); } #endif max_bspmodelindex = 0; for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue; if (model[0] == '*') { modelnum = atoi(model+1); if (modelnum > max_bspmodelindex) max_bspmodelindex = modelnum; } } //initialize the waypoint heap BotInitWaypoints(); } /* ================== BotShutdownDeathmatchAI ================== */ void BotShutdownDeathmatchAI(void) { altroutegoals_setup = qfalse; } ================================================ FILE: code/game/ai_dmq3.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_dmq3.h * * desc: Quake3 bot AI * * $Archive: /source/code/botai/ai_chat.c $ * *****************************************************************************/ //setup the deathmatch AI void BotSetupDeathmatchAI(void); //shutdown the deathmatch AI void BotShutdownDeathmatchAI(void); //let the bot live within it's deathmatch AI net void BotDeathmatchAI(bot_state_t *bs, float thinktime); //free waypoints void BotFreeWaypoints(bot_waypoint_t *wp); //choose a weapon void BotChooseWeapon(bot_state_t *bs); //setup movement stuff void BotSetupForMovement(bot_state_t *bs); //update the inventory void BotUpdateInventory(bot_state_t *bs); //update the inventory during battle void BotUpdateBattleInventory(bot_state_t *bs, int enemy); //use holdable items during battle void BotBattleUseItems(bot_state_t *bs); //return true if the bot is dead qboolean BotIsDead(bot_state_t *bs); //returns true if the bot is in observer mode qboolean BotIsObserver(bot_state_t *bs); //returns true if the bot is in the intermission qboolean BotIntermission(bot_state_t *bs); //returns true if the bot is in lava or slime qboolean BotInLavaOrSlime(bot_state_t *bs); //returns true if the entity is dead qboolean EntityIsDead(aas_entityinfo_t *entinfo); //returns true if the entity is invisible qboolean EntityIsInvisible(aas_entityinfo_t *entinfo); //returns true if the entity is shooting qboolean EntityIsShooting(aas_entityinfo_t *entinfo); #ifdef MISSIONPACK //returns true if this entity has the kamikaze qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo); #endif // set a user info key/value pair void BotSetUserInfo(bot_state_t *bs, char *key, char *value); // set the team status (offense, defense etc.) void BotSetTeamStatus(bot_state_t *bs); //returns the name of the client char *ClientName(int client, char *name, int size); //returns an simplyfied client name char *EasyClientName(int client, char *name, int size); //returns the skin used by the client char *ClientSkin(int client, char *skin, int size); // returns the appropriate synonym context for the current game type and situation int BotSynonymContext(bot_state_t *bs); // set last ordered task int BotSetLastOrderedTask(bot_state_t *bs); // selection of goals for teamplay void BotTeamGoals(bot_state_t *bs, int retreat); //returns the aggression of the bot in the range [0, 100] float BotAggression(bot_state_t *bs); //returns how bad the bot feels float BotFeelingBad(bot_state_t *bs); //returns true if the bot wants to retreat int BotWantsToRetreat(bot_state_t *bs); //returns true if the bot wants to chase int BotWantsToChase(bot_state_t *bs); //returns true if the bot wants to help int BotWantsToHelp(bot_state_t *bs); //returns true if the bot can and wants to rocketjump int BotCanAndWantsToRocketJump(bot_state_t *bs); // returns true if the bot has a persistant powerup and a weapon int BotHasPersistantPowerupAndWeapon(bot_state_t *bs); //returns true if the bot wants to and goes camping int BotWantsToCamp(bot_state_t *bs); //the bot will perform attack movements bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl); //returns true if the bot and the entity are in the same team int BotSameTeam(bot_state_t *bs, int entnum); //returns true if teamplay is on int TeamPlayIsOn(void); // returns the client number of the team mate flag carrier (-1 if none) int BotTeamFlagCarrier(bot_state_t *bs); //returns visible team mate flag carrier if available int BotTeamFlagCarrierVisible(bot_state_t *bs); //returns visible enemy flag carrier if available int BotEnemyFlagCarrierVisible(bot_state_t *bs); //get the number of visible teammates and enemies void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range); //returns true if within the field of vision for the given angles qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles); //returns true and sets the .enemy field when an enemy is found int BotFindEnemy(bot_state_t *bs, int curenemy); //returns a roam goal void BotRoamGoal(bot_state_t *bs, vec3_t goal); //returns entity visibility in the range [0, 1] float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent); //the bot will aim at the current enemy void BotAimAtEnemy(bot_state_t *bs); //check if the bot should attack void BotCheckAttack(bot_state_t *bs); //AI when the bot is blocked void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate); //AI to predict obstacles int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal); //enable or disable the areas the blocking entity is in void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable); //pop an activate goal from the stack int BotPopFromActivateGoalStack(bot_state_t *bs); //clear the activate goal stack void BotClearActivateGoalStack(bot_state_t *bs); //returns the team the bot is in int BotTeam(bot_state_t *bs); //retuns the opposite team of the bot int BotOppositeTeam(bot_state_t *bs); //returns the flag the bot is carrying (CTFFLAG_?) int BotCTFCarryingFlag(bot_state_t *bs); //remember the last ordered task void BotRememberLastOrderedTask(bot_state_t *bs); //set ctf goals (defend base, get enemy flag) during seek void BotCTFSeekGoals(bot_state_t *bs); //set ctf goals (defend base, get enemy flag) during retreat void BotCTFRetreatGoals(bot_state_t *bs); // #ifdef MISSIONPACK int Bot1FCTFCarryingFlag(bot_state_t *bs); int BotHarvesterCarryingCubes(bot_state_t *bs); void Bot1FCTFSeekGoals(bot_state_t *bs); void Bot1FCTFRetreatGoals(bot_state_t *bs); void BotObeliskSeekGoals(bot_state_t *bs); void BotObeliskRetreatGoals(bot_state_t *bs); void BotGoHarvest(bot_state_t *bs); void BotHarvesterSeekGoals(bot_state_t *bs); void BotHarvesterRetreatGoals(bot_state_t *bs); int BotTeamCubeCarrierVisible(bot_state_t *bs); int BotEnemyCubeCarrierVisible(bot_state_t *bs); #endif //get a random alternate route goal towards the given base int BotGetAlternateRouteGoal(bot_state_t *bs, int base); //returns either the alternate route goal or the given goal bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal); //create a new waypoint bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum); //find a waypoint with the given name bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name); //strstr but case insensitive char *stristr(char *str, char *charset); //returns the number of the client with the given name int ClientFromName(char *name); int ClientOnSameTeamFromName(bot_state_t *bs, char *name); // int BotPointAreaNum(vec3_t origin); // void BotMapScripts(bot_state_t *bs); //ctf flags #define CTF_FLAG_NONE 0 #define CTF_FLAG_RED 1 #define CTF_FLAG_BLUE 2 //CTF skins #define CTF_SKIN_REDTEAM "red" #define CTF_SKIN_BLUETEAM "blue" extern int gametype; //game type extern int maxclients; //maximum number of clients extern vmCvar_t bot_grapple; extern vmCvar_t bot_rocketjump; extern vmCvar_t bot_fastchat; extern vmCvar_t bot_nochat; extern vmCvar_t bot_testrchat; extern vmCvar_t bot_challenge; extern bot_goal_t ctf_redflag; extern bot_goal_t ctf_blueflag; #ifdef MISSIONPACK extern bot_goal_t ctf_neutralflag; extern bot_goal_t redobelisk; extern bot_goal_t blueobelisk; extern bot_goal_t neutralobelisk; #endif ================================================ FILE: code/game/ai_main.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_main.c * * desc: Quake3 bot AI * * $Archive: /MissionPack/code/game/ai_main.c $ * *****************************************************************************/ #include "g_local.h" #include "q_shared.h" #include "botlib.h" //bot lib interface #include "be_aas.h" #include "be_ea.h" #include "be_ai_char.h" #include "be_ai_chat.h" #include "be_ai_gen.h" #include "be_ai_goal.h" #include "be_ai_move.h" #include "be_ai_weap.h" // #include "ai_main.h" #include "ai_dmq3.h" #include "ai_chat.h" #include "ai_cmd.h" #include "ai_dmnet.h" #include "ai_vcmd.h" // #include "chars.h" #include "inv.h" #include "syn.h" #define MAX_PATH 144 //bot states bot_state_t *botstates[MAX_CLIENTS]; //number of bots int numbots; //floating point time float floattime; //time to do a regular update float regularupdate_time; // int bot_interbreed; int bot_interbreedmatchcount; // vmCvar_t bot_thinktime; vmCvar_t bot_memorydump; vmCvar_t bot_saveroutingcache; vmCvar_t bot_pause; vmCvar_t bot_report; vmCvar_t bot_testsolid; vmCvar_t bot_testclusters; vmCvar_t bot_developer; vmCvar_t bot_interbreedchar; vmCvar_t bot_interbreedbots; vmCvar_t bot_interbreedcycle; vmCvar_t bot_interbreedwrite; void ExitLevel( void ); /* ================== BotAI_Print ================== */ void QDECL BotAI_Print(int type, char *fmt, ...) { char str[2048]; va_list ap; va_start(ap, fmt); vsprintf(str, fmt, ap); va_end(ap); switch(type) { case PRT_MESSAGE: { G_Printf("%s", str); break; } case PRT_WARNING: { G_Printf( S_COLOR_YELLOW "Warning: %s", str ); break; } case PRT_ERROR: { G_Printf( S_COLOR_RED "Error: %s", str ); break; } case PRT_FATAL: { G_Printf( S_COLOR_RED "Fatal: %s", str ); break; } case PRT_EXIT: { G_Error( S_COLOR_RED "Exit: %s", str ); break; } default: { G_Printf( "unknown print type\n" ); break; } } } /* ================== BotAI_Trace ================== */ void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { trace_t trace; trap_Trace(&trace, start, mins, maxs, end, passent, contentmask); //copy the trace information bsptrace->allsolid = trace.allsolid; bsptrace->startsolid = trace.startsolid; bsptrace->fraction = trace.fraction; VectorCopy(trace.endpos, bsptrace->endpos); bsptrace->plane.dist = trace.plane.dist; VectorCopy(trace.plane.normal, bsptrace->plane.normal); bsptrace->plane.signbits = trace.plane.signbits; bsptrace->plane.type = trace.plane.type; bsptrace->surface.value = trace.surfaceFlags; bsptrace->ent = trace.entityNum; bsptrace->exp_dist = 0; bsptrace->sidenum = 0; bsptrace->contents = 0; } /* ================== BotAI_GetClientState ================== */ int BotAI_GetClientState( int clientNum, playerState_t *state ) { gentity_t *ent; ent = &g_entities[clientNum]; if ( !ent->inuse ) { return qfalse; } if ( !ent->client ) { return qfalse; } memcpy( state, &ent->client->ps, sizeof(playerState_t) ); return qtrue; } /* ================== BotAI_GetEntityState ================== */ int BotAI_GetEntityState( int entityNum, entityState_t *state ) { gentity_t *ent; ent = &g_entities[entityNum]; memset( state, 0, sizeof(entityState_t) ); if (!ent->inuse) return qfalse; if (!ent->r.linked) return qfalse; if (ent->r.svFlags & SVF_NOCLIENT) return qfalse; memcpy( state, &ent->s, sizeof(entityState_t) ); return qtrue; } /* ================== BotAI_GetSnapshotEntity ================== */ int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) { int entNum; entNum = trap_BotGetSnapshotEntity( clientNum, sequence ); if ( entNum == -1 ) { memset(state, 0, sizeof(entityState_t)); return -1; } BotAI_GetEntityState( entNum, state ); return sequence + 1; } /* ================== BotAI_BotInitialChat ================== */ void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) { int i, mcontext; va_list ap; char *p; char *vars[MAX_MATCHVARIABLES]; memset(vars, 0, sizeof(vars)); va_start(ap, type); p = va_arg(ap, char *); for (i = 0; i < MAX_MATCHVARIABLES; i++) { if( !p ) { break; } vars[i] = p; p = va_arg(ap, char *); } va_end(ap); mcontext = BotSynonymContext(bs); trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] ); } /* ================== BotTestAAS ================== */ void BotTestAAS(vec3_t origin) { int areanum; aas_areainfo_t info; trap_Cvar_Update(&bot_testsolid); trap_Cvar_Update(&bot_testclusters); if (bot_testsolid.integer) { if (!trap_AAS_Initialized()) return; areanum = BotPointAreaNum(origin); if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area"); else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area"); } else if (bot_testclusters.integer) { if (!trap_AAS_Initialized()) return; areanum = BotPointAreaNum(origin); if (!areanum) BotAI_Print(PRT_MESSAGE, "\r^1Solid! "); else { trap_AAS_AreaInfo(areanum, &info); BotAI_Print(PRT_MESSAGE, "\rarea %d, cluster %d ", areanum, info.cluster); } } } /* ================== BotReportStatus ================== */ void BotReportStatus(bot_state_t *bs) { char goalname[MAX_MESSAGE_SIZE]; char netname[MAX_MESSAGE_SIZE]; char *leader, flagstatus[32]; // ClientName(bs->client, netname, sizeof(netname)); if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; else leader = " "; strcpy(flagstatus, " "); if (gametype == GT_CTF) { if (BotCTFCarryingFlag(bs)) { if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); else strcpy(flagstatus, S_COLOR_BLUE"F "); } } #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (Bot1FCTFCarryingFlag(bs)) { if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); else strcpy(flagstatus, S_COLOR_BLUE"F "); } } else if (gametype == GT_HARVESTER) { if (BotHarvesterCarryingCubes(bs)) { if (BotTeam(bs) == TEAM_RED) Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_RED"%2d", bs->inventory[INVENTORY_REDCUBE]); else Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_BLUE"%2d", bs->inventory[INVENTORY_BLUECUBE]); } } #endif switch(bs->ltgtype) { case LTG_TEAMHELP: { EasyClientName(bs->teammate, goalname, sizeof(goalname)); BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname); break; } case LTG_TEAMACCOMPANY: { EasyClientName(bs->teammate, goalname, sizeof(goalname)); BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname); break; } case LTG_DEFENDKEYAREA: { trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname); break; } case LTG_GETITEM: { trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname); break; } case LTG_KILL: { ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname); break; } case LTG_CAMP: case LTG_CAMPORDER: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus); break; } case LTG_PATROL: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus); break; } case LTG_GETFLAG: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus); break; } case LTG_RUSHBASE: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus); break; } case LTG_RETURNFLAG: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus); break; } case LTG_ATTACKENEMYBASE: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: attacking the enemy base\n", netname, leader, flagstatus); break; } case LTG_HARVEST: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: harvesting\n", netname, leader, flagstatus); break; } default: { BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus); break; } } } /* ================== BotTeamplayReport ================== */ void BotTeamplayReport(void) { int i; char buf[MAX_INFO_STRING]; BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) { BotReportStatus(botstates[i]); } } BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) { BotReportStatus(botstates[i]); } } } /* ================== BotSetInfoConfigString ================== */ void BotSetInfoConfigString(bot_state_t *bs) { char goalname[MAX_MESSAGE_SIZE]; char netname[MAX_MESSAGE_SIZE]; char action[MAX_MESSAGE_SIZE]; char *leader, carrying[32], *cs; bot_goal_t goal; // ClientName(bs->client, netname, sizeof(netname)); if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; else leader = " "; strcpy(carrying, " "); if (gametype == GT_CTF) { if (BotCTFCarryingFlag(bs)) { strcpy(carrying, "F "); } } #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (Bot1FCTFCarryingFlag(bs)) { strcpy(carrying, "F "); } } else if (gametype == GT_HARVESTER) { if (BotHarvesterCarryingCubes(bs)) { if (BotTeam(bs) == TEAM_RED) Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_REDCUBE]); else Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_BLUECUBE]); } } #endif switch(bs->ltgtype) { case LTG_TEAMHELP: { EasyClientName(bs->teammate, goalname, sizeof(goalname)); Com_sprintf(action, sizeof(action), "helping %s", goalname); break; } case LTG_TEAMACCOMPANY: { EasyClientName(bs->teammate, goalname, sizeof(goalname)); Com_sprintf(action, sizeof(action), "accompanying %s", goalname); break; } case LTG_DEFENDKEYAREA: { trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); Com_sprintf(action, sizeof(action), "defending %s", goalname); break; } case LTG_GETITEM: { trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); Com_sprintf(action, sizeof(action), "getting item %s", goalname); break; } case LTG_KILL: { ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); Com_sprintf(action, sizeof(action), "killing %s", goalname); break; } case LTG_CAMP: case LTG_CAMPORDER: { Com_sprintf(action, sizeof(action), "camping"); break; } case LTG_PATROL: { Com_sprintf(action, sizeof(action), "patrolling"); break; } case LTG_GETFLAG: { Com_sprintf(action, sizeof(action), "capturing flag"); break; } case LTG_RUSHBASE: { Com_sprintf(action, sizeof(action), "rushing base"); break; } case LTG_RETURNFLAG: { Com_sprintf(action, sizeof(action), "returning flag"); break; } case LTG_ATTACKENEMYBASE: { Com_sprintf(action, sizeof(action), "attacking the enemy base"); break; } case LTG_HARVEST: { Com_sprintf(action, sizeof(action), "harvesting"); break; } default: { trap_BotGetTopGoal(bs->gs, &goal); trap_BotGoalName(goal.number, goalname, sizeof(goalname)); Com_sprintf(action, sizeof(action), "roaming %s", goalname); break; } } cs = va("l\\%s\\c\\%s\\a\\%s", leader, carrying, action); trap_SetConfigstring (CS_BOTINFO + bs->client, cs); } /* ============== BotUpdateInfoConfigStrings ============== */ void BotUpdateInfoConfigStrings(void) { int i; char buf[MAX_INFO_STRING]; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { // if ( !botstates[i] || !botstates[i]->inuse ) continue; // trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; BotSetInfoConfigString(botstates[i]); } } /* ============== BotInterbreedBots ============== */ void BotInterbreedBots(void) { float ranks[MAX_CLIENTS]; int parent1, parent2, child; int i; // get rankings for all the bots for (i = 0; i < MAX_CLIENTS; i++) { if ( botstates[i] && botstates[i]->inuse ) { ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; } else { ranks[i] = -1; } } if (trap_GeneticParentsAndChildSelection(MAX_CLIENTS, ranks, &parent1, &parent2, &child)) { trap_BotInterbreedGoalFuzzyLogic(botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs); trap_BotMutateGoalFuzzyLogic(botstates[child]->gs, 1); } // reset the kills and deaths for (i = 0; i < MAX_CLIENTS; i++) { if (botstates[i] && botstates[i]->inuse) { botstates[i]->num_kills = 0; botstates[i]->num_deaths = 0; } } } /* ============== BotWriteInterbreeded ============== */ void BotWriteInterbreeded(char *filename) { float rank, bestrank; int i, bestbot; bestrank = 0; bestbot = -1; // get the best bot for (i = 0; i < MAX_CLIENTS; i++) { if ( botstates[i] && botstates[i]->inuse ) { rank = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; } else { rank = -1; } if (rank > bestrank) { bestrank = rank; bestbot = i; } } if (bestbot >= 0) { //write out the new goal fuzzy logic trap_BotSaveGoalFuzzyLogic(botstates[bestbot]->gs, filename); } } /* ============== BotInterbreedEndMatch add link back into ExitLevel? ============== */ void BotInterbreedEndMatch(void) { if (!bot_interbreed) return; bot_interbreedmatchcount++; if (bot_interbreedmatchcount >= bot_interbreedcycle.integer) { bot_interbreedmatchcount = 0; // trap_Cvar_Update(&bot_interbreedwrite); if (strlen(bot_interbreedwrite.string)) { BotWriteInterbreeded(bot_interbreedwrite.string); trap_Cvar_Set("bot_interbreedwrite", ""); } BotInterbreedBots(); } } /* ============== BotInterbreeding ============== */ void BotInterbreeding(void) { int i; trap_Cvar_Update(&bot_interbreedchar); if (!strlen(bot_interbreedchar.string)) return; //make sure we are in tournament mode if (gametype != GT_TOURNAMENT) { trap_Cvar_Set("g_gametype", va("%d", GT_TOURNAMENT)); ExitLevel(); return; } //shutdown all the bots for (i = 0; i < MAX_CLIENTS; i++) { if (botstates[i] && botstates[i]->inuse) { BotAIShutdownClient(botstates[i]->client, qfalse); } } //make sure all item weight configs are reloaded and Not shared trap_BotLibVarSet("bot_reloadcharacters", "1"); //add a number of bots using the desired bot character for (i = 0; i < bot_interbreedbots.integer; i++) { trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s 4 free %i %s%d\n", bot_interbreedchar.string, i * 50, bot_interbreedchar.string, i) ); } // trap_Cvar_Set("bot_interbreedchar", ""); bot_interbreed = qtrue; } /* ============== BotEntityInfo ============== */ void BotEntityInfo(int entnum, aas_entityinfo_t *info) { trap_AAS_EntityInfo(entnum, info); } /* ============== NumBots ============== */ int NumBots(void) { return numbots; } /* ============== BotTeamLeader ============== */ int BotTeamLeader(bot_state_t *bs) { int leader; leader = ClientFromName(bs->teamleader); if (leader < 0) return qfalse; if (!botstates[leader] || !botstates[leader]->inuse) return qfalse; return qtrue; } /* ============== AngleDifference ============== */ float AngleDifference(float ang1, float ang2) { float diff; diff = ang1 - ang2; if (ang1 > ang2) { if (diff > 180.0) diff -= 360.0; } else { if (diff < -180.0) diff += 360.0; } return diff; } /* ============== BotChangeViewAngle ============== */ float BotChangeViewAngle(float angle, float ideal_angle, float speed) { float move; angle = AngleMod(angle); ideal_angle = AngleMod(ideal_angle); if (angle == ideal_angle) return angle; move = ideal_angle - angle; if (ideal_angle > angle) { if (move > 180.0) move -= 360.0; } else { if (move < -180.0) move += 360.0; } if (move > 0) { if (move > speed) move = speed; } else { if (move < -speed) move = -speed; } return AngleMod(angle + move); } /* ============== BotChangeViewAngles ============== */ void BotChangeViewAngles(bot_state_t *bs, float thinktime) { float diff, factor, maxchange, anglespeed, disired_speed; int i; if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; // if (bs->enemy >= 0) { factor = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_FACTOR, 0.01f, 1); maxchange = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_MAXCHANGE, 1, 1800); } else { factor = 0.05f; maxchange = 360; } if (maxchange < 240) maxchange = 240; maxchange *= thinktime; for (i = 0; i < 2; i++) { // if (bot_challenge.integer) { //smooth slowdown view model diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i])); anglespeed = diff * factor; if (anglespeed > maxchange) anglespeed = maxchange; bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i], bs->ideal_viewangles[i], anglespeed); } else { //over reaction view model bs->viewangles[i] = AngleMod(bs->viewangles[i]); bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]); diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]); disired_speed = diff * factor; bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed); if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange; if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange; anglespeed = bs->viewanglespeed[i]; if (anglespeed > maxchange) anglespeed = maxchange; if (anglespeed < -maxchange) anglespeed = -maxchange; bs->viewangles[i] += anglespeed; bs->viewangles[i] = AngleMod(bs->viewangles[i]); //demping bs->viewanglespeed[i] *= 0.45 * (1 - factor); } //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);` //bs->viewangles[i] = bs->ideal_viewangles[i]; } //bs->viewangles[PITCH] = 0; if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360; //elementary action: view trap_EA_View(bs->client, bs->viewangles); } /* ============== BotInputToUserCommand ============== */ void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time) { vec3_t angles, forward, right; short temp; int j; //clear the whole structure memset(ucmd, 0, sizeof(usercmd_t)); // //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); //the duration for the user command in milli seconds ucmd->serverTime = time; // if (bi->actionflags & ACTION_DELAYEDJUMP) { bi->actionflags |= ACTION_JUMP; bi->actionflags &= ~ACTION_DELAYEDJUMP; } //set the buttons if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK; if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK; if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK; if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE; if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE; if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING; if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE; if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE; if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG; if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE; if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL; if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME; // ucmd->weapon = bi->weapon; //set the view angles //NOTE: the ucmd->angles are the angles WITHOUT the delta angles ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]); ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]); ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]); //subtract the delta angles for (j = 0; j < 3; j++) { temp = ucmd->angles[j] - delta_angles[j]; /*NOTE: disabled because temp should be mod first if ( j == PITCH ) { // don't let the player look up or down more than 90 degrees if ( temp > 16000 ) temp = 16000; else if ( temp < -16000 ) temp = -16000; } */ ucmd->angles[j] = temp; } //NOTE: movement is relative to the REAL view angles //get the horizontal forward and right vector //get the pitch in the range [-180, 180] if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH]; else angles[PITCH] = 0; angles[YAW] = bi->viewangles[YAW]; angles[ROLL] = 0; AngleVectors(angles, forward, right, NULL); //bot input speed is in the range [0, 400] bi->speed = bi->speed * 127 / 400; //set the view independent movement ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed; ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed; ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed; //normal keyboard movement if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127; if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127; if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127; if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127; //jump/moveup if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127; //crouch/movedown if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127; // //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime); } /* ============== BotUpdateInput ============== */ void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) { bot_input_t bi; int j; //add the delta angles to the bot's current view angles for (j = 0; j < 3; j++) { bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); } //change the bot view angles BotChangeViewAngles(bs, (float) elapsed_time / 1000); //retrieve the bot input trap_EA_GetInput(bs->client, (float) time / 1000, &bi); //respawn hack if (bi.actionflags & ACTION_RESPAWN) { if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK); } //convert the bot input to a usercmd BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time); //subtract the delta angles for (j = 0; j < 3; j++) { bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); } } /* ============== BotAIRegularUpdate ============== */ void BotAIRegularUpdate(void) { if (regularupdate_time < FloatTime()) { trap_BotUpdateEntityItems(); regularupdate_time = FloatTime() + 0.3; } } /* ============== RemoveColorEscapeSequences ============== */ void RemoveColorEscapeSequences( char *text ) { int i, l; l = 0; for ( i = 0; text[i]; i++ ) { if (Q_IsColorString(&text[i])) { i++; continue; } if (text[i] > 0x7E) continue; text[l++] = text[i]; } text[l] = '\0'; } /* ============== BotAI ============== */ int BotAI(int client, float thinktime) { bot_state_t *bs; char buf[1024], *args; int j; trap_EA_ResetInput(client); // bs = botstates[client]; if (!bs || !bs->inuse) { BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client); return qfalse; } //retrieve the current client state BotAI_GetClientState( client, &bs->cur_ps ); //retrieve any waiting server commands while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) { //have buf point to the command and args to the command arguments args = strchr( buf, ' '); if (!args) continue; *args++ = '\0'; //remove color espace sequences from the arguments RemoveColorEscapeSequences( args ); if (!Q_stricmp(buf, "cp ")) { /*CenterPrintf*/ } else if (!Q_stricmp(buf, "cs")) { /*ConfigStringModified*/ } else if (!Q_stricmp(buf, "print")) { //remove first and last quote from the chat message memmove(args, args+1, strlen(args)); args[strlen(args)-1] = '\0'; trap_BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args); } else if (!Q_stricmp(buf, "chat")) { //remove first and last quote from the chat message memmove(args, args+1, strlen(args)); args[strlen(args)-1] = '\0'; trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); } else if (!Q_stricmp(buf, "tchat")) { //remove first and last quote from the chat message memmove(args, args+1, strlen(args)); args[strlen(args)-1] = '\0'; trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); } #ifdef MISSIONPACK else if (!Q_stricmp(buf, "vchat")) { BotVoiceChatCommand(bs, SAY_ALL, args); } else if (!Q_stricmp(buf, "vtchat")) { BotVoiceChatCommand(bs, SAY_TEAM, args); } else if (!Q_stricmp(buf, "vtell")) { BotVoiceChatCommand(bs, SAY_TELL, args); } #endif else if (!Q_stricmp(buf, "scores")) { /*FIXME: parse scores?*/ } else if (!Q_stricmp(buf, "clientLevelShot")) { /*ignore*/ } } //add the delta angles to the bot's current view angles for (j = 0; j < 3; j++) { bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); } //increase the local time of the bot bs->ltime += thinktime; // bs->thinktime = thinktime; //origin of the bot VectorCopy(bs->cur_ps.origin, bs->origin); //eye coordinates of the bot VectorCopy(bs->cur_ps.origin, bs->eye); bs->eye[2] += bs->cur_ps.viewheight; //get the area the bot is in bs->areanum = BotPointAreaNum(bs->origin); //the real AI BotDeathmatchAI(bs, thinktime); //set the weapon selection every AI frame trap_EA_SelectWeapon(bs->client, bs->weaponnum); //subtract the delta angles for (j = 0; j < 3; j++) { bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); } //everything was ok return qtrue; } /* ================== BotScheduleBotThink ================== */ void BotScheduleBotThink(void) { int i, botnum; botnum = 0; for( i = 0; i < MAX_CLIENTS; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } //initialize the bot think residual time botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots; botnum++; } } /* ============== BotWriteSessionData ============== */ void BotWriteSessionData(bot_state_t *bs) { const char *s; const char *var; s = va( "%i %i %i %i %i %i %i %i" " %f %f %f" " %f %f %f" " %f %f %f", bs->lastgoal_decisionmaker, bs->lastgoal_ltgtype, bs->lastgoal_teammate, bs->lastgoal_teamgoal.areanum, bs->lastgoal_teamgoal.entitynum, bs->lastgoal_teamgoal.flags, bs->lastgoal_teamgoal.iteminfo, bs->lastgoal_teamgoal.number, bs->lastgoal_teamgoal.origin[0], bs->lastgoal_teamgoal.origin[1], bs->lastgoal_teamgoal.origin[2], bs->lastgoal_teamgoal.mins[0], bs->lastgoal_teamgoal.mins[1], bs->lastgoal_teamgoal.mins[2], bs->lastgoal_teamgoal.maxs[0], bs->lastgoal_teamgoal.maxs[1], bs->lastgoal_teamgoal.maxs[2] ); var = va( "botsession%i", bs->client ); trap_Cvar_Set( var, s ); } /* ============== BotReadSessionData ============== */ void BotReadSessionData(bot_state_t *bs) { char s[MAX_STRING_CHARS]; const char *var; var = va( "botsession%i", bs->client ); trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); sscanf(s, "%i %i %i %i %i %i %i %i" " %f %f %f" " %f %f %f" " %f %f %f", &bs->lastgoal_decisionmaker, &bs->lastgoal_ltgtype, &bs->lastgoal_teammate, &bs->lastgoal_teamgoal.areanum, &bs->lastgoal_teamgoal.entitynum, &bs->lastgoal_teamgoal.flags, &bs->lastgoal_teamgoal.iteminfo, &bs->lastgoal_teamgoal.number, &bs->lastgoal_teamgoal.origin[0], &bs->lastgoal_teamgoal.origin[1], &bs->lastgoal_teamgoal.origin[2], &bs->lastgoal_teamgoal.mins[0], &bs->lastgoal_teamgoal.mins[1], &bs->lastgoal_teamgoal.mins[2], &bs->lastgoal_teamgoal.maxs[0], &bs->lastgoal_teamgoal.maxs[1], &bs->lastgoal_teamgoal.maxs[2] ); } /* ============== BotAISetupClient ============== */ int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; bot_state_t *bs; int errnum; if (!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t)); bs = botstates[client]; if (bs && bs->inuse) { BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); return qfalse; } if (!trap_AAS_Initialized()) { BotAI_Print(PRT_FATAL, "AAS not initialized\n"); return qfalse; } //load the bot character bs->character = trap_BotLoadCharacter(settings->characterfile, settings->skill); if (!bs->character) { BotAI_Print(PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile); return qfalse; } //copy the settings memcpy(&bs->settings, settings, sizeof(bot_settings_t)); //allocate a goal state bs->gs = trap_BotAllocGoalState(client); //load the item weights trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH); errnum = trap_BotLoadItemWeights(bs->gs, filename); if (errnum != BLERR_NOERROR) { trap_BotFreeGoalState(bs->gs); return qfalse; } //allocate a weapon state bs->ws = trap_BotAllocWeaponState(); //load the weapon weights trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH); errnum = trap_BotLoadWeaponWeights(bs->ws, filename); if (errnum != BLERR_NOERROR) { trap_BotFreeGoalState(bs->gs); trap_BotFreeWeaponState(bs->ws); return qfalse; } //allocate a chat state bs->cs = trap_BotAllocChatState(); //load the chat file trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH); trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH); errnum = trap_BotLoadChatFile(bs->cs, filename, name); if (errnum != BLERR_NOERROR) { trap_BotFreeChatState(bs->cs); trap_BotFreeGoalState(bs->gs); trap_BotFreeWeaponState(bs->ws); return qfalse; } //get the gender characteristic trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH); //set the chat gender if (*gender == 'f' || *gender == 'F') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); else if (*gender == 'm' || *gender == 'M') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); bs->inuse = qtrue; bs->client = client; bs->entitynum = client; bs->setupcount = 4; bs->entergame_time = FloatTime(); bs->ms = trap_BotAllocMoveState(); bs->walker = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WALKER, 0, 1); numbots++; if (trap_Cvar_VariableIntegerValue("bot_testichat")) { trap_BotLibVarSet("bot_testichat", "1"); BotChatTest(bs); } //NOTE: reschedule the bot thinking BotScheduleBotThink(); //if interbreeding start with a mutation if (bot_interbreed) { trap_BotMutateGoalFuzzyLogic(bs->gs, 1); } // if we kept the bot client if (restart) { BotReadSessionData(bs); } //bot has been setup succesfully return qtrue; } /* ============== BotAIShutdownClient ============== */ int BotAIShutdownClient(int client, qboolean restart) { bot_state_t *bs; bs = botstates[client]; if (!bs || !bs->inuse) { //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client); return qfalse; } if (restart) { BotWriteSessionData(bs); } if (BotChat_ExitGame(bs)) { trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); } trap_BotFreeMoveState(bs->ms); //free the goal state` trap_BotFreeGoalState(bs->gs); //free the chat file trap_BotFreeChatState(bs->cs); //free the weapon weights trap_BotFreeWeaponState(bs->ws); //free the bot character trap_BotFreeCharacter(bs->character); // BotFreeWaypoints(bs->checkpoints); BotFreeWaypoints(bs->patrolpoints); //clear activate goal stack BotClearActivateGoalStack(bs); //clear the bot state memset(bs, 0, sizeof(bot_state_t)); //set the inuse flag to qfalse bs->inuse = qfalse; //there's one bot less numbots--; //everything went ok return qtrue; } /* ============== BotResetState called when a bot enters the intermission or observer mode and when the level is changed ============== */ void BotResetState(bot_state_t *bs) { int client, entitynum, inuse; int movestate, goalstate, chatstate, weaponstate; bot_settings_t settings; int character; playerState_t ps; //current player state float entergame_time; //save some things that should not be reset here memcpy(&settings, &bs->settings, sizeof(bot_settings_t)); memcpy(&ps, &bs->cur_ps, sizeof(playerState_t)); inuse = bs->inuse; client = bs->client; entitynum = bs->entitynum; character = bs->character; movestate = bs->ms; goalstate = bs->gs; chatstate = bs->cs; weaponstate = bs->ws; entergame_time = bs->entergame_time; //free checkpoints and patrol points BotFreeWaypoints(bs->checkpoints); BotFreeWaypoints(bs->patrolpoints); //reset the whole state memset(bs, 0, sizeof(bot_state_t)); //copy back some state stuff that should not be reset bs->ms = movestate; bs->gs = goalstate; bs->cs = chatstate; bs->ws = weaponstate; memcpy(&bs->cur_ps, &ps, sizeof(playerState_t)); memcpy(&bs->settings, &settings, sizeof(bot_settings_t)); bs->inuse = inuse; bs->client = client; bs->entitynum = entitynum; bs->character = character; bs->entergame_time = entergame_time; //reset several states if (bs->ms) trap_BotResetMoveState(bs->ms); if (bs->gs) trap_BotResetGoalState(bs->gs); if (bs->ws) trap_BotResetWeaponState(bs->ws); if (bs->gs) trap_BotResetAvoidGoals(bs->gs); if (bs->ms) trap_BotResetAvoidReach(bs->ms); } /* ============== BotAILoadMap ============== */ int BotAILoadMap( int restart ) { int i; vmCvar_t mapname; if (!restart) { trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); trap_BotLibLoadMap( mapname.string ); } for (i = 0; i < MAX_CLIENTS; i++) { if (botstates[i] && botstates[i]->inuse) { BotResetState( botstates[i] ); botstates[i]->setupcount = 4; } } BotSetupDeathmatchAI(); return qtrue; } #ifdef MISSIONPACK void ProximityMine_Trigger( gentity_t *trigger, gentity_t *other, trace_t *trace ); #endif /* ================== BotAIStartFrame ================== */ int BotAIStartFrame(int time) { int i; gentity_t *ent; bot_entitystate_t state; int elapsed_time, thinktime; static int local_time; static int botlib_residual; static int lastbotthink_time; G_CheckBotSpawn(); trap_Cvar_Update(&bot_rocketjump); trap_Cvar_Update(&bot_grapple); trap_Cvar_Update(&bot_fastchat); trap_Cvar_Update(&bot_nochat); trap_Cvar_Update(&bot_testrchat); trap_Cvar_Update(&bot_thinktime); trap_Cvar_Update(&bot_memorydump); trap_Cvar_Update(&bot_saveroutingcache); trap_Cvar_Update(&bot_pause); trap_Cvar_Update(&bot_report); if (bot_report.integer) { // BotTeamplayReport(); // trap_Cvar_Set("bot_report", "0"); BotUpdateInfoConfigStrings(); } if (bot_pause.integer) { // execute bot user commands every frame for( i = 0; i < MAX_CLIENTS; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } if( g_entities[i].client->pers.connected != CON_CONNECTED ) { continue; } botstates[i]->lastucmd.forwardmove = 0; botstates[i]->lastucmd.rightmove = 0; botstates[i]->lastucmd.upmove = 0; botstates[i]->lastucmd.buttons = 0; botstates[i]->lastucmd.serverTime = time; trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); } return qtrue; } if (bot_memorydump.integer) { trap_BotLibVarSet("memorydump", "1"); trap_Cvar_Set("bot_memorydump", "0"); } if (bot_saveroutingcache.integer) { trap_BotLibVarSet("saveroutingcache", "1"); trap_Cvar_Set("bot_saveroutingcache", "0"); } //check if bot interbreeding is activated BotInterbreeding(); //cap the bot think time if (bot_thinktime.integer > 200) { trap_Cvar_Set("bot_thinktime", "200"); } //if the bot think time changed we should reschedule the bots if (bot_thinktime.integer != lastbotthink_time) { lastbotthink_time = bot_thinktime.integer; BotScheduleBotThink(); } elapsed_time = time - local_time; local_time = time; botlib_residual += elapsed_time; if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time; else thinktime = bot_thinktime.integer; // update the bot library if ( botlib_residual >= thinktime ) { botlib_residual -= thinktime; trap_BotLibStartFrame((float) time / 1000); if (!trap_AAS_Initialized()) return qfalse; //update entities in the botlib for (i = 0; i < MAX_GENTITIES; i++) { ent = &g_entities[i]; if (!ent->inuse) { trap_BotLibUpdateEntity(i, NULL); continue; } if (!ent->r.linked) { trap_BotLibUpdateEntity(i, NULL); continue; } if (ent->r.svFlags & SVF_NOCLIENT) { trap_BotLibUpdateEntity(i, NULL); continue; } // do not update missiles if (ent->s.eType == ET_MISSILE && ent->s.weapon != WP_GRAPPLING_HOOK) { trap_BotLibUpdateEntity(i, NULL); continue; } // do not update event only entities if (ent->s.eType > ET_EVENTS) { trap_BotLibUpdateEntity(i, NULL); continue; } #ifdef MISSIONPACK // never link prox mine triggers if (ent->r.contents == CONTENTS_TRIGGER) { if (ent->touch == ProximityMine_Trigger) { trap_BotLibUpdateEntity(i, NULL); continue; } } #endif // memset(&state, 0, sizeof(bot_entitystate_t)); // VectorCopy(ent->r.currentOrigin, state.origin); if (i < MAX_CLIENTS) { VectorCopy(ent->s.apos.trBase, state.angles); } else { VectorCopy(ent->r.currentAngles, state.angles); } VectorCopy(ent->s.origin2, state.old_origin); VectorCopy(ent->r.mins, state.mins); VectorCopy(ent->r.maxs, state.maxs); state.type = ent->s.eType; state.flags = ent->s.eFlags; if (ent->r.bmodel) state.solid = SOLID_BSP; else state.solid = SOLID_BBOX; state.groundent = ent->s.groundEntityNum; state.modelindex = ent->s.modelindex; state.modelindex2 = ent->s.modelindex2; state.frame = ent->s.frame; state.event = ent->s.event; state.eventParm = ent->s.eventParm; state.powerups = ent->s.powerups; state.legsAnim = ent->s.legsAnim; state.torsoAnim = ent->s.torsoAnim; state.weapon = ent->s.weapon; // trap_BotLibUpdateEntity(i, &state); } BotAIRegularUpdate(); } floattime = trap_AAS_Time(); // execute scheduled bot AI for( i = 0; i < MAX_CLIENTS; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } // botstates[i]->botthink_residual += elapsed_time; // if ( botstates[i]->botthink_residual >= thinktime ) { botstates[i]->botthink_residual -= thinktime; if (!trap_AAS_Initialized()) return qfalse; if (g_entities[i].client->pers.connected == CON_CONNECTED) { BotAI(i, (float) thinktime / 1000); } } } // execute bot user commands every frame for( i = 0; i < MAX_CLIENTS; i++ ) { if( !botstates[i] || !botstates[i]->inuse ) { continue; } if( g_entities[i].client->pers.connected != CON_CONNECTED ) { continue; } BotUpdateInput(botstates[i], time, elapsed_time); trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); } return qtrue; } /* ============== BotInitLibrary ============== */ int BotInitLibrary(void) { char buf[144]; //set the maxclients and maxentities library variables before calling BotSetupLibrary trap_Cvar_VariableStringBuffer("sv_maxclients", buf, sizeof(buf)); if (!strlen(buf)) strcpy(buf, "8"); trap_BotLibVarSet("maxclients", buf); Com_sprintf(buf, sizeof(buf), "%d", MAX_GENTITIES); trap_BotLibVarSet("maxentities", buf); //bsp checksum trap_Cvar_VariableStringBuffer("sv_mapChecksum", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("sv_mapChecksum", buf); //maximum number of aas links trap_Cvar_VariableStringBuffer("max_aaslinks", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("max_aaslinks", buf); //maximum number of items in a level trap_Cvar_VariableStringBuffer("max_levelitems", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("max_levelitems", buf); //game type trap_Cvar_VariableStringBuffer("g_gametype", buf, sizeof(buf)); if (!strlen(buf)) strcpy(buf, "0"); trap_BotLibVarSet("g_gametype", buf); //bot developer mode and log file trap_BotLibVarSet("bot_developer", bot_developer.string); trap_BotLibVarSet("log", buf); //no chatting trap_Cvar_VariableStringBuffer("bot_nochat", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("nochat", "0"); //visualize jump pads trap_Cvar_VariableStringBuffer("bot_visualizejumppads", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("bot_visualizejumppads", buf); //forced clustering calculations trap_Cvar_VariableStringBuffer("bot_forceclustering", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("forceclustering", buf); //forced reachability calculations trap_Cvar_VariableStringBuffer("bot_forcereachability", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("forcereachability", buf); //force writing of AAS to file trap_Cvar_VariableStringBuffer("bot_forcewrite", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("forcewrite", buf); //no AAS optimization trap_Cvar_VariableStringBuffer("bot_aasoptimize", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("aasoptimize", buf); // trap_Cvar_VariableStringBuffer("bot_saveroutingcache", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("saveroutingcache", buf); //reload instead of cache bot character files trap_Cvar_VariableStringBuffer("bot_reloadcharacters", buf, sizeof(buf)); if (!strlen(buf)) strcpy(buf, "0"); trap_BotLibVarSet("bot_reloadcharacters", buf); //base directory trap_Cvar_VariableStringBuffer("fs_basepath", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("basedir", buf); //game directory trap_Cvar_VariableStringBuffer("fs_game", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("gamedir", buf); //cd directory trap_Cvar_VariableStringBuffer("fs_cdpath", buf, sizeof(buf)); if (strlen(buf)) trap_BotLibVarSet("cddir", buf); // #ifdef MISSIONPACK trap_BotLibDefine("MISSIONPACK"); #endif //setup the bot library return trap_BotLibSetup(); } /* ============== BotAISetup ============== */ int BotAISetup( int restart ) { int errnum; trap_Cvar_Register(&bot_thinktime, "bot_thinktime", "100", CVAR_CHEAT); trap_Cvar_Register(&bot_memorydump, "bot_memorydump", "0", CVAR_CHEAT); trap_Cvar_Register(&bot_saveroutingcache, "bot_saveroutingcache", "0", CVAR_CHEAT); trap_Cvar_Register(&bot_pause, "bot_pause", "0", CVAR_CHEAT); trap_Cvar_Register(&bot_report, "bot_report", "0", CVAR_CHEAT); trap_Cvar_Register(&bot_testsolid, "bot_testsolid", "0", CVAR_CHEAT); trap_Cvar_Register(&bot_testclusters, "bot_testclusters", "0", CVAR_CHEAT); trap_Cvar_Register(&bot_developer, "bot_developer", "0", CVAR_CHEAT); trap_Cvar_Register(&bot_interbreedchar, "bot_interbreedchar", "", 0); trap_Cvar_Register(&bot_interbreedbots, "bot_interbreedbots", "10", 0); trap_Cvar_Register(&bot_interbreedcycle, "bot_interbreedcycle", "20", 0); trap_Cvar_Register(&bot_interbreedwrite, "bot_interbreedwrite", "", 0); //if the game is restarted for a tournament if (restart) { return qtrue; } //initialize the bot states memset( botstates, 0, sizeof(botstates) ); errnum = BotInitLibrary(); if (errnum != BLERR_NOERROR) return qfalse; return qtrue; } /* ============== BotAIShutdown ============== */ int BotAIShutdown( int restart ) { int i; //if the game is restarted for a tournament if ( restart ) { //shutdown all the bots in the botlib for (i = 0; i < MAX_CLIENTS; i++) { if (botstates[i] && botstates[i]->inuse) { BotAIShutdownClient(botstates[i]->client, restart); } } //don't shutdown the bot library } else { trap_BotLibShutdown(); } return qtrue; } ================================================ FILE: code/game/ai_main.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_main.h * * desc: Quake3 bot AI * * $Archive: /source/code/botai/ai_chat.c $ * *****************************************************************************/ //#define DEBUG #define CTF #define MAX_ITEMS 256 //bot flags #define BFL_STRAFERIGHT 1 //strafe to the right #define BFL_ATTACKED 2 //bot has attacked last ai frame #define BFL_ATTACKJUMPED 4 //bot jumped during attack last frame #define BFL_AIMATENEMY 8 //bot aimed at the enemy this frame #define BFL_AVOIDRIGHT 16 //avoid obstacles by going to the right #define BFL_IDEALVIEWSET 32 //bot has ideal view angles set #define BFL_FIGHTSUICIDAL 64 //bot is in a suicidal fight //long term goal types #define LTG_TEAMHELP 1 //help a team mate #define LTG_TEAMACCOMPANY 2 //accompany a team mate #define LTG_DEFENDKEYAREA 3 //defend a key area #define LTG_GETFLAG 4 //get the enemy flag #define LTG_RUSHBASE 5 //rush to the base #define LTG_RETURNFLAG 6 //return the flag #define LTG_CAMP 7 //camp somewhere #define LTG_CAMPORDER 8 //ordered to camp somewhere #define LTG_PATROL 9 //patrol #define LTG_GETITEM 10 //get an item #define LTG_KILL 11 //kill someone #define LTG_HARVEST 12 //harvest skulls #define LTG_ATTACKENEMYBASE 13 //attack the enemy base #define LTG_MAKELOVE_UNDER 14 #define LTG_MAKELOVE_ONTOP 15 //some goal dedication times #define TEAM_HELP_TIME 60 //1 minute teamplay help time #define TEAM_ACCOMPANY_TIME 600 //10 minutes teamplay accompany time #define TEAM_DEFENDKEYAREA_TIME 600 //10 minutes ctf defend base time #define TEAM_CAMP_TIME 600 //10 minutes camping time #define TEAM_PATROL_TIME 600 //10 minutes patrolling time #define TEAM_LEAD_TIME 600 //10 minutes taking the lead #define TEAM_GETITEM_TIME 60 //1 minute #define TEAM_KILL_SOMEONE 180 //3 minute to kill someone #define TEAM_ATTACKENEMYBASE_TIME 600 //10 minutes #define TEAM_HARVEST_TIME 120 //2 minutes #define CTF_GETFLAG_TIME 600 //10 minutes ctf get flag time #define CTF_RUSHBASE_TIME 120 //2 minutes ctf rush base time #define CTF_RETURNFLAG_TIME 180 //3 minutes to return the flag #define CTF_ROAM_TIME 60 //1 minute ctf roam time //patrol flags #define PATROL_LOOP 1 #define PATROL_REVERSE 2 #define PATROL_BACK 4 //teamplay task preference #define TEAMTP_DEFENDER 1 #define TEAMTP_ATTACKER 2 //CTF strategy #define CTFS_AGRESSIVE 1 //copied from the aas file header #define PRESENCE_NONE 1 #define PRESENCE_NORMAL 2 #define PRESENCE_CROUCH 4 // #define MAX_PROXMINES 64 //check points typedef struct bot_waypoint_s { int inuse; char name[32]; bot_goal_t goal; struct bot_waypoint_s *next, *prev; } bot_waypoint_t; #define MAX_ACTIVATESTACK 8 #define MAX_ACTIVATEAREAS 32 typedef struct bot_activategoal_s { int inuse; bot_goal_t goal; //goal to activate (buttons etc.) float time; //time to activate something float start_time; //time starting to activate something float justused_time; //time the goal was used int shoot; //true if bot has to shoot to activate int weapon; //weapon to be used for activation vec3_t target; //target to shoot at to activate something vec3_t origin; //origin of the blocking entity to activate int areas[MAX_ACTIVATEAREAS]; //routing areas disabled by blocking entity int numareas; //number of disabled routing areas int areasdisabled; //true if the areas are disabled for the routing struct bot_activategoal_s *next; //next activate goal on stack } bot_activategoal_t; //bot state typedef struct bot_state_s { int inuse; //true if this state is used by a bot client int botthink_residual; //residual for the bot thinks int client; //client number of the bot int entitynum; //entity number of the bot playerState_t cur_ps; //current player state int last_eFlags; //last ps flags usercmd_t lastucmd; //usercmd from last frame int entityeventTime[1024]; //last entity event time // bot_settings_t settings; //several bot settings int (*ainode)(struct bot_state_s *bs); //current AI node float thinktime; //time the bot thinks this frame vec3_t origin; //origin of the bot vec3_t velocity; //velocity of the bot int presencetype; //presence type of the bot vec3_t eye; //eye coordinates of the bot int areanum; //the number of the area the bot is in int inventory[MAX_ITEMS]; //string with items amounts the bot has int tfl; //the travel flags the bot uses int flags; //several flags int respawn_wait; //wait until respawned int lasthealth; //health value previous frame int lastkilledplayer; //last killed player int lastkilledby; //player that last killed this bot int botdeathtype; //the death type of the bot int enemydeathtype; //the death type of the enemy int botsuicide; //true when the bot suicides int enemysuicide; //true when the enemy of the bot suicides int setupcount; //true when the bot has just been setup int map_restart; //true when the map is being restarted int entergamechat; //true when the bot used an enter game chat int num_deaths; //number of time this bot died int num_kills; //number of kills of this bot int revenge_enemy; //the revenge enemy int revenge_kills; //number of kills the enemy made int lastframe_health; //health value the last frame int lasthitcount; //number of hits last frame int chatto; //chat to all or team float walker; //walker charactertic float ltime; //local bot time float entergame_time; //time the bot entered the game float ltg_time; //long term goal time float nbg_time; //nearby goal time float respawn_time; //time the bot takes to respawn float respawnchat_time; //time the bot started a chat during respawn float chase_time; //time the bot will chase the enemy float enemyvisible_time; //time the enemy was last visible float check_time; //time to check for nearby items float stand_time; //time the bot is standing still float lastchat_time; //time the bot last selected a chat float kamikaze_time; //time to check for kamikaze usage float invulnerability_time; //time to check for invulnerability usage float standfindenemy_time; //time to find enemy while standing float attackstrafe_time; //time the bot is strafing in one dir float attackcrouch_time; //time the bot will stop crouching float attackchase_time; //time the bot chases during actual attack float attackjump_time; //time the bot jumped during attack float enemysight_time; //time before reacting to enemy float enemydeath_time; //time the enemy died float enemyposition_time; //time the position and velocity of the enemy were stored float defendaway_time; //time away while defending float defendaway_range; //max travel time away from defend area float rushbaseaway_time; //time away from rushing to the base float attackaway_time; //time away from attacking the enemy base float harvestaway_time; //time away from harvesting float ctfroam_time; //time the bot is roaming in ctf float killedenemy_time; //time the bot killed the enemy float arrive_time; //time arrived (at companion) float lastair_time; //last time the bot had air float teleport_time; //last time the bot teleported float camp_time; //last time camped float camp_range; //camp range float weaponchange_time; //time the bot started changing weapons float firethrottlewait_time; //amount of time to wait float firethrottleshoot_time; //amount of time to shoot float notblocked_time; //last time the bot was not blocked float blockedbyavoidspot_time; //time blocked by an avoid spot float predictobstacles_time; //last time the bot predicted obstacles int predictobstacles_goalareanum; //last goal areanum the bot predicted obstacles for vec3_t aimtarget; vec3_t enemyvelocity; //enemy velocity 0.5 secs ago during battle vec3_t enemyorigin; //enemy origin 0.5 secs ago during battle // int kamikazebody; //kamikaze body int proxmines[MAX_PROXMINES]; int numproxmines; // int character; //the bot character int ms; //move state of the bot int gs; //goal state of the bot int cs; //chat state of the bot int ws; //weapon state of the bot // int enemy; //enemy entity number int lastenemyareanum; //last reachability area the enemy was in vec3_t lastenemyorigin; //last origin of the enemy in the reachability area int weaponnum; //current weapon number vec3_t viewangles; //current view angles vec3_t ideal_viewangles; //ideal view angles vec3_t viewanglespeed; // int ltgtype; //long term goal type // team goals int teammate; //team mate involved in this team goal int decisionmaker; //player who decided to go for this goal int ordered; //true if ordered to do something float order_time; //time ordered to do something int owndecision_time; //time the bot made it's own decision bot_goal_t teamgoal; //the team goal bot_goal_t altroutegoal; //alternative route goal float reachedaltroutegoal_time; //time the bot reached the alt route goal float teammessage_time; //time to message team mates what the bot is doing float teamgoal_time; //time to stop helping team mate float teammatevisible_time; //last time the team mate was NOT visible int teamtaskpreference; //team task preference // last ordered team goal int lastgoal_decisionmaker; int lastgoal_ltgtype; int lastgoal_teammate; bot_goal_t lastgoal_teamgoal; // for leading team mates int lead_teammate; //team mate the bot is leading bot_goal_t lead_teamgoal; //team goal while leading float lead_time; //time leading someone float leadvisible_time; //last time the team mate was visible float leadmessage_time; //last time a messaged was sent to the team mate float leadbackup_time; //time backing up towards team mate // char teamleader[32]; //netname of the team leader float askteamleader_time; //time asked for team leader float becometeamleader_time; //time the bot will become the team leader float teamgiveorders_time; //time to give team orders float lastflagcapture_time; //last time a flag was captured int numteammates; //number of team mates int redflagstatus; //0 = at base, 1 = not at base int blueflagstatus; //0 = at base, 1 = not at base int neutralflagstatus; //0 = at base, 1 = our team has flag, 2 = enemy team has flag, 3 = enemy team dropped the flag int flagstatuschanged; //flag status changed int forceorders; //true if forced to give orders int flagcarrier; //team mate carrying the enemy flag int ctfstrategy; //ctf strategy char subteam[32]; //sub team name float formation_dist; //formation team mate intervening space char formation_teammate[16]; //netname of the team mate the bot uses for relative positioning float formation_angle; //angle relative to the formation team mate vec3_t formation_dir; //the direction the formation is moving in vec3_t formation_origin; //origin the bot uses for relative positioning bot_goal_t formation_goal; //formation goal bot_activategoal_t *activatestack; //first activate goal on the stack bot_activategoal_t activategoalheap[MAX_ACTIVATESTACK]; //activate goal heap bot_waypoint_t *checkpoints; //check points bot_waypoint_t *patrolpoints; //patrol points bot_waypoint_t *curpatrolpoint; //current patrol point the bot is going for int patrolflags; //patrol flags } bot_state_t; //resets the whole bot state void BotResetState(bot_state_t *bs); //returns the number of bots in the game int NumBots(void); //returns info about the entity void BotEntityInfo(int entnum, aas_entityinfo_t *info); extern float floattime; #define FloatTime() floattime // from the game source void QDECL BotAI_Print(int type, char *fmt, ...); void QDECL QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ); void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); int BotAI_GetClientState( int clientNum, playerState_t *state ); int BotAI_GetEntityState( int entityNum, entityState_t *state ); int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ); int BotTeamLeader(bot_state_t *bs); ================================================ FILE: code/game/ai_team.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_team.c * * desc: Quake3 bot AI * * $Archive: /MissionPack/code/game/ai_team.c $ * *****************************************************************************/ #include "g_local.h" #include "botlib.h" #include "be_aas.h" #include "be_ea.h" #include "be_ai_char.h" #include "be_ai_chat.h" #include "be_ai_gen.h" #include "be_ai_goal.h" #include "be_ai_move.h" #include "be_ai_weap.h" // #include "ai_main.h" #include "ai_dmq3.h" #include "ai_chat.h" #include "ai_cmd.h" #include "ai_dmnet.h" #include "ai_team.h" #include "ai_vcmd.h" #include "match.h" // for the voice chats #include "../../ui/menudef.h" //ctf task preferences for a client typedef struct bot_ctftaskpreference_s { char name[36]; int preference; } bot_ctftaskpreference_t; bot_ctftaskpreference_t ctftaskpreferences[MAX_CLIENTS]; /* ================== BotValidTeamLeader ================== */ int BotValidTeamLeader(bot_state_t *bs) { if (!strlen(bs->teamleader)) return qfalse; if (ClientFromName(bs->teamleader) == -1) return qfalse; return qtrue; } /* ================== BotNumTeamMates ================== */ int BotNumTeamMates(bot_state_t *bs) { int i, numplayers; char buf[MAX_INFO_STRING]; static int maxclients; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); numplayers = 0; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // if (BotSameTeam(bs, i)) { numplayers++; } } return numplayers; } /* ================== BotClientTravelTimeToGoal ================== */ int BotClientTravelTimeToGoal(int client, bot_goal_t *goal) { playerState_t ps; int areanum; BotAI_GetClientState(client, &ps); areanum = BotPointAreaNum(ps.origin); if (!areanum) return 1; return trap_AAS_AreaTravelTimeToGoalArea(areanum, ps.origin, goal->areanum, TFL_DEFAULT); } /* ================== BotSortTeamMatesByBaseTravelTime ================== */ int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxteammates) { int i, j, k, numteammates, traveltime; char buf[MAX_INFO_STRING]; static int maxclients; int traveltimes[MAX_CLIENTS]; bot_goal_t *goal = NULL; if (gametype == GT_CTF || gametype == GT_1FCTF) { if (BotTeam(bs) == TEAM_RED) goal = &ctf_redflag; else goal = &ctf_blueflag; } #ifdef MISSIONPACK else { if (BotTeam(bs) == TEAM_RED) goal = &redobelisk; else goal = &blueobelisk; } #endif if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); numteammates = 0; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // if (BotSameTeam(bs, i)) { // traveltime = BotClientTravelTimeToGoal(i, goal); // for (j = 0; j < numteammates; j++) { if (traveltime < traveltimes[j]) { for (k = numteammates; k > j; k--) { traveltimes[k] = traveltimes[k-1]; teammates[k] = teammates[k-1]; } break; } } traveltimes[j] = traveltime; teammates[j] = i; numteammates++; if (numteammates >= maxteammates) break; } } return numteammates; } /* ================== BotSetTeamMateTaskPreference ================== */ void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference) { char teammatename[MAX_NETNAME]; ctftaskpreferences[teammate].preference = preference; ClientName(teammate, teammatename, sizeof(teammatename)); strcpy(ctftaskpreferences[teammate].name, teammatename); } /* ================== BotGetTeamMateTaskPreference ================== */ int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate) { char teammatename[MAX_NETNAME]; if (!ctftaskpreferences[teammate].preference) return 0; ClientName(teammate, teammatename, sizeof(teammatename)); if (Q_stricmp(teammatename, ctftaskpreferences[teammate].name)) return 0; return ctftaskpreferences[teammate].preference; } /* ================== BotSortTeamMatesByTaskPreference ================== */ int BotSortTeamMatesByTaskPreference(bot_state_t *bs, int *teammates, int numteammates) { int defenders[MAX_CLIENTS], numdefenders; int attackers[MAX_CLIENTS], numattackers; int roamers[MAX_CLIENTS], numroamers; int i, preference; numdefenders = numattackers = numroamers = 0; for (i = 0; i < numteammates; i++) { preference = BotGetTeamMateTaskPreference(bs, teammates[i]); if (preference & TEAMTP_DEFENDER) { defenders[numdefenders++] = teammates[i]; } else if (preference & TEAMTP_ATTACKER) { attackers[numattackers++] = teammates[i]; } else { roamers[numroamers++] = teammates[i]; } } numteammates = 0; //defenders at the front of the list memcpy(&teammates[numteammates], defenders, numdefenders * sizeof(int)); numteammates += numdefenders; //roamers in the middle memcpy(&teammates[numteammates], roamers, numroamers * sizeof(int)); numteammates += numroamers; //attacker in the back of the list memcpy(&teammates[numteammates], attackers, numattackers * sizeof(int)); numteammates += numattackers; return numteammates; } /* ================== BotSayTeamOrders ================== */ void BotSayTeamOrderAlways(bot_state_t *bs, int toclient) { char teamchat[MAX_MESSAGE_SIZE]; char buf[MAX_MESSAGE_SIZE]; char name[MAX_NETNAME]; //if the bot is talking to itself if (bs->client == toclient) { //don't show the message just put it in the console message queue trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); ClientName(bs->client, name, sizeof(name)); Com_sprintf(teamchat, sizeof(teamchat), EC"(%s"EC")"EC": %s", name, buf); trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, teamchat); } else { trap_BotEnterChat(bs->cs, toclient, CHAT_TELL); } } /* ================== BotSayTeamOrders ================== */ void BotSayTeamOrder(bot_state_t *bs, int toclient) { #ifdef MISSIONPACK // voice chats only char buf[MAX_MESSAGE_SIZE]; trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); #else BotSayTeamOrderAlways(bs, toclient); #endif } /* ================== BotVoiceChat ================== */ void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat) { #ifdef MISSIONPACK if (toclient == -1) // voice only say team trap_EA_Command(bs->client, va("vsay_team %s", voicechat)); else // voice only tell single player trap_EA_Command(bs->client, va("vtell %d %s", toclient, voicechat)); #endif } /* ================== BotVoiceChatOnly ================== */ void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat) { #ifdef MISSIONPACK if (toclient == -1) // voice only say team trap_EA_Command(bs->client, va("vosay_team %s", voicechat)); else // voice only tell single player trap_EA_Command(bs->client, va("votell %d %s", toclient, voicechat)); #endif } /* ================== BotSayVoiceTeamOrder ================== */ void BotSayVoiceTeamOrder(bot_state_t *bs, int toclient, char *voicechat) { #ifdef MISSIONPACK BotVoiceChat(bs, toclient, voicechat); #endif } /* ================== BotCTFOrders ================== */ void BotCTFOrders_BothFlagsNotAtBase(bot_state_t *bs) { int numteammates, defenders, attackers, i, other; int teammates[MAX_CLIENTS]; char name[MAX_NETNAME], carriername[MAX_NETNAME]; numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); //different orders based on the number of team mates switch(bs->numteammates) { case 1: break; case 2: { //tell the one not carrying the flag to attack the enemy base if (teammates[0] != bs->flagcarrier) other = teammates[0]; else other = teammates[1]; ClientName(other, name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, other); BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); break; } case 3: { //tell the one closest to the base not carrying the flag to accompany the flag carrier if (teammates[0] != bs->flagcarrier) other = teammates[0]; else other = teammates[1]; ClientName(other, name, sizeof(name)); if ( bs->flagcarrier != -1 ) { ClientName(bs->flagcarrier, carriername, sizeof(carriername)); if (bs->flagcarrier == bs->client) { BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); } else { BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); } } else { // BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); } BotSayTeamOrder(bs, other); //tell the one furthest from the the base not carrying the flag to get the enemy flag if (teammates[2] != bs->flagcarrier) other = teammates[2]; else other = teammates[1]; ClientName(other, name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, other); BotSayVoiceTeamOrder(bs, other, VOICECHAT_RETURNFLAG); break; } default: { defenders = (int) (float) numteammates * 0.4 + 0.5; if (defenders > 4) defenders = 4; attackers = (int) (float) numteammates * 0.5 + 0.5; if (attackers > 5) attackers = 5; if (bs->flagcarrier != -1) { ClientName(bs->flagcarrier, carriername, sizeof(carriername)); for (i = 0; i < defenders; i++) { // if (teammates[i] == bs->flagcarrier) { continue; } // ClientName(teammates[i], name, sizeof(name)); if (bs->flagcarrier == bs->client) { BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWME); } else { BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWFLAGCARRIER); } BotSayTeamOrder(bs, teammates[i]); } } else { for (i = 0; i < defenders; i++) { // if (teammates[i] == bs->flagcarrier) { continue; } // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_GETFLAG); BotSayTeamOrder(bs, teammates[i]); } } for (i = 0; i < attackers; i++) { // if (teammates[numteammates - i - 1] == bs->flagcarrier) { continue; } // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_RETURNFLAG); } // break; } } } /* ================== BotCTFOrders ================== */ void BotCTFOrders_FlagNotAtBase(bot_state_t *bs) { int numteammates, defenders, attackers, i; int teammates[MAX_CLIENTS]; char name[MAX_NETNAME]; numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); //passive strategy if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { //different orders based on the number of team mates switch(bs->numteammates) { case 1: break; case 2: { //both will go for the enemy flag ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); // ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); break; } case 3: { //keep one near the base for when the flag is returned ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other two get the flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); // ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); break; } default: { //keep some people near the base for when the flag is returned defenders = (int) (float) numteammates * 0.3 + 0.5; if (defenders > 3) defenders = 3; attackers = (int) (float) numteammates * 0.7 + 0.5; if (attackers > 6) attackers = 6; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); } // break; } } } else { //different orders based on the number of team mates switch(bs->numteammates) { case 1: break; case 2: { //both will go for the enemy flag ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); // ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); break; } case 3: { //everyone go for the flag ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); // ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); // ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); break; } default: { //keep some people near the base for when the flag is returned defenders = (int) (float) numteammates * 0.2 + 0.5; if (defenders > 2) defenders = 2; attackers = (int) (float) numteammates * 0.7 + 0.5; if (attackers > 7) attackers = 7; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); } // break; } } } } /* ================== BotCTFOrders ================== */ void BotCTFOrders_EnemyFlagNotAtBase(bot_state_t *bs) { int numteammates, defenders, attackers, i, other; int teammates[MAX_CLIENTS]; char name[MAX_NETNAME], carriername[MAX_NETNAME]; numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //tell the one not carrying the flag to defend the base if (teammates[0] == bs->flagcarrier) other = teammates[1]; else other = teammates[0]; ClientName(other, name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, other); BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); break; } case 3: { //tell the one closest to the base not carrying the flag to defend the base if (teammates[0] != bs->flagcarrier) other = teammates[0]; else other = teammates[1]; ClientName(other, name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, other); BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); //tell the other also to defend the base if (teammates[2] != bs->flagcarrier) other = teammates[2]; else other = teammates[1]; ClientName(other, name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, other); BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); break; } default: { //60% will defend the base defenders = (int) (float) numteammates * 0.6 + 0.5; if (defenders > 6) defenders = 6; //30% accompanies the flag carrier attackers = (int) (float) numteammates * 0.3 + 0.5; if (attackers > 3) attackers = 3; for (i = 0; i < defenders; i++) { // if (teammates[i] == bs->flagcarrier) { continue; } ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } // if we have a flag carrier if ( bs->flagcarrier != -1 ) { ClientName(bs->flagcarrier, carriername, sizeof(carriername)); for (i = 0; i < attackers; i++) { // if (teammates[numteammates - i - 1] == bs->flagcarrier) { continue; } // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); if (bs->flagcarrier == bs->client) { BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); } else { BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); } BotSayTeamOrder(bs, teammates[numteammates - i - 1]); } } else { for (i = 0; i < attackers; i++) { // if (teammates[numteammates - i - 1] == bs->flagcarrier) { continue; } // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); } } // break; } } } /* ================== BotCTFOrders ================== */ void BotCTFOrders_BothFlagsAtBase(bot_state_t *bs) { int numteammates, defenders, attackers, i; int teammates[MAX_CLIENTS]; char name[MAX_NETNAME]; //sort team mates by travel time to base numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); //sort team mates by CTF preference BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); //passive strategy if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will get the flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the second one closest to the base will defend the base ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); //the other will get the flag ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); break; } default: { defenders = (int) (float) numteammates * 0.5 + 0.5; if (defenders > 5) defenders = 5; attackers = (int) (float) numteammates * 0.4 + 0.5; if (attackers > 4) attackers = 4; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); } // break; } } } else { //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will get the flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the others should go for the enemy flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); // ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); break; } default: { defenders = (int) (float) numteammates * 0.4 + 0.5; if (defenders > 4) defenders = 4; attackers = (int) (float) numteammates * 0.5 + 0.5; if (attackers > 5) attackers = 5; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); } // break; } } } } /* ================== BotCTFOrders ================== */ void BotCTFOrders(bot_state_t *bs) { int flagstatus; // if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; // switch(flagstatus) { case 0: BotCTFOrders_BothFlagsAtBase(bs); break; case 1: BotCTFOrders_EnemyFlagNotAtBase(bs); break; case 2: BotCTFOrders_FlagNotAtBase(bs); break; case 3: BotCTFOrders_BothFlagsNotAtBase(bs); break; } } /* ================== BotCreateGroup ================== */ void BotCreateGroup(bot_state_t *bs, int *teammates, int groupsize) { char name[MAX_NETNAME], leadername[MAX_NETNAME]; int i; // the others in the group will follow the teammates[0] ClientName(teammates[0], leadername, sizeof(leadername)); for (i = 1; i < groupsize; i++) { ClientName(teammates[i], name, sizeof(name)); if (teammates[0] == bs->client) { BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); } else { BotAI_BotInitialChat(bs, "cmd_accompany", name, leadername, NULL); } BotSayTeamOrderAlways(bs, teammates[i]); } } /* ================== BotTeamOrders FIXME: defend key areas? ================== */ void BotTeamOrders(bot_state_t *bs) { int teammates[MAX_CLIENTS]; int numteammates, i; char buf[MAX_INFO_STRING]; static int maxclients; if (!maxclients) maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); numteammates = 0; for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); //if no config string or no name if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; //skip spectators if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; // if (BotSameTeam(bs, i)) { teammates[numteammates] = i; numteammates++; } } // switch(numteammates) { case 1: break; case 2: { //nothing special break; } case 3: { //have one follow another and one free roaming BotCreateGroup(bs, teammates, 2); break; } case 4: { BotCreateGroup(bs, teammates, 2); //a group of 2 BotCreateGroup(bs, &teammates[2], 2); //a group of 2 break; } case 5: { BotCreateGroup(bs, teammates, 2); //a group of 2 BotCreateGroup(bs, &teammates[2], 3); //a group of 3 break; } default: { if (numteammates <= 10) { for (i = 0; i < numteammates / 2; i++) { BotCreateGroup(bs, &teammates[i*2], 2); //groups of 2 } } break; } } } #ifdef MISSIONPACK /* ================== Bot1FCTFOrders_FlagAtCenter X% defend the base, Y% get the flag ================== */ void Bot1FCTFOrders_FlagAtCenter(bot_state_t *bs) { int numteammates, defenders, attackers, i; int teammates[MAX_CLIENTS]; char name[MAX_NETNAME]; //sort team mates by travel time to base numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); //sort team mates by CTF preference BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); //passive strategy if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will get the flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the second one closest to the base will defend the base ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will get the flag ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); break; } default: { //50% defend the base defenders = (int) (float) numteammates * 0.5 + 0.5; if (defenders > 5) defenders = 5; //40% get the flag attackers = (int) (float) numteammates * 0.4 + 0.5; if (attackers > 4) attackers = 4; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); } // break; } } } else { //agressive //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will get the flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the others should go for the enemy flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); // ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); break; } default: { //30% defend the base defenders = (int) (float) numteammates * 0.3 + 0.5; if (defenders > 3) defenders = 3; //60% get the flag attackers = (int) (float) numteammates * 0.6 + 0.5; if (attackers > 6) attackers = 6; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); } // break; } } } } /* ================== Bot1FCTFOrders_TeamHasFlag X% towards neutral flag, Y% go towards enemy base and accompany flag carrier if visible ================== */ void Bot1FCTFOrders_TeamHasFlag(bot_state_t *bs) { int numteammates, defenders, attackers, i, other; int teammates[MAX_CLIENTS]; char name[MAX_NETNAME], carriername[MAX_NETNAME]; //sort team mates by travel time to base numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); //sort team mates by CTF preference BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); //passive strategy if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //tell the one not carrying the flag to attack the enemy base if (teammates[0] == bs->flagcarrier) other = teammates[1]; else other = teammates[0]; ClientName(other, name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); BotSayTeamOrder(bs, other); BotSayVoiceTeamOrder(bs, other, VOICECHAT_OFFENSE); break; } case 3: { //tell the one closest to the base not carrying the flag to defend the base if (teammates[0] != bs->flagcarrier) other = teammates[0]; else other = teammates[1]; ClientName(other, name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, other); BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); //tell the one furthest from the base not carrying the flag to accompany the flag carrier if (teammates[2] != bs->flagcarrier) other = teammates[2]; else other = teammates[1]; ClientName(other, name, sizeof(name)); if ( bs->flagcarrier != -1 ) { ClientName(bs->flagcarrier, carriername, sizeof(carriername)); if (bs->flagcarrier == bs->client) { BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); } else { BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); } } else { // BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); } BotSayTeamOrder(bs, other); break; } default: { //30% will defend the base defenders = (int) (float) numteammates * 0.3 + 0.5; if (defenders > 3) defenders = 3; //70% accompanies the flag carrier attackers = (int) (float) numteammates * 0.7 + 0.5; if (attackers > 7) attackers = 7; for (i = 0; i < defenders; i++) { // if (teammates[i] == bs->flagcarrier) { continue; } ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } if (bs->flagcarrier != -1) { ClientName(bs->flagcarrier, carriername, sizeof(carriername)); for (i = 0; i < attackers; i++) { // if (teammates[numteammates - i - 1] == bs->flagcarrier) { continue; } // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); if (bs->flagcarrier == bs->client) { BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); } else { BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); } BotSayTeamOrder(bs, teammates[numteammates - i - 1]); } } else { for (i = 0; i < attackers; i++) { // if (teammates[numteammates - i - 1] == bs->flagcarrier) { continue; } // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); } } // break; } } } else { //agressive //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //tell the one not carrying the flag to defend the base if (teammates[0] == bs->flagcarrier) other = teammates[1]; else other = teammates[0]; ClientName(other, name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, other); BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); break; } case 3: { //tell the one closest to the base not carrying the flag to defend the base if (teammates[0] != bs->flagcarrier) other = teammates[0]; else other = teammates[1]; ClientName(other, name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, other); BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); //tell the one furthest from the base not carrying the flag to accompany the flag carrier if (teammates[2] != bs->flagcarrier) other = teammates[2]; else other = teammates[1]; ClientName(other, name, sizeof(name)); ClientName(bs->flagcarrier, carriername, sizeof(carriername)); if (bs->flagcarrier == bs->client) { BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); } else { BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); } BotSayTeamOrder(bs, other); break; } default: { //20% will defend the base defenders = (int) (float) numteammates * 0.2 + 0.5; if (defenders > 2) defenders = 2; //80% accompanies the flag carrier attackers = (int) (float) numteammates * 0.8 + 0.5; if (attackers > 8) attackers = 8; for (i = 0; i < defenders; i++) { // if (teammates[i] == bs->flagcarrier) { continue; } ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } ClientName(bs->flagcarrier, carriername, sizeof(carriername)); for (i = 0; i < attackers; i++) { // if (teammates[numteammates - i - 1] == bs->flagcarrier) { continue; } // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); if (bs->flagcarrier == bs->client) { BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); } else { BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); } BotSayTeamOrder(bs, teammates[numteammates - i - 1]); } // break; } } } } /* ================== Bot1FCTFOrders_EnemyHasFlag X% defend the base, Y% towards neutral flag ================== */ void Bot1FCTFOrders_EnemyHasFlag(bot_state_t *bs) { int numteammates, defenders, attackers, i; int teammates[MAX_CLIENTS]; char name[MAX_NETNAME]; //sort team mates by travel time to base numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); //sort team mates by CTF preference BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); //passive strategy if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //both defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); // ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the second one closest to the base will defend the base ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); //the other will also defend the base ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_DEFEND); break; } default: { //80% will defend the base defenders = (int) (float) numteammates * 0.8 + 0.5; if (defenders > 8) defenders = 8; //10% will try to return the flag attackers = (int) (float) numteammates * 0.1 + 0.5; if (attackers > 2) attackers = 2; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); } // break; } } } else { //agressive //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will get the flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the others should go for the enemy flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); // ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); break; } default: { //70% defend the base defenders = (int) (float) numteammates * 0.7 + 0.5; if (defenders > 8) defenders = 8; //20% try to return the flag attackers = (int) (float) numteammates * 0.2 + 0.5; if (attackers > 2) attackers = 2; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); } // break; } } } } /* ================== Bot1FCTFOrders_EnemyDroppedFlag X% defend the base, Y% get the flag ================== */ void Bot1FCTFOrders_EnemyDroppedFlag(bot_state_t *bs) { int numteammates, defenders, attackers, i; int teammates[MAX_CLIENTS]; char name[MAX_NETNAME]; //sort team mates by travel time to base numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); //sort team mates by CTF preference BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); //passive strategy if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will get the flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the second one closest to the base will defend the base ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); //the other will get the flag ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); break; } default: { //50% defend the base defenders = (int) (float) numteammates * 0.5 + 0.5; if (defenders > 5) defenders = 5; //40% get the flag attackers = (int) (float) numteammates * 0.4 + 0.5; if (attackers > 4) attackers = 4; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); } // break; } } } else { //agressive //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will get the flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the others should go for the enemy flag ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); // ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); break; } default: { //30% defend the base defenders = (int) (float) numteammates * 0.3 + 0.5; if (defenders > 3) defenders = 3; //60% get the flag attackers = (int) (float) numteammates * 0.6 + 0.5; if (attackers > 6) attackers = 6; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_DEFEND); } // break; } } } } /* ================== Bot1FCTFOrders ================== */ void Bot1FCTFOrders(bot_state_t *bs) { switch(bs->neutralflagstatus) { case 0: Bot1FCTFOrders_FlagAtCenter(bs); break; case 1: Bot1FCTFOrders_TeamHasFlag(bs); break; case 2: Bot1FCTFOrders_EnemyHasFlag(bs); break; case 3: Bot1FCTFOrders_EnemyDroppedFlag(bs); break; } } /* ================== BotObeliskOrders X% in defence Y% in offence ================== */ void BotObeliskOrders(bot_state_t *bs) { int numteammates, defenders, attackers, i; int teammates[MAX_CLIENTS]; char name[MAX_NETNAME]; //sort team mates by travel time to base numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); //sort team mates by CTF preference BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); //passive strategy if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will attack the enemy base ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the one second closest to the base also defends the base ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); //the other one attacks the enemy base ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); break; } default: { //50% defend the base defenders = (int) (float) numteammates * 0.5 + 0.5; if (defenders > 5) defenders = 5; //40% attack the enemy base attackers = (int) (float) numteammates * 0.4 + 0.5; if (attackers > 4) attackers = 4; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); } // break; } } } else { //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will attack the enemy base ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the others attack the enemy base ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); // ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); break; } default: { //30% defend the base defenders = (int) (float) numteammates * 0.3 + 0.5; if (defenders > 3) defenders = 3; //70% attack the enemy base attackers = (int) (float) numteammates * 0.7 + 0.5; if (attackers > 7) attackers = 7; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); } // break; } } } } /* ================== BotHarvesterOrders X% defend the base, Y% harvest ================== */ void BotHarvesterOrders(bot_state_t *bs) { int numteammates, defenders, attackers, i; int teammates[MAX_CLIENTS]; char name[MAX_NETNAME]; //sort team mates by travel time to base numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); //sort team mates by CTF preference BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); //passive strategy if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will harvest ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the one second closest to the base also defends the base ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); //the other one goes harvesting ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); break; } default: { //50% defend the base defenders = (int) (float) numteammates * 0.5 + 0.5; if (defenders > 5) defenders = 5; //40% goes harvesting attackers = (int) (float) numteammates * 0.4 + 0.5; if (attackers > 4) attackers = 4; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); } // break; } } } else { //different orders based on the number of team mates switch(numteammates) { case 1: break; case 2: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the other will harvest ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); break; } case 3: { //the one closest to the base will defend the base ClientName(teammates[0], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[0]); BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); //the others go harvesting ClientName(teammates[1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); BotSayTeamOrder(bs, teammates[1]); BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); // ClientName(teammates[2], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); BotSayTeamOrder(bs, teammates[2]); BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); break; } default: { //30% defend the base defenders = (int) (float) numteammates * 0.3 + 0.5; if (defenders > 3) defenders = 3; //70% go harvesting attackers = (int) (float) numteammates * 0.7 + 0.5; if (attackers > 7) attackers = 7; for (i = 0; i < defenders; i++) { // ClientName(teammates[i], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); BotSayTeamOrder(bs, teammates[i]); BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); } for (i = 0; i < attackers; i++) { // ClientName(teammates[numteammates - i - 1], name, sizeof(name)); BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); BotSayTeamOrder(bs, teammates[numteammates - i - 1]); BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); } // break; } } } } #endif /* ================== FindHumanTeamLeader ================== */ int FindHumanTeamLeader(bot_state_t *bs) { int i; for (i = 0; i < MAX_CLIENTS; i++) { if ( g_entities[i].inuse ) { // if this player is not a bot if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { // if this player is ok with being the leader if (!notleader[i]) { // if this player is on the same team if ( BotSameTeam(bs, i) ) { ClientName(i, bs->teamleader, sizeof(bs->teamleader)); // if not yet ordered to do anything if ( !BotSetLastOrderedTask(bs) ) { // go on defense by default BotVoiceChat_Defend(bs, i, SAY_TELL); } return qtrue; } } } } } return qfalse; } /* ================== BotTeamAI ================== */ void BotTeamAI(bot_state_t *bs) { int numteammates; char netname[MAX_NETNAME]; // if ( gametype < GT_TEAM ) return; // make sure we've got a valid team leader if (!BotValidTeamLeader(bs)) { // if (!FindHumanTeamLeader(bs)) { // if (!bs->askteamleader_time && !bs->becometeamleader_time) { if (bs->entergame_time + 10 > FloatTime()) { bs->askteamleader_time = FloatTime() + 5 + random() * 10; } else { bs->becometeamleader_time = FloatTime() + 5 + random() * 10; } } if (bs->askteamleader_time && bs->askteamleader_time < FloatTime()) { // if asked for a team leader and no response BotAI_BotInitialChat(bs, "whoisteamleader", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); bs->askteamleader_time = 0; bs->becometeamleader_time = FloatTime() + 8 + random() * 10; } if (bs->becometeamleader_time && bs->becometeamleader_time < FloatTime()) { BotAI_BotInitialChat(bs, "iamteamleader", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotSayVoiceTeamOrder(bs, -1, VOICECHAT_STARTLEADER); ClientName(bs->client, netname, sizeof(netname)); strncpy(bs->teamleader, netname, sizeof(bs->teamleader)); bs->teamleader[sizeof(bs->teamleader)] = '\0'; bs->becometeamleader_time = 0; } return; } } bs->askteamleader_time = 0; bs->becometeamleader_time = 0; //return if this bot is NOT the team leader ClientName(bs->client, netname, sizeof(netname)); if (Q_stricmp(netname, bs->teamleader) != 0) return; // numteammates = BotNumTeamMates(bs); //give orders switch(gametype) { case GT_TEAM: { if (bs->numteammates != numteammates || bs->forceorders) { bs->teamgiveorders_time = FloatTime(); bs->numteammates = numteammates; bs->forceorders = qfalse; } //if it's time to give orders if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { BotTeamOrders(bs); //give orders again after 120 seconds bs->teamgiveorders_time = FloatTime() + 120; } break; } case GT_CTF: { //if the number of team mates changed or the flag status changed //or someone wants to know what to do if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { bs->teamgiveorders_time = FloatTime(); bs->numteammates = numteammates; bs->flagstatuschanged = qfalse; bs->forceorders = qfalse; } //if there were no flag captures the last 3 minutes if (bs->lastflagcapture_time < FloatTime() - 240) { bs->lastflagcapture_time = FloatTime(); //randomly change the CTF strategy if (random() < 0.4) { bs->ctfstrategy ^= CTFS_AGRESSIVE; bs->teamgiveorders_time = FloatTime(); } } //if it's time to give orders if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 3) { BotCTFOrders(bs); // bs->teamgiveorders_time = 0; } break; } #ifdef MISSIONPACK case GT_1FCTF: { if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { bs->teamgiveorders_time = FloatTime(); bs->numteammates = numteammates; bs->flagstatuschanged = qfalse; bs->forceorders = qfalse; } //if there were no flag captures the last 4 minutes if (bs->lastflagcapture_time < FloatTime() - 240) { bs->lastflagcapture_time = FloatTime(); //randomly change the CTF strategy if (random() < 0.4) { bs->ctfstrategy ^= CTFS_AGRESSIVE; bs->teamgiveorders_time = FloatTime(); } } //if it's time to give orders if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 2) { Bot1FCTFOrders(bs); // bs->teamgiveorders_time = 0; } break; } case GT_OBELISK: { if (bs->numteammates != numteammates || bs->forceorders) { bs->teamgiveorders_time = FloatTime(); bs->numteammates = numteammates; bs->forceorders = qfalse; } //if it's time to give orders if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { BotObeliskOrders(bs); //give orders again after 30 seconds bs->teamgiveorders_time = FloatTime() + 30; } break; } case GT_HARVESTER: { if (bs->numteammates != numteammates || bs->forceorders) { bs->teamgiveorders_time = FloatTime(); bs->numteammates = numteammates; bs->forceorders = qfalse; } //if it's time to give orders if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { BotHarvesterOrders(bs); //give orders again after 30 seconds bs->teamgiveorders_time = FloatTime() + 30; } break; } #endif } } ================================================ FILE: code/game/ai_team.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_team.h * * desc: Quake3 bot AI * * $Archive: /source/code/botai/ai_chat.c $ * *****************************************************************************/ void BotTeamAI(bot_state_t *bs); int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate); void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference); void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat); void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat); ================================================ FILE: code/game/ai_vcmd.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_vcmd.c * * desc: Quake3 bot AI * * $Archive: /MissionPack/code/game/ai_vcmd.c $ * *****************************************************************************/ #include "g_local.h" #include "botlib.h" #include "be_aas.h" #include "be_ea.h" #include "be_ai_char.h" #include "be_ai_chat.h" #include "be_ai_gen.h" #include "be_ai_goal.h" #include "be_ai_move.h" #include "be_ai_weap.h" // #include "ai_main.h" #include "ai_dmq3.h" #include "ai_chat.h" #include "ai_cmd.h" #include "ai_dmnet.h" #include "ai_team.h" #include "ai_vcmd.h" // #include "chars.h" //characteristics #include "inv.h" //indexes into the inventory #include "syn.h" //synonyms #include "match.h" //string matching types and vars // for the voice chats #include "../../ui/menudef.h" typedef struct voiceCommand_s { char *cmd; void (*func)(bot_state_t *bs, int client, int mode); } voiceCommand_t; /* ================== BotVoiceChat_GetFlag ================== */ void BotVoiceChat_GetFlag(bot_state_t *bs, int client, int mode) { // if (gametype == GT_CTF) { if (!ctf_redflag.areanum || !ctf_blueflag.areanum) return; } #ifdef MISSIONPACK else if (gametype == GT_1FCTF) { if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) return; } #endif else { return; } // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_GETFLAG; //set the team goal time bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; // get an alternate route in ctf if (gametype == GT_CTF) { //get an alternative route goal towards the enemy base BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); } // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotVoiceChat_Offense ================== */ void BotVoiceChat_Offense(bot_state_t *bs, int client, int mode) { if ( gametype == GT_CTF #ifdef MISSIONPACK || gametype == GT_1FCTF #endif ) { BotVoiceChat_GetFlag(bs, client, mode); return; } #ifdef MISSIONPACK if (gametype == GT_HARVESTER) { // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_HARVEST; //set the team goal time bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; bs->harvestaway_time = 0; // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); } else #endif { // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_ATTACKENEMYBASE; //set the team goal time bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; bs->attackaway_time = 0; // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); } #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotVoiceChat_Defend ================== */ void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode) { #ifdef MISSIONPACK if ( gametype == GT_OBELISK || gametype == GT_HARVESTER) { // switch(BotTeam(bs)) { case TEAM_RED: memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); break; default: return; } } else #endif if (gametype == GT_CTF #ifdef MISSIONPACK || gametype == GT_1FCTF #endif ) { // switch(BotTeam(bs)) { case TEAM_RED: memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); break; case TEAM_BLUE: memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); break; default: return; } } else { return; } // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_DEFENDKEYAREA; //get the team goal time bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; //away from defending bs->defendaway_time = 0; // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotVoiceChat_DefendFlag ================== */ void BotVoiceChat_DefendFlag(bot_state_t *bs, int client, int mode) { BotVoiceChat_Defend(bs, client, mode); } /* ================== BotVoiceChat_Patrol ================== */ void BotVoiceChat_Patrol(bot_state_t *bs, int client, int mode) { // bs->decisionmaker = client; // bs->ltgtype = 0; bs->lead_time = 0; bs->lastgoal_ltgtype = 0; // BotAI_BotInitialChat(bs, "dismissed", NULL); trap_BotEnterChat(bs->cs, client, CHAT_TELL); BotVoiceChatOnly(bs, -1, VOICECHAT_ONPATROL); // BotSetTeamStatus(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotVoiceChat_Camp ================== */ void BotVoiceChat_Camp(bot_state_t *bs, int client, int mode) { int areanum; aas_entityinfo_t entinfo; char netname[MAX_NETNAME]; // bs->teamgoal.entitynum = -1; BotEntityInfo(client, &entinfo); //if info is valid (in PVS) if (entinfo.valid) { areanum = BotPointAreaNum(entinfo.origin); if (areanum) { // && trap_AAS_AreaReachability(areanum)) { //NOTE: just assume the bot knows where the person is //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { bs->teamgoal.entitynum = client; bs->teamgoal.areanum = areanum; VectorCopy(entinfo.origin, bs->teamgoal.origin); VectorSet(bs->teamgoal.mins, -8, -8, -8); VectorSet(bs->teamgoal.maxs, 8, 8, 8); //} } } //if the other is not visible if (bs->teamgoal.entitynum < 0) { BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); trap_BotEnterChat(bs->cs, client, CHAT_TELL); return; } // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_CAMPORDER; //get the team goal time bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; //the teammate that requested the camping bs->teammate = client; //not arrived yet bs->arrive_time = 0; // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotVoiceChat_FollowMe ================== */ void BotVoiceChat_FollowMe(bot_state_t *bs, int client, int mode) { int areanum; aas_entityinfo_t entinfo; char netname[MAX_NETNAME]; bs->teamgoal.entitynum = -1; BotEntityInfo(client, &entinfo); //if info is valid (in PVS) if (entinfo.valid) { areanum = BotPointAreaNum(entinfo.origin); if (areanum) { // && trap_AAS_AreaReachability(areanum)) { bs->teamgoal.entitynum = client; bs->teamgoal.areanum = areanum; VectorCopy(entinfo.origin, bs->teamgoal.origin); VectorSet(bs->teamgoal.mins, -8, -8, -8); VectorSet(bs->teamgoal.maxs, 8, 8, 8); } } //if the other is not visible if (bs->teamgoal.entitynum < 0) { BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); trap_BotEnterChat(bs->cs, client, CHAT_TELL); return; } // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //the team mate bs->teammate = client; //last time the team mate was assumed visible bs->teammatevisible_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //get the team goal time bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; //set the ltg type bs->ltgtype = LTG_TEAMACCOMPANY; bs->formation_dist = 3.5 * 32; //3.5 meter bs->arrive_time = 0; // BotSetTeamStatus(bs); // remember last ordered task BotRememberLastOrderedTask(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotVoiceChat_FollowFlagCarrier ================== */ void BotVoiceChat_FollowFlagCarrier(bot_state_t *bs, int client, int mode) { int carrier; carrier = BotTeamFlagCarrier(bs); if (carrier >= 0) BotVoiceChat_FollowMe(bs, carrier, mode); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotVoiceChat_ReturnFlag ================== */ void BotVoiceChat_ReturnFlag(bot_state_t *bs, int client, int mode) { //if not in CTF mode if ( gametype != GT_CTF #ifdef MISSIONPACK && gametype != GT_1FCTF #endif ) { return; } // bs->decisionmaker = client; bs->ordered = qtrue; bs->order_time = FloatTime(); //set the time to send a message to the team mates bs->teammessage_time = FloatTime() + 2 * random(); //set the ltg type bs->ltgtype = LTG_RETURNFLAG; //set the team goal time bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; bs->rushbaseaway_time = 0; BotSetTeamStatus(bs); #ifdef DEBUG BotPrintTeamGoal(bs); #endif //DEBUG } /* ================== BotVoiceChat_StartLeader ================== */ void BotVoiceChat_StartLeader(bot_state_t *bs, int client, int mode) { ClientName(client, bs->teamleader, sizeof(bs->teamleader)); } /* ================== BotVoiceChat_StopLeader ================== */ void BotVoiceChat_StopLeader(bot_state_t *bs, int client, int mode) { char netname[MAX_MESSAGE_SIZE]; if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { bs->teamleader[0] = '\0'; notleader[client] = qtrue; } } /* ================== BotVoiceChat_WhoIsLeader ================== */ void BotVoiceChat_WhoIsLeader(bot_state_t *bs, int client, int mode) { char netname[MAX_MESSAGE_SIZE]; if (!TeamPlayIsOn()) return; ClientName(bs->client, netname, sizeof(netname)); //if this bot IS the team leader if (!Q_stricmp(netname, bs->teamleader)) { BotAI_BotInitialChat(bs, "iamteamleader", NULL); trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); BotVoiceChatOnly(bs, -1, VOICECHAT_STARTLEADER); } } /* ================== BotVoiceChat_WantOnDefense ================== */ void BotVoiceChat_WantOnDefense(bot_state_t *bs, int client, int mode) { char netname[MAX_NETNAME]; int preference; preference = BotGetTeamMateTaskPreference(bs, client); preference &= ~TEAMTP_ATTACKER; preference |= TEAMTP_DEFENDER; BotSetTeamMateTaskPreference(bs, client, preference); // EasyClientName(client, netname, sizeof(netname)); BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); trap_BotEnterChat(bs->cs, client, CHAT_TELL); BotVoiceChatOnly(bs, client, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); } /* ================== BotVoiceChat_WantOnOffense ================== */ void BotVoiceChat_WantOnOffense(bot_state_t *bs, int client, int mode) { char netname[MAX_NETNAME]; int preference; preference = BotGetTeamMateTaskPreference(bs, client); preference &= ~TEAMTP_DEFENDER; preference |= TEAMTP_ATTACKER; BotSetTeamMateTaskPreference(bs, client, preference); // EasyClientName(client, netname, sizeof(netname)); BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); trap_BotEnterChat(bs->cs, client, CHAT_TELL); BotVoiceChatOnly(bs, client, VOICECHAT_YES); trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); } void BotVoiceChat_Dummy(bot_state_t *bs, int client, int mode) { } voiceCommand_t voiceCommands[] = { {VOICECHAT_GETFLAG, BotVoiceChat_GetFlag}, {VOICECHAT_OFFENSE, BotVoiceChat_Offense }, {VOICECHAT_DEFEND, BotVoiceChat_Defend }, {VOICECHAT_DEFENDFLAG, BotVoiceChat_DefendFlag }, {VOICECHAT_PATROL, BotVoiceChat_Patrol }, {VOICECHAT_CAMP, BotVoiceChat_Camp }, {VOICECHAT_FOLLOWME, BotVoiceChat_FollowMe }, {VOICECHAT_FOLLOWFLAGCARRIER, BotVoiceChat_FollowFlagCarrier }, {VOICECHAT_RETURNFLAG, BotVoiceChat_ReturnFlag }, {VOICECHAT_STARTLEADER, BotVoiceChat_StartLeader }, {VOICECHAT_STOPLEADER, BotVoiceChat_StopLeader }, {VOICECHAT_WHOISLEADER, BotVoiceChat_WhoIsLeader }, {VOICECHAT_WANTONDEFENSE, BotVoiceChat_WantOnDefense }, {VOICECHAT_WANTONOFFENSE, BotVoiceChat_WantOnOffense }, {NULL, BotVoiceChat_Dummy} }; int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voiceChat) { int i, voiceOnly, clientNum, color; char *ptr, buf[MAX_MESSAGE_SIZE], *cmd; if (!TeamPlayIsOn()) { return qfalse; } if ( mode == SAY_ALL ) { return qfalse; // don't do anything with voice chats to everyone } Q_strncpyz(buf, voiceChat, sizeof(buf)); cmd = buf; for (ptr = cmd; *cmd && *cmd > ' '; cmd++); while (*cmd && *cmd <= ' ') *cmd++ = '\0'; voiceOnly = atoi(ptr); for (ptr = cmd; *cmd && *cmd > ' '; cmd++); while (*cmd && *cmd <= ' ') *cmd++ = '\0'; clientNum = atoi(ptr); for (ptr = cmd; *cmd && *cmd > ' '; cmd++); while (*cmd && *cmd <= ' ') *cmd++ = '\0'; color = atoi(ptr); if (!BotSameTeam(bs, clientNum)) { return qfalse; } for (i = 0; voiceCommands[i].cmd; i++) { if (!Q_stricmp(cmd, voiceCommands[i].cmd)) { voiceCommands[i].func(bs, clientNum, mode); return qtrue; } } return qfalse; } ================================================ FILE: code/game/ai_vcmd.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: ai_vcmd.h * * desc: Quake3 bot AI * * $Archive: /source/code/botai/ai_vcmd.c $ * *****************************************************************************/ int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voicechat); void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode); ================================================ FILE: code/game/be_aas.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: be_aas.h * * desc: Area Awareness System, stuff exported to the AI * * $Archive: /source/code/botlib/be_aas.h $ * *****************************************************************************/ #ifndef MAX_STRINGFIELD #define MAX_STRINGFIELD 80 #endif //travel flags #define TFL_INVALID 0x00000001 //traveling temporary not possible #define TFL_WALK 0x00000002 //walking #define TFL_CROUCH 0x00000004 //crouching #define TFL_BARRIERJUMP 0x00000008 //jumping onto a barrier #define TFL_JUMP 0x00000010 //jumping #define TFL_LADDER 0x00000020 //climbing a ladder #define TFL_WALKOFFLEDGE 0x00000080 //walking of a ledge #define TFL_SWIM 0x00000100 //swimming #define TFL_WATERJUMP 0x00000200 //jumping out of the water #define TFL_TELEPORT 0x00000400 //teleporting #define TFL_ELEVATOR 0x00000800 //elevator #define TFL_ROCKETJUMP 0x00001000 //rocket jumping #define TFL_BFGJUMP 0x00002000 //bfg jumping #define TFL_GRAPPLEHOOK 0x00004000 //grappling hook #define TFL_DOUBLEJUMP 0x00008000 //double jump #define TFL_RAMPJUMP 0x00010000 //ramp jump #define TFL_STRAFEJUMP 0x00020000 //strafe jump #define TFL_JUMPPAD 0x00040000 //jump pad #define TFL_AIR 0x00080000 //travel through air #define TFL_WATER 0x00100000 //travel through water #define TFL_SLIME 0x00200000 //travel through slime #define TFL_LAVA 0x00400000 //travel through lava #define TFL_DONOTENTER 0x00800000 //travel through donotenter area #define TFL_FUNCBOB 0x01000000 //func bobbing #define TFL_FLIGHT 0x02000000 //flight #define TFL_BRIDGE 0x04000000 //move over a bridge // #define TFL_NOTTEAM1 0x08000000 //not team 1 #define TFL_NOTTEAM2 0x10000000 //not team 2 //default travel flags #define TFL_DEFAULT TFL_WALK|TFL_CROUCH|TFL_BARRIERJUMP|\ TFL_JUMP|TFL_LADDER|\ TFL_WALKOFFLEDGE|TFL_SWIM|TFL_WATERJUMP|\ TFL_TELEPORT|TFL_ELEVATOR|\ TFL_AIR|TFL_WATER|TFL_JUMPPAD|TFL_FUNCBOB typedef enum { SOLID_NOT, // no interaction with other objects SOLID_TRIGGER, // only touch when inside, after moving SOLID_BBOX, // touch on edge SOLID_BSP // bsp clip, touch on edge } solid_t; //a trace is returned when a box is swept through the AAS world typedef struct aas_trace_s { 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 int ent; // entity blocking the trace int lastarea; // last area the trace was in (zero if none) int area; // area blocking the trace (zero if none) int planenum; // number of the plane that was hit } aas_trace_t; /* Defined in botlib.h //bsp_trace_t hit surface typedef struct bsp_surface_s { char name[16]; int flags; int value; } bsp_surface_t; //a trace is returned when a box is swept through the BSP world 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; // hit surface int contents; // contents on other side of surface hit int ent; // number of entity hit } bsp_trace_t; // */ //entity info typedef struct aas_entityinfo_s { int valid; // true if updated this frame int type; // entity type int flags; // entity flags float ltime; // local time float update_time; // time between last and current update int number; // number of the entity vec3_t origin; // origin of the entity vec3_t angles; // angles of the model vec3_t old_origin; // for lerping vec3_t lastvisorigin; // last visible origin vec3_t mins; // bounding box minimums vec3_t maxs; // bounding box maximums int groundent; // ground entity int solid; // solid type int modelindex; // model used int modelindex2; // weapons, CTF flags, etc int frame; // model frame number int event; // impulse events -- muzzle flashes, footsteps, etc int eventParm; // even parameter int powerups; // bit flags int weapon; // determines weapon and flash model, etc int legsAnim; // mask off ANIM_TOGGLEBIT int torsoAnim; // mask off ANIM_TOGGLEBIT } aas_entityinfo_t; // area info typedef struct aas_areainfo_s { int contents; int flags; int presencetype; int cluster; vec3_t mins; vec3_t maxs; vec3_t center; } aas_areainfo_t; // client movement prediction stop events, stop as soon as: #define SE_NONE 0 #define SE_HITGROUND 1 // the ground is hit #define SE_LEAVEGROUND 2 // there's no ground #define SE_ENTERWATER 4 // water is entered #define SE_ENTERSLIME 8 // slime is entered #define SE_ENTERLAVA 16 // lava is entered #define SE_HITGROUNDDAMAGE 32 // the ground is hit with damage #define SE_GAP 64 // there's a gap #define SE_TOUCHJUMPPAD 128 // touching a jump pad area #define SE_TOUCHTELEPORTER 256 // touching teleporter #define SE_ENTERAREA 512 // the given stoparea is entered #define SE_HITGROUNDAREA 1024 // a ground face in the area is hit #define SE_HITBOUNDINGBOX 2048 // hit the specified bounding box #define SE_TOUCHCLUSTERPORTAL 4096 // touching a cluster portal typedef struct aas_clientmove_s { vec3_t endpos; //position at the end of movement prediction int endarea; //area at end of movement prediction vec3_t velocity; //velocity at the end of movement prediction aas_trace_t trace; //last trace int presencetype; //presence type at end of movement prediction int stopevent; //event that made the prediction stop int endcontents; //contents at the end of movement prediction float time; //time predicted ahead int frames; //number of frames predicted ahead } aas_clientmove_t; // alternate route goals #define ALTROUTEGOAL_ALL 1 #define ALTROUTEGOAL_CLUSTERPORTALS 2 #define ALTROUTEGOAL_VIEWPORTALS 4 typedef struct aas_altroutegoal_s { vec3_t origin; int areanum; unsigned short starttraveltime; unsigned short goaltraveltime; unsigned short extratraveltime; } aas_altroutegoal_t; // route prediction stop events #define RSE_NONE 0 #define RSE_NOROUTE 1 //no route to goal #define RSE_USETRAVELTYPE 2 //stop as soon as on of the given travel types is used #define RSE_ENTERCONTENTS 4 //stop when entering the given contents #define RSE_ENTERAREA 8 //stop when entering the given area typedef struct aas_predictroute_s { vec3_t endpos; //position at the end of movement prediction int endarea; //area at end of movement prediction int stopevent; //event that made the prediction stop int endcontents; //contents at the end of movement prediction int endtravelflags; //end travel flags int numareas; //number of areas predicted ahead int time; //time predicted ahead (in hundreth of a sec) } aas_predictroute_t; ================================================ FILE: code/game/be_ai_char.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: be_ai_char.h * * desc: bot characters * * $Archive: /source/code/botlib/be_ai_char.h $ * *****************************************************************************/ //loads a bot character from a file int BotLoadCharacter(char *charfile, float skill); //frees a bot character void BotFreeCharacter(int character); //returns a float characteristic float Characteristic_Float(int character, int index); //returns a bounded float characteristic float Characteristic_BFloat(int character, int index, float min, float max); //returns an integer characteristic int Characteristic_Integer(int character, int index); //returns a bounded integer characteristic int Characteristic_BInteger(int character, int index, int min, int max); //returns a string characteristic void Characteristic_String(int character, int index, char *buf, int size); //free cached bot characters void BotShutdownCharacters(void); ================================================ FILE: code/game/be_ai_chat.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: be_ai_chat.h * * desc: char AI * * $Archive: /source/code/botlib/be_ai_chat.h $ * *****************************************************************************/ #define MAX_MESSAGE_SIZE 256 #define MAX_CHATTYPE_NAME 32 #define MAX_MATCHVARIABLES 8 #define CHAT_GENDERLESS 0 #define CHAT_GENDERFEMALE 1 #define CHAT_GENDERMALE 2 #define CHAT_ALL 0 #define CHAT_TEAM 1 #define CHAT_TELL 2 //a console message typedef struct bot_consolemessage_s { int handle; float time; //message time int type; //message type char message[MAX_MESSAGE_SIZE]; //message struct bot_consolemessage_s *prev, *next; //prev and next in list } bot_consolemessage_t; //match variable typedef struct bot_matchvariable_s { char offset; int length; } bot_matchvariable_t; //returned to AI when a match is found typedef struct bot_match_s { char string[MAX_MESSAGE_SIZE]; int type; int subtype; bot_matchvariable_t variables[MAX_MATCHVARIABLES]; } bot_match_t; //setup the chat AI int BotSetupChatAI(void); //shutdown the chat AI void BotShutdownChatAI(void); //returns the handle to a newly allocated chat state int BotAllocChatState(void); //frees the chatstate void BotFreeChatState(int handle); //adds a console message to the chat state void BotQueueConsoleMessage(int chatstate, int type, char *message); //removes the console message from the chat state void BotRemoveConsoleMessage(int chatstate, int handle); //returns the next console message from the state int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm); //returns the number of console messages currently stored in the state int BotNumConsoleMessages(int chatstate); //selects a chat message of the given type void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); //returns the number of initial chat messages of the given type int BotNumInitialChats(int chatstate, char *type); //find and select a reply for the given message int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); //returns the length of the currently selected chat message int BotChatLength(int chatstate); //enters the selected chat message void BotEnterChat(int chatstate, int clientto, int sendto); //get the chat message ready to be output void BotGetChatMessage(int chatstate, char *buf, int size); //checks if the first string contains the second one, returns index into first string or -1 if not found int StringContains(char *str1, char *str2, int casesensitive); //finds a match for the given string using the match templates int BotFindMatch(char *str, bot_match_t *match, unsigned long int context); //returns a variable from a match void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size); //unify all the white spaces in the string void UnifyWhiteSpaces(char *string); //replace all the context related synonyms in the string void BotReplaceSynonyms(char *string, unsigned long int context); //loads a chat file for the chat state int BotLoadChatFile(int chatstate, char *chatfile, char *chatname); //store the gender of the bot in the chat state void BotSetChatGender(int chatstate, int gender); //store the bot name in the chat state void BotSetChatName(int chatstate, char *name, int client); ================================================ FILE: code/game/be_ai_gen.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: be_ai_gen.h * * desc: genetic selection * * $Archive: /source/code/botlib/be_ai_gen.h $ * *****************************************************************************/ int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child); ================================================ FILE: code/game/be_ai_goal.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: be_ai_goal.h * * desc: goal AI * * $Archive: /source/code/botlib/be_ai_goal.h $ * *****************************************************************************/ #define MAX_AVOIDGOALS 256 #define MAX_GOALSTACK 8 #define GFL_NONE 0 #define GFL_ITEM 1 #define GFL_ROAM 2 #define GFL_DROPPED 4 //a bot goal typedef struct bot_goal_s { vec3_t origin; //origin of the goal int areanum; //area number of the goal vec3_t mins, maxs; //mins and maxs of the goal int entitynum; //number of the goal entity int number; //goal number int flags; //goal flags int iteminfo; //item information } bot_goal_t; //reset the whole goal state, but keep the item weights void BotResetGoalState(int goalstate); //reset avoid goals void BotResetAvoidGoals(int goalstate); //remove the goal with the given number from the avoid goals void BotRemoveFromAvoidGoals(int goalstate, int number); //push a goal onto the goal stack void BotPushGoal(int goalstate, bot_goal_t *goal); //pop a goal from the goal stack void BotPopGoal(int goalstate); //empty the bot's goal stack void BotEmptyGoalStack(int goalstate); //dump the avoid goals void BotDumpAvoidGoals(int goalstate); //dump the goal stack void BotDumpGoalStack(int goalstate); //get the name name of the goal with the given number void BotGoalName(int number, char *name, int size); //get the top goal from the stack int BotGetTopGoal(int goalstate, bot_goal_t *goal); //get the second goal on the stack int BotGetSecondGoal(int goalstate, bot_goal_t *goal); //choose the best long term goal item for the bot int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags); //choose the best nearby goal item for the bot //the item may not be further away from the current bot position than maxtime //also the travel time from the nearby goal towards the long term goal may not //be larger than the travel time towards the long term goal from the current bot position int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, bot_goal_t *ltg, float maxtime); //returns true if the bot touches the goal int BotTouchingGoal(vec3_t origin, bot_goal_t *goal); //returns true if the goal should be visible but isn't int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal); //search for a goal for the given classname, the index can be used //as a start point for the search when multiple goals are available with that same classname int BotGetLevelItemGoal(int index, char *classname, bot_goal_t *goal); //get the next camp spot in the map int BotGetNextCampSpotGoal(int num, bot_goal_t *goal); //get the map location with the given name int BotGetMapLocationGoal(char *name, bot_goal_t *goal); //returns the avoid goal time float BotAvoidGoalTime(int goalstate, int number); //set the avoid goal time void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime); //initializes the items in the level void BotInitLevelItems(void); //regularly update dynamic entity items (dropped weapons, flags etc.) void BotUpdateEntityItems(void); //interbreed the goal fuzzy logic void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child); //save the goal fuzzy logic to disk void BotSaveGoalFuzzyLogic(int goalstate, char *filename); //mutate the goal fuzzy logic void BotMutateGoalFuzzyLogic(int goalstate, float range); //loads item weights for the bot int BotLoadItemWeights(int goalstate, char *filename); //frees the item weights of the bot void BotFreeItemWeights(int goalstate); //returns the handle of a newly allocated goal state int BotAllocGoalState(int client); //free the given goal state void BotFreeGoalState(int handle); //setup the goal AI int BotSetupGoalAI(void); //shut down the goal AI void BotShutdownGoalAI(void); ================================================ FILE: code/game/be_ai_move.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: be_ai_move.h * * desc: movement AI * * $Archive: /source/code/botlib/be_ai_move.h $ * *****************************************************************************/ //movement types #define MOVE_WALK 1 #define MOVE_CROUCH 2 #define MOVE_JUMP 4 #define MOVE_GRAPPLE 8 #define MOVE_ROCKETJUMP 16 #define MOVE_BFGJUMP 32 //move flags #define MFL_BARRIERJUMP 1 //bot is performing a barrier jump #define MFL_ONGROUND 2 //bot is in the ground #define MFL_SWIMMING 4 //bot is swimming #define MFL_AGAINSTLADDER 8 //bot is against a ladder #define MFL_WATERJUMP 16 //bot is waterjumping #define MFL_TELEPORTED 32 //bot is being teleported #define MFL_GRAPPLEPULL 64 //bot is being pulled by the grapple #define MFL_ACTIVEGRAPPLE 128 //bot is using the grapple hook #define MFL_GRAPPLERESET 256 //bot has reset the grapple #define MFL_WALK 512 //bot should walk slowly // move result flags #define MOVERESULT_MOVEMENTVIEW 1 //bot uses view for movement #define MOVERESULT_SWIMVIEW 2 //bot uses view for swimming #define MOVERESULT_WAITING 4 //bot is waiting for something #define MOVERESULT_MOVEMENTVIEWSET 8 //bot has set the view in movement code #define MOVERESULT_MOVEMENTWEAPON 16 //bot uses weapon for movement #define MOVERESULT_ONTOPOFOBSTACLE 32 //bot is ontop of obstacle #define MOVERESULT_ONTOPOF_FUNCBOB 64 //bot is ontop of a func_bobbing #define MOVERESULT_ONTOPOF_ELEVATOR 128 //bot is ontop of an elevator (func_plat) #define MOVERESULT_BLOCKEDBYAVOIDSPOT 256 //bot is blocked by an avoid spot // #define MAX_AVOIDREACH 1 #define MAX_AVOIDSPOTS 32 // avoid spot types #define AVOID_CLEAR 0 //clear all avoid spots #define AVOID_ALWAYS 1 //avoid always #define AVOID_DONTBLOCK 2 //never totally block // restult types #define RESULTTYPE_ELEVATORUP 1 //elevator is up #define RESULTTYPE_WAITFORFUNCBOBBING 2 //waiting for func bobbing to arrive #define RESULTTYPE_BADGRAPPLEPATH 4 //grapple path is obstructed #define RESULTTYPE_INSOLIDAREA 8 //stuck in solid area, this is bad //structure used to initialize the movement state //the or_moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP come from the playerstate typedef struct bot_initmove_s { vec3_t origin; //origin of the bot vec3_t velocity; //velocity of the bot vec3_t viewoffset; //view offset int entitynum; //entity number of the bot int client; //client number of the bot float thinktime; //time the bot thinks int presencetype; //presencetype of the bot vec3_t viewangles; //view angles of the bot int or_moveflags; //values ored to the movement flags } bot_initmove_t; //NOTE: the ideal_viewangles are only valid if MFL_MOVEMENTVIEW is set typedef struct bot_moveresult_s { int failure; //true if movement failed all together int type; //failure or blocked type int blocked; //true if blocked by an entity int blockentity; //entity blocking the bot int traveltype; //last executed travel type int flags; //result flags int weapon; //weapon used for movement vec3_t movedir; //movement direction vec3_t ideal_viewangles; //ideal viewangles for the movement } bot_moveresult_t; // bk001204: from code/botlib/be_ai_move.c // TTimo 04/12/2001 was moved here to avoid dup defines typedef struct bot_avoidspot_s { vec3_t origin; float radius; int type; } bot_avoidspot_t; //resets the whole move state void BotResetMoveState(int movestate); //moves the bot to the given goal void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags); //moves the bot in the specified direction using the specified type of movement int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type); //reset avoid reachability void BotResetAvoidReach(int movestate); //resets the last avoid reachability void BotResetLastAvoidReach(int movestate); //returns a reachability area if the origin is in one int BotReachabilityArea(vec3_t origin, int client); //view target based on movement int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target); //predict the position of a player based on movement towards a goal int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target); //returns the handle of a newly allocated movestate int BotAllocMoveState(void); //frees the movestate with the given handle void BotFreeMoveState(int handle); //initialize movement state before performing any movement void BotInitMoveState(int handle, bot_initmove_t *initmove); //add a spot to avoid (if type == AVOID_CLEAR all spots are removed) void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type); //must be called every map change void BotSetBrushModelTypes(void); //setup movement AI int BotSetupMoveAI(void); //shutdown movement AI void BotShutdownMoveAI(void); ================================================ FILE: code/game/be_ai_weap.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: be_ai_weap.h * * desc: weapon AI * * $Archive: /source/code/botlib/be_ai_weap.h $ * *****************************************************************************/ //projectile flags #define PFL_WINDOWDAMAGE 1 //projectile damages through window #define PFL_RETURN 2 //set when projectile returns to owner //weapon flags #define WFL_FIRERELEASED 1 //set when projectile is fired with key-up event //damage types #define DAMAGETYPE_IMPACT 1 //damage on impact #define DAMAGETYPE_RADIAL 2 //radial damage #define DAMAGETYPE_VISIBLE 4 //damage to all entities visible to the projectile typedef struct projectileinfo_s { char name[MAX_STRINGFIELD]; char model[MAX_STRINGFIELD]; int flags; float gravity; int damage; float radius; int visdamage; int damagetype; int healthinc; float push; float detonation; float bounce; float bouncefric; float bouncestop; } projectileinfo_t; typedef struct weaponinfo_s { int valid; //true if the weapon info is valid int number; //number of the weapon char name[MAX_STRINGFIELD]; char model[MAX_STRINGFIELD]; int level; int weaponindex; int flags; char projectile[MAX_STRINGFIELD]; int numprojectiles; float hspread; float vspread; float speed; float acceleration; vec3_t recoil; vec3_t offset; vec3_t angleoffset; float extrazvelocity; int ammoamount; int ammoindex; float activate; float reload; float spinup; float spindown; projectileinfo_t proj; //pointer to the used projectile } weaponinfo_t; //setup the weapon AI int BotSetupWeaponAI(void); //shut down the weapon AI void BotShutdownWeaponAI(void); //returns the best weapon to fight with int BotChooseBestFightWeapon(int weaponstate, int *inventory); //returns the information of the current weapon void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo); //loads the weapon weights int BotLoadWeaponWeights(int weaponstate, char *filename); //returns a handle to a newly allocated weapon state int BotAllocWeaponState(void); //frees the weapon state void BotFreeWeaponState(int weaponstate); //resets the whole weapon state void BotResetWeaponState(int weaponstate); ================================================ FILE: code/game/be_ea.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: be_ea.h * * desc: elementary actions * * $Archive: /source/code/botlib/be_ea.h $ * *****************************************************************************/ //ClientCommand elementary actions void EA_Say(int client, char *str); void EA_SayTeam(int client, char *str); void EA_Command(int client, char *command ); void EA_Action(int client, int action); void EA_Crouch(int client); void EA_Walk(int client); void EA_MoveUp(int client); void EA_MoveDown(int client); void EA_MoveForward(int client); void EA_MoveBack(int client); void EA_MoveLeft(int client); void EA_MoveRight(int client); void EA_Attack(int client); void EA_Respawn(int client); void EA_Talk(int client); void EA_Gesture(int client); void EA_Use(int client); //regular elementary actions void EA_SelectWeapon(int client, int weapon); void EA_Jump(int client); void EA_DelayedJump(int client); void EA_Move(int client, vec3_t dir, float speed); void EA_View(int client, vec3_t viewangles); //send regular input to the server void EA_EndRegular(int client, float thinktime); void EA_GetInput(int client, float thinktime, bot_input_t *input); void EA_ResetInput(int client); //setup and shutdown routines int EA_Setup(void); void EA_Shutdown(void); ================================================ FILE: code/game/bg_lib.c ================================================ // // // bg_lib,c -- standard C library replacement routines used by code // compiled for the virtual machine #include "q_shared.h" /*- * Copyright (c) 1992, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if defined(LIBC_SCCS) && !defined(lint) #if 0 static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; #endif static const char rcsid[] = #endif /* LIBC_SCCS and not lint */ // bk001127 - needed for DLL's #if !defined( Q3_VM ) typedef int cmp_t(const void *, const void *); #endif static char* med3(char *, char *, char *, cmp_t *); static void swapfunc(char *, char *, int, int); #ifndef min #define min(a, b) (a) < (b) ? a : b #endif /* * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". */ #define swapcode(TYPE, parmi, parmj, n) { \ long i = (n) / sizeof (TYPE); \ register TYPE *pi = (TYPE *) (parmi); \ register TYPE *pj = (TYPE *) (parmj); \ do { \ register TYPE t = *pi; \ *pi++ = *pj; \ *pj++ = t; \ } while (--i > 0); \ } #define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; static void swapfunc(a, b, n, swaptype) char *a, *b; int n, swaptype; { if(swaptype <= 1) swapcode(long, a, b, n) else swapcode(char, a, b, n) } #define swap(a, b) \ if (swaptype == 0) { \ long t = *(long *)(a); \ *(long *)(a) = *(long *)(b); \ *(long *)(b) = t; \ } else \ swapfunc(a, b, es, swaptype) #define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) static char * med3(a, b, c, cmp) char *a, *b, *c; cmp_t *cmp; { return cmp(a, b) < 0 ? (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); } void qsort(a, n, es, cmp) void *a; size_t n, es; cmp_t *cmp; { char *pa, *pb, *pc, *pd, *pl, *pm, *pn; int d, r, swaptype, swap_cnt; loop: SWAPINIT(a, es); swap_cnt = 0; if (n < 7) { for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; pl -= es) swap(pl, pl - es); return; } pm = (char *)a + (n / 2) * es; if (n > 7) { pl = a; pn = (char *)a + (n - 1) * es; if (n > 40) { d = (n / 8) * es; pl = med3(pl, pl + d, pl + 2 * d, cmp); pm = med3(pm - d, pm, pm + d, cmp); pn = med3(pn - 2 * d, pn - d, pn, cmp); } pm = med3(pl, pm, pn, cmp); } swap(a, pm); pa = pb = (char *)a + es; pc = pd = (char *)a + (n - 1) * es; for (;;) { while (pb <= pc && (r = cmp(pb, a)) <= 0) { if (r == 0) { swap_cnt = 1; swap(pa, pb); pa += es; } pb += es; } while (pb <= pc && (r = cmp(pc, a)) >= 0) { if (r == 0) { swap_cnt = 1; swap(pc, pd); pd -= es; } pc -= es; } if (pb > pc) break; swap(pb, pc); swap_cnt = 1; pb += es; pc -= es; } if (swap_cnt == 0) { /* Switch to insertion sort */ for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; pl -= es) swap(pl, pl - es); return; } pn = (char *)a + n * es; r = min(pa - (char *)a, pb - pa); vecswap(a, pb - r, r); r = min(pd - pc, pn - pd - es); vecswap(pb, pn - r, r); if ((r = pb - pa) > es) qsort(a, r / es, es, cmp); if ((r = pd - pc) > es) { /* Iterate rather than recurse to save stack space */ a = pn - r; n = r / es; goto loop; } /* qsort(pn - r, r / es, es, cmp);*/ } //================================================================================== // this file is excluded from release builds because of intrinsics // bk001211 - gcc errors on compiling strcpy: parse error before `__extension__' #if defined ( Q3_VM ) size_t strlen( const char *string ) { const char *s; s = string; while ( *s ) { s++; } return s - string; } char *strcat( char *strDestination, const char *strSource ) { char *s; s = strDestination; while ( *s ) { s++; } while ( *strSource ) { *s++ = *strSource++; } *s = 0; return strDestination; } char *strcpy( char *strDestination, const char *strSource ) { char *s; s = strDestination; while ( *strSource ) { *s++ = *strSource++; } *s = 0; return strDestination; } int strcmp( const char *string1, const char *string2 ) { while ( *string1 == *string2 && *string1 && *string2 ) { string1++; string2++; } return *string1 - *string2; } char *strchr( const char *string, int c ) { while ( *string ) { if ( *string == c ) { return ( char * )string; } string++; } return (char *)0; } char *strstr( const char *string, const char *strCharSet ) { while ( *string ) { int i; for ( i = 0 ; strCharSet[i] ; i++ ) { if ( string[i] != strCharSet[i] ) { break; } } if ( !strCharSet[i] ) { return (char *)string; } string++; } return (char *)0; } #endif // bk001211 // bk001120 - presumably needed for Mac //#if !defined(_MSC_VER) && !defined(__linux__) // bk001127 - undid undo #if defined ( Q3_VM ) int tolower( int c ) { if ( c >= 'A' && c <= 'Z' ) { c += 'a' - 'A'; } return c; } int toupper( int c ) { if ( c >= 'a' && c <= 'z' ) { c += 'A' - 'a'; } return c; } #endif //#ifndef _MSC_VER void *memmove( void *dest, const void *src, size_t count ) { int i; if ( dest > src ) { for ( i = count-1 ; i >= 0 ; i-- ) { ((char *)dest)[i] = ((char *)src)[i]; } } else { for ( i = 0 ; i < count ; i++ ) { ((char *)dest)[i] = ((char *)src)[i]; } } return dest; } #if 0 double floor( double x ) { return (int)(x + 0x40000000) - 0x40000000; } void *memset( void *dest, int c, size_t count ) { while ( count-- ) { ((char *)dest)[count] = c; } return dest; } void *memcpy( void *dest, const void *src, size_t count ) { while ( count-- ) { ((char *)dest)[count] = ((char *)src)[count]; } return dest; } char *strncpy( char *strDest, const char *strSource, size_t count ) { char *s; s = strDest; while ( *strSource && count ) { *s++ = *strSource++; count--; } while ( count-- ) { *s++ = 0; } return strDest; } double sqrt( double x ) { float y; float delta; float maxError; if ( x <= 0 ) { return 0; } // initial guess y = x / 2; // refine maxError = x * 0.001; do { delta = ( y * y ) - x; y -= delta / ( 2 * y ); } while ( delta > maxError || delta < -maxError ); return y; } float sintable[1024] = { 0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, 0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, 0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, 0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, 0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, 0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, 0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, 0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, 0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, 0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, 0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, 0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, 0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, 0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, 0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, 0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, 0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, 0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, 0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, 0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, 0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, 0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, 0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, 0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, 0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, 0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, 0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, 0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, 0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, 0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, 0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, 0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, 0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, 0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, 0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, 0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, 0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, 0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, 0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, 0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, 0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, 0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, 0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, 0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, 0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, 0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, 0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, 0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, 0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, 0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, 0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, 0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, 0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, 0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, 0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, 0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, 0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, 0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, 0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, 0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, 0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, 0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, 0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, 0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, 0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, 0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, 0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, 0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, 0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, 0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, 0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, 0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, 0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, 0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, 0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, 0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, 0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, 0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, 0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, 0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, 0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, 0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, 0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, 0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, 0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, 0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, 0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, 0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, 0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, 0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, 0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, 0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, 0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, 0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, 0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, 0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, 0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, 0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, 0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, 0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, 0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, 0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, 0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, 0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, 0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, 0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, 0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, 0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, 0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, 0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, 0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, 0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, 0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, 0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, 0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, 0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, 0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, 0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, 0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, 0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, 0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, 0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, 0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, 0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, 0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, 0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, 0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, 0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 }; double sin( double x ) { int index; int quad; index = 1024 * x / (M_PI * 0.5); quad = ( index >> 10 ) & 3; index &= 1023; switch ( quad ) { case 0: return sintable[index]; case 1: return sintable[1023-index]; case 2: return -sintable[index]; case 3: return -sintable[1023-index]; } return 0; } double cos( double x ) { int index; int quad; index = 1024 * x / (M_PI * 0.5); quad = ( index >> 10 ) & 3; index &= 1023; switch ( quad ) { case 3: return sintable[index]; case 0: return sintable[1023-index]; case 1: return -sintable[index]; case 2: return -sintable[1023-index]; } return 0; } /* void create_acostable( void ) { int i; FILE *fp; float a; fp = fopen("c:\\acostable.txt", "w"); fprintf(fp, "float acostable[] = {"); for (i = 0; i < 1024; i++) { if (!(i & 7)) fprintf(fp, "\n"); a = acos( (float) -1 + i / 512 ); fprintf(fp, "%1.8f,", a); } fprintf(fp, "\n}\n"); fclose(fp); } */ float acostable[] = { 3.14159265,3.07908248,3.05317551,3.03328655,3.01651113,3.00172442,2.98834964,2.97604422, 2.96458497,2.95381690,2.94362719,2.93393068,2.92466119,2.91576615,2.90720289,2.89893629, 2.89093699,2.88318015,2.87564455,2.86831188,2.86116621,2.85419358,2.84738169,2.84071962, 2.83419760,2.82780691,2.82153967,2.81538876,2.80934770,2.80341062,2.79757211,2.79182724, 2.78617145,2.78060056,2.77511069,2.76969824,2.76435988,2.75909250,2.75389319,2.74875926, 2.74368816,2.73867752,2.73372510,2.72882880,2.72398665,2.71919677,2.71445741,2.70976688, 2.70512362,2.70052613,2.69597298,2.69146283,2.68699438,2.68256642,2.67817778,2.67382735, 2.66951407,2.66523692,2.66099493,2.65678719,2.65261279,2.64847088,2.64436066,2.64028133, 2.63623214,2.63221238,2.62822133,2.62425835,2.62032277,2.61641398,2.61253138,2.60867440, 2.60484248,2.60103507,2.59725167,2.59349176,2.58975488,2.58604053,2.58234828,2.57867769, 2.57502832,2.57139977,2.56779164,2.56420354,2.56063509,2.55708594,2.55355572,2.55004409, 2.54655073,2.54307530,2.53961750,2.53617701,2.53275354,2.52934680,2.52595650,2.52258238, 2.51922417,2.51588159,2.51255441,2.50924238,2.50594525,2.50266278,2.49939476,2.49614096, 2.49290115,2.48967513,2.48646269,2.48326362,2.48007773,2.47690482,2.47374472,2.47059722, 2.46746215,2.46433933,2.46122860,2.45812977,2.45504269,2.45196720,2.44890314,2.44585034, 2.44280867,2.43977797,2.43675809,2.43374890,2.43075025,2.42776201,2.42478404,2.42181622, 2.41885841,2.41591048,2.41297232,2.41004380,2.40712480,2.40421521,2.40131491,2.39842379, 2.39554173,2.39266863,2.38980439,2.38694889,2.38410204,2.38126374,2.37843388,2.37561237, 2.37279910,2.36999400,2.36719697,2.36440790,2.36162673,2.35885335,2.35608768,2.35332964, 2.35057914,2.34783610,2.34510044,2.34237208,2.33965094,2.33693695,2.33423003,2.33153010, 2.32883709,2.32615093,2.32347155,2.32079888,2.31813284,2.31547337,2.31282041,2.31017388, 2.30753373,2.30489988,2.30227228,2.29965086,2.29703556,2.29442632,2.29182309,2.28922580, 2.28663439,2.28404881,2.28146900,2.27889490,2.27632647,2.27376364,2.27120637,2.26865460, 2.26610827,2.26356735,2.26103177,2.25850149,2.25597646,2.25345663,2.25094195,2.24843238, 2.24592786,2.24342836,2.24093382,2.23844420,2.23595946,2.23347956,2.23100444,2.22853408, 2.22606842,2.22360742,2.22115104,2.21869925,2.21625199,2.21380924,2.21137096,2.20893709, 2.20650761,2.20408248,2.20166166,2.19924511,2.19683280,2.19442469,2.19202074,2.18962092, 2.18722520,2.18483354,2.18244590,2.18006225,2.17768257,2.17530680,2.17293493,2.17056692, 2.16820274,2.16584236,2.16348574,2.16113285,2.15878367,2.15643816,2.15409630,2.15175805, 2.14942338,2.14709226,2.14476468,2.14244059,2.14011997,2.13780279,2.13548903,2.13317865, 2.13087163,2.12856795,2.12626757,2.12397047,2.12167662,2.11938600,2.11709859,2.11481435, 2.11253326,2.11025530,2.10798044,2.10570867,2.10343994,2.10117424,2.09891156,2.09665185, 2.09439510,2.09214129,2.08989040,2.08764239,2.08539725,2.08315496,2.08091550,2.07867884, 2.07644495,2.07421383,2.07198545,2.06975978,2.06753681,2.06531651,2.06309887,2.06088387, 2.05867147,2.05646168,2.05425445,2.05204979,2.04984765,2.04764804,2.04545092,2.04325628, 2.04106409,2.03887435,2.03668703,2.03450211,2.03231957,2.03013941,2.02796159,2.02578610, 2.02361292,2.02144204,2.01927344,2.01710710,2.01494300,2.01278113,2.01062146,2.00846399, 2.00630870,2.00415556,2.00200457,1.99985570,1.99770895,1.99556429,1.99342171,1.99128119, 1.98914271,1.98700627,1.98487185,1.98273942,1.98060898,1.97848051,1.97635399,1.97422942, 1.97210676,1.96998602,1.96786718,1.96575021,1.96363511,1.96152187,1.95941046,1.95730088, 1.95519310,1.95308712,1.95098292,1.94888050,1.94677982,1.94468089,1.94258368,1.94048818, 1.93839439,1.93630228,1.93421185,1.93212308,1.93003595,1.92795046,1.92586659,1.92378433, 1.92170367,1.91962459,1.91754708,1.91547113,1.91339673,1.91132385,1.90925250,1.90718266, 1.90511432,1.90304746,1.90098208,1.89891815,1.89685568,1.89479464,1.89273503,1.89067683, 1.88862003,1.88656463,1.88451060,1.88245794,1.88040664,1.87835668,1.87630806,1.87426076, 1.87221477,1.87017008,1.86812668,1.86608457,1.86404371,1.86200412,1.85996577,1.85792866, 1.85589277,1.85385809,1.85182462,1.84979234,1.84776125,1.84573132,1.84370256,1.84167495, 1.83964848,1.83762314,1.83559892,1.83357582,1.83155381,1.82953289,1.82751305,1.82549429, 1.82347658,1.82145993,1.81944431,1.81742973,1.81541617,1.81340362,1.81139207,1.80938151, 1.80737194,1.80536334,1.80335570,1.80134902,1.79934328,1.79733848,1.79533460,1.79333164, 1.79132959,1.78932843,1.78732817,1.78532878,1.78333027,1.78133261,1.77933581,1.77733985, 1.77534473,1.77335043,1.77135695,1.76936428,1.76737240,1.76538132,1.76339101,1.76140148, 1.75941271,1.75742470,1.75543743,1.75345090,1.75146510,1.74948002,1.74749565,1.74551198, 1.74352900,1.74154672,1.73956511,1.73758417,1.73560389,1.73362426,1.73164527,1.72966692, 1.72768920,1.72571209,1.72373560,1.72175971,1.71978441,1.71780969,1.71583556,1.71386199, 1.71188899,1.70991653,1.70794462,1.70597325,1.70400241,1.70203209,1.70006228,1.69809297, 1.69612416,1.69415584,1.69218799,1.69022062,1.68825372,1.68628727,1.68432127,1.68235571, 1.68039058,1.67842588,1.67646160,1.67449772,1.67253424,1.67057116,1.66860847,1.66664615, 1.66468420,1.66272262,1.66076139,1.65880050,1.65683996,1.65487975,1.65291986,1.65096028, 1.64900102,1.64704205,1.64508338,1.64312500,1.64116689,1.63920905,1.63725148,1.63529416, 1.63333709,1.63138026,1.62942366,1.62746728,1.62551112,1.62355517,1.62159943,1.61964388, 1.61768851,1.61573332,1.61377831,1.61182346,1.60986877,1.60791422,1.60595982,1.60400556, 1.60205142,1.60009739,1.59814349,1.59618968,1.59423597,1.59228235,1.59032882,1.58837536, 1.58642196,1.58446863,1.58251535,1.58056211,1.57860891,1.57665574,1.57470259,1.57274945, 1.57079633,1.56884320,1.56689007,1.56493692,1.56298375,1.56103055,1.55907731,1.55712403, 1.55517069,1.55321730,1.55126383,1.54931030,1.54735668,1.54540297,1.54344917,1.54149526, 1.53954124,1.53758710,1.53563283,1.53367843,1.53172389,1.52976919,1.52781434,1.52585933, 1.52390414,1.52194878,1.51999323,1.51803748,1.51608153,1.51412537,1.51216900,1.51021240, 1.50825556,1.50629849,1.50434117,1.50238360,1.50042576,1.49846765,1.49650927,1.49455060, 1.49259163,1.49063237,1.48867280,1.48671291,1.48475270,1.48279215,1.48083127,1.47887004, 1.47690845,1.47494650,1.47298419,1.47102149,1.46905841,1.46709493,1.46513106,1.46316677, 1.46120207,1.45923694,1.45727138,1.45530538,1.45333893,1.45137203,1.44940466,1.44743682, 1.44546850,1.44349969,1.44153038,1.43956057,1.43759024,1.43561940,1.43364803,1.43167612, 1.42970367,1.42773066,1.42575709,1.42378296,1.42180825,1.41983295,1.41785705,1.41588056, 1.41390346,1.41192573,1.40994738,1.40796840,1.40598877,1.40400849,1.40202755,1.40004594, 1.39806365,1.39608068,1.39409701,1.39211264,1.39012756,1.38814175,1.38615522,1.38416795, 1.38217994,1.38019117,1.37820164,1.37621134,1.37422025,1.37222837,1.37023570,1.36824222, 1.36624792,1.36425280,1.36225684,1.36026004,1.35826239,1.35626387,1.35426449,1.35226422, 1.35026307,1.34826101,1.34625805,1.34425418,1.34224937,1.34024364,1.33823695,1.33622932, 1.33422072,1.33221114,1.33020059,1.32818904,1.32617649,1.32416292,1.32214834,1.32013273, 1.31811607,1.31609837,1.31407960,1.31205976,1.31003885,1.30801684,1.30599373,1.30396951, 1.30194417,1.29991770,1.29789009,1.29586133,1.29383141,1.29180031,1.28976803,1.28773456, 1.28569989,1.28366400,1.28162688,1.27958854,1.27754894,1.27550809,1.27346597,1.27142257, 1.26937788,1.26733189,1.26528459,1.26323597,1.26118602,1.25913471,1.25708205,1.25502803, 1.25297262,1.25091583,1.24885763,1.24679802,1.24473698,1.24267450,1.24061058,1.23854519, 1.23647833,1.23440999,1.23234015,1.23026880,1.22819593,1.22612152,1.22404557,1.22196806, 1.21988898,1.21780832,1.21572606,1.21364219,1.21155670,1.20946958,1.20738080,1.20529037, 1.20319826,1.20110447,1.19900898,1.19691177,1.19481283,1.19271216,1.19060973,1.18850553, 1.18639955,1.18429178,1.18218219,1.18007079,1.17795754,1.17584244,1.17372548,1.17160663, 1.16948589,1.16736324,1.16523866,1.16311215,1.16098368,1.15885323,1.15672081,1.15458638, 1.15244994,1.15031147,1.14817095,1.14602836,1.14388370,1.14173695,1.13958808,1.13743709, 1.13528396,1.13312866,1.13097119,1.12881153,1.12664966,1.12448556,1.12231921,1.12015061, 1.11797973,1.11580656,1.11363107,1.11145325,1.10927308,1.10709055,1.10490563,1.10271831, 1.10052856,1.09833638,1.09614174,1.09394462,1.09174500,1.08954287,1.08733820,1.08513098, 1.08292118,1.08070879,1.07849378,1.07627614,1.07405585,1.07183287,1.06960721,1.06737882, 1.06514770,1.06291382,1.06067715,1.05843769,1.05619540,1.05395026,1.05170226,1.04945136, 1.04719755,1.04494080,1.04268110,1.04041841,1.03815271,1.03588399,1.03361221,1.03133735, 1.02905939,1.02677830,1.02449407,1.02220665,1.01991603,1.01762219,1.01532509,1.01302471, 1.01072102,1.00841400,1.00610363,1.00378986,1.00147268,0.99915206,0.99682798,0.99450039, 0.99216928,0.98983461,0.98749636,0.98515449,0.98280898,0.98045980,0.97810691,0.97575030, 0.97338991,0.97102573,0.96865772,0.96628585,0.96391009,0.96153040,0.95914675,0.95675912, 0.95436745,0.95197173,0.94957191,0.94716796,0.94475985,0.94234754,0.93993099,0.93751017, 0.93508504,0.93265556,0.93022170,0.92778341,0.92534066,0.92289341,0.92044161,0.91798524, 0.91552424,0.91305858,0.91058821,0.90811309,0.90563319,0.90314845,0.90065884,0.89816430, 0.89566479,0.89316028,0.89065070,0.88813602,0.88561619,0.88309116,0.88056088,0.87802531, 0.87548438,0.87293806,0.87038629,0.86782901,0.86526619,0.86269775,0.86012366,0.85754385, 0.85495827,0.85236686,0.84976956,0.84716633,0.84455709,0.84194179,0.83932037,0.83669277, 0.83405893,0.83141877,0.82877225,0.82611928,0.82345981,0.82079378,0.81812110,0.81544172, 0.81275556,0.81006255,0.80736262,0.80465570,0.80194171,0.79922057,0.79649221,0.79375655, 0.79101352,0.78826302,0.78550497,0.78273931,0.77996593,0.77718475,0.77439569,0.77159865, 0.76879355,0.76598029,0.76315878,0.76032891,0.75749061,0.75464376,0.75178826,0.74892402, 0.74605092,0.74316887,0.74027775,0.73737744,0.73446785,0.73154885,0.72862033,0.72568217, 0.72273425,0.71977644,0.71680861,0.71383064,0.71084240,0.70784376,0.70483456,0.70181469, 0.69878398,0.69574231,0.69268952,0.68962545,0.68654996,0.68346288,0.68036406,0.67725332, 0.67413051,0.67099544,0.66784794,0.66468783,0.66151492,0.65832903,0.65512997,0.65191753, 0.64869151,0.64545170,0.64219789,0.63892987,0.63564741,0.63235028,0.62903824,0.62571106, 0.62236849,0.61901027,0.61563615,0.61224585,0.60883911,0.60541564,0.60197515,0.59851735, 0.59504192,0.59154856,0.58803694,0.58450672,0.58095756,0.57738911,0.57380101,0.57019288, 0.56656433,0.56291496,0.55924437,0.55555212,0.55183778,0.54810089,0.54434099,0.54055758, 0.53675018,0.53291825,0.52906127,0.52517867,0.52126988,0.51733431,0.51337132,0.50938028, 0.50536051,0.50131132,0.49723200,0.49312177,0.48897987,0.48480547,0.48059772,0.47635573, 0.47207859,0.46776530,0.46341487,0.45902623,0.45459827,0.45012983,0.44561967,0.44106652, 0.43646903,0.43182577,0.42713525,0.42239588,0.41760600,0.41276385,0.40786755,0.40291513, 0.39790449,0.39283339,0.38769946,0.38250016,0.37723277,0.37189441,0.36648196,0.36099209, 0.35542120,0.34976542,0.34402054,0.33818204,0.33224495,0.32620390,0.32005298,0.31378574, 0.30739505,0.30087304,0.29421096,0.28739907,0.28042645,0.27328078,0.26594810,0.25841250, 0.25065566,0.24265636,0.23438976,0.22582651,0.21693146,0.20766198,0.19796546,0.18777575, 0.17700769,0.16554844,0.15324301,0.13986823,0.12508152,0.10830610,0.08841715,0.06251018, } double acos( double x ) { int index; if (x < -1) x = -1; if (x > 1) x = 1; index = (float) (1.0 + x) * 511.9; return acostable[index]; } double atan2( double y, double x ) { float base; float temp; float dir; float test; int i; if ( x < 0 ) { if ( y >= 0 ) { // quad 1 base = M_PI / 2; temp = x; x = y; y = -temp; } else { // quad 2 base = M_PI; x = -x; y = -y; } } else { if ( y < 0 ) { // quad 3 base = 3 * M_PI / 2; temp = x; x = -y; y = temp; } } if ( y > x ) { base += M_PI/2; temp = x; x = y; y = temp; dir = -1; } else { dir = 1; } // calcualte angle in octant 0 if ( x == 0 ) { return base; } y /= x; for ( i = 0 ; i < 512 ; i++ ) { test = sintable[i] / sintable[1023-i]; if ( test > y ) { break; } } return base + dir * i * ( M_PI/2048); } #endif #ifdef Q3_VM // bk001127 - guarded this tan replacement // ld: undefined versioned symbol name tan@@GLIBC_2.0 double tan( double x ) { return sin(x) / cos(x); } #endif static int randSeed = 0; void srand( unsigned seed ) { randSeed = seed; } int rand( void ) { randSeed = (69069 * randSeed + 1); return randSeed & 0x7fff; } double atof( const char *string ) { float sign; float value; int c; // skip whitespace while ( *string <= ' ' ) { if ( !*string ) { return 0; } string++; } // check sign switch ( *string ) { case '+': string++; sign = 1; break; case '-': string++; sign = -1; break; default: sign = 1; break; } // read digits value = 0; c = string[0]; if ( c != '.' ) { do { c = *string++; if ( c < '0' || c > '9' ) { break; } c -= '0'; value = value * 10 + c; } while ( 1 ); } else { string++; } // check for decimal point if ( c == '.' ) { double fraction; fraction = 0.1; do { c = *string++; if ( c < '0' || c > '9' ) { break; } c -= '0'; value += c * fraction; fraction *= 0.1; } while ( 1 ); } // not handling 10e10 notation... return value * sign; } double _atof( const char **stringPtr ) { const char *string; float sign; float value; int c = '0'; // bk001211 - uninitialized use possible string = *stringPtr; // skip whitespace while ( *string <= ' ' ) { if ( !*string ) { *stringPtr = string; return 0; } string++; } // check sign switch ( *string ) { case '+': string++; sign = 1; break; case '-': string++; sign = -1; break; default: sign = 1; break; } // read digits value = 0; if ( string[0] != '.' ) { do { c = *string++; if ( c < '0' || c > '9' ) { break; } c -= '0'; value = value * 10 + c; } while ( 1 ); } // check for decimal point if ( c == '.' ) { double fraction; fraction = 0.1; do { c = *string++; if ( c < '0' || c > '9' ) { break; } c -= '0'; value += c * fraction; fraction *= 0.1; } while ( 1 ); } // not handling 10e10 notation... *stringPtr = string; return value * sign; } // bk001120 - presumably needed for Mac //#if !defined ( _MSC_VER ) && ! defined ( __linux__ ) // bk001127 - undid undo #if defined ( Q3_VM ) int atoi( const char *string ) { int sign; int value; int c; // skip whitespace while ( *string <= ' ' ) { if ( !*string ) { return 0; } string++; } // check sign switch ( *string ) { case '+': string++; sign = 1; break; case '-': string++; sign = -1; break; default: sign = 1; break; } // read digits value = 0; do { c = *string++; if ( c < '0' || c > '9' ) { break; } c -= '0'; value = value * 10 + c; } while ( 1 ); // not handling 10e10 notation... return value * sign; } int _atoi( const char **stringPtr ) { int sign; int value; int c; const char *string; string = *stringPtr; // skip whitespace while ( *string <= ' ' ) { if ( !*string ) { return 0; } string++; } // check sign switch ( *string ) { case '+': string++; sign = 1; break; case '-': string++; sign = -1; break; default: sign = 1; break; } // read digits value = 0; do { c = *string++; if ( c < '0' || c > '9' ) { break; } c -= '0'; value = value * 10 + c; } while ( 1 ); // not handling 10e10 notation... *stringPtr = string; return value * sign; } int abs( int n ) { return n < 0 ? -n : n; } double fabs( double x ) { return x < 0 ? -x : x; } //========================================================= #define ALT 0x00000001 /* alternate form */ #define HEXPREFIX 0x00000002 /* add 0x or 0X prefix */ #define LADJUST 0x00000004 /* left adjustment */ #define LONGDBL 0x00000008 /* long double */ #define LONGINT 0x00000010 /* long integer */ #define QUADINT 0x00000020 /* quad integer */ #define SHORTINT 0x00000040 /* short integer */ #define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ #define FPT 0x00000100 /* floating point number */ #define to_digit(c) ((c) - '0') #define is_digit(c) ((unsigned)to_digit(c) <= 9) #define to_char(n) ((n) + '0') void AddInt( char **buf_p, int val, int width, int flags ) { char text[32]; int digits; int signedVal; char *buf; digits = 0; signedVal = val; if ( val < 0 ) { val = -val; } do { text[digits++] = '0' + val % 10; val /= 10; } while ( val ); if ( signedVal < 0 ) { text[digits++] = '-'; } buf = *buf_p; if( !( flags & LADJUST ) ) { while ( digits < width ) { *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; width--; } } while ( digits-- ) { *buf++ = text[digits]; width--; } if( flags & LADJUST ) { while ( width-- ) { *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; } } *buf_p = buf; } void AddFloat( char **buf_p, float fval, int width, int prec ) { char text[32]; int digits; float signedVal; char *buf; int val; // get the sign signedVal = fval; if ( fval < 0 ) { fval = -fval; } // write the float number digits = 0; val = (int)fval; do { text[digits++] = '0' + val % 10; val /= 10; } while ( val ); if ( signedVal < 0 ) { text[digits++] = '-'; } buf = *buf_p; while ( digits < width ) { *buf++ = ' '; width--; } while ( digits-- ) { *buf++ = text[digits]; } *buf_p = buf; if (prec < 0) prec = 6; // write the fraction digits = 0; while (digits < prec) { fval -= (int) fval; fval *= 10.0; val = (int) fval; text[digits++] = '0' + val % 10; } if (digits > 0) { buf = *buf_p; *buf++ = '.'; for (prec = 0; prec < digits; prec++) { *buf++ = text[prec]; } *buf_p = buf; } } void AddString( char **buf_p, char *string, int width, int prec ) { int size; char *buf; buf = *buf_p; if ( string == NULL ) { string = "(null)"; prec = -1; } if ( prec >= 0 ) { for( size = 0; size < prec; size++ ) { if( string[size] == '\0' ) { break; } } } else { size = strlen( string ); } width -= size; while( size-- ) { *buf++ = *string++; } while( width-- > 0 ) { *buf++ = ' '; } *buf_p = buf; } /* vsprintf I'm not going to support a bunch of the more arcane stuff in here just to keep it simpler. For example, the '*' and '$' are not currently supported. I've tried to make it so that it will just parse and ignore formats we don't support. */ int vsprintf( char *buffer, const char *fmt, va_list argptr ) { int *arg; char *buf_p; char ch; int flags; int width; int prec; int n; char sign; buf_p = buffer; arg = (int *)argptr; while( qtrue ) { // run through the format string until we hit a '%' or '\0' for ( ch = *fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++ ) { *buf_p++ = ch; } if ( ch == '\0' ) { goto done; } // skip over the '%' fmt++; // reset formatting state flags = 0; width = 0; prec = -1; sign = '\0'; rflag: ch = *fmt++; reswitch: switch( ch ) { case '-': flags |= LADJUST; goto rflag; case '.': n = 0; while( is_digit( ( ch = *fmt++ ) ) ) { n = 10 * n + ( ch - '0' ); } prec = n < 0 ? -1 : n; goto reswitch; case '0': flags |= ZEROPAD; goto rflag; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': n = 0; do { n = 10 * n + ( ch - '0' ); ch = *fmt++; } while( is_digit( ch ) ); width = n; goto reswitch; case 'c': *buf_p++ = (char)*arg; arg++; break; case 'd': case 'i': AddInt( &buf_p, *arg, width, flags ); arg++; break; case 'f': AddFloat( &buf_p, *(double *)arg, width, prec ); #ifdef __LCC__ arg += 1; // everything is 32 bit in my compiler #else arg += 2; #endif break; case 's': AddString( &buf_p, (char *)*arg, width, prec ); arg++; break; case '%': *buf_p++ = ch; break; default: *buf_p++ = (char)*arg; arg++; break; } } done: *buf_p = 0; return buf_p - buffer; } /* this is really crappy */ int sscanf( const char *buffer, const char *fmt, ... ) { int cmd; int **arg; int count; arg = (int **)&fmt + 1; count = 0; while ( *fmt ) { if ( fmt[0] != '%' ) { fmt++; continue; } cmd = fmt[1]; fmt += 2; switch ( cmd ) { case 'i': case 'd': case 'u': **arg = _atoi( &buffer ); break; case 'f': *(float *)*arg = _atof( &buffer ); break; } arg++; } return count; } #endif ================================================ FILE: code/game/bg_lib.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // bg_lib.h -- standard C library replacement routines used by code // compiled for the virtual machine // This file is NOT included on native builds typedef int size_t; typedef char * va_list; #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define va_end(ap) ( ap = (va_list)0 ) #define CHAR_BIT 8 /* number of bits in a char */ #define SCHAR_MIN (-128) /* minimum signed char value */ #define SCHAR_MAX 127 /* maximum signed char value */ #define UCHAR_MAX 0xff /* maximum unsigned char value */ #define SHRT_MIN (-32768) /* minimum (signed) short value */ #define SHRT_MAX 32767 /* maximum (signed) short value */ #define USHRT_MAX 0xffff /* maximum unsigned short value */ #define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ #define INT_MAX 2147483647 /* maximum (signed) int value */ #define UINT_MAX 0xffffffff /* maximum unsigned int value */ #define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */ #define LONG_MAX 2147483647L /* maximum (signed) long value */ #define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */ // Misc functions typedef int cmp_t(const void *, const void *); void qsort(void *a, size_t n, size_t es, cmp_t *cmp); void srand( unsigned seed ); int rand( void ); // String functions size_t strlen( const char *string ); char *strcat( char *strDestination, const char *strSource ); char *strcpy( char *strDestination, const char *strSource ); int strcmp( const char *string1, const char *string2 ); char *strchr( const char *string, int c ); char *strstr( const char *string, const char *strCharSet ); char *strncpy( char *strDest, const char *strSource, size_t count ); int tolower( int c ); int toupper( int c ); double atof( const char *string ); double _atof( const char **stringPtr ); int atoi( const char *string ); int _atoi( const char **stringPtr ); int vsprintf( char *buffer, const char *fmt, va_list argptr ); int sscanf( const char *buffer, const char *fmt, ... ); // Memory functions void *memmove( void *dest, const void *src, size_t count ); void *memset( void *dest, int c, size_t count ); void *memcpy( void *dest, const void *src, size_t count ); // Math functions double ceil( double x ); double floor( double x ); double sqrt( double x ); double sin( double x ); double cos( double x ); double atan2( double y, double x ); double tan( double x ); int abs( int n ); double fabs( double x ); double acos( double x ); ================================================ FILE: code/game/bg_local.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // bg_local.h -- local definitions for the bg (both games) files #define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes #define STEPSIZE 18 #define JUMP_VELOCITY 270 #define TIMER_LAND 130 #define TIMER_GESTURE (34*66+50) #define OVERCLIP 1.001f // all of the locals will be zeroed before each // pmove, just to make damn sure we don't have // any differences when running on client or server typedef struct { vec3_t forward, right, up; float frametime; int msec; qboolean walking; qboolean groundPlane; trace_t groundTrace; float impactSpeed; vec3_t previous_origin; vec3_t previous_velocity; int previous_waterlevel; } pml_t; extern pmove_t *pm; extern pml_t pml; // movement parameters extern float pm_stopspeed; extern float pm_duckScale; extern float pm_swimScale; extern float pm_wadeScale; extern float pm_accelerate; extern float pm_airaccelerate; extern float pm_wateraccelerate; extern float pm_flyaccelerate; extern float pm_friction; extern float pm_waterfriction; extern float pm_flightfriction; extern int c_pmove; void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); void PM_AddTouchEnt( int entityNum ); void PM_AddEvent( int newEvent ); qboolean PM_SlideMove( qboolean gravity ); void PM_StepSlideMove( qboolean gravity ); ================================================ FILE: code/game/bg_misc.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // bg_misc.c -- both games misc functions, all completely stateless #include "q_shared.h" #include "bg_public.h" /*QUAKED item_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) suspended DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION. The suspended flag will allow items to hang in the air, otherwise they are dropped to the next surface. If an item is the target of another entity, it will not spawn in until fired. An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired. "notfree" if set to 1, don't spawn in free for all games "notteam" if set to 1, don't spawn in team games "notsingle" if set to 1, don't spawn in single player games "wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning. "random" random number of plus or minus seconds varied from the respawn time "count" override quantity or duration on most items. */ gitem_t bg_itemlist[] = { { NULL, NULL, { NULL, NULL, 0, 0} , /* icon */ NULL, /* pickup */ NULL, 0, 0, 0, /* precache */ "", /* sounds */ "" }, // leave index 0 alone // // ARMOR // /*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_armor_shard", "sound/misc/ar1_pkup.wav", { "models/powerups/armor/shard.md3", "models/powerups/armor/shard_sphere.md3", 0, 0} , /* icon */ "icons/iconr_shard", /* pickup */ "Armor Shard", 5, IT_ARMOR, 0, /* precache */ "", /* sounds */ "" }, /*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_armor_combat", "sound/misc/ar2_pkup.wav", { "models/powerups/armor/armor_yel.md3", 0, 0, 0}, /* icon */ "icons/iconr_yellow", /* pickup */ "Armor", 50, IT_ARMOR, 0, /* precache */ "", /* sounds */ "" }, /*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_armor_body", "sound/misc/ar2_pkup.wav", { "models/powerups/armor/armor_red.md3", 0, 0, 0}, /* icon */ "icons/iconr_red", /* pickup */ "Heavy Armor", 100, IT_ARMOR, 0, /* precache */ "", /* sounds */ "" }, // // health // /*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_health_small", "sound/items/s_health.wav", { "models/powerups/health/small_cross.md3", "models/powerups/health/small_sphere.md3", 0, 0 }, /* icon */ "icons/iconh_green", /* pickup */ "5 Health", 5, IT_HEALTH, 0, /* precache */ "", /* sounds */ "" }, /*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_health", "sound/items/n_health.wav", { "models/powerups/health/medium_cross.md3", "models/powerups/health/medium_sphere.md3", 0, 0 }, /* icon */ "icons/iconh_yellow", /* pickup */ "25 Health", 25, IT_HEALTH, 0, /* precache */ "", /* sounds */ "" }, /*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_health_large", "sound/items/l_health.wav", { "models/powerups/health/large_cross.md3", "models/powerups/health/large_sphere.md3", 0, 0 }, /* icon */ "icons/iconh_red", /* pickup */ "50 Health", 50, IT_HEALTH, 0, /* precache */ "", /* sounds */ "" }, /*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_health_mega", "sound/items/m_health.wav", { "models/powerups/health/mega_cross.md3", "models/powerups/health/mega_sphere.md3", 0, 0 }, /* icon */ "icons/iconh_mega", /* pickup */ "Mega Health", 100, IT_HEALTH, 0, /* precache */ "", /* sounds */ "" }, // // WEAPONS // /*QUAKED weapon_gauntlet (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_gauntlet", "sound/misc/w_pkup.wav", { "models/weapons2/gauntlet/gauntlet.md3", 0, 0, 0}, /* icon */ "icons/iconw_gauntlet", /* pickup */ "Gauntlet", 0, IT_WEAPON, WP_GAUNTLET, /* precache */ "", /* sounds */ "" }, /*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_shotgun", "sound/misc/w_pkup.wav", { "models/weapons2/shotgun/shotgun.md3", 0, 0, 0}, /* icon */ "icons/iconw_shotgun", /* pickup */ "Shotgun", 10, IT_WEAPON, WP_SHOTGUN, /* precache */ "", /* sounds */ "" }, /*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_machinegun", "sound/misc/w_pkup.wav", { "models/weapons2/machinegun/machinegun.md3", 0, 0, 0}, /* icon */ "icons/iconw_machinegun", /* pickup */ "Machinegun", 40, IT_WEAPON, WP_MACHINEGUN, /* precache */ "", /* sounds */ "" }, /*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_grenadelauncher", "sound/misc/w_pkup.wav", { "models/weapons2/grenadel/grenadel.md3", 0, 0, 0}, /* icon */ "icons/iconw_grenade", /* pickup */ "Grenade Launcher", 10, IT_WEAPON, WP_GRENADE_LAUNCHER, /* precache */ "", /* sounds */ "sound/weapons/grenade/hgrenb1a.wav sound/weapons/grenade/hgrenb2a.wav" }, /*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_rocketlauncher", "sound/misc/w_pkup.wav", { "models/weapons2/rocketl/rocketl.md3", 0, 0, 0}, /* icon */ "icons/iconw_rocket", /* pickup */ "Rocket Launcher", 10, IT_WEAPON, WP_ROCKET_LAUNCHER, /* precache */ "", /* sounds */ "" }, /*QUAKED weapon_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_lightning", "sound/misc/w_pkup.wav", { "models/weapons2/lightning/lightning.md3", 0, 0, 0}, /* icon */ "icons/iconw_lightning", /* pickup */ "Lightning Gun", 100, IT_WEAPON, WP_LIGHTNING, /* precache */ "", /* sounds */ "" }, /*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_railgun", "sound/misc/w_pkup.wav", { "models/weapons2/railgun/railgun.md3", 0, 0, 0}, /* icon */ "icons/iconw_railgun", /* pickup */ "Railgun", 10, IT_WEAPON, WP_RAILGUN, /* precache */ "", /* sounds */ "" }, /*QUAKED weapon_plasmagun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_plasmagun", "sound/misc/w_pkup.wav", { "models/weapons2/plasma/plasma.md3", 0, 0, 0}, /* icon */ "icons/iconw_plasma", /* pickup */ "Plasma Gun", 50, IT_WEAPON, WP_PLASMAGUN, /* precache */ "", /* sounds */ "" }, /*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_bfg", "sound/misc/w_pkup.wav", { "models/weapons2/bfg/bfg.md3", 0, 0, 0}, /* icon */ "icons/iconw_bfg", /* pickup */ "BFG10K", 20, IT_WEAPON, WP_BFG, /* precache */ "", /* sounds */ "" }, /*QUAKED weapon_grapplinghook (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_grapplinghook", "sound/misc/w_pkup.wav", { "models/weapons2/grapple/grapple.md3", 0, 0, 0}, /* icon */ "icons/iconw_grapple", /* pickup */ "Grappling Hook", 0, IT_WEAPON, WP_GRAPPLING_HOOK, /* precache */ "", /* sounds */ "" }, // // AMMO ITEMS // /*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "ammo_shells", "sound/misc/am_pkup.wav", { "models/powerups/ammo/shotgunam.md3", 0, 0, 0}, /* icon */ "icons/icona_shotgun", /* pickup */ "Shells", 10, IT_AMMO, WP_SHOTGUN, /* precache */ "", /* sounds */ "" }, /*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "ammo_bullets", "sound/misc/am_pkup.wav", { "models/powerups/ammo/machinegunam.md3", 0, 0, 0}, /* icon */ "icons/icona_machinegun", /* pickup */ "Bullets", 50, IT_AMMO, WP_MACHINEGUN, /* precache */ "", /* sounds */ "" }, /*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "ammo_grenades", "sound/misc/am_pkup.wav", { "models/powerups/ammo/grenadeam.md3", 0, 0, 0}, /* icon */ "icons/icona_grenade", /* pickup */ "Grenades", 5, IT_AMMO, WP_GRENADE_LAUNCHER, /* precache */ "", /* sounds */ "" }, /*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "ammo_cells", "sound/misc/am_pkup.wav", { "models/powerups/ammo/plasmaam.md3", 0, 0, 0}, /* icon */ "icons/icona_plasma", /* pickup */ "Cells", 30, IT_AMMO, WP_PLASMAGUN, /* precache */ "", /* sounds */ "" }, /*QUAKED ammo_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "ammo_lightning", "sound/misc/am_pkup.wav", { "models/powerups/ammo/lightningam.md3", 0, 0, 0}, /* icon */ "icons/icona_lightning", /* pickup */ "Lightning", 60, IT_AMMO, WP_LIGHTNING, /* precache */ "", /* sounds */ "" }, /*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "ammo_rockets", "sound/misc/am_pkup.wav", { "models/powerups/ammo/rocketam.md3", 0, 0, 0}, /* icon */ "icons/icona_rocket", /* pickup */ "Rockets", 5, IT_AMMO, WP_ROCKET_LAUNCHER, /* precache */ "", /* sounds */ "" }, /*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "ammo_slugs", "sound/misc/am_pkup.wav", { "models/powerups/ammo/railgunam.md3", 0, 0, 0}, /* icon */ "icons/icona_railgun", /* pickup */ "Slugs", 10, IT_AMMO, WP_RAILGUN, /* precache */ "", /* sounds */ "" }, /*QUAKED ammo_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "ammo_bfg", "sound/misc/am_pkup.wav", { "models/powerups/ammo/bfgam.md3", 0, 0, 0}, /* icon */ "icons/icona_bfg", /* pickup */ "Bfg Ammo", 15, IT_AMMO, WP_BFG, /* precache */ "", /* sounds */ "" }, // // HOLDABLE ITEMS // /*QUAKED holdable_teleporter (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "holdable_teleporter", "sound/items/holdable.wav", { "models/powerups/holdable/teleporter.md3", 0, 0, 0}, /* icon */ "icons/teleporter", /* pickup */ "Personal Teleporter", 60, IT_HOLDABLE, HI_TELEPORTER, /* precache */ "", /* sounds */ "" }, /*QUAKED holdable_medkit (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "holdable_medkit", "sound/items/holdable.wav", { "models/powerups/holdable/medkit.md3", "models/powerups/holdable/medkit_sphere.md3", 0, 0}, /* icon */ "icons/medkit", /* pickup */ "Medkit", 60, IT_HOLDABLE, HI_MEDKIT, /* precache */ "", /* sounds */ "sound/items/use_medkit.wav" }, // // POWERUP ITEMS // /*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_quad", "sound/items/quaddamage.wav", { "models/powerups/instant/quad.md3", "models/powerups/instant/quad_ring.md3", 0, 0 }, /* icon */ "icons/quad", /* pickup */ "Quad Damage", 30, IT_POWERUP, PW_QUAD, /* precache */ "", /* sounds */ "sound/items/damage2.wav sound/items/damage3.wav" }, /*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_enviro", "sound/items/protect.wav", { "models/powerups/instant/enviro.md3", "models/powerups/instant/enviro_ring.md3", 0, 0 }, /* icon */ "icons/envirosuit", /* pickup */ "Battle Suit", 30, IT_POWERUP, PW_BATTLESUIT, /* precache */ "", /* sounds */ "sound/items/airout.wav sound/items/protect3.wav" }, /*QUAKED item_haste (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_haste", "sound/items/haste.wav", { "models/powerups/instant/haste.md3", "models/powerups/instant/haste_ring.md3", 0, 0 }, /* icon */ "icons/haste", /* pickup */ "Speed", 30, IT_POWERUP, PW_HASTE, /* precache */ "", /* sounds */ "" }, /*QUAKED item_invis (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_invis", "sound/items/invisibility.wav", { "models/powerups/instant/invis.md3", "models/powerups/instant/invis_ring.md3", 0, 0 }, /* icon */ "icons/invis", /* pickup */ "Invisibility", 30, IT_POWERUP, PW_INVIS, /* precache */ "", /* sounds */ "" }, /*QUAKED item_regen (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_regen", "sound/items/regeneration.wav", { "models/powerups/instant/regen.md3", "models/powerups/instant/regen_ring.md3", 0, 0 }, /* icon */ "icons/regen", /* pickup */ "Regeneration", 30, IT_POWERUP, PW_REGEN, /* precache */ "", /* sounds */ "sound/items/regen.wav" }, /*QUAKED item_flight (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "item_flight", "sound/items/flight.wav", { "models/powerups/instant/flight.md3", "models/powerups/instant/flight_ring.md3", 0, 0 }, /* icon */ "icons/flight", /* pickup */ "Flight", 60, IT_POWERUP, PW_FLIGHT, /* precache */ "", /* sounds */ "sound/items/flight.wav" }, /*QUAKED team_CTF_redflag (1 0 0) (-16 -16 -16) (16 16 16) Only in CTF games */ { "team_CTF_redflag", NULL, { "models/flags/r_flag.md3", 0, 0, 0 }, /* icon */ "icons/iconf_red1", /* pickup */ "Red Flag", 0, IT_TEAM, PW_REDFLAG, /* precache */ "", /* sounds */ "" }, /*QUAKED team_CTF_blueflag (0 0 1) (-16 -16 -16) (16 16 16) Only in CTF games */ { "team_CTF_blueflag", NULL, { "models/flags/b_flag.md3", 0, 0, 0 }, /* icon */ "icons/iconf_blu1", /* pickup */ "Blue Flag", 0, IT_TEAM, PW_BLUEFLAG, /* precache */ "", /* sounds */ "" }, #ifdef MISSIONPACK /*QUAKED holdable_kamikaze (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "holdable_kamikaze", "sound/items/holdable.wav", { "models/powerups/kamikazi.md3", 0, 0, 0}, /* icon */ "icons/kamikaze", /* pickup */ "Kamikaze", 60, IT_HOLDABLE, HI_KAMIKAZE, /* precache */ "", /* sounds */ "sound/items/kamikazerespawn.wav" }, /*QUAKED holdable_portal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "holdable_portal", "sound/items/holdable.wav", { "models/powerups/holdable/porter.md3", 0, 0, 0}, /* icon */ "icons/portal", /* pickup */ "Portal", 60, IT_HOLDABLE, HI_PORTAL, /* precache */ "", /* sounds */ "" }, /*QUAKED holdable_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "holdable_invulnerability", "sound/items/holdable.wav", { "models/powerups/holdable/invulnerability.md3", 0, 0, 0}, /* icon */ "icons/invulnerability", /* pickup */ "Invulnerability", 60, IT_HOLDABLE, HI_INVULNERABILITY, /* precache */ "", /* sounds */ "" }, /*QUAKED ammo_nails (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "ammo_nails", "sound/misc/am_pkup.wav", { "models/powerups/ammo/nailgunam.md3", 0, 0, 0}, /* icon */ "icons/icona_nailgun", /* pickup */ "Nails", 20, IT_AMMO, WP_NAILGUN, /* precache */ "", /* sounds */ "" }, /*QUAKED ammo_mines (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "ammo_mines", "sound/misc/am_pkup.wav", { "models/powerups/ammo/proxmineam.md3", 0, 0, 0}, /* icon */ "icons/icona_proxlauncher", /* pickup */ "Proximity Mines", 10, IT_AMMO, WP_PROX_LAUNCHER, /* precache */ "", /* sounds */ "" }, /*QUAKED ammo_belt (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "ammo_belt", "sound/misc/am_pkup.wav", { "models/powerups/ammo/chaingunam.md3", 0, 0, 0}, /* icon */ "icons/icona_chaingun", /* pickup */ "Chaingun Belt", 100, IT_AMMO, WP_CHAINGUN, /* precache */ "", /* sounds */ "" }, // // PERSISTANT POWERUP ITEMS // /*QUAKED item_scout (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam */ { "item_scout", "sound/items/scout.wav", { "models/powerups/scout.md3", 0, 0, 0 }, /* icon */ "icons/scout", /* pickup */ "Scout", 30, IT_PERSISTANT_POWERUP, PW_SCOUT, /* precache */ "", /* sounds */ "" }, /*QUAKED item_guard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam */ { "item_guard", "sound/items/guard.wav", { "models/powerups/guard.md3", 0, 0, 0 }, /* icon */ "icons/guard", /* pickup */ "Guard", 30, IT_PERSISTANT_POWERUP, PW_GUARD, /* precache */ "", /* sounds */ "" }, /*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam */ { "item_doubler", "sound/items/doubler.wav", { "models/powerups/doubler.md3", 0, 0, 0 }, /* icon */ "icons/doubler", /* pickup */ "Doubler", 30, IT_PERSISTANT_POWERUP, PW_DOUBLER, /* precache */ "", /* sounds */ "" }, /*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam */ { "item_ammoregen", "sound/items/ammoregen.wav", { "models/powerups/ammo.md3", 0, 0, 0 }, /* icon */ "icons/ammo_regen", /* pickup */ "Ammo Regen", 30, IT_PERSISTANT_POWERUP, PW_AMMOREGEN, /* precache */ "", /* sounds */ "" }, /*QUAKED team_CTF_neutralflag (0 0 1) (-16 -16 -16) (16 16 16) Only in One Flag CTF games */ { "team_CTF_neutralflag", NULL, { "models/flags/n_flag.md3", 0, 0, 0 }, /* icon */ "icons/iconf_neutral1", /* pickup */ "Neutral Flag", 0, IT_TEAM, PW_NEUTRALFLAG, /* precache */ "", /* sounds */ "" }, { "item_redcube", "sound/misc/am_pkup.wav", { "models/powerups/orb/r_orb.md3", 0, 0, 0 }, /* icon */ "icons/iconh_rorb", /* pickup */ "Red Cube", 0, IT_TEAM, 0, /* precache */ "", /* sounds */ "" }, { "item_bluecube", "sound/misc/am_pkup.wav", { "models/powerups/orb/b_orb.md3", 0, 0, 0 }, /* icon */ "icons/iconh_borb", /* pickup */ "Blue Cube", 0, IT_TEAM, 0, /* precache */ "", /* sounds */ "" }, /*QUAKED weapon_nailgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_nailgun", "sound/misc/w_pkup.wav", { "models/weapons/nailgun/nailgun.md3", 0, 0, 0}, /* icon */ "icons/iconw_nailgun", /* pickup */ "Nailgun", 10, IT_WEAPON, WP_NAILGUN, /* precache */ "", /* sounds */ "" }, /*QUAKED weapon_prox_launcher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_prox_launcher", "sound/misc/w_pkup.wav", { "models/weapons/proxmine/proxmine.md3", 0, 0, 0}, /* icon */ "icons/iconw_proxlauncher", /* pickup */ "Prox Launcher", 5, IT_WEAPON, WP_PROX_LAUNCHER, /* precache */ "", /* sounds */ "sound/weapons/proxmine/wstbtick.wav " "sound/weapons/proxmine/wstbactv.wav " "sound/weapons/proxmine/wstbimpl.wav " "sound/weapons/proxmine/wstbimpm.wav " "sound/weapons/proxmine/wstbimpd.wav " "sound/weapons/proxmine/wstbactv.wav" }, /*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended */ { "weapon_chaingun", "sound/misc/w_pkup.wav", { "models/weapons/vulcan/vulcan.md3", 0, 0, 0}, /* icon */ "icons/iconw_chaingun", /* pickup */ "Chaingun", 80, IT_WEAPON, WP_CHAINGUN, /* precache */ "", /* sounds */ "sound/weapons/vulcan/wvulwind.wav" }, #endif // end of list marker {NULL} }; int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) - 1; /* ============== BG_FindItemForPowerup ============== */ gitem_t *BG_FindItemForPowerup( powerup_t pw ) { int i; for ( i = 0 ; i < bg_numItems ; i++ ) { if ( (bg_itemlist[i].giType == IT_POWERUP || bg_itemlist[i].giType == IT_TEAM || bg_itemlist[i].giType == IT_PERSISTANT_POWERUP) && bg_itemlist[i].giTag == pw ) { return &bg_itemlist[i]; } } return NULL; } /* ============== BG_FindItemForHoldable ============== */ gitem_t *BG_FindItemForHoldable( holdable_t pw ) { int i; for ( i = 0 ; i < bg_numItems ; i++ ) { if ( bg_itemlist[i].giType == IT_HOLDABLE && bg_itemlist[i].giTag == pw ) { return &bg_itemlist[i]; } } Com_Error( ERR_DROP, "HoldableItem not found" ); return NULL; } /* =============== BG_FindItemForWeapon =============== */ gitem_t *BG_FindItemForWeapon( weapon_t weapon ) { gitem_t *it; for ( it = bg_itemlist + 1 ; it->classname ; it++) { if ( it->giType == IT_WEAPON && it->giTag == weapon ) { return it; } } Com_Error( ERR_DROP, "Couldn't find item for weapon %i", weapon); return NULL; } /* =============== BG_FindItem =============== */ gitem_t *BG_FindItem( const char *pickupName ) { gitem_t *it; for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { if ( !Q_stricmp( it->pickup_name, pickupName ) ) return it; } return NULL; } /* ============ BG_PlayerTouchesItem Items can be picked up without actually touching their physical bounds to make grabbing them easier ============ */ qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) { vec3_t origin; BG_EvaluateTrajectory( &item->pos, atTime, origin ); // we are ignoring ducked differences here if ( ps->origin[0] - origin[0] > 44 || ps->origin[0] - origin[0] < -50 || ps->origin[1] - origin[1] > 36 || ps->origin[1] - origin[1] < -36 || ps->origin[2] - origin[2] > 36 || ps->origin[2] - origin[2] < -36 ) { return qfalse; } return qtrue; } /* ================ BG_CanItemBeGrabbed Returns false if the item should not be picked up. This needs to be the same for client side prediction and server use. ================ */ qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ) { gitem_t *item; #ifdef MISSIONPACK int upperBound; #endif if ( ent->modelindex < 1 || ent->modelindex >= bg_numItems ) { Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: index out of range" ); } item = &bg_itemlist[ent->modelindex]; switch( item->giType ) { case IT_WEAPON: return qtrue; // weapons are always picked up case IT_AMMO: if ( ps->ammo[ item->giTag ] >= 200 ) { return qfalse; // can't hold any more } return qtrue; case IT_ARMOR: #ifdef MISSIONPACK if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { return qfalse; } // we also clamp armor to the maxhealth for handicapping if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { upperBound = ps->stats[STAT_MAX_HEALTH]; } else { upperBound = ps->stats[STAT_MAX_HEALTH] * 2; } if ( ps->stats[STAT_ARMOR] >= upperBound ) { return qfalse; } #else if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { return qfalse; } #endif return qtrue; case IT_HEALTH: // small and mega healths will go over the max, otherwise // don't pick up if already at max #ifdef MISSIONPACK if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { upperBound = ps->stats[STAT_MAX_HEALTH]; } else #endif if ( item->quantity == 5 || item->quantity == 100 ) { if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { return qfalse; } return qtrue; } if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { return qfalse; } return qtrue; case IT_POWERUP: return qtrue; // powerups are always picked up #ifdef MISSIONPACK case IT_PERSISTANT_POWERUP: // can only hold one item at a time if ( ps->stats[STAT_PERSISTANT_POWERUP] ) { return qfalse; } // check team only if( ( ent->generic1 & 2 ) && ( ps->persistant[PERS_TEAM] != TEAM_RED ) ) { return qfalse; } if( ( ent->generic1 & 4 ) && ( ps->persistant[PERS_TEAM] != TEAM_BLUE ) ) { return qfalse; } return qtrue; #endif case IT_TEAM: // team items, such as flags #ifdef MISSIONPACK if( gametype == GT_1FCTF ) { // neutral flag can always be picked up if( item->giTag == PW_NEUTRALFLAG ) { return qtrue; } if (ps->persistant[PERS_TEAM] == TEAM_RED) { if (item->giTag == PW_BLUEFLAG && ps->powerups[PW_NEUTRALFLAG] ) { return qtrue; } } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { if (item->giTag == PW_REDFLAG && ps->powerups[PW_NEUTRALFLAG] ) { return qtrue; } } } #endif if( gametype == GT_CTF ) { // ent->modelindex2 is non-zero on items if they are dropped // we need to know this because we can pick up our dropped flag (and return it) // but we can't pick up our flag at base if (ps->persistant[PERS_TEAM] == TEAM_RED) { if (item->giTag == PW_BLUEFLAG || (item->giTag == PW_REDFLAG && ent->modelindex2) || (item->giTag == PW_REDFLAG && ps->powerups[PW_BLUEFLAG]) ) return qtrue; } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { if (item->giTag == PW_REDFLAG || (item->giTag == PW_BLUEFLAG && ent->modelindex2) || (item->giTag == PW_BLUEFLAG && ps->powerups[PW_REDFLAG]) ) return qtrue; } } #ifdef MISSIONPACK if( gametype == GT_HARVESTER ) { return qtrue; } #endif return qfalse; case IT_HOLDABLE: // can only hold one item at a time if ( ps->stats[STAT_HOLDABLE_ITEM] ) { return qfalse; } return qtrue; case IT_BAD: Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: IT_BAD" ); default: #ifndef Q3_VM #ifndef NDEBUG // bk0001204 Com_Printf("BG_CanItemBeGrabbed: unknown enum %d\n", item->giType ); #endif #endif break; } return qfalse; } //====================================================================== /* ================ BG_EvaluateTrajectory ================ */ void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { float deltaTime; float phase; switch( tr->trType ) { case TR_STATIONARY: case TR_INTERPOLATE: VectorCopy( tr->trBase, result ); break; case TR_LINEAR: deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); break; case TR_SINE: deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; phase = sin( deltaTime * M_PI * 2 ); VectorMA( tr->trBase, phase, tr->trDelta, result ); break; case TR_LINEAR_STOP: if ( atTime > tr->trTime + tr->trDuration ) { atTime = tr->trTime + tr->trDuration; } deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds if ( deltaTime < 0 ) { deltaTime = 0; } VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); break; case TR_GRAVITY: deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... break; default: Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); break; } } /* ================ BG_EvaluateTrajectoryDelta For determining velocity at a given time ================ */ void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { float deltaTime; float phase; switch( tr->trType ) { case TR_STATIONARY: case TR_INTERPOLATE: VectorClear( result ); break; case TR_LINEAR: VectorCopy( tr->trDelta, result ); break; case TR_SINE: deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos phase *= 0.5; VectorScale( tr->trDelta, phase, result ); break; case TR_LINEAR_STOP: if ( atTime > tr->trTime + tr->trDuration ) { VectorClear( result ); return; } VectorCopy( tr->trDelta, result ); break; case TR_GRAVITY: deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds VectorCopy( tr->trDelta, result ); result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... break; default: Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); break; } } char *eventnames[] = { "EV_NONE", "EV_FOOTSTEP", "EV_FOOTSTEP_METAL", "EV_FOOTSPLASH", "EV_FOOTWADE", "EV_SWIM", "EV_STEP_4", "EV_STEP_8", "EV_STEP_12", "EV_STEP_16", "EV_FALL_SHORT", "EV_FALL_MEDIUM", "EV_FALL_FAR", "EV_JUMP_PAD", // boing sound at origin", jump sound on player "EV_JUMP", "EV_WATER_TOUCH", // foot touches "EV_WATER_LEAVE", // foot leaves "EV_WATER_UNDER", // head touches "EV_WATER_CLEAR", // head leaves "EV_ITEM_PICKUP", // normal item pickups are predictable "EV_GLOBAL_ITEM_PICKUP", // powerup / team sounds are broadcast to everyone "EV_NOAMMO", "EV_CHANGE_WEAPON", "EV_FIRE_WEAPON", "EV_USE_ITEM0", "EV_USE_ITEM1", "EV_USE_ITEM2", "EV_USE_ITEM3", "EV_USE_ITEM4", "EV_USE_ITEM5", "EV_USE_ITEM6", "EV_USE_ITEM7", "EV_USE_ITEM8", "EV_USE_ITEM9", "EV_USE_ITEM10", "EV_USE_ITEM11", "EV_USE_ITEM12", "EV_USE_ITEM13", "EV_USE_ITEM14", "EV_USE_ITEM15", "EV_ITEM_RESPAWN", "EV_ITEM_POP", "EV_PLAYER_TELEPORT_IN", "EV_PLAYER_TELEPORT_OUT", "EV_GRENADE_BOUNCE", // eventParm will be the soundindex "EV_GENERAL_SOUND", "EV_GLOBAL_SOUND", // no attenuation "EV_GLOBAL_TEAM_SOUND", "EV_BULLET_HIT_FLESH", "EV_BULLET_HIT_WALL", "EV_MISSILE_HIT", "EV_MISSILE_MISS", "EV_MISSILE_MISS_METAL", "EV_RAILTRAIL", "EV_SHOTGUN", "EV_BULLET", // otherEntity is the shooter "EV_PAIN", "EV_DEATH1", "EV_DEATH2", "EV_DEATH3", "EV_OBITUARY", "EV_POWERUP_QUAD", "EV_POWERUP_BATTLESUIT", "EV_POWERUP_REGEN", "EV_GIB_PLAYER", // gib a previously living player "EV_SCOREPLUM", // score plum //#ifdef MISSIONPACK "EV_PROXIMITY_MINE_STICK", "EV_PROXIMITY_MINE_TRIGGER", "EV_KAMIKAZE", // kamikaze explodes "EV_OBELISKEXPLODE", // obelisk explodes "EV_INVUL_IMPACT", // invulnerability sphere impact "EV_JUICED", // invulnerability juiced effect "EV_LIGHTNINGBOLT", // lightning bolt bounced of invulnerability sphere //#endif "EV_DEBUG_LINE", "EV_STOPLOOPINGSOUND", "EV_TAUNT" }; /* =============== BG_AddPredictableEventToPlayerstate Handles the sequence numbers =============== */ void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { #ifdef _DEBUG { char buf[256]; trap_Cvar_VariableStringBuffer("showevents", buf, sizeof(buf)); if ( atof(buf) != 0 ) { #ifdef QAGAME Com_Printf(" game event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); #else Com_Printf("Cgame event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); #endif } } #endif ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent; ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm; ps->eventSequence++; } /* ======================== BG_TouchJumpPad ======================== */ void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ) { vec3_t angles; float p; int effectNum; // spectators don't use jump pads if ( ps->pm_type != PM_NORMAL ) { return; } // flying characters don't hit bounce pads if ( ps->powerups[PW_FLIGHT] ) { return; } // if we didn't hit this same jumppad the previous frame // then don't play the event sound again if we are in a fat trigger if ( ps->jumppad_ent != jumppad->number ) { vectoangles( jumppad->origin2, angles); p = fabs( AngleNormalize180( angles[PITCH] ) ); if( p < 45 ) { effectNum = 0; } else { effectNum = 1; } BG_AddPredictableEventToPlayerstate( EV_JUMP_PAD, effectNum, ps ); } // remember hitting this jumppad this frame ps->jumppad_ent = jumppad->number; ps->jumppad_frame = ps->pmove_framecount; // give the player the velocity from the jumppad VectorCopy( jumppad->origin2, ps->velocity ); } /* ======================== BG_PlayerStateToEntityState This is done after each set of usercmd_t on the server, and after local prediction on the client ======================== */ void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) { int i; if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { s->eType = ET_INVISIBLE; } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { s->eType = ET_INVISIBLE; } else { s->eType = ET_PLAYER; } s->number = ps->clientNum; s->pos.trType = TR_INTERPOLATE; VectorCopy( ps->origin, s->pos.trBase ); if ( snap ) { SnapVector( s->pos.trBase ); } // set the trDelta for flag direction VectorCopy( ps->velocity, s->pos.trDelta ); s->apos.trType = TR_INTERPOLATE; VectorCopy( ps->viewangles, s->apos.trBase ); if ( snap ) { SnapVector( s->apos.trBase ); } s->angles2[YAW] = ps->movementDir; s->legsAnim = ps->legsAnim; s->torsoAnim = ps->torsoAnim; s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number // so corpses can also reference the proper config s->eFlags = ps->eFlags; if ( ps->stats[STAT_HEALTH] <= 0 ) { s->eFlags |= EF_DEAD; } else { s->eFlags &= ~EF_DEAD; } if ( ps->externalEvent ) { s->event = ps->externalEvent; s->eventParm = ps->externalEventParm; } else if ( ps->entityEventSequence < ps->eventSequence ) { int seq; if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; } seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); s->eventParm = ps->eventParms[ seq ]; ps->entityEventSequence++; } s->weapon = ps->weapon; s->groundEntityNum = ps->groundEntityNum; s->powerups = 0; for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { if ( ps->powerups[ i ] ) { s->powerups |= 1 << i; } } s->loopSound = ps->loopSound; s->generic1 = ps->generic1; } /* ======================== BG_PlayerStateToEntityStateExtraPolate This is done after each set of usercmd_t on the server, and after local prediction on the client ======================== */ void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) { int i; if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { s->eType = ET_INVISIBLE; } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { s->eType = ET_INVISIBLE; } else { s->eType = ET_PLAYER; } s->number = ps->clientNum; s->pos.trType = TR_LINEAR_STOP; VectorCopy( ps->origin, s->pos.trBase ); if ( snap ) { SnapVector( s->pos.trBase ); } // set the trDelta for flag direction and linear prediction VectorCopy( ps->velocity, s->pos.trDelta ); // set the time for linear prediction s->pos.trTime = time; // set maximum extra polation time s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) s->apos.trType = TR_INTERPOLATE; VectorCopy( ps->viewangles, s->apos.trBase ); if ( snap ) { SnapVector( s->apos.trBase ); } s->angles2[YAW] = ps->movementDir; s->legsAnim = ps->legsAnim; s->torsoAnim = ps->torsoAnim; s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number // so corpses can also reference the proper config s->eFlags = ps->eFlags; if ( ps->stats[STAT_HEALTH] <= 0 ) { s->eFlags |= EF_DEAD; } else { s->eFlags &= ~EF_DEAD; } if ( ps->externalEvent ) { s->event = ps->externalEvent; s->eventParm = ps->externalEventParm; } else if ( ps->entityEventSequence < ps->eventSequence ) { int seq; if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; } seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); s->eventParm = ps->eventParms[ seq ]; ps->entityEventSequence++; } s->weapon = ps->weapon; s->groundEntityNum = ps->groundEntityNum; s->powerups = 0; for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { if ( ps->powerups[ i ] ) { s->powerups |= 1 << i; } } s->loopSound = ps->loopSound; s->generic1 = ps->generic1; } ================================================ FILE: code/game/bg_pmove.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // bg_pmove.c -- both games player movement code // takes a playerstate and a usercmd as input and returns a modifed playerstate #include "q_shared.h" #include "bg_public.h" #include "bg_local.h" pmove_t *pm; pml_t pml; // movement parameters float pm_stopspeed = 100.0f; float pm_duckScale = 0.25f; float pm_swimScale = 0.50f; float pm_wadeScale = 0.70f; float pm_accelerate = 10.0f; float pm_airaccelerate = 1.0f; float pm_wateraccelerate = 4.0f; float pm_flyaccelerate = 8.0f; float pm_friction = 6.0f; float pm_waterfriction = 1.0f; float pm_flightfriction = 3.0f; float pm_spectatorfriction = 5.0f; int c_pmove = 0; /* =============== PM_AddEvent =============== */ void PM_AddEvent( int newEvent ) { BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); } /* =============== PM_AddTouchEnt =============== */ void PM_AddTouchEnt( int entityNum ) { int i; if ( entityNum == ENTITYNUM_WORLD ) { return; } if ( pm->numtouch == MAXTOUCH ) { return; } // see if it is already added for ( i = 0 ; i < pm->numtouch ; i++ ) { if ( pm->touchents[ i ] == entityNum ) { return; } } // add it pm->touchents[pm->numtouch] = entityNum; pm->numtouch++; } /* =================== PM_StartTorsoAnim =================== */ static void PM_StartTorsoAnim( int anim ) { if ( pm->ps->pm_type >= PM_DEAD ) { return; } pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; } static void PM_StartLegsAnim( int anim ) { if ( pm->ps->pm_type >= PM_DEAD ) { return; } if ( pm->ps->legsTimer > 0 ) { return; // a high priority animation is running } pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; } static void PM_ContinueLegsAnim( int anim ) { if ( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) { return; } if ( pm->ps->legsTimer > 0 ) { return; // a high priority animation is running } PM_StartLegsAnim( anim ); } static void PM_ContinueTorsoAnim( int anim ) { if ( ( pm->ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) { return; } if ( pm->ps->torsoTimer > 0 ) { return; // a high priority animation is running } PM_StartTorsoAnim( anim ); } static void PM_ForceLegsAnim( int anim ) { pm->ps->legsTimer = 0; PM_StartLegsAnim( anim ); } /* ================== PM_ClipVelocity Slide off of the impacting surface ================== */ void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { float backoff; float change; int i; backoff = DotProduct (in, normal); if ( backoff < 0 ) { backoff *= overbounce; } else { backoff /= overbounce; } for ( i=0 ; i<3 ; i++ ) { change = normal[i]*backoff; out[i] = in[i] - change; } } /* ================== PM_Friction Handles both ground friction and water friction ================== */ static void PM_Friction( void ) { vec3_t vec; float *vel; float speed, newspeed, control; float drop; vel = pm->ps->velocity; VectorCopy( vel, vec ); if ( pml.walking ) { vec[2] = 0; // ignore slope movement } speed = VectorLength(vec); if (speed < 1) { vel[0] = 0; vel[1] = 0; // allow sinking underwater // FIXME: still have z friction underwater? return; } drop = 0; // apply ground friction if ( pm->waterlevel <= 1 ) { if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) { // if getting knocked back, no friction if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) { control = speed < pm_stopspeed ? pm_stopspeed : speed; drop += control*pm_friction*pml.frametime; } } } // apply water friction even if just wading if ( pm->waterlevel ) { drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; } // apply flying friction if ( pm->ps->powerups[PW_FLIGHT]) { drop += speed*pm_flightfriction*pml.frametime; } if ( pm->ps->pm_type == PM_SPECTATOR) { drop += speed*pm_spectatorfriction*pml.frametime; } // scale the velocity newspeed = speed - drop; if (newspeed < 0) { newspeed = 0; } newspeed /= speed; vel[0] = vel[0] * newspeed; vel[1] = vel[1] * newspeed; vel[2] = vel[2] * newspeed; } /* ============== PM_Accelerate Handles user intended acceleration ============== */ static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) { #if 1 // q2 style int i; float addspeed, accelspeed, currentspeed; currentspeed = DotProduct (pm->ps->velocity, wishdir); addspeed = wishspeed - currentspeed; if (addspeed <= 0) { return; } accelspeed = accel*pml.frametime*wishspeed; if (accelspeed > addspeed) { accelspeed = addspeed; } for (i=0 ; i<3 ; i++) { pm->ps->velocity[i] += accelspeed*wishdir[i]; } #else // proper way (avoids strafe jump maxspeed bug), but feels bad vec3_t wishVelocity; vec3_t pushDir; float pushLen; float canPush; VectorScale( wishdir, wishspeed, wishVelocity ); VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); pushLen = VectorNormalize( pushDir ); canPush = accel*pml.frametime*wishspeed; if (canPush > pushLen) { canPush = pushLen; } VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); #endif } /* ============ PM_CmdScale Returns the scale factor to apply to cmd movements This allows the clients to use axial -127 to 127 values for all directions without getting a sqrt(2) distortion in speed. ============ */ static float PM_CmdScale( usercmd_t *cmd ) { int max; float total; float scale; max = abs( cmd->forwardmove ); if ( abs( cmd->rightmove ) > max ) { max = abs( cmd->rightmove ); } if ( abs( cmd->upmove ) > max ) { max = abs( cmd->upmove ); } if ( !max ) { return 0; } total = sqrt( cmd->forwardmove * cmd->forwardmove + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); scale = (float)pm->ps->speed * max / ( 127.0 * total ); return scale; } /* ================ PM_SetMovementDir Determine the rotation of the legs reletive to the facing dir ================ */ static void PM_SetMovementDir( void ) { if ( pm->cmd.forwardmove || pm->cmd.rightmove ) { if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) { pm->ps->movementDir = 0; } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) { pm->ps->movementDir = 1; } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) { pm->ps->movementDir = 2; } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) { pm->ps->movementDir = 3; } else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) { pm->ps->movementDir = 4; } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) { pm->ps->movementDir = 5; } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) { pm->ps->movementDir = 6; } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) { pm->ps->movementDir = 7; } } else { // if they aren't actively going directly sideways, // change the animation to the diagonal so they // don't stop too crooked if ( pm->ps->movementDir == 2 ) { pm->ps->movementDir = 1; } else if ( pm->ps->movementDir == 6 ) { pm->ps->movementDir = 7; } } } /* ============= PM_CheckJump ============= */ static qboolean PM_CheckJump( void ) { if ( pm->ps->pm_flags & PMF_RESPAWNED ) { return qfalse; // don't allow jump until all buttons are up } if ( pm->cmd.upmove < 10 ) { // not holding jump return qfalse; } // must wait for jump to be released if ( pm->ps->pm_flags & PMF_JUMP_HELD ) { // clear upmove so cmdscale doesn't lower running speed pm->cmd.upmove = 0; return qfalse; } pml.groundPlane = qfalse; // jumping away pml.walking = qfalse; pm->ps->pm_flags |= PMF_JUMP_HELD; pm->ps->groundEntityNum = ENTITYNUM_NONE; pm->ps->velocity[2] = JUMP_VELOCITY; PM_AddEvent( EV_JUMP ); if ( pm->cmd.forwardmove >= 0 ) { PM_ForceLegsAnim( LEGS_JUMP ); pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; } else { PM_ForceLegsAnim( LEGS_JUMPB ); pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; } return qtrue; } /* ============= PM_CheckWaterJump ============= */ static qboolean PM_CheckWaterJump( void ) { vec3_t spot; int cont; vec3_t flatforward; if (pm->ps->pm_time) { return qfalse; } // check for water jump if ( pm->waterlevel != 2 ) { return qfalse; } flatforward[0] = pml.forward[0]; flatforward[1] = pml.forward[1]; flatforward[2] = 0; VectorNormalize (flatforward); VectorMA (pm->ps->origin, 30, flatforward, spot); spot[2] += 4; cont = pm->pointcontents (spot, pm->ps->clientNum ); if ( !(cont & CONTENTS_SOLID) ) { return qfalse; } spot[2] += 16; cont = pm->pointcontents (spot, pm->ps->clientNum ); if ( cont ) { return qfalse; } // jump out of water VectorScale (pml.forward, 200, pm->ps->velocity); pm->ps->velocity[2] = 350; pm->ps->pm_flags |= PMF_TIME_WATERJUMP; pm->ps->pm_time = 2000; return qtrue; } //============================================================================ /* =================== PM_WaterJumpMove Flying out of the water =================== */ static void PM_WaterJumpMove( void ) { // waterjump has no control, but falls PM_StepSlideMove( qtrue ); pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; if (pm->ps->velocity[2] < 0) { // cancel as soon as we are falling down again pm->ps->pm_flags &= ~PMF_ALL_TIMES; pm->ps->pm_time = 0; } } /* =================== PM_WaterMove =================== */ static void PM_WaterMove( void ) { int i; vec3_t wishvel; float wishspeed; vec3_t wishdir; float scale; float vel; if ( PM_CheckWaterJump() ) { PM_WaterJumpMove(); return; } #if 0 // jump = head for surface if ( pm->cmd.upmove >= 10 ) { if (pm->ps->velocity[2] > -300) { if ( pm->watertype == CONTENTS_WATER ) { pm->ps->velocity[2] = 100; } else if (pm->watertype == CONTENTS_SLIME) { pm->ps->velocity[2] = 80; } else { pm->ps->velocity[2] = 50; } } } #endif PM_Friction (); scale = PM_CmdScale( &pm->cmd ); // // user intentions // if ( !scale ) { wishvel[0] = 0; wishvel[1] = 0; wishvel[2] = -60; // sink towards bottom } else { for (i=0 ; i<3 ; i++) wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; wishvel[2] += scale * pm->cmd.upmove; } VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); if ( wishspeed > pm->ps->speed * pm_swimScale ) { wishspeed = pm->ps->speed * pm_swimScale; } PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); // make sure we can go up slopes easily under water if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { vel = VectorLength(pm->ps->velocity); // slide along the ground plane PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); VectorNormalize(pm->ps->velocity); VectorScale(pm->ps->velocity, vel, pm->ps->velocity); } PM_SlideMove( qfalse ); } #ifdef MISSIONPACK /* =================== PM_InvulnerabilityMove Only with the invulnerability powerup =================== */ static void PM_InvulnerabilityMove( void ) { pm->cmd.forwardmove = 0; pm->cmd.rightmove = 0; pm->cmd.upmove = 0; VectorClear(pm->ps->velocity); } #endif /* =================== PM_FlyMove Only with the flight powerup =================== */ static void PM_FlyMove( void ) { int i; vec3_t wishvel; float wishspeed; vec3_t wishdir; float scale; // normal slowdown PM_Friction (); scale = PM_CmdScale( &pm->cmd ); // // user intentions // if ( !scale ) { wishvel[0] = 0; wishvel[1] = 0; wishvel[2] = 0; } else { for (i=0 ; i<3 ; i++) { wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; } wishvel[2] += scale * pm->cmd.upmove; } VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); PM_StepSlideMove( qfalse ); } /* =================== PM_AirMove =================== */ static void PM_AirMove( void ) { int i; vec3_t wishvel; float fmove, smove; vec3_t wishdir; float wishspeed; float scale; usercmd_t cmd; PM_Friction(); fmove = pm->cmd.forwardmove; smove = pm->cmd.rightmove; cmd = pm->cmd; scale = PM_CmdScale( &cmd ); // set the movementDir so clients can rotate the legs for strafing PM_SetMovementDir(); // project moves down to flat plane pml.forward[2] = 0; pml.right[2] = 0; VectorNormalize (pml.forward); VectorNormalize (pml.right); for ( i = 0 ; i < 2 ; i++ ) { wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; } wishvel[2] = 0; VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); wishspeed *= scale; // not on ground, so little effect on velocity PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); // we may have a ground plane that is very steep, even // though we don't have a groundentity // slide along the steep plane if ( pml.groundPlane ) { PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); } #if 0 //ZOID: If we are on the grapple, try stair-stepping //this allows a player to use the grapple to pull himself //over a ledge if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) PM_StepSlideMove ( qtrue ); else PM_SlideMove ( qtrue ); #endif PM_StepSlideMove ( qtrue ); } /* =================== PM_GrappleMove =================== */ static void PM_GrappleMove( void ) { vec3_t vel, v; float vlen; VectorScale(pml.forward, -16, v); VectorAdd(pm->ps->grapplePoint, v, v); VectorSubtract(v, pm->ps->origin, vel); vlen = VectorLength(vel); VectorNormalize( vel ); if (vlen <= 100) VectorScale(vel, 10 * vlen, vel); else VectorScale(vel, 800, vel); VectorCopy(vel, pm->ps->velocity); pml.groundPlane = qfalse; } /* =================== PM_WalkMove =================== */ static void PM_WalkMove( void ) { int i; vec3_t wishvel; float fmove, smove; vec3_t wishdir; float wishspeed; float scale; usercmd_t cmd; float accelerate; float vel; if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { // begin swimming PM_WaterMove(); return; } if ( PM_CheckJump () ) { // jumped away if ( pm->waterlevel > 1 ) { PM_WaterMove(); } else { PM_AirMove(); } return; } PM_Friction (); fmove = pm->cmd.forwardmove; smove = pm->cmd.rightmove; cmd = pm->cmd; scale = PM_CmdScale( &cmd ); // set the movementDir so clients can rotate the legs for strafing PM_SetMovementDir(); // project moves down to flat plane pml.forward[2] = 0; pml.right[2] = 0; // project the forward and right directions onto the ground plane PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); // VectorNormalize (pml.forward); VectorNormalize (pml.right); for ( i = 0 ; i < 3 ; i++ ) { wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; } // when going up or down slopes the wish velocity should Not be zero // wishvel[2] = 0; VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); wishspeed *= scale; // clamp the speed lower if ducking if ( pm->ps->pm_flags & PMF_DUCKED ) { if ( wishspeed > pm->ps->speed * pm_duckScale ) { wishspeed = pm->ps->speed * pm_duckScale; } } // clamp the speed lower if wading or walking on the bottom if ( pm->waterlevel ) { float waterScale; waterScale = pm->waterlevel / 3.0; waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; if ( wishspeed > pm->ps->speed * waterScale ) { wishspeed = pm->ps->speed * waterScale; } } // when a player gets hit, they temporarily lose // full control, which allows them to be moved a bit if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { accelerate = pm_airaccelerate; } else { accelerate = pm_accelerate; } PM_Accelerate (wishdir, wishspeed, accelerate); //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; } else { // don't reset the z velocity for slopes // pm->ps->velocity[2] = 0; } vel = VectorLength(pm->ps->velocity); // slide along the ground plane PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); // don't decrease velocity when going up or down a slope VectorNormalize(pm->ps->velocity); VectorScale(pm->ps->velocity, vel, pm->ps->velocity); // don't do anything if standing still if (!pm->ps->velocity[0] && !pm->ps->velocity[1]) { return; } PM_StepSlideMove( qfalse ); //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); } /* ============== PM_DeadMove ============== */ static void PM_DeadMove( void ) { float forward; if ( !pml.walking ) { return; } // extra friction forward = VectorLength (pm->ps->velocity); forward -= 20; if ( forward <= 0 ) { VectorClear (pm->ps->velocity); } else { VectorNormalize (pm->ps->velocity); VectorScale (pm->ps->velocity, forward, pm->ps->velocity); } } /* =============== PM_NoclipMove =============== */ static void PM_NoclipMove( void ) { float speed, drop, friction, control, newspeed; int i; vec3_t wishvel; float fmove, smove; vec3_t wishdir; float wishspeed; float scale; pm->ps->viewheight = DEFAULT_VIEWHEIGHT; // friction speed = VectorLength (pm->ps->velocity); if (speed < 1) { VectorCopy (vec3_origin, pm->ps->velocity); } else { drop = 0; friction = pm_friction*1.5; // extra friction control = speed < pm_stopspeed ? pm_stopspeed : speed; drop += control*friction*pml.frametime; // scale the velocity newspeed = speed - drop; if (newspeed < 0) newspeed = 0; newspeed /= speed; VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); } // accelerate scale = PM_CmdScale( &pm->cmd ); fmove = pm->cmd.forwardmove; smove = pm->cmd.rightmove; for (i=0 ; i<3 ; i++) wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; wishvel[2] += pm->cmd.upmove; VectorCopy (wishvel, wishdir); wishspeed = VectorNormalize(wishdir); wishspeed *= scale; PM_Accelerate( wishdir, wishspeed, pm_accelerate ); // move VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); } //============================================================================ /* ================ PM_FootstepForSurface Returns an event number apropriate for the groundsurface ================ */ static int PM_FootstepForSurface( void ) { if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) { return 0; } if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) { return EV_FOOTSTEP_METAL; } return EV_FOOTSTEP; } /* ================= PM_CrashLand Check for hard landings that generate sound events ================= */ static void PM_CrashLand( void ) { float delta; float dist; float vel, acc; float t; float a, b, c, den; // decide which landing animation to use if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) { PM_ForceLegsAnim( LEGS_LANDB ); } else { PM_ForceLegsAnim( LEGS_LAND ); } pm->ps->legsTimer = TIMER_LAND; // calculate the exact velocity on landing dist = pm->ps->origin[2] - pml.previous_origin[2]; vel = pml.previous_velocity[2]; acc = -pm->ps->gravity; a = acc / 2; b = vel; c = -dist; den = b * b - 4 * a * c; if ( den < 0 ) { return; } t = (-b - sqrt( den ) ) / ( 2 * a ); delta = vel + t * acc; delta = delta*delta * 0.0001; // ducking while falling doubles damage if ( pm->ps->pm_flags & PMF_DUCKED ) { delta *= 2; } // never take falling damage if completely underwater if ( pm->waterlevel == 3 ) { return; } // reduce falling damage if there is standing water if ( pm->waterlevel == 2 ) { delta *= 0.25; } if ( pm->waterlevel == 1 ) { delta *= 0.5; } if ( delta < 1 ) { return; } // create a local entity event to play the sound // SURF_NODAMAGE is used for bounce pads where you don't ever // want to take damage or play a crunch sound if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) { if ( delta > 60 ) { PM_AddEvent( EV_FALL_FAR ); } else if ( delta > 40 ) { // this is a pain grunt, so don't play it if dead if ( pm->ps->stats[STAT_HEALTH] > 0 ) { PM_AddEvent( EV_FALL_MEDIUM ); } } else if ( delta > 7 ) { PM_AddEvent( EV_FALL_SHORT ); } else { PM_AddEvent( PM_FootstepForSurface() ); } } // start footstep cycle over pm->ps->bobCycle = 0; } /* ============= PM_CheckStuck ============= */ /* void PM_CheckStuck(void) { trace_t trace; pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask); if (trace.allsolid) { //int shit = qtrue; } } */ /* ============= PM_CorrectAllSolid ============= */ static int PM_CorrectAllSolid( trace_t *trace ) { int i, j, k; vec3_t point; if ( pm->debugLevel ) { Com_Printf("%i:allsolid\n", c_pmove); } // jitter around for (i = -1; i <= 1; i++) { for (j = -1; j <= 1; j++) { for (k = -1; k <= 1; k++) { VectorCopy(pm->ps->origin, point); point[0] += (float) i; point[1] += (float) j; point[2] += (float) k; pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); if ( !trace->allsolid ) { point[0] = pm->ps->origin[0]; point[1] = pm->ps->origin[1]; point[2] = pm->ps->origin[2] - 0.25; pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); pml.groundTrace = *trace; return qtrue; } } } } pm->ps->groundEntityNum = ENTITYNUM_NONE; pml.groundPlane = qfalse; pml.walking = qfalse; return qfalse; } /* ============= PM_GroundTraceMissed The ground trace didn't hit a surface, so we are in freefall ============= */ static void PM_GroundTraceMissed( void ) { trace_t trace; vec3_t point; if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) { // we just transitioned into freefall if ( pm->debugLevel ) { Com_Printf("%i:lift\n", c_pmove); } // if they aren't in a jumping animation and the ground is a ways away, force into it // if we didn't do the trace, the player would be backflipping down staircases VectorCopy( pm->ps->origin, point ); point[2] -= 64; pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); if ( trace.fraction == 1.0 ) { if ( pm->cmd.forwardmove >= 0 ) { PM_ForceLegsAnim( LEGS_JUMP ); pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; } else { PM_ForceLegsAnim( LEGS_JUMPB ); pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; } } } pm->ps->groundEntityNum = ENTITYNUM_NONE; pml.groundPlane = qfalse; pml.walking = qfalse; } /* ============= PM_GroundTrace ============= */ static void PM_GroundTrace( void ) { vec3_t point; trace_t trace; point[0] = pm->ps->origin[0]; point[1] = pm->ps->origin[1]; point[2] = pm->ps->origin[2] - 0.25; pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); pml.groundTrace = trace; // do something corrective if the trace starts in a solid... if ( trace.allsolid ) { if ( !PM_CorrectAllSolid(&trace) ) return; } // if the trace didn't hit anything, we are in free fall if ( trace.fraction == 1.0 ) { PM_GroundTraceMissed(); pml.groundPlane = qfalse; pml.walking = qfalse; return; } // check if getting thrown off the ground if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) { if ( pm->debugLevel ) { Com_Printf("%i:kickoff\n", c_pmove); } // go into jump animation if ( pm->cmd.forwardmove >= 0 ) { PM_ForceLegsAnim( LEGS_JUMP ); pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; } else { PM_ForceLegsAnim( LEGS_JUMPB ); pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; } pm->ps->groundEntityNum = ENTITYNUM_NONE; pml.groundPlane = qfalse; pml.walking = qfalse; return; } // slopes that are too steep will not be considered onground if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { if ( pm->debugLevel ) { Com_Printf("%i:steep\n", c_pmove); } // FIXME: if they can't slide down the slope, let them // walk (sharp crevices) pm->ps->groundEntityNum = ENTITYNUM_NONE; pml.groundPlane = qtrue; pml.walking = qfalse; return; } pml.groundPlane = qtrue; pml.walking = qtrue; // hitting solid ground will end a waterjump if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) { pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); pm->ps->pm_time = 0; } if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { // just hit the ground if ( pm->debugLevel ) { Com_Printf("%i:Land\n", c_pmove); } PM_CrashLand(); // don't do landing time if we were just going down a slope if ( pml.previous_velocity[2] < -200 ) { // don't allow another jump for a little while pm->ps->pm_flags |= PMF_TIME_LAND; pm->ps->pm_time = 250; } } pm->ps->groundEntityNum = trace.entityNum; // don't reset the z velocity for slopes // pm->ps->velocity[2] = 0; PM_AddTouchEnt( trace.entityNum ); } /* ============= PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving ============= */ static void PM_SetWaterLevel( void ) { vec3_t point; int cont; int sample1; int sample2; // // get waterlevel, accounting for ducking // pm->waterlevel = 0; pm->watertype = 0; point[0] = pm->ps->origin[0]; point[1] = pm->ps->origin[1]; point[2] = pm->ps->origin[2] + MINS_Z + 1; cont = pm->pointcontents( point, pm->ps->clientNum ); if ( cont & MASK_WATER ) { sample2 = pm->ps->viewheight - MINS_Z; sample1 = sample2 / 2; pm->watertype = cont; pm->waterlevel = 1; point[2] = pm->ps->origin[2] + MINS_Z + sample1; cont = pm->pointcontents (point, pm->ps->clientNum ); if ( cont & MASK_WATER ) { pm->waterlevel = 2; point[2] = pm->ps->origin[2] + MINS_Z + sample2; cont = pm->pointcontents (point, pm->ps->clientNum ); if ( cont & MASK_WATER ){ pm->waterlevel = 3; } } } } /* ============== PM_CheckDuck Sets mins, maxs, and pm->ps->viewheight ============== */ static void PM_CheckDuck (void) { trace_t trace; if ( pm->ps->powerups[PW_INVULNERABILITY] ) { if ( pm->ps->pm_flags & PMF_INVULEXPAND ) { // invulnerability sphere has a 42 units radius VectorSet( pm->mins, -42, -42, -42 ); VectorSet( pm->maxs, 42, 42, 42 ); } else { VectorSet( pm->mins, -15, -15, MINS_Z ); VectorSet( pm->maxs, 15, 15, 16 ); } pm->ps->pm_flags |= PMF_DUCKED; pm->ps->viewheight = CROUCH_VIEWHEIGHT; return; } pm->ps->pm_flags &= ~PMF_INVULEXPAND; pm->mins[0] = -15; pm->mins[1] = -15; pm->maxs[0] = 15; pm->maxs[1] = 15; pm->mins[2] = MINS_Z; if (pm->ps->pm_type == PM_DEAD) { pm->maxs[2] = -8; pm->ps->viewheight = DEAD_VIEWHEIGHT; return; } if (pm->cmd.upmove < 0) { // duck pm->ps->pm_flags |= PMF_DUCKED; } else { // stand up if possible if (pm->ps->pm_flags & PMF_DUCKED) { // try to stand up pm->maxs[2] = 32; pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); if (!trace.allsolid) pm->ps->pm_flags &= ~PMF_DUCKED; } } if (pm->ps->pm_flags & PMF_DUCKED) { pm->maxs[2] = 16; pm->ps->viewheight = CROUCH_VIEWHEIGHT; } else { pm->maxs[2] = 32; pm->ps->viewheight = DEFAULT_VIEWHEIGHT; } } //=================================================================== /* =============== PM_Footsteps =============== */ static void PM_Footsteps( void ) { float bobmove; int old; qboolean footstep; // // calculate speed and cycle to be used for // all cyclic walking effects // pm->xyspeed = sqrt( pm->ps->velocity[0] * pm->ps->velocity[0] + pm->ps->velocity[1] * pm->ps->velocity[1] ); if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { if ( pm->ps->powerups[PW_INVULNERABILITY] ) { PM_ContinueLegsAnim( LEGS_IDLECR ); } // airborne leaves position in cycle intact, but doesn't advance if ( pm->waterlevel > 1 ) { PM_ContinueLegsAnim( LEGS_SWIM ); } return; } // if not trying to move if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { if ( pm->xyspeed < 5 ) { pm->ps->bobCycle = 0; // start at beginning of cycle again if ( pm->ps->pm_flags & PMF_DUCKED ) { PM_ContinueLegsAnim( LEGS_IDLECR ); } else { PM_ContinueLegsAnim( LEGS_IDLE ); } } return; } footstep = qfalse; if ( pm->ps->pm_flags & PMF_DUCKED ) { bobmove = 0.5; // ducked characters bob much faster if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { PM_ContinueLegsAnim( LEGS_BACKCR ); } else { PM_ContinueLegsAnim( LEGS_WALKCR ); } // ducked characters never play footsteps /* } else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { bobmove = 0.4; // faster speeds bob faster footstep = qtrue; } else { bobmove = 0.3; } PM_ContinueLegsAnim( LEGS_BACK ); */ } else { if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { bobmove = 0.4f; // faster speeds bob faster if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { PM_ContinueLegsAnim( LEGS_BACK ); } else { PM_ContinueLegsAnim( LEGS_RUN ); } footstep = qtrue; } else { bobmove = 0.3f; // walking bobs slow if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { PM_ContinueLegsAnim( LEGS_BACKWALK ); } else { PM_ContinueLegsAnim( LEGS_WALK ); } } } // check for footstep / splash sounds old = pm->ps->bobCycle; pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; // if we just crossed a cycle boundary, play an apropriate footstep event if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) { if ( pm->waterlevel == 0 ) { // on ground will only play sounds if running if ( footstep && !pm->noFootsteps ) { PM_AddEvent( PM_FootstepForSurface() ); } } else if ( pm->waterlevel == 1 ) { // splashing PM_AddEvent( EV_FOOTSPLASH ); } else if ( pm->waterlevel == 2 ) { // wading / swimming at surface PM_AddEvent( EV_SWIM ); } else if ( pm->waterlevel == 3 ) { // no sound when completely underwater } } } /* ============== PM_WaterEvents Generate sound events for entering and leaving water ============== */ static void PM_WaterEvents( void ) { // FIXME? // // if just entered a water volume, play a sound // if (!pml.previous_waterlevel && pm->waterlevel) { PM_AddEvent( EV_WATER_TOUCH ); } // // if just completely exited a water volume, play a sound // if (pml.previous_waterlevel && !pm->waterlevel) { PM_AddEvent( EV_WATER_LEAVE ); } // // check for head just going under water // if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) { PM_AddEvent( EV_WATER_UNDER ); } // // check for head just coming out of water // if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { PM_AddEvent( EV_WATER_CLEAR ); } } /* =============== PM_BeginWeaponChange =============== */ static void PM_BeginWeaponChange( int weapon ) { if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) { return; } if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { return; } if ( pm->ps->weaponstate == WEAPON_DROPPING ) { return; } PM_AddEvent( EV_CHANGE_WEAPON ); pm->ps->weaponstate = WEAPON_DROPPING; pm->ps->weaponTime += 200; PM_StartTorsoAnim( TORSO_DROP ); } /* =============== PM_FinishWeaponChange =============== */ static void PM_FinishWeaponChange( void ) { int weapon; weapon = pm->cmd.weapon; if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { weapon = WP_NONE; } if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { weapon = WP_NONE; } pm->ps->weapon = weapon; pm->ps->weaponstate = WEAPON_RAISING; pm->ps->weaponTime += 250; PM_StartTorsoAnim( TORSO_RAISE ); } /* ============== PM_TorsoAnimation ============== */ static void PM_TorsoAnimation( void ) { if ( pm->ps->weaponstate == WEAPON_READY ) { if ( pm->ps->weapon == WP_GAUNTLET ) { PM_ContinueTorsoAnim( TORSO_STAND2 ); } else { PM_ContinueTorsoAnim( TORSO_STAND ); } return; } } /* ============== PM_Weapon Generates weapon events and modifes the weapon counter ============== */ static void PM_Weapon( void ) { int addTime; // don't allow attack until all buttons are up if ( pm->ps->pm_flags & PMF_RESPAWNED ) { return; } // ignore if spectator if ( pm->ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { return; } // check for dead player if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { pm->ps->weapon = WP_NONE; return; } // check for item using if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) { if ( ! ( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) { if ( bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_MEDKIT && pm->ps->stats[STAT_HEALTH] >= (pm->ps->stats[STAT_MAX_HEALTH] + 25) ) { // don't use medkit if at max health } else { pm->ps->pm_flags |= PMF_USE_ITEM_HELD; PM_AddEvent( EV_USE_ITEM0 + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag ); pm->ps->stats[STAT_HOLDABLE_ITEM] = 0; } return; } } else { pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; } // make weapon function if ( pm->ps->weaponTime > 0 ) { pm->ps->weaponTime -= pml.msec; } // check for weapon change // can't change if weapon is firing, but can change // again if lowering or raising if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { if ( pm->ps->weapon != pm->cmd.weapon ) { PM_BeginWeaponChange( pm->cmd.weapon ); } } if ( pm->ps->weaponTime > 0 ) { return; } // change weapon if time if ( pm->ps->weaponstate == WEAPON_DROPPING ) { PM_FinishWeaponChange(); return; } if ( pm->ps->weaponstate == WEAPON_RAISING ) { pm->ps->weaponstate = WEAPON_READY; if ( pm->ps->weapon == WP_GAUNTLET ) { PM_StartTorsoAnim( TORSO_STAND2 ); } else { PM_StartTorsoAnim( TORSO_STAND ); } return; } // check for fire if ( ! (pm->cmd.buttons & BUTTON_ATTACK) ) { pm->ps->weaponTime = 0; pm->ps->weaponstate = WEAPON_READY; return; } // start the animation even if out of ammo if ( pm->ps->weapon == WP_GAUNTLET ) { // the guantlet only "fires" when it actually hits something if ( !pm->gauntletHit ) { pm->ps->weaponTime = 0; pm->ps->weaponstate = WEAPON_READY; return; } PM_StartTorsoAnim( TORSO_ATTACK2 ); } else { PM_StartTorsoAnim( TORSO_ATTACK ); } pm->ps->weaponstate = WEAPON_FIRING; // check for out of ammo if ( ! pm->ps->ammo[ pm->ps->weapon ] ) { PM_AddEvent( EV_NOAMMO ); pm->ps->weaponTime += 500; return; } // take an ammo away if not infinite if ( pm->ps->ammo[ pm->ps->weapon ] != -1 ) { pm->ps->ammo[ pm->ps->weapon ]--; } // fire weapon PM_AddEvent( EV_FIRE_WEAPON ); switch( pm->ps->weapon ) { default: case WP_GAUNTLET: addTime = 400; break; case WP_LIGHTNING: addTime = 50; break; case WP_SHOTGUN: addTime = 1000; break; case WP_MACHINEGUN: addTime = 100; break; case WP_GRENADE_LAUNCHER: addTime = 800; break; case WP_ROCKET_LAUNCHER: addTime = 800; break; case WP_PLASMAGUN: addTime = 100; break; case WP_RAILGUN: addTime = 1500; break; case WP_BFG: addTime = 200; break; case WP_GRAPPLING_HOOK: addTime = 400; break; #ifdef MISSIONPACK case WP_NAILGUN: addTime = 1000; break; case WP_PROX_LAUNCHER: addTime = 800; break; case WP_CHAINGUN: addTime = 30; break; #endif } #ifdef MISSIONPACK if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { addTime /= 1.5; } else if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { addTime /= 1.3; } else #endif if ( pm->ps->powerups[PW_HASTE] ) { addTime /= 1.3; } pm->ps->weaponTime += addTime; } /* ================ PM_Animate ================ */ static void PM_Animate( void ) { if ( pm->cmd.buttons & BUTTON_GESTURE ) { if ( pm->ps->torsoTimer == 0 ) { PM_StartTorsoAnim( TORSO_GESTURE ); pm->ps->torsoTimer = TIMER_GESTURE; PM_AddEvent( EV_TAUNT ); } #ifdef MISSIONPACK } else if ( pm->cmd.buttons & BUTTON_GETFLAG ) { if ( pm->ps->torsoTimer == 0 ) { PM_StartTorsoAnim( TORSO_GETFLAG ); pm->ps->torsoTimer = 600; //TIMER_GESTURE; } } else if ( pm->cmd.buttons & BUTTON_GUARDBASE ) { if ( pm->ps->torsoTimer == 0 ) { PM_StartTorsoAnim( TORSO_GUARDBASE ); pm->ps->torsoTimer = 600; //TIMER_GESTURE; } } else if ( pm->cmd.buttons & BUTTON_PATROL ) { if ( pm->ps->torsoTimer == 0 ) { PM_StartTorsoAnim( TORSO_PATROL ); pm->ps->torsoTimer = 600; //TIMER_GESTURE; } } else if ( pm->cmd.buttons & BUTTON_FOLLOWME ) { if ( pm->ps->torsoTimer == 0 ) { PM_StartTorsoAnim( TORSO_FOLLOWME ); pm->ps->torsoTimer = 600; //TIMER_GESTURE; } } else if ( pm->cmd.buttons & BUTTON_AFFIRMATIVE ) { if ( pm->ps->torsoTimer == 0 ) { PM_StartTorsoAnim( TORSO_AFFIRMATIVE); pm->ps->torsoTimer = 600; //TIMER_GESTURE; } } else if ( pm->cmd.buttons & BUTTON_NEGATIVE ) { if ( pm->ps->torsoTimer == 0 ) { PM_StartTorsoAnim( TORSO_NEGATIVE ); pm->ps->torsoTimer = 600; //TIMER_GESTURE; } #endif } } /* ================ PM_DropTimers ================ */ static void PM_DropTimers( void ) { // drop misc timing counter if ( pm->ps->pm_time ) { if ( pml.msec >= pm->ps->pm_time ) { pm->ps->pm_flags &= ~PMF_ALL_TIMES; pm->ps->pm_time = 0; } else { pm->ps->pm_time -= pml.msec; } } // drop animation counter if ( pm->ps->legsTimer > 0 ) { pm->ps->legsTimer -= pml.msec; if ( pm->ps->legsTimer < 0 ) { pm->ps->legsTimer = 0; } } if ( pm->ps->torsoTimer > 0 ) { pm->ps->torsoTimer -= pml.msec; if ( pm->ps->torsoTimer < 0 ) { pm->ps->torsoTimer = 0; } } } /* ================ PM_UpdateViewAngles This can be used as another entry point when only the viewangles are being updated isntead of a full move ================ */ void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) { short temp; int i; if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) { return; // no view changes at all } if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) { return; // no view changes at all } // circularly clamp the angles with deltas for (i=0 ; i<3 ; i++) { temp = cmd->angles[i] + ps->delta_angles[i]; if ( i == PITCH ) { // don't let the player look up or down more than 90 degrees if ( temp > 16000 ) { ps->delta_angles[i] = 16000 - cmd->angles[i]; temp = 16000; } else if ( temp < -16000 ) { ps->delta_angles[i] = -16000 - cmd->angles[i]; temp = -16000; } } ps->viewangles[i] = SHORT2ANGLE(temp); } } /* ================ PmoveSingle ================ */ void trap_SnapVector( float *v ); void PmoveSingle (pmove_t *pmove) { pm = pmove; // this counter lets us debug movement problems with a journal // by setting a conditional breakpoint fot the previous frame c_pmove++; // clear results pm->numtouch = 0; pm->watertype = 0; pm->waterlevel = 0; if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies } // make sure walking button is clear if they are running, to avoid // proxy no-footsteps cheats if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) { pm->cmd.buttons &= ~BUTTON_WALKING; } // set the talk balloon flag if ( pm->cmd.buttons & BUTTON_TALK ) { pm->ps->eFlags |= EF_TALK; } else { pm->ps->eFlags &= ~EF_TALK; } // set the firing flag for continuous beam weapons if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION && ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] ) { pm->ps->eFlags |= EF_FIRING; } else { pm->ps->eFlags &= ~EF_FIRING; } // clear the respawned flag if attack and use are cleared if ( pm->ps->stats[STAT_HEALTH] > 0 && !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) { pm->ps->pm_flags &= ~PMF_RESPAWNED; } // if talk button is down, dissallow all other input // this is to prevent any possible intercept proxy from // adding fake talk balloons if ( pmove->cmd.buttons & BUTTON_TALK ) { // keep the talk button set tho for when the cmd.serverTime > 66 msec // and the same cmd is used multiple times in Pmove pmove->cmd.buttons = BUTTON_TALK; pmove->cmd.forwardmove = 0; pmove->cmd.rightmove = 0; pmove->cmd.upmove = 0; } // clear all pmove local vars memset (&pml, 0, sizeof(pml)); // determine the time pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; if ( pml.msec < 1 ) { pml.msec = 1; } else if ( pml.msec > 200 ) { pml.msec = 200; } pm->ps->commandTime = pmove->cmd.serverTime; // save old org in case we get stuck VectorCopy (pm->ps->origin, pml.previous_origin); // save old velocity for crashlanding VectorCopy (pm->ps->velocity, pml.previous_velocity); pml.frametime = pml.msec * 0.001; // update the viewangles PM_UpdateViewAngles( pm->ps, &pm->cmd ); AngleVectors (pm->ps->viewangles, pml.forward, pml.right, pml.up); if ( pm->cmd.upmove < 10 ) { // not holding jump pm->ps->pm_flags &= ~PMF_JUMP_HELD; } // decide if backpedaling animations should be used if ( pm->cmd.forwardmove < 0 ) { pm->ps->pm_flags |= PMF_BACKWARDS_RUN; } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; } if ( pm->ps->pm_type >= PM_DEAD ) { pm->cmd.forwardmove = 0; pm->cmd.rightmove = 0; pm->cmd.upmove = 0; } if ( pm->ps->pm_type == PM_SPECTATOR ) { PM_CheckDuck (); PM_FlyMove (); PM_DropTimers (); return; } if ( pm->ps->pm_type == PM_NOCLIP ) { PM_NoclipMove (); PM_DropTimers (); return; } if (pm->ps->pm_type == PM_FREEZE) { return; // no movement at all } if ( pm->ps->pm_type == PM_INTERMISSION || pm->ps->pm_type == PM_SPINTERMISSION) { return; // no movement at all } // set watertype, and waterlevel PM_SetWaterLevel(); pml.previous_waterlevel = pmove->waterlevel; // set mins, maxs, and viewheight PM_CheckDuck (); // set groundentity PM_GroundTrace(); if ( pm->ps->pm_type == PM_DEAD ) { PM_DeadMove (); } PM_DropTimers(); #ifdef MISSIONPACK if ( pm->ps->powerups[PW_INVULNERABILITY] ) { PM_InvulnerabilityMove(); } else #endif if ( pm->ps->powerups[PW_FLIGHT] ) { // flight powerup doesn't allow jump and has different friction PM_FlyMove(); } else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) { PM_GrappleMove(); // We can wiggle a bit PM_AirMove(); } else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) { PM_WaterJumpMove(); } else if ( pm->waterlevel > 1 ) { // swimming PM_WaterMove(); } else if ( pml.walking ) { // walking on ground PM_WalkMove(); } else { // airborne PM_AirMove(); } PM_Animate(); // set groundentity, watertype, and waterlevel PM_GroundTrace(); PM_SetWaterLevel(); // weapons PM_Weapon(); // torso animation PM_TorsoAnimation(); // footstep events / legs animations PM_Footsteps(); // entering / leaving water splashes PM_WaterEvents(); // snap some parts of playerstate to save network bandwidth trap_SnapVector( pm->ps->velocity ); } /* ================ Pmove Can be called by either the server or the client ================ */ void Pmove (pmove_t *pmove) { int finalTime; finalTime = pmove->cmd.serverTime; if ( finalTime < pmove->ps->commandTime ) { return; // should not happen } if ( finalTime > pmove->ps->commandTime + 1000 ) { pmove->ps->commandTime = finalTime - 1000; } pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<ps->commandTime != finalTime ) { int msec; msec = finalTime - pmove->ps->commandTime; if ( pmove->pmove_fixed ) { if ( msec > pmove->pmove_msec ) { msec = pmove->pmove_msec; } } else { if ( msec > 66 ) { msec = 66; } } pmove->cmd.serverTime = pmove->ps->commandTime + msec; PmoveSingle( pmove ); if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) { pmove->cmd.upmove = 20; } } //PM_CheckStuck(); } ================================================ FILE: code/game/bg_public.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // bg_public.h -- definitions shared by both the server game and client game modules // because games can change separately from the main system version, we need a // second version that must match between game and cgame #define GAME_VERSION "baseq3-1" #define DEFAULT_GRAVITY 800 #define GIB_HEALTH -40 #define ARMOR_PROTECTION 0.66 #define MAX_ITEMS 256 #define RANK_TIED_FLAG 0x4000 #define DEFAULT_SHOTGUN_SPREAD 700 #define DEFAULT_SHOTGUN_COUNT 11 #define ITEM_RADIUS 15 // item sizes are needed for client side pickup detection #define LIGHTNING_RANGE 768 #define SCORE_NOT_PRESENT -9999 // for the CS_SCORES[12] when only one player is present #define VOTE_TIME 30000 // 30 seconds before vote times out #define MINS_Z -24 #define DEFAULT_VIEWHEIGHT 26 #define CROUCH_VIEWHEIGHT 12 #define DEAD_VIEWHEIGHT -16 // // config strings are a general means of communicating variable length strings // from the server to all connected clients. // // CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h #define CS_MUSIC 2 #define CS_MESSAGE 3 // from the map worldspawn's message field #define CS_MOTD 4 // g_motd string for server message of the day #define CS_WARMUP 5 // server time when the match will be restarted #define CS_SCORES1 6 #define CS_SCORES2 7 #define CS_VOTE_TIME 8 #define CS_VOTE_STRING 9 #define CS_VOTE_YES 10 #define CS_VOTE_NO 11 #define CS_TEAMVOTE_TIME 12 #define CS_TEAMVOTE_STRING 14 #define CS_TEAMVOTE_YES 16 #define CS_TEAMVOTE_NO 18 #define CS_GAME_VERSION 20 #define CS_LEVEL_START_TIME 21 // so the timer only shows the current level #define CS_INTERMISSION 22 // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two #define CS_FLAGSTATUS 23 // string indicating flag status in CTF #define CS_SHADERSTATE 24 #define CS_BOTINFO 25 #define CS_ITEMS 27 // string of 0's and 1's that tell which items are present #define CS_MODELS 32 #define CS_SOUNDS (CS_MODELS+MAX_MODELS) #define CS_PLAYERS (CS_SOUNDS+MAX_SOUNDS) #define CS_LOCATIONS (CS_PLAYERS+MAX_CLIENTS) #define CS_PARTICLES (CS_LOCATIONS+MAX_LOCATIONS) #define CS_MAX (CS_PARTICLES+MAX_LOCATIONS) #if (CS_MAX) > MAX_CONFIGSTRINGS #error overflow: (CS_MAX) > MAX_CONFIGSTRINGS #endif typedef enum { GT_FFA, // free for all GT_TOURNAMENT, // one on one tournament GT_SINGLE_PLAYER, // single player ffa //-- team games go after this -- GT_TEAM, // team deathmatch GT_CTF, // capture the flag GT_1FCTF, GT_OBELISK, GT_HARVESTER, GT_MAX_GAME_TYPE } gametype_t; typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t; /* =================================================================================== PMOVE MODULE The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t and some other output data. Used for local prediction on the client game and true movement on the server game. =================================================================================== */ typedef enum { PM_NORMAL, // can accelerate and turn PM_NOCLIP, // noclip movement PM_SPECTATOR, // still run into walls PM_DEAD, // no acceleration or turning, but free falling PM_FREEZE, // stuck in place with no control PM_INTERMISSION, // no movement or status bar PM_SPINTERMISSION // no movement or status bar } pmtype_t; typedef enum { WEAPON_READY, WEAPON_RAISING, WEAPON_DROPPING, WEAPON_FIRING } weaponstate_t; // pmove->pm_flags #define PMF_DUCKED 1 #define PMF_JUMP_HELD 2 #define PMF_BACKWARDS_JUMP 8 // go into backwards land #define PMF_BACKWARDS_RUN 16 // coast down to backwards run #define PMF_TIME_LAND 32 // pm_time is time before rejump #define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time #define PMF_TIME_WATERJUMP 256 // pm_time is waterjump #define PMF_RESPAWNED 512 // clear after attack and jump buttons come up #define PMF_USE_ITEM_HELD 1024 #define PMF_GRAPPLE_PULL 2048 // pull towards grapple location #define PMF_FOLLOW 4096 // spectate following another player #define PMF_SCOREBOARD 8192 // spectate as a scoreboard #define PMF_INVULEXPAND 16384 // invulnerability sphere set to full size #define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) #define MAXTOUCH 32 typedef struct { // state (in / out) playerState_t *ps; // command (in) usercmd_t cmd; int tracemask; // collide against these types of surfaces int debugLevel; // if set, diagnostic output will be printed qboolean noFootsteps; // if the game is setup for no footsteps by the server qboolean gauntletHit; // true if a gauntlet attack would actually hit something int framecount; // results (out) int numtouch; int touchents[MAXTOUCH]; vec3_t mins, maxs; // bounding box size int watertype; int waterlevel; float xyspeed; // for fixed msec Pmove int pmove_fixed; int pmove_msec; // callbacks to test the world // these will be different functions during game and cgame void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ); int (*pointcontents)( const vec3_t point, int passEntityNum ); } pmove_t; // if a full pmove isn't done on the client, you can just update the angles void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); void Pmove (pmove_t *pmove); //=================================================================================== // player_state->stats[] indexes // NOTE: may not have more than 16 typedef enum { STAT_HEALTH, STAT_HOLDABLE_ITEM, #ifdef MISSIONPACK STAT_PERSISTANT_POWERUP, #endif STAT_WEAPONS, // 16 bit fields STAT_ARMOR, STAT_DEAD_YAW, // look this direction when dead (FIXME: get rid of?) STAT_CLIENTS_READY, // bit mask of clients wishing to exit the intermission (FIXME: configstring?) STAT_MAX_HEALTH // health / armor limit, changable by handicap } statIndex_t; // player_state->persistant[] indexes // these fields are the only part of player_state that isn't // cleared on respawn // NOTE: may not have more than 16 typedef enum { PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! PERS_HITS, // total points damage inflicted so damage beeps can sound on change PERS_RANK, // player rank or team rank PERS_TEAM, // player team PERS_SPAWN_COUNT, // incremented every respawn PERS_PLAYEREVENTS, // 16 bits that can be flipped for events PERS_ATTACKER, // clientnum of last damage inflicter PERS_ATTACKEE_ARMOR, // health/armor of last person we attacked PERS_KILLED, // count of the number of times you died // player awards tracking PERS_IMPRESSIVE_COUNT, // two railgun hits in a row PERS_EXCELLENT_COUNT, // two successive kills in a short amount of time PERS_DEFEND_COUNT, // defend awards PERS_ASSIST_COUNT, // assist awards PERS_GAUNTLET_FRAG_COUNT, // kills with the guantlet PERS_CAPTURES // captures } persEnum_t; // entityState_t->eFlags #define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD #ifdef MISSIONPACK #define EF_TICKING 0x00000002 // used to make players play the prox mine ticking sound #endif #define EF_TELEPORT_BIT 0x00000004 // toggled every time the origin abruptly changes #define EF_AWARD_EXCELLENT 0x00000008 // draw an excellent sprite #define EF_PLAYER_EVENT 0x00000010 #define EF_BOUNCE 0x00000010 // for missiles #define EF_BOUNCE_HALF 0x00000020 // for missiles #define EF_AWARD_GAUNTLET 0x00000040 // draw a gauntlet sprite #define EF_NODRAW 0x00000080 // may have an event, but no model (unspawned items) #define EF_FIRING 0x00000100 // for lightning gun #define EF_KAMIKAZE 0x00000200 #define EF_MOVER_STOP 0x00000400 // will push otherwise #define EF_AWARD_CAP 0x00000800 // draw the capture sprite #define EF_TALK 0x00001000 // draw a talk balloon #define EF_CONNECTION 0x00002000 // draw a connection trouble sprite #define EF_VOTED 0x00004000 // already cast a vote #define EF_AWARD_IMPRESSIVE 0x00008000 // draw an impressive sprite #define EF_AWARD_DEFEND 0x00010000 // draw a defend sprite #define EF_AWARD_ASSIST 0x00020000 // draw a assist sprite #define EF_AWARD_DENIED 0x00040000 // denied #define EF_TEAMVOTED 0x00080000 // already cast a team vote // NOTE: may not have more than 16 typedef enum { PW_NONE, PW_QUAD, PW_BATTLESUIT, PW_HASTE, PW_INVIS, PW_REGEN, PW_FLIGHT, PW_REDFLAG, PW_BLUEFLAG, PW_NEUTRALFLAG, PW_SCOUT, PW_GUARD, PW_DOUBLER, PW_AMMOREGEN, PW_INVULNERABILITY, PW_NUM_POWERUPS } powerup_t; typedef enum { HI_NONE, HI_TELEPORTER, HI_MEDKIT, HI_KAMIKAZE, HI_PORTAL, HI_INVULNERABILITY, HI_NUM_HOLDABLE } holdable_t; typedef enum { WP_NONE, WP_GAUNTLET, WP_MACHINEGUN, WP_SHOTGUN, WP_GRENADE_LAUNCHER, WP_ROCKET_LAUNCHER, WP_LIGHTNING, WP_RAILGUN, WP_PLASMAGUN, WP_BFG, WP_GRAPPLING_HOOK, #ifdef MISSIONPACK WP_NAILGUN, WP_PROX_LAUNCHER, WP_CHAINGUN, #endif WP_NUM_WEAPONS } weapon_t; // reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) #define PLAYEREVENT_DENIEDREWARD 0x0001 #define PLAYEREVENT_GAUNTLETREWARD 0x0002 #define PLAYEREVENT_HOLYSHIT 0x0004 // entityState_t->event values // entity events are for effects that take place reletive // to an existing entities origin. Very network efficient. // two bits at the top of the entityState->event field // will be incremented with each change in the event so // that an identical event started twice in a row can // be distinguished. And off the value with ~EV_EVENT_BITS // to retrieve the actual event number #define EV_EVENT_BIT1 0x00000100 #define EV_EVENT_BIT2 0x00000200 #define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2) #define EVENT_VALID_MSEC 300 typedef enum { EV_NONE, EV_FOOTSTEP, EV_FOOTSTEP_METAL, EV_FOOTSPLASH, EV_FOOTWADE, EV_SWIM, EV_STEP_4, EV_STEP_8, EV_STEP_12, EV_STEP_16, EV_FALL_SHORT, EV_FALL_MEDIUM, EV_FALL_FAR, EV_JUMP_PAD, // boing sound at origin, jump sound on player EV_JUMP, EV_WATER_TOUCH, // foot touches EV_WATER_LEAVE, // foot leaves EV_WATER_UNDER, // head touches EV_WATER_CLEAR, // head leaves EV_ITEM_PICKUP, // normal item pickups are predictable EV_GLOBAL_ITEM_PICKUP, // powerup / team sounds are broadcast to everyone EV_NOAMMO, EV_CHANGE_WEAPON, EV_FIRE_WEAPON, EV_USE_ITEM0, EV_USE_ITEM1, EV_USE_ITEM2, EV_USE_ITEM3, EV_USE_ITEM4, EV_USE_ITEM5, EV_USE_ITEM6, EV_USE_ITEM7, EV_USE_ITEM8, EV_USE_ITEM9, EV_USE_ITEM10, EV_USE_ITEM11, EV_USE_ITEM12, EV_USE_ITEM13, EV_USE_ITEM14, EV_USE_ITEM15, EV_ITEM_RESPAWN, EV_ITEM_POP, EV_PLAYER_TELEPORT_IN, EV_PLAYER_TELEPORT_OUT, EV_GRENADE_BOUNCE, // eventParm will be the soundindex EV_GENERAL_SOUND, EV_GLOBAL_SOUND, // no attenuation EV_GLOBAL_TEAM_SOUND, EV_BULLET_HIT_FLESH, EV_BULLET_HIT_WALL, EV_MISSILE_HIT, EV_MISSILE_MISS, EV_MISSILE_MISS_METAL, EV_RAILTRAIL, EV_SHOTGUN, EV_BULLET, // otherEntity is the shooter EV_PAIN, EV_DEATH1, EV_DEATH2, EV_DEATH3, EV_OBITUARY, EV_POWERUP_QUAD, EV_POWERUP_BATTLESUIT, EV_POWERUP_REGEN, EV_GIB_PLAYER, // gib a previously living player EV_SCOREPLUM, // score plum //#ifdef MISSIONPACK EV_PROXIMITY_MINE_STICK, EV_PROXIMITY_MINE_TRIGGER, EV_KAMIKAZE, // kamikaze explodes EV_OBELISKEXPLODE, // obelisk explodes EV_OBELISKPAIN, // obelisk is in pain EV_INVUL_IMPACT, // invulnerability sphere impact EV_JUICED, // invulnerability juiced effect EV_LIGHTNINGBOLT, // lightning bolt bounced of invulnerability sphere //#endif EV_DEBUG_LINE, EV_STOPLOOPINGSOUND, EV_TAUNT, EV_TAUNT_YES, EV_TAUNT_NO, EV_TAUNT_FOLLOWME, EV_TAUNT_GETFLAG, EV_TAUNT_GUARDBASE, EV_TAUNT_PATROL } entity_event_t; typedef enum { GTS_RED_CAPTURE, GTS_BLUE_CAPTURE, GTS_RED_RETURN, GTS_BLUE_RETURN, GTS_RED_TAKEN, GTS_BLUE_TAKEN, GTS_REDOBELISK_ATTACKED, GTS_BLUEOBELISK_ATTACKED, GTS_REDTEAM_SCORED, GTS_BLUETEAM_SCORED, GTS_REDTEAM_TOOK_LEAD, GTS_BLUETEAM_TOOK_LEAD, GTS_TEAMS_ARE_TIED, GTS_KAMIKAZE } global_team_sound_t; // animations typedef enum { BOTH_DEATH1, BOTH_DEAD1, BOTH_DEATH2, BOTH_DEAD2, BOTH_DEATH3, BOTH_DEAD3, TORSO_GESTURE, TORSO_ATTACK, TORSO_ATTACK2, TORSO_DROP, TORSO_RAISE, TORSO_STAND, TORSO_STAND2, LEGS_WALKCR, LEGS_WALK, LEGS_RUN, LEGS_BACK, LEGS_SWIM, LEGS_JUMP, LEGS_LAND, LEGS_JUMPB, LEGS_LANDB, LEGS_IDLE, LEGS_IDLECR, LEGS_TURN, TORSO_GETFLAG, TORSO_GUARDBASE, TORSO_PATROL, TORSO_FOLLOWME, TORSO_AFFIRMATIVE, TORSO_NEGATIVE, MAX_ANIMATIONS, LEGS_BACKCR, LEGS_BACKWALK, FLAG_RUN, FLAG_STAND, FLAG_STAND2RUN, MAX_TOTALANIMATIONS } animNumber_t; typedef struct animation_s { int firstFrame; int numFrames; int loopFrames; // 0 to numFrames int frameLerp; // msec between frames int initialLerp; // msec to get to first frame int reversed; // true if animation is reversed int flipflop; // true if animation should flipflop back to base } animation_t; // flip the togglebit every time an animation // changes so a restart of the same anim can be detected #define ANIM_TOGGLEBIT 128 typedef enum { TEAM_FREE, TEAM_RED, TEAM_BLUE, TEAM_SPECTATOR, TEAM_NUM_TEAMS } team_t; // Time between location updates #define TEAM_LOCATION_UPDATE_TIME 1000 // How many players on the overlay #define TEAM_MAXOVERLAY 32 //team task typedef enum { TEAMTASK_NONE, TEAMTASK_OFFENSE, TEAMTASK_DEFENSE, TEAMTASK_PATROL, TEAMTASK_FOLLOW, TEAMTASK_RETRIEVE, TEAMTASK_ESCORT, TEAMTASK_CAMP } teamtask_t; // means of death typedef enum { MOD_UNKNOWN, MOD_SHOTGUN, MOD_GAUNTLET, MOD_MACHINEGUN, MOD_GRENADE, MOD_GRENADE_SPLASH, MOD_ROCKET, MOD_ROCKET_SPLASH, MOD_PLASMA, MOD_PLASMA_SPLASH, MOD_RAILGUN, MOD_LIGHTNING, MOD_BFG, MOD_BFG_SPLASH, MOD_WATER, MOD_SLIME, MOD_LAVA, MOD_CRUSH, MOD_TELEFRAG, MOD_FALLING, MOD_SUICIDE, MOD_TARGET_LASER, MOD_TRIGGER_HURT, #ifdef MISSIONPACK MOD_NAIL, MOD_CHAINGUN, MOD_PROXIMITY_MINE, MOD_KAMIKAZE, MOD_JUICED, #endif MOD_GRAPPLE } meansOfDeath_t; //--------------------------------------------------------- // gitem_t->type typedef enum { IT_BAD, IT_WEAPON, // EFX: rotate + upscale + minlight IT_AMMO, // EFX: rotate IT_ARMOR, // EFX: rotate + minlight IT_HEALTH, // EFX: static external sphere + rotating internal IT_POWERUP, // instant on, timer based // EFX: rotate + external ring that rotates IT_HOLDABLE, // single use, holdable item // EFX: rotate + bob IT_PERSISTANT_POWERUP, IT_TEAM } itemType_t; #define MAX_ITEM_MODELS 4 typedef struct gitem_s { char *classname; // spawning name char *pickup_sound; char *world_model[MAX_ITEM_MODELS]; char *icon; char *pickup_name; // for printing on pickup int quantity; // for ammo how much, or duration of powerup itemType_t giType; // IT_* flags int giTag; char *precaches; // string of all models and images this item will use char *sounds; // string of all sounds this item will use } gitem_t; // included in both the game dll and the client extern gitem_t bg_itemlist[]; extern int bg_numItems; gitem_t *BG_FindItem( const char *pickupName ); gitem_t *BG_FindItemForWeapon( weapon_t weapon ); gitem_t *BG_FindItemForPowerup( powerup_t pw ); gitem_t *BG_FindItemForHoldable( holdable_t pw ); #define ITEM_INDEX(x) ((x)-bg_itemlist) qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ); // g_dmflags->integer flags #define DF_NO_FALLING 8 #define DF_FIXED_FOV 16 #define DF_NO_FOOTSTEPS 32 // content masks #define MASK_ALL (-1) #define MASK_SOLID (CONTENTS_SOLID) #define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) #define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) #define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) #define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) #define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE) // // entityState_t->eType // typedef enum { ET_GENERAL, ET_PLAYER, ET_ITEM, ET_MISSILE, ET_MOVER, ET_BEAM, ET_PORTAL, ET_SPEAKER, ET_PUSH_TRIGGER, ET_TELEPORT_TRIGGER, ET_INVISIBLE, ET_GRAPPLE, // grapple hooked on wall ET_TEAM, ET_EVENTS // any of the EV_* events can be added freestanding // by setting eType to ET_EVENTS + eventNum // this avoids having to set eFlags and eventNum } entityType_t; void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ); void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); #define ARENAS_PER_TIER 4 #define MAX_ARENAS 1024 #define MAX_ARENAS_TEXT 8192 #define MAX_BOTS 1024 #define MAX_BOTS_TEXT 8192 // Kamikaze // 1st shockwave times #define KAMI_SHOCKWAVE_STARTTIME 0 #define KAMI_SHOCKWAVEFADE_STARTTIME 1500 #define KAMI_SHOCKWAVE_ENDTIME 2000 // explosion/implosion times #define KAMI_EXPLODE_STARTTIME 250 #define KAMI_IMPLODE_STARTTIME 2000 #define KAMI_IMPLODE_ENDTIME 2250 // 2nd shockwave times #define KAMI_SHOCKWAVE2_STARTTIME 2000 #define KAMI_SHOCKWAVE2FADE_STARTTIME 2500 #define KAMI_SHOCKWAVE2_ENDTIME 3000 // radius of the models without scaling #define KAMI_SHOCKWAVEMODEL_RADIUS 88 #define KAMI_BOOMSPHEREMODEL_RADIUS 72 // maximum radius of the models during the effect #define KAMI_SHOCKWAVE_MAXRADIUS 1320 #define KAMI_BOOMSPHERE_MAXRADIUS 720 #define KAMI_SHOCKWAVE2_MAXRADIUS 704 ================================================ FILE: code/game/bg_slidemove.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // bg_slidemove.c -- part of bg_pmove functionality #include "q_shared.h" #include "bg_public.h" #include "bg_local.h" /* input: origin, velocity, bounds, groundPlane, trace function output: origin, velocity, impacts, stairup boolean */ /* ================== PM_SlideMove Returns qtrue if the velocity was clipped in some way ================== */ #define MAX_CLIP_PLANES 5 qboolean PM_SlideMove( qboolean gravity ) { int bumpcount, numbumps; vec3_t dir; float d; int numplanes; vec3_t planes[MAX_CLIP_PLANES]; vec3_t primal_velocity; vec3_t clipVelocity; int i, j, k; trace_t trace; vec3_t end; float time_left; float into; vec3_t endVelocity; vec3_t endClipVelocity; numbumps = 4; VectorCopy (pm->ps->velocity, primal_velocity); if ( gravity ) { VectorCopy( pm->ps->velocity, endVelocity ); endVelocity[2] -= pm->ps->gravity * pml.frametime; pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; primal_velocity[2] = endVelocity[2]; if ( pml.groundPlane ) { // slide along the ground plane PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); } } time_left = pml.frametime; // never turn against the ground plane if ( pml.groundPlane ) { numplanes = 1; VectorCopy( pml.groundTrace.plane.normal, planes[0] ); } else { numplanes = 0; } // never turn against original velocity VectorNormalize2( pm->ps->velocity, planes[numplanes] ); numplanes++; for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) { // calculate position we are trying to move to VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); // see if we can make it there pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask); if (trace.allsolid) { // entity is completely trapped in another solid pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration return qtrue; } if (trace.fraction > 0) { // actually covered some distance VectorCopy (trace.endpos, pm->ps->origin); } if (trace.fraction == 1) { break; // moved the entire distance } // save entity for contact PM_AddTouchEnt( trace.entityNum ); time_left -= time_left * trace.fraction; if (numplanes >= MAX_CLIP_PLANES) { // this shouldn't really happen VectorClear( pm->ps->velocity ); return qtrue; } // // if this is the same plane we hit before, nudge velocity // out along it, which fixes some epsilon issues with // non-axial planes // for ( i = 0 ; i < numplanes ; i++ ) { if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) { VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); break; } } if ( i < numplanes ) { continue; } VectorCopy (trace.plane.normal, planes[numplanes]); numplanes++; // // modify velocity so it parallels all of the clip planes // // find a plane that it enters for ( i = 0 ; i < numplanes ; i++ ) { into = DotProduct( pm->ps->velocity, planes[i] ); if ( into >= 0.1 ) { continue; // move doesn't interact with the plane } // see how hard we are hitting things if ( -into > pml.impactSpeed ) { pml.impactSpeed = -into; } // slide along the plane PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); // slide along the plane PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); // see if there is a second plane that the new move enters for ( j = 0 ; j < numplanes ; j++ ) { if ( j == i ) { continue; } if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) { continue; // move doesn't interact with the plane } // try clipping the move to the plane PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); // see if it goes back into the first clip plane if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) { continue; } // slide the original velocity along the crease CrossProduct (planes[i], planes[j], dir); VectorNormalize( dir ); d = DotProduct( dir, pm->ps->velocity ); VectorScale( dir, d, clipVelocity ); CrossProduct (planes[i], planes[j], dir); VectorNormalize( dir ); d = DotProduct( dir, endVelocity ); VectorScale( dir, d, endClipVelocity ); // see if there is a third plane the the new move enters for ( k = 0 ; k < numplanes ; k++ ) { if ( k == i || k == j ) { continue; } if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) { continue; // move doesn't interact with the plane } // stop dead at a tripple plane interaction VectorClear( pm->ps->velocity ); return qtrue; } } // if we have fixed all interactions, try another move VectorCopy( clipVelocity, pm->ps->velocity ); VectorCopy( endClipVelocity, endVelocity ); break; } } if ( gravity ) { VectorCopy( endVelocity, pm->ps->velocity ); } // don't change velocity if in a timer (FIXME: is this correct?) if ( pm->ps->pm_time ) { VectorCopy( primal_velocity, pm->ps->velocity ); } return ( bumpcount != 0 ); } /* ================== PM_StepSlideMove ================== */ void PM_StepSlideMove( qboolean gravity ) { vec3_t start_o, start_v; vec3_t down_o, down_v; trace_t trace; // float down_dist, up_dist; // vec3_t delta, delta2; vec3_t up, down; float stepSize; VectorCopy (pm->ps->origin, start_o); VectorCopy (pm->ps->velocity, start_v); if ( PM_SlideMove( gravity ) == 0 ) { return; // we got exactly where we wanted to go first try } VectorCopy(start_o, down); down[2] -= STEPSIZE; pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); VectorSet(up, 0, 0, 1); // never step up when you still have up velocity if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || DotProduct(trace.plane.normal, up) < 0.7)) { return; } VectorCopy (pm->ps->origin, down_o); VectorCopy (pm->ps->velocity, down_v); VectorCopy (start_o, up); up[2] += STEPSIZE; // test the player position if they were a stepheight higher pm->trace (&trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask); if ( trace.allsolid ) { if ( pm->debugLevel ) { Com_Printf("%i:bend can't step\n", c_pmove); } return; // can't step up } stepSize = trace.endpos[2] - start_o[2]; // try slidemove from this position VectorCopy (trace.endpos, pm->ps->origin); VectorCopy (start_v, pm->ps->velocity); PM_SlideMove( gravity ); // push down the final amount VectorCopy (pm->ps->origin, down); down[2] -= stepSize; pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); if ( !trace.allsolid ) { VectorCopy (trace.endpos, pm->ps->origin); } if ( trace.fraction < 1.0 ) { PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); } #if 0 // if the down trace can trace back to the original position directly, don't step pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, start_o, pm->ps->clientNum, pm->tracemask); if ( trace.fraction == 1.0 ) { // use the original move VectorCopy (down_o, pm->ps->origin); VectorCopy (down_v, pm->ps->velocity); if ( pm->debugLevel ) { Com_Printf("%i:bend\n", c_pmove); } } else #endif { // use the step move float delta; delta = pm->ps->origin[2] - start_o[2]; if ( delta > 2 ) { if ( delta < 7 ) { PM_AddEvent( EV_STEP_4 ); } else if ( delta < 11 ) { PM_AddEvent( EV_STEP_8 ); } else if ( delta < 15 ) { PM_AddEvent( EV_STEP_12 ); } else { PM_AddEvent( EV_STEP_16 ); } } if ( pm->debugLevel ) { Com_Printf("%i:stepped\n", c_pmove); } } } ================================================ FILE: code/game/botlib.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // /***************************************************************************** * name: botlib.h * * desc: bot AI library * * $Archive: /source/code/game/botai.h $ * *****************************************************************************/ #define BOTLIB_API_VERSION 2 struct aas_clientmove_s; struct aas_entityinfo_s; struct aas_areainfo_s; struct aas_altroutegoal_s; struct aas_predictroute_s; struct bot_consolemessage_s; struct bot_match_s; struct bot_goal_s; struct bot_moveresult_s; struct bot_initmove_s; struct weaponinfo_s; #define BOTFILESBASEFOLDER "botfiles" //debug line colors #define LINECOLOR_NONE -1 #define LINECOLOR_RED 1//0xf2f2f0f0L #define LINECOLOR_GREEN 2//0xd0d1d2d3L #define LINECOLOR_BLUE 3//0xf3f3f1f1L #define LINECOLOR_YELLOW 4//0xdcdddedfL #define LINECOLOR_ORANGE 5//0xe0e1e2e3L //Print types #define PRT_MESSAGE 1 #define PRT_WARNING 2 #define PRT_ERROR 3 #define PRT_FATAL 4 #define PRT_EXIT 5 //console message types #define CMS_NORMAL 0 #define CMS_CHAT 1 //botlib error codes #define BLERR_NOERROR 0 //no error #define BLERR_LIBRARYNOTSETUP 1 //library not setup #define BLERR_INVALIDENTITYNUMBER 2 //invalid entity number #define BLERR_NOAASFILE 3 //no AAS file available #define BLERR_CANNOTOPENAASFILE 4 //cannot open AAS file #define BLERR_WRONGAASFILEID 5 //incorrect AAS file id #define BLERR_WRONGAASFILEVERSION 6 //incorrect AAS file version #define BLERR_CANNOTREADAASLUMP 7 //cannot read AAS file lump #define BLERR_CANNOTLOADICHAT 8 //cannot load initial chats #define BLERR_CANNOTLOADITEMWEIGHTS 9 //cannot load item weights #define BLERR_CANNOTLOADITEMCONFIG 10 //cannot load item config #define BLERR_CANNOTLOADWEAPONWEIGHTS 11 //cannot load weapon weights #define BLERR_CANNOTLOADWEAPONCONFIG 12 //cannot load weapon config //action flags #define ACTION_ATTACK 0x0000001 #define ACTION_USE 0x0000002 #define ACTION_RESPAWN 0x0000008 #define ACTION_JUMP 0x0000010 #define ACTION_MOVEUP 0x0000020 #define ACTION_CROUCH 0x0000080 #define ACTION_MOVEDOWN 0x0000100 #define ACTION_MOVEFORWARD 0x0000200 #define ACTION_MOVEBACK 0x0000800 #define ACTION_MOVELEFT 0x0001000 #define ACTION_MOVERIGHT 0x0002000 #define ACTION_DELAYEDJUMP 0x0008000 #define ACTION_TALK 0x0010000 #define ACTION_GESTURE 0x0020000 #define ACTION_WALK 0x0080000 #define ACTION_AFFIRMATIVE 0x0100000 #define ACTION_NEGATIVE 0x0200000 #define ACTION_GETFLAG 0x0800000 #define ACTION_GUARDBASE 0x1000000 #define ACTION_PATROL 0x2000000 #define ACTION_FOLLOWME 0x8000000 //the bot input, will be converted to an usercmd_t typedef struct bot_input_s { float thinktime; //time since last output (in seconds) vec3_t dir; //movement direction float speed; //speed in the range [0, 400] vec3_t viewangles; //the view angles int actionflags; //one of the ACTION_? flags int weapon; //weapon to use } bot_input_t; #ifndef BSPTRACE #define BSPTRACE //bsp_trace_t hit surface typedef struct bsp_surface_s { char name[16]; int flags; int value; } bsp_surface_t; //remove the bsp_trace_s structure definition l8r on //a trace is returned when a box is swept through the world 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; #endif // BSPTRACE //entity state typedef struct bot_entitystate_s { int type; // entity type int flags; // entity flags vec3_t origin; // origin of the entity vec3_t angles; // angles of the model vec3_t old_origin; // for lerping vec3_t mins; // bounding box minimums vec3_t maxs; // bounding box maximums int groundent; // ground entity int solid; // solid type int modelindex; // model used int modelindex2; // weapons, CTF flags, etc int frame; // model frame number int event; // impulse events -- muzzle flashes, footsteps, etc int eventParm; // even parameter int powerups; // bit flags int weapon; // determines weapon and flash model, etc int legsAnim; // mask off ANIM_TOGGLEBIT int torsoAnim; // mask off ANIM_TOGGLEBIT } bot_entitystate_t; //bot AI library exported functions typedef struct botlib_import_s { //print messages from the bot library void (QDECL *Print)(int type, char *fmt, ...); //trace a bbox through the world void (*Trace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); //trace a bbox against a specific entity void (*EntityTrace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask); //retrieve the contents at the given point int (*PointContents)(vec3_t point); //check if the point is in potential visible sight int (*inPVS)(vec3_t p1, vec3_t p2); //retrieve the BSP entity data lump char *(*BSPEntityData)(void); // void (*BSPModelMinsMaxsOrigin)(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); //send a bot client command void (*BotClientCommand)(int client, char *command); //memory allocation void *(*GetMemory)(int size); // allocate from Zone void (*FreeMemory)(void *ptr); // free memory from Zone int (*AvailableMemory)(void); // available Zone memory void *(*HunkAlloc)(int size); // allocate from hunk //file system access int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); int (*FS_Read)( void *buffer, int len, fileHandle_t f ); int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); void (*FS_FCloseFile)( fileHandle_t f ); int (*FS_Seek)( fileHandle_t f, long offset, int origin ); //debug visualisation stuff int (*DebugLineCreate)(void); void (*DebugLineDelete)(int line); void (*DebugLineShow)(int line, vec3_t start, vec3_t end, int color); // int (*DebugPolygonCreate)(int color, int numPoints, vec3_t *points); void (*DebugPolygonDelete)(int id); } botlib_import_t; typedef struct aas_export_s { //----------------------------------- // be_aas_entity.h //----------------------------------- void (*AAS_EntityInfo)(int entnum, struct aas_entityinfo_s *info); //----------------------------------- // be_aas_main.h //----------------------------------- int (*AAS_Initialized)(void); void (*AAS_PresenceTypeBoundingBox)(int presencetype, vec3_t mins, vec3_t maxs); float (*AAS_Time)(void); //-------------------------------------------- // be_aas_sample.c //-------------------------------------------- int (*AAS_PointAreaNum)(vec3_t point); int (*AAS_PointReachabilityAreaIndex)( vec3_t point ); int (*AAS_TraceAreas)(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); int (*AAS_BBoxAreas)(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); int (*AAS_AreaInfo)( int areanum, struct aas_areainfo_s *info ); //-------------------------------------------- // be_aas_bspq3.c //-------------------------------------------- int (*AAS_PointContents)(vec3_t point); int (*AAS_NextBSPEntity)(int ent); int (*AAS_ValueForBSPEpairKey)(int ent, char *key, char *value, int size); int (*AAS_VectorForBSPEpairKey)(int ent, char *key, vec3_t v); int (*AAS_FloatForBSPEpairKey)(int ent, char *key, float *value); int (*AAS_IntForBSPEpairKey)(int ent, char *key, int *value); //-------------------------------------------- // be_aas_reach.c //-------------------------------------------- int (*AAS_AreaReachability)(int areanum); //-------------------------------------------- // be_aas_route.c //-------------------------------------------- int (*AAS_AreaTravelTimeToGoalArea)(int areanum, vec3_t origin, int goalareanum, int travelflags); int (*AAS_EnableRoutingArea)(int areanum, int enable); int (*AAS_PredictRoute)(struct aas_predictroute_s *route, int areanum, vec3_t origin, int goalareanum, int travelflags, int maxareas, int maxtime, int stopevent, int stopcontents, int stoptfl, int stopareanum); //-------------------------------------------- // be_aas_altroute.c //-------------------------------------------- int (*AAS_AlternativeRouteGoals)(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, struct aas_altroutegoal_s *altroutegoals, int maxaltroutegoals, int type); //-------------------------------------------- // be_aas_move.c //-------------------------------------------- int (*AAS_Swimming)(vec3_t origin); int (*AAS_PredictClientMovement)(struct aas_clientmove_s *move, int entnum, vec3_t origin, int presencetype, int onground, vec3_t velocity, vec3_t cmdmove, int cmdframes, int maxframes, float frametime, int stopevent, int stopareanum, int visualize); } aas_export_t; typedef struct ea_export_s { //ClientCommand elementary actions void (*EA_Command)(int client, char *command ); void (*EA_Say)(int client, char *str); void (*EA_SayTeam)(int client, char *str); // void (*EA_Action)(int client, int action); void (*EA_Gesture)(int client); void (*EA_Talk)(int client); void (*EA_Attack)(int client); void (*EA_Use)(int client); void (*EA_Respawn)(int client); void (*EA_MoveUp)(int client); void (*EA_MoveDown)(int client); void (*EA_MoveForward)(int client); void (*EA_MoveBack)(int client); void (*EA_MoveLeft)(int client); void (*EA_MoveRight)(int client); void (*EA_Crouch)(int client); void (*EA_SelectWeapon)(int client, int weapon); void (*EA_Jump)(int client); void (*EA_DelayedJump)(int client); void (*EA_Move)(int client, vec3_t dir, float speed); void (*EA_View)(int client, vec3_t viewangles); //send regular input to the server void (*EA_EndRegular)(int client, float thinktime); void (*EA_GetInput)(int client, float thinktime, bot_input_t *input); void (*EA_ResetInput)(int client); } ea_export_t; typedef struct ai_export_s { //----------------------------------- // be_ai_char.h //----------------------------------- int (*BotLoadCharacter)(char *charfile, float skill); void (*BotFreeCharacter)(int character); float (*Characteristic_Float)(int character, int index); float (*Characteristic_BFloat)(int character, int index, float min, float max); int (*Characteristic_Integer)(int character, int index); int (*Characteristic_BInteger)(int character, int index, int min, int max); void (*Characteristic_String)(int character, int index, char *buf, int size); //----------------------------------- // be_ai_chat.h //----------------------------------- int (*BotAllocChatState)(void); void (*BotFreeChatState)(int handle); void (*BotQueueConsoleMessage)(int chatstate, int type, char *message); void (*BotRemoveConsoleMessage)(int chatstate, int handle); int (*BotNextConsoleMessage)(int chatstate, struct bot_consolemessage_s *cm); int (*BotNumConsoleMessages)(int chatstate); void (*BotInitialChat)(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); int (*BotNumInitialChats)(int chatstate, char *type); int (*BotReplyChat)(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); int (*BotChatLength)(int chatstate); void (*BotEnterChat)(int chatstate, int client, int sendto); void (*BotGetChatMessage)(int chatstate, char *buf, int size); int (*StringContains)(char *str1, char *str2, int casesensitive); int (*BotFindMatch)(char *str, struct bot_match_s *match, unsigned long int context); void (*BotMatchVariable)(struct bot_match_s *match, int variable, char *buf, int size); void (*UnifyWhiteSpaces)(char *string); void (*BotReplaceSynonyms)(char *string, unsigned long int context); int (*BotLoadChatFile)(int chatstate, char *chatfile, char *chatname); void (*BotSetChatGender)(int chatstate, int gender); void (*BotSetChatName)(int chatstate, char *name, int client); //----------------------------------- // be_ai_goal.h //----------------------------------- void (*BotResetGoalState)(int goalstate); void (*BotResetAvoidGoals)(int goalstate); void (*BotRemoveFromAvoidGoals)(int goalstate, int number); void (*BotPushGoal)(int goalstate, struct bot_goal_s *goal); void (*BotPopGoal)(int goalstate); void (*BotEmptyGoalStack)(int goalstate); void (*BotDumpAvoidGoals)(int goalstate); void (*BotDumpGoalStack)(int goalstate); void (*BotGoalName)(int number, char *name, int size); int (*BotGetTopGoal)(int goalstate, struct bot_goal_s *goal); int (*BotGetSecondGoal)(int goalstate, struct bot_goal_s *goal); int (*BotChooseLTGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags); int (*BotChooseNBGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags, struct bot_goal_s *ltg, float maxtime); int (*BotTouchingGoal)(vec3_t origin, struct bot_goal_s *goal); int (*BotItemGoalInVisButNotVisible)(int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal); int (*BotGetLevelItemGoal)(int index, char *classname, struct bot_goal_s *goal); int (*BotGetNextCampSpotGoal)(int num, struct bot_goal_s *goal); int (*BotGetMapLocationGoal)(char *name, struct bot_goal_s *goal); float (*BotAvoidGoalTime)(int goalstate, int number); void (*BotSetAvoidGoalTime)(int goalstate, int number, float avoidtime); void (*BotInitLevelItems)(void); void (*BotUpdateEntityItems)(void); int (*BotLoadItemWeights)(int goalstate, char *filename); void (*BotFreeItemWeights)(int goalstate); void (*BotInterbreedGoalFuzzyLogic)(int parent1, int parent2, int child); void (*BotSaveGoalFuzzyLogic)(int goalstate, char *filename); void (*BotMutateGoalFuzzyLogic)(int goalstate, float range); int (*BotAllocGoalState)(int client); void (*BotFreeGoalState)(int handle); //----------------------------------- // be_ai_move.h //----------------------------------- void (*BotResetMoveState)(int movestate); void (*BotMoveToGoal)(struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags); int (*BotMoveInDirection)(int movestate, vec3_t dir, float speed, int type); void (*BotResetAvoidReach)(int movestate); void (*BotResetLastAvoidReach)(int movestate); int (*BotReachabilityArea)(vec3_t origin, int testground); int (*BotMovementViewTarget)(int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target); int (*BotPredictVisiblePosition)(vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target); int (*BotAllocMoveState)(void); void (*BotFreeMoveState)(int handle); void (*BotInitMoveState)(int handle, struct bot_initmove_s *initmove); void (*BotAddAvoidSpot)(int movestate, vec3_t origin, float radius, int type); //----------------------------------- // be_ai_weap.h //----------------------------------- int (*BotChooseBestFightWeapon)(int weaponstate, int *inventory); void (*BotGetWeaponInfo)(int weaponstate, int weapon, struct weaponinfo_s *weaponinfo); int (*BotLoadWeaponWeights)(int weaponstate, char *filename); int (*BotAllocWeaponState)(void); void (*BotFreeWeaponState)(int weaponstate); void (*BotResetWeaponState)(int weaponstate); //----------------------------------- // be_ai_gen.h //----------------------------------- int (*GeneticParentsAndChildSelection)(int numranks, float *ranks, int *parent1, int *parent2, int *child); } ai_export_t; //bot AI library imported functions typedef struct botlib_export_s { //Area Awareness System functions aas_export_t aas; //Elementary Action functions ea_export_t ea; //AI functions ai_export_t ai; //setup the bot library, returns BLERR_ int (*BotLibSetup)(void); //shutdown the bot library, returns BLERR_ int (*BotLibShutdown)(void); //sets a library variable returns BLERR_ int (*BotLibVarSet)(char *var_name, char *value); //gets a library variable returns BLERR_ int (*BotLibVarGet)(char *var_name, char *value, int size); //sets a C-like define returns BLERR_ int (*PC_AddGlobalDefine)(char *string); int (*PC_LoadSourceHandle)(const char *filename); int (*PC_FreeSourceHandle)(int handle); int (*PC_ReadTokenHandle)(int handle, pc_token_t *pc_token); int (*PC_SourceFileAndLine)(int handle, char *filename, int *line); //start a frame in the bot library int (*BotLibStartFrame)(float time); //load a new map in the bot library int (*BotLibLoadMap)(const char *mapname); //entity updates int (*BotLibUpdateEntity)(int ent, bot_entitystate_t *state); //just for testing int (*Test)(int parm0, char *parm1, vec3_t parm2, vec3_t parm3); } botlib_export_t; //linking of bot library botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ); /* Library variables: name: default: module(s): description: "basedir" "" l_utils.c base directory "gamedir" "" l_utils.c game directory "cddir" "" l_utils.c CD directory "log" "0" l_log.c enable/disable creating a log file "maxclients" "4" be_interface.c maximum number of clients "maxentities" "1024" be_interface.c maximum number of entities "bot_developer" "0" be_interface.c bot developer mode "phys_friction" "6" be_aas_move.c ground friction "phys_stopspeed" "100" be_aas_move.c stop speed "phys_gravity" "800" be_aas_move.c gravity value "phys_waterfriction" "1" be_aas_move.c water friction "phys_watergravity" "400" be_aas_move.c gravity in water "phys_maxvelocity" "320" be_aas_move.c maximum velocity "phys_maxwalkvelocity" "320" be_aas_move.c maximum walk velocity "phys_maxcrouchvelocity" "100" be_aas_move.c maximum crouch velocity "phys_maxswimvelocity" "150" be_aas_move.c maximum swim velocity "phys_walkaccelerate" "10" be_aas_move.c walk acceleration "phys_airaccelerate" "1" be_aas_move.c air acceleration "phys_swimaccelerate" "4" be_aas_move.c swim acceleration "phys_maxstep" "18" be_aas_move.c maximum step height "phys_maxsteepness" "0.7" be_aas_move.c maximum floor steepness "phys_maxbarrier" "32" be_aas_move.c maximum barrier height "phys_maxwaterjump" "19" be_aas_move.c maximum waterjump height "phys_jumpvel" "270" be_aas_move.c jump z velocity "phys_falldelta5" "40" be_aas_move.c "phys_falldelta10" "60" be_aas_move.c "rs_waterjump" "400" be_aas_move.c "rs_teleport" "50" be_aas_move.c "rs_barrierjump" "100" be_aas_move.c "rs_startcrouch" "300" be_aas_move.c "rs_startgrapple" "500" be_aas_move.c "rs_startwalkoffledge" "70" be_aas_move.c "rs_startjump" "300" be_aas_move.c "rs_rocketjump" "500" be_aas_move.c "rs_bfgjump" "500" be_aas_move.c "rs_jumppad" "250" be_aas_move.c "rs_aircontrolledjumppad" "300" be_aas_move.c "rs_funcbob" "300" be_aas_move.c "rs_startelevator" "50" be_aas_move.c "rs_falldamage5" "300" be_aas_move.c "rs_falldamage10" "500" be_aas_move.c "rs_maxjumpfallheight" "450" be_aas_move.c "max_aaslinks" "4096" be_aas_sample.c maximum links in the AAS "max_routingcache" "4096" be_aas_route.c maximum routing cache size in KB "forceclustering" "0" be_aas_main.c force recalculation of clusters "forcereachability" "0" be_aas_main.c force recalculation of reachabilities "forcewrite" "0" be_aas_main.c force writing of aas file "aasoptimize" "0" be_aas_main.c enable aas optimization "sv_mapChecksum" "0" be_aas_main.c BSP file checksum "bot_visualizejumppads" "0" be_aas_reach.c visualize jump pads "bot_reloadcharacters" "0" - reload bot character files "ai_gametype" "0" be_ai_goal.c game type "droppedweight" "1000" be_ai_goal.c additional dropped item weight "weapindex_rocketlauncher" "5" be_ai_move.c rl weapon index for rocket jumping "weapindex_bfg10k" "9" be_ai_move.c bfg weapon index for bfg jumping "weapindex_grapple" "10" be_ai_move.c grapple weapon index for grappling "entitytypemissile" "3" be_ai_move.c ET_MISSILE "offhandgrapple" "0" be_ai_move.c enable off hand grapple hook "cmd_grappleon" "grappleon" be_ai_move.c command to activate off hand grapple "cmd_grappleoff" "grappleoff" be_ai_move.c command to deactivate off hand grapple "itemconfig" "items.c" be_ai_goal.c item configuration file "weaponconfig" "weapons.c" be_ai_weap.c weapon configuration file "synfile" "syn.c" be_ai_chat.c file with synonyms "rndfile" "rnd.c" be_ai_chat.c file with random strings "matchfile" "match.c" be_ai_chat.c file with match strings "nochat" "0" be_ai_chat.c disable chats "max_messages" "1024" be_ai_chat.c console message heap size "max_weaponinfo" "32" be_ai_weap.c maximum number of weapon info "max_projectileinfo" "32" be_ai_weap.c maximum number of projectile info "max_iteminfo" "256" be_ai_goal.c maximum number of item info "max_levelitems" "256" be_ai_goal.c maximum number of level items */ ================================================ FILE: code/game/chars.h ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ //======================================================== //======================================================== //name #define CHARACTERISTIC_NAME 0 //string //gender of the bot #define CHARACTERISTIC_GENDER 1 //string ("male", "female", "it") //attack skill // > 0.0 && < 0.2 = don't move // > 0.3 && < 1.0 = aim at enemy during retreat // > 0.0 && < 0.4 = only move forward/backward // >= 0.4 && < 1.0 = circle strafing // > 0.7 && < 1.0 = random strafe direction change #define CHARACTERISTIC_ATTACK_SKILL 2 //float [0, 1] //weapon weight file #define CHARACTERISTIC_WEAPONWEIGHTS 3 //string //view angle difference to angle change factor #define CHARACTERISTIC_VIEW_FACTOR 4 //float <0, 1] //maximum view angle change #define CHARACTERISTIC_VIEW_MAXCHANGE 5 //float [1, 360] //reaction time in seconds #define CHARACTERISTIC_REACTIONTIME 6 //float [0, 5] //accuracy when aiming #define CHARACTERISTIC_AIM_ACCURACY 7 //float [0, 1] //weapon specific aim accuracy #define CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN 8 //float [0, 1] #define CHARACTERISTIC_AIM_ACCURACY_SHOTGUN 9 //float [0, 1] #define CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER 10 //float [0, 1] #define CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER 11 //float [0, 1] #define CHARACTERISTIC_AIM_ACCURACY_LIGHTNING 12 #define CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN 13 //float [0, 1] #define CHARACTERISTIC_AIM_ACCURACY_RAILGUN 14 #define CHARACTERISTIC_AIM_ACCURACY_BFG10K 15 //float [0, 1] //skill when aiming // > 0.0 && < 0.9 = aim is affected by enemy movement // > 0.4 && <= 0.8 = enemy linear leading // > 0.8 && <= 1.0 = enemy exact movement leading // > 0.5 && <= 1.0 = prediction shots when enemy is not visible // > 0.6 && <= 1.0 = splash damage by shooting nearby geometry #define CHARACTERISTIC_AIM_SKILL 16 //float [0, 1] //weapon specific aim skill #define CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER 17 //float [0, 1] #define CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER 18 //float [0, 1] #define CHARACTERISTIC_AIM_SKILL_PLASMAGUN 19 //float [0, 1] #define CHARACTERISTIC_AIM_SKILL_BFG10K 20 //float [0, 1] //======================================================== //chat //======================================================== //file with chats #define CHARACTERISTIC_CHAT_FILE 21 //string //name of the chat character #define CHARACTERISTIC_CHAT_NAME 22 //string //characters per minute type speed #define CHARACTERISTIC_CHAT_CPM 23 //integer [1, 4000] //tendency to insult/praise #define CHARACTERISTIC_CHAT_INSULT 24 //float [0, 1] //tendency to chat misc #define CHARACTERISTIC_CHAT_MISC 25 //float [0, 1] //tendency to chat at start or end of level #define CHARACTERISTIC_CHAT_STARTENDLEVEL 26 //float [0, 1] //tendency to chat entering or exiting the game #define CHARACTERISTIC_CHAT_ENTEREXITGAME 27 //float [0, 1] //tendency to chat when killed someone #define CHARACTERISTIC_CHAT_KILL 28 //float [0, 1] //tendency to chat when died #define CHARACTERISTIC_CHAT_DEATH 29 //float [0, 1] //tendency to chat when enemy suicides #define CHARACTERISTIC_CHAT_ENEMYSUICIDE 30 //float [0, 1] //tendency to chat when hit while talking #define CHARACTERISTIC_CHAT_HITTALKING 31 //float [0, 1] //tendency to chat when bot was hit but didn't dye #define CHARACTERISTIC_CHAT_HITNODEATH 32 //float [0, 1] //tendency to chat when bot hit the enemy but enemy didn't dye #define CHARACTERISTIC_CHAT_HITNOKILL 33 //float [0, 1] //tendency to randomly chat #define CHARACTERISTIC_CHAT_RANDOM 34 //float [0, 1] //tendency to reply #define CHARACTERISTIC_CHAT_REPLY 35 //float [0, 1] //======================================================== //movement //======================================================== //tendency to crouch #define CHARACTERISTIC_CROUCHER 36 //float [0, 1] //tendency to jump #define CHARACTERISTIC_JUMPER 37 //float [0, 1] //tendency to walk #define CHARACTERISTIC_WALKER 48 //float [0, 1] //tendency to jump using a weapon #define CHARACTERISTIC_WEAPONJUMPING 38 //float [0, 1] //tendency to use the grapple hook when available #define CHARACTERISTIC_GRAPPLE_USER 39 //float [0, 1] //use this!! //======================================================== //goal //======================================================== //item weight file #define CHARACTERISTIC_ITEMWEIGHTS 40 //string //the aggression of the bot #define CHARACTERISTIC_AGGRESSION 41 //float [0, 1] //the self preservation of the bot (rockets near walls etc.) #define CHARACTERISTIC_SELFPRESERVATION 42 //float [0, 1] //how likely the bot is to take revenge #define CHARACTERISTIC_VENGEFULNESS 43 //float [0, 1] //use this!! //tendency to camp #define CHARACTERISTIC_CAMPER 44 //float [0, 1] //======================================================== //======================================================== //tendency to get easy frags #define CHARACTERISTIC_EASY_FRAGGER 45 //float [0, 1] //how alert the bot is (view distance) #define CHARACTERISTIC_ALERTNESS 46 //float [0, 1] //how much the bot fires it's weapon #define CHARACTERISTIC_FIRETHROTTLE 47 //float [0, 1] ================================================ FILE: code/game/g_active.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // #include "g_local.h" /* =============== G_DamageFeedback Called just before a snapshot is sent to the given player. Totals up all damage and generates both the player_state_t damage values to that client for pain blends and kicks, and global pain sound events for all clients. =============== */ void P_DamageFeedback( gentity_t *player ) { gclient_t *client; float count; vec3_t angles; client = player->client; if ( client->ps.pm_type == PM_DEAD ) { return; } // total points of damage shot at the player this frame count = client->damage_blood + client->damage_armor; if ( count == 0 ) { return; // didn't take any damage } if ( count > 255 ) { count = 255; } // send the information to the client // world damage (falling, slime, etc) uses a special code // to make the blend blob centered instead of positional if ( client->damage_fromWorld ) { client->ps.damagePitch = 255; client->ps.damageYaw = 255; client->damage_fromWorld = qfalse; } else { vectoangles( client->damage_from, angles ); client->ps.damagePitch = angles[PITCH]/360.0 * 256; client->ps.damageYaw = angles[YAW]/360.0 * 256; } // play an apropriate pain sound if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) ) { player->pain_debounce_time = level.time + 700; G_AddEvent( player, EV_PAIN, player->health ); client->ps.damageEvent++; } client->ps.damageCount = count; // // clear totals // client->damage_blood = 0; client->damage_armor = 0; client->damage_knockback = 0; } /* ============= P_WorldEffects Check for lava / slime contents and drowning ============= */ void P_WorldEffects( gentity_t *ent ) { qboolean envirosuit; int waterlevel; if ( ent->client->noclip ) { ent->client->airOutTime = level.time + 12000; // don't need air return; } waterlevel = ent->waterlevel; envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; // // check for drowning // if ( waterlevel == 3 ) { // envirosuit give air if ( envirosuit ) { ent->client->airOutTime = level.time + 10000; } // if out of air, start drowning if ( ent->client->airOutTime < level.time) { // drown! ent->client->airOutTime += 1000; if ( ent->health > 0 ) { // take more damage the longer underwater ent->damage += 2; if (ent->damage > 15) ent->damage = 15; // play a gurp sound instead of a normal pain sound if (ent->health <= ent->damage) { G_Sound(ent, CHAN_VOICE, G_SoundIndex("*drown.wav")); } else if (rand()&1) { G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav")); } else { G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav")); } // don't play a normal pain sound ent->pain_debounce_time = level.time + 200; G_Damage (ent, NULL, NULL, NULL, NULL, ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); } } } else { ent->client->airOutTime = level.time + 12000; ent->damage = 2; } // // check for sizzle damage (move to pmove?) // if (waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { if (ent->health > 0 && ent->pain_debounce_time <= level.time ) { if ( envirosuit ) { G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); } else { if (ent->watertype & CONTENTS_LAVA) { G_Damage (ent, NULL, NULL, NULL, NULL, 30*waterlevel, 0, MOD_LAVA); } if (ent->watertype & CONTENTS_SLIME) { G_Damage (ent, NULL, NULL, NULL, NULL, 10*waterlevel, 0, MOD_SLIME); } } } } } /* =============== G_SetClientSound =============== */ void G_SetClientSound( gentity_t *ent ) { #ifdef MISSIONPACK if( ent->s.eFlags & EF_TICKING ) { ent->client->ps.loopSound = G_SoundIndex( "sound/weapons/proxmine/wstbtick.wav"); } else #endif if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { ent->client->ps.loopSound = level.snd_fry; } else { ent->client->ps.loopSound = 0; } } //============================================================== /* ============== ClientImpacts ============== */ void ClientImpacts( gentity_t *ent, pmove_t *pm ) { int i, j; trace_t trace; gentity_t *other; memset( &trace, 0, sizeof( trace ) ); for (i=0 ; inumtouch ; i++) { for (j=0 ; jtouchents[j] == pm->touchents[i] ) { break; } } if (j != i) { continue; // duplicated } other = &g_entities[ pm->touchents[i] ]; if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { ent->touch( ent, other, &trace ); } if ( !other->touch ) { continue; } other->touch( other, ent, &trace ); } } /* ============ G_TouchTriggers Find all trigger entities that ent's current position touches. Spectators will only interact with teleporters. ============ */ void G_TouchTriggers( gentity_t *ent ) { int i, num; int touch[MAX_GENTITIES]; gentity_t *hit; trace_t trace; vec3_t mins, maxs; static vec3_t range = { 40, 40, 52 }; if ( !ent->client ) { return; } // dead clients don't activate triggers! if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { return; } VectorSubtract( ent->client->ps.origin, range, mins ); VectorAdd( ent->client->ps.origin, range, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); // can't use ent->absmin, because that has a one unit pad VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); for ( i=0 ; itouch && !ent->touch ) { continue; } if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { continue; } // ignore most entities if a spectator if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { if ( hit->s.eType != ET_TELEPORT_TRIGGER && // this is ugly but adding a new ET_? type will // most likely cause network incompatibilities hit->touch != Touch_DoorTrigger) { continue; } } // use seperate code for determining if an item is picked up // so you don't have to actually contact its bounding box if ( hit->s.eType == ET_ITEM ) { if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { continue; } } else { if ( !trap_EntityContact( mins, maxs, hit ) ) { continue; } } memset( &trace, 0, sizeof(trace) ); if ( hit->touch ) { hit->touch (hit, ent, &trace); } if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { ent->touch( ent, hit, &trace ); } } // if we didn't touch a jump pad this pmove frame if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) { ent->client->ps.jumppad_frame = 0; ent->client->ps.jumppad_ent = 0; } } /* ================= SpectatorThink ================= */ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { pmove_t pm; gclient_t *client; client = ent->client; if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { client->ps.pm_type = PM_SPECTATOR; client->ps.speed = 400; // faster than normal // set up for pmove memset (&pm, 0, sizeof(pm)); pm.ps = &client->ps; pm.cmd = *ucmd; pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; // perform a pmove Pmove (&pm); // save results of pmove VectorCopy( client->ps.origin, ent->s.origin ); G_TouchTriggers( ent ); trap_UnlinkEntity( ent ); } client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; // attack button cycles through spectators if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { Cmd_FollowCycle_f( ent, 1 ); } } /* ================= ClientInactivityTimer Returns qfalse if the client is dropped ================= */ qboolean ClientInactivityTimer( gclient_t *client ) { if ( ! g_inactivity.integer ) { // give everyone some time, so if the operator sets g_inactivity during // gameplay, everyone isn't kicked client->inactivityTime = level.time + 60 * 1000; client->inactivityWarning = qfalse; } else if ( client->pers.cmd.forwardmove || client->pers.cmd.rightmove || client->pers.cmd.upmove || (client->pers.cmd.buttons & BUTTON_ATTACK) ) { client->inactivityTime = level.time + g_inactivity.integer * 1000; client->inactivityWarning = qfalse; } else if ( !client->pers.localClient ) { if ( level.time > client->inactivityTime ) { trap_DropClient( client - level.clients, "Dropped due to inactivity" ); return qfalse; } if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { client->inactivityWarning = qtrue; trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); } } return qtrue; } /* ================== ClientTimerActions Actions that happen once a second ================== */ void ClientTimerActions( gentity_t *ent, int msec ) { gclient_t *client; #ifdef MISSIONPACK int maxHealth; #endif client = ent->client; client->timeResidual += msec; while ( client->timeResidual >= 1000 ) { client->timeResidual -= 1000; // regenerate #ifdef MISSIONPACK if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { maxHealth = client->ps.stats[STAT_MAX_HEALTH] / 2; } else if ( client->ps.powerups[PW_REGEN] ) { maxHealth = client->ps.stats[STAT_MAX_HEALTH]; } else { maxHealth = 0; } if( maxHealth ) { if ( ent->health < maxHealth ) { ent->health += 15; if ( ent->health > maxHealth * 1.1 ) { ent->health = maxHealth * 1.1; } G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); } else if ( ent->health < maxHealth * 2) { ent->health += 5; if ( ent->health > maxHealth * 2 ) { ent->health = maxHealth * 2; } G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); } #else if ( client->ps.powerups[PW_REGEN] ) { if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) { ent->health += 15; if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) { ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1; } G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2) { ent->health += 5; if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) { ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2; } G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); } #endif } else { // count down health when over max if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { ent->health--; } } // count down armor when over max if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) { client->ps.stats[STAT_ARMOR]--; } } #ifdef MISSIONPACK if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { int w, max, inc, t, i; int weapList[]={WP_MACHINEGUN,WP_SHOTGUN,WP_GRENADE_LAUNCHER,WP_ROCKET_LAUNCHER,WP_LIGHTNING,WP_RAILGUN,WP_PLASMAGUN,WP_BFG,WP_NAILGUN,WP_PROX_LAUNCHER,WP_CHAINGUN}; int weapCount = sizeof(weapList) / sizeof(int); // for (i = 0; i < weapCount; i++) { w = weapList[i]; switch(w) { case WP_MACHINEGUN: max = 50; inc = 4; t = 1000; break; case WP_SHOTGUN: max = 10; inc = 1; t = 1500; break; case WP_GRENADE_LAUNCHER: max = 10; inc = 1; t = 2000; break; case WP_ROCKET_LAUNCHER: max = 10; inc = 1; t = 1750; break; case WP_LIGHTNING: max = 50; inc = 5; t = 1500; break; case WP_RAILGUN: max = 10; inc = 1; t = 1750; break; case WP_PLASMAGUN: max = 50; inc = 5; t = 1500; break; case WP_BFG: max = 10; inc = 1; t = 4000; break; case WP_NAILGUN: max = 10; inc = 1; t = 1250; break; case WP_PROX_LAUNCHER: max = 5; inc = 1; t = 2000; break; case WP_CHAINGUN: max = 100; inc = 5; t = 1000; break; default: max = 0; inc = 0; t = 1000; break; } client->ammoTimes[w] += msec; if ( client->ps.ammo[w] >= max ) { client->ammoTimes[w] = 0; } if ( client->ammoTimes[w] >= t ) { while ( client->ammoTimes[w] >= t ) client->ammoTimes[w] -= t; client->ps.ammo[w] += inc; if ( client->ps.ammo[w] > max ) { client->ps.ammo[w] = max; } } } } #endif } /* ==================== ClientIntermissionThink ==================== */ void ClientIntermissionThink( gclient_t *client ) { client->ps.eFlags &= ~EF_TALK; client->ps.eFlags &= ~EF_FIRING; // the level will exit when everyone wants to or after timeouts // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = client->pers.cmd.buttons; if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) { // this used to be an ^1 but once a player says ready, it should stick client->readyToExit = 1; } } /* ================ ClientEvents Events will be passed on to the clients for presentation, but any server game effects are handled here ================ */ void ClientEvents( gentity_t *ent, int oldEventSequence ) { int i, j; int event; gclient_t *client; int damage; vec3_t dir; vec3_t origin, angles; // qboolean fired; gitem_t *item; gentity_t *drop; client = ent->client; if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) { oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; } for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; switch ( event ) { case EV_FALL_MEDIUM: case EV_FALL_FAR: if ( ent->s.eType != ET_PLAYER ) { break; // not in the player model } if ( g_dmflags.integer & DF_NO_FALLING ) { break; } if ( event == EV_FALL_FAR ) { damage = 10; } else { damage = 5; } VectorSet (dir, 0, 0, 1); ent->pain_debounce_time = level.time + 200; // no normal pain sound G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING); break; case EV_FIRE_WEAPON: FireWeapon( ent ); break; case EV_USE_ITEM1: // teleporter // drop flags in CTF item = NULL; j = 0; if ( ent->client->ps.powerups[ PW_REDFLAG ] ) { item = BG_FindItemForPowerup( PW_REDFLAG ); j = PW_REDFLAG; } else if ( ent->client->ps.powerups[ PW_BLUEFLAG ] ) { item = BG_FindItemForPowerup( PW_BLUEFLAG ); j = PW_BLUEFLAG; } else if ( ent->client->ps.powerups[ PW_NEUTRALFLAG ] ) { item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); j = PW_NEUTRALFLAG; } if ( item ) { drop = Drop_Item( ent, item, 0 ); // decide how many seconds it has left drop->count = ( ent->client->ps.powerups[ j ] - level.time ) / 1000; if ( drop->count < 1 ) { drop->count = 1; } ent->client->ps.powerups[ j ] = 0; } #ifdef MISSIONPACK if ( g_gametype.integer == GT_HARVESTER ) { if ( ent->client->ps.generic1 > 0 ) { if ( ent->client->sess.sessionTeam == TEAM_RED ) { item = BG_FindItem( "Blue Cube" ); } else { item = BG_FindItem( "Red Cube" ); } if ( item ) { for ( j = 0; j < ent->client->ps.generic1; j++ ) { drop = Drop_Item( ent, item, 0 ); if ( ent->client->sess.sessionTeam == TEAM_RED ) { drop->spawnflags = TEAM_BLUE; } else { drop->spawnflags = TEAM_RED; } } } ent->client->ps.generic1 = 0; } } #endif SelectSpawnPoint( ent->client->ps.origin, origin, angles ); TeleportPlayer( ent, origin, angles ); break; case EV_USE_ITEM2: // medkit ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] + 25; break; #ifdef MISSIONPACK case EV_USE_ITEM3: // kamikaze // make sure the invulnerability is off ent->client->invulnerabilityTime = 0; // start the kamikze G_StartKamikaze( ent ); break; case EV_USE_ITEM4: // portal if( ent->client->portalID ) { DropPortalSource( ent ); } else { DropPortalDestination( ent ); } break; case EV_USE_ITEM5: // invulnerability ent->client->invulnerabilityTime = level.time + 10000; break; #endif default: break; } } } #ifdef MISSIONPACK /* ============== StuckInOtherClient ============== */ static int StuckInOtherClient(gentity_t *ent) { int i; gentity_t *ent2; ent2 = &g_entities[0]; for ( i = 0; i < MAX_CLIENTS; i++, ent2++ ) { if ( ent2 == ent ) { continue; } if ( !ent2->inuse ) { continue; } if ( !ent2->client ) { continue; } if ( ent2->health <= 0 ) { continue; } // if (ent2->r.absmin[0] > ent->r.absmax[0]) continue; if (ent2->r.absmin[1] > ent->r.absmax[1]) continue; if (ent2->r.absmin[2] > ent->r.absmax[2]) continue; if (ent2->r.absmax[0] < ent->r.absmin[0]) continue; if (ent2->r.absmax[1] < ent->r.absmin[1]) continue; if (ent2->r.absmax[2] < ent->r.absmin[2]) continue; return qtrue; } return qfalse; } #endif void BotTestSolid(vec3_t origin); /* ============== SendPendingPredictableEvents ============== */ void SendPendingPredictableEvents( playerState_t *ps ) { gentity_t *t; int event, seq; int extEvent, number; // if there are still events pending if ( ps->entityEventSequence < ps->eventSequence ) { // create a temporary entity for this event which is sent to everyone // except the client who generated the event seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); // set external event to zero before calling BG_PlayerStateToEntityState extEvent = ps->externalEvent; ps->externalEvent = 0; // create temporary entity for event t = G_TempEntity( ps->origin, event ); number = t->s.number; BG_PlayerStateToEntityState( ps, &t->s, qtrue ); t->s.number = number; t->s.eType = ET_EVENTS + event; t->s.eFlags |= EF_PLAYER_EVENT; t->s.otherEntityNum = ps->clientNum; // send to everyone except the client who generated the event t->r.svFlags |= SVF_NOTSINGLECLIENT; t->r.singleClient = ps->clientNum; // set back external event ps->externalEvent = extEvent; } } /* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real( gentity_t *ent ) { gclient_t *client; pmove_t pm; int oldEventSequence; int msec; usercmd_t *ucmd; client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; // sanity check the command time to prevent speedup cheating if ( ucmd->serverTime > level.time + 200 ) { ucmd->serverTime = level.time + 200; // G_Printf("serverTime <<<<<\n" ); } if ( ucmd->serverTime < level.time - 1000 ) { ucmd->serverTime = level.time - 1000; // G_Printf("serverTime >>>>>\n" ); } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { return; } if ( msec > 200 ) { msec = 200; } if ( pmove_msec.integer < 8 ) { trap_Cvar_Set("pmove_msec", "8"); } else if (pmove_msec.integer > 33) { trap_Cvar_Set("pmove_msec", "33"); } if ( pmove_fixed.integer || client->pers.pmoveFixed ) { ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; //if (ucmd->serverTime - client->ps.commandTime <= 0) // return; } // // check for exiting intermission // if ( level.intermissiontime ) { ClientIntermissionThink( client ); return; } // spectators don't do much if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { return; } SpectatorThink( ent, ucmd ); return; } // check for inactivity timer, but never drop the local client of a non-dedicated server if ( !ClientInactivityTimer( client ) ) { return; } // clear the rewards if time if ( level.time > client->rewardTime ) { client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP ); } if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } client->ps.gravity = g_gravity.value; // set speed client->ps.speed = g_speed.value; #ifdef MISSIONPACK if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { client->ps.speed *= 1.5; } else #endif if ( client->ps.powerups[PW_HASTE] ) { client->ps.speed *= 1.3; } // Let go of the hook if we aren't firing if ( client->ps.weapon == WP_GRAPPLING_HOOK && client->hook && !( ucmd->buttons & BUTTON_ATTACK ) ) { Weapon_HookFree(client->hook); } // set up for pmove oldEventSequence = client->ps.eventSequence; memset (&pm, 0, sizeof(pm)); // check for the hit-scan gauntlet, don't let the action // go through as an attack unless it actually hits something if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) && ( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) { pm.gauntletHit = CheckGauntletAttack( ent ); } if ( ent->flags & FL_FORCE_GESTURE ) { ent->flags &= ~FL_FORCE_GESTURE; ent->client->pers.cmd.buttons |= BUTTON_GESTURE; } #ifdef MISSIONPACK // check for invulnerability expansion before doing the Pmove if (client->ps.powerups[PW_INVULNERABILITY] ) { if ( !(client->ps.pm_flags & PMF_INVULEXPAND) ) { vec3_t mins = { -42, -42, -42 }; vec3_t maxs = { 42, 42, 42 }; vec3_t oldmins, oldmaxs; VectorCopy (ent->r.mins, oldmins); VectorCopy (ent->r.maxs, oldmaxs); // expand VectorCopy (mins, ent->r.mins); VectorCopy (maxs, ent->r.maxs); trap_LinkEntity(ent); // check if this would get anyone stuck in this player if ( !StuckInOtherClient(ent) ) { // set flag so the expanded size will be set in PM_CheckDuck client->ps.pm_flags |= PMF_INVULEXPAND; } // set back VectorCopy (oldmins, ent->r.mins); VectorCopy (oldmaxs, ent->r.maxs); trap_LinkEntity(ent); } } #endif pm.ps = &client->ps; pm.cmd = *ucmd; if ( pm.ps->pm_type == PM_DEAD ) { pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; } else if ( ent->r.svFlags & SVF_BOT ) { pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; } else { pm.tracemask = MASK_PLAYERSOLID; } pm.trace = trap_Trace; pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; VectorCopy( client->ps.origin, client->oldOrigin ); #ifdef MISSIONPACK if (level.intermissionQueued != 0 && g_singlePlayer.integer) { if ( level.time - level.intermissionQueued >= 1000 ) { pm.cmd.buttons = 0; pm.cmd.forwardmove = 0; pm.cmd.rightmove = 0; pm.cmd.upmove = 0; if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) { trap_SendConsoleCommand( EXEC_APPEND, "centerview\n"); } ent->client->ps.pm_type = PM_SPINTERMISSION; } } Pmove (&pm); #else Pmove (&pm); #endif // save results of pmove if ( ent->client->ps.eventSequence != oldEventSequence ) { ent->eventTime = level.time; } if (g_smoothClients.integer) { BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); } else { BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); } SendPendingPredictableEvents( &ent->client->ps ); if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { client->fireHeld = qfalse; // for grapple } // use the snapped origin for linking so it matches client predicted versions VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); VectorCopy (pm.mins, ent->r.mins); VectorCopy (pm.maxs, ent->r.maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents( ent, oldEventSequence ); // link entity now, after any personal teleporters have been used trap_LinkEntity (ent); if ( !ent->client->noclip ) { G_TouchTriggers( ent ); } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); //test for solid areas in the AAS file BotTestAAS(ent->r.currentOrigin); // touch other objects ClientImpacts( ent, &pm ); // save results of triggers and client events if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons |= client->buttons & ~client->oldbuttons; // check for respawning if ( client->ps.stats[STAT_HEALTH] <= 0 ) { // wait for the attack button to be pressed if ( level.time > client->respawnTime ) { // forcerespawn is to prevent users from waiting out powerups if ( g_forcerespawn.integer > 0 && ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) { respawn( ent ); return; } // pressing attack or use is the normal respawn method if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { respawn( ent ); } } return; } // perform once-a-second actions ClientTimerActions( ent, msec ); } /* ================== ClientThink A new command has arrived from the client ================== */ void ClientThink( int clientNum ) { gentity_t *ent; ent = g_entities + clientNum; trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); // mark the time we got info, so we can display the // phone jack if they don't get any for a while ent->client->lastCmdTime = level.time; if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { ClientThink_real( ent ); } } void G_RunClient( gentity_t *ent ) { if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { return; } ent->client->pers.cmd.serverTime = level.time; ClientThink_real( ent ); } /* ================== SpectatorClientEndFrame ================== */ void SpectatorClientEndFrame( gentity_t *ent ) { gclient_t *cl; // if we are doing a chase cam or a remote view, grab the latest info if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { int clientNum, flags; clientNum = ent->client->sess.spectatorClient; // team follow1 and team follow2 go to whatever clients are playing if ( clientNum == -1 ) { clientNum = level.follow1; } else if ( clientNum == -2 ) { clientNum = level.follow2; } if ( clientNum >= 0 ) { cl = &level.clients[ clientNum ]; if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) { flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED)); ent->client->ps = cl->ps; ent->client->ps.pm_flags |= PMF_FOLLOW; ent->client->ps.eFlags = flags; return; } else { // drop them to free spectators unless they are dedicated camera followers if ( ent->client->sess.spectatorClient >= 0 ) { ent->client->sess.spectatorState = SPECTATOR_FREE; ClientBegin( ent->client - level.clients ); } } } } if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { ent->client->ps.pm_flags |= PMF_SCOREBOARD; } else { ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; } } /* ============== ClientEndFrame Called at the end of each server frame for each connected client A fast client will have multiple ClientThink for each ClientEdFrame, while a slow client may have multiple ClientEndFrame between ClientThink. ============== */ void ClientEndFrame( gentity_t *ent ) { int i; clientPersistant_t *pers; if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { SpectatorClientEndFrame( ent ); return; } pers = &ent->client->pers; // turn off any expired powerups for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { if ( ent->client->ps.powerups[ i ] < level.time ) { ent->client->ps.powerups[ i ] = 0; } } #ifdef MISSIONPACK // set powerup for player animation if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { ent->client->ps.powerups[PW_GUARD] = level.time; } if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { ent->client->ps.powerups[PW_SCOUT] = level.time; } if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_DOUBLER ) { ent->client->ps.powerups[PW_DOUBLER] = level.time; } if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { ent->client->ps.powerups[PW_AMMOREGEN] = level.time; } if ( ent->client->invulnerabilityTime > level.time ) { ent->client->ps.powerups[PW_INVULNERABILITY] = level.time; } #endif // save network bandwidth #if 0 if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) { // FIXME: this must change eventually for non-sync demo recording VectorClear( ent->client->ps.viewangles ); } #endif // // If the end of unit layout is displayed, don't give // the player any normal movement attributes // if ( level.intermissiontime ) { return; } // burn from lava, etc P_WorldEffects (ent); // apply all the damage taken this frame P_DamageFeedback (ent); // add the EF_CONNECTION flag if we haven't gotten commands recently if ( level.time - ent->client->lastCmdTime > 1000 ) { ent->s.eFlags |= EF_CONNECTION; } else { ent->s.eFlags &= ~EF_CONNECTION; } ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... G_SetClientSound (ent); // set the latest infor if (g_smoothClients.integer) { BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); } else { BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); } SendPendingPredictableEvents( &ent->client->ps ); // set the bit for the reachability area the client is currently in // i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin ); // ent->client->areabits[i >> 3] |= 1 << (i & 7); } ================================================ FILE: code/game/g_arenas.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // // g_arenas.c // #include "g_local.h" gentity_t *podium1; gentity_t *podium2; gentity_t *podium3; /* ================== UpdateTournamentInfo ================== */ void UpdateTournamentInfo( void ) { int i; gentity_t *player; int playerClientNum; int n, accuracy, perfect, msglen; int buflen; #ifdef MISSIONPACK // bk001205 int score1, score2; qboolean won; #endif char buf[32]; char msg[MAX_STRING_CHARS]; // find the real player player = NULL; for (i = 0; i < level.maxclients; i++ ) { player = &g_entities[i]; if ( !player->inuse ) { continue; } if ( !( player->r.svFlags & SVF_BOT ) ) { break; } } // this should never happen! if ( !player || i == level.maxclients ) { return; } playerClientNum = i; CalculateRanks(); if ( level.clients[playerClientNum].sess.sessionTeam == TEAM_SPECTATOR ) { #ifdef MISSIONPACK Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); #else Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); #endif } else { if( player->client->accuracy_shots ) { accuracy = player->client->accuracy_hits * 100 / player->client->accuracy_shots; } else { accuracy = 0; } #ifdef MISSIONPACK won = qfalse; if (g_gametype.integer >= GT_CTF) { score1 = level.teamScores[TEAM_RED]; score2 = level.teamScores[TEAM_BLUE]; if (level.clients[playerClientNum].sess.sessionTeam == TEAM_RED) { won = (level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE]); } else { won = (level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED]); } } else { if (&level.clients[playerClientNum] == &level.clients[ level.sortedClients[0] ]) { won = qtrue; score1 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; score2 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; } else { score2 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; score1 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; } } if (won && player->client->ps.persistant[PERS_KILLED] == 0) { perfect = 1; } else { perfect = 0; } Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT],player->client->ps.persistant[PERS_DEFEND_COUNT], player->client->ps.persistant[PERS_ASSIST_COUNT], player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], perfect, score1, score2, level.time, player->client->ps.persistant[PERS_CAPTURES] ); #else perfect = ( level.clients[playerClientNum].ps.persistant[PERS_RANK] == 0 && player->client->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT], player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], perfect ); #endif } msglen = strlen( msg ); for( i = 0; i < level.numNonSpectatorClients; i++ ) { n = level.sortedClients[i]; Com_sprintf( buf, sizeof(buf), " %i %i %i", n, level.clients[n].ps.persistant[PERS_RANK], level.clients[n].ps.persistant[PERS_SCORE] ); buflen = strlen( buf ); if( msglen + buflen + 1 >= sizeof(msg) ) { break; } strcat( msg, buf ); } trap_SendConsoleCommand( EXEC_APPEND, msg ); } static gentity_t *SpawnModelOnVictoryPad( gentity_t *pad, vec3_t offset, gentity_t *ent, int place ) { gentity_t *body; vec3_t vec; vec3_t f, r, u; body = G_Spawn(); if ( !body ) { G_Printf( S_COLOR_RED "ERROR: out of gentities\n" ); return NULL; } body->classname = ent->client->pers.netname; body->client = ent->client; body->s = ent->s; body->s.eType = ET_PLAYER; // could be ET_INVISIBLE body->s.eFlags = 0; // clear EF_TALK, etc body->s.powerups = 0; // clear powerups body->s.loopSound = 0; // clear lava burning body->s.number = body - g_entities; body->timestamp = level.time; body->physicsObject = qtrue; body->physicsBounce = 0; // don't bounce body->s.event = 0; body->s.pos.trType = TR_STATIONARY; body->s.groundEntityNum = ENTITYNUM_WORLD; body->s.legsAnim = LEGS_IDLE; body->s.torsoAnim = TORSO_STAND; if( body->s.weapon == WP_NONE ) { body->s.weapon = WP_MACHINEGUN; } if( body->s.weapon == WP_GAUNTLET) { body->s.torsoAnim = TORSO_STAND2; } body->s.event = 0; body->r.svFlags = ent->r.svFlags; VectorCopy (ent->r.mins, body->r.mins); VectorCopy (ent->r.maxs, body->r.maxs); VectorCopy (ent->r.absmin, body->r.absmin); VectorCopy (ent->r.absmax, body->r.absmax); body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; body->r.contents = CONTENTS_BODY; body->r.ownerNum = ent->r.ownerNum; body->takedamage = qfalse; VectorSubtract( level.intermission_origin, pad->r.currentOrigin, vec ); vectoangles( vec, body->s.apos.trBase ); body->s.apos.trBase[PITCH] = 0; body->s.apos.trBase[ROLL] = 0; AngleVectors( body->s.apos.trBase, f, r, u ); VectorMA( pad->r.currentOrigin, offset[0], f, vec ); VectorMA( vec, offset[1], r, vec ); VectorMA( vec, offset[2], u, vec ); G_SetOrigin( body, vec ); trap_LinkEntity (body); body->count = place; return body; } static void CelebrateStop( gentity_t *player ) { int anim; if( player->s.weapon == WP_GAUNTLET) { anim = TORSO_STAND2; } else { anim = TORSO_STAND; } player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; } #define TIMER_GESTURE (34*66+50) static void CelebrateStart( gentity_t *player ) { player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_GESTURE; player->nextthink = level.time + TIMER_GESTURE; player->think = CelebrateStop; /* player->client->ps.events[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = EV_TAUNT; player->client->ps.eventParms[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = 0; player->client->ps.eventSequence++; */ G_AddEvent(player, EV_TAUNT, 0); } static vec3_t offsetFirst = {0, 0, 74}; static vec3_t offsetSecond = {-10, 60, 54}; static vec3_t offsetThird = {-19, -60, 45}; static void PodiumPlacementThink( gentity_t *podium ) { vec3_t vec; vec3_t origin; vec3_t f, r, u; podium->nextthink = level.time + 100; AngleVectors( level.intermission_angle, vec, NULL, NULL ); VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); G_SetOrigin( podium, origin ); if( podium1 ) { VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); vectoangles( vec, podium1->s.apos.trBase ); podium1->s.apos.trBase[PITCH] = 0; podium1->s.apos.trBase[ROLL] = 0; AngleVectors( podium1->s.apos.trBase, f, r, u ); VectorMA( podium->r.currentOrigin, offsetFirst[0], f, vec ); VectorMA( vec, offsetFirst[1], r, vec ); VectorMA( vec, offsetFirst[2], u, vec ); G_SetOrigin( podium1, vec ); } if( podium2 ) { VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); vectoangles( vec, podium2->s.apos.trBase ); podium2->s.apos.trBase[PITCH] = 0; podium2->s.apos.trBase[ROLL] = 0; AngleVectors( podium2->s.apos.trBase, f, r, u ); VectorMA( podium->r.currentOrigin, offsetSecond[0], f, vec ); VectorMA( vec, offsetSecond[1], r, vec ); VectorMA( vec, offsetSecond[2], u, vec ); G_SetOrigin( podium2, vec ); } if( podium3 ) { VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); vectoangles( vec, podium3->s.apos.trBase ); podium3->s.apos.trBase[PITCH] = 0; podium3->s.apos.trBase[ROLL] = 0; AngleVectors( podium3->s.apos.trBase, f, r, u ); VectorMA( podium->r.currentOrigin, offsetThird[0], f, vec ); VectorMA( vec, offsetThird[1], r, vec ); VectorMA( vec, offsetThird[2], u, vec ); G_SetOrigin( podium3, vec ); } } static gentity_t *SpawnPodium( void ) { gentity_t *podium; vec3_t vec; vec3_t origin; podium = G_Spawn(); if ( !podium ) { return NULL; } podium->classname = "podium"; podium->s.eType = ET_GENERAL; podium->s.number = podium - g_entities; podium->clipmask = CONTENTS_SOLID; podium->r.contents = CONTENTS_SOLID; podium->s.modelindex = G_ModelIndex( SP_PODIUM_MODEL ); AngleVectors( level.intermission_angle, vec, NULL, NULL ); VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); G_SetOrigin( podium, origin ); VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); podium->s.apos.trBase[YAW] = vectoyaw( vec ); trap_LinkEntity (podium); podium->think = PodiumPlacementThink; podium->nextthink = level.time + 100; return podium; } /* ================== SpawnModelsOnVictoryPads ================== */ void SpawnModelsOnVictoryPads( void ) { gentity_t *player; gentity_t *podium; podium1 = NULL; podium2 = NULL; podium3 = NULL; podium = SpawnPodium(); player = SpawnModelOnVictoryPad( podium, offsetFirst, &g_entities[level.sortedClients[0]], level.clients[ level.sortedClients[0] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); if ( player ) { player->nextthink = level.time + 2000; player->think = CelebrateStart; podium1 = player; } player = SpawnModelOnVictoryPad( podium, offsetSecond, &g_entities[level.sortedClients[1]], level.clients[ level.sortedClients[1] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); if ( player ) { podium2 = player; } if ( level.numNonSpectatorClients > 2 ) { player = SpawnModelOnVictoryPad( podium, offsetThird, &g_entities[level.sortedClients[2]], level.clients[ level.sortedClients[2] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); if ( player ) { podium3 = player; } } } /* =============== Svcmd_AbortPodium_f =============== */ void Svcmd_AbortPodium_f( void ) { if( g_gametype.integer != GT_SINGLE_PLAYER ) { return; } if( podium1 ) { podium1->nextthink = level.time; podium1->think = CelebrateStop; } } ================================================ FILE: code/game/g_bot.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // // g_bot.c #include "g_local.h" static int g_numBots; static char *g_botInfos[MAX_BOTS]; int g_numArenas; static char *g_arenaInfos[MAX_ARENAS]; #define BOT_BEGIN_DELAY_BASE 2000 #define BOT_BEGIN_DELAY_INCREMENT 1500 #define BOT_SPAWN_QUEUE_DEPTH 16 typedef struct { int clientNum; int spawnTime; } botSpawnQueue_t; //static int botBeginDelay = 0; // bk001206 - unused, init static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; vmCvar_t bot_minplayers; extern gentity_t *podium1; extern gentity_t *podium2; extern gentity_t *podium3; float trap_Cvar_VariableValue( const char *var_name ) { char buf[128]; trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf)); return atof(buf); } /* =============== G_ParseInfos =============== */ int G_ParseInfos( char *buf, int max, char *infos[] ) { char *token; int count; char key[MAX_TOKEN_CHARS]; char info[MAX_INFO_STRING]; count = 0; while ( 1 ) { token = COM_Parse( &buf ); if ( !token[0] ) { break; } if ( strcmp( token, "{" ) ) { Com_Printf( "Missing { in info file\n" ); break; } if ( count == max ) { Com_Printf( "Max infos exceeded\n" ); break; } info[0] = '\0'; while ( 1 ) { token = COM_ParseExt( &buf, qtrue ); if ( !token[0] ) { Com_Printf( "Unexpected end of info file\n" ); break; } if ( !strcmp( token, "}" ) ) { break; } Q_strncpyz( key, token, sizeof( key ) ); token = COM_ParseExt( &buf, qfalse ); if ( !token[0] ) { strcpy( token, "" ); } Info_SetValueForKey( info, key, token ); } //NOTE: extra space for arena number infos[count] = G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); if (infos[count]) { strcpy(infos[count], info); count++; } } return count; } /* =============== G_LoadArenasFromFile =============== */ static void G_LoadArenasFromFile( char *filename ) { int len; fileHandle_t f; char buf[MAX_ARENAS_TEXT]; len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( !f ) { trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); return; } if ( len >= MAX_ARENAS_TEXT ) { trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); trap_FS_FCloseFile( f ); return; } trap_FS_Read( buf, len, f ); buf[len] = 0; trap_FS_FCloseFile( f ); g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] ); } /* =============== G_LoadArenas =============== */ static void G_LoadArenas( void ) { int numdirs; vmCvar_t arenasFile; char filename[128]; char dirlist[1024]; char* dirptr; int i, n; int dirlen; g_numArenas = 0; trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM ); if( *arenasFile.string ) { G_LoadArenasFromFile(arenasFile.string); } else { G_LoadArenasFromFile("scripts/arenas.txt"); } // get all arenas from .arena files numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 ); dirptr = dirlist; for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { dirlen = strlen(dirptr); strcpy(filename, "scripts/"); strcat(filename, dirptr); G_LoadArenasFromFile(filename); } trap_Printf( va( "%i arenas parsed\n", g_numArenas ) ); for( n = 0; n < g_numArenas; n++ ) { Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) ); } } /* =============== G_GetArenaInfoByNumber =============== */ const char *G_GetArenaInfoByMap( const char *map ) { int n; for( n = 0; n < g_numArenas; n++ ) { if( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) { return g_arenaInfos[n]; } } return NULL; } /* ================= PlayerIntroSound ================= */ static void PlayerIntroSound( const char *modelAndSkin ) { char model[MAX_QPATH]; char *skin; Q_strncpyz( model, modelAndSkin, sizeof(model) ); skin = Q_strrchr( model, '/' ); if ( skin ) { *skin++ = '\0'; } else { skin = model; } if( Q_stricmp( skin, "default" ) == 0 ) { skin = model; } trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) ); } /* =============== G_AddRandomBot =============== */ void G_AddRandomBot( int team ) { int i, n, num; float skill; char *value, netname[36], *teamstr; gclient_t *cl; num = 0; for ( n = 0; n < g_numBots ; n++ ) { value = Info_ValueForKey( g_botInfos[n], "name" ); // for ( i=0 ; i< g_maxclients.integer ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { continue; } if ( !Q_stricmp( value, cl->pers.netname ) ) { break; } } if (i >= g_maxclients.integer) { num++; } } num = random() * num; for ( n = 0; n < g_numBots ; n++ ) { value = Info_ValueForKey( g_botInfos[n], "name" ); // for ( i=0 ; i< g_maxclients.integer ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { continue; } if ( !Q_stricmp( value, cl->pers.netname ) ) { break; } } if (i >= g_maxclients.integer) { num--; if (num <= 0) { skill = trap_Cvar_VariableValue( "g_spSkill" ); if (team == TEAM_RED) teamstr = "red"; else if (team == TEAM_BLUE) teamstr = "blue"; else teamstr = ""; strncpy(netname, value, sizeof(netname)-1); netname[sizeof(netname)-1] = '\0'; Q_CleanStr(netname); trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) ); return; } } } } /* =============== G_RemoveRandomBot =============== */ int G_RemoveRandomBot( int team ) { int i; char netname[36]; gclient_t *cl; for ( i=0 ; i< g_maxclients.integer ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { continue; } strcpy(netname, cl->pers.netname); Q_CleanStr(netname); trap_SendConsoleCommand( EXEC_INSERT, va("kick %s\n", netname) ); return qtrue; } return qfalse; } /* =============== G_CountHumanPlayers =============== */ int G_CountHumanPlayers( int team ) { int i, num; gclient_t *cl; num = 0; for ( i=0 ; i< g_maxclients.integer ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { continue; } num++; } return num; } /* =============== G_CountBotPlayers =============== */ int G_CountBotPlayers( int team ) { int i, n, num; gclient_t *cl; num = 0; for ( i=0 ; i< g_maxclients.integer ; i++ ) { cl = level.clients + i; if ( cl->pers.connected != CON_CONNECTED ) { continue; } if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { continue; } if ( team >= 0 && cl->sess.sessionTeam != team ) { continue; } num++; } for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { if( !botSpawnQueue[n].spawnTime ) { continue; } if ( botSpawnQueue[n].spawnTime > level.time ) { continue; } num++; } return num; } /* =============== G_CheckMinimumPlayers =============== */ void G_CheckMinimumPlayers( void ) { int minplayers; int humanplayers, botplayers; static int checkminimumplayers_time; if (level.intermissiontime) return; //only check once each 10 seconds if (checkminimumplayers_time > level.time - 10000) { return; } checkminimumplayers_time = level.time; trap_Cvar_Update(&bot_minplayers); minplayers = bot_minplayers.integer; if (minplayers <= 0) return; if (g_gametype.integer >= GT_TEAM) { if (minplayers >= g_maxclients.integer / 2) { minplayers = (g_maxclients.integer / 2) -1; } humanplayers = G_CountHumanPlayers( TEAM_RED ); botplayers = G_CountBotPlayers( TEAM_RED ); // if (humanplayers + botplayers < minplayers) { G_AddRandomBot( TEAM_RED ); } else if (humanplayers + botplayers > minplayers && botplayers) { G_RemoveRandomBot( TEAM_RED ); } // humanplayers = G_CountHumanPlayers( TEAM_BLUE ); botplayers = G_CountBotPlayers( TEAM_BLUE ); // if (humanplayers + botplayers < minplayers) { G_AddRandomBot( TEAM_BLUE ); } else if (humanplayers + botplayers > minplayers && botplayers) { G_RemoveRandomBot( TEAM_BLUE ); } } else if (g_gametype.integer == GT_TOURNAMENT ) { if (minplayers >= g_maxclients.integer) { minplayers = g_maxclients.integer-1; } humanplayers = G_CountHumanPlayers( -1 ); botplayers = G_CountBotPlayers( -1 ); // if (humanplayers + botplayers < minplayers) { G_AddRandomBot( TEAM_FREE ); } else if (humanplayers + botplayers > minplayers && botplayers) { // try to remove spectators first if (!G_RemoveRandomBot( TEAM_SPECTATOR )) { // just remove the bot that is playing G_RemoveRandomBot( -1 ); } } } else if (g_gametype.integer == GT_FFA) { if (minplayers >= g_maxclients.integer) { minplayers = g_maxclients.integer-1; } humanplayers = G_CountHumanPlayers( TEAM_FREE ); botplayers = G_CountBotPlayers( TEAM_FREE ); // if (humanplayers + botplayers < minplayers) { G_AddRandomBot( TEAM_FREE ); } else if (humanplayers + botplayers > minplayers && botplayers) { G_RemoveRandomBot( TEAM_FREE ); } } } /* =============== G_CheckBotSpawn =============== */ void G_CheckBotSpawn( void ) { int n; char userinfo[MAX_INFO_VALUE]; G_CheckMinimumPlayers(); for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { if( !botSpawnQueue[n].spawnTime ) { continue; } if ( botSpawnQueue[n].spawnTime > level.time ) { continue; } ClientBegin( botSpawnQueue[n].clientNum ); botSpawnQueue[n].spawnTime = 0; if( g_gametype.integer == GT_SINGLE_PLAYER ) { trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof(userinfo) ); PlayerIntroSound( Info_ValueForKey (userinfo, "model") ); } } } /* =============== AddBotToSpawnQueue =============== */ static void AddBotToSpawnQueue( int clientNum, int delay ) { int n; for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { if( !botSpawnQueue[n].spawnTime ) { botSpawnQueue[n].spawnTime = level.time + delay; botSpawnQueue[n].clientNum = clientNum; return; } } G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" ); ClientBegin( clientNum ); } /* =============== G_RemoveQueuedBotBegin Called on client disconnect to make sure the delayed spawn doesn't happen on a freed index =============== */ void G_RemoveQueuedBotBegin( int clientNum ) { int n; for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { if( botSpawnQueue[n].clientNum == clientNum ) { botSpawnQueue[n].spawnTime = 0; return; } } } /* =============== G_BotConnect =============== */ qboolean G_BotConnect( int clientNum, qboolean restart ) { bot_settings_t settings; char userinfo[MAX_INFO_STRING]; trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) ); settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); if (!BotAISetupClient( clientNum, &settings, restart )) { trap_DropClient( clientNum, "BotAISetupClient failed" ); return qfalse; } return qtrue; } /* =============== G_AddBot =============== */ static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) { int clientNum; char *botinfo; gentity_t *bot; char *key; char *s; char *botname; char *model; char *headmodel; char userinfo[MAX_INFO_STRING]; // get the botinfo from bots.txt botinfo = G_GetBotInfoByName( name ); if ( !botinfo ) { G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name ); return; } // create the bot's userinfo userinfo[0] = '\0'; botname = Info_ValueForKey( botinfo, "funname" ); if( !botname[0] ) { botname = Info_ValueForKey( botinfo, "name" ); } // check for an alternative name if (altname && altname[0]) { botname = altname; } Info_SetValueForKey( userinfo, "name", botname ); Info_SetValueForKey( userinfo, "rate", "25000" ); Info_SetValueForKey( userinfo, "snaps", "20" ); Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) ); if ( skill >= 1 && skill < 2 ) { Info_SetValueForKey( userinfo, "handicap", "50" ); } else if ( skill >= 2 && skill < 3 ) { Info_SetValueForKey( userinfo, "handicap", "70" ); } else if ( skill >= 3 && skill < 4 ) { Info_SetValueForKey( userinfo, "handicap", "90" ); } key = "model"; model = Info_ValueForKey( botinfo, key ); if ( !*model ) { model = "visor/default"; } Info_SetValueForKey( userinfo, key, model ); key = "team_model"; Info_SetValueForKey( userinfo, key, model ); key = "headmodel"; headmodel = Info_ValueForKey( botinfo, key ); if ( !*headmodel ) { headmodel = model; } Info_SetValueForKey( userinfo, key, headmodel ); key = "team_headmodel"; Info_SetValueForKey( userinfo, key, headmodel ); key = "gender"; s = Info_ValueForKey( botinfo, key ); if ( !*s ) { s = "male"; } Info_SetValueForKey( userinfo, "sex", s ); key = "color1"; s = Info_ValueForKey( botinfo, key ); if ( !*s ) { s = "4"; } Info_SetValueForKey( userinfo, key, s ); key = "color2"; s = Info_ValueForKey( botinfo, key ); if ( !*s ) { s = "5"; } Info_SetValueForKey( userinfo, key, s ); s = Info_ValueForKey(botinfo, "aifile"); if (!*s ) { trap_Printf( S_COLOR_RED "Error: bot has no aifile specified\n" ); return; } // have the server allocate a client slot clientNum = trap_BotAllocateClient(); if ( clientNum == -1 ) { G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" ); G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" ); return; } // initialize the bot settings if( !team || !*team ) { if( g_gametype.integer >= GT_TEAM ) { if( PickTeam(clientNum) == TEAM_RED) { team = "red"; } else { team = "blue"; } } else { team = "red"; } } Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) ); Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) ); Info_SetValueForKey( userinfo, "team", team ); bot = &g_entities[ clientNum ]; bot->r.svFlags |= SVF_BOT; bot->inuse = qtrue; // register the userinfo trap_SetUserinfo( clientNum, userinfo ); // have it connect to the game as a normal client if ( ClientConnect( clientNum, qtrue, qtrue ) ) { return; } if( delay == 0 ) { ClientBegin( clientNum ); return; } AddBotToSpawnQueue( clientNum, delay ); } /* =============== Svcmd_AddBot_f =============== */ void Svcmd_AddBot_f( void ) { float skill; int delay; char name[MAX_TOKEN_CHARS]; char altname[MAX_TOKEN_CHARS]; char string[MAX_TOKEN_CHARS]; char team[MAX_TOKEN_CHARS]; // are bots enabled? if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { return; } // name trap_Argv( 1, name, sizeof( name ) ); if ( !name[0] ) { trap_Printf( "Usage: Addbot [skill 1-5] [team] [msec delay] [altname]\n" ); return; } // skill trap_Argv( 2, string, sizeof( string ) ); if ( !string[0] ) { skill = 4; } else { skill = atof( string ); } // team trap_Argv( 3, team, sizeof( team ) ); // delay trap_Argv( 4, string, sizeof( string ) ); if ( !string[0] ) { delay = 0; } else { delay = atoi( string ); } // alternative name trap_Argv( 5, altname, sizeof( altname ) ); G_AddBot( name, skill, team, delay, altname ); // if this was issued during gameplay and we are playing locally, // go ahead and load the bot's media immediately if ( level.time - level.startTime > 1000 && trap_Cvar_VariableIntegerValue( "cl_running" ) ) { trap_SendServerCommand( -1, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo } } /* =============== Svcmd_BotList_f =============== */ void Svcmd_BotList_f( void ) { int i; char name[MAX_TOKEN_CHARS]; char funname[MAX_TOKEN_CHARS]; char model[MAX_TOKEN_CHARS]; char aifile[MAX_TOKEN_CHARS]; trap_Printf("^1name model aifile funname\n"); for (i = 0; i < g_numBots; i++) { strcpy(name, Info_ValueForKey( g_botInfos[i], "name" )); if ( !*name ) { strcpy(name, "UnnamedPlayer"); } strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" )); if ( !*funname ) { strcpy(funname, ""); } strcpy(model, Info_ValueForKey( g_botInfos[i], "model" )); if ( !*model ) { strcpy(model, "visor/default"); } strcpy(aifile, Info_ValueForKey( g_botInfos[i], "aifile")); if (!*aifile ) { strcpy(aifile, "bots/default_c.c"); } trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, aifile, funname)); } } /* =============== G_SpawnBots =============== */ static void G_SpawnBots( char *botList, int baseDelay ) { char *bot; char *p; float skill; int delay; char bots[MAX_INFO_VALUE]; podium1 = NULL; podium2 = NULL; podium3 = NULL; skill = trap_Cvar_VariableValue( "g_spSkill" ); if( skill < 1 ) { trap_Cvar_Set( "g_spSkill", "1" ); skill = 1; } else if ( skill > 5 ) { trap_Cvar_Set( "g_spSkill", "5" ); skill = 5; } Q_strncpyz( bots, botList, sizeof(bots) ); p = &bots[0]; delay = baseDelay; while( *p ) { //skip spaces while( *p && *p == ' ' ) { p++; } if( !p ) { break; } // mark start of bot name bot = p; // skip until space of null while( *p && *p != ' ' ) { p++; } if( *p ) { *p++ = 0; } // we must add the bot this way, calling G_AddBot directly at this stage // does "Bad Things" trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f free %i\n", bot, skill, delay) ); delay += BOT_BEGIN_DELAY_INCREMENT; } } /* =============== G_LoadBotsFromFile =============== */ static void G_LoadBotsFromFile( char *filename ) { int len; fileHandle_t f; char buf[MAX_BOTS_TEXT]; len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( !f ) { trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); return; } if ( len >= MAX_BOTS_TEXT ) { trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); trap_FS_FCloseFile( f ); return; } trap_FS_Read( buf, len, f ); buf[len] = 0; trap_FS_FCloseFile( f ); g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] ); } /* =============== G_LoadBots =============== */ static void G_LoadBots( void ) { vmCvar_t botsFile; int numdirs; char filename[128]; char dirlist[1024]; char* dirptr; int i; int dirlen; if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { return; } g_numBots = 0; trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); if( *botsFile.string ) { G_LoadBotsFromFile(botsFile.string); } else { G_LoadBotsFromFile("scripts/bots.txt"); } // get all bots from .bot files numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 ); dirptr = dirlist; for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { dirlen = strlen(dirptr); strcpy(filename, "scripts/"); strcat(filename, dirptr); G_LoadBotsFromFile(filename); } trap_Printf( va( "%i bots parsed\n", g_numBots ) ); } /* =============== G_GetBotInfoByNumber =============== */ char *G_GetBotInfoByNumber( int num ) { if( num < 0 || num >= g_numBots ) { trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); return NULL; } return g_botInfos[num]; } /* =============== G_GetBotInfoByName =============== */ char *G_GetBotInfoByName( const char *name ) { int n; char *value; for ( n = 0; n < g_numBots ; n++ ) { value = Info_ValueForKey( g_botInfos[n], "name" ); if ( !Q_stricmp( value, name ) ) { return g_botInfos[n]; } } return NULL; } /* =============== G_InitBots =============== */ void G_InitBots( qboolean restart ) { int fragLimit; int timeLimit; const char *arenainfo; char *strValue; int basedelay; char map[MAX_QPATH]; char serverinfo[MAX_INFO_STRING]; G_LoadBots(); G_LoadArenas(); trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO ); if( g_gametype.integer == GT_SINGLE_PLAYER ) { trap_GetServerinfo( serverinfo, sizeof(serverinfo) ); Q_strncpyz( map, Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) ); arenainfo = G_GetArenaInfoByMap( map ); if ( !arenainfo ) { return; } strValue = Info_ValueForKey( arenainfo, "fraglimit" ); fragLimit = atoi( strValue ); if ( fragLimit ) { trap_Cvar_Set( "fraglimit", strValue ); } else { trap_Cvar_Set( "fraglimit", "0" ); } strValue = Info_ValueForKey( arenainfo, "timelimit" ); timeLimit = atoi( strValue ); if ( timeLimit ) { trap_Cvar_Set( "timelimit", strValue ); } else { trap_Cvar_Set( "timelimit", "0" ); } if ( !fragLimit && !timeLimit ) { trap_Cvar_Set( "fraglimit", "10" ); trap_Cvar_Set( "timelimit", "0" ); } basedelay = BOT_BEGIN_DELAY_BASE; strValue = Info_ValueForKey( arenainfo, "special" ); if( Q_stricmp( strValue, "training" ) == 0 ) { basedelay += 10000; } if( !restart ) { G_SpawnBots( Info_ValueForKey( arenainfo, "bots" ), basedelay ); } } } ================================================ FILE: code/game/g_client.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // #include "g_local.h" // g_client.c -- client functions that don't happen every frame static vec3_t playerMins = {-15, -15, -24}; static vec3_t playerMaxs = {15, 15, 32}; /*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial potential spawning position for deathmatch games. The first time a player enters the game, they will be at an 'initial' spot. Targets will be fired when someone spawns in on them. "nobots" will prevent bots from using this spot. "nohumans" will prevent non-bots from using this spot. */ void SP_info_player_deathmatch( gentity_t *ent ) { int i; G_SpawnInt( "nobots", "0", &i); if ( i ) { ent->flags |= FL_NO_BOTS; } G_SpawnInt( "nohumans", "0", &i ); if ( i ) { ent->flags |= FL_NO_HUMANS; } } /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) equivelant to info_player_deathmatch */ void SP_info_player_start(gentity_t *ent) { ent->classname = "info_player_deathmatch"; SP_info_player_deathmatch( ent ); } /*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) The intermission will be viewed from this point. Target an info_notnull for the view direction. */ void SP_info_player_intermission( gentity_t *ent ) { } /* ======================================================================= SelectSpawnPoint ======================================================================= */ /* ================ SpotWouldTelefrag ================ */ qboolean SpotWouldTelefrag( gentity_t *spot ) { int i, num; int touch[MAX_GENTITIES]; gentity_t *hit; vec3_t mins, maxs; VectorAdd( spot->s.origin, playerMins, mins ); VectorAdd( spot->s.origin, playerMaxs, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { if ( hit->client) { return qtrue; } } return qfalse; } /* ================ SelectNearestDeathmatchSpawnPoint Find the spot that we DON'T want to use ================ */ #define MAX_SPAWN_POINTS 128 gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) { gentity_t *spot; vec3_t delta; float dist, nearestDist; gentity_t *nearestSpot; nearestDist = 999999; nearestSpot = NULL; spot = NULL; while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { VectorSubtract( spot->s.origin, from, delta ); dist = VectorLength( delta ); if ( dist < nearestDist ) { nearestDist = dist; nearestSpot = spot; } } return nearestSpot; } /* ================ SelectRandomDeathmatchSpawnPoint go to a random point that doesn't telefrag ================ */ #define MAX_SPAWN_POINTS 128 gentity_t *SelectRandomDeathmatchSpawnPoint( void ) { gentity_t *spot; int count; int selection; gentity_t *spots[MAX_SPAWN_POINTS]; count = 0; spot = NULL; while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { if ( SpotWouldTelefrag( spot ) ) { continue; } spots[ count ] = spot; count++; } if ( !count ) { // no spots that won't telefrag return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); } selection = rand() % count; return spots[ selection ]; } /* =========== SelectRandomFurthestSpawnPoint Chooses a player start, deathmatch start, etc ============ */ gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { gentity_t *spot; vec3_t delta; float dist; float list_dist[64]; gentity_t *list_spot[64]; int numSpots, rnd, i, j; numSpots = 0; spot = NULL; while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { if ( SpotWouldTelefrag( spot ) ) { continue; } VectorSubtract( spot->s.origin, avoidPoint, delta ); dist = VectorLength( delta ); for (i = 0; i < numSpots; i++) { if ( dist > list_dist[i] ) { if ( numSpots >= 64 ) numSpots = 64-1; for (j = numSpots; j > i; j--) { list_dist[j] = list_dist[j-1]; list_spot[j] = list_spot[j-1]; } list_dist[i] = dist; list_spot[i] = spot; numSpots++; if (numSpots > 64) numSpots = 64; break; } } if (i >= numSpots && numSpots < 64) { list_dist[numSpots] = dist; list_spot[numSpots] = spot; numSpots++; } } if (!numSpots) { spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); if (!spot) G_Error( "Couldn't find a spawn point" ); VectorCopy (spot->s.origin, origin); origin[2] += 9; VectorCopy (spot->s.angles, angles); return spot; } // select a random spot from the spawn points furthest away rnd = random() * (numSpots / 2); VectorCopy (list_spot[rnd]->s.origin, origin); origin[2] += 9; VectorCopy (list_spot[rnd]->s.angles, angles); return list_spot[rnd]; } /* =========== SelectSpawnPoint Chooses a player start, deathmatch start, etc ============ */ gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles ); /* gentity_t *spot; gentity_t *nearestSpot; nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); spot = SelectRandomDeathmatchSpawnPoint ( ); if ( spot == nearestSpot ) { // roll again if it would be real close to point of death spot = SelectRandomDeathmatchSpawnPoint ( ); if ( spot == nearestSpot ) { // last try spot = SelectRandomDeathmatchSpawnPoint ( ); } } // find a single player start spot if (!spot) { G_Error( "Couldn't find a spawn point" ); } VectorCopy (spot->s.origin, origin); origin[2] += 9; VectorCopy (spot->s.angles, angles); return spot; */ } /* =========== SelectInitialSpawnPoint Try to find a spawn point marked 'initial', otherwise use normal spawn selection. ============ */ gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) { gentity_t *spot; spot = NULL; while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { if ( spot->spawnflags & 1 ) { break; } } if ( !spot || SpotWouldTelefrag( spot ) ) { return SelectSpawnPoint( vec3_origin, origin, angles ); } VectorCopy (spot->s.origin, origin); origin[2] += 9; VectorCopy (spot->s.angles, angles); return spot; } /* =========== SelectSpectatorSpawnPoint ============ */ gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { FindIntermissionPoint(); VectorCopy( level.intermission_origin, origin ); VectorCopy( level.intermission_angle, angles ); return NULL; } /* ======================================================================= BODYQUE ======================================================================= */ /* =============== InitBodyQue =============== */ void InitBodyQue (void) { int i; gentity_t *ent; level.bodyQueIndex = 0; for (i=0; iclassname = "bodyque"; ent->neverFree = qtrue; level.bodyQue[i] = ent; } } /* ============= BodySink After sitting around for five seconds, fall into the ground and dissapear ============= */ void BodySink( gentity_t *ent ) { if ( level.time - ent->timestamp > 6500 ) { // the body ques are never actually freed, they are just unlinked trap_UnlinkEntity( ent ); ent->physicsObject = qfalse; return; } ent->nextthink = level.time + 100; ent->s.pos.trBase[2] -= 1; } /* ============= CopyToBodyQue A player is respawning, so make an entity that looks just like the existing corpse to leave behind. ============= */ void CopyToBodyQue( gentity_t *ent ) { #ifdef MISSIONPACK gentity_t *e; int i; #endif gentity_t *body; int contents; trap_UnlinkEntity (ent); // if client is in a nodrop area, don't leave the body contents = trap_PointContents( ent->s.origin, -1 ); if ( contents & CONTENTS_NODROP ) { return; } // grab a body que and cycle to the next one body = level.bodyQue[ level.bodyQueIndex ]; level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; trap_UnlinkEntity (body); body->s = ent->s; body->s.eFlags = EF_DEAD; // clear EF_TALK, etc #ifdef MISSIONPACK if ( ent->s.eFlags & EF_KAMIKAZE ) { body->s.eFlags |= EF_KAMIKAZE; // check if there is a kamikaze timer around for this owner for (i = 0; i < MAX_GENTITIES; i++) { e = &g_entities[i]; if (!e->inuse) continue; if (e->activator != ent) continue; if (strcmp(e->classname, "kamikaze timer")) continue; e->activator = body; break; } } #endif body->s.powerups = 0; // clear powerups body->s.loopSound = 0; // clear lava burning body->s.number = body - g_entities; body->timestamp = level.time; body->physicsObject = qtrue; body->physicsBounce = 0; // don't bounce if ( body->s.groundEntityNum == ENTITYNUM_NONE ) { body->s.pos.trType = TR_GRAVITY; body->s.pos.trTime = level.time; VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); } else { body->s.pos.trType = TR_STATIONARY; } body->s.event = 0; // change the animation to the last-frame only, so the sequence // doesn't repeat anew for the body switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { case BOTH_DEATH1: case BOTH_DEAD1: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; break; case BOTH_DEATH2: case BOTH_DEAD2: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; break; case BOTH_DEATH3: case BOTH_DEAD3: default: body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; break; } body->r.svFlags = ent->r.svFlags; VectorCopy (ent->r.mins, body->r.mins); VectorCopy (ent->r.maxs, body->r.maxs); VectorCopy (ent->r.absmin, body->r.absmin); VectorCopy (ent->r.absmax, body->r.absmax); body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; body->r.contents = CONTENTS_CORPSE; body->r.ownerNum = ent->s.number; body->nextthink = level.time + 5000; body->think = BodySink; body->die = body_die; // don't take more damage if already gibbed if ( ent->health <= GIB_HEALTH ) { body->takedamage = qfalse; } else { body->takedamage = qtrue; } VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); trap_LinkEntity (body); } //====================================================================== /* ================== SetClientViewAngle ================== */ void SetClientViewAngle( gentity_t *ent, vec3_t angle ) { int i; // set the delta angle for (i=0 ; i<3 ; i++) { int cmdAngle; cmdAngle = ANGLE2SHORT(angle[i]); ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; } VectorCopy( angle, ent->s.angles ); VectorCopy (ent->s.angles, ent->client->ps.viewangles); } /* ================ respawn ================ */ void respawn( gentity_t *ent ) { gentity_t *tent; CopyToBodyQue (ent); ClientSpawn(ent); // add a teleportation effect tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); tent->s.clientNum = ent->s.clientNum; } /* ================ TeamCount Returns number of players on a team ================ */ team_t TeamCount( int ignoreClientNum, int team ) { int i; int count = 0; for ( i = 0 ; i < level.maxclients ; i++ ) { if ( i == ignoreClientNum ) { continue; } if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { continue; } if ( level.clients[i].sess.sessionTeam == team ) { count++; } } return count; } /* ================ TeamLeader Returns the client number of the team leader ================ */ int TeamLeader( int team ) { int i; for ( i = 0 ; i < level.maxclients ; i++ ) { if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { continue; } if ( level.clients[i].sess.sessionTeam == team ) { if ( level.clients[i].sess.teamLeader ) return i; } } return -1; } /* ================ PickTeam ================ */ team_t PickTeam( int ignoreClientNum ) { int counts[TEAM_NUM_TEAMS]; counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE ); counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED ); if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) { return TEAM_RED; } if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) { return TEAM_BLUE; } // equal team count, so join the team with the lowest score if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) { return TEAM_RED; } return TEAM_BLUE; } /* =========== ForceClientSkin Forces a client's skin (for teamplay) =========== */ /* static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) { char *p; if ((p = Q_strrchr(model, '/')) != 0) { *p = 0; } Q_strcat(model, MAX_QPATH, "/"); Q_strcat(model, MAX_QPATH, skin); } */ /* =========== ClientCheckName ============ */ static void ClientCleanName( const char *in, char *out, int outSize ) { int len, colorlessLen; char ch; char *p; int spaces; //save room for trailing null byte outSize--; len = 0; colorlessLen = 0; p = out; *p = 0; spaces = 0; while( 1 ) { ch = *in++; if( !ch ) { break; } // don't allow leading spaces if( !*p && ch == ' ' ) { continue; } // check colors if( ch == Q_COLOR_ESCAPE ) { // solo trailing carat is not a color prefix if( !*in ) { break; } // don't allow black in a name, period if( ColorIndex(*in) == 0 ) { in++; continue; } // make sure room in dest for both chars if( len > outSize - 2 ) { break; } *out++ = ch; *out++ = *in++; len += 2; continue; } // don't allow too many consecutive spaces if( ch == ' ' ) { spaces++; if( spaces > 3 ) { continue; } } else { spaces = 0; } if( len > outSize - 1 ) { break; } *out++ = ch; colorlessLen++; len++; } *out = 0; // don't allow empty names if( *p == 0 || colorlessLen == 0 ) { Q_strncpyz( p, "UnnamedPlayer", outSize ); } } /* =========== ClientUserInfoChanged Called from ClientConnect when the player first connects and directly by the server system when the player updates a userinfo variable. The game can override any of the settings and call trap_SetUserinfo if desired. ============ */ void ClientUserinfoChanged( int clientNum ) { gentity_t *ent; int teamTask, teamLeader, team, health; char *s; char model[MAX_QPATH]; char headModel[MAX_QPATH]; char oldname[MAX_STRING_CHARS]; gclient_t *client; char c1[MAX_INFO_STRING]; char c2[MAX_INFO_STRING]; char redTeam[MAX_INFO_STRING]; char blueTeam[MAX_INFO_STRING]; char userinfo[MAX_INFO_STRING]; ent = g_entities + clientNum; client = ent->client; trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); // check for malformed or illegal info strings if ( !Info_Validate(userinfo) ) { strcpy (userinfo, "\\name\\badinfo"); } // check for local client s = Info_ValueForKey( userinfo, "ip" ); if ( !strcmp( s, "localhost" ) ) { client->pers.localClient = qtrue; } // check the item prediction s = Info_ValueForKey( userinfo, "cg_predictItems" ); if ( !atoi( s ) ) { client->pers.predictItemPickup = qfalse; } else { client->pers.predictItemPickup = qtrue; } // set name Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey (userinfo, "name"); ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) ); if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) ); } } if ( client->pers.connected == CON_CONNECTED ) { if ( strcmp( oldname, client->pers.netname ) ) { trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, client->pers.netname) ); } } // set max health #ifdef MISSIONPACK if (client->ps.powerups[PW_GUARD]) { client->pers.maxHealth = 200; } else { health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); client->pers.maxHealth = health; if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { client->pers.maxHealth = 100; } } #else health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); client->pers.maxHealth = health; if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { client->pers.maxHealth = 100; } #endif client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; // set model if( g_gametype.integer >= GT_TEAM ) { Q_strncpyz( model, Info_ValueForKey (userinfo, "team_model"), sizeof( model ) ); Q_strncpyz( headModel, Info_ValueForKey (userinfo, "team_headmodel"), sizeof( headModel ) ); } else { Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) ); } // bots set their team a few frames later if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) { s = Info_ValueForKey( userinfo, "team" ); if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { team = TEAM_RED; } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { team = TEAM_BLUE; } else { // pick the team with the least number of players team = PickTeam( clientNum ); } } else { team = client->sess.sessionTeam; } /* NOTE: all client side now // team switch( team ) { case TEAM_RED: ForceClientSkin(client, model, "red"); // ForceClientSkin(client, headModel, "red"); break; case TEAM_BLUE: ForceClientSkin(client, model, "blue"); // ForceClientSkin(client, headModel, "blue"); break; } // don't ever use a default skin in teamplay, it would just waste memory // however bots will always join a team but they spawn in as spectator if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) { ForceClientSkin(client, model, "red"); // ForceClientSkin(client, headModel, "red"); } */ #ifdef MISSIONPACK if (g_gametype.integer >= GT_TEAM) { client->pers.teamInfo = qtrue; } else { s = Info_ValueForKey( userinfo, "teamoverlay" ); if ( ! *s || atoi( s ) != 0 ) { client->pers.teamInfo = qtrue; } else { client->pers.teamInfo = qfalse; } } #else // teamInfo s = Info_ValueForKey( userinfo, "teamoverlay" ); if ( ! *s || atoi( s ) != 0 ) { client->pers.teamInfo = qtrue; } else { client->pers.teamInfo = qfalse; } #endif /* s = Info_ValueForKey( userinfo, "cg_pmove_fixed" ); if ( !*s || atoi( s ) == 0 ) { client->pers.pmoveFixed = qfalse; } else { client->pers.pmoveFixed = qtrue; } */ // team task (0 = none, 1 = offence, 2 = defence) teamTask = atoi(Info_ValueForKey(userinfo, "teamtask")); // team Leader (1 = leader, 0 is normal player) teamLeader = client->sess.teamLeader; // colors strcpy(c1, Info_ValueForKey( userinfo, "color1" )); strcpy(c2, Info_ValueForKey( userinfo, "color2" )); strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" )); strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" )); // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds if ( ent->r.svFlags & SVF_BOT ) { s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d", client->pers.netname, team, model, headModel, c1, c2, client->pers.maxHealth, client->sess.wins, client->sess.losses, Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader ); } else { s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d", client->pers.netname, client->sess.sessionTeam, model, headModel, redTeam, blueTeam, c1, c2, client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader); } trap_SetConfigstring( CS_PLAYERS+clientNum, s ); // this is not the userinfo, more like the configstring actually G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); } /* =========== ClientConnect Called when a player begins connecting to the server. Called again for every map change or tournement restart. The session information will be valid after exit. Return NULL if the client should be allowed, otherwise return a string with the reason for denial. Otherwise, the client will be sent the current gamestate and will eventually get to ClientBegin. firstTime will be qtrue the very first time a client connects to the server machine, but qfalse on map changes and tournement restarts. ============ */ char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { char *value; // char *areabits; gclient_t *client; char userinfo[MAX_INFO_STRING]; gentity_t *ent; ent = &g_entities[ clientNum ]; trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); // IP filtering // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 // recommanding PB based IP / GUID banning, the builtin system is pretty limited // check to see if they are on the banned IP list value = Info_ValueForKey (userinfo, "ip"); if ( G_FilterPacket( value ) ) { return "You are banned from this server."; } // we don't check password for bots and local client // NOTE: local client <-> "ip" "localhost" // this means this client is not running in our current process if ( !( ent->r.svFlags & SVF_BOT ) && (strcmp(value, "localhost") != 0)) { // check for a password value = Info_ValueForKey (userinfo, "password"); if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) && strcmp( g_password.string, value) != 0) { return "Invalid password"; } } // they can connect ent->client = level.clients + clientNum; client = ent->client; // areabits = client->areabits; memset( client, 0, sizeof(*client) ); client->pers.connected = CON_CONNECTING; // read or initialize the session data if ( firstTime || level.newSession ) { G_InitSessionData( client, userinfo ); } G_ReadSessionData( client ); if( isBot ) { ent->r.svFlags |= SVF_BOT; ent->inuse = qtrue; if( !G_BotConnect( clientNum, !firstTime ) ) { return "BotConnectfailed"; } } // get and distribute relevent paramters G_LogPrintf( "ClientConnect: %i\n", clientNum ); ClientUserinfoChanged( clientNum ); // don't do the "xxx connected" messages if they were caried over from previous level if ( firstTime ) { trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) ); } if ( g_gametype.integer >= GT_TEAM && client->sess.sessionTeam != TEAM_SPECTATOR ) { BroadcastTeamChange( client, -1 ); } // count current clients and rank for scoreboard CalculateRanks(); // for statistics // client->areabits = areabits; // if ( !client->areabits ) // client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 ); return NULL; } /* =========== ClientBegin called when a client has finished connecting, and is ready to be placed into the level. This will happen every level load, and on transition between teams, but doesn't happen on respawns ============ */ void ClientBegin( int clientNum ) { gentity_t *ent; gclient_t *client; gentity_t *tent; int flags; ent = g_entities + clientNum; client = level.clients + clientNum; if ( ent->r.linked ) { trap_UnlinkEntity( ent ); } G_InitGentity( ent ); ent->touch = 0; ent->pain = 0; ent->client = client; client->pers.connected = CON_CONNECTED; client->pers.enterTime = level.time; client->pers.teamState.state = TEAM_BEGIN; // save eflags around this, because changing teams will // cause this to happen with a valid entity, and we // want to make sure the teleport bit is set right // so the viewpoint doesn't interpolate through the // world to the new position flags = client->ps.eFlags; memset( &client->ps, 0, sizeof( client->ps ) ); client->ps.eFlags = flags; // locate ent at a spawn point ClientSpawn( ent ); if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { // send event tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); tent->s.clientNum = ent->s.clientNum; if ( g_gametype.integer != GT_TOURNAMENT ) { trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) ); } } G_LogPrintf( "ClientBegin: %i\n", clientNum ); // count current clients and rank for scoreboard CalculateRanks(); } /* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ void ClientSpawn(gentity_t *ent) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; int persistant[MAX_PERSISTANT]; gentity_t *spawnPoint; int flags; int savedPing; // char *savedAreaBits; int accuracy_hits, accuracy_shots; int eventSequence; char userinfo[MAX_INFO_STRING]; index = ent - g_entities; client = ent->client; // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { spawnPoint = SelectSpectatorSpawnPoint ( spawn_origin, spawn_angles); } else if (g_gametype.integer >= GT_CTF ) { // all base oriented team games use the CTF spawn points spawnPoint = SelectCTFSpawnPoint ( client->sess.sessionTeam, client->pers.teamState.state, spawn_origin, spawn_angles); } else { do { // the first spawn should be at a good looking spot if ( !client->pers.initialSpawn && client->pers.localClient ) { client->pers.initialSpawn = qtrue; spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); } else { // don't spawn near existing origin if possible spawnPoint = SelectSpawnPoint ( client->ps.origin, spawn_origin, spawn_angles); } // Tim needs to prevent bots from spawning at the initial point // on q3dm0... if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) { continue; // try again } // just to be symetric, we have a nohumans option... if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) { continue; // try again } break; } while ( 1 ); } client->pers.teamState.state = TEAM_ACTIVE; // always clear the kamikaze flag ent->s.eFlags &= ~EF_KAMIKAZE; // toggle the teleport bit so the client knows to not lerp // and never clear the voted flag flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED); flags ^= EF_TELEPORT_BIT; // clear everything but the persistant data saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; // savedAreaBits = client->areabits; accuracy_hits = client->accuracy_hits; accuracy_shots = client->accuracy_shots; for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { persistant[i] = client->ps.persistant[i]; } eventSequence = client->ps.eventSequence; memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; // client->areabits = savedAreaBits; client->accuracy_hits = accuracy_hits; client->accuracy_shots = accuracy_shots; client->lastkilled_client = -1; for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { client->ps.persistant[i] = persistant[i]; } client->ps.eventSequence = eventSequence; // increment the spawncount so the client will detect the respawn client->ps.persistant[PERS_SPAWN_COUNT]++; client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; client->airOutTime = level.time + 12000; trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); // set max health client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { client->pers.maxHealth = 100; } // clear entity values client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; client->ps.eFlags = flags; ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[index]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; VectorCopy (playerMins, ent->r.mins); VectorCopy (playerMaxs, ent->r.maxs); client->ps.clientNum = index; client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN ); if ( g_gametype.integer == GT_TEAM ) { client->ps.ammo[WP_MACHINEGUN] = 50; } else { client->ps.ammo[WP_MACHINEGUN] = 100; } client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET ); client->ps.ammo[WP_GAUNTLET] = -1; client->ps.ammo[WP_GRAPPLING_HOOK] = -1; // health will count down towards max_health ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); SetClientViewAngle( ent, spawn_angles ); if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { } else { G_KillBox( ent ); trap_LinkEntity (ent); // force the base weapon up client->ps.weapon = WP_MACHINEGUN; client->ps.weaponstate = WEAPON_READY; } // don't allow full run speed for a bit client->ps.pm_flags |= PMF_TIME_KNOCKBACK; client->ps.pm_time = 100; client->respawnTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; // set default animations client->ps.torsoAnim = TORSO_STAND; client->ps.legsAnim = LEGS_IDLE; if ( level.intermissiontime ) { MoveClientToIntermission( ent ); } else { // fire the targets of the spawn point G_UseTargets( spawnPoint, ent ); // select the highest weapon number available, after any // spawn given items have fired client->ps.weapon = 1; for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { client->ps.weapon = i; break; } } } // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink( ent-g_entities ); // positively link the client, even if the command times are weird if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); } /* =========== ClientDisconnect Called when a player drops from the server. Will not be called between levels. This should NOT be called directly by any game logic, call trap_DropClient(), which will call this and do server system housekeeping. ============ */ void ClientDisconnect( int clientNum ) { gentity_t *ent; gentity_t *tent; int i; // cleanup if we are kicking a bot that // hasn't spawned yet G_RemoveQueuedBotBegin( clientNum ); ent = g_entities + clientNum; if ( !ent->client ) { return; } // stop any following clients for ( i = 0 ; i < level.maxclients ; i++ ) { if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW && level.clients[i].sess.spectatorClient == clientNum ) { StopFollowing( &g_entities[i] ); } } // send effect if they were completely connected if ( ent->client->pers.connected == CON_CONNECTED && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); tent->s.clientNum = ent->s.clientNum; // They don't get to take powerups with them! // Especially important for stuff like CTF flags TossClientItems( ent ); #ifdef MISSIONPACK TossClientPersistantPowerups( ent ); if( g_gametype.integer == GT_HARVESTER ) { TossClientCubes( ent ); } #endif } G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); // if we are playing in tourney mode and losing, give a win to the other player if ( (g_gametype.integer == GT_TOURNAMENT ) && !level.intermissiontime && !level.warmupTime && level.sortedClients[1] == clientNum ) { level.clients[ level.sortedClients[0] ].sess.wins++; ClientUserinfoChanged( level.sortedClients[0] ); } trap_UnlinkEntity (ent); ent->s.modelindex = 0; ent->inuse = qfalse; ent->classname = "disconnected"; ent->client->pers.connected = CON_DISCONNECTED; ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; ent->client->sess.sessionTeam = TEAM_FREE; trap_SetConfigstring( CS_PLAYERS + clientNum, ""); CalculateRanks(); if ( ent->r.svFlags & SVF_BOT ) { BotAIShutdownClient( clientNum, qfalse ); } } ================================================ FILE: code/game/g_cmds.c ================================================ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // #include "g_local.h" #include "../../ui/menudef.h" // for the voice chats /* ================== DeathmatchScoreboardMessage ================== */ void DeathmatchScoreboardMessage( gentity_t *ent ) { char entry[1024]; char string[1400]; int stringlength; int i, j; gclient_t *cl; int numSorted, scoreFlags, accuracy, perfect; // send the latest information on all clients string[0] = 0; stringlength = 0; scoreFlags = 0; numSorted = level.numConnectedClients; for (i=0 ; i < numSorted ; i++) { int ping; cl = &level.clients[level.sortedClients[i]]; if ( cl->pers.connected == CON_CONNECTING ) { ping = -1; } else { ping = cl->ps.ping < 999 ? cl->ps.ping : 999; } if( cl->accuracy_shots ) { accuracy = cl->accuracy_hits * 100 / cl->accuracy_shots; } else { accuracy = 0; } perfect = ( cl->ps.persistant[PERS_RANK] == 0 && cl->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; Com_sprintf (entry, sizeof(entry), " %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.sortedClients[i], cl->ps.persistant[PERS_SCORE], ping, (level.time - cl->pers.enterTime)/60000, scoreFlags, g_entities[level.sortedClients[i]].s.powerups, accuracy, cl->ps.persistant[PERS_IMPRESSIVE_COUNT], cl->ps.persistant[PERS_EXCELLENT_COUNT], cl->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], cl->ps.persistant[PERS_DEFEND_COUNT], cl->ps.persistant[PERS_ASSIST_COUNT], perfect, cl->ps.persistant[PERS_CAPTURES]); j = strlen(entry); if (stringlength + j > 1024) break; strcpy (string + stringlength, entry); stringlength += j; } trap_SendServerCommand( ent-g_entities, va("scores %i %i %i%s", i, level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE], string ) ); } /* ================== Cmd_Score_f Request current scoreboard information ================== */ void Cmd_Score_f( gentity_t *ent ) { DeathmatchScoreboardMessage( ent ); } /* ================== CheatsOk ================== */ qboolean CheatsOk( gentity_t *ent ) { if ( !g_cheats.integer ) { trap_SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\"")); return qfalse; } if ( ent->health <= 0 ) { trap_SendServerCommand( ent-g_entities, va("print \"You must be alive to use this command.\n\"")); return qfalse; } return qtrue; } /* ================== ConcatArgs ================== */ char *ConcatArgs( int start ) { int i, c, tlen; static char line[MAX_STRING_CHARS]; int len; char arg[MAX_STRING_CHARS]; len = 0; c = trap_Argc(); for ( i = start ; i < c ; i++ ) { trap_Argv( i, arg, sizeof( arg ) ); tlen = strlen( arg ); if ( len + tlen >= MAX_STRING_CHARS - 1 ) { break; } memcpy( line + len, arg, tlen ); len += tlen; if ( i != c - 1 ) { line[len] = ' '; len++; } } line[len] = 0; return line; } /* ================== SanitizeString Remove case and control characters ================== */ void SanitizeString( char *in, char *out ) { while ( *in ) { if ( *in == 27 ) { in += 2; // skip color code continue; } if ( *in < 32 ) { in++; continue; } *out++ = tolower( *in++ ); } *out = 0; } /* ================== ClientNumberFromString Returns a player number for either a number or name string Returns -1 if invalid ================== */ int ClientNumberFromString( gentity_t *to, char *s ) { gclient_t *cl; int idnum; char s2[MAX_STRING_CHARS]; char n2[MAX_STRING_CHARS]; // numeric values are just slot numbers if (s[0] >= '0' && s[0] <= '9') { idnum = atoi( s ); if ( idnum < 0 || idnum >= level.maxclients ) { trap_SendServerCommand( to-g_entities, va("print \"Bad client slot: %i\n\"", idnum)); return -1; } cl = &level.clients[idnum]; if ( cl->pers.connected != CON_CONNECTED ) { trap_SendServerCommand( to-g_entities, va("print \"Client %i is not active\n\"", idnum)); return -1; } return idnum; } // check for a name match SanitizeString( s, s2 ); for ( idnum=0,cl=level.clients ; idnum < level.maxclients ; idnum++,cl++ ) { if ( cl->pers.connected != CON_CONNECTED ) { continue; } SanitizeString( cl->pers.netname, n2 ); if ( !strcmp( n2, s2 ) ) { return idnum; } } trap_SendServerCommand( to-g_entities, va("print \"User %s is not on the server\n\"", s)); return -1; } /* ================== Cmd_Give_f Give items to a client ================== */ void Cmd_Give_f (gentity_t *ent) { char *name; gitem_t *it; int i; qboolean give_all; gentity_t *it_ent; trace_t trace; if ( !CheatsOk( ent ) ) { return; } name = ConcatArgs( 1 ); if (Q_stricmp(name, "all") == 0) give_all = qtrue; else give_all = qfalse; if (give_all || Q_stricmp( name, "health") == 0) { ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; if (!give_all) return; } if (give_all || Q_stricmp(name, "weapons") == 0) { ent->client->ps.stats[STAT_WEAPONS] = (1 << WP_NUM_WEAPONS) - 1 - ( 1 << WP_GRAPPLING_HOOK ) - ( 1 << WP_NONE ); if (!give_all) return; } if (give_all || Q_stricmp(name, "ammo") == 0) { for ( i = 0 ; i < MAX_WEAPONS ; i++ ) { ent->client->ps.ammo[i] = 999; } if (!give_all) return; } if (give_all || Q_stricmp(name, "armor") == 0) { ent->client->ps.stats[STAT_ARMOR] = 200; if (!give_all) return; } if (Q_stricmp(name, "excellent") == 0) { ent->client->ps.persistant[PERS_EXCELLENT_COUNT]++; return; } if (Q_stricmp(name, "impressive") == 0) { ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; return; } if (Q_stricmp(name, "gauntletaward") == 0) { ent->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++; return; } if (Q_stricmp(name, "defend") == 0) { ent->client->ps.persistant[PERS_DEFEND_COUNT]++; return; } if (Q_stricmp(name, "assist") == 0) { ent->client->ps.persistant[PERS_ASSIST_COUNT]++; return; } // spawn a specific item right on the player if ( !give_all ) { it = BG_FindItem (name); if (!it) { return; } it_ent = G_Spawn(); VectorCopy( ent->r.currentOrigin, it_ent->s.origin ); it_ent->classname = it->classname; G_SpawnItem (it_ent, it); FinishSpawningItem(it_ent ); memset( &trace, 0, sizeof( trace ) ); Touch_Item (it_ent, ent, &trace); if (it_ent->inuse) { G_FreeEntity( it_ent ); } } } /* ================== Cmd_God_f Sets client to godmode argv(0) god ================== */ void Cmd_God_f (gentity_t *ent) { char *msg; if ( !CheatsOk( ent ) ) { return; } ent->flags ^= FL_GODMODE; if (!(ent->flags & FL_GODMODE) ) msg = "godmode OFF\n"; else msg = "godmode ON\n"; trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); } /* ================== Cmd_Notarget_f Sets client to notarget argv(0) notarget ================== */ void Cmd_Notarget_f( gentity_t *ent ) { char *msg; if ( !CheatsOk( ent ) ) { return; } ent->flags ^= FL_NOTARGET; if (!(ent->flags & FL_NOTARGET) ) msg = "notarget OFF\n"; else msg = "notarget ON\n"; trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); } /* ================== Cmd_Noclip_f argv(0) noclip ================== */ void Cmd_Noclip_f( gentity_t *ent ) { char *msg; if ( !CheatsOk( ent ) ) { return; } if ( ent->client->noclip ) { msg = "noclip OFF\n"; } else { msg = "noclip ON\n"; } ent->client->noclip = !ent->client->noclip; trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); } /* ================== Cmd_LevelShot_f This is just to help generate the level pictures for the menus. It goes to the intermission immediately and sends over a command to the client to resize the view, hide the scoreboard, and take a special screenshot ================== */ void Cmd_LevelShot_f( gentity_t *ent ) { if ( !CheatsOk( ent ) ) { return; } // doesn't work in single player if ( g_gametype.integer != 0 ) { trap_SendServerCommand( ent-g_entities, "print \"Must be in g_gametype 0 for levelshot\n\"" ); return; } BeginIntermission(); trap_SendServerCommand( ent-g_entities, "clientLevelShot" ); } /* ================== Cmd_LevelShot_f This is just to help generate the level pictures for the menus. It goes to the intermission immediately and sends over a command to the client to resize the view, hide the scoreboard, and take a special screenshot ================== */ void Cmd_TeamTask_f( gentity_t *ent ) { char userinfo[MAX_INFO_STRING]; char arg[MAX_TOKEN_CHARS]; int task; int client = ent->client - level.clients; if ( trap_Argc() != 2 ) { return; } trap_Argv( 1, arg, sizeof( arg ) ); task = atoi( arg ); trap_GetUserinfo(client, userinfo, sizeof(userinfo)); Info_SetValueForKey(userinfo, "teamtask", va("%d", task)); trap_SetUserinfo(client, userinfo); ClientUserinfoChanged(client); } /* ================= Cmd_Kill_f ================= */ void Cmd_Kill_f( gentity_t *ent ) { if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { return; } if (ent->health <= 0) { return; } ent->flags &= ~FL_GODMODE; ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; player_die (ent, ent, ent, 100000, MOD_SUICIDE); } /* ================= BroadCastTeamChange Let everyone know about a team change ================= */ void BroadcastTeamChange( gclient_t *client, int oldTeam ) { if ( client->sess.sessionTeam == TEAM_RED ) { trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the red team.\n\"", client->pers.netname) ); } else if ( client->sess.sessionTeam == TEAM_BLUE ) { trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the blue team.\n\"", client->pers.netname)); } else if ( client->sess.sessionTeam == TEAM_SPECTATOR && oldTeam != TEAM_SPECTATOR ) { trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the spectators.\n\"", client->pers.netname)); } else if ( client->sess.sessionTeam == TEAM_FREE ) { trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the battle.\n\"", client->pers.netname)); } } /* ================= SetTeam ================= */ void SetTeam( gentity_t *ent, char *s ) { int team, oldTeam; gclient_t *client; int clientNum; spectatorState_t specState; int specClient; int teamLeader; // // see what change is requested // client = ent->client; clientNum = client - level.clients; specClient = 0; specState = SPECTATOR_NOT; if ( !Q_stricmp( s, "scoreboard" ) || !Q_stricmp( s, "score" ) ) { team = TEAM_SPECTATOR; specState = SPECTATOR_SCOREBOARD; } else if ( !Q_stricmp( s, "follow1" ) ) { team = TEAM_SPECTATOR; specState = SPECTATOR_FOLLOW; specClient = -1; } else if ( !Q_stricmp( s, "follow2" ) ) { team = TEAM_SPECTATOR; specState = SPECTATOR_FOLLOW; specClient = -2; } else if ( !Q_stricmp( s, "spectator" ) || !Q_stricmp( s, "s" ) ) { team = TEAM_SPECTATOR; specState = SPECTATOR_FREE; } else if ( g_gametype.integer >= GT_TEAM ) { // if running a team game, assign player to one of the teams specState = SPECTATOR_NOT; if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { team = TEAM_RED; } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { team = TEAM_BLUE; } else { // pick the team with the least number of players team = PickTeam( clientNum ); } if ( g_teamForceBalance.integer ) { int counts[TEAM_NUM_TEAMS]; counts[TEAM_BLUE] = TeamCount( ent->client->ps.clientNum, TEAM_BLUE ); counts[TEAM_RED] = TeamCount( ent->client->ps.clientNum, TEAM_RED ); // We allow a spread of two if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 ) { trap_SendServerCommand( ent->client->ps.clientNum, "cp \"Red team has too many players.\n\"" ); return; // ignore the request } if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 ) { trap_SendServerCommand( ent->client->ps.clientNum, "cp \"Blue team has too many players.\n\"" ); return; // ignore the request } // It's ok, the team we are switching to has less or same number of players } } else { // force them to spectators if there aren't any spots free team = TEAM_FREE; } // override decision if limiting the players if ( (g_gametype.integer == GT_TOURNAMENT) && level.numNonSpectatorClients >= 2 ) { team = TEAM_SPECTATOR; } else if ( g_maxGameClients.integer > 0 && level.numNonSpectatorClients >= g_maxGameClients.integer ) { team = TEAM_SPECTATOR; } // // decide if we will allow the change // oldTeam = client->sess.sessionTeam; if ( team == oldTeam && team != TEAM_SPECTATOR ) { return; } // // execute the team change // // if the player was dead leave the body if ( client->ps.stats[STAT_HEALTH] <= 0 ) { CopyToBodyQue(ent); } // he starts at 'base' client->pers.teamState.state = TEAM_BEGIN; if ( oldTeam != TEAM_SPECTATOR ) { // Kill him (makes sure he loses flags, etc) ent->flags &= ~FL_GODMODE; ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; player_die (ent, ent, ent, 100000, MOD_SUICIDE); } // they go to the end of the line for tournements if ( team == TEAM_SPECTATOR ) { client->sess.spectatorTime = level.time; } client->sess.sessionTeam = team; client->sess.spectatorState = specState; client->sess.spectatorClient = specClient; client->sess.teamLeader = qfalse; if ( team == TEAM_RED || team == TEAM_BLUE ) { teamLeader = TeamLeader( team ); // if there is no team leader or the team leader is a bot and this client is not a bot if ( teamLeader == -1 || ( !(g_entities[clientNum].r.svFlags & SVF_BOT) && (g_entities[teamLeader].r.svFlags & SVF_BOT) ) ) { SetLeader( team, clientNum ); } } // make sure there is a team leader on the team the player came from if ( oldTeam == TEAM_RED || oldTeam == TEAM_BLUE ) { CheckTeamLeader( oldTeam ); } BroadcastTeamChange( client, oldTeam ); // get and distribute relevent paramters ClientUserinfoChanged( clientNum ); ClientBegin( clientNum ); } /* ================= StopFollowing If the client being followed leaves the game, or you just want to drop to free floating spectator mode ================= */ void StopFollowing( gentity_t *ent ) { ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; ent->client->sess.sessionTeam = TEAM_SPECTATOR; ent->client->sess.spectatorState = SPECTATOR_FREE; ent->client->ps.pm_flags &= ~PMF_FOLLOW; ent->r.svFlags &= ~SVF_BOT; ent->client->ps.clientNum = ent - g_entities; } /* ================= Cmd_Team_f ================= */ void Cmd_Team_f( gentity_t *ent ) { int oldTeam; char s[MAX_TOKEN_CHARS]; if ( trap_Argc() != 2 ) { oldTeam = ent->client->sess.sessionTeam; switch ( oldTeam ) { case TEAM_BLUE: trap_SendServerCommand( ent-g_entities, "print \"Blue team\n\"" ); break; case TEAM_RED: trap_SendServerCommand( ent-g_entities, "print \"Red team\n\"" ); break; case TEAM_FREE: trap_SendServerCommand( ent-g_entities, "print \"Free team\n\"" ); break; case TEAM_SPECTATOR: trap_SendServerCommand( ent-g_entities, "print \"Spectator team\n\"" ); break; } return; } if ( ent->client->switchTeamTime > level.time ) { trap_SendServerCommand( ent-g_entities, "print \"May not switch teams more than once per 5 seconds.\n\"" ); return; } // if they are playing a tournement game, count as a loss if ( (g_gametype.integer == GT_TOURNAMENT ) && ent->client->sess.sessionTeam == TEAM_FREE ) { ent->client->sess.losses++; } trap_Argv( 1, s, sizeof( s ) ); SetTeam( ent, s ); ent->client->switchTeamTime = level.time + 5000; } /* ================= Cmd_Follow_f ================= */ void Cmd_Follow_f( gentity_t *ent ) { int i; char arg[MAX_TOKEN_CHARS]; if ( trap_Argc() != 2 ) { if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { StopFollowing( ent ); } return; } trap_Argv( 1, arg, sizeof( arg ) ); i = ClientNumberFromString( ent, arg ); if ( i == -1 ) { return; } // can't follow self if ( &level.clients[ i ] == ent->client ) { return; } // can't follow another spectator if ( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR ) { return; } // if they are playing a tournement game, count as a loss if ( (g_gametype.integer == GT_TOURNAMENT ) && ent->client->sess.sessionTeam == TEAM_FREE ) { ent->client->sess.losses++; } // first set them to spectator if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { SetTeam( ent, "spectator" ); } ent->client->sess.spectatorState = SPECTATOR_FOLLOW; ent->client->sess.spectatorClient = i; } /* ================= Cmd_FollowCycle_f ================= */ void Cmd_FollowCycle_f( gentity_t *ent, int dir ) { int clientnum; int original; // if they are playing a tournement game, count as a loss if ( (g_gametype.integer == GT_TOURNAMENT ) && ent->client->sess.sessionTeam == TEAM_FREE ) { ent->client->sess.losses++; } // first set them to spectator if ( ent->client->sess.spectatorState == SPECTATOR_NOT ) { SetTeam( ent, "spectator" ); } if ( dir != 1 && dir != -1 ) { G_Error( "Cmd_FollowCycle_f: bad dir %i", dir ); } clientnum = ent->client->sess.spectatorClient; original = clientnum; do { clientnum += dir; if ( clientnum >= level.maxclients ) { clientnum = 0; } if ( clientnum < 0 ) { clientnum = level.maxclients - 1; } // can only follow connected clients if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) { continue; } // can't follow another spectator if ( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR ) { continue; } // this is good, we can use it ent->client->sess.spectatorClient = clientnum; ent->client->sess.spectatorState = SPECTATOR_FOLLOW; return; } while ( clientnum != original ); // leave it where it was } /* ================== G_Say ================== */ static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message ) { if (!other) { return; } if (!other->inuse) { return; } if (!other->client) { return; } if ( other->client->pers.connected != CON_CONNECTED ) { return; } if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { return; } // no chatting to players in tournements if ( (g_gametype.integer == GT_TOURNAMENT ) && other->client->sess.sessionTeam == TEAM_FREE && ent->client->sess.sessionTeam != TEAM_FREE ) { return; } trap_SendServerCommand( other-g_entities, va("%s \"%s%c%c%s\"", mode == SAY_TEAM ? "tchat" : "chat", name, Q_COLOR_ESCAPE, color, message)); } #define EC "\x19" void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) { int j; gentity_t *other; int color; char name[64]; // don't let text be too long for malicious reasons char text[MAX_SAY_TEXT]; char location[64]; if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { mode = SAY_ALL; } switch ( mode ) { default: case SAY_ALL: G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText ); Com_sprintf (name, sizeof(name), "%s%c%c"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); color = COLOR_GREEN; break; case SAY_TEAM: G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, chatText ); if (Team_GetLocationMsg(ent, location, sizeof(location))) Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC") (%s)"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location); else Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC")"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); color = COLOR_CYAN; break; case SAY_TELL: if (target && g_gametype.integer >= GT_TEAM && target->client->sess.sessionTeam == ent->client->sess.sessionTeam && Team_GetLocationMsg(ent, location, sizeof(location))) Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"] (%s)"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); else Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"]"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); color = COLOR_MAGENTA; break; } Q_strncpyz( text, chatText, sizeof(text) ); if ( target ) { G_SayTo( ent, target, mode, color, name, text ); return; } // echo the text to the console if ( g_dedicated.integer ) { G_Printf( "%s%s\n", name, text); } // send it to all the apropriate clients for (j = 0; j < level.maxclients; j++) { other = &g_entities[j]; G_SayTo( ent, other, mode, color, name, text ); } } /* ================== Cmd_Say_f ================== */ static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) { char *p; if ( trap_Argc () < 2 && !arg0 ) { return; } if (arg0) { p = ConcatArgs( 0 ); } else { p = ConcatArgs( 1 ); } G_Say( ent, NULL, mode, p ); } /* ================== Cmd_Tell_f ================== */ static void Cmd_Tell_f( gentity_t *ent ) { int targetNum; gentity_t *target; char *p; char arg[MAX_TOKEN_CHARS]; if ( trap_Argc () < 2 ) { return; } trap_Argv( 1, arg, sizeof( arg ) ); targetNum = atoi( arg ); if ( targetNum < 0 || targetNum >= level.maxclients ) { return; } target = &g_entities[targetNum]; if ( !target || !target->inuse || !target->client ) { return; } p = ConcatArgs( 2 ); G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); G_Say( ent, target, SAY_TELL, p ); // don't tell to the player self if it was already directed to this player // also don't send the chat back to a bot if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { G_Say( ent, ent, SAY_TELL, p ); } } static void G_VoiceTo( gentity_t *ent, gentity_t *other, int mode, const char *id, qboolean voiceonly ) { int color; char *cmd; if (!other) { return; } if (!other->inuse) { return; } if (!other->client) { return; } if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { return; } // no chatting to players in tournements if ( (g_gametype.integer == GT_TOURNAMENT )) { return; } if (mode == SAY_TEAM) { color = COLOR_CYAN; cmd = "vtchat"; } else if (mode == SAY_TELL) { color = COLOR_MAGENTA; cmd = "vtell"; } else { color = COLOR_GREEN; cmd = "vchat"; } trap_SendServerCommand( other-g_entities, va("%s %d %d %d %s", cmd, voiceonly, ent->s.number, color, id)); } void G_Voice( gentity_t *ent, gentity_t *target, int mode, const char *id, qboolean voiceonly ) { int j; gentity_t *other; if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { mode = SAY_ALL; } if ( target ) { G_VoiceTo( ent, target, mode, id, voiceonly ); return; } // echo the text to the console if ( g_dedicated.integer ) { G_Printf( "voice: %s %s\n", ent->client->pers.netname, id); } // send it to all the apropriate clients for (j = 0; j < level.maxclients; j++) { other = &g_entities[j]; G_VoiceTo( ent, other, mode, id, voiceonly ); } } /* ================== Cmd_Voice_f ================== */ static void Cmd_Voice_f( gentity_t *ent, int mode, qboolean arg0, qboolean voiceonly ) { char *p; if ( trap_Argc () < 2 && !arg0 ) { return; } if (arg0) { p = ConcatArgs( 0 ); } else { p = ConcatArgs( 1 ); } G_Voice( ent, NULL, mode, p, voiceonly ); } /* ================== Cmd_VoiceTell_f ================== */ static void Cmd_VoiceTell_f( gentity_t *ent, qboolean voiceonly ) { int targetNum; gentity_t *target; char *id; char arg[MAX_TOKEN_CHARS]; if ( trap_Argc () < 2 ) { return; } trap_Argv( 1, arg, sizeof( arg ) ); targetNum = atoi( arg ); if ( targetNum < 0 || targetNum >= level.maxclients ) { return; } target = &g_entities[targetNum]; if ( !target || !target->inuse || !target->client ) { return; } id = ConcatArgs( 2 ); G_LogPrintf( "vtell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, id ); G_Voice( ent, target, SAY_TELL, id, voiceonly ); // don't tell to the player self if it was already directed to this player // also don't send the chat back to a bot if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { G_Voice( ent, ent, SAY_TELL, id, voiceonly ); } } /* ================== Cmd_VoiceTaunt_f ================== */ static void Cmd_VoiceTaunt_f( gentity_t *ent ) { gentity_t *who; int i; if (!ent->client) { return; } // insult someone who just killed you if (ent->enemy && ent->enemy->client && ent->enemy->client->lastkilled_client == ent->s.number) { // i am a dead corpse if (!(ent->enemy->r.svFlags & SVF_BOT)) { G_Voice( ent, ent->enemy, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); } if (!(ent->r.svFlags & SVF_BOT)) { G_Voice( ent, ent, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); } ent->enemy = NULL; return; } // insult someone you just killed if (ent->client->lastkilled_client >= 0 && ent->client->lastkilled_client != ent->s.number) { who = g_entities + ent->client->lastkilled_client; if (who->client) { // who is the person I just killed if (who->client->lasthurt_mod == MOD_GAUNTLET) { if (!(who->r.svFlags & SVF_BOT)) { G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); // and I killed them with a gauntlet } if (!(ent->r.svFlags & SVF_BOT)) { G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); } } else { if (!(who->r.svFlags & SVF_BOT)) { G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); // and I killed them with something else } if (!(ent->r.svFlags & SVF_BOT)) { G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); } } ent->client->lastkilled_client = -1; return; } } if (g_gametype.integer >= GT_TEAM) { // praise a team mate who just got a reward for(i = 0; i < MAX_CLIENTS; i++) { who = g_entities + i; if (who->client && who != ent && who->client->sess.sessionTeam == ent->client->sess.sessionTeam) { if (who->client->rewardTime > level.time) { if (!(who->r.svFlags & SVF_BOT)) { G_Voice( ent, who, SAY_TELL, VOICECHAT_PRAISE, qfalse ); } if (!(ent->r.svFlags & SVF_BOT)) { G_Voice( ent, ent, SAY_TELL, VOICECHAT_PRAISE, qfalse ); } return; } } } } // just say something G_Voice( ent, NULL, SAY_ALL, VOICECHAT_TAUNT, qfalse ); } static char *gc_orders[] = { "hold your position", "hold this position", "come here", "cover me", "guard location", "search and destroy", "report" }; void Cmd_GameCommand_f( gentity_t *ent ) { int player; int order; char str[MAX_TOKEN_CHARS]; trap_Argv( 1, str, sizeof( str ) ); player = atoi( str ); trap_Argv( 2, str, sizeof( str ) ); order = atoi( str ); if ( player < 0 || player >= MAX_CLIENTS ) { return; } if ( order < 0 || order > sizeof(gc_orders)/sizeof(char *) ) { return; } G_Say( ent, &g_entities[player], SAY_TELL, gc_orders[order] ); G_Say( ent, ent, SAY_TELL, gc_orders[order] ); } /* ================== Cmd_Where_f ================== */ void Cmd_Where_f( gentity_t *ent ) { trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) ); } static const char *gameNames[] = { "Free For All", "Tournament", "Single Player", "Team Deathmatch", "Capture the Flag", "One Flag CTF", "Overload", "Harvester" }; /* ================== Cmd_CallVote_f ================== */ void Cmd_CallVote_f( gentity_t *ent ) { int i; char arg1[MAX_STRING_TOKENS]; char arg2[MAX_STRING_TOKENS]; if ( !g_allowVote.integer ) { trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here.\n\"" ); return; } if ( level.voteTime ) { trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress.\n\"" ); return; } if ( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) { trap_SendServerCommand( ent-g_entities, "print \"You have called the maximum number of votes.\n\"" ); return; } if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" ); return; } // make sure it is a valid command to vote on trap_Argv( 1, arg1, sizeof( arg1 ) ); trap_Argv( 2, arg2, sizeof( arg2 ) ); if( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); return; } if ( !Q_stricmp( arg1, "map_restart" ) ) { } else if ( !Q_stricmp( arg1, "nextmap" ) ) { } else if ( !Q_stricmp( arg1, "map" ) ) { } else if ( !Q_stricmp( arg1, "g_gametype" ) ) { } else if ( !Q_stricmp( arg1, "kick" ) ) { } else if ( !Q_stricmp( arg1, "clientkick" ) ) { } else if ( !Q_stricmp( arg1, "g_doWarmup" ) ) { } else if ( !Q_stricmp( arg1, "timelimit" ) ) { } else if ( !Q_stricmp( arg1, "fraglimit" ) ) { } else { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); trap_SendServerCommand( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map , g_gametype , kick , clientkick , g_doWarmup, timelimit