From 3d4ea805d372a86b5464d5724053f8406af39c62 Mon Sep 17 00:00:00 2001 From: driAn Date: Fri, 9 Jun 2006 12:22:46 +0000 Subject: [PATCH] no message git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@302 e016b1c5-ed68-44aa-a2f7-31cac88cffde --- actioncube.bat | 3 +- config/autoexec.cfg | 48 +- source/lib/SDL.lib | Bin 39580 -> 39580 bytes source/lib/SDL_image.lib | Bin 16664 -> 16664 bytes source/lib/SDL_mixer.lib | Bin 13520 -> 13520 bytes source/lib/SDLmain.lib | Bin 6882 -> 6882 bytes source/lib/fmodvc.lib | Bin 164858 -> 164858 bytes source/lib/zlib.lib | Bin 34674 -> 34674 bytes source/src/bot/ac_bot.cpp | 634 +-- source/src/bot/ac_bot.h | 94 +- source/src/bot/ac_bot_ai.cpp | 668 ++-- source/src/bot/bot.cpp | 990 ++--- source/src/bot/bot.h | 860 ++-- source/src/bot/bot_ai.cpp | 4590 +++++++++++----------- source/src/bot/bot_util.cpp | 1129 +++--- source/src/bot/bot_waypoint.cpp | 6516 +++++++++++++++---------------- source/src/bot/botmanager.cpp | 2870 +++++++------- source/src/client.cpp | 666 ++-- source/src/clientextras.cpp | 436 +-- source/src/clientgame.cpp | 1359 +++---- source/src/clients2c.cpp | 1346 +++---- source/src/command.cpp | 670 ++-- source/src/console.cpp | 488 +-- source/src/cube.h | 916 ++--- source/src/editing.cpp | 968 ++--- source/src/entities.cpp | 750 ++-- source/src/main.cpp | 505 ++- source/src/menus.cpp | 450 +-- source/src/pch.cpp | 2 +- source/src/physics.cpp | 830 ++-- source/src/protos.h | 557 +-- source/src/rendercubes.cpp | 718 ++-- source/src/renderextras.cpp | 1134 +++--- source/src/rendergl.cpp | 836 ++-- source/src/rendermd2.cpp | 566 +-- source/src/rendermd3.cpp | 1118 +++--- source/src/renderparticles.cpp | 290 +- source/src/rendertext.cpp | 444 +-- source/src/rndmap.cpp | 140 +- source/src/savegamedemo.cpp | 700 ++-- source/src/server.cpp | 1932 ++++----- source/src/serverbrowser.cpp | 606 +-- source/src/serverms.cpp | 282 +- source/src/serverutil.cpp | 290 +- source/src/sound.cpp | 468 +-- source/src/tools.cpp | 280 +- source/src/tools.h | 550 +-- source/src/weapon.cpp | 1441 +++---- source/src/world.cpp | 796 ++-- source/src/worldio.cpp | 736 ++-- source/src/worldlight.cpp | 426 +- source/src/worldocull.cpp | 272 +- source/src/worldrender.cpp | 592 +-- 53 files changed, 21488 insertions(+), 21474 deletions(-) diff --git a/actioncube.bat b/actioncube.bat index af9290c5..43f89f23 100755 --- a/actioncube.bat +++ b/actioncube.bat @@ -1 +1,2 @@ -bin_win32\ac_client.exe -w1024 -h768 %1 %2 %3 %4 %5 \ No newline at end of file +bin_win32\ac_client.exe -w1280 -h1024 %1 %2 %3 %4 %5 +pause \ No newline at end of file diff --git a/config/autoexec.cfg b/config/autoexec.cfg index 12477c60..587fad16 100755 --- a/config/autoexec.cfg +++ b/config/autoexec.cfg @@ -2,7 +2,7 @@ // and all your keybindings that are different from the default (see keymap.cfg) name "unarmed" // change! -team "AC" +team "cuber" invmouse 0 // 1 for flightsim mode sensitivity 25 @@ -69,14 +69,18 @@ alias universaldelta [ bind MOUSE4 [ universaldelta 1 ] bind MOUSE5 [ universaldelta -1 ] -//bind 1 "weapon 1" -//bind 2 "weapon 2" -//bind 3 "weapon 3" -//bind 4 "weapon 4" -//bind 5 "weapon 5" -//bind 6 "weapon 6" +alias primary [ weapon 0 ] +alias secondary [ weapon 1 ] +alias melee [ weapon 2 ] +alias grenade [ weapon 3 ] + +bind 1 primary +bind 2 secondary +bind 3 melee +bind 4 grenade + bind R reload -bind MOUSE2 "scoped 1; onrelease [ scoped 0 ]" +bind MOUSE2 [ altattack ] alias delta_edit_0 [ editheight $flrceil $arg1 ] alias delta_edit_1 [ if $flrceil [ vdelta $arg1 ] [ vdelta (- 0 $arg1) ] ] @@ -85,39 +89,13 @@ alias delta_edit_3 [ edittex (+ $flrceil 1) $arg1 ] alias delta_edit_4 [ equalize $flrceil ] alias delta_edit_5 [ entproperty 0 $arg1 ] // and the others -alias delta_game_0 [ if (= $arg1 1) [ wup ] [ wdw ] ] - -alias zfov 100 - -alias delta_game_1 [ - alias zfov (+ $zfov (* $arg1 (div $zfov -5))) - if (< $zfov 10) [ alias zfov 10 ] - if (> $zfov 120) [ alias zfov 120 ] - fov $zfov -] +alias delta_game_0 [ if (= $arg1 1) [ shiftweapon 1 ] [ shiftweapon -1 ] ] if (only_menu_music) [ musicvol 1; music $menu_music ] -bind p "connect 67.163.146.134" - // change hudgun 1 -bind KP_MINUS "conskip 1" -bind KP_PLUS "conskip -1" - -bind 1 "newent light 16 100" -bind 2 "newent light 13 120" -bind 3 "newent light 10 150" -bind 4 "newent light 8 150" -bind 5 "newent light 6 175" -bind 6 "newent light 5 180" -bind 7 "newent light 4 100" -bind 8 "newent light 3 225" -bind 9 "newent light 2 225" - -bind right "vdelta 1" -bind left "vdelta -1" diff --git a/source/lib/SDL.lib b/source/lib/SDL.lib index 1852730a276053a35330f0ee12d24d58bb586dd9..df81f8e2bb37c34319f3644179db567da0b35fc4 100755 GIT binary patch delta 35 mcmbQUm1)jarVanp7`Zn8SF2?NQ>BcXKWR2-0NKSsnjQcM8V#QS delta 35 mcmbQUm1)jarVanp7BcXKWR2-0NKSsnjQcMtqrIE diff --git a/source/lib/SDL_image.lib b/source/lib/SDL_image.lib index 3b6d691a8f9709f68e9c4ac758572ad15acad3a0..736d4390c53ea1e47c23352242c37193ed1fbd5e 100755 GIT binary patch delta 44 xcmbQy#5kjgaY7j@R|EqC*T(7!7DlegUs#HOWIC%cBiH8Hto*7#YPD9b7yuvX3_}0_ delta 44 xcmbQy#5kjgaY7j@Zv+Da@5bs17DnF5Us#HOWIC%cBk$(fto*7#YPD9b7yuz`3{?OC diff --git a/source/lib/SDL_mixer.lib b/source/lib/SDL_mixer.lib index 880d6d902714cd9c29713f5493c530ba0e1756c4..381b6e75ecdf5f852a546ec1b3f6d8eb8dcb230e 100755 GIT binary patch delta 64 zcmcbRc_DMd1|CMP$s2i!!KCZvEZ!enK<0UA`^jshcs8$-*vAOrZeAm$1Y~YLFWt)q Jq+*qdg#Zp*7Cis} delta 64 zcmcbRc_DMd1|CM<$s2i!!KCZvEZ!enK<0UA`^jshcs8$-*vAOrZeAm$1Y~YLFWt)q Jq+*qdg#Zwc7Eb^G diff --git a/source/lib/SDLmain.lib b/source/lib/SDLmain.lib index 532a9423c1a33dfb5e6814592a2491a9295ca6c2..b68e80ec5e0ae41ee914e548003b31fc2715d98c 100755 GIT binary patch delta 129 zcmaE4`p9&{Q)Wi4&Ci(AnOV6Q7#z4Z2eG>_GIC9x&M^^4YI4p5${yyF019os!?gw^ z)5+rsB+v7h0!db05ZjygAy8VE?=eVPP#_1$sT1&GVFMb-z|1)L0jmf%14A_<0|N&T Qb4`9LQVUd(En3bB0J*3iMgRZ+ delta 129 zcmaE4`p9&{Q)Wiq&Ci(AnOS)m7#w&v2eG>_GV)HI&M^^4YI4p5${yyF019os!?gw^ z)5+rsB+v7h0!db05ZjygAy8VE?=eVPP#_1$sT1&GVFMb-z|1)L0jmf%14A_<0|N&T Q^G<#%QVUd(En3bB0LOwKc>n+a diff --git a/source/lib/fmodvc.lib b/source/lib/fmodvc.lib index 74af88a8de0c6621fbc76b947b955b283eea3325..7e7b33c203ad465947d7fe2f9c1408e6a1f1bd7c 100755 GIT binary patch delta 39 scmey>&h@LEYr_INMy|~Z?M^a+sb0p-KO8l}7`d7iXKYuT!FcmE07nTB9RL6T delta 39 scmey>&h@LEYr_INM&8W}?M^a+sb0p-KO8l}7X~Q-iMy|<*ys49ActsewHXHJ`s!yJxyKeG@M#;^!re6aA9%T;N delta 45 zcmey=$MmU>X~Q-iM&8MWys49Actsd_HyiS{s -// -// -// - -#include "../cube.h" - -#ifdef AC_CUBE - -extern vector sents; -extern int triggertime; -extern itemstat itemstats[]; -extern void spawnstate(dynent *d); - -//AC Bot class begin - -void CACBot::Spawn() -{ - // Init all bot variabeles - m_pMyEnt->eyeheight = 4.25f; - m_pMyEnt->aboveeye = 0.7f; - m_pMyEnt->radius = 1.1f; - m_pMyEnt->primary = m_pMyEnt->nextprimary = rnd(3)+GUN_PISTOL; - - spawnplayer(m_pMyEnt); - - m_pMyEnt->targetyaw = m_pMyEnt->yaw = m_pMyEnt->targetpitch = m_pMyEnt->pitch = 0.0f; - m_pMyEnt->primary = m_pMyEnt->nextprimary = GUN_ASSULT; - m_pMyEnt->move = 0; - m_pMyEnt->enemy = NULL; - m_pMyEnt->maxspeed = 16.0f; - m_pMyEnt->health = 100; - m_pMyEnt->armour = 50; - m_pMyEnt->pitch = 0; - m_pMyEnt->roll = 0; - m_pMyEnt->state = CS_ALIVE; - m_pMyEnt->anger = 0; - m_pMyEnt->pBot = this; -// loopi(NUMGUNS) m_pMyEnt->ammo[i] = m_pMyEnt->mag[i] = 0; - - /* UNDONE - m_pMyEnt->ammo[GUN_FIST] = 1; - if(m_noitems) - { - m_pMyEnt->gunselect = GUN_RIFLE; - m_pMyEnt->armour = 0; - if(m_noitemsrail) - { - m_pMyEnt->health = 1; - m_pMyEnt->ammo[GUN_RIFLE] = 100; - } - else - { - if(gamemode==12) // eihrul's secret "instafist" mode - { - m_pMyEnt->gunselect = GUN_FIST; - } - else - { - m_pMyEnt->health = 256; - if(m_tarena) - { - int gun1 = rnd(4)+1; - botbaseammo(m_pMyEnt->gunselect = gun1, m_pMyEnt); - for(;;) - { - int gun2 = rnd(4)+1; - if(gun1!=gun2) { botbaseammo(gun2, m_pMyEnt); break; }; - } - } - else if(m_arena) // insta arena - { - m_pMyEnt->ammo[GUN_RIFLE] = 100; - } - else // efficiency - { - loopi(4) botbaseammo(i+1, m_pMyEnt); - m_pMyEnt->gunselect = GUN_CG; - } - m_pMyEnt->ammo[GUN_CG] /= 2; - } - } - } - else - { - m_pMyEnt->ammo[GUN_SEMIPISTOL] = 5; - SelectGun(GUN_SG); - }*/ - - m_pMyEnt->ammo[GUN_KNIFE] = 1; - m_pMyEnt->ammo[GUN_PISTOL] = 5; - m_pMyEnt->mag[GUN_PISTOL] = 5; - SelectGun(m_pMyEnt->primary); - - m_eCurrentBotState = STATE_NORMAL; - m_iShootDelay = m_iChangeWeaponDelay = 0; - m_iCheckEnvDelay = 0; - m_vPrevOrigin = g_vecZero; - m_iStuckCheckDelay = lastmillis + 250; - m_bStuck = false; - m_iStuckTime = 0; - m_iStrafeTime = m_iStrafeCheckDelay = 0; - m_iMoveDir = DIR_NONE; - - m_pPrevEnemy = NULL; - m_iCombatNavTime = 0; - m_iSPMoveTime = 0; - m_iEnemySearchDelay = 0; - m_bCombatJump = false; - m_iCombatJumpDelay = 0; - m_bShootAtFeet = (RandomLong(1, 100) <= m_pBotSkill->sShootAtFeetWithRLPercent); - m_iHuntDelay = 0; - m_vHuntLocation = m_vPrevHuntLocation = g_vecZero; - m_pHuntTarget = NULL; - m_fPrevHuntDist = 0.0f; - m_iHuntLastTurnLessTime = m_iHuntPlayerUpdateTime = m_iHuntPauseTime = 0; - - m_iLastJumpPad = 0; - m_pTargetEnt = NULL; - m_iCheckTeleporterDelay = m_iCheckJumppadsDelay = 0; - m_iCheckEntsDelay = 0; - m_iCheckTriggersDelay = 0; - m_iLookForWaypointTime = 0; - - m_iAimDelay = 0; - m_fYawToTurn = m_fPitchToTurn = 0.0f; - - m_vGoal = m_vWaterGoal = g_vecZero; - - ResetWaypointVars(); -} - -void CACBot::BotPain(int damage, dynent *d) -{ - if(m_pMyEnt->state!=CS_ALIVE || editmode || intermission) return; -//fixmebot - int ad = damage/**(m_pMyEnt->armourtype+1)*20/100*/; // let armour absorb when possible - if(ad>m_pMyEnt->armour) ad = m_pMyEnt->armour; - m_pMyEnt->armour -= ad; - damage -= ad; - float droll = damage/0.5f; - m_pMyEnt->roll += m_pMyEnt->roll>0 ? droll : (m_pMyEnt->roll<0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending on amount of damage - if((m_pMyEnt->health -= damage)<=0) - { - if (player1 == d) - { - int frags; - if(isteam(player1->team, m_pMyEnt->team)) - { - frags = -1; - conoutf("you fragged a teammate (%s)", (int)m_pMyEnt->name); - } - else - { - frags = 1; - conoutf("you fragged %s", (int)m_pMyEnt->name); - } - addmsg(1, 2, SV_FRAGS, player1->frags += frags); - addmsg(1, 4, SV_BOTDIED, BotManager.GetBotIndex(m_pMyEnt), -1, false); - } - else if (d->monsterstate) - conoutf("%s got killed by %s", (int)m_pMyEnt->name, (int)d->name); - else - { - int KillerIndex = -1; - - if (d == m_pMyEnt) - { - conoutf("%s suicided", (int)d->name); - KillerIndex = BotManager.GetBotIndex(d); - addmsg(1, 3, SV_BOTFRAGS, KillerIndex, --d->frags); - } - else - { - if (d->bIsBot) - { - KillerIndex = BotManager.GetBotIndex(d); - addmsg(1, 3, SV_BOTFRAGS, KillerIndex, ++d->frags); - } - else - loopv(players){ if (players[i] == d) { KillerIndex = i; break;} } - - if(isteam(m_pMyEnt->team, d->team)) - { - conoutf("%s fragged his teammate (%s)", (int)d->name, - (int)m_pMyEnt->name); - } - else - { - conoutf("%s fragged %s", (int)d->name, (int)m_pMyEnt->name); - } - } - addmsg(1, 4, SV_BOTDIED, BotManager.GetBotIndex(m_pMyEnt), KillerIndex, - d->bIsBot); - } - - m_pMyEnt->lifesequence++; - m_pMyEnt->attacking = false; - m_pMyEnt->state = CS_DEAD; - m_pMyEnt->pitch = 0; - m_pMyEnt->roll = 60; - playsound(S_DIE1+rnd(2), &m_pMyEnt->o); - spawnstate(m_pMyEnt); - m_pMyEnt->lastaction = lastmillis; - } - else - { - playsound(S_PAIN6, &m_pMyEnt->o); - } -} - -void CACBot::CheckItemPickup() -{ - if(editmode) return; - loopv(ents) - { - entity &e = ents[i]; - if(e.type==NOTUSED) continue; - - if (!e.spawned) continue; - if ((i < sents.length()) && (!sents[i].spawned)) continue; - - if(OUTBORD(e.x, e.y)) continue; - vec v = { e.x, e.y, S(e.x, e.y)->floor+m_pMyEnt->eyeheight }; - vdist(dist, t, m_pMyEnt->o, v); - if(dist<2.5) - { - PickUp(i); - } - } -} - -void CACBot::PickUp(int n) -{ - int np = 1; - loopv(players) if(players[i]) np++; - loopv(bots) if(bots[i]) np++; - np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players - int ammo = np*2; - switch(ents[n].type) - { - case I_CLIPS: AddItem(n, m_pMyEnt->ammo[GUN_PISTOL], ammo); break; - case I_AMMO: AddItem(n, m_pMyEnt->ammo[m_pMyEnt->primary], ammo); break; - case I_GRENADE: AddItem(n, m_pMyEnt->ammo[GUN_GRENADE], ammo); break; - -/* case I_HELMET: - // (100h/100g only absorbs 166 damage) - if(m_pMyEnt->armourtype==A_YELLOW && m_pMyEnt->armour>66) break; - AddItem(n, m_pMyEnt->armour, 20); - break; - - case I_ARMOUR: - AddItem(n, m_pMyEnt->armour, 20); - break; - - case OBJ_ITEM: - AddItem(n, m_pMyEnt->quadmillis, 60); - break;*/ - -/* case TRIGGER: - ents[n].spawned = false; - triggertime = lastmillis; - trigger(ents[n].attr1, ents[n].attr2, false); // needs to go over server for multiplayer - - // HACK: Reset ent goal if bot was looking for this ent - if (m_pTargetEnt == &ents[n]) - { - m_pTargetEnt = NULL; - m_vGoal = g_vecZero; - ResetWaypointVars(); - m_iCheckEntsDelay = lastmillis + RandomLong(5000, 10000); - } - - BotManager.PickNextTrigger(); - break;*/ - }; -}; - -void CACBot::AddItem(int i, int &v, int spawnsec) -{ - if((i>=sents.length()) || (!sents[i].spawned)) - return; - -//fixmebot - if(v>=itemstats[ents[i].type-1].max) // don't pick up if not needed - return; - - itemstat &is = itemstats[ents[i].type-1]; - ents[i].spawned = false; - v += is.add; - if(v>is.max) v = is.max; - botplaysound(is.sound, m_pMyEnt); - sents[i].spawned = false; - sents[i].spawnsecs = spawnsec; - - addmsg(1, 3, SV_BOTITEMPICKUP, i, m_classicsp ? 100000 : spawnsec); - - // HACK: Reset ent goal if bot was looking for for this ent - if (m_pTargetEnt == &ents[i]) - { - m_pTargetEnt = NULL; - m_vGoal = g_vecZero; - ResetWaypointVars(); - m_iCheckEntsDelay = lastmillis + RandomLong(5000, 10000); - }; -} - -// AC Bot class end - -#endif +// +// C++ Implementation: bot +// +// Description: Main bot code for Action Cube +// +// Main bot file +// +// Author: Rick +// +// +// + +#include "../cube.h" + +#ifdef AC_CUBE + +extern vector sents; +extern int triggertime; +extern itemstat itemstats[]; +extern void spawnstate(dynent *d); + +//AC Bot class begin + +void CACBot::Spawn() +{ + // Init all bot variabeles + m_pMyEnt->eyeheight = 4.25f; + m_pMyEnt->aboveeye = 0.7f; + m_pMyEnt->radius = 1.1f; + m_pMyEnt->primary = m_pMyEnt->nextprimary = rnd(3)+GUN_PISTOL; + + spawnplayer(m_pMyEnt); + + m_pMyEnt->targetyaw = m_pMyEnt->yaw = m_pMyEnt->targetpitch = m_pMyEnt->pitch = 0.0f; + m_pMyEnt->primary = m_pMyEnt->nextprimary = GUN_ASSULT; + m_pMyEnt->move = 0; + m_pMyEnt->enemy = NULL; + m_pMyEnt->maxspeed = 16.0f; + m_pMyEnt->health = 100; + m_pMyEnt->armour = 50; + m_pMyEnt->pitch = 0; + m_pMyEnt->roll = 0; + m_pMyEnt->state = CS_ALIVE; + m_pMyEnt->anger = 0; + m_pMyEnt->pBot = this; +// loopi(NUMGUNS) m_pMyEnt->ammo[i] = m_pMyEnt->mag[i] = 0; + + /* UNDONE + m_pMyEnt->ammo[GUN_FIST] = 1; + if(m_noitems) + { + m_pMyEnt->gunselect = GUN_RIFLE; + m_pMyEnt->armour = 0; + if(m_noitemsrail) + { + m_pMyEnt->health = 1; + m_pMyEnt->ammo[GUN_RIFLE] = 100; + } + else + { + if(gamemode==12) // eihrul's secret "instafist" mode + { + m_pMyEnt->gunselect = GUN_FIST; + } + else + { + m_pMyEnt->health = 256; + if(m_tarena) + { + int gun1 = rnd(4)+1; + botbaseammo(m_pMyEnt->gunselect = gun1, m_pMyEnt); + for(;;) + { + int gun2 = rnd(4)+1; + if(gun1!=gun2) { botbaseammo(gun2, m_pMyEnt); break; }; + } + } + else if(m_arena) // insta arena + { + m_pMyEnt->ammo[GUN_RIFLE] = 100; + } + else // efficiency + { + loopi(4) botbaseammo(i+1, m_pMyEnt); + m_pMyEnt->gunselect = GUN_CG; + } + m_pMyEnt->ammo[GUN_CG] /= 2; + } + } + } + else + { + m_pMyEnt->ammo[GUN_SEMIPISTOL] = 5; + SelectGun(GUN_SG); + }*/ + + m_pMyEnt->ammo[GUN_KNIFE] = 1; + m_pMyEnt->ammo[GUN_PISTOL] = 5; + m_pMyEnt->mag[GUN_PISTOL] = 5; + SelectGun(m_pMyEnt->primary); + + m_eCurrentBotState = STATE_NORMAL; + m_iShootDelay = m_iChangeWeaponDelay = 0; + m_iCheckEnvDelay = 0; + m_vPrevOrigin = g_vecZero; + m_iStuckCheckDelay = lastmillis + 250; + m_bStuck = false; + m_iStuckTime = 0; + m_iStrafeTime = m_iStrafeCheckDelay = 0; + m_iMoveDir = DIR_NONE; + + m_pPrevEnemy = NULL; + m_iCombatNavTime = 0; + m_iSPMoveTime = 0; + m_iEnemySearchDelay = 0; + m_bCombatJump = false; + m_iCombatJumpDelay = 0; + m_bShootAtFeet = (RandomLong(1, 100) <= m_pBotSkill->sShootAtFeetWithRLPercent); + m_iHuntDelay = 0; + m_vHuntLocation = m_vPrevHuntLocation = g_vecZero; + m_pHuntTarget = NULL; + m_fPrevHuntDist = 0.0f; + m_iHuntLastTurnLessTime = m_iHuntPlayerUpdateTime = m_iHuntPauseTime = 0; + + m_iLastJumpPad = 0; + m_pTargetEnt = NULL; + m_iCheckTeleporterDelay = m_iCheckJumppadsDelay = 0; + m_iCheckEntsDelay = 0; + m_iCheckTriggersDelay = 0; + m_iLookForWaypointTime = 0; + + m_iAimDelay = 0; + m_fYawToTurn = m_fPitchToTurn = 0.0f; + + m_vGoal = m_vWaterGoal = g_vecZero; + + ResetWaypointVars(); +} + +void CACBot::BotPain(int damage, dynent *d) +{ + if(m_pMyEnt->state!=CS_ALIVE || editmode || intermission) return; +//fixmebot + int ad = damage/**(m_pMyEnt->armourtype+1)*20/100*/; // let armour absorb when possible + if(ad>m_pMyEnt->armour) ad = m_pMyEnt->armour; + m_pMyEnt->armour -= ad; + damage -= ad; + float droll = damage/0.5f; + m_pMyEnt->roll += m_pMyEnt->roll>0 ? droll : (m_pMyEnt->roll<0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending on amount of damage + if((m_pMyEnt->health -= damage)<=0) + { + if (player1 == d) + { + int frags; + if(isteam(player1->team, m_pMyEnt->team)) + { + frags = -1; + conoutf("you fragged a teammate (%s)", (int)m_pMyEnt->name); + } + else + { + frags = 1; + conoutf("you fragged %s", (int)m_pMyEnt->name); + } + addmsg(1, 2, SV_FRAGS, player1->frags += frags); + addmsg(1, 4, SV_BOTDIED, BotManager.GetBotIndex(m_pMyEnt), -1, false); + } + else if (d->monsterstate) + conoutf("%s got killed by %s", (int)m_pMyEnt->name, (int)d->name); + else + { + int KillerIndex = -1; + + if (d == m_pMyEnt) + { + conoutf("%s suicided", (int)d->name); + KillerIndex = BotManager.GetBotIndex(d); + addmsg(1, 3, SV_BOTFRAGS, KillerIndex, --d->frags); + } + else + { + if (d->bIsBot) + { + KillerIndex = BotManager.GetBotIndex(d); + addmsg(1, 3, SV_BOTFRAGS, KillerIndex, ++d->frags); + } + else + loopv(players){ if (players[i] == d) { KillerIndex = i; break;} } + + if(isteam(m_pMyEnt->team, d->team)) + { + conoutf("%s fragged his teammate (%s)", (int)d->name, + (int)m_pMyEnt->name); + } + else + { + conoutf("%s fragged %s", (int)d->name, (int)m_pMyEnt->name); + } + } + addmsg(1, 4, SV_BOTDIED, BotManager.GetBotIndex(m_pMyEnt), KillerIndex, + d->bIsBot); + } + + m_pMyEnt->lifesequence++; + m_pMyEnt->attacking = false; + m_pMyEnt->state = CS_DEAD; + m_pMyEnt->pitch = 0; + m_pMyEnt->roll = 60; + playsound(S_DIE1+rnd(2), &m_pMyEnt->o); + spawnstate(m_pMyEnt); + m_pMyEnt->lastaction = lastmillis; + } + else + { + playsound(S_PAIN6, &m_pMyEnt->o); + } +} + +void CACBot::CheckItemPickup() +{ + if(editmode) return; + loopv(ents) + { + entity &e = ents[i]; + if(e.type==NOTUSED) continue; + + if (!e.spawned) continue; + if ((i < sents.length()) && (!sents[i].spawned)) continue; + + if(OUTBORD(e.x, e.y)) continue; + vec v = { e.x, e.y, S(e.x, e.y)->floor+m_pMyEnt->eyeheight }; + vdist(dist, t, m_pMyEnt->o, v); + if(dist<2.5) + { + PickUp(i); + } + } +} + +void CACBot::PickUp(int n) +{ + int np = 1; + loopv(players) if(players[i]) np++; + loopv(bots) if(bots[i]) np++; + np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players + int ammo = np*2; + switch(ents[n].type) + { + case I_CLIPS: AddItem(n, m_pMyEnt->ammo[GUN_PISTOL], ammo); break; + case I_AMMO: AddItem(n, m_pMyEnt->ammo[m_pMyEnt->primary], ammo); break; + case I_GRENADE: AddItem(n, m_pMyEnt->ammo[GUN_GRENADE], ammo); break; + +/* case I_HELMET: + // (100h/100g only absorbs 166 damage) + if(m_pMyEnt->armourtype==A_YELLOW && m_pMyEnt->armour>66) break; + AddItem(n, m_pMyEnt->armour, 20); + break; + + case I_ARMOUR: + AddItem(n, m_pMyEnt->armour, 20); + break; + + case OBJ_ITEM: + AddItem(n, m_pMyEnt->quadmillis, 60); + break;*/ + +/* case TRIGGER: + ents[n].spawned = false; + triggertime = lastmillis; + trigger(ents[n].attr1, ents[n].attr2, false); // needs to go over server for multiplayer + + // HACK: Reset ent goal if bot was looking for this ent + if (m_pTargetEnt == &ents[n]) + { + m_pTargetEnt = NULL; + m_vGoal = g_vecZero; + ResetWaypointVars(); + m_iCheckEntsDelay = lastmillis + RandomLong(5000, 10000); + } + + BotManager.PickNextTrigger(); + break;*/ + }; +}; + +void CACBot::AddItem(int i, int &v, int spawnsec) +{ + if((i>=sents.length()) || (!sents[i].spawned)) + return; + +//fixmebot + if(v>=itemstats[ents[i].type-1].max) // don't pick up if not needed + return; + + itemstat &is = itemstats[ents[i].type-1]; + ents[i].spawned = false; + v += is.add; + if(v>is.max) v = is.max; + botplaysound(is.sound, m_pMyEnt); + sents[i].spawned = false; + sents[i].spawnsecs = spawnsec; + + addmsg(1, 3, SV_BOTITEMPICKUP, i, m_classicsp ? 100000 : spawnsec); + + // HACK: Reset ent goal if bot was looking for for this ent + if (m_pTargetEnt == &ents[i]) + { + m_pTargetEnt = NULL; + m_vGoal = g_vecZero; + ResetWaypointVars(); + m_iCheckEntsDelay = lastmillis + RandomLong(5000, 10000); + }; +} + +// AC Bot class end + +#endif diff --git a/source/src/bot/ac_bot.h b/source/src/bot/ac_bot.h index c83c66c5..502d5627 100644 --- a/source/src/bot/ac_bot.h +++ b/source/src/bot/ac_bot.h @@ -1,47 +1,47 @@ -// -// C++ Implementation: bot -// -// Description: -// -// Header specific for AC_CUBE -// -// Author: Rick -// -// -// - -#ifndef AC_BOT_H -#define AC_BOT_H - -#ifdef AC_CUBE - -#define MAX_WEAPONS 7 - -class CACBot: public CBot -{ -public: - friend class CBotManager; - friend class CWaypointClass; - - virtual void CheckItemPickup(void); - virtual void PickUp(int n); - virtual void AddItem(int i, int &v, int spawnsec); - - // AI Functions - virtual bool ChoosePreferredWeapon(void); - void Reload(int Gun); - virtual entity *SearchForEnts(bool bUseWPs, float flRange=9999.0f, - float flMaxHeight=JUMP_HEIGHT); - virtual bool HeadToTargetEnt(void); - virtual bool DoSPStuff(void); - - virtual void Spawn(void); - virtual void BotPain(int damage, dynent *d); -}; - -inline void AddScreenText(char *t, ...) {}; // UNDONE -inline void AddDebugText(char *t, ...) {}; // UNDONE - -#endif - -#endif +// +// C++ Implementation: bot +// +// Description: +// +// Header specific for AC_CUBE +// +// Author: Rick +// +// +// + +#ifndef AC_BOT_H +#define AC_BOT_H + +#ifdef AC_CUBE + +#define MAX_WEAPONS 7 + +class CACBot: public CBot +{ +public: + friend class CBotManager; + friend class CWaypointClass; + + virtual void CheckItemPickup(void); + virtual void PickUp(int n); + virtual void AddItem(int i, int &v, int spawnsec); + + // AI Functions + virtual bool ChoosePreferredWeapon(void); + void Reload(int Gun); + virtual entity *SearchForEnts(bool bUseWPs, float flRange=9999.0f, + float flMaxHeight=JUMP_HEIGHT); + virtual bool HeadToTargetEnt(void); + virtual bool DoSPStuff(void); + + virtual void Spawn(void); + virtual void BotPain(int damage, dynent *d); +}; + +inline void AddScreenText(char *t, ...) {}; // UNDONE +inline void AddDebugText(char *t, ...) {}; // UNDONE + +#endif + +#endif diff --git a/source/src/bot/ac_bot_ai.cpp b/source/src/bot/ac_bot_ai.cpp index e2520f79..745e45f6 100644 --- a/source/src/bot/ac_bot_ai.cpp +++ b/source/src/bot/ac_bot_ai.cpp @@ -1,334 +1,334 @@ -// -// C++ Implementation: ac_bot_ai -// -// Description: The AI part of the bot for ac is here(navigation, shooting etc) -// -// -// Author: -// - - - -#include "../cube.h" - -#ifdef AC_CUBE - -extern vector sents; -extern vector monsters; - -// UNDONE -weaponinfo_s WeaponInfoTable[MAX_WEAPONS] = -{ - // KNIFE - { TYPE_MELEE, 0.0f, 3.5f, 0.0f, 5.0f, 1 }, - // PISTOL - { TYPE_NORMAL, 5.0f, 12.0f, 0.0f, 50.0f, 6 }, - // SHOTGUN - { TYPE_SHOTGUN, 0.0f, 20.0f, 0.0f, 70.0f, 4 }, - // SUBGUN - { TYPE_AUTO, 12.0f, 25.0f, 0.0f, 100.0f, 15 }, - // SNIPER - { TYPE_SNIPER, 15.0f, 25.0f, 0.0f, 100.0f, 3 }, - // ASSAULT - { TYPE_AUTO, 12.0f, 25.0f, 0.0f, 100.0f, 10 }, - // GRENADE - { TYPE_GRENADE, 12.0f, 25.0f, 0.0f, 50.0f, 1 } -}; - -// Code of CACBot - Start - -bool CACBot::ChoosePreferredWeapon() -{ - printf("chose weapon\n"); - TMultiChoice WeaponChoices; - short sWeaponScore; - float flDist = GetDistance(m_pMyEnt->enemy->o); - - if ((m_iChangeWeaponDelay > lastmillis) && (m_pMyEnt->ammo[m_pMyEnt->gunselect])) - { - if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE) || (flDist <= 3.5f)) - return true; - } - - // Choose a weapon - for(int i=0;iammo[i] == 0) continue; - - sWeaponScore = 5; // Minimal score for a weapon - - if ((flDist >= WeaponInfoTable[i].flMinDesiredDistance) && - (flDist <= WeaponInfoTable[i].flMaxDesiredDistance)) - { - // In desired range for this weapon - sWeaponScore += 5; // Increase score much - } - else if ((flDist < WeaponInfoTable[i].flMinFireDistance) || - (flDist > WeaponInfoTable[i].flMaxFireDistance)) - continue; // Wrong distance for this weapon - - // The ideal distance would be between the Min and Max desired distance. - // Score on the difference of the avarage of the Min and Max desired distance. - float flAvarage = (WeaponInfoTable[i].flMinDesiredDistance + - WeaponInfoTable[i].flMaxDesiredDistance) / 2.0f; - float flIdealDiff = fabs(flDist - flAvarage); - - if (flIdealDiff < 0.5f) // Close to ideal distance - sWeaponScore += 4; - else if (flIdealDiff <= 1.0f) - sWeaponScore += 2; - - // Now rate the weapon on available ammo... - if (WeaponInfoTable[i].sMinDesiredAmmo > 0) - { - // Calculate how much percent of the min desired ammo the bot has - float flDesiredPercent = (float(m_pMyEnt->mag[i]) / - float(WeaponInfoTable[i].sMinDesiredAmmo)) * - 100.0f; - - if(flDesiredPercent >= 175.0f) - sWeaponScore += 4; - else if(flDesiredPercent >= 100.0f) - sWeaponScore += 3; - } - - WeaponChoices.Insert(i, sWeaponScore); - } - - int WeaponSelect; - if (WeaponChoices.GetSelection(WeaponSelect)) - { - m_iChangeWeaponDelay = lastmillis + RandomLong(2000, 8000); - m_bShootAtFeet = ((WeaponInfoTable[WeaponSelect].eWeaponType==TYPE_ROCKET) && - (RandomLong(1, 100) <= - m_pBotSkill->sShootAtFeetWithRLPercent)); - if(SelectGun(WeaponSelect)) - { - if(m_pMyEnt->mag[WeaponSelect]==0) reload(m_pMyEnt); // fixme, more intelligent reload - printf("gun selected %i\n", WeaponSelect); - return true; - } - } - - return false; - - -}; - -void CACBot::Reload(int Gun) -{ - -}; - -entity *CACBot::SearchForEnts(bool bUseWPs, float flRange, float flMaxHeight) -{ - /* Entities are scored on the following things: - - Visibility - - For ammo: Need(ie has this bot much of this type or not) - - distance - */ - - float flNearestDist = 9999, flDist; - entity *pNewTargetEnt = NULL; - waypoint_s *pWptNearBot = NULL, *pBestWpt = NULL; - short sScore, sHighestScore = 0; - - if ((WaypointClass.m_iWaypointCount >= 1) && bUseWPs) - pWptNearBot = GetNearestWaypoint(15.0f); - -#ifdef WP_FLOOD - if (!pWptNearBot && bUseWPs) - pWptNearBot = GetNearestFloodWP(5.0f); -#endif - - loopv(ents) - { - sScore = 0; - entity &e = ents[i]; - vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - - if (!ents[i].spawned) continue; - if (OUTBORD(e.x, e.y)) continue; - - bool bInteresting = false; - short sAmmo = 0, sMaxAmmo = 0; - //fixmebot - switch(e.type) - { - case I_CLIPS: - sMaxAmmo = itemstats[GUN_PISTOL-1].max; - bInteresting = (m_pMyEnt->ammo[GUN_PISTOL]ammo[GUN_PISTOL]; - break; - case I_AMMO: - sMaxAmmo = itemstats[m_pMyEnt->primary-1].max; - bInteresting = (m_pMyEnt->ammo[m_pMyEnt->primary]ammo[m_pMyEnt->primary]; - break; - case I_GRENADE: - sMaxAmmo = itemstats[GUN_GRENADE-1].max; - bInteresting = (m_pMyEnt->ammo[GUN_GRENADE]ammo[GUN_GRENADE]; - break; - }; - - if (!bInteresting) - continue; // Not an interesting item, skip - - // Score on ammo and need - if (sAmmo == -1) - { - // This entity doesn't have/need ammo - // Score on type instead - switch(e.type) - { // UNDONE - } - } - else - { - // Calculate current percentage of max ammo - float percent = ((float)sAmmo / (float)sMaxAmmo) * 100.0f; - if (percent > 100.0f) percent = 100.0f; - sScore += ((100 - short(percent))/2); - } - - flDist = GetDistance(o); - - if (flDist > flRange) continue; - - // Score on distance - float f = flDist; - if (f > 100.0f) f = 100.0f; - sScore += ((100 - short(f)) / 2); - - waypoint_s *pWptNearEnt = NULL; - // If this entity isn't visible check if there is a nearby waypoint - if (!IsReachable(o, flMaxHeight))//(!IsVisible(o)) - { - if (!pWptNearBot) continue; - -#ifdef WP_FLOOD - if (pWptNearBot->pNode->iFlags & W_FL_FLOOD) - pWptNearEnt = GetNearestFloodWP(o, 8.0f); - else -#endif - pWptNearEnt = GetNearestWaypoint(o, 15.0f); - - if (!pWptNearEnt) continue; - } - - // Score on visibility - if (pWptNearEnt == NULL) // Ent is visible - sScore += 30; - else - sScore += 15; - - if (sScore > sHighestScore) - { - // Found a valid wp near the bot and the ent,so...lets store it :) - if (pWptNearEnt) - pBestWpt = pWptNearEnt; - else - pBestWpt = NULL; // Best ent so far doesn't need any waypoints - - sHighestScore = sScore; - flNearestDist = flDist; - pNewTargetEnt = &ents[i]; - } - } - - if (pNewTargetEnt) - { - // Need waypoints to reach it? - if (pBestWpt) - { - ResetWaypointVars(); - SetCurrentWaypoint(pWptNearBot); - SetCurrentGoalWaypoint(pBestWpt); - } - - m_vGoal.x = pNewTargetEnt->x; - m_vGoal.y = pNewTargetEnt->y; - m_vGoal.z = S(pNewTargetEnt->x, pNewTargetEnt->y)->floor+player1->eyeheight; - } - - return pNewTargetEnt; -} - -bool CACBot::HeadToTargetEnt() -{ - if (m_pTargetEnt) - { - vec o = { m_pTargetEnt->x, m_pTargetEnt->y, - S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; - - if (m_pTargetEnt->spawned && (!UnderWater(m_pMyEnt->o) || - !UnderWater(o))) - { - bool bIsVisible = false; - if (m_pCurrentGoalWaypoint) - { - if ((GetDistance(o) <= 20.0f) && IsReachable(o, 1.0f)) - bIsVisible = true; - else if (HeadToGoal()) - { - //debugbeam(m_pMyEnt->o, m_pCurrentWaypoint->pNode->v_origin); - //debugbeam(m_pMyEnt->o, - // m_pCurrentGoalWaypoint->pNode->v_origin); - AddDebugText("Using WPs for ents"); - return true; - } - } - else - bIsVisible = IsVisible(o); - - if (bIsVisible) - { - if (m_pCurrentWaypoint || m_pCurrentGoalWaypoint) - { - condebug("ent is now visible"); - ResetWaypointVars(); - } - - float flHeightDiff = o.z - m_pMyEnt->o.z; - bool bToHigh = false; - if (Get2DDistance(o) <= 2.0f) - { - if (flHeightDiff >= 1.5f) - { - if (flHeightDiff <= JUMP_HEIGHT) - { -#ifndef RELEASE_BUILD - char sz[64]; - sprintf(sz, "Ent z diff: %f", o.z-m_pMyEnt->o.z); - condebug(sz); -#endif - // Jump if close to ent and the ent is high - m_pMyEnt->jumpnext = true; - } - else - bToHigh = true; - } - } - - if (!bToHigh) - { - AimToVec(o); - return true; - } - } - } - } - - return false; -} - -bool CACBot::DoSPStuff() -{ - return false; -} - -// Code of CACBot - End - -#endif +// +// C++ Implementation: ac_bot_ai +// +// Description: The AI part of the bot for ac is here(navigation, shooting etc) +// +// +// Author: +// + + + +#include "../cube.h" + +#ifdef AC_CUBE + +extern vector sents; +extern vector monsters; + +// UNDONE +weaponinfo_s WeaponInfoTable[MAX_WEAPONS] = +{ + // KNIFE + { TYPE_MELEE, 0.0f, 3.5f, 0.0f, 5.0f, 1 }, + // PISTOL + { TYPE_NORMAL, 5.0f, 12.0f, 0.0f, 50.0f, 6 }, + // SHOTGUN + { TYPE_SHOTGUN, 0.0f, 20.0f, 0.0f, 70.0f, 4 }, + // SUBGUN + { TYPE_AUTO, 12.0f, 25.0f, 0.0f, 100.0f, 15 }, + // SNIPER + { TYPE_SNIPER, 15.0f, 25.0f, 0.0f, 100.0f, 3 }, + // ASSAULT + { TYPE_AUTO, 12.0f, 25.0f, 0.0f, 100.0f, 10 }, + // GRENADE + { TYPE_GRENADE, 12.0f, 25.0f, 0.0f, 50.0f, 1 } +}; + +// Code of CACBot - Start + +bool CACBot::ChoosePreferredWeapon() +{ + printf("chose weapon\n"); + TMultiChoice WeaponChoices; + short sWeaponScore; + float flDist = GetDistance(m_pMyEnt->enemy->o); + + if ((m_iChangeWeaponDelay > lastmillis) && (m_pMyEnt->ammo[m_pMyEnt->gunselect])) + { + if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE) || (flDist <= 3.5f)) + return true; + } + + // Choose a weapon + for(int i=0;iammo[i] == 0) continue; + + sWeaponScore = 5; // Minimal score for a weapon + + if ((flDist >= WeaponInfoTable[i].flMinDesiredDistance) && + (flDist <= WeaponInfoTable[i].flMaxDesiredDistance)) + { + // In desired range for this weapon + sWeaponScore += 5; // Increase score much + } + else if ((flDist < WeaponInfoTable[i].flMinFireDistance) || + (flDist > WeaponInfoTable[i].flMaxFireDistance)) + continue; // Wrong distance for this weapon + + // The ideal distance would be between the Min and Max desired distance. + // Score on the difference of the avarage of the Min and Max desired distance. + float flAvarage = (WeaponInfoTable[i].flMinDesiredDistance + + WeaponInfoTable[i].flMaxDesiredDistance) / 2.0f; + float flIdealDiff = fabs(flDist - flAvarage); + + if (flIdealDiff < 0.5f) // Close to ideal distance + sWeaponScore += 4; + else if (flIdealDiff <= 1.0f) + sWeaponScore += 2; + + // Now rate the weapon on available ammo... + if (WeaponInfoTable[i].sMinDesiredAmmo > 0) + { + // Calculate how much percent of the min desired ammo the bot has + float flDesiredPercent = (float(m_pMyEnt->mag[i]) / + float(WeaponInfoTable[i].sMinDesiredAmmo)) * + 100.0f; + + if(flDesiredPercent >= 175.0f) + sWeaponScore += 4; + else if(flDesiredPercent >= 100.0f) + sWeaponScore += 3; + } + + WeaponChoices.Insert(i, sWeaponScore); + } + + int WeaponSelect; + if (WeaponChoices.GetSelection(WeaponSelect)) + { + m_iChangeWeaponDelay = lastmillis + RandomLong(2000, 8000); + m_bShootAtFeet = ((WeaponInfoTable[WeaponSelect].eWeaponType==TYPE_ROCKET) && + (RandomLong(1, 100) <= + m_pBotSkill->sShootAtFeetWithRLPercent)); + if(SelectGun(WeaponSelect)) + { + if(m_pMyEnt->mag[WeaponSelect]==0) reload(m_pMyEnt); // fixme, more intelligent reload + printf("gun selected %i\n", WeaponSelect); + return true; + } + } + + return false; + + +}; + +void CACBot::Reload(int Gun) +{ + +}; + +entity *CACBot::SearchForEnts(bool bUseWPs, float flRange, float flMaxHeight) +{ + /* Entities are scored on the following things: + - Visibility + - For ammo: Need(ie has this bot much of this type or not) + - distance + */ + + float flNearestDist = 9999, flDist; + entity *pNewTargetEnt = NULL; + waypoint_s *pWptNearBot = NULL, *pBestWpt = NULL; + short sScore, sHighestScore = 0; + + if ((WaypointClass.m_iWaypointCount >= 1) && bUseWPs) + pWptNearBot = GetNearestWaypoint(15.0f); + +#ifdef WP_FLOOD + if (!pWptNearBot && bUseWPs) + pWptNearBot = GetNearestFloodWP(5.0f); +#endif + + loopv(ents) + { + sScore = 0; + entity &e = ents[i]; + vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + + if (!ents[i].spawned) continue; + if (OUTBORD(e.x, e.y)) continue; + + bool bInteresting = false; + short sAmmo = 0, sMaxAmmo = 0; + //fixmebot + switch(e.type) + { + case I_CLIPS: + sMaxAmmo = itemstats[GUN_PISTOL-1].max; + bInteresting = (m_pMyEnt->ammo[GUN_PISTOL]ammo[GUN_PISTOL]; + break; + case I_AMMO: + sMaxAmmo = itemstats[m_pMyEnt->primary-1].max; + bInteresting = (m_pMyEnt->ammo[m_pMyEnt->primary]ammo[m_pMyEnt->primary]; + break; + case I_GRENADE: + sMaxAmmo = itemstats[GUN_GRENADE-1].max; + bInteresting = (m_pMyEnt->ammo[GUN_GRENADE]ammo[GUN_GRENADE]; + break; + }; + + if (!bInteresting) + continue; // Not an interesting item, skip + + // Score on ammo and need + if (sAmmo == -1) + { + // This entity doesn't have/need ammo + // Score on type instead + switch(e.type) + { // UNDONE + } + } + else + { + // Calculate current percentage of max ammo + float percent = ((float)sAmmo / (float)sMaxAmmo) * 100.0f; + if (percent > 100.0f) percent = 100.0f; + sScore += ((100 - short(percent))/2); + } + + flDist = GetDistance(o); + + if (flDist > flRange) continue; + + // Score on distance + float f = flDist; + if (f > 100.0f) f = 100.0f; + sScore += ((100 - short(f)) / 2); + + waypoint_s *pWptNearEnt = NULL; + // If this entity isn't visible check if there is a nearby waypoint + if (!IsReachable(o, flMaxHeight))//(!IsVisible(o)) + { + if (!pWptNearBot) continue; + +#ifdef WP_FLOOD + if (pWptNearBot->pNode->iFlags & W_FL_FLOOD) + pWptNearEnt = GetNearestFloodWP(o, 8.0f); + else +#endif + pWptNearEnt = GetNearestWaypoint(o, 15.0f); + + if (!pWptNearEnt) continue; + } + + // Score on visibility + if (pWptNearEnt == NULL) // Ent is visible + sScore += 30; + else + sScore += 15; + + if (sScore > sHighestScore) + { + // Found a valid wp near the bot and the ent,so...lets store it :) + if (pWptNearEnt) + pBestWpt = pWptNearEnt; + else + pBestWpt = NULL; // Best ent so far doesn't need any waypoints + + sHighestScore = sScore; + flNearestDist = flDist; + pNewTargetEnt = &ents[i]; + } + } + + if (pNewTargetEnt) + { + // Need waypoints to reach it? + if (pBestWpt) + { + ResetWaypointVars(); + SetCurrentWaypoint(pWptNearBot); + SetCurrentGoalWaypoint(pBestWpt); + } + + m_vGoal.x = pNewTargetEnt->x; + m_vGoal.y = pNewTargetEnt->y; + m_vGoal.z = S(pNewTargetEnt->x, pNewTargetEnt->y)->floor+player1->eyeheight; + } + + return pNewTargetEnt; +} + +bool CACBot::HeadToTargetEnt() +{ + if (m_pTargetEnt) + { + vec o = { m_pTargetEnt->x, m_pTargetEnt->y, + S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; + + if (m_pTargetEnt->spawned && (!UnderWater(m_pMyEnt->o) || + !UnderWater(o))) + { + bool bIsVisible = false; + if (m_pCurrentGoalWaypoint) + { + if ((GetDistance(o) <= 20.0f) && IsReachable(o, 1.0f)) + bIsVisible = true; + else if (HeadToGoal()) + { + //debugbeam(m_pMyEnt->o, m_pCurrentWaypoint->pNode->v_origin); + //debugbeam(m_pMyEnt->o, + // m_pCurrentGoalWaypoint->pNode->v_origin); + AddDebugText("Using WPs for ents"); + return true; + } + } + else + bIsVisible = IsVisible(o); + + if (bIsVisible) + { + if (m_pCurrentWaypoint || m_pCurrentGoalWaypoint) + { + condebug("ent is now visible"); + ResetWaypointVars(); + } + + float flHeightDiff = o.z - m_pMyEnt->o.z; + bool bToHigh = false; + if (Get2DDistance(o) <= 2.0f) + { + if (flHeightDiff >= 1.5f) + { + if (flHeightDiff <= JUMP_HEIGHT) + { +#ifndef RELEASE_BUILD + char sz[64]; + sprintf(sz, "Ent z diff: %f", o.z-m_pMyEnt->o.z); + condebug(sz); +#endif + // Jump if close to ent and the ent is high + m_pMyEnt->jumpnext = true; + } + else + bToHigh = true; + } + } + + if (!bToHigh) + { + AimToVec(o); + return true; + } + } + } + } + + return false; +} + +bool CACBot::DoSPStuff() +{ + return false; +} + +// Code of CACBot - End + +#endif diff --git a/source/src/bot/bot.cpp b/source/src/bot/bot.cpp index c1c00dbe..4cdfcd85 100644 --- a/source/src/bot/bot.cpp +++ b/source/src/bot/bot.cpp @@ -1,495 +1,495 @@ -// -// C++ Implementation: bot -// -// Description: -// -// Contains code of the base bot class -// -// Author: Rick -// -// -// - -#include "../cube.h" - -dvector bots; - -extern vector sents; -extern int triggertime; -extern itemstat itemstats[]; -extern ENetHost *clienthost; - -const vec g_vecZero = {0,0,0}; - -//Bot class begin - -CBot::~CBot() -{ - // Delete all waypoints - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - while (m_WaypointList[i][j].Empty() == false) - delete m_WaypointList[i][j].Pop(); - } - } -} - -void CBot::Spawn() -{ - // Init all bot variabeles - m_pMyEnt->eyeheight = 3.2f; - m_pMyEnt->aboveeye = 0.7f; - m_pMyEnt->radius = 1.1f; - - spawnplayer(m_pMyEnt); - - m_pMyEnt->targetyaw = m_pMyEnt->yaw = m_pMyEnt->targetpitch = m_pMyEnt->pitch = 0.0f; - m_pMyEnt->move = 0; - m_pMyEnt->enemy = NULL; - m_pMyEnt->maxspeed = 22.0f; - m_pMyEnt->health = 100; - m_pMyEnt->armour = 50; - m_pMyEnt->pitch = 0; - m_pMyEnt->roll = 0; - m_pMyEnt->state = CS_ALIVE; - m_pMyEnt->anger = 0; - m_pMyEnt->pBot = this; - - m_eCurrentBotState = STATE_NORMAL; - m_iShootDelay = m_iChangeWeaponDelay = 0; - m_iCheckEnvDelay = 0; - m_vPrevOrigin = g_vecZero; - m_iStuckCheckDelay = lastmillis + 250; - m_bStuck = false; - m_iStuckTime = 0; - m_iStrafeTime = m_iStrafeCheckDelay = 0; - m_iMoveDir = DIR_NONE; - - m_pPrevEnemy = NULL; - m_iCombatNavTime = 0; - m_iSPMoveTime = 0; - m_iEnemySearchDelay = 0; - m_iSawEnemyTime = 0; - m_bCombatJump = false; - m_iCombatJumpDelay = 0; - m_bShootAtFeet = (RandomLong(1, 100) <= m_pBotSkill->sShootAtFeetWithRLPercent); - m_iHuntDelay = 0; - m_vHuntLocation = m_vPrevHuntLocation = g_vecZero; - m_pHuntTarget = NULL; - m_fPrevHuntDist = 0.0f; - m_iHuntLastTurnLessTime = m_iHuntPlayerUpdateTime = m_iHuntPauseTime = 0; - m_iLookAroundDelay = m_iLookAroundTime = m_iLookAroundUpdateTime = 0; - m_fLookAroundYaw = 0.0f; - m_bLookLeft = false; - - m_iLastJumpPad = 0; - m_pTargetEnt = NULL; - while(!m_UnreachableEnts.Empty()) delete m_UnreachableEnts.Pop(); - m_iCheckTeleporterDelay = m_iCheckJumppadsDelay = 0; - m_iCheckEntsDelay = 0; - m_iCheckTriggersDelay = 0; - m_iLookForWaypointTime = 0; - - m_iAimDelay = 0; - m_fYawToTurn = m_fPitchToTurn = 0.0f; - - m_vGoal = m_vWaterGoal = g_vecZero; - - ResetWaypointVars(); -} - -void CBot::Think() -{ - if (intermission) - return; - - // Bot is dead? - if (m_pMyEnt->state == CS_DEAD) - { - if(lastmillis-m_pMyEnt->lastaction<1200) - { - m_pMyEnt->move = 0; - moveplayer(m_pMyEnt, 1, false); - } - else if (!m_arena) - Spawn(); - - SendBotInfo(); - return; - } - - CheckItemPickup(); - - TLinkedList::node_s *p = m_UnreachableEnts.GetFirst(), *tmp; - while(p) - { - if ((lastmillis - p->Entry->time) > 3500) - { - tmp = p; - p = p->next; - delete tmp->Entry; - m_UnreachableEnts.DeleteNode(tmp); - continue; - } - p = p->next; - } - - if (!BotManager.IdleBots()) - { - MainAI(); - } - else - { - ResetMoveSpeed(); - } - - // Aim to ideal yaw and pitch - AimToIdeal(); - - // Store current location, to see if the bot is stuck - m_vPrevOrigin = m_pMyEnt->o; - - // Don't check for stuck if the bot doesn't want to move - if (!m_pMyEnt->move && !m_pMyEnt->strafe) - m_iStuckCheckDelay = max(m_iStuckCheckDelay, (lastmillis+100)); - - // Move the bot - moveplayer(m_pMyEnt, 1, false); - - // Update bot info on all clients - SendBotInfo(); -} - -void CBot::AimToVec(const vec &o) -{ - m_pMyEnt->targetpitch = atan2(o.z-m_pMyEnt->o.z, GetDistance(o))*180/PI; - m_pMyEnt->targetyaw = -(float)atan2(o.x - m_pMyEnt->o.x, o.y - - m_pMyEnt->o.y)/PI*180+180; -} - -void CBot::AimToIdeal() -{ - if (m_iAimDelay > lastmillis) - return; - - float MaxXSpeed = RandomFloat(m_pBotSkill->flMinAimXSpeed, - m_pBotSkill->flMaxAimXSpeed); - float MaxYSpeed = RandomFloat(m_pBotSkill->flMinAimYSpeed, - m_pBotSkill->flMaxAimYSpeed); - float XOffset = RandomFloat(m_pBotSkill->flMinAimXOffset, - m_pBotSkill->flMaxAimXOffset); - float YOffset = RandomFloat(m_pBotSkill->flMinAimYOffset, - m_pBotSkill->flMaxAimYOffset); - float RealXOffset, RealYOffset; - float AimXSpeed = MaxXSpeed, AimYSpeed = MaxYSpeed; - float XDiff = fabs(m_pMyEnt->targetpitch - m_pMyEnt->pitch); - float YDiff = fabs(m_pMyEnt->targetyaw - m_pMyEnt->yaw); - - // How higher the diff, how higher the offsets and aim speed - - if (XOffset) - { - if (RandomLong(0, 1)) - RealXOffset = XDiff * (XOffset / 100.0f); - else - RealXOffset = -(XDiff * (XOffset / 100.0f)); - } - else - RealXOffset = 0.0f; - - if (YOffset) - { - if (RandomLong(0, 1)) - RealYOffset = YDiff * (YOffset / 100.0f); - else - RealYOffset = -(YDiff * (YOffset / 100.0f)); - } - else - RealYOffset = 0.0f; - - - if (XDiff >= 1.0f) - AimXSpeed = (AimXSpeed * (XDiff / 80.0f)) + (AimXSpeed * 0.25f); - else - AimXSpeed *= 0.01f; - - if (YDiff >= 1.0f) - AimYSpeed = (AimYSpeed * (YDiff / 70.0f)) + (AimYSpeed * 0.25f); - else - AimYSpeed *= 0.015f; - - m_fPitchToTurn = fabs((m_pMyEnt->targetpitch + RealXOffset) - m_pMyEnt->pitch); - m_fYawToTurn = fabs((m_pMyEnt->targetyaw + RealYOffset) - m_pMyEnt->yaw); - - float flIdealPitch = ChangeAngle(AimXSpeed, m_pMyEnt->targetpitch + RealXOffset, m_pMyEnt->pitch); - float flIdealYaw = ChangeAngle(AimYSpeed, m_pMyEnt->targetyaw + RealYOffset, m_pMyEnt->yaw); - -// m_pMyEnt->pitch = WrapXAngle(m_pMyEnt->targetpitch); // Uncomment for instant aiming -// m_pMyEnt->yaw = WrapYZAngle(m_pMyEnt->targetyaw); - - m_pMyEnt->pitch = WrapXAngle(flIdealPitch); - m_pMyEnt->yaw = WrapYZAngle(flIdealYaw); -} - -// Function code by Botman -float CBot::ChangeAngle(float speed, float ideal, float current) -{ - float current_180; // current +/- 180 degrees - float diff; - - // find the difference in the current and ideal angle - diff = fabs(current - ideal); - - // speed that we can turn during this frame... - speed = speed * (float(BotManager.m_iFrameTime)/1000.0f); - - // check if difference is less than the max degrees per turn - if (diff < speed) - speed = diff; // just need to turn a little bit (less than max) - - // check if the bot is already facing the idealpitch direction... - if (diff <= 1.0f) - return ideal; - - if ((current >= 180) && (ideal >= 180)) - { - if (current > ideal) - current -= speed; - else - current += speed; - } - else if ((current >= 180) && (ideal < 180)) - { - current_180 = current - 180; - - if (current_180 > ideal) - current += speed; - else - current -= speed; - } - else if ((current < 180) && (ideal >= 180)) - { - current_180 = current + 180; - - if (current_180 > ideal) - current += speed; - else - current -= speed; - } - else // (current < 180) && (ideal < 180) - { - if (current > ideal) - current -= speed; - else - current += speed; - } - - - return current; -} - -void CBot::SendBotInfo() -{ - if(lastmillis-m_iLastBotUpdate<40) return; // don't update faster than 25fps - m_iLastBotUpdate = lastmillis; - - int x = (int)(m_pMyEnt->o.x*DMF); - int y = (int)(m_pMyEnt->o.y*DMF); - int z = (int)(m_pMyEnt->o.z*DMF); - int yaw = (int)(m_pMyEnt->yaw*DAF); - int pitch = (int)(m_pMyEnt->pitch*DAF); - int roll = (int)(m_pMyEnt->roll*DAF); - int velx = (int)(m_pMyEnt->vel.x*DVF); - int vely = (int)(m_pMyEnt->vel.y*DVF); - int velz = (int)(m_pMyEnt->vel.z*DVF); - // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 - int moveflags = (m_pMyEnt->strafe&3) | ((m_pMyEnt->move&3)<<2) | - (((int)m_pMyEnt->onfloor)<<4) | - ((editmode ? CS_EDITING : - m_pMyEnt->state)<<5); - - ENetPacket *packet = enet_packet_create (NULL, MAXTRANS, 0); - uchar *start = packet->data; - uchar *p = start+2; - - putint(p, SV_BOTUPDATE); - putint(p, BotManager.GetBotIndex(m_pMyEnt)); - putint(p, x); // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes - putint(p, y); - putint(p, z); - putint(p, yaw); - putint(p, pitch); - putint(p, roll); - putint(p, velx); // quantize to 1/100, almost always 1 byte - putint(p, vely); - putint(p, velz); - // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 - putint(p, moveflags); - - if(!m_bSendC2SInit) // tell other clients who I am - { - packet->flags = ENET_PACKET_FLAG_RELIABLE; - m_bSendC2SInit = true; - putint(p, SV_ADDBOT); - putint(p, BotManager.GetBotIndex(m_pMyEnt)); - sendstring(m_pMyEnt->name, p); - sendstring(m_pMyEnt->team, p); - putint(p, m_pMyEnt->lifesequence); - }; - - - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - incomingdemodata(start, p-start, true); - if(clienthost) - { - enet_host_broadcast(clienthost, 0, packet); - enet_host_flush(clienthost); - } - else - localclienttoserver(packet); -} - -float CBot::GetDistance(const vec &o) -{ - vdist(flDist, vecto, m_pMyEnt->o, o); - return flDist; -} - -float CBot::GetDistance(const vec &v1, const vec &v2) -{ - vdist(flDist, vecto, v1, v2); - return flDist; -} - -float CBot::GetDistance(entity *e) -{ - vec v = { e->x, e->y, e->z }; - vdist(flDist, vecto, m_pMyEnt->o, v); - return flDist; -} - -bool CBot::SelectGun(int Gun) -{ - if (m_pMyEnt->ammo[Gun]) - { - //temporary doc out argh -/* if (m_pMyEnt->gunselect != Gun) botplaysound(S_WEAPLOAD, m_pMyEnt); */ - m_pMyEnt->gunselect = Gun; - return true; - } - return false; -} - -bool CBot::IsVisible(entity *e, bool CheckPlayers) -{ - vec v = { e->x, e->y, e->z }; - return ::IsVisible(m_pMyEnt->o, v, (CheckPlayers) ? m_pMyEnt : NULL); -} - -bool CBot::IsVisible(vec o, int Dir, float flDist, bool CheckPlayers, float *pEndDist) -{ - static vec angles, end, forward, right, up; - static traceresult_s tr; - - end = o; - angles = GetViewAngles(); - angles.x = 0; - - if (Dir & UP) - angles.x = WrapXAngle(angles.x + 45.0f); - else if (Dir & DOWN) - angles.x = WrapXAngle(angles.x - 45.0f); - - if ((Dir & FORWARD) || (Dir & BACKWARD)) - { - if (Dir & BACKWARD) - angles.y = WrapYZAngle(angles.y + 180.0f); - - if (Dir & LEFT) - { - if (Dir & FORWARD) - angles.y = WrapYZAngle(angles.y - 45.0f); - else - angles.y = WrapYZAngle(angles.y + 45.0f); - } - else if (Dir & RIGHT) - { - if (Dir & FORWARD) - angles.y = WrapYZAngle(angles.y + 45.0f); - else - angles.y = WrapYZAngle(angles.y - 45.0f); - } - } - else if (Dir & LEFT) - angles.y = WrapYZAngle(angles.y - 90.0f); - else if (Dir & RIGHT) - angles.y = WrapYZAngle(angles.y + 90.0f); - else if (Dir & UP) - angles.x = WrapXAngle(angles.x + 90.0f); - else if (Dir & DOWN) - angles.x = WrapXAngle(angles.x - 90.0f); - - AnglesToVectors(angles, forward, right, up); - - vmul(forward, flDist); - vadd(end, forward); - - TraceLine(o, end, m_pMyEnt, CheckPlayers, &tr); - - if (pEndDist) - *pEndDist = GetDistance(o, tr.end); - - return !tr.collided; -} - -void CBot::SetMoveDir(int iMoveDir, bool add) -{ - if (iMoveDir & FORWARD) - m_pMyEnt->move = 1; - else if (m_iMoveDir & BACKWARD) - m_pMyEnt->move = -1; - else if (!add) - m_pMyEnt->move = 0; - - if (iMoveDir & LEFT) - m_pMyEnt->strafe = 1; - else if (m_iMoveDir & RIGHT) - m_pMyEnt->strafe = -1; - else if (!add) - m_pMyEnt->strafe = 0; - - if (iMoveDir & UP) - m_pMyEnt->jumpnext = true; -} - -// Used when switching to another task/state -void CBot::ResetCurrentTask() -{ - switch (m_eCurrentBotState) - { - case STATE_ENEMY: - m_pMyEnt->enemy = NULL; - m_pTargetEnt = NULL; - m_iCombatNavTime = m_iMoveDir = 0; - m_bCombatJump = false; - m_vGoal = g_vecZero; - break; - case STATE_ENT: - m_pTargetEnt = NULL; - m_vGoal = g_vecZero; - break; - case STATE_SP: - m_iSPMoveTime = m_iMoveDir = 0; - m_pTargetEnt = NULL; - m_vGoal = g_vecZero; - break; - case STATE_NORMAL: - m_iStrafeTime = m_iMoveDir = 0; - break; - } -} - -// Bot class end +// +// C++ Implementation: bot +// +// Description: +// +// Contains code of the base bot class +// +// Author: Rick +// +// +// + +#include "../cube.h" + +dvector bots; + +extern vector sents; +extern int triggertime; +extern itemstat itemstats[]; +extern ENetHost *clienthost; + +const vec g_vecZero = {0,0,0}; + +//Bot class begin + +CBot::~CBot() +{ + // Delete all waypoints + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + while (m_WaypointList[i][j].Empty() == false) + delete m_WaypointList[i][j].Pop(); + } + } +} + +void CBot::Spawn() +{ + // Init all bot variabeles + m_pMyEnt->eyeheight = 3.2f; + m_pMyEnt->aboveeye = 0.7f; + m_pMyEnt->radius = 1.1f; + + spawnplayer(m_pMyEnt); + + m_pMyEnt->targetyaw = m_pMyEnt->yaw = m_pMyEnt->targetpitch = m_pMyEnt->pitch = 0.0f; + m_pMyEnt->move = 0; + m_pMyEnt->enemy = NULL; + m_pMyEnt->maxspeed = 22.0f; + m_pMyEnt->health = 100; + m_pMyEnt->armour = 50; + m_pMyEnt->pitch = 0; + m_pMyEnt->roll = 0; + m_pMyEnt->state = CS_ALIVE; + m_pMyEnt->anger = 0; + m_pMyEnt->pBot = this; + + m_eCurrentBotState = STATE_NORMAL; + m_iShootDelay = m_iChangeWeaponDelay = 0; + m_iCheckEnvDelay = 0; + m_vPrevOrigin = g_vecZero; + m_iStuckCheckDelay = lastmillis + 250; + m_bStuck = false; + m_iStuckTime = 0; + m_iStrafeTime = m_iStrafeCheckDelay = 0; + m_iMoveDir = DIR_NONE; + + m_pPrevEnemy = NULL; + m_iCombatNavTime = 0; + m_iSPMoveTime = 0; + m_iEnemySearchDelay = 0; + m_iSawEnemyTime = 0; + m_bCombatJump = false; + m_iCombatJumpDelay = 0; + m_bShootAtFeet = (RandomLong(1, 100) <= m_pBotSkill->sShootAtFeetWithRLPercent); + m_iHuntDelay = 0; + m_vHuntLocation = m_vPrevHuntLocation = g_vecZero; + m_pHuntTarget = NULL; + m_fPrevHuntDist = 0.0f; + m_iHuntLastTurnLessTime = m_iHuntPlayerUpdateTime = m_iHuntPauseTime = 0; + m_iLookAroundDelay = m_iLookAroundTime = m_iLookAroundUpdateTime = 0; + m_fLookAroundYaw = 0.0f; + m_bLookLeft = false; + + m_iLastJumpPad = 0; + m_pTargetEnt = NULL; + while(!m_UnreachableEnts.Empty()) delete m_UnreachableEnts.Pop(); + m_iCheckTeleporterDelay = m_iCheckJumppadsDelay = 0; + m_iCheckEntsDelay = 0; + m_iCheckTriggersDelay = 0; + m_iLookForWaypointTime = 0; + + m_iAimDelay = 0; + m_fYawToTurn = m_fPitchToTurn = 0.0f; + + m_vGoal = m_vWaterGoal = g_vecZero; + + ResetWaypointVars(); +} + +void CBot::Think() +{ + if (intermission) + return; + + // Bot is dead? + if (m_pMyEnt->state == CS_DEAD) + { + if(lastmillis-m_pMyEnt->lastaction<1200) + { + m_pMyEnt->move = 0; + moveplayer(m_pMyEnt, 1, false); + } + else if (!m_arena) + Spawn(); + + SendBotInfo(); + return; + } + + CheckItemPickup(); + + TLinkedList::node_s *p = m_UnreachableEnts.GetFirst(), *tmp; + while(p) + { + if ((lastmillis - p->Entry->time) > 3500) + { + tmp = p; + p = p->next; + delete tmp->Entry; + m_UnreachableEnts.DeleteNode(tmp); + continue; + } + p = p->next; + } + + if (!BotManager.IdleBots()) + { + MainAI(); + } + else + { + ResetMoveSpeed(); + } + + // Aim to ideal yaw and pitch + AimToIdeal(); + + // Store current location, to see if the bot is stuck + m_vPrevOrigin = m_pMyEnt->o; + + // Don't check for stuck if the bot doesn't want to move + if (!m_pMyEnt->move && !m_pMyEnt->strafe) + m_iStuckCheckDelay = max(m_iStuckCheckDelay, (lastmillis+100)); + + // Move the bot + moveplayer(m_pMyEnt, 1, false); + + // Update bot info on all clients + SendBotInfo(); +} + +void CBot::AimToVec(const vec &o) +{ + m_pMyEnt->targetpitch = atan2(o.z-m_pMyEnt->o.z, GetDistance(o))*180/PI; + m_pMyEnt->targetyaw = -(float)atan2(o.x - m_pMyEnt->o.x, o.y - + m_pMyEnt->o.y)/PI*180+180; +} + +void CBot::AimToIdeal() +{ + if (m_iAimDelay > lastmillis) + return; + + float MaxXSpeed = RandomFloat(m_pBotSkill->flMinAimXSpeed, + m_pBotSkill->flMaxAimXSpeed); + float MaxYSpeed = RandomFloat(m_pBotSkill->flMinAimYSpeed, + m_pBotSkill->flMaxAimYSpeed); + float XOffset = RandomFloat(m_pBotSkill->flMinAimXOffset, + m_pBotSkill->flMaxAimXOffset); + float YOffset = RandomFloat(m_pBotSkill->flMinAimYOffset, + m_pBotSkill->flMaxAimYOffset); + float RealXOffset, RealYOffset; + float AimXSpeed = MaxXSpeed, AimYSpeed = MaxYSpeed; + float XDiff = fabs(m_pMyEnt->targetpitch - m_pMyEnt->pitch); + float YDiff = fabs(m_pMyEnt->targetyaw - m_pMyEnt->yaw); + + // How higher the diff, how higher the offsets and aim speed + + if (XOffset) + { + if (RandomLong(0, 1)) + RealXOffset = XDiff * (XOffset / 100.0f); + else + RealXOffset = -(XDiff * (XOffset / 100.0f)); + } + else + RealXOffset = 0.0f; + + if (YOffset) + { + if (RandomLong(0, 1)) + RealYOffset = YDiff * (YOffset / 100.0f); + else + RealYOffset = -(YDiff * (YOffset / 100.0f)); + } + else + RealYOffset = 0.0f; + + + if (XDiff >= 1.0f) + AimXSpeed = (AimXSpeed * (XDiff / 80.0f)) + (AimXSpeed * 0.25f); + else + AimXSpeed *= 0.01f; + + if (YDiff >= 1.0f) + AimYSpeed = (AimYSpeed * (YDiff / 70.0f)) + (AimYSpeed * 0.25f); + else + AimYSpeed *= 0.015f; + + m_fPitchToTurn = fabs((m_pMyEnt->targetpitch + RealXOffset) - m_pMyEnt->pitch); + m_fYawToTurn = fabs((m_pMyEnt->targetyaw + RealYOffset) - m_pMyEnt->yaw); + + float flIdealPitch = ChangeAngle(AimXSpeed, m_pMyEnt->targetpitch + RealXOffset, m_pMyEnt->pitch); + float flIdealYaw = ChangeAngle(AimYSpeed, m_pMyEnt->targetyaw + RealYOffset, m_pMyEnt->yaw); + +// m_pMyEnt->pitch = WrapXAngle(m_pMyEnt->targetpitch); // Uncomment for instant aiming +// m_pMyEnt->yaw = WrapYZAngle(m_pMyEnt->targetyaw); + + m_pMyEnt->pitch = WrapXAngle(flIdealPitch); + m_pMyEnt->yaw = WrapYZAngle(flIdealYaw); +} + +// Function code by Botman +float CBot::ChangeAngle(float speed, float ideal, float current) +{ + float current_180; // current +/- 180 degrees + float diff; + + // find the difference in the current and ideal angle + diff = fabs(current - ideal); + + // speed that we can turn during this frame... + speed = speed * (float(BotManager.m_iFrameTime)/1000.0f); + + // check if difference is less than the max degrees per turn + if (diff < speed) + speed = diff; // just need to turn a little bit (less than max) + + // check if the bot is already facing the idealpitch direction... + if (diff <= 1.0f) + return ideal; + + if ((current >= 180) && (ideal >= 180)) + { + if (current > ideal) + current -= speed; + else + current += speed; + } + else if ((current >= 180) && (ideal < 180)) + { + current_180 = current - 180; + + if (current_180 > ideal) + current += speed; + else + current -= speed; + } + else if ((current < 180) && (ideal >= 180)) + { + current_180 = current + 180; + + if (current_180 > ideal) + current += speed; + else + current -= speed; + } + else // (current < 180) && (ideal < 180) + { + if (current > ideal) + current -= speed; + else + current += speed; + } + + + return current; +} + +void CBot::SendBotInfo() +{ + if(lastmillis-m_iLastBotUpdate<40) return; // don't update faster than 25fps + m_iLastBotUpdate = lastmillis; + + int x = (int)(m_pMyEnt->o.x*DMF); + int y = (int)(m_pMyEnt->o.y*DMF); + int z = (int)(m_pMyEnt->o.z*DMF); + int yaw = (int)(m_pMyEnt->yaw*DAF); + int pitch = (int)(m_pMyEnt->pitch*DAF); + int roll = (int)(m_pMyEnt->roll*DAF); + int velx = (int)(m_pMyEnt->vel.x*DVF); + int vely = (int)(m_pMyEnt->vel.y*DVF); + int velz = (int)(m_pMyEnt->vel.z*DVF); + // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 + int moveflags = (m_pMyEnt->strafe&3) | ((m_pMyEnt->move&3)<<2) | + (((int)m_pMyEnt->onfloor)<<4) | + ((editmode ? CS_EDITING : + m_pMyEnt->state)<<5); + + ENetPacket *packet = enet_packet_create (NULL, MAXTRANS, 0); + uchar *start = packet->data; + uchar *p = start+2; + + putint(p, SV_BOTUPDATE); + putint(p, BotManager.GetBotIndex(m_pMyEnt)); + putint(p, x); // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes + putint(p, y); + putint(p, z); + putint(p, yaw); + putint(p, pitch); + putint(p, roll); + putint(p, velx); // quantize to 1/100, almost always 1 byte + putint(p, vely); + putint(p, velz); + // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 + putint(p, moveflags); + + if(!m_bSendC2SInit) // tell other clients who I am + { + packet->flags = ENET_PACKET_FLAG_RELIABLE; + m_bSendC2SInit = true; + putint(p, SV_ADDBOT); + putint(p, BotManager.GetBotIndex(m_pMyEnt)); + sendstring(m_pMyEnt->name, p); + sendstring(m_pMyEnt->team, p); + putint(p, m_pMyEnt->lifesequence); + }; + + + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + incomingdemodata(start, p-start, true); + if(clienthost) + { + enet_host_broadcast(clienthost, 0, packet); + enet_host_flush(clienthost); + } + else + localclienttoserver(packet); +} + +float CBot::GetDistance(const vec &o) +{ + vdist(flDist, vecto, m_pMyEnt->o, o); + return flDist; +} + +float CBot::GetDistance(const vec &v1, const vec &v2) +{ + vdist(flDist, vecto, v1, v2); + return flDist; +} + +float CBot::GetDistance(entity *e) +{ + vec v = { e->x, e->y, e->z }; + vdist(flDist, vecto, m_pMyEnt->o, v); + return flDist; +} + +bool CBot::SelectGun(int Gun) +{ + if (m_pMyEnt->ammo[Gun]) + { + //temporary doc out argh +/* if (m_pMyEnt->gunselect != Gun) botplaysound(S_WEAPLOAD, m_pMyEnt); */ + m_pMyEnt->gunselect = Gun; + return true; + } + return false; +} + +bool CBot::IsVisible(entity *e, bool CheckPlayers) +{ + vec v = { e->x, e->y, e->z }; + return ::IsVisible(m_pMyEnt->o, v, (CheckPlayers) ? m_pMyEnt : NULL); +} + +bool CBot::IsVisible(vec o, int Dir, float flDist, bool CheckPlayers, float *pEndDist) +{ + static vec angles, end, forward, right, up; + static traceresult_s tr; + + end = o; + angles = GetViewAngles(); + angles.x = 0; + + if (Dir & UP) + angles.x = WrapXAngle(angles.x + 45.0f); + else if (Dir & DOWN) + angles.x = WrapXAngle(angles.x - 45.0f); + + if ((Dir & FORWARD) || (Dir & BACKWARD)) + { + if (Dir & BACKWARD) + angles.y = WrapYZAngle(angles.y + 180.0f); + + if (Dir & LEFT) + { + if (Dir & FORWARD) + angles.y = WrapYZAngle(angles.y - 45.0f); + else + angles.y = WrapYZAngle(angles.y + 45.0f); + } + else if (Dir & RIGHT) + { + if (Dir & FORWARD) + angles.y = WrapYZAngle(angles.y + 45.0f); + else + angles.y = WrapYZAngle(angles.y - 45.0f); + } + } + else if (Dir & LEFT) + angles.y = WrapYZAngle(angles.y - 90.0f); + else if (Dir & RIGHT) + angles.y = WrapYZAngle(angles.y + 90.0f); + else if (Dir & UP) + angles.x = WrapXAngle(angles.x + 90.0f); + else if (Dir & DOWN) + angles.x = WrapXAngle(angles.x - 90.0f); + + AnglesToVectors(angles, forward, right, up); + + vmul(forward, flDist); + vadd(end, forward); + + TraceLine(o, end, m_pMyEnt, CheckPlayers, &tr); + + if (pEndDist) + *pEndDist = GetDistance(o, tr.end); + + return !tr.collided; +} + +void CBot::SetMoveDir(int iMoveDir, bool add) +{ + if (iMoveDir & FORWARD) + m_pMyEnt->move = 1; + else if (m_iMoveDir & BACKWARD) + m_pMyEnt->move = -1; + else if (!add) + m_pMyEnt->move = 0; + + if (iMoveDir & LEFT) + m_pMyEnt->strafe = 1; + else if (m_iMoveDir & RIGHT) + m_pMyEnt->strafe = -1; + else if (!add) + m_pMyEnt->strafe = 0; + + if (iMoveDir & UP) + m_pMyEnt->jumpnext = true; +} + +// Used when switching to another task/state +void CBot::ResetCurrentTask() +{ + switch (m_eCurrentBotState) + { + case STATE_ENEMY: + m_pMyEnt->enemy = NULL; + m_pTargetEnt = NULL; + m_iCombatNavTime = m_iMoveDir = 0; + m_bCombatJump = false; + m_vGoal = g_vecZero; + break; + case STATE_ENT: + m_pTargetEnt = NULL; + m_vGoal = g_vecZero; + break; + case STATE_SP: + m_iSPMoveTime = m_iMoveDir = 0; + m_pTargetEnt = NULL; + m_vGoal = g_vecZero; + break; + case STATE_NORMAL: + m_iStrafeTime = m_iMoveDir = 0; + break; + } +} + +// Bot class end diff --git a/source/src/bot/bot.h b/source/src/bot/bot.h index d4ad1b1c..db4a3c16 100644 --- a/source/src/bot/bot.h +++ b/source/src/bot/bot.h @@ -1,430 +1,430 @@ -// -// C++ Interface: bot -// -// Description: -// -// Main bot header -// -// -// Author: Rick -// -// -// - - -/* - - TODO list - - -bots that "cover" each other in team matches ? - -If a bot gets ammo it should ignore that kind of ammo for the next 10 seconds. ? - -bots should ignore players that are too far away ? - -bots should dodge/jump when shot at - -If the bot's only weapon is the fist it should make looking for ammo its priority - Done ? - -bots should rocket jump to get quads and the 150 armor if normal jumping doesn't - work (only if health is x so they don't suicide). - -Make all the "UNDONE's" done :) - -Finish waypoint navigation - -Make bot personalities - -More advanced enemy scoring - -Bots should less bump into walls - -(Better) Coop support - -Check for reachable instead of visible in GetNearestWaypoint - -Waypoint experience file - -Bots should avoid team mates - -Handle failed goals better(ie remember them) - -Test multiplayer - -Fix bots getting stuck in each other - -Optimize hunting code - -Check/Fix msg code -*/ - -#ifndef BOT_H -#define BOT_H - - -//#define RELEASE_BUILD // Set when you want to make a release build - - -// Set for which mod the code is - -//#define VANILLA_CUBE -#define AC_CUBE -//#define CUBE_X - -#include "bot_util.h" -#include "bot_waypoint.h" - -#ifdef __linux__ -#include -#else // Windows doesn't have syslog -#define openlog (void)0 -#define syslog (void)0 -#define closelog (void)0 -#endif - -extern bool dedserv; -extern itemstat itemstats[]; - -#ifdef RELEASE_BUILD -inline void condebug(const char *s, int a = 0, int b = 0, int c = 0) {}; -inline void debugnav(const char *s, int a = 0, int b = 0, int c = 0) {}; -inline void debugbeam(vec &s, vec &e) { }; -#else -inline void condebug(const char *s, int a = 0, int b = 0, int c = 0) { /*conoutf(s, a, b, c);*/ }; -inline void debugnav(const char *s, int a = 0, int b = 0, int c = 0) { /*conoutf(s, a, b, c);*/ }; -inline void debugbeam(vec &s, vec &e) { /*if (!dedserv) particle_trail(1, 500, s, e);*/ }; -#endif - -#define JUMP_HEIGHT 4.0f // NOT accurate - -#define FORWARD (1<<1) -#define BACKWARD (1<<2) -#define LEFT (1<<3) -#define RIGHT (1<<4) -#define UP (1<<5) -#define DOWN (1<<6) -#define DIR_NONE 0 - -//fixmebot -#define m_sp 1000 -#define m_classicsp 1000 - -enum EBotCommands // For voting of bot commands -{ - COMMAND_ADDBOT=0, - COMMAND_KICKBOT, - COMMAND_BOTSKILL -}; - -struct bot_skill_s -{ - float flMinReactionDelay; // Minimal reaction time - float flMaxReactionDelay; // Maximal reaction time - float flMinAimXSpeed; // Minimal X aim speed in degrees(base value) - float flMaxAimXSpeed; // Maximal X aim speed in degrees(base value) - float flMinAimYSpeed; // Minimal Y aim speed in degrees(base value) - float flMaxAimYSpeed; // Maximal Y aim speed in degrees(base value) - float flMinAimXOffset; // Minimal X aim offset in degrees(base value) - float flMaxAimXOffset; // Maximal X aim offset in degrees(base value) - float flMinAimYOffset; // Minimal Y aim offset in degrees(base value) - float flMaxAimYOffset; // Maximal Y aim offset in degrees(base value) - float flMinAttackDelay; // Minimal delay for when a bot can attack again - float flMaxAttackDelay; // Maximal delay for when a bot can attack again - float flMinEnemySearchDelay; // Minimal delay for when a bot can search for an - // enemy again - float flMaxEnemySearchDelay; // Maximal delay for when a bot can search for an - // enemy again - short sShootAtFeetWithRLPercent; // Percent that a bot shoot with a rocket - // launcher at the enemy feet. - int iMaxHearVolume; // Max volume that bot can hear - bool bCanPredict; // Can this bot predict his enemy position? - bool bCircleStrafe; // Can this bot circle strafe? - bool bCanSearchItemsInCombat; -}; - -enum EBotWeaponTypes -{ - TYPE_MELEE, // Fist, knife etc - TYPE_NORMAL, // Normal weapon, distance doesn't really matter(ie pistol) - TYPE_SHOTGUN, - TYPE_ROCKET, - TYPE_SNIPER, // Rifle, sniper etc - TYPE_GRENADE, - TYPE_AUTO // Chain gun, machine gun etc -}; - -struct weaponinfo_s -{ - EBotWeaponTypes eWeaponType; - float flMinDesiredDistance; - float flMaxDesiredDistance; - float flMinFireDistance; - float flMaxFireDistance; - short sMinDesiredAmmo; -}; - -enum ECurrentBotState -{ - STATE_ENEMY, // Bot has an enemy - STATE_HUNT, // Bot is hunting an enemy - STATE_ENT, // Bot is heading to an entity - STATE_SP, // Bot is doing sp specific stuff - STATE_NORMAL // Bot is doing normal navigation -}; - -struct unreachable_ent_s -{ - entity *ent; - int time; - - unreachable_ent_s(entity *e, int t) : ent(e), time(t) { }; -}; - -class CBot -{ -public: - dynent *m_pMyEnt; - int m_iLastBotUpdate; - - // Combat variabels - dynent *m_pPrevEnemy; - int m_iShootDelay; - int m_iChangeWeaponDelay; - int m_iCombatNavTime; - int m_iEnemySearchDelay; - int m_iSawEnemyTime; - bool m_bCombatJump; - float m_iCombatJumpDelay; - bool m_bShootAtFeet; - - // Hunting variabeles - int m_iHuntDelay; - vec m_vHuntLocation, m_vPrevHuntLocation; - dynent *m_pHuntTarget; - float m_fPrevHuntDist; - int m_iHuntPauseTime, m_iHuntPlayerUpdateTime, m_iHuntLastTurnLessTime; - int m_iLookAroundDelay, m_iLookAroundTime, m_iLookAroundUpdateTime; - float m_fLookAroundYaw; - bool m_bLookLeft; - - // Navigation variabeles - int m_iCheckEnvDelay; - int m_iLastJumpPad; - vec m_vPrevOrigin; - float m_iStuckCheckDelay; - bool m_bStuck; - int m_iStuckTime; - int m_iStrafeTime; - int m_iStrafeCheckDelay; - int m_iMoveDir; - int m_iSPMoveTime; - bool m_bCalculatingAStarPath; - TLinkedList m_AStarNodeList; - TPriorList m_AStarOpenList[2]; - TLinkedList m_AStarClosedList[2]; - vec m_vWaterGoal; // Place to go to when underwater - - // Waypoint variabeles - TLinkedList m_WaypointList[MAX_MAP_GRIDS][MAX_MAP_GRIDS]; - float m_iWaypointTime; - waypoint_s *m_pCurrentWaypoint; - waypoint_s *m_pCurrentGoalWaypoint; - vec m_vGoal; - waypoint_s *m_pPrevWaypoints[5]; - int m_iRandomWaypointTime; - float m_fPrevWaypointDistance; - int m_iLookForWaypointTime; - int m_iWaypointHeadLastTurnLessTime; // Last time the didn't had to turn much wile - // heading to a WP - int m_iWaypointHeadPauseTime; // Pause time to give the bot time to aim. - bool m_bGoToDebugGoal; - - // Misc stuff - ECurrentBotState m_eCurrentBotState; - entity *m_pTargetEnt; - TLinkedList m_UnreachableEnts; - int m_iCheckTeleporterDelay; - int m_iCheckJumppadsDelay; - int m_iCheckEntsDelay; - int m_iCheckTriggersDelay; - int m_iAimDelay; - float m_fYawToTurn, m_fPitchToTurn; - bot_skill_s *m_pBotSkill; // Pointer to current bot skill - short m_sSkillNr; // Skill number, 0 == best 4 == worst - - void AimToVec(const vec &o); - void AimToIdeal(void); - float ChangeAngle(float speed, float ideal, float current); - bool SelectGun(int Gun); - virtual void CheckItemPickup(void) = 0; - virtual void PickUp(int n) = 0; - virtual void AddItem(int i, int &v, int spawnsec) = 0; - virtual void BotPain(int damage, dynent *d) = 0; - void SendBotInfo(void); - float GetDistance(const vec &o); - float GetDistance(const vec &v1, const vec &v2); - float GetDistance(entity *e); - float Get2DDistance(const vec &v) { return ::Get2DDistance(m_pMyEnt->o, v); }; - vec GetViewAngles(void) { vec temp = { m_pMyEnt->pitch, m_pMyEnt->yaw, - m_pMyEnt->roll }; return temp; }; - void ResetMoveSpeed(void) { m_pMyEnt->move = m_pMyEnt->strafe = 0; }; - void SetMoveDir(int iMoveDir, bool add); - void ResetCurrentTask(); - - // AI Functions - bool FindEnemy(void); - void ShootEnemy(void); - bool CheckHunt(void); - bool HuntEnemy(void); - void DoCombatNav(void); - void MainAI(void); - bool CheckStuck(void); - bool CheckJump(void); - bool CheckStrafe(void); - void CheckFOV(void); - bool IsVisible(const vec &o, bool CheckPlayers = false) { return ::IsVisible(m_pMyEnt->o, o, - (CheckPlayers) ? m_pMyEnt : - NULL); }; - bool IsVisible(dynent *d, bool CheckPlayers = false) { return ::IsVisible(m_pMyEnt->o, d->o, - (CheckPlayers) ? - m_pMyEnt : NULL); }; - bool IsVisible(entity *e, bool CheckPlayers = false); - bool IsVisible(vec o, int Dir, float flDist, bool CheckPlayers, float *pEndDist = NULL); - bool IsVisible(int Dir, float flDist, bool CheckPlayers) - { return IsVisible(m_pMyEnt->o, Dir, flDist, CheckPlayers); }; - bool IsInFOV(const vec &o); - bool IsInFOV(dynent *d) { return IsInFOV(d->o); }; - int GetShootDelay(void); - virtual bool ChoosePreferredWeapon(void); - virtual entity *SearchForEnts(bool bUseWPs, float flRange=9999.0f, - float flMaxHeight=JUMP_HEIGHT) = NULL; - virtual bool HeadToTargetEnt(void) = 0; - bool CheckItems(void); - bool InUnreachableList(entity *e); - virtual bool DoSPStuff(void) = 0; - vec GetEnemyPos(dynent *d); - bool AStar(void); - float AStarCost(waypoint_s *pWP1, waypoint_s *pWP2); - void CleanAStarLists(bool bPathFailed); - bool IsReachable(vec to, float flMaxHeight=JUMP_HEIGHT); - bool WaterNav(void); - void HearSound(int n, vec *o); - - // Waypoint functions - bool HeadToWaypoint(void); - bool FindWaypoint(void); - void ResetWaypointVars(void); - void SetCurrentWaypoint(node_s *pWP); - void SetCurrentWaypoint(waypoint_s *pWP); - void SetCurrentGoalWaypoint(node_s *pNode); - void SetCurrentGoalWaypoint(waypoint_s *pWP); - bool CurrentWPIsValid(void); - bool ReachedGoalWP(void); - bool HeadToGoal(void); - waypoint_s *GetWPFromNode(node_s *pNode); - waypoint_s *GetNearestWaypoint(vec v_src, float flRange); - waypoint_s *GetNearestWaypoint(float flRange) { return GetNearestWaypoint(m_pMyEnt->o, flRange); }; - waypoint_s *GetNearestTriggerWaypoint(vec v_src, float flRange); -#ifdef WP_FLOOD - waypoint_s *GetNearestFloodWP(vec v_origin, float flRange); - waypoint_s *GetNearestFloodWP(float flRange) { return GetNearestFloodWP(m_pMyEnt->o, flRange); }; - waypoint_s *GetNearestTriggerFloodWP(vec v_origin, float flRange); -#endif - void SyncWaypoints(void); - - friend class CBotManager; - friend class CWaypointClass; - - bool m_bSendC2SInit; - - virtual ~CBot(void); - - virtual void Spawn(void); - virtual void Think(void); - void GoToDebugGoal(vec o); -}; - -class CStoredBot; - -extern dvector bots; - -class CBotManager -{ - char m_szBotNames[150][16]; // Max 150 bot names with a length of 16 characters - short m_sBotNameCount; - char m_szBotTeams[20][5]; // Max 100 bot teams co a length of 5 characters - short m_sBotTeamCount; - bool m_bInit; - bool m_bBotsShoot; - bool m_bIdleBots; - bot_skill_s m_BotSkills[5]; // 5 different bot skills, 0 = best 4 = worst - int m_iFrameTime; - int m_iPrevTime; - short m_sBotSkill; // Bad - Worse - Medium - Good - Best - short m_sMaxAStarBots; // Max bots that can use a* at the same time - short m_sUsingAStarBotsCount; // Number of bots that are using a* - short m_sCurrentTriggerNr; // Current waypoint trigger bots should use - TLinkedList m_StoredBots; // List of bots that should be re-added after map change - float m_fReAddBotDelay; - - void LoadBotNamesFile(void); - char *GetBotName(void); - void LoadBotTeamsFile(void); - char *GetBotTeam(void); - void CreateSkillData(void); - void InitSkillData(void); - void InitBotItems(void); - - friend class CBot; - friend class CCubeBot; - friend class CACBot; - friend class CWaypointClass; - -public: - dynent *m_pBotToView; - - // Construction - CBotManager(void) { m_bInit = true; m_fReAddBotDelay = -1.0f; }; - - // Destruction - ~CBotManager(void); - - void Init(void); - void Think(void); - void RenderBots(void); - void RenderBotScore(void) { loopv(bots) if(bots[i]) renderscore(bots[i]); } - void AddBotTeamScore(void) { loopv(bots) addteamscore(bots[i]); }; - void RespawnBots(void) { loopv(bots) if (bots[i] && bots[i]->pBot) bots[i]->pBot->Spawn(); }; - void EndMap(void); - void BeginMap(char *szMapName); - int GetBotIndex(dynent *m); - void LetBotsUpdateStats(void); - void LetBotsHear(int n, vec *loc); - void AddWaypoint(node_s *pNode); - void DelWaypoint(node_s *pNode); - bool BotsShoot(void) { return m_bBotsShoot; }; - bool IdleBots(void) { return m_bIdleBots; }; - void SetBotsShoot(bool bShoot) { m_bBotsShoot = bShoot; }; - void SetIdleBots(bool bIdle) { m_bIdleBots = bIdle; }; - void EnableBotView(dynent *bot) { m_pBotToView = bot; }; - void DisableBotView(void); - void ChangeBotSkill(short Skill, dynent *bot = NULL); - dynent *CreateBot(const char *team, const char *skill, const char *name); - void ViewBot(void); - void CalculateMaxAStarCount(void); - void PickNextTrigger(void); - - void MakeBotFileName(const char *szFileName, const char *szDir1, const char *szDir2, - char *szOutput); -}; - -#if defined VANILLA_CUBE -#include "cube_bot.h" -#elif defined AC_CUBE -#include "ac_bot.h" -#endif - -extern CBotManager BotManager; -extern const vec g_vecZero; - -#if defined AC_CUBE -extern CACWaypointClass WaypointClass; -#elif defined VANILLA_CUBE -extern CCubeWaypointClass WaypointClass; -#endif - -class CStoredBot // Used to store bots after mapchange, so that they can be readded -{ - char m_szName[32]; - char m_szTeam[32]; - short m_sSkillNr; - -public: - - CStoredBot(const char *name, const char *team, const short skill) : m_sSkillNr(skill) - { strcpy(m_szName, name); strcpy(m_szTeam, team); }; - void ReAddBot(void) { BotManager.CreateBot(m_szTeam, SkillNrToSkillName(m_sSkillNr), m_szName); }; -}; - -#endif +// +// C++ Interface: bot +// +// Description: +// +// Main bot header +// +// +// Author: Rick +// +// +// + + +/* + + TODO list + + -bots that "cover" each other in team matches ? + -If a bot gets ammo it should ignore that kind of ammo for the next 10 seconds. ? + -bots should ignore players that are too far away ? + -bots should dodge/jump when shot at + -If the bot's only weapon is the fist it should make looking for ammo its priority - Done ? + -bots should rocket jump to get quads and the 150 armor if normal jumping doesn't + work (only if health is x so they don't suicide). + -Make all the "UNDONE's" done :) + -Finish waypoint navigation + -Make bot personalities + -More advanced enemy scoring + -Bots should less bump into walls + -(Better) Coop support + -Check for reachable instead of visible in GetNearestWaypoint + -Waypoint experience file + -Bots should avoid team mates + -Handle failed goals better(ie remember them) + -Test multiplayer + -Fix bots getting stuck in each other + -Optimize hunting code + -Check/Fix msg code +*/ + +#ifndef BOT_H +#define BOT_H + + +//#define RELEASE_BUILD // Set when you want to make a release build + + +// Set for which mod the code is + +//#define VANILLA_CUBE +#define AC_CUBE +//#define CUBE_X + +#include "bot_util.h" +#include "bot_waypoint.h" + +#ifdef __linux__ +#include +#else // Windows doesn't have syslog +#define openlog (void)0 +#define syslog (void)0 +#define closelog (void)0 +#endif + +extern bool dedserv; +extern itemstat itemstats[]; + +#ifdef RELEASE_BUILD +inline void condebug(const char *s, int a = 0, int b = 0, int c = 0) {}; +inline void debugnav(const char *s, int a = 0, int b = 0, int c = 0) {}; +inline void debugbeam(vec &s, vec &e) { }; +#else +inline void condebug(const char *s, int a = 0, int b = 0, int c = 0) { /*conoutf(s, a, b, c);*/ }; +inline void debugnav(const char *s, int a = 0, int b = 0, int c = 0) { /*conoutf(s, a, b, c);*/ }; +inline void debugbeam(vec &s, vec &e) { /*if (!dedserv) particle_trail(1, 500, s, e);*/ }; +#endif + +#define JUMP_HEIGHT 4.0f // NOT accurate + +#define FORWARD (1<<1) +#define BACKWARD (1<<2) +#define LEFT (1<<3) +#define RIGHT (1<<4) +#define UP (1<<5) +#define DOWN (1<<6) +#define DIR_NONE 0 + +//fixmebot +#define m_sp 1000 +#define m_classicsp 1000 + +enum EBotCommands // For voting of bot commands +{ + COMMAND_ADDBOT=0, + COMMAND_KICKBOT, + COMMAND_BOTSKILL +}; + +struct bot_skill_s +{ + float flMinReactionDelay; // Minimal reaction time + float flMaxReactionDelay; // Maximal reaction time + float flMinAimXSpeed; // Minimal X aim speed in degrees(base value) + float flMaxAimXSpeed; // Maximal X aim speed in degrees(base value) + float flMinAimYSpeed; // Minimal Y aim speed in degrees(base value) + float flMaxAimYSpeed; // Maximal Y aim speed in degrees(base value) + float flMinAimXOffset; // Minimal X aim offset in degrees(base value) + float flMaxAimXOffset; // Maximal X aim offset in degrees(base value) + float flMinAimYOffset; // Minimal Y aim offset in degrees(base value) + float flMaxAimYOffset; // Maximal Y aim offset in degrees(base value) + float flMinAttackDelay; // Minimal delay for when a bot can attack again + float flMaxAttackDelay; // Maximal delay for when a bot can attack again + float flMinEnemySearchDelay; // Minimal delay for when a bot can search for an + // enemy again + float flMaxEnemySearchDelay; // Maximal delay for when a bot can search for an + // enemy again + short sShootAtFeetWithRLPercent; // Percent that a bot shoot with a rocket + // launcher at the enemy feet. + int iMaxHearVolume; // Max volume that bot can hear + bool bCanPredict; // Can this bot predict his enemy position? + bool bCircleStrafe; // Can this bot circle strafe? + bool bCanSearchItemsInCombat; +}; + +enum EBotWeaponTypes +{ + TYPE_MELEE, // Fist, knife etc + TYPE_NORMAL, // Normal weapon, distance doesn't really matter(ie pistol) + TYPE_SHOTGUN, + TYPE_ROCKET, + TYPE_SNIPER, // Rifle, sniper etc + TYPE_GRENADE, + TYPE_AUTO // Chain gun, machine gun etc +}; + +struct weaponinfo_s +{ + EBotWeaponTypes eWeaponType; + float flMinDesiredDistance; + float flMaxDesiredDistance; + float flMinFireDistance; + float flMaxFireDistance; + short sMinDesiredAmmo; +}; + +enum ECurrentBotState +{ + STATE_ENEMY, // Bot has an enemy + STATE_HUNT, // Bot is hunting an enemy + STATE_ENT, // Bot is heading to an entity + STATE_SP, // Bot is doing sp specific stuff + STATE_NORMAL // Bot is doing normal navigation +}; + +struct unreachable_ent_s +{ + entity *ent; + int time; + + unreachable_ent_s(entity *e, int t) : ent(e), time(t) { }; +}; + +class CBot +{ +public: + dynent *m_pMyEnt; + int m_iLastBotUpdate; + + // Combat variabels + dynent *m_pPrevEnemy; + int m_iShootDelay; + int m_iChangeWeaponDelay; + int m_iCombatNavTime; + int m_iEnemySearchDelay; + int m_iSawEnemyTime; + bool m_bCombatJump; + float m_iCombatJumpDelay; + bool m_bShootAtFeet; + + // Hunting variabeles + int m_iHuntDelay; + vec m_vHuntLocation, m_vPrevHuntLocation; + dynent *m_pHuntTarget; + float m_fPrevHuntDist; + int m_iHuntPauseTime, m_iHuntPlayerUpdateTime, m_iHuntLastTurnLessTime; + int m_iLookAroundDelay, m_iLookAroundTime, m_iLookAroundUpdateTime; + float m_fLookAroundYaw; + bool m_bLookLeft; + + // Navigation variabeles + int m_iCheckEnvDelay; + int m_iLastJumpPad; + vec m_vPrevOrigin; + float m_iStuckCheckDelay; + bool m_bStuck; + int m_iStuckTime; + int m_iStrafeTime; + int m_iStrafeCheckDelay; + int m_iMoveDir; + int m_iSPMoveTime; + bool m_bCalculatingAStarPath; + TLinkedList m_AStarNodeList; + TPriorList m_AStarOpenList[2]; + TLinkedList m_AStarClosedList[2]; + vec m_vWaterGoal; // Place to go to when underwater + + // Waypoint variabeles + TLinkedList m_WaypointList[MAX_MAP_GRIDS][MAX_MAP_GRIDS]; + float m_iWaypointTime; + waypoint_s *m_pCurrentWaypoint; + waypoint_s *m_pCurrentGoalWaypoint; + vec m_vGoal; + waypoint_s *m_pPrevWaypoints[5]; + int m_iRandomWaypointTime; + float m_fPrevWaypointDistance; + int m_iLookForWaypointTime; + int m_iWaypointHeadLastTurnLessTime; // Last time the didn't had to turn much wile + // heading to a WP + int m_iWaypointHeadPauseTime; // Pause time to give the bot time to aim. + bool m_bGoToDebugGoal; + + // Misc stuff + ECurrentBotState m_eCurrentBotState; + entity *m_pTargetEnt; + TLinkedList m_UnreachableEnts; + int m_iCheckTeleporterDelay; + int m_iCheckJumppadsDelay; + int m_iCheckEntsDelay; + int m_iCheckTriggersDelay; + int m_iAimDelay; + float m_fYawToTurn, m_fPitchToTurn; + bot_skill_s *m_pBotSkill; // Pointer to current bot skill + short m_sSkillNr; // Skill number, 0 == best 4 == worst + + void AimToVec(const vec &o); + void AimToIdeal(void); + float ChangeAngle(float speed, float ideal, float current); + bool SelectGun(int Gun); + virtual void CheckItemPickup(void) = 0; + virtual void PickUp(int n) = 0; + virtual void AddItem(int i, int &v, int spawnsec) = 0; + virtual void BotPain(int damage, dynent *d) = 0; + void SendBotInfo(void); + float GetDistance(const vec &o); + float GetDistance(const vec &v1, const vec &v2); + float GetDistance(entity *e); + float Get2DDistance(const vec &v) { return ::Get2DDistance(m_pMyEnt->o, v); }; + vec GetViewAngles(void) { vec temp = { m_pMyEnt->pitch, m_pMyEnt->yaw, + m_pMyEnt->roll }; return temp; }; + void ResetMoveSpeed(void) { m_pMyEnt->move = m_pMyEnt->strafe = 0; }; + void SetMoveDir(int iMoveDir, bool add); + void ResetCurrentTask(); + + // AI Functions + bool FindEnemy(void); + void ShootEnemy(void); + bool CheckHunt(void); + bool HuntEnemy(void); + void DoCombatNav(void); + void MainAI(void); + bool CheckStuck(void); + bool CheckJump(void); + bool CheckStrafe(void); + void CheckFOV(void); + bool IsVisible(const vec &o, bool CheckPlayers = false) { return ::IsVisible(m_pMyEnt->o, o, + (CheckPlayers) ? m_pMyEnt : + NULL); }; + bool IsVisible(dynent *d, bool CheckPlayers = false) { return ::IsVisible(m_pMyEnt->o, d->o, + (CheckPlayers) ? + m_pMyEnt : NULL); }; + bool IsVisible(entity *e, bool CheckPlayers = false); + bool IsVisible(vec o, int Dir, float flDist, bool CheckPlayers, float *pEndDist = NULL); + bool IsVisible(int Dir, float flDist, bool CheckPlayers) + { return IsVisible(m_pMyEnt->o, Dir, flDist, CheckPlayers); }; + bool IsInFOV(const vec &o); + bool IsInFOV(dynent *d) { return IsInFOV(d->o); }; + int GetShootDelay(void); + virtual bool ChoosePreferredWeapon(void); + virtual entity *SearchForEnts(bool bUseWPs, float flRange=9999.0f, + float flMaxHeight=JUMP_HEIGHT) = NULL; + virtual bool HeadToTargetEnt(void) = 0; + bool CheckItems(void); + bool InUnreachableList(entity *e); + virtual bool DoSPStuff(void) = 0; + vec GetEnemyPos(dynent *d); + bool AStar(void); + float AStarCost(waypoint_s *pWP1, waypoint_s *pWP2); + void CleanAStarLists(bool bPathFailed); + bool IsReachable(vec to, float flMaxHeight=JUMP_HEIGHT); + bool WaterNav(void); + void HearSound(int n, vec *o); + + // Waypoint functions + bool HeadToWaypoint(void); + bool FindWaypoint(void); + void ResetWaypointVars(void); + void SetCurrentWaypoint(node_s *pWP); + void SetCurrentWaypoint(waypoint_s *pWP); + void SetCurrentGoalWaypoint(node_s *pNode); + void SetCurrentGoalWaypoint(waypoint_s *pWP); + bool CurrentWPIsValid(void); + bool ReachedGoalWP(void); + bool HeadToGoal(void); + waypoint_s *GetWPFromNode(node_s *pNode); + waypoint_s *GetNearestWaypoint(vec v_src, float flRange); + waypoint_s *GetNearestWaypoint(float flRange) { return GetNearestWaypoint(m_pMyEnt->o, flRange); }; + waypoint_s *GetNearestTriggerWaypoint(vec v_src, float flRange); +#ifdef WP_FLOOD + waypoint_s *GetNearestFloodWP(vec v_origin, float flRange); + waypoint_s *GetNearestFloodWP(float flRange) { return GetNearestFloodWP(m_pMyEnt->o, flRange); }; + waypoint_s *GetNearestTriggerFloodWP(vec v_origin, float flRange); +#endif + void SyncWaypoints(void); + + friend class CBotManager; + friend class CWaypointClass; + + bool m_bSendC2SInit; + + virtual ~CBot(void); + + virtual void Spawn(void); + virtual void Think(void); + void GoToDebugGoal(vec o); +}; + +class CStoredBot; + +extern dvector bots; + +class CBotManager +{ + char m_szBotNames[150][16]; // Max 150 bot names with a length of 16 characters + short m_sBotNameCount; + char m_szBotTeams[20][5]; // Max 100 bot teams co a length of 5 characters + short m_sBotTeamCount; + bool m_bInit; + bool m_bBotsShoot; + bool m_bIdleBots; + bot_skill_s m_BotSkills[5]; // 5 different bot skills, 0 = best 4 = worst + int m_iFrameTime; + int m_iPrevTime; + short m_sBotSkill; // Bad - Worse - Medium - Good - Best + short m_sMaxAStarBots; // Max bots that can use a* at the same time + short m_sUsingAStarBotsCount; // Number of bots that are using a* + short m_sCurrentTriggerNr; // Current waypoint trigger bots should use + TLinkedList m_StoredBots; // List of bots that should be re-added after map change + float m_fReAddBotDelay; + + void LoadBotNamesFile(void); + char *GetBotName(void); + void LoadBotTeamsFile(void); + char *GetBotTeam(void); + void CreateSkillData(void); + void InitSkillData(void); + void InitBotItems(void); + + friend class CBot; + friend class CCubeBot; + friend class CACBot; + friend class CWaypointClass; + +public: + dynent *m_pBotToView; + + // Construction + CBotManager(void) { m_bInit = true; m_fReAddBotDelay = -1.0f; }; + + // Destruction + ~CBotManager(void); + + void Init(void); + void Think(void); + void RenderBots(void); + void RenderBotScore(void) { loopv(bots) if(bots[i]) renderscore(bots[i]); } + void AddBotTeamScore(void) { loopv(bots) addteamscore(bots[i]); }; + void RespawnBots(void) { loopv(bots) if (bots[i] && bots[i]->pBot) bots[i]->pBot->Spawn(); }; + void EndMap(void); + void BeginMap(char *szMapName); + int GetBotIndex(dynent *m); + void LetBotsUpdateStats(void); + void LetBotsHear(int n, vec *loc); + void AddWaypoint(node_s *pNode); + void DelWaypoint(node_s *pNode); + bool BotsShoot(void) { return m_bBotsShoot; }; + bool IdleBots(void) { return m_bIdleBots; }; + void SetBotsShoot(bool bShoot) { m_bBotsShoot = bShoot; }; + void SetIdleBots(bool bIdle) { m_bIdleBots = bIdle; }; + void EnableBotView(dynent *bot) { m_pBotToView = bot; }; + void DisableBotView(void); + void ChangeBotSkill(short Skill, dynent *bot = NULL); + dynent *CreateBot(const char *team, const char *skill, const char *name); + void ViewBot(void); + void CalculateMaxAStarCount(void); + void PickNextTrigger(void); + + void MakeBotFileName(const char *szFileName, const char *szDir1, const char *szDir2, + char *szOutput); +}; + +#if defined VANILLA_CUBE +#include "cube_bot.h" +#elif defined AC_CUBE +#include "ac_bot.h" +#endif + +extern CBotManager BotManager; +extern const vec g_vecZero; + +#if defined AC_CUBE +extern CACWaypointClass WaypointClass; +#elif defined VANILLA_CUBE +extern CCubeWaypointClass WaypointClass; +#endif + +class CStoredBot // Used to store bots after mapchange, so that they can be readded +{ + char m_szName[32]; + char m_szTeam[32]; + short m_sSkillNr; + +public: + + CStoredBot(const char *name, const char *team, const short skill) : m_sSkillNr(skill) + { strcpy(m_szName, name); strcpy(m_szTeam, team); }; + void ReAddBot(void) { BotManager.CreateBot(m_szTeam, SkillNrToSkillName(m_sSkillNr), m_szName); }; +}; + +#endif diff --git a/source/src/bot/bot_ai.cpp b/source/src/bot/bot_ai.cpp index 551990a5..c3fadb31 100644 --- a/source/src/bot/bot_ai.cpp +++ b/source/src/bot/bot_ai.cpp @@ -1,2295 +1,2295 @@ -// -// C++ Implementation: bot_ai -// -// Description: The AI part comes here(navigation, shooting etc) -// -// -// Author: -// - - -// Code of CBot - Start - -#include "../cube.h" - -extern vector sents; -extern vector monsters; - -extern weaponinfo_s WeaponInfoTable[MAX_WEAPONS]; - -vec CBot::GetEnemyPos(dynent *d) -{ - // Aim offset idea by botman - vec o = d->o, offset; - float flDist = GetDistance(d->o), flScale; - - if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_ROCKET) - { - // Bot is using a rocket launcher, aim at enemy feet? - if (m_bShootAtFeet && !OUTBORD(d->o.x, d->o.y)) - { - // Only do this when enemy is fairly close to the ground - vec end = o; - end.z -= 900.0f; - traceresult_s tr; - TraceLine(o, end, NULL, false, &tr); - if ((o.z - tr.end.z) < 8.0f) - { - end = o; - end.z = tr.end.z; - if (IsVisible(end)) - { - // Target at ground - o.z = tr.end.z; - } - } - } - - if (m_pBotSkill->bCanPredict) - { - // How higher the skill, how further the bot predicts - vec org = o; - float flPredictTime = RandomFloat(1.25f, 1.7f) / (m_sSkillNr+1); - o = PredictPos(o, d->vel, flPredictTime); - } - } - else - { - if (m_pBotSkill->bCanPredict) - { - // How higher the skill, how 'more' the bot predicts - vec org = o; - float flPredictTime = RandomFloat(0.8f, 1.2f) / (m_sSkillNr+1); - o = PredictPos(o, d->vel, flPredictTime); - } - } - - if (flDist > 60.0f) - flScale = 1.0; - else if (flDist > 6.0f) - flScale = flDist / 60.0; - else - flScale = 0.1; - - switch (m_sSkillNr) - { - case 0: - // no offset - offset.x = 0; - offset.y = 0; - offset.z = 0; - break; - case 1: - // GOOD, offset a little for x, y, and z - offset.x = RandomFloat(-3, 3) * flScale; - offset.y = RandomFloat(-3, 3) * flScale; - offset.z = RandomFloat(-6, 6) * flScale; - break; - case 2: - // FAIR, offset somewhat for x, y, and z - offset.x = RandomFloat(-8, 8) * flScale; - offset.y = RandomFloat(-8, 8) * flScale; - offset.z = RandomFloat(-12, 12) * flScale; - break; - case 3: - // POOR, offset for x, y, and z - offset.x = RandomFloat(-15, 15) * flScale; - offset.y = RandomFloat(-15, 15) * flScale; - offset.z = RandomFloat(-25, 25) * flScale; - break; - case 4: - // BAD, offset lots for x, y, and z - offset.x = RandomFloat(-20, 20) * flScale; - offset.y = RandomFloat(-20, 20) * flScale; - offset.z = RandomFloat(-35, 35) * flScale; - break; - } - - vadd(o, offset); - return o; -} - -bool CBot::FindEnemy(void) -{ - // UNDONE: Enemies are now only scored on their distance - - if (m_pMyEnt->enemy) // Bot already has an enemy - { - // Check if the enemy is still in game - bool found = IsInGame(m_pMyEnt->enemy); - - // Check if the enemy is still ingame, still alive, not joined my team and is visible - if (found && !isteam(m_pMyEnt->team, m_pMyEnt->enemy->team)) - { - if ((m_pMyEnt->enemy->state == CS_ALIVE) && (IsVisible(m_pMyEnt->enemy))) - return true; - else - m_pPrevEnemy = m_pMyEnt->enemy; - } - else - m_pMyEnt->enemy = NULL; - } - - if (m_iEnemySearchDelay > lastmillis) return (m_pMyEnt->enemy!=NULL); - - m_pMyEnt->enemy = NULL; - - // Add enemy searchy delay - float MinDelay = m_pBotSkill->flMinEnemySearchDelay; - float MaxDelay = m_pBotSkill->flMaxEnemySearchDelay; - m_iEnemySearchDelay = lastmillis + int(RandomFloat(MinDelay, MaxDelay) * 1000.0f); - - dynent *pNewEnemy = NULL, *d = NULL; - float flDist, flNearestDist = 99999.9f; - short EnemyVal, BestEnemyVal = -100; - - // search for only for monsters when SP mode - /* no more sp as such -argh (sorry bout messy commenting) - if (m_sp) - { - loopv(monsters) - { - d = monsters[i]; // Handy shortcut - - if (d->state != CS_ALIVE) - continue; - - // Check if the enemy is visible - if(!IsInFOV(d) || !IsVisible(d)) - continue; - - flDist = GetDistance(d->o); - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (EnemyVal > BestEnemyVal) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - } - } - } */ - - //else - //{ - - // First loop through all players - loopv(players) - { - d = players[i]; // Handy shortcut - - if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) - continue; - - // Check if the enemy is visible - if(!IsInFOV(d) || !IsVisible(d)) - continue; - - flDist = GetDistance(d->o); - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (EnemyVal > BestEnemyVal) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - } - } - - // Then loop through all bots - loopv(bots) - { - d = bots[i]; // Handy shortcut - - if (d == m_pMyEnt) - continue; - - if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) - continue; - - // Check if the enemy is visible - if(!IsInFOV(d) || !IsVisible(d)) - continue; - - flDist = GetDistance(d->o); - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (EnemyVal > BestEnemyVal) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - } - } - - // Then examine the local player - if (player1 && !isteam(player1->team, m_pMyEnt->team) && - (player1->state == CS_ALIVE)) - { - // Check if the enemy is visible - if(IsInFOV(player1) && IsVisible(player1)) - { - flDist = GetDistance(player1->o); - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (EnemyVal > BestEnemyVal) - { - pNewEnemy = player1; - BestEnemyVal = EnemyVal; - } - } - } - //} - - if (pNewEnemy) - { - if (!m_pMyEnt->enemy) // Add shoot delay if new enemy is found - { - float flMinShootDelay = m_pBotSkill->flMinAttackDelay; - float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; - - m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay, - flMaxShootDelay) * 1000.0f); - } - - if ((m_pMyEnt->enemy != pNewEnemy) && m_pMyEnt->enemy) - m_pPrevEnemy = m_pMyEnt->enemy; - - m_pMyEnt->enemy = pNewEnemy; - return true; - } - - return false; -} - -bool CBot::CheckHunt(void) -{ - if (!BotManager.BotsShoot()) return false; - - if (m_pHuntTarget) // Bot already has an enemy to hunt - { - // Check if the enemy is still in game - bool found = IsInGame(m_pHuntTarget); - - // Check if the enemy is still ingame, still alive, not joined my team and is visible - if (found && !isteam(m_pMyEnt->team, m_pHuntTarget->team)) - { - if ((m_pHuntTarget->state == CS_ALIVE) && IsReachable(m_vHuntLocation)) - return true; - } - else - m_pHuntTarget = NULL; - } - - if (m_iHuntDelay > lastmillis) return (m_pHuntTarget!=NULL); - - if (!vis(m_vHuntLocation, g_vecZero)) - m_vPrevHuntLocation = m_vHuntLocation; - - m_pHuntTarget = NULL; - m_vHuntLocation = g_vecZero; - - // Add enemy hunt search delay - m_iHuntDelay = lastmillis + 1500; - - dynent *pNewEnemy = NULL, *d = NULL; - float flDist, flNearestDist = 99999.9f, flNearestOldPosDistToEnemy = 99999.9f; - float flNearestOldPosDistToBot = 99999.9f; - short EnemyVal, BestEnemyVal = -100; - vec BestOldPos; - - // search for only for monsters when SP mode - //edit -argh - /*if (m_sp) - { - loopv(monsters) - { - d = monsters[i]; // Handy shortcut - - if (d->state != CS_ALIVE) - continue; - - flDist = GetDistance(d->o); - - if (flDist > 250.0f) continue; - - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (d == m_pPrevEnemy) - EnemyVal+=2; - - if (EnemyVal < BestEnemyVal) continue; - - vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; - flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; - - // Check previous locations of enemy - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = d->PrevLocations.prevloc[j]; - - flDist = GetDistance(d->o, v); - - if ((flDist < flNearestOldPosDistToBot) && IsReachable(v)) - { - flNearestOldPosDistToEnemy = flDist; - bestfromenemy = v; - } - } - - // Check previous locations of bot hisself - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = m_pMyEnt->PrevLocations.prevloc[j]; - - flDist = GetDistance(v); - - if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && - IsReachable(v)) - { - flNearestOldPosDistToBot = flDist; - bestfrombot = v; - } - } - - if (!vis(bestfromenemy, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfromenemy; - } - else if (!vis(bestfrombot, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfrombot; - } - } - } */ - //else - //{ - // First loop through all players - loopv(players) - { - d = players[i]; // Handy shortcut - - if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) - continue; - - flDist = GetDistance(d->o); - - if (flDist > 250.0f) continue; - - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (d == m_pPrevEnemy) - EnemyVal+=2; - - if (EnemyVal < BestEnemyVal) continue; - - vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; - flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; - - // Check previous locations of enemy - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = d->PrevLocations.prevloc[j]; - - flDist = GetDistance(d->o, v); - - if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) - { - flNearestOldPosDistToEnemy = flDist; - bestfromenemy = v; - } - } - - // Check previous locations of bot hisself - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = m_pMyEnt->PrevLocations.prevloc[j]; - - flDist = GetDistance(v); - - if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && - IsReachable(v)) - { - flNearestOldPosDistToBot = flDist; - bestfrombot = v; - } - } - - if (!vis(bestfromenemy, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfromenemy; - } - else if (!vis(bestfrombot, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfrombot; - } - //} - - // Then loop through all bots - loopv(bots) - { - d = bots[i]; // Handy shortcut - - if (d == m_pMyEnt) - continue; - - if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) - continue; - - flDist = GetDistance(d->o); - - if (flDist > 250.0f) continue; - - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (d == m_pPrevEnemy) - EnemyVal+=2; - - if (EnemyVal < BestEnemyVal) continue; - - vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; - flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; - - // Check previous locations of enemy - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = d->PrevLocations.prevloc[j]; - - flDist = GetDistance(d->o, v); - - if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) - { - flNearestOldPosDistToEnemy = flDist; - bestfromenemy = v; - } - } - - // Check previous locations of bot hisself - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = m_pMyEnt->PrevLocations.prevloc[j]; - - flDist = GetDistance(v); - - if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && - IsReachable(v)) - { - flNearestOldPosDistToBot = flDist; - bestfrombot = v; - } - } - - if (!vis(bestfromenemy, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfromenemy; - } - else if (!vis(bestfrombot, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfrombot; - } - } - - // Then examine the local player - if (player1 && !isteam(player1->team, m_pMyEnt->team) && - (player1->state == CS_ALIVE) && ((flDist = GetDistance(player1->o)) <= 250.0f)) - { - d = player1; - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (d == m_pPrevEnemy) - EnemyVal+=2; - - if (EnemyVal >= BestEnemyVal) - { - BestEnemyVal = EnemyVal; - vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; - flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; - - // Check previous locations of enemy - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = d->PrevLocations.prevloc[j]; - - flDist = GetDistance(d->o, v); - - if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) - { - flNearestOldPosDistToEnemy = flDist; - bestfromenemy = v; - } - } - - // Check previous locations of bot hisself - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = m_pMyEnt->PrevLocations.prevloc[j]; - - flDist = GetDistance(v); - - if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && - IsReachable(v)) - { - flNearestOldPosDistToBot = flDist; - bestfrombot = v; - } - } - - if (!vis(bestfromenemy, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfromenemy; - } - else if (!vis(bestfrombot, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfrombot; - } - } - } - } - - if (pNewEnemy) - { - if (!m_pHuntTarget) // Add shoot delay if new enemy is found - { - float flMinShootDelay = m_pBotSkill->flMinAttackDelay; - float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; - - m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay, - flMaxShootDelay) * 1000.0f); - } - - if (!vis(m_vHuntLocation, g_vecZero)) - m_vPrevHuntLocation = m_vHuntLocation; - - m_pHuntTarget = pNewEnemy; - m_vHuntLocation = BestOldPos; - m_fPrevHuntDist = 0.0f; - return true; - } - - return false; -} - -bool CBot::HuntEnemy(void) -{ - if (m_iCombatNavTime > lastmillis) - { - SetMoveDir(m_iMoveDir, false); - return true; - } - - m_iCombatNavTime = m_iMoveDir = 0; - - bool bDone = false, bNew = false; - float flDist = GetDistance(m_vHuntLocation); - - if (flDist <= 3.0f) - bDone = true; - - if ((m_fPrevHuntDist > 0.0) && (flDist > m_fPrevHuntDist)) - bDone = true; - - m_fPrevHuntDist = flDist; - - if ((m_iHuntPlayerUpdateTime < lastmillis) || bDone) - { - m_iHuntPlayerUpdateTime = lastmillis + 1250; - - short BestPosIndexFromEnemy = -1, BestPosIndexFromBot = -1, j; - float NearestDistToEnemy = 9999.9f, NearestDistToBot = 9999.9f; - - // Check previous locations of enemy - for (j=0;jPrevLocations.prevloc[j]; - - if (bDone && vis(m_vHuntLocation, OldPos)) - continue; - - if (vis(OldPos, g_vecZero)) - continue; - - if (GetDistance(OldPos) > 250.0f) - continue; - - flDist = GetDistance(m_pHuntTarget->o, OldPos); - - if ((flDist < NearestDistToEnemy) && (IsReachable(OldPos))) - { - NearestDistToEnemy = flDist; - BestPosIndexFromEnemy = j; - break; - } - } - - // Check previous locations of bot - for (j=0;jPrevLocations.prevloc[j]; - - if (bDone && vis(m_vHuntLocation, OldPos)) - continue; - - if (vis(OldPos, g_vecZero)) - continue; - - if (GetDistance(OldPos) > 25.0f) - continue; - - flDist = GetDistance(OldPos); - - if ((flDist < NearestDistToBot) && ::IsVisible(m_pHuntTarget->o, OldPos) && - (IsReachable(OldPos))) - { - NearestDistToBot = flDist; - BestPosIndexFromBot = j; - break; - } - } - - if (BestPosIndexFromEnemy > -1) - { - m_vPrevHuntLocation = m_vHuntLocation; - m_vHuntLocation = m_pHuntTarget->PrevLocations.prevloc[BestPosIndexFromEnemy]; - bNew = true; - m_fPrevHuntDist = 0.0f; - } - else if (BestPosIndexFromBot > -1) - { - m_vPrevHuntLocation = m_vHuntLocation; - m_vHuntLocation = m_pMyEnt->PrevLocations.prevloc[BestPosIndexFromBot]; - bNew = true; - m_fPrevHuntDist = 0.0f; - } - } - - if (!bNew) // Check if current location is still reachable - { - if (bDone || !IsReachable(m_vHuntLocation)) - { - m_pHuntTarget = NULL; - m_vPrevHuntLocation = m_vHuntLocation; - m_vHuntLocation = g_vecZero; - m_fPrevHuntDist = 0.0f; - m_iHuntDelay = lastmillis + 3500; - return false; - } - } - else - condebug("New hunt pos"); - - // Aim to position - //AimToVec(m_vHuntLocation); - - int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, m_vHuntLocation); - - if (iMoveDir != DIR_NONE) - { - m_iMoveDir = iMoveDir; - m_iCombatNavTime = lastmillis + 125; - } - - bool aimtopos = true; - - if ((lastmillis - m_iSawEnemyTime) > 1500) - { - if (m_iLookAroundDelay < lastmillis) - { - if (m_iLookAroundTime > lastmillis) - { - if (m_iLookAroundUpdateTime < lastmillis) - { - float flAddAngle; - if (m_bLookLeft) flAddAngle = RandomFloat(-110, -80); - else flAddAngle = RandomFloat(80, 110); - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->targetyaw + flAddAngle); - m_iLookAroundUpdateTime = lastmillis + RandomLong(400, 800); - } - aimtopos = false; - } - else if (m_iLookAroundTime > 0) - { - m_iLookAroundTime = 0; - m_iLookAroundDelay = lastmillis + RandomLong(750, 1000); - } - else - m_iLookAroundTime = lastmillis + RandomLong(2200, 3200); - } - } - - if (aimtopos) - AimToVec(m_vHuntLocation); - - debugbeam(m_pMyEnt->o, m_vHuntLocation); - - if (m_fYawToTurn <= 25.0f) - m_iHuntLastTurnLessTime = lastmillis; - - // Bot had to turn much for a while? - if ((m_iHuntLastTurnLessTime > 0) && (m_iHuntLastTurnLessTime < (lastmillis - 1000))) - { - m_iHuntPauseTime = lastmillis + 200; - } - - if (m_iHuntPauseTime >= lastmillis) - { - m_pMyEnt->move = 0; - m_fPrevHuntDist = 0.0f; - } - else - { - // Check if bot has to jump over a wall... - if (CheckJump()) - m_pMyEnt->jumpnext = true; - else // Check if bot has to jump to reach this location - { - float flHeightDiff = m_vHuntLocation.z - m_pMyEnt->o.z; - bool bToHigh = false; - if (Get2DDistance(m_vHuntLocation) <= 2.0f) - { - if (flHeightDiff >= 1.5f) - { - if (flHeightDiff <= JUMP_HEIGHT) - { -#ifndef RELEASE_BUILD - char sz[64]; - sprintf(sz, "OldPos z diff: %f", m_vHuntLocation.z-m_pMyEnt->o.z); - condebug(sz); -#endif - // Jump if close to pos and the pos is high - m_pMyEnt->jumpnext = true; - } - else - bToHigh = true; - } - } - - if (bToHigh) - { - m_pHuntTarget = NULL; - m_vPrevHuntLocation = m_vHuntLocation; - m_vHuntLocation = g_vecZero; - m_fPrevHuntDist = 0.0f; - m_iHuntDelay = lastmillis + 3500; - return false; - } - } - } - - return true; -} - -void CBot::ShootEnemy() -{ - if (!m_pMyEnt->enemy) - return; - - m_iSawEnemyTime = lastmillis; - - // Aim to enemy - vec enemypos = GetEnemyPos(m_pMyEnt->enemy); - AimToVec(enemypos); - - // Time to shoot? - if (m_iShootDelay < lastmillis) - //if ((lastmillis-m_pMyEnt->lastaction) >= m_pMyEnt->gunwait) - { - if (m_pMyEnt->ammo[m_pMyEnt->gunselect]) - { - // If the bot is using a sniper only shoot if crosshair is near the enemy - if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_SNIPER) - { - float yawtoturn = fabs(WrapYZAngle(m_pMyEnt->yaw - m_pMyEnt->targetyaw)); - float pitchtoturn = fabs(WrapYZAngle(m_pMyEnt->pitch - m_pMyEnt->targetpitch)); - - if ((yawtoturn > 5) || (pitchtoturn > 15)) // UNDONE: Should be skill based - return; - } - - float flDist = GetDistance(enemypos); - - // Check if bot is in fire range - if ((flDist < WeaponInfoTable[m_pMyEnt->gunselect].flMinFireDistance) || - (flDist > WeaponInfoTable[m_pMyEnt->gunselect].flMaxFireDistance)) - return; - - // Now shoot! - m_pMyEnt->attacking = true; - - // Get the position the bot is aiming at - vec forward, right, up, dest; - traceresult_s tr; - - AnglesToVectors(GetViewAngles(), forward, right, up); - - dest = m_pMyEnt->o; - vmul(forward, 1000); - vadd(dest, forward); - - TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr); - debugbeam(m_pMyEnt->o, tr.end); - - // Shoot - shoot(m_pMyEnt, tr.end); - - // Add shoot delay - m_iShootDelay = lastmillis + GetShootDelay(); - } - ChoosePreferredWeapon(); - } -#ifndef RELEASE_BUILD - else - { - char sz[64]; - sprintf(sz, "shootdelay: %d\n", (m_iShootDelay-lastmillis)); - AddDebugText(sz); - } -#endif -} - -bool CBot::ChoosePreferredWeapon() -{ - TMultiChoice WeaponChoices; - short sWeaponScore; - float flDist = GetDistance(m_pMyEnt->enemy->o); - - if ((m_iChangeWeaponDelay > lastmillis) && (m_pMyEnt->ammo[m_pMyEnt->gunselect])) - { - if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE) || (flDist <= 3.5f)) - return true; - } - - // Choose a weapon - for(int i=0;iammo[i] == 0) continue; - - sWeaponScore = 5; // Minimal score for a weapon - - if ((flDist >= WeaponInfoTable[i].flMinDesiredDistance) && - (flDist <= WeaponInfoTable[i].flMaxDesiredDistance)) - { - // In desired range for this weapon - sWeaponScore += 5; // Increase score much - } - else if ((flDist < WeaponInfoTable[i].flMinFireDistance) || - (flDist > WeaponInfoTable[i].flMaxFireDistance)) - continue; // Wrong distance for this weapon - - // The ideal distance would be between the Min and Max desired distance. - // Score on the difference of the avarage of the Min and Max desired distance. - float flAvarage = (WeaponInfoTable[i].flMinDesiredDistance + - WeaponInfoTable[i].flMaxDesiredDistance) / 2.0f; - float flIdealDiff = fabs(flDist - flAvarage); - - if (flIdealDiff < 0.5f) // Close to ideal distance - sWeaponScore += 4; - else if (flIdealDiff <= 1.0f) - sWeaponScore += 2; - - // Now rate the weapon on available ammo... - if (WeaponInfoTable[i].sMinDesiredAmmo > 0) - { - // Calculate how much percent of the min desired ammo the bot has - float flDesiredPercent = (float(m_pMyEnt->ammo[i]) / - float(WeaponInfoTable[i].sMinDesiredAmmo)) * - 100.0f; - - if (flDesiredPercent >= 400.0f) - sWeaponScore += 4; - else if (flDesiredPercent >= 200.0f) - sWeaponScore += 3; - else if (flDesiredPercent >= 100.0f) - sWeaponScore += 1; - } - - WeaponChoices.Insert(i, sWeaponScore); - } - - int WeaponSelect; - if (WeaponChoices.GetSelection(WeaponSelect)) - { - m_iChangeWeaponDelay = lastmillis + RandomLong(2000, 8000); - m_bShootAtFeet = ((WeaponInfoTable[WeaponSelect].eWeaponType==TYPE_ROCKET) && - (RandomLong(1, 100) <= - m_pBotSkill->sShootAtFeetWithRLPercent)); - return SelectGun(WeaponSelect); - } - - return false; -} - -int CBot::GetShootDelay() -{ - // UNDONE - if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) || - (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_AUTO)) - return m_pMyEnt->gunwait; - - float flMinShootDelay = m_pBotSkill->flMinAttackDelay; - float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; - return max(m_pMyEnt->gunwait, int(RandomFloat(flMinShootDelay, flMaxShootDelay) * 1000.0f)); -} - -void CBot::MainAI() -{ - // Default bots will run forward - m_pMyEnt->move = 1; - - // Default bots won't strafe - m_pMyEnt->strafe = 0; - - if (!BotManager.BotsShoot() && m_pMyEnt->enemy) - m_pMyEnt->enemy = NULL; // Clear enemy when bots may not shoot - - if (m_bGoToDebugGoal) // For debugging the waypoint navigation - { - if (!HeadToGoal()) - { - ResetWaypointVars(); - m_vGoal = g_vecZero; - } - else - AddDebugText("Heading to debug goal..."); - } - if (BotManager.BotsShoot() && FindEnemy()) // Combat - { - AddDebugText("has enemy"); - - // Shoot at enemy - ShootEnemy(); - - if (m_eCurrentBotState != STATE_ENEMY) - { - m_vGoal = g_vecZero; - ResetWaypointVars(); - } - - m_eCurrentBotState = STATE_ENEMY; - if (!CheckJump()) - DoCombatNav(); - } - else if (CheckHunt() && HuntEnemy()) - { - AddDebugText("Hunting to %s", m_pHuntTarget->name); - m_eCurrentBotState = STATE_HUNT; - } - // Heading to an interesting entity(ammo, armour etc) - else if (CheckItems()) - { - AddDebugText("has ent"); - m_eCurrentBotState = STATE_ENT; - } - else if (m_classicsp && DoSPStuff()) // Home to goal, find/follow friends etc. - { - AddDebugText("SP stuff"); - m_eCurrentBotState = STATE_SP; - } - else // Normal navigation - { - if (m_eCurrentBotState != STATE_NORMAL) - { - m_vGoal = g_vecZero; - ResetWaypointVars(); - } - - m_eCurrentBotState = STATE_NORMAL; - bool bDoNormalNav = true; - - AddDebugText("normal nav"); - - // Make sure the bot looks straight forward and not up or down - m_pMyEnt->pitch = 0; - - // if it is time to look for a waypoint AND if there are waypoints in this - // level... - if (WaypointClass.m_iWaypointCount >= 1) - { - // check if we need to find a waypoint... - if (CurrentWPIsValid() == false) - { - if (m_iLookForWaypointTime <= lastmillis) - { - // find the nearest reachable waypoint - waypoint_s *pWP = GetNearestWaypoint(10.0f); - - if (pWP && (pWP != m_pCurrentWaypoint)) - { - SetCurrentWaypoint(pWP); - condebug("New nav wp"); - bDoNormalNav = !HeadToWaypoint(); - if (bDoNormalNav) - ResetWaypointVars(); - } - else - ResetWaypointVars(); - - m_iLookForWaypointTime = lastmillis + 250; - } - } - else - { - bDoNormalNav = !HeadToWaypoint(); - if (bDoNormalNav) - ResetWaypointVars(); - AddDebugText("Using wps for nav"); - } - } - - // If nothing special, do regular (waypointless) navigation - if(bDoNormalNav) - { - // Is the bot underwater? - if (UnderWater(m_pMyEnt->o) && WaterNav()) - { - // Bot is under water, navigation happens in WaterNav - } - // Time to check the environment? - else if (m_iCheckEnvDelay < lastmillis) - { - if (!vis(m_vWaterGoal, g_vecZero)) m_vWaterGoal = g_vecZero; - - // Check for stuck and strafe - if (UnderWater(m_pMyEnt->o) || !CheckStuck()) - { - // Only do this when the bot is underwater or when the bot isn't stuck - - // Check field of view (FOV) - CheckFOV(); - } - } - - // Check if the bot has to strafe - CheckStrafe(); - - m_pMyEnt->move = 1; - } - } -} - -void CBot::DoCombatNav() -{ - if (m_iCombatNavTime > lastmillis) - { - // If bot has a lower skill and has to turn much, wait - if ((m_sSkillNr > 2) && (m_fYawToTurn > 90.0f)) - { - ResetMoveSpeed(); - } - else - { - SetMoveDir(m_iMoveDir, false); - } - return; - } - - if (m_bCombatJump) - { - m_pMyEnt->jumpnext = true; - m_bCombatJump = false; - m_iCombatJumpDelay = lastmillis + RandomLong(1500, 2800); - return; - } - - m_iMoveDir = DIR_NONE; - - // Check if bot is on top of his enemy - float r = m_pMyEnt->radius+m_pMyEnt->enemy->radius; - if ((fabs(m_pMyEnt->enemy->o.x-m_pMyEnt->o.x)enemy->o.y-m_pMyEnt->o.y)enemy->o.z+m_pMyEnt->enemy->aboveeye) < (m_pMyEnt->o.z + m_pMyEnt->aboveeye))) - { - // Try to get off him! - condebug("On enemy!"); - TMultiChoice AwayDirChoices; - - if (IsVisible(LEFT, 4.0f, false)) - AwayDirChoices.Insert(LEFT); - if (IsVisible(RIGHT, 4.0f, false)) - AwayDirChoices.Insert(RIGHT); - if (IsVisible(FORWARD, 4.0f, false)) - AwayDirChoices.Insert(FORWARD); - if (IsVisible(BACKWARD, 4.0f, false)) - AwayDirChoices.Insert(BACKWARD); - - int iDir; - if (AwayDirChoices.GetSelection(iDir)) - { - m_iMoveDir = iDir; - m_iCombatNavTime = lastmillis + 500; - } - } - - float flDist = GetDistance(m_pMyEnt->enemy->o); - - // Check for nearby items? - if (((m_iCheckEntsDelay < lastmillis) || m_pTargetEnt) && - m_pBotSkill->bCanSearchItemsInCombat) - { - m_iCheckEntsDelay = lastmillis + 125; - bool bSearchItems = false; - - if (m_pTargetEnt) - { - // Bot has already found an entity, still valid? - vec v = { m_pTargetEnt->x, m_pTargetEnt->y, - S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; - if ((GetDistance(v) > 25.0f) || !IsVisible(m_pTargetEnt)) - m_pTargetEnt = NULL; - } - - if (!m_pTargetEnt && (m_iCheckEntsDelay <= lastmillis)) - { - if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) - bSearchItems = (flDist >= 8.0f); - else - bSearchItems = (m_pMyEnt->ammo[m_pMyEnt->gunselect] <= - WeaponInfoTable[m_pMyEnt->gunselect].sMinDesiredAmmo); - - if (bSearchItems) - m_pTargetEnt = SearchForEnts(false, 25.0f, 1.0f); - } - - if (m_pTargetEnt) - { - condebug("Combat ent"); - vec v = { m_pTargetEnt->x, m_pTargetEnt->y, - S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; - - debugbeam(m_pMyEnt->o, v); - - float flHeightDiff = v.z - m_pMyEnt->o.z; - bool bToHigh = false; - - // Check he height for this ent - if (Get2DDistance(v) <= 2.0f) - { - if (flHeightDiff >= 1.5f) - { - if (flHeightDiff <= JUMP_HEIGHT) - { -#ifndef RELEASE_BUILD - char sz[64]; - sprintf(sz, "Ent z diff: %f", v.z-m_pMyEnt->o.z); - condebug(sz); -#endif - m_pMyEnt->jumpnext = true; // Jump if close to ent and the ent is high - } - else - bToHigh = true; - } - } - - if (!bToHigh) - { - int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, v); - - if (iMoveDir != DIR_NONE) - { - m_iMoveDir = iMoveDir; - m_iCombatNavTime = lastmillis + RandomLong(125, 250); - } - - // Check if bot needs to jump over something - vec from = m_pMyEnt->o; - from.z -= 1.0f; - if (!IsVisible(from, iMoveDir, 3.0f, false)) - m_pMyEnt->jumpnext = true; - - return; - } - } - } - - // High skill and enemy is close? - if ((m_sSkillNr <= 1) && (m_fYawToTurn < 80.0f) && (flDist <= 20.0f) && - (m_iCombatJumpDelay < lastmillis)) - { - // Randomly jump a bit, to avoid some basic firepower ;) - - // How lower the distance to the enemy, how higher the chance for a jump - short sJumpPercent = (100 - ((short)flDist * 8)); - if (RandomLong(1, 100) <= sJumpPercent) - { - // Choose a nice direction to jump to - - // Is the enemy close? - if ((GetDistance(m_pMyEnt->enemy->o) <= 4.0f) || - (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE)) - { - m_iMoveDir = FORWARD; // Jump forward - SetMoveDir(FORWARD, false); - m_bCombatJump = true; - } - else if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE)// else jump to a random direction - { - /* - Directions to choose: - - Forward-right - - Right - - Backward-right - - Backward - - Backward-left - - Left - - Forward-left - - */ - - TMultiChoice JumpDirChoices; - short sForwardScore = ((flDist > 8.0f) || (flDist < 4.0f)) ? 20 : 10; - short sBackwardScore = (flDist <= 6.0f) ? 20 : 10; - short sStrafeScore = (flDist < 6.0f) ? 20 : 10; - - if (IsVisible((FORWARD | LEFT), 4.0f, false)) - JumpDirChoices.Insert((FORWARD | LEFT), sForwardScore); - if (IsVisible((FORWARD | RIGHT), 4.0f, false)) - JumpDirChoices.Insert((FORWARD | RIGHT), sForwardScore); - if (IsVisible(BACKWARD, 4.0f, false)) - JumpDirChoices.Insert(BACKWARD, sBackwardScore); - if (IsVisible((BACKWARD | LEFT), 4.0f, false)) - JumpDirChoices.Insert((BACKWARD | LEFT), sBackwardScore); - if (IsVisible((BACKWARD | RIGHT), 4.0f, false)) - JumpDirChoices.Insert((BACKWARD | RIGHT), sBackwardScore); - if (IsVisible(LEFT, 4.0f, false)) - JumpDirChoices.Insert(LEFT, sStrafeScore); - if (IsVisible(RIGHT, 4.0f, false)) - JumpDirChoices.Insert(RIGHT, sStrafeScore); - - int JumpDir; - if (JumpDirChoices.GetSelection(JumpDir)) - { - m_iMoveDir = JumpDir; - SetMoveDir(JumpDir, false); - m_bCombatJump = true; - } - } - - if (m_bCombatJump) - { - m_iCombatNavTime = lastmillis + RandomLong(125, 250); - return; - } - } - } - - if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) - return; // Simply walk towards enemy if using a melee type - - flDist = Get2DDistance(m_pMyEnt->enemy->o); - - // Out of desired range for current weapon? - if ((flDist <= WeaponInfoTable[m_pMyEnt->gunselect].flMinDesiredDistance) || - (flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance)) - { - if (flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance) - { - m_iMoveDir = FORWARD; - } - else - { - m_iMoveDir = BACKWARD; - } - - vec src, forward, right, up, dest, MyAngles = GetViewAngles(), o = m_pMyEnt->o; - traceresult_s tr; - - // Is it furthest or farthest? bleh - float flFurthestDist = 0; - int bestdir = -1, dir = 0; - bool moveback = (m_pMyEnt->move == -1); - - for(int j=-45;j<=45;j+=45) - { - src = MyAngles; - src.y = WrapYZAngle(src.y + j); - src.x = 0.0f; - - // If we're moving backwards, trace backwards - if (moveback) - src.y = WrapYZAngle(src.y + 180); - - AnglesToVectors(src, forward, right, up); - - dest = o; - vmul(forward, 40); - vadd(dest, forward); - - TraceLine(o, dest, m_pMyEnt, false, &tr); - - //debugbeam(origin, end); - flDist = GetDistance(tr.end); - - if (flFurthestDist < flDist) - { - flFurthestDist = flDist; - bestdir = dir; - } - dir++; - } - - switch(bestdir) - { - case 0: - if (moveback) - m_iMoveDir |= RIGHT; // Strafe right - else - m_iMoveDir |= LEFT; // Strafe left - break; - case 2: - if (moveback) - m_iMoveDir |= LEFT; // Strafe left - else - m_iMoveDir |= RIGHT; // Strafe right - break; - } - - if (m_iMoveDir != DIR_NONE) - { - SetMoveDir(m_iMoveDir, false); - m_iCombatNavTime = lastmillis + 500; - } - } - else if (m_pBotSkill->bCircleStrafe) // Circle strafe when in desired range... - { - traceresult_s tr; - vec angles, end, forward, right, up; - TMultiChoice StrafeDirChoices; - - // Check the left side... - angles = GetViewAngles(); - angles.y = WrapYZAngle(angles.y - 75.0f); // Not 90 degrees because the bot - // doesn't strafe in a straight line - // (aims still to enemy). - - AnglesToVectors(angles, forward, right, up); - end = m_pMyEnt->o; - vmul(forward, 15.0f); - vadd(end, forward); - - TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr); - StrafeDirChoices.Insert(LEFT, (int)GetDistance(m_pMyEnt->o, tr.end)); - - // Check the right side... - angles = GetViewAngles(); - angles.y = WrapYZAngle(angles.y + 75.0f); // Not 90 degrees because the bot - // doesn't strafe in a straight line - // (aims still to enemy). - - AnglesToVectors(angles, forward, right, up); - end = m_pMyEnt->o; - vmul(forward, 15.0f); - vadd(end, forward); - - TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr); - StrafeDirChoices.Insert(RIGHT, (int)GetDistance(m_pMyEnt->o, tr.end)); - - int StrafeDir; - if (StrafeDirChoices.GetSelection(StrafeDir)) - { - m_iMoveDir = StrafeDir; - SetMoveDir(StrafeDir, false); - m_iCombatNavTime = lastmillis + RandomLong(1500, 3000); - } - } - else // Bot can't circle strafe(low skill), just stand still - ResetMoveSpeed(); -} - -bool CBot::CheckStuck() -{ - if (m_iStuckCheckDelay >= lastmillis) - return false; - - if ((!vis(m_vGoal, g_vecZero)) && (GetDistance(m_vGoal) < 2.0f)) - return false; - - bool IsStuck = false; - - vec CurPos = m_pMyEnt->o, PrevPos = m_vPrevOrigin; - CurPos.z = PrevPos.z = 0; - // Did the bot hardly move the last frame? - if (GetDistance(CurPos, PrevPos) <= 0.1f) - { - if (m_bStuck) - { - if (m_iStuckTime < lastmillis) - IsStuck = true; - } - else - { - m_bStuck = true; - m_iStuckTime = lastmillis + 1000; - } - } - else - { - m_bStuck = false; - m_iStuckTime = 0; - } - - if (IsStuck) - { -#ifndef RELEASE_BUILD - char msg[64]; - sprintf(msg, "stuck (%f)", GetDistance(m_vPrevOrigin)); - condebug(msg); -#endif - - m_bStuck = false; - m_iStuckTime = 0; - - // Crap bot is stuck, lets just try some random things - - // Check if the bot can turn around - vec src = GetViewAngles(); - src.x = 0; - vec forward, right, up, dir, dest; - traceresult_s tr; - - AnglesToVectors(src, forward, right, up); - - // Check the left side... - dir = right; - dest = m_pMyEnt->o; - vmul(dir, 3); - vsub(dest, dir); - - TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr); - //debugbeam(m_pMyEnt->o, end); - - if (!tr.collided) - { - // Bot can turn left, do so - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw - 90); - m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; - return true; - } - - // Check the right side... - dir = right; - dest = m_pMyEnt->o; - vmul(dir, 3); - vadd(dest, dir); - - TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr); - //debugbeam(m_pMyEnt->o, end); - - if (!tr.collided) - { - // Bot can turn right, do so - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 90); - m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; - return true; - } - - // Check if bot can turn 180 degrees - dir = forward; - dest = m_pMyEnt->o; - vmul(dir, 3); - vsub(dest, dir); - - TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr); - //debugbeam(m_pMyEnt->o, end); - - if (!tr.collided) - { - // Bot can turn around, do so - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180); - m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; - return true; - } - - // Bleh bot couldn't turn, lets just randomly jump :| - - condebug("Randomly avoiding stuck..."); - if (RandomLong(0, 2) == 0) - m_pMyEnt->jumpnext = true; - else - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomLong(60, 160)); - return true; - } - - return false; -} - -// Check if a near wall is blocking and we can jump over it -bool CBot::CheckJump() -{ - bool bHasGoal = (!vis(m_vGoal, g_vecZero)); - float flGoalDist = (bHasGoal) ? GetDistance(m_vGoal) : 0.0f; - -// if ((bHasGoal) && (flGoalDist < 2.0f)) - // return false; UNDONE? - - vec start = m_pMyEnt->o; - float flTraceDist = 3.0f; - - if (bHasGoal && (flGoalDist < flTraceDist)) - flTraceDist = flGoalDist; - - // Something blocks at eye hight? - if (!IsVisible(start, FORWARD, flTraceDist, false)) - { - // Check if the bot can jump over it - start.z += (JUMP_HEIGHT - 1.0f); - if (IsVisible(start) && !IsVisible(start, FORWARD, flTraceDist, false)) - { - // Jump - debugnav("High wall"); - m_pMyEnt->jumpnext = true; - return true; - } - } - else - { - // Check if something is blocking at feet height, so the bot can jump over it - start.z -= 1.7f; - - // Trace was blocked? - if (!IsVisible(start, FORWARD, flTraceDist, false)) - { - //debugbeam(start, end); - - // Jump - debugnav("Low wall"); - m_pMyEnt->jumpnext = true; - return true; - } - } - - return false; // Bot didn't had to jump(or couldn't) -} - -bool CBot::CheckStrafe() -{ - if (m_iStrafeTime >= lastmillis) - { - SetMoveDir(m_iMoveDir, true); - return true; - } - - if (m_iStrafeCheckDelay >= lastmillis) - return false; - - // Check for near walls - traceresult_s tr; - vec from = m_pMyEnt->o, to, forward, right, up, dir; - float flLeftDist = -1.0f, flRightDist = -1.0f; - bool bStrafe = false; - int iStrafeDir = DIR_NONE; - - AnglesToVectors(GetViewAngles(), forward, right, up); - - // Check for a near left wall - to = from; - dir = right; - vmul(dir, 3.0f); - vsub(to, dir); - TraceLine(from, to, m_pMyEnt, false, &tr); - if (tr.collided) - flLeftDist = GetDistance(from, tr.end); - //debugbeam(m_pMyEnt->o, to); - - // Check for a near right wall - to = from; - dir = right; - vmul(dir, 3.0f); - vadd(to, dir); - TraceLine(from, to, m_pMyEnt, false, &tr); - if (tr.collided) - flRightDist = GetDistance(from, tr.end); - //debugbeam(m_pMyEnt->o, to); - - if ((flLeftDist == -1.0f) && (flRightDist == -1.0f)) - { - dir = right; - vmul(dir, m_pMyEnt->radius); - - // Check left - from = m_pMyEnt->o; - vsub(from, dir); - if (IsVisible(from, FORWARD, 3.0f, false, &flLeftDist)) - flLeftDist = -1.0f; - - // Check right - from = m_pMyEnt->o; - vadd(from, dir); - if (IsVisible(from, FORWARD, 3.0f, false, &flRightDist)) - flRightDist = -1.0f; - } - - if ((flLeftDist != -1.0f) && (flRightDist != -1.0f)) - { - if (flLeftDist < flRightDist) - { - // Strafe right - bStrafe = true; - iStrafeDir = RIGHT; - } - else if (flRightDist < flLeftDist) - { - // Strafe left - bStrafe = true; - iStrafeDir = LEFT; - } - else - { - // Randomly choose a strafe direction - bStrafe = true; - if (RandomLong(0, 1)) - iStrafeDir = LEFT; - else - iStrafeDir = RIGHT; - } - } - else if (flLeftDist != -1.0f) - { - // Strafe right - bStrafe = true; - iStrafeDir = RIGHT; - } - else if (flRightDist != -1.0f) - { - // Strafe left - bStrafe = true; - iStrafeDir = LEFT; - } - - if (bStrafe) - { - SetMoveDir(iStrafeDir, true); - m_iMoveDir = iStrafeDir; - m_iStrafeTime = lastmillis + RandomLong(75, 150); - } - - return bStrafe; -} - -void CBot::CheckFOV() -{ - m_iCheckEnvDelay = lastmillis + RandomLong(125, 250); - vec MyAngles = GetViewAngles(); - vec src, forward, right, up, dest, best = {0,0,0}; - vec origin = m_pMyEnt->o; - float flDist, flFurthestDist = 0; - bool WallLeft = false; - traceresult_s tr; - - //origin.z -= 1.5; // Slightly under eye level - - // Scan 90 degrees FOV - for(int angle=-45;angle<=45;angle+=5) - { - src = MyAngles; - src.y = WrapYZAngle(src.y + angle); - - AnglesToVectors(src, forward, right, up); - - dest = origin; - vmul(forward, 40); - vadd(dest, forward); - - TraceLine(origin, dest, m_pMyEnt, false, &tr); - - //debugbeam(origin, end); - flDist = GetDistance(tr.end); - - if (flFurthestDist < flDist) - { - flFurthestDist = flDist; - best = tr.end; - } - } - - if (best.x && best.y && best.z) - { - AimToVec(best); - // Update MyAngles, since their (going to be) change(d) - MyAngles.x = m_pMyEnt->targetpitch; - MyAngles.y = m_pMyEnt->targetyaw; - } - - float flNearestHitDist = GetDistance(best); - - if (!UnderWater(m_pMyEnt->o) && m_pMyEnt->onfloor) - { - // Check if a near wall is blocking and we can jump over it - if (flNearestHitDist < 4) - { - // Check if the bot can jump over it - src = MyAngles; - src.x = 0; - - AnglesToVectors(src, forward, right, up); - - vec start = origin; - start.z += 2.0f; - dest = start; - vmul(forward, 6); - vadd(dest, forward); - - TraceLine(start, dest, m_pMyEnt, false, &tr); - //debugbeam(start, end); - - if (!tr.collided) - { - // Jump - debugnav("High wall"); - m_pMyEnt->jumpnext = true; - m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500); - return; - } - } - else - { - // Check if something is blocking below us, so the bot can jump over it - src = MyAngles; - src.x = 0; - - AnglesToVectors(src, forward, right, up); - - vec start = origin; - start.z -= 1.7f; - dest = start; - vmul(forward, 4); - vadd(dest, forward); - - TraceLine(start, dest, m_pMyEnt, false, &tr); - - // Trace was blocked? - if (tr.collided) - { - //debugbeam(start, end); - - // Jump - debugnav("Low wall"); - m_pMyEnt->jumpnext = true; - m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500); - return; - } - } - - // Check if the bot is going to fall... - src = MyAngles; - src.x = 0.0f; - AnglesToVectors(src, forward, right, up); - - dest = origin; - vmul(forward, 3.0f); - vadd(dest, forward); - - TraceLine(origin, dest, m_pMyEnt, false, &tr); - - int cx = fast_f2nat(tr.end.x), cy = fast_f2nat(tr.end.y); - short CubesInWater=0; - for(int x=cx-1;x<=(cx+1);x++) - { - for(int y=cy-1;y<=(cy+1);y++) - { - if (OUTBORD(x, y)) continue; - //sqr *s = S(fast_f2nat(x), fast_f2nat(y)); - //if (!SOLID(s)) - { - vec from = { x, y, m_pMyEnt->o.z }; - dest = from; - dest.z -= 6.0f; - TraceLine(from, dest, m_pMyEnt, false, &tr); - bool turn = false; - if (UnderWater(tr.end)) CubesInWater++; - if (CubesInWater > 2) turn = true; // Always avoid water - if (!tr.collided && RandomLong(0, 1)) - turn = true; // Randomly avoid a fall - - if (turn) - { - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180); - m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500); - debugnav("Water or a fall in front"); - return; - } - } - } - } - } - - // Is the bot about to head a corner? - if (flNearestHitDist <= 4.0f) - { - src = MyAngles; - src.y = WrapYZAngle(src.y - 45.0f); - AnglesToVectors(src, forward, right, up); - - dest = origin; - vmul(forward, 4.0f); - vadd(dest, forward); - - TraceLine(origin, dest, m_pMyEnt, false, &tr); - - WallLeft = (tr.collided); - - src = MyAngles; - src.y += WrapYZAngle(src.y + 45.0f); - AnglesToVectors(src, forward, right, up); - - dest = origin; - vmul(forward, 4.0f); - vadd(dest, forward); - - TraceLine(origin, dest, m_pMyEnt, false, &tr); - - if (WallLeft && tr.collided) - { - // We're about to hit a corner, turn away - debugnav("Corner"); - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomFloat(160.0f, 200.0f)); - m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500); - return; - } - } -} - -// Called when bot is underwater -bool CBot::WaterNav() -{ - const int iSearchRange = 4; - - if (vis(m_vWaterGoal, g_vecZero)) - { - AddDebugText("WaterNav"); - // Find the nearest and reachable cube which isn't underwater - - int cx = fast_f2nat(m_pMyEnt->o.x); - int cy = fast_f2nat(m_pMyEnt->o.y); - float flNearestDist = 9999.0f, flDist; - - if (OUTBORD(cx, cy)) return false; - - // Check all cubes in range... - for (int x=(cx-iSearchRange);x<=(cx+iSearchRange);x++) - { - for (int y=(cy-iSearchRange);y<=(cy+iSearchRange);y++) - { - sqr *s = S(x, y); - - if (SOLID(s)) continue; - if ((x==cx) && (y==cy)) continue; - - vec v = { x, y, GetCubeFloor(x, y) }; - - if (UnderWater(v)) continue; // Skip, cube is underwater - - if (hdr.waterlevel < (v.z - 2.0f)) continue; // Cube is too high - - // Check if the bot 'can fit' on the cube(no near obstacles) - bool small = false; - for (int a=(x-2);a<=(x+2);a++) - { - if (small) break; - for (int b=(y-2);b<=(y+2);b++) - { - if ((x==a) && (y==b)) continue; - vec v2 = { a, b, GetCubeFloor(a, b) }; - if (v.z < (v2.z-1-JUMP_HEIGHT)) - { - small=true; - break; - } - - if ((a >= (x-1)) && (a <= (x+1)) && (b >= (y-1)) && (b <= (y+1))) - { - if ((v2.z) < (v.z-2.0f)) - { - small = true; - break; - } - } - - traceresult_s tr; - TraceLine(v, v2, NULL, false, &tr); - if (tr.collided) - { - small=true; - break; - } - } - if (small) break; - } - if (small) - { - debugbeam(m_pMyEnt->o, v); - continue; - } - - // Okay, cube is valid. - flDist = GetDistance(v); - if (flDist < flNearestDist) - { - flNearestDist = flDist; - m_vWaterGoal = v; - } - } - } - } - - if (!vis(m_vWaterGoal, g_vecZero)) - { - AddDebugText("WaterNav"); - //debugbeam(m_pMyEnt->o, m_vWaterGoal); - vec aim = m_vWaterGoal; - aim.z += 1.5f; // Aim a bit further up - AimToVec(aim); - if ((RandomLong(1, 100) <= 15) && (Get2DDistance(m_vWaterGoal) <= 7.0f)) - m_pMyEnt->jumpnext = true; - return true; - } - - return false; -} - -bool CBot::CheckItems() -{ - if (!m_pCurrentGoalWaypoint && !CheckJump() && CheckStuck()) - { - // Don't check for ents a while when stuck - m_iCheckEntsDelay = lastmillis + RandomLong(1000, 2000); - return false; - } - - if (vis(m_vGoal, g_vecZero)) - m_pTargetEnt = NULL; - - if (!m_pTargetEnt) - { - if (m_iCheckEntsDelay > lastmillis) - return false; - else - { - m_pTargetEnt = SearchForEnts(!m_classicsp); - m_iCheckEntsDelay = lastmillis + RandomLong(2500, 5000); - } - } - - if (m_pTargetEnt) - { - if (HeadToTargetEnt()) - return true; - } - - if (m_eCurrentBotState == STATE_ENT) - { - ResetWaypointVars(); - m_vGoal = g_vecZero; - m_pTargetEnt = NULL; - } - - return false; -} - -bool CBot::InUnreachableList(entity *e) -{ - TLinkedList::node_s *p = m_UnreachableEnts.GetFirst(); - while(p) - { - if (p->Entry->ent == e) return true; - p = p->next; - } - return false; -} - -bool CBot::IsReachable(vec to, float flMaxHeight) -{ - vec from = m_pMyEnt->o; - traceresult_s tr; - float curr_height, last_height; - - float distance = GetDistance(from, to); - - // is the destination close enough? - //if (distance < REACHABLE_RANGE) - { - if (IsVisible(to)) - { - // Look if bot can 'fit trough' - vec src = from, forward, right, up; - AnglesToVectors(GetViewAngles(), forward, right, up); - - // Trace from 1 cube to the left - vec temp = right; - vmul(temp, 1.0f); - vsub(src, temp); - if (!::IsVisible(src, to)) return false; - - // Trace from 1 cube to the right - vadd(src, temp); - if (!::IsVisible(src, to)) return false; - - if (UnderWater(from) && UnderWater(to)) - { - // No need to worry about heights in water - return true; - } -/* - if (to.z > (from.z + JUMP_HEIGHT)) - { - vec v_new_src = to; - vec v_new_dest = to; - - v_new_dest.z = v_new_dest.z - (JUMP_HEIGHT + 1.0f); - - // check if we didn't hit anything, if so then it's in mid-air - if (::IsVisible(v_new_src, v_new_dest, NULL)) - { - condebug("to is in midair"); - debugbeam(from, to); - return false; // can't reach this one - } - } -*/ - - // check if distance to ground increases more than jump height - // at points between from and to... - - vec v_temp = to; vsub(v_temp, from); - vec v_direction = Normalize(v_temp); // 1 unit long - vec v_check = from; - vec v_down = from; - - v_down.z = v_down.z - 100.0f; // straight down - - TraceLine(v_check, v_down, NULL, false, &tr); - - // height from ground - last_height = GetDistance(v_check, tr.end); - - distance = GetDistance(to, v_check); // distance from goal - - while (distance > 2.0f) - { - // move 2 units closer to the goal - v_temp = v_direction; - vmul(v_temp, 2.0f); - vadd(v_check, v_temp); - - v_down = v_check; - v_down.z = v_down.z - 100.0f; - - TraceLine(v_check, v_down, NULL, false, &tr); - - curr_height = GetDistance(v_check, tr.end); - - // is the difference in the last height and the current height - // higher that the jump height? - if ((last_height - curr_height) >= flMaxHeight) - { - // can't get there from here... - //condebug("traces failed to to"); - debugbeam(from, to); - return false; - } - - last_height = curr_height; - - distance = GetDistance(to, v_check); // distance from goal - } - - return true; - } - } - - return false; -} - -void CBot::HearSound(int n, vec *o) -{ - // Has the bot already an enemy? - if (m_pMyEnt->enemy) return; - - // Is the sound not interesting? -//fixmebot -/* if ((n==S_DIE1) || (n==S_DIE2) || (n==S_DEATHR) || (n==S_DEATHE) || (n==S_DEATHS) || - (n==S_DEATHB) || (n==S_DEATHH) || (n==S_DEATHD) || (n==S_PIGGR2)) - return;*/ - - int soundvol = m_pBotSkill->iMaxHearVolume - - (int)(GetDistance(*o)*3*m_pBotSkill->iMaxHearVolume/255); - - if (soundvol == 0) return; - - // Look who made the sound(check for the nearest enemy) - float flDist, flNearestDist = 3.0f; // Range of 3 units - dynent *pNearest = NULL; - - /* edit -argh - if (m_sp) - { - // Check all monsters - loopv(monsters) - { - if (!monsters[i] || monsters[i]->state != CS_ALIVE) continue; - - flDist = GetDistance(*o, monsters[i]->o); - if ((flDist < flNearestDist) && IsVisible(monsters[i])) - { - pNearest = monsters[i]; - flNearestDist = flDist; - } - } - } */ - //else - //{ - // Check all players first - loopv(players) - { - if (!players[i] || (players[i]->state != CS_ALIVE) || - isteam(m_pMyEnt->team, players[i]->team)) - continue; - - flDist = GetDistance(*o, players[i]->o); - if ((flDist < flNearestDist) && IsVisible(players[i])) - { - pNearest = players[i]; - flNearestDist = flDist; - } - } - - // Check all bots - loopv(bots) - { - if (!bots[i] || (bots[i]->state != CS_ALIVE) || - isteam(m_pMyEnt->team, bots[i]->team) || (bots[i] == m_pMyEnt)) - continue; - - flDist = GetDistance(*o, bots[i]->o); - if ((flDist < flNearestDist) && IsVisible(bots[i])) - { - pNearest = bots[i]; - flNearestDist = flDist; - } - } - - // Check local player - if (player1 && (player1->state == CS_ALIVE) && - !isteam(m_pMyEnt->team, player1->team)) - { - flDist = GetDistance(*o, player1->o); - if ((flDist < flNearestDist) && IsVisible(player1)) - { - pNearest = player1; - flNearestDist = flDist; - } - } - //} - - if (pNearest) - { - if (m_pMyEnt->enemy != pNearest) - m_iShootDelay = lastmillis + GetShootDelay(); // Add shoot delay when new enemy found - - m_pMyEnt->enemy = pNearest; - } -} - -bool CBot::IsInFOV(const vec &o) -{ - vec v2, forward, right, up; - float flDot; - - AnglesToVectors(GetViewAngles(), forward, right, up); - - v2 = o; - vsub(v2, m_pMyEnt->o); - v2.z = 0.0f; // Make 2D - v2 = Normalize(v2); - forward.z = 0; // Make 2D - - flDot = dotprod(v2 , forward); - - return(flDot >= 0.5f); // sin2(0.5) == 60 degrees FOV -} - -// Code of CBot - End +// +// C++ Implementation: bot_ai +// +// Description: The AI part comes here(navigation, shooting etc) +// +// +// Author: +// + + +// Code of CBot - Start + +#include "../cube.h" + +extern vector sents; +extern vector monsters; + +extern weaponinfo_s WeaponInfoTable[MAX_WEAPONS]; + +vec CBot::GetEnemyPos(dynent *d) +{ + // Aim offset idea by botman + vec o = d->o, offset; + float flDist = GetDistance(d->o), flScale; + + if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_ROCKET) + { + // Bot is using a rocket launcher, aim at enemy feet? + if (m_bShootAtFeet && !OUTBORD(d->o.x, d->o.y)) + { + // Only do this when enemy is fairly close to the ground + vec end = o; + end.z -= 900.0f; + traceresult_s tr; + TraceLine(o, end, NULL, false, &tr); + if ((o.z - tr.end.z) < 8.0f) + { + end = o; + end.z = tr.end.z; + if (IsVisible(end)) + { + // Target at ground + o.z = tr.end.z; + } + } + } + + if (m_pBotSkill->bCanPredict) + { + // How higher the skill, how further the bot predicts + vec org = o; + float flPredictTime = RandomFloat(1.25f, 1.7f) / (m_sSkillNr+1); + o = PredictPos(o, d->vel, flPredictTime); + } + } + else + { + if (m_pBotSkill->bCanPredict) + { + // How higher the skill, how 'more' the bot predicts + vec org = o; + float flPredictTime = RandomFloat(0.8f, 1.2f) / (m_sSkillNr+1); + o = PredictPos(o, d->vel, flPredictTime); + } + } + + if (flDist > 60.0f) + flScale = 1.0; + else if (flDist > 6.0f) + flScale = flDist / 60.0; + else + flScale = 0.1; + + switch (m_sSkillNr) + { + case 0: + // no offset + offset.x = 0; + offset.y = 0; + offset.z = 0; + break; + case 1: + // GOOD, offset a little for x, y, and z + offset.x = RandomFloat(-3, 3) * flScale; + offset.y = RandomFloat(-3, 3) * flScale; + offset.z = RandomFloat(-6, 6) * flScale; + break; + case 2: + // FAIR, offset somewhat for x, y, and z + offset.x = RandomFloat(-8, 8) * flScale; + offset.y = RandomFloat(-8, 8) * flScale; + offset.z = RandomFloat(-12, 12) * flScale; + break; + case 3: + // POOR, offset for x, y, and z + offset.x = RandomFloat(-15, 15) * flScale; + offset.y = RandomFloat(-15, 15) * flScale; + offset.z = RandomFloat(-25, 25) * flScale; + break; + case 4: + // BAD, offset lots for x, y, and z + offset.x = RandomFloat(-20, 20) * flScale; + offset.y = RandomFloat(-20, 20) * flScale; + offset.z = RandomFloat(-35, 35) * flScale; + break; + } + + vadd(o, offset); + return o; +} + +bool CBot::FindEnemy(void) +{ + // UNDONE: Enemies are now only scored on their distance + + if (m_pMyEnt->enemy) // Bot already has an enemy + { + // Check if the enemy is still in game + bool found = IsInGame(m_pMyEnt->enemy); + + // Check if the enemy is still ingame, still alive, not joined my team and is visible + if (found && !isteam(m_pMyEnt->team, m_pMyEnt->enemy->team)) + { + if ((m_pMyEnt->enemy->state == CS_ALIVE) && (IsVisible(m_pMyEnt->enemy))) + return true; + else + m_pPrevEnemy = m_pMyEnt->enemy; + } + else + m_pMyEnt->enemy = NULL; + } + + if (m_iEnemySearchDelay > lastmillis) return (m_pMyEnt->enemy!=NULL); + + m_pMyEnt->enemy = NULL; + + // Add enemy searchy delay + float MinDelay = m_pBotSkill->flMinEnemySearchDelay; + float MaxDelay = m_pBotSkill->flMaxEnemySearchDelay; + m_iEnemySearchDelay = lastmillis + int(RandomFloat(MinDelay, MaxDelay) * 1000.0f); + + dynent *pNewEnemy = NULL, *d = NULL; + float flDist, flNearestDist = 99999.9f; + short EnemyVal, BestEnemyVal = -100; + + // search for only for monsters when SP mode + /* no more sp as such -argh (sorry bout messy commenting) + if (m_sp) + { + loopv(monsters) + { + d = monsters[i]; // Handy shortcut + + if (d->state != CS_ALIVE) + continue; + + // Check if the enemy is visible + if(!IsInFOV(d) || !IsVisible(d)) + continue; + + flDist = GetDistance(d->o); + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (EnemyVal > BestEnemyVal) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + } + } + } */ + + //else + //{ + + // First loop through all players + loopv(players) + { + d = players[i]; // Handy shortcut + + if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) + continue; + + // Check if the enemy is visible + if(!IsInFOV(d) || !IsVisible(d)) + continue; + + flDist = GetDistance(d->o); + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (EnemyVal > BestEnemyVal) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + } + } + + // Then loop through all bots + loopv(bots) + { + d = bots[i]; // Handy shortcut + + if (d == m_pMyEnt) + continue; + + if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) + continue; + + // Check if the enemy is visible + if(!IsInFOV(d) || !IsVisible(d)) + continue; + + flDist = GetDistance(d->o); + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (EnemyVal > BestEnemyVal) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + } + } + + // Then examine the local player + if (player1 && !isteam(player1->team, m_pMyEnt->team) && + (player1->state == CS_ALIVE)) + { + // Check if the enemy is visible + if(IsInFOV(player1) && IsVisible(player1)) + { + flDist = GetDistance(player1->o); + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (EnemyVal > BestEnemyVal) + { + pNewEnemy = player1; + BestEnemyVal = EnemyVal; + } + } + } + //} + + if (pNewEnemy) + { + if (!m_pMyEnt->enemy) // Add shoot delay if new enemy is found + { + float flMinShootDelay = m_pBotSkill->flMinAttackDelay; + float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; + + m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay, + flMaxShootDelay) * 1000.0f); + } + + if ((m_pMyEnt->enemy != pNewEnemy) && m_pMyEnt->enemy) + m_pPrevEnemy = m_pMyEnt->enemy; + + m_pMyEnt->enemy = pNewEnemy; + return true; + } + + return false; +} + +bool CBot::CheckHunt(void) +{ + if (!BotManager.BotsShoot()) return false; + + if (m_pHuntTarget) // Bot already has an enemy to hunt + { + // Check if the enemy is still in game + bool found = IsInGame(m_pHuntTarget); + + // Check if the enemy is still ingame, still alive, not joined my team and is visible + if (found && !isteam(m_pMyEnt->team, m_pHuntTarget->team)) + { + if ((m_pHuntTarget->state == CS_ALIVE) && IsReachable(m_vHuntLocation)) + return true; + } + else + m_pHuntTarget = NULL; + } + + if (m_iHuntDelay > lastmillis) return (m_pHuntTarget!=NULL); + + if (!vis(m_vHuntLocation, g_vecZero)) + m_vPrevHuntLocation = m_vHuntLocation; + + m_pHuntTarget = NULL; + m_vHuntLocation = g_vecZero; + + // Add enemy hunt search delay + m_iHuntDelay = lastmillis + 1500; + + dynent *pNewEnemy = NULL, *d = NULL; + float flDist, flNearestDist = 99999.9f, flNearestOldPosDistToEnemy = 99999.9f; + float flNearestOldPosDistToBot = 99999.9f; + short EnemyVal, BestEnemyVal = -100; + vec BestOldPos; + + // search for only for monsters when SP mode + //edit -argh + /*if (m_sp) + { + loopv(monsters) + { + d = monsters[i]; // Handy shortcut + + if (d->state != CS_ALIVE) + continue; + + flDist = GetDistance(d->o); + + if (flDist > 250.0f) continue; + + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (d == m_pPrevEnemy) + EnemyVal+=2; + + if (EnemyVal < BestEnemyVal) continue; + + vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; + flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; + + // Check previous locations of enemy + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = d->PrevLocations.prevloc[j]; + + flDist = GetDistance(d->o, v); + + if ((flDist < flNearestOldPosDistToBot) && IsReachable(v)) + { + flNearestOldPosDistToEnemy = flDist; + bestfromenemy = v; + } + } + + // Check previous locations of bot hisself + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = m_pMyEnt->PrevLocations.prevloc[j]; + + flDist = GetDistance(v); + + if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && + IsReachable(v)) + { + flNearestOldPosDistToBot = flDist; + bestfrombot = v; + } + } + + if (!vis(bestfromenemy, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfromenemy; + } + else if (!vis(bestfrombot, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfrombot; + } + } + } */ + //else + //{ + // First loop through all players + loopv(players) + { + d = players[i]; // Handy shortcut + + if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) + continue; + + flDist = GetDistance(d->o); + + if (flDist > 250.0f) continue; + + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (d == m_pPrevEnemy) + EnemyVal+=2; + + if (EnemyVal < BestEnemyVal) continue; + + vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; + flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; + + // Check previous locations of enemy + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = d->PrevLocations.prevloc[j]; + + flDist = GetDistance(d->o, v); + + if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) + { + flNearestOldPosDistToEnemy = flDist; + bestfromenemy = v; + } + } + + // Check previous locations of bot hisself + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = m_pMyEnt->PrevLocations.prevloc[j]; + + flDist = GetDistance(v); + + if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && + IsReachable(v)) + { + flNearestOldPosDistToBot = flDist; + bestfrombot = v; + } + } + + if (!vis(bestfromenemy, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfromenemy; + } + else if (!vis(bestfrombot, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfrombot; + } + //} + + // Then loop through all bots + loopv(bots) + { + d = bots[i]; // Handy shortcut + + if (d == m_pMyEnt) + continue; + + if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) + continue; + + flDist = GetDistance(d->o); + + if (flDist > 250.0f) continue; + + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (d == m_pPrevEnemy) + EnemyVal+=2; + + if (EnemyVal < BestEnemyVal) continue; + + vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; + flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; + + // Check previous locations of enemy + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = d->PrevLocations.prevloc[j]; + + flDist = GetDistance(d->o, v); + + if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) + { + flNearestOldPosDistToEnemy = flDist; + bestfromenemy = v; + } + } + + // Check previous locations of bot hisself + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = m_pMyEnt->PrevLocations.prevloc[j]; + + flDist = GetDistance(v); + + if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && + IsReachable(v)) + { + flNearestOldPosDistToBot = flDist; + bestfrombot = v; + } + } + + if (!vis(bestfromenemy, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfromenemy; + } + else if (!vis(bestfrombot, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfrombot; + } + } + + // Then examine the local player + if (player1 && !isteam(player1->team, m_pMyEnt->team) && + (player1->state == CS_ALIVE) && ((flDist = GetDistance(player1->o)) <= 250.0f)) + { + d = player1; + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (d == m_pPrevEnemy) + EnemyVal+=2; + + if (EnemyVal >= BestEnemyVal) + { + BestEnemyVal = EnemyVal; + vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; + flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; + + // Check previous locations of enemy + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = d->PrevLocations.prevloc[j]; + + flDist = GetDistance(d->o, v); + + if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) + { + flNearestOldPosDistToEnemy = flDist; + bestfromenemy = v; + } + } + + // Check previous locations of bot hisself + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = m_pMyEnt->PrevLocations.prevloc[j]; + + flDist = GetDistance(v); + + if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && + IsReachable(v)) + { + flNearestOldPosDistToBot = flDist; + bestfrombot = v; + } + } + + if (!vis(bestfromenemy, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfromenemy; + } + else if (!vis(bestfrombot, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfrombot; + } + } + } + } + + if (pNewEnemy) + { + if (!m_pHuntTarget) // Add shoot delay if new enemy is found + { + float flMinShootDelay = m_pBotSkill->flMinAttackDelay; + float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; + + m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay, + flMaxShootDelay) * 1000.0f); + } + + if (!vis(m_vHuntLocation, g_vecZero)) + m_vPrevHuntLocation = m_vHuntLocation; + + m_pHuntTarget = pNewEnemy; + m_vHuntLocation = BestOldPos; + m_fPrevHuntDist = 0.0f; + return true; + } + + return false; +} + +bool CBot::HuntEnemy(void) +{ + if (m_iCombatNavTime > lastmillis) + { + SetMoveDir(m_iMoveDir, false); + return true; + } + + m_iCombatNavTime = m_iMoveDir = 0; + + bool bDone = false, bNew = false; + float flDist = GetDistance(m_vHuntLocation); + + if (flDist <= 3.0f) + bDone = true; + + if ((m_fPrevHuntDist > 0.0) && (flDist > m_fPrevHuntDist)) + bDone = true; + + m_fPrevHuntDist = flDist; + + if ((m_iHuntPlayerUpdateTime < lastmillis) || bDone) + { + m_iHuntPlayerUpdateTime = lastmillis + 1250; + + short BestPosIndexFromEnemy = -1, BestPosIndexFromBot = -1, j; + float NearestDistToEnemy = 9999.9f, NearestDistToBot = 9999.9f; + + // Check previous locations of enemy + for (j=0;jPrevLocations.prevloc[j]; + + if (bDone && vis(m_vHuntLocation, OldPos)) + continue; + + if (vis(OldPos, g_vecZero)) + continue; + + if (GetDistance(OldPos) > 250.0f) + continue; + + flDist = GetDistance(m_pHuntTarget->o, OldPos); + + if ((flDist < NearestDistToEnemy) && (IsReachable(OldPos))) + { + NearestDistToEnemy = flDist; + BestPosIndexFromEnemy = j; + break; + } + } + + // Check previous locations of bot + for (j=0;jPrevLocations.prevloc[j]; + + if (bDone && vis(m_vHuntLocation, OldPos)) + continue; + + if (vis(OldPos, g_vecZero)) + continue; + + if (GetDistance(OldPos) > 25.0f) + continue; + + flDist = GetDistance(OldPos); + + if ((flDist < NearestDistToBot) && ::IsVisible(m_pHuntTarget->o, OldPos) && + (IsReachable(OldPos))) + { + NearestDistToBot = flDist; + BestPosIndexFromBot = j; + break; + } + } + + if (BestPosIndexFromEnemy > -1) + { + m_vPrevHuntLocation = m_vHuntLocation; + m_vHuntLocation = m_pHuntTarget->PrevLocations.prevloc[BestPosIndexFromEnemy]; + bNew = true; + m_fPrevHuntDist = 0.0f; + } + else if (BestPosIndexFromBot > -1) + { + m_vPrevHuntLocation = m_vHuntLocation; + m_vHuntLocation = m_pMyEnt->PrevLocations.prevloc[BestPosIndexFromBot]; + bNew = true; + m_fPrevHuntDist = 0.0f; + } + } + + if (!bNew) // Check if current location is still reachable + { + if (bDone || !IsReachable(m_vHuntLocation)) + { + m_pHuntTarget = NULL; + m_vPrevHuntLocation = m_vHuntLocation; + m_vHuntLocation = g_vecZero; + m_fPrevHuntDist = 0.0f; + m_iHuntDelay = lastmillis + 3500; + return false; + } + } + else + condebug("New hunt pos"); + + // Aim to position + //AimToVec(m_vHuntLocation); + + int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, m_vHuntLocation); + + if (iMoveDir != DIR_NONE) + { + m_iMoveDir = iMoveDir; + m_iCombatNavTime = lastmillis + 125; + } + + bool aimtopos = true; + + if ((lastmillis - m_iSawEnemyTime) > 1500) + { + if (m_iLookAroundDelay < lastmillis) + { + if (m_iLookAroundTime > lastmillis) + { + if (m_iLookAroundUpdateTime < lastmillis) + { + float flAddAngle; + if (m_bLookLeft) flAddAngle = RandomFloat(-110, -80); + else flAddAngle = RandomFloat(80, 110); + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->targetyaw + flAddAngle); + m_iLookAroundUpdateTime = lastmillis + RandomLong(400, 800); + } + aimtopos = false; + } + else if (m_iLookAroundTime > 0) + { + m_iLookAroundTime = 0; + m_iLookAroundDelay = lastmillis + RandomLong(750, 1000); + } + else + m_iLookAroundTime = lastmillis + RandomLong(2200, 3200); + } + } + + if (aimtopos) + AimToVec(m_vHuntLocation); + + debugbeam(m_pMyEnt->o, m_vHuntLocation); + + if (m_fYawToTurn <= 25.0f) + m_iHuntLastTurnLessTime = lastmillis; + + // Bot had to turn much for a while? + if ((m_iHuntLastTurnLessTime > 0) && (m_iHuntLastTurnLessTime < (lastmillis - 1000))) + { + m_iHuntPauseTime = lastmillis + 200; + } + + if (m_iHuntPauseTime >= lastmillis) + { + m_pMyEnt->move = 0; + m_fPrevHuntDist = 0.0f; + } + else + { + // Check if bot has to jump over a wall... + if (CheckJump()) + m_pMyEnt->jumpnext = true; + else // Check if bot has to jump to reach this location + { + float flHeightDiff = m_vHuntLocation.z - m_pMyEnt->o.z; + bool bToHigh = false; + if (Get2DDistance(m_vHuntLocation) <= 2.0f) + { + if (flHeightDiff >= 1.5f) + { + if (flHeightDiff <= JUMP_HEIGHT) + { +#ifndef RELEASE_BUILD + char sz[64]; + sprintf(sz, "OldPos z diff: %f", m_vHuntLocation.z-m_pMyEnt->o.z); + condebug(sz); +#endif + // Jump if close to pos and the pos is high + m_pMyEnt->jumpnext = true; + } + else + bToHigh = true; + } + } + + if (bToHigh) + { + m_pHuntTarget = NULL; + m_vPrevHuntLocation = m_vHuntLocation; + m_vHuntLocation = g_vecZero; + m_fPrevHuntDist = 0.0f; + m_iHuntDelay = lastmillis + 3500; + return false; + } + } + } + + return true; +} + +void CBot::ShootEnemy() +{ + if (!m_pMyEnt->enemy) + return; + + m_iSawEnemyTime = lastmillis; + + // Aim to enemy + vec enemypos = GetEnemyPos(m_pMyEnt->enemy); + AimToVec(enemypos); + + // Time to shoot? + if (m_iShootDelay < lastmillis) + //if ((lastmillis-m_pMyEnt->lastaction) >= m_pMyEnt->gunwait) + { + if (m_pMyEnt->ammo[m_pMyEnt->gunselect]) + { + // If the bot is using a sniper only shoot if crosshair is near the enemy + if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_SNIPER) + { + float yawtoturn = fabs(WrapYZAngle(m_pMyEnt->yaw - m_pMyEnt->targetyaw)); + float pitchtoturn = fabs(WrapYZAngle(m_pMyEnt->pitch - m_pMyEnt->targetpitch)); + + if ((yawtoturn > 5) || (pitchtoturn > 15)) // UNDONE: Should be skill based + return; + } + + float flDist = GetDistance(enemypos); + + // Check if bot is in fire range + if ((flDist < WeaponInfoTable[m_pMyEnt->gunselect].flMinFireDistance) || + (flDist > WeaponInfoTable[m_pMyEnt->gunselect].flMaxFireDistance)) + return; + + // Now shoot! + m_pMyEnt->attacking = true; + + // Get the position the bot is aiming at + vec forward, right, up, dest; + traceresult_s tr; + + AnglesToVectors(GetViewAngles(), forward, right, up); + + dest = m_pMyEnt->o; + vmul(forward, 1000); + vadd(dest, forward); + + TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr); + debugbeam(m_pMyEnt->o, tr.end); + + // Shoot + shoot(m_pMyEnt, tr.end); + + // Add shoot delay + m_iShootDelay = lastmillis + GetShootDelay(); + } + ChoosePreferredWeapon(); + } +#ifndef RELEASE_BUILD + else + { + char sz[64]; + sprintf(sz, "shootdelay: %d\n", (m_iShootDelay-lastmillis)); + AddDebugText(sz); + } +#endif +} + +bool CBot::ChoosePreferredWeapon() +{ + TMultiChoice WeaponChoices; + short sWeaponScore; + float flDist = GetDistance(m_pMyEnt->enemy->o); + + if ((m_iChangeWeaponDelay > lastmillis) && (m_pMyEnt->ammo[m_pMyEnt->gunselect])) + { + if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE) || (flDist <= 3.5f)) + return true; + } + + // Choose a weapon + for(int i=0;iammo[i] == 0) continue; + + sWeaponScore = 5; // Minimal score for a weapon + + if ((flDist >= WeaponInfoTable[i].flMinDesiredDistance) && + (flDist <= WeaponInfoTable[i].flMaxDesiredDistance)) + { + // In desired range for this weapon + sWeaponScore += 5; // Increase score much + } + else if ((flDist < WeaponInfoTable[i].flMinFireDistance) || + (flDist > WeaponInfoTable[i].flMaxFireDistance)) + continue; // Wrong distance for this weapon + + // The ideal distance would be between the Min and Max desired distance. + // Score on the difference of the avarage of the Min and Max desired distance. + float flAvarage = (WeaponInfoTable[i].flMinDesiredDistance + + WeaponInfoTable[i].flMaxDesiredDistance) / 2.0f; + float flIdealDiff = fabs(flDist - flAvarage); + + if (flIdealDiff < 0.5f) // Close to ideal distance + sWeaponScore += 4; + else if (flIdealDiff <= 1.0f) + sWeaponScore += 2; + + // Now rate the weapon on available ammo... + if (WeaponInfoTable[i].sMinDesiredAmmo > 0) + { + // Calculate how much percent of the min desired ammo the bot has + float flDesiredPercent = (float(m_pMyEnt->ammo[i]) / + float(WeaponInfoTable[i].sMinDesiredAmmo)) * + 100.0f; + + if (flDesiredPercent >= 400.0f) + sWeaponScore += 4; + else if (flDesiredPercent >= 200.0f) + sWeaponScore += 3; + else if (flDesiredPercent >= 100.0f) + sWeaponScore += 1; + } + + WeaponChoices.Insert(i, sWeaponScore); + } + + int WeaponSelect; + if (WeaponChoices.GetSelection(WeaponSelect)) + { + m_iChangeWeaponDelay = lastmillis + RandomLong(2000, 8000); + m_bShootAtFeet = ((WeaponInfoTable[WeaponSelect].eWeaponType==TYPE_ROCKET) && + (RandomLong(1, 100) <= + m_pBotSkill->sShootAtFeetWithRLPercent)); + return SelectGun(WeaponSelect); + } + + return false; +} + +int CBot::GetShootDelay() +{ + // UNDONE + if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) || + (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_AUTO)) + return m_pMyEnt->gunwait; + + float flMinShootDelay = m_pBotSkill->flMinAttackDelay; + float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; + return max(m_pMyEnt->gunwait, int(RandomFloat(flMinShootDelay, flMaxShootDelay) * 1000.0f)); +} + +void CBot::MainAI() +{ + // Default bots will run forward + m_pMyEnt->move = 1; + + // Default bots won't strafe + m_pMyEnt->strafe = 0; + + if (!BotManager.BotsShoot() && m_pMyEnt->enemy) + m_pMyEnt->enemy = NULL; // Clear enemy when bots may not shoot + + if (m_bGoToDebugGoal) // For debugging the waypoint navigation + { + if (!HeadToGoal()) + { + ResetWaypointVars(); + m_vGoal = g_vecZero; + } + else + AddDebugText("Heading to debug goal..."); + } + if (BotManager.BotsShoot() && FindEnemy()) // Combat + { + AddDebugText("has enemy"); + + // Shoot at enemy + ShootEnemy(); + + if (m_eCurrentBotState != STATE_ENEMY) + { + m_vGoal = g_vecZero; + ResetWaypointVars(); + } + + m_eCurrentBotState = STATE_ENEMY; + if (!CheckJump()) + DoCombatNav(); + } + else if (CheckHunt() && HuntEnemy()) + { + AddDebugText("Hunting to %s", m_pHuntTarget->name); + m_eCurrentBotState = STATE_HUNT; + } + // Heading to an interesting entity(ammo, armour etc) + else if (CheckItems()) + { + AddDebugText("has ent"); + m_eCurrentBotState = STATE_ENT; + } + else if (m_classicsp && DoSPStuff()) // Home to goal, find/follow friends etc. + { + AddDebugText("SP stuff"); + m_eCurrentBotState = STATE_SP; + } + else // Normal navigation + { + if (m_eCurrentBotState != STATE_NORMAL) + { + m_vGoal = g_vecZero; + ResetWaypointVars(); + } + + m_eCurrentBotState = STATE_NORMAL; + bool bDoNormalNav = true; + + AddDebugText("normal nav"); + + // Make sure the bot looks straight forward and not up or down + m_pMyEnt->pitch = 0; + + // if it is time to look for a waypoint AND if there are waypoints in this + // level... + if (WaypointClass.m_iWaypointCount >= 1) + { + // check if we need to find a waypoint... + if (CurrentWPIsValid() == false) + { + if (m_iLookForWaypointTime <= lastmillis) + { + // find the nearest reachable waypoint + waypoint_s *pWP = GetNearestWaypoint(10.0f); + + if (pWP && (pWP != m_pCurrentWaypoint)) + { + SetCurrentWaypoint(pWP); + condebug("New nav wp"); + bDoNormalNav = !HeadToWaypoint(); + if (bDoNormalNav) + ResetWaypointVars(); + } + else + ResetWaypointVars(); + + m_iLookForWaypointTime = lastmillis + 250; + } + } + else + { + bDoNormalNav = !HeadToWaypoint(); + if (bDoNormalNav) + ResetWaypointVars(); + AddDebugText("Using wps for nav"); + } + } + + // If nothing special, do regular (waypointless) navigation + if(bDoNormalNav) + { + // Is the bot underwater? + if (UnderWater(m_pMyEnt->o) && WaterNav()) + { + // Bot is under water, navigation happens in WaterNav + } + // Time to check the environment? + else if (m_iCheckEnvDelay < lastmillis) + { + if (!vis(m_vWaterGoal, g_vecZero)) m_vWaterGoal = g_vecZero; + + // Check for stuck and strafe + if (UnderWater(m_pMyEnt->o) || !CheckStuck()) + { + // Only do this when the bot is underwater or when the bot isn't stuck + + // Check field of view (FOV) + CheckFOV(); + } + } + + // Check if the bot has to strafe + CheckStrafe(); + + m_pMyEnt->move = 1; + } + } +} + +void CBot::DoCombatNav() +{ + if (m_iCombatNavTime > lastmillis) + { + // If bot has a lower skill and has to turn much, wait + if ((m_sSkillNr > 2) && (m_fYawToTurn > 90.0f)) + { + ResetMoveSpeed(); + } + else + { + SetMoveDir(m_iMoveDir, false); + } + return; + } + + if (m_bCombatJump) + { + m_pMyEnt->jumpnext = true; + m_bCombatJump = false; + m_iCombatJumpDelay = lastmillis + RandomLong(1500, 2800); + return; + } + + m_iMoveDir = DIR_NONE; + + // Check if bot is on top of his enemy + float r = m_pMyEnt->radius+m_pMyEnt->enemy->radius; + if ((fabs(m_pMyEnt->enemy->o.x-m_pMyEnt->o.x)enemy->o.y-m_pMyEnt->o.y)enemy->o.z+m_pMyEnt->enemy->aboveeye) < (m_pMyEnt->o.z + m_pMyEnt->aboveeye))) + { + // Try to get off him! + condebug("On enemy!"); + TMultiChoice AwayDirChoices; + + if (IsVisible(LEFT, 4.0f, false)) + AwayDirChoices.Insert(LEFT); + if (IsVisible(RIGHT, 4.0f, false)) + AwayDirChoices.Insert(RIGHT); + if (IsVisible(FORWARD, 4.0f, false)) + AwayDirChoices.Insert(FORWARD); + if (IsVisible(BACKWARD, 4.0f, false)) + AwayDirChoices.Insert(BACKWARD); + + int iDir; + if (AwayDirChoices.GetSelection(iDir)) + { + m_iMoveDir = iDir; + m_iCombatNavTime = lastmillis + 500; + } + } + + float flDist = GetDistance(m_pMyEnt->enemy->o); + + // Check for nearby items? + if (((m_iCheckEntsDelay < lastmillis) || m_pTargetEnt) && + m_pBotSkill->bCanSearchItemsInCombat) + { + m_iCheckEntsDelay = lastmillis + 125; + bool bSearchItems = false; + + if (m_pTargetEnt) + { + // Bot has already found an entity, still valid? + vec v = { m_pTargetEnt->x, m_pTargetEnt->y, + S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; + if ((GetDistance(v) > 25.0f) || !IsVisible(m_pTargetEnt)) + m_pTargetEnt = NULL; + } + + if (!m_pTargetEnt && (m_iCheckEntsDelay <= lastmillis)) + { + if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) + bSearchItems = (flDist >= 8.0f); + else + bSearchItems = (m_pMyEnt->ammo[m_pMyEnt->gunselect] <= + WeaponInfoTable[m_pMyEnt->gunselect].sMinDesiredAmmo); + + if (bSearchItems) + m_pTargetEnt = SearchForEnts(false, 25.0f, 1.0f); + } + + if (m_pTargetEnt) + { + condebug("Combat ent"); + vec v = { m_pTargetEnt->x, m_pTargetEnt->y, + S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; + + debugbeam(m_pMyEnt->o, v); + + float flHeightDiff = v.z - m_pMyEnt->o.z; + bool bToHigh = false; + + // Check he height for this ent + if (Get2DDistance(v) <= 2.0f) + { + if (flHeightDiff >= 1.5f) + { + if (flHeightDiff <= JUMP_HEIGHT) + { +#ifndef RELEASE_BUILD + char sz[64]; + sprintf(sz, "Ent z diff: %f", v.z-m_pMyEnt->o.z); + condebug(sz); +#endif + m_pMyEnt->jumpnext = true; // Jump if close to ent and the ent is high + } + else + bToHigh = true; + } + } + + if (!bToHigh) + { + int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, v); + + if (iMoveDir != DIR_NONE) + { + m_iMoveDir = iMoveDir; + m_iCombatNavTime = lastmillis + RandomLong(125, 250); + } + + // Check if bot needs to jump over something + vec from = m_pMyEnt->o; + from.z -= 1.0f; + if (!IsVisible(from, iMoveDir, 3.0f, false)) + m_pMyEnt->jumpnext = true; + + return; + } + } + } + + // High skill and enemy is close? + if ((m_sSkillNr <= 1) && (m_fYawToTurn < 80.0f) && (flDist <= 20.0f) && + (m_iCombatJumpDelay < lastmillis)) + { + // Randomly jump a bit, to avoid some basic firepower ;) + + // How lower the distance to the enemy, how higher the chance for a jump + short sJumpPercent = (100 - ((short)flDist * 8)); + if (RandomLong(1, 100) <= sJumpPercent) + { + // Choose a nice direction to jump to + + // Is the enemy close? + if ((GetDistance(m_pMyEnt->enemy->o) <= 4.0f) || + (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE)) + { + m_iMoveDir = FORWARD; // Jump forward + SetMoveDir(FORWARD, false); + m_bCombatJump = true; + } + else if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE)// else jump to a random direction + { + /* + Directions to choose: + - Forward-right + - Right + - Backward-right + - Backward + - Backward-left + - Left + - Forward-left + + */ + + TMultiChoice JumpDirChoices; + short sForwardScore = ((flDist > 8.0f) || (flDist < 4.0f)) ? 20 : 10; + short sBackwardScore = (flDist <= 6.0f) ? 20 : 10; + short sStrafeScore = (flDist < 6.0f) ? 20 : 10; + + if (IsVisible((FORWARD | LEFT), 4.0f, false)) + JumpDirChoices.Insert((FORWARD | LEFT), sForwardScore); + if (IsVisible((FORWARD | RIGHT), 4.0f, false)) + JumpDirChoices.Insert((FORWARD | RIGHT), sForwardScore); + if (IsVisible(BACKWARD, 4.0f, false)) + JumpDirChoices.Insert(BACKWARD, sBackwardScore); + if (IsVisible((BACKWARD | LEFT), 4.0f, false)) + JumpDirChoices.Insert((BACKWARD | LEFT), sBackwardScore); + if (IsVisible((BACKWARD | RIGHT), 4.0f, false)) + JumpDirChoices.Insert((BACKWARD | RIGHT), sBackwardScore); + if (IsVisible(LEFT, 4.0f, false)) + JumpDirChoices.Insert(LEFT, sStrafeScore); + if (IsVisible(RIGHT, 4.0f, false)) + JumpDirChoices.Insert(RIGHT, sStrafeScore); + + int JumpDir; + if (JumpDirChoices.GetSelection(JumpDir)) + { + m_iMoveDir = JumpDir; + SetMoveDir(JumpDir, false); + m_bCombatJump = true; + } + } + + if (m_bCombatJump) + { + m_iCombatNavTime = lastmillis + RandomLong(125, 250); + return; + } + } + } + + if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) + return; // Simply walk towards enemy if using a melee type + + flDist = Get2DDistance(m_pMyEnt->enemy->o); + + // Out of desired range for current weapon? + if ((flDist <= WeaponInfoTable[m_pMyEnt->gunselect].flMinDesiredDistance) || + (flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance)) + { + if (flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance) + { + m_iMoveDir = FORWARD; + } + else + { + m_iMoveDir = BACKWARD; + } + + vec src, forward, right, up, dest, MyAngles = GetViewAngles(), o = m_pMyEnt->o; + traceresult_s tr; + + // Is it furthest or farthest? bleh + float flFurthestDist = 0; + int bestdir = -1, dir = 0; + bool moveback = (m_pMyEnt->move == -1); + + for(int j=-45;j<=45;j+=45) + { + src = MyAngles; + src.y = WrapYZAngle(src.y + j); + src.x = 0.0f; + + // If we're moving backwards, trace backwards + if (moveback) + src.y = WrapYZAngle(src.y + 180); + + AnglesToVectors(src, forward, right, up); + + dest = o; + vmul(forward, 40); + vadd(dest, forward); + + TraceLine(o, dest, m_pMyEnt, false, &tr); + + //debugbeam(origin, end); + flDist = GetDistance(tr.end); + + if (flFurthestDist < flDist) + { + flFurthestDist = flDist; + bestdir = dir; + } + dir++; + } + + switch(bestdir) + { + case 0: + if (moveback) + m_iMoveDir |= RIGHT; // Strafe right + else + m_iMoveDir |= LEFT; // Strafe left + break; + case 2: + if (moveback) + m_iMoveDir |= LEFT; // Strafe left + else + m_iMoveDir |= RIGHT; // Strafe right + break; + } + + if (m_iMoveDir != DIR_NONE) + { + SetMoveDir(m_iMoveDir, false); + m_iCombatNavTime = lastmillis + 500; + } + } + else if (m_pBotSkill->bCircleStrafe) // Circle strafe when in desired range... + { + traceresult_s tr; + vec angles, end, forward, right, up; + TMultiChoice StrafeDirChoices; + + // Check the left side... + angles = GetViewAngles(); + angles.y = WrapYZAngle(angles.y - 75.0f); // Not 90 degrees because the bot + // doesn't strafe in a straight line + // (aims still to enemy). + + AnglesToVectors(angles, forward, right, up); + end = m_pMyEnt->o; + vmul(forward, 15.0f); + vadd(end, forward); + + TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr); + StrafeDirChoices.Insert(LEFT, (int)GetDistance(m_pMyEnt->o, tr.end)); + + // Check the right side... + angles = GetViewAngles(); + angles.y = WrapYZAngle(angles.y + 75.0f); // Not 90 degrees because the bot + // doesn't strafe in a straight line + // (aims still to enemy). + + AnglesToVectors(angles, forward, right, up); + end = m_pMyEnt->o; + vmul(forward, 15.0f); + vadd(end, forward); + + TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr); + StrafeDirChoices.Insert(RIGHT, (int)GetDistance(m_pMyEnt->o, tr.end)); + + int StrafeDir; + if (StrafeDirChoices.GetSelection(StrafeDir)) + { + m_iMoveDir = StrafeDir; + SetMoveDir(StrafeDir, false); + m_iCombatNavTime = lastmillis + RandomLong(1500, 3000); + } + } + else // Bot can't circle strafe(low skill), just stand still + ResetMoveSpeed(); +} + +bool CBot::CheckStuck() +{ + if (m_iStuckCheckDelay >= lastmillis) + return false; + + if ((!vis(m_vGoal, g_vecZero)) && (GetDistance(m_vGoal) < 2.0f)) + return false; + + bool IsStuck = false; + + vec CurPos = m_pMyEnt->o, PrevPos = m_vPrevOrigin; + CurPos.z = PrevPos.z = 0; + // Did the bot hardly move the last frame? + if (GetDistance(CurPos, PrevPos) <= 0.1f) + { + if (m_bStuck) + { + if (m_iStuckTime < lastmillis) + IsStuck = true; + } + else + { + m_bStuck = true; + m_iStuckTime = lastmillis + 1000; + } + } + else + { + m_bStuck = false; + m_iStuckTime = 0; + } + + if (IsStuck) + { +#ifndef RELEASE_BUILD + char msg[64]; + sprintf(msg, "stuck (%f)", GetDistance(m_vPrevOrigin)); + condebug(msg); +#endif + + m_bStuck = false; + m_iStuckTime = 0; + + // Crap bot is stuck, lets just try some random things + + // Check if the bot can turn around + vec src = GetViewAngles(); + src.x = 0; + vec forward, right, up, dir, dest; + traceresult_s tr; + + AnglesToVectors(src, forward, right, up); + + // Check the left side... + dir = right; + dest = m_pMyEnt->o; + vmul(dir, 3); + vsub(dest, dir); + + TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr); + //debugbeam(m_pMyEnt->o, end); + + if (!tr.collided) + { + // Bot can turn left, do so + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw - 90); + m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; + return true; + } + + // Check the right side... + dir = right; + dest = m_pMyEnt->o; + vmul(dir, 3); + vadd(dest, dir); + + TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr); + //debugbeam(m_pMyEnt->o, end); + + if (!tr.collided) + { + // Bot can turn right, do so + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 90); + m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; + return true; + } + + // Check if bot can turn 180 degrees + dir = forward; + dest = m_pMyEnt->o; + vmul(dir, 3); + vsub(dest, dir); + + TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr); + //debugbeam(m_pMyEnt->o, end); + + if (!tr.collided) + { + // Bot can turn around, do so + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180); + m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; + return true; + } + + // Bleh bot couldn't turn, lets just randomly jump :| + + condebug("Randomly avoiding stuck..."); + if (RandomLong(0, 2) == 0) + m_pMyEnt->jumpnext = true; + else + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomLong(60, 160)); + return true; + } + + return false; +} + +// Check if a near wall is blocking and we can jump over it +bool CBot::CheckJump() +{ + bool bHasGoal = (!vis(m_vGoal, g_vecZero)); + float flGoalDist = (bHasGoal) ? GetDistance(m_vGoal) : 0.0f; + +// if ((bHasGoal) && (flGoalDist < 2.0f)) + // return false; UNDONE? + + vec start = m_pMyEnt->o; + float flTraceDist = 3.0f; + + if (bHasGoal && (flGoalDist < flTraceDist)) + flTraceDist = flGoalDist; + + // Something blocks at eye hight? + if (!IsVisible(start, FORWARD, flTraceDist, false)) + { + // Check if the bot can jump over it + start.z += (JUMP_HEIGHT - 1.0f); + if (IsVisible(start) && !IsVisible(start, FORWARD, flTraceDist, false)) + { + // Jump + debugnav("High wall"); + m_pMyEnt->jumpnext = true; + return true; + } + } + else + { + // Check if something is blocking at feet height, so the bot can jump over it + start.z -= 1.7f; + + // Trace was blocked? + if (!IsVisible(start, FORWARD, flTraceDist, false)) + { + //debugbeam(start, end); + + // Jump + debugnav("Low wall"); + m_pMyEnt->jumpnext = true; + return true; + } + } + + return false; // Bot didn't had to jump(or couldn't) +} + +bool CBot::CheckStrafe() +{ + if (m_iStrafeTime >= lastmillis) + { + SetMoveDir(m_iMoveDir, true); + return true; + } + + if (m_iStrafeCheckDelay >= lastmillis) + return false; + + // Check for near walls + traceresult_s tr; + vec from = m_pMyEnt->o, to, forward, right, up, dir; + float flLeftDist = -1.0f, flRightDist = -1.0f; + bool bStrafe = false; + int iStrafeDir = DIR_NONE; + + AnglesToVectors(GetViewAngles(), forward, right, up); + + // Check for a near left wall + to = from; + dir = right; + vmul(dir, 3.0f); + vsub(to, dir); + TraceLine(from, to, m_pMyEnt, false, &tr); + if (tr.collided) + flLeftDist = GetDistance(from, tr.end); + //debugbeam(m_pMyEnt->o, to); + + // Check for a near right wall + to = from; + dir = right; + vmul(dir, 3.0f); + vadd(to, dir); + TraceLine(from, to, m_pMyEnt, false, &tr); + if (tr.collided) + flRightDist = GetDistance(from, tr.end); + //debugbeam(m_pMyEnt->o, to); + + if ((flLeftDist == -1.0f) && (flRightDist == -1.0f)) + { + dir = right; + vmul(dir, m_pMyEnt->radius); + + // Check left + from = m_pMyEnt->o; + vsub(from, dir); + if (IsVisible(from, FORWARD, 3.0f, false, &flLeftDist)) + flLeftDist = -1.0f; + + // Check right + from = m_pMyEnt->o; + vadd(from, dir); + if (IsVisible(from, FORWARD, 3.0f, false, &flRightDist)) + flRightDist = -1.0f; + } + + if ((flLeftDist != -1.0f) && (flRightDist != -1.0f)) + { + if (flLeftDist < flRightDist) + { + // Strafe right + bStrafe = true; + iStrafeDir = RIGHT; + } + else if (flRightDist < flLeftDist) + { + // Strafe left + bStrafe = true; + iStrafeDir = LEFT; + } + else + { + // Randomly choose a strafe direction + bStrafe = true; + if (RandomLong(0, 1)) + iStrafeDir = LEFT; + else + iStrafeDir = RIGHT; + } + } + else if (flLeftDist != -1.0f) + { + // Strafe right + bStrafe = true; + iStrafeDir = RIGHT; + } + else if (flRightDist != -1.0f) + { + // Strafe left + bStrafe = true; + iStrafeDir = LEFT; + } + + if (bStrafe) + { + SetMoveDir(iStrafeDir, true); + m_iMoveDir = iStrafeDir; + m_iStrafeTime = lastmillis + RandomLong(75, 150); + } + + return bStrafe; +} + +void CBot::CheckFOV() +{ + m_iCheckEnvDelay = lastmillis + RandomLong(125, 250); + vec MyAngles = GetViewAngles(); + vec src, forward, right, up, dest, best = {0,0,0}; + vec origin = m_pMyEnt->o; + float flDist, flFurthestDist = 0; + bool WallLeft = false; + traceresult_s tr; + + //origin.z -= 1.5; // Slightly under eye level + + // Scan 90 degrees FOV + for(int angle=-45;angle<=45;angle+=5) + { + src = MyAngles; + src.y = WrapYZAngle(src.y + angle); + + AnglesToVectors(src, forward, right, up); + + dest = origin; + vmul(forward, 40); + vadd(dest, forward); + + TraceLine(origin, dest, m_pMyEnt, false, &tr); + + //debugbeam(origin, end); + flDist = GetDistance(tr.end); + + if (flFurthestDist < flDist) + { + flFurthestDist = flDist; + best = tr.end; + } + } + + if (best.x && best.y && best.z) + { + AimToVec(best); + // Update MyAngles, since their (going to be) change(d) + MyAngles.x = m_pMyEnt->targetpitch; + MyAngles.y = m_pMyEnt->targetyaw; + } + + float flNearestHitDist = GetDistance(best); + + if (!UnderWater(m_pMyEnt->o) && m_pMyEnt->onfloor) + { + // Check if a near wall is blocking and we can jump over it + if (flNearestHitDist < 4) + { + // Check if the bot can jump over it + src = MyAngles; + src.x = 0; + + AnglesToVectors(src, forward, right, up); + + vec start = origin; + start.z += 2.0f; + dest = start; + vmul(forward, 6); + vadd(dest, forward); + + TraceLine(start, dest, m_pMyEnt, false, &tr); + //debugbeam(start, end); + + if (!tr.collided) + { + // Jump + debugnav("High wall"); + m_pMyEnt->jumpnext = true; + m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500); + return; + } + } + else + { + // Check if something is blocking below us, so the bot can jump over it + src = MyAngles; + src.x = 0; + + AnglesToVectors(src, forward, right, up); + + vec start = origin; + start.z -= 1.7f; + dest = start; + vmul(forward, 4); + vadd(dest, forward); + + TraceLine(start, dest, m_pMyEnt, false, &tr); + + // Trace was blocked? + if (tr.collided) + { + //debugbeam(start, end); + + // Jump + debugnav("Low wall"); + m_pMyEnt->jumpnext = true; + m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500); + return; + } + } + + // Check if the bot is going to fall... + src = MyAngles; + src.x = 0.0f; + AnglesToVectors(src, forward, right, up); + + dest = origin; + vmul(forward, 3.0f); + vadd(dest, forward); + + TraceLine(origin, dest, m_pMyEnt, false, &tr); + + int cx = fast_f2nat(tr.end.x), cy = fast_f2nat(tr.end.y); + short CubesInWater=0; + for(int x=cx-1;x<=(cx+1);x++) + { + for(int y=cy-1;y<=(cy+1);y++) + { + if (OUTBORD(x, y)) continue; + //sqr *s = S(fast_f2nat(x), fast_f2nat(y)); + //if (!SOLID(s)) + { + vec from = { x, y, m_pMyEnt->o.z }; + dest = from; + dest.z -= 6.0f; + TraceLine(from, dest, m_pMyEnt, false, &tr); + bool turn = false; + if (UnderWater(tr.end)) CubesInWater++; + if (CubesInWater > 2) turn = true; // Always avoid water + if (!tr.collided && RandomLong(0, 1)) + turn = true; // Randomly avoid a fall + + if (turn) + { + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180); + m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500); + debugnav("Water or a fall in front"); + return; + } + } + } + } + } + + // Is the bot about to head a corner? + if (flNearestHitDist <= 4.0f) + { + src = MyAngles; + src.y = WrapYZAngle(src.y - 45.0f); + AnglesToVectors(src, forward, right, up); + + dest = origin; + vmul(forward, 4.0f); + vadd(dest, forward); + + TraceLine(origin, dest, m_pMyEnt, false, &tr); + + WallLeft = (tr.collided); + + src = MyAngles; + src.y += WrapYZAngle(src.y + 45.0f); + AnglesToVectors(src, forward, right, up); + + dest = origin; + vmul(forward, 4.0f); + vadd(dest, forward); + + TraceLine(origin, dest, m_pMyEnt, false, &tr); + + if (WallLeft && tr.collided) + { + // We're about to hit a corner, turn away + debugnav("Corner"); + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomFloat(160.0f, 200.0f)); + m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500); + return; + } + } +} + +// Called when bot is underwater +bool CBot::WaterNav() +{ + const int iSearchRange = 4; + + if (vis(m_vWaterGoal, g_vecZero)) + { + AddDebugText("WaterNav"); + // Find the nearest and reachable cube which isn't underwater + + int cx = fast_f2nat(m_pMyEnt->o.x); + int cy = fast_f2nat(m_pMyEnt->o.y); + float flNearestDist = 9999.0f, flDist; + + if (OUTBORD(cx, cy)) return false; + + // Check all cubes in range... + for (int x=(cx-iSearchRange);x<=(cx+iSearchRange);x++) + { + for (int y=(cy-iSearchRange);y<=(cy+iSearchRange);y++) + { + sqr *s = S(x, y); + + if (SOLID(s)) continue; + if ((x==cx) && (y==cy)) continue; + + vec v = { x, y, GetCubeFloor(x, y) }; + + if (UnderWater(v)) continue; // Skip, cube is underwater + + if (hdr.waterlevel < (v.z - 2.0f)) continue; // Cube is too high + + // Check if the bot 'can fit' on the cube(no near obstacles) + bool small = false; + for (int a=(x-2);a<=(x+2);a++) + { + if (small) break; + for (int b=(y-2);b<=(y+2);b++) + { + if ((x==a) && (y==b)) continue; + vec v2 = { a, b, GetCubeFloor(a, b) }; + if (v.z < (v2.z-1-JUMP_HEIGHT)) + { + small=true; + break; + } + + if ((a >= (x-1)) && (a <= (x+1)) && (b >= (y-1)) && (b <= (y+1))) + { + if ((v2.z) < (v.z-2.0f)) + { + small = true; + break; + } + } + + traceresult_s tr; + TraceLine(v, v2, NULL, false, &tr); + if (tr.collided) + { + small=true; + break; + } + } + if (small) break; + } + if (small) + { + debugbeam(m_pMyEnt->o, v); + continue; + } + + // Okay, cube is valid. + flDist = GetDistance(v); + if (flDist < flNearestDist) + { + flNearestDist = flDist; + m_vWaterGoal = v; + } + } + } + } + + if (!vis(m_vWaterGoal, g_vecZero)) + { + AddDebugText("WaterNav"); + //debugbeam(m_pMyEnt->o, m_vWaterGoal); + vec aim = m_vWaterGoal; + aim.z += 1.5f; // Aim a bit further up + AimToVec(aim); + if ((RandomLong(1, 100) <= 15) && (Get2DDistance(m_vWaterGoal) <= 7.0f)) + m_pMyEnt->jumpnext = true; + return true; + } + + return false; +} + +bool CBot::CheckItems() +{ + if (!m_pCurrentGoalWaypoint && !CheckJump() && CheckStuck()) + { + // Don't check for ents a while when stuck + m_iCheckEntsDelay = lastmillis + RandomLong(1000, 2000); + return false; + } + + if (vis(m_vGoal, g_vecZero)) + m_pTargetEnt = NULL; + + if (!m_pTargetEnt) + { + if (m_iCheckEntsDelay > lastmillis) + return false; + else + { + m_pTargetEnt = SearchForEnts(!m_classicsp); + m_iCheckEntsDelay = lastmillis + RandomLong(2500, 5000); + } + } + + if (m_pTargetEnt) + { + if (HeadToTargetEnt()) + return true; + } + + if (m_eCurrentBotState == STATE_ENT) + { + ResetWaypointVars(); + m_vGoal = g_vecZero; + m_pTargetEnt = NULL; + } + + return false; +} + +bool CBot::InUnreachableList(entity *e) +{ + TLinkedList::node_s *p = m_UnreachableEnts.GetFirst(); + while(p) + { + if (p->Entry->ent == e) return true; + p = p->next; + } + return false; +} + +bool CBot::IsReachable(vec to, float flMaxHeight) +{ + vec from = m_pMyEnt->o; + traceresult_s tr; + float curr_height, last_height; + + float distance = GetDistance(from, to); + + // is the destination close enough? + //if (distance < REACHABLE_RANGE) + { + if (IsVisible(to)) + { + // Look if bot can 'fit trough' + vec src = from, forward, right, up; + AnglesToVectors(GetViewAngles(), forward, right, up); + + // Trace from 1 cube to the left + vec temp = right; + vmul(temp, 1.0f); + vsub(src, temp); + if (!::IsVisible(src, to)) return false; + + // Trace from 1 cube to the right + vadd(src, temp); + if (!::IsVisible(src, to)) return false; + + if (UnderWater(from) && UnderWater(to)) + { + // No need to worry about heights in water + return true; + } +/* + if (to.z > (from.z + JUMP_HEIGHT)) + { + vec v_new_src = to; + vec v_new_dest = to; + + v_new_dest.z = v_new_dest.z - (JUMP_HEIGHT + 1.0f); + + // check if we didn't hit anything, if so then it's in mid-air + if (::IsVisible(v_new_src, v_new_dest, NULL)) + { + condebug("to is in midair"); + debugbeam(from, to); + return false; // can't reach this one + } + } +*/ + + // check if distance to ground increases more than jump height + // at points between from and to... + + vec v_temp = to; vsub(v_temp, from); + vec v_direction = Normalize(v_temp); // 1 unit long + vec v_check = from; + vec v_down = from; + + v_down.z = v_down.z - 100.0f; // straight down + + TraceLine(v_check, v_down, NULL, false, &tr); + + // height from ground + last_height = GetDistance(v_check, tr.end); + + distance = GetDistance(to, v_check); // distance from goal + + while (distance > 2.0f) + { + // move 2 units closer to the goal + v_temp = v_direction; + vmul(v_temp, 2.0f); + vadd(v_check, v_temp); + + v_down = v_check; + v_down.z = v_down.z - 100.0f; + + TraceLine(v_check, v_down, NULL, false, &tr); + + curr_height = GetDistance(v_check, tr.end); + + // is the difference in the last height and the current height + // higher that the jump height? + if ((last_height - curr_height) >= flMaxHeight) + { + // can't get there from here... + //condebug("traces failed to to"); + debugbeam(from, to); + return false; + } + + last_height = curr_height; + + distance = GetDistance(to, v_check); // distance from goal + } + + return true; + } + } + + return false; +} + +void CBot::HearSound(int n, vec *o) +{ + // Has the bot already an enemy? + if (m_pMyEnt->enemy) return; + + // Is the sound not interesting? +//fixmebot +/* if ((n==S_DIE1) || (n==S_DIE2) || (n==S_DEATHR) || (n==S_DEATHE) || (n==S_DEATHS) || + (n==S_DEATHB) || (n==S_DEATHH) || (n==S_DEATHD) || (n==S_PIGGR2)) + return;*/ + + int soundvol = m_pBotSkill->iMaxHearVolume - + (int)(GetDistance(*o)*3*m_pBotSkill->iMaxHearVolume/255); + + if (soundvol == 0) return; + + // Look who made the sound(check for the nearest enemy) + float flDist, flNearestDist = 3.0f; // Range of 3 units + dynent *pNearest = NULL; + + /* edit -argh + if (m_sp) + { + // Check all monsters + loopv(monsters) + { + if (!monsters[i] || monsters[i]->state != CS_ALIVE) continue; + + flDist = GetDistance(*o, monsters[i]->o); + if ((flDist < flNearestDist) && IsVisible(monsters[i])) + { + pNearest = monsters[i]; + flNearestDist = flDist; + } + } + } */ + //else + //{ + // Check all players first + loopv(players) + { + if (!players[i] || (players[i]->state != CS_ALIVE) || + isteam(m_pMyEnt->team, players[i]->team)) + continue; + + flDist = GetDistance(*o, players[i]->o); + if ((flDist < flNearestDist) && IsVisible(players[i])) + { + pNearest = players[i]; + flNearestDist = flDist; + } + } + + // Check all bots + loopv(bots) + { + if (!bots[i] || (bots[i]->state != CS_ALIVE) || + isteam(m_pMyEnt->team, bots[i]->team) || (bots[i] == m_pMyEnt)) + continue; + + flDist = GetDistance(*o, bots[i]->o); + if ((flDist < flNearestDist) && IsVisible(bots[i])) + { + pNearest = bots[i]; + flNearestDist = flDist; + } + } + + // Check local player + if (player1 && (player1->state == CS_ALIVE) && + !isteam(m_pMyEnt->team, player1->team)) + { + flDist = GetDistance(*o, player1->o); + if ((flDist < flNearestDist) && IsVisible(player1)) + { + pNearest = player1; + flNearestDist = flDist; + } + } + //} + + if (pNearest) + { + if (m_pMyEnt->enemy != pNearest) + m_iShootDelay = lastmillis + GetShootDelay(); // Add shoot delay when new enemy found + + m_pMyEnt->enemy = pNearest; + } +} + +bool CBot::IsInFOV(const vec &o) +{ + vec v2, forward, right, up; + float flDot; + + AnglesToVectors(GetViewAngles(), forward, right, up); + + v2 = o; + vsub(v2, m_pMyEnt->o); + v2.z = 0.0f; // Make 2D + v2 = Normalize(v2); + forward.z = 0; // Make 2D + + flDot = dotprod(v2 , forward); + + return(flDot >= 0.5f); // sin2(0.5) == 60 degrees FOV +} + +// Code of CBot - End diff --git a/source/src/bot/bot_util.cpp b/source/src/bot/bot_util.cpp index ff802865..6ca78b6a 100644 --- a/source/src/bot/bot_util.cpp +++ b/source/src/bot/bot_util.cpp @@ -1,565 +1,564 @@ -// -// C++ Implementation: bot_util -// -// Description: Basic util functions, mostly bot related -// -// -// Author: -// -// -// -// - -#include "../cube.h" - -#ifndef __linux__ -#include -#endif -#include -#if (defined __linux__) -#include -#include -#else -#include -#endif - -#include - -extern dvector monsters; - -// Function code by PMB - Begin - - -// Random functions begin -// copied from -// http://forums.bots-united.com/showthread.php?t=244&page=1&pp=10&highlight=random -// So credits to PMB and the person(s) who actually made it :] - -// maximum value returned by our number generator (2^31 - 1 = 0x7FFFFFFF) -#define LRAND_MAX 2147483647L -long lseed; // our random number generator's seed - -long lrand (void) -{ - // this function is the equivalent of the rand() standard C library function, - // except that whereas rand() works only with short integers - // (i.e. not above 32767), this function is able to generate 32-bit random - // numbers. Isn't that nice ? - // credits go to Ray Gardner for his fast implementation of minimal random - // number generators - // http://c.snippets.org/snip_lister.php?fname=rg_rand.c - - // compose the two 16-bit parts of the long integer and assemble them - static unsigned long lrand_lo, lrand_hi; - lrand_lo = 16807 * (long) (lseed & 0xFFFF); // low part - lrand_hi = 16807 * (long) ((unsigned long) lseed >> 16); - // high part - lrand_lo += (lrand_hi & 0x7FFF) << 16; - // assemble both in lrand_lo - // is the resulting number greater than LRAND_MAX (half the capacity) ? - if (lrand_lo > LRAND_MAX) - { - lrand_lo &= LRAND_MAX; // then get rid of the disturbing bit - lrand_lo++; // and increase it a bit (to avoid overflow problems, I suppose) - } - - lrand_lo += lrand_hi >> 15; - // now do twisted maths to generate the next seed - // is the resulting number greater than LRAND_MAX (half the capacity) ? - if (lrand_lo > LRAND_MAX) - { - lrand_lo &= LRAND_MAX; // then get rid of the disturbing bit - lrand_lo++; // and increase it a bit (to avoid overflow problems, I suppose) - } - // now we've got our (pseudo-)random number. - lseed = (long) lrand_lo; // put it in the seed for next time - return (lseed); // and return it. Yeah, simple as that. -} - -void lsrand (unsigned long initial_seed) -{ - // this function initializes the random seed based on the initial seed value - // passed in the initial_seed parameter. Since random seeds are - // usually initialized with the time of day, and time is a value that - // changes slowly, we bump the seed twice to have it in a more - // random state for future calls of the random number generator. - - // fill in the initial seed of the random number generator - lseed = (long) initial_seed; - lseed = lrand (); // bump it once - lseed = lrand (); // bump it twice - return; // that's all folks -} - -long RandomLong (long from, long to) -{ - // this function returns a random integer number between (and including) - // the starting and ending values passed by parameters from and to. - if (to <= from) - return (from); - - return (from + lrand () / (LRAND_MAX / (to - from + 1))); -} - -float RandomFloat (float from, float to) -{ - // this function returns a random floating-point number between (and including) - // the starting and ending values passed by parameters from and to. - - if (to <= from) - return (from); - - return (from + (float) lrand () / (LRAND_MAX / (to - from))); -} - -// End random functions - -void AnglesToVectors(vec angles, vec &forward, vec &right, vec &up) -{ - static float degrees_to_radians = 2 * PI / 360; - float angle; - float sin_pitch; - float sin_yaw; - float sin_roll; - float cos_pitch; - float cos_yaw; - float cos_roll; - - // For some reason this has to be done :) - // Me = math n00b - angles.x = -angles.x; - angles.y -= 90; - - // compute the sin and cosine of the pitch component - angle = angles.x * degrees_to_radians; - sin_pitch = sinf (angle); - cos_pitch = cosf (angle); - - // compute the sin and cosine of the yaw component - angle = angles.y * degrees_to_radians; - sin_yaw = sinf (angle); - cos_yaw = cosf (angle); - - // compute the sin and cosine of the roll component - angle = angles.z * degrees_to_radians; - sin_roll = sinf (angle); - cos_roll = cosf (angle); - - // build the FORWARD vector - forward.x = cos_pitch * cos_yaw; - forward.y = cos_pitch * sin_yaw; - forward.z = -sin_pitch; - - // build the RIGHT vector - right.x = -(-(sin_roll * sin_pitch * cos_yaw) - (cos_roll * -sin_yaw)); - right.y = -(-(sin_roll * sin_pitch * sin_yaw) - (cos_roll * cos_yaw)); - right.z = -(sin_roll * cos_pitch); - - // build the UPWARDS vector - up.x = ((cos_roll * sin_pitch * cos_yaw) - (sin_roll * -sin_yaw)); - up.y = ((cos_roll * sin_pitch * sin_yaw) - (sin_roll * cos_yaw)); - up.z = cos_roll * cos_pitch; - - return; -} - -// Function code by PMB - End - -float WrapXAngle(float angle) -{ - if (angle > 90) angle = 90; - else if (angle < -90) angle = -90; - - return angle; -} - -float WrapYZAngle(float angle) -{ - while (angle >= 360.0f) angle -= 360.0f; - while (angle < 0.0f) angle += 360.0f; - - return angle; -} - -// Sees how far it can come from 'from' to 'to' and puts the end vector in 'end' -// (1337 description :-) -// UNDONE: Optimize this as much as possible -void TraceLine(vec from, vec to, dynent *pTracer, bool CheckPlayers, traceresult_s *tr, - bool SkipTags) -{ - tr->end = from; - tr->collided = false; - - static float flNearestDist, flDist; - static vec v; - static bool solid; - - flNearestDist = 9999.0f; - - if(OUTBORD((int)from.x, (int)from.y)) return; - - // Check if the 'line' collides with entities like mapmodels - loopv(ents) - { - entity &e = ents[i]; - if(e.type!=MAPMODEL) continue; // Only check map models for now - - mapmodelinfo &mmi = getmminfo(e.attr2); - if(!&mmi || !mmi.h) continue; - - float lo = (float)(S(e.x, e.y)->floor+mmi.zoff+e.attr3); - float hi = lo+mmi.h; - float z = (fabs(from.z-lo) < fabs(from.z-hi)) ? lo : hi; - makevec(&v, e.x, e.y, z); - flDist = GetDistance(from, v); - - if ((flDist < flNearestDist) && (intersect(&e, from, to, &tr->end))) - { - flNearestDist = GetDistance(from, tr->end); - tr->collided = true; - } - } - - if (CheckPlayers) - { - // Check if the 'line' collides with players - loopv(players) - { - dynent *d = players[i]; - if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid players - - flDist = GetDistance(from, d->o); - - if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) - { - flNearestDist = flDist; - tr->collided = true; - } - } - - // Check if the 'line' collides with bots - loopv(bots) - { - dynent *d = bots[i]; - if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid bots - - flDist = GetDistance(from, d->o); - - if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) - { - flNearestDist = flDist; - tr->collided = true; - } - } - - // Check if the 'line' collides with monsters - /* we don't have monsters anymore, thanks for the bots :) -argh - dvector &mv = getmonsters(); - loopv(mv) - { - dynent *d = mv[i]; - if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid monsters - - flDist = GetDistance(from, d->o); - - if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) - { - flNearestDist = flDist; - tr->collided = true; - } - } - */ - - - // Check if the 'line' collides with the local player(player1) - dynent *d = player1; // Shortcut - if (d && (d!=pTracer) && !BotManager.m_pBotToView && (d->state == CS_ALIVE)) - { - flDist = GetDistance(from, d->o); - - if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) - { - flNearestDist = flDist; - tr->collided = true; - } - } - } - - - float dx = to.x-from.x; - float dy = to.y-from.y; - int steps = (int)(sqrt(dx*dx+dy*dy)/0.9); - if(!steps) // If from and to are on the same cube... - { - if (GetDistance(from, to) < flNearestDist) - { - tr->end = to; - sqr *s = S(fast_f2nat(from.x), fast_f2nat(from.y)); - float flr = GetCubeFloor(fast_f2nat(from.x), fast_f2nat(from.y)); - solid = ((!SkipTags || !s->tag) && SOLID(s)); - if (solid || (to.z < flr) || (to.z > s->ceil)) - { - tr->collided = true; - tr->end.z = (fabs(to.z-s->ceil) < fabs(to.z-flr)) ? s->ceil : flr; - } - } - return; - } - - float x = from.x; - float y = from.y; - int i = 0; - vec endcube = from; - - // Now check if the 'line' is hit by a cube - while(i= flNearestDist) break; - sqr *s = S(fast_f2nat(x), fast_f2nat(y)); - solid = ((!SkipTags || !s->tag) && SOLID(s)); - if(solid) break; - float floor = s->floor; - if(s->type==FHF) floor -= s->vdelta/4.0f; - float ceil = s->ceil; - if(s->type==CHF) ceil += s->vdelta/4.0f; - float rz = from.z-((from.z-to.z)*(i/(float)steps)); - if(rzceil) break; - - endcube.x = x; - endcube.y = y; - endcube.z = rz; - x += dx/(float)steps; - y += dy/(float)steps; - i++; - } - - if ((i>=steps) && !tr->collided) - { - tr->end = to; - } - else - { - tr->collided = true; - if (GetDistance(from, endcube) < flNearestDist) - tr->end = endcube; - } - - return; -} - -float GetDistance(vec v1, vec v2) -{ - if ((v1.x == 0) && (v2.x == 0) && (v1.y == 0) && (v2.y == 0) && (v1.z == 0) && - (v2.z == 0)) - return 0.0f; - - vdist(flDist, vecto, v1, v2); - return flDist; -} - -// As GetDistance(), but doesn take height in account. -float Get2DDistance(vec v1, vec v2) -{ - if ((v1.x == 0) && (v2.x == 0) && (v1.y == 0) && (v2.y == 0)) - return 0.0f; - - v1.z = v2.z = 0.0f; - vdist(flDist, vecto, v1, v2); - return flDist; -} - -bool IsVisible(vec v1, vec v2, dynent *tracer, bool SkipTags) -{ - traceresult_s tr; - TraceLine(v1, v2, tracer, (tracer!=NULL), &tr, SkipTags); - return !tr.collided; -} - -// Prediction: -// - pos: Current position -// - vel: Current velocity -// - Time: In seconds, predict how far it is in Time seconds -vec PredictPos(vec pos, vec vel, float Time) -{ - float flVelLength = sqrt(vel.x*vel.x + vel.y*vel.y + vel.z*vel.z); - - if (flVelLength <= 1.0) - return pos; // don't bother with low velocities... - - float speed = flVelLength * Time; - float flTemp = 1/flVelLength; - vec vecNormalize; - - vecNormalize.x = vel.x * flTemp; - vecNormalize.y = vel.y * flTemp; - vecNormalize.z = vel.z * flTemp; - - vec v_src = pos; - vmul(vecNormalize, speed); - vec v_end = v_src; - vadd(v_end, vecNormalize); - - return v_end; -} - -// returns true if the given file is valid -bool IsValidFile(const char *szFileName) -{ - FILE *fp = fopen(szFileName, "r"); - - if (fp) - { - fclose(fp); - return true; - } - - return false; -} - -bool FileIsOlder(const char *szFileName1, const char *szFileName2) -{ - int file1, file2; - struct stat stat1, stat2; - - file1 = open(szFileName1, O_RDONLY); - file2 = open(szFileName2, O_RDONLY); - - fstat(file1, &stat1); - fstat(file2, &stat2); - - close(file1); - close(file2); - - return(stat1.st_mtime < stat2.st_mtime); -} - -vec Normalize(vec v) -{ - float flLen = sqrt(v.x*v.x + v.y*v.y + v.z*v.z); - static vec v_norm = g_vecZero; - if (flLen == 0) - { - v_norm.x=0;v_norm.y=0;v_norm.z=1; - } - else - { - flLen = 1 / flLen; - v_norm.x = v.x * flLen; v_norm.y = v.y * flLen; v_norm.z = v.z * flLen; - } - return v_norm; -} - -float GetYawDiff(float curyaw, vec v1, vec v2) -{ - float yaw = -(float)atan2(v2.x - v1.x, v2.y - v1.y)/PI*180+180; - return yaw-curyaw; -} - -vec CrossProduct(const vec &a, const vec &b) -{ - vec cross = { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x }; - return cross; -} - -int GetDirection(const vec &angles, const vec &v1, const vec &v2) -{ - vec tmp, forward, right, up, cross; - float flDot; - - AnglesToVectors(angles, forward, right, up); - - tmp = v2; - vsub(tmp, v1); - tmp.z = 0.0f; // Make 2D - tmp = Normalize(tmp); - forward.z = 0; // Make 2D - - flDot = dotprod(tmp , forward); - cross = CrossProduct(tmp, forward); - - int Dir = DIR_NONE; - - if (cross.z > 0.1f) Dir |= LEFT; - else if (cross.z < -0.1f) Dir |= RIGHT; - if (flDot < -0.1f) Dir |= BACKWARD; - else if (flDot > 0.1f) Dir |= FORWARD; - - if (v1.z > v2.z) Dir |= DOWN; - if (v1.z < v2.z) Dir |= UP; - - return Dir; -} - -float GetCubeFloor(int x, int y) -{ - sqr *s = S(x, y); - float floor = s->floor; - if (s->type == FHF) - floor -= (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; - return floor; -} - -float GetCubeHeight(int x, int y) -{ - sqr *s = S(x, y); - float ceil = s->ceil; - if(s->type == CHF) - ceil += (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; - return ceil; -} - -char *SkillNrToSkillName(short skillnr) -{ - if (skillnr == 0) return "best"; - else if (skillnr == 1) return "good"; - else if (skillnr == 2) return "medium"; - else if (skillnr == 3) return "worse"; - else if (skillnr == 4) return "bad"; - - return NULL; -} - -bool IsInGame(dynent *d) -{ - if (!d) return false; - - /*edit -argh - if (d->monsterstate) - { - loopv(monsters) - { - if (monsters[i] == d) - return true; - } - } - */ - //else - if (d->bIsBot) - { - loopv(bots) - { - if (bots[i] == d) - return true; - } - } - else - { - if (d == player1) - return true; - else - { - loopv(players) - { -#ifdef VANILLA_CUBE - if (!players[i] || (players[i]->state == CS_DEDHOST)) continue; -#elif defined AC_CUBE - if (!players[i]) continue; -#endif - if (players[i] == d) - return true; - } - } - } - return false; -} +// +// C++ Implementation: bot_util +// +// Description: Basic util functions, mostly bot related +// +// +// Author: +// +// +// +// + +#include "../cube.h" + +#ifndef __linux__ +#include +#endif +#include +#if (defined __linux__) +#include +#include +#include +#else +#include +#endif + +extern dvector monsters; + +// Function code by PMB - Begin + + +// Random functions begin +// copied from +// http://forums.bots-united.com/showthread.php?t=244&page=1&pp=10&highlight=random +// So credits to PMB and the person(s) who actually made it :] + +// maximum value returned by our number generator (2^31 - 1 = 0x7FFFFFFF) +#define LRAND_MAX 2147483647L +long lseed; // our random number generator's seed + +long lrand (void) +{ + // this function is the equivalent of the rand() standard C library function, + // except that whereas rand() works only with short integers + // (i.e. not above 32767), this function is able to generate 32-bit random + // numbers. Isn't that nice ? + // credits go to Ray Gardner for his fast implementation of minimal random + // number generators + // http://c.snippets.org/snip_lister.php?fname=rg_rand.c + + // compose the two 16-bit parts of the long integer and assemble them + static unsigned long lrand_lo, lrand_hi; + lrand_lo = 16807 * (long) (lseed & 0xFFFF); // low part + lrand_hi = 16807 * (long) ((unsigned long) lseed >> 16); + // high part + lrand_lo += (lrand_hi & 0x7FFF) << 16; + // assemble both in lrand_lo + // is the resulting number greater than LRAND_MAX (half the capacity) ? + if (lrand_lo > LRAND_MAX) + { + lrand_lo &= LRAND_MAX; // then get rid of the disturbing bit + lrand_lo++; // and increase it a bit (to avoid overflow problems, I suppose) + } + + lrand_lo += lrand_hi >> 15; + // now do twisted maths to generate the next seed + // is the resulting number greater than LRAND_MAX (half the capacity) ? + if (lrand_lo > LRAND_MAX) + { + lrand_lo &= LRAND_MAX; // then get rid of the disturbing bit + lrand_lo++; // and increase it a bit (to avoid overflow problems, I suppose) + } + // now we've got our (pseudo-)random number. + lseed = (long) lrand_lo; // put it in the seed for next time + return (lseed); // and return it. Yeah, simple as that. +} + +void lsrand (unsigned long initial_seed) +{ + // this function initializes the random seed based on the initial seed value + // passed in the initial_seed parameter. Since random seeds are + // usually initialized with the time of day, and time is a value that + // changes slowly, we bump the seed twice to have it in a more + // random state for future calls of the random number generator. + + // fill in the initial seed of the random number generator + lseed = (long) initial_seed; + lseed = lrand (); // bump it once + lseed = lrand (); // bump it twice + return; // that's all folks +} + +long RandomLong (long from, long to) +{ + // this function returns a random integer number between (and including) + // the starting and ending values passed by parameters from and to. + if (to <= from) + return (from); + + return (from + lrand () / (LRAND_MAX / (to - from + 1))); +} + +float RandomFloat (float from, float to) +{ + // this function returns a random floating-point number between (and including) + // the starting and ending values passed by parameters from and to. + + if (to <= from) + return (from); + + return (from + (float) lrand () / (LRAND_MAX / (to - from))); +} + +// End random functions + +void AnglesToVectors(vec angles, vec &forward, vec &right, vec &up) +{ + static float degrees_to_radians = 2 * PI / 360; + float angle; + float sin_pitch; + float sin_yaw; + float sin_roll; + float cos_pitch; + float cos_yaw; + float cos_roll; + + // For some reason this has to be done :) + // Me = math n00b + angles.x = -angles.x; + angles.y -= 90; + + // compute the sin and cosine of the pitch component + angle = angles.x * degrees_to_radians; + sin_pitch = sinf (angle); + cos_pitch = cosf (angle); + + // compute the sin and cosine of the yaw component + angle = angles.y * degrees_to_radians; + sin_yaw = sinf (angle); + cos_yaw = cosf (angle); + + // compute the sin and cosine of the roll component + angle = angles.z * degrees_to_radians; + sin_roll = sinf (angle); + cos_roll = cosf (angle); + + // build the FORWARD vector + forward.x = cos_pitch * cos_yaw; + forward.y = cos_pitch * sin_yaw; + forward.z = -sin_pitch; + + // build the RIGHT vector + right.x = -(-(sin_roll * sin_pitch * cos_yaw) - (cos_roll * -sin_yaw)); + right.y = -(-(sin_roll * sin_pitch * sin_yaw) - (cos_roll * cos_yaw)); + right.z = -(sin_roll * cos_pitch); + + // build the UPWARDS vector + up.x = ((cos_roll * sin_pitch * cos_yaw) - (sin_roll * -sin_yaw)); + up.y = ((cos_roll * sin_pitch * sin_yaw) - (sin_roll * cos_yaw)); + up.z = cos_roll * cos_pitch; + + return; +} + +// Function code by PMB - End + +float WrapXAngle(float angle) +{ + if (angle > 90) angle = 90; + else if (angle < -90) angle = -90; + + return angle; +} + +float WrapYZAngle(float angle) +{ + while (angle >= 360.0f) angle -= 360.0f; + while (angle < 0.0f) angle += 360.0f; + + return angle; +} + +// Sees how far it can come from 'from' to 'to' and puts the end vector in 'end' +// (1337 description :-) +// UNDONE: Optimize this as much as possible +void TraceLine(vec from, vec to, dynent *pTracer, bool CheckPlayers, traceresult_s *tr, + bool SkipTags) +{ + tr->end = from; + tr->collided = false; + + static float flNearestDist, flDist; + static vec v; + static bool solid; + + flNearestDist = 9999.0f; + + if(OUTBORD((int)from.x, (int)from.y)) return; + + // Check if the 'line' collides with entities like mapmodels + loopv(ents) + { + entity &e = ents[i]; + if(e.type!=MAPMODEL) continue; // Only check map models for now + + mapmodelinfo &mmi = getmminfo(e.attr2); + if(!&mmi || !mmi.h) continue; + + float lo = (float)(S(e.x, e.y)->floor+mmi.zoff+e.attr3); + float hi = lo+mmi.h; + float z = (fabs(from.z-lo) < fabs(from.z-hi)) ? lo : hi; + makevec(&v, e.x, e.y, z); + flDist = GetDistance(from, v); + + if ((flDist < flNearestDist) && (intersect(&e, from, to, &tr->end))) + { + flNearestDist = GetDistance(from, tr->end); + tr->collided = true; + } + } + + if (CheckPlayers) + { + // Check if the 'line' collides with players + loopv(players) + { + dynent *d = players[i]; + if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid players + + flDist = GetDistance(from, d->o); + + if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) + { + flNearestDist = flDist; + tr->collided = true; + } + } + + // Check if the 'line' collides with bots + loopv(bots) + { + dynent *d = bots[i]; + if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid bots + + flDist = GetDistance(from, d->o); + + if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) + { + flNearestDist = flDist; + tr->collided = true; + } + } + + // Check if the 'line' collides with monsters + /* we don't have monsters anymore, thanks for the bots :) -argh + dvector &mv = getmonsters(); + loopv(mv) + { + dynent *d = mv[i]; + if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid monsters + + flDist = GetDistance(from, d->o); + + if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) + { + flNearestDist = flDist; + tr->collided = true; + } + } + */ + + + // Check if the 'line' collides with the local player(player1) + dynent *d = player1; // Shortcut + if (d && (d!=pTracer) && !BotManager.m_pBotToView && (d->state == CS_ALIVE)) + { + flDist = GetDistance(from, d->o); + + if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) + { + flNearestDist = flDist; + tr->collided = true; + } + } + } + + + float dx = to.x-from.x; + float dy = to.y-from.y; + int steps = (int)(sqrt(dx*dx+dy*dy)/0.9); + if(!steps) // If from and to are on the same cube... + { + if (GetDistance(from, to) < flNearestDist) + { + tr->end = to; + sqr *s = S(fast_f2nat(from.x), fast_f2nat(from.y)); + float flr = GetCubeFloor(fast_f2nat(from.x), fast_f2nat(from.y)); + solid = ((!SkipTags || !s->tag) && SOLID(s)); + if (solid || (to.z < flr) || (to.z > s->ceil)) + { + tr->collided = true; + tr->end.z = (fabs(to.z-s->ceil) < fabs(to.z-flr)) ? s->ceil : flr; + } + } + return; + } + + float x = from.x; + float y = from.y; + int i = 0; + vec endcube = from; + + // Now check if the 'line' is hit by a cube + while(i= flNearestDist) break; + sqr *s = S(fast_f2nat(x), fast_f2nat(y)); + solid = ((!SkipTags || !s->tag) && SOLID(s)); + if(solid) break; + float floor = s->floor; + if(s->type==FHF) floor -= s->vdelta/4.0f; + float ceil = s->ceil; + if(s->type==CHF) ceil += s->vdelta/4.0f; + float rz = from.z-((from.z-to.z)*(i/(float)steps)); + if(rzceil) break; + + endcube.x = x; + endcube.y = y; + endcube.z = rz; + x += dx/(float)steps; + y += dy/(float)steps; + i++; + } + + if ((i>=steps) && !tr->collided) + { + tr->end = to; + } + else + { + tr->collided = true; + if (GetDistance(from, endcube) < flNearestDist) + tr->end = endcube; + } + + return; +} + +float GetDistance(vec v1, vec v2) +{ + if ((v1.x == 0) && (v2.x == 0) && (v1.y == 0) && (v2.y == 0) && (v1.z == 0) && + (v2.z == 0)) + return 0.0f; + + vdist(flDist, vecto, v1, v2); + return flDist; +} + +// As GetDistance(), but doesn take height in account. +float Get2DDistance(vec v1, vec v2) +{ + if ((v1.x == 0) && (v2.x == 0) && (v1.y == 0) && (v2.y == 0)) + return 0.0f; + + v1.z = v2.z = 0.0f; + vdist(flDist, vecto, v1, v2); + return flDist; +} + +bool IsVisible(vec v1, vec v2, dynent *tracer, bool SkipTags) +{ + traceresult_s tr; + TraceLine(v1, v2, tracer, (tracer!=NULL), &tr, SkipTags); + return !tr.collided; +} + +// Prediction: +// - pos: Current position +// - vel: Current velocity +// - Time: In seconds, predict how far it is in Time seconds +vec PredictPos(vec pos, vec vel, float Time) +{ + float flVelLength = sqrt(vel.x*vel.x + vel.y*vel.y + vel.z*vel.z); + + if (flVelLength <= 1.0) + return pos; // don't bother with low velocities... + + float speed = flVelLength * Time; + float flTemp = 1/flVelLength; + vec vecNormalize; + + vecNormalize.x = vel.x * flTemp; + vecNormalize.y = vel.y * flTemp; + vecNormalize.z = vel.z * flTemp; + + vec v_src = pos; + vmul(vecNormalize, speed); + vec v_end = v_src; + vadd(v_end, vecNormalize); + + return v_end; +} + +// returns true if the given file is valid +bool IsValidFile(const char *szFileName) +{ + FILE *fp = fopen(szFileName, "r"); + + if (fp) + { + fclose(fp); + return true; + } + + return false; +} + +bool FileIsOlder(const char *szFileName1, const char *szFileName2) +{ +/* int file1, file2; + struct stat stat1, stat2; + + file1 = open(szFileName1, O_RDONLY); + file2 = open(szFileName2, O_RDONLY); + + fstat(file1, &stat1); + fstat(file2, &stat2); + + close(file1); + close(file2); + + return(stat1.st_mtime < stat2.st_mtime);*/ return false; // FIXME +} + +vec Normalize(vec v) +{ + float flLen = sqrt(v.x*v.x + v.y*v.y + v.z*v.z); + static vec v_norm = g_vecZero; + if (flLen == 0) + { + v_norm.x=0;v_norm.y=0;v_norm.z=1; + } + else + { + flLen = 1 / flLen; + v_norm.x = v.x * flLen; v_norm.y = v.y * flLen; v_norm.z = v.z * flLen; + } + return v_norm; +} + +float GetYawDiff(float curyaw, vec v1, vec v2) +{ + float yaw = -(float)atan2(v2.x - v1.x, v2.y - v1.y)/PI*180+180; + return yaw-curyaw; +} + +vec CrossProduct(const vec &a, const vec &b) +{ + vec cross = { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x }; + return cross; +} + +int GetDirection(const vec &angles, const vec &v1, const vec &v2) +{ + vec tmp, forward, right, up, cross; + float flDot; + + AnglesToVectors(angles, forward, right, up); + + tmp = v2; + vsub(tmp, v1); + tmp.z = 0.0f; // Make 2D + tmp = Normalize(tmp); + forward.z = 0; // Make 2D + + flDot = dotprod(tmp , forward); + cross = CrossProduct(tmp, forward); + + int Dir = DIR_NONE; + + if (cross.z > 0.1f) Dir |= LEFT; + else if (cross.z < -0.1f) Dir |= RIGHT; + if (flDot < -0.1f) Dir |= BACKWARD; + else if (flDot > 0.1f) Dir |= FORWARD; + + if (v1.z > v2.z) Dir |= DOWN; + if (v1.z < v2.z) Dir |= UP; + + return Dir; +} + +float GetCubeFloor(int x, int y) +{ + sqr *s = S(x, y); + float floor = s->floor; + if (s->type == FHF) + floor -= (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; + return floor; +} + +float GetCubeHeight(int x, int y) +{ + sqr *s = S(x, y); + float ceil = s->ceil; + if(s->type == CHF) + ceil += (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; + return ceil; +} + +char *SkillNrToSkillName(short skillnr) +{ + if (skillnr == 0) return "best"; + else if (skillnr == 1) return "good"; + else if (skillnr == 2) return "medium"; + else if (skillnr == 3) return "worse"; + else if (skillnr == 4) return "bad"; + + return NULL; +} + +bool IsInGame(dynent *d) +{ + if (!d) return false; + + /*edit -argh + if (d->monsterstate) + { + loopv(monsters) + { + if (monsters[i] == d) + return true; + } + } + */ + //else + if (d->bIsBot) + { + loopv(bots) + { + if (bots[i] == d) + return true; + } + } + else + { + if (d == player1) + return true; + else + { + loopv(players) + { +#ifdef VANILLA_CUBE + if (!players[i] || (players[i]->state == CS_DEDHOST)) continue; +#elif defined AC_CUBE + if (!players[i]) continue; +#endif + if (players[i] == d) + return true; + } + } + } + return false; +} diff --git a/source/src/bot/bot_waypoint.cpp b/source/src/bot/bot_waypoint.cpp index 302c80de..80b4b221 100644 --- a/source/src/bot/bot_waypoint.cpp +++ b/source/src/bot/bot_waypoint.cpp @@ -1,3258 +1,3258 @@ -// -// C++ Implementation: bot_waypoint -// -// Description: Waypoint class for bots -// -// -// Author: Rick -// - -#include "../cube.h" - -vec v_debuggoal = g_vecZero; - -#if defined AC_CUBE -CACWaypointClass WaypointClass; -#elif defined VANILLA_CUBE -CCubeWaypointClass WaypointClass; -#endif - -// Waypoint class begin - -CWaypointClass::CWaypointClass(void) : m_bDrawWaypoints(false), m_bDrawWPPaths(true), - m_bAutoWaypoint(false), m_bAutoPlacePaths(true), - m_bDrawWPText(false), m_vLastCreatedWP(g_vecZero), - m_bFlooding(false), m_bFilteringNodes(false), - m_iFloodStartTime(0), m_iCurFloodX(0), - m_iCurFloodY(0), m_iFloodSize(0), - m_iFilteredNodes(0), m_iWaypointCount(0) -{ - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - while(!m_Waypoints[i][j].Empty()) - delete m_Waypoints[i][j].Pop(); - } - } - - m_szMapName[0] = 0; -} - -void CWaypointClass::Init() -{ - Clear(); // Free previously allocated path memory - - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - while(!m_Waypoints[i][j].Empty()) - delete m_Waypoints[i][j].Pop(); - } - } - - m_fPathDrawTime = 0.0; - m_iWaypointCount = 0; - m_vLastCreatedWP = g_vecZero; -} - -void CWaypointClass::Clear() -{ - for(int i=0;iConnectedWPs.DeleteAllNodes(); - p->ConnectedWPsWithMe.DeleteAllNodes(); - BotManager.DelWaypoint(p); - delete p; - } - } - } -} - -// returns true if waypoints succesfull loaded -bool CWaypointClass::LoadWaypoints() -{ - FILE *bfp; - char szWPFileName[64]; - char filename[256]; - waypoint_header_s header; - short num, triggernr, yaw; - int i, j, flags; - vec from, to; - - strcpy(szWPFileName, m_szMapName); - strcat(szWPFileName, ".wpt"); - - BotManager.MakeBotFileName(szWPFileName, "waypoints", NULL, filename); - - bfp = fopen(filename, "rb"); - - BotManager.m_sCurrentTriggerNr = -1; - - // if file exists, read the waypoint structure from it - if (bfp != NULL) - { - fread(&header, sizeof(header), 1, bfp); - - header.szFileType[10] = 0; - if (strcmp(header.szFileType, "cube_bot") == 0) - { - header.szMapName[31] = 0; - - if (strcmp(header.szMapName, m_szMapName) == 0) - { - Init(); // remove any existing waypoints - - // Check which waypoint file version this is, handle each of them different - if (header.iFileVersion == 1) - { - // First version, works with an big array and has a limit - - waypoint_version_1_s WPs[1024]; - int path_index; - - m_iWaypointCount = header.iWPCount; - for (i=0; i < header.iWPCount; i++) - { - fread(&WPs[i], sizeof(WPs[0]), 1, bfp); - WPs[i].ConnectedWPs.Reset(); // We get garbage when we read this from - // a file, so just clear it - - // Convert to new waypoint structure - node_s *pNode = new node_s(WPs[i].v_origin, WPs[i].iFlags, 0); - - short x, y; - GetNodeIndexes(pNode->v_origin, &x, &y); - - m_Waypoints[x][y].AddNode(pNode); - BotManager.AddWaypoint(pNode); - } - - // read and add waypoint paths... - for (i=0; i < m_iWaypointCount; i++) - { - // read the number of paths from this node... - fread(&num, sizeof(num), 1, bfp); - - // See which waypoint the current is - node_s *pCurrent = GetWaypointFromVec(WPs[i].v_origin); - - if (!pCurrent) - { - conoutf("Error: NULL path in waypoint file"); - continue; - } - - for (j=0; j < num; j++) - { - fread(&path_index, sizeof(path_index), 1, bfp); - - // See which waypoint this is - node_s *pTo = GetWaypointFromVec(WPs[path_index].v_origin); - - if (!pTo) - { - conoutf("Error: NULL path in waypoint file"); - continue; - } - - AddPath(pCurrent, pTo); - } - } - conoutf("Old waypoint version(%d) converted to new version(%d)", - header.iFileVersion, WAYPOINT_VERSION); - conoutf("Use the wpsave command to convert permently"); - - } - else if (header.iFileVersion == 2) - { - m_iWaypointCount = header.iWPCount; - - for (i=0; i < header.iWPCount; i++) - { - fread(&from, sizeof(from), 1, bfp); // Read origin - fread(&flags, sizeof(flags), 1, bfp); // Read waypoint flags - fread(&triggernr, sizeof(triggernr), 1, bfp); // Read trigger nr - fread(&yaw, sizeof(yaw), 1, bfp); // Read target yaw - - node_s *pNode = new node_s(from, flags, triggernr, yaw); - - short x, y; - GetNodeIndexes(pNode->v_origin, &x, &y); - - m_Waypoints[x][y].AddNode(pNode); - BotManager.AddWaypoint(pNode); - - if ((BotManager.m_sCurrentTriggerNr == -1) || - (triggernr < BotManager.m_sCurrentTriggerNr)) - BotManager.m_sCurrentTriggerNr = triggernr; - } - - for (i=0; i < header.iWPCount; i++) - { - // read the number of paths from this node... - fread(&num, sizeof(num), 1, bfp); - fread(&from, sizeof(from), 1, bfp); - - if (!num) - continue; - - node_s *pCurrent = GetWaypointFromVec(from); - - if (!pCurrent) - { - conoutf("Error: NULL path in waypoint file"); - - for(j=0;j WAYPOINT_VERSION) - { - conoutf("Error: Waypoint file is newer then current, upgrade cube bot."); - fclose(bfp); - return false; - } - else - { - conoutf("Error: Unknown waypoint version for cube bot"); - fclose(bfp); - return false; - } - } - else - { - conoutf("Waypoints aren't for map %s but for map %s", int(m_szMapName), - int(header.szMapName)); - fclose(bfp); - return false; - } - } - else - { - conoutf("Waypoint file isn't for cube bot"); - //conoutf("Header FileType: %s", int(header.szFileType)); - - fclose(bfp); - return false; - } - - fclose(bfp); - - //RouteInit(); - } - else - { - conoutf("Waypoint file %s does not exist", int(filename)); - return false; - } - - if (BotManager.m_sCurrentTriggerNr == -1) - BotManager.m_sCurrentTriggerNr = 0; - - ReCalcCosts(); - - conoutf("Waypoints for map %s loaded", int(m_szMapName)); - return true; -} - -void CWaypointClass::SaveWaypoints() -{ - char filename[256]; - char mapname[64]; - waypoint_header_s header; - short int num; - TLinkedList::node_s *pPath; - - strcpy(header.szFileType, "cube_bot"); - - header.iFileVersion = WAYPOINT_VERSION; - - header.iFileFlags = 0; // not currently used - - header.iWPCount = m_iWaypointCount; - - memset(header.szMapName, 0, sizeof(header.szMapName)); - strncpy(header.szMapName, m_szMapName, 31); - header.szMapName[31] = 0; - - strcpy(mapname, m_szMapName); - strcat(mapname, ".wpt"); - - BotManager.MakeBotFileName(mapname, "waypoints", NULL, filename); - - FILE *bfp = fopen(filename, "wb"); - - if (!bfp) - { - conoutf("Error writing waypoint file, check if the directory \"bot/waypoint\" exists and " - "the right permissions are set"); - return; - } - - // write the waypoint header to the file... - fwrite(&header, sizeof(header), 1, bfp); - - // write the waypoint data to the file... - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); - while(p) - { - fwrite(&p->Entry->v_origin, sizeof(p->Entry->v_origin), 1, bfp); // Write origin - fwrite(&p->Entry->iFlags, sizeof(p->Entry->iFlags), 1, bfp); // Write waypoint flags - fwrite(&p->Entry->sTriggerNr, sizeof(p->Entry->sTriggerNr), 1, bfp); // Write trigger nr - fwrite(&p->Entry->sYaw, sizeof(p->Entry->sYaw), 1, bfp); // Write target yaw - - p = p->next; - } - } - } - - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); - while(p) - { - // save the waypoint paths... - - // count the number of paths from this node... - pPath = p->Entry->ConnectedWPs.GetFirst(); - num = p->Entry->ConnectedWPs.NodeCount(); - - fwrite(&num, sizeof(num), 1, bfp); // write the count - fwrite(&p->Entry->v_origin, sizeof(p->Entry->v_origin), 1, bfp); // write the origin of this path - - // now write out each path... - while (pPath != NULL) - { - fwrite(&pPath->Entry->v_origin, sizeof(pPath->Entry->v_origin), 1, bfp); - pPath = pPath->next; // go to next node in linked list - } - p = p->next; - } - } - } - - fclose(bfp); -} - -bool CWaypointClass::LoadWPExpFile() -{ - FILE *bfp; - char szWPFileName[64]; - char filename[256]; - waypoint_header_s header; - short int num; - int i, j; - vec from, to; - - if (m_iWaypointCount == 0) - return false; - - strcpy(szWPFileName, m_szMapName); - strcat(szWPFileName, ".exp"); - - BotManager.MakeBotFileName(szWPFileName, "waypoints", NULL, filename); - - bfp = fopen(filename, "rb"); - - // if file exists, read the waypoint structure from it - if (bfp != NULL) - { - fread(&header, sizeof(header), 1, bfp); - - header.szFileType[10] = 0; - if (strcmp(header.szFileType, "cube_bot") == 0) - { - header.szMapName[31] = 0; - - if (strcmp(header.szMapName, m_szMapName) == 0) - { - // Check which waypoint file version this is, handle each of them different - if (header.iFileVersion == 1) - { - for (i=0; i < header.iWPCount; i++) - { - // read the number of paths from this node... - fread(&num, sizeof(num), 1, bfp); - fread(&from, sizeof(vec), 1, bfp); - - if (!num) continue; - - node_s *pCurrent = GetWaypointFromVec(from); - - if (!pCurrent) - { - conoutf("Error: NULL node in waypoint experience file"); - continue; - } - - for (j=0; j < num; j++) - { - fread(&to, sizeof(vec), 1, bfp); - node_s *p = GetWaypointFromVec(to); - if (p) - { - pCurrent->FailedGoalList.AddNode(p); - } - } - } - } - else if (header.iFileVersion > EXP_WP_VERSION) - { - conoutf("Error: Waypoint experience file is newer then current, upgrade cube bot."); - fclose(bfp); - return false; - } - else - { - conoutf("Error: Unknown waypoint experience file version for cube bot"); - fclose(bfp); - return false; - } - } - else - { - conoutf("Waypoint experience file isn't for map %s but for map %s", int(m_szMapName), - int(header.szMapName)); - fclose(bfp); - return false; - } - } - else - { - conoutf("Waypoint experience file isn't for cube bot"); - //conoutf("Header FileType: %s", int(header.szFileType)); - - fclose(bfp); - return false; - } - - fclose(bfp); - - //RouteInit(); - } - else - { - conoutf("Waypoint experience file %s does not exist", int(filename)); - return false; - } - - conoutf("Waypoint experience file for map %s loaded", int(m_szMapName)); - return true; -} - -void CWaypointClass::SaveWPExpFile() -{ - if (!m_iWaypointCount) - return; - - char filename[256]; - char mapname[64]; - waypoint_header_s header; - short int num; - - strcpy(header.szFileType, "cube_bot"); - - header.iFileVersion = EXP_WP_VERSION; - - header.iFileFlags = 0; // not currently used - - header.iWPCount = m_iWaypointCount; - - memset(header.szMapName, 0, sizeof(header.szMapName)); - strncpy(header.szMapName, m_szMapName, 31); - header.szMapName[31] = 0; - - strcpy(mapname, m_szMapName); - strcat(mapname, ".exp"); - - BotManager.MakeBotFileName(mapname, "waypoints", NULL, filename); - - FILE *bfp = fopen(filename, "wb"); - - if (!bfp) - { - conoutf("Error writing waypoint experience file, check if the directory \"bot/waypoint\" exists and " - "the right permissions are set"); - return; - } - - // write the waypoint header to the file... - fwrite(&header, sizeof(header), 1, bfp); - - // save the waypoint experience data... - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); - while(p) - { - // count the number of paths from this node... - TLinkedList::node_s *p2 = p->Entry->FailedGoalList.GetFirst(); - num = p->Entry->FailedGoalList.NodeCount(); - - if (!num || !p2) continue; - - fwrite(&num, sizeof(num), 1, bfp); // write the count - fwrite(&p->Entry->v_origin, sizeof(vec), 1, bfp); // write the origin of this node - - while (p2 != NULL) - { - // Write out the node which a bot can't reach with the current node - fwrite(&p2->Entry->v_origin, sizeof(vec), 1, bfp); - p2 = p2->next; // go to next node in linked list - } - p = p->next; - } - } - } - - fclose(bfp); -} - -void CWaypointClass::Think() -{ - if (dedserv) return; - -#ifdef WP_FLOOD - FloodThink(); -#endif - - if (m_bAutoWaypoint) // is auto waypoint on? - { - // find the distance from the last used waypoint - float flDistance = GetDistance(m_vLastCreatedWP, player1->o); - - bool NoLastCreatedWP = ((m_vLastCreatedWP.x == 0) && (m_vLastCreatedWP.y == 0) && - (m_vLastCreatedWP.z == 0)); - - if ((flDistance > 3.0f) || NoLastCreatedWP) - { - // check that no other reachable waypoints are nearby... - if (!GetNearestWaypoint(player1, 10.0f)) - { - AddWaypoint(player1->o, true); // place a waypoint here - } - } - } - - // Draw info for nearest waypoint? - if (m_bDrawWPText) - { - node_s *nearestwp = GetNearestWaypoint(player1, 20.0f); - -#ifdef WP_FLOOD - if (!nearestwp) - nearestwp = GetNearestFloodWP(player1, 10.0f); -#endif - - if (nearestwp) - { - char szWPInfo[256]; - sprintf(szWPInfo, "Distance nearest waypoint: %f", GetDistance(player1->o, nearestwp->v_origin)); - AddScreenText(szWPInfo); - - strcpy(szWPInfo, "Flags: "); - if (nearestwp->iFlags & W_FL_TELEPORT) - strcat(szWPInfo, "Teleport "); - if (nearestwp->iFlags & W_FL_TELEPORTDEST) - strcat(szWPInfo, "Teleport destination "); - if (nearestwp->iFlags & W_FL_JUMP) - strcat(szWPInfo, "Jump "); - if (nearestwp->iFlags & W_FL_TRIGGER) - { - char sz[32]; - sprintf(sz, "Trigger(nr %d) ", nearestwp->sTriggerNr); - strcat(szWPInfo, sz); - } - if (nearestwp->iFlags & W_FL_INTAG) - strcat(szWPInfo, "In tagged cube(s) "); - if (strlen(szWPInfo) == 7) - strcat(szWPInfo, "None"); - - AddScreenText(szWPInfo); - - if (nearestwp->sYaw != -1) - AddScreenText("yaw %d", nearestwp->sYaw); - - sprintf(szWPInfo, "Waypoint has %d connections", - nearestwp->ConnectedWPs.NodeCount()); - AddScreenText(szWPInfo); - AddScreenText("Base Cost: %d", nearestwp->sCost); - } - } - - if (m_bDrawWaypoints) // display the waypoints if turned on... - { - DrawNearWaypoints(); - } -} - -void CWaypointClass::DrawNearWaypoints() -{ - TLinkedList::node_s *pNode; - node_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = ceil(15.0f / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - GetNodeIndexes(player1->o, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - node_s *nearestwp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - -#ifdef WP_FLOOD - if (!nearestwp) - nearestwp = GetNearestFloodWP(player1, 20.0f); -#endif - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - pNode = m_Waypoints[x][y].GetFirst(); - - while(pNode) - { - flDist = GetDistance(player1->o, pNode->Entry->v_origin); - if (flDist <= 15) - { - vec o = pNode->Entry->v_origin; - vec e = o; - o.z -= 2; - e.z += 2; - - if (pNode->Entry->iFlags & W_FL_JUMP) - { - // draw a red waypoint - linestyle(2.5f, 0xFF, 0x40, 0x40); - } - else if (nearestwp == pNode->Entry) - { - // draw a green waypoint - linestyle(2.5f, 0x40, 0xFF, 0x40); - } - else - { - // draw a blue waypoint - linestyle(2.5f, 0x40, 0x40, 0xFF); - } - - line(int(o.x), int(o.y), int(o.z), int(e.x), int(e.y), int(e.z)); - - if (flNearestDist > flDist) - { - flNearestDist = flDist; - pNearest = pNode->Entry; - } - } - - pNode = pNode->next; - } - } - } - - if (pNearest) - { - pNode = pNearest->ConnectedWPs.GetFirst(); - - linestyle(2.0f, 0xFF, 0xFF, 0xFF); - - while(pNode) - { - vec o = pNode->Entry->v_origin; - vec e = pNearest->v_origin; - - line(int(o.x), int(o.y), int(o.z), int(e.x), int(e.y), int(e.z)); - - pNode = pNode->next; - } - - // Has this waypoint an target yaw? - if (pNearest->sYaw != -1) - { - vec angles = { 0, pNearest->sYaw, 0 }; - vec from = pNearest->v_origin, end = pNearest->v_origin; - vec forward, right, up; - - from.z = end.z = (pNearest->v_origin.z-1.0f); - - AnglesToVectors(angles, forward, right, up); - vmul(forward, 5.0f); - vadd(end, forward); - - linestyle(2.0f, 0xFF, 0x40, 0x40); - line(int(from.x), int(from.y), int(from.z), int(end.x), int(end.y), int(end.z)); - } - } - -#ifndef RELEASE_BUILD - // check if path waypointing is on... - if (m_bDrawWPPaths) - { - // Draw path from first bot - if (bots.length() && bots[0] && bots[0]->pBot && bots[0]->pBot->m_pCurrentWaypoint && - bots[0]->pBot->m_pCurrentGoalWaypoint) - { - CBot *pB = bots[0]->pBot; - if (!pB->m_bCalculatingAStarPath && !pB->m_AStarNodeList.Empty()) - { - TLinkedList::node_s *pNode = pB->m_AStarNodeList.GetFirst(), *pNext; - - linestyle(2.5f, 0xFF, 0x40, 0x40); - - line((int)pB->m_pCurrentWaypoint->pNode->v_origin.x, - (int)pB->m_pCurrentWaypoint->pNode->v_origin.y, - (int)pB->m_pCurrentWaypoint->pNode->v_origin.z, - (int)pNode->Entry->pNode->v_origin.x, - (int)pNode->Entry->pNode->v_origin.y, - (int)pNode->Entry->pNode->v_origin.z); - - while(pNode && pNode->next) - { - pNext = pNode->next; - vec &v1 = pNode->Entry->pNode->v_origin; - vec &v2 = pNext->Entry->pNode->v_origin; - - line(int(v1.x), int(v1.y), int(v1.z), int(v2.x), int(v2.y), int(v2.z)); - - pNode = pNode->next; - } - } - } - } - - extern bool intermission; - if (intermission) return; - - for(int i=0;iPrevLocations.prevloc[i], g_vecZero)) continue; - vec v1 = player1->PrevLocations.prevloc[i]; - v1.z -= 1.0f; - vec v2 = v1; - v2.z += 2.0f; - linestyle(2.5f, 0xFF, 0x40, 0x40); - line(int(v1.x), int(v1.y), int(v1.z), int(v2.x), int(v2.y), int(v2.z)); - } -#endif -} - -// Add waypoint at location o, returns pointer of created wp -node_s *CWaypointClass::AddWaypoint(vec o, bool connectwp) -{ - short x, y; - int flags = 0; - if (S((int)o.x, (int)o.y)->tag) flags |= W_FL_INTAG; - - node_s *pNode = new node_s(o, flags, 0); - m_vLastCreatedWP = o; - - GetNodeIndexes(o, &x, &y); - - m_Waypoints[x][y].AddNode(pNode); - BotManager.AddWaypoint(pNode); - - m_iWaypointCount++; - - if (connectwp && m_bAutoPlacePaths) - { - // Connect new waypoint with other near waypoints. - - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); - - while(p) - { - if (p->Entry == pNode) - { - p = p->next; - continue; // skip the waypoint that was just added - } - - // check if the waypoint is reachable from the new one (one-way) - if (WPIsReachable(o, p->Entry->v_origin)) - AddPath(pNode, p->Entry); // Add a path from a to b - if (WPIsReachable(p->Entry->v_origin, pNode->v_origin)) - AddPath(p->Entry, pNode); // Add a path from b to a - - p = p->next; - } - } - } - } - - return pNode; -} - -void CWaypointClass::DeleteWaypoint(dynent *d) -{ - node_s *pWP; - - pWP = GetNearestWaypoint(d, 7.0f); - - if (!pWP) - { - conoutf("Error: Couldn´t find near waypoint"); - return; - } - - BotManager.DelWaypoint(pWP); - - // delete any paths that lead to this index... - DeletePath(pWP); - - short x, y; - GetNodeIndexes(pWP->v_origin, &x, &y); - - m_Waypoints[x][y].DeleteEntry(pWP); - - pWP->ConnectedWPs.DeleteAllNodes(); - pWP->ConnectedWPsWithMe.DeleteAllNodes(); - - m_iWaypointCount--; - delete pWP; -} - -void CWaypointClass::AddPath(node_s *pWP1, node_s *pWP2) -{ - pWP1->ConnectedWPs.AddNode(pWP2); - pWP2->ConnectedWPsWithMe.AddNode(pWP1); -} - -// Deletes all paths connected to the given waypoint -void CWaypointClass::DeletePath(node_s *pWP) -{ - TLinkedList::node_s *p; - - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - p = m_Waypoints[i][j].GetFirst(); - while (p) - { - p->Entry->ConnectedWPs.DeleteEntry(pWP); - pWP->ConnectedWPsWithMe.DeleteEntry(p->Entry); - p = p->next; - } - } - } -} - -// Deletes path between 2 waypoints(1 way) -void CWaypointClass::DeletePath(node_s *pWP1, node_s *pWP2) -{ - pWP1->ConnectedWPs.DeleteEntry(pWP2); - pWP2->ConnectedWPsWithMe.DeleteEntry(pWP1); -} - -void CWaypointClass::ManuallyCreatePath(dynent *d, int iCmd, bool TwoWay) -{ - static node_s *waypoint1 = NULL; // initialized to unassigned - static node_s *waypoint2 = NULL; // initialized to unassigned - - if (iCmd == 1) // assign source of path - { - waypoint1 = GetNearestWaypoint(d, 7.0f); - - if (!waypoint1) - { - conoutf("Error: Couldn't find near waypoint"); - return; - } - - return; - } - - if (iCmd == 2) // assign dest of path and make path - { - if (!waypoint1) - { - conoutf("Error: First waypoint unset"); - return; - } - - waypoint2 = GetNearestWaypoint(d, 7.0f); - - if (!waypoint2) - { - conoutf("Error: Couldn't find near waypoint"); - return; - } - - AddPath(waypoint1, waypoint2); - - if (TwoWay) - AddPath(waypoint2, waypoint1); - } -} - -void CWaypointClass::ManuallyDeletePath(dynent *d, int iCmd, bool TwoWay) -{ - static node_s *waypoint1 = NULL; // initialized to unassigned - static node_s *waypoint2 = NULL; // initialized to unassigned - - if (iCmd == 1) // assign source of path - { - waypoint1 = GetNearestWaypoint(d, 7.0f); - - if (!waypoint1) - { - conoutf("Error: Couldn't find near waypoint"); - return; - } - - return; - } - - if (iCmd == 2) // assign dest of path and delete path - { - if (!waypoint1) - { - conoutf("Error: First waypoint unset"); - return; - } - - waypoint2 = GetNearestWaypoint(d, 7.0f); - - if (!waypoint2) - { - conoutf("Error: Couldn't find near waypoint"); - return; - } - - DeletePath(waypoint1, waypoint2); - - if (TwoWay) - DeletePath(waypoint2, waypoint1); - } -} - -bool CWaypointClass::WPIsReachable(vec from, vec to) -{ - traceresult_s tr; - float curr_height, last_height; - - float distance = GetDistance(from, to); - - // is the destination close enough? - if (distance < REACHABLE_RANGE) - { - if (IsVisible(from, to)) - { - if (UnderWater(from) && UnderWater(to)) - { - // No need to worry about heights in water - return true; - } -/* - if (to.z > (from.z + JUMP_HEIGHT)) - { - vec v_new_src = to; - vec v_new_dest = to; - - v_new_dest.z = v_new_dest.z - (JUMP_HEIGHT + 1.0f); - - // check if we didn't hit anything, if so then it's in mid-air - if (::IsVisible(v_new_src, v_new_dest, NULL)) - { - conoutf("to is in midair"); - debugbeam(from, to); - return false; // can't reach this one - } - } -*/ - - // check if distance to ground increases more than jump height - // at points between from and to... - - vec v_temp = to; vsub(v_temp, from); - vec v_direction = Normalize(v_temp); // 1 unit long - vec v_check = from; - vec v_down = from; - - v_down.z = v_down.z - 100.0f; // straight down - - TraceLine(v_check, v_down, NULL, false, &tr); - - // height from ground - last_height = GetDistance(v_check, tr.end); - - distance = GetDistance(to, v_check); // distance from goal - - while (distance > 2.0f) - { - // move 2 units closer to the goal - v_temp = v_direction; - vmul(v_temp, 2.0f); - vadd(v_check, v_temp); - - v_down = v_check; - v_down.z = v_down.z - 100.0f; - - TraceLine(v_check, v_down, NULL, false, &tr); - - curr_height = GetDistance(v_check, tr.end); - - // is the difference in the last height and the current height - // higher that the jump height? - if ((last_height - curr_height) >= JUMP_HEIGHT) - { - // can't get there from here... - //conoutf("traces failed to to"); - return false; - } - - last_height = curr_height; - - distance = GetDistance(to, v_check); // distance from goal - } - - return true; - } - } - - return false; -} - -node_s *CWaypointClass::GetNearestWaypoint(vec v_src, float flRange) -{ - TLinkedList::node_s *pNode; - node_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - GetNodeIndexes(v_src, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - pNode = m_Waypoints[x][y].GetFirst(); - - while(pNode) - { - if (pNode->Entry->iFlags & W_FL_FLOOD) - { - pNode = pNode->next; - continue; - } - - flDist = GetDistance(v_src, pNode->Entry->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (IsVisible(v_src, pNode->Entry->v_origin, NULL)) - { - pNearest = pNode->Entry; - flNearestDist = flDist; - } - } - - pNode = pNode->next; - } - } - } - return pNearest; -} - -node_s *CWaypointClass::GetNearestTriggerWaypoint(vec v_src, float flRange) -{ - TLinkedList::node_s *pNode; - node_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - GetNodeIndexes(v_src, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - pNode = m_Waypoints[x][y].GetFirst(); - - while(pNode) - { - if ((pNode->Entry->iFlags & W_FL_FLOOD) || !(pNode->Entry->iFlags & W_FL_TRIGGER)) - { - pNode = pNode->next; - continue; - } - - flDist = GetDistance(v_src, pNode->Entry->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (IsVisible(v_src, pNode->Entry->v_origin, NULL)) - { - pNearest = pNode->Entry; - flNearestDist = flDist; - } - } - - pNode = pNode->next; - } - } - } - return pNearest; -} - -node_s *CWaypointClass::GetWaypointFromVec(const vec &v_src) -{ - static TLinkedList::node_s *pNode; - static short x, y; - - GetNodeIndexes(v_src, &x, &y); - - pNode = m_Waypoints[x][y].GetFirst(); - - while(pNode) - { - if (vis(pNode->Entry->v_origin, v_src)) - return pNode->Entry; - - pNode = pNode->next; - } - return NULL; -} - -void CWaypointClass::CalcCost(node_s *pNode) -{ - float flCost = 10.0f; - - // Check nearby cubes... - int x = int(pNode->v_origin.x); - int y = int(pNode->v_origin.y); - int a, b, row; - - for (row=0;row<=1;row++) - { - if (row==0) b = y - 6; - else b = y + 6; - - for (a=(x-6);a<=(x+6);a++) - { - if (OUTBORD(a, b)) continue; - - vec to = { a, b, GetCubeFloor(a, b) + 1.0f }; - - // See if there is a obstacle(cube or mapmodel) nearby - traceresult_s tr; - TraceLine(pNode->v_origin, to, NULL, false, &tr); - if (tr.collided) - { - float flFraction = (GetDistance(pNode->v_origin, tr.end) / - GetDistance(pNode->v_origin, to)); - flCost += (1.0f-flFraction)*0.5f; - } - - if ((abs(a) > 4) || (abs(b) > 4)) continue; - - vec from = to; - to.z -= (JUMP_HEIGHT - 1.0f); - TraceLine(from, to, NULL, false, &tr); - if (!tr.collided) - flCost += 0.5f; - } - - if (row==0) a = x - 6; - else a = x + 6; - - for (b=(y-6);b<=(y+6);b++) - { - if (OUTBORD(a, b)) continue; - - vec to = { a, b, GetCubeFloor(a, b) + 1.0f }; - - // See if there is a obstacle(cube or mapmodel) nearby - traceresult_s tr; - TraceLine(pNode->v_origin, to, NULL, false, &tr); - if (tr.collided) - { - float flFraction = (GetDistance(pNode->v_origin, tr.end) / - GetDistance(pNode->v_origin, to)); - flCost += (1.0f-flFraction)*0.5f; - } - - if ((abs(a) > 4) || (abs(b) > 4)) continue; - - vec from = to; - to.z -= (JUMP_HEIGHT - 1.0f); - TraceLine(from, to, NULL, false, &tr); - if (!tr.collided) - flCost += 0.5f; - } - } - - if (UnderWater(pNode->v_origin)) // Water is annoying - flCost += 5.0f; - - pNode->sCost = (short)flCost; -} - -void CWaypointClass::ReCalcCosts(void) -{ - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); - while(p) - { - CalcCost(p->Entry); - p = p->next; - } - } - } -} - -#ifdef WP_FLOOD - -void CWaypointClass::StartFlood() -{ - if (m_bFlooding) return; - - conoutf("Starting flood, this may take a while on large maps...."); - m_bFlooding = true; - m_iFloodStartTime = SDL_GetTicks(); - m_iCurFloodX = m_iCurFloodY = MINBORD; - m_iFloodSize = 0; -} - -void CWaypointClass::FloodThink() -{ - if (!m_bFlooding) return; - - static int x, y, count; - count = 0; - - if (!m_bFilteringNodes) - { - // loop through ALL cubes and check if we should add a waypoint here - for (x=m_iCurFloodX; x<(ssize-MINBORD); x+=4) - { - if (count >= 3) - { - AddScreenText("Flooding map with waypoints... %.2f %%", - ((float)x / float(ssize-MINBORD)) * 100.0f); - m_iCurFloodX = x; - return; - } - - count++; - - for (y=MINBORD; y<(ssize-MINBORD); y+=4) - { - vec from = { x, y, GetCubeFloor(x, y)+2.0f }; - bool bFound = CanPlaceNodeHere(from); - - if (!bFound) - { - for (int a=x-2;a<=(x+2);a++) - { - for (int b=y-2;b<=(y+2);b++) - { - if (OUTBORD(a, b)) continue; - if ((a==x) && (b==y)) continue; - makevec(&from, a, b, GetCubeFloor(a, b) + 2.0f); - if (CanPlaceNodeHere(from)) - { - bFound = true; - break; - } - } - - if (bFound) break; - } - } - - if (!bFound) continue; - - // Add WP - int flags = W_FL_FLOOD; - if (S((int)from.x, (int)from.y)->tag) flags |= W_FL_INTAG; - - node_s *pWP = new node_s(from, flags, 0); - - short i, j; - GetNodeIndexes(from, &i, &j); - m_Waypoints[i][j].PushNode(pWP); - BotManager.AddWaypoint(pWP); - m_iFloodSize += sizeof(node_s); - m_iWaypointCount++; - - // Connect with other nearby nodes - ConnectFloodWP(pWP); - } - } - } - - if (!m_bFilteringNodes) - { - m_bFilteringNodes = true; - m_iCurFloodX = m_iCurFloodY = 0; - m_iFilteredNodes = 0; - } - - count = 0; - - // Filter all nodes which aren't connected to any other nodes - for (x=m_iCurFloodX;x 3) - { - AddScreenText("Filtering useless waypoints and"); - AddScreenText("adding costs... %.2f %%", ((float)x / float(MAX_MAP_GRIDS)) * 100.0f); - m_iCurFloodX = x; - return; - } - - count++; - - for (y=0;y::node_s *pNode = m_Waypoints[x][y].GetFirst(), *pTemp; - while(pNode) - { - if (pNode->Entry->ConnectedWPs.Empty() || - pNode->Entry->ConnectedWPsWithMe.Empty()) - { - BotManager.DelWaypoint(pNode->Entry); - pTemp = pNode; - pNode = pNode->next; - delete pTemp->Entry; - m_Waypoints[x][y].DeleteNode(pTemp); - m_iFilteredNodes++; - m_iFloodSize -= sizeof(node_s); - m_iWaypointCount--; - continue; - } - else - CalcCost(pNode->Entry); - pNode = pNode->next; - } - } - } - - // Done with flooding - m_bFlooding = false; - m_bFilteringNodes = false; - - //ReCalcCosts(); - BotManager.PickNextTrigger(); - - m_iFloodSize += sizeof(m_Waypoints); - conoutf("Added %d wps in %d milliseconds", m_iWaypointCount, SDL_GetTicks()-m_iFloodStartTime); - conoutf("Filtered %d wps", m_iFilteredNodes); - - BotManager.CalculateMaxAStarCount(); - - char szSize[64]; - sprintf(szSize, "Total size: %.2f Kb", float(m_iFloodSize)/1024.0f); - conoutf(szSize); -} - -bool CWaypointClass::CanPlaceNodeHere(const vec &from) -{ - static short x, y, a, b; - static traceresult_s tr; - static vec to, v1, v2; - - x = fast_f2nat(from.x); - y = fast_f2nat(from.y); - - sqr *s = S(x, y); - if (SOLID(s)) - { - return false; - } - - if (fabs(s->ceil - s->floor) < player1->radius) - { - return false; - } - - if (GetNearestFloodWP(from, 2.0f, NULL)) return false; - - for (a=(x-1);a<=(x+1);a++) - { - for (b=(y-1);b<=(y+1);b++) - { - if ((x==a) && (y==b)) continue; - if (OUTBORD(a, b)) return false; - makevec(&v1, a, b, from.z); - v2 = v1; - v2.z -= 1000.0f; - - TraceLine(v1, v2, NULL, false, &tr, true); - to = tr.end; - - if ((a >= (x-1)) && (a <= (x+1)) && (b >= (y-1)) && (b <= (y+1))) - { - if (!tr.collided || (to.z < (from.z-4.0f))) - { - return false; - } - } - - to.z += 2.0f; - - if (from.z < (to.z-JUMP_HEIGHT)) - { - return false; - } - - TraceLine(from, to, NULL, false, &tr, true); - if (tr.collided) - return false; - } - } - - return true; -} - -void CWaypointClass::ConnectFloodWP(node_s *pWP) -{ - if (!pWP) return; - - static float flRange; - static TLinkedList::node_s *pNode; - static short i, j, MinI, MaxI, MinJ, MaxJ, x, y, Offset; - static float flDist; - static node_s *p; - - // Calculate range, based on distance to nearest node - p = GetNearestFloodWP(pWP->v_origin, 15.0f, pWP, true); - if (p) - { - flDist = GetDistance(pWP->v_origin, p->v_origin); - flRange = min(flDist+2.0f, 15.0f); - if (flRange < 5.0f) flRange = 5.0f; - } - else - return; - - Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - - GetNodeIndexes(pWP->v_origin, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (x=MinI;x<=MaxI;x++) - { - for(y=MinJ;y<=MaxJ;y++) - { - pNode = m_Waypoints[x][y].GetFirst(); - - while(pNode) - { - if (pNode->Entry == pWP) - { - pNode = pNode->next; - continue; - } - - flDist = GetDistance(pWP->v_origin, pNode->Entry->v_origin); - if (flDist <= flRange) - { - if (IsVisible(pWP->v_origin, pNode->Entry->v_origin, NULL, true)) - { - // Connect a with b - pWP->ConnectedWPs.AddNode(pNode->Entry); - pNode->Entry->ConnectedWPsWithMe.AddNode(pWP); - - // Connect b with a - pNode->Entry->ConnectedWPs.AddNode(pWP); - pWP->ConnectedWPsWithMe.AddNode(pNode->Entry); - - m_iFloodSize += (2 * sizeof(node_s *)); - } - } - - pNode = pNode->next; - } - } - } -} - -node_s *CWaypointClass::GetNearestFloodWP(vec v_origin, float flRange, node_s *pIgnore, - bool SkipTags) -{ - TLinkedList::node_s *p; - node_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - GetNodeIndexes(v_origin, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_Waypoints[x][y].GetFirst(); - - while(p) - { - if ((p->Entry == pIgnore) || (!(p->Entry->iFlags & W_FL_FLOOD))) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_origin, p->Entry->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (IsVisible(v_origin, p->Entry->v_origin, NULL, SkipTags)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -node_s *CWaypointClass::GetNearestTriggerFloodWP(vec v_origin, float flRange) -{ - TLinkedList::node_s *p; - node_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - GetNodeIndexes(v_origin, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_Waypoints[x][y].GetFirst(); - - while(p) - { - if (!(p->Entry->iFlags & (W_FL_FLOOD | W_FL_TRIGGER))) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_origin, p->Entry->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (IsVisible(v_origin, p->Entry->v_origin, NULL)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -void CWaypointClass::GetNodeIndexes(const vec &v_origin, short *i, short *j) -{ - // Function code by cheesy and PMB - //*i = abs((int)((int)(v_origin.x + (2*ssize)) / SECTOR_SIZE)); - //*j = abs((int)((int)(v_origin.y + (2*ssize)) / SECTOR_SIZE)); - //*i = (int)((v_origin.x) / ssize * MAX_MAP_GRIDS); - //*j = (int)((v_origin.y) / ssize * MAX_MAP_GRIDS); - *i = abs((int)((v_origin.x) / MAX_MAP_GRIDS)); - *j = abs((int)((v_origin.y) / MAX_MAP_GRIDS)); - - if (*i > MAX_MAP_GRIDS - 1) - *i = MAX_MAP_GRIDS - 1; - if (*j > MAX_MAP_GRIDS - 1) - *j = MAX_MAP_GRIDS - 1; -} - -#endif // WP_FLOOD -// Waypoint class end - -#if defined AC_CUBE - -// AC waypoint class begin -void CACWaypointClass::StartFlood() -{ - // UNDONE? - CWaypointClass::StartFlood(); -} - -// AC waypoint class end - -#elif defined VANILLA_CUBE - -// Cube waypoint class begin - -void CCubeWaypointClass::StartFlood() -{ - CWaypointClass::StartFlood(); - - // Add wps at triggers and teleporters and their destination - loopv(ents) - { - entity &e = ents[i]; - - if (OUTBORD(e.x, e.y)) continue; - - if (e.type == TELEPORT) - { - vec telepos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }, teledestpos = g_vecZero; - - // Find the teleport destination - int n = -1, tag = e.attr1, beenhere = -1; - for(;;) - { - n = findentity(TELEDEST, n+1); - if(n==beenhere || n<0) { conoutf("no teleport destination for tag %d", tag); break; }; - if(beenhere<0) beenhere = n; - if(ents[n].attr2==tag) - { - teledestpos.x = ents[n].x; - teledestpos.y = ents[n].y; - teledestpos.z = S(ents[n].x, ents[n].y)->floor+player1->eyeheight; - break; - } - } - - if (vis(teledestpos, g_vecZero)) continue; - - int flags = (W_FL_FLOOD | W_FL_TELEPORT); - if (S((int)telepos.x, (int)telepos.y)->tag) flags |= W_FL_INTAG; - - // Add waypoint at teleporter and teleport destination - node_s *pWP = new node_s(telepos, flags, 0); - - short i, j; - GetNodeIndexes(telepos, &i, &j); - m_Waypoints[i][j].PushNode(pWP); - BotManager.AddWaypoint(pWP); - m_iFloodSize += sizeof(node_s); - m_iWaypointCount++; - - flags = (W_FL_FLOOD | W_FL_TELEPORTDEST); - if (S((int)teledestpos.x, (int)teledestpos.y)->tag) flags |= W_FL_INTAG; - - node_s *pWP2 = new node_s(teledestpos, flags, 0); - - GetNodeIndexes(teledestpos, &i, &j); - m_Waypoints[i][j].PushNode(pWP2); - BotManager.AddWaypoint(pWP2); - m_iFloodSize += sizeof(node_s); - m_iWaypointCount++; - - // Connect the teleporter waypoint with the teleport-destination waypoint(1 way) - AddPath(pWP, pWP2); - - // Connect with other nearby nodes - ConnectFloodWP(pWP); - } - else if (e.type == CARROT) - { - vec pos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - - int flags = (W_FL_FLOOD | W_FL_TRIGGER); - if (S(e.x, e.y)->tag) flags |= W_FL_INTAG; - - node_s *pWP = new node_s(pos, flags, 0); - - short i, j; - GetNodeIndexes(pos, &i, &j); - m_Waypoints[i][j].PushNode(pWP); - BotManager.AddWaypoint(pWP); - m_iFloodSize += sizeof(node_s); - m_iWaypointCount++; - - // Connect with other nearby nodes - ConnectFloodWP(pWP); - } - else if (e.type == MAPMODEL) - { - mapmodelinfo &mmi = getmminfo(e.attr2); - if(!&mmi || !mmi.h || !mmi.rad) continue; - - float floor = (float)(S(e.x, e.y)->floor+mmi.zoff+e.attr3)+mmi.h; - - float x1 = e.x - mmi.rad; - float x2 = e.x + mmi.rad; - float y1 = e.y - mmi.rad; - float y2 = e.y + mmi.rad; - - // UNDONE? - for (float x=(x1+1.0f);x<=(x2-1.0f);x++) - { - for (float y=(y1+1.0f);y<=(y2-1.0f);y++) - { - vec from = { x, y, floor+2.0f }; - if (GetNearestFloodWP(from, 2.0f, NULL)) continue; - - // Add WP - int flags = W_FL_FLOOD; - if (S((int)x, (int)y)->tag) flags |= W_FL_INTAG; - - node_s *pWP = new node_s(from, flags, 0); - - short i, j; - GetNodeIndexes(from, &i, &j); - m_Waypoints[i][j].PushNode(pWP); - BotManager.AddWaypoint(pWP); - m_iFloodSize += sizeof(node_s); - m_iWaypointCount++; - - // Connect with other nearby nodes - ConnectFloodWP(pWP); - } - } - } - } - CWaypointClass::StartFlood(); -} - -void CCubeWaypointClass::CreateWPsAtTeleporters() -{ - loopv(ents) - { - entity &e = ents[i]; - - if (e.type != TELEPORT) continue; - if (OUTBORD(e.x, e.y)) continue; - - vec telepos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }, teledestpos = g_vecZero; - - // Find the teleport destination - int n = -1, tag = e.attr1, beenhere = -1; - for(;;) - { - n = findentity(TELEDEST, n+1); - if(n==beenhere || n<0) { conoutf("no teleport destination for tag %d", tag); continue; }; - if(beenhere<0) beenhere = n; - if(ents[n].attr2==tag) - { - teledestpos.x = ents[n].x; - teledestpos.y = ents[n].y; - teledestpos.z = S(ents[n].x, ents[n].y)->floor+player1->eyeheight; - break; - } - } - - if (vis(teledestpos, g_vecZero)) continue; - - // Add waypoint at teleporter and teleport destination - node_s *telewp = AddWaypoint(telepos, false); - node_s *teledestwp = AddWaypoint(teledestpos, false); - - if (telewp && teledestwp) - { - // Connect the teleporter waypoint with the teleport-destination waypoint(1 way) - AddPath(telewp, teledestwp); - - // Flag waypoints - telewp->iFlags = W_FL_TELEPORT; - teledestwp->iFlags = W_FL_TELEPORTDEST; - } - } -} - -void CCubeWaypointClass::CreateWPsAtTriggers() -{ - loopv(ents) - { - entity &e = ents[i]; - - if (e.type != CARROT) continue; - if (OUTBORD(e.x, e.y)) continue; - - vec pos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - - node_s *wp = AddWaypoint(pos, false); - - if (wp) - { - // Flag waypoints - wp->iFlags = W_FL_TRIGGER; - } - } -} - -#endif - -// Cube waypoint class end - -// Waypoint commands begin - -void addwp(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.AddWaypoint(player1->o, true); -} - -COMMAND(addwp, ARG_NONE); - -void delwp(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.DeleteWaypoint(player1); -} - -COMMAND(delwp, ARG_NONE); - -void wpvisible(int on) -{ - WaypointClass.SetWaypointsVisible(bool(on)); -} - -COMMAND(wpvisible, ARG_1INT); - -void wpsave(void) -{ - WaypointClass.SaveWaypoints(); -} - -COMMAND(wpsave, ARG_NONE); - -void wpload(void) -{ - WaypointClass.LoadWaypoints(); -} - -COMMAND(wpload, ARG_NONE); - -void autowp(int on) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.SetAutoWaypoint(bool(on)); -} - -COMMAND(autowp, ARG_1INT); - -void wpinfo(int on) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.SetWPInfo(bool(on)); -} - -COMMAND(wpinfo, ARG_1INT); - -void addpath1way1(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyCreatePath(player1, 1, false); -} - -COMMAND(addpath1way1, ARG_NONE); - -void addpath1way2(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyCreatePath(player1, 2, false); -} - -COMMAND(addpath1way2, ARG_NONE); - -void addpath2way1(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyCreatePath(player1, 1, true); -} - -COMMAND(addpath2way1, ARG_NONE); - -void addpath2way2(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyCreatePath(player1, 2, true); -} - -COMMAND(addpath2way2, ARG_NONE); - -void delpath1way1(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyDeletePath(player1, 1, false); -} - -COMMAND(delpath1way1, ARG_NONE); - -void delpath1way2(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyDeletePath(player1, 2, false); -} - -COMMAND(delpath1way2, ARG_NONE); - -void delpath2way1(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyDeletePath(player1, 1, true); -} - -COMMAND(delpath2way1, ARG_NONE); - -void delpath2way2(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyDeletePath(player1, 2, true); -} - -COMMAND(delpath2way2, ARG_NONE); - -void setjumpwp(void) -{ - node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - if (wp) - { - WaypointClass.SetWPFlags(wp, W_FL_JUMP); - } -} - -COMMAND(setjumpwp, ARG_NONE); - -void unsetjumpwp(void) -{ - node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - if (wp) - { - WaypointClass.UnsetWPFlags(wp, W_FL_JUMP); - } -} - -COMMAND(unsetjumpwp, ARG_NONE); - -void setwptriggernr(int nr) -{ - node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - if (wp) - { - WaypointClass.SetWPTriggerNr(wp, nr); - } -} - -COMMAND(setwptriggernr, ARG_1INT); - -void setwpyaw(void) -{ - node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - if (wp) - { - WaypointClass.SetWPYaw(wp, short(player1->yaw)); - } -} - -COMMAND(setwpyaw, ARG_NONE); - -#ifdef WP_FLOOD -void startflood(void) -{ - WaypointClass.StartFlood(); -} - -COMMAND(startflood, ARG_NONE); -#endif - -#ifdef VANILLA_CUBE -// Commands specific for cube -void addtelewps(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.CreateWPsAtTeleporters(); -} - -COMMAND(addtelewps, ARG_NONE); - -void addtriggerwps(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.CreateWPsAtTriggers(); -} - -COMMAND(addtriggerwps, ARG_NONE); -#endif - -// Debug functions -#ifdef WP_FLOOD - -#ifndef RELEASE_BUILD -void botsheadtome(void) -{ - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - - bots[i]->pBot->GoToDebugGoal(player1->o); - } -} - -COMMAND(botsheadtome, ARG_NONE); - -void setdebuggoal(void) { v_debuggoal = player1->o; }; -COMMAND(setdebuggoal, ARG_NONE); - -void botsheadtodebuggoal(void) -{ - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - - bots[i]->pBot->GoToDebugGoal(v_debuggoal); - } -} - -COMMAND(botsheadtodebuggoal, ARG_NONE); - -#endif // RELEASE_BUILD - -#endif // WP_FLOOD - -// End debug functions -// Waypoint commands end - - -// Bot class begin - -bool CBot::FindWaypoint() -{ - waypoint_s *wp, *wpselect; - int index; - float distance, min_distance[3]; - waypoint_s *min_wp[3]; - - for (index=0; index < 3; index++) - { - min_distance[index] = 9999.0; - min_wp[index] = NULL; - } - - TLinkedList::node_s *pNode = m_pCurrentWaypoint->pNode->ConnectedWPs.GetFirst(); - - while (pNode) - { - if ((pNode->Entry->iFlags & W_FL_INTAG) && - SOLID(S((int)pNode->Entry->v_origin.x, (int)pNode->Entry->v_origin.y))) - { - pNode = pNode->next; - continue; - } - - wp = GetWPFromNode(pNode->Entry); - if (!wp) - { - pNode = pNode->next; - continue; - } - - // if index is not a current or recent previous waypoint... - if ((wp != m_pCurrentWaypoint) && - (wp != m_pPrevWaypoints[0]) && - (wp != m_pPrevWaypoints[1]) && - (wp != m_pPrevWaypoints[2]) && - (wp != m_pPrevWaypoints[3]) && - (wp != m_pPrevWaypoints[4])) - { - // find the distance from the bot to this waypoint - distance = GetDistance(wp->pNode->v_origin); - - if (distance < min_distance[0]) - { - min_distance[2] = min_distance[1]; - min_wp[2] = min_wp[1]; - - min_distance[1] = min_distance[0]; - min_wp[1] = min_wp[0]; - - min_distance[0] = distance; - min_wp[0] = wp; - } - else if (distance < min_distance [1]) - { - min_distance[2] = min_distance[1]; - min_wp[2] = min_wp[1]; - - min_distance[1] = distance; - min_wp[1] = wp; - } - else if (distance < min_distance[2]) - { - min_distance[2] = distance; - min_wp[2] = wp; - } - } - pNode = pNode->next; - } - - wpselect = NULL; - - // about 20% of the time choose a waypoint at random - // (don't do this any more often than every 10 seconds) - - if ((RandomLong(1, 100) <= 20) && (m_iRandomWaypointTime <= lastmillis)) - { - m_iRandomWaypointTime = lastmillis + 10000; - - if (min_wp[2]) - index = RandomLong(0, 2); - else if (min_wp[1]) - index = RandomLong(0, 1); - else if (min_wp[0]) - index = 0; - else - return false; // no waypoints found! - - wpselect = min_wp[index]; - } - else - { - // use the closest waypoint that has been recently used - wpselect = min_wp[0]; - } - - if (wpselect) // was a waypoint found? - { - m_pPrevWaypoints[4] = m_pPrevWaypoints[3]; - m_pPrevWaypoints[3] = m_pPrevWaypoints[2]; - m_pPrevWaypoints[2] = m_pPrevWaypoints[1]; - m_pPrevWaypoints[1] = m_pPrevWaypoints[0]; - m_pPrevWaypoints[0] = m_pCurrentWaypoint; - - SetCurrentWaypoint(wpselect); - return true; - } - - return false; // couldn't find a waypoint -} - -bool CBot::HeadToWaypoint() -{ - if (!m_pCurrentWaypoint) - return false; // Can't head to waypoint - - bool Touching = false; - float WPDist = GetDistance(m_pCurrentWaypoint->pNode->v_origin); - -#ifndef RELEASE_BUILD - if (m_pCurrentGoalWaypoint && vis(m_vGoal, g_vecZero)) - condebug("Warning: m_vGoal unset"); -#endif - - // did the bot run past the waypoint? (prevent the loop-the-loop problem) - if ((m_fPrevWaypointDistance > 1.0) && (WPDist > m_fPrevWaypointDistance) && - (WPDist <= 5.0f)) - Touching = true; - // bot needs to be close for jump and trigger waypoints - else if ((m_pCurrentWaypoint->pNode->iFlags & W_FL_JUMP) || - (m_pCurrentWaypoint->pNode->iFlags & W_FL_TRIGGER)) - Touching = (WPDist <= 1.5f); - else if (m_pCurrentWaypoint->pNode->iFlags & W_FL_TELEPORT) - Touching = (WPDist <= 4.0f); - // are we close enough to a target waypoint... - else if (WPDist <= 3.0f) - { - if (!m_pCurrentGoalWaypoint || (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) || - IsVisible(m_vGoal) || (WPDist <= 1.5f)) - Touching = true; - // If the bot has a goal check if he can see his next wp - if (m_pCurrentGoalWaypoint && (m_pCurrentGoalWaypoint != m_pCurrentWaypoint) && - !m_AStarNodeList.Empty() && (WPDist >= 1.0f) && - !IsVisible(m_AStarNodeList.GetFirst()->Entry->pNode->v_origin)) - Touching = false; - } - - // save current distance as previous - m_fPrevWaypointDistance = WPDist; - - // Reached the waypoint? - if (Touching) - { - // Does this waypoint has a targetyaw? - if (m_pCurrentWaypoint->pNode->sYaw != -1) - { - // UNDONE: Inhuman - m_pMyEnt->yaw = m_pMyEnt->targetyaw = m_pCurrentWaypoint->pNode->sYaw; - } - - // Reached a jump waypoint? - if (m_pCurrentWaypoint->pNode->iFlags & W_FL_JUMP) - m_pMyEnt->jumpnext = true; - - m_fPrevWaypointDistance = 0.0f; - - // Does the bot has a goal? - if (m_pCurrentGoalWaypoint) - { - if (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) - { - if (m_AStarNodeList.Empty()) - { - if (!AStar()) - { - // Bot is calculating a new path, just stand still for now - ResetMoveSpeed(); - m_iWaypointTime += 200; - return true; - } - else - { - if (m_AStarNodeList.Empty()) - { - //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); - return false; // Couldn't get a new wp to go to - } - } - } - - m_pCurrentWaypoint = m_AStarNodeList.Pop(); - - if (!IsVisible(m_pCurrentWaypoint->pNode->v_origin)) - //(!(m_pCurrentWaypoint->iFlags & W_FL_TELEPORT))) - { - //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); - condebug("Next WP not visible"); - return false; - } - - SetCurrentWaypoint(m_pCurrentWaypoint); - } - else - { - // Bot reached the goal waypoint but couldn't reach the goal itself - // (otherwise we wouldn't be in this function) - //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); - return false; - } - } - else - { - short index = 4; - bool status; - - // try to find the next waypoint - while (((status = FindWaypoint()) == false) && (index > 0)) - { - // if waypoint not found, clear oldest previous index and try again - - m_pPrevWaypoints[index] = NULL; - index--; - } - - if (status == false) - { - ResetWaypointVars(); - condebug("Couldn't find new random waypoint"); - return false; - } - } - m_iWaypointHeadPauseTime = lastmillis + 75; - m_iWaypointTime += 75; - } - - // keep turning towards the waypoint... - /*if (m_pCurrentWaypoint->pNode->iFlags & W_FL_FLOOD) - {UNDONE? - vec aim = m_pCurrentWaypoint->pNode->v_origin; - aim.x+=0.5f; - aim.y+=0.5f; - AimToVec(aim); - } - else*/ - AimToVec(m_pCurrentWaypoint->pNode->v_origin); - - if (m_fYawToTurn <= 25.0f) - m_iWaypointHeadLastTurnLessTime = lastmillis; - - // Bot had to turn much for a while? - if ((m_iWaypointHeadLastTurnLessTime > 0) && - (m_iWaypointHeadLastTurnLessTime < (lastmillis - 1000))) - { - m_iWaypointHeadPauseTime = lastmillis + 200; - m_iWaypointTime += 200; - } - - if (m_iWaypointHeadPauseTime >= lastmillis) - { - m_pMyEnt->move = 0; - //conoutf("Pause in HeadToWaypoint()"); - } - else - { - // Check if bot has to jump - vec from = m_pMyEnt->o; - from.z -= (m_pMyEnt->eyeheight - 1.25f); - float flEndDist; - if (!IsVisible(from, FORWARD, 3.0f, false, &flEndDist) && - (GetDistance(from, m_pCurrentWaypoint->pNode->v_origin) > flEndDist)) - { - m_pMyEnt->jumpnext = true; - condebug("Low wall in HeadToWaypoint()"); - } - - // Check if bot has to strafe - if (m_iStrafeTime > lastmillis) - SetMoveDir(m_iMoveDir, true); - else - { - m_iStrafeTime = 0; - m_iMoveDir = DIR_NONE; - - vec forward, up, right; - AnglesToVectors(GetViewAngles(), forward, right, up); - - float flLeftDist = -1.0f, flRightDist = -1.0f; - vec dir = right; - bool bStrafe = false; - int iStrafeDir = 0; - - vmul(dir, m_pMyEnt->radius); - - // Check left - from = m_pMyEnt->o; - vsub(from, dir); - if (IsVisible(from, FORWARD, 3.0f, false, &flLeftDist)) - flLeftDist = -1.0f; - - // Check right - from = m_pMyEnt->o; - vadd(from, dir); - if (IsVisible(from, FORWARD, 3.0f, false, &flRightDist)) - flRightDist = -1.0f; - - if ((flLeftDist != -1.0f) && (flRightDist != -1.0f)) - { - if (flLeftDist < flRightDist) - { - // Strafe right - bStrafe = true; - iStrafeDir = RIGHT; - } - else if (flRightDist < flLeftDist) - { - // Strafe left - bStrafe = true; - iStrafeDir = LEFT; - } - else - { - // Randomly choose a strafe direction - bStrafe = true; - if (RandomLong(0, 1)) - iStrafeDir = LEFT; - else - iStrafeDir = RIGHT; - } - } - else if (flLeftDist != -1.0f) - { - // Strafe right - bStrafe = true; - iStrafeDir = RIGHT; - } - else if (flRightDist != -1.0f) - { - // Strafe left - bStrafe = true; - iStrafeDir = LEFT; - } - - if (bStrafe) - { - SetMoveDir(iStrafeDir, true); - m_iMoveDir = iStrafeDir; - m_iStrafeTime = lastmillis + RandomLong(30, 50); - } - } - } - - return true; -} - -// returns true when done or failure -bool CBot::HeadToGoal() -{ - // Does the bot has a goal(waypoint)? - if (m_pCurrentGoalWaypoint) - { - if (ReachedGoalWP()) - { - return false; - } - else - { - if (CurrentWPIsValid()) - { - if (m_bCalculatingAStarPath) - { - // Bot is calculating a new path, just stand still for now - ResetMoveSpeed(); - m_iWaypointTime += 200; - - // Done calculating the path? - if (AStar()) - { - if (m_AStarNodeList.Empty()) - { - return false; // Couldn't find a path - } - //else - // SetCurrentWaypoint(m_AStarNodeList.Pop()); - } - else - return true; // else just wait a little bit longer - } - //ResetMoveSpeed(); - //return true; - } - else - { - // Current waypoint isn't reachable, search new one - waypoint_s *pWP = NULL; - -#ifdef WP_FLOOD - if (m_pCurrentGoalWaypoint->pNode->iFlags & W_FL_FLOOD) - pWP = GetNearestFloodWP(8.0f); - else -#endif - pWP = GetNearestWaypoint(15.0f); - - if (!pWP || (pWP == m_pCurrentWaypoint)) - return false; - - SetCurrentWaypoint(pWP); - if (AStar()) - { - if (m_AStarNodeList.Empty()) return false; - } - else - { - m_iWaypointTime += 200; - ResetMoveSpeed(); - return true; - } - } - - //debugbeam(m_pMyEnt->o, m_pCurrentWaypoint->pNode->v_origin); - } - } - else - return false; - - return HeadToWaypoint(); -} - -// return true when done calculating -bool CBot::AStar() -{ - if (!m_pCurrentGoalWaypoint || !m_pCurrentWaypoint) - { - if (m_bCalculatingAStarPath) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - } - return true; - } - - // Ideas by PMB : - // * Make locals static to speed up a bit - // * MaxCycles per frame and make it fps dependent - - static int iMaxCycles; - static int iCurrentCycles; - static short newg; - static waypoint_s *n, *n2; - static TLinkedList::node_s *pPath = NULL; - static bool bPathFailed; - - iMaxCycles = BotManager.m_iFrameTime / 10; - if (iMaxCycles < 10) iMaxCycles = 10; - //condebug("MaxCycles: %d", iMaxCycles); - iCurrentCycles = 0; - bPathFailed = false; - - if (!m_bCalculatingAStarPath) - { - if ((BotManager.m_sUsingAStarBotsCount+1) > BotManager.m_sMaxAStarBots) - { - return true; - } - - BotManager.m_sUsingAStarBotsCount++; - - CleanAStarLists(false); - - m_pCurrentWaypoint->g[0] = m_pCurrentWaypoint->g[1] = 0; - m_pCurrentWaypoint->pParent[0] = m_pCurrentWaypoint->pParent[1] = NULL; - m_pCurrentGoalWaypoint->g[0] = m_pCurrentGoalWaypoint->g[1] = 0; - m_pCurrentGoalWaypoint->pParent[0] = m_pCurrentGoalWaypoint->pParent[1] = NULL; - - m_AStarNodeList.DeleteAllNodes(); - - m_AStarOpenList[0].Clear(); - m_AStarOpenList[1].Clear(); - m_AStarClosedList[0].DeleteAllNodes(); - m_AStarClosedList[1].DeleteAllNodes(); - - m_AStarOpenList[0].AddEntry(m_pCurrentWaypoint, - GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin)); - m_AStarOpenList[1].AddEntry(m_pCurrentGoalWaypoint, - GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin)); - - m_pCurrentWaypoint->bIsOpen[0] = m_pCurrentGoalWaypoint->bIsOpen[1] = true; - m_pCurrentWaypoint->bIsOpen[1] = m_pCurrentGoalWaypoint->bIsOpen[0] = false; - m_pCurrentWaypoint->bIsClosed[0] = m_pCurrentGoalWaypoint->bIsClosed[1] = false; - m_pCurrentWaypoint->bIsClosed[1] = m_pCurrentGoalWaypoint->bIsClosed[0] = false; - } - - while(!m_AStarOpenList[0].Empty()) - { - if (iCurrentCycles >= (iMaxCycles/2)) - { - m_bCalculatingAStarPath = true; - break; - } - - n = m_AStarOpenList[0].Pop(); - n->bIsOpen[0] = false; - - if (n->pNode->FailedGoalList.IsInList(m_pCurrentGoalWaypoint->pNode)) - { - bPathFailed = true; - break; // Can't make path to goal - } - - // Done with calculating - if (n == m_pCurrentGoalWaypoint) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - - while(n) - { - m_AStarNodeList.PushNode(n); - n = n->pParent[0]; - } - - CleanAStarLists(false); - - return true; - } - - pPath = n->pNode->ConnectedWPs.GetFirst(); - while(pPath) - { - if ((pPath->Entry->iFlags & W_FL_INTAG) && - SOLID(S((int)pPath->Entry->v_origin.x, (int)pPath->Entry->v_origin.y))) - { - pPath = pPath->next; - continue; - } - - n2 = GetWPFromNode(pPath->Entry); - - if (!n2) - { - pPath = pPath->next; - continue; - } - - if (n2->bIsClosed[1]) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - - while(n2) - { - m_AStarNodeList.AddNode(n2); - n2 = n2->pParent[1]; - } - - while(n) - { - m_AStarNodeList.PushNode(n); - n = n->pParent[0]; - } - - CleanAStarLists(false); - - return true; - } - - newg = n->g[0] + (short)AStarCost(n, n2); - - if ((n2->g[0] <= newg) && (n2->bIsOpen[0] || n2->bIsClosed[0])) - { - pPath = pPath->next; - continue; - } - - n2->pParent[0] = n; - n2->g[0] = newg; - n2->bIsClosed[0] = false; - - if (!n2->bIsOpen[0]) - { - m_AStarOpenList[0].AddEntry(n2, n2->g[1] + GetDistance(n2->pNode->v_origin, - m_pCurrentGoalWaypoint->pNode->v_origin)); - n2->bIsOpen[0] = true; - } - pPath = pPath->next; - } - - m_AStarClosedList[0].PushNode(n); - n->bIsClosed[0] = true; - iCurrentCycles++; - } - - if (!bPathFailed) - { - while(!m_AStarOpenList[1].Empty()) - { - if (iCurrentCycles >= iMaxCycles) - { - m_bCalculatingAStarPath = true; - return false; - } - - n = m_AStarOpenList[1].Pop(); - n->bIsOpen[1] = false; - - if (n->pNode->FailedGoalList.IsInList(m_pCurrentWaypoint->pNode)) - { - bPathFailed = true; - break; // Can't make path to goal - } - - // Done with calculating - if (n == m_pCurrentWaypoint) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - - while(n) - { - m_AStarNodeList.AddNode(n); - n = n->pParent[1]; - } - - CleanAStarLists(false); - - return true; - } - - pPath = n->pNode->ConnectedWPsWithMe.GetFirst(); - while(pPath) - { - if ((pPath->Entry->iFlags & W_FL_INTAG) && - SOLID(S((int)pPath->Entry->v_origin.x, (int)pPath->Entry->v_origin.y))) - { - pPath = pPath->next; - continue; - } - - n2 = GetWPFromNode(pPath->Entry); - - if (!n2) - { - pPath = pPath->next; - continue; - } - - if (n2->bIsClosed[0]) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - - while(n2) - { - m_AStarNodeList.PushNode(n2); - n2 = n2->pParent[0]; - } - - while(n) - { - m_AStarNodeList.AddNode(n); - n = n->pParent[1]; - } - - CleanAStarLists(false); - - return true; - } - - newg = n->g[1] + (short)AStarCost(n, n2); - - if ((n2->g[1] <= newg) && (n2->bIsOpen[1] || n2->bIsClosed[1])) - { - pPath = pPath->next; - continue; - } - - n2->pParent[1] = n; - n2->g[1] = newg; - n2->bIsClosed[1] = false; - - if (!n2->bIsOpen[1]) - { - m_AStarOpenList[1].AddEntry(n2, n2->g[1] + GetDistance(n2->pNode->v_origin, - m_pCurrentWaypoint->pNode->v_origin)); - n2->bIsOpen[1] = true; - } - pPath = pPath->next; - } - - m_AStarClosedList[1].PushNode(n); - n->bIsClosed[1] = true; - iCurrentCycles++; - } - } - - // Failed making path - condebug("Path failed"); - - CleanAStarLists(true); - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - return true; -} - -float CBot::AStarCost(waypoint_s *pWP1, waypoint_s *pWP2) -{ - // UNDONE? - return (GetDistance(pWP1->pNode->v_origin, pWP2->pNode->v_origin) * pWP2->pNode->sCost); -} - -void CBot::CleanAStarLists(bool bPathFailed) -{ - while(m_AStarOpenList[0].Empty() == false) - { - waypoint_s *p = m_AStarOpenList[0].Pop(); - p->bIsOpen[0] = p->bIsOpen[1] = false; - p->bIsClosed[0] = p->bIsClosed[1] = false; - p->pParent[0] = p->pParent[1] = NULL; - p->g[0] = p->g[1] = 0; - } - - while(m_AStarOpenList[1].Empty() == false) - { - waypoint_s *p = m_AStarOpenList[1].Pop(); - p->bIsOpen[0] = p->bIsOpen[1] = false; - p->bIsClosed[0] = p->bIsClosed[1] = false; - p->pParent[0] = p->pParent[1] = NULL; - p->g[0] = p->g[1] = 0; - } - - while(m_AStarClosedList[0].Empty() == false) - { - waypoint_s *p = m_AStarClosedList[0].Pop(); - p->bIsOpen[0] = p->bIsOpen[1] = false; - p->bIsClosed[0] = p->bIsClosed[1] = false; - p->pParent[0] = p->pParent[1] = NULL; - p->g[0] = p->g[1] = 0; - if (bPathFailed) - p->pNode->FailedGoalList.AddNode(m_pCurrentGoalWaypoint->pNode); - } - - while(m_AStarClosedList[1].Empty() == false) - { - waypoint_s *p = m_AStarClosedList[1].Pop(); - p->bIsOpen[0] = p->bIsOpen[1] = false; - p->bIsClosed[0] = p->bIsClosed[1] = false; - p->pParent[0] = p->pParent[1] = NULL; - p->g[0] = p->g[1] = 0; - if (bPathFailed) - p->pNode->FailedGoalList.AddNode(m_pCurrentWaypoint->pNode); - } -} - -void CBot::ResetWaypointVars() -{ - m_iWaypointTime = 0; - m_pCurrentWaypoint = NULL; - m_pCurrentGoalWaypoint = NULL; - m_pPrevWaypoints[0] = NULL; - m_pPrevWaypoints[1] = NULL; - m_pPrevWaypoints[2] = NULL; - m_pPrevWaypoints[3] = NULL; - m_pPrevWaypoints[4] = NULL; - m_fPrevWaypointDistance = 0; - m_iWaypointHeadLastTurnLessTime = 0; - m_iWaypointHeadPauseTime = 0; - if (m_bCalculatingAStarPath) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - } - m_AStarNodeList.DeleteAllNodes(); - CleanAStarLists(false); - m_bGoToDebugGoal = false; -} - -void CBot::SetCurrentWaypoint(node_s *pNode) -{ - waypoint_s *pWP = GetWPFromNode(pNode); -#ifndef RELEASE_BUILD - if (!pWP || !pNode) condebug("NULL WP In SetCurrentWP"); -#endif - - m_pCurrentWaypoint = pWP; - m_iWaypointTime = lastmillis; -} - -void CBot::SetCurrentWaypoint(waypoint_s *pWP) -{ -#ifndef RELEASE_BUILD - if (!pWP) condebug("NULL WP In SetCurrentWP(2)"); -#endif - - m_pCurrentWaypoint = pWP; - m_iWaypointTime = lastmillis; -} - -void CBot::SetCurrentGoalWaypoint(node_s *pNode) -{ - waypoint_s *pWP = GetWPFromNode(pNode); -#ifndef RELEASE_BUILD - if (!pWP || !pNode) condebug("NULL WP In SetCurrentGoalWP"); -#endif - - m_pCurrentGoalWaypoint = pWP; -} - -void CBot::SetCurrentGoalWaypoint(waypoint_s *pWP) -{ -#ifndef RELEASE_BUILD - if (!pWP) condebug("NULL WP In SetCurrentGoalWP(2)"); -#endif - - m_pCurrentGoalWaypoint = pWP; -} - -bool CBot::CurrentWPIsValid() -{ - if (!m_pCurrentWaypoint) - { - //condebug("Invalid WP: Is NULL"); - return false; - } - - // check if the bot has been trying to get to this waypoint for a while... - if ((m_iWaypointTime + 5000) < lastmillis) - { - condebug("Invalid WP: time over"); - return false; - } - -#ifndef RELEASE_BUILD - if (!IsVisible(m_pCurrentWaypoint->pNode->v_origin)) - condebug("Invalid WP: Not visible"); -#endif - - return (IsVisible(m_pCurrentWaypoint->pNode->v_origin)); -} - -bool CBot::ReachedGoalWP() -{ - if ((!m_pCurrentWaypoint) || (!m_pCurrentGoalWaypoint)) - return false; - - if (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) - return false; - - return ((GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin) <= 3.0f) && - (IsVisible(m_pCurrentGoalWaypoint->pNode->v_origin))); -} - -waypoint_s *CBot::GetWPFromNode(node_s *pNode) -{ - if (!pNode) return NULL; - - short x, y; - WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); - - TLinkedList::node_s *p = m_WaypointList[x][y].GetFirst(); - while(p) - { - if (p->Entry->pNode == pNode) - return p->Entry; - - p = p->next; - } - - return NULL; -} - -waypoint_s *CBot::GetNearestWaypoint(vec v_src, float flRange) -{ - TLinkedList::node_s *p; - waypoint_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - WaypointClass.GetNodeIndexes(v_src, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_WaypointList[x][y].GetFirst(); - - while(p) - { - if (p->Entry->pNode->iFlags & W_FL_FLOOD) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_src, p->Entry->pNode->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (::IsVisible(v_src, p->Entry->pNode->v_origin, NULL)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -waypoint_s *CBot::GetNearestTriggerWaypoint(vec v_src, float flRange) -{ - TLinkedList::node_s *p; - waypoint_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - WaypointClass.GetNodeIndexes(v_src, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_WaypointList[x][y].GetFirst(); - - while(p) - { - if (!(p->Entry->pNode->iFlags & W_FL_TRIGGER)) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_src, p->Entry->pNode->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (::IsVisible(v_src, p->Entry->pNode->v_origin, NULL)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -// Makes a waypoint list for this bot based on the list from WaypointClass -void CBot::SyncWaypoints() -{ - short x, y; - TLinkedList::node_s *p; - - // Clean everything first - for (x=0;xpNode = p->Entry; - m_WaypointList[x][y].AddNode(pWP); - -#ifndef RELEASE_BUILD - if (!GetWPFromNode(p->Entry)) condebug("Error adding bot wp!"); -#endif - - p = p->next; - } - } - } -} - -#ifdef WP_FLOOD -waypoint_s *CBot::GetNearestFloodWP(vec v_origin, float flRange) -{ - TLinkedList::node_s *p; - waypoint_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - WaypointClass.GetNodeIndexes(v_origin, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_WaypointList[x][y].GetFirst(); - - while(p) - { - if (!(p->Entry->pNode->iFlags & W_FL_FLOOD)) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_origin, p->Entry->pNode->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (::IsVisible(v_origin, p->Entry->pNode->v_origin, NULL)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -waypoint_s *CBot::GetNearestTriggerFloodWP(vec v_origin, float flRange) -{ - TLinkedList::node_s *p; - waypoint_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - WaypointClass.GetNodeIndexes(v_origin, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_WaypointList[x][y].GetFirst(); - - while(p) - { - if (!(p->Entry->pNode->iFlags & (W_FL_FLOOD | W_FL_TRIGGER))) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_origin, p->Entry->pNode->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (::IsVisible(v_origin, p->Entry->pNode->v_origin, NULL)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -void CBot::GoToDebugGoal(vec o) -{ - ResetWaypointVars(); - - node_s *wp = WaypointClass.GetNearestWaypoint(m_pMyEnt, 20.0f); - node_s *goalwp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - - if (!wp || !goalwp) - { - wp = WaypointClass.GetNearestFloodWP(m_pMyEnt, 8.0f); - goalwp = WaypointClass.GetNearestFloodWP(player1, 5.0f); - } - if (!wp || !goalwp) { condebug("No near WP"); return; } - - SetCurrentWaypoint(wp); - SetCurrentGoalWaypoint(goalwp); - m_vGoal = o; - m_bGoToDebugGoal = true; -} -#endif - -// Bot class end +// +// C++ Implementation: bot_waypoint +// +// Description: Waypoint class for bots +// +// +// Author: Rick +// + +#include "../cube.h" + +vec v_debuggoal = g_vecZero; + +#if defined AC_CUBE +CACWaypointClass WaypointClass; +#elif defined VANILLA_CUBE +CCubeWaypointClass WaypointClass; +#endif + +// Waypoint class begin + +CWaypointClass::CWaypointClass(void) : m_bDrawWaypoints(false), m_bDrawWPPaths(true), + m_bAutoWaypoint(false), m_bAutoPlacePaths(true), + m_bDrawWPText(false), m_vLastCreatedWP(g_vecZero), + m_bFlooding(false), m_bFilteringNodes(false), + m_iFloodStartTime(0), m_iCurFloodX(0), + m_iCurFloodY(0), m_iFloodSize(0), + m_iFilteredNodes(0), m_iWaypointCount(0) +{ + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + while(!m_Waypoints[i][j].Empty()) + delete m_Waypoints[i][j].Pop(); + } + } + + m_szMapName[0] = 0; +} + +void CWaypointClass::Init() +{ + Clear(); // Free previously allocated path memory + + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + while(!m_Waypoints[i][j].Empty()) + delete m_Waypoints[i][j].Pop(); + } + } + + m_fPathDrawTime = 0.0; + m_iWaypointCount = 0; + m_vLastCreatedWP = g_vecZero; +} + +void CWaypointClass::Clear() +{ + for(int i=0;iConnectedWPs.DeleteAllNodes(); + p->ConnectedWPsWithMe.DeleteAllNodes(); + BotManager.DelWaypoint(p); + delete p; + } + } + } +} + +// returns true if waypoints succesfull loaded +bool CWaypointClass::LoadWaypoints() +{ + FILE *bfp; + char szWPFileName[64]; + char filename[256]; + waypoint_header_s header; + short num, triggernr, yaw; + int i, j, flags; + vec from, to; + + strcpy(szWPFileName, m_szMapName); + strcat(szWPFileName, ".wpt"); + + BotManager.MakeBotFileName(szWPFileName, "waypoints", NULL, filename); + + bfp = fopen(filename, "rb"); + + BotManager.m_sCurrentTriggerNr = -1; + + // if file exists, read the waypoint structure from it + if (bfp != NULL) + { + fread(&header, sizeof(header), 1, bfp); + + header.szFileType[10] = 0; + if (strcmp(header.szFileType, "cube_bot") == 0) + { + header.szMapName[31] = 0; + + if (strcmp(header.szMapName, m_szMapName) == 0) + { + Init(); // remove any existing waypoints + + // Check which waypoint file version this is, handle each of them different + if (header.iFileVersion == 1) + { + // First version, works with an big array and has a limit + + waypoint_version_1_s WPs[1024]; + int path_index; + + m_iWaypointCount = header.iWPCount; + for (i=0; i < header.iWPCount; i++) + { + fread(&WPs[i], sizeof(WPs[0]), 1, bfp); + WPs[i].ConnectedWPs.Reset(); // We get garbage when we read this from + // a file, so just clear it + + // Convert to new waypoint structure + node_s *pNode = new node_s(WPs[i].v_origin, WPs[i].iFlags, 0); + + short x, y; + GetNodeIndexes(pNode->v_origin, &x, &y); + + m_Waypoints[x][y].AddNode(pNode); + BotManager.AddWaypoint(pNode); + } + + // read and add waypoint paths... + for (i=0; i < m_iWaypointCount; i++) + { + // read the number of paths from this node... + fread(&num, sizeof(num), 1, bfp); + + // See which waypoint the current is + node_s *pCurrent = GetWaypointFromVec(WPs[i].v_origin); + + if (!pCurrent) + { + conoutf("Error: NULL path in waypoint file"); + continue; + } + + for (j=0; j < num; j++) + { + fread(&path_index, sizeof(path_index), 1, bfp); + + // See which waypoint this is + node_s *pTo = GetWaypointFromVec(WPs[path_index].v_origin); + + if (!pTo) + { + conoutf("Error: NULL path in waypoint file"); + continue; + } + + AddPath(pCurrent, pTo); + } + } + conoutf("Old waypoint version(%d) converted to new version(%d)", + header.iFileVersion, WAYPOINT_VERSION); + conoutf("Use the wpsave command to convert permently"); + + } + else if (header.iFileVersion == 2) + { + m_iWaypointCount = header.iWPCount; + + for (i=0; i < header.iWPCount; i++) + { + fread(&from, sizeof(from), 1, bfp); // Read origin + fread(&flags, sizeof(flags), 1, bfp); // Read waypoint flags + fread(&triggernr, sizeof(triggernr), 1, bfp); // Read trigger nr + fread(&yaw, sizeof(yaw), 1, bfp); // Read target yaw + + node_s *pNode = new node_s(from, flags, triggernr, yaw); + + short x, y; + GetNodeIndexes(pNode->v_origin, &x, &y); + + m_Waypoints[x][y].AddNode(pNode); + BotManager.AddWaypoint(pNode); + + if ((BotManager.m_sCurrentTriggerNr == -1) || + (triggernr < BotManager.m_sCurrentTriggerNr)) + BotManager.m_sCurrentTriggerNr = triggernr; + } + + for (i=0; i < header.iWPCount; i++) + { + // read the number of paths from this node... + fread(&num, sizeof(num), 1, bfp); + fread(&from, sizeof(from), 1, bfp); + + if (!num) + continue; + + node_s *pCurrent = GetWaypointFromVec(from); + + if (!pCurrent) + { + conoutf("Error: NULL path in waypoint file"); + + for(j=0;j WAYPOINT_VERSION) + { + conoutf("Error: Waypoint file is newer then current, upgrade cube bot."); + fclose(bfp); + return false; + } + else + { + conoutf("Error: Unknown waypoint version for cube bot"); + fclose(bfp); + return false; + } + } + else + { + conoutf("Waypoints aren't for map %s but for map %s", int(m_szMapName), + int(header.szMapName)); + fclose(bfp); + return false; + } + } + else + { + conoutf("Waypoint file isn't for cube bot"); + //conoutf("Header FileType: %s", int(header.szFileType)); + + fclose(bfp); + return false; + } + + fclose(bfp); + + //RouteInit(); + } + else + { + conoutf("Waypoint file %s does not exist", int(filename)); + return false; + } + + if (BotManager.m_sCurrentTriggerNr == -1) + BotManager.m_sCurrentTriggerNr = 0; + + ReCalcCosts(); + + conoutf("Waypoints for map %s loaded", int(m_szMapName)); + return true; +} + +void CWaypointClass::SaveWaypoints() +{ + char filename[256]; + char mapname[64]; + waypoint_header_s header; + short int num; + TLinkedList::node_s *pPath; + + strcpy(header.szFileType, "cube_bot"); + + header.iFileVersion = WAYPOINT_VERSION; + + header.iFileFlags = 0; // not currently used + + header.iWPCount = m_iWaypointCount; + + memset(header.szMapName, 0, sizeof(header.szMapName)); + strncpy(header.szMapName, m_szMapName, 31); + header.szMapName[31] = 0; + + strcpy(mapname, m_szMapName); + strcat(mapname, ".wpt"); + + BotManager.MakeBotFileName(mapname, "waypoints", NULL, filename); + + FILE *bfp = fopen(filename, "wb"); + + if (!bfp) + { + conoutf("Error writing waypoint file, check if the directory \"bot/waypoint\" exists and " + "the right permissions are set"); + return; + } + + // write the waypoint header to the file... + fwrite(&header, sizeof(header), 1, bfp); + + // write the waypoint data to the file... + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); + while(p) + { + fwrite(&p->Entry->v_origin, sizeof(p->Entry->v_origin), 1, bfp); // Write origin + fwrite(&p->Entry->iFlags, sizeof(p->Entry->iFlags), 1, bfp); // Write waypoint flags + fwrite(&p->Entry->sTriggerNr, sizeof(p->Entry->sTriggerNr), 1, bfp); // Write trigger nr + fwrite(&p->Entry->sYaw, sizeof(p->Entry->sYaw), 1, bfp); // Write target yaw + + p = p->next; + } + } + } + + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); + while(p) + { + // save the waypoint paths... + + // count the number of paths from this node... + pPath = p->Entry->ConnectedWPs.GetFirst(); + num = p->Entry->ConnectedWPs.NodeCount(); + + fwrite(&num, sizeof(num), 1, bfp); // write the count + fwrite(&p->Entry->v_origin, sizeof(p->Entry->v_origin), 1, bfp); // write the origin of this path + + // now write out each path... + while (pPath != NULL) + { + fwrite(&pPath->Entry->v_origin, sizeof(pPath->Entry->v_origin), 1, bfp); + pPath = pPath->next; // go to next node in linked list + } + p = p->next; + } + } + } + + fclose(bfp); +} + +bool CWaypointClass::LoadWPExpFile() +{ + FILE *bfp; + char szWPFileName[64]; + char filename[256]; + waypoint_header_s header; + short int num; + int i, j; + vec from, to; + + if (m_iWaypointCount == 0) + return false; + + strcpy(szWPFileName, m_szMapName); + strcat(szWPFileName, ".exp"); + + BotManager.MakeBotFileName(szWPFileName, "waypoints", NULL, filename); + + bfp = fopen(filename, "rb"); + + // if file exists, read the waypoint structure from it + if (bfp != NULL) + { + fread(&header, sizeof(header), 1, bfp); + + header.szFileType[10] = 0; + if (strcmp(header.szFileType, "cube_bot") == 0) + { + header.szMapName[31] = 0; + + if (strcmp(header.szMapName, m_szMapName) == 0) + { + // Check which waypoint file version this is, handle each of them different + if (header.iFileVersion == 1) + { + for (i=0; i < header.iWPCount; i++) + { + // read the number of paths from this node... + fread(&num, sizeof(num), 1, bfp); + fread(&from, sizeof(vec), 1, bfp); + + if (!num) continue; + + node_s *pCurrent = GetWaypointFromVec(from); + + if (!pCurrent) + { + conoutf("Error: NULL node in waypoint experience file"); + continue; + } + + for (j=0; j < num; j++) + { + fread(&to, sizeof(vec), 1, bfp); + node_s *p = GetWaypointFromVec(to); + if (p) + { + pCurrent->FailedGoalList.AddNode(p); + } + } + } + } + else if (header.iFileVersion > EXP_WP_VERSION) + { + conoutf("Error: Waypoint experience file is newer then current, upgrade cube bot."); + fclose(bfp); + return false; + } + else + { + conoutf("Error: Unknown waypoint experience file version for cube bot"); + fclose(bfp); + return false; + } + } + else + { + conoutf("Waypoint experience file isn't for map %s but for map %s", int(m_szMapName), + int(header.szMapName)); + fclose(bfp); + return false; + } + } + else + { + conoutf("Waypoint experience file isn't for cube bot"); + //conoutf("Header FileType: %s", int(header.szFileType)); + + fclose(bfp); + return false; + } + + fclose(bfp); + + //RouteInit(); + } + else + { + conoutf("Waypoint experience file %s does not exist", int(filename)); + return false; + } + + conoutf("Waypoint experience file for map %s loaded", int(m_szMapName)); + return true; +} + +void CWaypointClass::SaveWPExpFile() +{ + if (!m_iWaypointCount) + return; + + char filename[256]; + char mapname[64]; + waypoint_header_s header; + short int num; + + strcpy(header.szFileType, "cube_bot"); + + header.iFileVersion = EXP_WP_VERSION; + + header.iFileFlags = 0; // not currently used + + header.iWPCount = m_iWaypointCount; + + memset(header.szMapName, 0, sizeof(header.szMapName)); + strncpy(header.szMapName, m_szMapName, 31); + header.szMapName[31] = 0; + + strcpy(mapname, m_szMapName); + strcat(mapname, ".exp"); + + BotManager.MakeBotFileName(mapname, "waypoints", NULL, filename); + + FILE *bfp = fopen(filename, "wb"); + + if (!bfp) + { + conoutf("Error writing waypoint experience file, check if the directory \"bot/waypoint\" exists and " + "the right permissions are set"); + return; + } + + // write the waypoint header to the file... + fwrite(&header, sizeof(header), 1, bfp); + + // save the waypoint experience data... + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); + while(p) + { + // count the number of paths from this node... + TLinkedList::node_s *p2 = p->Entry->FailedGoalList.GetFirst(); + num = p->Entry->FailedGoalList.NodeCount(); + + if (!num || !p2) continue; + + fwrite(&num, sizeof(num), 1, bfp); // write the count + fwrite(&p->Entry->v_origin, sizeof(vec), 1, bfp); // write the origin of this node + + while (p2 != NULL) + { + // Write out the node which a bot can't reach with the current node + fwrite(&p2->Entry->v_origin, sizeof(vec), 1, bfp); + p2 = p2->next; // go to next node in linked list + } + p = p->next; + } + } + } + + fclose(bfp); +} + +void CWaypointClass::Think() +{ + if (dedserv) return; + +#ifdef WP_FLOOD + FloodThink(); +#endif + + if (m_bAutoWaypoint) // is auto waypoint on? + { + // find the distance from the last used waypoint + float flDistance = GetDistance(m_vLastCreatedWP, player1->o); + + bool NoLastCreatedWP = ((m_vLastCreatedWP.x == 0) && (m_vLastCreatedWP.y == 0) && + (m_vLastCreatedWP.z == 0)); + + if ((flDistance > 3.0f) || NoLastCreatedWP) + { + // check that no other reachable waypoints are nearby... + if (!GetNearestWaypoint(player1, 10.0f)) + { + AddWaypoint(player1->o, true); // place a waypoint here + } + } + } + + // Draw info for nearest waypoint? + if (m_bDrawWPText) + { + node_s *nearestwp = GetNearestWaypoint(player1, 20.0f); + +#ifdef WP_FLOOD + if (!nearestwp) + nearestwp = GetNearestFloodWP(player1, 10.0f); +#endif + + if (nearestwp) + { + char szWPInfo[256]; + sprintf(szWPInfo, "Distance nearest waypoint: %f", GetDistance(player1->o, nearestwp->v_origin)); + AddScreenText(szWPInfo); + + strcpy(szWPInfo, "Flags: "); + if (nearestwp->iFlags & W_FL_TELEPORT) + strcat(szWPInfo, "Teleport "); + if (nearestwp->iFlags & W_FL_TELEPORTDEST) + strcat(szWPInfo, "Teleport destination "); + if (nearestwp->iFlags & W_FL_JUMP) + strcat(szWPInfo, "Jump "); + if (nearestwp->iFlags & W_FL_TRIGGER) + { + char sz[32]; + sprintf(sz, "Trigger(nr %d) ", nearestwp->sTriggerNr); + strcat(szWPInfo, sz); + } + if (nearestwp->iFlags & W_FL_INTAG) + strcat(szWPInfo, "In tagged cube(s) "); + if (strlen(szWPInfo) == 7) + strcat(szWPInfo, "None"); + + AddScreenText(szWPInfo); + + if (nearestwp->sYaw != -1) + AddScreenText("yaw %d", nearestwp->sYaw); + + sprintf(szWPInfo, "Waypoint has %d connections", + nearestwp->ConnectedWPs.NodeCount()); + AddScreenText(szWPInfo); + AddScreenText("Base Cost: %d", nearestwp->sCost); + } + } + + if (m_bDrawWaypoints) // display the waypoints if turned on... + { + DrawNearWaypoints(); + } +} + +void CWaypointClass::DrawNearWaypoints() +{ + TLinkedList::node_s *pNode; + node_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = ceil(15.0f / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + GetNodeIndexes(player1->o, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + node_s *nearestwp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + +#ifdef WP_FLOOD + if (!nearestwp) + nearestwp = GetNearestFloodWP(player1, 20.0f); +#endif + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + pNode = m_Waypoints[x][y].GetFirst(); + + while(pNode) + { + flDist = GetDistance(player1->o, pNode->Entry->v_origin); + if (flDist <= 15) + { + vec o = pNode->Entry->v_origin; + vec e = o; + o.z -= 2; + e.z += 2; + + if (pNode->Entry->iFlags & W_FL_JUMP) + { + // draw a red waypoint + linestyle(2.5f, 0xFF, 0x40, 0x40); + } + else if (nearestwp == pNode->Entry) + { + // draw a green waypoint + linestyle(2.5f, 0x40, 0xFF, 0x40); + } + else + { + // draw a blue waypoint + linestyle(2.5f, 0x40, 0x40, 0xFF); + } + + line(int(o.x), int(o.y), int(o.z), int(e.x), int(e.y), int(e.z)); + + if (flNearestDist > flDist) + { + flNearestDist = flDist; + pNearest = pNode->Entry; + } + } + + pNode = pNode->next; + } + } + } + + if (pNearest) + { + pNode = pNearest->ConnectedWPs.GetFirst(); + + linestyle(2.0f, 0xFF, 0xFF, 0xFF); + + while(pNode) + { + vec o = pNode->Entry->v_origin; + vec e = pNearest->v_origin; + + line(int(o.x), int(o.y), int(o.z), int(e.x), int(e.y), int(e.z)); + + pNode = pNode->next; + } + + // Has this waypoint an target yaw? + if (pNearest->sYaw != -1) + { + vec angles = { 0, pNearest->sYaw, 0 }; + vec from = pNearest->v_origin, end = pNearest->v_origin; + vec forward, right, up; + + from.z = end.z = (pNearest->v_origin.z-1.0f); + + AnglesToVectors(angles, forward, right, up); + vmul(forward, 5.0f); + vadd(end, forward); + + linestyle(2.0f, 0xFF, 0x40, 0x40); + line(int(from.x), int(from.y), int(from.z), int(end.x), int(end.y), int(end.z)); + } + } + +#ifndef RELEASE_BUILD + // check if path waypointing is on... + if (m_bDrawWPPaths) + { + // Draw path from first bot + if (bots.length() && bots[0] && bots[0]->pBot && bots[0]->pBot->m_pCurrentWaypoint && + bots[0]->pBot->m_pCurrentGoalWaypoint) + { + CBot *pB = bots[0]->pBot; + if (!pB->m_bCalculatingAStarPath && !pB->m_AStarNodeList.Empty()) + { + TLinkedList::node_s *pNode = pB->m_AStarNodeList.GetFirst(), *pNext; + + linestyle(2.5f, 0xFF, 0x40, 0x40); + + line((int)pB->m_pCurrentWaypoint->pNode->v_origin.x, + (int)pB->m_pCurrentWaypoint->pNode->v_origin.y, + (int)pB->m_pCurrentWaypoint->pNode->v_origin.z, + (int)pNode->Entry->pNode->v_origin.x, + (int)pNode->Entry->pNode->v_origin.y, + (int)pNode->Entry->pNode->v_origin.z); + + while(pNode && pNode->next) + { + pNext = pNode->next; + vec &v1 = pNode->Entry->pNode->v_origin; + vec &v2 = pNext->Entry->pNode->v_origin; + + line(int(v1.x), int(v1.y), int(v1.z), int(v2.x), int(v2.y), int(v2.z)); + + pNode = pNode->next; + } + } + } + } + + extern bool intermission; + if (intermission) return; + + for(int i=0;iPrevLocations.prevloc[i], g_vecZero)) continue; + vec v1 = player1->PrevLocations.prevloc[i]; + v1.z -= 1.0f; + vec v2 = v1; + v2.z += 2.0f; + linestyle(2.5f, 0xFF, 0x40, 0x40); + line(int(v1.x), int(v1.y), int(v1.z), int(v2.x), int(v2.y), int(v2.z)); + } +#endif +} + +// Add waypoint at location o, returns pointer of created wp +node_s *CWaypointClass::AddWaypoint(vec o, bool connectwp) +{ + short x, y; + int flags = 0; + if (S((int)o.x, (int)o.y)->tag) flags |= W_FL_INTAG; + + node_s *pNode = new node_s(o, flags, 0); + m_vLastCreatedWP = o; + + GetNodeIndexes(o, &x, &y); + + m_Waypoints[x][y].AddNode(pNode); + BotManager.AddWaypoint(pNode); + + m_iWaypointCount++; + + if (connectwp && m_bAutoPlacePaths) + { + // Connect new waypoint with other near waypoints. + + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); + + while(p) + { + if (p->Entry == pNode) + { + p = p->next; + continue; // skip the waypoint that was just added + } + + // check if the waypoint is reachable from the new one (one-way) + if (WPIsReachable(o, p->Entry->v_origin)) + AddPath(pNode, p->Entry); // Add a path from a to b + if (WPIsReachable(p->Entry->v_origin, pNode->v_origin)) + AddPath(p->Entry, pNode); // Add a path from b to a + + p = p->next; + } + } + } + } + + return pNode; +} + +void CWaypointClass::DeleteWaypoint(dynent *d) +{ + node_s *pWP; + + pWP = GetNearestWaypoint(d, 7.0f); + + if (!pWP) + { + conoutf("Error: Couldn´t find near waypoint"); + return; + } + + BotManager.DelWaypoint(pWP); + + // delete any paths that lead to this index... + DeletePath(pWP); + + short x, y; + GetNodeIndexes(pWP->v_origin, &x, &y); + + m_Waypoints[x][y].DeleteEntry(pWP); + + pWP->ConnectedWPs.DeleteAllNodes(); + pWP->ConnectedWPsWithMe.DeleteAllNodes(); + + m_iWaypointCount--; + delete pWP; +} + +void CWaypointClass::AddPath(node_s *pWP1, node_s *pWP2) +{ + pWP1->ConnectedWPs.AddNode(pWP2); + pWP2->ConnectedWPsWithMe.AddNode(pWP1); +} + +// Deletes all paths connected to the given waypoint +void CWaypointClass::DeletePath(node_s *pWP) +{ + TLinkedList::node_s *p; + + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + p = m_Waypoints[i][j].GetFirst(); + while (p) + { + p->Entry->ConnectedWPs.DeleteEntry(pWP); + pWP->ConnectedWPsWithMe.DeleteEntry(p->Entry); + p = p->next; + } + } + } +} + +// Deletes path between 2 waypoints(1 way) +void CWaypointClass::DeletePath(node_s *pWP1, node_s *pWP2) +{ + pWP1->ConnectedWPs.DeleteEntry(pWP2); + pWP2->ConnectedWPsWithMe.DeleteEntry(pWP1); +} + +void CWaypointClass::ManuallyCreatePath(dynent *d, int iCmd, bool TwoWay) +{ + static node_s *waypoint1 = NULL; // initialized to unassigned + static node_s *waypoint2 = NULL; // initialized to unassigned + + if (iCmd == 1) // assign source of path + { + waypoint1 = GetNearestWaypoint(d, 7.0f); + + if (!waypoint1) + { + conoutf("Error: Couldn't find near waypoint"); + return; + } + + return; + } + + if (iCmd == 2) // assign dest of path and make path + { + if (!waypoint1) + { + conoutf("Error: First waypoint unset"); + return; + } + + waypoint2 = GetNearestWaypoint(d, 7.0f); + + if (!waypoint2) + { + conoutf("Error: Couldn't find near waypoint"); + return; + } + + AddPath(waypoint1, waypoint2); + + if (TwoWay) + AddPath(waypoint2, waypoint1); + } +} + +void CWaypointClass::ManuallyDeletePath(dynent *d, int iCmd, bool TwoWay) +{ + static node_s *waypoint1 = NULL; // initialized to unassigned + static node_s *waypoint2 = NULL; // initialized to unassigned + + if (iCmd == 1) // assign source of path + { + waypoint1 = GetNearestWaypoint(d, 7.0f); + + if (!waypoint1) + { + conoutf("Error: Couldn't find near waypoint"); + return; + } + + return; + } + + if (iCmd == 2) // assign dest of path and delete path + { + if (!waypoint1) + { + conoutf("Error: First waypoint unset"); + return; + } + + waypoint2 = GetNearestWaypoint(d, 7.0f); + + if (!waypoint2) + { + conoutf("Error: Couldn't find near waypoint"); + return; + } + + DeletePath(waypoint1, waypoint2); + + if (TwoWay) + DeletePath(waypoint2, waypoint1); + } +} + +bool CWaypointClass::WPIsReachable(vec from, vec to) +{ + traceresult_s tr; + float curr_height, last_height; + + float distance = GetDistance(from, to); + + // is the destination close enough? + if (distance < REACHABLE_RANGE) + { + if (IsVisible(from, to)) + { + if (UnderWater(from) && UnderWater(to)) + { + // No need to worry about heights in water + return true; + } +/* + if (to.z > (from.z + JUMP_HEIGHT)) + { + vec v_new_src = to; + vec v_new_dest = to; + + v_new_dest.z = v_new_dest.z - (JUMP_HEIGHT + 1.0f); + + // check if we didn't hit anything, if so then it's in mid-air + if (::IsVisible(v_new_src, v_new_dest, NULL)) + { + conoutf("to is in midair"); + debugbeam(from, to); + return false; // can't reach this one + } + } +*/ + + // check if distance to ground increases more than jump height + // at points between from and to... + + vec v_temp = to; vsub(v_temp, from); + vec v_direction = Normalize(v_temp); // 1 unit long + vec v_check = from; + vec v_down = from; + + v_down.z = v_down.z - 100.0f; // straight down + + TraceLine(v_check, v_down, NULL, false, &tr); + + // height from ground + last_height = GetDistance(v_check, tr.end); + + distance = GetDistance(to, v_check); // distance from goal + + while (distance > 2.0f) + { + // move 2 units closer to the goal + v_temp = v_direction; + vmul(v_temp, 2.0f); + vadd(v_check, v_temp); + + v_down = v_check; + v_down.z = v_down.z - 100.0f; + + TraceLine(v_check, v_down, NULL, false, &tr); + + curr_height = GetDistance(v_check, tr.end); + + // is the difference in the last height and the current height + // higher that the jump height? + if ((last_height - curr_height) >= JUMP_HEIGHT) + { + // can't get there from here... + //conoutf("traces failed to to"); + return false; + } + + last_height = curr_height; + + distance = GetDistance(to, v_check); // distance from goal + } + + return true; + } + } + + return false; +} + +node_s *CWaypointClass::GetNearestWaypoint(vec v_src, float flRange) +{ + TLinkedList::node_s *pNode; + node_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + GetNodeIndexes(v_src, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + pNode = m_Waypoints[x][y].GetFirst(); + + while(pNode) + { + if (pNode->Entry->iFlags & W_FL_FLOOD) + { + pNode = pNode->next; + continue; + } + + flDist = GetDistance(v_src, pNode->Entry->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (IsVisible(v_src, pNode->Entry->v_origin, NULL)) + { + pNearest = pNode->Entry; + flNearestDist = flDist; + } + } + + pNode = pNode->next; + } + } + } + return pNearest; +} + +node_s *CWaypointClass::GetNearestTriggerWaypoint(vec v_src, float flRange) +{ + TLinkedList::node_s *pNode; + node_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + GetNodeIndexes(v_src, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + pNode = m_Waypoints[x][y].GetFirst(); + + while(pNode) + { + if ((pNode->Entry->iFlags & W_FL_FLOOD) || !(pNode->Entry->iFlags & W_FL_TRIGGER)) + { + pNode = pNode->next; + continue; + } + + flDist = GetDistance(v_src, pNode->Entry->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (IsVisible(v_src, pNode->Entry->v_origin, NULL)) + { + pNearest = pNode->Entry; + flNearestDist = flDist; + } + } + + pNode = pNode->next; + } + } + } + return pNearest; +} + +node_s *CWaypointClass::GetWaypointFromVec(const vec &v_src) +{ + static TLinkedList::node_s *pNode; + static short x, y; + + GetNodeIndexes(v_src, &x, &y); + + pNode = m_Waypoints[x][y].GetFirst(); + + while(pNode) + { + if (vis(pNode->Entry->v_origin, v_src)) + return pNode->Entry; + + pNode = pNode->next; + } + return NULL; +} + +void CWaypointClass::CalcCost(node_s *pNode) +{ + float flCost = 10.0f; + + // Check nearby cubes... + int x = int(pNode->v_origin.x); + int y = int(pNode->v_origin.y); + int a, b, row; + + for (row=0;row<=1;row++) + { + if (row==0) b = y - 6; + else b = y + 6; + + for (a=(x-6);a<=(x+6);a++) + { + if (OUTBORD(a, b)) continue; + + vec to = { a, b, GetCubeFloor(a, b) + 1.0f }; + + // See if there is a obstacle(cube or mapmodel) nearby + traceresult_s tr; + TraceLine(pNode->v_origin, to, NULL, false, &tr); + if (tr.collided) + { + float flFraction = (GetDistance(pNode->v_origin, tr.end) / + GetDistance(pNode->v_origin, to)); + flCost += (1.0f-flFraction)*0.5f; + } + + if ((abs(a) > 4) || (abs(b) > 4)) continue; + + vec from = to; + to.z -= (JUMP_HEIGHT - 1.0f); + TraceLine(from, to, NULL, false, &tr); + if (!tr.collided) + flCost += 0.5f; + } + + if (row==0) a = x - 6; + else a = x + 6; + + for (b=(y-6);b<=(y+6);b++) + { + if (OUTBORD(a, b)) continue; + + vec to = { a, b, GetCubeFloor(a, b) + 1.0f }; + + // See if there is a obstacle(cube or mapmodel) nearby + traceresult_s tr; + TraceLine(pNode->v_origin, to, NULL, false, &tr); + if (tr.collided) + { + float flFraction = (GetDistance(pNode->v_origin, tr.end) / + GetDistance(pNode->v_origin, to)); + flCost += (1.0f-flFraction)*0.5f; + } + + if ((abs(a) > 4) || (abs(b) > 4)) continue; + + vec from = to; + to.z -= (JUMP_HEIGHT - 1.0f); + TraceLine(from, to, NULL, false, &tr); + if (!tr.collided) + flCost += 0.5f; + } + } + + if (UnderWater(pNode->v_origin)) // Water is annoying + flCost += 5.0f; + + pNode->sCost = (short)flCost; +} + +void CWaypointClass::ReCalcCosts(void) +{ + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); + while(p) + { + CalcCost(p->Entry); + p = p->next; + } + } + } +} + +#ifdef WP_FLOOD + +void CWaypointClass::StartFlood() +{ + if (m_bFlooding) return; + + conoutf("Starting flood, this may take a while on large maps...."); + m_bFlooding = true; + m_iFloodStartTime = SDL_GetTicks(); + m_iCurFloodX = m_iCurFloodY = MINBORD; + m_iFloodSize = 0; +} + +void CWaypointClass::FloodThink() +{ + if (!m_bFlooding) return; + + static int x, y, count; + count = 0; + + if (!m_bFilteringNodes) + { + // loop through ALL cubes and check if we should add a waypoint here + for (x=m_iCurFloodX; x<(ssize-MINBORD); x+=4) + { + if (count >= 3) + { + AddScreenText("Flooding map with waypoints... %.2f %%", + ((float)x / float(ssize-MINBORD)) * 100.0f); + m_iCurFloodX = x; + return; + } + + count++; + + for (y=MINBORD; y<(ssize-MINBORD); y+=4) + { + vec from = { x, y, GetCubeFloor(x, y)+2.0f }; + bool bFound = CanPlaceNodeHere(from); + + if (!bFound) + { + for (int a=x-2;a<=(x+2);a++) + { + for (int b=y-2;b<=(y+2);b++) + { + if (OUTBORD(a, b)) continue; + if ((a==x) && (b==y)) continue; + makevec(&from, a, b, GetCubeFloor(a, b) + 2.0f); + if (CanPlaceNodeHere(from)) + { + bFound = true; + break; + } + } + + if (bFound) break; + } + } + + if (!bFound) continue; + + // Add WP + int flags = W_FL_FLOOD; + if (S((int)from.x, (int)from.y)->tag) flags |= W_FL_INTAG; + + node_s *pWP = new node_s(from, flags, 0); + + short i, j; + GetNodeIndexes(from, &i, &j); + m_Waypoints[i][j].PushNode(pWP); + BotManager.AddWaypoint(pWP); + m_iFloodSize += sizeof(node_s); + m_iWaypointCount++; + + // Connect with other nearby nodes + ConnectFloodWP(pWP); + } + } + } + + if (!m_bFilteringNodes) + { + m_bFilteringNodes = true; + m_iCurFloodX = m_iCurFloodY = 0; + m_iFilteredNodes = 0; + } + + count = 0; + + // Filter all nodes which aren't connected to any other nodes + for (x=m_iCurFloodX;x 3) + { + AddScreenText("Filtering useless waypoints and"); + AddScreenText("adding costs... %.2f %%", ((float)x / float(MAX_MAP_GRIDS)) * 100.0f); + m_iCurFloodX = x; + return; + } + + count++; + + for (y=0;y::node_s *pNode = m_Waypoints[x][y].GetFirst(), *pTemp; + while(pNode) + { + if (pNode->Entry->ConnectedWPs.Empty() || + pNode->Entry->ConnectedWPsWithMe.Empty()) + { + BotManager.DelWaypoint(pNode->Entry); + pTemp = pNode; + pNode = pNode->next; + delete pTemp->Entry; + m_Waypoints[x][y].DeleteNode(pTemp); + m_iFilteredNodes++; + m_iFloodSize -= sizeof(node_s); + m_iWaypointCount--; + continue; + } + else + CalcCost(pNode->Entry); + pNode = pNode->next; + } + } + } + + // Done with flooding + m_bFlooding = false; + m_bFilteringNodes = false; + + //ReCalcCosts(); + BotManager.PickNextTrigger(); + + m_iFloodSize += sizeof(m_Waypoints); + conoutf("Added %d wps in %d milliseconds", m_iWaypointCount, SDL_GetTicks()-m_iFloodStartTime); + conoutf("Filtered %d wps", m_iFilteredNodes); + + BotManager.CalculateMaxAStarCount(); + + char szSize[64]; + sprintf(szSize, "Total size: %.2f Kb", float(m_iFloodSize)/1024.0f); + conoutf(szSize); +} + +bool CWaypointClass::CanPlaceNodeHere(const vec &from) +{ + static short x, y, a, b; + static traceresult_s tr; + static vec to, v1, v2; + + x = fast_f2nat(from.x); + y = fast_f2nat(from.y); + + sqr *s = S(x, y); + if (SOLID(s)) + { + return false; + } + + if (fabs((float)(s->ceil - s->floor)) < player1->radius) + { + return false; + } + + if (GetNearestFloodWP(from, 2.0f, NULL)) return false; + + for (a=(x-1);a<=(x+1);a++) + { + for (b=(y-1);b<=(y+1);b++) + { + if ((x==a) && (y==b)) continue; + if (OUTBORD(a, b)) return false; + makevec(&v1, a, b, from.z); + v2 = v1; + v2.z -= 1000.0f; + + TraceLine(v1, v2, NULL, false, &tr, true); + to = tr.end; + + if ((a >= (x-1)) && (a <= (x+1)) && (b >= (y-1)) && (b <= (y+1))) + { + if (!tr.collided || (to.z < (from.z-4.0f))) + { + return false; + } + } + + to.z += 2.0f; + + if (from.z < (to.z-JUMP_HEIGHT)) + { + return false; + } + + TraceLine(from, to, NULL, false, &tr, true); + if (tr.collided) + return false; + } + } + + return true; +} + +void CWaypointClass::ConnectFloodWP(node_s *pWP) +{ + if (!pWP) return; + + static float flRange; + static TLinkedList::node_s *pNode; + static short i, j, MinI, MaxI, MinJ, MaxJ, x, y, Offset; + static float flDist; + static node_s *p; + + // Calculate range, based on distance to nearest node + p = GetNearestFloodWP(pWP->v_origin, 15.0f, pWP, true); + if (p) + { + flDist = GetDistance(pWP->v_origin, p->v_origin); + flRange = min(flDist+2.0f, 15.0f); + if (flRange < 5.0f) flRange = 5.0f; + } + else + return; + + Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + + GetNodeIndexes(pWP->v_origin, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (x=MinI;x<=MaxI;x++) + { + for(y=MinJ;y<=MaxJ;y++) + { + pNode = m_Waypoints[x][y].GetFirst(); + + while(pNode) + { + if (pNode->Entry == pWP) + { + pNode = pNode->next; + continue; + } + + flDist = GetDistance(pWP->v_origin, pNode->Entry->v_origin); + if (flDist <= flRange) + { + if (IsVisible(pWP->v_origin, pNode->Entry->v_origin, NULL, true)) + { + // Connect a with b + pWP->ConnectedWPs.AddNode(pNode->Entry); + pNode->Entry->ConnectedWPsWithMe.AddNode(pWP); + + // Connect b with a + pNode->Entry->ConnectedWPs.AddNode(pWP); + pWP->ConnectedWPsWithMe.AddNode(pNode->Entry); + + m_iFloodSize += (2 * sizeof(node_s *)); + } + } + + pNode = pNode->next; + } + } + } +} + +node_s *CWaypointClass::GetNearestFloodWP(vec v_origin, float flRange, node_s *pIgnore, + bool SkipTags) +{ + TLinkedList::node_s *p; + node_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + GetNodeIndexes(v_origin, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_Waypoints[x][y].GetFirst(); + + while(p) + { + if ((p->Entry == pIgnore) || (!(p->Entry->iFlags & W_FL_FLOOD))) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_origin, p->Entry->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (IsVisible(v_origin, p->Entry->v_origin, NULL, SkipTags)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +node_s *CWaypointClass::GetNearestTriggerFloodWP(vec v_origin, float flRange) +{ + TLinkedList::node_s *p; + node_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + GetNodeIndexes(v_origin, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_Waypoints[x][y].GetFirst(); + + while(p) + { + if (!(p->Entry->iFlags & (W_FL_FLOOD | W_FL_TRIGGER))) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_origin, p->Entry->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (IsVisible(v_origin, p->Entry->v_origin, NULL)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +void CWaypointClass::GetNodeIndexes(const vec &v_origin, short *i, short *j) +{ + // Function code by cheesy and PMB + //*i = abs((int)((int)(v_origin.x + (2*ssize)) / SECTOR_SIZE)); + //*j = abs((int)((int)(v_origin.y + (2*ssize)) / SECTOR_SIZE)); + //*i = (int)((v_origin.x) / ssize * MAX_MAP_GRIDS); + //*j = (int)((v_origin.y) / ssize * MAX_MAP_GRIDS); + *i = abs((int)((v_origin.x) / MAX_MAP_GRIDS)); + *j = abs((int)((v_origin.y) / MAX_MAP_GRIDS)); + + if (*i > MAX_MAP_GRIDS - 1) + *i = MAX_MAP_GRIDS - 1; + if (*j > MAX_MAP_GRIDS - 1) + *j = MAX_MAP_GRIDS - 1; +} + +#endif // WP_FLOOD +// Waypoint class end + +#if defined AC_CUBE + +// AC waypoint class begin +void CACWaypointClass::StartFlood() +{ + // UNDONE? + CWaypointClass::StartFlood(); +} + +// AC waypoint class end + +#elif defined VANILLA_CUBE + +// Cube waypoint class begin + +void CCubeWaypointClass::StartFlood() +{ + CWaypointClass::StartFlood(); + + // Add wps at triggers and teleporters and their destination + loopv(ents) + { + entity &e = ents[i]; + + if (OUTBORD(e.x, e.y)) continue; + + if (e.type == TELEPORT) + { + vec telepos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }, teledestpos = g_vecZero; + + // Find the teleport destination + int n = -1, tag = e.attr1, beenhere = -1; + for(;;) + { + n = findentity(TELEDEST, n+1); + if(n==beenhere || n<0) { conoutf("no teleport destination for tag %d", tag); break; }; + if(beenhere<0) beenhere = n; + if(ents[n].attr2==tag) + { + teledestpos.x = ents[n].x; + teledestpos.y = ents[n].y; + teledestpos.z = S(ents[n].x, ents[n].y)->floor+player1->eyeheight; + break; + } + } + + if (vis(teledestpos, g_vecZero)) continue; + + int flags = (W_FL_FLOOD | W_FL_TELEPORT); + if (S((int)telepos.x, (int)telepos.y)->tag) flags |= W_FL_INTAG; + + // Add waypoint at teleporter and teleport destination + node_s *pWP = new node_s(telepos, flags, 0); + + short i, j; + GetNodeIndexes(telepos, &i, &j); + m_Waypoints[i][j].PushNode(pWP); + BotManager.AddWaypoint(pWP); + m_iFloodSize += sizeof(node_s); + m_iWaypointCount++; + + flags = (W_FL_FLOOD | W_FL_TELEPORTDEST); + if (S((int)teledestpos.x, (int)teledestpos.y)->tag) flags |= W_FL_INTAG; + + node_s *pWP2 = new node_s(teledestpos, flags, 0); + + GetNodeIndexes(teledestpos, &i, &j); + m_Waypoints[i][j].PushNode(pWP2); + BotManager.AddWaypoint(pWP2); + m_iFloodSize += sizeof(node_s); + m_iWaypointCount++; + + // Connect the teleporter waypoint with the teleport-destination waypoint(1 way) + AddPath(pWP, pWP2); + + // Connect with other nearby nodes + ConnectFloodWP(pWP); + } + else if (e.type == CARROT) + { + vec pos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + + int flags = (W_FL_FLOOD | W_FL_TRIGGER); + if (S(e.x, e.y)->tag) flags |= W_FL_INTAG; + + node_s *pWP = new node_s(pos, flags, 0); + + short i, j; + GetNodeIndexes(pos, &i, &j); + m_Waypoints[i][j].PushNode(pWP); + BotManager.AddWaypoint(pWP); + m_iFloodSize += sizeof(node_s); + m_iWaypointCount++; + + // Connect with other nearby nodes + ConnectFloodWP(pWP); + } + else if (e.type == MAPMODEL) + { + mapmodelinfo &mmi = getmminfo(e.attr2); + if(!&mmi || !mmi.h || !mmi.rad) continue; + + float floor = (float)(S(e.x, e.y)->floor+mmi.zoff+e.attr3)+mmi.h; + + float x1 = e.x - mmi.rad; + float x2 = e.x + mmi.rad; + float y1 = e.y - mmi.rad; + float y2 = e.y + mmi.rad; + + // UNDONE? + for (float x=(x1+1.0f);x<=(x2-1.0f);x++) + { + for (float y=(y1+1.0f);y<=(y2-1.0f);y++) + { + vec from = { x, y, floor+2.0f }; + if (GetNearestFloodWP(from, 2.0f, NULL)) continue; + + // Add WP + int flags = W_FL_FLOOD; + if (S((int)x, (int)y)->tag) flags |= W_FL_INTAG; + + node_s *pWP = new node_s(from, flags, 0); + + short i, j; + GetNodeIndexes(from, &i, &j); + m_Waypoints[i][j].PushNode(pWP); + BotManager.AddWaypoint(pWP); + m_iFloodSize += sizeof(node_s); + m_iWaypointCount++; + + // Connect with other nearby nodes + ConnectFloodWP(pWP); + } + } + } + } + CWaypointClass::StartFlood(); +} + +void CCubeWaypointClass::CreateWPsAtTeleporters() +{ + loopv(ents) + { + entity &e = ents[i]; + + if (e.type != TELEPORT) continue; + if (OUTBORD(e.x, e.y)) continue; + + vec telepos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }, teledestpos = g_vecZero; + + // Find the teleport destination + int n = -1, tag = e.attr1, beenhere = -1; + for(;;) + { + n = findentity(TELEDEST, n+1); + if(n==beenhere || n<0) { conoutf("no teleport destination for tag %d", tag); continue; }; + if(beenhere<0) beenhere = n; + if(ents[n].attr2==tag) + { + teledestpos.x = ents[n].x; + teledestpos.y = ents[n].y; + teledestpos.z = S(ents[n].x, ents[n].y)->floor+player1->eyeheight; + break; + } + } + + if (vis(teledestpos, g_vecZero)) continue; + + // Add waypoint at teleporter and teleport destination + node_s *telewp = AddWaypoint(telepos, false); + node_s *teledestwp = AddWaypoint(teledestpos, false); + + if (telewp && teledestwp) + { + // Connect the teleporter waypoint with the teleport-destination waypoint(1 way) + AddPath(telewp, teledestwp); + + // Flag waypoints + telewp->iFlags = W_FL_TELEPORT; + teledestwp->iFlags = W_FL_TELEPORTDEST; + } + } +} + +void CCubeWaypointClass::CreateWPsAtTriggers() +{ + loopv(ents) + { + entity &e = ents[i]; + + if (e.type != CARROT) continue; + if (OUTBORD(e.x, e.y)) continue; + + vec pos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + + node_s *wp = AddWaypoint(pos, false); + + if (wp) + { + // Flag waypoints + wp->iFlags = W_FL_TRIGGER; + } + } +} + +#endif + +// Cube waypoint class end + +// Waypoint commands begin + +void addwp(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.AddWaypoint(player1->o, true); +} + +COMMAND(addwp, ARG_NONE); + +void delwp(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.DeleteWaypoint(player1); +} + +COMMAND(delwp, ARG_NONE); + +void wpvisible(int on) +{ + WaypointClass.SetWaypointsVisible(bool(on)); +} + +COMMAND(wpvisible, ARG_1INT); + +void wpsave(void) +{ + WaypointClass.SaveWaypoints(); +} + +COMMAND(wpsave, ARG_NONE); + +void wpload(void) +{ + WaypointClass.LoadWaypoints(); +} + +COMMAND(wpload, ARG_NONE); + +void autowp(int on) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.SetAutoWaypoint(bool(on)); +} + +COMMAND(autowp, ARG_1INT); + +void wpinfo(int on) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.SetWPInfo(bool(on)); +} + +COMMAND(wpinfo, ARG_1INT); + +void addpath1way1(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyCreatePath(player1, 1, false); +} + +COMMAND(addpath1way1, ARG_NONE); + +void addpath1way2(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyCreatePath(player1, 2, false); +} + +COMMAND(addpath1way2, ARG_NONE); + +void addpath2way1(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyCreatePath(player1, 1, true); +} + +COMMAND(addpath2way1, ARG_NONE); + +void addpath2way2(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyCreatePath(player1, 2, true); +} + +COMMAND(addpath2way2, ARG_NONE); + +void delpath1way1(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyDeletePath(player1, 1, false); +} + +COMMAND(delpath1way1, ARG_NONE); + +void delpath1way2(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyDeletePath(player1, 2, false); +} + +COMMAND(delpath1way2, ARG_NONE); + +void delpath2way1(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyDeletePath(player1, 1, true); +} + +COMMAND(delpath2way1, ARG_NONE); + +void delpath2way2(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyDeletePath(player1, 2, true); +} + +COMMAND(delpath2way2, ARG_NONE); + +void setjumpwp(void) +{ + node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + if (wp) + { + WaypointClass.SetWPFlags(wp, W_FL_JUMP); + } +} + +COMMAND(setjumpwp, ARG_NONE); + +void unsetjumpwp(void) +{ + node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + if (wp) + { + WaypointClass.UnsetWPFlags(wp, W_FL_JUMP); + } +} + +COMMAND(unsetjumpwp, ARG_NONE); + +void setwptriggernr(int nr) +{ + node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + if (wp) + { + WaypointClass.SetWPTriggerNr(wp, nr); + } +} + +COMMAND(setwptriggernr, ARG_1INT); + +void setwpyaw(void) +{ + node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + if (wp) + { + WaypointClass.SetWPYaw(wp, short(player1->yaw)); + } +} + +COMMAND(setwpyaw, ARG_NONE); + +#ifdef WP_FLOOD +void startflood(void) +{ + WaypointClass.StartFlood(); +} + +COMMAND(startflood, ARG_NONE); +#endif + +#ifdef VANILLA_CUBE +// Commands specific for cube +void addtelewps(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.CreateWPsAtTeleporters(); +} + +COMMAND(addtelewps, ARG_NONE); + +void addtriggerwps(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.CreateWPsAtTriggers(); +} + +COMMAND(addtriggerwps, ARG_NONE); +#endif + +// Debug functions +#ifdef WP_FLOOD + +#ifndef RELEASE_BUILD +void botsheadtome(void) +{ + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + + bots[i]->pBot->GoToDebugGoal(player1->o); + } +} + +COMMAND(botsheadtome, ARG_NONE); + +void setdebuggoal(void) { v_debuggoal = player1->o; }; +COMMAND(setdebuggoal, ARG_NONE); + +void botsheadtodebuggoal(void) +{ + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + + bots[i]->pBot->GoToDebugGoal(v_debuggoal); + } +} + +COMMAND(botsheadtodebuggoal, ARG_NONE); + +#endif // RELEASE_BUILD + +#endif // WP_FLOOD + +// End debug functions +// Waypoint commands end + + +// Bot class begin + +bool CBot::FindWaypoint() +{ + waypoint_s *wp, *wpselect; + int index; + float distance, min_distance[3]; + waypoint_s *min_wp[3]; + + for (index=0; index < 3; index++) + { + min_distance[index] = 9999.0; + min_wp[index] = NULL; + } + + TLinkedList::node_s *pNode = m_pCurrentWaypoint->pNode->ConnectedWPs.GetFirst(); + + while (pNode) + { + if ((pNode->Entry->iFlags & W_FL_INTAG) && + SOLID(S((int)pNode->Entry->v_origin.x, (int)pNode->Entry->v_origin.y))) + { + pNode = pNode->next; + continue; + } + + wp = GetWPFromNode(pNode->Entry); + if (!wp) + { + pNode = pNode->next; + continue; + } + + // if index is not a current or recent previous waypoint... + if ((wp != m_pCurrentWaypoint) && + (wp != m_pPrevWaypoints[0]) && + (wp != m_pPrevWaypoints[1]) && + (wp != m_pPrevWaypoints[2]) && + (wp != m_pPrevWaypoints[3]) && + (wp != m_pPrevWaypoints[4])) + { + // find the distance from the bot to this waypoint + distance = GetDistance(wp->pNode->v_origin); + + if (distance < min_distance[0]) + { + min_distance[2] = min_distance[1]; + min_wp[2] = min_wp[1]; + + min_distance[1] = min_distance[0]; + min_wp[1] = min_wp[0]; + + min_distance[0] = distance; + min_wp[0] = wp; + } + else if (distance < min_distance [1]) + { + min_distance[2] = min_distance[1]; + min_wp[2] = min_wp[1]; + + min_distance[1] = distance; + min_wp[1] = wp; + } + else if (distance < min_distance[2]) + { + min_distance[2] = distance; + min_wp[2] = wp; + } + } + pNode = pNode->next; + } + + wpselect = NULL; + + // about 20% of the time choose a waypoint at random + // (don't do this any more often than every 10 seconds) + + if ((RandomLong(1, 100) <= 20) && (m_iRandomWaypointTime <= lastmillis)) + { + m_iRandomWaypointTime = lastmillis + 10000; + + if (min_wp[2]) + index = RandomLong(0, 2); + else if (min_wp[1]) + index = RandomLong(0, 1); + else if (min_wp[0]) + index = 0; + else + return false; // no waypoints found! + + wpselect = min_wp[index]; + } + else + { + // use the closest waypoint that has been recently used + wpselect = min_wp[0]; + } + + if (wpselect) // was a waypoint found? + { + m_pPrevWaypoints[4] = m_pPrevWaypoints[3]; + m_pPrevWaypoints[3] = m_pPrevWaypoints[2]; + m_pPrevWaypoints[2] = m_pPrevWaypoints[1]; + m_pPrevWaypoints[1] = m_pPrevWaypoints[0]; + m_pPrevWaypoints[0] = m_pCurrentWaypoint; + + SetCurrentWaypoint(wpselect); + return true; + } + + return false; // couldn't find a waypoint +} + +bool CBot::HeadToWaypoint() +{ + if (!m_pCurrentWaypoint) + return false; // Can't head to waypoint + + bool Touching = false; + float WPDist = GetDistance(m_pCurrentWaypoint->pNode->v_origin); + +#ifndef RELEASE_BUILD + if (m_pCurrentGoalWaypoint && vis(m_vGoal, g_vecZero)) + condebug("Warning: m_vGoal unset"); +#endif + + // did the bot run past the waypoint? (prevent the loop-the-loop problem) + if ((m_fPrevWaypointDistance > 1.0) && (WPDist > m_fPrevWaypointDistance) && + (WPDist <= 5.0f)) + Touching = true; + // bot needs to be close for jump and trigger waypoints + else if ((m_pCurrentWaypoint->pNode->iFlags & W_FL_JUMP) || + (m_pCurrentWaypoint->pNode->iFlags & W_FL_TRIGGER)) + Touching = (WPDist <= 1.5f); + else if (m_pCurrentWaypoint->pNode->iFlags & W_FL_TELEPORT) + Touching = (WPDist <= 4.0f); + // are we close enough to a target waypoint... + else if (WPDist <= 3.0f) + { + if (!m_pCurrentGoalWaypoint || (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) || + IsVisible(m_vGoal) || (WPDist <= 1.5f)) + Touching = true; + // If the bot has a goal check if he can see his next wp + if (m_pCurrentGoalWaypoint && (m_pCurrentGoalWaypoint != m_pCurrentWaypoint) && + !m_AStarNodeList.Empty() && (WPDist >= 1.0f) && + !IsVisible(m_AStarNodeList.GetFirst()->Entry->pNode->v_origin)) + Touching = false; + } + + // save current distance as previous + m_fPrevWaypointDistance = WPDist; + + // Reached the waypoint? + if (Touching) + { + // Does this waypoint has a targetyaw? + if (m_pCurrentWaypoint->pNode->sYaw != -1) + { + // UNDONE: Inhuman + m_pMyEnt->yaw = m_pMyEnt->targetyaw = m_pCurrentWaypoint->pNode->sYaw; + } + + // Reached a jump waypoint? + if (m_pCurrentWaypoint->pNode->iFlags & W_FL_JUMP) + m_pMyEnt->jumpnext = true; + + m_fPrevWaypointDistance = 0.0f; + + // Does the bot has a goal? + if (m_pCurrentGoalWaypoint) + { + if (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) + { + if (m_AStarNodeList.Empty()) + { + if (!AStar()) + { + // Bot is calculating a new path, just stand still for now + ResetMoveSpeed(); + m_iWaypointTime += 200; + return true; + } + else + { + if (m_AStarNodeList.Empty()) + { + //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); + return false; // Couldn't get a new wp to go to + } + } + } + + m_pCurrentWaypoint = m_AStarNodeList.Pop(); + + if (!IsVisible(m_pCurrentWaypoint->pNode->v_origin)) + //(!(m_pCurrentWaypoint->iFlags & W_FL_TELEPORT))) + { + //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); + condebug("Next WP not visible"); + return false; + } + + SetCurrentWaypoint(m_pCurrentWaypoint); + } + else + { + // Bot reached the goal waypoint but couldn't reach the goal itself + // (otherwise we wouldn't be in this function) + //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); + return false; + } + } + else + { + short index = 4; + bool status; + + // try to find the next waypoint + while (((status = FindWaypoint()) == false) && (index > 0)) + { + // if waypoint not found, clear oldest previous index and try again + + m_pPrevWaypoints[index] = NULL; + index--; + } + + if (status == false) + { + ResetWaypointVars(); + condebug("Couldn't find new random waypoint"); + return false; + } + } + m_iWaypointHeadPauseTime = lastmillis + 75; + m_iWaypointTime += 75; + } + + // keep turning towards the waypoint... + /*if (m_pCurrentWaypoint->pNode->iFlags & W_FL_FLOOD) + {UNDONE? + vec aim = m_pCurrentWaypoint->pNode->v_origin; + aim.x+=0.5f; + aim.y+=0.5f; + AimToVec(aim); + } + else*/ + AimToVec(m_pCurrentWaypoint->pNode->v_origin); + + if (m_fYawToTurn <= 25.0f) + m_iWaypointHeadLastTurnLessTime = lastmillis; + + // Bot had to turn much for a while? + if ((m_iWaypointHeadLastTurnLessTime > 0) && + (m_iWaypointHeadLastTurnLessTime < (lastmillis - 1000))) + { + m_iWaypointHeadPauseTime = lastmillis + 200; + m_iWaypointTime += 200; + } + + if (m_iWaypointHeadPauseTime >= lastmillis) + { + m_pMyEnt->move = 0; + //conoutf("Pause in HeadToWaypoint()"); + } + else + { + // Check if bot has to jump + vec from = m_pMyEnt->o; + from.z -= (m_pMyEnt->eyeheight - 1.25f); + float flEndDist; + if (!IsVisible(from, FORWARD, 3.0f, false, &flEndDist) && + (GetDistance(from, m_pCurrentWaypoint->pNode->v_origin) > flEndDist)) + { + m_pMyEnt->jumpnext = true; + condebug("Low wall in HeadToWaypoint()"); + } + + // Check if bot has to strafe + if (m_iStrafeTime > lastmillis) + SetMoveDir(m_iMoveDir, true); + else + { + m_iStrafeTime = 0; + m_iMoveDir = DIR_NONE; + + vec forward, up, right; + AnglesToVectors(GetViewAngles(), forward, right, up); + + float flLeftDist = -1.0f, flRightDist = -1.0f; + vec dir = right; + bool bStrafe = false; + int iStrafeDir = 0; + + vmul(dir, m_pMyEnt->radius); + + // Check left + from = m_pMyEnt->o; + vsub(from, dir); + if (IsVisible(from, FORWARD, 3.0f, false, &flLeftDist)) + flLeftDist = -1.0f; + + // Check right + from = m_pMyEnt->o; + vadd(from, dir); + if (IsVisible(from, FORWARD, 3.0f, false, &flRightDist)) + flRightDist = -1.0f; + + if ((flLeftDist != -1.0f) && (flRightDist != -1.0f)) + { + if (flLeftDist < flRightDist) + { + // Strafe right + bStrafe = true; + iStrafeDir = RIGHT; + } + else if (flRightDist < flLeftDist) + { + // Strafe left + bStrafe = true; + iStrafeDir = LEFT; + } + else + { + // Randomly choose a strafe direction + bStrafe = true; + if (RandomLong(0, 1)) + iStrafeDir = LEFT; + else + iStrafeDir = RIGHT; + } + } + else if (flLeftDist != -1.0f) + { + // Strafe right + bStrafe = true; + iStrafeDir = RIGHT; + } + else if (flRightDist != -1.0f) + { + // Strafe left + bStrafe = true; + iStrafeDir = LEFT; + } + + if (bStrafe) + { + SetMoveDir(iStrafeDir, true); + m_iMoveDir = iStrafeDir; + m_iStrafeTime = lastmillis + RandomLong(30, 50); + } + } + } + + return true; +} + +// returns true when done or failure +bool CBot::HeadToGoal() +{ + // Does the bot has a goal(waypoint)? + if (m_pCurrentGoalWaypoint) + { + if (ReachedGoalWP()) + { + return false; + } + else + { + if (CurrentWPIsValid()) + { + if (m_bCalculatingAStarPath) + { + // Bot is calculating a new path, just stand still for now + ResetMoveSpeed(); + m_iWaypointTime += 200; + + // Done calculating the path? + if (AStar()) + { + if (m_AStarNodeList.Empty()) + { + return false; // Couldn't find a path + } + //else + // SetCurrentWaypoint(m_AStarNodeList.Pop()); + } + else + return true; // else just wait a little bit longer + } + //ResetMoveSpeed(); + //return true; + } + else + { + // Current waypoint isn't reachable, search new one + waypoint_s *pWP = NULL; + +#ifdef WP_FLOOD + if (m_pCurrentGoalWaypoint->pNode->iFlags & W_FL_FLOOD) + pWP = GetNearestFloodWP(8.0f); + else +#endif + pWP = GetNearestWaypoint(15.0f); + + if (!pWP || (pWP == m_pCurrentWaypoint)) + return false; + + SetCurrentWaypoint(pWP); + if (AStar()) + { + if (m_AStarNodeList.Empty()) return false; + } + else + { + m_iWaypointTime += 200; + ResetMoveSpeed(); + return true; + } + } + + //debugbeam(m_pMyEnt->o, m_pCurrentWaypoint->pNode->v_origin); + } + } + else + return false; + + return HeadToWaypoint(); +} + +// return true when done calculating +bool CBot::AStar() +{ + if (!m_pCurrentGoalWaypoint || !m_pCurrentWaypoint) + { + if (m_bCalculatingAStarPath) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + } + return true; + } + + // Ideas by PMB : + // * Make locals static to speed up a bit + // * MaxCycles per frame and make it fps dependent + + static int iMaxCycles; + static int iCurrentCycles; + static short newg; + static waypoint_s *n, *n2; + static TLinkedList::node_s *pPath = NULL; + static bool bPathFailed; + + iMaxCycles = BotManager.m_iFrameTime / 10; + if (iMaxCycles < 10) iMaxCycles = 10; + //condebug("MaxCycles: %d", iMaxCycles); + iCurrentCycles = 0; + bPathFailed = false; + + if (!m_bCalculatingAStarPath) + { + if ((BotManager.m_sUsingAStarBotsCount+1) > BotManager.m_sMaxAStarBots) + { + return true; + } + + BotManager.m_sUsingAStarBotsCount++; + + CleanAStarLists(false); + + m_pCurrentWaypoint->g[0] = m_pCurrentWaypoint->g[1] = 0; + m_pCurrentWaypoint->pParent[0] = m_pCurrentWaypoint->pParent[1] = NULL; + m_pCurrentGoalWaypoint->g[0] = m_pCurrentGoalWaypoint->g[1] = 0; + m_pCurrentGoalWaypoint->pParent[0] = m_pCurrentGoalWaypoint->pParent[1] = NULL; + + m_AStarNodeList.DeleteAllNodes(); + + m_AStarOpenList[0].Clear(); + m_AStarOpenList[1].Clear(); + m_AStarClosedList[0].DeleteAllNodes(); + m_AStarClosedList[1].DeleteAllNodes(); + + m_AStarOpenList[0].AddEntry(m_pCurrentWaypoint, + GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin)); + m_AStarOpenList[1].AddEntry(m_pCurrentGoalWaypoint, + GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin)); + + m_pCurrentWaypoint->bIsOpen[0] = m_pCurrentGoalWaypoint->bIsOpen[1] = true; + m_pCurrentWaypoint->bIsOpen[1] = m_pCurrentGoalWaypoint->bIsOpen[0] = false; + m_pCurrentWaypoint->bIsClosed[0] = m_pCurrentGoalWaypoint->bIsClosed[1] = false; + m_pCurrentWaypoint->bIsClosed[1] = m_pCurrentGoalWaypoint->bIsClosed[0] = false; + } + + while(!m_AStarOpenList[0].Empty()) + { + if (iCurrentCycles >= (iMaxCycles/2)) + { + m_bCalculatingAStarPath = true; + break; + } + + n = m_AStarOpenList[0].Pop(); + n->bIsOpen[0] = false; + + if (n->pNode->FailedGoalList.IsInList(m_pCurrentGoalWaypoint->pNode)) + { + bPathFailed = true; + break; // Can't make path to goal + } + + // Done with calculating + if (n == m_pCurrentGoalWaypoint) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + + while(n) + { + m_AStarNodeList.PushNode(n); + n = n->pParent[0]; + } + + CleanAStarLists(false); + + return true; + } + + pPath = n->pNode->ConnectedWPs.GetFirst(); + while(pPath) + { + if ((pPath->Entry->iFlags & W_FL_INTAG) && + SOLID(S((int)pPath->Entry->v_origin.x, (int)pPath->Entry->v_origin.y))) + { + pPath = pPath->next; + continue; + } + + n2 = GetWPFromNode(pPath->Entry); + + if (!n2) + { + pPath = pPath->next; + continue; + } + + if (n2->bIsClosed[1]) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + + while(n2) + { + m_AStarNodeList.AddNode(n2); + n2 = n2->pParent[1]; + } + + while(n) + { + m_AStarNodeList.PushNode(n); + n = n->pParent[0]; + } + + CleanAStarLists(false); + + return true; + } + + newg = n->g[0] + (short)AStarCost(n, n2); + + if ((n2->g[0] <= newg) && (n2->bIsOpen[0] || n2->bIsClosed[0])) + { + pPath = pPath->next; + continue; + } + + n2->pParent[0] = n; + n2->g[0] = newg; + n2->bIsClosed[0] = false; + + if (!n2->bIsOpen[0]) + { + m_AStarOpenList[0].AddEntry(n2, n2->g[1] + GetDistance(n2->pNode->v_origin, + m_pCurrentGoalWaypoint->pNode->v_origin)); + n2->bIsOpen[0] = true; + } + pPath = pPath->next; + } + + m_AStarClosedList[0].PushNode(n); + n->bIsClosed[0] = true; + iCurrentCycles++; + } + + if (!bPathFailed) + { + while(!m_AStarOpenList[1].Empty()) + { + if (iCurrentCycles >= iMaxCycles) + { + m_bCalculatingAStarPath = true; + return false; + } + + n = m_AStarOpenList[1].Pop(); + n->bIsOpen[1] = false; + + if (n->pNode->FailedGoalList.IsInList(m_pCurrentWaypoint->pNode)) + { + bPathFailed = true; + break; // Can't make path to goal + } + + // Done with calculating + if (n == m_pCurrentWaypoint) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + + while(n) + { + m_AStarNodeList.AddNode(n); + n = n->pParent[1]; + } + + CleanAStarLists(false); + + return true; + } + + pPath = n->pNode->ConnectedWPsWithMe.GetFirst(); + while(pPath) + { + if ((pPath->Entry->iFlags & W_FL_INTAG) && + SOLID(S((int)pPath->Entry->v_origin.x, (int)pPath->Entry->v_origin.y))) + { + pPath = pPath->next; + continue; + } + + n2 = GetWPFromNode(pPath->Entry); + + if (!n2) + { + pPath = pPath->next; + continue; + } + + if (n2->bIsClosed[0]) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + + while(n2) + { + m_AStarNodeList.PushNode(n2); + n2 = n2->pParent[0]; + } + + while(n) + { + m_AStarNodeList.AddNode(n); + n = n->pParent[1]; + } + + CleanAStarLists(false); + + return true; + } + + newg = n->g[1] + (short)AStarCost(n, n2); + + if ((n2->g[1] <= newg) && (n2->bIsOpen[1] || n2->bIsClosed[1])) + { + pPath = pPath->next; + continue; + } + + n2->pParent[1] = n; + n2->g[1] = newg; + n2->bIsClosed[1] = false; + + if (!n2->bIsOpen[1]) + { + m_AStarOpenList[1].AddEntry(n2, n2->g[1] + GetDistance(n2->pNode->v_origin, + m_pCurrentWaypoint->pNode->v_origin)); + n2->bIsOpen[1] = true; + } + pPath = pPath->next; + } + + m_AStarClosedList[1].PushNode(n); + n->bIsClosed[1] = true; + iCurrentCycles++; + } + } + + // Failed making path + condebug("Path failed"); + + CleanAStarLists(true); + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + return true; +} + +float CBot::AStarCost(waypoint_s *pWP1, waypoint_s *pWP2) +{ + // UNDONE? + return (GetDistance(pWP1->pNode->v_origin, pWP2->pNode->v_origin) * pWP2->pNode->sCost); +} + +void CBot::CleanAStarLists(bool bPathFailed) +{ + while(m_AStarOpenList[0].Empty() == false) + { + waypoint_s *p = m_AStarOpenList[0].Pop(); + p->bIsOpen[0] = p->bIsOpen[1] = false; + p->bIsClosed[0] = p->bIsClosed[1] = false; + p->pParent[0] = p->pParent[1] = NULL; + p->g[0] = p->g[1] = 0; + } + + while(m_AStarOpenList[1].Empty() == false) + { + waypoint_s *p = m_AStarOpenList[1].Pop(); + p->bIsOpen[0] = p->bIsOpen[1] = false; + p->bIsClosed[0] = p->bIsClosed[1] = false; + p->pParent[0] = p->pParent[1] = NULL; + p->g[0] = p->g[1] = 0; + } + + while(m_AStarClosedList[0].Empty() == false) + { + waypoint_s *p = m_AStarClosedList[0].Pop(); + p->bIsOpen[0] = p->bIsOpen[1] = false; + p->bIsClosed[0] = p->bIsClosed[1] = false; + p->pParent[0] = p->pParent[1] = NULL; + p->g[0] = p->g[1] = 0; + if (bPathFailed) + p->pNode->FailedGoalList.AddNode(m_pCurrentGoalWaypoint->pNode); + } + + while(m_AStarClosedList[1].Empty() == false) + { + waypoint_s *p = m_AStarClosedList[1].Pop(); + p->bIsOpen[0] = p->bIsOpen[1] = false; + p->bIsClosed[0] = p->bIsClosed[1] = false; + p->pParent[0] = p->pParent[1] = NULL; + p->g[0] = p->g[1] = 0; + if (bPathFailed) + p->pNode->FailedGoalList.AddNode(m_pCurrentWaypoint->pNode); + } +} + +void CBot::ResetWaypointVars() +{ + m_iWaypointTime = 0; + m_pCurrentWaypoint = NULL; + m_pCurrentGoalWaypoint = NULL; + m_pPrevWaypoints[0] = NULL; + m_pPrevWaypoints[1] = NULL; + m_pPrevWaypoints[2] = NULL; + m_pPrevWaypoints[3] = NULL; + m_pPrevWaypoints[4] = NULL; + m_fPrevWaypointDistance = 0; + m_iWaypointHeadLastTurnLessTime = 0; + m_iWaypointHeadPauseTime = 0; + if (m_bCalculatingAStarPath) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + } + m_AStarNodeList.DeleteAllNodes(); + CleanAStarLists(false); + m_bGoToDebugGoal = false; +} + +void CBot::SetCurrentWaypoint(node_s *pNode) +{ + waypoint_s *pWP = GetWPFromNode(pNode); +#ifndef RELEASE_BUILD + if (!pWP || !pNode) condebug("NULL WP In SetCurrentWP"); +#endif + + m_pCurrentWaypoint = pWP; + m_iWaypointTime = lastmillis; +} + +void CBot::SetCurrentWaypoint(waypoint_s *pWP) +{ +#ifndef RELEASE_BUILD + if (!pWP) condebug("NULL WP In SetCurrentWP(2)"); +#endif + + m_pCurrentWaypoint = pWP; + m_iWaypointTime = lastmillis; +} + +void CBot::SetCurrentGoalWaypoint(node_s *pNode) +{ + waypoint_s *pWP = GetWPFromNode(pNode); +#ifndef RELEASE_BUILD + if (!pWP || !pNode) condebug("NULL WP In SetCurrentGoalWP"); +#endif + + m_pCurrentGoalWaypoint = pWP; +} + +void CBot::SetCurrentGoalWaypoint(waypoint_s *pWP) +{ +#ifndef RELEASE_BUILD + if (!pWP) condebug("NULL WP In SetCurrentGoalWP(2)"); +#endif + + m_pCurrentGoalWaypoint = pWP; +} + +bool CBot::CurrentWPIsValid() +{ + if (!m_pCurrentWaypoint) + { + //condebug("Invalid WP: Is NULL"); + return false; + } + + // check if the bot has been trying to get to this waypoint for a while... + if ((m_iWaypointTime + 5000) < lastmillis) + { + condebug("Invalid WP: time over"); + return false; + } + +#ifndef RELEASE_BUILD + if (!IsVisible(m_pCurrentWaypoint->pNode->v_origin)) + condebug("Invalid WP: Not visible"); +#endif + + return (IsVisible(m_pCurrentWaypoint->pNode->v_origin)); +} + +bool CBot::ReachedGoalWP() +{ + if ((!m_pCurrentWaypoint) || (!m_pCurrentGoalWaypoint)) + return false; + + if (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) + return false; + + return ((GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin) <= 3.0f) && + (IsVisible(m_pCurrentGoalWaypoint->pNode->v_origin))); +} + +waypoint_s *CBot::GetWPFromNode(node_s *pNode) +{ + if (!pNode) return NULL; + + short x, y; + WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); + + TLinkedList::node_s *p = m_WaypointList[x][y].GetFirst(); + while(p) + { + if (p->Entry->pNode == pNode) + return p->Entry; + + p = p->next; + } + + return NULL; +} + +waypoint_s *CBot::GetNearestWaypoint(vec v_src, float flRange) +{ + TLinkedList::node_s *p; + waypoint_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + WaypointClass.GetNodeIndexes(v_src, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_WaypointList[x][y].GetFirst(); + + while(p) + { + if (p->Entry->pNode->iFlags & W_FL_FLOOD) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_src, p->Entry->pNode->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (::IsVisible(v_src, p->Entry->pNode->v_origin, NULL)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +waypoint_s *CBot::GetNearestTriggerWaypoint(vec v_src, float flRange) +{ + TLinkedList::node_s *p; + waypoint_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + WaypointClass.GetNodeIndexes(v_src, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_WaypointList[x][y].GetFirst(); + + while(p) + { + if (!(p->Entry->pNode->iFlags & W_FL_TRIGGER)) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_src, p->Entry->pNode->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (::IsVisible(v_src, p->Entry->pNode->v_origin, NULL)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +// Makes a waypoint list for this bot based on the list from WaypointClass +void CBot::SyncWaypoints() +{ + short x, y; + TLinkedList::node_s *p; + + // Clean everything first + for (x=0;xpNode = p->Entry; + m_WaypointList[x][y].AddNode(pWP); + +#ifndef RELEASE_BUILD + if (!GetWPFromNode(p->Entry)) condebug("Error adding bot wp!"); +#endif + + p = p->next; + } + } + } +} + +#ifdef WP_FLOOD +waypoint_s *CBot::GetNearestFloodWP(vec v_origin, float flRange) +{ + TLinkedList::node_s *p; + waypoint_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + WaypointClass.GetNodeIndexes(v_origin, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_WaypointList[x][y].GetFirst(); + + while(p) + { + if (!(p->Entry->pNode->iFlags & W_FL_FLOOD)) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_origin, p->Entry->pNode->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (::IsVisible(v_origin, p->Entry->pNode->v_origin, NULL)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +waypoint_s *CBot::GetNearestTriggerFloodWP(vec v_origin, float flRange) +{ + TLinkedList::node_s *p; + waypoint_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + WaypointClass.GetNodeIndexes(v_origin, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_WaypointList[x][y].GetFirst(); + + while(p) + { + if (!(p->Entry->pNode->iFlags & (W_FL_FLOOD | W_FL_TRIGGER))) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_origin, p->Entry->pNode->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (::IsVisible(v_origin, p->Entry->pNode->v_origin, NULL)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +void CBot::GoToDebugGoal(vec o) +{ + ResetWaypointVars(); + + node_s *wp = WaypointClass.GetNearestWaypoint(m_pMyEnt, 20.0f); + node_s *goalwp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + + if (!wp || !goalwp) + { + wp = WaypointClass.GetNearestFloodWP(m_pMyEnt, 8.0f); + goalwp = WaypointClass.GetNearestFloodWP(player1, 5.0f); + } + if (!wp || !goalwp) { condebug("No near WP"); return; } + + SetCurrentWaypoint(wp); + SetCurrentGoalWaypoint(goalwp); + m_vGoal = o; + m_bGoToDebugGoal = true; +} +#endif + +// Bot class end diff --git a/source/src/bot/botmanager.cpp b/source/src/bot/botmanager.cpp index 99889033..86c71a74 100644 --- a/source/src/bot/botmanager.cpp +++ b/source/src/bot/botmanager.cpp @@ -1,1435 +1,1435 @@ -// -// C++ Implementation: bot -// -// Description: Code for botmanager -// -// Main bot file -// -// Author: Rick -// -// -// - -#include "../cube.h" - -#ifndef VANILLA_CUBE // UNDONE -bool dedserv = false; -#define CS_DEDHOST -1 -#endif - -extern ENetHost *clienthost; - -extern void respawnself(); - -CBotManager BotManager; - -// Bot manager class begin - -CBotManager::~CBotManager(void) -{ - EndMap(); - - while(m_StoredBots.Empty() == false) - delete m_StoredBots.Pop(); -} - -void CBotManager::Init() -{ - m_pBotToView = NULL; - - if (!ishost()) - return; - - m_bBotsShoot = true; - m_bIdleBots = false; - m_iFrameTime = 0; - m_iPrevTime = lastmillis; - m_sBotSkill = 1; // Default all bots have the skill 'Good' - - CreateSkillData(); - LoadBotNamesFile(); - LoadBotTeamsFile(); - WaypointClass.Init(); - lsrand(time(NULL)); -} - -void CBotManager::Think() -{ - if (m_bInit) - { - Init(); - m_bInit = false; - } - - if (m_pBotToView) - ViewBot(); - - if (!ishost()) - return; - - AddDebugText("m_sMaxAStarBots: %d", m_sMaxAStarBots); - AddDebugText("m_sCurrentTriggerNr: %d", m_sCurrentTriggerNr); - short x, y; - WaypointClass.GetNodeIndexes(player1->o, &x, &y); - AddDebugText("x: %d y: %d", x, y); - - m_iFrameTime = lastmillis - m_iPrevTime; - if (m_iFrameTime > 250) m_iFrameTime = 250; - m_iPrevTime = lastmillis; - - // Is it time to re-add bots? - if ((m_fReAddBotDelay < lastmillis) && (m_fReAddBotDelay != -1.0f)) - { - while(m_StoredBots.Empty() == false) - { - CStoredBot *pStoredBot = m_StoredBots.Pop(); - pStoredBot->ReAddBot(); - delete pStoredBot; - } - - m_fReAddBotDelay = -1.0f; - } - - // If this is a ded server check if there are any players, if not bots should be idle - if (dedserv) - { - bool botsbeidle = true; - loopv(players) - { - if (players[i] && (players[i]->state != CS_DEDHOST)) { botsbeidle = false; break; } - } - - if (botsbeidle) return; - } - - // Let all bots 'think' - loopv(bots) - { - if (!bots[i]) - continue; - - if (bots[i]->pBot) - bots[i]->pBot->Think(); - else - condebug("Error: pBot == NULL in bot ent\n"); - } -} - -void CBotManager::LoadBotNamesFile() -{ - if (!ishost()) - return; - - // Init bot names array first - for (int i=0;i<100;i++) - strcpy(m_szBotNames[i], "Bot"); - - m_sBotNameCount = 0; - - // Load bot file - char szNameFileName[256]; - MakeBotFileName("bot_names.txt", NULL, NULL, szNameFileName); - FILE *fp = fopen(szNameFileName, "r"); - char szNameBuffer[256]; - int iIndex, iStrIndex; - - if (!fp) - { - conoutf("Warning: Couldn't load bot names file"); - return; - } - - while (fgets(szNameBuffer, 80, fp) != NULL) - { - if (m_sBotNameCount >= 150) - { - conoutf("Warning: Max bot names reached(150), ignoring the rest of the" - "names"); - break; - } - - short length = strlen(szNameBuffer); - - if (szNameBuffer[length-1] == '\n') - { - szNameBuffer[length-1] = 0; // remove '\n' - length--; - } - - iStrIndex = 0; - while (iStrIndex < length) - { - if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') || - (szNameBuffer[iStrIndex] == '"')) - { - for (iIndex=iStrIndex; iIndex < length; iIndex++) - szNameBuffer[iIndex] = szNameBuffer[iIndex+1]; - } - - iStrIndex++; - } - - if (szNameBuffer[0] != 0) - { - if (strlen(szNameBuffer) >= 16) - { conoutf("Warning: bot name \"%s\" has to many characters(16 is max)", - (int)szNameBuffer); - } - strn0cpy(m_szBotNames[m_sBotNameCount], szNameBuffer, 16); - m_sBotNameCount++; - } - } - fclose(fp); -} - -char *CBotManager::GetBotName() -{ - char *szOutput = NULL; - TMultiChoice BotNameChoices; - short ChoiceVal; - - for(int j=0;jstate != CS_DEDHOST) && - !strcasecmp(players[i]->name, m_szBotNames[j])) - ChoiceVal -= 10; - } - - loopv(bots) - { - if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j]))) - ChoiceVal -= 10; - } - - if ((player1->state != CS_DEDHOST) && !strcasecmp(player1->name, m_szBotNames[j])) - ChoiceVal -= 10; - - if (ChoiceVal <= 0) - ChoiceVal = 1; - - BotNameChoices.Insert(m_szBotNames[j], ChoiceVal); - } - - // Couldn't find a selection? - if (!BotNameChoices.GetSelection(szOutput)) - szOutput = "Bot"; - - return szOutput; -} - -void CBotManager::LoadBotTeamsFile() -{ - if (!ishost()) - return; - - // Init bot teams array first - for (int i=0;i<20;i++) - strcpy(m_szBotTeams[i], "b0ts"); - - m_sBotTeamCount = 0; - - // Load bot file - char szNameFileName[256]; - MakeBotFileName("bot_teams.txt", NULL, NULL, szNameFileName); - FILE *fp = fopen(szNameFileName, "r"); - char szNameBuffer[256]; - int iIndex, iStrIndex; - - if (!fp) - { - conoutf("Warning: Couldn't load bot teams file"); - return; - } - - while ((m_sBotTeamCount < 20) && (fgets(szNameBuffer, 80, fp) != NULL)) - { - short length = strlen(szNameBuffer); - - if (szNameBuffer[length-1] == '\n') - { - szNameBuffer[length-1] = 0; // remove '\n' - length--; - } - - iStrIndex = 0; - while (iStrIndex < length) - { - if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') || - (szNameBuffer[iStrIndex] == '"')) - { - for (iIndex=iStrIndex; iIndex < length; iIndex++) - szNameBuffer[iIndex] = szNameBuffer[iIndex+1]; - } - - iStrIndex++; - } - - if (szNameBuffer[0] != 0) - { - strn0cpy(m_szBotTeams[m_sBotTeamCount], szNameBuffer, 5); - m_sBotTeamCount++; - } - } - fclose(fp); -} - -char *CBotManager::GetBotTeam() -{ - char *szOutput = NULL; - TMultiChoice BotTeamChoices; - short ChoiceVal; - - for(int j=0;jname, m_szBotNames[j]))) - ChoiceVal -= 10; - } - - loopv(bots) - { - if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j]))) - ChoiceVal -= 10; - } - - if (!strcasecmp(player1->name, m_szBotNames[j])) - ChoiceVal -= 10; - - if (ChoiceVal <= 0) - ChoiceVmonsterclearal = 1;*/ - - BotTeamChoices.Insert(m_szBotTeams[j], ChoiceVal); - } - - // Couldn't find a selection? - if (!BotTeamChoices.GetSelection(szOutput)) - szOutput = "b0t"; - - return szOutput; -} - -void CBotManager::RenderBots() -{ - static bool drawblue; - - loopv(bots) - { - if (bots[i] && (bots[i] != m_pBotToView)) - { - drawblue = (m_sp || isteam(player1->team, bots[i]->team)); - renderclient(bots[i], drawblue, "playermodels/counterterrorist", false, 1.0f); - } - } -} - -void CBotManager::EndMap() -{ - // Remove all bots - loopv(bots) - { - if (!bots[i]) - continue; - - if (ishost()) // Store bots so they can be re-added after map change - { - if (bots[i]->pBot && bots[i]->name[0] && bots[i]->team[0]) - { - CStoredBot *pStoredBot = new CStoredBot(bots[i]->name, bots[i]->team, - bots[i]->pBot->m_sSkillNr); - m_StoredBots.AddNode(pStoredBot); - } - delete bots[i]->pBot; - } - - bots[i]->pBot = NULL; - zapdynent(bots[i]); - } - bots.setsize(0); - condebug("Cleared all bots"); - m_fReAddBotDelay = lastmillis + 7500; - //if(ishost()) WaypointClass.SaveWPExpFile(); //UNDONE -} - -void CBotManager::BeginMap(char *szMapName) -{ - EndMap(); // End previous map - - if (!ishost()) return; - - WaypointClass.Init(); - WaypointClass.SetMapName(szMapName); - if (!WaypointClass.LoadWaypoints()) - WaypointClass.StartFlood(); - //WaypointClass.LoadWPExpFile(); // UNDONE - - CalculateMaxAStarCount(); - m_sUsingAStarBotsCount = 0; - PickNextTrigger(); -} - -int CBotManager::GetBotIndex(dynent *m) -{ - loopv(bots) - { - if (!bots[i]) - continue; - - if (bots[i] == m) - return i; - } - - return -1; -} - -void CBotManager::LetBotsUpdateStats() -{ - loopv(bots) if (bots[i] && bots[i]->pBot) bots[i]->pBot->m_bSendC2SInit = false; -} - -void CBotManager::LetBotsHear(int n, vec *loc) -{ - if (bots.length() == 0) return; - - if (!loc) return; - - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot || (bots[i]->state == CS_DEAD)) continue; - bots[i]->pBot->HearSound(n, loc); - } -} - -// Notify all bots of a new waypoint -void CBotManager::AddWaypoint(node_s *pNode) -{ - if (bots.length()) - { - short x, y; - waypoint_s *pWP; - - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - - pWP = new waypoint_s; - pWP->pNode = pNode; - WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); - bots[i]->pBot->m_WaypointList[x][y].AddNode(pWP); - -#ifndef RELEASE_BUILD - if (!bots[i]->pBot->GetWPFromNode(pNode)) condebug("Error adding bot wp!"); -#endif - } - } - - CalculateMaxAStarCount(); -} - -// Notify all bots of a deleted waypoint -void CBotManager::DelWaypoint(node_s *pNode) -{ - if (bots.length()) - { - short x, y; - TLinkedList::node_s *p; - - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - - WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); - p = bots[i]->pBot->m_WaypointList[x][y].GetFirst(); - - while(p) - { - if (p->Entry->pNode == pNode) - { - delete p->Entry; - bots[i]->pBot->m_WaypointList[x][y].DeleteNode(p); - break; - } - p = p->next; - } - } - } - - CalculateMaxAStarCount(); -} - -void CBotManager::MakeBotFileName(const char *szFileName, const char *szDir1, const char *szDir2, char *szOutput) -{ - char *DirSeperator; - -#ifndef __linux__ - DirSeperator = "\\"; - strcpy(szOutput, "bot\\"); -#else - DirSeperator = "/"; - strcpy(szOutput, "bot/"); -#endif - - if (szDir1) - { - strcat(szOutput, szDir1); - strcat(szOutput, DirSeperator); - } - - if (szDir2) - { - strcat(szOutput, szDir2); - strcat(szOutput, DirSeperator); - } - - strcat(szOutput, szFileName); -} - -void CBotManager::CreateSkillData() -{ - // First give the bot skill structure some default data - InitSkillData(); - - // Now see if we can load the skill.cfg file - char SkillFileName[256] = ""; - FILE *pSkillFile = NULL; - int SkillNr = -1; - float value = 0; - - MakeBotFileName("bot_skill.cfg", NULL, NULL, SkillFileName); - - pSkillFile = fopen(SkillFileName, "r"); - - conoutf("Reading bot_skill.cfg file... "); - - if (pSkillFile == NULL) // file doesn't exist - { - conoutf("skill file not found, default settings will be used\n"); - return; - } - - int ch; - char cmd_line[256]; - int cmd_index; - char *cmd, *arg1; - - while (pSkillFile) - { - cmd_index = 0; - cmd_line[cmd_index] = 0; - - ch = fgetc(pSkillFile); - - // skip any leading blanks - while (ch == ' ') - ch = fgetc(pSkillFile); - - while ((ch != EOF) && (ch != '\r') && (ch != '\n')) - { - if (ch == '\t') // convert tabs to spaces - ch = ' '; - - cmd_line[cmd_index] = ch; - - ch = fgetc(pSkillFile); - - // skip multiple spaces in input file - while ((cmd_line[cmd_index] == ' ') && (ch == ' ')) - ch = fgetc(pSkillFile); - - cmd_index++; - } - - if (ch == '\r') // is it a carriage return? - { - ch = fgetc(pSkillFile); // skip the linefeed - } - - // if reached end of file, then close it - if (ch == EOF) - { - fclose(pSkillFile); - pSkillFile = NULL; - } - - cmd_line[cmd_index] = 0; // terminate the command line - - cmd_index = 0; - cmd = cmd_line; - arg1 = NULL; - - // skip to blank or end of string... - while ((cmd_line[cmd_index] != ' ') && (cmd_line[cmd_index] != 0)) - cmd_index++; - - if (cmd_line[cmd_index] == ' ') - { - cmd_line[cmd_index++] = 0; - arg1 = &cmd_line[cmd_index]; - } - - if ((cmd_line[0] == '#') || (cmd_line[0] == 0)) - continue; // skip if comment or blank line - - - if (strcasecmp(cmd, "[SKILL1]") == 0) - SkillNr = 0; - else if (strcasecmp(cmd, "[SKILL2]") == 0) - SkillNr = 1; - else if (strcasecmp(cmd, "[SKILL3]") == 0) - SkillNr = 2; - else if (strcasecmp(cmd, "[SKILL4]") == 0) - SkillNr = 3; - else if (strcasecmp(cmd, "[SKILL5]") == 0) - SkillNr = 4; - - if ((arg1 == NULL) || (*arg1 == 0)) - continue; - - if (SkillNr == -1) // Not in a skill block yet? - continue; - - value = atof(arg1); - - if (strcasecmp(cmd, "min_x_aim_speed") == 0) - { - m_BotSkills[SkillNr].flMinAimXSpeed = value; - } - else if (strcasecmp(cmd, "max_x_aim_speed") == 0) - { - m_BotSkills[SkillNr].flMaxAimXSpeed = value; - } - else if (strcasecmp(cmd, "min_y_aim_speed") == 0) - { - m_BotSkills[SkillNr].flMinAimYSpeed = value; - } - else if (strcasecmp(cmd, "max_y_aim_speed") == 0) - { - m_BotSkills[SkillNr].flMaxAimYSpeed = value; - } - else if (strcasecmp(cmd, "min_x_aim_offset") == 0) - { - m_BotSkills[SkillNr].flMinAimXOffset = value; - } - else if (strcasecmp(cmd, "max_x_aim_offset") == 0) - { - m_BotSkills[SkillNr].flMaxAimXOffset = value; - } - else if (strcasecmp(cmd, "min_y_aim_offset") == 0) - { - m_BotSkills[SkillNr].flMinAimYOffset = value; - } - else if (strcasecmp(cmd, "max_y_aim_offset") == 0) - { - m_BotSkills[SkillNr].flMaxAimYOffset = value; - } - else if (strcasecmp(cmd, "min_attack_delay") == 0) - { - m_BotSkills[SkillNr].flMinAttackDelay = value; - } - else if (strcasecmp(cmd, "max_attack_delay") == 0) - { - m_BotSkills[SkillNr].flMaxAttackDelay = value; - } - else if (strcasecmp(cmd, "min_enemy_search_delay") == 0) - { - m_BotSkills[SkillNr].flMinEnemySearchDelay = value; - } - else if (strcasecmp(cmd, "max_enemy_search_delay") == 0) - { - m_BotSkills[SkillNr].flMaxEnemySearchDelay = value; - } - else if (strcasecmp(cmd, "shoot_at_feet_percent") == 0) - { - if (value < 0) value = 0; - else if (value > 100) value = 100; - m_BotSkills[SkillNr].sShootAtFeetWithRLPercent = (short)value; - } - else if (strcasecmp(cmd, "can_predict_position") == 0) - { - m_BotSkills[SkillNr].bCanPredict = value; - } - else if (strcasecmp(cmd, "max_hear_volume") == 0) - { - if (value < 0) value = 0; - else if (value > 255) value = 100; - m_BotSkills[SkillNr].iMaxHearVolume = (int)value; - } - else if (strcasecmp(cmd, "can_circle_strafe") == 0) - { - m_BotSkills[SkillNr].bCircleStrafe = value; - } - else if (strcasecmp(cmd, "can_search_items_in_combat") == 0) - { - m_BotSkills[SkillNr].bCanSearchItemsInCombat = value; - } - } - - conoutf("done\n"); -} - -void CBotManager::InitSkillData() -{ - // Best skill - m_BotSkills[0].flMinReactionDelay = 0.015f; - m_BotSkills[0].flMaxReactionDelay = 0.035f; - m_BotSkills[0].flMinAimXOffset = 15.0f; - m_BotSkills[0].flMaxAimXOffset = 20.0f; - m_BotSkills[0].flMinAimYOffset = 10.0f; - m_BotSkills[0].flMaxAimYOffset = 15.0f; - m_BotSkills[0].flMinAimXSpeed = 330.0f; - m_BotSkills[0].flMaxAimXSpeed = 355.0f; - m_BotSkills[0].flMinAimYSpeed = 400.0f; - m_BotSkills[0].flMaxAimYSpeed = 450.0f; - m_BotSkills[0].flMinAttackDelay = 0.1f; - m_BotSkills[0].flMaxAttackDelay = 0.4f; - m_BotSkills[0].flMinEnemySearchDelay = 0.09f; - m_BotSkills[0].flMaxEnemySearchDelay = 0.12f; - m_BotSkills[0].sShootAtFeetWithRLPercent = 85; - m_BotSkills[0].bCanPredict = true; - m_BotSkills[0].iMaxHearVolume = 75; - m_BotSkills[0].bCircleStrafe = true; - m_BotSkills[0].bCanSearchItemsInCombat = true; - - // Good skill - m_BotSkills[1].flMinReactionDelay = 0.035f; - m_BotSkills[1].flMaxReactionDelay = 0.045f; - m_BotSkills[1].flMinAimXOffset = 20.0f; - m_BotSkills[1].flMaxAimXOffset = 25.0f; - m_BotSkills[1].flMinAimYOffset = 15.0f; - m_BotSkills[1].flMaxAimYOffset = 20.0f; - m_BotSkills[1].flMinAimXSpeed = 250.0f; - m_BotSkills[1].flMaxAimXSpeed = 265.0f; - m_BotSkills[1].flMinAimYSpeed = 260.0f; - m_BotSkills[1].flMaxAimYSpeed = 285.0f; - m_BotSkills[1].flMinAttackDelay = 0.3f; - m_BotSkills[1].flMaxAttackDelay = 0.6f; - m_BotSkills[1].flMinEnemySearchDelay = 0.12f; - m_BotSkills[1].flMaxEnemySearchDelay = 0.17f; - m_BotSkills[1].sShootAtFeetWithRLPercent = 60; - m_BotSkills[1].bCanPredict = true; - m_BotSkills[1].iMaxHearVolume = 60; - m_BotSkills[1].bCircleStrafe = true; - m_BotSkills[1].bCanSearchItemsInCombat = true; - - // Medium skill - m_BotSkills[2].flMinReactionDelay = 0.075f; - m_BotSkills[2].flMaxReactionDelay = 0.010f; - m_BotSkills[2].flMinAimXOffset = 25.0f; - m_BotSkills[2].flMaxAimXOffset = 30.0f; - m_BotSkills[2].flMinAimYOffset = 20.0f; - m_BotSkills[2].flMaxAimYOffset = 25.0f; - m_BotSkills[2].flMinAimXSpeed = 190.0f; - m_BotSkills[2].flMaxAimXSpeed = 125.0f; - m_BotSkills[2].flMinAimYSpeed = 210.0f; - m_BotSkills[2].flMaxAimYSpeed = 240.0f; - m_BotSkills[2].flMinAttackDelay = 0.75f; - m_BotSkills[2].flMaxAttackDelay = 1.0f; - m_BotSkills[2].flMinEnemySearchDelay = 0.18f; - m_BotSkills[2].flMaxEnemySearchDelay = 0.22f; - m_BotSkills[2].sShootAtFeetWithRLPercent = 25; - m_BotSkills[2].bCanPredict = false; - m_BotSkills[2].iMaxHearVolume = 45; - m_BotSkills[2].bCircleStrafe = true; - m_BotSkills[2].bCanSearchItemsInCombat = false; - - // Worse skill - m_BotSkills[3].flMinReactionDelay = 0.15f; - m_BotSkills[3].flMaxReactionDelay = 0.20f; - m_BotSkills[3].flMinAimXOffset = 30.0f; - m_BotSkills[3].flMaxAimXOffset = 35.0f; - m_BotSkills[3].flMinAimYOffset = 25.0f; - m_BotSkills[3].flMaxAimYOffset = 30.0f; - m_BotSkills[3].flMinAimXSpeed = 155.0f; - m_BotSkills[3].flMaxAimXSpeed = 170.0f; - m_BotSkills[3].flMinAimYSpeed = 160.0f; - m_BotSkills[3].flMaxAimYSpeed = 210.0f; - m_BotSkills[3].flMinAttackDelay = 1.2f; - m_BotSkills[3].flMaxAttackDelay = 1.6f; - m_BotSkills[3].flMinEnemySearchDelay = 0.25f; - m_BotSkills[3].flMaxEnemySearchDelay = 0.30f; - m_BotSkills[3].sShootAtFeetWithRLPercent = 10; - m_BotSkills[3].bCanPredict = false; - m_BotSkills[3].iMaxHearVolume = 30; - m_BotSkills[3].bCircleStrafe = false; - m_BotSkills[3].bCanSearchItemsInCombat = false; - - // Bad skill - m_BotSkills[4].flMinReactionDelay = 0.30f; - m_BotSkills[4].flMaxReactionDelay = 0.50f; - m_BotSkills[4].flMinAimXOffset = 35.0f; - m_BotSkills[4].flMaxAimXOffset = 40.0f; - m_BotSkills[4].flMinAimYOffset = 30.0f; - m_BotSkills[4].flMaxAimYOffset = 35.0f; - m_BotSkills[4].flMinAimXSpeed = 45.0f; - m_BotSkills[4].flMaxAimXSpeed = 60.0f; - m_BotSkills[4].flMinAimYSpeed = 125.0f; - m_BotSkills[4].flMaxAimYSpeed = 180.0f; - m_BotSkills[4].flMinAttackDelay = 1.5f; - m_BotSkills[4].flMaxAttackDelay = 2.0f; - m_BotSkills[4].flMinEnemySearchDelay = 0.30f; - m_BotSkills[4].flMaxEnemySearchDelay = 0.36f; - m_BotSkills[4].sShootAtFeetWithRLPercent = 0; - m_BotSkills[4].bCanPredict = false; - m_BotSkills[4].iMaxHearVolume = 15; - m_BotSkills[4].bCircleStrafe = false; - m_BotSkills[4].bCanSearchItemsInCombat = false; -} - -void CBotManager::ChangeBotSkill(short Skill, dynent *bot) -{ - static char *SkillNames[5] = { "best", "good", "medium", "worse", "bad" }; - - if (bot && bot->pBot) - { - // Only change skill of a single bot - bot->pBot->m_pBotSkill = &m_BotSkills[Skill]; - bot->pBot->m_sSkillNr = Skill; - conoutf("Skill of %s is now %s", (int)bot->name, (int)SkillNames[Skill]); - return; - } - - // Change skill of all bots - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - - bots[i]->pBot->m_pBotSkill = &m_BotSkills[Skill]; - bots[i]->pBot->m_sSkillNr = Skill; - } - - // Change default bot skill - m_sBotSkill = Skill; - - conoutf("Skill of all bots is now %s", (int)SkillNames[Skill]); -} - -void CBotManager::ViewBot() -{ - // Check if this bot is still in game - bool bFound = false; - loopv(bots) - { - if (bots[i] == m_pBotToView) - { - bFound = true; - break; - } - } - - if (!bFound) - { - DisableBotView(); - return; - } - - player1->state = CS_DEAD; // Fake dead - - player1->o = m_pBotToView->o; - player1->o.z += 1.0f; - player1->yaw = m_pBotToView->yaw; - player1->pitch = m_pBotToView->pitch; - player1->roll = m_pBotToView->roll; - player1->radius = 0; // Don't collide -} - -void CBotManager::DisableBotView() -{ - m_pBotToView = NULL; - respawnself(); - player1->radius = 1.1f; -} - -void CBotManager::CalculateMaxAStarCount() -{ - if (WaypointClass.m_iWaypointCount > 0) // Are there any waypoints? - { - m_sMaxAStarBots = 8 - short(ceil((float)WaypointClass.m_iWaypointCount / - 1000.0f)); - if (m_sMaxAStarBots < 1) - m_sMaxAStarBots = 1; - } - else - m_sMaxAStarBots = 1; -} - -void CBotManager::PickNextTrigger() -{ - if (!ishost()) return; - - short lowest = -1; - bool found0 = false; // True if found a trigger with nr 0 - - loopv(ents) - { - entity &e = ents[i]; - -#if defined AC_BUBE - if ((e.type != TRIGGER) || !e.spawned) - continue; -#elif defined VANILLA_CUBE - if ((e.type != CARROT) || !e.spawned) - continue; -#endif - if (OUTBORD(e.x, e.y)) continue; - - vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - - node_s *pWptNearEnt = NULL; - - pWptNearEnt = WaypointClass.GetNearestTriggerWaypoint(o, 2.0f); - - if (pWptNearEnt) - { - if ((pWptNearEnt->sTriggerNr > 0) && - ((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1))) - lowest = pWptNearEnt->sTriggerNr; - if (pWptNearEnt->sTriggerNr == 0) found0 = true; - } - -#ifdef WP_FLOOD - pWptNearEnt = WaypointClass.GetNearestTriggerFloodWP(o, 2.0f); - - if (pWptNearEnt) - { - if ((pWptNearEnt->sTriggerNr > 0) && - ((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1))) - lowest = pWptNearEnt->sTriggerNr; - if (pWptNearEnt->sTriggerNr == 0) found0 = true; - } - -#endif - } - - if ((lowest == -1) && found0) lowest = 0; - - if (lowest != -1) - m_sCurrentTriggerNr = lowest; -} - -dynent *CBotManager::CreateBot(const char *team, const char *skill, const char *name) -{ - dynent *m = newdynent(); - if (!m) return NULL; - - // Create new bot class, dependand on the current mod -#if defined VANILLA_CUBE - m->pBot = new CCubeBot; -#elif defined AC_CUBE - m->pBot = new CACBot; -#else - #error "Unsupported mod!" -#endif - m->bIsBot = true; - m->pBot->m_pMyEnt = m; - m->pBot->m_iLastBotUpdate = 0; - m->pBot->m_bSendC2SInit = false; - - if (name && *name) - strn0cpy(m->name, name, 16); - else - strn0cpy(m->name, BotManager.GetBotName(), 16); - - if (team && *team && strcmp(team, "random")) - strn0cpy(m->team, team, 5); - else - strn0cpy(m->team, BotManager.GetBotTeam(), 5); - - if (skill && *skill && strcmp(skill, "random")) - { - if (!strcasecmp(skill, "best")) - m->pBot->m_sSkillNr = 0; - else if (!strcasecmp(skill, "good")) - m->pBot->m_sSkillNr = 1; - else if (!strcasecmp(skill, "medium")) - m->pBot->m_sSkillNr = 2; - else if (!strcasecmp(skill, "worse")) - m->pBot->m_sSkillNr = 3; - else if (!strcasecmp(skill, "bad")) - m->pBot->m_sSkillNr = 4; - else - { - conoutf("Wrong skill specified. Should be best, good, medium, " - "worse or bad"); - conoutf("Using default skill instead..."); - m->pBot->m_sSkillNr = BotManager.m_sBotSkill; - } - } - else // No skill specified, use default - m->pBot->m_sSkillNr = BotManager.m_sBotSkill; - - m->pBot->m_pBotSkill = &BotManager.m_BotSkills[m->pBot->m_sSkillNr]; - - // Sync waypoints - m->pBot->SyncWaypoints(); - - m->pBot->Spawn(); - - bots.add(m); - - return m; -} - -// Bot manager class end - -void addbot(char *arg1, char *arg2, char *arg3) -{ - if (ishost()) - { - conoutf("Creating bot...\n"); - dynent *b = BotManager.CreateBot(arg1, arg2, arg3); - if (b) - conoutf("connected: %s", (int)b->name); - else - { - conoutf("Error: Couldn't create bot!"); - return; - } - } - else if (clienthost) - { - char team[32], name[32]; - int skill = -1; - - team[0] = name[0] = 0; - - if (arg1 && arg1[0]) strcpy(team, arg1); - if (arg3 && arg3[0]) strcpy(name, arg3); - if (arg2 && arg2[0]) - { - if (!strcasecmp(arg2, "best")) skill = 0; - else if (!strcasecmp(arg2, "good")) skill = 1; - else if (!strcasecmp(arg2, "medium")) skill = 2; - else if (!strcasecmp(arg2, "worse")) skill = 3; - else if (!strcasecmp(arg2, "bad")) skill = 4; - } - - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_BOTCOMMAND); - putint(p, int(COMMAND_ADDBOT)); // Bot command type - putint(p, 1); // Bot count - putint(p, skill); // Bot skill - sendstring(team, p); // Bot team - sendstring(name, p); // Bot name - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - } -} - -COMMAND(addbot, ARG_3STR); - -void addnbot(char *arg1, char *arg2, char *arg3) -{ - if (!arg1 || !arg1[0]) return; - - int i = atoi(arg1); - - if (ishost()) - { - while(i > 0) - { - addbot(arg2, arg3, NULL); - i--; - } - } - else if (clienthost) - { - char team[32], name[32]; - int skill = -1; - - team[0] = name[0] = 0; - - if (arg2 && arg2[0]) strcpy(team, arg2); -// if (arg4 && arg4[0]) strcpy(name, arg4); - if (arg3 && arg3[0]) - { - if (!strcasecmp(arg2, "best")) skill = 0; - else if (!strcasecmp(arg2, "good")) skill = 1; - else if (!strcasecmp(arg2, "medium")) skill = 2; - else if (!strcasecmp(arg2, "worse")) skill = 3; - else if (!strcasecmp(arg2, "bad")) skill = 4; - } - - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_BOTCOMMAND); - putint(p, int(COMMAND_ADDBOT)); // Bot command type - putint(p, i); // Bot count - putint(p, skill); // Bot skill - sendstring(team, p); // Bot team - sendstring(name, p); // Bot name - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - } -} - -COMMAND(addnbot, ARG_3STR); - -void botsshoot(int Shoot) -{ - if (!ishost()) return; - if (Shoot) - { - BotManager.SetBotsShoot(true); - conoutf("Bots will shoot"); - } - else - { - BotManager.SetBotsShoot(false); - conoutf("Bots won't shoot"); - } -} - -COMMAND(botsshoot, ARG_1INT); - -void idlebots(int Idle) -{ - if (!ishost()) return; - if (Idle) - { - BotManager.SetIdleBots(true); - conoutf("Bots are idle"); - } - else - { - BotManager.SetIdleBots(false); - conoutf("Bots aren't idle"); - } -} - -COMMAND(idlebots, ARG_1INT); - -void drawbeamtobots() -{ - loopv(bots) - { - if (bots[i]) - particle_trail(1, 500, player1->o, bots[i]->o); - } -} - -COMMAND(drawbeamtobots, ARG_NONE); - -void kickbot(const char *szName) -{ - if (!szName || !(*szName)) - return; - - int iBotInd = -1; - loopv(bots) - { - if (!bots[i]) continue; - - if (!strcmp(bots[i]->name, szName)) - { - iBotInd = i; - break; - } - } - - if (iBotInd != -1) - { - if (ishost()) - { - addmsg(1, 2, SV_BOTDIS, iBotInd); - - conoutf("bot %s disconnected", (int)(bots[iBotInd]->name[0] ? bots[iBotInd]->name : - "[incompatible client]")); - delete bots[iBotInd]->pBot; - zapdynent(bots[iBotInd]); - bots.remove(iBotInd); - } - else if (clienthost) // Ask server to destroy bot(with voting) - { - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_BOTCOMMAND); - putint(p, int(COMMAND_KICKBOT)); // Bot command type - putint(p, 1); // 0==kick all bots, 1==kick one specific bot - char tmp[20]; - strcpy(tmp, szName); - sendstring(tmp, p); // Bot name - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - } - } -} - -COMMAND(kickbot, ARG_1STR); - -void kickallbots(void) -{ - loopv(bots) - { - if (bots[i]) - { - if (ishost()) - { - addmsg(1, 2, SV_BOTDIS, i); - - conoutf("bot %s disconnected",(int)(bots[i]->name[0] ? - bots[i]->name : "[incompatible client]")); - delete bots[i]->pBot; - zapdynent(bots[i]); - } - else if (clienthost) // Ask the server to destroy all bots - { - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, - ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_BOTCOMMAND); - putint(p, int(COMMAND_KICKBOT)); // Bot command type - putint(p, 0); // 0==kick all bots, 1==kick one specific bot - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - } - } - } - - bots.setsize(0); -} - -COMMAND(kickallbots, ARG_NONE); - -void togglegrap() -{ - if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GrabMode(0)) - SDL_WM_GrabInput(SDL_GRAB_ON); - else - SDL_WM_GrabInput(SDL_GrabMode(0)); -} - -COMMAND(togglegrap, ARG_NONE); - -void togglebotview(char *bot) -{ - if (BotManager.m_pBotToView) - BotManager.DisableBotView(); - else if (bot && *bot) - { - loopv(bots) - { - if (!bots[i]) continue; - - if (!strcmp(bots[i]->name, bot)) - { - BotManager.EnableBotView(bots[i]); - break; - } - } - } -} - -COMMAND(togglebotview, ARG_1STR); - -void botskill(char *bot, char *skill) -{ - if (!skill || !(*skill)) - return; - - short SkillNr; - - if (!strcasecmp(skill, "best")) - SkillNr = 0; - else if (!strcasecmp(skill, "good")) - SkillNr = 1; - else if (!strcasecmp(skill, "medium")) - SkillNr = 2; - else if (!strcasecmp(skill, "worse")) - SkillNr = 3; - else if (!strcasecmp(skill, "bad")) - SkillNr = 4; - else - { - conoutf("Wrong skill specified. Should be best, good, medium, worse or bad"); - return; - } - - if (ishost()) - { - if (bot) - { - loopv(bots) - { - if (bots[i] && !strcmp(bots[i]->name, bot)) - { - BotManager.ChangeBotSkill(SkillNr, bots[i]); - break; - } - } - } - else - BotManager.ChangeBotSkill(SkillNr); - } - else if (clienthost) // ask server to change botskill - { - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_BOTCOMMAND); - putint(p, int(COMMAND_BOTSKILL)); // Bot command type - putint(p, SkillNr); - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - } -} - -COMMAND(botskill, ARG_2STR); - -void botskillall(char *skill) -{ - botskill(NULL, skill); -} - -COMMAND(botskillall, ARG_1STR); - -#ifndef RELEASE_BUILD - -#ifdef VANILLA_CUBE -void drawbeamtocarrots() -{ - loopv(ents) - { - entity &e = ents[i]; - vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - if ((e.type != CARROT) || !e.spawned) continue; - particle_trail(1, 500, player1->o, o); - } -} - -COMMAND(drawbeamtocarrots, ARG_NONE); - -void drawbeamtoteleporters() -{ - loopv(ents) - { - entity &e = ents[i]; - vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - if (e.type != TELEPORT) continue; - particle_trail(1, 500, player1->o, o); - } -} - -COMMAND(drawbeamtoteleporters, ARG_NONE); -#endif - -void telebot(void) -{ - vec dest = player1->o, forward, right, up; - vec angles = { player1->pitch, player1->yaw, player1->roll }; - traceresult_s tr; - - AnglesToVectors(angles, forward, right, up); - vmul(forward, 4.0f); - vadd(dest, forward); - - TraceLine(player1->o, dest, player1, true, &tr); - - if (!tr.collided) - { - // Get the first bot - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - bots[i]->o = tr.end; - break; - } - } -} - -COMMAND(telebot, ARG_NONE); - -void testvisible(int iDir) -{ - vec angles, end, forward, right, up; - traceresult_s tr; - int Dir; - - switch(iDir) - { - case 0: default: Dir = FORWARD; break; - case 1: Dir = BACKWARD; break; - case 2: Dir = LEFT; break; - case 3: Dir = RIGHT; break; - case 4: Dir = UP; break; - case 5: Dir = DOWN; break; - } - - vec from = player1->o; - from.z -= (player1->eyeheight - 1.25f); - end = from; - makevec(&angles, player1->pitch, player1->yaw, player1->roll); - angles.x=0; - - if (Dir & UP) - angles.x = WrapXAngle(angles.x + 45.0f); - else if (Dir & DOWN) - angles.x = WrapXAngle(angles.x - 45.0f); - - if ((Dir & FORWARD) || (Dir & BACKWARD)) - { - if (Dir & BACKWARD) - angles.y = WrapYZAngle(angles.y + 180.0f); - - if (Dir & LEFT) - { - if (Dir & FORWARD) - angles.y = WrapYZAngle(angles.y - 45.0f); - else - angles.y = WrapYZAngle(angles.y + 45.0f); - } - else if (Dir & RIGHT) - { - if (Dir & FORWARD) - angles.y = WrapYZAngle(angles.y + 45.0f); - else - angles.y = WrapYZAngle(angles.y - 45.0f); - } - } - else if (Dir & LEFT) - angles.y = WrapYZAngle(angles.y - 90.0f); - else if (Dir & RIGHT) - angles.y = WrapYZAngle(angles.y + 90.0f); - else if (Dir & UP) - angles.x = WrapXAngle(angles.x + 90.0f); - else if (Dir & DOWN) - angles.x = WrapXAngle(angles.x - 90.0f); - - AnglesToVectors(angles, forward, right, up); - - vmul(forward, 20.0f); - vadd(end, forward); - - TraceLine(from, end, player1, false, &tr); - - //debugbeam(from, tr.end); - char sz[250]; - sprintf(sz, "dist: %f; hit: %d", GetDistance(from, tr.end), tr.collided); - condebug(sz); -} - -COMMAND(testvisible, ARG_1INT); - -void mapsize(void) -{ - conoutf("ssize: %d", ssize); -} - -COMMAND(mapsize, ARG_NONE); - -#endif +// +// C++ Implementation: bot +// +// Description: Code for botmanager +// +// Main bot file +// +// Author: Rick +// +// +// + +#include "../cube.h" + +#ifndef VANILLA_CUBE // UNDONE +bool dedserv = false; +#define CS_DEDHOST -1 +#endif + +extern ENetHost *clienthost; + +extern void respawnself(); + +CBotManager BotManager; + +// Bot manager class begin + +CBotManager::~CBotManager(void) +{ + EndMap(); + + while(m_StoredBots.Empty() == false) + delete m_StoredBots.Pop(); +} + +void CBotManager::Init() +{ + m_pBotToView = NULL; + + if (!ishost()) + return; + + m_bBotsShoot = true; + m_bIdleBots = false; + m_iFrameTime = 0; + m_iPrevTime = lastmillis; + m_sBotSkill = 1; // Default all bots have the skill 'Good' + + CreateSkillData(); + LoadBotNamesFile(); + LoadBotTeamsFile(); + WaypointClass.Init(); + lsrand(time(NULL)); +} + +void CBotManager::Think() +{ + if (m_bInit) + { + Init(); + m_bInit = false; + } + + if (m_pBotToView) + ViewBot(); + + if (!ishost()) + return; + + AddDebugText("m_sMaxAStarBots: %d", m_sMaxAStarBots); + AddDebugText("m_sCurrentTriggerNr: %d", m_sCurrentTriggerNr); + short x, y; + WaypointClass.GetNodeIndexes(player1->o, &x, &y); + AddDebugText("x: %d y: %d", x, y); + + m_iFrameTime = lastmillis - m_iPrevTime; + if (m_iFrameTime > 250) m_iFrameTime = 250; + m_iPrevTime = lastmillis; + + // Is it time to re-add bots? + if ((m_fReAddBotDelay < lastmillis) && (m_fReAddBotDelay != -1.0f)) + { + while(m_StoredBots.Empty() == false) + { + CStoredBot *pStoredBot = m_StoredBots.Pop(); + pStoredBot->ReAddBot(); + delete pStoredBot; + } + + m_fReAddBotDelay = -1.0f; + } + + // If this is a ded server check if there are any players, if not bots should be idle + if (dedserv) + { + bool botsbeidle = true; + loopv(players) + { + if (players[i] && (players[i]->state != CS_DEDHOST)) { botsbeidle = false; break; } + } + + if (botsbeidle) return; + } + + // Let all bots 'think' + loopv(bots) + { + if (!bots[i]) + continue; + + if (bots[i]->pBot) + bots[i]->pBot->Think(); + else + condebug("Error: pBot == NULL in bot ent\n"); + } +} + +void CBotManager::LoadBotNamesFile() +{ + if (!ishost()) + return; + + // Init bot names array first + for (int i=0;i<100;i++) + strcpy(m_szBotNames[i], "Bot"); + + m_sBotNameCount = 0; + + // Load bot file + char szNameFileName[256]; + MakeBotFileName("bot_names.txt", NULL, NULL, szNameFileName); + FILE *fp = fopen(szNameFileName, "r"); + char szNameBuffer[256]; + int iIndex, iStrIndex; + + if (!fp) + { + conoutf("Warning: Couldn't load bot names file"); + return; + } + + while (fgets(szNameBuffer, 80, fp) != NULL) + { + if (m_sBotNameCount >= 150) + { + conoutf("Warning: Max bot names reached(150), ignoring the rest of the" + "names"); + break; + } + + short length = strlen(szNameBuffer); + + if (szNameBuffer[length-1] == '\n') + { + szNameBuffer[length-1] = 0; // remove '\n' + length--; + } + + iStrIndex = 0; + while (iStrIndex < length) + { + if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') || + (szNameBuffer[iStrIndex] == '"')) + { + for (iIndex=iStrIndex; iIndex < length; iIndex++) + szNameBuffer[iIndex] = szNameBuffer[iIndex+1]; + } + + iStrIndex++; + } + + if (szNameBuffer[0] != 0) + { + if (strlen(szNameBuffer) >= 16) + { conoutf("Warning: bot name \"%s\" has to many characters(16 is max)", + (int)szNameBuffer); + } + strn0cpy(m_szBotNames[m_sBotNameCount], szNameBuffer, 16); + m_sBotNameCount++; + } + } + fclose(fp); +} + +char *CBotManager::GetBotName() +{ + char *szOutput = NULL; + TMultiChoice BotNameChoices; + short ChoiceVal; + + for(int j=0;jstate != CS_DEDHOST) && + !strcasecmp(players[i]->name, m_szBotNames[j])) + ChoiceVal -= 10; + } + + loopv(bots) + { + if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j]))) + ChoiceVal -= 10; + } + + if ((player1->state != CS_DEDHOST) && !strcasecmp(player1->name, m_szBotNames[j])) + ChoiceVal -= 10; + + if (ChoiceVal <= 0) + ChoiceVal = 1; + + BotNameChoices.Insert(m_szBotNames[j], ChoiceVal); + } + + // Couldn't find a selection? + if (!BotNameChoices.GetSelection(szOutput)) + szOutput = "Bot"; + + return szOutput; +} + +void CBotManager::LoadBotTeamsFile() +{ + if (!ishost()) + return; + + // Init bot teams array first + for (int i=0;i<20;i++) + strcpy(m_szBotTeams[i], "b0ts"); + + m_sBotTeamCount = 0; + + // Load bot file + char szNameFileName[256]; + MakeBotFileName("bot_teams.txt", NULL, NULL, szNameFileName); + FILE *fp = fopen(szNameFileName, "r"); + char szNameBuffer[256]; + int iIndex, iStrIndex; + + if (!fp) + { + conoutf("Warning: Couldn't load bot teams file"); + return; + } + + while ((m_sBotTeamCount < 20) && (fgets(szNameBuffer, 80, fp) != NULL)) + { + short length = strlen(szNameBuffer); + + if (szNameBuffer[length-1] == '\n') + { + szNameBuffer[length-1] = 0; // remove '\n' + length--; + } + + iStrIndex = 0; + while (iStrIndex < length) + { + if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') || + (szNameBuffer[iStrIndex] == '"')) + { + for (iIndex=iStrIndex; iIndex < length; iIndex++) + szNameBuffer[iIndex] = szNameBuffer[iIndex+1]; + } + + iStrIndex++; + } + + if (szNameBuffer[0] != 0) + { + strn0cpy(m_szBotTeams[m_sBotTeamCount], szNameBuffer, 5); + m_sBotTeamCount++; + } + } + fclose(fp); +} + +char *CBotManager::GetBotTeam() +{ + char *szOutput = NULL; + TMultiChoice BotTeamChoices; + short ChoiceVal; + + for(int j=0;jname, m_szBotNames[j]))) + ChoiceVal -= 10; + } + + loopv(bots) + { + if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j]))) + ChoiceVal -= 10; + } + + if (!strcasecmp(player1->name, m_szBotNames[j])) + ChoiceVal -= 10; + + if (ChoiceVal <= 0) + ChoiceVmonsterclearal = 1;*/ + + BotTeamChoices.Insert(m_szBotTeams[j], ChoiceVal); + } + + // Couldn't find a selection? + if (!BotTeamChoices.GetSelection(szOutput)) + szOutput = "b0t"; + + return szOutput; +} + +void CBotManager::RenderBots() +{ + static bool drawblue; + + loopv(bots) + { + if (bots[i] && (bots[i] != m_pBotToView)) + { + drawblue = (m_sp || isteam(player1->team, bots[i]->team)); + renderclient(bots[i], drawblue, "playermodels/counterterrorist", false, 1.0f); + } + } +} + +void CBotManager::EndMap() +{ + // Remove all bots + loopv(bots) + { + if (!bots[i]) + continue; + + if (ishost()) // Store bots so they can be re-added after map change + { + if (bots[i]->pBot && bots[i]->name[0] && bots[i]->team[0]) + { + CStoredBot *pStoredBot = new CStoredBot(bots[i]->name, bots[i]->team, + bots[i]->pBot->m_sSkillNr); + m_StoredBots.AddNode(pStoredBot); + } + delete bots[i]->pBot; + } + + bots[i]->pBot = NULL; + zapdynent(bots[i]); + } + bots.setsize(0); + condebug("Cleared all bots"); + m_fReAddBotDelay = lastmillis + 7500; + //if(ishost()) WaypointClass.SaveWPExpFile(); //UNDONE +} + +void CBotManager::BeginMap(char *szMapName) +{ + EndMap(); // End previous map + + if (!ishost()) return; + + WaypointClass.Init(); + WaypointClass.SetMapName(szMapName); + if (!WaypointClass.LoadWaypoints()) + WaypointClass.StartFlood(); + //WaypointClass.LoadWPExpFile(); // UNDONE + + CalculateMaxAStarCount(); + m_sUsingAStarBotsCount = 0; + PickNextTrigger(); +} + +int CBotManager::GetBotIndex(dynent *m) +{ + loopv(bots) + { + if (!bots[i]) + continue; + + if (bots[i] == m) + return i; + } + + return -1; +} + +void CBotManager::LetBotsUpdateStats() +{ + loopv(bots) if (bots[i] && bots[i]->pBot) bots[i]->pBot->m_bSendC2SInit = false; +} + +void CBotManager::LetBotsHear(int n, vec *loc) +{ + if (bots.length() == 0) return; + + if (!loc) return; + + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot || (bots[i]->state == CS_DEAD)) continue; + bots[i]->pBot->HearSound(n, loc); + } +} + +// Notify all bots of a new waypoint +void CBotManager::AddWaypoint(node_s *pNode) +{ + if (bots.length()) + { + short x, y; + waypoint_s *pWP; + + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + + pWP = new waypoint_s; + pWP->pNode = pNode; + WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); + bots[i]->pBot->m_WaypointList[x][y].AddNode(pWP); + +#ifndef RELEASE_BUILD + if (!bots[i]->pBot->GetWPFromNode(pNode)) condebug("Error adding bot wp!"); +#endif + } + } + + CalculateMaxAStarCount(); +} + +// Notify all bots of a deleted waypoint +void CBotManager::DelWaypoint(node_s *pNode) +{ + if (bots.length()) + { + short x, y; + TLinkedList::node_s *p; + + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + + WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); + p = bots[i]->pBot->m_WaypointList[x][y].GetFirst(); + + while(p) + { + if (p->Entry->pNode == pNode) + { + delete p->Entry; + bots[i]->pBot->m_WaypointList[x][y].DeleteNode(p); + break; + } + p = p->next; + } + } + } + + CalculateMaxAStarCount(); +} + +void CBotManager::MakeBotFileName(const char *szFileName, const char *szDir1, const char *szDir2, char *szOutput) +{ + char *DirSeperator; + +#ifndef __linux__ + DirSeperator = "\\"; + strcpy(szOutput, "bot\\"); +#else + DirSeperator = "/"; + strcpy(szOutput, "bot/"); +#endif + + if (szDir1) + { + strcat(szOutput, szDir1); + strcat(szOutput, DirSeperator); + } + + if (szDir2) + { + strcat(szOutput, szDir2); + strcat(szOutput, DirSeperator); + } + + strcat(szOutput, szFileName); +} + +void CBotManager::CreateSkillData() +{ + // First give the bot skill structure some default data + InitSkillData(); + + // Now see if we can load the skill.cfg file + char SkillFileName[256] = ""; + FILE *pSkillFile = NULL; + int SkillNr = -1; + float value = 0; + + MakeBotFileName("bot_skill.cfg", NULL, NULL, SkillFileName); + + pSkillFile = fopen(SkillFileName, "r"); + + conoutf("Reading bot_skill.cfg file... "); + + if (pSkillFile == NULL) // file doesn't exist + { + conoutf("skill file not found, default settings will be used\n"); + return; + } + + int ch; + char cmd_line[256]; + int cmd_index; + char *cmd, *arg1; + + while (pSkillFile) + { + cmd_index = 0; + cmd_line[cmd_index] = 0; + + ch = fgetc(pSkillFile); + + // skip any leading blanks + while (ch == ' ') + ch = fgetc(pSkillFile); + + while ((ch != EOF) && (ch != '\r') && (ch != '\n')) + { + if (ch == '\t') // convert tabs to spaces + ch = ' '; + + cmd_line[cmd_index] = ch; + + ch = fgetc(pSkillFile); + + // skip multiple spaces in input file + while ((cmd_line[cmd_index] == ' ') && (ch == ' ')) + ch = fgetc(pSkillFile); + + cmd_index++; + } + + if (ch == '\r') // is it a carriage return? + { + ch = fgetc(pSkillFile); // skip the linefeed + } + + // if reached end of file, then close it + if (ch == EOF) + { + fclose(pSkillFile); + pSkillFile = NULL; + } + + cmd_line[cmd_index] = 0; // terminate the command line + + cmd_index = 0; + cmd = cmd_line; + arg1 = NULL; + + // skip to blank or end of string... + while ((cmd_line[cmd_index] != ' ') && (cmd_line[cmd_index] != 0)) + cmd_index++; + + if (cmd_line[cmd_index] == ' ') + { + cmd_line[cmd_index++] = 0; + arg1 = &cmd_line[cmd_index]; + } + + if ((cmd_line[0] == '#') || (cmd_line[0] == 0)) + continue; // skip if comment or blank line + + + if (strcasecmp(cmd, "[SKILL1]") == 0) + SkillNr = 0; + else if (strcasecmp(cmd, "[SKILL2]") == 0) + SkillNr = 1; + else if (strcasecmp(cmd, "[SKILL3]") == 0) + SkillNr = 2; + else if (strcasecmp(cmd, "[SKILL4]") == 0) + SkillNr = 3; + else if (strcasecmp(cmd, "[SKILL5]") == 0) + SkillNr = 4; + + if ((arg1 == NULL) || (*arg1 == 0)) + continue; + + if (SkillNr == -1) // Not in a skill block yet? + continue; + + value = atof(arg1); + + if (strcasecmp(cmd, "min_x_aim_speed") == 0) + { + m_BotSkills[SkillNr].flMinAimXSpeed = value; + } + else if (strcasecmp(cmd, "max_x_aim_speed") == 0) + { + m_BotSkills[SkillNr].flMaxAimXSpeed = value; + } + else if (strcasecmp(cmd, "min_y_aim_speed") == 0) + { + m_BotSkills[SkillNr].flMinAimYSpeed = value; + } + else if (strcasecmp(cmd, "max_y_aim_speed") == 0) + { + m_BotSkills[SkillNr].flMaxAimYSpeed = value; + } + else if (strcasecmp(cmd, "min_x_aim_offset") == 0) + { + m_BotSkills[SkillNr].flMinAimXOffset = value; + } + else if (strcasecmp(cmd, "max_x_aim_offset") == 0) + { + m_BotSkills[SkillNr].flMaxAimXOffset = value; + } + else if (strcasecmp(cmd, "min_y_aim_offset") == 0) + { + m_BotSkills[SkillNr].flMinAimYOffset = value; + } + else if (strcasecmp(cmd, "max_y_aim_offset") == 0) + { + m_BotSkills[SkillNr].flMaxAimYOffset = value; + } + else if (strcasecmp(cmd, "min_attack_delay") == 0) + { + m_BotSkills[SkillNr].flMinAttackDelay = value; + } + else if (strcasecmp(cmd, "max_attack_delay") == 0) + { + m_BotSkills[SkillNr].flMaxAttackDelay = value; + } + else if (strcasecmp(cmd, "min_enemy_search_delay") == 0) + { + m_BotSkills[SkillNr].flMinEnemySearchDelay = value; + } + else if (strcasecmp(cmd, "max_enemy_search_delay") == 0) + { + m_BotSkills[SkillNr].flMaxEnemySearchDelay = value; + } + else if (strcasecmp(cmd, "shoot_at_feet_percent") == 0) + { + if (value < 0) value = 0; + else if (value > 100) value = 100; + m_BotSkills[SkillNr].sShootAtFeetWithRLPercent = (short)value; + } + else if (strcasecmp(cmd, "can_predict_position") == 0) + { + m_BotSkills[SkillNr].bCanPredict = value; + } + else if (strcasecmp(cmd, "max_hear_volume") == 0) + { + if (value < 0) value = 0; + else if (value > 255) value = 100; + m_BotSkills[SkillNr].iMaxHearVolume = (int)value; + } + else if (strcasecmp(cmd, "can_circle_strafe") == 0) + { + m_BotSkills[SkillNr].bCircleStrafe = value; + } + else if (strcasecmp(cmd, "can_search_items_in_combat") == 0) + { + m_BotSkills[SkillNr].bCanSearchItemsInCombat = value; + } + } + + conoutf("done\n"); +} + +void CBotManager::InitSkillData() +{ + // Best skill + m_BotSkills[0].flMinReactionDelay = 0.015f; + m_BotSkills[0].flMaxReactionDelay = 0.035f; + m_BotSkills[0].flMinAimXOffset = 15.0f; + m_BotSkills[0].flMaxAimXOffset = 20.0f; + m_BotSkills[0].flMinAimYOffset = 10.0f; + m_BotSkills[0].flMaxAimYOffset = 15.0f; + m_BotSkills[0].flMinAimXSpeed = 330.0f; + m_BotSkills[0].flMaxAimXSpeed = 355.0f; + m_BotSkills[0].flMinAimYSpeed = 400.0f; + m_BotSkills[0].flMaxAimYSpeed = 450.0f; + m_BotSkills[0].flMinAttackDelay = 0.1f; + m_BotSkills[0].flMaxAttackDelay = 0.4f; + m_BotSkills[0].flMinEnemySearchDelay = 0.09f; + m_BotSkills[0].flMaxEnemySearchDelay = 0.12f; + m_BotSkills[0].sShootAtFeetWithRLPercent = 85; + m_BotSkills[0].bCanPredict = true; + m_BotSkills[0].iMaxHearVolume = 75; + m_BotSkills[0].bCircleStrafe = true; + m_BotSkills[0].bCanSearchItemsInCombat = true; + + // Good skill + m_BotSkills[1].flMinReactionDelay = 0.035f; + m_BotSkills[1].flMaxReactionDelay = 0.045f; + m_BotSkills[1].flMinAimXOffset = 20.0f; + m_BotSkills[1].flMaxAimXOffset = 25.0f; + m_BotSkills[1].flMinAimYOffset = 15.0f; + m_BotSkills[1].flMaxAimYOffset = 20.0f; + m_BotSkills[1].flMinAimXSpeed = 250.0f; + m_BotSkills[1].flMaxAimXSpeed = 265.0f; + m_BotSkills[1].flMinAimYSpeed = 260.0f; + m_BotSkills[1].flMaxAimYSpeed = 285.0f; + m_BotSkills[1].flMinAttackDelay = 0.3f; + m_BotSkills[1].flMaxAttackDelay = 0.6f; + m_BotSkills[1].flMinEnemySearchDelay = 0.12f; + m_BotSkills[1].flMaxEnemySearchDelay = 0.17f; + m_BotSkills[1].sShootAtFeetWithRLPercent = 60; + m_BotSkills[1].bCanPredict = true; + m_BotSkills[1].iMaxHearVolume = 60; + m_BotSkills[1].bCircleStrafe = true; + m_BotSkills[1].bCanSearchItemsInCombat = true; + + // Medium skill + m_BotSkills[2].flMinReactionDelay = 0.075f; + m_BotSkills[2].flMaxReactionDelay = 0.010f; + m_BotSkills[2].flMinAimXOffset = 25.0f; + m_BotSkills[2].flMaxAimXOffset = 30.0f; + m_BotSkills[2].flMinAimYOffset = 20.0f; + m_BotSkills[2].flMaxAimYOffset = 25.0f; + m_BotSkills[2].flMinAimXSpeed = 190.0f; + m_BotSkills[2].flMaxAimXSpeed = 125.0f; + m_BotSkills[2].flMinAimYSpeed = 210.0f; + m_BotSkills[2].flMaxAimYSpeed = 240.0f; + m_BotSkills[2].flMinAttackDelay = 0.75f; + m_BotSkills[2].flMaxAttackDelay = 1.0f; + m_BotSkills[2].flMinEnemySearchDelay = 0.18f; + m_BotSkills[2].flMaxEnemySearchDelay = 0.22f; + m_BotSkills[2].sShootAtFeetWithRLPercent = 25; + m_BotSkills[2].bCanPredict = false; + m_BotSkills[2].iMaxHearVolume = 45; + m_BotSkills[2].bCircleStrafe = true; + m_BotSkills[2].bCanSearchItemsInCombat = false; + + // Worse skill + m_BotSkills[3].flMinReactionDelay = 0.15f; + m_BotSkills[3].flMaxReactionDelay = 0.20f; + m_BotSkills[3].flMinAimXOffset = 30.0f; + m_BotSkills[3].flMaxAimXOffset = 35.0f; + m_BotSkills[3].flMinAimYOffset = 25.0f; + m_BotSkills[3].flMaxAimYOffset = 30.0f; + m_BotSkills[3].flMinAimXSpeed = 155.0f; + m_BotSkills[3].flMaxAimXSpeed = 170.0f; + m_BotSkills[3].flMinAimYSpeed = 160.0f; + m_BotSkills[3].flMaxAimYSpeed = 210.0f; + m_BotSkills[3].flMinAttackDelay = 1.2f; + m_BotSkills[3].flMaxAttackDelay = 1.6f; + m_BotSkills[3].flMinEnemySearchDelay = 0.25f; + m_BotSkills[3].flMaxEnemySearchDelay = 0.30f; + m_BotSkills[3].sShootAtFeetWithRLPercent = 10; + m_BotSkills[3].bCanPredict = false; + m_BotSkills[3].iMaxHearVolume = 30; + m_BotSkills[3].bCircleStrafe = false; + m_BotSkills[3].bCanSearchItemsInCombat = false; + + // Bad skill + m_BotSkills[4].flMinReactionDelay = 0.30f; + m_BotSkills[4].flMaxReactionDelay = 0.50f; + m_BotSkills[4].flMinAimXOffset = 35.0f; + m_BotSkills[4].flMaxAimXOffset = 40.0f; + m_BotSkills[4].flMinAimYOffset = 30.0f; + m_BotSkills[4].flMaxAimYOffset = 35.0f; + m_BotSkills[4].flMinAimXSpeed = 45.0f; + m_BotSkills[4].flMaxAimXSpeed = 60.0f; + m_BotSkills[4].flMinAimYSpeed = 125.0f; + m_BotSkills[4].flMaxAimYSpeed = 180.0f; + m_BotSkills[4].flMinAttackDelay = 1.5f; + m_BotSkills[4].flMaxAttackDelay = 2.0f; + m_BotSkills[4].flMinEnemySearchDelay = 0.30f; + m_BotSkills[4].flMaxEnemySearchDelay = 0.36f; + m_BotSkills[4].sShootAtFeetWithRLPercent = 0; + m_BotSkills[4].bCanPredict = false; + m_BotSkills[4].iMaxHearVolume = 15; + m_BotSkills[4].bCircleStrafe = false; + m_BotSkills[4].bCanSearchItemsInCombat = false; +} + +void CBotManager::ChangeBotSkill(short Skill, dynent *bot) +{ + static char *SkillNames[5] = { "best", "good", "medium", "worse", "bad" }; + + if (bot && bot->pBot) + { + // Only change skill of a single bot + bot->pBot->m_pBotSkill = &m_BotSkills[Skill]; + bot->pBot->m_sSkillNr = Skill; + conoutf("Skill of %s is now %s", (int)bot->name, (int)SkillNames[Skill]); + return; + } + + // Change skill of all bots + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + + bots[i]->pBot->m_pBotSkill = &m_BotSkills[Skill]; + bots[i]->pBot->m_sSkillNr = Skill; + } + + // Change default bot skill + m_sBotSkill = Skill; + + conoutf("Skill of all bots is now %s", (int)SkillNames[Skill]); +} + +void CBotManager::ViewBot() +{ + // Check if this bot is still in game + bool bFound = false; + loopv(bots) + { + if (bots[i] == m_pBotToView) + { + bFound = true; + break; + } + } + + if (!bFound) + { + DisableBotView(); + return; + } + + player1->state = CS_DEAD; // Fake dead + + player1->o = m_pBotToView->o; + player1->o.z += 1.0f; + player1->yaw = m_pBotToView->yaw; + player1->pitch = m_pBotToView->pitch; + player1->roll = m_pBotToView->roll; + player1->radius = 0; // Don't collide +} + +void CBotManager::DisableBotView() +{ + m_pBotToView = NULL; + respawnself(); + player1->radius = 1.1f; +} + +void CBotManager::CalculateMaxAStarCount() +{ + if (WaypointClass.m_iWaypointCount > 0) // Are there any waypoints? + { + m_sMaxAStarBots = 8 - short(ceil((float)WaypointClass.m_iWaypointCount / + 1000.0f)); + if (m_sMaxAStarBots < 1) + m_sMaxAStarBots = 1; + } + else + m_sMaxAStarBots = 1; +} + +void CBotManager::PickNextTrigger() +{ + if (!ishost()) return; + + short lowest = -1; + bool found0 = false; // True if found a trigger with nr 0 + + loopv(ents) + { + entity &e = ents[i]; + +#if defined AC_CUBE +/* if ((e.type != TRIGGER) || !e.spawned) + continue;*/ +#elif defined VANILLA_CUBE + if ((e.type != CARROT) || !e.spawned) + continue; +#endif + if (OUTBORD(e.x, e.y)) continue; + + vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + + node_s *pWptNearEnt = NULL; + + pWptNearEnt = WaypointClass.GetNearestTriggerWaypoint(o, 2.0f); + + if (pWptNearEnt) + { + if ((pWptNearEnt->sTriggerNr > 0) && + ((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1))) + lowest = pWptNearEnt->sTriggerNr; + if (pWptNearEnt->sTriggerNr == 0) found0 = true; + } + +#ifdef WP_FLOOD + pWptNearEnt = WaypointClass.GetNearestTriggerFloodWP(o, 2.0f); + + if (pWptNearEnt) + { + if ((pWptNearEnt->sTriggerNr > 0) && + ((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1))) + lowest = pWptNearEnt->sTriggerNr; + if (pWptNearEnt->sTriggerNr == 0) found0 = true; + } + +#endif + } + + if ((lowest == -1) && found0) lowest = 0; + + if (lowest != -1) + m_sCurrentTriggerNr = lowest; +} + +dynent *CBotManager::CreateBot(const char *team, const char *skill, const char *name) +{ + dynent *m = newdynent(); + if (!m) return NULL; + + // Create new bot class, dependand on the current mod +#if defined VANILLA_CUBE + m->pBot = new CCubeBot; +#elif defined AC_CUBE + m->pBot = new CACBot; +#else + #error "Unsupported mod!" +#endif + m->bIsBot = true; + m->pBot->m_pMyEnt = m; + m->pBot->m_iLastBotUpdate = 0; + m->pBot->m_bSendC2SInit = false; + + if (name && *name) + strn0cpy(m->name, name, 16); + else + strn0cpy(m->name, BotManager.GetBotName(), 16); + + if (team && *team && strcmp(team, "random")) + strn0cpy(m->team, team, 5); + else + strn0cpy(m->team, BotManager.GetBotTeam(), 5); + + if (skill && *skill && strcmp(skill, "random")) + { + if (!strcasecmp(skill, "best")) + m->pBot->m_sSkillNr = 0; + else if (!strcasecmp(skill, "good")) + m->pBot->m_sSkillNr = 1; + else if (!strcasecmp(skill, "medium")) + m->pBot->m_sSkillNr = 2; + else if (!strcasecmp(skill, "worse")) + m->pBot->m_sSkillNr = 3; + else if (!strcasecmp(skill, "bad")) + m->pBot->m_sSkillNr = 4; + else + { + conoutf("Wrong skill specified. Should be best, good, medium, " + "worse or bad"); + conoutf("Using default skill instead..."); + m->pBot->m_sSkillNr = BotManager.m_sBotSkill; + } + } + else // No skill specified, use default + m->pBot->m_sSkillNr = BotManager.m_sBotSkill; + + m->pBot->m_pBotSkill = &BotManager.m_BotSkills[m->pBot->m_sSkillNr]; + + // Sync waypoints + m->pBot->SyncWaypoints(); + + m->pBot->Spawn(); + + bots.add(m); + + return m; +} + +// Bot manager class end + +void addbot(char *arg1, char *arg2, char *arg3) +{ + if (ishost()) + { + conoutf("Creating bot...\n"); + dynent *b = BotManager.CreateBot(arg1, arg2, arg3); + if (b) + conoutf("connected: %s", (int)b->name); + else + { + conoutf("Error: Couldn't create bot!"); + return; + } + } + else if (clienthost) + { + char team[32], name[32]; + int skill = -1; + + team[0] = name[0] = 0; + + if (arg1 && arg1[0]) strcpy(team, arg1); + if (arg3 && arg3[0]) strcpy(name, arg3); + if (arg2 && arg2[0]) + { + if (!strcasecmp(arg2, "best")) skill = 0; + else if (!strcasecmp(arg2, "good")) skill = 1; + else if (!strcasecmp(arg2, "medium")) skill = 2; + else if (!strcasecmp(arg2, "worse")) skill = 3; + else if (!strcasecmp(arg2, "bad")) skill = 4; + } + + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_BOTCOMMAND); + putint(p, int(COMMAND_ADDBOT)); // Bot command type + putint(p, 1); // Bot count + putint(p, skill); // Bot skill + sendstring(team, p); // Bot team + sendstring(name, p); // Bot name + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + } +} + +COMMAND(addbot, ARG_3STR); + +void addnbot(char *arg1, char *arg2, char *arg3) +{ + if (!arg1 || !arg1[0]) return; + + int i = atoi(arg1); + + if (ishost()) + { + while(i > 0) + { + addbot(arg2, arg3, NULL); + i--; + } + } + else if (clienthost) + { + char team[32], name[32]; + int skill = -1; + + team[0] = name[0] = 0; + + if (arg2 && arg2[0]) strcpy(team, arg2); +// if (arg4 && arg4[0]) strcpy(name, arg4); + if (arg3 && arg3[0]) + { + if (!strcasecmp(arg2, "best")) skill = 0; + else if (!strcasecmp(arg2, "good")) skill = 1; + else if (!strcasecmp(arg2, "medium")) skill = 2; + else if (!strcasecmp(arg2, "worse")) skill = 3; + else if (!strcasecmp(arg2, "bad")) skill = 4; + } + + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_BOTCOMMAND); + putint(p, int(COMMAND_ADDBOT)); // Bot command type + putint(p, i); // Bot count + putint(p, skill); // Bot skill + sendstring(team, p); // Bot team + sendstring(name, p); // Bot name + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + } +} + +COMMAND(addnbot, ARG_3STR); + +void botsshoot(int Shoot) +{ + if (!ishost()) return; + if (Shoot) + { + BotManager.SetBotsShoot(true); + conoutf("Bots will shoot"); + } + else + { + BotManager.SetBotsShoot(false); + conoutf("Bots won't shoot"); + } +} + +COMMAND(botsshoot, ARG_1INT); + +void idlebots(int Idle) +{ + if (!ishost()) return; + if (Idle) + { + BotManager.SetIdleBots(true); + conoutf("Bots are idle"); + } + else + { + BotManager.SetIdleBots(false); + conoutf("Bots aren't idle"); + } +} + +COMMAND(idlebots, ARG_1INT); + +void drawbeamtobots() +{ + loopv(bots) + { + if (bots[i]) + particle_trail(1, 500, player1->o, bots[i]->o); + } +} + +COMMAND(drawbeamtobots, ARG_NONE); + +void kickbot(const char *szName) +{ + if (!szName || !(*szName)) + return; + + int iBotInd = -1; + loopv(bots) + { + if (!bots[i]) continue; + + if (!strcmp(bots[i]->name, szName)) + { + iBotInd = i; + break; + } + } + + if (iBotInd != -1) + { + if (ishost()) + { + addmsg(1, 2, SV_BOTDIS, iBotInd); + + conoutf("bot %s disconnected", (int)(bots[iBotInd]->name[0] ? bots[iBotInd]->name : + "[incompatible client]")); + delete bots[iBotInd]->pBot; + zapdynent(bots[iBotInd]); + bots.remove(iBotInd); + } + else if (clienthost) // Ask server to destroy bot(with voting) + { + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_BOTCOMMAND); + putint(p, int(COMMAND_KICKBOT)); // Bot command type + putint(p, 1); // 0==kick all bots, 1==kick one specific bot + char tmp[20]; + strcpy(tmp, szName); + sendstring(tmp, p); // Bot name + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + } + } +} + +COMMAND(kickbot, ARG_1STR); + +void kickallbots(void) +{ + loopv(bots) + { + if (bots[i]) + { + if (ishost()) + { + addmsg(1, 2, SV_BOTDIS, i); + + conoutf("bot %s disconnected",(int)(bots[i]->name[0] ? + bots[i]->name : "[incompatible client]")); + delete bots[i]->pBot; + zapdynent(bots[i]); + } + else if (clienthost) // Ask the server to destroy all bots + { + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, + ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_BOTCOMMAND); + putint(p, int(COMMAND_KICKBOT)); // Bot command type + putint(p, 0); // 0==kick all bots, 1==kick one specific bot + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + } + } + } + + bots.setsize(0); +} + +COMMAND(kickallbots, ARG_NONE); + +void togglegrap() +{ + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GrabMode(0)) + SDL_WM_GrabInput(SDL_GRAB_ON); + else + SDL_WM_GrabInput(SDL_GrabMode(0)); +} + +COMMAND(togglegrap, ARG_NONE); + +void togglebotview(char *bot) +{ + if (BotManager.m_pBotToView) + BotManager.DisableBotView(); + else if (bot && *bot) + { + loopv(bots) + { + if (!bots[i]) continue; + + if (!strcmp(bots[i]->name, bot)) + { + BotManager.EnableBotView(bots[i]); + break; + } + } + } +} + +COMMAND(togglebotview, ARG_1STR); + +void botskill(char *bot, char *skill) +{ + if (!skill || !(*skill)) + return; + + short SkillNr; + + if (!strcasecmp(skill, "best")) + SkillNr = 0; + else if (!strcasecmp(skill, "good")) + SkillNr = 1; + else if (!strcasecmp(skill, "medium")) + SkillNr = 2; + else if (!strcasecmp(skill, "worse")) + SkillNr = 3; + else if (!strcasecmp(skill, "bad")) + SkillNr = 4; + else + { + conoutf("Wrong skill specified. Should be best, good, medium, worse or bad"); + return; + } + + if (ishost()) + { + if (bot) + { + loopv(bots) + { + if (bots[i] && !strcmp(bots[i]->name, bot)) + { + BotManager.ChangeBotSkill(SkillNr, bots[i]); + break; + } + } + } + else + BotManager.ChangeBotSkill(SkillNr); + } + else if (clienthost) // ask server to change botskill + { + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_BOTCOMMAND); + putint(p, int(COMMAND_BOTSKILL)); // Bot command type + putint(p, SkillNr); + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + } +} + +COMMAND(botskill, ARG_2STR); + +void botskillall(char *skill) +{ + botskill(NULL, skill); +} + +COMMAND(botskillall, ARG_1STR); + +#ifndef RELEASE_BUILD + +#ifdef VANILLA_CUBE +void drawbeamtocarrots() +{ + loopv(ents) + { + entity &e = ents[i]; + vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + if ((e.type != CARROT) || !e.spawned) continue; + particle_trail(1, 500, player1->o, o); + } +} + +COMMAND(drawbeamtocarrots, ARG_NONE); + +void drawbeamtoteleporters() +{ + loopv(ents) + { + entity &e = ents[i]; + vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + if (e.type != TELEPORT) continue; + particle_trail(1, 500, player1->o, o); + } +} + +COMMAND(drawbeamtoteleporters, ARG_NONE); +#endif + +void telebot(void) +{ + vec dest = player1->o, forward, right, up; + vec angles = { player1->pitch, player1->yaw, player1->roll }; + traceresult_s tr; + + AnglesToVectors(angles, forward, right, up); + vmul(forward, 4.0f); + vadd(dest, forward); + + TraceLine(player1->o, dest, player1, true, &tr); + + if (!tr.collided) + { + // Get the first bot + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + bots[i]->o = tr.end; + break; + } + } +} + +COMMAND(telebot, ARG_NONE); + +void testvisible(int iDir) +{ + vec angles, end, forward, right, up; + traceresult_s tr; + int Dir; + + switch(iDir) + { + case 0: default: Dir = FORWARD; break; + case 1: Dir = BACKWARD; break; + case 2: Dir = LEFT; break; + case 3: Dir = RIGHT; break; + case 4: Dir = UP; break; + case 5: Dir = DOWN; break; + } + + vec from = player1->o; + from.z -= (player1->eyeheight - 1.25f); + end = from; + makevec(&angles, player1->pitch, player1->yaw, player1->roll); + angles.x=0; + + if (Dir & UP) + angles.x = WrapXAngle(angles.x + 45.0f); + else if (Dir & DOWN) + angles.x = WrapXAngle(angles.x - 45.0f); + + if ((Dir & FORWARD) || (Dir & BACKWARD)) + { + if (Dir & BACKWARD) + angles.y = WrapYZAngle(angles.y + 180.0f); + + if (Dir & LEFT) + { + if (Dir & FORWARD) + angles.y = WrapYZAngle(angles.y - 45.0f); + else + angles.y = WrapYZAngle(angles.y + 45.0f); + } + else if (Dir & RIGHT) + { + if (Dir & FORWARD) + angles.y = WrapYZAngle(angles.y + 45.0f); + else + angles.y = WrapYZAngle(angles.y - 45.0f); + } + } + else if (Dir & LEFT) + angles.y = WrapYZAngle(angles.y - 90.0f); + else if (Dir & RIGHT) + angles.y = WrapYZAngle(angles.y + 90.0f); + else if (Dir & UP) + angles.x = WrapXAngle(angles.x + 90.0f); + else if (Dir & DOWN) + angles.x = WrapXAngle(angles.x - 90.0f); + + AnglesToVectors(angles, forward, right, up); + + vmul(forward, 20.0f); + vadd(end, forward); + + TraceLine(from, end, player1, false, &tr); + + //debugbeam(from, tr.end); + char sz[250]; + sprintf(sz, "dist: %f; hit: %d", GetDistance(from, tr.end), tr.collided); + condebug(sz); +} + +COMMAND(testvisible, ARG_1INT); + +void mapsize(void) +{ + conoutf("ssize: %d", ssize); +} + +COMMAND(mapsize, ARG_NONE); + +#endif diff --git a/source/src/client.cpp b/source/src/client.cpp index 19bb1541..a0c364aa 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -1,333 +1,333 @@ -// client.cpp, mostly network related client game code - -#include "cube.h" - -ENetHost *clienthost = NULL; -int connecting = 0; -int connattempts = 0; -int disconnecting = 0; -int clientnum = -1; // our client id in the game -bool c2sinit = false; // whether we need to tell the other clients our stats - -int getclientnum() { return clientnum; }; - -bool multiplayer() -{ - // check not correct on listen server? - if(clienthost) conoutf("operation not available in multiplayer"); - return clienthost!=NULL; -}; - -bool allowedittoggle() -{ - bool allow = !clienthost || gamemode==1; - if(!allow) conoutf("editing in multiplayer requires coopedit mode (1)"); - return allow; -}; - -VARF(rate, 0, 0, 25000, if(clienthost && (!rate || rate>1000)) enet_host_bandwidth_limit (clienthost, rate, rate)); - -void throttle(); - -VARF(throttle_interval, 0, 5, 30, throttle()); -VARF(throttle_accel, 0, 2, 32, throttle()); -VARF(throttle_decel, 0, 2, 32, throttle()); - -void throttle() -{ - if(!clienthost || connecting) return; - assert(ENET_PEER_PACKET_THROTTLE_SCALE==32); - enet_peer_throttle_configure(clienthost->peers, throttle_interval*1000, throttle_accel, throttle_decel); -}; - -void ctf_team(char *name) -{ - bool isvalidteam = strcmp(name, "RVSF") == 0 || strcmp(name, "CLA") == 0; - if(strcmp(name, player1->team) == 0 && isvalidteam) return; - c2sinit = false; - ctf_death(); - if(isvalidteam) strcpy(player1->team, name); - else strcpy(player1->team, rb_team_string(rnd(2))); - player1->lastaction = lastmillis; - player1->state = CS_DEAD; -}; - -void newname(char *name) { c2sinit = false; strn0cpy(player1->name, name, 16); }; -void newskin(int skin) { player1->nextskin = max(0, min(skin, (rb_team_int(player1->team)==TEAM_CLA ? 3 : 5))); }; -void newteam(char *name) -{ - if(m_teammode) ctf_team(name); - else { strn0cpy(player1->team, name, 5); c2sinit=false; }; -}; - -COMMANDN(team, newteam, ARG_1STR); -COMMANDN(name, newname, ARG_1STR); -COMMANDN(skin, newskin, ARG_1INT); - -void connects(char *servername) -{ - disconnect(1); // reset state - addserver(servername); - - conoutf("attempting to connect to %s", (int)servername); - ENetAddress address = { ENET_HOST_ANY, CUBE_SERVER_PORT }; - if(enet_address_set_host(&address, servername) < 0) - { - conoutf("could not resolve server %s", (int) servername); - return; - }; - - clienthost = enet_host_create(NULL, 1, rate, rate); - - if(clienthost) - { - enet_host_connect(clienthost, &address, 1); - enet_host_flush(clienthost); - connecting = lastmillis; - connattempts = 0; - } - else - { - conoutf("could not connect to server"); - disconnect(); - }; -}; - -void disconnect(int onlyclean, int async) -{ - if(clienthost) - { - if(!connecting && !disconnecting) - { - enet_peer_disconnect(clienthost->peers); - enet_host_flush(clienthost); - disconnecting = lastmillis; - }; - if(clienthost->peers->state != ENET_PEER_STATE_DISCONNECTED) - { - if(async) return; - enet_peer_reset(clienthost->peers); - }; - enet_host_destroy(clienthost); - }; - - if(clienthost && !connecting) conoutf("disconnected"); - clienthost = NULL; - connecting = 0; - connattempts = 0; - disconnecting = 0; - clientnum = -1; - c2sinit = false; - player1->lifesequence = 0; - loopv(players) zapdynent(players[i]); - - localdisconnect(); - - if(!onlyclean) { stop(); localconnect(); }; -}; - -void trydisconnect() -{ - if(!clienthost) - { - conoutf("not connected"); - return; - }; - if(connecting) - { - conoutf("aborting connection attempt"); - disconnect(); - return; - }; - conoutf("attempting to disconnect..."); - disconnect(0, !disconnecting); -}; - -string ctext; -void toserver(char *text) { conoutf("%s:\f %s", (int)player1->name, (int)text); strn0cpy(ctext, text, 80); }; -void echo(char *text) { conoutf("%s", (int)text); }; - -COMMAND(echo, ARG_VARI); -COMMANDN(say, toserver, ARG_VARI); -COMMANDN(connect, connects, ARG_1STR); -COMMANDN(disconnect, trydisconnect, ARG_NONE); - -// collect c2s messages conveniently - -vector messages; - -void addmsg(int rel, int num, int type, ...) -{ - if(demoplayback) return; - if(num!=msgsizelookup(type)) { sprintf_sd(s)("inconsistant msg size for %d (%d != %d)", type, num, msgsizelookup(type)); fatal(s); }; - if(messages.length()==100) { conoutf("command flood protection (type %d)", type); return; }; - ivector &msg = messages.add(); - msg.add(num); - msg.add(rel); - msg.add(type); - va_list marker; - va_start(marker, type); - loopi(num-1) msg.add(va_arg(marker, int)); - va_end(marker); -}; - -void server_err() -{ - conoutf("server network error, disconnecting..."); - disconnect(); -}; - -int lastupdate = 0, lastping = 0; -string toservermap; -bool senditemstoserver = false; // after a map change, since server doesn't have map data - -string clientpassword; -void password(char *p) { strcpy_s(clientpassword, p); }; -COMMAND(password, ARG_1STR); - -bool netmapstart() { senditemstoserver = true; return clienthost!=NULL; }; - -void initclientnet() -{ - ctext[0] = 0; - toservermap[0] = 0; - clientpassword[0] = 0; - newname("unnamed"); - ctf_team("red"); -}; - -void sendpackettoserv(void *packet) -{ - if(clienthost) { enet_host_broadcast(clienthost, 0, (ENetPacket *)packet); enet_host_flush(clienthost); } - else localclienttoserver((ENetPacket *)packet); -} - -void c2sinfo(dynent *d) // send update to the server -{ - if(clientnum<0) return; // we haven't had a welcome message from the server yet - if(lastmillis-lastupdate<40) return; // don't update faster than 25fps - ENetPacket *packet = enet_packet_create (NULL, MAXTRANS, 0); - uchar *start = packet->data; - uchar *p = start+2; - bool serveriteminitdone = false; - if(toservermap[0]) // suggest server to change map - { // do this exclusively as map change may invalidate rest of update - packet->flags = ENET_PACKET_FLAG_RELIABLE; - putint(p, SV_MAPCHANGE); - sendstring(toservermap, p); - toservermap[0] = 0; - putint(p, nextmode); - } - else - { - putint(p, SV_POS); - putint(p, clientnum); - putint(p, (int)(d->o.x*DMF)); // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes - putint(p, (int)(d->o.y*DMF)); - putint(p, (int)(d->o.z*DMF)); - putint(p, (int)(d->yaw*DAF)); - putint(p, (int)(d->pitch*DAF)); - putint(p, (int)(d->roll*DAF)); - putint(p, (int)(d->vel.x*DVF)); // quantize to 1/100, almost always 1 byte - putint(p, (int)(d->vel.y*DVF)); - putint(p, (int)(d->vel.z*DVF)); - // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 - putint(p, (d->strafe&3) | ((d->move&3)<<2) | (((int)d->onfloor)<<4) | ((editmode ? CS_EDITING : d->state)<<5) ); - - if(gun_changed) - { - putint(p, SV_WEAPCHANGE); - putint(p, player1->gunselect); - gun_changed = false; - } - if(senditemstoserver) - { - packet->flags = ENET_PACKET_FLAG_RELIABLE; - putint(p, SV_ITEMLIST); - if(!m_noitems) putitems(p); - putint(p, -1); - senditemstoserver = false; - serveriteminitdone = true; - }; - if(ctext[0]) // player chat, not flood protected for now - { - packet->flags = ENET_PACKET_FLAG_RELIABLE; - putint(p, SV_TEXT); - sendstring(ctext, p); - ctext[0] = 0; - }; - if(!c2sinit) // tell other clients who I am - { - packet->flags = ENET_PACKET_FLAG_RELIABLE; - c2sinit = true; - putint(p, SV_INITC2S); - sendstring(player1->name, p); - sendstring(player1->team, p); - putint(p, player1->skin); - putint(p, player1->lifesequence); - }; - loopv(messages) // send messages collected during the previous frames - { - ivector &msg = messages[i]; - if(msg[1]) packet->flags = ENET_PACKET_FLAG_RELIABLE; - loopi(msg[0]) putint(p, msg[i+2]); - }; - messages.setsize(0); - if(lastmillis-lastping>250) - { - putint(p, SV_PING); - putint(p, lastmillis); - lastping = lastmillis; - }; - }; - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - incomingdemodata(start, p-start, true); - if(clienthost) { enet_host_broadcast(clienthost, 0, packet); enet_host_flush(clienthost); } - else localclienttoserver(packet); - lastupdate = lastmillis; - if(serveriteminitdone) loadgamerest(); // hack -}; - -void gets2c() // get updates from the server -{ - ENetEvent event; - if(!clienthost) return; - if(connecting && lastmillis/3000 > connecting/3000) - { - conoutf("attempting to connect..."); - connecting = lastmillis; - ++connattempts; - if(connattempts > 3) - { - conoutf("could not connect to server"); - disconnect(); - return; - }; - }; - while(clienthost!=NULL && enet_host_service(clienthost, &event, 0)>0) - switch(event.type) - { - case ENET_EVENT_TYPE_CONNECT: - conoutf("connected to server"); - connecting = 0; - throttle(); - break; - - case ENET_EVENT_TYPE_RECEIVE: - if(disconnecting) conoutf("attempting to disconnect..."); - else localservertoclient(event.packet->data, event.packet->dataLength); - enet_packet_destroy(event.packet); - break; - - case ENET_EVENT_TYPE_DISCONNECT: - if(disconnecting) disconnect(); - else server_err(); - return; - } -}; - -void lsinfo() -{ - printf("%i %i\n", player1->ammo[player1->gunselect], player1->mag[player1->gunselect]); -}; COMMAND(lsinfo, ARG_NONE); +// client.cpp, mostly network related client game code + +#include "cube.h" + +ENetHost *clienthost = NULL; +int connecting = 0; +int connattempts = 0; +int disconnecting = 0; +int clientnum = -1; // our client id in the game +bool c2sinit = false; // whether we need to tell the other clients our stats + +int getclientnum() { return clientnum; }; + +bool multiplayer() +{ + // check not correct on listen server? + if(clienthost) conoutf("operation not available in multiplayer"); + return clienthost!=NULL; +}; + +bool allowedittoggle() +{ + bool allow = !clienthost || gamemode==1; + if(!allow) conoutf("editing in multiplayer requires coopedit mode (1)"); + return allow; +}; + +VARF(rate, 0, 0, 25000, if(clienthost && (!rate || rate>1000)) enet_host_bandwidth_limit (clienthost, rate, rate)); + +void throttle(); + +VARF(throttle_interval, 0, 5, 30, throttle()); +VARF(throttle_accel, 0, 2, 32, throttle()); +VARF(throttle_decel, 0, 2, 32, throttle()); + +void throttle() +{ + if(!clienthost || connecting) return; + assert(ENET_PEER_PACKET_THROTTLE_SCALE==32); + enet_peer_throttle_configure(clienthost->peers, throttle_interval*1000, throttle_accel, throttle_decel); +}; + +void ctf_team(char *name) +{ + bool isvalidteam = strcmp(name, "RVSF") == 0 || strcmp(name, "CLA") == 0; + if(strcmp(name, player1->team) == 0 && isvalidteam) return; + c2sinit = false; + ctf_death(); + if(isvalidteam) strcpy(player1->team, name); + else strcpy(player1->team, rb_team_string(rnd(2))); + player1->lastaction = lastmillis; + player1->state = CS_DEAD; +}; + +void newname(char *name) { c2sinit = false; strn0cpy(player1->name, name, 16); }; +void newskin(int skin) { player1->nextskin = max(0, min(skin, (rb_team_int(player1->team)==TEAM_CLA ? 3 : 5))); }; +void newteam(char *name) +{ + if(m_teammode) ctf_team(name); + else { strn0cpy(player1->team, name, 5); c2sinit=false; }; +}; + +COMMANDN(team, newteam, ARG_1STR); +COMMANDN(name, newname, ARG_1STR); +COMMANDN(skin, newskin, ARG_1INT); + +void connects(char *servername) +{ + disconnect(1); // reset state + addserver(servername); + + conoutf("attempting to connect to %s", (int)servername); + ENetAddress address = { ENET_HOST_ANY, CUBE_SERVER_PORT }; + if(enet_address_set_host(&address, servername) < 0) + { + conoutf("could not resolve server %s", (int) servername); + return; + }; + + clienthost = enet_host_create(NULL, 1, rate, rate); + + if(clienthost) + { + enet_host_connect(clienthost, &address, 1); + enet_host_flush(clienthost); + connecting = lastmillis; + connattempts = 0; + } + else + { + conoutf("could not connect to server"); + disconnect(); + }; +}; + +void disconnect(int onlyclean, int async) +{ + if(clienthost) + { + if(!connecting && !disconnecting) + { + enet_peer_disconnect(clienthost->peers); + enet_host_flush(clienthost); + disconnecting = lastmillis; + }; + if(clienthost->peers->state != ENET_PEER_STATE_DISCONNECTED) + { + if(async) return; + enet_peer_reset(clienthost->peers); + }; + enet_host_destroy(clienthost); + }; + + if(clienthost && !connecting) conoutf("disconnected"); + clienthost = NULL; + connecting = 0; + connattempts = 0; + disconnecting = 0; + clientnum = -1; + c2sinit = false; + player1->lifesequence = 0; + loopv(players) zapdynent(players[i]); + + localdisconnect(); + + if(!onlyclean) { stop(); localconnect(); }; +}; + +void trydisconnect() +{ + if(!clienthost) + { + conoutf("not connected"); + return; + }; + if(connecting) + { + conoutf("aborting connection attempt"); + disconnect(); + return; + }; + conoutf("attempting to disconnect..."); + disconnect(0, !disconnecting); +}; + +string ctext; +void toserver(char *text) { conoutf("%s:\f %s", (int)player1->name, (int)text); strn0cpy(ctext, text, 80); }; +void echo(char *text) { conoutf("%s", (int)text); }; + +COMMAND(echo, ARG_VARI); +COMMANDN(say, toserver, ARG_VARI); +COMMANDN(connect, connects, ARG_1STR); +COMMANDN(disconnect, trydisconnect, ARG_NONE); + +// collect c2s messages conveniently + +vector messages; + +void addmsg(int rel, int num, int type, ...) +{ + if(demoplayback) return; + if(num!=msgsizelookup(type)) { sprintf_sd(s)("inconsistant msg size for %d (%d != %d)", type, num, msgsizelookup(type)); fatal(s); }; + if(messages.length()==100) { conoutf("command flood protection (type %d)", type); return; }; + ivector &msg = messages.add(); + msg.add(num); + msg.add(rel); + msg.add(type); + va_list marker; + va_start(marker, type); + loopi(num-1) msg.add(va_arg(marker, int)); + va_end(marker); +}; + +void server_err() +{ + conoutf("server network error, disconnecting..."); + disconnect(); +}; + +int lastupdate = 0, lastping = 0; +string toservermap; +bool senditemstoserver = false; // after a map change, since server doesn't have map data + +string clientpassword; +void password(char *p) { strcpy_s(clientpassword, p); }; +COMMAND(password, ARG_1STR); + +bool netmapstart() { senditemstoserver = true; return clienthost!=NULL; }; + +void initclientnet() +{ + ctext[0] = 0; + toservermap[0] = 0; + clientpassword[0] = 0; + newname("unnamed"); + ctf_team("red"); +}; + +void sendpackettoserv(void *packet) +{ + if(clienthost) { enet_host_broadcast(clienthost, 0, (ENetPacket *)packet); enet_host_flush(clienthost); } + else localclienttoserver((ENetPacket *)packet); +} + +void c2sinfo(dynent *d) // send update to the server +{ + if(clientnum<0) return; // we haven't had a welcome message from the server yet + if(lastmillis-lastupdate<40) return; // don't update faster than 25fps + ENetPacket *packet = enet_packet_create (NULL, MAXTRANS, 0); + uchar *start = packet->data; + uchar *p = start+2; + bool serveriteminitdone = false; + if(toservermap[0]) // suggest server to change map + { // do this exclusively as map change may invalidate rest of update + packet->flags = ENET_PACKET_FLAG_RELIABLE; + putint(p, SV_MAPCHANGE); + sendstring(toservermap, p); + toservermap[0] = 0; + putint(p, nextmode); + } + else + { + putint(p, SV_POS); + putint(p, clientnum); + putint(p, (int)(d->o.x*DMF)); // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes + putint(p, (int)(d->o.y*DMF)); + putint(p, (int)(d->o.z*DMF)); + putint(p, (int)(d->yaw*DAF)); + putint(p, (int)(d->pitch*DAF)); + putint(p, (int)(d->roll*DAF)); + putint(p, (int)(d->vel.x*DVF)); // quantize to 1/100, almost always 1 byte + putint(p, (int)(d->vel.y*DVF)); + putint(p, (int)(d->vel.z*DVF)); + // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 + putint(p, (d->strafe&3) | ((d->move&3)<<2) | (((int)d->onfloor)<<4) | ((editmode ? CS_EDITING : d->state)<<5) ); + + if(gun_changed) + { + putint(p, SV_WEAPCHANGE); + putint(p, player1->gunselect); + gun_changed = false; + } + if(senditemstoserver) + { + packet->flags = ENET_PACKET_FLAG_RELIABLE; + putint(p, SV_ITEMLIST); + if(!m_noitems) putitems(p); + putint(p, -1); + senditemstoserver = false; + serveriteminitdone = true; + }; + if(ctext[0]) // player chat, not flood protected for now + { + packet->flags = ENET_PACKET_FLAG_RELIABLE; + putint(p, SV_TEXT); + sendstring(ctext, p); + ctext[0] = 0; + }; + if(!c2sinit) // tell other clients who I am + { + packet->flags = ENET_PACKET_FLAG_RELIABLE; + c2sinit = true; + putint(p, SV_INITC2S); + sendstring(player1->name, p); + sendstring(player1->team, p); + putint(p, player1->skin); + putint(p, player1->lifesequence); + }; + loopv(messages) // send messages collected during the previous frames + { + ivector &msg = messages[i]; + if(msg[1]) packet->flags = ENET_PACKET_FLAG_RELIABLE; + loopi(msg[0]) putint(p, msg[i+2]); + }; + messages.setsize(0); + if(lastmillis-lastping>250) + { + putint(p, SV_PING); + putint(p, lastmillis); + lastping = lastmillis; + }; + }; + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + incomingdemodata(start, p-start, true); + if(clienthost) { enet_host_broadcast(clienthost, 0, packet); enet_host_flush(clienthost); } + else localclienttoserver(packet); + lastupdate = lastmillis; + if(serveriteminitdone) loadgamerest(); // hack +}; + +void gets2c() // get updates from the server +{ + ENetEvent event; + if(!clienthost) return; + if(connecting && lastmillis/3000 > connecting/3000) + { + conoutf("attempting to connect..."); + connecting = lastmillis; + ++connattempts; + if(connattempts > 3) + { + conoutf("could not connect to server"); + disconnect(); + return; + }; + }; + while(clienthost!=NULL && enet_host_service(clienthost, &event, 0)>0) + switch(event.type) + { + case ENET_EVENT_TYPE_CONNECT: + conoutf("connected to server"); + connecting = 0; + throttle(); + break; + + case ENET_EVENT_TYPE_RECEIVE: + if(disconnecting) conoutf("attempting to disconnect..."); + else localservertoclient(event.packet->data, event.packet->dataLength); + enet_packet_destroy(event.packet); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + if(disconnecting) disconnect(); + else server_err(); + return; + } +}; + +void lsinfo() +{ + printf("%i %i\n", player1->ammo[player1->gunselect], player1->mag[player1->gunselect]); +}; COMMAND(lsinfo, ARG_NONE); diff --git a/source/src/clientextras.cpp b/source/src/clientextras.cpp index 9a89ccc3..9f920aba 100644 --- a/source/src/clientextras.cpp +++ b/source/src/clientextras.cpp @@ -1,218 +1,218 @@ -// clientextras.cpp: stuff that didn't fit in client.cpp or clientgame.cpp :) - -#include "cube.h" - -// render players & monsters -// very messy ad-hoc handling of animation frames, should be made more configurable - -// D D D D' D D D D' A A' P P' I I' R, R' E L J J' -int frame[] = { 178, 184, 190, 137, 183, 189, 197, 164, 46, 51, 54, 32, 0, 0, 40, 1, 162, 162, 67, 168 }; -int range[] = { 6, 6, 8, 28, 1, 1, 1, 1, 8, 19, 4, 18, 40, 1, 6, 15, 1, 1, 1, 1 }; - -void renderclient(dynent *d, bool team, char *mdlname, bool vwep, float scale) -{ - int n = 3; - float speed = 100.0f; - float mz = d->o.z-d->eyeheight+1.55f*scale; - int basetime = -((int)d&0xFFF); - if(d->state==CS_DEAD) - { - d->pitch = 0.1f; - //if(vwep) mz = S((int)d->o.x, (int)d->o.y)->floor; - int r; - n = (int)d%3; r = range[n]; - basetime = d->lastaction; - int t = lastmillis-d->lastaction; - if(t<0 || t>20000) return; - if(t>(r-1)*100) { n += 4; if(t>(r+10)*100) { t -= (r+10)*100; mz -= t*t/10000000000.0f*t; }; }; - if(mz<-1000) return; - //mdl = (((int)d>>6)&1)+1; - //mz = d->o.z-d->eyeheight+0.2f; - //scale = 1.2f; - /*if(vwep) - { - rendermodel(mdlname, 0, 1, 0, 1.5f, d->o.x, mz, d->o.y, -90, 90, team, scale, speed, 0, basetime); - return; - };*/ - } - else if(d->state==CS_EDITING) { n = 16; } - else if(d->state==CS_LAGGED) { n = 17; } - else if(d->monsterstate==M_ATTACKING) { n = 8; } - else if(d->monsterstate==M_PAIN) { n = 10; } - else if((!d->move && !d->strafe) || !d->moving) { n = 12; } - else if(!d->onfloor && d->timeinair>100) { n = 18; } - else { n = 14; speed = 1200/d->maxspeed*scale; }; - rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d->o.x, mz, d->o.y, d->yaw+90, d->pitch/2, team, scale, speed, 0, basetime); -}; - -extern int democlientnum; - -void renderplayer(dynent *d) -{ - if(!d) return; - - int team = rb_team_int(d->team); - sprintf_sd(mdl)("playermodels/%s/0%i", team==TEAM_CLA ? "terrorist" : "counterterrorist", 1 + max(0, min(d->skin, (team==TEAM_CLA ? 3 : 5)))); - renderclient(d, isteam(player1->team, d->team), mdl, false, 1.6f); - - if(d->gunselect>=0 && d->gunselectgunselect]); - renderclient(d, isteam(player1->team, d->team), vwep, true, 1.6f); - }; -} - -void renderclients() -{ - dynent *d; - loopv(players) - if((d = players[i]) && (!demoplayback || i!=democlientnum)) - { - if(strcmp(d->name, "dummy") == 0) - { - d->gunselect = player1->gunselect; - d->moving = true; - d->health != 1 ? d->state=CS_ALIVE : d->state=CS_DEAD; - }; - renderplayer(d); - }; - - if(player1->state==CS_DEAD) renderplayer(player1); -}; - -void spawn_dummy() -{ - dynent *d = newdynent(); - players.add(d); - d->o = player1->o; - strcpy(d->team, player1->team); - d->gunselect = player1->gunselect; - d->monsterstate = M_NONE; - d->state = CS_ALIVE; - strcpy(d->name, "dummy"); -}; COMMAND(spawn_dummy, ARG_NONE); - -// creation of scoreboard pseudo-menu - -bool scoreson = false; - -void showscores(bool on) -{ - scoreson = on; -// menuset(((int)on)-1); - menuset(on ? (m_ctf ? 2 : 0) : -1); // EDIT: AH -}; - -struct sline { string s; }; -vector scorelines; -int menu = 0; - -void renderscore(dynent *d) -{ - sprintf_sd(lag)("%d", d->plag); - sprintf_sd(name) ("(%s)", d->name); - if(m_ctf) sprintf_s(scorelines.add().s)("%d\t%d\t%s\t%d\t%s\t%s", d->flagscore, d->frags, d->state==CS_LAGGED ? "LAG" : lag, d->ping, d->team, d->state==CS_DEAD ? name : d->name); - else sprintf_s(scorelines.add().s)("%d\t%s\t%d\t%s\t%s", d->frags, d->state==CS_LAGGED ? "LAG" : lag, d->ping, d->team, d->state==CS_DEAD ? name : d->name); - menumanual(menu, scorelines.length()-1, scorelines.last().s); -}; - -const int maxteams = 4; -char *teamname[maxteams]; -int teamscore[maxteams], teamflagscore[maxteams], teamsused; // EDIT: AH -string teamscores; -int timeremain = 0; - -void addteamscore(dynent *d) -{ - if(!d) return; - loopi(teamsused) if(strcmp(teamname[i], d->team)==0) - { - teamscore[i] += d->frags; - if(m_ctf) teamflagscore[i] += d->flagscore; - return; - }; - if(teamsused==maxteams) return; - teamname[teamsused] = d->team; - teamscore[teamsused] = d->frags; - if(m_ctf) teamflagscore[teamsused++] = d->flagscore; -}; - -void renderscores() -{ - if(!scoreson) return; - menu = m_ctf ? 2 : 0; - scorelines.setsize(0); - if(!demoplayback) renderscore(player1); - loopv(players) if(players[i]) renderscore(players[i]); - - // Added by Rick: Render Score for bots - BotManager.RenderBotScore(); - - sortmenu(menu, 0, scorelines.length()); - if(m_teammode) - { - teamsused = 0; - loopv(players) addteamscore(players[i]); - if(!demoplayback) addteamscore(player1); - teamscores[0] = 0; - loopj(teamsused) - { - string sc; - if(m_ctf) sprintf_s(sc)("[ %s: %d %d ]", teamname[j], teamflagscore[j], teamscore[j]); - else sprintf_s(sc)("[ %s: %d ]", teamname[j], teamscore[j]); - strcat_s(teamscores, sc); - }; - menumanual(menu, scorelines.length(), ""); - menumanual(menu, scorelines.length()+1, teamscores); - }; -}; - -// sendmap/getmap commands, should be replaced by more intuitive map downloading - -void sendmap(char *mapname) -{ - if(*mapname) save_world(mapname); - changemap(mapname); - mapname = getclientmap(); - int mapsize; - uchar *mapdata = readmap(mapname, &mapsize); - if(!mapdata) return; - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS + mapsize, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_SENDMAP); - sendstring(mapname, p); - putint(p, mapsize); - if(65535 - (p - start) < mapsize) - { - conoutf("map %s is too large to send", (int)mapname); - free(mapdata); - enet_packet_destroy(packet); - return; - }; - memcpy(p, mapdata, mapsize); - p += mapsize; - free(mapdata); - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - conoutf("sending map %s to server...", (int)mapname); - sprintf_sd(msg)("[map %s uploaded to server, \"getmap\" to receive it]", mapname); - toserver(msg); -} - -void getmap() -{ - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_RECVMAP); - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - conoutf("requesting map from server..."); -} - -COMMAND(sendmap, ARG_1STR); -COMMAND(getmap, ARG_NONE); - +// clientextras.cpp: stuff that didn't fit in client.cpp or clientgame.cpp :) + +#include "cube.h" + +// render players & monsters +// very messy ad-hoc handling of animation frames, should be made more configurable + +// D D D D' D D D D' A A' P P' I I' R, R' E L J J' +int frame[] = { 178, 184, 190, 137, 183, 189, 197, 164, 46, 51, 54, 32, 0, 0, 40, 1, 162, 162, 67, 168 }; +int range[] = { 6, 6, 8, 28, 1, 1, 1, 1, 8, 19, 4, 18, 40, 1, 6, 15, 1, 1, 1, 1 }; + +void renderclient(dynent *d, bool team, char *mdlname, bool vwep, float scale) +{ + int n = 3; + float speed = 100.0f; + float mz = d->o.z-d->eyeheight+1.55f*scale; + int basetime = -((int)d&0xFFF); + if(d->state==CS_DEAD) + { + d->pitch = 0.1f; + //if(vwep) mz = S((int)d->o.x, (int)d->o.y)->floor; + int r; + n = (int)d%3; r = range[n]; + basetime = d->lastaction; + int t = lastmillis-d->lastaction; + if(t<0 || t>20000) return; + if(t>(r-1)*100) { n += 4; if(t>(r+10)*100) { t -= (r+10)*100; mz -= t*t/10000000000.0f*t; }; }; + if(mz<-1000) return; + //mdl = (((int)d>>6)&1)+1; + //mz = d->o.z-d->eyeheight+0.2f; + //scale = 1.2f; + /*if(vwep) + { + rendermodel(mdlname, 0, 1, 0, 1.5f, d->o.x, mz, d->o.y, -90, 90, team, scale, speed, 0, basetime); + return; + };*/ + } + else if(d->state==CS_EDITING) { n = 16; } + else if(d->state==CS_LAGGED) { n = 17; } + else if(d->monsterstate==M_ATTACKING) { n = 8; } + else if(d->monsterstate==M_PAIN) { n = 10; } + else if((!d->move && !d->strafe) || !d->moving) { n = 12; } + else if(!d->onfloor && d->timeinair>100) { n = 18; } + else { n = 14; speed = 1200/d->maxspeed*scale; }; + rendermodel(mdlname, frame[n], range[n], 0, 1.5f, d->o.x, mz, d->o.y, d->yaw+90, d->pitch/2, team, scale, speed, 0, basetime); +}; + +extern int democlientnum; + +void renderplayer(dynent *d) +{ + if(!d) return; + + int team = rb_team_int(d->team); + sprintf_sd(mdl)("playermodels/%s/0%i", team==TEAM_CLA ? "terrorist" : "counterterrorist", 1 + max(0, min(d->skin, (team==TEAM_CLA ? 3 : 5)))); + renderclient(d, isteam(player1->team, d->team), mdl, false, 1.6f); + + if(d->gunselect>=0 && d->gunselectgunselect]); + renderclient(d, isteam(player1->team, d->team), vwep, true, 1.6f); + }; +} + +void renderclients() +{ + dynent *d; + loopv(players) + if((d = players[i]) && (!demoplayback || i!=democlientnum)) + { + if(strcmp(d->name, "dummy") == 0) + { + d->gunselect = player1->gunselect; + d->moving = true; + d->health != 1 ? d->state=CS_ALIVE : d->state=CS_DEAD; + }; + renderplayer(d); + }; + + if(player1->state==CS_DEAD) renderplayer(player1); +}; + +void spawn_dummy() +{ + dynent *d = newdynent(); + players.add(d); + d->o = player1->o; + strcpy(d->team, player1->team); + d->gunselect = player1->gunselect; + d->monsterstate = M_NONE; + d->state = CS_ALIVE; + strcpy(d->name, "dummy"); +}; COMMAND(spawn_dummy, ARG_NONE); + +// creation of scoreboard pseudo-menu + +bool scoreson = false; + +void showscores(bool on) +{ + scoreson = on; +// menuset(((int)on)-1); + menuset(on ? (m_ctf ? 2 : 0) : -1); // EDIT: AH +}; + +struct sline { string s; }; +vector scorelines; +int menu = 0; + +void renderscore(dynent *d) +{ + sprintf_sd(lag)("%d", d->plag); + sprintf_sd(name) ("(%s)", d->name); + if(m_ctf) sprintf_s(scorelines.add().s)("%d\t%d\t%s\t%d\t%s\t%s", d->flagscore, d->frags, d->state==CS_LAGGED ? "LAG" : lag, d->ping, d->team, d->state==CS_DEAD ? name : d->name); + else sprintf_s(scorelines.add().s)("%d\t%s\t%d\t%s\t%s", d->frags, d->state==CS_LAGGED ? "LAG" : lag, d->ping, d->team, d->state==CS_DEAD ? name : d->name); + menumanual(menu, scorelines.length()-1, scorelines.last().s); +}; + +const int maxteams = 4; +char *teamname[maxteams]; +int teamscore[maxteams], teamflagscore[maxteams], teamsused; // EDIT: AH +string teamscores; +int timeremain = 0; + +void addteamscore(dynent *d) +{ + if(!d) return; + loopi(teamsused) if(strcmp(teamname[i], d->team)==0) + { + teamscore[i] += d->frags; + if(m_ctf) teamflagscore[i] += d->flagscore; + return; + }; + if(teamsused==maxteams) return; + teamname[teamsused] = d->team; + teamscore[teamsused] = d->frags; + if(m_ctf) teamflagscore[teamsused++] = d->flagscore; +}; + +void renderscores() +{ + if(!scoreson) return; + menu = m_ctf ? 2 : 0; + scorelines.setsize(0); + if(!demoplayback) renderscore(player1); + loopv(players) if(players[i]) renderscore(players[i]); + + // Added by Rick: Render Score for bots + BotManager.RenderBotScore(); + + sortmenu(menu, 0, scorelines.length()); + if(m_teammode) + { + teamsused = 0; + loopv(players) addteamscore(players[i]); + if(!demoplayback) addteamscore(player1); + teamscores[0] = 0; + loopj(teamsused) + { + string sc; + if(m_ctf) sprintf_s(sc)("[ %s: %d %d ]", teamname[j], teamflagscore[j], teamscore[j]); + else sprintf_s(sc)("[ %s: %d ]", teamname[j], teamscore[j]); + strcat_s(teamscores, sc); + }; + menumanual(menu, scorelines.length(), ""); + menumanual(menu, scorelines.length()+1, teamscores); + }; +}; + +// sendmap/getmap commands, should be replaced by more intuitive map downloading + +void sendmap(char *mapname) +{ + if(*mapname) save_world(mapname); + changemap(mapname); + mapname = getclientmap(); + int mapsize; + uchar *mapdata = readmap(mapname, &mapsize); + if(!mapdata) return; + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS + mapsize, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_SENDMAP); + sendstring(mapname, p); + putint(p, mapsize); + if(65535 - (p - start) < mapsize) + { + conoutf("map %s is too large to send", (int)mapname); + free(mapdata); + enet_packet_destroy(packet); + return; + }; + memcpy(p, mapdata, mapsize); + p += mapsize; + free(mapdata); + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + conoutf("sending map %s to server...", (int)mapname); + sprintf_sd(msg)("[map %s uploaded to server, \"getmap\" to receive it]", mapname); + toserver(msg); +} + +void getmap() +{ + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_RECVMAP); + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + conoutf("requesting map from server..."); +} + +COMMAND(sendmap, ARG_1STR); +COMMAND(getmap, ARG_NONE); + diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 08dfdbd3..36f7d3ea 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -1,679 +1,680 @@ -// clientgame.cpp: core game related stuff - -#include "cube.h" -#define sleep my_sleep -int nextmode = 0; // nextmode becomes gamemode after next map load -VAR(gamemode, 1, 0, 0); - -flaginfo flaginfos[2]; - -void mode(int n) { addmsg(1, 2, SV_GAMEMODE, nextmode = n); }; -COMMAND(mode, ARG_1INT); - -bool intermission = false; - -dynent *player1 = newdynent(); // our client -dvector players; // other clients - -vector physents; - -VAR(sensitivity, 0, 10, 1000); -VAR(sensitivityscale, 1, 1, 100); -VAR(invmouse, 0, 0, 1); - -int lastmillis = 0; -int curtime; -string clientmap; - -extern int framesinmap; - -char *getclientmap() { return clientmap; }; - -void resetmovement(dynent *d) -{ - d->k_left = false; - d->k_right = false; - d->k_up = false; - d->k_down = false; - d->jumpnext = false; - d->strafe = 0; - d->move = 0; -}; - -extern bool c2sinit; - -void spawnstate(dynent *d) // reset player state not persistent accross spawns -{ - resetmovement(d); - d->vel.x = d->vel.y = d->vel.z = 0; - d->onfloor = false; - d->timeinair = 0; - d->health = 100; - d->armour = 0; - //d->hasarmour = false; - //d->armourtype = A_BLUE; - d->akimbomillis = 0; - d->gunselect = GUN_PISTOL; - d->gunwait = 0; - d->attacking = false; - d->lastaction = 0; - if(d==player1) - { - gun_changed = true; - player1->primary = player1->nextprimary; - if(player1->skin!=player1->nextskin) - { - c2sinit=false; - player1->skin=player1->nextskin; - } - scoped = false; - }; - radd(d); - d->akimbo = false; -}; - -dynent *newdynent() // create a new blank player or monster -{ - dynent *d = (dynent *)gp()->alloc(sizeof(dynent)); - d->o.x = 0; - d->o.y = 0; - d->o.z = 0; - d->yaw = 270; - d->pitch = 0; - d->roll = 0; - d->maxspeed = 16; //16 max speed, 14 max with armour - d->outsidemap = false; - d->inwater = false; - d->radius = 1.1f; - d->eyeheight = 4.25f; - d->aboveeye = 0.7f; - d->frags = 0; - d->flagscore = 0; // EDIT: AH - d->plag = 0; - d->ping = 0; - d->lastupdate = lastmillis; - d->enemy = NULL; - d->monsterstate = 0; - d->name[0] = d->team[0] = 0; - d->blocked = false; - d->lifesequence = 0; - d->state = CS_ALIVE; - d->shots = 0; - d->reloading = false; - d->primary = d->nextprimary = GUN_ASSULT; - d->hasarmour = false; - d->gunselect = GUN_PISTOL; - d->onladder = false; - d->isphysent = false; - d->skin = d->nextskin = 0; - spawnstate(d); - return d; -}; - -void ctf_death() // EDIT: AH -{ - int flag = rb_opposite(rb_team_int(player1->team)); - flaginfo &f = flaginfos[flag]; - if(f.state==CTFF_STOLEN && f.thief==player1) - { - addmsg(1, 2, SV_FLAGDROP, flag); - f.flag->spawned = false; - f.state = CTFF_DROPPED; - }; -}; - -void respawnself() -{ - spawnplayer(player1); - showscores(false); -}; - -void respawn() -{ - if(player1->state==CS_DEAD) - { - player1->attacking = false; - if(m_arena) { conoutf("waiting for new round to start..."); return; }; - respawnself(); - }; -}; - -void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, char *&lastname, bool &oneteam) -{ - if(d->state!=CS_DEAD) - { - alive++; - if(lastteam && strcmp(lastteam, d->team)) oneteam = false; - lastteam = d->team; - lastname = d->name; - } - else - { - dead++; - }; -}; - -int arenarespawnwait = 0; -int arenadetectwait = 0; - -void arenarespawn() -{ - if (!m_arena) return; - - if(arenarespawnwait) - { - if(arenarespawnwait0 && (alive<=1 || (m_teammode && oneteam))) - { - conoutf("arena round is over! next round in 5 seconds..."); - if(alive) - { - if(m_teammode) - conoutf("team %s has won the round", (int)lastteam); - else - conoutf("%s is the survior!", (int)lastname); - } - else conoutf("everyone died!"); - arenarespawnwait = lastmillis+5000; - arenadetectwait = lastmillis+10000; - player1->roll = 0; - }; - }; -}; - - -void checkakimbo(int time) -{ - if(player1->gunselect==GUN_PISTOL && player1->akimbo && (player1->akimbomillis -= time)<0) - { - player1->akimbomillis = 0; - player1->akimbo = false; - playsoundc(S_PUPOUT); - //conoutf("quad damage is over"); - }; -}; - -void zapdynent(dynent *&d) -{ - if(d) gp()->dealloc(d, sizeof(dynent)); - d = NULL; -}; - -extern int democlientnum; - -void otherplayers() -{ - loopv(players) if(players[i]) - { - const int lagtime = lastmillis-players[i]->lastupdate; - if(lagtime>1000 && players[i]->state==CS_ALIVE) - { - players[i]->state = CS_LAGGED; - continue; - }; - if(lagtime && players[i]->state != CS_DEAD && (!demoplayback || i!=democlientnum)) moveplayer(players[i], 2, false); // use physics to extrapolate player position - }; - // Added by Rick - if (!ishost()) - { - loopv(bots) - { - if(bots[i] && bots[i]->state != CS_DEAD && (!demoplayback)) - moveplayer(bots[i], 2, false); // use physics to extrapolate bot position - } - } - // End add -}; - -extern void explode_nade(physent *i); - -void mphysents() -{ - loopv(physents) if(physents[i]) - { - physent *p = physents[i]; - if(p->state == NADE_THROWED) moveplayer(p, 2, false); - - if(lastmillis - p->millis >= p->timetolife) - { - if(p->state==NADE_ACTIVATED || p->state==NADE_THROWED) explode_nade(physents[i]); - physents.remove(i); - i--; - }; - }; -}; - -int sleepwait = 0; -string sleepcmd; -void sleep(char *msec, char *cmd) { sleepwait = atoi(msec)+lastmillis; strcpy_s(sleepcmd, cmd); }; -COMMAND(sleep, ARG_2STR); - -void updateworld(int millis) // main game update loop -{ - if(lastmillis) - { - curtime = millis - lastmillis; - if(sleepwait && lastmillis>sleepwait) { execute(sleepcmd); sleepwait = 0; }; - physicsframe(); - checkakimbo(curtime); - //if(m_arena) arenarespawn(); - arenarespawn(); - moveprojectiles((float)curtime); - demoplaybackstep(); - if(!demoplayback) - { - if(getclientnum()>=0) shoot(player1, worldpos); // only shoot when connected to server - gets2c(); // do this first, so we have most accurate information when our player moves - }; - mphysents(); - otherplayers(); - if(!demoplayback) - { - //monsterthink(); - - // Added by Rick: let bots think - BotManager.Think(); - - //put game mode extra call here - if(player1->state==CS_DEAD) - { - if(lastmillis-player1->lastaction<2000) - { - player1->move = player1->strafe = 0; - moveplayer(player1, 10, false); - }; - } - else if(!intermission) - { - moveplayer(player1, 20, true); - checkitems(); - }; - c2sinfo(player1); // do this last, to reduce the effective frame lag - }; - }; - lastmillis = millis; -}; - -void entinmap(dynent *d) // brute force but effective way to find a free spawn spot in the map -{ - loopi(100) // try max 100 times - { - float dx = (rnd(21)-10)/10.0f*i; // increasing distance - float dy = (rnd(21)-10)/10.0f*i; - d->o.x += dx; - d->o.y += dy; - if(collide(d, true, 0, 0)) return; - d->o.x -= dx; - d->o.y -= dy; - }; - conoutf("can't find entity spawn spot! (%d, %d)", (int)d->o.x, (int)d->o.y); - // leave ent at original pos, possibly stuck -}; - -// EDIT: AH -int securespawndist = 15; - -// Returns -1 for a free place, if not it returns the vdist to the nearest enemy -int nearestenemy(vec *v, string team) -{ - float nearestPlayerDistSquared = -1; - loopv(players) - { - dynent *other = players[i]; - if(!other) continue; - if(isteam(team,other->team))continue; // its a teammate - vec place = {v->x, v->y, v->z}; - vdistsquared(distsquared, t, place, other->o); - if(nearestPlayerDistSquared == -1) nearestPlayerDistSquared = distsquared; // first run - else if(distsquared < nearestPlayerDistSquared) nearestPlayerDistSquared = distsquared; // if a player is closer - }; - if(nearestPlayerDistSquared >= securespawndist * securespawndist || nearestPlayerDistSquared == -1) return -1; // a distance more than securespawndist means the place is free - else return sqrt(nearestPlayerDistSquared); -}; - -int spawncycle = -1; -int fixspawn = 2; - -void spawnplayer(dynent *d, bool secure) // place at random spawn -{ - loopj(10) // EDIT: AH - { - int r = fixspawn-->0 ? 4 : rnd(10)+1; - loopi(r) spawncycle = findplayerstart(m_teammode ? rb_team_int(d->team) : 100, spawncycle+1); - if(spawncycle!=-1 && secure) - { - entity &e = ents[spawncycle]; - vec pos = { e.x, e.y, e.z }; - if(nearestenemy(&pos, d->team) == -1) break; - } else break; - }; - if(spawncycle!=-1) - { - d->o.x = ents[spawncycle].x; - d->o.y = ents[spawncycle].y; - d->o.z = ents[spawncycle].z; - d->yaw = ents[spawncycle].attr1; - d->pitch = 0; - d->roll = 0; - } - else - { - d->o.x = d->o.y = (float)ssize/2; - d->o.z = 4; - }; - entinmap(d); - spawnstate(d); - d->state = CS_ALIVE; -}; - -// movement input code - -#define dir(name,v,d,s,os) void name(bool isdown) { player1->s = isdown; player1->v = isdown ? d : (player1->os ? -(d) : 0); player1->lastmove = lastmillis; }; - -dir(backward, move, -1, k_down, k_up); -dir(forward, move, 1, k_up, k_down); -dir(left, strafe, 1, k_left, k_right); -dir(right, strafe, -1, k_right, k_left); - -void attack(bool on) -{ - if(intermission) return; - if(editmode) editdrag(on); - else if(player1->attacking = on) respawn(); -}; - -void jumpn(bool on) { if(!intermission && (player1->jumpnext = on) ) respawn(); }; - -COMMAND(backward, ARG_DOWN); -COMMAND(forward, ARG_DOWN); -COMMAND(left, ARG_DOWN); -COMMAND(right, ARG_DOWN); -COMMANDN(jump, jumpn, ARG_DOWN); -COMMAND(attack, ARG_DOWN); -COMMAND(showscores, ARG_DOWN); - -void fixplayer1range() -{ - const float MAXPITCH = 90.0f; - if(player1->pitch>MAXPITCH) player1->pitch = MAXPITCH; - if(player1->pitch<-MAXPITCH) player1->pitch = -MAXPITCH; - while(player1->yaw<0.0f) player1->yaw += 360.0f; - while(player1->yaw>=360.0f) player1->yaw -= 360.0f; -}; - -void mousemove(int dx, int dy) -{ - if(player1->state==CS_DEAD || intermission) return; - const float SENSF = 33.0f; // try match quake sens - player1->yaw += (dx/SENSF)*(sensitivity/(float)sensitivityscale); - player1->pitch -= (dy/SENSF)*(sensitivity/(float)sensitivityscale)*(invmouse ? -1 : 1); - fixplayer1range(); -}; - -// damage arriving from the network, monsters, yourself, all ends up here. - -void selfdamage(int damage, int actor, dynent *act) -{ printf("selfdmg\n"); - if(player1->state!=CS_ALIVE || editmode || intermission) return; - damageblend(damage); - demoblend(damage); - int ad = damage*30/100;//int ad = damage*(player1->armourtype+1)*20/100; // let armour absorb when possible - if(ad>player1->armour) ad = player1->armour; - player1->armour -= ad; - damage -= ad; - float droll = damage/0.5f; - player1->roll += player1->roll>0 ? droll : (player1->roll<0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending on amount of damage - if((player1->health -= damage)<=0) - { - if(actor==-2) - { - conoutf("you got killed by %s!", (int)&act->name); - } - else if(actor==-1) - { - actor = getclientnum(); - conoutf("you suicided!"); - addmsg(1, 2, SV_FRAGS, --player1->frags); - } - else - { - // Modified by Rick - //dynent *a = getclient(actor); - dynent *a; - if (act->bIsBot) a = act; - else a = getclient(actor); - // End mod - - if(a) - { - if(isteam(a->team, player1->team)) - { - conoutf("you got fragged by a teammate (%s)", (int)a->name); - } - else - { - conoutf("you got fragged by %s", (int)a->name); - }; - }; - }; - // EDIT: AH - if(m_ctf) ctf_death(); - showscores(true); - if(act->bIsBot) addmsg(1, 2, SV_DIEDBYBOT, actor); - else addmsg(1, 2, SV_DIED, actor); - player1->lifesequence++; - player1->attacking = false; - player1->state = CS_DEAD; - player1->oldpitch = player1->pitch; - player1->pitch = 0; - player1->roll = 60; - playsound(S_DIE1+rnd(2)); - spawnstate(player1); - player1->lastaction = lastmillis; - } - else - { - playsound(S_PAIN6); - }; -}; - -void timeupdate(int timeremain) -{ - if(!timeremain) - { - intermission = true; - player1->attacking = false; - conoutf("intermission:"); - conoutf("game has ended!"); - showscores(true); - } - else - { - conoutf("time remaining: %d minutes", timeremain); - }; -}; - -dynent *getclient(int cn) // ensure valid entity -{ - if(cn<0 || cn>=MAXCLIENTS) - { - neterr("clientnum"); - return NULL; - }; - while(cn>=players.length()) players.add(NULL); - return players[cn] ? players[cn] : (players[cn] = newdynent()); -}; - -// Added by Rick -dynent *getbot(int cn) // ensure valid entity -{ - if(cn<0 || cn>=MAXCLIENTS) - { - neterr("botnum"); - return NULL; - }; - - while(cn>=bots.length()) bots.add(NULL); - if (!bots[cn]) - { - bots[cn] = newdynent(); - if (bots[cn]) - { - bots[cn]->pBot = NULL; - bots[cn]->bIsBot = true; - } - } - - return bots[cn]; -}; -// End add by Rick - -void initclient() -{ - clientmap[0] = 0; - initclientnet(); -}; - -entity flagdummies[2]; // in case the map does not provide flags - -void preparectf(bool cleanonly=false) -{ - loopi(2) flaginfos[i].flag = &flagdummies[i]; - if(!cleanonly) - { - loopv(ents) - { - entity &e = ents[i]; - if(e.type==CTF_FLAG) - { - e.spawned = true; - if(e.attr2>2) { conoutf("invalid ctf-flag entity (%i)", i); e.attr2 = 0; }; - flaginfo &f = flaginfos[e.attr2]; - f.flag = &e; - f.state = CTFF_INBASE; - f.originalpos.x = (float) e.x; - f.originalpos.y = (float) e.y; - f.originalpos.z = (float) e.z; - }; - }; - newteam(player1->team); // ensure valid team - }; -}; - -void startmap(char *name) // called just after a map load -{ - //if(netmapstart()) { gamemode = 0;}; //needs fixed to switch modes? - netmapstart(); //should work - sleepwait = 0; - //monsterclear(); - // Added by Rick - BotManager.BeginMap(name); - // End add by Rick - projreset(); - if(m_ctf) preparectf(); - shotlinereset(); - spawncycle = -1; - spawnplayer(player1, true); - player1->frags = 0; - player1->flagscore = 0; - loopv(players) if(players[i]) players[i]->frags = 0; - resetspawns(); - strcpy_s(clientmap, name); - if(editmode) toggleedit(); - setvar("gamespeed", 100); - setvar("fog", 180); - setvar("fogcolour", 0x8099B3); - showscores(false); - intermission = false; - framesinmap = 0; - conoutf("game mode is %s", (int)modestr(gamemode)); -}; - -COMMANDN(map, changemap, ARG_1STR); - -void suicide() -{ - if (multiplayer()) conoutf("there are others willing to kill you..."); - else conoutf("this is not the answer"); - selfdamage(1000, -1, player1); - //if(d==player1) selfdamage(dm, -1, d); - //else { addmsg(1, 4, SV_DAMAGE, d, dm, d->lifesequence); playsound(S_FALL1+rnd(5), &d->o); }; - //particle_splash(3, 1000, 1000, player1->o); //edit out? - demodamage(1000, player1->o); - playsound(S_SUICIDE, &player1->o); -}; - -COMMAND(suicide, ARG_NONE); - -void killdummies() -{ - loopv(players) if(strcmp(players[i]->name, "dummy") == 0) { players[i]->lastaction = lastmillis; players[i]->health=1; }; -}; COMMAND(killdummies, ARG_NONE); - -// EDIT: AH -void flagaction(int flag, int action) -{ - flaginfo &f = flaginfos[flag]; - if(!f.thief) return; - bool ownflag = flag == rb_team_int(player1->team); - switch(action) - { - case SV_FLAGPICKUP: - { - playsound(S_FLAGPICKUP); - if(f.thief==player1) - { - conoutf("you got the enemy flag"); - f.pick_ack = true; - } - else conoutf("%s got %s flag", (int) f.thief->name, (int)(ownflag ? "your": "the enemy")); - break; - }; - case SV_FLAGDROP: - { - playsound(S_FLAGDROP); - if(f.thief==player1) conoutf("you lost the flag"); - else conoutf("%s lost %s flag", (int) f.thief->name, (int)(ownflag ? "your" : "the enemy")); - break; - }; - case SV_FLAGRETURN: - { - playsound(S_FLAGRETURN); - if(f.thief==player1) conoutf("you returned your flag"); - else conoutf("%s returned %s flag", (int) f.thief->name, (int)(ownflag ? "your" : "the enemy")); - break; - }; - case SV_FLAGSCORE: - { - playsound(S_FLAGSCORE); - if(f.thief==player1) - { - conoutf("you scored"); - addmsg(1, 2, SV_FLAGS, ++player1->flagscore); - } - else conoutf("%s scored for %s team", (int) f.thief->name, (int)(ownflag ? "the enemy" : "your")); - break; - }; - default: break; - }; -}; +// clientgame.cpp: core game related stuff + +#include "cube.h" +#define sleep my_sleep +int nextmode = 0; // nextmode becomes gamemode after next map load +VAR(gamemode, 1, 0, 0); + +flaginfo flaginfos[2]; + +void mode(int n) { addmsg(1, 2, SV_GAMEMODE, nextmode = n); }; +COMMAND(mode, ARG_1INT); + +bool intermission = false; + +dynent *player1 = newdynent(); // our client +dvector players; // other clients + +vector physents; + +VAR(sensitivity, 0, 10, 1000); +VAR(sensitivityscale, 1, 1, 100); +VAR(invmouse, 0, 0, 1); + +int lastmillis = 0; +int curtime; +string clientmap; + +extern int framesinmap; + +char *getclientmap() { return clientmap; }; + +void resetmovement(dynent *d) +{ + d->k_left = false; + d->k_right = false; + d->k_up = false; + d->k_down = false; + d->jumpnext = false; + d->strafe = 0; + d->move = 0; +}; + +extern bool c2sinit; + +void spawnstate(dynent *d) // reset player state not persistent accross spawns +{ + resetmovement(d); + d->vel.x = d->vel.y = d->vel.z = 0; + d->onfloor = false; + d->timeinair = 0; + d->health = 100; + d->armour = 0; + //d->hasarmour = false; + //d->armourtype = A_BLUE; + d->akimbomillis = 0; + d->gunselect = GUN_PISTOL; + d->gunwait = 0; + d->attacking = false; + d->lastaction = 0; + if(d==player1) + { + gun_changed = true; + player1->primary = player1->nextprimary; + if(player1->skin!=player1->nextskin) + { + c2sinit=false; + player1->skin=player1->nextskin; + } + scoped = false; + }; + radd(d); + d->akimbo = false; +}; + +dynent *newdynent() // create a new blank player or monster +{ + dynent *d = (dynent *)gp()->alloc(sizeof(dynent)); + d->o.x = 0; + d->o.y = 0; + d->o.z = 0; + d->yaw = 270; + d->pitch = 0; + d->roll = 0; + d->maxspeed = 16; //16 max speed, 14 max with armour + d->outsidemap = false; + d->inwater = false; + d->radius = 1.1f; + d->eyeheight = 4.25f; + d->aboveeye = 0.7f; + d->frags = 0; + d->flagscore = 0; // EDIT: AH + d->plag = 0; + d->ping = 0; + d->lastupdate = lastmillis; + d->enemy = NULL; + d->monsterstate = 0; + d->name[0] = d->team[0] = 0; + d->blocked = false; + d->lifesequence = 0; + d->state = CS_ALIVE; + d->shots = 0; + d->reloading = false; + d->primary = d->nextprimary = GUN_ASSULT; + d->hasarmour = false; + d->gunselect = GUN_PISTOL; + d->onladder = false; + d->isphysent = false; + d->skin = d->nextskin = 0; + d->bIsBot = false; + spawnstate(d); + return d; +}; + +void ctf_death() // EDIT: AH +{ + int flag = rb_opposite(rb_team_int(player1->team)); + flaginfo &f = flaginfos[flag]; + if(f.state==CTFF_STOLEN && f.thief==player1) + { + addmsg(1, 2, SV_FLAGDROP, flag); + f.flag->spawned = false; + f.state = CTFF_DROPPED; + }; +}; + +void respawnself() +{ + spawnplayer(player1); + showscores(false); +}; + +void respawn() +{ + if(player1->state==CS_DEAD) + { + player1->attacking = false; + if(m_arena) { conoutf("waiting for new round to start..."); return; }; + respawnself(); + }; +}; + +void arenacount(dynent *d, int &alive, int &dead, char *&lastteam, char *&lastname, bool &oneteam) +{ + if(d->state!=CS_DEAD) + { + alive++; + if(lastteam && strcmp(lastteam, d->team)) oneteam = false; + lastteam = d->team; + lastname = d->name; + } + else + { + dead++; + }; +}; + +int arenarespawnwait = 0; +int arenadetectwait = 0; + +void arenarespawn() +{ + if (!m_arena) return; + + if(arenarespawnwait) + { + if(arenarespawnwait0 && (alive<=1 || (m_teammode && oneteam))) + { + conoutf("arena round is over! next round in 5 seconds..."); + if(alive) + { + if(m_teammode) + conoutf("team %s has won the round", (int)lastteam); + else + conoutf("%s is the survior!", (int)lastname); + } + else conoutf("everyone died!"); + arenarespawnwait = lastmillis+5000; + arenadetectwait = lastmillis+10000; + player1->roll = 0; + }; + }; +}; + + +void checkakimbo(int time) +{ + if(player1->gunselect==GUN_PISTOL && player1->akimbo && (player1->akimbomillis -= time)<0) + { + player1->akimbomillis = 0; + player1->akimbo = false; + playsoundc(S_PUPOUT); + //conoutf("quad damage is over"); + }; +}; + +void zapdynent(dynent *&d) +{ + if(d) gp()->dealloc(d, sizeof(dynent)); + d = NULL; +}; + +extern int democlientnum; + +void otherplayers() +{ + loopv(players) if(players[i]) + { + const int lagtime = lastmillis-players[i]->lastupdate; + if(lagtime>1000 && players[i]->state==CS_ALIVE) + { + players[i]->state = CS_LAGGED; + continue; + }; + if(lagtime && players[i]->state != CS_DEAD && (!demoplayback || i!=democlientnum)) moveplayer(players[i], 2, false); // use physics to extrapolate player position + }; + // Added by Rick + if (!ishost()) + { + loopv(bots) + { + if(bots[i] && bots[i]->state != CS_DEAD && (!demoplayback)) + moveplayer(bots[i], 2, false); // use physics to extrapolate bot position + } + } + // End add +}; + +extern void explode_nade(physent *i); + +void mphysents() +{ + loopv(physents) if(physents[i]) + { + physent *p = physents[i]; + if(p->state == NADE_THROWED) moveplayer(p, 2, false); + + if(lastmillis - p->millis >= p->timetolife) + { + if(p->state==NADE_ACTIVATED || p->state==NADE_THROWED) explode_nade(physents[i]); + physents.remove(i); + i--; + }; + }; +}; + +int sleepwait = 0; +string sleepcmd; +void sleep(char *msec, char *cmd) { sleepwait = atoi(msec)+lastmillis; strcpy_s(sleepcmd, cmd); }; +COMMAND(sleep, ARG_2STR); + +void updateworld(int millis) // main game update loop +{ + if(lastmillis) + { + curtime = millis - lastmillis; + if(sleepwait && lastmillis>sleepwait) { execute(sleepcmd); sleepwait = 0; }; + physicsframe(); + checkakimbo(curtime); + //if(m_arena) arenarespawn(); + arenarespawn(); + moveprojectiles((float)curtime); + demoplaybackstep(); + if(!demoplayback) + { + if(getclientnum()>=0) shoot(player1, worldpos); // only shoot when connected to server + gets2c(); // do this first, so we have most accurate information when our player moves + }; + mphysents(); + otherplayers(); + if(!demoplayback) + { + //monsterthink(); + + // Added by Rick: let bots think + BotManager.Think(); + + //put game mode extra call here + if(player1->state==CS_DEAD) + { + if(lastmillis-player1->lastaction<2000) + { + player1->move = player1->strafe = 0; + moveplayer(player1, 10, false); + }; + } + else if(!intermission) + { + moveplayer(player1, 20, true); + checkitems(); + }; + c2sinfo(player1); // do this last, to reduce the effective frame lag + }; + }; + lastmillis = millis; +}; + +void entinmap(dynent *d) // brute force but effective way to find a free spawn spot in the map +{ + loopi(100) // try max 100 times + { + float dx = (rnd(21)-10)/10.0f*i; // increasing distance + float dy = (rnd(21)-10)/10.0f*i; + d->o.x += dx; + d->o.y += dy; + if(collide(d, true, 0, 0)) return; + d->o.x -= dx; + d->o.y -= dy; + }; + conoutf("can't find entity spawn spot! (%d, %d)", (int)d->o.x, (int)d->o.y); + // leave ent at original pos, possibly stuck +}; + +// EDIT: AH +int securespawndist = 15; + +// Returns -1 for a free place, if not it returns the vdist to the nearest enemy +int nearestenemy(vec *v, string team) +{ + float nearestPlayerDistSquared = -1; + loopv(players) + { + dynent *other = players[i]; + if(!other) continue; + if(isteam(team,other->team))continue; // its a teammate + vec place = {v->x, v->y, v->z}; + vdistsquared(distsquared, t, place, other->o); + if(nearestPlayerDistSquared == -1) nearestPlayerDistSquared = distsquared; // first run + else if(distsquared < nearestPlayerDistSquared) nearestPlayerDistSquared = distsquared; // if a player is closer + }; + if(nearestPlayerDistSquared >= securespawndist * securespawndist || nearestPlayerDistSquared == -1) return -1; // a distance more than securespawndist means the place is free + else return sqrt(nearestPlayerDistSquared); +}; + +int spawncycle = -1; +int fixspawn = 2; + +void spawnplayer(dynent *d, bool secure) // place at random spawn +{ + loopj(10) // EDIT: AH + { + int r = fixspawn-->0 ? 4 : rnd(10)+1; + loopi(r) spawncycle = findplayerstart(m_teammode ? rb_team_int(d->team) : 100, spawncycle+1); + if(spawncycle!=-1 && secure) + { + entity &e = ents[spawncycle]; + vec pos = { e.x, e.y, e.z }; + if(nearestenemy(&pos, d->team) == -1) break; + } else break; + }; + if(spawncycle!=-1) + { + d->o.x = ents[spawncycle].x; + d->o.y = ents[spawncycle].y; + d->o.z = ents[spawncycle].z; + d->yaw = ents[spawncycle].attr1; + d->pitch = 0; + d->roll = 0; + } + else + { + d->o.x = d->o.y = (float)ssize/2; + d->o.z = 4; + }; + entinmap(d); + spawnstate(d); + d->state = CS_ALIVE; +}; + +// movement input code + +#define dir(name,v,d,s,os) void name(bool isdown) { player1->s = isdown; player1->v = isdown ? d : (player1->os ? -(d) : 0); player1->lastmove = lastmillis; }; + +dir(backward, move, -1, k_down, k_up); +dir(forward, move, 1, k_up, k_down); +dir(left, strafe, 1, k_left, k_right); +dir(right, strafe, -1, k_right, k_left); + +void attack(bool on) +{ + if(intermission) return; + if(editmode) editdrag(on); + else if(player1->attacking = on) respawn(); +}; + +void jumpn(bool on) { if(!intermission && (player1->jumpnext = on) ) respawn(); }; + +COMMAND(backward, ARG_DOWN); +COMMAND(forward, ARG_DOWN); +COMMAND(left, ARG_DOWN); +COMMAND(right, ARG_DOWN); +COMMANDN(jump, jumpn, ARG_DOWN); +COMMAND(attack, ARG_DOWN); +COMMAND(showscores, ARG_DOWN); + +void fixplayer1range() +{ + const float MAXPITCH = 90.0f; + if(player1->pitch>MAXPITCH) player1->pitch = MAXPITCH; + if(player1->pitch<-MAXPITCH) player1->pitch = -MAXPITCH; + while(player1->yaw<0.0f) player1->yaw += 360.0f; + while(player1->yaw>=360.0f) player1->yaw -= 360.0f; +}; + +void mousemove(int dx, int dy) +{ + if(player1->state==CS_DEAD || intermission) return; + const float SENSF = 33.0f; // try match quake sens + player1->yaw += (dx/SENSF)*(sensitivity/(float)sensitivityscale); + player1->pitch -= (dy/SENSF)*(sensitivity/(float)sensitivityscale)*(invmouse ? -1 : 1); + fixplayer1range(); +}; + +// damage arriving from the network, monsters, yourself, all ends up here. + +void selfdamage(int damage, int actor, dynent *act) +{ printf("selfdmg\n"); + if(player1->state!=CS_ALIVE || editmode || intermission) return; + damageblend(damage); + demoblend(damage); + int ad = damage*30/100;//int ad = damage*(player1->armourtype+1)*20/100; // let armour absorb when possible + if(ad>player1->armour) ad = player1->armour; + player1->armour -= ad; + damage -= ad; + float droll = damage/0.5f; + player1->roll += player1->roll>0 ? droll : (player1->roll<0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending on amount of damage + if((player1->health -= damage)<=0) + { + if(actor==-2) + { + conoutf("you got killed by %s!", (int)&act->name); + } + else if(actor==-1) + { + actor = getclientnum(); + conoutf("you suicided!"); + addmsg(1, 2, SV_FRAGS, --player1->frags); + } + else + { + // Modified by Rick + //dynent *a = getclient(actor); + dynent *a; + if (act->bIsBot) a = act; + else a = getclient(actor); + // End mod + + if(a) + { + if(isteam(a->team, player1->team)) + { + conoutf("you got fragged by a teammate (%s)", (int)a->name); + } + else + { + conoutf("you got fragged by %s", (int)a->name); + }; + }; + }; + // EDIT: AH + if(m_ctf) ctf_death(); + showscores(true); + if(act->bIsBot) addmsg(1, 2, SV_DIEDBYBOT, actor); + else addmsg(1, 2, SV_DIED, actor); + player1->lifesequence++; + player1->attacking = false; + player1->state = CS_DEAD; + player1->oldpitch = player1->pitch; + player1->pitch = 0; + player1->roll = 60; + playsound(S_DIE1+rnd(2)); + spawnstate(player1); + player1->lastaction = lastmillis; + } + else + { + playsound(S_PAIN6); + }; +}; + +void timeupdate(int timeremain) +{ + if(!timeremain) + { + intermission = true; + player1->attacking = false; + conoutf("intermission:"); + conoutf("game has ended!"); + showscores(true); + } + else + { + conoutf("time remaining: %d minutes", timeremain); + }; +}; + +dynent *getclient(int cn) // ensure valid entity +{ + if(cn<0 || cn>=MAXCLIENTS) + { + neterr("clientnum"); + return NULL; + }; + while(cn>=players.length()) players.add(NULL); + return players[cn] ? players[cn] : (players[cn] = newdynent()); +}; + +// Added by Rick +dynent *getbot(int cn) // ensure valid entity +{ + if(cn<0 || cn>=MAXCLIENTS) + { + neterr("botnum"); + return NULL; + }; + + while(cn>=bots.length()) bots.add(NULL); + if (!bots[cn]) + { + bots[cn] = newdynent(); + if (bots[cn]) + { + bots[cn]->pBot = NULL; + bots[cn]->bIsBot = true; + } + } + + return bots[cn]; +}; +// End add by Rick + +void initclient() +{ + clientmap[0] = 0; + initclientnet(); +}; + +entity flagdummies[2]; // in case the map does not provide flags + +void preparectf(bool cleanonly=false) +{ + loopi(2) flaginfos[i].flag = &flagdummies[i]; + if(!cleanonly) + { + loopv(ents) + { + entity &e = ents[i]; + if(e.type==CTF_FLAG) + { + e.spawned = true; + if(e.attr2>2) { conoutf("invalid ctf-flag entity (%i)", i); e.attr2 = 0; }; + flaginfo &f = flaginfos[e.attr2]; + f.flag = &e; + f.state = CTFF_INBASE; + f.originalpos.x = (float) e.x; + f.originalpos.y = (float) e.y; + f.originalpos.z = (float) e.z; + }; + }; + newteam(player1->team); // ensure valid team + }; +}; + +void startmap(char *name) // called just after a map load +{ + //if(netmapstart()) { gamemode = 0;}; //needs fixed to switch modes? + netmapstart(); //should work + sleepwait = 0; + //monsterclear(); + // Added by Rick + //BotManager.BeginMap(name); FIXME + // End add by Rick + projreset(); + if(m_ctf) preparectf(); + shotlinereset(); + spawncycle = -1; + spawnplayer(player1, true); + player1->frags = 0; + player1->flagscore = 0; + loopv(players) if(players[i]) players[i]->frags = 0; + resetspawns(); + strcpy_s(clientmap, name); + if(editmode) toggleedit(); + setvar("gamespeed", 100); + setvar("fog", 180); + setvar("fogcolour", 0x8099B3); + showscores(false); + intermission = false; + framesinmap = 0; + conoutf("game mode is %s", (int)modestr(gamemode)); +}; + +COMMANDN(map, changemap, ARG_1STR); + +void suicide() +{ + if (multiplayer()) conoutf("there are others willing to kill you..."); + else conoutf("this is not the answer"); + selfdamage(1000, -1, player1); + //if(d==player1) selfdamage(dm, -1, d); + //else { addmsg(1, 4, SV_DAMAGE, d, dm, d->lifesequence); playsound(S_FALL1+rnd(5), &d->o); }; + //particle_splash(3, 1000, 1000, player1->o); //edit out? + demodamage(1000, player1->o); + playsound(S_SUICIDE, &player1->o); +}; + +COMMAND(suicide, ARG_NONE); + +void killdummies() +{ + loopv(players) if(strcmp(players[i]->name, "dummy") == 0) { players[i]->lastaction = lastmillis; players[i]->health=1; }; +}; COMMAND(killdummies, ARG_NONE); + +// EDIT: AH +void flagaction(int flag, int action) +{ + flaginfo &f = flaginfos[flag]; + if(!f.thief) return; + bool ownflag = flag == rb_team_int(player1->team); + switch(action) + { + case SV_FLAGPICKUP: + { + playsound(S_FLAGPICKUP); + if(f.thief==player1) + { + conoutf("you got the enemy flag"); + f.pick_ack = true; + } + else conoutf("%s got %s flag", (int) f.thief->name, (int)(ownflag ? "your": "the enemy")); + break; + }; + case SV_FLAGDROP: + { + playsound(S_FLAGDROP); + if(f.thief==player1) conoutf("you lost the flag"); + else conoutf("%s lost %s flag", (int) f.thief->name, (int)(ownflag ? "your" : "the enemy")); + break; + }; + case SV_FLAGRETURN: + { + playsound(S_FLAGRETURN); + if(f.thief==player1) conoutf("you returned your flag"); + else conoutf("%s returned %s flag", (int) f.thief->name, (int)(ownflag ? "your" : "the enemy")); + break; + }; + case SV_FLAGSCORE: + { + playsound(S_FLAGSCORE); + if(f.thief==player1) + { + conoutf("you scored"); + addmsg(1, 2, SV_FLAGS, ++player1->flagscore); + } + else conoutf("%s scored for %s team", (int) f.thief->name, (int)(ownflag ? "the enemy" : "your")); + break; + }; + default: break; + }; +}; diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 85fd7b43..cbf1c470 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -1,673 +1,673 @@ -// client processing of the incoming network stream - -#include "cube.h" - -extern int clientnum; -extern bool c2sinit, senditemstoserver; -extern string toservermap; -extern string clientpassword; - -void neterr(char *s) -{ - conoutf("illegal network message (%s)", (int)s); - disconnect(); -}; - -void changemapserv(char *name, int mode) // forced map change from the server -{ - gamemode = mode; - load_world(name); -}; - -void changemap(char *name) // request map change, server may ignore -{ - strcpy_s(toservermap, name); -}; - -// Added by Rick -void botcommand(uchar *&p, char *text) -{ - int type = getint(p); - switch(EBotCommands(type)) - { - case COMMAND_ADDBOT: - getint(p); - getint(p); - sgetstr(); - sgetstr(); - break; - case COMMAND_KICKBOT: - if (getint(p)==1) // Kick a specific bot - sgetstr(); - break; - case COMMAND_BOTSKILL: - getint(p); - break; - } -} -// End add - -// update the position of other clients in the game in our world -// don't care if he's in the scenery or other players, -// just don't overlap with our client - -void updatepos(dynent *d) -{ - const float r = player1->radius+d->radius; - const float dx = player1->o.x-d->o.x; - const float dy = player1->o.y-d->o.y; - const float dz = player1->o.z-d->o.z; - const float rz = player1->aboveeye+d->eyeheight; - const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz); - if(fxstate!=CS_DEAD) - { - if(fxo.y += dy<0 ? r-fy : -(r-fy); // push aside - else d->o.x += dx<0 ? r-fx : -(r-fx); - }; - int lagtime = lastmillis-d->lastupdate; - if(lagtime) - { - d->plag = (d->plag*5+lagtime)/6; - d->lastupdate = lastmillis; - }; -}; - -void localservertoclient(uchar *buf, int len) // processes any updates from the server -{ - if(ENET_NET_TO_HOST_16(*(ushort *)buf)!=len) neterr("packet length"); - incomingdemodata(buf, len); - - uchar *end = buf+len; - uchar *p = buf+2; - char text[MAXTRANS]; - int cn = -1, type; - dynent *d = NULL; - bool mapchanged = false; - bool c2si = false, killedbybot=false; - - while(po.x = getint(p)/DMF; - d->o.y = getint(p)/DMF; - d->o.z = getint(p)/DMF; - d->yaw = getint(p)/DAF; - d->pitch = getint(p)/DAF; - d->roll = getint(p)/DAF; - d->vel.x = getint(p)/DVF; - d->vel.y = getint(p)/DVF; - d->vel.z = getint(p)/DVF; - int f = getint(p); - d->strafe = (f&3)==3 ? -1 : f&3; - f >>= 2; - d->move = (f&3)==3 ? -1 : f&3; - d->onfloor = (f>>2)&1; - int state = f>>3; - if(state==CS_DEAD && d->state!=CS_DEAD) d->lastaction = lastmillis; - d->state = state; - if(!demoplayback) updatepos(d); - break; - }; - - case SV_SOUND: - playsound(getint(p), &d->o); - break; - - case SV_TEXT: - sgetstr(); - conoutf("%s:\f %s", (int)d->name, (int)&text); - break; - - case SV_MAPCHANGE: - sgetstr(); - changemapserv(text, getint(p)); - mapchanged = true; - break; - - case SV_ITEMLIST: - { - int n; - if(mapchanged) { senditemstoserver = false; resetspawns(); }; - while((n = getint(p))!=-1) if(mapchanged) setspawn(n, true); - break; - }; - - case SV_MAPRELOAD: // server requests next map - { - getint(p); - sprintf_sd(nextmapalias)("nextmap_%s", getclientmap()); - char *map = getalias(nextmapalias); // look up map in the cycle - changemap(map ? map : getclientmap()); - break; - }; - - case SV_INITC2S: // another client either connected or changed name/team - { - sgetstr(); - if(d->name[0]) // already connected - { - if(strcmp(d->name, text)) - conoutf("%s is now known as %s", (int)d->name, (int)&text); - } - else // new client - { - c2sinit = false; // send new players my info again - conoutf("connected: %s", (int)&text); - gun_changed = true; - // Added by Rick: If we are the host("the bot owner"), tell the bots - // to update their stats - if (ishost()) - BotManager.LetBotsUpdateStats(); - // End add by Rick - }; - strcpy_s(d->name, text); - sgetstr(); - strcpy_s(d->team, text); - d->skin = getint(p); - d->lifesequence = getint(p); - c2si = true; - printf("SV_INITC2S\n"); - break; - }; - - case SV_CDIS: - cn = getint(p); - if(!(d = getclient(cn))) break; - conoutf("player %s disconnected", (int)(d->name[0] ? d->name : "[incompatible client]")); - zapdynent(players[cn]); - break; - - case SV_SHOT: - { - int gun = getint(p); - vec s, e; - s.x = getint(p)/DMF; - s.y = getint(p)/DMF; - s.z = getint(p)/DMF; - e.x = getint(p)/DMF; - e.y = getint(p)/DMF; - e.z = getint(p)/DMF; - if(gun==GUN_SHOTGUN) createrays(s, e); - int nademillis = 2000/100*getint(p); - if(nademillis<0 || nademillis>100) nademillis = 100; - shootv(gun, s, e, d, nademillis); - break; - }; - - case SV_DAMAGE: - { - int target = getint(p); - int damage = getint(p); - int ls = getint(p); - if(target==clientnum) { if(ls==player1->lifesequence) selfdamage(damage, cn, d); else printf("wrong lifesequence\n"); } - else playsound(S_PAIN1+rnd(5), &getclient(target)->o); - break; - }; - - case SV_BOT2CLIENTDMG: - { - - int target = getint(p); - int damage = getint(p); - int ls = getint(p); - int damager = getint(p); - - if(target==clientnum) - { - dynent *a = getbot(damager); - if(ls==player1->lifesequence) - selfdamage(damage, cn, a); - } - else playsound(S_PAIN1+rnd(5), &getclient(target)->o); - break; - } - - case SV_DIEDBYBOT: - killedbybot = true; - case SV_DIED: - { - int actor = getint(p); - if(actor==cn && !killedbybot) - { - conoutf("%s suicided", (int)d->name); - } - else if(actor==clientnum && !killedbybot) - { - int frags; - if(isteam(player1->team, d->team)) - { - frags = -1; - conoutf("you fragged a teammate (%s)", (int)d->name); - } - else - { - frags = 1; - conoutf("you fragged %s", (int)d->name); - }; - addmsg(1, 2, SV_FRAGS, player1->frags += frags); - } - else - { - dynent *a = killedbybot ? getbot(actor) : getclient(actor); - if(a) - { - if(isteam(a->team, d->name)) - { - conoutf("%s fragged his teammate (%s)", (int)a->name, (int)d->name); - } - else - { - conoutf("%s fragged %s", (int)a->name, (int)d->name); - }; - }; - }; - playsound(S_DIE1+rnd(2), &d->o); - if(!c2si) d->lifesequence++; - killedbybot = false; - break; - }; - - - - case SV_FRAGS: - players[cn]->frags = getint(p); - break; - - case SV_ITEMPICKUP: - case SV_BOTITEMPICKUP: - setspawn(getint(p), false); - getint(p); - break; - - case SV_ITEMSPAWN: - { - uint i = getint(p); - setspawn(i, true); - if(i>=ents.length()) break; - vec v = { ents[i].x, ents[i].y, ents[i].z }; - break; - }; - - case SV_ITEMACC: // server acknowledges that I picked up this item - realpickup(getint(p), player1); - break; - - case SV_EDITH: // coop editing messages, should be extended to include all possible editing ops - case SV_EDITT: - case SV_EDITS: - case SV_EDITD: - case SV_EDITE: - { - int x = getint(p); - int y = getint(p); - int xs = getint(p); - int ys = getint(p); - int v = getint(p); - block b = { x, y, xs, ys }; - switch(type) - { - case SV_EDITH: editheightxy(v!=0, getint(p), b); break; - case SV_EDITT: edittexxy(v, getint(p), b); break; - case SV_EDITS: edittypexy(v, b); break; - case SV_EDITD: setvdeltaxy(v, b); break; - case SV_EDITE: editequalisexy(v!=0, b); break; - }; - break; - }; - - case SV_EDITENT: // coop edit of ent - { - uint i = getint(p); - while((uint)ents.length()<=i) ents.add().type = NOTUSED; - int to = ents[i].type; - ents[i].type = getint(p); - ents[i].x = getint(p); - ents[i].y = getint(p); - ents[i].z = getint(p); - ents[i].attr1 = getint(p); - ents[i].attr2 = getint(p); - ents[i].attr3 = getint(p); - ents[i].attr4 = getint(p); - ents[i].spawned = false; - if(ents[i].type==LIGHT || to==LIGHT) calclight(); - break; - }; - - case SV_PING: - getint(p); - break; - - case SV_PONG: - addmsg(0, 2, SV_CLIENTPING, player1->ping = (player1->ping*5+lastmillis-getint(p))/6); - break; - - case SV_CLIENTPING: - players[cn]->ping = getint(p); - break; - - case SV_GAMEMODE: - nextmode = getint(p); - break; - - case SV_TIMEUP: - timeupdate(getint(p)); - break; - - case SV_RECVMAP: - { - sgetstr(); - conoutf("received map \"%s\" from server, reloading..", (int)&text); - int mapsize = getint(p); - writemap(text, mapsize, p); - p += mapsize; - changemapserv(text, gamemode); - break; - }; - - case SV_WEAPCHANGE: - { - players[cn]->gunselect = getint(p); - break; - }; - - case SV_SERVMSG: - sgetstr(); - conoutf("%s", (int)text); - break; - - // EDIT: AH - case SV_FLAGINFO: - { - int flag = getint(p); - if(flag<0||flag>1) return; - flaginfo &f = flaginfos[flag]; - f.state = getint(p); - int action = getint(p); - if(f.state==CTFF_STOLEN) - { - int thief_cn = getint(p); - f.thief = thief_cn == getclientnum() ? player1 : getclient(thief_cn); - f.flag->spawned = false; - printf("cl_sstolen by %i\n", thief_cn); - } - else if(f.state==CTFF_DROPPED) - { - printf("cl_dropped\n"); - f.flag->x = (ushort) (getint(p)/DMF); - f.flag->y = (ushort) (getint(p)/DMF); - f.flag->z = (ushort) (getint(p)/DMF); - f.flag->z -= 4.1; - float floor = (float)S(f.flag->x, f.flag->y)->floor; - if(f.flag->z > hdr.waterlevel) // above water - { - if(floor < hdr.waterlevel) - f.flag->z = hdr.waterlevel; // avoid dropping into water - else - f.flag->z = floor; - }; - f.flag->spawned = true; - } - else if(f.state==CTFF_INBASE) - { - printf("cl_inbase\n"); - if(action==SV_FLAGRETURN) - { - int returnerer_cn = getint(p); - f.thief = returnerer_cn == getclientnum() ? player1 : getclient(returnerer_cn); - printf("\tcl_return\n"); - } - f.flag->x = (ushort) f.originalpos.x; - f.flag->y = (ushort) f.originalpos.y; - f.flag->z = (ushort) f.originalpos.z; - f.flag->spawned = true; - }; - flagaction(flag, action); - break; - }; - - case SV_FLAGS: - { - players[cn]->flagscore = getint(p); - break; - }; - - case SV_FLAGPICKUP: - { - int flag = getint(p); - if(flag<0|flag>1) return; - flaginfos[flag].state=CTFF_STOLEN; - break; - }; - - case SV_FLAGDROP: - case SV_FLAGRETURN: - case SV_FLAGSCORE: - { - getint(p); - break; - }; - -/* - case SV_EXT: // so we can messages without breaking previous clients/servers, if necessary - { - for(int n = getint(p); n; n--) getint(p); - break; - }; -*/ - // Added by Rick: Bot specific messages - case SV_BOTSOUND: - { - int Sound = getint(p); - dynent *pBot = getbot(getint(p)); - - if (pBot) - playsound(Sound, &pBot->o); - - break; - } - - case SV_ADDBOT: // Bot joined the game - { - dynent *b = getbot(getint(p)); - if (!b) break; - - sgetstr(); - if(b->name[0]) // already connected - { - if(strcmp(b->name, text)) - conoutf("%s is now known as %s", (int)b->name, (int)&text); - } - else // new client - { - //c2sinit = false; // send new players my info again - conoutf("connected: %s", (int)&text); - }; - strcpy_s(b->name, text); - sgetstr(); - strcpy_s(b->team, text); - b->lifesequence = getint(p); - b->bIsBot = true; - b->pBot = NULL; - break; - }; - - case SV_BOTDIS: - { - int n = getint(p); - dynent *b = getbot(n); - - if (!b) - break; - - conoutf("bot %s disconnected", (int)(b->name[0] ? b->name : "[incompatible client]")); - delete b->pBot; - zapdynent(bots[n]); - bots.remove(n); - break; - } - - case SV_CLIENT2BOTDMG: - case SV_BOT2BOTDMG: - { - int target = getint(p); - int damage = getint(p); - int damager = getint(p); - dynent *b = getbot(target); - - dynent *a; - if (damager == -1) - { - // HACK! if the local client who sended the message is the damager, its -1 - a = d; - } - else if (type == SV_CLIENT2BOTDMG) - { - if (damager == clientnum) a = player1; - else a = getclient(damager); - } - else - a = getbot(damager); - - if (b && a) - { - // Do we know the bot info? if so we are the host... - if (b->pBot) b->pBot->BotPain(damage, a); - playsound(S_PAIN1+rnd(5), &b->o); - } - break; - } - - case SV_BOTDIED: - { - int b = getint(p); - int killer = getint(p); - bool KilledByABot = getint(p); - dynent *bot = getbot(b); - - if((b==killer) && KilledByABot) - { - conoutf("%s suicided", (int)bot->name); - } - else if((killer==clientnum) && !KilledByABot) - { - int frags; - if(isteam(player1->team, bot->team)) - { - frags = -1; - conoutf("you fragged a teammate (%s)", (int)bot->name); - } - else - { - frags = 1; - conoutf("you fragged %s", (int)bot->name); - }; - addmsg(1, 2, SV_FRAGS, player1->frags += frags); - } - else - { - dynent *k; - if (KilledByABot) - k = getbot(killer); - else if (killer == -1) - // if killer = -1, 'a player1' sended the message(hack) - k = d; - else - k = getclient(killer); - - if(bot && k) - { - if(isteam(bot->team, k->name)) - { - conoutf("%s fragged his teammate (%s)", (int)bot->name, (int)k->name); - } - else - { - conoutf("%s fragged %s", (int)bot->name, (int)k->name); - } - } - } - - playsound(S_DIE1+rnd(2), &bot->o); - bot->lifesequence++; - break; - }; - - case SV_BOTFRAGS: - { - dynent *b = getbot(getint(p)); - if (b) b->frags = getint(p); - break; - } - - case SV_BOTUPDATE: - { - int n = getint(p); - dynent *b = getbot(n); - if(!b) return; - b->o.x = getint(p)/DMF; - b->o.y = getint(p)/DMF; - b->o.z = getint(p)/DMF; - b->yaw = getint(p)/DAF; - b->pitch = getint(p)/DAF; - b->roll = getint(p)/DAF; - b->vel.x = getint(p)/DVF; - b->vel.y = getint(p)/DVF; - b->vel.z = getint(p)/DVF; - int f = getint(p); - b->strafe = (f&3)==3 ? -1 : f&3; - f >>= 2; - b->move = (f&3)==3 ? -1 : f&3; - b->onfloor = (f>>2)&1; - int state = f>>3; - if(state==CS_DEAD && b->state!=CS_DEAD) b->lastaction = lastmillis; - b->state = state; - - if (!b->bIsBot) - b->bIsBot = true; - - if(!demoplayback) updatepos(b); - break; - } - case SV_BOTCOMMAND: - botcommand(p, text); - break; - - // End add - - default: - neterr("type"); - return; - }; -}; +// client processing of the incoming network stream + +#include "cube.h" + +extern int clientnum; +extern bool c2sinit, senditemstoserver; +extern string toservermap; +extern string clientpassword; + +void neterr(char *s) +{ + conoutf("illegal network message (%s)", (int)s); + disconnect(); +}; + +void changemapserv(char *name, int mode) // forced map change from the server +{ + gamemode = mode; + load_world(name); +}; + +void changemap(char *name) // request map change, server may ignore +{ + strcpy_s(toservermap, name); +}; + +// Added by Rick +void botcommand(uchar *&p, char *text) +{ + int type = getint(p); + switch(EBotCommands(type)) + { + case COMMAND_ADDBOT: + getint(p); + getint(p); + sgetstr(); + sgetstr(); + break; + case COMMAND_KICKBOT: + if (getint(p)==1) // Kick a specific bot + sgetstr(); + break; + case COMMAND_BOTSKILL: + getint(p); + break; + } +} +// End add + +// update the position of other clients in the game in our world +// don't care if he's in the scenery or other players, +// just don't overlap with our client + +void updatepos(dynent *d) +{ + const float r = player1->radius+d->radius; + const float dx = player1->o.x-d->o.x; + const float dy = player1->o.y-d->o.y; + const float dz = player1->o.z-d->o.z; + const float rz = player1->aboveeye+d->eyeheight; + const float fx = (float)fabs(dx), fy = (float)fabs(dy), fz = (float)fabs(dz); + if(fxstate!=CS_DEAD) + { + if(fxo.y += dy<0 ? r-fy : -(r-fy); // push aside + else d->o.x += dx<0 ? r-fx : -(r-fx); + }; + int lagtime = lastmillis-d->lastupdate; + if(lagtime) + { + d->plag = (d->plag*5+lagtime)/6; + d->lastupdate = lastmillis; + }; +}; + +void localservertoclient(uchar *buf, int len) // processes any updates from the server +{ + if(ENET_NET_TO_HOST_16(*(ushort *)buf)!=len) neterr("packet length"); + incomingdemodata(buf, len); + + uchar *end = buf+len; + uchar *p = buf+2; + char text[MAXTRANS]; + int cn = -1, type; + dynent *d = NULL; + bool mapchanged = false; + bool c2si = false, killedbybot=false; + + while(po.x = getint(p)/DMF; + d->o.y = getint(p)/DMF; + d->o.z = getint(p)/DMF; + d->yaw = getint(p)/DAF; + d->pitch = getint(p)/DAF; + d->roll = getint(p)/DAF; + d->vel.x = getint(p)/DVF; + d->vel.y = getint(p)/DVF; + d->vel.z = getint(p)/DVF; + int f = getint(p); + d->strafe = (f&3)==3 ? -1 : f&3; + f >>= 2; + d->move = (f&3)==3 ? -1 : f&3; + d->onfloor = (f>>2)&1; + int state = f>>3; + if(state==CS_DEAD && d->state!=CS_DEAD) d->lastaction = lastmillis; + d->state = state; + if(!demoplayback) updatepos(d); + break; + }; + + case SV_SOUND: + playsound(getint(p), &d->o); + break; + + case SV_TEXT: + sgetstr(); + conoutf("%s:\f %s", (int)d->name, (int)&text); + break; + + case SV_MAPCHANGE: + sgetstr(); + changemapserv(text, getint(p)); + mapchanged = true; + break; + + case SV_ITEMLIST: + { + int n; + if(mapchanged) { senditemstoserver = false; resetspawns(); }; + while((n = getint(p))!=-1) if(mapchanged) setspawn(n, true); + break; + }; + + case SV_MAPRELOAD: // server requests next map + { + getint(p); + sprintf_sd(nextmapalias)("nextmap_%s", getclientmap()); + char *map = getalias(nextmapalias); // look up map in the cycle + changemap(map ? map : getclientmap()); + break; + }; + + case SV_INITC2S: // another client either connected or changed name/team + { + sgetstr(); + if(d->name[0]) // already connected + { + if(strcmp(d->name, text)) + conoutf("%s is now known as %s", (int)d->name, (int)&text); + } + else // new client + { + c2sinit = false; // send new players my info again + conoutf("connected: %s", (int)&text); + gun_changed = true; + // Added by Rick: If we are the host("the bot owner"), tell the bots + // to update their stats + if (ishost()) + BotManager.LetBotsUpdateStats(); + // End add by Rick + }; + strcpy_s(d->name, text); + sgetstr(); + strcpy_s(d->team, text); + d->skin = getint(p); + d->lifesequence = getint(p); + c2si = true; + printf("SV_INITC2S\n"); + break; + }; + + case SV_CDIS: + cn = getint(p); + if(!(d = getclient(cn))) break; + conoutf("player %s disconnected", (int)(d->name[0] ? d->name : "[incompatible client]")); + zapdynent(players[cn]); + break; + + case SV_SHOT: + { + int gun = getint(p); + vec s, e; + s.x = getint(p)/DMF; + s.y = getint(p)/DMF; + s.z = getint(p)/DMF; + e.x = getint(p)/DMF; + e.y = getint(p)/DMF; + e.z = getint(p)/DMF; + if(gun==GUN_SHOTGUN) createrays(s, e); + int nademillis = 2000/100*getint(p); + if(nademillis<0 || nademillis>100) nademillis = 100; + shootv(gun, s, e, d, nademillis); + break; + }; + + case SV_DAMAGE: + { + int target = getint(p); + int damage = getint(p); + int ls = getint(p); + if(target==clientnum) { if(ls==player1->lifesequence) selfdamage(damage, cn, d); else printf("wrong lifesequence\n"); } + else playsound(S_PAIN1+rnd(5), &getclient(target)->o); + break; + }; + + case SV_BOT2CLIENTDMG: + { + + int target = getint(p); + int damage = getint(p); + int ls = getint(p); + int damager = getint(p); + + if(target==clientnum) + { + dynent *a = getbot(damager); + if(ls==player1->lifesequence) + selfdamage(damage, cn, a); + } + else playsound(S_PAIN1+rnd(5), &getclient(target)->o); + break; + } + + case SV_DIEDBYBOT: + killedbybot = true; + case SV_DIED: + { + int actor = getint(p); + if(actor==cn && !killedbybot) + { + conoutf("%s suicided", (int)d->name); + } + else if(actor==clientnum && !killedbybot) + { + int frags; + if(isteam(player1->team, d->team)) + { + frags = -1; + conoutf("you fragged a teammate (%s)", (int)d->name); + } + else + { + frags = 1; + conoutf("you fragged %s", (int)d->name); + }; + addmsg(1, 2, SV_FRAGS, player1->frags += frags); + } + else + { + dynent *a = killedbybot ? getbot(actor) : getclient(actor); + if(a) + { + if(isteam(a->team, d->name)) + { + conoutf("%s fragged his teammate (%s)", (int)a->name, (int)d->name); + } + else + { + conoutf("%s fragged %s", (int)a->name, (int)d->name); + }; + }; + }; + playsound(S_DIE1+rnd(2), &d->o); + if(!c2si) d->lifesequence++; + killedbybot = false; + break; + }; + + + + case SV_FRAGS: + players[cn]->frags = getint(p); + break; + + case SV_ITEMPICKUP: + case SV_BOTITEMPICKUP: + setspawn(getint(p), false); + getint(p); + break; + + case SV_ITEMSPAWN: + { + uint i = getint(p); + setspawn(i, true); + if(i>=ents.length()) break; + vec v = { ents[i].x, ents[i].y, ents[i].z }; + break; + }; + + case SV_ITEMACC: // server acknowledges that I picked up this item + realpickup(getint(p), player1); + break; + + case SV_EDITH: // coop editing messages, should be extended to include all possible editing ops + case SV_EDITT: + case SV_EDITS: + case SV_EDITD: + case SV_EDITE: + { + int x = getint(p); + int y = getint(p); + int xs = getint(p); + int ys = getint(p); + int v = getint(p); + block b = { x, y, xs, ys }; + switch(type) + { + case SV_EDITH: editheightxy(v!=0, getint(p), b); break; + case SV_EDITT: edittexxy(v, getint(p), b); break; + case SV_EDITS: edittypexy(v, b); break; + case SV_EDITD: setvdeltaxy(v, b); break; + case SV_EDITE: editequalisexy(v!=0, b); break; + }; + break; + }; + + case SV_EDITENT: // coop edit of ent + { + uint i = getint(p); + while((uint)ents.length()<=i) ents.add().type = NOTUSED; + int to = ents[i].type; + ents[i].type = getint(p); + ents[i].x = getint(p); + ents[i].y = getint(p); + ents[i].z = getint(p); + ents[i].attr1 = getint(p); + ents[i].attr2 = getint(p); + ents[i].attr3 = getint(p); + ents[i].attr4 = getint(p); + ents[i].spawned = false; + if(ents[i].type==LIGHT || to==LIGHT) calclight(); + break; + }; + + case SV_PING: + getint(p); + break; + + case SV_PONG: + addmsg(0, 2, SV_CLIENTPING, player1->ping = (player1->ping*5+lastmillis-getint(p))/6); + break; + + case SV_CLIENTPING: + players[cn]->ping = getint(p); + break; + + case SV_GAMEMODE: + nextmode = getint(p); + break; + + case SV_TIMEUP: + timeupdate(getint(p)); + break; + + case SV_RECVMAP: + { + sgetstr(); + conoutf("received map \"%s\" from server, reloading..", (int)&text); + int mapsize = getint(p); + writemap(text, mapsize, p); + p += mapsize; + changemapserv(text, gamemode); + break; + }; + + case SV_WEAPCHANGE: + { + players[cn]->gunselect = getint(p); + break; + }; + + case SV_SERVMSG: + sgetstr(); + conoutf("%s", (int)text); + break; + + // EDIT: AH + case SV_FLAGINFO: + { + int flag = getint(p); + if(flag<0||flag>1) return; + flaginfo &f = flaginfos[flag]; + f.state = getint(p); + int action = getint(p); + if(f.state==CTFF_STOLEN) + { + int thief_cn = getint(p); + f.thief = thief_cn == getclientnum() ? player1 : getclient(thief_cn); + f.flag->spawned = false; + printf("cl_sstolen by %i\n", thief_cn); + } + else if(f.state==CTFF_DROPPED) + { + printf("cl_dropped\n"); + f.flag->x = (ushort) (getint(p)/DMF); + f.flag->y = (ushort) (getint(p)/DMF); + f.flag->z = (ushort) (getint(p)/DMF); + f.flag->z -= 4.1; + float floor = (float)S(f.flag->x, f.flag->y)->floor; + if(f.flag->z > hdr.waterlevel) // above water + { + if(floor < hdr.waterlevel) + f.flag->z = hdr.waterlevel; // avoid dropping into water + else + f.flag->z = floor; + }; + f.flag->spawned = true; + } + else if(f.state==CTFF_INBASE) + { + printf("cl_inbase\n"); + if(action==SV_FLAGRETURN) + { + int returnerer_cn = getint(p); + f.thief = returnerer_cn == getclientnum() ? player1 : getclient(returnerer_cn); + printf("\tcl_return\n"); + } + f.flag->x = (ushort) f.originalpos.x; + f.flag->y = (ushort) f.originalpos.y; + f.flag->z = (ushort) f.originalpos.z; + f.flag->spawned = true; + }; + flagaction(flag, action); + break; + }; + + case SV_FLAGS: + { + players[cn]->flagscore = getint(p); + break; + }; + + case SV_FLAGPICKUP: + { + int flag = getint(p); + if(flag<0|flag>1) return; + flaginfos[flag].state=CTFF_STOLEN; + break; + }; + + case SV_FLAGDROP: + case SV_FLAGRETURN: + case SV_FLAGSCORE: + { + getint(p); + break; + }; + +/* + case SV_EXT: // so we can messages without breaking previous clients/servers, if necessary + { + for(int n = getint(p); n; n--) getint(p); + break; + }; +*/ + // Added by Rick: Bot specific messages + case SV_BOTSOUND: + { + int Sound = getint(p); + dynent *pBot = getbot(getint(p)); + + if (pBot) + playsound(Sound, &pBot->o); + + break; + } + + case SV_ADDBOT: // Bot joined the game + { + dynent *b = getbot(getint(p)); + if (!b) break; + + sgetstr(); + if(b->name[0]) // already connected + { + if(strcmp(b->name, text)) + conoutf("%s is now known as %s", (int)b->name, (int)&text); + } + else // new client + { + //c2sinit = false; // send new players my info again + conoutf("connected: %s", (int)&text); + }; + strcpy_s(b->name, text); + sgetstr(); + strcpy_s(b->team, text); + b->lifesequence = getint(p); + b->bIsBot = true; + b->pBot = NULL; + break; + }; + + case SV_BOTDIS: + { + int n = getint(p); + dynent *b = getbot(n); + + if (!b) + break; + + conoutf("bot %s disconnected", (int)(b->name[0] ? b->name : "[incompatible client]")); + delete b->pBot; + zapdynent(bots[n]); + bots.remove(n); + break; + } + + case SV_CLIENT2BOTDMG: + case SV_BOT2BOTDMG: + { + int target = getint(p); + int damage = getint(p); + int damager = getint(p); + dynent *b = getbot(target); + + dynent *a; + if (damager == -1) + { + // HACK! if the local client who sended the message is the damager, its -1 + a = d; + } + else if (type == SV_CLIENT2BOTDMG) + { + if (damager == clientnum) a = player1; + else a = getclient(damager); + } + else + a = getbot(damager); + + if (b && a) + { + // Do we know the bot info? if so we are the host... + if (b->pBot) b->pBot->BotPain(damage, a); + playsound(S_PAIN1+rnd(5), &b->o); + } + break; + } + + case SV_BOTDIED: + { + int b = getint(p); + int killer = getint(p); + bool KilledByABot = getint(p); + dynent *bot = getbot(b); + + if((b==killer) && KilledByABot) + { + conoutf("%s suicided", (int)bot->name); + } + else if((killer==clientnum) && !KilledByABot) + { + int frags; + if(isteam(player1->team, bot->team)) + { + frags = -1; + conoutf("you fragged a teammate (%s)", (int)bot->name); + } + else + { + frags = 1; + conoutf("you fragged %s", (int)bot->name); + }; + addmsg(1, 2, SV_FRAGS, player1->frags += frags); + } + else + { + dynent *k; + if (KilledByABot) + k = getbot(killer); + else if (killer == -1) + // if killer = -1, 'a player1' sended the message(hack) + k = d; + else + k = getclient(killer); + + if(bot && k) + { + if(isteam(bot->team, k->name)) + { + conoutf("%s fragged his teammate (%s)", (int)bot->name, (int)k->name); + } + else + { + conoutf("%s fragged %s", (int)bot->name, (int)k->name); + } + } + } + + playsound(S_DIE1+rnd(2), &bot->o); + bot->lifesequence++; + break; + }; + + case SV_BOTFRAGS: + { + dynent *b = getbot(getint(p)); + if (b) b->frags = getint(p); + break; + } + + case SV_BOTUPDATE: + { + int n = getint(p); + dynent *b = getbot(n); + if(!b) return; + b->o.x = getint(p)/DMF; + b->o.y = getint(p)/DMF; + b->o.z = getint(p)/DMF; + b->yaw = getint(p)/DAF; + b->pitch = getint(p)/DAF; + b->roll = getint(p)/DAF; + b->vel.x = getint(p)/DVF; + b->vel.y = getint(p)/DVF; + b->vel.z = getint(p)/DVF; + int f = getint(p); + b->strafe = (f&3)==3 ? -1 : f&3; + f >>= 2; + b->move = (f&3)==3 ? -1 : f&3; + b->onfloor = (f>>2)&1; + int state = f>>3; + if(state==CS_DEAD && b->state!=CS_DEAD) b->lastaction = lastmillis; + b->state = state; + + if (!b->bIsBot) + b->bIsBot = true; + + if(!demoplayback) updatepos(b); + break; + } + case SV_BOTCOMMAND: + botcommand(p, text); + break; + + // End add + + default: + neterr("type"); + return; + }; +}; diff --git a/source/src/command.cpp b/source/src/command.cpp index e9a924ec..ea3960ef 100644 --- a/source/src/command.cpp +++ b/source/src/command.cpp @@ -1,335 +1,335 @@ -// command.cpp: implements the parsing and execution of a tiny script language which -// is largely backwards compatible with the quake console language. - -#include "cube.h" - -enum { ID_VAR, ID_COMMAND, ID_ALIAS }; - -struct ident -{ - int type; // one of ID_* above - char *name; - int min, max; // ID_VAR - int *storage; // ID_VAR - void (*fun)(); // ID_VAR, ID_COMMAND - int narg; // ID_VAR, ID_COMMAND - char *action; // ID_ALIAS - bool persist; -}; - -void itoa(char *s, int i) { sprintf_s(s)("%d", i); }; -char *exchangestr(char *o, char *n) { gp()->deallocstr(o); return newstring(n); }; - -hashtable *idents = NULL; // contains ALL vars/commands/aliases - -void alias(char *name, char *action) -{ - ident *b = idents->access(name); - if(!b) - { - name = newstring(name); - ident b = { ID_ALIAS, name, 0, 0, 0, 0, 0, newstring(action) }; - idents->access(name, &b); - } - else - { - if(b->type==ID_ALIAS) b->action = exchangestr(b->action, action); - else conoutf("cannot redefine builtin %s with an alias", (int)name); - }; -}; - -COMMAND(alias, ARG_2STR); - -// variable's and commands are registered through globals, see cube.h - -int variable(char *name, int min, int cur, int max, int *storage, void (*fun)(), bool persist) -{ - if(!idents) idents = new hashtable; - ident v = { ID_VAR, name, min, max, storage, fun, 0, 0, persist }; - idents->access(name, &v); - return cur; -}; - -void setvar(char *name, int i) { *idents->access(name)->storage = i; }; -int getvar(char *name) { return *idents->access(name)->storage; }; -bool identexists(char *name) { return idents->access(name)!=NULL; }; - -char *getalias(char *name) -{ - ident *i = idents->access(name); - return i && i->type==ID_ALIAS ? i->action : NULL; -}; - -bool addcommand(char *name, void (*fun)(), int narg) -{ - if(!idents) idents = new hashtable; - ident c = { ID_COMMAND, name, 0, 0, 0, fun, narg, 0 }; - idents->access(name, &c); - return false; -}; - -char *parseexp(char *&p, int right) // parse any nested set of () or [] -{ - int left = *p++; - char *word = p; - for(int brak = 1; brak; ) - { - int c = *p++; - if(c==left) brak++; - else if(c==right) brak--; - else if(!c) { p--; conoutf("missing \"%c\"", right); return NULL; }; - }; - char *s = newstring(word, p-word-1); - if(left=='(') - { - string t; - itoa(t, execute(s)); // evaluate () exps directly, and substitute result - s = exchangestr(s, t); - }; - return s; -}; - -char *parseword(char *&p) // parse single argument, including expressions -{ - p += strspn(p, " \t\r"); - if(p[0]=='/' && p[1]=='/') p += strcspn(p, "\n\0"); - if(*p=='\"') - { - p++; - char *word = p; - p += strcspn(p, "\"\r\n\0"); - char *s = newstring(word, p-word); - if(*p=='\"') p++; - return s; - }; - if(*p=='(') return parseexp(p, ')'); - if(*p=='[') return parseexp(p, ']'); - char *word = p; - p += strcspn(p, "; \t\r\n\0"); - if(p-word==0) return NULL; - return newstring(word, p-word); -}; - -char *lookup(char *n) // find value of ident referenced with $ in exp -{ - ident *id = idents->access(n+1); - if(id) switch(id->type) - { - case ID_VAR: string t; itoa(t, *(id->storage)); return exchangestr(n, t); - case ID_ALIAS: return exchangestr(n, id->action); - }; - conoutf("unknown alias lookup: %s", (int)n+1); - return n; -}; - -int execute(char *p, bool isdown) // all evaluation happens here, recursively -{ - const int MAXWORDS = 25; // limit, remove - char *w[MAXWORDS]; - int val = 0; - for(bool cont = true; cont;) // for each ; seperated statement - { - int numargs = MAXWORDS; - loopi(MAXWORDS) // collect all argument values - { - w[i] = ""; - if(i>numargs) continue; - char *s = parseword(p); // parse and evaluate exps - if(!s) { numargs = i; s = ""; }; - if(*s=='$') s = lookup(s); // substitute variables - w[i] = s; - }; - - p += strcspn(p, ";\n\0"); - cont = *p++!=0; // more statements if this isn't the end of the string - char *c = w[0]; - if(*c=='/') c++; // strip irc-style command prefix - if(!*c) continue; // empty statement - - ident *id = idents->access(c); - if(!id) - { - val = ATOI(c); - if(!val && *c!='0') conoutf("unknown command: %s", (int)c); - } - else switch(id->type) - { - case ID_COMMAND: // game defined commands - switch(id->narg) // use very ad-hoc function signature, and just call it - { - case ARG_1INT: if(isdown) ((void (__cdecl *)(int))id->fun)(ATOI(w[1])); break; - case ARG_2INT: if(isdown) ((void (__cdecl *)(int, int))id->fun)(ATOI(w[1]), ATOI(w[2])); break; - case ARG_3INT: if(isdown) ((void (__cdecl *)(int, int, int))id->fun)(ATOI(w[1]), ATOI(w[2]), ATOI(w[3])); break; - case ARG_4INT: if(isdown) ((void (__cdecl *)(int, int, int, int))id->fun)(ATOI(w[1]), ATOI(w[2]), ATOI(w[3]), ATOI(w[4])); break; - case ARG_NONE: if(isdown) ((void (__cdecl *)())id->fun)(); break; - case ARG_1STR: if(isdown) ((void (__cdecl *)(char *))id->fun)(w[1]); break; - case ARG_2STR: if(isdown) ((void (__cdecl *)(char *, char *))id->fun)(w[1], w[2]); break; - case ARG_3STR: if(isdown) ((void (__cdecl *)(char *, char *, char*))id->fun)(w[1], w[2], w[3]); break; - case ARG_5STR: if(isdown) ((void (__cdecl *)(char *, char *, char*, char*, char*))id->fun)(w[1], w[2], w[3], w[4], w[5]); break; - case ARG_6STR: if(isdown) ((void (__cdecl *)(char *, char *, char*, char*, char*, char*))id->fun)(w[1], w[2], w[3], w[4], w[5], w[6]); break; - case ARG_DOWN: ((void (__cdecl *)(bool))id->fun)(isdown); break; - case ARG_DWN1: ((void (__cdecl *)(bool, char *))id->fun)(isdown, w[1]); break; - case ARG_1EXP: if(isdown) val = ((int (__cdecl *)(int))id->fun)(execute(w[1])); break; - case ARG_2EXP: if(isdown) val = ((int (__cdecl *)(int, int))id->fun)(execute(w[1]), execute(w[2])); break; - case ARG_1EST: if(isdown) val = ((int (__cdecl *)(char *))id->fun)(w[1]); break; - case ARG_2EST: if(isdown) val = ((int (__cdecl *)(char *, char *))id->fun)(w[1], w[2]); break; - case ARG_VARI: if(isdown) - { - string r; // limit, remove - r[0] = 0; - for(int i = 1; ifun)(r); - break; - } - }; - break; - - case ID_VAR: // game defined variabled - if(isdown) - { - if(!w[1][0]) conoutf("%s = %d", (int)c, *id->storage); // var with no value just prints its current value - else - { - if(id->min>id->max) - { - conoutf("variable is read-only"); - } - else - { - int i1 = ATOI(w[1]); - if(i1min || i1>id->max) - { - i1 = i1min ? id->min : id->max; // clamp to valid range - conoutf("valid range for %s is %d..%d", (int)c, id->min, id->max); - } - *id->storage = i1; - }; - if(id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available - }; - }; - break; - - case ID_ALIAS: // alias, also used as functions and (global) variables - for(int i = 1; iaction); // create new string here because alias could rebind itself - val = execute(action, isdown); - gp()->deallocstr(action); - break; - }; - loopj(numargs) gp()->deallocstr(w[j]); - }; - return val; -}; - -// tab-completion of all idents - -int completesize = 0, completeidx = 0; - -void resetcomplete() { completesize = 0; }; - -void complete(char *s) -{ - if(*s!='/') - { - string t; - strcpy_s(t, s); - strcpy_s(s, "/"); - strcat_s(s, t); - }; - if(!s[1]) return; - if(!completesize) { completesize = strlen(s)-1; completeidx = 0; }; - int idx = 0; - enumerate(idents, ident *, id, - if(strncmp(id->name, s+1, completesize)==0 && idx++==completeidx) - { - strcpy_s(s, "/"); - strcat_s(s, id->name); - }; - ); - completeidx++; - if(completeidx>=idx) completeidx = 0; -}; - -bool execfile(char *cfgfile) -{ - string s; - strcpy_s(s, cfgfile); - char *buf = loadfile(path(s), NULL); - if(!buf) return false; - execute(buf); - free(buf); - return true; -}; - -void exec(char *cfgfile) -{ - if(!execfile(cfgfile)) conoutf("could not read \"%s\"", (int)cfgfile); -}; - -// below the commands that implement a small imperative language. thanks to the semantics of -// () and [] expressions, any control construct can be defined trivially. - -void intset(char *name, int v) { string b; itoa(b, v); alias(name, b); }; - -void ifthen(char *cond, char *thenp, char *elsep) { execute(cond[0]!='0' ? thenp : elsep); }; -void loopa(char *times, char *body) { int t = atoi(times); loopi(t) { intset("i", i); execute(body); }; }; -void whilea(char *cond, char *body) { while(execute(cond)) execute(body); }; // can't get any simpler than this :) -void onrelease(bool on, char *body) { if(!on) execute(body); }; - -void concat(char *s) { alias("s", s); }; - -void concatword(char *s) -{ - for(char *a = s, *b = s; *a = *b; b++) if(*a!=' ') a++; - concat(s); -}; - -int listlen(char *a) -{ - if(!*a) return 0; - int n = 0; - while(*a) if(*a++==' ') n++; - return n+1; -}; - -void at(char *s, char *pos) -{ - int n = atoi(pos); - loopi(n) s += strspn(s += strcspn(s, " \0"), " "); - s[strcspn(s, " \0")] = 0; - concat(s); -}; - -COMMANDN(loop, loopa, ARG_2STR); -COMMANDN(while, whilea, ARG_2STR); -COMMANDN(if, ifthen, ARG_3STR); -COMMAND(onrelease, ARG_DWN1); -COMMAND(exec, ARG_1STR); -COMMAND(concat, ARG_VARI); -COMMAND(concatword, ARG_VARI); -COMMAND(at, ARG_2STR); -COMMAND(listlen, ARG_1EST); - -int add(int a, int b) { return a+b; }; COMMANDN(+, add, ARG_2EXP); -int mul(int a, int b) { return a*b; }; COMMANDN(*, mul, ARG_2EXP); -int sub(int a, int b) { return a-b; }; COMMANDN(-, sub, ARG_2EXP); -int divi(int a, int b) { return b ? a/b : 0; }; COMMANDN(div, divi, ARG_2EXP); -int mod(int a, int b) { return b ? a%b : 0; }; COMMAND(mod, ARG_2EXP); -int equal(int a, int b) { return (int)(a==b); }; COMMANDN(=, equal, ARG_2EXP); -int lt(int a, int b) { return (int)(ab); }; COMMANDN(>, gt, ARG_2EXP); - -int strcmpa(char *a, char *b) { return strcmp(a,b)==0; }; COMMANDN(strcmp, strcmpa, ARG_2EST); - -int rndn(int a) { return a>0 ? rnd(a) : 0; }; COMMANDN(rnd, rndn, ARG_1EXP); - - +// command.cpp: implements the parsing and execution of a tiny script language which +// is largely backwards compatible with the quake console language. + +#include "cube.h" + +enum { ID_VAR, ID_COMMAND, ID_ALIAS }; + +struct ident +{ + int type; // one of ID_* above + char *name; + int min, max; // ID_VAR + int *storage; // ID_VAR + void (*fun)(); // ID_VAR, ID_COMMAND + int narg; // ID_VAR, ID_COMMAND + char *action; // ID_ALIAS + bool persist; +}; + +void itoa(char *s, int i) { sprintf_s(s)("%d", i); }; +char *exchangestr(char *o, char *n) { gp()->deallocstr(o); return newstring(n); }; + +hashtable *idents = NULL; // contains ALL vars/commands/aliases + +void alias(char *name, char *action) +{ + ident *b = idents->access(name); + if(!b) + { + name = newstring(name); + ident b = { ID_ALIAS, name, 0, 0, 0, 0, 0, newstring(action) }; + idents->access(name, &b); + } + else + { + if(b->type==ID_ALIAS) b->action = exchangestr(b->action, action); + else conoutf("cannot redefine builtin %s with an alias", (int)name); + }; +}; + +COMMAND(alias, ARG_2STR); + +// variable's and commands are registered through globals, see cube.h + +int variable(char *name, int min, int cur, int max, int *storage, void (*fun)(), bool persist) +{ + if(!idents) idents = new hashtable; + ident v = { ID_VAR, name, min, max, storage, fun, 0, 0, persist }; + idents->access(name, &v); + return cur; +}; + +void setvar(char *name, int i) { *idents->access(name)->storage = i; }; +int getvar(char *name) { return *idents->access(name)->storage; }; +bool identexists(char *name) { return idents->access(name)!=NULL; }; + +char *getalias(char *name) +{ + ident *i = idents->access(name); + return i && i->type==ID_ALIAS ? i->action : NULL; +}; + +bool addcommand(char *name, void (*fun)(), int narg) +{ + if(!idents) idents = new hashtable; + ident c = { ID_COMMAND, name, 0, 0, 0, fun, narg, 0 }; + idents->access(name, &c); + return false; +}; + +char *parseexp(char *&p, int right) // parse any nested set of () or [] +{ + int left = *p++; + char *word = p; + for(int brak = 1; brak; ) + { + int c = *p++; + if(c==left) brak++; + else if(c==right) brak--; + else if(!c) { p--; conoutf("missing \"%c\"", right); return NULL; }; + }; + char *s = newstring(word, p-word-1); + if(left=='(') + { + string t; + itoa(t, execute(s)); // evaluate () exps directly, and substitute result + s = exchangestr(s, t); + }; + return s; +}; + +char *parseword(char *&p) // parse single argument, including expressions +{ + p += strspn(p, " \t\r"); + if(p[0]=='/' && p[1]=='/') p += strcspn(p, "\n\0"); + if(*p=='\"') + { + p++; + char *word = p; + p += strcspn(p, "\"\r\n\0"); + char *s = newstring(word, p-word); + if(*p=='\"') p++; + return s; + }; + if(*p=='(') return parseexp(p, ')'); + if(*p=='[') return parseexp(p, ']'); + char *word = p; + p += strcspn(p, "; \t\r\n\0"); + if(p-word==0) return NULL; + return newstring(word, p-word); +}; + +char *lookup(char *n) // find value of ident referenced with $ in exp +{ + ident *id = idents->access(n+1); + if(id) switch(id->type) + { + case ID_VAR: string t; itoa(t, *(id->storage)); return exchangestr(n, t); + case ID_ALIAS: return exchangestr(n, id->action); + }; + conoutf("unknown alias lookup: %s", (int)n+1); + return n; +}; + +int execute(char *p, bool isdown) // all evaluation happens here, recursively +{ + const int MAXWORDS = 25; // limit, remove + char *w[MAXWORDS]; + int val = 0; + for(bool cont = true; cont;) // for each ; seperated statement + { + int numargs = MAXWORDS; + loopi(MAXWORDS) // collect all argument values + { + w[i] = ""; + if(i>numargs) continue; + char *s = parseword(p); // parse and evaluate exps + if(!s) { numargs = i; s = ""; }; + if(*s=='$') s = lookup(s); // substitute variables + w[i] = s; + }; + + p += strcspn(p, ";\n\0"); + cont = *p++!=0; // more statements if this isn't the end of the string + char *c = w[0]; + if(*c=='/') c++; // strip irc-style command prefix + if(!*c) continue; // empty statement + + ident *id = idents->access(c); + if(!id) + { + val = ATOI(c); + if(!val && *c!='0') conoutf("unknown command: %s", (int)c); + } + else switch(id->type) + { + case ID_COMMAND: // game defined commands + switch(id->narg) // use very ad-hoc function signature, and just call it + { + case ARG_1INT: if(isdown) ((void (__cdecl *)(int))id->fun)(ATOI(w[1])); break; + case ARG_2INT: if(isdown) ((void (__cdecl *)(int, int))id->fun)(ATOI(w[1]), ATOI(w[2])); break; + case ARG_3INT: if(isdown) ((void (__cdecl *)(int, int, int))id->fun)(ATOI(w[1]), ATOI(w[2]), ATOI(w[3])); break; + case ARG_4INT: if(isdown) ((void (__cdecl *)(int, int, int, int))id->fun)(ATOI(w[1]), ATOI(w[2]), ATOI(w[3]), ATOI(w[4])); break; + case ARG_NONE: if(isdown) ((void (__cdecl *)())id->fun)(); break; + case ARG_1STR: if(isdown) ((void (__cdecl *)(char *))id->fun)(w[1]); break; + case ARG_2STR: if(isdown) ((void (__cdecl *)(char *, char *))id->fun)(w[1], w[2]); break; + case ARG_3STR: if(isdown) ((void (__cdecl *)(char *, char *, char*))id->fun)(w[1], w[2], w[3]); break; + case ARG_5STR: if(isdown) ((void (__cdecl *)(char *, char *, char*, char*, char*))id->fun)(w[1], w[2], w[3], w[4], w[5]); break; + case ARG_6STR: if(isdown) ((void (__cdecl *)(char *, char *, char*, char*, char*, char*))id->fun)(w[1], w[2], w[3], w[4], w[5], w[6]); break; + case ARG_DOWN: ((void (__cdecl *)(bool))id->fun)(isdown); break; + case ARG_DWN1: ((void (__cdecl *)(bool, char *))id->fun)(isdown, w[1]); break; + case ARG_1EXP: if(isdown) val = ((int (__cdecl *)(int))id->fun)(execute(w[1])); break; + case ARG_2EXP: if(isdown) val = ((int (__cdecl *)(int, int))id->fun)(execute(w[1]), execute(w[2])); break; + case ARG_1EST: if(isdown) val = ((int (__cdecl *)(char *))id->fun)(w[1]); break; + case ARG_2EST: if(isdown) val = ((int (__cdecl *)(char *, char *))id->fun)(w[1], w[2]); break; + case ARG_VARI: if(isdown) + { + string r; // limit, remove + r[0] = 0; + for(int i = 1; ifun)(r); + break; + } + }; + break; + + case ID_VAR: // game defined variabled + if(isdown) + { + if(!w[1][0]) conoutf("%s = %d", (int)c, *id->storage); // var with no value just prints its current value + else + { + if(id->min>id->max) + { + conoutf("variable is read-only"); + } + else + { + int i1 = ATOI(w[1]); + if(i1min || i1>id->max) + { + i1 = i1min ? id->min : id->max; // clamp to valid range + conoutf("valid range for %s is %d..%d", (int)c, id->min, id->max); + } + *id->storage = i1; + }; + if(id->fun) ((void (__cdecl *)())id->fun)(); // call trigger function if available + }; + }; + break; + + case ID_ALIAS: // alias, also used as functions and (global) variables + for(int i = 1; iaction); // create new string here because alias could rebind itself + val = execute(action, isdown); + gp()->deallocstr(action); + break; + }; + loopj(numargs) gp()->deallocstr(w[j]); + }; + return val; +}; + +// tab-completion of all idents + +int completesize = 0, completeidx = 0; + +void resetcomplete() { completesize = 0; }; + +void complete(char *s) +{ + if(*s!='/') + { + string t; + strcpy_s(t, s); + strcpy_s(s, "/"); + strcat_s(s, t); + }; + if(!s[1]) return; + if(!completesize) { completesize = strlen(s)-1; completeidx = 0; }; + int idx = 0; + enumerate(idents, ident *, id, + if(strncmp(id->name, s+1, completesize)==0 && idx++==completeidx) + { + strcpy_s(s, "/"); + strcat_s(s, id->name); + }; + ); + completeidx++; + if(completeidx>=idx) completeidx = 0; +}; + +bool execfile(char *cfgfile) +{ + string s; + strcpy_s(s, cfgfile); + char *buf = loadfile(path(s), NULL); + if(!buf) return false; + execute(buf); + free(buf); + return true; +}; + +void exec(char *cfgfile) +{ + if(!execfile(cfgfile)) conoutf("could not read \"%s\"", (int)cfgfile); +}; + +// below the commands that implement a small imperative language. thanks to the semantics of +// () and [] expressions, any control construct can be defined trivially. + +void intset(char *name, int v) { string b; itoa(b, v); alias(name, b); }; + +void ifthen(char *cond, char *thenp, char *elsep) { execute(cond[0]!='0' ? thenp : elsep); }; +void loopa(char *times, char *body) { int t = atoi(times); loopi(t) { intset("i", i); execute(body); }; }; +void whilea(char *cond, char *body) { while(execute(cond)) execute(body); }; // can't get any simpler than this :) +void onrelease(bool on, char *body) { if(!on) execute(body); }; + +void concat(char *s) { alias("s", s); }; + +void concatword(char *s) +{ + for(char *a = s, *b = s; *a = *b; b++) if(*a!=' ') a++; + concat(s); +}; + +int listlen(char *a) +{ + if(!*a) return 0; + int n = 0; + while(*a) if(*a++==' ') n++; + return n+1; +}; + +void at(char *s, char *pos) +{ + int n = atoi(pos); + loopi(n) s += strspn(s += strcspn(s, " \0"), " "); + s[strcspn(s, " \0")] = 0; + concat(s); +}; + +COMMANDN(loop, loopa, ARG_2STR); +COMMANDN(while, whilea, ARG_2STR); +COMMANDN(if, ifthen, ARG_3STR); +COMMAND(onrelease, ARG_DWN1); +COMMAND(exec, ARG_1STR); +COMMAND(concat, ARG_VARI); +COMMAND(concatword, ARG_VARI); +COMMAND(at, ARG_2STR); +COMMAND(listlen, ARG_1EST); + +int add(int a, int b) { return a+b; }; COMMANDN(+, add, ARG_2EXP); +int mul(int a, int b) { return a*b; }; COMMANDN(*, mul, ARG_2EXP); +int sub(int a, int b) { return a-b; }; COMMANDN(-, sub, ARG_2EXP); +int divi(int a, int b) { return b ? a/b : 0; }; COMMANDN(div, divi, ARG_2EXP); +int mod(int a, int b) { return b ? a%b : 0; }; COMMAND(mod, ARG_2EXP); +int equal(int a, int b) { return (int)(a==b); }; COMMANDN(=, equal, ARG_2EXP); +int lt(int a, int b) { return (int)(ab); }; COMMANDN(>, gt, ARG_2EXP); + +int strcmpa(char *a, char *b) { return strcmp(a,b)==0; }; COMMANDN(strcmp, strcmpa, ARG_2EST); + +int rndn(int a) { return a>0 ? rnd(a) : 0; }; COMMANDN(rnd, rndn, ARG_1EXP); + + diff --git a/source/src/console.cpp b/source/src/console.cpp index 46700d06..78d53f53 100644 --- a/source/src/console.cpp +++ b/source/src/console.cpp @@ -1,244 +1,244 @@ -// console.cpp: the console buffer, its display, and command line control - -#include "cube.h" -#include - -struct cline { char *cref; int outtime; }; -vector conlines; - -const int ndraw = 5; -const int WORDWRAP = 80; -int conskip = 0; - -bool saycommandon = false; -string commandbuf; - -void setconskip(int n) -{ - conskip += n; - if(conskip<0) conskip = 0; -}; - -COMMANDN(conskip, setconskip, ARG_1INT); - -void conline(const char *sf, bool highlight) // add a line to the console buffer -{ - cline cl; - cl.cref = conlines.length()>100 ? conlines.pop().cref : newstringbuf(""); // constrain the buffer size - cl.outtime = lastmillis; // for how long to keep line on screen - conlines.insert(0,cl); - if(highlight) // show line in a different colour, for chat etc. - { - cl.cref[0] = '\f'; - cl.cref[1] = 0; - strcat_s(cl.cref, sf); - } - else - { - strcpy_s(cl.cref, sf); - }; - puts(cl.cref); -}; - -void conoutf(const char *s, int a, int b, int c) -{ - sprintf_sd(sf)(s, a, b, c); - s = sf; - int n = 0; - while(strlen(s)>WORDWRAP) // cut strings to fit on screen - { - string t; - strn0cpy(t, s, WORDWRAP+1); - conline(t, n++!=0); - s += WORDWRAP; - }; - conline(s, n!=0); -}; - -void renderconsole() // render buffer taking into account time & scrolling -{ - int nd = 0; - char *refs[ndraw]; - loopv(conlines) if(conskip ? i>=conskip-1 || i>=conlines.length()-ndraw : lastmillis-conlines[i].outtime<20000) - { - refs[nd++] = conlines[i].cref; - if(nd==ndraw) break; - }; - loopj(nd) - { - draw_text(refs[j], FONTH/3, (FONTH/4*5)*(nd-j-1)+FONTH/3, 2); - }; -}; - -// keymap is defined externally in keymap.cfg - -struct keym { int code; char *name; char *action; } keyms[256]; -int numkm = 0; - -void keymap(char *code, char *key, char *action) -{ - keyms[numkm].code = atoi(code); - keyms[numkm].name = newstring(key); - keyms[numkm++].action = newstringbuf(action); -}; - -COMMAND(keymap, ARG_3STR); - -void bindkey(char *key, char *action) -{ - for(char *x = key; *x; x++) *x = toupper(*x); - loopi(numkm) if(strcmp(keyms[i].name, key)==0) - { - strcpy_s(keyms[i].action, action); - return; - }; - conoutf("unknown key \"%s\"", (int)key); -}; - -COMMANDN(bind, bindkey, ARG_2STR); - -void saycommand(char *init) // turns input to the command line on or off -{ - SDL_EnableUNICODE(saycommandon = (init!=NULL)); - if(!editmode) keyrepeat(saycommandon); - if(!init) init = ""; - strcpy_s(commandbuf, init); -}; - -void mapmsg(char *s) { strn0cpy(hdr.maptitle, s, 128); }; - -COMMAND(saycommand, ARG_VARI); -COMMAND(mapmsg, ARG_1STR); - -#ifndef WIN32 -#include -#include -#endif - -void pasteconsole() -{ - #ifdef WIN32 - if(!IsClipboardFormatAvailable(CF_TEXT)) return; - if(!OpenClipboard(NULL)) return; - char *cb = (char *)GlobalLock(GetClipboardData(CF_TEXT)); - strcat_s(commandbuf, cb); - GlobalUnlock(cb); - CloseClipboard(); - #else - SDL_SysWMinfo wminfo; - SDL_VERSION(&wminfo.version); - wminfo.subsystem = SDL_SYSWM_X11; - if(!SDL_GetWMInfo(&wminfo)) return; - int cbsize; - char *cb = XFetchBytes(wminfo.info.x11.display, &cbsize); - if(!cb || !cbsize) return; - int commandlen = strlen(commandbuf); - for(char *cbline = cb, *cbend; commandlen + 1 < _MAXDEFSTR && cbline < &cb[cbsize]; cbline = cbend + 1) - { - cbend = (char *)memchr(cbline, '\0', &cb[cbsize] - cbline); - if(!cbend) cbend = &cb[cbsize]; - if(commandlen + cbend - cbline + 1 > _MAXDEFSTR) cbend = cbline + _MAXDEFSTR - commandlen - 1; - memcpy(&commandbuf[commandlen], cbline, cbend - cbline); - commandlen += cbend - cbline; - commandbuf[commandlen] = '\n'; - if(commandlen + 1 < _MAXDEFSTR && cbend < &cb[cbsize]) ++commandlen; - commandbuf[commandlen] = '\0'; - }; - XFree(cb); - #endif -}; - -cvector vhistory; -int histpos = 0; - -void history(int n) -{ - static bool rec = false; - if(!rec && n>=0 && n + +struct cline { char *cref; int outtime; }; +vector conlines; + +const int ndraw = 5; +const int WORDWRAP = 80; +int conskip = 0; + +bool saycommandon = false; +string commandbuf; + +void setconskip(int n) +{ + conskip += n; + if(conskip<0) conskip = 0; +}; + +COMMANDN(conskip, setconskip, ARG_1INT); + +void conline(const char *sf, bool highlight) // add a line to the console buffer +{ + cline cl; + cl.cref = conlines.length()>100 ? conlines.pop().cref : newstringbuf(""); // constrain the buffer size + cl.outtime = lastmillis; // for how long to keep line on screen + conlines.insert(0,cl); + if(highlight) // show line in a different colour, for chat etc. + { + cl.cref[0] = '\f'; + cl.cref[1] = 0; + strcat_s(cl.cref, sf); + } + else + { + strcpy_s(cl.cref, sf); + }; + puts(cl.cref); +}; + +void conoutf(const char *s, int a, int b, int c) +{ + sprintf_sd(sf)(s, a, b, c); + s = sf; + int n = 0; + while(strlen(s)>WORDWRAP) // cut strings to fit on screen + { + string t; + strn0cpy(t, s, WORDWRAP+1); + conline(t, n++!=0); + s += WORDWRAP; + }; + conline(s, n!=0); +}; + +void renderconsole() // render buffer taking into account time & scrolling +{ + int nd = 0; + char *refs[ndraw]; + loopv(conlines) if(conskip ? i>=conskip-1 || i>=conlines.length()-ndraw : lastmillis-conlines[i].outtime<20000) + { + refs[nd++] = conlines[i].cref; + if(nd==ndraw) break; + }; + loopj(nd) + { + draw_text(refs[j], FONTH/3, (FONTH/4*5)*(nd-j-1)+FONTH/3, 2); + }; +}; + +// keymap is defined externally in keymap.cfg + +struct keym { int code; char *name; char *action; } keyms[256]; +int numkm = 0; + +void keymap(char *code, char *key, char *action) +{ + keyms[numkm].code = atoi(code); + keyms[numkm].name = newstring(key); + keyms[numkm++].action = newstringbuf(action); +}; + +COMMAND(keymap, ARG_3STR); + +void bindkey(char *key, char *action) +{ + for(char *x = key; *x; x++) *x = toupper(*x); + loopi(numkm) if(strcmp(keyms[i].name, key)==0) + { + strcpy_s(keyms[i].action, action); + return; + }; + conoutf("unknown key \"%s\"", (int)key); +}; + +COMMANDN(bind, bindkey, ARG_2STR); + +void saycommand(char *init) // turns input to the command line on or off +{ + SDL_EnableUNICODE(saycommandon = (init!=NULL)); + if(!editmode) keyrepeat(saycommandon); + if(!init) init = ""; + strcpy_s(commandbuf, init); +}; + +void mapmsg(char *s) { strn0cpy(hdr.maptitle, s, 128); }; + +COMMAND(saycommand, ARG_VARI); +COMMAND(mapmsg, ARG_1STR); + +#ifndef WIN32 +#include +#include +#endif + +void pasteconsole() +{ + #ifdef WIN32 + if(!IsClipboardFormatAvailable(CF_TEXT)) return; + if(!OpenClipboard(NULL)) return; + char *cb = (char *)GlobalLock(GetClipboardData(CF_TEXT)); + strcat_s(commandbuf, cb); + GlobalUnlock(cb); + CloseClipboard(); + #else + SDL_SysWMinfo wminfo; + SDL_VERSION(&wminfo.version); + wminfo.subsystem = SDL_SYSWM_X11; + if(!SDL_GetWMInfo(&wminfo)) return; + int cbsize; + char *cb = XFetchBytes(wminfo.info.x11.display, &cbsize); + if(!cb || !cbsize) return; + int commandlen = strlen(commandbuf); + for(char *cbline = cb, *cbend; commandlen + 1 < _MAXDEFSTR && cbline < &cb[cbsize]; cbline = cbend + 1) + { + cbend = (char *)memchr(cbline, '\0', &cb[cbsize] - cbline); + if(!cbend) cbend = &cb[cbsize]; + if(commandlen + cbend - cbline + 1 > _MAXDEFSTR) cbend = cbline + _MAXDEFSTR - commandlen - 1; + memcpy(&commandbuf[commandlen], cbline, cbend - cbline); + commandlen += cbend - cbline; + commandbuf[commandlen] = '\n'; + if(commandlen + 1 < _MAXDEFSTR && cbend < &cb[cbsize]) ++commandlen; + commandbuf[commandlen] = '\0'; + }; + XFree(cb); + #endif +}; + +cvector vhistory; +int histpos = 0; + +void history(int n) +{ + static bool rec = false; + if(!rec && n>=0 && n8bit quantity is a little indian - int headersize; // sizeof(header) - int sfactor; // in bits - int numents; - char maptitle[128]; - uchar texlists[3][256]; - int waterlevel; - int reserved[15]; -}; - -#define SWS(w,x,y,s) (&(w)[(y)*(s)+(x)]) -#define SW(w,x,y) SWS(w,x,y,ssize) -#define S(x,y) SW(world,x,y) // convenient lookup of a lowest mip cube -#define SMALLEST_FACTOR 6 // determines number of mips there can be -#define DEFAULT_FACTOR 8 -#define LARGEST_FACTOR 11 // 10 is already insane -#define SOLID(x) ((x)->type==SOLID) -#define MINBORD 2 // 2 cubes from the edge of the world are always solid -#define OUTBORD(x,y) ((x)=ssize-MINBORD || (y)>=ssize-MINBORD) - -struct vec { float x, y, z; }; -struct block { int x, y, xs, ys; }; -struct mapmodelinfo { int rad, h, zoff, snap; char *name; }; - -enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSULT, GUN_GRENADE, NUMGUNS }; -enum { G_PRIMARY=0, G_SECONDARY, G_MELEE, G_GRENADE, G_NUM }; - - - -// Added by Rick -class CBot; - -#define MAX_STORED_LOCATIONS 7 - -extern int lastmillis; // last time (Moved up by Rick) - -class CPrevLocation -{ - int nextupdate; - -public: - vec prevloc[MAX_STORED_LOCATIONS]; - - CPrevLocation(void) : nextupdate(0) { Reset(); }; - void Reset(void) { loopi(MAX_STORED_LOCATIONS) prevloc[i].x=prevloc[i].y=prevloc[i].z=0.0f; }; - void Update(const vec &o) - { - extern float GetDistance(vec v1, vec v2); - - if (nextupdate > lastmillis) return; - if (GetDistance(o, prevloc[0]) >= 4.0f) - { - for(int i=(MAX_STORED_LOCATIONS-1);i>=1;i--) - prevloc[i] = prevloc[i-1]; - prevloc[0] = o; - } - nextupdate = lastmillis + 100; - }; -}; - -struct itemstat { int add, start, max, sound; }; -// End add - -struct md3state -{ - int anim; - int frm; - int lastTime; -}; - -enum // md3 animations -{ - BOTH_DEATH1 = 0, - 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 -}; - -struct dynent // players & monsters -{ - vec o, vel; // origin, velocity - float yaw, pitch, roll; // used as vec in one place - float oldpitch; - float maxspeed; // cubes per second, 24 for player - bool outsidemap; // from his eyes - bool inwater; - bool onfloor, jumpnext; - int move, strafe; - bool k_left, k_right, k_up, k_down; // see input code - int timeinair; // used for fake gravity - float radius, eyeheight, aboveeye; // bounding box size - int lastupdate, plag, ping; - int lifesequence; // sequence id for each respawn, used in damage test - int state; // one of CS_* below - int frags; - int health, armour; //armourtype, quadmillis; - int akimbomillis; - int gunselect, gunwait; - int lastaction, lastattackgun, lastmove; - bool attacking; - int ammo[NUMGUNS]; - int mag[NUMGUNS]; - int monsterstate; // one of M_* below, M_NONE means human - int mtype; // see monster.cpp - dynent *enemy; // monster wants to kill this entity - // Added by Rick: targetpitch - float targetpitch; // monster wants to look in this direction - // End add - float targetyaw; // monster wants to look in this direction - bool blocked, moving; // used by physics to signal ai - int trigger; // millis at which transition to another monsterstate takes place - vec attacktarget; // delayed attacks - int anger; // how many times already hit by fellow monster - int flagscore; // EDIT: AH - string name, team; - int startheight; - int shots; //keeps track of shots from auto weapons - bool reloading, hasarmour; - int primary; //primary gun - int nextprimary; - int skin, nextskin; - md3state animstate[3]; - - bool onladder; - int gravity; - bool isphysent; // hack - int thrownademillis; - int akimbo; - // Added by Rick - CBot *pBot; // Only used if this is a bot, points to the bot class if we are the host, - // for other clients its NULL - bool bIsBot; // Is this dynent a bot? - CPrevLocation PrevLocations; // Previous stored locations of this player - // End add by Rick -}; -// Moved from server.cpp by Rick -struct server_entity // server side version of "entity" type -{ - bool spawned; - int spawnsecs; -}; -// End move - -// EDIT: AH -enum { CTFF_INBASE = 0, CTFF_STOLEN, CTFF_DROPPED }; - -struct flaginfo -{ - entity *flag; - dynent *thief; - vec originalpos; - int state; // one of the types above - bool pick_ack; - flaginfo() : flag(0), thief(0), state(CTFF_INBASE), pick_ack(false) {}; -}; - -enum { PHYSENT_NONE = 0, NADE_ACTIVATED, NADE_THROWED }; - -struct physent : dynent // nades, gibs -{ - int millis, timetolife, state; // see enum above - dynent *owner; -}; - -#define SAVEGAMEVERSION 6 // bump if dynent/netprotocol changes or any other savegame/demo data bumped from 5 - -//enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off -enum { M_NONE = 0, M_SEARCH, M_HOME, M_ATTACKING, M_PAIN, M_SLEEP, M_AIMING, M_NADE /* bloody hack*/ }; // monster states - -#define MAXCLIENTS 256 // in a multiplayer game, can be arbitrarily changed -#define MAXTRANS 5000 // max amount of data to swallow in 1 go -#define CUBE_SERVER_PORT 28765 -#define CUBE_SERVINFO_PORT 28766 -#define PROTOCOL_VERSION 121 // bump when protocol changes - -// network messages codes, c2s, c2c, s2c -enum -{ - SV_INITS2C = 0, SV_INITC2S, SV_POS, SV_TEXT, SV_SOUND, SV_CDIS, - SV_DIED, SV_DAMAGE, SV_SHOT, SV_FRAGS, - SV_TIMEUP, SV_EDITENT, SV_MAPRELOAD, SV_ITEMACC, - SV_MAPCHANGE, SV_ITEMSPAWN, SV_ITEMPICKUP, SV_DENIED, - SV_PING, SV_PONG, SV_CLIENTPING, SV_GAMEMODE, - SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, - SV_SENDMAP, SV_RECVMAP, SV_SERVMSG, SV_ITEMLIST, SV_WEAPCHANGE, - SV_MODELSKIN, - SV_FLAGPICKUP, SV_FLAGDROP, SV_FLAGRETURN, SV_FLAGSCORE, SV_FLAGINFO, SV_FLAGS, //EDIT: AH - // Added by Rick: Bot specific messages - SV_BOTSOUND, SV_BOTDIS, SV_BOTDIED, SV_CLIENT2BOTDMG, SV_BOT2BOTDMG, - SV_BOTFRAGS, SV_ADDBOT, SV_BOTUPDATE, SV_BOTCOMMAND, - // End add - SV_BOTITEMPICKUP, SV_BOT2CLIENTDMG, SV_DIEDBYBOT, - SV_EXT, -}; - -enum { CS_ALIVE = 0, CS_DEAD, CS_LAGGED, CS_EDITING }; - -// hardcoded sounds, defined in sounds.cfg -enum -{ - S_JUMP = 0, S_LAND, - S_KNIFE, - S_PISTOL, S_RPISTOL, - S_SHOTGUN, S_RSHOTGUN, - S_SUBGUN, S_RSUBGUN, - S_SNIPER, S_RSNIPER, //10 - S_ASSULT, S_RASSULT, - S_GRENADE, - S_ITEMAMMO, S_ITEMHEALTH, - S_ITEMARMOUR, S_ITEMPUP, S_ITEMSPAWN, - S_NOAMMO, S_PUPOUT, //20 - S_PAIN1, S_PAIN2, S_PAIN3, S_PAIN4, S_PAIN5, S_PAIN6, - S_DIE1, S_DIE2, S_SUICIDE, - S_FALL1, S_FALL2, //31 - S_FLAUNCH, S_FEXPLODE, //33 - S_SPLASH1, S_SPLASH2, - S_GRUNT1, S_GRUNT2, S_RUMBLE, - S_FLAGDROP, S_FLAGPICKUP, S_FLAGRETURN, S_FLAGSCORE, - S_GRENADEPULL, S_GRENADETHROW, - S_RAKIMBO, - S_NULL -}; - -// vertex array format - -struct vertex { float u, v, x, y, z; uchar r, g, b, a; }; - -typedef vector dvector; -typedef vector cvector; -typedef vector ivector; - -// globals ooh naughty - -extern sqr *world, *wmip[]; // map data, the mips are sequential 2D arrays in memory -extern header hdr; // current map header -extern int sfactor, ssize; // ssize = 2^sfactor -extern int cubicsize, mipsize; // cubicsize = ssize^2 -extern dynent *player1; // special client ent that receives input and acts as camera -extern dvector players; // all the other clients (in multiplayer) -extern vectorphysents; -extern bool editmode; -extern vector ents; // map entities -extern vec worldpos; // current target of the crosshair in the world -extern int lastmillis; // last time -extern int curtime; // current frame time -extern int gamemode, nextmode; -extern int xtraverts; -extern bool demoplayback; - - -#define DMF 16.0f -#define DAF 1.0f -#define DVF 100.0f - -#define VIRTW 2400 // virtual screen size for text & HUD -#define VIRTH 1800 -#define FONTH 64 -#define PIXELTAB (VIRTW/12) - -#define PI (3.1415927f) -#define PI2 (2*PI) - -// simplistic vector ops -#define dotprod(u,v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z) -#define vmul(u,f) { (u).x *= (f); (u).y *= (f); (u).z *= (f); } -#define vdiv(u,f) { (u).x /= (f); (u).y /= (f); (u).z /= (f); } -#define vadd(u,v) { (u).x += (v).x; (u).y += (v).y; (u).z += (v).z; }; -#define vsub(u,v) { (u).x -= (v).x; (u).y -= (v).y; (u).z -= (v).z; }; -#define vdist(d,v,e,s) vec v = s; vsub(v,e); float d = (float)sqrt(dotprod(v,v)); -#define vdistsquared(d,v,e,s) vec v = s; vsub(v,e); float d = (dotprod(v,v)); -#define vreject(v,u,max) ((v).x>(u).x+(max) || (v).x<(u).x-(max) || (v).y>(u).y+(max) || (v).y<(u).y-(max)) -#define vlinterp(v,f,u,g) { (v).x = (v).x*f+(u).x*g; (v).y = (v).y*f+(u).y*g; (v).z = (v).z*f+(u).z*g; } -// Added by Rick (compares 2 vectors) -#define vis(v1,v2) ((v1.x==v2.x) && (v1.y==v2.y) && (v1.z==v2.z)) -// End add by Rick -#define sgetstr() { char *t = text; int tlen=0; do { *t = getint(p); } while(*t++ && ++tlen -#endif - -#include - -#include -#include -#include - -#include -#include - -#include - -#include - -// Added by Rick -extern ENetHost *clienthost; -inline bool ishost(void) { return !clienthost; }; - -void splaysound(int n, vec *loc=0); -void addteamscore(dynent *d); -void renderscore(dynent *d); -extern void conoutf(const char *s, int a = 0, int b = 0, int c = 0); // Moved from protos.h -extern void particle_trail(int type, int fade, vec &from, vec &to); // Moved from protos.h -extern bool listenserv; -extern bool intermission; -#include "bot/bot.h" -// End add by Rick - -#include "protos.h" // external function decls - +/// one big bad include file for the whole engine... nasty! + +#include "tools.h" + +enum // block types, order matters! +{ + SOLID = 0, // entirely solid cube [only specifies wtex] + CORNER, // half full corner of a wall + FHF, // floor heightfield using neighbour vdelta values + CHF, // idem ceiling + SPACE, // entirely empty cube + SEMISOLID, // generated by mipmapping + MAXTYPE +}; + +struct sqr +{ + uchar type; // one of the above + char floor, ceil; // height, in cubes + uchar wtex, ftex, ctex; // wall/floor/ceil texture ids + uchar r, g, b; // light value at upper left vertex + uchar vdelta; // vertex delta, used for heightfield cubes + char defer; // used in mipmapping, when true this cube is not a perfect mip + char occluded; // true when occluded + uchar utex; // upper wall tex id + uchar tag; // used by triggers +}; + +enum // hardcoded texture numbers +{ + DEFAULT_SKY = 0, + DEFAULT_LIQUID, + DEFAULT_WALL, + DEFAULT_FLOOR, + DEFAULT_CEIL +}; + +enum // static entity types +{ + NOTUSED = 0, // entity slot not in use in map + LIGHT, // lightsource, attr1 = radius, attr2 = intensity + PLAYERSTART, // attr1 = angle + I_CLIPS, I_AMMO,I_GRENADE, + I_HEALTH, I_ARMOUR, I_AKIMBO, + MAPMODEL, // attr1 = angle, attr2 = idx + CARROT, // attr1 = tag, attr2 = type + LADDER, + CTF_FLAG, // attr1 = angle, attr2 = red/blue + SMOKESOURCE, // attr1 = type, attr2 = particles + MAXENTTYPES +}; + +struct persistent_entity // map entity +{ + short x, y, z; // cube aligned position + short attr1; + uchar type; // type is one of the above + uchar attr2, attr3, attr4; +}; + +struct entity : public persistent_entity +{ + bool spawned; // the only dynamic state of a map entity +}; + +#define MAPVERSION 6 // bump if map format changes, see worldio.cpp + +struct header // map file format header +{ + char head[4]; // "CUBE" + int version; // any >8bit quantity is a little indian + int headersize; // sizeof(header) + int sfactor; // in bits + int numents; + char maptitle[128]; + uchar texlists[3][256]; + int waterlevel; + int reserved[15]; +}; + +#define SWS(w,x,y,s) (&(w)[(y)*(s)+(x)]) +#define SW(w,x,y) SWS(w,x,y,ssize) +#define S(x,y) SW(world,x,y) // convenient lookup of a lowest mip cube +#define SMALLEST_FACTOR 6 // determines number of mips there can be +#define DEFAULT_FACTOR 8 +#define LARGEST_FACTOR 11 // 10 is already insane +#define SOLID(x) ((x)->type==SOLID) +#define MINBORD 2 // 2 cubes from the edge of the world are always solid +#define OUTBORD(x,y) ((x)=ssize-MINBORD || (y)>=ssize-MINBORD) + +struct vec { float x, y, z; }; +struct block { int x, y, xs, ys; }; +struct mapmodelinfo { int rad, h, zoff, snap; char *name; }; + +enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSULT, GUN_GRENADE, NUMGUNS }; +enum { G_PRIMARY=0, G_SECONDARY, G_MELEE, G_GRENADE, G_NUM }; + + + +// Added by Rick +class CBot; + +#define MAX_STORED_LOCATIONS 7 + +extern int lastmillis; // last time (Moved up by Rick) + +class CPrevLocation +{ + int nextupdate; + +public: + vec prevloc[MAX_STORED_LOCATIONS]; + + CPrevLocation(void) : nextupdate(0) { Reset(); }; + void Reset(void) { loopi(MAX_STORED_LOCATIONS) prevloc[i].x=prevloc[i].y=prevloc[i].z=0.0f; }; + void Update(const vec &o) + { + extern float GetDistance(vec v1, vec v2); + + if (nextupdate > lastmillis) return; + if (GetDistance(o, prevloc[0]) >= 4.0f) + { + for(int i=(MAX_STORED_LOCATIONS-1);i>=1;i--) + prevloc[i] = prevloc[i-1]; + prevloc[0] = o; + } + nextupdate = lastmillis + 100; + }; +}; + +struct itemstat { int add, start, max, sound; }; +// End add + +struct md3state +{ + int anim; + int frm; + int lastTime; +}; + +enum // md3 animations +{ + BOTH_DEATH1 = 0, + 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 +}; + +struct dynent // players & monsters +{ + vec o, vel; // origin, velocity + float yaw, pitch, roll; // used as vec in one place + float oldpitch; + float maxspeed; // cubes per second, 24 for player + bool outsidemap; // from his eyes + bool inwater; + bool onfloor, jumpnext; + int move, strafe; + bool k_left, k_right, k_up, k_down; // see input code + int timeinair; // used for fake gravity + float radius, eyeheight, aboveeye; // bounding box size + int lastupdate, plag, ping; + int lifesequence; // sequence id for each respawn, used in damage test + int state; // one of CS_* below + int frags; + int health, armour; //armourtype, quadmillis; + int akimbomillis; + int gunselect, gunwait; + int lastaction, lastattackgun, lastmove; + bool attacking; + int ammo[NUMGUNS]; + int mag[NUMGUNS]; + int monsterstate; // one of M_* below, M_NONE means human + int mtype; // see monster.cpp + dynent *enemy; // monster wants to kill this entity + // Added by Rick: targetpitch + float targetpitch; // monster wants to look in this direction + // End add + float targetyaw; // monster wants to look in this direction + bool blocked, moving; // used by physics to signal ai + int trigger; // millis at which transition to another monsterstate takes place + vec attacktarget; // delayed attacks + int anger; // how many times already hit by fellow monster + int flagscore; // EDIT: AH + string name, team; + int startheight; + int shots; //keeps track of shots from auto weapons + bool reloading, hasarmour; + int primary; //primary gun + int nextprimary; + int skin, nextskin; + md3state animstate[3]; + + bool onladder; + int gravity; + bool isphysent; // hack + int thrownademillis; + int akimbo; + // Added by Rick + CBot *pBot; // Only used if this is a bot, points to the bot class if we are the host, + // for other clients its NULL + bool bIsBot; // Is this dynent a bot? + CPrevLocation PrevLocations; // Previous stored locations of this player + // End add by Rick +}; +// Moved from server.cpp by Rick +struct server_entity // server side version of "entity" type +{ + bool spawned; + int spawnsecs; +}; +// End move + +// EDIT: AH +enum { CTFF_INBASE = 0, CTFF_STOLEN, CTFF_DROPPED }; + +struct flaginfo +{ + entity *flag; + dynent *thief; + vec originalpos; + int state; // one of the types above + bool pick_ack; + flaginfo() : flag(0), thief(0), state(CTFF_INBASE), pick_ack(false) {}; +}; + +enum { PHYSENT_NONE = 0, NADE_ACTIVATED, NADE_THROWED }; + +struct physent : dynent // nades, gibs +{ + int millis, timetolife, state; // see enum above + dynent *owner; +}; + +#define SAVEGAMEVERSION 6 // bump if dynent/netprotocol changes or any other savegame/demo data bumped from 5 + +//enum { A_BLUE, A_GREEN, A_YELLOW }; // armour types... take 20/40/60 % off +enum { M_NONE = 0, M_SEARCH, M_HOME, M_ATTACKING, M_PAIN, M_SLEEP, M_AIMING, M_NADE /* bloody hack*/ }; // monster states + +#define MAXCLIENTS 256 // in a multiplayer game, can be arbitrarily changed +#define MAXTRANS 5000 // max amount of data to swallow in 1 go +#define CUBE_SERVER_PORT 28765 +#define CUBE_SERVINFO_PORT 28766 +#define PROTOCOL_VERSION 121 // bump when protocol changes + +// network messages codes, c2s, c2c, s2c +enum +{ + SV_INITS2C = 0, SV_INITC2S, SV_POS, SV_TEXT, SV_SOUND, SV_CDIS, + SV_DIED, SV_DAMAGE, SV_SHOT, SV_FRAGS, + SV_TIMEUP, SV_EDITENT, SV_MAPRELOAD, SV_ITEMACC, + SV_MAPCHANGE, SV_ITEMSPAWN, SV_ITEMPICKUP, SV_DENIED, + SV_PING, SV_PONG, SV_CLIENTPING, SV_GAMEMODE, + SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, + SV_SENDMAP, SV_RECVMAP, SV_SERVMSG, SV_ITEMLIST, SV_WEAPCHANGE, + SV_MODELSKIN, + SV_FLAGPICKUP, SV_FLAGDROP, SV_FLAGRETURN, SV_FLAGSCORE, SV_FLAGINFO, SV_FLAGS, //EDIT: AH + // Added by Rick: Bot specific messages + SV_BOTSOUND, SV_BOTDIS, SV_BOTDIED, SV_CLIENT2BOTDMG, SV_BOT2BOTDMG, + SV_BOTFRAGS, SV_ADDBOT, SV_BOTUPDATE, SV_BOTCOMMAND, + // End add + SV_BOTITEMPICKUP, SV_BOT2CLIENTDMG, SV_DIEDBYBOT, + SV_EXT, +}; + +enum { CS_ALIVE = 0, CS_DEAD, CS_LAGGED, CS_EDITING }; + +// hardcoded sounds, defined in sounds.cfg +enum +{ + S_JUMP = 0, S_LAND, + S_KNIFE, + S_PISTOL, S_RPISTOL, + S_SHOTGUN, S_RSHOTGUN, + S_SUBGUN, S_RSUBGUN, + S_SNIPER, S_RSNIPER, //10 + S_ASSULT, S_RASSULT, + S_GRENADE, + S_ITEMAMMO, S_ITEMHEALTH, + S_ITEMARMOUR, S_ITEMPUP, S_ITEMSPAWN, + S_NOAMMO, S_PUPOUT, //20 + S_PAIN1, S_PAIN2, S_PAIN3, S_PAIN4, S_PAIN5, S_PAIN6, + S_DIE1, S_DIE2, S_SUICIDE, + S_FALL1, S_FALL2, //31 + S_FLAUNCH, S_FEXPLODE, //33 + S_SPLASH1, S_SPLASH2, + S_GRUNT1, S_GRUNT2, S_RUMBLE, + S_FLAGDROP, S_FLAGPICKUP, S_FLAGRETURN, S_FLAGSCORE, + S_GRENADEPULL, S_GRENADETHROW, + S_RAKIMBO, + S_NULL +}; + +// vertex array format + +struct vertex { float u, v, x, y, z; uchar r, g, b, a; }; + +typedef vector dvector; +typedef vector cvector; +typedef vector ivector; + +// globals ooh naughty + +extern sqr *world, *wmip[]; // map data, the mips are sequential 2D arrays in memory +extern header hdr; // current map header +extern int sfactor, ssize; // ssize = 2^sfactor +extern int cubicsize, mipsize; // cubicsize = ssize^2 +extern dynent *player1; // special client ent that receives input and acts as camera +extern dvector players; // all the other clients (in multiplayer) +extern vectorphysents; +extern bool editmode; +extern vector ents; // map entities +extern vec worldpos; // current target of the crosshair in the world +extern int lastmillis; // last time +extern int curtime; // current frame time +extern int gamemode, nextmode; +extern int xtraverts; +extern bool demoplayback; + + +#define DMF 16.0f +#define DAF 1.0f +#define DVF 100.0f + +#define VIRTW 2400 // virtual screen size for text & HUD +#define VIRTH 1800 +#define FONTH 64 +#define PIXELTAB (VIRTW/12) + +#define PI (3.1415927f) +#define PI2 (2*PI) + +// simplistic vector ops +#define dotprod(u,v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z) +#define vmul(u,f) { (u).x *= (f); (u).y *= (f); (u).z *= (f); } +#define vdiv(u,f) { (u).x /= (f); (u).y /= (f); (u).z /= (f); } +#define vadd(u,v) { (u).x += (v).x; (u).y += (v).y; (u).z += (v).z; }; +#define vsub(u,v) { (u).x -= (v).x; (u).y -= (v).y; (u).z -= (v).z; }; +#define vdist(d,v,e,s) vec v = s; vsub(v,e); float d = (float)sqrt(dotprod(v,v)); +#define vdistsquared(d,v,e,s) vec v = s; vsub(v,e); float d = (dotprod(v,v)); +#define vreject(v,u,max) ((v).x>(u).x+(max) || (v).x<(u).x-(max) || (v).y>(u).y+(max) || (v).y<(u).y-(max)) +#define vlinterp(v,f,u,g) { (v).x = (v).x*f+(u).x*g; (v).y = (v).y*f+(u).y*g; (v).z = (v).z*f+(u).z*g; } +// Added by Rick (compares 2 vectors) +#define vis(v1,v2) ((v1.x==v2.x) && (v1.y==v2.y) && (v1.z==v2.z)) +// End add by Rick +#define sgetstr() { char *t = text; int tlen=0; do { *t = getint(p); } while(*t++ && ++tlen +#endif + +#include + +#include +#include +#include + +#include +#include + +#include + +#include + +// Added by Rick +extern ENetHost *clienthost; +inline bool ishost(void) { return !clienthost; }; + +void splaysound(int n, vec *loc=0); +void addteamscore(dynent *d); +void renderscore(dynent *d); +extern void conoutf(const char *s, int a = 0, int b = 0, int c = 0); // Moved from protos.h +extern void particle_trail(int type, int fade, vec &from, vec &to); // Moved from protos.h +extern bool listenserv; +extern bool intermission; +#include "bot/bot.h" +// End add by Rick + +#include "protos.h" // external function decls + diff --git a/source/src/editing.cpp b/source/src/editing.cpp index 2063d899..ef8fb57e 100644 --- a/source/src/editing.cpp +++ b/source/src/editing.cpp @@ -1,484 +1,484 @@ -// editing.cpp: most map editing commands go here, entity editing commands are in world.cpp - -#include "cube.h" - -bool editmode = false; - -// the current selection, used by almost all editing commands -// invariant: all code assumes that these are kept inside MINBORD distance of the edge of the map - -block sel = -{ - variable("selx", 0, 0, 4096, &sel.x, NULL, false), - variable("sely", 0, 0, 4096, &sel.y, NULL, false), - variable("selxs", 0, 0, 4096, &sel.xs, NULL, false), - variable("selys", 0, 0, 4096, &sel.ys, NULL, false), -}; - -int selh = 0; -bool selset = false; - -#define loopselxy(b) { makeundo(); loop(x,sel.xs) loop(y,sel.ys) { sqr *s = S(sel.x+x, sel.y+y); b; }; remip(sel); } - -int cx, cy, ch; - -int curedittex[] = { -1, -1, -1 }; - -bool dragging = false; -int lastx, lasty, lasth; - -int lasttype = 0, lasttex = 0; -sqr rtex; - -VAR(editing,0,0,1); - -void toggleedit() -{ - if(player1->state==CS_DEAD) return; // do not allow dead players to edit to avoid state confusion - if(!editmode && !allowedittoggle()) return; // not in most multiplayer modes - if(!(editmode = !editmode)) - { - settagareas(); // reset triggers to allow quick playtesting - entinmap(player1); // find spawn closest to current floating pos - } - else - { - resettagareas(); // clear trigger areas to allow them to be edited - player1->health = 100; - //put call to clear/restar gamemode - projreset(); - }; - keyrepeat(editmode); - selset = false; - editing = editmode; -}; - -COMMANDN(edittoggle, toggleedit, ARG_NONE); - -void correctsel() // ensures above invariant -{ - selset = !OUTBORD(sel.x, sel.y); - int bsize = ssize-MINBORD; - if(sel.xs+sel.x>bsize) sel.xs = bsize-sel.x; - if(sel.ys+sel.y>bsize) sel.ys = bsize-sel.y; - if(sel.xs<=0 || sel.ys<=0) selset = false; -}; - -bool noteditmode() -{ - correctsel(); - if(!editmode) conoutf("this function is only allowed in edit mode"); - return !editmode; -}; - -bool noselection() -{ - if(!selset) conoutf("no selection"); - return !selset; -}; - -#define EDITSEL if(noteditmode() || noselection()) return; -#define EDITSELMP if(noteditmode() || noselection() || multiplayer()) return; -#define EDITMP if(noteditmode() || multiplayer()) return; - -void selectpos(int x, int y, int xs, int ys) -{ - block s = { x, y, xs, ys }; - sel = s; - selh = 0; - correctsel(); -}; - -void makesel() -{ - block s = { min(lastx,cx), min(lasty,cy), abs(lastx-cx)+1, abs(lasty-cy)+1 }; - sel = s; - selh = max(lasth,ch); - correctsel(); - if(selset) rtex = *S(sel.x, sel.y); -}; - -VAR(flrceil,0,0,2); - -float sheight(sqr *s, sqr *t, float z) // finds out z height when cursor points at wall -{ - return !flrceil //z-s->floorceil-z - ? (s->type==FHF ? s->floor-t->vdelta/4.0f : (float)s->floor) - : (s->type==CHF ? s->ceil+t->vdelta/4.0f : (float)s->ceil); -}; - -void cursorupdate() // called every frame from hud -{ - flrceil = ((int)(player1->pitch>=0))*2; - - volatile float x = worldpos.x; // volatile needed to prevent msvc7 optimizer bug? - volatile float y = worldpos.y; - volatile float z = worldpos.z; - - cx = (int)x; - cy = (int)y; - - if(OUTBORD(cx, cy)) return; - sqr *s = S(cx,cy); - - if(fabs(sheight(s,s,z)-z)>1) // selected wall - { - x += x>player1->o.x ? 0.5f : -0.5f; // find right wall cube - y += y>player1->o.y ? 0.5f : -0.5f; - - cx = (int)x; - cy = (int)y; - - if(OUTBORD(cx, cy)) return; - }; - - if(dragging) makesel(); - - const int GRIDSIZE = 5; - const float GRIDW = 0.5f; - const float GRID8 = 2.0f; - const float GRIDS = 2.0f; - const int GRIDM = 0x7; - - // render editing grid - - for(int ix = cx-GRIDSIZE; ix<=cx+GRIDSIZE; ix++) for(int iy = cy-GRIDSIZE; iy<=cy+GRIDSIZE; iy++) - { - if(OUTBORD(ix, iy)) continue; - sqr *s = S(ix,iy); - if(SOLID(s)) continue; - float h1 = sheight(s, s, z); - float h2 = sheight(s, SWS(s,1,0,ssize), z); - float h3 = sheight(s, SWS(s,1,1,ssize), z); - float h4 = sheight(s, SWS(s,0,1,ssize), z); - if(s->tag) linestyle(GRIDW, 0xFF, 0x40, 0x40); - else if(s->type==FHF || s->type==CHF) linestyle(GRIDW, 0x80, 0xFF, 0x80); - else linestyle(GRIDW, 0x80, 0x80, 0x80); - block b = { ix, iy, 1, 1 }; - box(b, h1, h2, h3, h4); - linestyle(GRID8, 0x40, 0x40, 0xFF); - if(!(ix&GRIDM)) line(ix, iy, h1, ix, iy+1, h4); - if(!(ix+1&GRIDM)) line(ix+1, iy, h2, ix+1, iy+1, h3); - if(!(iy&GRIDM)) line(ix, iy, h1, ix+1, iy, h2); - if(!(iy+1&GRIDM)) line(ix, iy+1, h4, ix+1, iy+1, h3); - }; - - if(!SOLID(s)) - { - float ih = sheight(s, s, z); - linestyle(GRIDS, 0xFF, 0xFF, 0xFF); - block b = { cx, cy, 1, 1 }; - box(b, ih, sheight(s, SWS(s,1,0,ssize), z), sheight(s, SWS(s,1,1,ssize), z), sheight(s, SWS(s,0,1,ssize), z)); - linestyle(GRIDS, 0xFF, 0x00, 0x00); - dot(cx, cy, ih); - ch = (int)ih; - }; - - if(selset) - { - linestyle(GRIDS, 0xFF, 0x40, 0x40); - box(sel, (float)selh, (float)selh, (float)selh, (float)selh); - }; -}; - -vector undos; // unlimited undo -VAR(undomegs, 0, 1, 10); // bounded by n megs - -void pruneundos(int maxremain) // bound memory -{ - int t = 0; - loopvrev(undos) - { - t += undos[i]->xs*undos[i]->ys*sizeof(sqr); - if(t>maxremain) free(undos.remove(i)); - }; -}; - -void makeundo() -{ - undos.add(blockcopy(sel)); - pruneundos(undomegs<<20); -}; - -void editundo() -{ - EDITMP; - if(undos.empty()) { conoutf("nothing more to undo"); return; }; - block *p = undos.pop(); - blockpaste(*p); - free(p); -}; - -block *copybuf = NULL; - -void copy() -{ - EDITSELMP; - if(copybuf) free(copybuf); - copybuf = blockcopy(sel); -}; - -void paste() -{ - EDITMP; - if(!copybuf) { conoutf("nothing to paste"); return; }; - sel.xs = copybuf->xs; - sel.ys = copybuf->ys; - correctsel(); - if(!selset || sel.xs!=copybuf->xs || sel.ys!=copybuf->ys) { conoutf("incorrect selection"); return; }; - makeundo(); - copybuf->x = sel.x; - copybuf->y = sel.y; - blockpaste(*copybuf); -}; - -void tofronttex() // maintain most recently used of the texture lists when applying texture -{ - loopi(3) - { - int c = curedittex[i]; - if(c>=0) - { - uchar *p = hdr.texlists[i]; - int t = p[c]; - for(int a = c-1; a>=0; a--) p[a+1] = p[a]; - p[0] = t; - curedittex[i] = -1; - }; - }; -}; - -void editdrag(bool isdown) -{ - if(dragging = isdown) - { - lastx = cx; - lasty = cy; - lasth = ch; - selset = false; - tofronttex(); - }; - makesel(); -}; - -// the core editing function. all the *xy functions perform the core operations -// and are also called directly from the network, the function below it is strictly -// triggered locally. They all have very similar structure. - -void editheightxy(bool isfloor, int amount, block &sel) -{ - loopselxy(if(isfloor) - { - s->floor += amount; - if(s->floor>=s->ceil) s->floor = s->ceil-1; - } - else - { - s->ceil += amount; - if(s->ceil<=s->floor) s->ceil = s->floor+1; - }); -}; - -void editheight(int flr, int amount) -{ - EDITSEL; - bool isfloor = flr==0; - editheightxy(isfloor, amount, sel); - addmsg(1, 7, SV_EDITH, sel.x, sel.y, sel.xs, sel.ys, isfloor, amount); -}; - -COMMAND(editheight, ARG_2INT); - -void edittexxy(int type, int t, block &sel) -{ - loopselxy(switch(type) - { - case 0: s->ftex = t; break; - case 1: s->wtex = t; break; - case 2: s->ctex = t; break; - case 3: s->utex = t; break; - }); -}; - -void edittex(int type, int dir) -{ - EDITSEL; - if(type<0 || type>3) return; - if(type!=lasttype) { tofronttex(); lasttype = type; }; - int atype = type==3 ? 1 : type; - int i = curedittex[atype]; - i = i<0 ? 0 : i+dir; - curedittex[atype] = i = min(max(i, 0), 255); - int t = lasttex = hdr.texlists[atype][i]; - edittexxy(type, t, sel); - addmsg(1, 7, SV_EDITT, sel.x, sel.y, sel.xs, sel.ys, type, t); -}; - -void replace() -{ - EDITSELMP; - loop(x,ssize) loop(y,ssize) - { - sqr *s = S(x, y); - switch(lasttype) - { - case 0: if(s->ftex == rtex.ftex) s->ftex = lasttex; break; - case 1: if(s->wtex == rtex.wtex) s->wtex = lasttex; break; - case 2: if(s->ctex == rtex.ctex) s->ctex = lasttex; break; - case 3: if(s->utex == rtex.utex) s->utex = lasttex; break; - }; - }; - block b = { 0, 0, ssize, ssize }; - remip(b); -}; - -void edittypexy(int type, block &sel) -{ - loopselxy(s->type = type); -}; - -void edittype(int type) -{ - EDITSEL; - if(type==CORNER && (sel.xs!=sel.ys || sel.xs==3 || sel.xs>4 && sel.xs!=8 - || sel.x&~-sel.xs || sel.y&~-sel.ys)) - { conoutf("corner selection must be power of 2 aligned"); return; }; - edittypexy(type, sel); - addmsg(1, 6, SV_EDITS, sel.x, sel.y, sel.xs, sel.ys, type); -}; - -void heightfield(int t) { edittype(t==0 ? FHF : CHF); }; -void solid(int t) { edittype(t==0 ? SPACE : SOLID); }; -void corner() { edittype(CORNER); }; - -COMMAND(heightfield, ARG_1INT); -COMMAND(solid, ARG_1INT); -COMMAND(corner, ARG_NONE); - -void editequalisexy(bool isfloor, block &sel) -{ - int low = 127, hi = -128; - loopselxy( - { - if(s->floorfloor; - if(s->ceil>hi) hi = s->ceil; - }); - loopselxy( - { - if(isfloor) s->floor = low; else s->ceil = hi; - if(s->floor>=s->ceil) s->floor = s->ceil-1; - }); -}; - -void equalize(int flr) -{ - bool isfloor = flr==0; - EDITSEL; - editequalisexy(isfloor, sel); - addmsg(1, 6, SV_EDITE, sel.x, sel.y, sel.xs, sel.ys, isfloor); -}; - -COMMAND(equalize, ARG_1INT); - -void setvdeltaxy(int delta, block &sel) -{ - loopselxy(s->vdelta = max(s->vdelta+delta, 0)); -}; - -void setvdelta(int delta) -{ - EDITSEL; - setvdeltaxy(delta, sel); - addmsg(1, 6, SV_EDITD, sel.x, sel.y, sel.xs, sel.ys, delta); -}; - -const int MAXARCHVERT = 50; -int archverts[MAXARCHVERT][MAXARCHVERT]; -bool archvinit = false; - -void archvertex(int span, int vert, int delta) -{ - if(!archvinit) - { - archvinit = true; - loop(s,MAXARCHVERT) loop(v,MAXARCHVERT) archverts[s][v] = 0; - }; - if(span>=MAXARCHVERT || vert>=MAXARCHVERT || span<0 || vert<0) return; - archverts[span][vert] = delta; -}; - -void arch(int sidedelta, int _a) -{ - EDITSELMP; - sel.xs++; - sel.ys++; - if(sel.xs>MAXARCHVERT) sel.xs = MAXARCHVERT; - if(sel.ys>MAXARCHVERT) sel.ys = MAXARCHVERT; - loopselxy(s->vdelta = - sel.xs>sel.ys - ? (archverts[sel.xs-1][x] + (y==0 || y==sel.ys-1 ? sidedelta : 0)) - : (archverts[sel.ys-1][y] + (x==0 || x==sel.xs-1 ? sidedelta : 0))); -}; - -void slope(int xd, int yd) -{ - EDITSELMP; - int off = 0; - if(xd<0) off -= xd*sel.xs; - if(yd<0) off -= yd*sel.ys; - sel.xs++; - sel.ys++; - loopselxy(s->vdelta = xd*x+yd*y+off); -}; - -void perlin(int scale, int seed, int psize) -{ - EDITSELMP; - sel.xs++; - sel.ys++; - makeundo(); - sel.xs--; - sel.ys--; - perlinarea(sel, scale, seed, psize); - sel.xs++; - sel.ys++; - remip(sel); - sel.xs--; - sel.ys--; -}; - -VARF(fullbright, 0, 0, 1, - if(fullbright) - { - if(noteditmode()) return; - loopi(mipsize) world[i].r = world[i].g = world[i].b = 176; - }; -); - -void edittag(int tag) -{ - EDITSELMP; - loopselxy(s->tag = tag); -}; - -void newent(char *what, char *a1, char *a2, char *a3, char *a4) -{ - EDITSEL; - newentity(sel.x, sel.y, (int)player1->o.z, what, ATOI(a1), ATOI(a2), ATOI(a3), ATOI(a4)); -}; - -COMMANDN(select, selectpos, ARG_4INT); -COMMAND(edittag, ARG_1INT); -COMMAND(replace, ARG_NONE); -COMMAND(archvertex, ARG_3INT); -COMMAND(arch, ARG_2INT); -COMMAND(slope, ARG_2INT); -COMMANDN(vdelta, setvdelta, ARG_1INT); -COMMANDN(undo, editundo, ARG_NONE); -COMMAND(copy, ARG_NONE); -COMMAND(paste, ARG_NONE); -COMMAND(edittex, ARG_2INT); -COMMAND(newent, ARG_5STR); -COMMAND(perlin, ARG_3INT); - - +// editing.cpp: most map editing commands go here, entity editing commands are in world.cpp + +#include "cube.h" + +bool editmode = false; + +// the current selection, used by almost all editing commands +// invariant: all code assumes that these are kept inside MINBORD distance of the edge of the map + +block sel = +{ + variable("selx", 0, 0, 4096, &sel.x, NULL, false), + variable("sely", 0, 0, 4096, &sel.y, NULL, false), + variable("selxs", 0, 0, 4096, &sel.xs, NULL, false), + variable("selys", 0, 0, 4096, &sel.ys, NULL, false), +}; + +int selh = 0; +bool selset = false; + +#define loopselxy(b) { makeundo(); loop(x,sel.xs) loop(y,sel.ys) { sqr *s = S(sel.x+x, sel.y+y); b; }; remip(sel); } + +int cx, cy, ch; + +int curedittex[] = { -1, -1, -1 }; + +bool dragging = false; +int lastx, lasty, lasth; + +int lasttype = 0, lasttex = 0; +sqr rtex; + +VAR(editing,0,0,1); + +void toggleedit() +{ + if(player1->state==CS_DEAD) return; // do not allow dead players to edit to avoid state confusion + if(!editmode && !allowedittoggle()) return; // not in most multiplayer modes + if(!(editmode = !editmode)) + { + settagareas(); // reset triggers to allow quick playtesting + entinmap(player1); // find spawn closest to current floating pos + } + else + { + resettagareas(); // clear trigger areas to allow them to be edited + player1->health = 100; + //put call to clear/restar gamemode + projreset(); + }; + keyrepeat(editmode); + selset = false; + editing = editmode; +}; + +COMMANDN(edittoggle, toggleedit, ARG_NONE); + +void correctsel() // ensures above invariant +{ + selset = !OUTBORD(sel.x, sel.y); + int bsize = ssize-MINBORD; + if(sel.xs+sel.x>bsize) sel.xs = bsize-sel.x; + if(sel.ys+sel.y>bsize) sel.ys = bsize-sel.y; + if(sel.xs<=0 || sel.ys<=0) selset = false; +}; + +bool noteditmode() +{ + correctsel(); + if(!editmode) conoutf("this function is only allowed in edit mode"); + return !editmode; +}; + +bool noselection() +{ + if(!selset) conoutf("no selection"); + return !selset; +}; + +#define EDITSEL if(noteditmode() || noselection()) return; +#define EDITSELMP if(noteditmode() || noselection() || multiplayer()) return; +#define EDITMP if(noteditmode() || multiplayer()) return; + +void selectpos(int x, int y, int xs, int ys) +{ + block s = { x, y, xs, ys }; + sel = s; + selh = 0; + correctsel(); +}; + +void makesel() +{ + block s = { min(lastx,cx), min(lasty,cy), abs(lastx-cx)+1, abs(lasty-cy)+1 }; + sel = s; + selh = max(lasth,ch); + correctsel(); + if(selset) rtex = *S(sel.x, sel.y); +}; + +VAR(flrceil,0,0,2); + +float sheight(sqr *s, sqr *t, float z) // finds out z height when cursor points at wall +{ + return !flrceil //z-s->floorceil-z + ? (s->type==FHF ? s->floor-t->vdelta/4.0f : (float)s->floor) + : (s->type==CHF ? s->ceil+t->vdelta/4.0f : (float)s->ceil); +}; + +void cursorupdate() // called every frame from hud +{ + flrceil = ((int)(player1->pitch>=0))*2; + + volatile float x = worldpos.x; // volatile needed to prevent msvc7 optimizer bug? + volatile float y = worldpos.y; + volatile float z = worldpos.z; + + cx = (int)x; + cy = (int)y; + + if(OUTBORD(cx, cy)) return; + sqr *s = S(cx,cy); + + if(fabs(sheight(s,s,z)-z)>1) // selected wall + { + x += x>player1->o.x ? 0.5f : -0.5f; // find right wall cube + y += y>player1->o.y ? 0.5f : -0.5f; + + cx = (int)x; + cy = (int)y; + + if(OUTBORD(cx, cy)) return; + }; + + if(dragging) makesel(); + + const int GRIDSIZE = 5; + const float GRIDW = 0.5f; + const float GRID8 = 2.0f; + const float GRIDS = 2.0f; + const int GRIDM = 0x7; + + // render editing grid + + for(int ix = cx-GRIDSIZE; ix<=cx+GRIDSIZE; ix++) for(int iy = cy-GRIDSIZE; iy<=cy+GRIDSIZE; iy++) + { + if(OUTBORD(ix, iy)) continue; + sqr *s = S(ix,iy); + if(SOLID(s)) continue; + float h1 = sheight(s, s, z); + float h2 = sheight(s, SWS(s,1,0,ssize), z); + float h3 = sheight(s, SWS(s,1,1,ssize), z); + float h4 = sheight(s, SWS(s,0,1,ssize), z); + if(s->tag) linestyle(GRIDW, 0xFF, 0x40, 0x40); + else if(s->type==FHF || s->type==CHF) linestyle(GRIDW, 0x80, 0xFF, 0x80); + else linestyle(GRIDW, 0x80, 0x80, 0x80); + block b = { ix, iy, 1, 1 }; + box(b, h1, h2, h3, h4); + linestyle(GRID8, 0x40, 0x40, 0xFF); + if(!(ix&GRIDM)) line(ix, iy, h1, ix, iy+1, h4); + if(!(ix+1&GRIDM)) line(ix+1, iy, h2, ix+1, iy+1, h3); + if(!(iy&GRIDM)) line(ix, iy, h1, ix+1, iy, h2); + if(!(iy+1&GRIDM)) line(ix, iy+1, h4, ix+1, iy+1, h3); + }; + + if(!SOLID(s)) + { + float ih = sheight(s, s, z); + linestyle(GRIDS, 0xFF, 0xFF, 0xFF); + block b = { cx, cy, 1, 1 }; + box(b, ih, sheight(s, SWS(s,1,0,ssize), z), sheight(s, SWS(s,1,1,ssize), z), sheight(s, SWS(s,0,1,ssize), z)); + linestyle(GRIDS, 0xFF, 0x00, 0x00); + dot(cx, cy, ih); + ch = (int)ih; + }; + + if(selset) + { + linestyle(GRIDS, 0xFF, 0x40, 0x40); + box(sel, (float)selh, (float)selh, (float)selh, (float)selh); + }; +}; + +vector undos; // unlimited undo +VAR(undomegs, 0, 1, 10); // bounded by n megs + +void pruneundos(int maxremain) // bound memory +{ + int t = 0; + loopvrev(undos) + { + t += undos[i]->xs*undos[i]->ys*sizeof(sqr); + if(t>maxremain) free(undos.remove(i)); + }; +}; + +void makeundo() +{ + undos.add(blockcopy(sel)); + pruneundos(undomegs<<20); +}; + +void editundo() +{ + EDITMP; + if(undos.empty()) { conoutf("nothing more to undo"); return; }; + block *p = undos.pop(); + blockpaste(*p); + free(p); +}; + +block *copybuf = NULL; + +void copy() +{ + EDITSELMP; + if(copybuf) free(copybuf); + copybuf = blockcopy(sel); +}; + +void paste() +{ + EDITMP; + if(!copybuf) { conoutf("nothing to paste"); return; }; + sel.xs = copybuf->xs; + sel.ys = copybuf->ys; + correctsel(); + if(!selset || sel.xs!=copybuf->xs || sel.ys!=copybuf->ys) { conoutf("incorrect selection"); return; }; + makeundo(); + copybuf->x = sel.x; + copybuf->y = sel.y; + blockpaste(*copybuf); +}; + +void tofronttex() // maintain most recently used of the texture lists when applying texture +{ + loopi(3) + { + int c = curedittex[i]; + if(c>=0) + { + uchar *p = hdr.texlists[i]; + int t = p[c]; + for(int a = c-1; a>=0; a--) p[a+1] = p[a]; + p[0] = t; + curedittex[i] = -1; + }; + }; +}; + +void editdrag(bool isdown) +{ + if(dragging = isdown) + { + lastx = cx; + lasty = cy; + lasth = ch; + selset = false; + tofronttex(); + }; + makesel(); +}; + +// the core editing function. all the *xy functions perform the core operations +// and are also called directly from the network, the function below it is strictly +// triggered locally. They all have very similar structure. + +void editheightxy(bool isfloor, int amount, block &sel) +{ + loopselxy(if(isfloor) + { + s->floor += amount; + if(s->floor>=s->ceil) s->floor = s->ceil-1; + } + else + { + s->ceil += amount; + if(s->ceil<=s->floor) s->ceil = s->floor+1; + }); +}; + +void editheight(int flr, int amount) +{ + EDITSEL; + bool isfloor = flr==0; + editheightxy(isfloor, amount, sel); + addmsg(1, 7, SV_EDITH, sel.x, sel.y, sel.xs, sel.ys, isfloor, amount); +}; + +COMMAND(editheight, ARG_2INT); + +void edittexxy(int type, int t, block &sel) +{ + loopselxy(switch(type) + { + case 0: s->ftex = t; break; + case 1: s->wtex = t; break; + case 2: s->ctex = t; break; + case 3: s->utex = t; break; + }); +}; + +void edittex(int type, int dir) +{ + EDITSEL; + if(type<0 || type>3) return; + if(type!=lasttype) { tofronttex(); lasttype = type; }; + int atype = type==3 ? 1 : type; + int i = curedittex[atype]; + i = i<0 ? 0 : i+dir; + curedittex[atype] = i = min(max(i, 0), 255); + int t = lasttex = hdr.texlists[atype][i]; + edittexxy(type, t, sel); + addmsg(1, 7, SV_EDITT, sel.x, sel.y, sel.xs, sel.ys, type, t); +}; + +void replace() +{ + EDITSELMP; + loop(x,ssize) loop(y,ssize) + { + sqr *s = S(x, y); + switch(lasttype) + { + case 0: if(s->ftex == rtex.ftex) s->ftex = lasttex; break; + case 1: if(s->wtex == rtex.wtex) s->wtex = lasttex; break; + case 2: if(s->ctex == rtex.ctex) s->ctex = lasttex; break; + case 3: if(s->utex == rtex.utex) s->utex = lasttex; break; + }; + }; + block b = { 0, 0, ssize, ssize }; + remip(b); +}; + +void edittypexy(int type, block &sel) +{ + loopselxy(s->type = type); +}; + +void edittype(int type) +{ + EDITSEL; + if(type==CORNER && (sel.xs!=sel.ys || sel.xs==3 || sel.xs>4 && sel.xs!=8 + || sel.x&~-sel.xs || sel.y&~-sel.ys)) + { conoutf("corner selection must be power of 2 aligned"); return; }; + edittypexy(type, sel); + addmsg(1, 6, SV_EDITS, sel.x, sel.y, sel.xs, sel.ys, type); +}; + +void heightfield(int t) { edittype(t==0 ? FHF : CHF); }; +void solid(int t) { edittype(t==0 ? SPACE : SOLID); }; +void corner() { edittype(CORNER); }; + +COMMAND(heightfield, ARG_1INT); +COMMAND(solid, ARG_1INT); +COMMAND(corner, ARG_NONE); + +void editequalisexy(bool isfloor, block &sel) +{ + int low = 127, hi = -128; + loopselxy( + { + if(s->floorfloor; + if(s->ceil>hi) hi = s->ceil; + }); + loopselxy( + { + if(isfloor) s->floor = low; else s->ceil = hi; + if(s->floor>=s->ceil) s->floor = s->ceil-1; + }); +}; + +void equalize(int flr) +{ + bool isfloor = flr==0; + EDITSEL; + editequalisexy(isfloor, sel); + addmsg(1, 6, SV_EDITE, sel.x, sel.y, sel.xs, sel.ys, isfloor); +}; + +COMMAND(equalize, ARG_1INT); + +void setvdeltaxy(int delta, block &sel) +{ + loopselxy(s->vdelta = max(s->vdelta+delta, 0)); +}; + +void setvdelta(int delta) +{ + EDITSEL; + setvdeltaxy(delta, sel); + addmsg(1, 6, SV_EDITD, sel.x, sel.y, sel.xs, sel.ys, delta); +}; + +const int MAXARCHVERT = 50; +int archverts[MAXARCHVERT][MAXARCHVERT]; +bool archvinit = false; + +void archvertex(int span, int vert, int delta) +{ + if(!archvinit) + { + archvinit = true; + loop(s,MAXARCHVERT) loop(v,MAXARCHVERT) archverts[s][v] = 0; + }; + if(span>=MAXARCHVERT || vert>=MAXARCHVERT || span<0 || vert<0) return; + archverts[span][vert] = delta; +}; + +void arch(int sidedelta, int _a) +{ + EDITSELMP; + sel.xs++; + sel.ys++; + if(sel.xs>MAXARCHVERT) sel.xs = MAXARCHVERT; + if(sel.ys>MAXARCHVERT) sel.ys = MAXARCHVERT; + loopselxy(s->vdelta = + sel.xs>sel.ys + ? (archverts[sel.xs-1][x] + (y==0 || y==sel.ys-1 ? sidedelta : 0)) + : (archverts[sel.ys-1][y] + (x==0 || x==sel.xs-1 ? sidedelta : 0))); +}; + +void slope(int xd, int yd) +{ + EDITSELMP; + int off = 0; + if(xd<0) off -= xd*sel.xs; + if(yd<0) off -= yd*sel.ys; + sel.xs++; + sel.ys++; + loopselxy(s->vdelta = xd*x+yd*y+off); +}; + +void perlin(int scale, int seed, int psize) +{ + EDITSELMP; + sel.xs++; + sel.ys++; + makeundo(); + sel.xs--; + sel.ys--; + perlinarea(sel, scale, seed, psize); + sel.xs++; + sel.ys++; + remip(sel); + sel.xs--; + sel.ys--; +}; + +VARF(fullbright, 0, 0, 1, + if(fullbright) + { + if(noteditmode()) return; + loopi(mipsize) world[i].r = world[i].g = world[i].b = 176; + }; +); + +void edittag(int tag) +{ + EDITSELMP; + loopselxy(s->tag = tag); +}; + +void newent(char *what, char *a1, char *a2, char *a3, char *a4) +{ + EDITSEL; + newentity(sel.x, sel.y, (int)player1->o.z, what, ATOI(a1), ATOI(a2), ATOI(a3), ATOI(a4)); +}; + +COMMANDN(select, selectpos, ARG_4INT); +COMMAND(edittag, ARG_1INT); +COMMAND(replace, ARG_NONE); +COMMAND(archvertex, ARG_3INT); +COMMAND(arch, ARG_2INT); +COMMAND(slope, ARG_2INT); +COMMANDN(vdelta, setvdelta, ARG_1INT); +COMMANDN(undo, editundo, ARG_NONE); +COMMAND(copy, ARG_NONE); +COMMAND(paste, ARG_NONE); +COMMAND(edittex, ARG_2INT); +COMMAND(newent, ARG_5STR); +COMMAND(perlin, ARG_3INT); + + diff --git a/source/src/entities.cpp b/source/src/entities.cpp index c5bc52d8..c3a5a0cc 100644 --- a/source/src/entities.cpp +++ b/source/src/entities.cpp @@ -1,365 +1,385 @@ -// entities.cpp: map entity related functions (pickup etc.) - -#include "cube.h" - -vector ents; - -char *entmdlnames[] = -{ -//FIXME : fix the "pickups" infront - "pickups/pistolclips", "pickups/ammobox", "pickups/nades", "pickups/health", "pickups/kevlar", "pickups/akimbo", -}; - -int triggertime = 0; - -void renderent(entity &e, char *mdlname, float z, float yaw, int frame = 0, int numf = 1, int basetime = 0, float speed = 10.0f) -{ - rendermodel(mdlname, frame, numf, 0, 1.1f, e.x, z+S(e.x, e.y)->floor, e.y, yaw, 0, false, 1.0f, speed, 0, basetime); -}; - -void renderentities() -{ - if(lastmillis>triggertime+1000) triggertime = 0; - loopv(ents) - { - entity &e = ents[i]; - if(e.type==MAPMODEL) - { - mapmodelinfo &mmi = getmminfo(e.attr2); - if(!&mmi) continue; - rendermodel(mmi.name, 0, 1, e.attr4, (float)mmi.rad, e.x, (float)S(e.x, e.y)->floor+mmi.zoff+e.attr3, e.y, (float)((e.attr1+7)-(e.attr1+7)%15), 0, false, 1.0f, 10.0f, mmi.snap); - } - else if(e.type==CTF_FLAG && m_ctf) // EDIT: AH - { - flaginfo &f = flaginfos[e.attr2]; - if(f.state==CTFF_STOLEN && f.thief) - { - if(f.thief == player1) continue; - sprintf_sd(path)("pickups/flags/small_%s", rb_team_string(e.attr2)); - mapmodelinfo mmi = {10, 4, 0, 0, path}; - if(!&mmi) continue; - rendermodel(mmi.name, 0, 1, 0, 0, f.thief->o.x, f.thief->o.z+0.3f+(sin(lastmillis/100.0f)+1)/10, f.thief->o.y, lastmillis/2.5f, 0, false, 0.6f, 120.0f, mmi.snap); - } - else - { - sprintf_sd(path)("pickups/flags/%s", rb_team_string(e.attr2)); - mapmodelinfo mmi = {10, 4, 0, 0, path}; - if(!&mmi) continue; - rendermodel(mmi.name, 0, 7, 0, (float)mmi.rad, e.x, f.state==CTFF_INBASE ? (float)S(e.x, e.y)->floor : e.z, e.y, (float)((e.attr1+7)-(e.attr1+7)%15), 0, false, 1.0f, 120.0f, mmi.snap); - }; - } - else - { - if(OUTBORD(e.x, e.y)) continue; - if(e.type!=CARROT) - { - if(!e.spawned) continue; - if(e.typeI_AKIMBO) continue; - renderent(e, entmdlnames[e.type-I_CLIPS], (float)(1+sin(lastmillis/100.0+e.x+e.y)/20), lastmillis/10.0f); - } - else switch(e.attr2) - { - case 1: - case 3: - continue; - - case 2: - case 0: - if(!e.spawned) continue; - renderent(e, "carrot", (float)(1+sin(lastmillis/100.0+e.x+e.y)/20), lastmillis/(e.attr2 ? 1.0f : 10.0f)); - break; - - case 4: renderent(e, "switch2", 3, (float)e.attr3*90, (!e.spawned && !triggertime) ? 1 : 0, (e.spawned || !triggertime) ? 1 : 2, triggertime, 1050.0f); break; - case 5: renderent(e, "switch1", -0.15f, (float)e.attr3*90, (!e.spawned && !triggertime) ? 30 : 0, (e.spawned || !triggertime) ? 1 : 30, triggertime, 35.0f); break; - }; - }; - }; -}; - -itemstat itemstats[] = -{ - 16, 8, 72, S_ITEMAMMO, //pistol - 14, 7, 21, S_ITEMAMMO, //shotgun - 60, 30, 90, S_ITEMAMMO, //subgun - 10, 5, 15, S_ITEMAMMO, //sniper - 40, 20, 60, S_ITEMAMMO, //assult - 2, 0, 2, S_ITEMAMMO, //grenade - 33, 100, 100, S_ITEMHEALTH, //health - 50, 100, 100, S_ITEMARMOUR, //armour - 16, 0, 72, S_ITEMPUP, //powerup -}; - -void baseammo(int gun) { player1->ammo[gun] = itemstats[gun-1].add*2; }; -// Added by Rick: baseammo for bots -void botbaseammo(int gun, dynent *d) { d->ammo[gun] = itemstats[gun-1].add*2; }; -// End add - -// these two functions are called when the server acknowledges that you really -// picked up the item (in multiplayer someone may grab it before you). - -/* -void radditem(int i, int &v) -{ - itemstat &is = itemstats[ents[i].type-I_CLIPS]; - ents[i].spawned = false; - v += is.add; - if(v>is.max) v = is.max; - playsoundc(is.sound); -}; -*/ - -void radditem(int i, int &v, int t) -{ - itemstat &is = itemstats[t-1]; - ents[i].spawned = false; - v += is.add; - if(v>is.max) v = is.max; - playsoundc(is.sound); -}; - -extern void weapon(int gun); - -void realpickup(int n, dynent *d) -{ - switch(ents[n].type) - { - case I_CLIPS: - radditem(n, d->ammo[1], 1); - break; - case I_AMMO: - radditem(n, d->ammo[d->primary], d->primary); - break; - case I_GRENADE: - radditem(n, d->mag[6], 6); - break; - case I_HEALTH: - radditem(n, d->health, 7); - break; - case I_ARMOUR: - radditem(n, d->armour, 8); - //d->hasarmour = true; - break; - case I_AKIMBO: - d->akimbo = true; - d->akimbomillis = 30000; - d->mag[GUN_PISTOL] = 16; - radditem(n, d->ammo[1], 9); - weapon(G_SECONDARY); - if(d==player1) conoutf("a lesser man would use a single pistol"); - break; - }; -}; - -// these functions are called when the client touches the item - -/* -void additem(int i, int &v, int spawnsec) -{ - if(v4 ? 2 : 3); // spawn times are dependent on number of players - int ammo = np*2; - switch(ents[n].type) - { - case I_CLIPS: - additem(n, d->ammo[1], ammo, 1); - break; - case I_AMMO: - additem(n, d->ammo[d->primary], ammo, d->primary); - break; - case I_GRENADE: additem(n, d->mag[6], ammo, 6); break; - case I_HEALTH: additem(n, d->health, np*5, 7); break; - - case I_ARMOUR: - additem(n, d->armour, 20, 8); - break; - - case I_AKIMBO: - //additem(n, d->ammo[1], 60, 9); - additem(n, d->akimbo, 60, 9); - break; - - case CARROT: - ents[n].spawned = false; - triggertime = lastmillis; - trigger(ents[n].attr1, ents[n].attr2, false); // needs to go over server for multiplayer - break; - - case LADDER: - { - d->onladder = true; - break; - }; - - // EDIT: AH - case CTF_FLAG: - { - int team = ents[n].attr2; - flaginfo &f = flaginfos[team]; - if(f.state == CTFF_STOLEN) break; - else if(team == rb_team_int(player1->team) && f.state == CTFF_DROPPED) - { - addmsg(1, 2, SV_FLAGRETURN, team); - ents[n].spawned = false; - } - else if(team != rb_team_int(player1->team)) - { - addmsg(1, 2, SV_FLAGPICKUP, team); - ents[n].spawned = false; - f.thief = player1; // do this although we don't know if we picked the flag to avoid getting it after a possible respawn - f.state = CTFF_STOLEN; - f.pick_ack = false; - } - else if(team == rb_team_int(player1->team) && f.state == CTFF_INBASE && flaginfos[rb_opposite(team)].state == CTFF_STOLEN && flaginfos[rb_opposite(team)].thief == player1) addmsg(1, 2, SV_FLAGSCORE, rb_opposite(team)); - break; - }; - }; -}; - -void checkitems() -{ - if(editmode) return; - player1->onladder = false; - loopv(ents) - { - entity &e = ents[i]; - if(e.type==NOTUSED) continue; - if(e.type==LADDER) - { - if(OUTBORD(e.x, e.y)) continue; - vec v = { e.x, e.y, player1->o.z }; - vdist(dist1, t, player1->o, v); - float dist2 = player1->o.z - (S(e.x, e.y)->floor+player1->eyeheight); - if(dist1<1.5 && dist2floor+player1->eyeheight }; - vdist(dist, t, player1->o, v); - if(dist<2.5) pickup(i, player1); - }; -}; - -void putitems(uchar *&p) // puts items in network stream and also spawns them locally -{ - loopv(ents) if((ents[i].type>=I_CLIPS && ents[i].type<=I_AKIMBO) || ents[i].type==CARROT) - { - putint(p, i); - ents[i].spawned = true; - }; -}; - -void resetspawns() { loopv(ents) ents[i].spawned = false; }; -void setspawn(int i, bool on) { if(inextprimary!=i) d->ammo[i] = 0; - d->mag[GUN_KNIFE] = 1; - - d->mag[GUN_PISTOL] = 8; - d->ammo[GUN_PISTOL] = 24; - d->mag[GUN_GRENADE] = d->ammo[GUN_GRENADE] = 0; - - if (m_pistol || d->primary==GUN_PISTOL) // || pistol only mode! - { - d->primary = GUN_PISTOL; - d->ammo[d->primary] = 72; - } - else if (d->primary>GUN_PISTOL && d->primaryammo[d->primary] = itemstats[d->primary-1].max; - d->mag[d->primary] = itemstats[d->primary-1].start; - printf("player is you: %i\n", d==player1?1:0); - }; - - if (d->hasarmour) - { - if(gamemode==m_arena) - d->armour = 100; - } - - d->gunselect = d->primary; //draw main weapon -}; - -void item(int num) -{ - if (num>0 && num<7) player1->nextprimary = num; - - //if (num==6) conoutf("sorry, no nades yet"); - - if (num==7) { player1->hasarmour=!player1->hasarmour; /*toggle armour*/ }; -}; - -COMMAND(item,ARG_1INT); - -void akimbo() -{ - conoutf("akimbo=%d",player1->akimbo); -}; - -COMMAND(akimbo, ARG_NONE); - -// Added by Rick -bool intersect(entity *e, vec &from, vec &to, vec *end) // if lineseg hits entity bounding box(entity version) -{ - mapmodelinfo &mmi = getmminfo(e->attr2); - if(!&mmi || !mmi.h) return false; - - float lo = (float)(S(e->x, e->y)->floor+mmi.zoff+e->attr3); - float hi = lo+mmi.h; - vec v = to, w = { e->x, e->y, lo + (fabs(hi-lo)/2.0f) }, *p; - vsub(v, from); - vsub(w, from); - float c1 = dotprod(w, v); - - if(c1<=0) p = &from; - else - { - float c2 = dotprod(v, v); - if(c2<=c1) p = &to; - else - { - float f = c1/c2; - vmul(v, f); - vadd(v, from); - p = &v; - }; - }; - - if (p->x <= e->x+mmi.rad - && p->x >= e->x-mmi.rad - && p->y <= e->y+mmi.rad - && p->y >= e->y-mmi.rad - && p->z <= hi - && p->z >= lo) - { - if (end) *end = *p; - return true; - } - return false; -}; -// End add by Ricks - +// entities.cpp: map entity related functions (pickup etc.) + +#include "cube.h" + +vector ents; + +char *entmdlnames[] = +{ +//FIXME : fix the "pickups" infront + "pickups/pistolclips", "pickups/ammobox", "pickups/nades", "pickups/health", "pickups/kevlar", "pickups/akimbo", +}; + +int triggertime = 0; + +void renderent(entity &e, char *mdlname, float z, float yaw, int frame = 0, int numf = 1, int basetime = 0, float speed = 10.0f) +{ + rendermodel(mdlname, frame, numf, 0, 1.1f, e.x, z+S(e.x, e.y)->floor, e.y, yaw, 0, false, 1.0f, speed, 0, basetime); +}; + +extern void newparticle(vec &o, vec &d, int fade, int type, int tex = -1); + +void renderentities() +{ + if(lastmillis>triggertime+1000) triggertime = 0; + loopv(ents) + { + entity &e = ents[i]; + if(e.type==MAPMODEL) + { + mapmodelinfo &mmi = getmminfo(e.attr2); + if(!&mmi) continue; + rendermodel(mmi.name, 0, 1, e.attr4, (float)mmi.rad, e.x, (float)S(e.x, e.y)->floor+mmi.zoff+e.attr3, e.y, (float)((e.attr1+7)-(e.attr1+7)%15), 0, false, 1.0f, 10.0f, mmi.snap); + } + else if(e.type==CTF_FLAG && m_ctf) // EDIT: AH + { + flaginfo &f = flaginfos[e.attr2]; + if(f.state==CTFF_STOLEN && f.thief) + { + if(f.thief == player1) continue; + sprintf_sd(path)("pickups/flags/small_%s", rb_team_string(e.attr2)); + mapmodelinfo mmi = {10, 4, 0, 0, path}; + if(!&mmi) continue; + rendermodel(mmi.name, 0, 1, 0, 0, f.thief->o.x, f.thief->o.z+0.3f+(sin(lastmillis/100.0f)+1)/10, f.thief->o.y, lastmillis/2.5f, 0, false, 0.6f, 120.0f, mmi.snap); + } + else + { + sprintf_sd(path)("pickups/flags/%s", rb_team_string(e.attr2)); + mapmodelinfo mmi = {10, 4, 0, 0, path}; + if(!&mmi) continue; + rendermodel(mmi.name, 0, 7, 0, (float)mmi.rad, e.x, f.state==CTFF_INBASE ? (float)S(e.x, e.y)->floor : e.z, e.y, (float)((e.attr1+7)-(e.attr1+7)%15), 0, false, 1.0f, 120.0f, mmi.snap); + }; + } + else if(e.type==SMOKESOURCE) + { + + vec o, to; + o.x = e.x; + o.y = e.y; + o.z = e.z; + to = o; + to.z += 1000; + + //newparticle(o, to, 10000, 5); + static int lastsplash = lastmillis; + if(lastmillis - lastsplash > 100) + { + particle_splash(e.attr1, e.attr2, 1000, o); + lastsplash = lastmillis; + } + } + else + { + if(OUTBORD(e.x, e.y)) continue; + if(e.type!=CARROT) + { + if(!e.spawned) continue; + if(e.typeI_AKIMBO) continue; + renderent(e, entmdlnames[e.type-I_CLIPS], (float)(1+sin(lastmillis/100.0+e.x+e.y)/20), lastmillis/10.0f); + } + else switch(e.attr2) + { + case 1: + case 3: + continue; + + case 2: + case 0: + if(!e.spawned) continue; + renderent(e, "carrot", (float)(1+sin(lastmillis/100.0+e.x+e.y)/20), lastmillis/(e.attr2 ? 1.0f : 10.0f)); + break; + + case 4: renderent(e, "switch2", 3, (float)e.attr3*90, (!e.spawned && !triggertime) ? 1 : 0, (e.spawned || !triggertime) ? 1 : 2, triggertime, 1050.0f); break; + case 5: renderent(e, "switch1", -0.15f, (float)e.attr3*90, (!e.spawned && !triggertime) ? 30 : 0, (e.spawned || !triggertime) ? 1 : 30, triggertime, 35.0f); break; + }; + }; + }; +}; + +itemstat itemstats[] = +{ + 16, 8, 72, S_ITEMAMMO, //pistol + 14, 7, 21, S_ITEMAMMO, //shotgun + 60, 30, 90, S_ITEMAMMO, //subgun + 10, 5, 15, S_ITEMAMMO, //sniper + 40, 20, 60, S_ITEMAMMO, //assult + 2, 0, 2, S_ITEMAMMO, //grenade + 33, 100, 100, S_ITEMHEALTH, //health + 50, 100, 100, S_ITEMARMOUR, //armour + 16, 0, 72, S_ITEMPUP, //powerup +}; + +void baseammo(int gun) { player1->ammo[gun] = itemstats[gun-1].add*2; }; +// Added by Rick: baseammo for bots +void botbaseammo(int gun, dynent *d) { d->ammo[gun] = itemstats[gun-1].add*2; }; +// End add + +// these two functions are called when the server acknowledges that you really +// picked up the item (in multiplayer someone may grab it before you). + +/* +void radditem(int i, int &v) +{ + itemstat &is = itemstats[ents[i].type-I_CLIPS]; + ents[i].spawned = false; + v += is.add; + if(v>is.max) v = is.max; + playsoundc(is.sound); +}; +*/ + +void radditem(int i, int &v, int t) +{ + itemstat &is = itemstats[t-1]; + ents[i].spawned = false; + v += is.add; + if(v>is.max) v = is.max; + playsoundc(is.sound); +}; + +extern void weapon(int gun); + +void realpickup(int n, dynent *d) +{ + switch(ents[n].type) + { + case I_CLIPS: + radditem(n, d->ammo[1], 1); + break; + case I_AMMO: + radditem(n, d->ammo[d->primary], d->primary); + break; + case I_GRENADE: + radditem(n, d->mag[6], 6); + break; + case I_HEALTH: + radditem(n, d->health, 7); + break; + case I_ARMOUR: + radditem(n, d->armour, 8); + //d->hasarmour = true; + break; + case I_AKIMBO: + d->akimbo = true; + d->akimbomillis = 30000; + d->mag[GUN_PISTOL] = 16; + radditem(n, d->ammo[1], 9); + weapon(G_SECONDARY); + if(d==player1) conoutf("a lesser man would use a single pistol"); + break; + }; +}; + +// these functions are called when the client touches the item + +/* +void additem(int i, int &v, int spawnsec) +{ + if(v4 ? 2 : 3); // spawn times are dependent on number of players + int ammo = np*2; + switch(ents[n].type) + { + case I_CLIPS: + additem(n, d->ammo[1], ammo, 1); + break; + case I_AMMO: + additem(n, d->ammo[d->primary], ammo, d->primary); + break; + case I_GRENADE: additem(n, d->mag[6], ammo, 6); break; + case I_HEALTH: additem(n, d->health, np*5, 7); break; + + case I_ARMOUR: + additem(n, d->armour, 20, 8); + break; + + case I_AKIMBO: + //additem(n, d->ammo[1], 60, 9); + additem(n, d->akimbo, 60, 9); + break; + + case CARROT: + ents[n].spawned = false; + triggertime = lastmillis; + trigger(ents[n].attr1, ents[n].attr2, false); // needs to go over server for multiplayer + break; + + case LADDER: + { + d->onladder = true; + break; + }; + + // EDIT: AH + case CTF_FLAG: + { + int team = ents[n].attr2; + flaginfo &f = flaginfos[team]; + if(f.state == CTFF_STOLEN) break; + else if(team == rb_team_int(player1->team) && f.state == CTFF_DROPPED) + { + addmsg(1, 2, SV_FLAGRETURN, team); + ents[n].spawned = false; + } + else if(team != rb_team_int(player1->team)) + { + addmsg(1, 2, SV_FLAGPICKUP, team); + ents[n].spawned = false; + f.thief = player1; // do this although we don't know if we picked the flag to avoid getting it after a possible respawn + f.state = CTFF_STOLEN; + f.pick_ack = false; + } + else if(team == rb_team_int(player1->team) && f.state == CTFF_INBASE && flaginfos[rb_opposite(team)].state == CTFF_STOLEN && flaginfos[rb_opposite(team)].thief == player1) addmsg(1, 2, SV_FLAGSCORE, rb_opposite(team)); + break; + }; + }; +}; + +void checkitems() +{ + if(editmode) return; + player1->onladder = false; + loopv(ents) + { + entity &e = ents[i]; + if(e.type==NOTUSED) continue; + if(e.type==LADDER) + { + if(OUTBORD(e.x, e.y)) continue; + vec v = { e.x, e.y, player1->o.z }; + vdist(dist1, t, player1->o, v); + float dist2 = player1->o.z - (S(e.x, e.y)->floor+player1->eyeheight); + if(dist1<1.5 && dist2floor+player1->eyeheight }; + vdist(dist, t, player1->o, v); + if(dist<2.5) pickup(i, player1); + }; +}; + +void putitems(uchar *&p) // puts items in network stream and also spawns them locally +{ + loopv(ents) if((ents[i].type>=I_CLIPS && ents[i].type<=I_AKIMBO) || ents[i].type==CARROT) + { + putint(p, i); + ents[i].spawned = true; + }; +}; + +void resetspawns() { loopv(ents) ents[i].spawned = false; }; +void setspawn(int i, bool on) { if(inextprimary!=i) d->ammo[i] = 0; + d->mag[GUN_KNIFE] = 1; + + d->mag[GUN_PISTOL] = 8; + d->ammo[GUN_PISTOL] = 24; + d->mag[GUN_GRENADE] = d->ammo[GUN_GRENADE] = 0; + + if (m_pistol || d->primary==GUN_PISTOL) // || pistol only mode! + { + d->primary = GUN_PISTOL; + d->ammo[d->primary] = 72; + } + else if (d->primary>GUN_PISTOL && d->primaryammo[d->primary] = itemstats[d->primary-1].max; + d->mag[d->primary] = itemstats[d->primary-1].start; + printf("player is you: %i\n", d==player1?1:0); + }; + + if (d->hasarmour) + { + if(gamemode==m_arena) + d->armour = 100; + } + + d->gunselect = d->primary; //draw main weapon +}; + +void item(int num) +{ + if (num>0 && num<7) player1->nextprimary = num; + + //if (num==6) conoutf("sorry, no nades yet"); + + if (num==7) { player1->hasarmour=!player1->hasarmour; /*toggle armour*/ }; +}; + +COMMAND(item,ARG_1INT); + +void akimbo() +{ + conoutf("akimbo=%d",player1->akimbo); +}; + +COMMAND(akimbo, ARG_NONE); + +// Added by Rick +bool intersect(entity *e, vec &from, vec &to, vec *end) // if lineseg hits entity bounding box(entity version) +{ + mapmodelinfo &mmi = getmminfo(e->attr2); + if(!&mmi || !mmi.h) return false; + + float lo = (float)(S(e->x, e->y)->floor+mmi.zoff+e->attr3); + float hi = lo+mmi.h; + vec v = to, w = { e->x, e->y, lo + (fabs(hi-lo)/2.0f) }, *p; + vsub(v, from); + vsub(w, from); + float c1 = dotprod(w, v); + + if(c1<=0) p = &from; + else + { + float c2 = dotprod(v, v); + if(c2<=c1) p = &to; + else + { + float f = c1/c2; + vmul(v, f); + vadd(v, from); + p = &v; + }; + }; + + if (p->x <= e->x+mmi.rad + && p->x >= e->x-mmi.rad + && p->y <= e->y+mmi.rad + && p->y >= e->y-mmi.rad + && p->z <= hi + && p->z >= lo) + { + if (end) *end = *p; + return true; + } + return false; +}; +// End add by Ricks + diff --git a/source/src/main.cpp b/source/src/main.cpp index 6b1c2de2..0c97d9c7 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -1,254 +1,251 @@ -// main.cpp: initialisation & main loop - -#include "cube.h" - -void cleanup(char *msg) // single program exit point; -{ - stop(); - disconnect(true); - cleangl(); - cleansound(); - cleanupserver(); - SDL_ShowCursor(1); - if(msg) - { - #ifdef WIN32 - MessageBox(NULL, msg, "cube fatal error", MB_OK|MB_SYSTEMMODAL); - #else - printf(msg); - #endif - }; - SDL_Quit(); - exit(1); -}; - -void quit() // normal exit -{ - writeservercfg(); - cleanup(NULL); -}; - -void fatal(char *s, char *o) // failure exit -{ - sprintf_sd(msg)("%s%s (%s)\n", s, o, SDL_GetError()); - cleanup(msg); -}; - -void *alloc(int s) // for some big chunks... most other allocs use the memory pool -{ - void *b = calloc(1,s); - if(!b) fatal("out of memory!"); - return b; -}; - -int scr_w = 640; -int scr_h = 480; - -void screenshot() -{ - SDL_Surface *image; - SDL_Surface *temp; - int idx; - if(image = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24, 0x0000FF, 0x00FF00, 0xFF0000, 0)) - { - if(temp = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24, 0x0000FF, 0x00FF00, 0xFF0000, 0)) - { - glReadPixels(0, 0, scr_w, scr_h, GL_RGB, GL_UNSIGNED_BYTE, image->pixels); - for (idx = 0; idxpixels+3*scr_w*idx; - memcpy(dest, (char *)image->pixels+3*scr_w*(scr_h-1-idx), 3*scr_w); - endianswap(dest, 3, scr_w); - }; - sprintf_sd(buf)("screenshots/screenshot_%d.bmp", lastmillis); - SDL_SaveBMP(temp, path(buf)); - SDL_FreeSurface(temp); - }; - SDL_FreeSurface(image); - }; -}; - -COMMAND(screenshot, ARG_NONE); -COMMAND(quit, ARG_NONE); - -int minfps, maxfps; - -void fpsrange(int low, int high) -{ - if(low>high || low<1) return; - minfps = low; - maxfps = high; -}; - -COMMAND(fpsrange, ARG_2INT); - -void keyrepeat(bool on) -{ - SDL_EnableKeyRepeat(on ? SDL_DEFAULT_REPEAT_DELAY : 0, - SDL_DEFAULT_REPEAT_INTERVAL); -}; - -VARF(gamespeed, 10, 100, 1000, if(multiplayer()) gamespeed = 100); - -int islittleendian = 1; -int framesinmap = 0; - -int main(int argc, char **argv) -{ - bool dedicated = false; - int fs = SDL_FULLSCREEN, par = 0, uprate = 0, maxcl = 4; - char *sdesc = "", *ip = "", *master = NULL, *passwd = "", *maprot = ""; - islittleendian = *((char *)&islittleendian); - - #define log(s) puts("init: " s) - log("sdl"); - - for(int i = 1; i200) lastmillis = millis-200; - else if(millis-lastmillis<1) lastmillis = millis-1; - cleardlights(); - updateworld(millis); - if(!demoplayback) serverslice((int)time(NULL), 0); - static float fps = 30.0f; - fps = (1000.0f/curtime+fps*50)/51; - computeraytable(player1->o.x, player1->o.y); - readdepth(scr_w, scr_h); - SDL_GL_SwapBuffers(); - extern void updatevol(); updatevol(); - if(framesinmap++<5) // cheap hack to get rid of initial sparklies, even when triple buffering etc. - { - player1->yaw += 5; - gl_drawframe(scr_w, scr_h, 1.0f, fps); - player1->yaw -= 5; - }; - gl_drawframe(scr_w, scr_h, fpsmaxfps ? fps/maxfps : 1.0f), fps); - //SDL_Delay(100); - SDL_Event event; - int lasttype = 0, lastbut = 0; - while(SDL_PollEvent(&event)) - { - switch(event.type) - { - case SDL_QUIT: - quit(); - break; - - case SDL_KEYDOWN: - case SDL_KEYUP: - keypress(event.key.keysym.sym, event.key.state==SDL_PRESSED, event.key.keysym.unicode); - break; - - case SDL_MOUSEMOTION: - if(ignore) { ignore--; break; }; - mousemove(event.motion.xrel, event.motion.yrel); - break; - - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - if(lasttype==event.type && lastbut==event.button.button) break; // why?? get event twice without it - keypress(-event.button.button, event.button.state!=0, 0); - lasttype = event.type; - lastbut = event.button.button; - break; - }; - }; - }; - quit(); - return 1; -}; - - +// main.cpp: initialisation & main loop + +#include "cube.h" + +void cleanup(char *msg) // single program exit point; +{ + stop(); + disconnect(true); + cleangl(); + cleansound(); + cleanupserver(); + SDL_ShowCursor(1); + if(msg) + { + #ifdef WIN32 + MessageBox(NULL, msg, "cube fatal error", MB_OK|MB_SYSTEMMODAL); + #else + printf(msg); + #endif + }; + SDL_Quit(); + exit(1); +}; + +void quit() // normal exit +{ + writeservercfg(); + cleanup(NULL); +}; + +void fatal(char *s, char *o) // failure exit +{ + sprintf_sd(msg)("%s%s (%s)\n", s, o, SDL_GetError()); + cleanup(msg); +}; + +void *alloc(int s) // for some big chunks... most other allocs use the memory pool +{ + void *b = calloc(1,s); + if(!b) fatal("out of memory!"); + return b; +}; + +int scr_w = 640; +int scr_h = 480; + +void screenshot() +{ + SDL_Surface *image; + SDL_Surface *temp; + int idx; + if(image = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24, 0x0000FF, 0x00FF00, 0xFF0000, 0)) + { + if(temp = SDL_CreateRGBSurface(SDL_SWSURFACE, scr_w, scr_h, 24, 0x0000FF, 0x00FF00, 0xFF0000, 0)) + { + glReadPixels(0, 0, scr_w, scr_h, GL_RGB, GL_UNSIGNED_BYTE, image->pixels); + for (idx = 0; idxpixels+3*scr_w*idx; + memcpy(dest, (char *)image->pixels+3*scr_w*(scr_h-1-idx), 3*scr_w); + endianswap(dest, 3, scr_w); + }; + sprintf_sd(buf)("screenshots/screenshot_%d.bmp", lastmillis); + SDL_SaveBMP(temp, path(buf)); + SDL_FreeSurface(temp); + }; + SDL_FreeSurface(image); + }; +}; + +COMMAND(screenshot, ARG_NONE); +COMMAND(quit, ARG_NONE); + +int minfps, maxfps; + +void fpsrange(int low, int high) +{ + if(low>high || low<1) return; + minfps = low; + maxfps = high; +}; + +COMMAND(fpsrange, ARG_2INT); + +void keyrepeat(bool on) +{ + SDL_EnableKeyRepeat(on ? SDL_DEFAULT_REPEAT_DELAY : 0, + SDL_DEFAULT_REPEAT_INTERVAL); +}; + +VARF(gamespeed, 10, 100, 1000, if(multiplayer()) gamespeed = 100); + +int islittleendian = 1; +int framesinmap = 0; + +int main(int argc, char **argv) +{ + bool dedicated = false; + int fs = SDL_FULLSCREEN, par = 0, uprate = 0, maxcl = 4; + char *sdesc = "", *ip = "", *master = NULL, *passwd = "", *maprot = ""; + islittleendian = *((char *)&islittleendian); + + #define log(s) puts("init: " s) + log("sdl"); + + for(int i = 1; i200) lastmillis = millis-200; + else if(millis-lastmillis<1) lastmillis = millis-1; + cleardlights(); + updateworld(millis); + if(!demoplayback) serverslice((int)time(NULL), 0); + static float fps = 30.0f; + fps = (1000.0f/curtime+fps*50)/51; + computeraytable(player1->o.x, player1->o.y); + readdepth(scr_w, scr_h); + SDL_GL_SwapBuffers(); + extern void updatevol(); updatevol(); + if(framesinmap++<5) // cheap hack to get rid of initial sparklies, even when triple buffering etc. + { + player1->yaw += 5; + gl_drawframe(scr_w, scr_h, 1.0f, fps); + player1->yaw -= 5; + }; + gl_drawframe(scr_w, scr_h, fpsmaxfps ? fps/maxfps : 1.0f), fps); + //SDL_Delay(100); + SDL_Event event; + int lasttype = 0, lastbut = 0; + while(SDL_PollEvent(&event)) + { + switch(event.type) + { + case SDL_QUIT: + quit(); + break; + + case SDL_KEYDOWN: + case SDL_KEYUP: + keypress(event.key.keysym.sym, event.key.state==SDL_PRESSED, event.key.keysym.unicode); + break; + + case SDL_MOUSEMOTION: + if(ignore) { ignore--; break; }; + mousemove(event.motion.xrel, event.motion.yrel); + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if(lasttype==event.type && lastbut==event.button.button) break; // why?? get event twice without it + keypress(-event.button.button, event.button.state!=0, 0); + lasttype = event.type; + lastbut = event.button.button; + break; + }; + }; + }; + quit(); + return 1; +}; + + diff --git a/source/src/menus.cpp b/source/src/menus.cpp index 58c46d72..ddfbc406 100644 --- a/source/src/menus.cpp +++ b/source/src/menus.cpp @@ -1,223 +1,227 @@ -// menus.cpp: ingame menu system (also used for scores and serverlist) - -#include "cube.h" - -#define FIRSTMENU 120 - -struct mitem { char *text, *action, *hoveraction; }; - -struct gmenu -{ - char *name; - vector items; - int mwidth; - int menusel; - int tex; // (optional) bg tex - char *mdl; // (optional) md2 mdl - int frame, range, rotspeed, scale; -}; - -vector menus; - -int vmenu = -1; - -ivector menustack; - -void menuset(int menu) // EDIT: AH -{ - if((vmenu = menu)>=1 && menu != 2) resetmovement(player1); - if(vmenu==1) menus[1].menusel = 0; -}; - -void showmenu(char *name) -{ - loopv(menus) if(i>1 && strcmp(menus[i].name, name)==0) - { - menuset(i); - return; - }; -}; - -int menucompare(mitem *a, mitem *b) -{ - int x = atoi(a->text); - int y = atoi(b->text); - if(x>y) return -1; - if(x0 && vmenu!=2 ? "[ %s menu ]" : "%s", m.name); // EDIT: AH - int mdisp = m.items.length(); - int w = 0; - loopi(mdisp) - { - int x = text_width(m.items[i].text); - if(x>w) w = x; - }; - int tw = text_width(title); - if(tw>w) w = tw; - int step = FONTH/4*5; - int h = (mdisp+2)*step; - int y = (VIRTH-h)/2; - int x = (VIRTW-w)/2; - blendbox(x-FONTH/2*3, y-FONTH, x+w+FONTH/2*3, y+h+FONTH, true, m.tex >= 0 ? FIRSTMENU + m.tex : -1); - draw_text(title, x, y,2); - y += FONTH*2; - if(vmenu && vmenu!=2) - { - int bh = y+m.menusel*step; - blendbox(x-FONTH, bh-10, x+w+FONTH, bh+FONTH+10, false); - }; - loopj(mdisp) - { - draw_text(m.items[j].text, x, y, 2); - y += step; - }; - return true; -}; - -void rendermenumdl() -{ - if(vmenu<0) { menustack.setsize(0); return; }; - if(vmenu==1) refreshservers(); - gmenu &m = menus[vmenu]; - if(!m.mdl) return; - - glPushMatrix (); - - glTranslatef(player1->o.x, player1->o.z, player1->o.y); - glRotatef(player1->yaw+90+180, 0, -1, 0); - glRotatef(player1->pitch, 0, 0, 1); - - if(strstr(m.mdl, "playermodel")==NULL) glTranslatef(2.0f, 1.7f, 0.0f); - else glTranslatef(2.0f, 1.0f, 1.5f); - if(m.rotspeed) glRotatef(lastmillis/5.0f/100.0f*m.rotspeed, 0, 1, 0); - - rendermodel(m.mdl, m.frame, m.range, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, false, (float)(m.scale ? m.scale/25.0f : 1), 100, 0, 0, false); - glPopMatrix(); -} - -void newmenu(char *name) -{ - gmenu &menu = menus.add(); - menu.name = newstring(name); - menu.menusel = 0; - menu.tex = -1; - menu.mdl = NULL; -}; - -void menumanual(int m, int n, char *text) -{ - if(!n) menus[m].items.setsize(0); - mitem &mitem = menus[m].items.add(); - mitem.text = text; - mitem.action = ""; -} - -void menuitem(char *text, char *action, char *hoveraction) -{ - gmenu &menu = menus.last(); - mitem &mi = menu.items.add(); - mi.text = newstring(text); - mi.action = action[0] ? newstring(action) : mi.text; - if(hoveraction) mi.hoveraction = newstring(hoveraction); - else mi.hoveraction = NULL; -}; - -void menubg(char *tex) -{ - gmenu &menu = menus.last(); - int curmtex = 0; - loopv(menus) curmtex = max(curmtex, menus[i].tex); - if(curmtex>=100) return; - sprintf_sd(path)("packages/%s", tex); - int xs, ys; - if(installtex(FIRSTMENU + curmtex, path, xs, ys, false, true)) - menu.tex = curmtex; -}; - -void menumdl(char *mdl, char *frame, char *range, char *rotspeed, char *scale) -{ - if(!mdl || !frame || !range) return; - gmenu &menu = menus.last(); - menu.mdl = newstring(mdl, _MAXDEFSTR); - menu.frame = max(0, atoi(frame)); - menu.range = max(1, atoi(range)); - menu.rotspeed = max(0, min(atoi(rotspeed), 100)); - menu.scale = max(0, min(atoi(scale), 100)); -}; - -void chmenumdl(char *menu, char *mdl, char *frame, char *range, char *rotspeed, char *scale) -{ - if(!menu || !mdl) return; - loopv(menus) - { - gmenu &m = menus[i]; - if(strcmp(m.name, menu) == 0) - { - if(m.mdl) strcpy_s(m.mdl, mdl); - else m.mdl = newstring(mdl, _MAXDEFSTR); - m.frame = atoi(frame); - m.range = atoi(range); - m.rotspeed = max(0, min(atoi(rotspeed), 100)); - m.scale = max(0, min(atoi(scale), 100)); - return; - }; - }; -}; - - -COMMAND(menuitem, ARG_3STR); -COMMAND(showmenu, ARG_1STR); -COMMAND(newmenu, ARG_1STR); -COMMAND(menubg, ARG_1STR); -COMMAND(menumdl, ARG_5STR); -COMMAND(chmenumdl, ARG_6STR); - -bool menukey(int code, bool isdown) -{ - if(vmenu<=0 || vmenu == 2) return false; // EDIT: AH - int menusel = menus[vmenu].menusel; - if(isdown) - { - int oldmenusel = menusel; - if(code==SDLK_ESCAPE) - { - menuset(-1); - if(!menustack.empty()) menuset(menustack.pop()); - return true; - } - else if(code==SDLK_UP || code==-4) menusel--; - else if(code==SDLK_DOWN || code==-5) menusel++; - int n = menus[vmenu].items.length(); - if(menusel<0) menusel = n-1; - else if(menusel>=n) menusel = 0; - menus[vmenu].menusel = menusel; - if(menusel != oldmenusel)execute(menus[vmenu].items[menusel].hoveraction, true); - } - else - { - if(code==SDLK_RETURN || code==-2) - { - char *action = menus[vmenu].items[menusel].action; - if(vmenu==1) connects(getservername(menusel)); - menustack.add(vmenu); - menuset(-1); - execute(action, true); - }; - }; - return true; -}; +// menus.cpp: ingame menu system (also used for scores and serverlist) + +#include "cube.h" + +#define FIRSTMENU 120 + +struct mitem { char *text, *action, *hoveraction; }; + +struct gmenu +{ + char *name; + vector items; + int mwidth; + int menusel; + int tex; // (optional) bg tex + char *mdl; // (optional) md2 mdl + int frame, range, rotspeed, scale; +}; + +vector menus; + +int vmenu = -1; + +ivector menustack; + +void menuset(int menu) // EDIT: AH +{ + if((vmenu = menu)>=1 && menu != 2) resetmovement(player1); + if(vmenu==1) menus[1].menusel = 0; +}; + +void showmenu(char *name) +{ + loopv(menus) if(i>1 && strcmp(menus[i].name, name)==0) + { + menuset(i); + return; + }; +}; + +int menucompare(mitem *a, mitem *b) +{ + int x = atoi(a->text); + int y = atoi(b->text); + if(x>y) return -1; + if(x0 && vmenu!=2 ? "[ %s menu ]" : "%s", m.name); // EDIT: AH + int mdisp = m.items.length(); + int w = 0; + loopi(mdisp) + { + int x = text_width(m.items[i].text); + if(x>w) w = x; + }; + int tw = text_width(title); + if(tw>w) w = tw; + int step = FONTH/4*5; + int h = (mdisp+2)*step; + int y = (VIRTH-h)/2; + int x = (VIRTW-w)/2; + blendbox(x-FONTH/2*3, y-FONTH, x+w+FONTH/2*3, y+h+FONTH, true, m.tex >= 0 ? FIRSTMENU + m.tex : -1); + draw_text(title, x, y,2); + y += FONTH*2; + if(vmenu && vmenu!=2) + { + int bh = y+m.menusel*step; + blendbox(x-FONTH, bh-10, x+w+FONTH, bh+FONTH+10, false); + }; + loopj(mdisp) + { + draw_text(m.items[j].text, x, y, 2); + y += step; + }; + return true; +}; + +void rendermenumdl() +{ + if(vmenu<0) { menustack.setsize(0); return; }; + if(vmenu==1) refreshservers(); + gmenu &m = menus[vmenu]; + if(!m.mdl) return; + + glPushMatrix (); + + glTranslatef(player1->o.x, player1->o.z, player1->o.y); + glRotatef(player1->yaw+90+180, 0, -1, 0); + glRotatef(player1->pitch, 0, 0, 1); + + if(strstr(m.mdl, "playermodel")==NULL) glTranslatef(2.0f, 1.7f, 0.0f); + else glTranslatef(2.0f, 1.0f, 1.5f); + if(m.rotspeed) glRotatef(lastmillis/5.0f/100.0f*m.rotspeed, 0, 1, 0); + + rendermodel(m.mdl, m.frame, m.range, 0.0f, 0.0f, 0.0f, 0, 0, 0, 0, false, (float)(m.scale ? m.scale/25.0f : 1), 100, 0, 0, false); + glPopMatrix(); +} + +void newmenu(char *name) +{ + gmenu &menu = menus.add(); + menu.name = newstring(name); + menu.menusel = 0; + menu.tex = -1; + menu.mdl = NULL; +}; + +void menumanual(int m, int n, char *text) +{ + if(!n) menus[m].items.setsize(0); + mitem &mitem = menus[m].items.add(); + mitem.text = text; + mitem.action = ""; +} + +void menuitem(char *text, char *action, char *hoveraction) +{ + gmenu &menu = menus.last(); + mitem &mi = menu.items.add(); + mi.text = newstring(text); + mi.action = action[0] ? newstring(action) : mi.text; + if(hoveraction) mi.hoveraction = newstring(hoveraction); + else mi.hoveraction = NULL; +}; + +void menubg(char *tex) +{ + gmenu &menu = menus.last(); + int curmtex = 0; + loopv(menus) curmtex = max(curmtex, menus[i].tex); + if(curmtex>=100) return; + sprintf_sd(path)("packages/%s", tex); + int xs, ys; + if(installtex(FIRSTMENU + curmtex, path, xs, ys, false, true)) + menu.tex = curmtex; +}; + +void menumdl(char *mdl, char *frame, char *range, char *rotspeed, char *scale) +{ + if(!mdl || !frame || !range) return; + gmenu &menu = menus.last(); + menu.mdl = newstring(mdl, _MAXDEFSTR); + menu.frame = max(0, atoi(frame)); + menu.range = max(1, atoi(range)); + menu.rotspeed = max(0, min(atoi(rotspeed), 100)); + menu.scale = max(0, min(atoi(scale), 100)); +}; + +void chmenumdl(char *menu, char *mdl, char *frame, char *range, char *rotspeed, char *scale) +{ + if(!menu || !mdl) return; + loopv(menus) + { + gmenu &m = menus[i]; + if(strcmp(m.name, menu) == 0) + { + if(m.mdl) strcpy_s(m.mdl, mdl); + else m.mdl = newstring(mdl, _MAXDEFSTR); + m.frame = atoi(frame); + m.range = atoi(range); + m.rotspeed = max(0, min(atoi(rotspeed), 100)); + m.scale = max(0, min(atoi(scale), 100)); + return; + }; + }; +}; + + +COMMAND(menuitem, ARG_3STR); +COMMAND(showmenu, ARG_1STR); +COMMAND(newmenu, ARG_1STR); +COMMAND(menubg, ARG_1STR); +COMMAND(menumdl, ARG_5STR); +COMMAND(chmenumdl, ARG_6STR); + +bool menukey(int code, bool isdown) +{ + if(vmenu<=0 || vmenu == 2) return false; // EDIT: AH + int menusel = menus[vmenu].menusel; + + if(isdown) + { + int oldmenusel = menusel; + if(code==SDLK_ESCAPE) + { + menuset(-1); + if(!menustack.empty()) menuset(menustack.pop()); + return true; + } + else if(code==SDLK_UP || code==-4) menusel--; + else if(code==SDLK_DOWN || code==-5) menusel++; + int n = menus[vmenu].items.length(); + if(menusel<0) menusel = n-1; + else if(menusel>=n) menusel = 0; + menus[vmenu].menusel = menusel; + if(menusel != oldmenusel)execute(menus[vmenu].items[menusel].hoveraction, true); + } + else + { + if(code==SDLK_RETURN || code==-2) + { + char *action = menus[vmenu].items[menusel].action; + if(vmenu==1) connects(getservername(menusel)); + menustack.add(vmenu); + menuset(-1); + execute(action, true); + }; + }; + return true; +}; diff --git a/source/src/pch.cpp b/source/src/pch.cpp index ae3cb3ac..5b241e7f 100644 --- a/source/src/pch.cpp +++ b/source/src/pch.cpp @@ -1 +1 @@ -#include "cube.h" +#include "cube.h" diff --git a/source/src/physics.cpp b/source/src/physics.cpp index c617ed55..5d8910a2 100644 --- a/source/src/physics.cpp +++ b/source/src/physics.cpp @@ -1,415 +1,415 @@ -// physics.cpp: no physics books were hurt nor consulted in the construction of this code. -// All physics computations and constants were invented on the fly and simply tweaked until -// they "felt right", and have no basis in reality. Collision detection is simplistic but -// very robust (uses discrete steps at fixed fps). - -#include "cube.h" - -/* Modified by Rick: Fix, so we can jump on players aswell -bool plcollide(dynent *d, dynent *o, float &headspace) // collide with player or monster -{ - if(o->state!=CS_ALIVE) return true; - const float r = o->radius+d->radius; - if(fabs(o->o.x-d->o.x)o.y-d->o.y)o.z-d->o.z)aboveeye+d->eyeheight) return false; - if(d->monsterstate) return false; // hack - headspace = d->o.z-o->o.z-o->aboveeye-d->eyeheight; - if(headspace<0) headspace = 10; - }; - return true; -}; -*/ - -bool plcollide(dynent *d, dynent *o, float &headspace, float &hi, float &lo) // collide with player or monster -{ - if(o->state!=CS_ALIVE) return true; - const float r = o->radius+d->radius; - if(fabs(o->o.x-d->o.x)o.y-d->o.y)o.z-d->eyeheighto.z-o->eyeheight) { if(o->o.z-o->eyeheighto.z-o->eyeheight-1; } - else if(o->o.z+o->aboveeye>lo) lo = o->o.z+o->aboveeye+1; - - if(fabs(o->o.z-d->o.z)aboveeye+d->eyeheight) return false; - //if(d->monsterstate) return false; // hack - headspace = d->o.z-o->o.z-o->aboveeye-d->eyeheight; - if(headspace<0) headspace = 10; - }; - return true; -}; - -bool cornertest(int mip, int x, int y, int dx, int dy, int &bx, int &by, int &bs) // recursively collide with a mipmapped corner cube -{ - sqr *w = wmip[mip]; - int sz = ssize>>mip; - bool stest = SOLID(SWS(w, x+dx, y, sz)) && SOLID(SWS(w, x, y+dy, sz)); - mip++; - x /= 2; - y /= 2; - if(SWS(wmip[mip], x, y, ssize>>mip)->type==CORNER) - { - bx = x<radius; - if(fabs(e.x-d->o.x)o.y)floor+mmi.zoff+e.attr3); - if(d->o.z-d->eyeheightlo) lo = mmz+mmi.h; - }; - }; -}; - -// all collision happens here -// spawn is a dirty side effect used in spawning -// drop & rise are supplied by the physics below to indicate gravity/push for current mini-timestep - -bool collide(dynent *d, bool spawn, float drop, float rise) -{ - const float fx1 = d->o.x-d->radius; // figure out integer cube rectangle this entity covers in map - const float fy1 = d->o.y-d->radius; - const float fx2 = d->o.x+d->radius; - const float fy2 = d->o.y+d->radius; - const int x1 = fast_f2nat(fx1); - const int y1 = fast_f2nat(fy1); - const int x2 = fast_f2nat(fx2); - const int y2 = fast_f2nat(fy2); - float hi = 127, lo = -128; - float minfloor = (d->monsterstate && !spawn && d->health>100) ? d->o.z-d->eyeheight-4.5f : -1000.0f; // big monsters are afraid of heights, unless angry :) - - for(int x = x1; x<=x2; x++) for(int y = y1; y<=y2; y++) // collide with map - { - if(OUTBORD(x,y)) return false; - sqr *s = S(x,y); - float ceil = s->ceil; - float floor = s->floor; - switch(s->type) - { - case SOLID: - return false; - - case CORNER: - { - int bx = x, by = y, bs = 1; - if(x==x1 && y==y1 && cornertest(0, x, y, -1, -1, bx, by, bs) && fx1-bx+fy1-by<=bs - || x==x2 && y==y1 && cornertest(0, x, y, 1, -1, bx, by, bs) && fx2-bx>=fy1-by - || x==x1 && y==y2 && cornertest(0, x, y, -1, 1, bx, by, bs) && fx1-bx<=fy2-by - || x==x2 && y==y2 && cornertest(0, x, y, 1, 1, bx, by, bs) && fx2-bx+fy2-by>=bs) - return false; - break; - }; - - case FHF: // FIXME: too simplistic collision with slopes, makes it feels like tiny stairs - floor -= (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; - break; - - case CHF: - ceil += (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; - - }; - if(ceillo) lo = floor; - if(flooreyeheight+d->aboveeye) return false; - - // Modified by Rick: plcollide now takes hi and lo in account aswell, that way we can jump/walk on players - - float headspace = 10; - loopv(players) // collide with other players - { - dynent *o = players[i]; - if(!o || o==d) continue; - if(!plcollide(d, o, headspace, hi, lo)) return false; - }; - loopv(bots) // Added by Rick: collide with other bots - { - dynent *o = bots[i]; - if(!o || o==d) continue; - if(!plcollide(d, o, headspace, hi, lo)) return false; - }; - - if(d!=player1 && d->mtype!=M_NADE) if(!plcollide(d, player1, headspace, hi, lo)) return false; - //dvector &v = getmonsters(); - // this loop can be a performance bottleneck with many monster on a slow cpu, - // should replace with a blockmap but seems mostly fast enough - // Modified by Rick: Added v[i] pointer check - //loopv(v) if(v[i] && !vreject(d->o, v[i]->o, 7.0f) && d!=v[i] && !plcollide(d, v[i], headspace, hi, lo)) return false; - headspace -= 0.01f; - - mmcollide(d, hi, lo); // collide with map models - - if(spawn) - { - d->o.z = lo+d->eyeheight; // just drop to floor (sideeffect) - d->startheight = d->o.z; //no falling damage on spawn - d->onfloor = true; - } - else - { - const float space = d->o.z-d->eyeheight-lo; - if(space<0) - { - if(space>-0.01) - { - d->o.z = lo+d->eyeheight; // stick on step - } - else if(space>-1.26f && !d->isphysent) d->o.z += rise; // rise thru stair - else return false; - } - else - { - d->o.z -= min(min(drop, space), headspace); // gravity - }; - - const float space2 = hi-(d->o.z+d->aboveeye); - if(space2<0) - { - if(space2<-0.1) return false; // hack alert! - d->o.z = hi-d->aboveeye; // glue to ceiling - d->vel.z = 0; // cancel out jumping velocity - }; - - d->onfloor = d->o.z-d->eyeheight-lo<0.01f; - }; - return true; -} - -float rad(float x) { return x*3.14159f/180; }; - -VAR(maxroll, 0, 3, 20); - -int physicsfraction = 0, physicsrepeat = 0; -const int MINFRAMETIME = 20; // physics always simulated at 50fps or better - -void physicsframe() // optimally schedule physics frames inside the graphics frames -{ - if(curtime>=MINFRAMETIME) - { - int faketime = curtime+physicsfraction; - physicsrepeat = faketime/MINFRAMETIME; - physicsfraction = faketime-physicsrepeat*MINFRAMETIME; - } - else - { - physicsrepeat = 1; - }; -}; - -void selfdamage(dynent *d, int dm) -{ - if(d==player1) selfdamage(dm, -1, d); - else { addmsg(1, 4, SV_DAMAGE, d, dm, d->lifesequence); playsound(S_FALL1+rnd(5), &d->o); }; - particle_splash(3, dm, 1000, d->o); //edit out? - demodamage(dm, d->o); -}; - - -// main physics routine, moves a player/monster for a curtime step -// moveres indicated the physics precision (which is lower for monsters and multiplayer prediction) -// local is false for multiplayer prediction - -void moveplayer(dynent *pl, int moveres, bool local, int curtime) -{ - const bool water = hdr.waterlevel>pl->o.z-0.5f; - const bool floating = (editmode && local) || pl->state==CS_EDITING; - - vec d; // vector of direction we ideally want to move in - - - int move = pl->onladder && !pl->onfloor && pl->move == -1 ? 0 : pl->move; // fix movement on ladder - - d.x = (float)(move*cos(rad(pl->yaw-90))); - d.y = (float)(move*sin(rad(pl->yaw-90))); - d.z = (float) pl->isphysent ? pl->vel.z : 0; - - if(floating || water) - { - d.x *= (float)cos(rad(pl->pitch)); - d.y *= (float)cos(rad(pl->pitch)); - d.z = (float)(move*sin(rad(pl->pitch))); - }; - - d.x += (float)(pl->strafe*cos(rad(pl->yaw-180))); - d.y += (float)(pl->strafe*sin(rad(pl->yaw-180))); - - const float speed = curtime/(water ? 2000.0f : 1000.0f)*pl->maxspeed; - const float friction = water ? 20.0f : (pl->onfloor || floating ? 6.0f : (pl->onladder ? 1.5f : 30.0f)); - - const float fpsfric = friction/curtime*20.0f; - - vmul(pl->vel, fpsfric-1); // slowly apply friction and direction to velocity, gives a smooth movement - vadd(pl->vel, d); - vdiv(pl->vel, fpsfric); - d = pl->vel; - vmul(d, speed); // d is now frametime based velocity vector - - if(pl->isphysent) - { - pl->pitch += ((float) sqrt(dotprod(pl->vel, pl->vel)))*speed*5.0f; - if(pl->pitch>360.0f) pl->pitch = 0.0f; - pl->yaw += ((float) sqrt(dotprod(pl->vel, pl->vel)))*speed*5.0f; - if(pl->yaw>360.0f) pl->yaw = 0.0f; - } - - pl->blocked = false; - pl->moving = true; - - if(floating) // just apply velocity - { - vadd(pl->o, d); - if(pl->jumpnext) { pl->jumpnext = false; pl->vel.z = 2; } - } - else // apply velocity with collision - { - if(pl->onladder) - { - if(pl->k_up) pl->vel.z = 0.75; - else if(pl->k_down) pl->vel.z = -0.75; - pl->timeinair = 0; - } - else - { - - if(pl->onfloor || water) - { - if(pl->onfloor || !pl->vel.z) - { - int damage = (int)pl->startheight - (int)pl->o.z; - if (damage > 8 && local) //try adding "&& local" if falling not fixed - { - selfdamage(damage,-1,pl); - demodamage(damage, player1->o); - playsound(S_FALL1+rnd(2), &pl->o); - }; - pl->startheight=pl->o.z; - }; - - if(pl->jumpnext) - { - pl->jumpnext = false; - pl->vel.z = 1.7f; // physics impulse upwards - if(water) { pl->vel.x /= 8; pl->vel.y /= 8; }; // dampen velocity change even harder, gives correct water feel - if(local) playsoundc(S_JUMP); - else if(pl->monsterstate) playsound(S_JUMP, &pl->o); - else if(pl->bIsBot) botplaysound(S_JUMP, pl); // Added by Rick - } - /*else if(pl->timeinair>800) // if we land after long time must have been a high jump, make thud sound - { - if(local) playsoundc(S_LAND); - else if(pl->monsterstate) playsound(S_LAND, &pl->o); - else if(pl->bIsBot) botplaysound(S_LAND, pl); // Added by Rick - };*/ - pl->timeinair = 0; - if(pl->isphysent) pl->vel.z *= 0.7f; - } - else - { - pl->timeinair += curtime; - }; - }; - - const float gravity = pl->isphysent ? pl->gravity : 20; - const float f = 1.0f/moveres; - float dropf = pl->isphysent ? ((gravity-1)+pl->timeinair/14.0f) : ((gravity-1)+pl->timeinair/15.0f); // incorrect, but works fine - if(water) { dropf = 5; pl->timeinair = 0; }; // float slowly down in water - if(pl->onladder) { dropf = 0; pl->timeinair = 0; }; - float drop = dropf*curtime/gravity/100/moveres; // at high fps, gravity kicks in too fast - const float rise = speed/moveres/1.2f; // extra smoothness when lifting up stairs - - loopi(moveres) // discrete steps collision detection & sliding - { - // try move forward - pl->o.x += f*d.x; - pl->o.y += f*d.y; - pl->o.z += f*d.z; - if(collide(pl, false, drop, rise)) continue; - // player stuck, try slide along y axis - pl->blocked = true; - pl->o.x -= f*d.x; - if(collide(pl, false, drop, rise)) - { - d.x = 0; - if(pl->isphysent) pl->vel.x = -pl->vel.x; - continue; - }; - pl->o.x += f*d.x; - // still stuck, try x axis - pl->o.y -= f*d.y; - if(collide(pl, false, drop, rise)) - { - d.y = 0; - if(pl->isphysent) pl->vel.y = -pl->vel.y; - continue; - }; - pl->o.y += f*d.y; - // try just dropping down - pl->moving = false; - pl->o.x -= f*d.x; - pl->o.y -= f*d.y; - if(collide(pl, false, drop, rise)) - { - d.y = d.x = 0; - continue; - }; - pl->o.z -= f*d.z; - break; - }; - }; - - // detect wether player is outside map, used for skipping zbuffer clear mostly - - if(pl->o.x < 0 || pl->o.x >= ssize || pl->o.y <0 || pl->o.y > ssize) - { - pl->outsidemap = true; - } - else - { - sqr *s = S((int)pl->o.x, (int)pl->o.y); - pl->outsidemap = SOLID(s) - || pl->o.z < s->floor - (s->type==FHF ? s->vdelta/4 : 0) - || pl->o.z > s->ceil + (s->type==CHF ? s->vdelta/4 : 0); - }; - - // automatically apply smooth roll when strafing - - if(pl->strafe==0) - { - pl->roll = pl->roll/(1+(float)sqrt((float)curtime)/25); - } - else - { - pl->roll += pl->strafe*curtime/-30.0f; - if(pl->roll>maxroll) pl->roll = (float)maxroll; - if(pl->roll<-maxroll) pl->roll = (float)-maxroll; - }; - - // play sounds on water transitions - - if(!pl->inwater && water) { playsound(S_SPLASH2, &pl->o); pl->vel.z = 0; } - else if(pl->inwater && !water) playsound(S_SPLASH1, &pl->o); - pl->inwater = water; - // Added by Rick: Easy hack to store previous locations of all players/monsters/bots - pl->PrevLocations.Update(pl->o); - // End add -}; - -void moveplayer(dynent *pl, int moveres, bool local) -{ - loopi(physicsrepeat) moveplayer(pl, moveres, local, i ? curtime/physicsrepeat : curtime-curtime/physicsrepeat*(physicsrepeat-1)); -}; +// physics.cpp: no physics books were hurt nor consulted in the construction of this code. +// All physics computations and constants were invented on the fly and simply tweaked until +// they "felt right", and have no basis in reality. Collision detection is simplistic but +// very robust (uses discrete steps at fixed fps). + +#include "cube.h" + +/* Modified by Rick: Fix, so we can jump on players aswell +bool plcollide(dynent *d, dynent *o, float &headspace) // collide with player or monster +{ + if(o->state!=CS_ALIVE) return true; + const float r = o->radius+d->radius; + if(fabs(o->o.x-d->o.x)o.y-d->o.y)o.z-d->o.z)aboveeye+d->eyeheight) return false; + if(d->monsterstate) return false; // hack + headspace = d->o.z-o->o.z-o->aboveeye-d->eyeheight; + if(headspace<0) headspace = 10; + }; + return true; +}; +*/ + +bool plcollide(dynent *d, dynent *o, float &headspace, float &hi, float &lo) // collide with player or monster +{ + if(o->state!=CS_ALIVE) return true; + const float r = o->radius+d->radius; + if(fabs(o->o.x-d->o.x)o.y-d->o.y)o.z-d->eyeheighto.z-o->eyeheight) { if(o->o.z-o->eyeheighto.z-o->eyeheight-1; } + else if(o->o.z+o->aboveeye>lo) lo = o->o.z+o->aboveeye+1; + + if(fabs(o->o.z-d->o.z)aboveeye+d->eyeheight) return false; + //if(d->monsterstate) return false; // hack + headspace = d->o.z-o->o.z-o->aboveeye-d->eyeheight; + if(headspace<0) headspace = 10; + }; + return true; +}; + +bool cornertest(int mip, int x, int y, int dx, int dy, int &bx, int &by, int &bs) // recursively collide with a mipmapped corner cube +{ + sqr *w = wmip[mip]; + int sz = ssize>>mip; + bool stest = SOLID(SWS(w, x+dx, y, sz)) && SOLID(SWS(w, x, y+dy, sz)); + mip++; + x /= 2; + y /= 2; + if(SWS(wmip[mip], x, y, ssize>>mip)->type==CORNER) + { + bx = x<radius; + if(fabs(e.x-d->o.x)o.y)floor+mmi.zoff+e.attr3); + if(d->o.z-d->eyeheightlo) lo = mmz+mmi.h; + }; + }; +}; + +// all collision happens here +// spawn is a dirty side effect used in spawning +// drop & rise are supplied by the physics below to indicate gravity/push for current mini-timestep + +bool collide(dynent *d, bool spawn, float drop, float rise) +{ + const float fx1 = d->o.x-d->radius; // figure out integer cube rectangle this entity covers in map + const float fy1 = d->o.y-d->radius; + const float fx2 = d->o.x+d->radius; + const float fy2 = d->o.y+d->radius; + const int x1 = fast_f2nat(fx1); + const int y1 = fast_f2nat(fy1); + const int x2 = fast_f2nat(fx2); + const int y2 = fast_f2nat(fy2); + float hi = 127, lo = -128; + float minfloor = (d->monsterstate && !spawn && d->health>100) ? d->o.z-d->eyeheight-4.5f : -1000.0f; // big monsters are afraid of heights, unless angry :) + + for(int x = x1; x<=x2; x++) for(int y = y1; y<=y2; y++) // collide with map + { + if(OUTBORD(x,y)) return false; + sqr *s = S(x,y); + float ceil = s->ceil; + float floor = s->floor; + switch(s->type) + { + case SOLID: + return false; + + case CORNER: + { + int bx = x, by = y, bs = 1; + if(x==x1 && y==y1 && cornertest(0, x, y, -1, -1, bx, by, bs) && fx1-bx+fy1-by<=bs + || x==x2 && y==y1 && cornertest(0, x, y, 1, -1, bx, by, bs) && fx2-bx>=fy1-by + || x==x1 && y==y2 && cornertest(0, x, y, -1, 1, bx, by, bs) && fx1-bx<=fy2-by + || x==x2 && y==y2 && cornertest(0, x, y, 1, 1, bx, by, bs) && fx2-bx+fy2-by>=bs) + return false; + break; + }; + + case FHF: // FIXME: too simplistic collision with slopes, makes it feels like tiny stairs + floor -= (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; + break; + + case CHF: + ceil += (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; + + }; + if(ceillo) lo = floor; + if(flooreyeheight+d->aboveeye) return false; + + // Modified by Rick: plcollide now takes hi and lo in account aswell, that way we can jump/walk on players + + float headspace = 10; + loopv(players) // collide with other players + { + dynent *o = players[i]; + if(!o || o==d) continue; + if(!plcollide(d, o, headspace, hi, lo)) return false; + }; + loopv(bots) // Added by Rick: collide with other bots + { + dynent *o = bots[i]; + if(!o || o==d) continue; + if(!plcollide(d, o, headspace, hi, lo)) return false; + }; + + if(d!=player1 && d->mtype!=M_NADE) if(!plcollide(d, player1, headspace, hi, lo)) return false; + //dvector &v = getmonsters(); + // this loop can be a performance bottleneck with many monster on a slow cpu, + // should replace with a blockmap but seems mostly fast enough + // Modified by Rick: Added v[i] pointer check + //loopv(v) if(v[i] && !vreject(d->o, v[i]->o, 7.0f) && d!=v[i] && !plcollide(d, v[i], headspace, hi, lo)) return false; + headspace -= 0.01f; + + mmcollide(d, hi, lo); // collide with map models + + if(spawn) + { + d->o.z = lo+d->eyeheight; // just drop to floor (sideeffect) + d->startheight = d->o.z; //no falling damage on spawn + d->onfloor = true; + } + else + { + const float space = d->o.z-d->eyeheight-lo; + if(space<0) + { + if(space>-0.01) + { + d->o.z = lo+d->eyeheight; // stick on step + } + else if(space>-1.26f && !d->isphysent) d->o.z += rise; // rise thru stair + else return false; + } + else + { + d->o.z -= min(min(drop, space), headspace); // gravity + }; + + const float space2 = hi-(d->o.z+d->aboveeye); + if(space2<0) + { + if(space2<-0.1) return false; // hack alert! + d->o.z = hi-d->aboveeye; // glue to ceiling + d->vel.z = 0; // cancel out jumping velocity + }; + + d->onfloor = d->o.z-d->eyeheight-lo<0.01f; + }; + return true; +} + +float rad(float x) { return x*3.14159f/180; }; + +VAR(maxroll, 0, 3, 20); + +int physicsfraction = 0, physicsrepeat = 0; +const int MINFRAMETIME = 20; // physics always simulated at 50fps or better + +void physicsframe() // optimally schedule physics frames inside the graphics frames +{ + if(curtime>=MINFRAMETIME) + { + int faketime = curtime+physicsfraction; + physicsrepeat = faketime/MINFRAMETIME; + physicsfraction = faketime-physicsrepeat*MINFRAMETIME; + } + else + { + physicsrepeat = 1; + }; +}; + +void selfdamage(dynent *d, int dm) +{ + if(d==player1) selfdamage(dm, -1, d); + else { addmsg(1, 4, SV_DAMAGE, d, dm, d->lifesequence); playsound(S_FALL1+rnd(5), &d->o); }; + particle_splash(3, dm, 1000, d->o); //edit out? + demodamage(dm, d->o); +}; + + +// main physics routine, moves a player/monster for a curtime step +// moveres indicated the physics precision (which is lower for monsters and multiplayer prediction) +// local is false for multiplayer prediction + +void moveplayer(dynent *pl, int moveres, bool local, int curtime) +{ + const bool water = hdr.waterlevel>pl->o.z-0.5f; + const bool floating = (editmode && local) || pl->state==CS_EDITING; + + vec d; // vector of direction we ideally want to move in + + + int move = pl->onladder && !pl->onfloor && pl->move == -1 ? 0 : pl->move; // fix movement on ladder + + d.x = (float)(move*cos(rad(pl->yaw-90))); + d.y = (float)(move*sin(rad(pl->yaw-90))); + d.z = (float) pl->isphysent ? pl->vel.z : 0; + + if(floating || water) + { + d.x *= (float)cos(rad(pl->pitch)); + d.y *= (float)cos(rad(pl->pitch)); + d.z = (float)(move*sin(rad(pl->pitch))); + }; + + d.x += (float)(pl->strafe*cos(rad(pl->yaw-180))); + d.y += (float)(pl->strafe*sin(rad(pl->yaw-180))); + + const float speed = curtime/(water ? 2000.0f : 1000.0f)*pl->maxspeed; + const float friction = water ? 20.0f : (pl->onfloor || floating ? 6.0f : (pl->onladder ? 1.5f : 30.0f)); + + const float fpsfric = friction/curtime*20.0f; + + vmul(pl->vel, fpsfric-1); // slowly apply friction and direction to velocity, gives a smooth movement + vadd(pl->vel, d); + vdiv(pl->vel, fpsfric); + d = pl->vel; + vmul(d, speed); // d is now frametime based velocity vector + + if(pl->isphysent) + { + pl->pitch += ((float) sqrt(dotprod(pl->vel, pl->vel)))*speed*5.0f; + if(pl->pitch>360.0f) pl->pitch = 0.0f; + pl->yaw += ((float) sqrt(dotprod(pl->vel, pl->vel)))*speed*5.0f; + if(pl->yaw>360.0f) pl->yaw = 0.0f; + } + + pl->blocked = false; + pl->moving = true; + + if(floating) // just apply velocity + { + vadd(pl->o, d); + if(pl->jumpnext) { pl->jumpnext = false; pl->vel.z = 2; } + } + else // apply velocity with collision + { + if(pl->onladder) + { + if(pl->k_up) pl->vel.z = 0.75; + else if(pl->k_down) pl->vel.z = -0.75; + pl->timeinair = 0; + } + else + { + + if(pl->onfloor || water) + { + if(pl->onfloor || !pl->vel.z) + { + int damage = (int)pl->startheight - (int)pl->o.z; + if (damage > 8 && local) //try adding "&& local" if falling not fixed + { + selfdamage(damage,-1,pl); + demodamage(damage, player1->o); + playsound(S_FALL1+rnd(2), &pl->o); + }; + pl->startheight=pl->o.z; + }; + + if(pl->jumpnext) + { + pl->jumpnext = false; + pl->vel.z = 1.7f; // physics impulse upwards + if(water) { pl->vel.x /= 8; pl->vel.y /= 8; }; // dampen velocity change even harder, gives correct water feel + if(local) playsoundc(S_JUMP); + else if(pl->monsterstate) playsound(S_JUMP, &pl->o); + else if(pl->bIsBot) botplaysound(S_JUMP, pl); // Added by Rick + } + /*else if(pl->timeinair>800) // if we land after long time must have been a high jump, make thud sound + { + if(local) playsoundc(S_LAND); + else if(pl->monsterstate) playsound(S_LAND, &pl->o); + else if(pl->bIsBot) botplaysound(S_LAND, pl); // Added by Rick + };*/ + pl->timeinair = 0; + if(pl->isphysent) pl->vel.z *= 0.7f; + } + else + { + pl->timeinair += curtime; + }; + }; + + const float gravity = pl->isphysent ? pl->gravity : 20; + const float f = 1.0f/moveres; + float dropf = pl->isphysent ? ((gravity-1)+pl->timeinair/14.0f) : ((gravity-1)+pl->timeinair/15.0f); // incorrect, but works fine + if(water) { dropf = 5; pl->timeinair = 0; }; // float slowly down in water + if(pl->onladder) { dropf = 0; pl->timeinair = 0; }; + float drop = dropf*curtime/gravity/100/moveres; // at high fps, gravity kicks in too fast + const float rise = speed/moveres/1.2f; // extra smoothness when lifting up stairs + + loopi(moveres) // discrete steps collision detection & sliding + { + // try move forward + pl->o.x += f*d.x; + pl->o.y += f*d.y; + pl->o.z += f*d.z; + if(collide(pl, false, drop, rise)) continue; + // player stuck, try slide along y axis + pl->blocked = true; + pl->o.x -= f*d.x; + if(collide(pl, false, drop, rise)) + { + d.x = 0; + if(pl->isphysent) pl->vel.x = -pl->vel.x; + continue; + }; + pl->o.x += f*d.x; + // still stuck, try x axis + pl->o.y -= f*d.y; + if(collide(pl, false, drop, rise)) + { + d.y = 0; + if(pl->isphysent) pl->vel.y = -pl->vel.y; + continue; + }; + pl->o.y += f*d.y; + // try just dropping down + pl->moving = false; + pl->o.x -= f*d.x; + pl->o.y -= f*d.y; + if(collide(pl, false, drop, rise)) + { + d.y = d.x = 0; + continue; + }; + pl->o.z -= f*d.z; + break; + }; + }; + + // detect wether player is outside map, used for skipping zbuffer clear mostly + + if(pl->o.x < 0 || pl->o.x >= ssize || pl->o.y <0 || pl->o.y > ssize) + { + pl->outsidemap = true; + } + else + { + sqr *s = S((int)pl->o.x, (int)pl->o.y); + pl->outsidemap = SOLID(s) + || pl->o.z < s->floor - (s->type==FHF ? s->vdelta/4 : 0) + || pl->o.z > s->ceil + (s->type==CHF ? s->vdelta/4 : 0); + }; + + // automatically apply smooth roll when strafing + + if(pl->strafe==0) + { + pl->roll = pl->roll/(1+(float)sqrt((float)curtime)/25); + } + else + { + pl->roll += pl->strafe*curtime/-30.0f; + if(pl->roll>maxroll) pl->roll = (float)maxroll; + if(pl->roll<-maxroll) pl->roll = (float)-maxroll; + }; + + // play sounds on water transitions + + if(!pl->inwater && water) { playsound(S_SPLASH2, &pl->o); pl->vel.z = 0; } + else if(pl->inwater && !water) playsound(S_SPLASH1, &pl->o); + pl->inwater = water; + // Added by Rick: Easy hack to store previous locations of all players/monsters/bots + pl->PrevLocations.Update(pl->o); + // End add +}; + +void moveplayer(dynent *pl, int moveres, bool local) +{ + loopi(physicsrepeat) moveplayer(pl, moveres, local, i ? curtime/physicsrepeat : curtime-curtime/physicsrepeat*(physicsrepeat-1)); +}; diff --git a/source/src/protos.h b/source/src/protos.h index 9722b8d1..668a01dc 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -1,278 +1,279 @@ -// protos for ALL external functions in cube... - -// command -extern int variable(char *name, int min, int cur, int max, int *storage, void (*fun)(), bool persist); -extern void setvar(char *name, int i); -extern int getvar(char *name); -extern bool identexists(char *name); -extern bool addcommand(char *name, void (*fun)(), int narg); -extern int execute(char *p, bool down = true); -extern void exec(char *cfgfile); -extern bool execfile(char *cfgfile); -extern void resetcomplete(); -extern void complete(char *s); -extern void alias(char *name, char *action); -extern char *getalias(char *name); -extern void writecfg(); - -// console -extern void keypress(int code, bool isdown, int cooked); -extern void renderconsole(); -// extern void conoutf(const char *s, int a = 0, int b = 0, int c = 0); Moved to cube.h by Rick -extern char *getcurcommand(); - -// menus -extern bool rendermenu(); -extern void menuset(int menu); -extern void menumanual(int m, int n, char *text); -extern void sortmenu(int m, int start, int num); -extern bool menukey(int code, bool isdown); -extern void newmenu(char *name); -extern void rendermenumdl(); - -// serverbrowser -extern void addserver(char *servername); -extern char *getservername(int n); -extern void writeservercfg(); - -// rendergl -extern void gl_init(int w, int h); -extern void cleangl(); -extern void gl_drawframe(int w, int h, float changelod, float curfps); -extern bool installtex(int tnum, char *texname, int &xs, int &ys, bool clamp = false, bool highqual=false); -extern void mipstats(int a, int b, int c); -extern void vertf(float v1, float v2, float v3, sqr *ls, float t1, float t2); -extern void addstrip(int tex, int start, int n); -extern int lookuptexture(int tex, int &xs, int &ys); -extern char *hudgunnames[]; - -// rendercubes -extern void resetcubes(); -extern void render_flat(int tex, int x, int y, int size, int h, sqr *l1, sqr *l2, sqr *l3, sqr *l4, bool isceil); -extern void render_flatdelta(int wtex, int x, int y, int size, float h1, float h2, float h3, float h4, sqr *l1, sqr *l2, sqr *l3, sqr *l4, bool isceil); -extern void render_square(int wtex, float floor1, float floor2, float ceil1, float ceil2, int x1, int y1, int x2, int y2, int size, sqr *l1, sqr *l2, bool topleft); -extern void render_tris(int x, int y, int size, bool topleft, sqr *h1, sqr *h2, sqr *s, sqr *t, sqr *u, sqr *v); -extern void addwaterquad(int x, int y, int size); -extern int renderwater(float hf); -extern void finishstrips(); -extern void setarraypointers(); - -// client -extern void localservertoclient(uchar *buf, int len); -extern void connects(char *servername); -extern void disconnect(int onlyclean = 0, int async = 0); -extern void toserver(char *text); -extern void addmsg(int rel, int num, int type, ...); -extern bool multiplayer(); -extern bool allowedittoggle(); -extern void sendpackettoserv(void *packet); -extern void gets2c(); -extern void c2sinfo(dynent *d); -extern void neterr(char *s); -extern void initclientnet(); -extern bool netmapstart(); -extern int getclientnum(); -extern void changemapserv(char *name, int mode); -extern void newteam(char *name); - -// clientgame -extern flaginfo flaginfos[2]; -extern void mousemove(int dx, int dy); -extern void updateworld(int millis); -extern void startmap(char *name); -extern void changemap(char *name); -extern void initclient(); -extern void spawnplayer(dynent *d, bool secure=false); -extern void selfdamage(int damage, int actor, dynent *act); -extern dynent *newdynent(); -extern char *getclientmap(); -extern const char *modestr(int n); -extern void zapdynent(dynent *&d); -extern dynent *getclient(int cn); -extern dynent *getbot(int cn); // Added by Rick -extern void timeupdate(int timeremain); -extern void resetmovement(dynent *d); -extern void fixplayer1range(); -extern void respawnself(); // EDIT: AH -extern void ctf_death(); -//game mode extras -extern void arenarespawn(); -extern void respawn(); - -// clientextras -extern void renderclients(); -extern void renderclient(dynent *d, bool team, char *mdlname, bool hellpig, float scale); -void showscores(bool on); -extern void renderscores(); - -// world -extern void setupworld(int factor); -extern void empty_world(int factor, bool force); -extern void remip(block &b, int level = 0); -extern void remipmore(block &b, int level = 0); -extern int closestent(); -extern int findentity(int type, int index = 0); -extern int findplayerstart(int team, int index); -extern void trigger(int tag, int type, bool savegame); -extern void resettagareas(); -extern void settagareas(); -extern entity *newentity(int x, int y, int z, char *what, int v1, int v2, int v3, int v4); - -// worldlight -extern void calclight(); -extern void dodynlight(vec &vold, vec &v, int reach, int strength, dynent *owner); -extern void cleardlights(); -extern block *blockcopy(block &b); -extern void blockpaste(block &b); - -// worldrender -extern void render_world(float vx, float vy, float vh, float changelod, int yaw, int pitch, float widef, int w, int h); -extern int lod_factor(); - -// worldocull -extern void computeraytable(float vx, float vy); -extern int isoccluded(float vx, float vy, float cx, float cy, float csize); - -// main -extern void fatal(char *s, char *o = ""); -extern void *alloc(int s); -extern void keyrepeat(bool on); - -// rendertext -extern void draw_text(char *str, int left, int top, int gl_num); -extern void draw_textf(char *fstr, int left, int top, int gl_num, int arg); -extern int text_width(char *str); -extern void draw_envbox(int t, int fogdist); - -// editing -extern void cursorupdate(); -extern void toggleedit(); -extern void editdrag(bool isdown); -extern void setvdeltaxy(int delta, block &sel); -extern void editequalisexy(bool isfloor, block &sel); -extern void edittypexy(int type, block &sel); -extern void edittexxy(int type, int t, block &sel); -extern void editheightxy(bool isfloor, int amount, block &sel); -extern bool noteditmode(); -extern void pruneundos(int maxremain = 0); - -// renderextras -extern void line(int x1, int y1, float z1, int x2, int y2, float z2); -extern void box(block &b, float z1, float z2, float z3, float z4); -extern void dot(int x, int y, float z); -extern void linestyle(float width, int r, int g, int b); -extern void newsphere(vec &o, float max, int type); -extern void renderspheres(int time); -extern void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater); -extern void readdepth(int w, int h); -extern void blendbox(int x1, int y1, int x2, int y2, bool border, int tex = -1); -extern void damageblend(int n); -extern void addshotline(dynent *d, vec &from, vec &to); -extern void rendershotlines(); -extern void shotlinereset(); -extern void renderphysents(); -extern bool scoped; - -// renderparticles -extern void setorient(vec &r, vec &u); -extern void particle_splash(int type, int num, int fade, vec &p); -//extern void particle_splash(int num, int fade, vec &p); -// extern void particle_trail(int type, int fade, vec &from, vec &to); Moved to cube.h by Rick -extern void render_particles(int time); - -// worldio -extern void save_world(char *fname); -extern void load_world(char *mname); -extern void writemap(char *mname, int msize, uchar *mdata); -extern uchar *readmap(char *mname, int *msize); -extern void loadgamerest(); -extern void incomingdemodata(uchar *buf, int len, bool extras = false); -extern void demoplaybackstep(); -extern void stop(); -extern void stopifrecording(); -extern void demodamage(int damage, vec &o); -extern void demoblend(int damage); - -// physics -extern void moveplayer(dynent *pl, int moveres, bool local); -extern bool collide(dynent *d, bool spawn, float drop, float rise); -extern void entinmap(dynent *d); -extern void setentphysics(int mml, int mmr); -extern void physicsframe(); -extern void movephysent(physent *pl); - -// sound -extern void playsound(int n, vec *loc = 0); -extern void playsoundc(int n); -extern void initsound(); -extern void cleansound(); -extern void botplaysound(int n, dynent *b); // Added by Rick - -// rendermd2 -extern void rendermodel(char *mdl, int frame, int range, int tex, float rad, float x, float y, float z, float yaw, float pitch, bool teammate, float scale, float speed, int snap = 0, int basetime = 0, bool oculling=true); -extern mapmodelinfo &getmminfo(int i); - -// rendermd3 -extern void rendermd3gun(); -extern void loadweapons(); - -// server -extern void initserver(bool dedicated, int uprate, char *sdesc, char *ip, char *master, char *passwd, int maxcl, char *maprot); // EDIT: AH -extern void cleanupserver(); -extern void localconnect(); -extern void localdisconnect(); -extern void localclienttoserver(struct _ENetPacket *); -extern void serverslice(int seconds, unsigned int timeout); -extern void putint(uchar *&p, int n); -extern int getint(uchar *&p); -extern void sendstring(char *t, uchar *&p); -extern void startintermission(); -extern void restoreserverstate(vector &ents); -extern uchar *retrieveservers(uchar *buf, int buflen); -extern char msgsizelookup(int msg); -extern void serverms(int mode, int numplayers, int minremain, char *smapname, int seconds, bool isfull); -extern void servermsinit(const char *master, char *sdesc, bool listen); -extern void sendmaps(int n, string mapname, int mapsize, uchar *mapdata); -extern ENetPacket *recvmap(int n); - -// weapon -//extern void selectgun(int a = -1, int b = -1, int c =-1); -extern void shoot(dynent *d, vec &to); -extern void shootv(int gun, vec &from, vec &to, dynent *d = 0, bool local = false, int nadesec = -1); -extern void createrays(vec &from, vec &to); -extern void moveprojectiles(float time); -extern void projreset(); -extern dynent *playerincrosshair(); -extern int reloadtime(int gun); -extern void reload(dynent *d); -extern int attackdelay(int gun); -extern int magsize(int gun); -extern int kick_rot(int gun); -extern int kick_back(int gun); -extern bool gun_changed; -extern bool akimboside; -extern int akimbolastaction[2]; -// Added by Rick -extern char *botincrosshair(); -extern bool intersect(dynent *d, vec &from, vec &to, vec *end = NULL); -// End add by Rick - -// entities -extern void renderents(); -extern void putitems(uchar *&p); -extern void realpickup(int n, dynent *d); -extern void flagaction(int team, int action); -extern void renderentities(); -extern void resetspawns(); -extern void setspawn(int i, bool on); -extern void baseammo(int gun); -extern void checkitems(); -extern void radd(dynent *d); -// Added by Rick -extern void botbaseammo(int gun, dynent *d); -extern bool intersect(entity *e, vec &from, vec &to, vec *end=NULL); -// End add by Rick - -// rndmap -extern void perlinarea(block &b, int scale, int seed, int psize); - +// protos for ALL external functions in cube... + +// command +extern int variable(char *name, int min, int cur, int max, int *storage, void (*fun)(), bool persist); +extern void setvar(char *name, int i); +extern int getvar(char *name); +extern bool identexists(char *name); +extern bool addcommand(char *name, void (*fun)(), int narg); +extern int execute(char *p, bool down = true); +extern void exec(char *cfgfile); +extern bool execfile(char *cfgfile); +extern void resetcomplete(); +extern void complete(char *s); +extern void alias(char *name, char *action); +extern char *getalias(char *name); +extern void writecfg(); + +// console +extern void keypress(int code, bool isdown, int cooked); +extern void renderconsole(); +// extern void conoutf(const char *s, int a = 0, int b = 0, int c = 0); Moved to cube.h by Rick +extern char *getcurcommand(); + +// menus +extern bool rendermenu(); +extern void menuset(int menu); +extern void menumanual(int m, int n, char *text); +extern void sortmenu(int m, int start, int num); +extern bool menukey(int code, bool isdown); +extern void newmenu(char *name); +extern void rendermenumdl(); + +// serverbrowser +extern void addserver(char *servername); +extern char *getservername(int n); +extern void writeservercfg(); + +// rendergl +extern void gl_init(int w, int h); +extern void cleangl(); +extern void gl_drawframe(int w, int h, float changelod, float curfps); +extern bool installtex(int tnum, char *texname, int &xs, int &ys, bool clamp = false, bool highqual=false); +extern void mipstats(int a, int b, int c); +extern void vertf(float v1, float v2, float v3, sqr *ls, float t1, float t2); +extern void addstrip(int tex, int start, int n); +extern int lookuptexture(int tex, int &xs, int &ys); +extern char *hudgunnames[]; + +// rendercubes +extern void resetcubes(); +extern void render_flat(int tex, int x, int y, int size, int h, sqr *l1, sqr *l2, sqr *l3, sqr *l4, bool isceil); +extern void render_flatdelta(int wtex, int x, int y, int size, float h1, float h2, float h3, float h4, sqr *l1, sqr *l2, sqr *l3, sqr *l4, bool isceil); +extern void render_square(int wtex, float floor1, float floor2, float ceil1, float ceil2, int x1, int y1, int x2, int y2, int size, sqr *l1, sqr *l2, bool topleft); +extern void render_tris(int x, int y, int size, bool topleft, sqr *h1, sqr *h2, sqr *s, sqr *t, sqr *u, sqr *v); +extern void addwaterquad(int x, int y, int size); +extern int renderwater(float hf); +extern void finishstrips(); +extern void setarraypointers(); + +// client +extern void localservertoclient(uchar *buf, int len); +extern void connects(char *servername); +extern void disconnect(int onlyclean = 0, int async = 0); +extern void toserver(char *text); +extern void addmsg(int rel, int num, int type, ...); +extern bool multiplayer(); +extern bool allowedittoggle(); +extern void sendpackettoserv(void *packet); +extern void gets2c(); +extern void c2sinfo(dynent *d); +extern void neterr(char *s); +extern void initclientnet(); +extern bool netmapstart(); +extern int getclientnum(); +extern void changemapserv(char *name, int mode); +extern void newteam(char *name); + +// clientgame +extern flaginfo flaginfos[2]; +extern void mousemove(int dx, int dy); +extern void updateworld(int millis); +extern void startmap(char *name); +extern void changemap(char *name); +extern void initclient(); +extern void spawnplayer(dynent *d, bool secure=false); +extern void selfdamage(int damage, int actor, dynent *act); +extern dynent *newdynent(); +extern char *getclientmap(); +extern const char *modestr(int n); +extern void zapdynent(dynent *&d); +extern dynent *getclient(int cn); +extern dynent *getbot(int cn); // Added by Rick +extern void timeupdate(int timeremain); +extern void resetmovement(dynent *d); +extern void fixplayer1range(); +extern void respawnself(); // EDIT: AH +extern void ctf_death(); +//game mode extras +extern void arenarespawn(); +extern void respawn(); + +// clientextras +extern void renderclients(); +extern void renderclient(dynent *d, bool team, char *mdlname, bool hellpig, float scale); +void showscores(bool on); +extern void renderscores(); + +// world +extern void setupworld(int factor); +extern void empty_world(int factor, bool force); +extern void remip(block &b, int level = 0); +extern void remipmore(block &b, int level = 0); +extern int closestent(); +extern int findentity(int type, int index = 0); +extern int findplayerstart(int team, int index); +extern void trigger(int tag, int type, bool savegame); +extern void resettagareas(); +extern void settagareas(); +extern entity *newentity(int x, int y, int z, char *what, int v1, int v2, int v3, int v4); + +// worldlight +extern void calclight(); +extern void dodynlight(vec &vold, vec &v, int reach, int strength, dynent *owner); +extern void cleardlights(); +extern block *blockcopy(block &b); +extern void blockpaste(block &b); + +// worldrender +extern void render_world(float vx, float vy, float vh, float changelod, int yaw, int pitch, float widef, int w, int h); +extern int lod_factor(); + +// worldocull +extern void computeraytable(float vx, float vy); +extern int isoccluded(float vx, float vy, float cx, float cy, float csize); + +// main +extern void fatal(char *s, char *o = ""); +extern void *alloc(int s); +extern void keyrepeat(bool on); + +// rendertext +extern void draw_text(char *str, int left, int top, int gl_num); +extern void draw_textf(char *fstr, int left, int top, int gl_num, int arg); +extern int text_width(char *str); +extern void draw_envbox(int t, int fogdist); + +// editing +extern void cursorupdate(); +extern void toggleedit(); +extern void editdrag(bool isdown); +extern void setvdeltaxy(int delta, block &sel); +extern void editequalisexy(bool isfloor, block &sel); +extern void edittypexy(int type, block &sel); +extern void edittexxy(int type, int t, block &sel); +extern void editheightxy(bool isfloor, int amount, block &sel); +extern bool noteditmode(); +extern void pruneundos(int maxremain = 0); + +// renderextras +extern void line(int x1, int y1, float z1, int x2, int y2, float z2); +extern void box(block &b, float z1, float z2, float z3, float z4); +extern void dot(int x, int y, float z); +extern void linestyle(float width, int r, int g, int b); +extern void newsphere(vec &o, float max, int type); +extern void renderspheres(int time); +extern void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater); +extern void readdepth(int w, int h); +extern void blendbox(int x1, int y1, int x2, int y2, bool border, int tex = -1); +extern void damageblend(int n); +extern void addshotline(dynent *d, vec &from, vec &to); +extern void rendershotlines(); +extern void shotlinereset(); +extern void renderphysents(); +extern bool scoped; +extern void togglescope(bool deactivate = false); + +// renderparticles +extern void setorient(vec &r, vec &u); +extern void particle_splash(int type, int num, int fade, vec &p); +//extern void particle_splash(int num, int fade, vec &p); +// extern void particle_trail(int type, int fade, vec &from, vec &to); Moved to cube.h by Rick +extern void render_particles(int time); + +// worldio +extern void save_world(char *fname); +extern void load_world(char *mname); +extern void writemap(char *mname, int msize, uchar *mdata); +extern uchar *readmap(char *mname, int *msize); +extern void loadgamerest(); +extern void incomingdemodata(uchar *buf, int len, bool extras = false); +extern void demoplaybackstep(); +extern void stop(); +extern void stopifrecording(); +extern void demodamage(int damage, vec &o); +extern void demoblend(int damage); + +// physics +extern void moveplayer(dynent *pl, int moveres, bool local); +extern bool collide(dynent *d, bool spawn, float drop, float rise); +extern void entinmap(dynent *d); +extern void setentphysics(int mml, int mmr); +extern void physicsframe(); +extern void movephysent(physent *pl); + +// sound +extern void playsound(int n, vec *loc = 0); +extern void playsoundc(int n); +extern void initsound(); +extern void cleansound(); +extern void botplaysound(int n, dynent *b); // Added by Rick + +// rendermd2 +extern void rendermodel(char *mdl, int frame, int range, int tex, float rad, float x, float y, float z, float yaw, float pitch, bool teammate, float scale, float speed, int snap = 0, int basetime = 0, bool oculling=true); +extern mapmodelinfo &getmminfo(int i); + +// rendermd3 +extern void rendermd3gun(); +extern void loadweapons(); + +// server +extern void initserver(bool dedicated, int uprate, char *sdesc, char *ip, char *master, char *passwd, int maxcl, char *maprot); // EDIT: AH +extern void cleanupserver(); +extern void localconnect(); +extern void localdisconnect(); +extern void localclienttoserver(struct _ENetPacket *); +extern void serverslice(int seconds, unsigned int timeout); +extern void putint(uchar *&p, int n); +extern int getint(uchar *&p); +extern void sendstring(char *t, uchar *&p); +extern void startintermission(); +extern void restoreserverstate(vector &ents); +extern uchar *retrieveservers(uchar *buf, int buflen); +extern char msgsizelookup(int msg); +extern void serverms(int mode, int numplayers, int minremain, char *smapname, int seconds, bool isfull); +extern void servermsinit(const char *master, char *sdesc, bool listen); +extern void sendmaps(int n, string mapname, int mapsize, uchar *mapdata); +extern ENetPacket *recvmap(int n); + +// weapon +//extern void selectgun(int a = -1, int b = -1, int c =-1); +extern void shoot(dynent *d, vec &to); +extern void shootv(int gun, vec &from, vec &to, dynent *d = 0, bool local = false, int nadesec = -1); +extern void createrays(vec &from, vec &to); +extern void moveprojectiles(float time); +extern void projreset(); +extern dynent *playerincrosshair(); +extern int reloadtime(int gun); +extern void reload(dynent *d); +extern int attackdelay(int gun); +extern int magsize(int gun); +extern int kick_rot(int gun); +extern int kick_back(int gun); +extern bool gun_changed; +extern bool akimboside; +extern int akimbolastaction[2]; +// Added by Rick +extern char *botincrosshair(); +extern bool intersect(dynent *d, vec &from, vec &to, vec *end = NULL); +// End add by Rick + +// entities +extern void renderents(); +extern void putitems(uchar *&p); +extern void realpickup(int n, dynent *d); +extern void flagaction(int team, int action); +extern void renderentities(); +extern void resetspawns(); +extern void setspawn(int i, bool on); +extern void baseammo(int gun); +extern void checkitems(); +extern void radd(dynent *d); +// Added by Rick +extern void botbaseammo(int gun, dynent *d); +extern bool intersect(entity *e, vec &from, vec &to, vec *end=NULL); +// End add by Rick + +// rndmap +extern void perlinarea(block &b, int scale, int seed, int psize); + diff --git a/source/src/rendercubes.cpp b/source/src/rendercubes.cpp index 02e7e81a..54c54c8a 100644 --- a/source/src/rendercubes.cpp +++ b/source/src/rendercubes.cpp @@ -1,359 +1,359 @@ -// rendercubes.cpp: sits in between worldrender.cpp and rendergl.cpp and fills the vertex array for different cube surfaces. - -#include "cube.h" - -vertex *verts = NULL; -int curvert; -int curmaxverts = 10000; - -void setarraypointers() -{ - glVertexPointer(3, GL_FLOAT, sizeof(vertex), &verts[0].x); - glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertex), &verts[0].r); - glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), &verts[0].u); -}; - -void reallocv() -{ - verts = (vertex *)realloc(verts, (curmaxverts *= 2)*sizeof(vertex)); - curmaxverts -= 10; - if(!verts) fatal("no vertex memory!"); - setarraypointers(); -}; - -// generating the actual vertices is done dynamically every frame and sits at the -// leaves of all these functions, and are part of the cpu bottleneck on really slow -// machines, hence the macros. - -#define vertcheck() { if(curvert>=curmaxverts) reallocv(); } - -#define vertf(v1, v2, v3, ls, t1, t2) { vertex &v = verts[curvert++]; \ - v.u = t1; v.v = t2; \ - v.x = v1; v.y = v2; v.z = v3; \ - v.r = ls->r; v.g = ls->g; v.b = ls->b; v.a = 255; }; - -#define vert(v1, v2, v3, ls, t1, t2) { vertf((float)(v1), (float)(v2), (float)(v3), ls, t1, t2); } - -int nquads; -const float TEXTURESCALE = 32.0f; -bool floorstrip = false, deltastrip = false; -int oh, oy, ox, ogltex; // the o* vars are used by the stripification -int ol3r, ol3g, ol3b, ol4r, ol4g, ol4b; -int firstindex; -bool showm = false; - -void showmip() { showm = !showm; }; -void mipstats(int a, int b, int c) { if(showm) conoutf("1x1/2x2/4x4: %d / %d / %d", a, b, c); }; - -COMMAND(showmip, ARG_NONE); - -#define stripend() { if(floorstrip || deltastrip) { addstrip(ogltex, firstindex, curvert-firstindex); floorstrip = deltastrip = false; }; }; -void finishstrips() { stripend(); }; - -sqr sbright, sdark; -VAR(lighterror,1,8,100); - -void render_flat(int wtex, int x, int y, int size, int h, sqr *l1, sqr *l2, sqr *l3, sqr *l4, bool isceil) // floor/ceil quads -{ - vertcheck(); - if(showm) { l3 = l1 = &sbright; l4 = l2 = &sdark; }; - - int sx, sy; - int gltex = lookuptexture(wtex, sx, sy); - float xf = TEXTURESCALE/sx; - float yf = TEXTURESCALE/sy; - float xs = size*xf; - float ys = size*yf; - float xo = xf*x; - float yo = yf*y; - - bool first = !floorstrip || y!=oy+size || ogltex!=gltex || h!=oh || x!=ox; - - if(first) // start strip here - { - stripend(); - firstindex = curvert; - ogltex = gltex; - oh = h; - ox = x; - floorstrip = true; - if(isceil) - { - vert(x+size, h, y, l2, xo+xs, yo); - vert(x, h, y, l1, xo, yo); - } - else - { - vert(x, h, y, l1, xo, yo); - vert(x+size, h, y, l2, xo+xs, yo); - }; - ol3r = l1->r; - ol3g = l1->g; - ol3b = l1->b; - ol4r = l2->r; - ol4g = l2->g; - ol4b = l2->b; - } - else // continue strip - { - int lighterr = lighterror*2; - if((abs(ol3r-l3->r)r)g)g)b)b)r; - ol3g = l1->g; - ol3b = l1->b; - ol4r = l2->r; - ol4g = l2->g; - ol4b = l2->b; - }; - - if(isceil) - { - vertf((float)x+size, h3, (float)y+size, l3, xo+xs, yo+ys); - vertf((float)x, h4, (float)y+size, l4, xo, yo+ys); - } - else - { - vertf((float)x, h4, (float)y+size, l4, xo, yo+ys); - vertf((float)x+size, h3, (float)y+size, l3, xo+xs, yo+ys); - }; - - oy = y; - nquads++; -}; - -void render_2tris(sqr *h, sqr *s, int x1, int y1, int x2, int y2, int x3, int y3, sqr *l1, sqr *l2, sqr *l3) // floor/ceil tris on a corner cube -{ - stripend(); - vertcheck(); - - int sx, sy; - int gltex = lookuptexture(h->ftex, sx, sy); - float xf = TEXTURESCALE/sx; - float yf = TEXTURESCALE/sy; - - vertf((float)x1, h->floor, (float)y1, l1, xf*x1, yf*y1); - vertf((float)x2, h->floor, (float)y2, l2, xf*x2, yf*y2); - vertf((float)x3, h->floor, (float)y3, l3, xf*x3, yf*y3); - addstrip(gltex, curvert-3, 3); - - gltex = lookuptexture(h->ctex, sx, sy); - xf = TEXTURESCALE/sx; - yf = TEXTURESCALE/sy; - - vertf((float)x3, h->ceil, (float)y3, l3, xf*x3, yf*y3); - vertf((float)x2, h->ceil, (float)y2, l2, xf*x2, yf*y2); - vertf((float)x1, h->ceil, (float)y1, l1, xf*x1, yf*y1); - addstrip(gltex, curvert-3, 3); - nquads++; -}; - -void render_tris(int x, int y, int size, bool topleft, - sqr *h1, sqr *h2, sqr *s, sqr *t, sqr *u, sqr *v) -{ - if(topleft) - { - if(h1) render_2tris(h1, s, x+size, y+size, x, y+size, x, y, u, v, s); - if(h2) render_2tris(h2, s, x, y, x+size, y, x+size, y+size, s, t, v); - } - else - { - if(h1) render_2tris(h1, s, x, y, x+size, y, x, y+size, s, t, u); - if(h2) render_2tris(h2, s, x+size, y, x+size, y+size, x, y+size, t, u, v); - }; -}; - -void render_square(int wtex, float floor1, float floor2, float ceil1, float ceil2, int x1, int y1, int x2, int y2, int size, sqr *l1, sqr *l2, bool flip) // wall quads -{ - stripend(); - vertcheck(); - if(showm) { l1 = &sbright; l2 = &sdark; }; - - int sx, sy; - int gltex = lookuptexture(wtex, sx, sy); - float xf = TEXTURESCALE/sx; - float yf = TEXTURESCALE/sy; - float xs = size*xf; - float xo = xf*(x1==x2 ? min(y1,y2) : min(x1,x2)); - - if(!flip) - { - vertf((float)x2, ceil2, (float)y2, l2, xo+xs, -yf*ceil2); - vertf((float)x1, ceil1, (float)y1, l1, xo, -yf*ceil1); - vertf((float)x2, floor2, (float)y2, l2, xo+xs, -floor2*yf); - vertf((float)x1, floor1, (float)y1, l1, xo, -floor1*yf); - } - else - { - vertf((float)x1, ceil1, (float)y1, l1, xo, -yf*ceil1); - vertf((float)x2, ceil2, (float)y2, l2, xo+xs, -yf*ceil2); - vertf((float)x1, floor1, (float)y1, l1, xo, -floor1*yf); - vertf((float)x2, floor2, (float)y2, l2, xo+xs, -floor2*yf); - }; - - nquads++; - addstrip(gltex, curvert-4, 4); -}; - -int wx1, wy1, wx2, wy2; - -VAR(watersubdiv, 1, 4, 64); -VARF(waterlevel, -128, -128, 127, if(!noteditmode()) hdr.waterlevel = waterlevel); - -inline void vertw(int v1, float v2, int v3, sqr *c, float t1, float t2, float t) -{ - vertcheck(); - vertf((float)v1, v2-(float)sin(v1*v3*0.1+t)*0.2f, (float)v3, c, t1, t2); -}; - -inline float dx(float x) { return x+(float)sin(x*2+lastmillis/1000.0f)*0.04f; }; -inline float dy(float x) { return x+(float)sin(x*2+lastmillis/900.0f+PI/5)*0.05f; }; - -// renders water for bounding rect area that contains water... simple but very inefficient - -int renderwater(float hf) -{ - if(wx1<0) return nquads; - - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_SRC_COLOR); - int sx, sy; - glBindTexture(GL_TEXTURE_2D, lookuptexture(DEFAULT_LIQUID, sx, sy)); - - wx1 &= ~(watersubdiv-1); - wy1 &= ~(watersubdiv-1); - - float xf = TEXTURESCALE/sx; - float yf = TEXTURESCALE/sy; - float xs = watersubdiv*xf; - float ys = watersubdiv*yf; - float t1 = lastmillis/300.0f; - float t2 = lastmillis/4000.0f; - - sqr dl; - dl.r = dl.g = dl.b = 255; - - for(int xx = wx1; xxwx2) wx2 = x2; - if(y2>wy2) wy2 = y2; - }; -}; - -void resetcubes() -{ - if(!verts) reallocv(); - floorstrip = deltastrip = false; - wx1 = -1; - nquads = 0; - sbright.r = sbright.g = sbright.b = 255; - sdark.r = sdark.g = sdark.b = 0; -}; - - +// rendercubes.cpp: sits in between worldrender.cpp and rendergl.cpp and fills the vertex array for different cube surfaces. + +#include "cube.h" + +vertex *verts = NULL; +int curvert; +int curmaxverts = 10000; + +void setarraypointers() +{ + glVertexPointer(3, GL_FLOAT, sizeof(vertex), &verts[0].x); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vertex), &verts[0].r); + glTexCoordPointer(2, GL_FLOAT, sizeof(vertex), &verts[0].u); +}; + +void reallocv() +{ + verts = (vertex *)realloc(verts, (curmaxverts *= 2)*sizeof(vertex)); + curmaxverts -= 10; + if(!verts) fatal("no vertex memory!"); + setarraypointers(); +}; + +// generating the actual vertices is done dynamically every frame and sits at the +// leaves of all these functions, and are part of the cpu bottleneck on really slow +// machines, hence the macros. + +#define vertcheck() { if(curvert>=curmaxverts) reallocv(); } + +#define vertf(v1, v2, v3, ls, t1, t2) { vertex &v = verts[curvert++]; \ + v.u = t1; v.v = t2; \ + v.x = v1; v.y = v2; v.z = v3; \ + v.r = ls->r; v.g = ls->g; v.b = ls->b; v.a = 255; }; + +#define vert(v1, v2, v3, ls, t1, t2) { vertf((float)(v1), (float)(v2), (float)(v3), ls, t1, t2); } + +int nquads; +const float TEXTURESCALE = 32.0f; +bool floorstrip = false, deltastrip = false; +int oh, oy, ox, ogltex; // the o* vars are used by the stripification +int ol3r, ol3g, ol3b, ol4r, ol4g, ol4b; +int firstindex; +bool showm = false; + +void showmip() { showm = !showm; }; +void mipstats(int a, int b, int c) { if(showm) conoutf("1x1/2x2/4x4: %d / %d / %d", a, b, c); }; + +COMMAND(showmip, ARG_NONE); + +#define stripend() { if(floorstrip || deltastrip) { addstrip(ogltex, firstindex, curvert-firstindex); floorstrip = deltastrip = false; }; }; +void finishstrips() { stripend(); }; + +sqr sbright, sdark; +VAR(lighterror,1,8,100); + +void render_flat(int wtex, int x, int y, int size, int h, sqr *l1, sqr *l2, sqr *l3, sqr *l4, bool isceil) // floor/ceil quads +{ + vertcheck(); + if(showm) { l3 = l1 = &sbright; l4 = l2 = &sdark; }; + + int sx, sy; + int gltex = lookuptexture(wtex, sx, sy); + float xf = TEXTURESCALE/sx; + float yf = TEXTURESCALE/sy; + float xs = size*xf; + float ys = size*yf; + float xo = xf*x; + float yo = yf*y; + + bool first = !floorstrip || y!=oy+size || ogltex!=gltex || h!=oh || x!=ox; + + if(first) // start strip here + { + stripend(); + firstindex = curvert; + ogltex = gltex; + oh = h; + ox = x; + floorstrip = true; + if(isceil) + { + vert(x+size, h, y, l2, xo+xs, yo); + vert(x, h, y, l1, xo, yo); + } + else + { + vert(x, h, y, l1, xo, yo); + vert(x+size, h, y, l2, xo+xs, yo); + }; + ol3r = l1->r; + ol3g = l1->g; + ol3b = l1->b; + ol4r = l2->r; + ol4g = l2->g; + ol4b = l2->b; + } + else // continue strip + { + int lighterr = lighterror*2; + if((abs(ol3r-l3->r)r)g)g)b)b)r; + ol3g = l1->g; + ol3b = l1->b; + ol4r = l2->r; + ol4g = l2->g; + ol4b = l2->b; + }; + + if(isceil) + { + vertf((float)x+size, h3, (float)y+size, l3, xo+xs, yo+ys); + vertf((float)x, h4, (float)y+size, l4, xo, yo+ys); + } + else + { + vertf((float)x, h4, (float)y+size, l4, xo, yo+ys); + vertf((float)x+size, h3, (float)y+size, l3, xo+xs, yo+ys); + }; + + oy = y; + nquads++; +}; + +void render_2tris(sqr *h, sqr *s, int x1, int y1, int x2, int y2, int x3, int y3, sqr *l1, sqr *l2, sqr *l3) // floor/ceil tris on a corner cube +{ + stripend(); + vertcheck(); + + int sx, sy; + int gltex = lookuptexture(h->ftex, sx, sy); + float xf = TEXTURESCALE/sx; + float yf = TEXTURESCALE/sy; + + vertf((float)x1, h->floor, (float)y1, l1, xf*x1, yf*y1); + vertf((float)x2, h->floor, (float)y2, l2, xf*x2, yf*y2); + vertf((float)x3, h->floor, (float)y3, l3, xf*x3, yf*y3); + addstrip(gltex, curvert-3, 3); + + gltex = lookuptexture(h->ctex, sx, sy); + xf = TEXTURESCALE/sx; + yf = TEXTURESCALE/sy; + + vertf((float)x3, h->ceil, (float)y3, l3, xf*x3, yf*y3); + vertf((float)x2, h->ceil, (float)y2, l2, xf*x2, yf*y2); + vertf((float)x1, h->ceil, (float)y1, l1, xf*x1, yf*y1); + addstrip(gltex, curvert-3, 3); + nquads++; +}; + +void render_tris(int x, int y, int size, bool topleft, + sqr *h1, sqr *h2, sqr *s, sqr *t, sqr *u, sqr *v) +{ + if(topleft) + { + if(h1) render_2tris(h1, s, x+size, y+size, x, y+size, x, y, u, v, s); + if(h2) render_2tris(h2, s, x, y, x+size, y, x+size, y+size, s, t, v); + } + else + { + if(h1) render_2tris(h1, s, x, y, x+size, y, x, y+size, s, t, u); + if(h2) render_2tris(h2, s, x+size, y, x+size, y+size, x, y+size, t, u, v); + }; +}; + +void render_square(int wtex, float floor1, float floor2, float ceil1, float ceil2, int x1, int y1, int x2, int y2, int size, sqr *l1, sqr *l2, bool flip) // wall quads +{ + stripend(); + vertcheck(); + if(showm) { l1 = &sbright; l2 = &sdark; }; + + int sx, sy; + int gltex = lookuptexture(wtex, sx, sy); + float xf = TEXTURESCALE/sx; + float yf = TEXTURESCALE/sy; + float xs = size*xf; + float xo = xf*(x1==x2 ? min(y1,y2) : min(x1,x2)); + + if(!flip) + { + vertf((float)x2, ceil2, (float)y2, l2, xo+xs, -yf*ceil2); + vertf((float)x1, ceil1, (float)y1, l1, xo, -yf*ceil1); + vertf((float)x2, floor2, (float)y2, l2, xo+xs, -floor2*yf); + vertf((float)x1, floor1, (float)y1, l1, xo, -floor1*yf); + } + else + { + vertf((float)x1, ceil1, (float)y1, l1, xo, -yf*ceil1); + vertf((float)x2, ceil2, (float)y2, l2, xo+xs, -yf*ceil2); + vertf((float)x1, floor1, (float)y1, l1, xo, -floor1*yf); + vertf((float)x2, floor2, (float)y2, l2, xo+xs, -floor2*yf); + }; + + nquads++; + addstrip(gltex, curvert-4, 4); +}; + +int wx1, wy1, wx2, wy2; + +VAR(watersubdiv, 1, 4, 64); +VARF(waterlevel, -128, -128, 127, if(!noteditmode()) hdr.waterlevel = waterlevel); + +inline void vertw(int v1, float v2, int v3, sqr *c, float t1, float t2, float t) +{ + vertcheck(); + vertf((float)v1, v2-(float)sin(v1*v3*0.1+t)*0.2f, (float)v3, c, t1, t2); +}; + +inline float dx(float x) { return x+(float)sin(x*2+lastmillis/1000.0f)*0.04f; }; +inline float dy(float x) { return x+(float)sin(x*2+lastmillis/900.0f+PI/5)*0.05f; }; + +// renders water for bounding rect area that contains water... simple but very inefficient + +int renderwater(float hf) +{ + if(wx1<0) return nquads; + + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_ONE, GL_SRC_COLOR); + int sx, sy; + glBindTexture(GL_TEXTURE_2D, lookuptexture(DEFAULT_LIQUID, sx, sy)); + + wx1 &= ~(watersubdiv-1); + wy1 &= ~(watersubdiv-1); + + float xf = TEXTURESCALE/sx; + float yf = TEXTURESCALE/sy; + float xs = watersubdiv*xf; + float ys = watersubdiv*yf; + float t1 = lastmillis/300.0f; + float t2 = lastmillis/4000.0f; + + sqr dl; + dl.r = dl.g = dl.b = 255; + + for(int xx = wx1; xxwx2) wx2 = x2; + if(y2>wy2) wy2 = y2; + }; +}; + +void resetcubes() +{ + if(!verts) reallocv(); + floorstrip = deltastrip = false; + wx1 = -1; + nquads = 0; + sbright.r = sbright.g = sbright.b = 255; + sdark.r = sdark.g = sdark.b = 0; +}; + + diff --git a/source/src/renderextras.cpp b/source/src/renderextras.cpp index 1e1dda31..adfe6948 100644 --- a/source/src/renderextras.cpp +++ b/source/src/renderextras.cpp @@ -1,567 +1,567 @@ -// renderextras.cpp: misc gl render code and the HUD - -#include "cube.h" - -void line(int x1, int y1, float z1, int x2, int y2, float z2) -{ - glBegin(GL_POLYGON); - glVertex3f((float)x1, z1, (float)y1); - glVertex3f((float)x1, z1, y1+0.01f); - glVertex3f((float)x2, z2, y2+0.01f); - glVertex3f((float)x2, z2, (float)y2); - glEnd(); - xtraverts += 4; -}; - -void linestyle(float width, int r, int g, int b) -{ - glLineWidth(width); - glColor3ub(r,g,b); -}; - -void box(block &b, float z1, float z2, float z3, float z4) -{ - glBegin(GL_POLYGON); - glVertex3f((float)b.x, z1, (float)b.y); - glVertex3f((float)b.x+b.xs, z2, (float)b.y); - glVertex3f((float)b.x+b.xs, z3, (float)b.y+b.ys); - glVertex3f((float)b.x, z4, (float)b.y+b.ys); - glEnd(); - xtraverts += 4; -}; - -void dot(int x, int y, float z) -{ - const float DOF = 0.1f; - glBegin(GL_POLYGON); - glVertex3f(x-DOF, (float)z, y-DOF); - glVertex3f(x+DOF, (float)z, y-DOF); - glVertex3f(x+DOF, (float)z, y+DOF); - glVertex3f(x-DOF, (float)z, y+DOF); - glEnd(); - xtraverts += 4; -}; - -void blendbox(int x1, int y1, int x2, int y2, bool border, int tex) -{ - glDepthMask(GL_FALSE); - glDisable(GL_TEXTURE_2D); - if(tex>=0) - { - glBindTexture(GL_TEXTURE_2D, tex); - glEnable(GL_TEXTURE_2D); - glDisable(GL_BLEND); - } - else - { - if(border) glColor3d(0.7, 0.7, 0.7); //glColor3d(0.5, 0.3, 0.4); - else glColor3d(1.0, 1.0, 1.0); - glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); - }; - glBegin(GL_QUADS); - glTexCoord2f(0, 0); glVertex2i(x1, y1); - glTexCoord2f(1, 0); glVertex2i(x2, y1); - glTexCoord2f(1, 1); glVertex2i(x2, y2); - glTexCoord2f(0, 1); glVertex2i(x1, y2); - glEnd(); - glDisable(GL_BLEND); - if(tex>=0) glDisable(GL_TEXTURE_2D); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - glBegin(GL_POLYGON); - glColor3d(0.6, 0.6, 0.6); //glColor3d(0.2, 0.7, 0.4); - glVertex2i(x1, y1); - glVertex2i(x2, y1); - glVertex2i(x2, y2); - glVertex2i(x1, y2); - glEnd(); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - xtraverts += 8; - glEnable(GL_BLEND); - glEnable(GL_TEXTURE_2D); - glDepthMask(GL_TRUE); -}; - -const int MAXSPHERES = 50; -struct sphere { vec o; float size, max; int type; sphere *next; }; -sphere spheres[MAXSPHERES], *slist = NULL, *sempty = NULL; -bool sinit = false; - -void newsphere(vec &o, float max, int type) -{ - if(!sinit) - { - loopi(MAXSPHERES) - { - spheres[i].next = sempty; - sempty = &spheres[i]; - }; - sinit = true; - }; - if(sempty) - { - sphere *p = sempty; - sempty = p->next; - p->o = o; - p->max = max; - p->size = 1; - p->type = type; - p->next = slist; - slist = p; - }; -}; - -void renderspheres(int time) -{ - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - glBindTexture(GL_TEXTURE_2D, 4); - - for(sphere *p, **pp = &slist; p = *pp;) - { - glPushMatrix(); - float size = p->size/p->max; - glColor4f(1.0f, 1.0f, 1.0f, 1.0f-size); - glTranslatef(p->o.x, p->o.z, p->o.y); - glRotatef(lastmillis/5.0f, 1, 1, 1); - glScalef(p->size, p->size, p->size); - glCallList(1); - glScalef(0.8f, 0.8f, 0.8f); - glCallList(1); - glPopMatrix(); - xtraverts += 12*6*2; - - if(p->size>p->max) - { - *pp = p->next; - p->next = sempty; - sempty = p; - } - else - { - p->size += time/100.0f; - pp = &p->next; - }; - }; - - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); -}; - -string closeent; -char *entnames[] = -{ - "none?", "light", "playerstart", - "clips", "ammobox","grenades", - "health", "armour", "akimbo", - "mapmodel", "trigger", - "ladder", "ctf-flag", "?", "?", "?", -}; - -void renderents() // show sparkly thingies for map entities in edit mode -{ - closeent[0] = 0; - if(!editmode) return; - loopv(ents) - { - entity &e = ents[i]; - if(e.type==NOTUSED) continue; - vec v = { e.x, e.y, e.z }; - particle_splash(2, 2, 40, v); - }; - int e = closestent(); - if(e>=0) - { - entity &c = ents[e]; - sprintf_s(closeent)("closest entity = %s (%d, %d, %d, %d), selection = (%d, %d)", entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, getvar("selxs"), getvar("selys")); - }; -}; - -void loadsky(char *basename) -{ - static string lastsky = ""; - if(strcmp(lastsky, basename)==0) return; - char *side[] = { "ft", "bk", "lf", "rt", "dn", "up" }; - int texnum = 14; - loopi(6) - { - sprintf_sd(name)("packages/%s_%s.jpg", basename, side[i]); - int xs, ys; - if(!installtex(texnum+i, path(name), xs, ys, true)) conoutf("could not load sky textures"); - }; - strcpy_s(lastsky, basename); -}; - -COMMAND(loadsky, ARG_1STR); - -float cursordepth = 0.9f; -GLint viewport[4]; -GLdouble mm[16], pm[16]; -vec worldpos; - -void readmatrices() -{ - glGetIntegerv(GL_VIEWPORT, viewport); - glGetDoublev(GL_MODELVIEW_MATRIX, mm); - glGetDoublev(GL_PROJECTION_MATRIX, pm); -}; - -// stupid function to cater for stupid ATI linux drivers that return incorrect depth values - -float depthcorrect(float d) -{ - return (d<=1/256.0f) ? d*256 : d; -}; - -// find out the 3d target of the crosshair in the world easily and very acurately. -// sadly many very old cards and drivers appear to fuck up on glReadPixels() and give false -// coordinates, making shooting and such impossible. -// also hits map entities which is unwanted. -// could be replaced by a more acurate version of monster.cpp los() if needed - -void readdepth(int w, int h) -{ - glReadPixels(w/2, h/2, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &cursordepth); - double worldx = 0, worldy = 0, worldz = 0; - gluUnProject(w/2, h/2, depthcorrect(cursordepth), mm, pm, viewport, &worldx, &worldz, &worldy); - worldpos.x = (float)worldx; - worldpos.y = (float)worldy; - worldpos.z = (float)worldz; - vec r = { (float)mm[0], (float)mm[4], (float)mm[8] }; - vec u = { (float)mm[1], (float)mm[5], (float)mm[9] }; - setorient(r, u); -}; - -void drawicon(float tx, float ty, int x, int y, int tex = 5, float scaling = 1/3.0f) // EDIT: AH -{ - glBindTexture(GL_TEXTURE_2D, tex); - glBegin(GL_QUADS); - tx /= 192; - ty /= 192; - float o = scaling; - int s = 120; - glTexCoord2f(tx, ty); glVertex2i(x, y); - glTexCoord2f(tx+o, ty); glVertex2i(x+s, y); - glTexCoord2f(tx+o, ty+o); glVertex2i(x+s, y+s); - glTexCoord2f(tx, ty+o); glVertex2i(x, y+s); - glEnd(); - xtraverts += 4; -}; - -void invertperspective() -{ - // This only generates a valid inverse matrix for matrices generated by gluPerspective() - GLdouble inv[16]; - memset(inv, 0, sizeof(inv)); - - inv[0*4+0] = 1.0/pm[0*4+0]; - inv[1*4+1] = 1.0/pm[1*4+1]; - inv[2*4+3] = 1.0/pm[3*4+2]; - inv[3*4+2] = -1.0; - inv[3*4+3] = pm[2*4+2]/pm[3*4+2]; - - glLoadMatrixd(inv); -}; - -VAR(crosshairsize, 0, 15, 50); - -int dblend = 0; -void damageblend(int n) { dblend += n; }; - -VAR(hidestats, 0, 0, 1); -VAR(crosshairfx, 0, 1, 1); - -VAR(scopefov, 5, 50, 50); -bool scoped = false; -int oldfov = 100; - -void togglescope() -{ - if(player1->gunselect!=GUN_SNIPER) return; - if(!scoped) oldfov = getvar("fov"); - scoped = !scoped; - setvar("fov", scoped ? scopefov : oldfov); -} -COMMAND(togglescope, ARG_NONE); - -void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater) -{ - - readmatrices(); - if(editmode) - { - if(cursordepth==1.0f) worldpos = player1->o; - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - cursorupdate(); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - }; - - glDisable(GL_DEPTH_TEST); - invertperspective(); - glPushMatrix(); - glOrtho(0, VIRTW, VIRTH, 0, -1, 1); - glEnable(GL_BLEND); - - if(dblend || underwater) - { - glDepthMask(GL_FALSE); - glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); - glBegin(GL_QUADS); - if(dblend) glColor3d(0.0f, 0.9f, 0.9f); - else glColor3d(0.9f, 0.5f, 0.0f); - glVertex2i(0, 0); - glVertex2i(VIRTW, 0); - glVertex2i(VIRTW, VIRTH); - glVertex2i(0, VIRTH); - glEnd(); - glDepthMask(GL_TRUE); - dblend -= curtime/3; - if(dblend<0) dblend = 0; - }; - - glEnable(GL_TEXTURE_2D); - - char *command = getcurcommand(); - dynent *player = playerincrosshair(); - if(command) draw_textf("> %s_", 20, 1570, 2, (int)command); - else if(closeent[0]) draw_text(closeent, 20, 1570, 2); - else if(player) - { - string plinfo; - if(isteam(player->team, player1->team)) sprintf_s(plinfo)("%s (health:%i armour:%i)", player->name, player->health, player->armour); - else sprintf_s(plinfo)("%s", player->name); - draw_text(plinfo, 20, 1570, 2); - }; - - renderscores(); - if(!rendermenu()) - { - if(scoped && player1->gunselect == GUN_SNIPER) - { - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_ALPHA_TEST); - glAlphaFunc(GL_GREATER, 0.9f); - glBindTexture(GL_TEXTURE_2D, 10); - glBegin(GL_QUADS); - glColor3ub(255,255,255); - float chsize = (float)crosshairsize; - glTexCoord2d(0.0, 0.0); glVertex2f(0, 0); - glTexCoord2d(1.0, 0.0); glVertex2f(VIRTW, 0); - glTexCoord2d(1.0, 1.0); glVertex2f(VIRTW, VIRTH); - glTexCoord2d(0.0, 1.0); glVertex2f(0, VIRTH); - glEnd(); - glDisable(GL_ALPHA_TEST); - } - else if (player1->gunselect != GUN_SNIPER) - { - glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); - glBindTexture(GL_TEXTURE_2D, 1); - glBegin(GL_QUADS); - glColor3ub(255,255,255); - if(crosshairfx) - { - if(player1->gunwait) glColor3ub(128,128,128); - else if(player1->health<=25) glColor3ub(255,0,0); - else if(player1->health<=50) glColor3ub(255,128,0); - }; - float chsize = (float)crosshairsize; - glTexCoord2d(0.0, 0.0); glVertex2f(VIRTW/2 - chsize, VIRTH/2 - chsize); - glTexCoord2d(1.0, 0.0); glVertex2f(VIRTW/2 + chsize, VIRTH/2 - chsize); - glTexCoord2d(1.0, 1.0); glVertex2f(VIRTW/2 + chsize, VIRTH/2 + chsize); - glTexCoord2d(0.0, 1.0); glVertex2f(VIRTW/2 - chsize, VIRTH/2 + chsize); - glEnd(); - } - }; - - - - glPopMatrix(); - - glPushMatrix(); - glOrtho(0, VIRTW*4/3, VIRTH*4/3, 0, -1, 1); - renderconsole(); - - if(!hidestats) - { - glPopMatrix(); - glPushMatrix(); - glOrtho(0, VIRTW*3/2, VIRTH*3/2, 0, -1, 1); - draw_textf("fps %d", 3200, 2320, 2, curfps); - draw_textf("lod %d", 3200, 2390, 2, lod_factor()); - draw_textf("wqd %d", 3200, 2460, 2, nquads); - draw_textf("wvt %d", 3200, 2530, 2, curvert); - draw_textf("evt %d", 3200, 2600, 2, xtraverts); - }; - - glPopMatrix(); - - if(player1->state==CS_ALIVE) - { - glPushMatrix(); - glOrtho(0, VIRTW/2, VIRTH/2, 0, -1, 1); - draw_textf("%d", 90, 827, 2, player1->health); - if(player1->armour) draw_textf("%d", 390, 827, 2, player1->armour); - //draw_textf("%d", 690, 827, 2, player1->mag[player1->gunselect]); - if (player1->gunselect!=GUN_KNIFE) - { - //draw_textf("%d", 690, 827, 2, player1->mag[player1->gunselect]); - char gunstats [64]; - if (player1->gunselect!=GUN_GRENADE) - sprintf(gunstats,"%i/%i",player1->mag[player1->gunselect],player1->ammo[player1->gunselect]); - else sprintf(gunstats,"%i",player1->mag[player1->gunselect]); - draw_text(gunstats, 690, 827, 2); - //clean up pointer? - //delete gunstats; - } - - glPopMatrix(); - - // EDIT: AH - ctf - #define ctf_scl 0.75 - if(m_ctf) - { - glPushMatrix(); - glOrtho(0, VIRTW, VIRTH, 0, -1, 1); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - glColor4f(1.0f, 1.0f, 1.0f, 0.2f); - if(flaginfos[TEAM_CLA].state == CTFF_INBASE) glDisable(GL_BLEND); else glEnable(GL_BLEND); - drawicon(64*ctf_scl, 0, 1940, 1650, 11, 1.0f/4.0f); - if(flaginfos[TEAM_RVSF].state == CTFF_INBASE) glDisable(GL_BLEND); else glEnable(GL_BLEND); - drawicon(0, 0, 1820, 1650, 11, 1.0f/4.0f); - glDisable(GL_BLEND); - drawicon(rb_team_int(player1->team)*64*ctf_scl, 64*ctf_scl, VIRTW-128-10, 10, 11, 1.0f/4.0f); // shows which team you are - glPopMatrix(); - - // big flag icon - flaginfo &f = flaginfos[rb_opposite(rb_team_int(player1->team))]; - if(f.state==CTFF_STOLEN && f.thief == player1 && f.pick_ack ) - { - glPushMatrix(); - glOrtho(0, VIRTW/2, VIRTH/2, 0, -1, 1); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - glColor4f(1.0f, 1.0f, 1.0f, (float)(sin(lastmillis / 100.0) + 1) / 2); - glEnable(GL_BLEND); - drawicon(rb_opposite(rb_team_int(player1->team))*128*ctf_scl, 128*ctf_scl, 1065, VIRTH/2/3*2, 11, 1.0f/2.0f); - glPopMatrix(); - }; - }; - - glPushMatrix(); - glOrtho(0, VIRTW, VIRTH, 0, -1, 1); - glDisable(GL_BLEND); - - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glColor4f(1.0f, 1.0f, 1.0f, (float) 0.2f+(sin(lastmillis/100)+1)/2); - - if(player1->health <= 20) - { - glEnable(GL_BLEND); - drawicon(64, 0, 20, 1650); //drawicon(128, 128, 20, 1650); - glDisable(GL_BLEND); - } - else drawicon(64, 0, 20, 1650); //drawicon(128, 128, 20, 1650); - if(player1->armour) drawicon((float)(128), 0, 620, 1650); //if(player1->armour) drawicon((float)(player1->armourtype*64), 0, 620, 1650); - int g = player1->gunselect; - int r = 64; - if(g>2) { g -= 3; r = 128; }; - - if(!player1->mag[player1->gunselect] && player1->gunselect != GUN_KNIFE && player1->gunselect != GUN_GRENADE) - { - glEnable(GL_BLEND); - drawicon((float)(g*64), (float)r, 1220, 1650); - glDisable(GL_BLEND); - } - else - { - if (player1->gunselect==GUN_GRENADE) drawicon(0, 0, 1220, 1650); - else drawicon((float)(g*64), (float)r, 1220, 1650); - } - glPopMatrix(); - }; - - glDisable(GL_BLEND); - glDisable(GL_TEXTURE_2D); - glEnable(GL_DEPTH_TEST); -}; - -#define MAXSHOTLINES 100 -struct shotline -{ - vec from, to; - bool inuse; - int millis; -}; -shotline shotlines[MAXSHOTLINES]; - -void shotlinereset() { loopi(MAXSHOTLINES) shotlines[i].inuse = false; }; - -void addshotline(dynent *d, vec &from, vec &to) -{ - if(d == player1) return; - if(rnd(3) != 0) return; - loopi(MAXSHOTLINES) - { - shotline *s = &shotlines[i]; - if(s->inuse) continue; - int start = 10; - vdist(_dist, _unitv, player1->o, to); - if(_dist <= 10.0f) start = 8; - else start = 5; - - vdist(dist, unitv, from, to); - vdiv(unitv, dist); - s->inuse = true; - s->from = unitv; - vmul(s->from, dist/10*start); - vadd(s->from, from); - s->to = unitv; - vmul(s->to, dist/10*-(10-start-2)); - vadd(s->to, to); - s->millis = lastmillis; - return; - }; -}; - -VAR(shotline_duration, 0, 75, 10000); - -void rendershotlines() -{ - loopi(MAXSHOTLINES) - { - shotline *s = &shotlines[i]; - if(!s->inuse) continue; - if(lastmillis-s->millis > shotline_duration) { s->inuse = false; continue; } - glPushMatrix(); - glDisable(GL_TEXTURE_2D); - - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); - glDisable(GL_FOG); - - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - glBegin(GL_LINES); - glColor4f(1.0f, 1.0f, 0.7f, 0.5f); - glVertex3f(s->from.x, s->from.z, s->from.y); - glVertex3f(s->to.x, s->to.z, s->to.y); - glEnd(); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - - glEnable(GL_FOG); - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); - - glEnable(GL_TEXTURE_2D); - glPopMatrix(); - }; -}; - -void renderphysents() -{ - loopv(physents) - { - physent *d = physents[i]; - if(d && d->state==NADE_THROWED) rendermodel("weapons/grenade/static", 0, 1, 0, 0, d->o.x, d->o.z, d->o.y, d->yaw, d->pitch, false, 2.0f, 100.0f, 0, 0); - }; -}; - +// renderextras.cpp: misc gl render code and the HUD + +#include "cube.h" + +void line(int x1, int y1, float z1, int x2, int y2, float z2) +{ + glBegin(GL_POLYGON); + glVertex3f((float)x1, z1, (float)y1); + glVertex3f((float)x1, z1, y1+0.01f); + glVertex3f((float)x2, z2, y2+0.01f); + glVertex3f((float)x2, z2, (float)y2); + glEnd(); + xtraverts += 4; +}; + +void linestyle(float width, int r, int g, int b) +{ + glLineWidth(width); + glColor3ub(r,g,b); +}; + +void box(block &b, float z1, float z2, float z3, float z4) +{ + glBegin(GL_POLYGON); + glVertex3f((float)b.x, z1, (float)b.y); + glVertex3f((float)b.x+b.xs, z2, (float)b.y); + glVertex3f((float)b.x+b.xs, z3, (float)b.y+b.ys); + glVertex3f((float)b.x, z4, (float)b.y+b.ys); + glEnd(); + xtraverts += 4; +}; + +void dot(int x, int y, float z) +{ + const float DOF = 0.1f; + glBegin(GL_POLYGON); + glVertex3f(x-DOF, (float)z, y-DOF); + glVertex3f(x+DOF, (float)z, y-DOF); + glVertex3f(x+DOF, (float)z, y+DOF); + glVertex3f(x-DOF, (float)z, y+DOF); + glEnd(); + xtraverts += 4; +}; + +void blendbox(int x1, int y1, int x2, int y2, bool border, int tex) +{ + glDepthMask(GL_FALSE); + glDisable(GL_TEXTURE_2D); + if(tex>=0) + { + glBindTexture(GL_TEXTURE_2D, tex); + glEnable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + } + else + { + if(border) glColor3d(0.7, 0.7, 0.7); //glColor3d(0.5, 0.3, 0.4); + else glColor3d(1.0, 1.0, 1.0); + glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + }; + glBegin(GL_QUADS); + glTexCoord2f(0, 0); glVertex2i(x1, y1); + glTexCoord2f(1, 0); glVertex2i(x2, y1); + glTexCoord2f(1, 1); glVertex2i(x2, y2); + glTexCoord2f(0, 1); glVertex2i(x1, y2); + glEnd(); + glDisable(GL_BLEND); + if(tex>=0) glDisable(GL_TEXTURE_2D); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glBegin(GL_POLYGON); + glColor3d(0.6, 0.6, 0.6); //glColor3d(0.2, 0.7, 0.4); + glVertex2i(x1, y1); + glVertex2i(x2, y1); + glVertex2i(x2, y2); + glVertex2i(x1, y2); + glEnd(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + xtraverts += 8; + glEnable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + glDepthMask(GL_TRUE); +}; + +const int MAXSPHERES = 50; +struct sphere { vec o; float size, max; int type; sphere *next; }; +sphere spheres[MAXSPHERES], *slist = NULL, *sempty = NULL; +bool sinit = false; + +void newsphere(vec &o, float max, int type) +{ + if(!sinit) + { + loopi(MAXSPHERES) + { + spheres[i].next = sempty; + sempty = &spheres[i]; + }; + sinit = true; + }; + if(sempty) + { + sphere *p = sempty; + sempty = p->next; + p->o = o; + p->max = max; + p->size = 1; + p->type = type; + p->next = slist; + slist = p; + }; +}; + +void renderspheres(int time) +{ + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glBindTexture(GL_TEXTURE_2D, 4); + + for(sphere *p, **pp = &slist; p = *pp;) + { + glPushMatrix(); + float size = p->size/p->max; + glColor4f(1.0f, 1.0f, 1.0f, 1.0f-size); + glTranslatef(p->o.x, p->o.z, p->o.y); + glRotatef(lastmillis/5.0f, 1, 1, 1); + glScalef(p->size, p->size, p->size); + glCallList(1); + glScalef(0.8f, 0.8f, 0.8f); + glCallList(1); + glPopMatrix(); + xtraverts += 12*6*2; + + if(p->size>p->max) + { + *pp = p->next; + p->next = sempty; + sempty = p; + } + else + { + p->size += time/100.0f; + pp = &p->next; + }; + }; + + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); +}; + +string closeent; +char *entnames[] = +{ + "none?", "light", "playerstart", + "clips", "ammobox","grenades", + "health", "armour", "akimbo", + "mapmodel", "trigger", + "ladder", "ctf-flag", "smokesource", "?", "?", +}; + +void renderents() // show sparkly thingies for map entities in edit mode +{ + closeent[0] = 0; + if(!editmode) return; + loopv(ents) + { + entity &e = ents[i]; + if(e.type==NOTUSED) continue; + vec v = { e.x, e.y, e.z }; + particle_splash(2, 2, 40, v); + }; + int e = closestent(); + if(e>=0) + { + entity &c = ents[e]; + sprintf_s(closeent)("closest entity = %s (%d, %d, %d, %d), selection = (%d, %d)", entnames[c.type], c.attr1, c.attr2, c.attr3, c.attr4, getvar("selxs"), getvar("selys")); + }; +}; + +void loadsky(char *basename) +{ + static string lastsky = ""; + if(strcmp(lastsky, basename)==0) return; + char *side[] = { "ft", "bk", "lf", "rt", "dn", "up" }; + int texnum = 14; + loopi(6) + { + sprintf_sd(name)("packages/%s_%s.jpg", basename, side[i]); + int xs, ys; + if(!installtex(texnum+i, path(name), xs, ys, true)) conoutf("could not load sky textures"); + }; + strcpy_s(lastsky, basename); +}; + +COMMAND(loadsky, ARG_1STR); + +float cursordepth = 0.9f; +GLint viewport[4]; +GLdouble mm[16], pm[16]; +vec worldpos; + +void readmatrices() +{ + glGetIntegerv(GL_VIEWPORT, viewport); + glGetDoublev(GL_MODELVIEW_MATRIX, mm); + glGetDoublev(GL_PROJECTION_MATRIX, pm); +}; + +// stupid function to cater for stupid ATI linux drivers that return incorrect depth values + +float depthcorrect(float d) +{ + return (d<=1/256.0f) ? d*256 : d; +}; + +// find out the 3d target of the crosshair in the world easily and very acurately. +// sadly many very old cards and drivers appear to fuck up on glReadPixels() and give false +// coordinates, making shooting and such impossible. +// also hits map entities which is unwanted. +// could be replaced by a more acurate version of monster.cpp los() if needed + +void readdepth(int w, int h) +{ + glReadPixels(w/2, h/2, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &cursordepth); + double worldx = 0, worldy = 0, worldz = 0; + gluUnProject(w/2, h/2, depthcorrect(cursordepth), mm, pm, viewport, &worldx, &worldz, &worldy); + worldpos.x = (float)worldx; + worldpos.y = (float)worldy; + worldpos.z = (float)worldz; + vec r = { (float)mm[0], (float)mm[4], (float)mm[8] }; + vec u = { (float)mm[1], (float)mm[5], (float)mm[9] }; + setorient(r, u); +}; + +void drawicon(float tx, float ty, int x, int y, int tex = 5, float scaling = 1/3.0f) // EDIT: AH +{ + glBindTexture(GL_TEXTURE_2D, tex); + glBegin(GL_QUADS); + tx /= 192; + ty /= 192; + float o = scaling; + int s = 120; + glTexCoord2f(tx, ty); glVertex2i(x, y); + glTexCoord2f(tx+o, ty); glVertex2i(x+s, y); + glTexCoord2f(tx+o, ty+o); glVertex2i(x+s, y+s); + glTexCoord2f(tx, ty+o); glVertex2i(x, y+s); + glEnd(); + xtraverts += 4; +}; + +void invertperspective() +{ + // This only generates a valid inverse matrix for matrices generated by gluPerspective() + GLdouble inv[16]; + memset(inv, 0, sizeof(inv)); + + inv[0*4+0] = 1.0/pm[0*4+0]; + inv[1*4+1] = 1.0/pm[1*4+1]; + inv[2*4+3] = 1.0/pm[3*4+2]; + inv[3*4+2] = -1.0; + inv[3*4+3] = pm[2*4+2]/pm[3*4+2]; + + glLoadMatrixd(inv); +}; + +VAR(crosshairsize, 0, 15, 50); + +int dblend = 0; +void damageblend(int n) { dblend += n; }; + +VAR(hidestats, 0, 0, 1); +VAR(crosshairfx, 0, 1, 1); + +VAR(scopefov, 5, 50, 50); +bool scoped = false; +int oldfov = 100; + +void togglescope(bool deactivate) +{ + if(player1->gunselect==GUN_SNIPER) + { + if(!scoped) oldfov = getvar("fov"); + scoped = !scoped; + setvar("fov", scoped ? scopefov : oldfov); + } +} + +void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwater) +{ + readmatrices(); + if(editmode) + { + if(cursordepth==1.0f) worldpos = player1->o; + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + cursorupdate(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + }; + + glDisable(GL_DEPTH_TEST); + invertperspective(); + glPushMatrix(); + glOrtho(0, VIRTW, VIRTH, 0, -1, 1); + glEnable(GL_BLEND); + + if(dblend || underwater) + { + glDepthMask(GL_FALSE); + glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); + glBegin(GL_QUADS); + if(dblend) glColor3d(0.0f, 0.9f, 0.9f); + else glColor3d(0.9f, 0.5f, 0.0f); + glVertex2i(0, 0); + glVertex2i(VIRTW, 0); + glVertex2i(VIRTW, VIRTH); + glVertex2i(0, VIRTH); + glEnd(); + glDepthMask(GL_TRUE); + dblend -= curtime/3; + if(dblend<0) dblend = 0; + }; + + glEnable(GL_TEXTURE_2D); + + char *command = getcurcommand(); + dynent *player = playerincrosshair(); + if(command) draw_textf("> %s_", 20, 1570, 2, (int)command); + else if(closeent[0]) draw_text(closeent, 20, 1570, 2); + else if(player) + { + string plinfo; + if(isteam(player->team, player1->team)) sprintf_s(plinfo)("%s (health:%i armour:%i)", player->name, player->health, player->armour); + else sprintf_s(plinfo)("%s", player->name); + draw_text(plinfo, 20, 1570, 2); + }; + + renderscores(); + if(!rendermenu()) + { + if(scoped && player1->gunselect == GUN_SNIPER) + { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.9f); + glBindTexture(GL_TEXTURE_2D, 10); + glBegin(GL_QUADS); + glColor3ub(255,255,255); + float chsize = (float)crosshairsize; + glTexCoord2d(0.0, 0.0); glVertex2f(0, 0); + glTexCoord2d(1.0, 0.0); glVertex2f(VIRTW, 0); + glTexCoord2d(1.0, 1.0); glVertex2f(VIRTW, VIRTH); + glTexCoord2d(0.0, 1.0); glVertex2f(0, VIRTH); + glEnd(); + glDisable(GL_ALPHA_TEST); + } + else if (player1->gunselect != GUN_SNIPER) + { + glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); + glBindTexture(GL_TEXTURE_2D, 1); + glBegin(GL_QUADS); + glColor3ub(255,255,255); + if(crosshairfx) + { + if(player1->gunwait) glColor3ub(128,128,128); + else if(player1->health<=25) glColor3ub(255,0,0); + else if(player1->health<=50) glColor3ub(255,128,0); + }; + float chsize = (float)crosshairsize; + glTexCoord2d(0.0, 0.0); glVertex2f(VIRTW/2 - chsize, VIRTH/2 - chsize); + glTexCoord2d(1.0, 0.0); glVertex2f(VIRTW/2 + chsize, VIRTH/2 - chsize); + glTexCoord2d(1.0, 1.0); glVertex2f(VIRTW/2 + chsize, VIRTH/2 + chsize); + glTexCoord2d(0.0, 1.0); glVertex2f(VIRTW/2 - chsize, VIRTH/2 + chsize); + glEnd(); + } + }; + + + + glPopMatrix(); + + glPushMatrix(); + glOrtho(0, VIRTW*4/3, VIRTH*4/3, 0, -1, 1); + renderconsole(); + + if(!hidestats) + { + glPopMatrix(); + glPushMatrix(); + glOrtho(0, VIRTW*3/2, VIRTH*3/2, 0, -1, 1); + draw_textf("fps %d", 3200, 2320, 2, curfps); + draw_textf("lod %d", 3200, 2390, 2, lod_factor()); + draw_textf("wqd %d", 3200, 2460, 2, nquads); + draw_textf("wvt %d", 3200, 2530, 2, curvert); + draw_textf("evt %d", 3200, 2600, 2, xtraverts); + }; + + glPopMatrix(); + + if(player1->state==CS_ALIVE) + { + glPushMatrix(); + glOrtho(0, VIRTW/2, VIRTH/2, 0, -1, 1); + draw_textf("%d", 90, 827, 2, player1->health); + if(player1->armour) draw_textf("%d", 390, 827, 2, player1->armour); + //draw_textf("%d", 690, 827, 2, player1->mag[player1->gunselect]); + if (player1->gunselect!=GUN_KNIFE) + { + //draw_textf("%d", 690, 827, 2, player1->mag[player1->gunselect]); + char gunstats [64]; + if (player1->gunselect!=GUN_GRENADE) + sprintf(gunstats,"%i/%i",player1->mag[player1->gunselect],player1->ammo[player1->gunselect]); + else sprintf(gunstats,"%i",player1->mag[player1->gunselect]); + draw_text(gunstats, 690, 827, 2); + //clean up pointer? + //delete gunstats; + } + + glPopMatrix(); + + // EDIT: AH - ctf + #define ctf_scl 0.75 + if(m_ctf) + { + glPushMatrix(); + glOrtho(0, VIRTW, VIRTH, 0, -1, 1); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glColor4f(1.0f, 1.0f, 1.0f, 0.2f); + if(flaginfos[TEAM_CLA].state == CTFF_INBASE) glDisable(GL_BLEND); else glEnable(GL_BLEND); + drawicon(64*ctf_scl, 0, 1940, 1650, 11, 1.0f/4.0f); + if(flaginfos[TEAM_RVSF].state == CTFF_INBASE) glDisable(GL_BLEND); else glEnable(GL_BLEND); + drawicon(0, 0, 1820, 1650, 11, 1.0f/4.0f); + glDisable(GL_BLEND); + drawicon(rb_team_int(player1->team)*64*ctf_scl, 64*ctf_scl, VIRTW-128-10, 10, 11, 1.0f/4.0f); // shows which team you are + glPopMatrix(); + + // big flag icon + flaginfo &f = flaginfos[rb_opposite(rb_team_int(player1->team))]; + if(f.state==CTFF_STOLEN && f.thief == player1 && f.pick_ack ) + { + glPushMatrix(); + glOrtho(0, VIRTW/2, VIRTH/2, 0, -1, 1); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glColor4f(1.0f, 1.0f, 1.0f, (float)(sin(lastmillis / 100.0) + 1) / 2); + glEnable(GL_BLEND); + drawicon(rb_opposite(rb_team_int(player1->team))*128*ctf_scl, 128*ctf_scl, 1065, VIRTH/2/3*2, 11, 1.0f/2.0f); + glPopMatrix(); + }; + }; + + glPushMatrix(); + glOrtho(0, VIRTW, VIRTH, 0, -1, 1); + glDisable(GL_BLEND); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(1.0f, 1.0f, 1.0f, (float) 0.2f+(sin(lastmillis/100)+1)/2); + + if(player1->health <= 20) + { + glEnable(GL_BLEND); + drawicon(64, 0, 20, 1650); //drawicon(128, 128, 20, 1650); + glDisable(GL_BLEND); + } + else drawicon(64, 0, 20, 1650); //drawicon(128, 128, 20, 1650); + if(player1->armour) drawicon((float)(128), 0, 620, 1650); //if(player1->armour) drawicon((float)(player1->armourtype*64), 0, 620, 1650); + int g = player1->gunselect; + int r = 64; + if(g>2) { g -= 3; r = 128; }; + + if(!player1->mag[player1->gunselect] && player1->gunselect != GUN_KNIFE && player1->gunselect != GUN_GRENADE) + { + glEnable(GL_BLEND); + drawicon((float)(g*64), (float)r, 1220, 1650); + glDisable(GL_BLEND); + } + else + { + if (player1->gunselect==GUN_GRENADE) drawicon(0, 0, 1220, 1650); + else drawicon((float)(g*64), (float)r, 1220, 1650); + } + glPopMatrix(); + }; + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + glEnable(GL_DEPTH_TEST); +}; + +#define MAXSHOTLINES 100 +struct shotline +{ + vec from, to; + bool inuse; + int millis; +}; +shotline shotlines[MAXSHOTLINES]; + +void shotlinereset() { loopi(MAXSHOTLINES) shotlines[i].inuse = false; }; + +void addshotline(dynent *d, vec &from, vec &to) +{ + if(d == player1) return; + if(rnd(3) != 0) return; + loopi(MAXSHOTLINES) + { + shotline *s = &shotlines[i]; + if(s->inuse) continue; + int start = 10; + vdist(_dist, _unitv, player1->o, to); + if(_dist <= 10.0f) start = 8; + else start = 5; + + vdist(dist, unitv, from, to); + vdiv(unitv, dist); + s->inuse = true; + s->from = unitv; + vmul(s->from, dist/10*start); + vadd(s->from, from); + s->to = unitv; + vmul(s->to, dist/10*-(10-start-2)); + vadd(s->to, to); + s->millis = lastmillis; + return; + }; +}; + +VAR(shotline_duration, 0, 75, 10000); + +void rendershotlines() +{ + loopi(MAXSHOTLINES) + { + shotline *s = &shotlines[i]; + if(!s->inuse) continue; + if(lastmillis-s->millis > shotline_duration) { s->inuse = false; continue; } + glPushMatrix(); + glDisable(GL_TEXTURE_2D); + + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); + glDisable(GL_FOG); + + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glBegin(GL_LINES); + glColor4f(1.0f, 1.0f, 0.7f, 0.5f); + glVertex3f(s->from.x, s->from.z, s->from.y); + glVertex3f(s->to.x, s->to.z, s->to.y); + glEnd(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + glEnable(GL_FOG); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); + + glEnable(GL_TEXTURE_2D); + glPopMatrix(); + }; +}; + +void renderphysents() +{ + loopv(physents) + { + physent *d = physents[i]; + if(d && d->state==NADE_THROWED) rendermodel("weapons/grenade/static", 0, 1, 0, 0, d->o.x, d->o.z, d->o.y, d->yaw, d->pitch, false, 2.0f, 100.0f, 0, 0); + }; +}; + diff --git a/source/src/rendergl.cpp b/source/src/rendergl.cpp index 5d7864cb..f08e06dd 100644 --- a/source/src/rendergl.cpp +++ b/source/src/rendergl.cpp @@ -1,418 +1,418 @@ -// rendergl.cpp: core opengl rendering stuff - -#include "cube.h" - -#ifdef DARWIN -#define GL_COMBINE_EXT GL_COMBINE_ARB -#define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB -#define GL_SOURCE0_RBG_EXT GL_SOURCE0_RGB_ARB -#define GL_SOURCE1_RBG_EXT GL_SOURCE1_RGB_ARB -#define GL_RGB_SCALE_EXT GL_RGB_SCALE_ARB -#endif - -extern int curvert; - -bool hasoverbright = false; - -void purgetextures(); - -GLUquadricObj *qsphere = NULL; -int glmaxtexsize = 256; - -void gl_init(int w, int h) -{ - //#define fogvalues 0.5f, 0.6f, 0.7f, 1.0f - - glViewport(0, 0, w, h); - glClearDepth(1.0); - glDepthFunc(GL_LESS); - glEnable(GL_DEPTH_TEST); - glShadeModel(GL_SMOOTH); - - - glEnable(GL_FOG); - glFogi(GL_FOG_MODE, GL_LINEAR); - glFogf(GL_FOG_DENSITY, 0.25); - glHint(GL_FOG_HINT, GL_NICEST); - - - glEnable(GL_LINE_SMOOTH); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - glEnable(GL_POLYGON_OFFSET_LINE); - glPolygonOffset(-3.0, -3.0); - - glCullFace(GL_FRONT); - glEnable(GL_CULL_FACE); - - char *exts = (char *)glGetString(GL_EXTENSIONS); - - if(strstr(exts, "GL_EXT_texture_env_combine")) hasoverbright = true; - else conoutf("WARNING: cannot use overbright lighting, using old lighting model!"); - - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glmaxtexsize); - - purgetextures(); - - if(!(qsphere = gluNewQuadric())) fatal("glu sphere"); - gluQuadricDrawStyle(qsphere, GLU_FILL); - gluQuadricOrientation(qsphere, GLU_INSIDE); - gluQuadricTexture(qsphere, GL_TRUE); - glNewList(1, GL_COMPILE); - gluSphere(qsphere, 1, 12, 6); - glEndList(); -}; - -void cleangl() -{ - if(qsphere) gluDeleteQuadric(qsphere); -}; - -bool installtex(int tnum, char *texname, int &xs, int &ys, bool clamp, bool highqual) -{ - SDL_Surface *s = IMG_Load(texname); - if(!s) { conoutf("couldn't load texture %s", (int)texname); return false; }; - if(s->format->BitsPerPixel!=24 && s->format->BitsPerPixel!=32) { conoutf("texture must be 24bpp or 32bpp: %s", (int)texname); return false; }; - // loopi(s->w*s->h*3) { uchar *p = (uchar *)s->pixels+i; *p = 255-*p; }; - glBindTexture(GL_TEXTURE_2D, tnum); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, highqual ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR); //NEAREST); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - xs = s->w; - ys = s->h; - while(xs>glmaxtexsize || ys>glmaxtexsize) { xs /= 2; ys /= 2; }; - void *scaledimg = s->pixels; - if(xs!=s->w) - { - conoutf("warning: quality loss: scaling %s", (int)texname); // for voodoo cards under linux - scaledimg = alloc(xs*ys*3); - gluScaleImage(GL_RGB, s->w, s->h, GL_UNSIGNED_BYTE, s->pixels, xs, ys, GL_UNSIGNED_BYTE, scaledimg); - }; - int mode = s->format->BitsPerPixel==24 ? GL_RGB : GL_RGBA; - if(gluBuild2DMipmaps(GL_TEXTURE_2D, mode, xs, ys, mode, GL_UNSIGNED_BYTE, scaledimg)) fatal("could not build mipmaps"); - if(xs!=s->w) free(scaledimg); - SDL_FreeSurface(s); - return true; -}; - -// management of texture slots -// each texture slot can have multople texture frames, of which currently only the first is used -// additional frames can be used for various shaders - -const int MAXTEX = 1000; -int texx[MAXTEX]; // ( loaded texture ) -> ( name, size ) -int texy[MAXTEX]; -string texname[MAXTEX]; -int curtex = 0; -const int FIRSTTEX = 130; // opengl id = loaded id + FIRSTTEX -// std 1+, sky 14+, md3's 30+,md2's 40+, menu 120+ - -const int MAXFRAMES = 2; // increase to allow more complex shader defs -int mapping[256][MAXFRAMES]; // ( cube texture, frame ) -> ( opengl id, name ) -string mapname[256][MAXFRAMES]; - -void purgetextures() -{ - loopi(256) loop(j,MAXFRAMES) mapping[i][j] = 0; -}; - -int curtexnum = 0; - -void texturereset() { curtexnum = 0; }; - -void texture(char *aframe, char *name) -{ - int num = curtexnum++, frame = atoi(aframe); - if(num<0 || num>=256 || frame<0 || frame>=MAXFRAMES) return; - mapping[num][frame] = 1; - char *n = mapname[num][frame]; - strcpy_s(n, name); - path(n); -}; - -COMMAND(texturereset, ARG_NONE); -COMMAND(texture, ARG_2STR); - -int lookuptexture(int tex, int &xs, int &ys) -{ - int frame = 0; // other frames? - int tid = mapping[tex][frame]; - - if(tid>=FIRSTTEX) - { - xs = texx[tid-FIRSTTEX]; - ys = texy[tid-FIRSTTEX]; - return tid; - }; - - xs = ys = 16; - if(!tid) return 1; // crosshair :) - - loopi(curtex) // lazily happens once per "texture" command, basically - { - if(strcmp(mapname[tex][frame], texname[i])==0) - { - mapping[tex][frame] = tid = i+FIRSTTEX; - xs = texx[i]; - ys = texy[i]; - return tid; - }; - }; - - if(curtex==MAXTEX) fatal("loaded too many textures"); - - int tnum = curtex+FIRSTTEX; - strcpy_s(texname[curtex], mapname[tex][frame]); - - sprintf_sd(name)("packages%ctextures%c%s", PATHDIV, PATHDIV, texname[curtex]); - - if(installtex(tnum, name, xs, ys)) - { - mapping[tex][frame] = tnum; - texx[curtex] = xs; - texy[curtex] = ys; - curtex++; - return tnum; - } - else - { - return mapping[tex][frame] = FIRSTTEX; // temp fix - }; -}; - -void setupworld() -{ - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_COLOR_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - setarraypointers(); - - if(hasoverbright) - { - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); - glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); - }; -}; - -int skyoglid; - -struct strip { int tex, start, num; }; -vector strips; - -void renderstripssky() -{ - glBindTexture(GL_TEXTURE_2D, skyoglid); - loopv(strips) if(strips[i].tex==skyoglid) glDrawArrays(GL_TRIANGLE_STRIP, strips[i].start, strips[i].num); -}; - -void renderstrips() -{ - int lasttex = -1; - loopv(strips) if(strips[i].tex!=skyoglid) - { - if(strips[i].tex!=lasttex) - { - glBindTexture(GL_TEXTURE_2D, strips[i].tex); - lasttex = strips[i].tex; - }; - glDrawArrays(GL_TRIANGLE_STRIP, strips[i].start, strips[i].num); - }; -}; - -void overbright(float amount) { if(hasoverbright) glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, amount ); }; - -void addstrip(int tex, int start, int n) -{ - strip &s = strips.add(); - s.tex = tex; - s.start = start; - s.num = n; -}; - -VARF(gamma, 30, 100, 300, -{ - float f = gamma/100.0f; - if(SDL_SetGamma(f,f,f)==-1) - { - conoutf("Could not set gamma (card/driver doesn't support it?)"); - conoutf("sdl: %s", (int)SDL_GetError()); - }; -}); - -void transplayer() -{ - glLoadIdentity(); - - if(player1->state==CS_DEAD) - { - float t = (lastmillis-player1->lastaction)/1000.0f; - if(t >= 1.6f) t = 1.6f; - - player1->pitch = (float) sin(t)*(-70.0f-player1->oldpitch)+player1->oldpitch; - glRotated(player1->pitch,-1.0,0.0,0.0); - glRotated(player1->yaw,0.0,1.0,0.0); - - glTranslated(-player1->o.x, player1->eyeheight-sin(t)*7.0f-player1->o.z, -player1->o.y); - } - else - { - glRotated(player1->roll,0.0,0.0,1.0); - glRotated(player1->pitch,-1.0,0.0,0.0); - glRotated(player1->yaw,0.0,1.0,0.0); - - glTranslated(-player1->o.x, (player1->state==CS_DEAD ? player1->eyeheight-0.2f : 0)-player1->o.z, -player1->o.y); - }; -}; - -VAR(fov, 90, 105, 120); - -int xtraverts; - -VAR(fog, 64, 180, 1024); -VAR(fogcolour, 0, 0x8099B3, 0xFFFFFF); - -VAR(hudgun,0,1,1); - -char *hudgunnames[] = { "knife", "pistol", "shotgun", "subgun", "sniper", "assault", "grenade"}; - -void drawhudmodel(int start, int end, float speed, int base) -{ - sprintf_sd(mdl)("hudguns/%s", hudgunnames[player1->gunselect]); - rendermodel(mdl, start, end, 0, 1.0f, player1->o.x, player1->o.z, player1->o.y, player1->yaw+90, player1->pitch, false, 1.0f, speed, 0, base); -}; - - -void drawhudgun(int w, int h, float aspect, int farplane) -{ - if(!hudgun || (scoped && player1->gunselect==GUN_SNIPER) || player1->state==CS_DEAD) return; - - glEnable(GL_CULL_FACE); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluPerspective((float)100.0f*h/w, aspect, 0.3f, farplane); // fov fixed at 100° - glMatrixMode(GL_MODELVIEW); - - rendermd3gun(); - rendermenumdl(); // lazy bastard - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluPerspective((float)fov*h/w, aspect, 0.15f, farplane); - glMatrixMode(GL_MODELVIEW); - - glDisable(GL_CULL_FACE); -}; - -void gl_drawframe(int w, int h, float changelod, float curfps) -{ - float hf = hdr.waterlevel-0.3f; - float fovy = (float)fov*h/w; - float aspect = w/(float)h; - bool underwater = player1->o.z>16)/256.0f, ((fogcolour>>8)&255)/256.0f, (fogcolour&255)/256.0f, 1.0f }; - glFogfv(GL_FOG_COLOR, fogc); - glClearColor(fogc[0], fogc[1], fogc[2], 1.0f); - - if(underwater) - { - fovy += (float)sin(lastmillis/1000.0)*2.0f; - aspect += (float)sin(lastmillis/1000.0+PI)*0.1f; - glFogi(GL_FOG_START, 0); - glFogi(GL_FOG_END, (fog+96)/8); - }; - - glClear((player1->outsidemap ? GL_COLOR_BUFFER_BIT : 0) | GL_DEPTH_BUFFER_BIT); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - int farplane = fog*5/2; - gluPerspective(fovy, aspect, 0.15f, farplane); - glMatrixMode(GL_MODELVIEW); - - transplayer(); - - glEnable(GL_TEXTURE_2D); - - int xs, ys; - skyoglid = lookuptexture(DEFAULT_SKY, xs, ys); - - resetcubes(); - - curvert = 0; - strips.setsize(0); - - render_world(player1->o.x, player1->o.y, player1->o.z, changelod, - (int)player1->yaw, (int)player1->pitch, (float)fov, w, h); - finishstrips(); - - setupworld(); - - renderstripssky(); - - glLoadIdentity(); - glRotated(player1->pitch, -1.0, 0.0, 0.0); - glRotated(player1->yaw, 0.0, 1.0, 0.0); - glRotated(90.0, 1.0, 0.0, 0.0); - glColor3f(1.0f, 1.0f, 1.0f); - glDisable(GL_FOG); - glDepthFunc(GL_GREATER); - draw_envbox(14, fog*4/3); - glDepthFunc(GL_LESS); - glEnable(GL_FOG); - - transplayer(); - - overbright(2); - - renderstrips(); - - xtraverts = 0; - - renderclients(); - //monsterrender(); - BotManager.RenderBots(); // Added by Rick - - renderentities(); - - renderspheres(curtime); - renderents(); - - renderphysents(); - - rendershotlines(); - - glDisable(GL_CULL_FACE); - - // Added by Rick: Need todo here because of drawing the waypoints - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - WaypointClass.Think(); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - // end add - - drawhudgun(w, h, aspect, farplane); - - overbright(1); - int nquads = renderwater(hf); - - overbright(2); - render_particles(curtime); - overbright(1); - - glDisable(GL_FOG); - - glDisable(GL_TEXTURE_2D); - - gl_drawhud(w, h, (int)curfps, nquads, curvert, underwater); - - glEnable(GL_CULL_FACE); - glEnable(GL_FOG); -}; - +// rendergl.cpp: core opengl rendering stuff + +#include "cube.h" + +#ifdef DARWIN +#define GL_COMBINE_EXT GL_COMBINE_ARB +#define GL_COMBINE_RGB_EXT GL_COMBINE_RGB_ARB +#define GL_SOURCE0_RBG_EXT GL_SOURCE0_RGB_ARB +#define GL_SOURCE1_RBG_EXT GL_SOURCE1_RGB_ARB +#define GL_RGB_SCALE_EXT GL_RGB_SCALE_ARB +#endif + +extern int curvert; + +bool hasoverbright = false; + +void purgetextures(); + +GLUquadricObj *qsphere = NULL; +int glmaxtexsize = 256; + +void gl_init(int w, int h) +{ + //#define fogvalues 0.5f, 0.6f, 0.7f, 1.0f + + glViewport(0, 0, w, h); + glClearDepth(1.0); + glDepthFunc(GL_LESS); + glEnable(GL_DEPTH_TEST); + glShadeModel(GL_SMOOTH); + + + glEnable(GL_FOG); + glFogi(GL_FOG_MODE, GL_LINEAR); + glFogf(GL_FOG_DENSITY, 0.25); + glHint(GL_FOG_HINT, GL_NICEST); + + + glEnable(GL_LINE_SMOOTH); + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + glEnable(GL_POLYGON_OFFSET_LINE); + glPolygonOffset(-3.0, -3.0); + + glCullFace(GL_FRONT); + glEnable(GL_CULL_FACE); + + char *exts = (char *)glGetString(GL_EXTENSIONS); + + if(strstr(exts, "GL_EXT_texture_env_combine")) hasoverbright = true; + else conoutf("WARNING: cannot use overbright lighting, using old lighting model!"); + + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glmaxtexsize); + + purgetextures(); + + if(!(qsphere = gluNewQuadric())) fatal("glu sphere"); + gluQuadricDrawStyle(qsphere, GLU_FILL); + gluQuadricOrientation(qsphere, GLU_INSIDE); + gluQuadricTexture(qsphere, GL_TRUE); + glNewList(1, GL_COMPILE); + gluSphere(qsphere, 1, 12, 6); + glEndList(); +}; + +void cleangl() +{ + if(qsphere) gluDeleteQuadric(qsphere); +}; + +bool installtex(int tnum, char *texname, int &xs, int &ys, bool clamp, bool highqual) +{ + SDL_Surface *s = IMG_Load(texname); + if(!s) { conoutf("couldn't load texture %s", (int)texname); return false; }; + if(s->format->BitsPerPixel!=24 && s->format->BitsPerPixel!=32) { conoutf("texture must be 24bpp or 32bpp: %s", (int)texname); return false; }; + // loopi(s->w*s->h*3) { uchar *p = (uchar *)s->pixels+i; *p = 255-*p; }; + glBindTexture(GL_TEXTURE_2D, tnum); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, highqual ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR_MIPMAP_LINEAR); //NEAREST); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + xs = s->w; + ys = s->h; + while(xs>glmaxtexsize || ys>glmaxtexsize) { xs /= 2; ys /= 2; }; + void *scaledimg = s->pixels; + if(xs!=s->w) + { + conoutf("warning: quality loss: scaling %s", (int)texname); // for voodoo cards under linux + scaledimg = alloc(xs*ys*3); + gluScaleImage(GL_RGB, s->w, s->h, GL_UNSIGNED_BYTE, s->pixels, xs, ys, GL_UNSIGNED_BYTE, scaledimg); + }; + int mode = s->format->BitsPerPixel==24 ? GL_RGB : GL_RGBA; + if(gluBuild2DMipmaps(GL_TEXTURE_2D, mode, xs, ys, mode, GL_UNSIGNED_BYTE, scaledimg)) fatal("could not build mipmaps"); + if(xs!=s->w) free(scaledimg); + SDL_FreeSurface(s); + return true; +}; + +// management of texture slots +// each texture slot can have multople texture frames, of which currently only the first is used +// additional frames can be used for various shaders + +const int MAXTEX = 1000; +int texx[MAXTEX]; // ( loaded texture ) -> ( name, size ) +int texy[MAXTEX]; +string texname[MAXTEX]; +int curtex = 0; +const int FIRSTTEX = 130; // opengl id = loaded id + FIRSTTEX +// std 1+, sky 14+, md3's 30+,md2's 40+, menu 120+ + +const int MAXFRAMES = 2; // increase to allow more complex shader defs +int mapping[256][MAXFRAMES]; // ( cube texture, frame ) -> ( opengl id, name ) +string mapname[256][MAXFRAMES]; + +void purgetextures() +{ + loopi(256) loop(j,MAXFRAMES) mapping[i][j] = 0; +}; + +int curtexnum = 0; + +void texturereset() { curtexnum = 0; }; + +void texture(char *aframe, char *name) +{ + int num = curtexnum++, frame = atoi(aframe); + if(num<0 || num>=256 || frame<0 || frame>=MAXFRAMES) return; + mapping[num][frame] = 1; + char *n = mapname[num][frame]; + strcpy_s(n, name); + path(n); +}; + +COMMAND(texturereset, ARG_NONE); +COMMAND(texture, ARG_2STR); + +int lookuptexture(int tex, int &xs, int &ys) +{ + int frame = 0; // other frames? + int tid = mapping[tex][frame]; + + if(tid>=FIRSTTEX) + { + xs = texx[tid-FIRSTTEX]; + ys = texy[tid-FIRSTTEX]; + return tid; + }; + + xs = ys = 16; + if(!tid) return 1; // crosshair :) + + loopi(curtex) // lazily happens once per "texture" command, basically + { + if(strcmp(mapname[tex][frame], texname[i])==0) + { + mapping[tex][frame] = tid = i+FIRSTTEX; + xs = texx[i]; + ys = texy[i]; + return tid; + }; + }; + + if(curtex==MAXTEX) fatal("loaded too many textures"); + + int tnum = curtex+FIRSTTEX; + strcpy_s(texname[curtex], mapname[tex][frame]); + + sprintf_sd(name)("packages%ctextures%c%s", PATHDIV, PATHDIV, texname[curtex]); + + if(installtex(tnum, name, xs, ys)) + { + mapping[tex][frame] = tnum; + texx[curtex] = xs; + texy[curtex] = ys; + curtex++; + return tnum; + } + else + { + return mapping[tex][frame] = FIRSTTEX; // temp fix + }; +}; + +void setupworld() +{ + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + setarraypointers(); + + if(hasoverbright) + { + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PRIMARY_COLOR_EXT); + }; +}; + +int skyoglid; + +struct strip { int tex, start, num; }; +vector strips; + +void renderstripssky() +{ + glBindTexture(GL_TEXTURE_2D, skyoglid); + loopv(strips) if(strips[i].tex==skyoglid) glDrawArrays(GL_TRIANGLE_STRIP, strips[i].start, strips[i].num); +}; + +void renderstrips() +{ + int lasttex = -1; + loopv(strips) if(strips[i].tex!=skyoglid) + { + if(strips[i].tex!=lasttex) + { + glBindTexture(GL_TEXTURE_2D, strips[i].tex); + lasttex = strips[i].tex; + }; + glDrawArrays(GL_TRIANGLE_STRIP, strips[i].start, strips[i].num); + }; +}; + +void overbright(float amount) { if(hasoverbright) glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, amount ); }; + +void addstrip(int tex, int start, int n) +{ + strip &s = strips.add(); + s.tex = tex; + s.start = start; + s.num = n; +}; + +VARF(gamma, 30, 100, 300, +{ + float f = gamma/100.0f; + if(SDL_SetGamma(f,f,f)==-1) + { + conoutf("Could not set gamma (card/driver doesn't support it?)"); + conoutf("sdl: %s", (int)SDL_GetError()); + }; +}); + +void transplayer() +{ + glLoadIdentity(); + + if(player1->state==CS_DEAD) + { + float t = (lastmillis-player1->lastaction)/1000.0f; + if(t >= 1.6f) t = 1.6f; + + player1->pitch = (float) sin(t)*(-70.0f-player1->oldpitch)+player1->oldpitch; + glRotated(player1->pitch,-1.0,0.0,0.0); + glRotated(player1->yaw,0.0,1.0,0.0); + + glTranslated(-player1->o.x, player1->eyeheight-sin(t)*7.0f-player1->o.z, -player1->o.y); + } + else + { + glRotated(player1->roll,0.0,0.0,1.0); + glRotated(player1->pitch,-1.0,0.0,0.0); + glRotated(player1->yaw,0.0,1.0,0.0); + + glTranslated(-player1->o.x, (player1->state==CS_DEAD ? player1->eyeheight-0.2f : 0)-player1->o.z, -player1->o.y); + }; +}; + +VAR(fov, 90, 105, 120); + +int xtraverts; + +VAR(fog, 64, 180, 1024); +VAR(fogcolour, 0, 0x8099B3, 0xFFFFFF); + +VAR(hudgun,0,1,1); + +char *hudgunnames[] = { "knife", "pistol", "shotgun", "subgun", "sniper", "assault", "grenade"}; + +void drawhudmodel(int start, int end, float speed, int base) +{ + sprintf_sd(mdl)("hudguns/%s", hudgunnames[player1->gunselect]); + rendermodel(mdl, start, end, 0, 1.0f, player1->o.x, player1->o.z, player1->o.y, player1->yaw+90, player1->pitch, false, 1.0f, speed, 0, base); +}; + + +void drawhudgun(int w, int h, float aspect, int farplane) +{ + if(!hudgun || (scoped && player1->gunselect==GUN_SNIPER) || player1->state==CS_DEAD) return; + + glEnable(GL_CULL_FACE); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective((float)100.0f*h/w, aspect, 0.3f, farplane); // fov fixed at 100° + glMatrixMode(GL_MODELVIEW); + + rendermd3gun(); + rendermenumdl(); // lazy bastard + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective((float)fov*h/w, aspect, 0.15f, farplane); + glMatrixMode(GL_MODELVIEW); + + glDisable(GL_CULL_FACE); +}; + +void gl_drawframe(int w, int h, float changelod, float curfps) +{ + float hf = hdr.waterlevel-0.3f; + float fovy = (float)fov*h/w; + float aspect = w/(float)h; + bool underwater = player1->o.z>16)/256.0f, ((fogcolour>>8)&255)/256.0f, (fogcolour&255)/256.0f, 1.0f }; + glFogfv(GL_FOG_COLOR, fogc); + glClearColor(fogc[0], fogc[1], fogc[2], 1.0f); + + if(underwater) + { + fovy += (float)sin(lastmillis/1000.0)*2.0f; + aspect += (float)sin(lastmillis/1000.0+PI)*0.1f; + glFogi(GL_FOG_START, 0); + glFogi(GL_FOG_END, (fog+96)/8); + }; + + glClear((player1->outsidemap ? GL_COLOR_BUFFER_BIT : 0) | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + int farplane = fog*5/2; + gluPerspective(fovy, aspect, 0.15f, farplane); + glMatrixMode(GL_MODELVIEW); + + transplayer(); + + glEnable(GL_TEXTURE_2D); + + int xs, ys; + skyoglid = lookuptexture(DEFAULT_SKY, xs, ys); + + resetcubes(); + + curvert = 0; + strips.setsize(0); + + render_world(player1->o.x, player1->o.y, player1->o.z, changelod, + (int)player1->yaw, (int)player1->pitch, (float)fov, w, h); + finishstrips(); + + setupworld(); + + renderstripssky(); + + glLoadIdentity(); + glRotated(player1->pitch, -1.0, 0.0, 0.0); + glRotated(player1->yaw, 0.0, 1.0, 0.0); + glRotated(90.0, 1.0, 0.0, 0.0); + glColor3f(1.0f, 1.0f, 1.0f); + glDisable(GL_FOG); + glDepthFunc(GL_GREATER); + draw_envbox(14, fog*4/3); + glDepthFunc(GL_LESS); + glEnable(GL_FOG); + + transplayer(); + + overbright(2); + + renderstrips(); + + xtraverts = 0; + + renderclients(); + //monsterrender(); + BotManager.RenderBots(); // Added by Rick + + renderentities(); + + renderspheres(curtime); + renderents(); + + renderphysents(); + + rendershotlines(); + + glDisable(GL_CULL_FACE); + + // Added by Rick: Need todo here because of drawing the waypoints + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + WaypointClass.Think(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + // end add + + drawhudgun(w, h, aspect, farplane); + + overbright(1); + int nquads = renderwater(hf); + + overbright(2); + render_particles(curtime); + overbright(1); + + glDisable(GL_FOG); + + glDisable(GL_TEXTURE_2D); + + gl_drawhud(w, h, (int)curfps, nquads, curvert, underwater); + + glEnable(GL_CULL_FACE); + glEnable(GL_FOG); +}; + diff --git a/source/src/rendermd2.cpp b/source/src/rendermd2.cpp index d14fbbd7..7fdde80b 100644 --- a/source/src/rendermd2.cpp +++ b/source/src/rendermd2.cpp @@ -1,283 +1,283 @@ -// rendermd2.cpp: loader code adapted from a nehe tutorial - -#include "cube.h" - -struct md2_header -{ - int magic; - int version; - int skinWidth, skinHeight; - int frameSize; - int numSkins, numVertices, numTexcoords; - int numTriangles, numGlCommands, numFrames; - int offsetSkins, offsetTexcoords, offsetTriangles; - int offsetFrames, offsetGlCommands, offsetEnd; -}; - -struct md2_vertex -{ - uchar vertex[3], lightNormalIndex; -}; - -struct md2_frame -{ - float scale[3]; - float translate[3]; - char name[16]; - md2_vertex vertices[1]; -}; - -struct md2 -{ - int numGlCommands; - int* glCommands; - int numTriangles; - int frameSize; - int numFrames; - int numVerts; - char* frames; - vec **mverts; - int displaylist; - int displaylistverts; - - mapmodelinfo mmi; - char *loadname; - int mdlnum; - bool loaded; - - bool load(char* filename); - void render(vec &light, int numFrame, int range, float x, float y, float z, float yaw, float pitch, float scale, float speed, int snap, int basetime); - void scale(int frame, float scale, int sn); - - md2() : numGlCommands(0), frameSize(0), numFrames(0), displaylist(0), loaded(false) {}; - - ~md2() - { - if(glCommands) - delete [] glCommands; - if(frames) - delete [] frames; - } -}; - - -bool md2::load(char* filename) -{ - FILE* file; - md2_header header; - - if((file= fopen(filename, "rb"))==NULL) return false; - - fread(&header, sizeof(md2_header), 1, file); - endianswap(&header, sizeof(int), sizeof(md2_header)/sizeof(int)); - - if(header.magic!= 844121161 || header.version!=8) return false; - - frames = new char[header.frameSize*header.numFrames]; - if(frames==NULL) return false; - - fseek(file, header.offsetFrames, SEEK_SET); - fread(frames, header.frameSize*header.numFrames, 1, file); - - for(int i = 0; i < header.numFrames; ++i) - { - endianswap(frames + i * header.frameSize, sizeof(float), 6); - } - - glCommands = new int[header.numGlCommands]; - if(glCommands==NULL) return false; - - fseek(file, header.offsetGlCommands, SEEK_SET); - fread(glCommands, header.numGlCommands*sizeof(int), 1, file); - - endianswap(glCommands, sizeof(int), header.numGlCommands); - - numFrames = header.numFrames; - numGlCommands= header.numGlCommands; - frameSize = header.frameSize; - numTriangles = header.numTriangles; - numVerts = header.numVertices; - - fclose(file); - - mverts = new vec*[numFrames]; - loopj(numFrames) mverts[j] = NULL; - - return true; -}; - -float snap(int sn, float f) { return sn ? (float)(((int)(f+sn*0.5f))&(~(sn-1))) : f; }; - -void md2::scale(int frame, float scale, int sn) -{ - mverts[frame] = new vec[numVerts]; - md2_frame *cf = (md2_frame *) ((char*)frames+frameSize*frame); - float sc = 16.0f/scale; - loop(vi, numVerts) - { - uchar *cv = (uchar *)&cf->vertices[vi].vertex; - vec *v = &(mverts[frame])[vi]; - v->x = (snap(sn, cv[0]*cf->scale[0])+cf->translate[0])/sc; - v->y = -(snap(sn, cv[1]*cf->scale[1])+cf->translate[1])/sc; - v->z = (snap(sn, cv[2]*cf->scale[2])+cf->translate[2])/sc; - }; -}; - -void md2::render(vec &light, int frame, int range, float x, float y, float z, float yaw, float pitch, float sc, float speed, int snap, int basetime) -{ - loopi(range) if(!mverts[frame+i]) scale(frame+i, sc, snap); - - glPushMatrix (); - glTranslatef(x, y, z); - glRotatef(yaw+180, 0, -1, 0); - glRotatef(pitch, 0, 0, 1); - - glColor3fv((float *)&light); - - if(displaylist && frame==0 && range==1) - { - glCallList(displaylist); - xtraverts += displaylistverts; - } - else - { - if(frame==0 && range==1) - { - static int displaylistn = 10; - glNewList(displaylist = displaylistn++, GL_COMPILE); - displaylistverts = xtraverts; - }; - - int time = lastmillis-basetime; - int fr1 = (int)(time/speed); - float frac1 = (time-fr1*speed)/speed; - float frac2 = 1-frac1; - fr1 = fr1%range+frame; - int fr2 = fr1+1; - if(fr2>=frame+range) fr2 = frame; - vec *verts1 = mverts[fr1]; - vec *verts2 = mverts[fr2]; - - for(int *command = glCommands; (*command)!=0;) - { - int numVertex = *command++; - if(numVertex>0) { glBegin(GL_TRIANGLE_STRIP); } - else { glBegin(GL_TRIANGLE_FAN); numVertex = -numVertex; }; - - loopi(numVertex) - { - float tu = *((float*)command++); - float tv = *((float*)command++); - glTexCoord2f(tu, tv); - int vn = *command++; - vec &v1 = verts1[vn]; - vec &v2 = verts2[vn]; - #define ip(c) v1.c*frac2+v2.c*frac1 - glVertex3f(ip(x), ip(z), ip(y)); - }; - - xtraverts += numVertex; - - glEnd(); - }; - - if(displaylist) - { - glEndList(); - displaylistverts = xtraverts-displaylistverts; - }; - }; - - glPopMatrix(); -} - -hashtable *mdllookup = NULL; -vector mapmodels; -const int FIRSTMDL = 40; - -void delayedload(md2 *m) -{ - if(!m->loaded) - { - sprintf_sd(name1)("packages/models/%s/tris.md2", m->loadname); - if(!m->load(path(name1))) fatal("loadmodel: ", name1); - sprintf_sd(name2)("packages/models/%s/skin.jpg", m->loadname); - int xs, ys; - installtex(FIRSTMDL+m->mdlnum, path(name2), xs, ys); - m->loaded = true; - }; -}; - -int modelnum = 0; - -md2 *loadmodel(char *name) -{ - - if(!mdllookup) mdllookup = new hashtable; - md2 **mm = mdllookup->access(name); - if(mm) return *mm; - md2 *m = new md2(); - m->mdlnum = modelnum++; - mapmodelinfo mmi = { 2, 2, 0, 0, "" }; - m->mmi = mmi; - m->loadname = newstring(name); - mdllookup->access(m->loadname, &m); - - return m; -}; - -void mapmodel(char *rad, char *h, char *zoff, char *snap, char *name) -{ - char *mpdir = "mapmodels/"; - char *lname = new char[ strlen(mpdir) + strlen(name) + 1 ]; - strcpy(lname, mpdir); - strcat(lname, name); - md2 *m = loadmodel(lname); - mapmodelinfo mmi = { atoi(rad), atoi(h), atoi(zoff), atoi(snap), m->loadname }; - m->mmi = mmi; - mapmodels.add(m); -}; - -void mapmodelreset() { mapmodels.setsize(0); }; - -mapmodelinfo &getmminfo(int i) { return immi : *(mapmodelinfo *)0; }; - -COMMAND(mapmodel, ARG_5STR); -COMMAND(mapmodelreset, ARG_NONE); - -void rendermodel(char *mdl, int frame, int range, int tex, float rad, float x, float y, float z, float yaw, float pitch, bool teammate, float scale, float speed, int snap, int basetime, bool oculling) -{ - md2 *m = loadmodel(mdl); - - if(oculling && isoccluded(player1->o.x, player1->o.y, x-rad, z-rad, rad*2)) return; - - delayedload(m); - - int xs, ys; - glBindTexture(GL_TEXTURE_2D, tex ? lookuptexture(tex, xs, ys) : FIRSTMDL+m->mdlnum); - - int ix = (int)x; - int iy = (int)z; - vec light = { 1.0f, 1.0f, 1.0f }; - - if(!OUTBORD(ix, iy)) - { - sqr *s = S(ix,iy); - float ll = 256.0f; // 0.96f; - float of = 0.0f; // 0.1f; - light.x = s->r/ll+of; - light.y = s->g/ll+of; - light.z = s->b/ll+of; - }; - -/* - if(teammate) - { - light.x *= 0.6f; - light.y *= 0.7f; - light.z *= 1.2f; - }; -*/ - - m->render(light, frame, range, x, y, z, yaw, pitch, scale, speed, snap, basetime); -}; +// rendermd2.cpp: loader code adapted from a nehe tutorial + +#include "cube.h" + +struct md2_header +{ + int magic; + int version; + int skinWidth, skinHeight; + int frameSize; + int numSkins, numVertices, numTexcoords; + int numTriangles, numGlCommands, numFrames; + int offsetSkins, offsetTexcoords, offsetTriangles; + int offsetFrames, offsetGlCommands, offsetEnd; +}; + +struct md2_vertex +{ + uchar vertex[3], lightNormalIndex; +}; + +struct md2_frame +{ + float scale[3]; + float translate[3]; + char name[16]; + md2_vertex vertices[1]; +}; + +struct md2 +{ + int numGlCommands; + int* glCommands; + int numTriangles; + int frameSize; + int numFrames; + int numVerts; + char* frames; + vec **mverts; + int displaylist; + int displaylistverts; + + mapmodelinfo mmi; + char *loadname; + int mdlnum; + bool loaded; + + bool load(char* filename); + void render(vec &light, int numFrame, int range, float x, float y, float z, float yaw, float pitch, float scale, float speed, int snap, int basetime); + void scale(int frame, float scale, int sn); + + md2() : numGlCommands(0), frameSize(0), numFrames(0), displaylist(0), loaded(false) {}; + + ~md2() + { + if(glCommands) + delete [] glCommands; + if(frames) + delete [] frames; + } +}; + + +bool md2::load(char* filename) +{ + FILE* file; + md2_header header; + + if((file= fopen(filename, "rb"))==NULL) return false; + + fread(&header, sizeof(md2_header), 1, file); + endianswap(&header, sizeof(int), sizeof(md2_header)/sizeof(int)); + + if(header.magic!= 844121161 || header.version!=8) return false; + + frames = new char[header.frameSize*header.numFrames]; + if(frames==NULL) return false; + + fseek(file, header.offsetFrames, SEEK_SET); + fread(frames, header.frameSize*header.numFrames, 1, file); + + for(int i = 0; i < header.numFrames; ++i) + { + endianswap(frames + i * header.frameSize, sizeof(float), 6); + } + + glCommands = new int[header.numGlCommands]; + if(glCommands==NULL) return false; + + fseek(file, header.offsetGlCommands, SEEK_SET); + fread(glCommands, header.numGlCommands*sizeof(int), 1, file); + + endianswap(glCommands, sizeof(int), header.numGlCommands); + + numFrames = header.numFrames; + numGlCommands= header.numGlCommands; + frameSize = header.frameSize; + numTriangles = header.numTriangles; + numVerts = header.numVertices; + + fclose(file); + + mverts = new vec*[numFrames]; + loopj(numFrames) mverts[j] = NULL; + + return true; +}; + +float snap(int sn, float f) { return sn ? (float)(((int)(f+sn*0.5f))&(~(sn-1))) : f; }; + +void md2::scale(int frame, float scale, int sn) +{ + mverts[frame] = new vec[numVerts]; + md2_frame *cf = (md2_frame *) ((char*)frames+frameSize*frame); + float sc = 16.0f/scale; + loop(vi, numVerts) + { + uchar *cv = (uchar *)&cf->vertices[vi].vertex; + vec *v = &(mverts[frame])[vi]; + v->x = (snap(sn, cv[0]*cf->scale[0])+cf->translate[0])/sc; + v->y = -(snap(sn, cv[1]*cf->scale[1])+cf->translate[1])/sc; + v->z = (snap(sn, cv[2]*cf->scale[2])+cf->translate[2])/sc; + }; +}; + +void md2::render(vec &light, int frame, int range, float x, float y, float z, float yaw, float pitch, float sc, float speed, int snap, int basetime) +{ + loopi(range) if(!mverts[frame+i]) scale(frame+i, sc, snap); + + glPushMatrix (); + glTranslatef(x, y, z); + glRotatef(yaw+180, 0, -1, 0); + glRotatef(pitch, 0, 0, 1); + + glColor3fv((float *)&light); + + if(displaylist && frame==0 && range==1) + { + glCallList(displaylist); + xtraverts += displaylistverts; + } + else + { + if(frame==0 && range==1) + { + static int displaylistn = 10; + glNewList(displaylist = displaylistn++, GL_COMPILE); + displaylistverts = xtraverts; + }; + + int time = lastmillis-basetime; + int fr1 = (int)(time/speed); + float frac1 = (time-fr1*speed)/speed; + float frac2 = 1-frac1; + fr1 = fr1%range+frame; + int fr2 = fr1+1; + if(fr2>=frame+range) fr2 = frame; + vec *verts1 = mverts[fr1]; + vec *verts2 = mverts[fr2]; + + for(int *command = glCommands; (*command)!=0;) + { + int numVertex = *command++; + if(numVertex>0) { glBegin(GL_TRIANGLE_STRIP); } + else { glBegin(GL_TRIANGLE_FAN); numVertex = -numVertex; }; + + loopi(numVertex) + { + float tu = *((float*)command++); + float tv = *((float*)command++); + glTexCoord2f(tu, tv); + int vn = *command++; + vec &v1 = verts1[vn]; + vec &v2 = verts2[vn]; + #define ip(c) v1.c*frac2+v2.c*frac1 + glVertex3f(ip(x), ip(z), ip(y)); + }; + + xtraverts += numVertex; + + glEnd(); + }; + + if(displaylist) + { + glEndList(); + displaylistverts = xtraverts-displaylistverts; + }; + }; + + glPopMatrix(); +} + +hashtable *mdllookup = NULL; +vector mapmodels; +const int FIRSTMDL = 40; + +void delayedload(md2 *m) +{ + if(!m->loaded) + { + sprintf_sd(name1)("packages/models/%s/tris.md2", m->loadname); + if(!m->load(path(name1))) fatal("loadmodel: ", name1); + sprintf_sd(name2)("packages/models/%s/skin.jpg", m->loadname); + int xs, ys; + installtex(FIRSTMDL+m->mdlnum, path(name2), xs, ys); + m->loaded = true; + }; +}; + +int modelnum = 0; + +md2 *loadmodel(char *name) +{ + + if(!mdllookup) mdllookup = new hashtable; + md2 **mm = mdllookup->access(name); + if(mm) return *mm; + md2 *m = new md2(); + m->mdlnum = modelnum++; + mapmodelinfo mmi = { 2, 2, 0, 0, "" }; + m->mmi = mmi; + m->loadname = newstring(name); + mdllookup->access(m->loadname, &m); + + return m; +}; + +void mapmodel(char *rad, char *h, char *zoff, char *snap, char *name) +{ + char *mpdir = "mapmodels/"; + char *lname = new char[ strlen(mpdir) + strlen(name) + 1 ]; + strcpy(lname, mpdir); + strcat(lname, name); + md2 *m = loadmodel(lname); + mapmodelinfo mmi = { atoi(rad), atoi(h), atoi(zoff), atoi(snap), m->loadname }; + m->mmi = mmi; + mapmodels.add(m); +}; + +void mapmodelreset() { mapmodels.setsize(0); }; + +mapmodelinfo &getmminfo(int i) { return immi : *(mapmodelinfo *)0; }; + +COMMAND(mapmodel, ARG_5STR); +COMMAND(mapmodelreset, ARG_NONE); + +void rendermodel(char *mdl, int frame, int range, int tex, float rad, float x, float y, float z, float yaw, float pitch, bool teammate, float scale, float speed, int snap, int basetime, bool oculling) +{ + md2 *m = loadmodel(mdl); + + if(oculling && isoccluded(player1->o.x, player1->o.y, x-rad, z-rad, rad*2)) return; + + delayedload(m); + + int xs, ys; + glBindTexture(GL_TEXTURE_2D, tex ? lookuptexture(tex, xs, ys) : FIRSTMDL+m->mdlnum); + + int ix = (int)x; + int iy = (int)z; + vec light = { 1.0f, 1.0f, 1.0f }; + + if(!OUTBORD(ix, iy)) + { + sqr *s = S(ix,iy); + float ll = 256.0f; // 0.96f; + float of = 0.0f; // 0.1f; + light.x = s->r/ll+of; + light.y = s->g/ll+of; + light.z = s->b/ll+of; + }; + +/* + if(teammate) + { + light.x *= 0.6f; + light.y *= 0.7f; + light.z *= 1.2f; + }; +*/ + + m->render(light, frame, range, x, y, z, yaw, pitch, scale, speed, snap, basetime); +}; diff --git a/source/src/rendermd3.cpp b/source/src/rendermd3.cpp index f7140225..f1478da8 100644 --- a/source/src/rendermd3.cpp +++ b/source/src/rendermd3.cpp @@ -1,559 +1,559 @@ -// code for loading, linking and rendering md3 models -// See http://www.draekko.org/documentation/md3format.html for informations about the md3 format - -#include "cube.h" - -const int FIRSTMD3 = 20; -enum { MDL_LOWER = 0, MDL_UPPER, MDL_HEAD }; - -enum { MDL_GUN_IDLE = 0, MDL_GUN_ATTACK, MDL_GUN_RELOAD, MDL_GUN_ATTACK2}; // attack2 is for grenade only - -#define MD3_DEFAULT_SCALE 0.08f - -struct vec2 -{ - float x, y; -}; - -struct md3tag -{ - char name[64]; - float pos[3]; - float rotation[3][3]; -}; - -struct md3vertex -{ - signed short vertex[3]; - uchar normal[2]; -}; - -struct md3triangle -{ - int vertexindices[3]; -}; - -struct md3animinfo -{ - int start; - int num; - bool loop; - int fps; -}; - -struct md3header -{ - char id[4]; - int version; - char name[64]; - int flags; - int numframes, numtags, nummeshes, numskins; - int ofs_frames, ofs_tags, ofs_meshes, ofs_eof; // offsets -}; - -struct md3meshheader -{ - char id[4]; - char name[64]; - int flags; - int numframes, numshaders, numvertices, numtriangles; - int ofs_triangles, ofs_shaders, ofs_uv, ofs_vertices, meshsize; // offsets -}; - -struct md3mesh -{ - char name[64]; - md3triangle *triangles; - vec *vertices; - vec2 *uv; - int numtriangles, numvertices; // numvertices == numUV - int tex; -}; - -struct md3model -{ - vector meshes; - vector animations; - md3state *animstate; - md3model **links; - md3tag *tags; - int numframes, numtags; - bool loaded; - vec scale; - bool mirrored; - void setanim(int anim); - void setanimstate(md3state &as); - bool link(md3model *link, char *tag); - bool load(char *path, bool mirror); - void render(); - void draw(float x, float y, float z, float yaw, float pitch, float rad, vec &light); - md3model(); - ~md3model(); -}; - -md3model::md3model() -{ - mirrored = false; - loaded = false; - scale.x = scale.y = scale.z = MD3_DEFAULT_SCALE; -// animstate = NULL; - animstate = new md3state(); - animstate->lastTime = lastmillis; -}; - -md3model::~md3model() -{ - loopv(meshes){ - if(meshes[i].vertices) delete [] meshes[i].vertices; - if(meshes[i].triangles) delete [] meshes[i].triangles; - if(meshes[i].uv) delete [] meshes[i].uv; - }; - if(links) free(links); - if(tags) delete [] tags; -}; - -void md3model::setanimstate(md3state &as) -{ - animstate->anim = as.anim; - animstate->frm = as.frm; - animstate->lastTime = as.lastTime; -}; - -void md3model::setanim(int anim) -{ - if(animstate->anim != anim) animstate->lastTime = lastmillis; - animstate->anim = anim; -}; - -bool md3model::link(md3model *link, char *tag) -{ - loopi(numtags) - if(strcmp(tags[i].name, tag) == 0) - { - links[i] = link; - return true; - }; - return false; -}; - -bool md3model::load(char *path, bool mirror=false) -{ - mirrored = mirror; - if(!path) return false; - FILE *f = fopen(path, "rb"); - if(!f) return false; - md3header header; - fread(&header, sizeof(md3header), 1, f); - if(header.id[0] != 'I' || header.id[1] != 'D' || header.id[2] != 'P' || header.id[3] != '3' || header.version != 15) // header check - { printf("corruped header in md3 model: %s", path); return false; }; - - tags = new md3tag[header.numframes * header.numtags]; - fseek(f, header.ofs_tags, SEEK_SET); - fread(tags, sizeof(md3tag), header.numframes * header.numtags, f); - numframes = header.numframes; - numtags = header.numtags; - - links = (md3model **) malloc(sizeof(md3model) * header.numtags); - loopi(header.numtags) links[i] = NULL; - - int mesh_offset = ftell(f); - - loopi(header.nummeshes) - { - md3mesh mesh; - md3meshheader mheader; - fseek(f, mesh_offset, SEEK_SET); - fread(&mheader, sizeof(md3meshheader), 1, f); - strn0cpy(mesh.name, mheader.name, 64); - - mesh.triangles = new md3triangle[mheader.numtriangles]; - fseek(f, mesh_offset + mheader.ofs_triangles, SEEK_SET); - fread(mesh.triangles, sizeof(md3triangle), mheader.numtriangles, f); // read the triangles - mesh.numtriangles = mheader.numtriangles; - - mesh.uv = new vec2[mheader.numvertices]; - fseek(f, mesh_offset + mheader.ofs_uv , SEEK_SET); - fread(mesh.uv, sizeof(vec2), mheader.numvertices, f); // read the UV data - - md3vertex *vertices = new md3vertex[mheader.numframes * mheader.numvertices]; - fseek(f, mesh_offset + mheader.ofs_vertices, SEEK_SET); - fread(vertices, sizeof(md3vertex), mheader.numframes * mheader.numvertices, f); // read the vertices - mesh.vertices = new vec[mheader.numframes * mheader.numvertices]; // transform to our own structure - loopj(mheader.numframes * mheader.numvertices) - { - mesh.vertices[j].x = vertices[j].vertex[0] / 64.0f * (mirrored ? -1 : 1); - mesh.vertices[j].y = vertices[j].vertex[1] / 64.0f; - mesh.vertices[j].z = vertices[j].vertex[2] / 64.0f; - }; - mesh.numvertices = mheader.numvertices; - - meshes.add(mesh); - mesh_offset += mheader.meshsize; - delete [] vertices; - }; - - loaded = true; - return true; -}; - -VAR(animdebug, 0, 0, 1); - -void md3model::render() -{ - if(!loaded || meshes.length() <= 0) return; - float t = 0.0f; - int nextfrm; - md3state *a = animstate; - - if(a && animations.length() > a->anim) - { - md3animinfo *info = &animations[a->anim]; - - int time = lastmillis - a->lastTime; - float speed = 1000.0f / (float)info->fps; - a->frm = (int) (time / speed); - - //printf("no loop %i > %i\n", a->frm, info->start + info->num-1); - if(!info->loop && a->frm >= info->num -1) - { - if(animdebug) printf("no loop %i > %i\n", a->frm, info->start + info->num-1); - a->frm = info->start + info->num-1; - nextfrm = a->frm; - } - else - { - t = (time-a->frm*speed)/speed; - a->frm = info->start+(a->frm % info->num); - //if(a->frm>=info->start+info->num-1) - nextfrm = a->frm + 1; - //if(animdebug) printf("%i >= %i + %i", nextfrm, a->frm, info->num); - if(nextfrm>=info->start+info->num) nextfrm=a->frm; - }; - - //if(a->frm < info->start || a->frm > info->end) // we switched to a new animation, jump to the start - // a->frm = info->start; - //t = (float) (lastmillis - a->lastTime) / (1000.0f / (float) info->fps ); // t has a value from 0.0f to 1.0f - used for interpolation - - if(animdebug) printf("lm:%i lt:%i fps:%i\t(%i %i)\n", lastmillis, a->lastTime, info->fps, info->start, info->num); - - - /*if(t >= 1.0f) // jump to the next keyframe - { - a->frm++; - a->lastTime = lastmillis; - //t = 0.0f; - t = t % 1; - //printf("next keyframe\n"); - }; - if(a->frm >= info->end) - { - //printf("end\n"); - if(info->loop) - a->frm = info->start; - else - { - a->frm = info->end; - t = 0.0f; // stops the animation - }; - };*/ - } - else - { - if(animdebug) printf("overflow: curframe %i > maxframe %i\n", a->anim, animations.length()); - - if(!a) a = new md3state(); - a->frm = 0; - nextfrm = a->frm + 1; - } - - if(animdebug) printf("interp. from frame %i to %i, %f percent done\n", a->frm, nextfrm, t*100.0f); - - #define interpolate(p1,p2) ((p1) + t * ((p2) - (p1))) - - loopv(meshes) - { - md3mesh *mesh = &meshes[i]; - - glBindTexture(GL_TEXTURE_2D, FIRSTMD3 + mesh->tex); - - glBegin(GL_TRIANGLES); - loopj(mesh->numtriangles) // triangles - { - loopk(3) // vertices - { - int index = mesh->triangles[j].vertexindices[k]; - vec *point1 = &mesh->vertices[index + a->frm * mesh->numvertices]; - vec *point2 = &mesh->vertices[index + nextfrm * mesh->numvertices]; - - if(mesh->uv) - glTexCoord2f(mesh->uv[index].x, mesh->uv[index].y); - - glVertex3f( interpolate(point1->x, point2->x), - interpolate(point1->y, point2->y), - interpolate(point1->z, point2->z)); - }; - }; - glEnd(); - }; - - - loopi(numtags) // render the linked models - interpolate rotation and position of the 'link-tags' - { - md3model *link = links[i]; - if(!link) continue; - GLfloat matrix[16] = {0}; // fixme: un-obfuscate it! - loopj(3) matrix[j] = interpolate(tags[a->frm * numtags + i].rotation[0][j], tags[nextfrm * numtags + i].rotation[0][j]); // rotation - loopj(3) matrix[4 + j] = interpolate(tags[a->frm * numtags + i].rotation[1][j], tags[nextfrm * numtags + i].rotation[1][j]); - loopj(3) matrix[8 + j] = interpolate(tags[a->frm * numtags + i].rotation[2][j], tags[nextfrm * numtags + i].rotation[2][j]); - loopj(3) matrix[12 + j] = interpolate(tags[a->frm * numtags + i].pos[j] , tags[nextfrm * numtags + i].pos[j]); // position - matrix[15] = 1.0f; - glPushMatrix(); - glMultMatrixf(matrix); - link->render(); - glPopMatrix(); - }; -}; - -void md3model::draw(float x, float y, float z, float yaw, float pitch, float rad, vec &light) -{ - glPushMatrix(); - - glColor3fv((float *)&light); - - glTranslatef(x, y, z); - - glRotatef(yaw+180, 0, -1, 0); - glRotatef(pitch, 0, 0, 1); - - glRotatef(-90, 0, 1, 0); - glRotatef(-90, 1, 0, 0); - - glScalef( scale.x, scale.y, scale.z); - - if(mirrored) glCullFace(GL_BACK); - - render(); - - if(mirrored) glCullFace(GL_FRONT); - - glPopMatrix(); -}; - -void md3setanim(dynent *d, int anim) -{ - if(anim <= BOTH_DEAD3) // assign to both, legs and torso - { - d->animstate[MDL_LOWER].anim = anim; - d->animstate[MDL_UPPER].anim = anim; - } - else if(anim <= TORSO_STAND2) - d->animstate[MDL_UPPER].anim = anim; - else - d->animstate[MDL_LOWER].anim = anim - 7; -}; - -vector models; -vector tmp_animations; -int firstweapon = -1; -int numskins = 0; -char basedir[_MAXDEFSTR]; // necessary for relative path's - -void md3skin(char *objname, char *skin) // called by the {lower|upper|head}.cfg (originally .skin in Q3A) -{ - md3model *mdl = models.last(); - loopv(mdl->meshes) - { - md3mesh *mesh = &mdl->meshes[i]; - if(strcmp(mesh->name, objname) == 0) - { - int xs, ys; - sprintf_sd(path)("%s/%s", basedir, skin); // 'skin' is a relative url - bool highqual = strstr(basedir, "weapons") != NULL; - installtex(FIRSTMD3 + numskins, path, xs, ys, true, highqual); - mesh->tex = numskins; - numskins++; - }; - }; -}; - -COMMAND(md3skin, ARG_2STR); - -void md3animation(char *first, char *nums, char *loopings, char *fps) /* configurable animations */ -{ - md3animinfo &a = tmp_animations.add(); - a.start = atoi(first); - int n = atoi(nums); - if(n<=0) n = 1; - //a.end = a.start + n - 1; - a.num = n; - (atoi(loopings) > 0) ? a.loop = true : a.loop = false; - a.fps = atoi(fps); -}; - -COMMAND(md3animation, ARG_5STR); - -void loadweapons() -{ - firstweapon = models.length(); - loopi(NUMGUNS+1) - { - bool akimbo = i==NUMGUNS; - sprintf(basedir, "packages/models/weapons/%s", akimbo ? hudgunnames[GUN_PISTOL] : hudgunnames[i]); - md3model *mdl = new md3model(); - models.add(mdl); - sprintf_sd(mdl_path)("%s/tris_high.md3", basedir); - sprintf_sd(cfg_path)("%s/default.skin", basedir); - sprintf_sd(modelcfg_path) ("%s/animations.cfg", basedir); - if(!mdl->load(mdl_path, akimbo)) printf("could not load: %s\n", mdl_path); - exec(cfg_path); - tmp_animations.setsize(0); - exec(modelcfg_path); - loopj(tmp_animations.length()) mdl->animations.add(tmp_animations[j]); - } -}; - -VAR(swayspeeddiv, 1, 125, 1000); -VAR(swaymovediv, 1, 200, 1000); - -VAR(swayupspeeddiv, 1, 125, 1000); -VAR(swayupmovediv, 1, 200, 1000); - -VAR(x, 0, 0, 1000); -VAR(y, 0, 0, 1000); -VAR(z, 0, 0, 1000); - -struct weaponmove -{ - float k_rot, kick; - vec pos; - int anim; - - weaponmove(vec base, int basetime) - { - bool akimbo = player1->akimbo && player1->gunselect==GUN_PISTOL; - bool throwingnade = player1->gunselect==GUN_GRENADE && player1->thrownademillis; - int timediff = throwingnade ? (lastmillis-player1->thrownademillis) : lastmillis-basetime; - int animtime = attackdelay(player1->gunselect); - int rtime = reloadtime(player1->gunselect); - kick = k_rot = 0.0f; - pos = player1->o; - anim = MDL_GUN_IDLE; - bool attacking = player1->lastaction && player1->lastattackgun==player1->gunselect /*&& lastmillis-player1->lastactionlastmillis) // akimbo gun queued for reloading - { - - } - else if(player1->reloading) - { - anim = MDL_GUN_RELOAD; - float percent_done = (float)(timediff)*100.0f/rtime; - if(percent_done >= 100) percent_done = 100; - k_rot = -(sin((float)(percent_done*2/100.0f*90.0f)*PI/180.0f)*90); - } - else if(throwingnade && timediff>animtime-rtime/2 && timediffgunselect==player1->lastattackgun) - { - percent_done = timediff*100.0f/(float)animtime; - if(percent_done > 100.0f) percent_done = 100.0f; - // f(x) = -sin(x-1.5)^3 - kick = -sin(pow((1.5f/100.0f*percent_done)-1.5f,3)); - }; - - if(attacking && timediffgunselect!=GUN_GRENADE && player1->gunselect!=GUN_KNIFE) - { - k_rot = kick_rot(player1->gunselect)*kick; - k_back = kick_back(player1->gunselect)*kick/10; - }; - - int swayt = (lastmillis+1)/10 % 100; - float swayspeed = (float) (sin((float)lastmillis/swayspeeddiv))/(swaymovediv/10.0f); - float swayupspeed = (float) (sin((float)lastmillis/swayupspeeddiv-90))/(swayupmovediv/10.0f); - - #define g0(x) ((x) < 0.0f ? -(x) : (x)) - float plspeed = min(1.0f, sqrt(g0(player1->vel.x*player1->vel.x) + g0(player1->vel.y*player1->vel.y))); - - swayspeed *= plspeed/2; - swayupspeed *= plspeed/2; - - float tmp = sway.x; - sway.x = sway.y; - sway.y = -tmp; - - if(swayupspeed<0.0f)swayupspeed = -swayupspeed; // sway a semicirle only - sway.z = 1.0f; - - sway.x *= swayspeed; - sway.y *= swayspeed; - sway.z *= swayupspeed; - - pos.x = player1->o.x-base.x*k_back+sway.x; - pos.y = player1->o.y-base.y*k_back+sway.y; - pos.z = player1->o.z-base.z*k_back+sway.z; - }; - }; -}; - -void rendermd3gun() -{ - if(firstweapon >= 0) - { - int ix = (int)player1->o.x; - int iy = (int)player1->o.y; - vec light = { 1.0f, 1.0f, 1.0f }; - - if(!OUTBORD(ix, iy)) - { - sqr *s = S(ix,iy); - float ll = 256.0f; // 0.96f; - float of = 0.0f; // 0.1f; - light.x = s->r/ll+of; - light.y = s->g/ll+of; - light.z = s->b/ll+of; - }; - - vdist(dist, unitv, player1->o, worldpos); - vdiv(unitv, dist); - - md3model *weapon = models[firstweapon + player1->gunselect]; - md3model *akimbo = player1->akimbo && player1->gunselect==GUN_PISTOL ? models[firstweapon+NUMGUNS] : NULL; - - weaponmove *w1, *w2; - - if(akimbo) - { - w1 = new weaponmove(unitv, akimbolastaction[0]); - w2 = new weaponmove(unitv, akimbolastaction[1]); - akimbo->setanim(w2->anim); - } - else w1 = new weaponmove(unitv, player1->lastaction); - - if(!w1) return; - weapon->setanim(w1->anim); - weapon->draw( w1->pos.x, w1->pos.z, w1->pos.y, player1->yaw + 90, player1->pitch+w1->k_rot, 1.0f, light); - - if(akimbo && w2) akimbo->draw( w2->pos.x, w2->pos.z, w2->pos.y, player1->yaw + 90, player1->pitch+w2->k_rot, 1.0f, light); - }; -}; - - +// code for loading, linking and rendering md3 models +// See http://www.draekko.org/documentation/md3format.html for informations about the md3 format + +#include "cube.h" + +const int FIRSTMD3 = 20; +enum { MDL_LOWER = 0, MDL_UPPER, MDL_HEAD }; + +enum { MDL_GUN_IDLE = 0, MDL_GUN_ATTACK, MDL_GUN_RELOAD, MDL_GUN_ATTACK2}; // attack2 is for grenade only + +#define MD3_DEFAULT_SCALE 0.08f + +struct vec2 +{ + float x, y; +}; + +struct md3tag +{ + char name[64]; + float pos[3]; + float rotation[3][3]; +}; + +struct md3vertex +{ + signed short vertex[3]; + uchar normal[2]; +}; + +struct md3triangle +{ + int vertexindices[3]; +}; + +struct md3animinfo +{ + int start; + int num; + bool loop; + int fps; +}; + +struct md3header +{ + char id[4]; + int version; + char name[64]; + int flags; + int numframes, numtags, nummeshes, numskins; + int ofs_frames, ofs_tags, ofs_meshes, ofs_eof; // offsets +}; + +struct md3meshheader +{ + char id[4]; + char name[64]; + int flags; + int numframes, numshaders, numvertices, numtriangles; + int ofs_triangles, ofs_shaders, ofs_uv, ofs_vertices, meshsize; // offsets +}; + +struct md3mesh +{ + char name[64]; + md3triangle *triangles; + vec *vertices; + vec2 *uv; + int numtriangles, numvertices; // numvertices == numUV + int tex; +}; + +struct md3model +{ + vector meshes; + vector animations; + md3state *animstate; + md3model **links; + md3tag *tags; + int numframes, numtags; + bool loaded; + vec scale; + bool mirrored; + void setanim(int anim); + void setanimstate(md3state &as); + bool link(md3model *link, char *tag); + bool load(char *path, bool mirror); + void render(); + void draw(float x, float y, float z, float yaw, float pitch, float rad, vec &light); + md3model(); + ~md3model(); +}; + +md3model::md3model() +{ + mirrored = false; + loaded = false; + scale.x = scale.y = scale.z = MD3_DEFAULT_SCALE; +// animstate = NULL; + animstate = new md3state(); + animstate->lastTime = lastmillis; +}; + +md3model::~md3model() +{ + loopv(meshes){ + if(meshes[i].vertices) delete [] meshes[i].vertices; + if(meshes[i].triangles) delete [] meshes[i].triangles; + if(meshes[i].uv) delete [] meshes[i].uv; + }; + if(links) free(links); + if(tags) delete [] tags; +}; + +void md3model::setanimstate(md3state &as) +{ + animstate->anim = as.anim; + animstate->frm = as.frm; + animstate->lastTime = as.lastTime; +}; + +void md3model::setanim(int anim) +{ + if(animstate->anim != anim) animstate->lastTime = lastmillis; + animstate->anim = anim; +}; + +bool md3model::link(md3model *link, char *tag) +{ + loopi(numtags) + if(strcmp(tags[i].name, tag) == 0) + { + links[i] = link; + return true; + }; + return false; +}; + +bool md3model::load(char *path, bool mirror=false) +{ + mirrored = mirror; + if(!path) return false; + FILE *f = fopen(path, "rb"); + if(!f) return false; + md3header header; + fread(&header, sizeof(md3header), 1, f); + if(header.id[0] != 'I' || header.id[1] != 'D' || header.id[2] != 'P' || header.id[3] != '3' || header.version != 15) // header check + { printf("corruped header in md3 model: %s", path); return false; }; + + tags = new md3tag[header.numframes * header.numtags]; + fseek(f, header.ofs_tags, SEEK_SET); + fread(tags, sizeof(md3tag), header.numframes * header.numtags, f); + numframes = header.numframes; + numtags = header.numtags; + + links = (md3model **) malloc(sizeof(md3model) * header.numtags); + loopi(header.numtags) links[i] = NULL; + + int mesh_offset = ftell(f); + + loopi(header.nummeshes) + { + md3mesh mesh; + md3meshheader mheader; + fseek(f, mesh_offset, SEEK_SET); + fread(&mheader, sizeof(md3meshheader), 1, f); + strn0cpy(mesh.name, mheader.name, 64); + + mesh.triangles = new md3triangle[mheader.numtriangles]; + fseek(f, mesh_offset + mheader.ofs_triangles, SEEK_SET); + fread(mesh.triangles, sizeof(md3triangle), mheader.numtriangles, f); // read the triangles + mesh.numtriangles = mheader.numtriangles; + + mesh.uv = new vec2[mheader.numvertices]; + fseek(f, mesh_offset + mheader.ofs_uv , SEEK_SET); + fread(mesh.uv, sizeof(vec2), mheader.numvertices, f); // read the UV data + + md3vertex *vertices = new md3vertex[mheader.numframes * mheader.numvertices]; + fseek(f, mesh_offset + mheader.ofs_vertices, SEEK_SET); + fread(vertices, sizeof(md3vertex), mheader.numframes * mheader.numvertices, f); // read the vertices + mesh.vertices = new vec[mheader.numframes * mheader.numvertices]; // transform to our own structure + loopj(mheader.numframes * mheader.numvertices) + { + mesh.vertices[j].x = vertices[j].vertex[0] / 64.0f * (mirrored ? -1 : 1); + mesh.vertices[j].y = vertices[j].vertex[1] / 64.0f; + mesh.vertices[j].z = vertices[j].vertex[2] / 64.0f; + }; + mesh.numvertices = mheader.numvertices; + + meshes.add(mesh); + mesh_offset += mheader.meshsize; + delete [] vertices; + }; + + loaded = true; + return true; +}; + +VAR(animdebug, 0, 0, 1); + +void md3model::render() +{ + if(!loaded || meshes.length() <= 0) return; + float t = 0.0f; + int nextfrm; + md3state *a = animstate; + + if(a && animations.length() > a->anim) + { + md3animinfo *info = &animations[a->anim]; + + int time = lastmillis - a->lastTime; + float speed = 1000.0f / (float)info->fps; + a->frm = (int) (time / speed); + + //printf("no loop %i > %i\n", a->frm, info->start + info->num-1); + if(!info->loop && a->frm >= info->num -1) + { + if(animdebug) printf("no loop %i > %i\n", a->frm, info->start + info->num-1); + a->frm = info->start + info->num-1; + nextfrm = a->frm; + } + else + { + t = (time-a->frm*speed)/speed; + a->frm = info->start+(a->frm % info->num); + //if(a->frm>=info->start+info->num-1) + nextfrm = a->frm + 1; + //if(animdebug) printf("%i >= %i + %i", nextfrm, a->frm, info->num); + if(nextfrm>=info->start+info->num) nextfrm=a->frm; + }; + + //if(a->frm < info->start || a->frm > info->end) // we switched to a new animation, jump to the start + // a->frm = info->start; + //t = (float) (lastmillis - a->lastTime) / (1000.0f / (float) info->fps ); // t has a value from 0.0f to 1.0f - used for interpolation + + if(animdebug) printf("lm:%i lt:%i fps:%i\t(%i %i)\n", lastmillis, a->lastTime, info->fps, info->start, info->num); + + + /*if(t >= 1.0f) // jump to the next keyframe + { + a->frm++; + a->lastTime = lastmillis; + //t = 0.0f; + t = t % 1; + //printf("next keyframe\n"); + }; + if(a->frm >= info->end) + { + //printf("end\n"); + if(info->loop) + a->frm = info->start; + else + { + a->frm = info->end; + t = 0.0f; // stops the animation + }; + };*/ + } + else + { + if(animdebug) printf("overflow: curframe %i > maxframe %i\n", a->anim, animations.length()); + + if(!a) a = new md3state(); + a->frm = 0; + nextfrm = a->frm + 1; + } + + if(animdebug) printf("interp. from frame %i to %i, %f percent done\n", a->frm, nextfrm, t*100.0f); + + #define interpolate(p1,p2) ((p1) + t * ((p2) - (p1))) + + loopv(meshes) + { + md3mesh *mesh = &meshes[i]; + + glBindTexture(GL_TEXTURE_2D, FIRSTMD3 + mesh->tex); + + glBegin(GL_TRIANGLES); + loopj(mesh->numtriangles) // triangles + { + loopk(3) // vertices + { + int index = mesh->triangles[j].vertexindices[k]; + vec *point1 = &mesh->vertices[index + a->frm * mesh->numvertices]; + vec *point2 = &mesh->vertices[index + nextfrm * mesh->numvertices]; + + if(mesh->uv) + glTexCoord2f(mesh->uv[index].x, mesh->uv[index].y); + + glVertex3f( interpolate(point1->x, point2->x), + interpolate(point1->y, point2->y), + interpolate(point1->z, point2->z)); + }; + }; + glEnd(); + }; + + + loopi(numtags) // render the linked models - interpolate rotation and position of the 'link-tags' + { + md3model *link = links[i]; + if(!link) continue; + GLfloat matrix[16] = {0}; // fixme: un-obfuscate it! + loopj(3) matrix[j] = interpolate(tags[a->frm * numtags + i].rotation[0][j], tags[nextfrm * numtags + i].rotation[0][j]); // rotation + loopj(3) matrix[4 + j] = interpolate(tags[a->frm * numtags + i].rotation[1][j], tags[nextfrm * numtags + i].rotation[1][j]); + loopj(3) matrix[8 + j] = interpolate(tags[a->frm * numtags + i].rotation[2][j], tags[nextfrm * numtags + i].rotation[2][j]); + loopj(3) matrix[12 + j] = interpolate(tags[a->frm * numtags + i].pos[j] , tags[nextfrm * numtags + i].pos[j]); // position + matrix[15] = 1.0f; + glPushMatrix(); + glMultMatrixf(matrix); + link->render(); + glPopMatrix(); + }; +}; + +void md3model::draw(float x, float y, float z, float yaw, float pitch, float rad, vec &light) +{ + glPushMatrix(); + + glColor3fv((float *)&light); + + glTranslatef(x, y, z); + + glRotatef(yaw+180, 0, -1, 0); + glRotatef(pitch, 0, 0, 1); + + glRotatef(-90, 0, 1, 0); + glRotatef(-90, 1, 0, 0); + + glScalef( scale.x, scale.y, scale.z); + + if(mirrored) glCullFace(GL_BACK); + + render(); + + if(mirrored) glCullFace(GL_FRONT); + + glPopMatrix(); +}; + +void md3setanim(dynent *d, int anim) +{ + if(anim <= BOTH_DEAD3) // assign to both, legs and torso + { + d->animstate[MDL_LOWER].anim = anim; + d->animstate[MDL_UPPER].anim = anim; + } + else if(anim <= TORSO_STAND2) + d->animstate[MDL_UPPER].anim = anim; + else + d->animstate[MDL_LOWER].anim = anim - 7; +}; + +vector models; +vector tmp_animations; +int firstweapon = -1; +int numskins = 0; +char basedir[_MAXDEFSTR]; // necessary for relative path's + +void md3skin(char *objname, char *skin) // called by the {lower|upper|head}.cfg (originally .skin in Q3A) +{ + md3model *mdl = models.last(); + loopv(mdl->meshes) + { + md3mesh *mesh = &mdl->meshes[i]; + if(strcmp(mesh->name, objname) == 0) + { + int xs, ys; + sprintf_sd(path)("%s/%s", basedir, skin); // 'skin' is a relative url + bool highqual = strstr(basedir, "weapons") != NULL; + installtex(FIRSTMD3 + numskins, path, xs, ys, true, highqual); + mesh->tex = numskins; + numskins++; + }; + }; +}; + +COMMAND(md3skin, ARG_2STR); + +void md3animation(char *first, char *nums, char *loopings, char *fps) /* configurable animations */ +{ + md3animinfo &a = tmp_animations.add(); + a.start = atoi(first); + int n = atoi(nums); + if(n<=0) n = 1; + //a.end = a.start + n - 1; + a.num = n; + (atoi(loopings) > 0) ? a.loop = true : a.loop = false; + a.fps = atoi(fps); +}; + +COMMAND(md3animation, ARG_5STR); + +void loadweapons() +{ + firstweapon = models.length(); + loopi(NUMGUNS+1) + { + bool akimbo = i==NUMGUNS; + sprintf(basedir, "packages/models/weapons/%s", akimbo ? hudgunnames[GUN_PISTOL] : hudgunnames[i]); + md3model *mdl = new md3model(); + models.add(mdl); + sprintf_sd(mdl_path)("%s/tris_high.md3", basedir); + sprintf_sd(cfg_path)("%s/default.skin", basedir); + sprintf_sd(modelcfg_path) ("%s/animations.cfg", basedir); + if(!mdl->load(mdl_path, akimbo)) printf("could not load: %s\n", mdl_path); + exec(cfg_path); + tmp_animations.setsize(0); + exec(modelcfg_path); + loopj(tmp_animations.length()) mdl->animations.add(tmp_animations[j]); + } +}; + +VAR(swayspeeddiv, 1, 125, 1000); +VAR(swaymovediv, 1, 200, 1000); + +VAR(swayupspeeddiv, 1, 125, 1000); +VAR(swayupmovediv, 1, 200, 1000); + +VAR(x, 0, 0, 1000); +VAR(y, 0, 0, 1000); +VAR(z, 0, 0, 1000); + +struct weaponmove +{ + float k_rot, kick; + vec pos; + int anim; + + weaponmove(vec base, int basetime) + { + bool akimbo = player1->akimbo && player1->gunselect==GUN_PISTOL; + bool throwingnade = player1->gunselect==GUN_GRENADE && player1->thrownademillis; + int timediff = throwingnade ? (lastmillis-player1->thrownademillis) : lastmillis-basetime; + int animtime = attackdelay(player1->gunselect); + int rtime = reloadtime(player1->gunselect); + kick = k_rot = 0.0f; + pos = player1->o; + anim = MDL_GUN_IDLE; + bool attacking = player1->lastaction && player1->lastattackgun==player1->gunselect /*&& lastmillis-player1->lastactionlastmillis) // akimbo gun queued for reloading + { + + } + else if(player1->reloading) + { + anim = MDL_GUN_RELOAD; + float percent_done = (float)(timediff)*100.0f/rtime; + if(percent_done >= 100) percent_done = 100; + k_rot = -(sin((float)(percent_done*2/100.0f*90.0f)*PI/180.0f)*90); + } + else if(throwingnade && timediff>animtime-rtime/2 && timediffgunselect==player1->lastattackgun) + { + percent_done = timediff*100.0f/(float)animtime; + if(percent_done > 100.0f) percent_done = 100.0f; + // f(x) = -sin(x-1.5)^3 + kick = -sin(pow((1.5f/100.0f*percent_done)-1.5f,3)); + }; + + if(attacking && timediffgunselect!=GUN_GRENADE && player1->gunselect!=GUN_KNIFE) + { + k_rot = kick_rot(player1->gunselect)*kick; + k_back = kick_back(player1->gunselect)*kick/10; + }; + + int swayt = (lastmillis+1)/10 % 100; + float swayspeed = (float) (sin((float)lastmillis/swayspeeddiv))/(swaymovediv/10.0f); + float swayupspeed = (float) (sin((float)lastmillis/swayupspeeddiv-90))/(swayupmovediv/10.0f); + + #define g0(x) ((x) < 0.0f ? -(x) : (x)) + float plspeed = min(1.0f, sqrt(g0(player1->vel.x*player1->vel.x) + g0(player1->vel.y*player1->vel.y))); + + swayspeed *= plspeed/2; + swayupspeed *= plspeed/2; + + float tmp = sway.x; + sway.x = sway.y; + sway.y = -tmp; + + if(swayupspeed<0.0f)swayupspeed = -swayupspeed; // sway a semicirle only + sway.z = 1.0f; + + sway.x *= swayspeed; + sway.y *= swayspeed; + sway.z *= swayupspeed; + + pos.x = player1->o.x-base.x*k_back+sway.x; + pos.y = player1->o.y-base.y*k_back+sway.y; + pos.z = player1->o.z-base.z*k_back+sway.z; + }; + }; +}; + +void rendermd3gun() +{ + if(firstweapon >= 0) + { + int ix = (int)player1->o.x; + int iy = (int)player1->o.y; + vec light = { 1.0f, 1.0f, 1.0f }; + + if(!OUTBORD(ix, iy)) + { + sqr *s = S(ix,iy); + float ll = 256.0f; // 0.96f; + float of = 0.0f; // 0.1f; + light.x = s->r/ll+of; + light.y = s->g/ll+of; + light.z = s->b/ll+of; + }; + + vdist(dist, unitv, player1->o, worldpos); + vdiv(unitv, dist); + + md3model *weapon = models[firstweapon + player1->gunselect]; + md3model *akimbo = player1->akimbo && player1->gunselect==GUN_PISTOL ? models[firstweapon+NUMGUNS] : NULL; + + weaponmove *w1, *w2; + + if(akimbo) + { + w1 = new weaponmove(unitv, akimbolastaction[0]); + w2 = new weaponmove(unitv, akimbolastaction[1]); + akimbo->setanim(w2->anim); + } + else w1 = new weaponmove(unitv, player1->lastaction); + + if(!w1) return; + weapon->setanim(w1->anim); + weapon->draw( w1->pos.x, w1->pos.z, w1->pos.y, player1->yaw + 90, player1->pitch+w1->k_rot, 1.0f, light); + + if(akimbo && w2) akimbo->draw( w2->pos.x, w2->pos.z, w2->pos.y, player1->yaw + 90, player1->pitch+w2->k_rot, 1.0f, light); + }; +}; + + diff --git a/source/src/renderparticles.cpp b/source/src/renderparticles.cpp index 1601e7d5..04609f52 100644 --- a/source/src/renderparticles.cpp +++ b/source/src/renderparticles.cpp @@ -1,146 +1,144 @@ -// renderparticles.cpp - -#include "cube.h" - -const int MAXPARTICLES = 10500; -const int NUMPARTCUTOFF = 20; -struct particle { vec o, d; int fade, type; int millis; particle *next; int tex; }; // EDIT: AH -particle particles[MAXPARTICLES], *parlist = NULL, *parempty = NULL; -bool parinit = false; - -VAR(maxparticles, 100, 2000, MAXPARTICLES-500); - -void newparticle(vec &o, vec &d, int fade, int type, int tex = -1) -{ - if(!parinit) - { - loopi(MAXPARTICLES) - { - particles[i].next = parempty; - parempty = &particles[i]; - }; - parinit = true; - }; - if(parempty) - { - particle *p = parempty; - parempty = p->next; - p->o = o; - p->d = d; - p->fade = fade; - p->type = type; - p->millis = lastmillis; - p->next = parlist; - p->tex = tex; - parlist = p; - }; -}; - -VAR(demotracking, 0, 0, 1); -VAR(particlesize, 20, 100, 500); - -vec right, up; - -void setorient(vec &r, vec &u) { right = r; up = u; }; - -void render_particles(int time) -{ - if(demoplayback && demotracking) - { - vec nom = { 0, 0, 0 }; - newparticle(player1->o, nom, 100000000, 8); - }; - - glDepthMask(GL_FALSE); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); - glDisable(GL_FOG); - - struct parttype { float r, g, b; int gr, tex; float sz; } parttypes[] = - { - { 0.7f, 0.6f, 0.3f, 2, 3, 0.06f }, // yellow: sparks - { 0.5f, 0.5f, 0.5f, 20, 7, 0.15f }, // grey: small smoke - { 0.2f, 0.2f, 1.0f, 20, 3, 0.08f }, // blue: edit mode entities - { 1.0f, 0.1f, 0.1f, 1, 7, 0.06f }, // red: blood spats - { 1.0f, 0.8f, 0.8f, 20, 6, 1.2f }, // yellow: fireball1 - { 0.5f, 0.5f, 0.5f, 20, 7, 0.6f }, // grey: big smoke - { 1.0f, 1.0f, 1.0f, 20, 8, 1.2f }, // blue: fireball2 - { 1.0f, 1.0f, 1.0f, 20, 9, 1.2f }, // green: fireball3 - { 1.0f, 0.1f, 0.1f, 0, 7, 0.2f }, // red: demotrack - { 1.0f, 1.0f, 1.0f, 2, 3, 0.06f }, // - }; - - int numrender = 0; - - for(particle *p, **pp = &parlist; p = *pp;) - { - if(p->type==9 && p->tex!=-1) parttypes[9].tex = p->tex; // hack, AH - parttype *pt = &parttypes[p->type]; - - glBindTexture(GL_TEXTURE_2D, pt->tex); - glBegin(GL_QUADS); - - glColor3d(pt->r, pt->g, pt->b); - float sz = pt->sz*particlesize/100.0f; - // perf varray? - glTexCoord2f(0.0, 1.0); glVertex3d(p->o.x+(-right.x+up.x)*sz, p->o.z+(-right.y+up.y)*sz, p->o.y+(-right.z+up.z)*sz); - glTexCoord2f(1.0, 1.0); glVertex3d(p->o.x+( right.x+up.x)*sz, p->o.z+( right.y+up.y)*sz, p->o.y+( right.z+up.z)*sz); - glTexCoord2f(1.0, 0.0); glVertex3d(p->o.x+( right.x-up.x)*sz, p->o.z+( right.y-up.y)*sz, p->o.y+( right.z-up.z)*sz); - glTexCoord2f(0.0, 0.0); glVertex3d(p->o.x+(-right.x-up.x)*sz, p->o.z+(-right.y-up.y)*sz, p->o.y+(-right.z-up.z)*sz); - glEnd(); - xtraverts += 4; - - if(numrender++>maxparticles || (p->fade -= time)<0) - { - *pp = p->next; - p->next = parempty; - parempty = p; - } - else - { - if(pt->gr) p->o.z -= ((lastmillis-p->millis)/3.0f)*curtime/(pt->gr*10000); - vec a = p->d; - vmul(a,time); - vdiv(a,20000.0f); - vadd(p->o, a); - pp = &p->next; - }; - }; - - glEnable(GL_FOG); - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); -}; - -void particle_splash(int type, int num, int fade, vec &p) -{ - loopi(num) - { - const int radius = type==5 ? 50 : 150; - int x, y, z; - do - { - x = rnd(radius*2)-radius; - y = rnd(radius*2)-radius; - z = rnd(radius*2)-radius; - } - while(x*x+y*y+z*z>radius*radius); - vec d = { (float)x, (float)y, (float)z }; - newparticle(p, d, rnd(fade*3), type); - }; -}; - -void particle_trail(int type, int fade, vec &s, vec &e) -{ - vdist(d, v, s, e); - vdiv(v, d*2+0.1f); - vec p = s; - loopi((int)d*2) - { - vadd(p, v); - vec d = { float(rnd(11)-5), float(rnd(11)-5), float(rnd(11)-5) }; - newparticle(p, d, rnd(fade)+fade, type); - }; -}; - - +// renderparticles.cpp + +#include "cube.h" + +const int MAXPARTICLES = 10500; +const int NUMPARTCUTOFF = 20; +struct particle { vec o, d; int fade, type; int millis; particle *next; int tex; }; // EDIT: AH +particle particles[MAXPARTICLES], *parlist = NULL, *parempty = NULL; +bool parinit = false; + +VAR(maxparticles, 100, 2000, MAXPARTICLES-500); + +void newparticle(vec &o, vec &d, int fade, int type, int tex = -1) +{ + if(!parinit) + { + loopi(MAXPARTICLES) + { + particles[i].next = parempty; + parempty = &particles[i]; + }; + parinit = true; + }; + if(parempty) + { + particle *p = parempty; + parempty = p->next; + p->o = o; + p->d = d; + p->fade = fade; + p->type = type; + p->millis = lastmillis; + p->next = parlist; + p->tex = tex; + parlist = p; + }; +}; + +VAR(demotracking, 0, 0, 1); +VAR(particlesize, 20, 100, 500); + +vec right, up; + +void setorient(vec &r, vec &u) { right = r; up = u; }; + +void render_particles(int time) +{ + if(demoplayback && demotracking) + { + vec nom = { 0, 0, 0 }; + newparticle(player1->o, nom, 100000000, 8); + }; + + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_SRC_ALPHA); + glDisable(GL_FOG); + + struct parttype { float r, g, b; int gr, tex; float sz; } parttypes[] = + { + { 0.2f, 0.2f, 0.2f, 2, 3, 0.06f }, // yellow: sparks + { 0.5f, 0.5f, 0.5f, 20, 7, 0.15f }, // grey: small smoke + { 0.2f, 0.2f, 1.0f, 20, 3, 0.08f }, // blue: edit mode entities + { 1.0f, 0.1f, 0.1f, 1, 7, 0.06f }, // red: blood spats + { 1.0f, 0.8f, 0.8f, 20, 6, 1.2f }, // yellow: fireball1 + { 0.5f, 0.5f, 0.5f, 20, 7, 0.6f }, // grey: big smoke + { 1.0f, 1.0f, 1.0f, 20, 8, 1.2f }, // blue: fireball2 + { 1.0f, 1.0f, 1.0f, 20, 9, 1.2f }, // green: fireball3 + { 1.0f, 0.1f, 0.1f, 0, 7, 0.2f }, // red: demotrack + { 1.0f, 1.0f, 1.0f, 2, 3, 0.06f }, // + }; + + int numrender = 0; + + for(particle *p, **pp = &parlist; p = *pp;) + { + if(p->type==9 && p->tex!=-1) parttypes[9].tex = p->tex; // hack, AH + parttype *pt = &parttypes[p->type]; + + glBindTexture(GL_TEXTURE_2D, pt->tex); + glBegin(GL_QUADS); + + glColor3d(pt->r, pt->g, pt->b); + float sz = pt->sz*particlesize/100.0f; + // perf varray? + glTexCoord2f(0.0, 1.0); glVertex3d(p->o.x+(-right.x+up.x)*sz, p->o.z+(-right.y+up.y)*sz, p->o.y+(-right.z+up.z)*sz); + glTexCoord2f(1.0, 1.0); glVertex3d(p->o.x+( right.x+up.x)*sz, p->o.z+( right.y+up.y)*sz, p->o.y+( right.z+up.z)*sz); + glTexCoord2f(1.0, 0.0); glVertex3d(p->o.x+( right.x-up.x)*sz, p->o.z+( right.y-up.y)*sz, p->o.y+( right.z-up.z)*sz); + glTexCoord2f(0.0, 0.0); glVertex3d(p->o.x+(-right.x-up.x)*sz, p->o.z+(-right.y-up.y)*sz, p->o.y+(-right.z-up.z)*sz); + glEnd(); + xtraverts += 4; + + if(numrender++>maxparticles || (p->fade -= time)<0) + { + *pp = p->next; + p->next = parempty; + parempty = p; + } + else + { + if(pt->gr) p->o.z -= ((lastmillis-p->millis)/3.0f)*curtime/(pt->gr*10000); + vec a = p->d; + vmul(a,time); + vdiv(a,20000.0f); + vadd(p->o, a); + pp = &p->next; + }; + }; + + glEnable(GL_FOG); + glDisable(GL_BLEND); + glDepthMask(GL_TRUE); +}; + +void particle_splash(int type, int num, int fade, vec &p) +{ + loopi(num) + { + const int radius = type==5 ? 50 : 150; + int x, y, z; + do + { + x = rnd(radius*2)-radius; + y = rnd(radius*2)-radius; + z = rnd(radius*2)-radius; + } + while(x*x+y*y+z*z>radius*radius); + vec d = { (float)x, (float)y, (float)z }; + newparticle(p, d, rnd(fade*3), type); + }; +}; + +void particle_trail(int type, int fade, vec &s, vec &e) +{ + vdist(d, v, s, e); + vdiv(v, d*2+0.1f); + vec p = s; + loopi((int)d*2) + { + vadd(p, v); + vec d = { float(rnd(11)-5), float(rnd(11)-5), float(rnd(11)-5) }; + newparticle(p, d, rnd(fade)+fade, type); + }; +}; diff --git a/source/src/rendertext.cpp b/source/src/rendertext.cpp index 66255344..3f07b003 100644 --- a/source/src/rendertext.cpp +++ b/source/src/rendertext.cpp @@ -1,222 +1,222 @@ -// rendertext.cpp: based on Don's gl_text.cpp - -#include "cube.h" - -short char_coords[96][4] = -{ - {0,0,25,64}, //! - {25,0,54,64}, //" - {54,0,107,64}, //# - {107,0,148,64}, //$ - {148,0,217,64}, //% - {217,0,263,64}, //& - {263,0,280,64}, //' - {280,0,309,64}, //( - {309,0,338,64}, //) - {338,0,379,64}, //* - {379,0,432,64}, //+ - {432,0,455,64}, //, - {455,0,484,64}, //- - {0,64,21,128}, //. - {23,64,52,128}, /// - {52,64,93,128}, //0 - {93,64,133,128}, //1 - {133,64,174,128}, //2 - {174,64,215,128}, //3 - {215,64,256,128}, //4 - {256,64,296,128}, //5 - {296,64,337,128}, //6 - {337,64,378,128}, //7 - {378,64,419,128}, //8 - {419,64,459,128}, //9 - {459,64,488,128}, //: - {0,128,29,192}, //; - {29,128,81,192}, //< - {81,128,134,192}, //= - {134,128,186,192}, //> - {186,128,221,192}, //? - {221,128,285,192}, //@ - {285,128,329,192}, //A - {329,128,373,192}, //B - {373,128,418,192}, //C - {418,128,467,192}, //D - {0,192,40,256}, //E - {40,192,77,256}, //F - {77,192,127,256}, //G - {127,192,175,256}, //H - {175,192,202,256}, //I - {202,192,231,256}, //J - {231,192,275,256}, //K - {275,192,311,256}, //L - {311,192,365,256}, //M - {365,192,413,256}, //N - {413,192,463,256}, //O - {1,256,38,320}, //P - {38,256,89,320}, //Q - {89,256,133,320}, //R - {133,256,176,320}, //S - {177,256,216,320}, //T - {217,256,263,320}, //U - {263,256,307,320}, //V - {307,256,370,320}, //W - {370,256,414,320}, //X - {414,256,453,320}, //Y - {453,256,497,320}, //Z - {0,320,29,384}, //[ - {29,320,58,384}, //"\" - {59,320,87,384}, //] - {87,320,139,384}, //^ - {139,320,180,384}, //_ - {180,320,221,384}, //` - {221,320,259,384}, //a - {259,320,299,384}, //b - {299,320,332,384}, //c - {332,320,372,384}, //d - {372,320,411,384}, //e - {411,320,433,384}, //f - {435,320,473,384}, //g - {0,384,40,448}, //h - {40,384,56,448}, //i - {58,384,80,448}, //j - {80,384,118,448}, //k - {118,384,135,448}, //l - {135,384,197,448}, //m - {197,384,238,448}, //n - {238,384,277,448}, //o - {277,384,317,448}, //p - {317,384,356,448}, //q - {357,384,384,448}, //r - {385,384,417,448}, //s - {417,384,442,448}, //t - {443,384,483,448}, //u - {0,448,38,512}, //v - {38,448,90,512}, //w - {90,448,128,512}, //x - {128,448,166,512}, //y - {166,448,200,512}, //z - {200,448,241,512}, //{ - {241,448,270,512}, //| - {270,448,310,512}, //} - {310,448,363,512}, //~ -}; - -int text_width(char *str) -{ - int x = 0; - for (int i = 0; str[i] != 0; i++) - { - int c = str[i]; - if(c=='\t') { x = (x+PIXELTAB)/PIXELTAB*PIXELTAB; continue; }; - if(c=='\f') continue; - if(c==' ') { x += FONTH/2; continue; }; - c -= 33; - if(c<0 || c>=95) continue; - int in_width = char_coords[c][2] - char_coords[c][0]; - x += in_width + 1; - } - return x; -} - - -void draw_textf(char *fstr, int left, int top, int gl_num, int arg) -{ - sprintf_sd(str)(fstr,arg); - draw_text(str, left, top, gl_num); -}; - -void draw_text(char *str, int left, int top, int gl_num) -{ - glBlendFunc(GL_ONE, GL_ONE); - glBindTexture(GL_TEXTURE_2D, gl_num); - glColor3ub(255,255,255); - - int x = left; - int y = top; - - int i; - float in_left, in_top, in_right, in_bottom; - int in_width, in_height; - - for (i = 0; str[i] != 0; i++) - { - int c = str[i]; - if(c=='\t') { x = (x-left+PIXELTAB)/PIXELTAB*PIXELTAB+left; continue; }; - if(c=='\f') { glColor3ub(64,255,128); continue; }; - if(c==' ') { x += FONTH/2; continue; }; - c -= 33; - if(c<0 || c>=95) continue; - - in_left = ((float) char_coords[c][0]) / 512.0f; - in_top = ((float) char_coords[c][1]+2) / 512.0f; - in_right = ((float) char_coords[c][2]) / 512.0f; - in_bottom = ((float) char_coords[c][3]-2) / 512.0f; - - in_width = char_coords[c][2] - char_coords[c][0]; - in_height = char_coords[c][3] - char_coords[c][1]; - - glBegin(GL_QUADS); - glTexCoord2f(in_left, in_top ); glVertex2i(x, y); - glTexCoord2f(in_right, in_top ); glVertex2i(x + in_width, y); - glTexCoord2f(in_right, in_bottom); glVertex2i(x + in_width, y + in_height); - glTexCoord2f(in_left, in_bottom); glVertex2i(x, y + in_height); - glEnd(); - - xtraverts += 4; - x += in_width + 1; - } -} - -// also Don's code, so goes in here too :) - -void draw_envbox_aux(float s0, float t0, int x0, int y0, int z0, - float s1, float t1, int x1, int y1, int z1, - float s2, float t2, int x2, int y2, int z2, - float s3, float t3, int x3, int y3, int z3, - int texture) -{ - glBindTexture(GL_TEXTURE_2D, texture); - glBegin(GL_QUADS); - glTexCoord2f(s3, t3); glVertex3d(x3, y3, z3); - glTexCoord2f(s2, t2); glVertex3d(x2, y2, z2); - glTexCoord2f(s1, t1); glVertex3d(x1, y1, z1); - glTexCoord2f(s0, t0); glVertex3d(x0, y0, z0); - glEnd(); - xtraverts += 4; -} - -void draw_envbox(int t, int w) -{ - glDepthMask(GL_FALSE); - - draw_envbox_aux(1.0f, 1.0f, -w, -w, w, - 0.0f, 1.0f, w, -w, w, - 0.0f, 0.0f, w, -w, -w, - 1.0f, 0.0f, -w, -w, -w, t); - - draw_envbox_aux(1.0f, 1.0f, +w, w, w, - 0.0f, 1.0f, -w, w, w, - 0.0f, 0.0f, -w, w, -w, - 1.0f, 0.0f, +w, w, -w, t+1); - - draw_envbox_aux(0.0f, 0.0f, -w, -w, -w, - 1.0f, 0.0f, -w, w, -w, - 1.0f, 1.0f, -w, w, w, - 0.0f, 1.0f, -w, -w, w, t+2); - - draw_envbox_aux(1.0f, 1.0f, +w, -w, w, - 0.0f, 1.0f, +w, w, w, - 0.0f, 0.0f, +w, w, -w, - 1.0f, 0.0f, +w, -w, -w, t+3); - - draw_envbox_aux(0.0f, 1.0f, -w, w, w, - 0.0f, 0.0f, +w, w, w, - 1.0f, 0.0f, +w, -w, w, - 1.0f, 1.0f, -w, -w, w, t+4); - - draw_envbox_aux(0.0f, 1.0f, +w, w, -w, - 0.0f, 0.0f, -w, w, -w, - 1.0f, 0.0f, -w, -w, -w, - 1.0f, 1.0f, +w, -w, -w, t+5); - - glDepthMask(GL_TRUE); -} +// rendertext.cpp: based on Don's gl_text.cpp + +#include "cube.h" + +short char_coords[96][4] = +{ + {0,0,25,64}, //! + {25,0,54,64}, //" + {54,0,107,64}, //# + {107,0,148,64}, //$ + {148,0,217,64}, //% + {217,0,263,64}, //& + {263,0,280,64}, //' + {280,0,309,64}, //( + {309,0,338,64}, //) + {338,0,379,64}, //* + {379,0,432,64}, //+ + {432,0,455,64}, //, + {455,0,484,64}, //- + {0,64,21,128}, //. + {23,64,52,128}, /// + {52,64,93,128}, //0 + {93,64,133,128}, //1 + {133,64,174,128}, //2 + {174,64,215,128}, //3 + {215,64,256,128}, //4 + {256,64,296,128}, //5 + {296,64,337,128}, //6 + {337,64,378,128}, //7 + {378,64,419,128}, //8 + {419,64,459,128}, //9 + {459,64,488,128}, //: + {0,128,29,192}, //; + {29,128,81,192}, //< + {81,128,134,192}, //= + {134,128,186,192}, //> + {186,128,221,192}, //? + {221,128,285,192}, //@ + {285,128,329,192}, //A + {329,128,373,192}, //B + {373,128,418,192}, //C + {418,128,467,192}, //D + {0,192,40,256}, //E + {40,192,77,256}, //F + {77,192,127,256}, //G + {127,192,175,256}, //H + {175,192,202,256}, //I + {202,192,231,256}, //J + {231,192,275,256}, //K + {275,192,311,256}, //L + {311,192,365,256}, //M + {365,192,413,256}, //N + {413,192,463,256}, //O + {1,256,38,320}, //P + {38,256,89,320}, //Q + {89,256,133,320}, //R + {133,256,176,320}, //S + {177,256,216,320}, //T + {217,256,263,320}, //U + {263,256,307,320}, //V + {307,256,370,320}, //W + {370,256,414,320}, //X + {414,256,453,320}, //Y + {453,256,497,320}, //Z + {0,320,29,384}, //[ + {29,320,58,384}, //"\" + {59,320,87,384}, //] + {87,320,139,384}, //^ + {139,320,180,384}, //_ + {180,320,221,384}, //` + {221,320,259,384}, //a + {259,320,299,384}, //b + {299,320,332,384}, //c + {332,320,372,384}, //d + {372,320,411,384}, //e + {411,320,433,384}, //f + {435,320,473,384}, //g + {0,384,40,448}, //h + {40,384,56,448}, //i + {58,384,80,448}, //j + {80,384,118,448}, //k + {118,384,135,448}, //l + {135,384,197,448}, //m + {197,384,238,448}, //n + {238,384,277,448}, //o + {277,384,317,448}, //p + {317,384,356,448}, //q + {357,384,384,448}, //r + {385,384,417,448}, //s + {417,384,442,448}, //t + {443,384,483,448}, //u + {0,448,38,512}, //v + {38,448,90,512}, //w + {90,448,128,512}, //x + {128,448,166,512}, //y + {166,448,200,512}, //z + {200,448,241,512}, //{ + {241,448,270,512}, //| + {270,448,310,512}, //} + {310,448,363,512}, //~ +}; + +int text_width(char *str) +{ + int x = 0; + for (int i = 0; str[i] != 0; i++) + { + int c = str[i]; + if(c=='\t') { x = (x+PIXELTAB)/PIXELTAB*PIXELTAB; continue; }; + if(c=='\f') continue; + if(c==' ') { x += FONTH/2; continue; }; + c -= 33; + if(c<0 || c>=95) continue; + int in_width = char_coords[c][2] - char_coords[c][0]; + x += in_width + 1; + } + return x; +} + + +void draw_textf(char *fstr, int left, int top, int gl_num, int arg) +{ + sprintf_sd(str)(fstr,arg); + draw_text(str, left, top, gl_num); +}; + +void draw_text(char *str, int left, int top, int gl_num) +{ + glBlendFunc(GL_ONE, GL_ONE); + glBindTexture(GL_TEXTURE_2D, gl_num); + glColor3ub(255,255,255); + + int x = left; + int y = top; + + int i; + float in_left, in_top, in_right, in_bottom; + int in_width, in_height; + + for (i = 0; str[i] != 0; i++) + { + int c = str[i]; + if(c=='\t') { x = (x-left+PIXELTAB)/PIXELTAB*PIXELTAB+left; continue; }; + if(c=='\f') { glColor3ub(64,255,128); continue; }; + if(c==' ') { x += FONTH/2; continue; }; + c -= 33; + if(c<0 || c>=95) continue; + + in_left = ((float) char_coords[c][0]) / 512.0f; + in_top = ((float) char_coords[c][1]+2) / 512.0f; + in_right = ((float) char_coords[c][2]) / 512.0f; + in_bottom = ((float) char_coords[c][3]-2) / 512.0f; + + in_width = char_coords[c][2] - char_coords[c][0]; + in_height = char_coords[c][3] - char_coords[c][1]; + + glBegin(GL_QUADS); + glTexCoord2f(in_left, in_top ); glVertex2i(x, y); + glTexCoord2f(in_right, in_top ); glVertex2i(x + in_width, y); + glTexCoord2f(in_right, in_bottom); glVertex2i(x + in_width, y + in_height); + glTexCoord2f(in_left, in_bottom); glVertex2i(x, y + in_height); + glEnd(); + + xtraverts += 4; + x += in_width + 1; + } +} + +// also Don's code, so goes in here too :) + +void draw_envbox_aux(float s0, float t0, int x0, int y0, int z0, + float s1, float t1, int x1, int y1, int z1, + float s2, float t2, int x2, int y2, int z2, + float s3, float t3, int x3, int y3, int z3, + int texture) +{ + glBindTexture(GL_TEXTURE_2D, texture); + glBegin(GL_QUADS); + glTexCoord2f(s3, t3); glVertex3d(x3, y3, z3); + glTexCoord2f(s2, t2); glVertex3d(x2, y2, z2); + glTexCoord2f(s1, t1); glVertex3d(x1, y1, z1); + glTexCoord2f(s0, t0); glVertex3d(x0, y0, z0); + glEnd(); + xtraverts += 4; +} + +void draw_envbox(int t, int w) +{ + glDepthMask(GL_FALSE); + + draw_envbox_aux(1.0f, 1.0f, -w, -w, w, + 0.0f, 1.0f, w, -w, w, + 0.0f, 0.0f, w, -w, -w, + 1.0f, 0.0f, -w, -w, -w, t); + + draw_envbox_aux(1.0f, 1.0f, +w, w, w, + 0.0f, 1.0f, -w, w, w, + 0.0f, 0.0f, -w, w, -w, + 1.0f, 0.0f, +w, w, -w, t+1); + + draw_envbox_aux(0.0f, 0.0f, -w, -w, -w, + 1.0f, 0.0f, -w, w, -w, + 1.0f, 1.0f, -w, w, w, + 0.0f, 1.0f, -w, -w, w, t+2); + + draw_envbox_aux(1.0f, 1.0f, +w, -w, w, + 0.0f, 1.0f, +w, w, w, + 0.0f, 0.0f, +w, w, -w, + 1.0f, 0.0f, +w, -w, -w, t+3); + + draw_envbox_aux(0.0f, 1.0f, -w, w, w, + 0.0f, 0.0f, +w, w, w, + 1.0f, 0.0f, +w, -w, w, + 1.0f, 1.0f, -w, -w, w, t+4); + + draw_envbox_aux(0.0f, 1.0f, +w, w, -w, + 0.0f, 0.0f, -w, w, -w, + 1.0f, 0.0f, -w, -w, -w, + 1.0f, 1.0f, +w, -w, -w, t+5); + + glDepthMask(GL_TRUE); +} diff --git a/source/src/rndmap.cpp b/source/src/rndmap.cpp index 7a526484..5b56a61c 100644 --- a/source/src/rndmap.cpp +++ b/source/src/rndmap.cpp @@ -1,70 +1,70 @@ -// rndmap.cpp: perlin noise landscape generation and some experimental random map stuff, currently not used - -#include "cube.h" - -float noise(int x, int y, int seed) -{ - int n = x+y*57; - n = (n<<13)^n; - return 1.0f-((n*(n*n*15731+789221)+1376312589)&0x7fffffff)/1073741824.0f; -} - -float smoothednoise(int x, int y, int seed) -{ - float corners = (noise(x-1, y-1, seed)+noise(x+1, y-1, seed)+noise(x-1, y+1, seed)+noise(x+1, y+1, seed))/16; - float sides = (noise(x-1, y, seed)+noise(x+1, y, seed)+noise(x, y-1, seed)+noise(x, y+1, seed))/8; - float center = noise(x, y, seed)/4; - return corners+sides+center; -} - -float interpolate(float a, float b, float x) -{ - float ft = x*3.1415927f; - float f = (1.0f-(float)cos(ft))*0.5f; - return a*(1-f)+b*f; -} - -float interpolatednoise(float x, float y, int seed) -{ - int ix = (int)x; - float fx = x-ix; - int iy = (int)y; - float fy = y-iy; - float v1 = smoothednoise(ix, iy, seed); - float v2 = smoothednoise(ix+1, iy, seed); - float v3 = smoothednoise(ix, iy+1, seed); - float v4 = smoothednoise(ix+1, iy+1, seed); - float i1 = interpolate(v1, v2, fx); - float i2 = interpolate(v3, v4, fy); - return interpolate(i1, i2, fy); -} - -float perlinnoise_2D(float x, float y, int seedstep, float pers) -{ - float total = 0; - int seed = 0; - for(int i = 0; i<7; i++) - { - float frequency = (float)(2^i); - float amplitude = (float)pow(pers, i); - total += interpolatednoise(x*frequency, y*frequency, seed)*amplitude; - seed += seedstep; - } - return total; -} - -void perlinarea(block &b, int scale, int seed, int psize) -{ - srand(seed); - seed = rnd(10000); - if(!scale) scale = 10; - for(int x = b.x; x<=b.x+b.xs; x++) for(int y = b.y; y<=b.y+b.ys; y++) - { - sqr *s = S(x,y); - if(!SOLID(s) && x!=b.x+b.xs && y!=b.y+b.ys) s->type = FHF; - s->vdelta = (int)(perlinnoise_2D(x/((float)scale)+seed, y/((float)scale)+seed, 1000, 0.01f)*50+25); - if(s->vdelta>128) s->vdelta = 0; - }; -}; - - +// rndmap.cpp: perlin noise landscape generation and some experimental random map stuff, currently not used + +#include "cube.h" + +float noise(int x, int y, int seed) +{ + int n = x+y*57; + n = (n<<13)^n; + return 1.0f-((n*(n*n*15731+789221)+1376312589)&0x7fffffff)/1073741824.0f; +} + +float smoothednoise(int x, int y, int seed) +{ + float corners = (noise(x-1, y-1, seed)+noise(x+1, y-1, seed)+noise(x-1, y+1, seed)+noise(x+1, y+1, seed))/16; + float sides = (noise(x-1, y, seed)+noise(x+1, y, seed)+noise(x, y-1, seed)+noise(x, y+1, seed))/8; + float center = noise(x, y, seed)/4; + return corners+sides+center; +} + +float interpolate(float a, float b, float x) +{ + float ft = x*3.1415927f; + float f = (1.0f-(float)cos(ft))*0.5f; + return a*(1-f)+b*f; +} + +float interpolatednoise(float x, float y, int seed) +{ + int ix = (int)x; + float fx = x-ix; + int iy = (int)y; + float fy = y-iy; + float v1 = smoothednoise(ix, iy, seed); + float v2 = smoothednoise(ix+1, iy, seed); + float v3 = smoothednoise(ix, iy+1, seed); + float v4 = smoothednoise(ix+1, iy+1, seed); + float i1 = interpolate(v1, v2, fx); + float i2 = interpolate(v3, v4, fy); + return interpolate(i1, i2, fy); +} + +float perlinnoise_2D(float x, float y, int seedstep, float pers) +{ + float total = 0; + int seed = 0; + for(int i = 0; i<7; i++) + { + float frequency = (float)(2^i); + float amplitude = (float)pow(pers, i); + total += interpolatednoise(x*frequency, y*frequency, seed)*amplitude; + seed += seedstep; + } + return total; +} + +void perlinarea(block &b, int scale, int seed, int psize) +{ + srand(seed); + seed = rnd(10000); + if(!scale) scale = 10; + for(int x = b.x; x<=b.x+b.xs; x++) for(int y = b.y; y<=b.y+b.ys; y++) + { + sqr *s = S(x,y); + if(!SOLID(s) && x!=b.x+b.xs && y!=b.y+b.ys) s->type = FHF; + s->vdelta = (int)(perlinnoise_2D(x/((float)scale)+seed, y/((float)scale)+seed, 1000, 0.01f)*50+25); + if(s->vdelta>128) s->vdelta = 0; + }; +}; + + diff --git a/source/src/savegamedemo.cpp b/source/src/savegamedemo.cpp index 57e4e858..882019a2 100644 --- a/source/src/savegamedemo.cpp +++ b/source/src/savegamedemo.cpp @@ -1,350 +1,350 @@ -// loading and saving of savegames & demos, dumps the spawn state of all mapents, the full state of all dynents (monsters + player) - -#include "cube.h" - -extern int islittleendian; - -gzFile f = NULL; -bool demorecording = false; -bool demoplayback = false; -bool demoloading = false; -dvector playerhistory; -int democlientnum = 0; - -void startdemo(); - -void gzput(int i) { gzputc(f, i); }; -void gzputi(int i) { gzwrite(f, &i, sizeof(int)); }; -void gzputv(vec &v) { gzwrite(f, &v, sizeof(vec)); }; - -void gzcheck(int a, int b) { if(a!=b) fatal("savegame file corrupt (short)"); }; -int gzget() { char c = gzgetc(f); return c; }; -int gzgeti() { int i; gzcheck(gzread(f, &i, sizeof(int)), sizeof(int)); return i; }; -void gzgetv(vec &v) { gzcheck(gzread(f, &v, sizeof(vec)), sizeof(vec)); }; - -void stop() -{ - if(f) - { - if(demorecording) gzputi(-1); - gzclose(f); - }; - f = NULL; - demorecording = false; - demoplayback = false; - demoloading = false; - loopv(playerhistory) zapdynent(playerhistory[i]); - playerhistory.setsize(0); - // Added by Rick: Remove bots - loopv(bots) - { - if (!bots[i]) continue; - delete bots[i]->pBot; - bots[i]->pBot = NULL; - zapdynent(bots[i]); - } - bots.setsize(0); -}; - -void stopifrecording() { if(demorecording) stop(); }; - -void savestate(char *fn) -{ - stop(); - f = gzopen(fn, "wb9"); - if(!f) { conoutf("could not write %s", (int)fn); return; }; - gzwrite(f, (void *)"CUBESAVE", 8); - gzputc(f, islittleendian); - gzputi(SAVEGAMEVERSION); - gzputi(sizeof(dynent)); - gzwrite(f, getclientmap(), _MAXDEFSTR); - gzputi(gamemode); - gzputi(ents.length()); - loopv(ents) gzputc(f, ents[i].spawned); - gzwrite(f, player1, sizeof(dynent)); - gzputi(players.length()); - loopv(players) - { - gzput(players[i]==NULL); - gzwrite(f, players[i], sizeof(dynent)); - }; -}; - -void savegame(char *name) -{ - conoutf("can only save classic sp games"); - return; -}; - -void loadstate(char *fn) -{ - stop(); - if(multiplayer()) return; - f = gzopen(fn, "rb9"); - if(!f) { conoutf("could not open %s", (int)fn); return; }; - - string buf; - gzread(f, buf, 8); - if(strncmp(buf, "CUBESAVE", 8)) goto out; - if(gzgetc(f)!=islittleendian) goto out; // not supporting save->load accross incompatible architectures simpifies things a LOT - if(gzgeti()!=SAVEGAMEVERSION || gzgeti()!=sizeof(dynent)) goto out; - string mapname; - gzread(f, mapname, _MAXDEFSTR); - nextmode = gzgeti(); - changemap(mapname); // continue below once map has been loaded and client & server have updated - return; - out: - conoutf("aborting: savegame/demo from a different version of cube or cpu architecture"); - stop(); -}; - -void loadgame(char *name) -{ - sprintf_sd(fn)("savegames/%s.csgz", name); - loadstate(fn); -}; - -void loadgameout() -{ - stop(); - conoutf("loadgame incomplete: savegame from a different version of this map"); -}; - -void loadgamerest() -{ - if(demoplayback || !f) return; - - if(gzgeti()!=ents.length()) return loadgameout(); - loopv(ents) - { - ents[i].spawned = gzgetc(f)!=0; - if(ents[i].type==CARROT && !ents[i].spawned) trigger(ents[i].attr1, ents[i].attr2, true); - }; - restoreserverstate(ents); - - gzread(f, player1, sizeof(dynent)); - player1->lastaction = lastmillis; - - int nplayers = gzgeti(); - loopi(nplayers) if(!gzget()) - { - dynent *d = getclient(i); - assert(d); - gzread(f, d, sizeof(dynent)); - }; - - conoutf("savegame restored"); - if(demoloading) startdemo(); else stop(); -}; - -// demo functions - -int starttime = 0; -int playbacktime = 0; -int ddamage, bdamage; -vec dorig; - -void record(char *name) -{ - int cn = getclientnum(); - if(cn<0) return; - sprintf_sd(fn)("demos/%s.cdgz", name); - savestate(fn); - gzputi(cn); - conoutf("started recording demo to %s", (int)fn); - demorecording = true; - starttime = lastmillis; - ddamage = bdamage = 0; -}; - -void demodamage(int damage, vec &o) { ddamage = damage; dorig = o; }; -void demoblend(int damage) { bdamage = damage; }; - -void incomingdemodata(uchar *buf, int len, bool extras) -{ - if(!demorecording) return; - gzputi(lastmillis-starttime); - gzputi(len); - gzwrite(f, buf, len); - gzput(extras); - if(extras) - { - gzput(player1->gunselect); - gzput(player1->lastattackgun); - gzputi(player1->lastaction-starttime); - gzputi(player1->gunwait); - gzputi(player1->health); - gzputi(player1->armour); - //gzput(player1->armourtype); - loopi(NUMGUNS) gzput(player1->ammo[i]); - gzput(player1->state); - gzputi(bdamage); - bdamage = 0; - gzputi(ddamage); - if(ddamage) { gzputv(dorig); ddamage = 0; }; - // FIXME: add all other client state which is not send through the network - }; -}; - -void demo(char *name) -{ - sprintf_sd(fn)("demos/%s.cdgz", name); - loadstate(fn); - demoloading = true; -}; - -void stopreset() -{ - conoutf("demo stopped (%d msec elapsed)", lastmillis-starttime); - stop(); - loopv(players) zapdynent(players[i]); - disconnect(0, 0); -}; - -VAR(demoplaybackspeed, 10, 100, 1000); -int scaletime(int t) { return (int)(t*(100.0f/demoplaybackspeed))+starttime; }; - -void readdemotime() -{ - if(gzeof(f) || (playbacktime = gzgeti())==-1) - { - stopreset(); - return; - }; - playbacktime = scaletime(playbacktime); -}; - -void startdemo() -{ - democlientnum = gzgeti(); - demoplayback = true; - starttime = lastmillis; - conoutf("now playing demo"); - dynent *d = getclient(democlientnum); - assert(d); - *d = *player1; - readdemotime(); -}; - -VAR(demodelaymsec, 0, 120, 500); - -void catmulrom(vec &z, vec &a, vec &b, vec &c, float s, vec &dest) // spline interpolation -{ - vec t1 = b, t2 = c; - - vsub(t1, z); vmul(t1, 0.5f) - vsub(t2, a); vmul(t2, 0.5f); - - float s2 = s*s; - float s3 = s*s2; - - dest = a; - vec t = b; - - vmul(dest, 2*s3 - 3*s2 + 1); - vmul(t, -2*s3 + 3*s2); vadd(dest, t); - vmul(t1, s3 - 2*s2 + s); vadd(dest, t1); - vmul(t2, s3 - s2); vadd(dest, t2); -}; - -void fixwrap(dynent *a, dynent *b) -{ - while(b->yaw-a->yaw>180) a->yaw += 360; - while(b->yaw-a->yaw<-180) a->yaw -= 360; -}; - -void demoplaybackstep() -{ - while(demoplayback && lastmillis>=playbacktime) - { - int len = gzgeti(); - if(len<1 || len>MAXTRANS) - { - conoutf("error: huge packet during demo play (%d)", len); - stopreset(); - return; - }; - uchar buf[MAXTRANS]; - gzread(f, buf, len); - localservertoclient(buf, len); // update game state - - dynent *target = players[democlientnum]; - assert(target); - - int extras; - if(extras = gzget()) // read additional client side state not present in normal network stream - { - target->gunselect = gzget(); - target->lastattackgun = gzget(); - target->lastaction = scaletime(gzgeti()); - target->gunwait = gzgeti(); - target->health = gzgeti(); - target->armour = gzgeti(); - //target->armourtype = gzget(); - loopi(NUMGUNS) target->ammo[i] = gzget(); - target->state = gzget(); - target->lastmove = playbacktime; - if(bdamage = gzgeti()) damageblend(bdamage); - if(ddamage = gzgeti()) { gzgetv(dorig); particle_splash(3, ddamage, 1000, dorig); }; - // FIXME: set more client state here - }; - - // insert latest copy of player into history - if(extras && (playerhistory.empty() || playerhistory.last()->lastupdate!=playbacktime)) - { - dynent *d = newdynent(); - *d = *target; - d->lastupdate = playbacktime; - playerhistory.add(d); - if(playerhistory.length()>20) - { - zapdynent(playerhistory[0]); - playerhistory.remove(0); - }; - }; - - readdemotime(); - }; - - if(demoplayback) - { - int itime = lastmillis-demodelaymsec; - loopvrev(playerhistory) if(playerhistory[i]->lastupdate=0) z = playerhistory[i-1]; - //if(a==z || b==c) printf("* %d\n", lastmillis); - float bf = (itime-a->lastupdate)/(float)(b->lastupdate-a->lastupdate); - fixwrap(a, player1); - fixwrap(c, player1); - fixwrap(z, player1); - vdist(dist, v, z->o, c->o); - if(dist<16) // if teleport or spawn, dont't interpolate - { - catmulrom(z->o, a->o, b->o, c->o, bf, player1->o); - catmulrom(*(vec *)&z->yaw, *(vec *)&a->yaw, *(vec *)&b->yaw, *(vec *)&c->yaw, bf, *(vec *)&player1->yaw); - }; - fixplayer1range(); - }; - break; - }; - //if(player1->state!=CS_DEAD) showscores(false); - }; -}; - -void stopn() { if(demoplayback) stopreset(); else stop(); conoutf("demo stopped"); }; - -COMMAND(record, ARG_1STR); -COMMAND(demo, ARG_1STR); -COMMANDN(stop, stopn, ARG_NONE); - -COMMAND(savegame, ARG_1STR); -COMMAND(loadgame, ARG_1STR); +// loading and saving of savegames & demos, dumps the spawn state of all mapents, the full state of all dynents (monsters + player) + +#include "cube.h" + +extern int islittleendian; + +gzFile f = NULL; +bool demorecording = false; +bool demoplayback = false; +bool demoloading = false; +dvector playerhistory; +int democlientnum = 0; + +void startdemo(); + +void gzput(int i) { gzputc(f, i); }; +void gzputi(int i) { gzwrite(f, &i, sizeof(int)); }; +void gzputv(vec &v) { gzwrite(f, &v, sizeof(vec)); }; + +void gzcheck(int a, int b) { if(a!=b) fatal("savegame file corrupt (short)"); }; +int gzget() { char c = gzgetc(f); return c; }; +int gzgeti() { int i; gzcheck(gzread(f, &i, sizeof(int)), sizeof(int)); return i; }; +void gzgetv(vec &v) { gzcheck(gzread(f, &v, sizeof(vec)), sizeof(vec)); }; + +void stop() +{ + if(f) + { + if(demorecording) gzputi(-1); + gzclose(f); + }; + f = NULL; + demorecording = false; + demoplayback = false; + demoloading = false; + loopv(playerhistory) zapdynent(playerhistory[i]); + playerhistory.setsize(0); + // Added by Rick: Remove bots + loopv(bots) + { + if (!bots[i]) continue; + delete bots[i]->pBot; + bots[i]->pBot = NULL; + zapdynent(bots[i]); + } + bots.setsize(0); +}; + +void stopifrecording() { if(demorecording) stop(); }; + +void savestate(char *fn) +{ + stop(); + f = gzopen(fn, "wb9"); + if(!f) { conoutf("could not write %s", (int)fn); return; }; + gzwrite(f, (void *)"CUBESAVE", 8); + gzputc(f, islittleendian); + gzputi(SAVEGAMEVERSION); + gzputi(sizeof(dynent)); + gzwrite(f, getclientmap(), _MAXDEFSTR); + gzputi(gamemode); + gzputi(ents.length()); + loopv(ents) gzputc(f, ents[i].spawned); + gzwrite(f, player1, sizeof(dynent)); + gzputi(players.length()); + loopv(players) + { + gzput(players[i]==NULL); + gzwrite(f, players[i], sizeof(dynent)); + }; +}; + +void savegame(char *name) +{ + conoutf("can only save classic sp games"); + return; +}; + +void loadstate(char *fn) +{ + stop(); + if(multiplayer()) return; + f = gzopen(fn, "rb9"); + if(!f) { conoutf("could not open %s", (int)fn); return; }; + + string buf; + gzread(f, buf, 8); + if(strncmp(buf, "CUBESAVE", 8)) goto out; + if(gzgetc(f)!=islittleendian) goto out; // not supporting save->load accross incompatible architectures simpifies things a LOT + if(gzgeti()!=SAVEGAMEVERSION || gzgeti()!=sizeof(dynent)) goto out; + string mapname; + gzread(f, mapname, _MAXDEFSTR); + nextmode = gzgeti(); + changemap(mapname); // continue below once map has been loaded and client & server have updated + return; + out: + conoutf("aborting: savegame/demo from a different version of cube or cpu architecture"); + stop(); +}; + +void loadgame(char *name) +{ + sprintf_sd(fn)("savegames/%s.csgz", name); + loadstate(fn); +}; + +void loadgameout() +{ + stop(); + conoutf("loadgame incomplete: savegame from a different version of this map"); +}; + +void loadgamerest() +{ + if(demoplayback || !f) return; + + if(gzgeti()!=ents.length()) return loadgameout(); + loopv(ents) + { + ents[i].spawned = gzgetc(f)!=0; + if(ents[i].type==CARROT && !ents[i].spawned) trigger(ents[i].attr1, ents[i].attr2, true); + }; + restoreserverstate(ents); + + gzread(f, player1, sizeof(dynent)); + player1->lastaction = lastmillis; + + int nplayers = gzgeti(); + loopi(nplayers) if(!gzget()) + { + dynent *d = getclient(i); + assert(d); + gzread(f, d, sizeof(dynent)); + }; + + conoutf("savegame restored"); + if(demoloading) startdemo(); else stop(); +}; + +// demo functions + +int starttime = 0; +int playbacktime = 0; +int ddamage, bdamage; +vec dorig; + +void record(char *name) +{ + int cn = getclientnum(); + if(cn<0) return; + sprintf_sd(fn)("demos/%s.cdgz", name); + savestate(fn); + gzputi(cn); + conoutf("started recording demo to %s", (int)fn); + demorecording = true; + starttime = lastmillis; + ddamage = bdamage = 0; +}; + +void demodamage(int damage, vec &o) { ddamage = damage; dorig = o; }; +void demoblend(int damage) { bdamage = damage; }; + +void incomingdemodata(uchar *buf, int len, bool extras) +{ + if(!demorecording) return; + gzputi(lastmillis-starttime); + gzputi(len); + gzwrite(f, buf, len); + gzput(extras); + if(extras) + { + gzput(player1->gunselect); + gzput(player1->lastattackgun); + gzputi(player1->lastaction-starttime); + gzputi(player1->gunwait); + gzputi(player1->health); + gzputi(player1->armour); + //gzput(player1->armourtype); + loopi(NUMGUNS) gzput(player1->ammo[i]); + gzput(player1->state); + gzputi(bdamage); + bdamage = 0; + gzputi(ddamage); + if(ddamage) { gzputv(dorig); ddamage = 0; }; + // FIXME: add all other client state which is not send through the network + }; +}; + +void demo(char *name) +{ + sprintf_sd(fn)("demos/%s.cdgz", name); + loadstate(fn); + demoloading = true; +}; + +void stopreset() +{ + conoutf("demo stopped (%d msec elapsed)", lastmillis-starttime); + stop(); + loopv(players) zapdynent(players[i]); + disconnect(0, 0); +}; + +VAR(demoplaybackspeed, 10, 100, 1000); +int scaletime(int t) { return (int)(t*(100.0f/demoplaybackspeed))+starttime; }; + +void readdemotime() +{ + if(gzeof(f) || (playbacktime = gzgeti())==-1) + { + stopreset(); + return; + }; + playbacktime = scaletime(playbacktime); +}; + +void startdemo() +{ + democlientnum = gzgeti(); + demoplayback = true; + starttime = lastmillis; + conoutf("now playing demo"); + dynent *d = getclient(democlientnum); + assert(d); + *d = *player1; + readdemotime(); +}; + +VAR(demodelaymsec, 0, 120, 500); + +void catmulrom(vec &z, vec &a, vec &b, vec &c, float s, vec &dest) // spline interpolation +{ + vec t1 = b, t2 = c; + + vsub(t1, z); vmul(t1, 0.5f) + vsub(t2, a); vmul(t2, 0.5f); + + float s2 = s*s; + float s3 = s*s2; + + dest = a; + vec t = b; + + vmul(dest, 2*s3 - 3*s2 + 1); + vmul(t, -2*s3 + 3*s2); vadd(dest, t); + vmul(t1, s3 - 2*s2 + s); vadd(dest, t1); + vmul(t2, s3 - s2); vadd(dest, t2); +}; + +void fixwrap(dynent *a, dynent *b) +{ + while(b->yaw-a->yaw>180) a->yaw += 360; + while(b->yaw-a->yaw<-180) a->yaw -= 360; +}; + +void demoplaybackstep() +{ + while(demoplayback && lastmillis>=playbacktime) + { + int len = gzgeti(); + if(len<1 || len>MAXTRANS) + { + conoutf("error: huge packet during demo play (%d)", len); + stopreset(); + return; + }; + uchar buf[MAXTRANS]; + gzread(f, buf, len); + localservertoclient(buf, len); // update game state + + dynent *target = players[democlientnum]; + assert(target); + + int extras; + if(extras = gzget()) // read additional client side state not present in normal network stream + { + target->gunselect = gzget(); + target->lastattackgun = gzget(); + target->lastaction = scaletime(gzgeti()); + target->gunwait = gzgeti(); + target->health = gzgeti(); + target->armour = gzgeti(); + //target->armourtype = gzget(); + loopi(NUMGUNS) target->ammo[i] = gzget(); + target->state = gzget(); + target->lastmove = playbacktime; + if(bdamage = gzgeti()) damageblend(bdamage); + if(ddamage = gzgeti()) { gzgetv(dorig); particle_splash(3, ddamage, 1000, dorig); }; + // FIXME: set more client state here + }; + + // insert latest copy of player into history + if(extras && (playerhistory.empty() || playerhistory.last()->lastupdate!=playbacktime)) + { + dynent *d = newdynent(); + *d = *target; + d->lastupdate = playbacktime; + playerhistory.add(d); + if(playerhistory.length()>20) + { + zapdynent(playerhistory[0]); + playerhistory.remove(0); + }; + }; + + readdemotime(); + }; + + if(demoplayback) + { + int itime = lastmillis-demodelaymsec; + loopvrev(playerhistory) if(playerhistory[i]->lastupdate=0) z = playerhistory[i-1]; + //if(a==z || b==c) printf("* %d\n", lastmillis); + float bf = (itime-a->lastupdate)/(float)(b->lastupdate-a->lastupdate); + fixwrap(a, player1); + fixwrap(c, player1); + fixwrap(z, player1); + vdist(dist, v, z->o, c->o); + if(dist<16) // if teleport or spawn, dont't interpolate + { + catmulrom(z->o, a->o, b->o, c->o, bf, player1->o); + catmulrom(*(vec *)&z->yaw, *(vec *)&a->yaw, *(vec *)&b->yaw, *(vec *)&c->yaw, bf, *(vec *)&player1->yaw); + }; + fixplayer1range(); + }; + break; + }; + //if(player1->state!=CS_DEAD) showscores(false); + }; +}; + +void stopn() { if(demoplayback) stopreset(); else stop(); conoutf("demo stopped"); }; + +COMMAND(record, ARG_1STR); +COMMAND(demo, ARG_1STR); +COMMANDN(stop, stopn, ARG_NONE); + +COMMAND(savegame, ARG_1STR); +COMMAND(loadgame, ARG_1STR); diff --git a/source/src/server.cpp b/source/src/server.cpp index 06794e18..cda7371b 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -1,966 +1,966 @@ -// server.cpp: little more than enhanced multicaster -// runs dedicated or as client coroutine - -#include "cube.h" - -#define m_ctf_s (mode==5) - -enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; - -struct client // server side version of "dynent" type -{ - int type; - ENetPeer *peer; - string hostname; - string mapvote; - string name; - int modevote; - // Added by Rick: For bot voting - bool addbot, kickbot, changebotskill; - short botcount; - string botteam, botname; - short botskill; - bool kickallbots; - // End add -}; - -vector clients; - -int maxclients = 8; -string smapname; - -int numclients() -{ - int num = 0; - loopv(clients) if(clients[i].type!=ST_EMPTY) i++; - return num; -}; - -vector sents; - -bool notgotitems = true; // true when map has changed and waiting for clients to send item -int mode = 0; - -// Added by Rick -#ifndef STANDALONE -extern void kickbot(const char *szName); -extern void kickallbots(void); -#endif -// End add - -void restoreserverstate(vector &ents) // hack: called from savegame code, only works in SP -{ - loopv(sents) - { - sents[i].spawned = ents[i].spawned; - sents[i].spawnsecs = 0; - }; -}; - -int interm = 0, minremain = 0, mapend = 0; -bool mapreload = false; - -char *serverpassword = ""; - -bool isdedicated; -ENetHost * serverhost = NULL; -int bsend = 0, brec = 0, laststatus = 0, lastsec = 0; - -#define MAXOBUF 100000 - -void process(ENetPacket *packet, int sender); -void multicast(ENetPacket *packet, int sender); -void disconnect_client(int n, char *reason); - -void send(int n, ENetPacket *packet) -{ - if(!packet) return; - switch(clients[n].type) - { - case ST_TCPIP: - { - enet_peer_send(clients[n].peer, 0, packet); - bsend += packet->dataLength; - break; - }; - - case ST_LOCAL: - localservertoclient(packet->data, packet->dataLength); - break; - - }; -}; - -void send2(bool rel, int cn, int a, int b) -{ - ENetPacket *packet = enet_packet_create(NULL, 32, rel ? ENET_PACKET_FLAG_RELIABLE : 0); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, a); - putint(p, b); - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - if(cn<0) process(packet, -1); - else send(cn, packet); - if(packet->referenceCount==0) enet_packet_destroy(packet); -}; - -struct ctfflag -{ - int state; - int thief_cn; - int pos[3]; - int lastupdate; -} ctfflags[2]; - -bool ctfbroadcast = false; - -void sendflaginfo(int flag, int action, int cn = -1) -{ - ctfflag &f = ctfflags[flag]; - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_FLAGINFO); - putint(p, flag); printf("senflaginfo flag: %i\n", flag); - putint(p, f.state); printf("senflaginfo state: %i\n", f.state); - putint(p, action); printf("senflaginfo action: %i\n", action); - if(f.state==CTFF_STOLEN || action==SV_FLAGRETURN) putint(p, f.thief_cn); - else if(f.state==CTFF_DROPPED) loopi(3) putint(p, f.pos[i]); - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet,p-start); - if(cn<0) multicast(packet, -1); - else send(cn, packet); - if(packet->referenceCount==0) enet_packet_destroy(packet); -}; - -void ctfreset(bool send=true) -{ - if(!m_ctf_s) return; - loopi(2) - { - ctfflags[i].thief_cn = 0; - ctfflags[i].state = CTFF_INBASE; - ctfflags[i].lastupdate = -1; - if(send) sendflaginfo(i, -1); - }; -} - -void sendservmsg(char *msg) -{ - ENetPacket *packet = enet_packet_create(NULL, _MAXDEFSTR+10, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_SERVMSG); - sendstring(msg, p); - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - multicast(packet, -1); - if(packet->referenceCount==0) enet_packet_destroy(packet); -}; - -void disconnect_client(int n, char *reason) -{ - if(n<0 || n>=clients.length()) return; - printf("disconnecting client (%s) [%s]\n", clients[n].hostname, reason); - enet_peer_disconnect(clients[n].peer); - clients[n].type = ST_EMPTY; - send2(true, -1, SV_CDIS, n); -}; - -void resetitems() { sents.setsize(0); notgotitems = true; }; - -void pickup(uint i, int sec, int sender) // server side item pickup, acknowledge first client that gets it -{ - if(i>=(uint)sents.length()) return; - if(sents[i].spawned) - { - sents[i].spawned = false; - sents[i].spawnsecs = sec; - send2(true, sender, SV_ITEMACC, i); - }; -}; - -// EDIT: AH -struct configset -{ - string mapname; - int mode; - int time; - bool vote; -}; - -vector configsets; -int curcfgset = -1; - -void readscfg(char *cfg) -{ - configsets.setsize(0); - - string s; - strcpy_s(s, cfg); - char *buf = loadfile(path(s), NULL); - if(!buf) return; - char *p, *l; - - p = buf; - while((p = strstr(p, "//")) != NULL) // remove comments - while(p[0] != '\n' && p[0] != '\0') p++[0] = ' '; - - l = buf; - bool lastline = false; - while((p = strstr(l, "\n")) != NULL || (l && (lastline=true))) // remove empty/invalid lines - { - int len = lastline ? strlen(l) : p-l; - string line; - strn0cpy(line, l, len+1); - char *d = line; - int n = 0; - while((p = strstr(d, ":")) != NULL && (d = p+1)) n++; - if(n!=3) memset(l, ' ', len+1); - if(lastline) break; - l += len+1; - }; - - configset c; - int argc = 0; - string argv[4]; - - p = strtok(buf, ":\n\0"); - while(p != NULL) - { - strcpy(argv[argc], p); - if(++argc==4) - { - int numspaces; - for(numspaces = 0; argv[0][numspaces]==' '; numspaces++){}; // ingore space crap - strcpy(c.mapname, argv[0]+numspaces); - c.mode = atoi(argv[1]); - c.time = atoi(argv[2]); - c.vote = (bool) atoi(argv[3]); - configsets.add(c); - argc = 0; - }; - p = strtok(NULL, ":\n\0"); - }; -}; - -void nextcfgset() // load next maprotation set -{ - curcfgset++; - if(curcfgset>=configsets.length() || curcfgset<0) curcfgset=0; - - printf("nextcfgset: %i\n", curcfgset); - - configset &c = configsets[curcfgset]; - mode = c.mode; - minremain = c.time; - strcpy_s(smapname, c.mapname); - - mapend = lastsec+minremain*60; - mapreload = false; - interm = 0; - laststatus = lastsec-61; - resetitems(); -}; - -void resetvotes() -{ - loopv(clients) clients[i].mapvote[0] = 0; -}; - -bool vote(char *map, int reqmode, int sender) -{ - if(configsets.length() && curcfgset < configsets.length() && !configsets[curcfgset].vote) - { - sprintf_sd(msg)("%s voted, but voting is currently disabled", clients[sender].name); - sendservmsg(msg); - return false; - }; - strcpy_s(clients[sender].mapvote, map); - clients[sender].modevote = reqmode; - int yes = 0, no = 0; - loopv(clients) if(clients[i].type!=ST_EMPTY) - { - if(clients[i].mapvote[0]) { if(strcmp(clients[i].mapvote, map)==0 && clients[i].modevote==reqmode) yes++; else no++; } - else no++; - }; - if(yes==1 && no==0) return true; // single player - sprintf_sd(msg)("%s suggests %s on map %s (set map to vote)", clients[sender].name, modestr(reqmode), map); - sendservmsg(msg); - if(yes/(float)(yes+no) <= 0.5f) return false; - sendservmsg("vote passed"); - resetvotes(); - return true; -}; - -// Added by Rick: Vote for bot commands - -void resetbotvotes() -{ - loopv(clients) - { - clients[i].addbot = false; - clients[i].kickbot = false; - clients[i].changebotskill = false; - clients[i].kickallbots = false; - clients[i].botcount = 0; - clients[i].botteam[0] = 0; - clients[i].botname[0] = 0; - clients[i].botskill = -1; - } -}; - -bool addbotvote(int count, char *team, int skill, char *name, int sender) -{ -#ifndef STANDALONE - clients[sender].addbot = true; - clients[sender].kickbot = false; - clients[sender].changebotskill = false; - clients[sender].botcount = (count>=0) ? count : 0; - if (team && team[0]) strcpy_s(clients[sender].botteam, team); - else clients[sender].botteam[0] = 0; - clients[sender].botskill = skill; - if (name && name[0]) strcpy_s(clients[sender].botname, name); - else clients[sender].botname[0] = 0; - int yes = 0, no = 0; - loopv(clients) if(clients[i].type!=ST_EMPTY) - { - if (clients[i].addbot) - { - if (!strcmp(clients[i].botteam, team) && !strcmp(clients[i].botname, name) && - (clients[i].botskill == skill) && - (clients[i].botcount == clients[sender].botcount)) - yes++; - else - no++; - } - else no++; - } - if(yes==1 && no==0) return true; // single player - sprintf_sd(msg)("%s suggests to add a bot", clients[sender].name); - if (team && team[0]) - { - strcat(msg, " on team "); - strcat(msg, team); - } - - if (skill!=-1) - { - strcat(msg, ", with skill "); - strcat(msg, SkillNrToSkillName(skill)); - } - - if (name && name[0]) - { - strcat(msg, " named "); - strcat(msg, name); - } - - sendservmsg(msg); - if(yes/(float)(yes+no) <= 0.5f) return false; - sendservmsg("vote passed"); - resetbotvotes(); -#endif - return true; -}; - -bool kickbotvote(int specific, char *name, int sender) -{ - clients[sender].addbot = false; - clients[sender].kickbot = true; - clients[sender].changebotskill = false; - clients[sender].kickallbots = !specific; - if (name && name[0]) strcpy_s(clients[sender].botname, name); - else clients[sender].botname[0] = 0; - int yes = 0, no = 0; - loopv(clients) if(clients[i].type!=ST_EMPTY) - { - if (clients[i].kickbot) - { - if (clients[sender].kickallbots) - { - if (clients[i].kickallbots) yes++; - else no++; - } - else - { - if (!clients[i].kickallbots && !strcmp(clients[i].botname, name)) - yes++; - else - no++; - } - } - else no++; - } - if(yes==1 && no==0) return true; // single player - - char msg[256]; - if (clients[sender].kickallbots) - sprintf(msg, "%s suggests to kick all bots", clients[sender].name); - else - sprintf(msg, "%s suggests to kick bot %s", clients[sender].name, - clients[sender].botname); - - sendservmsg(msg); - if(yes/(float)(yes+no) <= 0.5f) return false; - sendservmsg("vote passed"); - resetbotvotes(); - return true; -}; - -bool botskillvote(int skill, int sender) -{ -#ifndef STANDALONE - clients[sender].addbot = false; - clients[sender].kickbot = false; - clients[sender].changebotskill = true; - clients[sender].botskill = skill; - int yes = 0, no = 0; - loopv(clients) if(clients[i].type!=ST_EMPTY) - { - if (clients[i].changebotskill) - { - if (clients[sender].botskill == clients[i].botskill) yes++; - else no++; - } - else no++; - } - if(yes==1 && no==0) return true; // single player - - sprintf_sd(msg)("%s suggests to change the skill of all bots to: %s", clients[sender].name, - SkillNrToSkillName(clients[sender].botskill)); - sendservmsg(msg); - if(yes/(float)(yes+no) <= 0.5f) return false; - sendservmsg("vote passed"); - resetbotvotes(); -#endif - return true; -}; - -void botcommand(uchar *&p, char *text, int sender) -{ - int type = getint(p); - switch(EBotCommands(type)) - { - case COMMAND_ADDBOT: - { - string name, team; - int count = getint(p); - int skill = getint(p); - sgetstr(); - strcpy(team, text); - sgetstr(); - strcpy(name, text); -#ifndef STANDALONE - if (addbotvote(count, team, skill, name, sender)) - { - while(count>0) - { - BotManager.CreateBot(team, SkillNrToSkillName(skill), name); - count--; - } - } -#endif - break; - } - case COMMAND_KICKBOT: - { - int specific = getint(p); - - if (specific) - sgetstr(); -#ifndef STANDALONE - if (kickbotvote(specific, text, sender)) - { - if (specific) - kickbot(text); - else - kickallbots(); - } -#endif - } - case COMMAND_BOTSKILL: - { - int skill = getint(p); -#ifndef STANDALONE - if (botskillvote(skill, sender)) - { - BotManager.ChangeBotSkill(skill, NULL); - } -#endif - } - } -} - -// End add - -// force map change, EDIT: AH -void changemap() -{ - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_MAPCHANGE); - sendstring(smapname, p); - putint(p, mode); - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet,p-start); - multicast(packet, -1); - if(packet->referenceCount==0) enet_packet_destroy(packet); -}; - -// server side processing of updates: does very little and most state is tracked client only -// could be extended to move more gameplay to server (at expense of lag) - -int tmp_pos[3]; - -void process(ENetPacket * packet, int sender) // sender may be -1 -{ - if(ENET_NET_TO_HOST_16(*(ushort *)packet->data)!=packet->dataLength) - { - disconnect_client(sender, "packet length"); - return; - }; - - uchar *end = packet->data+packet->dataLength; - uchar *p = packet->data+2; - char text[MAXTRANS]; - int cn = -1, type; - - while(p=clients.length() || clients[cn].type==ST_EMPTY) - { - disconnect_client(sender, "client num"); - return; - }; - int size = msgsizelookup(type); - assert(size!=-1); - loopi(3) tmp_pos[i] = getint(p); - loopi(size-5) getint(p); - break; - }; - - case SV_SENDMAP: - { - sgetstr(); - int mapsize = getint(p); - sendmaps(sender, text, mapsize, p); - return; - } - - case SV_RECVMAP: - send(sender, recvmap(sender)); - return; - - // Added by Rick: Bot specifc messages - case SV_ADDBOT: - getint(p); - sgetstr(); - //strcpy_s(clients[cn].name, text); - sgetstr(); - getint(p); - break; - case SV_BOTCOMMAND: // Client asked server for a bot command - { - botcommand(p, text, sender); - break; - } - // End add by Rick - - // EDIT: AH - case SV_FLAGPICKUP: - { - int flag = getint(p); - if(flag<0 || flag>1) return; - ctfflag &f = ctfflags[flag]; - if(f.state!=CTFF_STOLEN) - { - f.state = CTFF_STOLEN; - f.thief_cn = sender; - sendflaginfo(flag, SV_FLAGPICKUP); - printf("srv_flagpickup\n"); - }; - break; - }; - - case SV_FLAGDROP: - { - int flag = getint(p); - if(flag<0|| flag>1) return; - ctfflag &f = ctfflags[flag]; - if(f.state==CTFF_STOLEN && (sender==-1 || f.thief_cn==sender)) - { - f.state = CTFF_DROPPED; - f.lastupdate = lastsec; - loopi(3) f.pos[i] = tmp_pos[i]; - sendflaginfo(flag, SV_FLAGDROP); - printf("srv_flagdrop\n"); - }; - break; - }; - - case SV_FLAGRETURN: - { - int flag = getint(p); - if(flag<0|| flag>1) return; - ctfflag &f = ctfflags[flag]; - if(f.state==CTFF_DROPPED) - { - f.state = CTFF_INBASE; - f.thief_cn = sender; - sendflaginfo(flag, SV_FLAGRETURN); - printf("srv_flagsreturn\n"); - }; - break; - }; - - case SV_FLAGSCORE: - { - int flag = getint(p); - if(flag<0|| flag>1) return; - ctfflag &f = ctfflags[flag]; - if(f.state==CTFF_STOLEN) - { - f.state = CTFF_INBASE; - f.thief_cn = sender; - sendflaginfo(flag, SV_FLAGSCORE); - printf("srv_flagscore\n"); - }; - break; - }; - -/* - case SV_EXT: // allows for new features that require no server updates - { - for(int n = getint(p); n; n--) getint(p); - break; - }; -*/ - - default: - { - int size = msgsizelookup(type); - if(size==-1) { disconnect_client(sender, "tag type"); return; }; - loopi(size-1) getint(p); - }; - }; - - if(p>end) { disconnect_client(sender, "end of packet"); return; }; - multicast(packet, sender); -}; - -void send_welcome(int n) -{ - ENetPacket * packet = enet_packet_create (NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_INITS2C); - putint(p, n); - putint(p, PROTOCOL_VERSION); - if(!smapname[0] && configsets.length()) nextcfgset(); // EDIT:AH - putint(p, smapname[0]); - sendstring(serverpassword, p); - putint(p, numclients()>maxclients); - if(smapname[0]) - { - putint(p, SV_MAPCHANGE); - sendstring(smapname, p); - putint(p, mode); - putint(p, SV_ITEMLIST); - loopv(sents) if(sents[i].spawned) putint(p, i); - putint(p, -1); - }; - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - send(n, packet); - if(smapname[0] && m_ctf_s) loopi(2) sendflaginfo(i, -1, n); // EDIT: AH -}; - -void multicast(ENetPacket *packet, int sender) -{ - loopv(clients) - { - if(i==sender) continue; - send(i, packet); - }; -}; - -void localclienttoserver(ENetPacket *packet) -{ - process(packet, 0); - if(!packet->referenceCount) enet_packet_destroy (packet); -}; - -client &addclient() -{ - loopv(clients) if(clients[i].type==ST_EMPTY) return clients[i]; - return clients.add(); -}; - -void checkintermission() -{ - if(!minremain) - { - interm = lastsec+10; - mapend = lastsec+1000; - }; - send2(true, -1, SV_TIMEUP, minremain--); -}; - -void startintermission() { minremain = 0; checkintermission(); }; - -void resetserverifempty() -{ - loopv(clients) if(clients[i].type!=ST_EMPTY) return; - clients.setsize(0); - smapname[0] = 0; - resetvotes(); - resetbotvotes(); // Added by Rick - resetitems(); - mode = 0; - mapreload = false; - minremain = 10; - mapend = lastsec+minremain*60; - interm = 0; - ctfreset(false); -}; - -int nonlocalclients = 0; -int lastconnect = 0; - -void serverslice(int seconds, unsigned int timeout) // main server update, called from cube main loop in sp, or dedicated server loop -{ - loopv(sents) // spawn entities when timer reached - { - if(sents[i].spawnsecs && (sents[i].spawnsecs -= seconds-lastsec)<=0) - { - sents[i].spawnsecs = 0; - sents[i].spawned = true; - send2(true, -1, SV_ITEMSPAWN, i); - }; - }; - - if(m_ctf_s) - { - loopi(2) - { - ctfflag &f = ctfflags[i]; - if(f.state==CTFF_DROPPED && seconds-f.lastupdate>30) - { - f.state=CTFF_INBASE; - sendflaginfo(i, -1); - sprintf_sd(msg)("the server reset the %s flag", rb_team_string(i)); - sendservmsg(msg); - send2(true, -1, SV_SOUND, S_FLAGDROP); - }; - }; - }; - - lastsec = seconds; - - if((mode>1 || (mode==0 && nonlocalclients)) && seconds>mapend-minremain*60) checkintermission(); - if(interm && seconds>interm) - { - interm = 0; - - if(configsets.length()) // EDIT: AH - { - nextcfgset(); - mapreload = true; - changemap(); - ctfbroadcast = true; - } - else - loopv(clients) if(clients[i].type!=ST_EMPTY) - { - send2(true, i, SV_MAPRELOAD, 0); // ask a client to trigger map reload - mapreload = true; - break; - }; - }; - - if(ctfbroadcast) - { - ctfreset(); - ctfbroadcast = false; - }; - - resetserverifempty(); - - if(!isdedicated) return; // below is network only - - int numplayers = numclients(); - serverms(mode, numplayers, minremain, smapname, seconds, numclients()>=maxclients); - - if(seconds-laststatus>60) // display bandwidth stats, useful for server ops - { - nonlocalclients = 0; - loopv(clients) if(clients[i].type==ST_TCPIP) nonlocalclients++; - laststatus = seconds; - if(nonlocalclients || bsend || brec) printf("status: %d remote clients, %.1f send, %.1f rec (K/sec)\n", nonlocalclients, bsend/60.0f/1024, brec/60.0f/1024); - bsend = brec = 0; - }; - - ENetEvent event; - if(enet_host_service(serverhost, &event, timeout) > 0) - { - switch(event.type) - { - case ENET_EVENT_TYPE_CONNECT: - { - client &c = addclient(); - c.type = ST_TCPIP; - c.peer = event.peer; - c.peer->data = (void *)(&c-&clients[0]); - char hn[1024]; - strcpy_s(c.hostname, (enet_address_get_host(&c.peer->address, hn, sizeof(hn))==0) ? hn : "localhost"); - printf("client connected (%s)\n", c.hostname); - send_welcome(lastconnect = &c-&clients[0]); - break; - } - case ENET_EVENT_TYPE_RECEIVE: - brec += event.packet->dataLength; - process(event.packet, (int)event.peer->data); - if(event.packet->referenceCount==0) enet_packet_destroy(event.packet); - break; - - case ENET_EVENT_TYPE_DISCONNECT: - if((int)event.peer->data<0) break; - printf("disconnected client (%s)\n", clients[(int)event.peer->data].hostname); - clients[(int)event.peer->data].type = ST_EMPTY; - send2(true, -1, SV_CDIS, (int)event.peer->data); - event.peer->data = (void *)-1; - break; - }; - - if(numplayers>maxclients) - { - disconnect_client(lastconnect, "maxclients reached"); - }; - }; - #ifndef WIN32 - fflush(stdout); - #endif -}; - -void cleanupserver() -{ - if(serverhost) enet_host_destroy(serverhost); -}; - -void localdisconnect() -{ - loopv(clients) if(clients[i].type==ST_LOCAL) clients[i].type = ST_EMPTY; -}; - -void localconnect() -{ - client &c = addclient(); - c.type = ST_LOCAL; - strcpy_s(c.hostname, "local"); - send_welcome(&c-&clients[0]); -}; - -void initserver(bool dedicated, int uprate, char *sdesc, char *ip, char *master, char *passwd, int maxcl, char *maprot) // EDIT: AH -{ - serverpassword = passwd; - maxclients = maxcl; - servermsinit(master ? master : "cubebot.bots-united.com/cube/masterserver.cgi/", sdesc, dedicated); - - if(isdedicated = dedicated) - { - ENetAddress address = { ENET_HOST_ANY, CUBE_SERVER_PORT }; - if(*ip && enet_address_set_host(&address, ip)<0) printf("WARNING: server ip not resolved"); - serverhost = enet_host_create(&address, MAXCLIENTS, 0, uprate); - if(!serverhost) fatal("could not create server host\n"); - loopi(MAXCLIENTS) serverhost->peers[i].data = (void *)-1; - if(maprot) readscfg(maprot); // EDIT: AH - }; - - resetserverifempty(); - - if(isdedicated) // do not return, this becomes main loop - { - #ifdef WIN32 - SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); - #endif - printf("dedicated server started, waiting for clients...\nCtrl-C to exit\n\n"); - atexit(cleanupserver); - atexit(enet_deinitialize); - for(;;) serverslice(/*enet_time_get_sec()*/time(NULL), 5); - }; -}; +// server.cpp: little more than enhanced multicaster +// runs dedicated or as client coroutine + +#include "cube.h" + +#define m_ctf_s (mode==5) + +enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; + +struct client // server side version of "dynent" type +{ + int type; + ENetPeer *peer; + string hostname; + string mapvote; + string name; + int modevote; + // Added by Rick: For bot voting + bool addbot, kickbot, changebotskill; + short botcount; + string botteam, botname; + short botskill; + bool kickallbots; + // End add +}; + +vector clients; + +int maxclients = 8; +string smapname; + +int numclients() +{ + int num = 0; + loopv(clients) if(clients[i].type!=ST_EMPTY) i++; + return num; +}; + +vector sents; + +bool notgotitems = true; // true when map has changed and waiting for clients to send item +int mode = 0; + +// Added by Rick +#ifndef STANDALONE +extern void kickbot(const char *szName); +extern void kickallbots(void); +#endif +// End add + +void restoreserverstate(vector &ents) // hack: called from savegame code, only works in SP +{ + loopv(sents) + { + sents[i].spawned = ents[i].spawned; + sents[i].spawnsecs = 0; + }; +}; + +int interm = 0, minremain = 0, mapend = 0; +bool mapreload = false; + +char *serverpassword = ""; + +bool isdedicated; +ENetHost * serverhost = NULL; +int bsend = 0, brec = 0, laststatus = 0, lastsec = 0; + +#define MAXOBUF 100000 + +void process(ENetPacket *packet, int sender); +void multicast(ENetPacket *packet, int sender); +void disconnect_client(int n, char *reason); + +void send(int n, ENetPacket *packet) +{ + if(!packet) return; + switch(clients[n].type) + { + case ST_TCPIP: + { + enet_peer_send(clients[n].peer, 0, packet); + bsend += packet->dataLength; + break; + }; + + case ST_LOCAL: + localservertoclient(packet->data, packet->dataLength); + break; + + }; +}; + +void send2(bool rel, int cn, int a, int b) +{ + ENetPacket *packet = enet_packet_create(NULL, 32, rel ? ENET_PACKET_FLAG_RELIABLE : 0); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, a); + putint(p, b); + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + if(cn<0) process(packet, -1); + else send(cn, packet); + if(packet->referenceCount==0) enet_packet_destroy(packet); +}; + +struct ctfflag +{ + int state; + int thief_cn; + int pos[3]; + int lastupdate; +} ctfflags[2]; + +bool ctfbroadcast = false; + +void sendflaginfo(int flag, int action, int cn = -1) +{ + ctfflag &f = ctfflags[flag]; + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_FLAGINFO); + putint(p, flag); printf("senflaginfo flag: %i\n", flag); + putint(p, f.state); printf("senflaginfo state: %i\n", f.state); + putint(p, action); printf("senflaginfo action: %i\n", action); + if(f.state==CTFF_STOLEN || action==SV_FLAGRETURN) putint(p, f.thief_cn); + else if(f.state==CTFF_DROPPED) loopi(3) putint(p, f.pos[i]); + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet,p-start); + if(cn<0) multicast(packet, -1); + else send(cn, packet); + if(packet->referenceCount==0) enet_packet_destroy(packet); +}; + +void ctfreset(bool send=true) +{ + if(!m_ctf_s) return; + loopi(2) + { + ctfflags[i].thief_cn = 0; + ctfflags[i].state = CTFF_INBASE; + ctfflags[i].lastupdate = -1; + if(send) sendflaginfo(i, -1); + }; +} + +void sendservmsg(char *msg) +{ + ENetPacket *packet = enet_packet_create(NULL, _MAXDEFSTR+10, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_SERVMSG); + sendstring(msg, p); + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + multicast(packet, -1); + if(packet->referenceCount==0) enet_packet_destroy(packet); +}; + +void disconnect_client(int n, char *reason) +{ + if(n<0 || n>=clients.length()) return; + printf("disconnecting client (%s) [%s]\n", clients[n].hostname, reason); + enet_peer_disconnect(clients[n].peer); + clients[n].type = ST_EMPTY; + send2(true, -1, SV_CDIS, n); +}; + +void resetitems() { sents.setsize(0); notgotitems = true; }; + +void pickup(uint i, int sec, int sender) // server side item pickup, acknowledge first client that gets it +{ + if(i>=(uint)sents.length()) return; + if(sents[i].spawned) + { + sents[i].spawned = false; + sents[i].spawnsecs = sec; + send2(true, sender, SV_ITEMACC, i); + }; +}; + +// EDIT: AH +struct configset +{ + string mapname; + int mode; + int time; + bool vote; +}; + +vector configsets; +int curcfgset = -1; + +void readscfg(char *cfg) +{ + configsets.setsize(0); + + string s; + strcpy_s(s, cfg); + char *buf = loadfile(path(s), NULL); + if(!buf) return; + char *p, *l; + + p = buf; + while((p = strstr(p, "//")) != NULL) // remove comments + while(p[0] != '\n' && p[0] != '\0') p++[0] = ' '; + + l = buf; + bool lastline = false; + while((p = strstr(l, "\n")) != NULL || (l && (lastline=true))) // remove empty/invalid lines + { + int len = lastline ? strlen(l) : p-l; + string line; + strn0cpy(line, l, len+1); + char *d = line; + int n = 0; + while((p = strstr(d, ":")) != NULL && (d = p+1)) n++; + if(n!=3) memset(l, ' ', len+1); + if(lastline) break; + l += len+1; + }; + + configset c; + int argc = 0; + string argv[4]; + + p = strtok(buf, ":\n\0"); + while(p != NULL) + { + strcpy(argv[argc], p); + if(++argc==4) + { + int numspaces; + for(numspaces = 0; argv[0][numspaces]==' '; numspaces++){}; // ingore space crap + strcpy(c.mapname, argv[0]+numspaces); + c.mode = atoi(argv[1]); + c.time = atoi(argv[2]); + c.vote = (bool) atoi(argv[3]); + configsets.add(c); + argc = 0; + }; + p = strtok(NULL, ":\n\0"); + }; +}; + +void nextcfgset() // load next maprotation set +{ + curcfgset++; + if(curcfgset>=configsets.length() || curcfgset<0) curcfgset=0; + + printf("nextcfgset: %i\n", curcfgset); + + configset &c = configsets[curcfgset]; + mode = c.mode; + minremain = c.time; + strcpy_s(smapname, c.mapname); + + mapend = lastsec+minremain*60; + mapreload = false; + interm = 0; + laststatus = lastsec-61; + resetitems(); +}; + +void resetvotes() +{ + loopv(clients) clients[i].mapvote[0] = 0; +}; + +bool vote(char *map, int reqmode, int sender) +{ + if(configsets.length() && curcfgset < configsets.length() && !configsets[curcfgset].vote) + { + sprintf_sd(msg)("%s voted, but voting is currently disabled", clients[sender].name); + sendservmsg(msg); + return false; + }; + strcpy_s(clients[sender].mapvote, map); + clients[sender].modevote = reqmode; + int yes = 0, no = 0; + loopv(clients) if(clients[i].type!=ST_EMPTY) + { + if(clients[i].mapvote[0]) { if(strcmp(clients[i].mapvote, map)==0 && clients[i].modevote==reqmode) yes++; else no++; } + else no++; + }; + if(yes==1 && no==0) return true; // single player + sprintf_sd(msg)("%s suggests %s on map %s (set map to vote)", clients[sender].name, modestr(reqmode), map); + sendservmsg(msg); + if(yes/(float)(yes+no) <= 0.5f) return false; + sendservmsg("vote passed"); + resetvotes(); + return true; +}; + +// Added by Rick: Vote for bot commands + +void resetbotvotes() +{ + loopv(clients) + { + clients[i].addbot = false; + clients[i].kickbot = false; + clients[i].changebotskill = false; + clients[i].kickallbots = false; + clients[i].botcount = 0; + clients[i].botteam[0] = 0; + clients[i].botname[0] = 0; + clients[i].botskill = -1; + } +}; + +bool addbotvote(int count, char *team, int skill, char *name, int sender) +{ +#ifndef STANDALONE + clients[sender].addbot = true; + clients[sender].kickbot = false; + clients[sender].changebotskill = false; + clients[sender].botcount = (count>=0) ? count : 0; + if (team && team[0]) strcpy_s(clients[sender].botteam, team); + else clients[sender].botteam[0] = 0; + clients[sender].botskill = skill; + if (name && name[0]) strcpy_s(clients[sender].botname, name); + else clients[sender].botname[0] = 0; + int yes = 0, no = 0; + loopv(clients) if(clients[i].type!=ST_EMPTY) + { + if (clients[i].addbot) + { + if (!strcmp(clients[i].botteam, team) && !strcmp(clients[i].botname, name) && + (clients[i].botskill == skill) && + (clients[i].botcount == clients[sender].botcount)) + yes++; + else + no++; + } + else no++; + } + if(yes==1 && no==0) return true; // single player + sprintf_sd(msg)("%s suggests to add a bot", clients[sender].name); + if (team && team[0]) + { + strcat(msg, " on team "); + strcat(msg, team); + } + + if (skill!=-1) + { + strcat(msg, ", with skill "); + strcat(msg, SkillNrToSkillName(skill)); + } + + if (name && name[0]) + { + strcat(msg, " named "); + strcat(msg, name); + } + + sendservmsg(msg); + if(yes/(float)(yes+no) <= 0.5f) return false; + sendservmsg("vote passed"); + resetbotvotes(); +#endif + return true; +}; + +bool kickbotvote(int specific, char *name, int sender) +{ + clients[sender].addbot = false; + clients[sender].kickbot = true; + clients[sender].changebotskill = false; + clients[sender].kickallbots = !specific; + if (name && name[0]) strcpy_s(clients[sender].botname, name); + else clients[sender].botname[0] = 0; + int yes = 0, no = 0; + loopv(clients) if(clients[i].type!=ST_EMPTY) + { + if (clients[i].kickbot) + { + if (clients[sender].kickallbots) + { + if (clients[i].kickallbots) yes++; + else no++; + } + else + { + if (!clients[i].kickallbots && !strcmp(clients[i].botname, name)) + yes++; + else + no++; + } + } + else no++; + } + if(yes==1 && no==0) return true; // single player + + char msg[256]; + if (clients[sender].kickallbots) + sprintf(msg, "%s suggests to kick all bots", clients[sender].name); + else + sprintf(msg, "%s suggests to kick bot %s", clients[sender].name, + clients[sender].botname); + + sendservmsg(msg); + if(yes/(float)(yes+no) <= 0.5f) return false; + sendservmsg("vote passed"); + resetbotvotes(); + return true; +}; + +bool botskillvote(int skill, int sender) +{ +#ifndef STANDALONE + clients[sender].addbot = false; + clients[sender].kickbot = false; + clients[sender].changebotskill = true; + clients[sender].botskill = skill; + int yes = 0, no = 0; + loopv(clients) if(clients[i].type!=ST_EMPTY) + { + if (clients[i].changebotskill) + { + if (clients[sender].botskill == clients[i].botskill) yes++; + else no++; + } + else no++; + } + if(yes==1 && no==0) return true; // single player + + sprintf_sd(msg)("%s suggests to change the skill of all bots to: %s", clients[sender].name, + SkillNrToSkillName(clients[sender].botskill)); + sendservmsg(msg); + if(yes/(float)(yes+no) <= 0.5f) return false; + sendservmsg("vote passed"); + resetbotvotes(); +#endif + return true; +}; + +void botcommand(uchar *&p, char *text, int sender) +{ + int type = getint(p); + switch(EBotCommands(type)) + { + case COMMAND_ADDBOT: + { + string name, team; + int count = getint(p); + int skill = getint(p); + sgetstr(); + strcpy(team, text); + sgetstr(); + strcpy(name, text); +#ifndef STANDALONE + if (addbotvote(count, team, skill, name, sender)) + { + while(count>0) + { + BotManager.CreateBot(team, SkillNrToSkillName(skill), name); + count--; + } + } +#endif + break; + } + case COMMAND_KICKBOT: + { + int specific = getint(p); + + if (specific) + sgetstr(); +#ifndef STANDALONE + if (kickbotvote(specific, text, sender)) + { + if (specific) + kickbot(text); + else + kickallbots(); + } +#endif + } + case COMMAND_BOTSKILL: + { + int skill = getint(p); +#ifndef STANDALONE + if (botskillvote(skill, sender)) + { + BotManager.ChangeBotSkill(skill, NULL); + } +#endif + } + } +} + +// End add + +// force map change, EDIT: AH +void changemap() +{ + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_MAPCHANGE); + sendstring(smapname, p); + putint(p, mode); + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet,p-start); + multicast(packet, -1); + if(packet->referenceCount==0) enet_packet_destroy(packet); +}; + +// server side processing of updates: does very little and most state is tracked client only +// could be extended to move more gameplay to server (at expense of lag) + +int tmp_pos[3]; + +void process(ENetPacket * packet, int sender) // sender may be -1 +{ + if(ENET_NET_TO_HOST_16(*(ushort *)packet->data)!=packet->dataLength) + { + disconnect_client(sender, "packet length"); + return; + }; + + uchar *end = packet->data+packet->dataLength; + uchar *p = packet->data+2; + char text[MAXTRANS]; + int cn = -1, type; + + while(p=clients.length() || clients[cn].type==ST_EMPTY) + { + disconnect_client(sender, "client num"); + return; + }; + int size = msgsizelookup(type); + assert(size!=-1); + loopi(3) tmp_pos[i] = getint(p); + loopi(size-5) getint(p); + break; + }; + + case SV_SENDMAP: + { + sgetstr(); + int mapsize = getint(p); + sendmaps(sender, text, mapsize, p); + return; + } + + case SV_RECVMAP: + send(sender, recvmap(sender)); + return; + + // Added by Rick: Bot specifc messages + case SV_ADDBOT: + getint(p); + sgetstr(); + //strcpy_s(clients[cn].name, text); + sgetstr(); + getint(p); + break; + case SV_BOTCOMMAND: // Client asked server for a bot command + { + botcommand(p, text, sender); + break; + } + // End add by Rick + + // EDIT: AH + case SV_FLAGPICKUP: + { + int flag = getint(p); + if(flag<0 || flag>1) return; + ctfflag &f = ctfflags[flag]; + if(f.state!=CTFF_STOLEN) + { + f.state = CTFF_STOLEN; + f.thief_cn = sender; + sendflaginfo(flag, SV_FLAGPICKUP); + printf("srv_flagpickup\n"); + }; + break; + }; + + case SV_FLAGDROP: + { + int flag = getint(p); + if(flag<0|| flag>1) return; + ctfflag &f = ctfflags[flag]; + if(f.state==CTFF_STOLEN && (sender==-1 || f.thief_cn==sender)) + { + f.state = CTFF_DROPPED; + f.lastupdate = lastsec; + loopi(3) f.pos[i] = tmp_pos[i]; + sendflaginfo(flag, SV_FLAGDROP); + printf("srv_flagdrop\n"); + }; + break; + }; + + case SV_FLAGRETURN: + { + int flag = getint(p); + if(flag<0|| flag>1) return; + ctfflag &f = ctfflags[flag]; + if(f.state==CTFF_DROPPED) + { + f.state = CTFF_INBASE; + f.thief_cn = sender; + sendflaginfo(flag, SV_FLAGRETURN); + printf("srv_flagsreturn\n"); + }; + break; + }; + + case SV_FLAGSCORE: + { + int flag = getint(p); + if(flag<0|| flag>1) return; + ctfflag &f = ctfflags[flag]; + if(f.state==CTFF_STOLEN) + { + f.state = CTFF_INBASE; + f.thief_cn = sender; + sendflaginfo(flag, SV_FLAGSCORE); + printf("srv_flagscore\n"); + }; + break; + }; + +/* + case SV_EXT: // allows for new features that require no server updates + { + for(int n = getint(p); n; n--) getint(p); + break; + }; +*/ + + default: + { + int size = msgsizelookup(type); + if(size==-1) { disconnect_client(sender, "tag type"); return; }; + loopi(size-1) getint(p); + }; + }; + + if(p>end) { disconnect_client(sender, "end of packet"); return; }; + multicast(packet, sender); +}; + +void send_welcome(int n) +{ + ENetPacket * packet = enet_packet_create (NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_INITS2C); + putint(p, n); + putint(p, PROTOCOL_VERSION); + if(!smapname[0] && configsets.length()) nextcfgset(); // EDIT:AH + putint(p, smapname[0]); + sendstring(serverpassword, p); + putint(p, numclients()>maxclients); + if(smapname[0]) + { + putint(p, SV_MAPCHANGE); + sendstring(smapname, p); + putint(p, mode); + putint(p, SV_ITEMLIST); + loopv(sents) if(sents[i].spawned) putint(p, i); + putint(p, -1); + }; + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + send(n, packet); + if(smapname[0] && m_ctf_s) loopi(2) sendflaginfo(i, -1, n); // EDIT: AH +}; + +void multicast(ENetPacket *packet, int sender) +{ + loopv(clients) + { + if(i==sender) continue; + send(i, packet); + }; +}; + +void localclienttoserver(ENetPacket *packet) +{ + process(packet, 0); + if(!packet->referenceCount) enet_packet_destroy (packet); +}; + +client &addclient() +{ + loopv(clients) if(clients[i].type==ST_EMPTY) return clients[i]; + return clients.add(); +}; + +void checkintermission() +{ + if(!minremain) + { + interm = lastsec+10; + mapend = lastsec+1000; + }; + send2(true, -1, SV_TIMEUP, minremain--); +}; + +void startintermission() { minremain = 0; checkintermission(); }; + +void resetserverifempty() +{ + loopv(clients) if(clients[i].type!=ST_EMPTY) return; + clients.setsize(0); + smapname[0] = 0; + resetvotes(); + resetbotvotes(); // Added by Rick + resetitems(); + mode = 0; + mapreload = false; + minremain = 10; + mapend = lastsec+minremain*60; + interm = 0; + ctfreset(false); +}; + +int nonlocalclients = 0; +int lastconnect = 0; + +void serverslice(int seconds, unsigned int timeout) // main server update, called from cube main loop in sp, or dedicated server loop +{ + loopv(sents) // spawn entities when timer reached + { + if(sents[i].spawnsecs && (sents[i].spawnsecs -= seconds-lastsec)<=0) + { + sents[i].spawnsecs = 0; + sents[i].spawned = true; + send2(true, -1, SV_ITEMSPAWN, i); + }; + }; + + if(m_ctf_s) + { + loopi(2) + { + ctfflag &f = ctfflags[i]; + if(f.state==CTFF_DROPPED && seconds-f.lastupdate>30) + { + f.state=CTFF_INBASE; + sendflaginfo(i, -1); + sprintf_sd(msg)("the server reset the %s flag", rb_team_string(i)); + sendservmsg(msg); + send2(true, -1, SV_SOUND, S_FLAGDROP); + }; + }; + }; + + lastsec = seconds; + + if((mode>1 || (mode==0 && nonlocalclients)) && seconds>mapend-minremain*60) checkintermission(); + if(interm && seconds>interm) + { + interm = 0; + + if(configsets.length()) // EDIT: AH + { + nextcfgset(); + mapreload = true; + changemap(); + ctfbroadcast = true; + } + else + loopv(clients) if(clients[i].type!=ST_EMPTY) + { + send2(true, i, SV_MAPRELOAD, 0); // ask a client to trigger map reload + mapreload = true; + break; + }; + }; + + if(ctfbroadcast) + { + ctfreset(); + ctfbroadcast = false; + }; + + resetserverifempty(); + + if(!isdedicated) return; // below is network only + + int numplayers = numclients(); + serverms(mode, numplayers, minremain, smapname, seconds, numclients()>=maxclients); + + if(seconds-laststatus>60) // display bandwidth stats, useful for server ops + { + nonlocalclients = 0; + loopv(clients) if(clients[i].type==ST_TCPIP) nonlocalclients++; + laststatus = seconds; + if(nonlocalclients || bsend || brec) printf("status: %d remote clients, %.1f send, %.1f rec (K/sec)\n", nonlocalclients, bsend/60.0f/1024, brec/60.0f/1024); + bsend = brec = 0; + }; + + ENetEvent event; + if(enet_host_service(serverhost, &event, timeout) > 0) + { + switch(event.type) + { + case ENET_EVENT_TYPE_CONNECT: + { + client &c = addclient(); + c.type = ST_TCPIP; + c.peer = event.peer; + c.peer->data = (void *)(&c-&clients[0]); + char hn[1024]; + strcpy_s(c.hostname, (enet_address_get_host(&c.peer->address, hn, sizeof(hn))==0) ? hn : "localhost"); + printf("client connected (%s)\n", c.hostname); + send_welcome(lastconnect = &c-&clients[0]); + break; + } + case ENET_EVENT_TYPE_RECEIVE: + brec += event.packet->dataLength; + process(event.packet, (int)event.peer->data); + if(event.packet->referenceCount==0) enet_packet_destroy(event.packet); + break; + + case ENET_EVENT_TYPE_DISCONNECT: + if((int)event.peer->data<0) break; + printf("disconnected client (%s)\n", clients[(int)event.peer->data].hostname); + clients[(int)event.peer->data].type = ST_EMPTY; + send2(true, -1, SV_CDIS, (int)event.peer->data); + event.peer->data = (void *)-1; + break; + }; + + if(numplayers>maxclients) + { + disconnect_client(lastconnect, "maxclients reached"); + }; + }; + #ifndef WIN32 + fflush(stdout); + #endif +}; + +void cleanupserver() +{ + if(serverhost) enet_host_destroy(serverhost); +}; + +void localdisconnect() +{ + loopv(clients) if(clients[i].type==ST_LOCAL) clients[i].type = ST_EMPTY; +}; + +void localconnect() +{ + client &c = addclient(); + c.type = ST_LOCAL; + strcpy_s(c.hostname, "local"); + send_welcome(&c-&clients[0]); +}; + +void initserver(bool dedicated, int uprate, char *sdesc, char *ip, char *master, char *passwd, int maxcl, char *maprot) // EDIT: AH +{ + serverpassword = passwd; + maxclients = maxcl; + servermsinit(master ? master : "cubebot.bots-united.com/cube/masterserver.cgi/", sdesc, dedicated); + + if(isdedicated = dedicated) + { + ENetAddress address = { ENET_HOST_ANY, CUBE_SERVER_PORT }; + if(*ip && enet_address_set_host(&address, ip)<0) printf("WARNING: server ip not resolved"); + serverhost = enet_host_create(&address, MAXCLIENTS, 0, uprate); + if(!serverhost) fatal("could not create server host\n"); + loopi(MAXCLIENTS) serverhost->peers[i].data = (void *)-1; + if(maprot) readscfg(maprot); // EDIT: AH + }; + + resetserverifempty(); + + if(isdedicated) // do not return, this becomes main loop + { + #ifdef WIN32 + SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); + #endif + printf("dedicated server started, waiting for clients...\nCtrl-C to exit\n\n"); + atexit(cleanupserver); + atexit(enet_deinitialize); + for(;;) serverslice(/*enet_time_get_sec()*/time(NULL), 5); + }; +}; diff --git a/source/src/serverbrowser.cpp b/source/src/serverbrowser.cpp index 01694be8..c36a695c 100644 --- a/source/src/serverbrowser.cpp +++ b/source/src/serverbrowser.cpp @@ -1,303 +1,303 @@ -// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window management - -#include "cube.h" -#include "SDL_thread.h" - -struct resolverthread -{ - SDL_Thread *thread; - char *query; - int starttime; -}; - -struct resolverresult -{ - char *query; - ENetAddress address; -}; - -vector resolverthreads; -vector resolverqueries; -vector resolverresults; -SDL_mutex *resolvermutex; -SDL_sem *resolversem; -int resolverlimit = 1000; - -int resolverloop(void * data) -{ - resolverthread *rt = (resolverthread *)data; - for(;;) - { - SDL_SemWait(resolversem); - SDL_LockMutex(resolvermutex); - if(resolverqueries.empty()) - { - SDL_UnlockMutex(resolvermutex); - continue; - } - rt->query = resolverqueries.pop(); - rt->starttime = lastmillis; - SDL_UnlockMutex(resolvermutex); - ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; - enet_address_set_host(&address, rt->query); - SDL_LockMutex(resolvermutex); - resolverresult &rr = resolverresults.add(); - rr.query = rt->query; - rr.address = address; - rt->query = NULL; - rt->starttime = 0; - SDL_UnlockMutex(resolvermutex); - }; - return 0; -}; - -void resolverinit(int threads, int limit) -{ - resolverlimit = limit; - resolversem = SDL_CreateSemaphore(0); - resolvermutex = SDL_CreateMutex(); - - while(threads > 0) - { - resolverthread &rt = resolverthreads.add(); - rt.query = NULL; - rt.starttime = 0; - rt.thread = SDL_CreateThread(resolverloop, &rt); - --threads; - }; -}; - -void resolverstop(resolverthread &rt, bool restart) -{ - SDL_LockMutex(resolvermutex); - SDL_KillThread(rt.thread); - rt.query = NULL; - rt.starttime = 0; - rt.thread = NULL; - if(restart) rt.thread = SDL_CreateThread(resolverloop, &rt); - SDL_UnlockMutex(resolvermutex); -}; - -void resolverclear() -{ - SDL_LockMutex(resolvermutex); - resolverqueries.setsize(0); - resolverresults.setsize(0); - while (SDL_SemTryWait(resolversem) == 0); - loopv(resolverthreads) - { - resolverthread &rt = resolverthreads[i]; - resolverstop(rt, true); - }; - SDL_UnlockMutex(resolvermutex); -}; - -void resolverquery(char *name) -{ - SDL_LockMutex(resolvermutex); - resolverqueries.add(name); - SDL_SemPost(resolversem); - SDL_UnlockMutex(resolvermutex); -}; - -bool resolvercheck(char **name, ENetAddress *address) -{ - SDL_LockMutex(resolvermutex); - if(!resolverresults.empty()) - { - resolverresult &rr = resolverresults.pop(); - *name = rr.query; - *address = rr.address; - SDL_UnlockMutex(resolvermutex); - return true; - } - loopv(resolverthreads) - { - resolverthread &rt = resolverthreads[i]; - if(rt.query) - { - if(lastmillis - rt.starttime > resolverlimit) - { - resolverstop(rt, true); - *name = rt.query; - SDL_UnlockMutex(resolvermutex); - return true; - }; - }; - }; - SDL_UnlockMutex(resolvermutex); - return false; -}; - -struct serverinfo -{ - string name; - string full; - string map; - string sdesc; - int mode, numplayers, ping, protocol, minremain; - ENetAddress address; -}; - -vector servers; -ENetSocket pingsock = ENET_SOCKET_NULL; -int lastinfo = 0; - -char *getservername(int n) { return servers[n].name; }; - -void addserver(char *servername) -{ - loopv(servers) if(strcmp(servers[i].name, servername)==0) return; - serverinfo &si = servers.insert(0, serverinfo()); - strcpy_s(si.name, servername); - si.full[0] = 0; - si.mode = 0; - si.numplayers = 0; - si.ping = 9999; - si.protocol = 0; - si.minremain = 0; - si.map[0] = 0; - si.sdesc[0] = 0; - si.address.host = ENET_HOST_ANY; - si.address.port = CUBE_SERVINFO_PORT; -}; - -void pingservers() -{ - ENetBuffer buf; - uchar ping[MAXTRANS]; - uchar *p; - loopv(servers) - { - serverinfo &si = servers[i]; - if(si.address.host == ENET_HOST_ANY) continue; - p = ping; - putint(p, lastmillis); - buf.data = ping; - buf.dataLength = p - ping; - enet_socket_send(pingsock, &si.address, &buf, 1); - }; - lastinfo = lastmillis; -}; - -void checkresolver() -{ - char *name = NULL; - ENetAddress addr = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; - while(resolvercheck(&name, &addr)) - { - if(addr.host == ENET_HOST_ANY) continue; - loopv(servers) - { - serverinfo &si = servers[i]; - if(name == si.name) - { - si.address = addr; - addr.host = ENET_HOST_ANY; - break; - } - } - } -} - -void checkpings() -{ - enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; - ENetBuffer buf; - ENetAddress addr; - uchar ping[MAXTRANS], *p; - char text[MAXTRANS]; - buf.data = ping; - buf.dataLength = sizeof(ping); - while(enet_socket_wait(pingsock, &events, 0) >= 0 && events) - { - if(enet_socket_receive(pingsock, &addr, &buf, 1) <= 0) return; - loopv(servers) - { - serverinfo &si = servers[i]; - if(addr.host == si.address.host) - { - p = ping; - si.ping = lastmillis - getint(p); - si.protocol = getint(p); - if(si.protocol!=PROTOCOL_VERSION) si.ping = 9998; - si.mode = getint(p); - si.numplayers = getint(p); - si.minremain = getint(p); - sgetstr(); - strcpy_s(si.map, text); - sgetstr(); - strcpy_s(si.sdesc, text); - break; - }; - }; - }; -}; - -int sicompare(const serverinfo *a, const serverinfo *b) -{ - return a->ping>b->ping ? 1 : (a->pingping ? -1 : strcmp(a->name, b->name)); -}; - -void refreshservers() -{ - checkresolver(); - checkpings(); - if(lastmillis - lastinfo >= 5000) pingservers(); - servers.sort((void *)sicompare); - int maxmenu = 16; - loopv(servers) - { - serverinfo &si = servers[i]; - if(si.address.host != ENET_HOST_ANY && si.ping != 9999) - { - if(si.protocol!=PROTOCOL_VERSION) sprintf_s(si.full)("%s [different cube protocol]", si.name); - else sprintf_s(si.full)("%d\t%d\t%s, %s: %s %s", si.ping, si.numplayers, si.map[0] ? si.map : "[unknown]", modestr(si.mode), si.name, si.sdesc); - } - else - { - sprintf_s(si.full)(si.address.host != ENET_HOST_ANY ? "%s [waiting for server response]" : "%s [unknown host]\t", si.name); - } - si.full[50] = 0; // cut off too long server descriptions - menumanual(1, i, si.full); - if(!--maxmenu) return; - }; -}; - -void servermenu() -{ - if(pingsock == ENET_SOCKET_NULL) - { - pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL); - resolverinit(1, 1000); - }; - resolverclear(); - loopv(servers) resolverquery(servers[i].name); - refreshservers(); - menuset(1); -}; - -void updatefrommaster() -{ - const int MAXUPD = 32000; - uchar buf[MAXUPD]; - uchar *reply = retrieveservers(buf, MAXUPD); - if(!*reply || strstr((char *)reply, "") || strstr((char *)reply, "")) conoutf("master server not replying"); - else { servers.setsize(0); execute((char *)reply); }; - servermenu(); -}; - -COMMAND(addserver, ARG_1STR); -COMMAND(servermenu, ARG_NONE); -COMMAND(updatefrommaster, ARG_NONE); - -void writeservercfg() -{ - FILE *f = fopen("servers.cfg", "w"); - if(!f) return; - fprintf(f, "// servers connected to are added here automatically\n\n"); - loopvrev(servers) fprintf(f, "addserver %s\n", servers[i].name); - fclose(f); -}; - - +// serverbrowser.cpp: eihrul's concurrent resolver, and server browser window management + +#include "cube.h" +#include "SDL_thread.h" + +struct resolverthread +{ + SDL_Thread *thread; + char *query; + int starttime; +}; + +struct resolverresult +{ + char *query; + ENetAddress address; +}; + +vector resolverthreads; +vector resolverqueries; +vector resolverresults; +SDL_mutex *resolvermutex; +SDL_sem *resolversem; +int resolverlimit = 1000; + +int resolverloop(void * data) +{ + resolverthread *rt = (resolverthread *)data; + for(;;) + { + SDL_SemWait(resolversem); + SDL_LockMutex(resolvermutex); + if(resolverqueries.empty()) + { + SDL_UnlockMutex(resolvermutex); + continue; + } + rt->query = resolverqueries.pop(); + rt->starttime = lastmillis; + SDL_UnlockMutex(resolvermutex); + ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; + enet_address_set_host(&address, rt->query); + SDL_LockMutex(resolvermutex); + resolverresult &rr = resolverresults.add(); + rr.query = rt->query; + rr.address = address; + rt->query = NULL; + rt->starttime = 0; + SDL_UnlockMutex(resolvermutex); + }; + return 0; +}; + +void resolverinit(int threads, int limit) +{ + resolverlimit = limit; + resolversem = SDL_CreateSemaphore(0); + resolvermutex = SDL_CreateMutex(); + + while(threads > 0) + { + resolverthread &rt = resolverthreads.add(); + rt.query = NULL; + rt.starttime = 0; + rt.thread = SDL_CreateThread(resolverloop, &rt); + --threads; + }; +}; + +void resolverstop(resolverthread &rt, bool restart) +{ + SDL_LockMutex(resolvermutex); + SDL_KillThread(rt.thread); + rt.query = NULL; + rt.starttime = 0; + rt.thread = NULL; + if(restart) rt.thread = SDL_CreateThread(resolverloop, &rt); + SDL_UnlockMutex(resolvermutex); +}; + +void resolverclear() +{ + SDL_LockMutex(resolvermutex); + resolverqueries.setsize(0); + resolverresults.setsize(0); + while (SDL_SemTryWait(resolversem) == 0); + loopv(resolverthreads) + { + resolverthread &rt = resolverthreads[i]; + resolverstop(rt, true); + }; + SDL_UnlockMutex(resolvermutex); +}; + +void resolverquery(char *name) +{ + SDL_LockMutex(resolvermutex); + resolverqueries.add(name); + SDL_SemPost(resolversem); + SDL_UnlockMutex(resolvermutex); +}; + +bool resolvercheck(char **name, ENetAddress *address) +{ + SDL_LockMutex(resolvermutex); + if(!resolverresults.empty()) + { + resolverresult &rr = resolverresults.pop(); + *name = rr.query; + *address = rr.address; + SDL_UnlockMutex(resolvermutex); + return true; + } + loopv(resolverthreads) + { + resolverthread &rt = resolverthreads[i]; + if(rt.query) + { + if(lastmillis - rt.starttime > resolverlimit) + { + resolverstop(rt, true); + *name = rt.query; + SDL_UnlockMutex(resolvermutex); + return true; + }; + }; + }; + SDL_UnlockMutex(resolvermutex); + return false; +}; + +struct serverinfo +{ + string name; + string full; + string map; + string sdesc; + int mode, numplayers, ping, protocol, minremain; + ENetAddress address; +}; + +vector servers; +ENetSocket pingsock = ENET_SOCKET_NULL; +int lastinfo = 0; + +char *getservername(int n) { return servers[n].name; }; + +void addserver(char *servername) +{ + loopv(servers) if(strcmp(servers[i].name, servername)==0) return; + serverinfo &si = servers.insert(0, serverinfo()); + strcpy_s(si.name, servername); + si.full[0] = 0; + si.mode = 0; + si.numplayers = 0; + si.ping = 9999; + si.protocol = 0; + si.minremain = 0; + si.map[0] = 0; + si.sdesc[0] = 0; + si.address.host = ENET_HOST_ANY; + si.address.port = CUBE_SERVINFO_PORT; +}; + +void pingservers() +{ + ENetBuffer buf; + uchar ping[MAXTRANS]; + uchar *p; + loopv(servers) + { + serverinfo &si = servers[i]; + if(si.address.host == ENET_HOST_ANY) continue; + p = ping; + putint(p, lastmillis); + buf.data = ping; + buf.dataLength = p - ping; + enet_socket_send(pingsock, &si.address, &buf, 1); + }; + lastinfo = lastmillis; +}; + +void checkresolver() +{ + char *name = NULL; + ENetAddress addr = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; + while(resolvercheck(&name, &addr)) + { + if(addr.host == ENET_HOST_ANY) continue; + loopv(servers) + { + serverinfo &si = servers[i]; + if(name == si.name) + { + si.address = addr; + addr.host = ENET_HOST_ANY; + break; + } + } + } +} + +void checkpings() +{ + enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; + ENetBuffer buf; + ENetAddress addr; + uchar ping[MAXTRANS], *p; + char text[MAXTRANS]; + buf.data = ping; + buf.dataLength = sizeof(ping); + while(enet_socket_wait(pingsock, &events, 0) >= 0 && events) + { + if(enet_socket_receive(pingsock, &addr, &buf, 1) <= 0) return; + loopv(servers) + { + serverinfo &si = servers[i]; + if(addr.host == si.address.host) + { + p = ping; + si.ping = lastmillis - getint(p); + si.protocol = getint(p); + if(si.protocol!=PROTOCOL_VERSION) si.ping = 9998; + si.mode = getint(p); + si.numplayers = getint(p); + si.minremain = getint(p); + sgetstr(); + strcpy_s(si.map, text); + sgetstr(); + strcpy_s(si.sdesc, text); + break; + }; + }; + }; +}; + +int sicompare(const serverinfo *a, const serverinfo *b) +{ + return a->ping>b->ping ? 1 : (a->pingping ? -1 : strcmp(a->name, b->name)); +}; + +void refreshservers() +{ + checkresolver(); + checkpings(); + if(lastmillis - lastinfo >= 5000) pingservers(); + servers.sort((void *)sicompare); + int maxmenu = 16; + loopv(servers) + { + serverinfo &si = servers[i]; + if(si.address.host != ENET_HOST_ANY && si.ping != 9999) + { + if(si.protocol!=PROTOCOL_VERSION) sprintf_s(si.full)("%s [different cube protocol]", si.name); + else sprintf_s(si.full)("%d\t%d\t%s, %s: %s %s", si.ping, si.numplayers, si.map[0] ? si.map : "[unknown]", modestr(si.mode), si.name, si.sdesc); + } + else + { + sprintf_s(si.full)(si.address.host != ENET_HOST_ANY ? "%s [waiting for server response]" : "%s [unknown host]\t", si.name); + } + si.full[50] = 0; // cut off too long server descriptions + menumanual(1, i, si.full); + if(!--maxmenu) return; + }; +}; + +void servermenu() +{ + if(pingsock == ENET_SOCKET_NULL) + { + pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL); + resolverinit(1, 1000); + }; + resolverclear(); + loopv(servers) resolverquery(servers[i].name); + refreshservers(); + menuset(1); +}; + +void updatefrommaster() +{ + const int MAXUPD = 32000; + uchar buf[MAXUPD]; + uchar *reply = retrieveservers(buf, MAXUPD); + if(!*reply || strstr((char *)reply, "") || strstr((char *)reply, "")) conoutf("master server not replying"); + else { servers.setsize(0); execute((char *)reply); }; + servermenu(); +}; + +COMMAND(addserver, ARG_1STR); +COMMAND(servermenu, ARG_NONE); +COMMAND(updatefrommaster, ARG_NONE); + +void writeservercfg() +{ + FILE *f = fopen("servers.cfg", "w"); + if(!f) return; + fprintf(f, "// servers connected to are added here automatically\n\n"); + loopvrev(servers) fprintf(f, "addserver %s\n", servers[i].name); + fclose(f); +}; + + diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index f5dcc5fe..58d75e33 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -1,141 +1,141 @@ -// all server side masterserver and pinging functionality - -#include "cube.h" - -ENetSocket mssock = ENET_SOCKET_NULL; - -void httpgetsend(ENetAddress &ad, char *hostname, char *req, char *ref, char *agent) -{ - if(ad.host==ENET_HOST_ANY) - { - printf("looking up %s...\n", hostname); - enet_address_set_host(&ad, hostname); - if(ad.host==ENET_HOST_ANY) return; - }; - if(mssock!=ENET_SOCKET_NULL) enet_socket_destroy(mssock); - mssock = enet_socket_create(ENET_SOCKET_TYPE_STREAM, NULL); - if(mssock==ENET_SOCKET_NULL) { printf("could not open socket\n"); return; }; - if(enet_socket_connect(mssock, &ad)<0) { printf("could not connect\n"); return; }; - ENetBuffer buf; - sprintf_sd(httpget)("GET %s HTTP/1.0\nHost: %s\nReferer: %s\nUser-Agent: %s\n\n", req, hostname, ref, agent); - buf.data = httpget; - buf.dataLength = strlen((char *)buf.data); - printf("sending request to %s...\n", hostname); - enet_socket_send(mssock, NULL, &buf, 1); -}; - -void httpgetrecieve(ENetBuffer &buf) -{ - if(mssock==ENET_SOCKET_NULL) return; - enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; - if(enet_socket_wait(mssock, &events, 0) >= 0 && events) - { - int len = enet_socket_receive(mssock, NULL, &buf, 1); - if(len<=0) - { - enet_socket_destroy(mssock); - mssock = ENET_SOCKET_NULL; - return; - }; - buf.data = ((char *)buf.data)+len; - ((char*)buf.data)[0] = 0; - buf.dataLength -= len; - }; -}; - -uchar *stripheader(uchar *b) -{ - char *s = strstr((char *)b, "\n\r\n"); - if(!s) s = strstr((char *)b, "\n\n"); - return s ? (uchar *)s : b; -}; - -ENetAddress masterserver = { ENET_HOST_ANY, 80 }; -int updmaster = 0; -string masterbase; -string masterpath; -uchar masterrep[MAXTRANS]; -ENetBuffer masterb; - -void updatemasterserver(int seconds) -{ - if(seconds>updmaster) // send alive signal to masterserver every hour of uptime - { - sprintf_sd(path)("%sregister.do?action=add", masterpath); - httpgetsend(masterserver, masterbase, path, "cubeserver", "Cube Server"); - masterrep[0] = 0; - masterb.data = masterrep; - masterb.dataLength = MAXTRANS-1; - updmaster = seconds+60*60; - }; -}; - -void checkmasterreply() -{ - bool busy = mssock!=ENET_SOCKET_NULL; - httpgetrecieve(masterb); - if(busy && mssock==ENET_SOCKET_NULL) printf("masterserver reply: %s\n", stripheader(masterrep)); -}; - -uchar *retrieveservers(uchar *buf, int buflen) -{ - sprintf_sd(path)("%sretrieve.do?item=list", masterpath); - httpgetsend(masterserver, masterbase, path, "cubeserver", "Cube Server"); - ENetBuffer eb; - buf[0] = 0; - eb.data = buf; - eb.dataLength = buflen-1; - while(mssock!=ENET_SOCKET_NULL) httpgetrecieve(eb); - return stripheader(buf); -}; - -ENetSocket pongsock = ENET_SOCKET_NULL; -string serverdesc; - -void serverms(int mode, int numplayers, int minremain, char *smapname, int seconds, bool isfull) -{ - checkmasterreply(); - updatemasterserver(seconds); - - // reply all server info requests - ENetBuffer buf; - ENetAddress addr; - uchar pong[MAXTRANS], *p; - int len; - enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; - buf.data = pong; - while(enet_socket_wait(pongsock, &events, 0) >= 0 && events) - { - buf.dataLength = sizeof(pong); - len = enet_socket_receive(pongsock, &addr, &buf, 1); - if(len < 0) return; - p = &pong[len]; - putint(p, PROTOCOL_VERSION); - putint(p, mode); - putint(p, numplayers); - putint(p, minremain); - string mname; - strcpy_s(mname, isfull ? "[FULL] " : ""); - strcat_s(mname, smapname); - sendstring(mname, p); - sendstring(serverdesc, p); - buf.dataLength = p - pong; - enet_socket_send(pongsock, &addr, &buf, 1); - }; -}; - -void servermsinit(const char *master, char *sdesc, bool listen) -{ - const char *mid = strstr(master, "/"); - if(!mid) mid = master; - strcpy_s(masterpath, mid); - strn0cpy(masterbase, master, mid-master+1); - strcpy_s(serverdesc, sdesc); - - if(listen) - { - ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; - pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, &address); - if(pongsock == ENET_SOCKET_NULL) fatal("could not create server info socket\n"); - }; -}; +// all server side masterserver and pinging functionality + +#include "cube.h" + +ENetSocket mssock = ENET_SOCKET_NULL; + +void httpgetsend(ENetAddress &ad, char *hostname, char *req, char *ref, char *agent) +{ + if(ad.host==ENET_HOST_ANY) + { + printf("looking up %s...\n", hostname); + enet_address_set_host(&ad, hostname); + if(ad.host==ENET_HOST_ANY) return; + }; + if(mssock!=ENET_SOCKET_NULL) enet_socket_destroy(mssock); + mssock = enet_socket_create(ENET_SOCKET_TYPE_STREAM, NULL); + if(mssock==ENET_SOCKET_NULL) { printf("could not open socket\n"); return; }; + if(enet_socket_connect(mssock, &ad)<0) { printf("could not connect\n"); return; }; + ENetBuffer buf; + sprintf_sd(httpget)("GET %s HTTP/1.0\nHost: %s\nReferer: %s\nUser-Agent: %s\n\n", req, hostname, ref, agent); + buf.data = httpget; + buf.dataLength = strlen((char *)buf.data); + printf("sending request to %s...\n", hostname); + enet_socket_send(mssock, NULL, &buf, 1); +}; + +void httpgetrecieve(ENetBuffer &buf) +{ + if(mssock==ENET_SOCKET_NULL) return; + enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; + if(enet_socket_wait(mssock, &events, 0) >= 0 && events) + { + int len = enet_socket_receive(mssock, NULL, &buf, 1); + if(len<=0) + { + enet_socket_destroy(mssock); + mssock = ENET_SOCKET_NULL; + return; + }; + buf.data = ((char *)buf.data)+len; + ((char*)buf.data)[0] = 0; + buf.dataLength -= len; + }; +}; + +uchar *stripheader(uchar *b) +{ + char *s = strstr((char *)b, "\n\r\n"); + if(!s) s = strstr((char *)b, "\n\n"); + return s ? (uchar *)s : b; +}; + +ENetAddress masterserver = { ENET_HOST_ANY, 80 }; +int updmaster = 0; +string masterbase; +string masterpath; +uchar masterrep[MAXTRANS]; +ENetBuffer masterb; + +void updatemasterserver(int seconds) +{ + if(seconds>updmaster) // send alive signal to masterserver every hour of uptime + { + sprintf_sd(path)("%sregister.do?action=add", masterpath); + httpgetsend(masterserver, masterbase, path, "cubeserver", "Cube Server"); + masterrep[0] = 0; + masterb.data = masterrep; + masterb.dataLength = MAXTRANS-1; + updmaster = seconds+60*60; + }; +}; + +void checkmasterreply() +{ + bool busy = mssock!=ENET_SOCKET_NULL; + httpgetrecieve(masterb); + if(busy && mssock==ENET_SOCKET_NULL) printf("masterserver reply: %s\n", stripheader(masterrep)); +}; + +uchar *retrieveservers(uchar *buf, int buflen) +{ + sprintf_sd(path)("%sretrieve.do?item=list", masterpath); + httpgetsend(masterserver, masterbase, path, "cubeserver", "Cube Server"); + ENetBuffer eb; + buf[0] = 0; + eb.data = buf; + eb.dataLength = buflen-1; + while(mssock!=ENET_SOCKET_NULL) httpgetrecieve(eb); + return stripheader(buf); +}; + +ENetSocket pongsock = ENET_SOCKET_NULL; +string serverdesc; + +void serverms(int mode, int numplayers, int minremain, char *smapname, int seconds, bool isfull) +{ + checkmasterreply(); + updatemasterserver(seconds); + + // reply all server info requests + ENetBuffer buf; + ENetAddress addr; + uchar pong[MAXTRANS], *p; + int len; + enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE; + buf.data = pong; + while(enet_socket_wait(pongsock, &events, 0) >= 0 && events) + { + buf.dataLength = sizeof(pong); + len = enet_socket_receive(pongsock, &addr, &buf, 1); + if(len < 0) return; + p = &pong[len]; + putint(p, PROTOCOL_VERSION); + putint(p, mode); + putint(p, numplayers); + putint(p, minremain); + string mname; + strcpy_s(mname, isfull ? "[FULL] " : ""); + strcat_s(mname, smapname); + sendstring(mname, p); + sendstring(serverdesc, p); + buf.dataLength = p - pong; + enet_socket_send(pongsock, &addr, &buf, 1); + }; +}; + +void servermsinit(const char *master, char *sdesc, bool listen) +{ + const char *mid = strstr(master, "/"); + if(!mid) mid = master; + strcpy_s(masterpath, mid); + strn0cpy(masterbase, master, mid-master+1); + strcpy_s(serverdesc, sdesc); + + if(listen) + { + ENetAddress address = { ENET_HOST_ANY, CUBE_SERVINFO_PORT }; + pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, &address); + if(pongsock == ENET_SOCKET_NULL) fatal("could not create server info socket\n"); + }; +}; diff --git a/source/src/serverutil.cpp b/source/src/serverutil.cpp index ddce17d6..5b60ffc2 100644 --- a/source/src/serverutil.cpp +++ b/source/src/serverutil.cpp @@ -1,145 +1,145 @@ -// misc useful functions used by the server - -#include "cube.h" - -// all network traffic is in 32bit ints, which are then compressed using the following simple scheme (assumes that most values are small). - -void putint(uchar *&p, int n) -{ - if(n<128 && n>-127) { *p++ = n; } - else if(n<0x8000 && n>=-0x8000) { *p++ = 0x80; *p++ = n; *p++ = n>>8; } - else { *p++ = 0x81; *p++ = n; *p++ = n>>8; *p++ = n>>16; *p++ = n>>24; }; -}; - -int getint(uchar *&p) -{ - int c = *((char *)p); - p++; - if(c==-128) { int n = *p++; n |= *((char *)p)<<8; p++; return n;} - else if(c==-127) { int n = *p++; n |= *p++<<8; n |= *p++<<16; return n|(*p++<<24); } - else return c; -}; - -void sendstring(char *t, uchar *&p) -{ - while(*t) putint(p, *t++); - putint(p, 0); -}; - -const char *modenames[] = -{ - "team deathmatch", "coopedit", "deathmatch", "survior", - "team survior", "ctf", "pistols only!", -}; - -//const char *modestr(int n) { return (n>=-2 && n<12) ? modenames[n+2] : "unknown"; }; -const char *modestr(int n) { return (n>=0 && n<6) ? modenames[n] : "unknown"; }; - -/* Modified by Rick: New messages and different sizes for existing ones -char msgsizesl[] = // size inclusive message token, 0 for variable or not-checked sizes -{ - SV_INITS2C, 4, SV_INITC2S, 0, SV_POS, 12, SV_TEXT, 0, SV_SOUND, 2, SV_CDIS, 2, - SV_DIED, 2, SV_DAMAGE, 4, SV_SHOsT, 8, SV_FRAGS, 2, - SV_TIMEUP, 2, SV_EDITENT, 10, SV_MAPRELOAD, 2, SV_ITEMACC, 2, - SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 3, SV_DENIED, 2, - SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, - SV_EDITH, 7, SV_EDITT, 7, SV_EDITS, 6, SV_EDITD, 6, SV_EDITE, 6, - SV_SENDMAP, 0, SV_RECVMAP, 1, SV_SERVMSG, 0, SV_ITEMLIST, 0, - SV_EXT, 0, - -1 -}; -*/ - -char msgsizesl[] = // size inclusive message token, 0 for variable or not-checked sizes -{ - SV_INITS2C, 4, SV_INITC2S, 0, SV_POS, 12, SV_TEXT, 0, SV_SOUND, 2, SV_CDIS, 2, - SV_DIED, 2, SV_DAMAGE, 4, SV_SHOT, 9, SV_FRAGS, 2, - SV_TIMEUP, 2, SV_EDITENT, 10, SV_MAPRELOAD, 2, SV_ITEMACC, 2, - SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 3, SV_DENIED, 2, - SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, - SV_EDITH, 7, SV_EDITT, 7, SV_EDITS, 6, SV_EDITD, 6, SV_EDITE, 6, - SV_SENDMAP, 0, SV_RECVMAP, 1, SV_SERVMSG, 0, SV_ITEMLIST, 0, SV_WEAPCHANGE, 2, - SV_MODELSKIN, 2, - SV_FLAGPICKUP, 2, SV_FLAGDROP, 2, SV_FLAGRETURN, 2, SV_FLAGSCORE, 2, SV_FLAGINFO, 0, SV_FLAGS, 2, // EDIT: AH - // Added by Rick: Bot specific messages - SV_BOTSOUND, 3, SV_BOTDIS, 2, SV_BOTDIED, 4, SV_CLIENT2BOTDMG, 4, SV_BOT2BOTDMG, 2, - SV_BOTFRAGS, 3, SV_ADDBOT, 5, SV_BOTUPDATE, 12, SV_BOTCOMMAND, 0, - // End add - SV_BOTITEMPICKUP, 3, SV_BOT2CLIENTDMG, 5, SV_DIEDBYBOT, 2, - SV_EXT, 0, - -1 -}; - -char msgsizelookup(int msg) -{ - for(char *p = msgsizesl; *p>=0; p += 2) if(*p==msg) return p[1]; - return -1; -}; - -// sending of maps between clients - -string copyname; -int copysize; -uchar *copydata = NULL; - -void sendmaps(int n, string mapname, int mapsize, uchar *mapdata) -{ - if(mapsize <= 0 || mapsize > 256*256) return; - strcpy_s(copyname, mapname); - copysize = mapsize; - if(copydata) free(copydata); - copydata = (uchar *)alloc(mapsize); - memcpy(copydata, mapdata, mapsize); -} - -ENetPacket *recvmap(int n) -{ - if(!copydata) return NULL; - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS + copysize, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_RECVMAP); - sendstring(copyname, p); - putint(p, copysize); - memcpy(p, copydata, copysize); - p += copysize; - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - return packet; -} - -#ifdef STANDALONE - -void localservertoclient(uchar *buf, int len) {}; -void fatal(char *s, char *o) { cleanupserver(); printf("servererror: %s\n", s); exit(1); }; -void *alloc(int s) { void *b = calloc(1,s); if(!b) fatal("no memory!"); return b; }; - -int main(int argc, char* argv[]) // EDIT: AH -{ - int uprate = 0, maxcl = 4; - char *sdesc = "", *ip = "", *master = NULL, *passwd = "", *maprot = ""; - - for(int i = 1; i-127) { *p++ = n; } + else if(n<0x8000 && n>=-0x8000) { *p++ = 0x80; *p++ = n; *p++ = n>>8; } + else { *p++ = 0x81; *p++ = n; *p++ = n>>8; *p++ = n>>16; *p++ = n>>24; }; +}; + +int getint(uchar *&p) +{ + int c = *((char *)p); + p++; + if(c==-128) { int n = *p++; n |= *((char *)p)<<8; p++; return n;} + else if(c==-127) { int n = *p++; n |= *p++<<8; n |= *p++<<16; return n|(*p++<<24); } + else return c; +}; + +void sendstring(char *t, uchar *&p) +{ + while(*t) putint(p, *t++); + putint(p, 0); +}; + +const char *modenames[] = +{ + "team deathmatch", "coopedit", "deathmatch", "survior", + "team survior", "ctf", "pistols only!", +}; + +//const char *modestr(int n) { return (n>=-2 && n<12) ? modenames[n+2] : "unknown"; }; +const char *modestr(int n) { return (n>=0 && n<6) ? modenames[n] : "unknown"; }; + +/* Modified by Rick: New messages and different sizes for existing ones +char msgsizesl[] = // size inclusive message token, 0 for variable or not-checked sizes +{ + SV_INITS2C, 4, SV_INITC2S, 0, SV_POS, 12, SV_TEXT, 0, SV_SOUND, 2, SV_CDIS, 2, + SV_DIED, 2, SV_DAMAGE, 4, SV_SHOsT, 8, SV_FRAGS, 2, + SV_TIMEUP, 2, SV_EDITENT, 10, SV_MAPRELOAD, 2, SV_ITEMACC, 2, + SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 3, SV_DENIED, 2, + SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, + SV_EDITH, 7, SV_EDITT, 7, SV_EDITS, 6, SV_EDITD, 6, SV_EDITE, 6, + SV_SENDMAP, 0, SV_RECVMAP, 1, SV_SERVMSG, 0, SV_ITEMLIST, 0, + SV_EXT, 0, + -1 +}; +*/ + +char msgsizesl[] = // size inclusive message token, 0 for variable or not-checked sizes +{ + SV_INITS2C, 4, SV_INITC2S, 0, SV_POS, 12, SV_TEXT, 0, SV_SOUND, 2, SV_CDIS, 2, + SV_DIED, 2, SV_DAMAGE, 4, SV_SHOT, 9, SV_FRAGS, 2, + SV_TIMEUP, 2, SV_EDITENT, 10, SV_MAPRELOAD, 2, SV_ITEMACC, 2, + SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 3, SV_DENIED, 2, + SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, + SV_EDITH, 7, SV_EDITT, 7, SV_EDITS, 6, SV_EDITD, 6, SV_EDITE, 6, + SV_SENDMAP, 0, SV_RECVMAP, 1, SV_SERVMSG, 0, SV_ITEMLIST, 0, SV_WEAPCHANGE, 2, + SV_MODELSKIN, 2, + SV_FLAGPICKUP, 2, SV_FLAGDROP, 2, SV_FLAGRETURN, 2, SV_FLAGSCORE, 2, SV_FLAGINFO, 0, SV_FLAGS, 2, // EDIT: AH + // Added by Rick: Bot specific messages + SV_BOTSOUND, 3, SV_BOTDIS, 2, SV_BOTDIED, 4, SV_CLIENT2BOTDMG, 4, SV_BOT2BOTDMG, 2, + SV_BOTFRAGS, 3, SV_ADDBOT, 5, SV_BOTUPDATE, 12, SV_BOTCOMMAND, 0, + // End add + SV_BOTITEMPICKUP, 3, SV_BOT2CLIENTDMG, 5, SV_DIEDBYBOT, 2, + SV_EXT, 0, + -1 +}; + +char msgsizelookup(int msg) +{ + for(char *p = msgsizesl; *p>=0; p += 2) if(*p==msg) return p[1]; + return -1; +}; + +// sending of maps between clients + +string copyname; +int copysize; +uchar *copydata = NULL; + +void sendmaps(int n, string mapname, int mapsize, uchar *mapdata) +{ + if(mapsize <= 0 || mapsize > 256*256) return; + strcpy_s(copyname, mapname); + copysize = mapsize; + if(copydata) free(copydata); + copydata = (uchar *)alloc(mapsize); + memcpy(copydata, mapdata, mapsize); +} + +ENetPacket *recvmap(int n) +{ + if(!copydata) return NULL; + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS + copysize, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_RECVMAP); + sendstring(copyname, p); + putint(p, copysize); + memcpy(p, copydata, copysize); + p += copysize; + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + return packet; +} + +#ifdef STANDALONE + +void localservertoclient(uchar *buf, int len) {}; +void fatal(char *s, char *o) { cleanupserver(); printf("servererror: %s\n", s); exit(1); }; +void *alloc(int s) { void *b = calloc(1,s); if(!b) fatal("no memory!"); return b; }; + +int main(int argc, char* argv[]) // EDIT: AH +{ + int uprate = 0, maxcl = 4; + char *sdesc = "", *ip = "", *master = NULL, *passwd = "", *maprot = ""; + + for(int i = 1; i=0) { FSOUND_SetVolume(chan, (musicvol*MAXVOL)/255); FSOUND_SetPaused(chan, false); }; - } - else - { - conoutf("could not play music: %s", sn); - }; - #endif - }; -}; - -COMMAND(music, ARG_1STR); - -#ifdef USE_MIXER -vector samples; -#else -vector samples; -#endif - -cvector snames; - -int registersound(char *name) -{ - loopv(snames) if(strcmp(snames[i], name)==0) return i; - snames.add(newstring(name)); - samples.add(NULL); - return samples.length()-1; -}; - -COMMAND(registersound, ARG_1EST); - -void cleansound() -{ - if(nosound) return; - stopsound(); - #ifdef USE_MIXER - Mix_CloseAudio(); - #else - FSOUND_Close(); - #endif -}; - -VAR(stereo, 0, 1, 1); - -void updatechanvol(int chan, vec *loc) -{ - int vol = soundvol, pan = 255/2; - if(loc) - { - vdist(dist, v, *loc, player1->o); - vol -= (int)(dist*3*soundvol/255); // simple mono distance attenuation - if(vol<0) vol = 0; - if(stereo && (v.x != 0 || v.y != 0)) - { - float yaw = -atan2(v.x, v.y) - player1->yaw*(PI / 180.0f); // relative angle of sound along X-Y axis - pan = int(255.9f*(0.5*sin(yaw)+0.5f)); // range is from 0 (left) to 255 (right) - }; - }; - vol = (vol*MAXVOL)/255; - #ifdef USE_MIXER - Mix_Volume(chan, vol); - Mix_SetPanning(chan, 255-pan, pan); - #else - FSOUND_SetVolume(chan, vol); - FSOUND_SetPan(chan, pan); - #endif -}; - -void newsoundloc(int chan, vec *loc) -{ - assert(chan>=0 && chano); -} -// End add by Rick - -int soundsatonce = 0, lastsoundmillis = 0; - -void playsound(int n, vec *loc) -{ - if(nosound) return; - if(!soundvol) return; - if(lastmillis==lastsoundmillis) soundsatonce++; else soundsatonce = 1; - lastsoundmillis = lastmillis; - if(soundsatonce>5) return; // avoid bursts of sounds with heavy packetloss and in sp - if(n<0 || n>=samples.length()) { conoutf("unregistered sound: %d", n); return; }; - - if(!samples[n]) - { - sprintf_sd(buf)("packages/audio/sounds/%s.wav", snames[n]); - - #ifdef USE_MIXER - samples[n] = Mix_LoadWAV(path(buf)); - #else - samples[n] = FSOUND_Sample_Load(n, path(buf), FSOUND_LOOP_OFF, 0, 0); - #endif - - if(!samples[n]) { conoutf("failed to load sample: %s", (int)buf); return; }; - }; - - #ifdef USE_MIXER - int chan = Mix_PlayChannel(-1, samples[n], 0); - #else - int chan = FSOUND_PlaySoundEx(FSOUND_FREE, samples[n], NULL, true); - #endif - if(chan<0) return; - if(loc) newsoundloc(chan, loc); - updatechanvol(chan, loc); - #ifndef USE_MIXER - FSOUND_SetPaused(chan, false); - #endif -}; - -void sound(int n) { playsound(n, NULL); }; -COMMAND(sound, ARG_1INT); +// sound.cpp: uses fmod on windows and sdl_mixer on unix (both had problems on the other platform) + +#include "cube.h" + +//#ifndef WIN32 // NOTE: fmod not being supported for the moment as it does not allow stereo pan/vol updating during playback +#define USE_MIXER +//#endif + +VARP(soundvol, 0, 255, 255); +VARP(musicvol, 0, 128, 255); +bool nosound = false; + +#define MAXCHAN 32 +#define SOUNDFREQ 22050 + +struct soundloc { vec loc; bool inuse; } soundlocs[MAXCHAN]; + +#ifdef USE_MIXER + #include "SDL_mixer.h" + #define MAXVOL MIX_MAX_VOLUME + Mix_Music *mod = NULL; + void *stream = NULL; +#else + #include "fmod.h" + #define MAXVOL 255 + FMUSIC_MODULE *mod = NULL; + FSOUND_STREAM *stream = NULL; +#endif + +void stopsound() +{ + if(nosound) return; + if(mod) + { + #ifdef USE_MIXER + Mix_HaltMusic(); + Mix_FreeMusic(mod); + #else + FMUSIC_FreeSong(mod); + #endif + mod = NULL; + }; + if(stream) + { + #ifndef USE_MIXER + FSOUND_Stream_Close(stream); + #endif + stream = NULL; + }; +}; + +VAR(soundbufferlen, 128, 1024, 4096); + +void initsound() +{ + memset(soundlocs, 0, sizeof(soundloc)*MAXCHAN); + #ifdef USE_MIXER + if(Mix_OpenAudio(SOUNDFREQ, MIX_DEFAULT_FORMAT, 2, soundbufferlen)<0) + { + conoutf("sound init failed (SDL_mixer): %s", (size_t)Mix_GetError()); + nosound = true; + }; + Mix_AllocateChannels(MAXCHAN); + #else + if(FSOUND_GetVersion()=0) { FSOUND_SetVolume(chan, (musicvol*MAXVOL)/255); FSOUND_SetPaused(chan, false); }; + } + else + { + conoutf("could not play music: %s", sn); + }; + #endif + }; +}; + +COMMAND(music, ARG_1STR); + +#ifdef USE_MIXER +vector samples; +#else +vector samples; +#endif + +cvector snames; + +int registersound(char *name) +{ + loopv(snames) if(strcmp(snames[i], name)==0) return i; + snames.add(newstring(name)); + samples.add(NULL); + return samples.length()-1; +}; + +COMMAND(registersound, ARG_1EST); + +void cleansound() +{ + if(nosound) return; + stopsound(); + #ifdef USE_MIXER + Mix_CloseAudio(); + #else + FSOUND_Close(); + #endif +}; + +VAR(stereo, 0, 1, 1); + +void updatechanvol(int chan, vec *loc) +{ + int vol = soundvol, pan = 255/2; + if(loc) + { + vdist(dist, v, *loc, player1->o); + vol -= (int)(dist*3*soundvol/255); // simple mono distance attenuation + if(vol<0) vol = 0; + if(stereo && (v.x != 0 || v.y != 0)) + { + float yaw = -atan2(v.x, v.y) - player1->yaw*(PI / 180.0f); // relative angle of sound along X-Y axis + pan = int(255.9f*(0.5*sin(yaw)+0.5f)); // range is from 0 (left) to 255 (right) + }; + }; + vol = (vol*MAXVOL)/255; + #ifdef USE_MIXER + Mix_Volume(chan, vol); + Mix_SetPanning(chan, 255-pan, pan); + #else + FSOUND_SetVolume(chan, vol); + FSOUND_SetPan(chan, pan); + #endif +}; + +void newsoundloc(int chan, vec *loc) +{ + assert(chan>=0 && chano); +} +// End add by Rick + +int soundsatonce = 0, lastsoundmillis = 0; + +void playsound(int n, vec *loc) +{ + if(nosound) return; + if(!soundvol) return; + if(lastmillis==lastsoundmillis) soundsatonce++; else soundsatonce = 1; + lastsoundmillis = lastmillis; + if(soundsatonce>5) return; // avoid bursts of sounds with heavy packetloss and in sp + if(n<0 || n>=samples.length()) { conoutf("unregistered sound: %d", n); return; }; + + if(!samples[n]) + { + sprintf_sd(buf)("packages/audio/sounds/%s.wav", snames[n]); + + #ifdef USE_MIXER + samples[n] = Mix_LoadWAV(path(buf)); + #else + samples[n] = FSOUND_Sample_Load(n, path(buf), FSOUND_LOOP_OFF, 0, 0); + #endif + + if(!samples[n]) { conoutf("failed to load sample: %s", (int)buf); return; }; + }; + + #ifdef USE_MIXER + int chan = Mix_PlayChannel(-1, samples[n], 0); + #else + int chan = FSOUND_PlaySoundEx(FSOUND_FREE, samples[n], NULL, true); + #endif + if(chan<0) return; + if(loc) newsoundloc(chan, loc); + updatechanvol(chan, loc); + #ifndef USE_MIXER + FSOUND_SetPaused(chan, false); + #endif +}; + +void sound(int n) { playsound(n, NULL); }; +COMMAND(sound, ARG_1INT); diff --git a/source/src/tools.cpp b/source/src/tools.cpp index 4406ae14..a2bc8747 100644 --- a/source/src/tools.cpp +++ b/source/src/tools.cpp @@ -1,140 +1,140 @@ -// implementation of generic tools - -#include "tools.h" -#include - -//////////////////////////// pool /////////////////////////// - -pool::pool() -{ - blocks = 0; - allocnext(POOLSIZE); - for(int i = 0; iMAXREUSESIZE) - { - return malloc(size); - } - else - { - size = bucket(size); - void **r = (void **)reuse[size]; - if(r) - { - reuse[size] = *r; - return (void *)r; - } - else - { - size <<= PTRBITS; - if(leftMAXREUSESIZE) - { - free(p); - } - else - { - size = bucket(size); - if(size) // only needed for 0-size free, are there any? - { - *((void **)p) = reuse[size]; - reuse[size] = p; - }; - }; -}; - -void *pool::realloc(void *p, size_t oldsize, size_t newsize) -{ - void *np = alloc(newsize); - if(!oldsize) return np; - memcpy(np, p, newsize>oldsize ? oldsize : newsize); - dealloc(p, oldsize); - return np; -}; - -void pool::dealloc_block(void *b) -{ - if(b) - { - dealloc_block(*((char **)b)); - free(b); - }; -} - -void pool::allocnext(size_t allocsize) -{ - char *b = (char *)malloc(allocsize+PTRSIZE); - *((char **)b) = blocks; - blocks = b; - p = b+PTRSIZE; - left = allocsize; -}; - -char *pool::string(char *s, size_t l) -{ - char *b = (char *)alloc(l+1); - strncpy(b,s,l); - b[l] = 0; - return b; -}; - -pool *gp() // useful for global buffers that need to be initialisation order independant -{ - static pool *p = NULL; - return p ? p : (p = new pool()); -}; - - -///////////////////////// misc tools /////////////////////// - -char *path(char *s) -{ - for(char *t = s; t = strpbrk(t, "/\\"); *t++ = PATHDIV); - return s; -}; - -char *loadfile(char *fn, int *size) -{ - FILE *f = fopen(fn, "rb"); - if(!f) return NULL; - fseek(f, 0, SEEK_END); - int len = ftell(f); - fseek(f, 0, SEEK_SET); - char *buf = (char *)malloc(len+1); - if(!buf) return NULL; - buf[len] = 0; - size_t rlen = fread(buf, 1, len, f); - fclose(f); - if(len!=rlen || len<=0) - { - free(buf); - return NULL; - }; - if(size!=NULL) *size = len; - return buf; -}; - -void endianswap(void *memory, int stride, int length) // little indians as storage format -{ - if(*((char *)&stride)) return; - loop(w, length) loop(i, stride/2) - { - uchar *p = (uchar *)memory+w*stride; - uchar t = p[i]; - p[i] = p[stride-i-1]; - p[stride-i-1] = t; - }; -} +// implementation of generic tools + +#include "tools.h" +#include + +//////////////////////////// pool /////////////////////////// + +pool::pool() +{ + blocks = 0; + allocnext(POOLSIZE); + for(int i = 0; iMAXREUSESIZE) + { + return malloc(size); + } + else + { + size = bucket(size); + void **r = (void **)reuse[size]; + if(r) + { + reuse[size] = *r; + return (void *)r; + } + else + { + size <<= PTRBITS; + if(leftMAXREUSESIZE) + { + free(p); + } + else + { + size = bucket(size); + if(size) // only needed for 0-size free, are there any? + { + *((void **)p) = reuse[size]; + reuse[size] = p; + }; + }; +}; + +void *pool::realloc(void *p, size_t oldsize, size_t newsize) +{ + void *np = alloc(newsize); + if(!oldsize) return np; + memcpy(np, p, newsize>oldsize ? oldsize : newsize); + dealloc(p, oldsize); + return np; +}; + +void pool::dealloc_block(void *b) +{ + if(b) + { + dealloc_block(*((char **)b)); + free(b); + }; +} + +void pool::allocnext(size_t allocsize) +{ + char *b = (char *)malloc(allocsize+PTRSIZE); + *((char **)b) = blocks; + blocks = b; + p = b+PTRSIZE; + left = allocsize; +}; + +char *pool::string(char *s, size_t l) +{ + char *b = (char *)alloc(l+1); + strncpy(b,s,l); + b[l] = 0; + return b; +}; + +pool *gp() // useful for global buffers that need to be initialisation order independant +{ + static pool *p = NULL; + return p ? p : (p = new pool()); +}; + + +///////////////////////// misc tools /////////////////////// + +char *path(char *s) +{ + for(char *t = s; t = strpbrk(t, "/\\"); *t++ = PATHDIV); + return s; +}; + +char *loadfile(char *fn, int *size) +{ + FILE *f = fopen(fn, "rb"); + if(!f) return NULL; + fseek(f, 0, SEEK_END); + int len = ftell(f); + fseek(f, 0, SEEK_SET); + char *buf = (char *)malloc(len+1); + if(!buf) return NULL; + buf[len] = 0; + size_t rlen = fread(buf, 1, len, f); + fclose(f); + if(len!=rlen || len<=0) + { + free(buf); + return NULL; + }; + if(size!=NULL) *size = len; + return buf; +}; + +void endianswap(void *memory, int stride, int length) // little indians as storage format +{ + if(*((char *)&stride)) return; + loop(w, length) loop(i, stride/2) + { + uchar *p = (uchar *)memory+w*stride; + uchar t = p[i]; + p[i] = p[stride-i-1]; + p[stride-i-1] = t; + }; +} diff --git a/source/src/tools.h b/source/src/tools.h index 5292305d..f9a57b54 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -1,275 +1,275 @@ -// generic useful stuff for any C++ program - -#ifndef _TOOLS_H -#define _TOOLS_H - -#ifdef __GNUC__ -#define gamma __gamma -#endif - -#include - -#ifdef __GNUC__ -#undef gamma -#endif - -#include -#include -#include -#include -#include -#include -#include - -#ifdef NULL -#undef NULL -#endif -#define NULL 0 - -typedef unsigned char uchar; -typedef unsigned short ushort; -typedef unsigned int uint; - -#define max(a,b) (((a) > (b)) ? (a) : (b)) -#define min(a,b) (((a) < (b)) ? (a) : (b)) -#define rnd(max) (rand()%(max)) -#define rndreset() (srand(1)) -#define rndtime() { loopi(lastmillis&0xF) rnd(i+1); } -#define loop(v,m) for(int v = 0; v<(m); v++) -#define loopi(m) loop(i,m) -#define loopj(m) loop(j,m) -#define loopk(m) loop(k,m) -#define loopl(m) loop(l,m) - -#ifdef WIN32 -#pragma warning( 3 : 4189 ) -//#pragma comment(linker,"/OPT:NOWIN98") -#define PATHDIV '\\' -#else -#define __cdecl -#define _vsnprintf vsnprintf -#define PATHDIV '/' -#endif - - -// easy safe strings - -#define _MAXDEFSTR 260 -typedef char string[_MAXDEFSTR]; - -inline void strn0cpy(char *d, const char *s, size_t m) { strncpy(d,s,m); d[(m)-1] = 0; }; -inline void strcpy_s(char *d, const char *s) { strn0cpy(d,s,_MAXDEFSTR); }; -inline void strcat_s(char *d, const char *s) { size_t n = strlen(d); strn0cpy(d+n,s,_MAXDEFSTR-n); }; - -inline void formatstring(char *d, const char *fmt, va_list v) -{ - _vsnprintf(d, _MAXDEFSTR, fmt, v); - d[_MAXDEFSTR-1] = 0; -}; - -struct sprintf_s_f -{ - char *d; - sprintf_s_f(char *str): d(str) {}; - void operator()(const char* fmt, ...) - { - va_list v; - va_start(v, fmt); - _vsnprintf(d, _MAXDEFSTR, fmt, v); - va_end(v); - d[_MAXDEFSTR-1] = 0; - }; -}; - -#define sprintf_s(d) sprintf_s_f((char *)d) -#define sprintf_sd(d) string d; sprintf_s(d) -#define sprintf_sdlv(d,last,fmt) string d; { va_list ap; va_start(ap, last); formatstring(d, fmt, ap); va_end(ap); } -#define sprintf_sdv(d,fmt) sprintf_sdlv(d,fmt,fmt) - - -// fast pentium f2i - -#ifdef _MSC_VER -inline int fast_f2nat(float a) { // only for positive floats - static const float fhalf = 0.5f; - int retval; - - __asm fld a - __asm fsub fhalf - __asm fistp retval // perf regalloc? - - return retval; -}; -#else -#define fast_f2nat(val) ((int)(val)) -#endif - - - -extern char *path(char *s); -extern char *loadfile(char *fn, int *size); -extern void endianswap(void *, int, int); - -// memory pool that uses buckets and linear allocation for small objects -// VERY fast, and reasonably good memory reuse - -struct pool -{ - enum { POOLSIZE = 4096 }; // can be absolutely anything - enum { PTRSIZE = sizeof(char *) }; - enum { MAXBUCKETS = 65 }; // meaning up to size 256 on 32bit pointer systems - enum { MAXREUSESIZE = MAXBUCKETS*PTRSIZE-PTRSIZE }; - inline size_t bucket(size_t s) { return (s+PTRSIZE-1)>>PTRBITS; }; - enum { PTRBITS = PTRSIZE==2 ? 1 : PTRSIZE==4 ? 2 : 3 }; - - char *p; - size_t left; - char *blocks; - void *reuse[MAXBUCKETS]; - - pool(); - ~pool() { dealloc_block(blocks); }; - - void *alloc(size_t size); - void dealloc(void *p, size_t size); - void *realloc(void *p, size_t oldsize, size_t newsize); - - char *string(char *s, size_t l); - char *string(char *s) { return string(s, strlen(s)); }; - void deallocstr(char *s) { dealloc(s, strlen(s)+1); }; - char *stringbuf(char *s) { return string(s, _MAXDEFSTR-1); }; - - void dealloc_block(void *b); - void allocnext(size_t allocsize); -}; - -template struct vector -{ - T *buf; - int alen; - int ulen; - pool *p; - - vector() - { - this->p = gp(); - alen = 8; - buf = (T *)p->alloc(alen*sizeof(T)); - ulen = 0; - }; - - ~vector() { setsize(0); p->dealloc(buf, alen*sizeof(T)); }; - - vector(vector &v); - void operator=(vector &v); - - T &add(const T &x) - { - if(ulen==alen) realloc(); - new (&buf[ulen]) T(x); - return buf[ulen++]; - }; - - T &add() - { - if(ulen==alen) realloc(); - new (&buf[ulen]) T; - return buf[ulen++]; - }; - - T &pop() { return buf[--ulen]; }; - T &last() { return buf[ulen-1]; }; - bool empty() { return ulen==0; }; - - int length() { return ulen; }; - T &operator[](int i) { assert(i>=0 && ii; ulen--) buf[ulen-1].~T(); }; - T *getbuf() { return buf; }; - - void sort(void *cf) { qsort(buf, ulen, sizeof(T), (int (__cdecl *)(const void *,const void *))cf); }; - - void realloc() - { - int olen = alen; - buf = (T *)p->realloc(buf, olen*sizeof(T), (alen *= 2)*sizeof(T)); - }; - - T remove(int i) - { - T e = buf[i]; - for(int p = i+1; pi; p--) buf[p] = buf[p-1]; - buf[i] = e; - return buf[i]; - }; -}; - -#define loopv(v) if(false) {} else for(int i = 0; i<(v).length(); i++) -#define loopvrev(v) if(false) {} else for(int i = (v).length()-1; i>=0; i--) - -template struct hashtable -{ - struct chain { chain *next; char *key; T data; }; - - int size; - int numelems; - chain **table; - pool *parent; - chain *enumc; - - hashtable() - { - this->size = 1<<10; - this->parent = gp(); - numelems = 0; - table = (chain **)parent->alloc(size*sizeof(T)); - for(int i = 0; i &v); - void operator=(hashtable &v); - - T *access(char *key, T *data = NULL) - { - unsigned int h = 5381; - for(int i = 0, k; k = key[i]; i++) h = ((h<<5)+h)^k; // bernstein k=33 xor - h = h&(size-1); // primes not much of an advantage - for(chain *c = table[h]; c; c = c->next) - { - for(char *p1 = key, *p2 = c->key, ch; (ch = *p1++)==*p2++; ) if(!ch) //if(strcmp(key,c->key)==0) - { - T *d = &c->data; - if(data) c->data = *data; - return d; - }; - }; - if(data) - { - chain *c = (chain *)parent->alloc(sizeof(chain)); - c->data = *data; - c->key = key; - c->next = table[h]; - table[h] = c; - numelems++; - }; - return NULL; - }; -}; - - -#define enumerate(ht,t,e,b) loopi(ht->size) for(ht->enumc = ht->table[i]; ht->enumc; ht->enumc = ht->enumc->next) { t e = &ht->enumc->data; b; } - -pool *gp(); -inline char *newstring(char *s) { return gp()->string(s); }; -inline char *newstring(char *s, size_t l) { return gp()->string(s, l); }; -inline char *newstringbuf(char *s) { return gp()->stringbuf(s); }; - -#endif - +// generic useful stuff for any C++ program + +#ifndef _TOOLS_H +#define _TOOLS_H + +#ifdef __GNUC__ +#define gamma __gamma +#endif + +#include + +#ifdef __GNUC__ +#undef gamma +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef NULL +#undef NULL +#endif +#define NULL 0 + +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned int uint; + +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#define rnd(max) (rand()%(max)) +#define rndreset() (srand(1)) +#define rndtime() { loopi(lastmillis&0xF) rnd(i+1); } +#define loop(v,m) for(int v = 0; v<(m); v++) +#define loopi(m) loop(i,m) +#define loopj(m) loop(j,m) +#define loopk(m) loop(k,m) +#define loopl(m) loop(l,m) + +#ifdef WIN32 +#pragma warning( 3 : 4189 ) +//#pragma comment(linker,"/OPT:NOWIN98") +#define PATHDIV '\\' +#else +#define __cdecl +#define _vsnprintf vsnprintf +#define PATHDIV '/' +#endif + + +// easy safe strings + +#define _MAXDEFSTR 260 +typedef char string[_MAXDEFSTR]; + +inline void strn0cpy(char *d, const char *s, size_t m) { strncpy(d,s,m); d[(m)-1] = 0; }; +inline void strcpy_s(char *d, const char *s) { strn0cpy(d,s,_MAXDEFSTR); }; +inline void strcat_s(char *d, const char *s) { size_t n = strlen(d); strn0cpy(d+n,s,_MAXDEFSTR-n); }; + +inline void formatstring(char *d, const char *fmt, va_list v) +{ + _vsnprintf(d, _MAXDEFSTR, fmt, v); + d[_MAXDEFSTR-1] = 0; +}; + +struct sprintf_s_f +{ + char *d; + sprintf_s_f(char *str): d(str) {}; + void operator()(const char* fmt, ...) + { + va_list v; + va_start(v, fmt); + _vsnprintf(d, _MAXDEFSTR, fmt, v); + va_end(v); + d[_MAXDEFSTR-1] = 0; + }; +}; + +#define sprintf_s(d) sprintf_s_f((char *)d) +#define sprintf_sd(d) string d; sprintf_s(d) +#define sprintf_sdlv(d,last,fmt) string d; { va_list ap; va_start(ap, last); formatstring(d, fmt, ap); va_end(ap); } +#define sprintf_sdv(d,fmt) sprintf_sdlv(d,fmt,fmt) + + +// fast pentium f2i + +#ifdef _MSC_VER +inline int fast_f2nat(float a) { // only for positive floats + static const float fhalf = 0.5f; + int retval; + + __asm fld a + __asm fsub fhalf + __asm fistp retval // perf regalloc? + + return retval; +}; +#else +#define fast_f2nat(val) ((int)(val)) +#endif + + + +extern char *path(char *s); +extern char *loadfile(char *fn, int *size); +extern void endianswap(void *, int, int); + +// memory pool that uses buckets and linear allocation for small objects +// VERY fast, and reasonably good memory reuse + +struct pool +{ + enum { POOLSIZE = 4096 }; // can be absolutely anything + enum { PTRSIZE = sizeof(char *) }; + enum { MAXBUCKETS = 65 }; // meaning up to size 256 on 32bit pointer systems + enum { MAXREUSESIZE = MAXBUCKETS*PTRSIZE-PTRSIZE }; + inline size_t bucket(size_t s) { return (s+PTRSIZE-1)>>PTRBITS; }; + enum { PTRBITS = PTRSIZE==2 ? 1 : PTRSIZE==4 ? 2 : 3 }; + + char *p; + size_t left; + char *blocks; + void *reuse[MAXBUCKETS]; + + pool(); + ~pool() { dealloc_block(blocks); }; + + void *alloc(size_t size); + void dealloc(void *p, size_t size); + void *realloc(void *p, size_t oldsize, size_t newsize); + + char *string(char *s, size_t l); + char *string(char *s) { return string(s, strlen(s)); }; + void deallocstr(char *s) { dealloc(s, strlen(s)+1); }; + char *stringbuf(char *s) { return string(s, _MAXDEFSTR-1); }; + + void dealloc_block(void *b); + void allocnext(size_t allocsize); +}; + +template struct vector +{ + T *buf; + int alen; + int ulen; + pool *p; + + vector() + { + this->p = gp(); + alen = 8; + buf = (T *)p->alloc(alen*sizeof(T)); + ulen = 0; + }; + + ~vector() { setsize(0); p->dealloc(buf, alen*sizeof(T)); }; + + vector(vector &v); + void operator=(vector &v); + + T &add(const T &x) + { + if(ulen==alen) realloc(); + new (&buf[ulen]) T(x); + return buf[ulen++]; + }; + + T &add() + { + if(ulen==alen) realloc(); + new (&buf[ulen]) T; + return buf[ulen++]; + }; + + T &pop() { return buf[--ulen]; }; + T &last() { return buf[ulen-1]; }; + bool empty() { return ulen==0; }; + + int length() { return ulen; }; + T &operator[](int i) { assert(i>=0 && ii; ulen--) buf[ulen-1].~T(); }; + T *getbuf() { return buf; }; + + void sort(void *cf) { qsort(buf, ulen, sizeof(T), (int (__cdecl *)(const void *,const void *))cf); }; + + void realloc() + { + int olen = alen; + buf = (T *)p->realloc(buf, olen*sizeof(T), (alen *= 2)*sizeof(T)); + }; + + T remove(int i) + { + T e = buf[i]; + for(int p = i+1; pi; p--) buf[p] = buf[p-1]; + buf[i] = e; + return buf[i]; + }; +}; + +#define loopv(v) if(false) {} else for(int i = 0; i<(v).length(); i++) +#define loopvrev(v) if(false) {} else for(int i = (v).length()-1; i>=0; i--) + +template struct hashtable +{ + struct chain { chain *next; char *key; T data; }; + + int size; + int numelems; + chain **table; + pool *parent; + chain *enumc; + + hashtable() + { + this->size = 1<<10; + this->parent = gp(); + numelems = 0; + table = (chain **)parent->alloc(size*sizeof(T)); + for(int i = 0; i &v); + void operator=(hashtable &v); + + T *access(char *key, T *data = NULL) + { + unsigned int h = 5381; + for(int i = 0, k; k = key[i]; i++) h = ((h<<5)+h)^k; // bernstein k=33 xor + h = h&(size-1); // primes not much of an advantage + for(chain *c = table[h]; c; c = c->next) + { + for(char *p1 = key, *p2 = c->key, ch; (ch = *p1++)==*p2++; ) if(!ch) //if(strcmp(key,c->key)==0) + { + T *d = &c->data; + if(data) c->data = *data; + return d; + }; + }; + if(data) + { + chain *c = (chain *)parent->alloc(sizeof(chain)); + c->data = *data; + c->key = key; + c->next = table[h]; + table[h] = c; + numelems++; + }; + return NULL; + }; +}; + + +#define enumerate(ht,t,e,b) loopi(ht->size) for(ht->enumc = ht->table[i]; ht->enumc; ht->enumc = ht->enumc->next) { t e = &ht->enumc->data; b; } + +pool *gp(); +inline char *newstring(char *s) { return gp()->string(s); }; +inline char *newstring(char *s, size_t l) { return gp()->string(s, l); }; +inline char *newstringbuf(char *s) { return gp()->stringbuf(s); }; + +#endif + diff --git a/source/src/weapon.cpp b/source/src/weapon.cpp index b7003766..581b9db8 100644 --- a/source/src/weapon.cpp +++ b/source/src/weapon.cpp @@ -1,714 +1,727 @@ -// weapon.cpp: all shooting and effects code - -#include "cube.h" - -struct guninfo { short sound, reload, reloadtime, attackdelay, damage, projspeed, part, spread, recoil, magsize, mdl_kick_rot, mdl_kick_back; char *name; }; - -const int SGRAYS = 20; //down from 20 (default) -const float SGSPREAD = 2; -vec sg[SGRAYS]; - -//change the particales -guninfo guns[NUMGUNS] = -{ - { S_KNIFE, S_NULL, 0, 500, 50, 0, 0, 1, 1, 1, 0, 0, "knife" }, - { S_PISTOL, S_RPISTOL, 1400, 170, 20, 0, 0, 80, 10, 8, 6, 5, "pistol" }, // *SGRAYS - { S_SHOTGUN, S_RSHOTGUN, 2400, 1000, 6, 0, 0, 1, 35, 7, 9, 9, "shotgun" }, //reload time is for 1 shell from 7 too powerful to 6 - { S_SUBGUN, S_RSUBGUN, 1650, 80, 17, 0, 0, 70, 15, 30, 1, 2, "subgun" }, - { S_SNIPER, S_RSNIPER, 1950, 1500, 72, 0, 0, 60, 50, 5, 4, 4, "sniper" }, - { S_ASSULT, S_RASSULT, 2000, 130, 20, 0, 0, 20, 40, 20, 0, 2, "assult" }, //recoil was 44 - { S_GRENADE, S_NULL, 1000, 2500, 150, 20, 6, 1, 1, 1, 3, 1, "grenade" }, -}; - - -bool gun_changed = false; - -void weapon(int gun) -{ - if(gun>=0) gun %= G_NUM; - else gun = G_NUM-(gun % G_NUM); - gun_changed = true; - switch(gun) - { - case G_PRIMARY: player1->gunselect=player1->primary; break; - case G_SECONDARY: player1->gunselect=GUN_PISTOL; break; - case G_MELEE: player1->gunselect=GUN_KNIFE; break; - case G_GRENADE: player1->gunselect=GUN_GRENADE; break; - }; -}; - -void shiftweapon(int i) -{ - int gun; - switch(player1->gunselect) - { - case GUN_KNIFE: gun=G_MELEE; break; - case GUN_PISTOL: gun=G_SECONDARY; break; - case GUN_GRENADE: gun=G_GRENADE; break; - default: gun=G_PRIMARY; break; - }; - gun += i; - weapon(gun); -} - -COMMAND(weapon, ARG_1INT); -COMMAND(shiftweapon, ARG_1INT); - -void reload(dynent *d) -{ - if(!d) return; - - bool akimbo = d->gunselect==GUN_PISTOL && d->akimbo!=0; - - if(d->gunselect==GUN_KNIFE || d->gunselect==GUN_GRENADE) return; - if(akimbo && d->mag[d->gunselect]>=(guns[d->gunselect].magsize * 2)) return; - else if(d->mag[d->gunselect]>=guns[d->gunselect].magsize && d->akimbo==0) return; - if(d->ammo[d->gunselect]<=0) return; - if(d->reloading) return; - - d->reloading = true; - d->lastaction = lastmillis; - - if(akimbo) - { - akimbolastaction[akimboside?1:0] = lastmillis; - akimbolastaction[akimboside?0:1] = lastmillis;// + (reloadtime(GUN_PISTOL)/2); - }; - - d->gunwait = guns[d->gunselect].reloadtime; - - //temp hack in drawhudgun() so no guns drawn while reloading, needs fixed - - int a = guns[d->gunselect].magsize - d->mag[d->gunselect]; - if(d->gunselect==GUN_PISTOL && d->akimbo!=0) - a = guns[d->gunselect].magsize * 2 - d->mag[d->gunselect]; - - if (a >= d->ammo[d->gunselect]) - { - d->mag[d->gunselect] += d->ammo[d->gunselect]; - d->ammo[d->gunselect] = 0; - } - else - { - d->mag[d->gunselect] += a; - d->ammo[d->gunselect] -= a; - } - - if(akimbo) playsoundc(S_RAKIMBO); - else playsoundc(guns[d->gunselect].reload); -}; - -void selfreload() { reload(player1); }; -COMMANDN(reload, selfreload, ARG_NONE); - -int reloadtime(int gun) { return guns[gun].reloadtime; }; -int attackdelay(int gun) { return guns[gun].attackdelay; }; -int magsize(int gun) { return guns[gun].magsize; }; -int kick_rot(int gun) { return guns[gun].mdl_kick_rot; }; -int kick_back(int gun) { return guns[gun].mdl_kick_back; }; - - -void createrays(vec &from, vec &to) // create random spread of rays for the shotgun -{ - vdist(dist, dvec, from, to); - float f = dist*SGSPREAD/1000; - loopi(SGRAYS) - { - #define RNDD (rnd(101)-50)*f - vec r = { RNDD, RNDD, RNDD }; - sg[i] = to; - vadd(sg[i], r); - }; -}; - -//bool intersect(dynent *d, vec &from, vec &to) // if lineseg hits entity bounding box -bool intersect(dynent *d, vec &from, vec &to, vec *end) // if lineseg hits entity bounding box -{ - vec v = to, w = d->o, *p; - vsub(v, from); - vsub(w, from); - float c1 = dotprod(w, v); - - if(c1<=0) p = &from; - else - { - float c2 = dotprod(v, v); - if(c2<=c1) p = &to; - else - { - float f = c1/c2; - vmul(v, f); - vadd(v, from); - p = &v; - }; - }; - - return p->x <= d->o.x+d->radius - && p->x >= d->o.x-d->radius - && p->y <= d->o.y+d->radius - && p->y >= d->o.y-d->radius - && p->z <= d->o.z+d->aboveeye - && p->z >= d->o.z-d->eyeheight; -}; - -dynent *playerincrosshair() -{ - if(demoplayback) return NULL; - loopv(players) - { - dynent *o = players[i]; - if(!o) continue; - if(intersect(o, player1->o, worldpos)) return o; - }; - return NULL; -}; -// Added by Rick -char *botincrosshair() -{ - if(demoplayback) return NULL; - loopv(bots) - { - dynent *o = bots[i]; - if(!o) continue; - if(intersect(o, player1->o, worldpos)) return o->name; - }; - return NULL; -}; -// End add by Rick - -const int MAXPROJ = 100; -struct projectile { vec o, to; float speed; dynent *owner; int gun; bool inuse, local; }; -projectile projs[MAXPROJ]; - -void projreset() { loopi(MAXPROJ) projs[i].inuse = false; }; - -void newprojectile(vec &from, vec &to, float speed, bool local, dynent *owner, int gun) -{ - loopi(MAXPROJ) - { - projectile *p = &projs[i]; - if(p->inuse) continue; - p->inuse = true; - p->o = from; - p->to = to; - p->speed = speed; - p->local = local; - p->owner = owner; - p->gun = gun; - return; - }; -}; - -void hit(int target, int damage, dynent *d, dynent *at) -{ - if(d==player1) selfdamage(damage, at==player1 ? -1 : -2, at); - //else if(d->monsterstate) monsterpain(d, damage, at); - // Added by Rick: Let bots take damage - else if (d->pBot) d->pBot->BotPain(damage, at); - else if (d->bIsBot) - { - int PlayerIndex = -1; - if (at->bIsBot) PlayerIndex = BotManager.GetBotIndex(at); - else - { - loopv(players) - { - if (!players[i]) continue; - - if (players[i] == at) - { - PlayerIndex = i; - break; - } - } - } - - int msgtype = (at->bIsBot) ? SV_BOT2BOTDMG : SV_CLIENT2BOTDMG; - addmsg(1, 4, msgtype, BotManager.GetBotIndex(d), damage, PlayerIndex); - playsound(S_PAIN1+rnd(5), &d->o); - } - // End add by Rick - //else { addmsg(1, 4, SV_DAMAGE, target, damage, d->lifesequence); playsound(S_PAIN1+rnd(5), &d->o); }; Modified by Rick: Added IsBot and PlayerIndex to message - else - { - // Modified by Rick: Added IsBot and PlayerIndex to message - int PlayerIndex = -1; - if (at->bIsBot) - PlayerIndex = BotManager.GetBotIndex(at); - else - { - loopv(players) - { - if (!players[i]) - continue; - - if (players[i] == at) - { - PlayerIndex = i; - break; - } - } - } - if(at->bIsBot) addmsg(1, 5, SV_BOT2CLIENTDMG, target, damage, d->lifesequence, PlayerIndex); - else addmsg(1, 4, SV_DAMAGE, target, damage, d->lifesequence); - playsound(S_PAIN1+rnd(5), &d->o); - }; - - particle_splash(3, damage, 1000, d->o); - demodamage(damage, d->o); -}; - -const float RL_RADIUS = 7; -const float RL_DAMRAD = 10; // hack - -void radialeffect(dynent *o, vec &v, int cn, int qdam, dynent *at) -{ - if(o->state!=CS_ALIVE) return; - vdist(dist, temp, v, o->o); - dist -= 2; // account for eye distance imprecision - if(distvel, temp); - }; -}; - -// Modified by Rick: Added notthisbot -void splash(projectile *p, vec &v, vec &vold, int notthisplayer, int notthismonster, int notthisbot, int qdam) -{ - particle_splash(0, 50, 300, v); - p->inuse = false; - if(p->gun!=GUN_GRENADE) - { - playsound(S_FEXPLODE, &v); - // no push? - } - else - { - //playsound(S_RLHIT, &v); - newsphere(v, RL_RADIUS, 0); - dodynlight(vold, v, 0, 0, p->owner); - if(!p->local) return; - radialeffect(player1, v, -1, qdam, p->owner); - loopv(players) - { - if(i==notthisplayer) continue; - dynent *o = players[i]; - if(!o) continue; - radialeffect(o, v, i, qdam, p->owner); - }; - // Added by Rick - loopv(bots) - { - if(i==notthisbot) continue; - dynent *o = bots[i]; - if(!o) continue; - radialeffect(o, v, i, qdam, p->owner); - } - // End add by Rick - //dvector &mv = getmonsters(); - //loopv(mv) if(i!=notthismonster) radialeffect(mv[i], v, i, qdam, p->owner); - }; -}; - -// Modified by Rick -// inline void projdamage(dynent *o, projectile *p, vec &v, int i, int im, int qdam) -inline void projdamage(dynent *o, projectile *p, vec &v, int i, int im, int ib, int qdam) -{ - if(o->state!=CS_ALIVE) return; - if(intersect(o, p->o, v)) - { - // splash(p, v, p->o, i, im, qdam); Modified by Rick - splash(p, v, p->o, i, im, ib, qdam); - hit(i, qdam, o, p->owner); - }; -}; - -void moveprojectiles(float time) -{ - loopi(MAXPROJ) - { - projectile *p = &projs[i]; - if(!p->inuse) continue; - //int qdam = guns[p->gun].damage*(p->owner->quadmillis ? 4 : 1); - int qdam = guns[p->gun].damage; - vdist(dist, v, p->o, p->to); - float dtime = dist*1000/p->speed; - if(time>dtime) dtime = time; - vmul(v, time/dtime); - vadd(v, p->o) - if(p->local) - { - loopv(players) - { - dynent *o = players[i]; - if(!o) continue; - // projdamage(o, p, v, i, -1, qdam); Modified by Rick - projdamage(o, p, v, i, -1, -1, qdam); - }; - // Added by Rick: Damage bots aswell - loopv(bots) - { - dynent *o = bots[i]; - if(!o || (o == p->owner)) continue; - projdamage(o, p, v, -1, -1, i, qdam); - }; - // End add by Rick - // if(p->owner!=player1) projdamage(player1, p, v, -1, -1, qdam); Modified by Rick - if(p->owner!=player1) projdamage(player1, p, v, -1, -1, -1, qdam); - //dvector &mv = getmonsters(); - //loopv(mv) if(!vreject(mv[i]->o, v, 10.0f) && mv[i]!=p->owner) projdamage(mv[i], p, v, -1, i, qdam); Modified by Rick - //loopv(mv) if(!vreject(mv[i]->o, v, 10.0f) && mv[i]!=p->owner) projdamage(mv[i], p, v, -1, i, -1, qdam); - }; - if(p->inuse) - { - // if(time==dtime) splash(p, v, p->o, -1, -1, qdam); Modified by Rick - if(time==dtime) splash(p, v, p->o, -1, -1, -1, qdam); - else - { - if(p->gun==GUN_GRENADE) { dodynlight(p->o, v, 0, 255, p->owner); particle_splash(5, 2, 200, v); } - else { particle_splash(1, 1, 200, v); particle_splash(guns[p->gun].part, 1, 1, v); }; - // Added by Rick - traceresult_s tr; - TraceLine(p->o, v, p->owner, true, &tr); - if (tr.collided) splash(p, v, p->o, -1, -1, -1, qdam); - // End add - }; - }; - p->o = v; - }; -}; - -extern float rad(float x); - -physent *curnade = NULL; - -void throw_nade(dynent *d, vec &to, physent *p) -{ - if(!p || !d) return; - printf("thrownade\n"); - playsound(S_GRENADETHROW, &d->o); - - p->isphysent = true; - p->gravity = 20; - p->state = NADE_THROWED; - - p->timeinair = 0; - p->onfloor = false; - - p->o = d->o; - p->vel.z = sin(rad(d->pitch)); - float speed = cos(rad(d->pitch)); - p->vel.x = sin(rad(d->yaw))*speed; - p->vel.y = -cos(rad(d->yaw))*speed; - - vmul(p->vel, 1.7f); - - vec throwdir = p->vel; - vmul(throwdir, d->radius); - vadd(p->o, throwdir); - vadd(p->o, throwdir); - - vec &from = d->o; - - if(d==player1) - { - int percent_done = (lastmillis-p->millis)*100/2000; - if(percent_done < 0 || percent_done > 100) percent_done = 100; - addmsg(1, 9, SV_SHOT, d->gunselect, (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF), percent_done); -// printf("grenade: %i\n", (lastmillis-curnade->millis)/100); - if(curnade) curnade = NULL; - }; -}; - -physent *new_physent() -{ - physent *p = (physent *)gp()->alloc(sizeof(physent)); - - p->yaw = 270; - p->pitch = 0; - p->roll = 0; - p->isphysent = true; - - p->maxspeed = 40; - p->outsidemap = false; - p->inwater = false; - p->radius = 0.2f; - p->eyeheight = 0.3f; - p->aboveeye = 0.0f; - p->frags = 0; - p->plag = 0; - p->ping = 0; - p->lastupdate = lastmillis; - p->enemy = NULL; - p->monsterstate = 0; - p->name[0] = p->team[0] = 0; - p->blocked = false; - p->lifesequence = 0; - p->dynent::state = CS_ALIVE; - p->state = PHYSENT_NONE; - p->shots = 0; - p->reloading = false; - p->nextprimary = 1; - p->hasarmour = false; - - p->k_left = false; - p->k_right = false; - p->k_up = false; - p->k_down = false; - p->jumpnext = false; - p->onladder = false; - p->strafe = 0; - p->move = 0; - - physents.add(p); - return p; -} - -physent *new_nade(dynent *d, int millis = 0) -{ - printf("newnade\n"); - physent *p = new_physent(); - p->owner = d; - p->millis = lastmillis; - p->timetolife = 2000-millis; - p->state = NADE_ACTIVATED; - if(d==player1) - { - curnade = p; - d->thrownademillis = 0; - }; - playsound(S_GRENADEPULL, &d->o); - return p; -}; - -void explode_nade(physent *i) -{ - if(!i) return; - printf("explode\n"); - - if(i->state != NADE_THROWED) - { - vec o = i->owner->o; - vec dist = { 0.1f, 0.1f, 0.1f }; - vadd(o, dist); - throw_nade(i->owner, o, i); - }; - playsound(S_FEXPLODE, &i->o); - newprojectile(i->o, i->o, 1, i->owner==player1, i->owner, GUN_GRENADE); -}; - -void shootv(int gun, vec &from, vec &to, dynent *d, bool local, int nademillis) // create visual effect from a shot -{ - playsound(guns[gun].sound, d==player1 ? NULL : &d->o); - int pspeed = 25; - switch(gun) - { - case GUN_KNIFE: - break; - - case GUN_SHOTGUN: - { - loopi(SGRAYS) particle_splash(0, 5, 200, sg[i]); - break; - }; - - case GUN_PISTOL: - case GUN_SUBGUN: - case GUN_ASSULT: - addshotline(d, from, to); - particle_splash(9, 5, 250, to); - //particle_trail(1, 10, from, to); - break; - - case GUN_GRENADE: - { - if(d!=player1) - { - physent *p = new_nade(d, nademillis); - throw_nade(d, to, p); - } - } - break; - - case GUN_SNIPER: - addshotline(d, from, to); - particle_splash(0, 50, 200, to); - particle_trail(1, 500, from, to); - break; - }; -}; - -void hitpush(int target, int damage, dynent *d, dynent *at, vec &from, vec &to) -{ - hit(target, damage, d, at); - vdist(dist, v, from, to); - vmul(v, damage/dist/50); - vadd(d->vel, v); -}; - -void raydamage(dynent *o, vec &from, vec &to, dynent *d, int i) -{ - if(o->state!=CS_ALIVE) return; - int qdam = guns[d->gunselect].damage; - //if(d->quadmillis) qdam *= 4; - if(d->gunselect==GUN_SHOTGUN) - { - int damage = 0; - loop(r, SGRAYS) if(intersect(o, from, sg[r])) damage += qdam; - if(damage) hitpush(i, damage, o, d, from, to); - } - else if(intersect(o, from, to)) hitpush(i, qdam, o, d, from, to); -}; - -void spreadandrecoil(vec & from, vec & to, dynent * d) -{ - //nothing special for a knife - if (d->gunselect==GUN_KNIFE || d->gunselect==GUN_GRENADE) return; - - //spread - vdist(dist, unitv, from, to); - float f = dist/1000; - int spd = guns[d->gunselect].spread; - - //recoil - int rcl = guns[d->gunselect].recoil*-0.01f; - - if(d->gunselect==GUN_ASSULT) - { - if(d->shots > 3) - spd = 70; - rcl += (rnd(8)*-0.01f); - }; - - if((d->gunselect==GUN_SNIPER) && (d->vel.x<.25f && d->vel.y<.25f) && scoped) - { - spd = 1; - rcl = rcl / 3; - }; - - if(d->gunselect!=GUN_SHOTGUN) //no spread on shotgun - { - #define RNDD (rnd(spd)-spd/2)*f - vec r = { RNDD, RNDD, RNDD }; - vadd(to, r); - }; - - //increase pitch for recoil - vdiv(unitv, dist); - vec recoil = unitv; - vmul(recoil, rcl); - vadd(d->vel, recoil); - - if(d->pitch<80.0f) d->pitch += guns[d->gunselect].recoil*0.05f; -}; - -//VAR(grenadepulltime, 0, 100, 10000); -const int grenadepulltime = 650; - -bool akimboside = false; -int akimbolastaction[2] = {0,0}; - - -void shoot(dynent *d, vec &targ) -{ - int attacktime = lastmillis-d->lastaction; - - if(!d->attacking && d->gunselect==GUN_GRENADE && curnade) // throw - { - if(attacktime>grenadepulltime) - { - d->thrownademillis = lastmillis; - throw_nade(d, targ, curnade); - } - return; - }; - - if(attacktimegunwait) return; - d->gunwait = 0; - d->reloading = false; - - if(!d->attacking) - { - d->shots = 0; - return; - }; - - if(d->gunselect!=GUN_SUBGUN && d->gunselect!=GUN_ASSULT && d->gunselect!=GUN_GRENADE) d->attacking = false; //makes sub/assult autos - else d->shots++; - - if(d->gunselect==GUN_PISTOL && d->akimbo) - { - d->attacking = true; //make akimbo auto - akimbolastaction[akimboside?1:0] = lastmillis; - akimboside = !akimboside; - } - - d->lastaction = lastmillis; - d->lastattackgun = d->gunselect; - //if(!d->mag[d->gunselect]) { playsoundc(S_NOAMMO); d->gunwait = 250; d->lastattackgun = -1; return; }; - if(!d->mag[d->gunselect]) - { - if (d->bIsBot) botplaysound(S_NOAMMO, d); - else playsoundc(S_NOAMMO); - d->gunwait = 250; - d->lastattackgun = -1; - return; - }; - // End mod - - if(d->gunselect) d->mag[d->gunselect]--; - - vec from = d->o; - vec to = targ; - from.z -= 0.2f; // below eye - - spreadandrecoil(from,to,d); - - vdist(dist, unitv, from, to); - vdiv(unitv, dist); - - if(d->gunselect==GUN_KNIFE) - { - vmul(unitv, 3); // punch range - to = from; - vadd(to, unitv); - }; - if(d->gunselect==GUN_SHOTGUN) createrays(from, to); - - if(d->gunselect==GUN_PISTOL && d->akimbo) d->gunwait = guns[d->gunselect].attackdelay / 2; //make akimbo pistols shoot twice as fast as normal pistol - else d->gunwait = guns[d->gunselect].attackdelay; - - if(d->gunselect==GUN_GRENADE) // activate - { - if(!curnade) new_nade(d); - return; - } - -//fixmebot - shootv(d->gunselect, from, to, d, 0); - addmsg(1, 9, SV_SHOT, d->gunselect, (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF), 0 ); - - if(guns[d->gunselect].projspeed) return; - - loopv(players) - { - dynent *o = players[i]; - if(!o) continue; - raydamage(o, from, to, d, i); - }; - - // Added by Rick: raydamage on bots too - loopv(bots) - { - dynent *o = bots[i]; - if(!o || (o == d)) continue; - raydamage(o, from, to, d, i); - }; - // End add by Rick - - if(d->bIsBot) raydamage(player1, from, to, d, -1); -}; +// weapon.cpp: all shooting and effects code + +#include "cube.h" + +struct guninfo { short sound, reload, reloadtime, attackdelay, damage, projspeed, part, spread, recoil, magsize, mdl_kick_rot, mdl_kick_back; char *name; }; + +const int SGRAYS = 20; //down from 20 (default) +const float SGSPREAD = 2; +vec sg[SGRAYS]; + +//change the particales +guninfo guns[NUMGUNS] = +{ + { S_KNIFE, S_NULL, 0, 500, 50, 0, 0, 1, 1, 1, 0, 0, "knife" }, + { S_PISTOL, S_RPISTOL, 1400, 170, 20, 0, 0, 80, 10, 8, 6, 5, "pistol" }, // *SGRAYS + { S_SHOTGUN, S_RSHOTGUN, 2400, 1000, 6, 0, 0, 1, 35, 7, 9, 9, "shotgun" }, //reload time is for 1 shell from 7 too powerful to 6 + { S_SUBGUN, S_RSUBGUN, 1650, 80, 17, 0, 0, 70, 15, 30, 1, 2, "subgun" }, + { S_SNIPER, S_RSNIPER, 1950, 1500, 72, 0, 0, 60, 50, 5, 4, 4, "sniper" }, + { S_ASSULT, S_RASSULT, 2000, 130, 20, 0, 0, 20, 40, 20, 0, 2, "assult" }, //recoil was 44 + { S_GRENADE, S_NULL, 1000, 2500, 150, 20, 6, 1, 1, 1, 3, 1, "grenade" }, +}; + + +bool gun_changed = false; + +void weapon(int gun) +{ + if(scoped) togglescope(); + + gun %= G_NUM; + if(gun<0) gun = G_NUM+gun; + gun_changed = true; + switch(gun) + { + case G_PRIMARY: player1->gunselect=player1->primary; break; + case G_SECONDARY: player1->gunselect=GUN_PISTOL; break; + case G_MELEE: player1->gunselect=GUN_KNIFE; break; + case G_GRENADE: player1->gunselect=GUN_GRENADE; break; + }; +}; + +void shiftweapon(int i) +{ + if(scoped) togglescope(); + + int gun; + switch(player1->gunselect) + { + case GUN_KNIFE: gun=G_MELEE; break; + case GUN_PISTOL: gun=G_SECONDARY; break; + case GUN_GRENADE: gun=G_GRENADE; break; + default: gun=G_PRIMARY; break; + }; + gun += i; + weapon(gun); +} + +COMMAND(weapon, ARG_1INT); +COMMAND(shiftweapon, ARG_1INT); + +void altattack() +{ + if(player1->gunselect == GUN_SNIPER) togglescope(); +} + +COMMAND(altattack, ARG_NONE); + +void reload(dynent *d) +{ + if(!d) return; + + if(scoped) togglescope(); + + bool akimbo = d->gunselect==GUN_PISTOL && d->akimbo!=0; + + if(d->gunselect==GUN_KNIFE || d->gunselect==GUN_GRENADE) return; + if(akimbo && d->mag[d->gunselect]>=(guns[d->gunselect].magsize * 2)) return; + else if(d->mag[d->gunselect]>=guns[d->gunselect].magsize && d->akimbo==0) return; + if(d->ammo[d->gunselect]<=0) return; + if(d->reloading) return; + + d->reloading = true; + d->lastaction = lastmillis; + + if(akimbo) + { + akimbolastaction[akimboside?1:0] = lastmillis; + akimbolastaction[akimboside?0:1] = lastmillis;// + (reloadtime(GUN_PISTOL)/2); + }; + + d->gunwait = guns[d->gunselect].reloadtime; + + //temp hack in drawhudgun() so no guns drawn while reloading, needs fixed + + int a = guns[d->gunselect].magsize - d->mag[d->gunselect]; + if(d->gunselect==GUN_PISTOL && d->akimbo!=0) + a = guns[d->gunselect].magsize * 2 - d->mag[d->gunselect]; + + if (a >= d->ammo[d->gunselect]) + { + d->mag[d->gunselect] += d->ammo[d->gunselect]; + d->ammo[d->gunselect] = 0; + } + else + { + d->mag[d->gunselect] += a; + d->ammo[d->gunselect] -= a; + } + + if(akimbo) playsoundc(S_RAKIMBO); + else playsoundc(guns[d->gunselect].reload); +}; + +void selfreload() { reload(player1); }; +COMMANDN(reload, selfreload, ARG_NONE); + +int reloadtime(int gun) { return guns[gun].reloadtime; }; +int attackdelay(int gun) { return guns[gun].attackdelay; }; +int magsize(int gun) { return guns[gun].magsize; }; +int kick_rot(int gun) { return guns[gun].mdl_kick_rot; }; +int kick_back(int gun) { return guns[gun].mdl_kick_back; }; + + +void createrays(vec &from, vec &to) // create random spread of rays for the shotgun +{ + vdist(dist, dvec, from, to); + float f = dist*SGSPREAD/1000; + loopi(SGRAYS) + { + #define RNDD (rnd(101)-50)*f + vec r = { RNDD, RNDD, RNDD }; + sg[i] = to; + vadd(sg[i], r); + }; +}; + +//bool intersect(dynent *d, vec &from, vec &to) // if lineseg hits entity bounding box +bool intersect(dynent *d, vec &from, vec &to, vec *end) // if lineseg hits entity bounding box +{ + vec v = to, w = d->o, *p; + vsub(v, from); + vsub(w, from); + float c1 = dotprod(w, v); + + if(c1<=0) p = &from; + else + { + float c2 = dotprod(v, v); + if(c2<=c1) p = &to; + else + { + float f = c1/c2; + vmul(v, f); + vadd(v, from); + p = &v; + }; + }; + + return p->x <= d->o.x+d->radius + && p->x >= d->o.x-d->radius + && p->y <= d->o.y+d->radius + && p->y >= d->o.y-d->radius + && p->z <= d->o.z+d->aboveeye + && p->z >= d->o.z-d->eyeheight; +}; + +dynent *playerincrosshair() +{ + if(demoplayback) return NULL; + loopv(players) + { + dynent *o = players[i]; + if(!o) continue; + if(intersect(o, player1->o, worldpos)) return o; + }; + return NULL; +}; +// Added by Rick +char *botincrosshair() +{ + if(demoplayback) return NULL; + loopv(bots) + { + dynent *o = bots[i]; + if(!o) continue; + if(intersect(o, player1->o, worldpos)) return o->name; + }; + return NULL; +}; +// End add by Rick + +const int MAXPROJ = 100; +struct projectile { vec o, to; float speed; dynent *owner; int gun; bool inuse, local; }; +projectile projs[MAXPROJ]; + +void projreset() { loopi(MAXPROJ) projs[i].inuse = false; }; + +void newprojectile(vec &from, vec &to, float speed, bool local, dynent *owner, int gun) +{ + loopi(MAXPROJ) + { + projectile *p = &projs[i]; + if(p->inuse) continue; + p->inuse = true; + p->o = from; + p->to = to; + p->speed = speed; + p->local = local; + p->owner = owner; + p->gun = gun; + return; + }; +}; + +void hit(int target, int damage, dynent *d, dynent *at) +{ + if(d==player1) selfdamage(damage, at==player1 ? -1 : -2, at); + //else if(d->monsterstate) monsterpain(d, damage, at); + // Added by Rick: Let bots take damage + else if (d->pBot) d->pBot->BotPain(damage, at); + else if (d->bIsBot) + { + int PlayerIndex = -1; + if (at->bIsBot) PlayerIndex = BotManager.GetBotIndex(at); + else + { + loopv(players) + { + if (!players[i]) continue; + + if (players[i] == at) + { + PlayerIndex = i; + break; + } + } + } + + int msgtype = (at->bIsBot) ? SV_BOT2BOTDMG : SV_CLIENT2BOTDMG; + addmsg(1, 4, msgtype, BotManager.GetBotIndex(d), damage, PlayerIndex); + playsound(S_PAIN1+rnd(5), &d->o); + } + // End add by Rick + //else { addmsg(1, 4, SV_DAMAGE, target, damage, d->lifesequence); playsound(S_PAIN1+rnd(5), &d->o); }; Modified by Rick: Added IsBot and PlayerIndex to message + else + { + // Modified by Rick: Added IsBot and PlayerIndex to message + int PlayerIndex = -1; + if (at->bIsBot) + PlayerIndex = BotManager.GetBotIndex(at); + else + { + loopv(players) + { + if (!players[i]) + continue; + + if (players[i] == at) + { + PlayerIndex = i; + break; + } + } + } + if(at->bIsBot) addmsg(1, 5, SV_BOT2CLIENTDMG, target, damage, d->lifesequence, PlayerIndex); + else addmsg(1, 4, SV_DAMAGE, target, damage, d->lifesequence); + playsound(S_PAIN1+rnd(5), &d->o); + }; + + particle_splash(3, damage, 1000, d->o); + demodamage(damage, d->o); +}; + +const float RL_RADIUS = 7; +const float RL_DAMRAD = 10; // hack + +void radialeffect(dynent *o, vec &v, int cn, int qdam, dynent *at) +{ + if(o->state!=CS_ALIVE) return; + vdist(dist, temp, v, o->o); + dist -= 2; // account for eye distance imprecision + if(distvel, temp); + }; +}; + +// Modified by Rick: Added notthisbot +void splash(projectile *p, vec &v, vec &vold, int notthisplayer, int notthismonster, int notthisbot, int qdam) +{ + particle_splash(0, 50, 300, v); + p->inuse = false; + if(p->gun!=GUN_GRENADE) + { + playsound(S_FEXPLODE, &v); + // no push? + } + else + { + //playsound(S_RLHIT, &v); + newsphere(v, RL_RADIUS, 0); + dodynlight(vold, v, 0, 0, p->owner); + if(!p->local) return; + radialeffect(player1, v, -1, qdam, p->owner); + loopv(players) + { + if(i==notthisplayer) continue; + dynent *o = players[i]; + if(!o) continue; + radialeffect(o, v, i, qdam, p->owner); + }; + // Added by Rick + loopv(bots) + { + if(i==notthisbot) continue; + dynent *o = bots[i]; + if(!o) continue; + radialeffect(o, v, i, qdam, p->owner); + } + // End add by Rick + //dvector &mv = getmonsters(); + //loopv(mv) if(i!=notthismonster) radialeffect(mv[i], v, i, qdam, p->owner); + }; +}; + +// Modified by Rick +// inline void projdamage(dynent *o, projectile *p, vec &v, int i, int im, int qdam) +inline void projdamage(dynent *o, projectile *p, vec &v, int i, int im, int ib, int qdam) +{ + if(o->state!=CS_ALIVE) return; + if(intersect(o, p->o, v)) + { + // splash(p, v, p->o, i, im, qdam); Modified by Rick + splash(p, v, p->o, i, im, ib, qdam); + hit(i, qdam, o, p->owner); + }; +}; + +void moveprojectiles(float time) +{ + loopi(MAXPROJ) + { + projectile *p = &projs[i]; + if(!p->inuse) continue; + //int qdam = guns[p->gun].damage*(p->owner->quadmillis ? 4 : 1); + int qdam = guns[p->gun].damage; + vdist(dist, v, p->o, p->to); + float dtime = dist*1000/p->speed; + if(time>dtime) dtime = time; + vmul(v, time/dtime); + vadd(v, p->o) + if(p->local) + { + loopv(players) + { + dynent *o = players[i]; + if(!o) continue; + // projdamage(o, p, v, i, -1, qdam); Modified by Rick + projdamage(o, p, v, i, -1, -1, qdam); + }; + // Added by Rick: Damage bots aswell + loopv(bots) + { + dynent *o = bots[i]; + if(!o || (o == p->owner)) continue; + projdamage(o, p, v, -1, -1, i, qdam); + }; + // End add by Rick + // if(p->owner!=player1) projdamage(player1, p, v, -1, -1, qdam); Modified by Rick + if(p->owner!=player1) projdamage(player1, p, v, -1, -1, -1, qdam); + //dvector &mv = getmonsters(); + //loopv(mv) if(!vreject(mv[i]->o, v, 10.0f) && mv[i]!=p->owner) projdamage(mv[i], p, v, -1, i, qdam); Modified by Rick + //loopv(mv) if(!vreject(mv[i]->o, v, 10.0f) && mv[i]!=p->owner) projdamage(mv[i], p, v, -1, i, -1, qdam); + }; + if(p->inuse) + { + // if(time==dtime) splash(p, v, p->o, -1, -1, qdam); Modified by Rick + if(time==dtime) splash(p, v, p->o, -1, -1, -1, qdam); + else + { + if(p->gun==GUN_GRENADE) { dodynlight(p->o, v, 0, 255, p->owner); particle_splash(5, 2, 200, v); } + else { particle_splash(1, 1, 200, v); particle_splash(guns[p->gun].part, 1, 1, v); }; + // Added by Rick + traceresult_s tr; + TraceLine(p->o, v, p->owner, true, &tr); + if (tr.collided) splash(p, v, p->o, -1, -1, -1, qdam); + // End add + }; + }; + p->o = v; + }; +}; + +extern float rad(float x); + +physent *curnade = NULL; + +void throw_nade(dynent *d, vec &to, physent *p) +{ + if(!p || !d) return; + printf("thrownade\n"); + playsound(S_GRENADETHROW, &d->o); + + p->isphysent = true; + p->gravity = 20; + p->state = NADE_THROWED; + + p->timeinair = 0; + p->onfloor = false; + + p->o = d->o; + p->vel.z = sin(rad(d->pitch)); + float speed = cos(rad(d->pitch)); + p->vel.x = sin(rad(d->yaw))*speed; + p->vel.y = -cos(rad(d->yaw))*speed; + + vmul(p->vel, 1.7f); + + vec throwdir = p->vel; + vmul(throwdir, d->radius); + vadd(p->o, throwdir); + vadd(p->o, throwdir); + + vec &from = d->o; + + if(d==player1) + { + int percent_done = (lastmillis-p->millis)*100/2000; + if(percent_done < 0 || percent_done > 100) percent_done = 100; + addmsg(1, 9, SV_SHOT, d->gunselect, (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF), percent_done); +// printf("grenade: %i\n", (lastmillis-curnade->millis)/100); + if(curnade) curnade = NULL; + }; +}; + +physent *new_physent() +{ + physent *p = (physent *)gp()->alloc(sizeof(physent)); + + p->yaw = 270; + p->pitch = 0; + p->roll = 0; + p->isphysent = true; + + p->maxspeed = 40; + p->outsidemap = false; + p->inwater = false; + p->radius = 0.2f; + p->eyeheight = 0.3f; + p->aboveeye = 0.0f; + p->frags = 0; + p->plag = 0; + p->ping = 0; + p->lastupdate = lastmillis; + p->enemy = NULL; + p->monsterstate = 0; + p->name[0] = p->team[0] = 0; + p->blocked = false; + p->lifesequence = 0; + p->dynent::state = CS_ALIVE; + p->state = PHYSENT_NONE; + p->shots = 0; + p->reloading = false; + p->nextprimary = 1; + p->hasarmour = false; + + p->k_left = false; + p->k_right = false; + p->k_up = false; + p->k_down = false; + p->jumpnext = false; + p->onladder = false; + p->strafe = 0; + p->move = 0; + + physents.add(p); + return p; +} + +physent *new_nade(dynent *d, int millis = 0) +{ + printf("newnade\n"); + physent *p = new_physent(); + p->owner = d; + p->millis = lastmillis; + p->timetolife = 2000-millis; + p->state = NADE_ACTIVATED; + if(d==player1) + { + curnade = p; + d->thrownademillis = 0; + }; + playsound(S_GRENADEPULL, &d->o); + return p; +}; + +void explode_nade(physent *i) +{ + if(!i) return; + printf("explode\n"); + + if(i->state != NADE_THROWED) + { + vec o = i->owner->o; + vec dist = { 0.1f, 0.1f, 0.1f }; + vadd(o, dist); + throw_nade(i->owner, o, i); + }; + playsound(S_FEXPLODE, &i->o); + newprojectile(i->o, i->o, 1, i->owner==player1, i->owner, GUN_GRENADE); +}; + +void shootv(int gun, vec &from, vec &to, dynent *d, bool local, int nademillis) // create visual effect from a shot +{ + playsound(guns[gun].sound, d==player1 ? NULL : &d->o); + int pspeed = 25; + switch(gun) + { + case GUN_KNIFE: + break; + + case GUN_SHOTGUN: + { + loopi(SGRAYS) particle_splash(0, 5, 200, sg[i]); + break; + }; + + case GUN_PISTOL: + case GUN_SUBGUN: + case GUN_ASSULT: + addshotline(d, from, to); + particle_splash(0, 5, 250, to); + //particle_trail(1, 10, from, to); + break; + + case GUN_GRENADE: + { + if(d!=player1) + { + physent *p = new_nade(d, nademillis); + throw_nade(d, to, p); + } + } + break; + + case GUN_SNIPER: + addshotline(d, from, to); + particle_splash(0, 50, 200, to); + particle_trail(1, 500, from, to); + break; + }; +}; + +void hitpush(int target, int damage, dynent *d, dynent *at, vec &from, vec &to) +{ + hit(target, damage, d, at); + vdist(dist, v, from, to); + vmul(v, damage/dist/50); + vadd(d->vel, v); +}; + +void raydamage(dynent *o, vec &from, vec &to, dynent *d, int i) +{ + if(o->state!=CS_ALIVE) return; + int qdam = guns[d->gunselect].damage; + //if(d->quadmillis) qdam *= 4; + if(d->gunselect==GUN_SHOTGUN) + { + int damage = 0; + loop(r, SGRAYS) if(intersect(o, from, sg[r])) damage += qdam; + if(damage) hitpush(i, damage, o, d, from, to); + } + else if(intersect(o, from, to)) hitpush(i, qdam, o, d, from, to); +}; + +void spreadandrecoil(vec & from, vec & to, dynent * d) +{ + //nothing special for a knife + if (d->gunselect==GUN_KNIFE || d->gunselect==GUN_GRENADE) return; + + //spread + vdist(dist, unitv, from, to); + float f = dist/1000; + int spd = guns[d->gunselect].spread; + + //recoil + int rcl = guns[d->gunselect].recoil*-0.01f; + + if(d->gunselect==GUN_ASSULT) + { + if(d->shots > 3) + spd = 70; + rcl += (rnd(8)*-0.01f); + }; + + if((d->gunselect==GUN_SNIPER) && (d->vel.x<.25f && d->vel.y<.25f) && scoped) + { + spd = 1; + rcl = rcl / 3; + }; + + if(d->gunselect!=GUN_SHOTGUN) //no spread on shotgun + { + #define RNDD (rnd(spd)-spd/2)*f + vec r = { RNDD, RNDD, RNDD }; + vadd(to, r); + }; + + //increase pitch for recoil + vdiv(unitv, dist); + vec recoil = unitv; + vmul(recoil, rcl); + vadd(d->vel, recoil); + + if(d->pitch<80.0f) d->pitch += guns[d->gunselect].recoil*0.05f; +}; + +//VAR(grenadepulltime, 0, 100, 10000); +const int grenadepulltime = 650; + +bool akimboside = false; +int akimbolastaction[2] = {0,0}; + + +void shoot(dynent *d, vec &targ) +{ + int attacktime = lastmillis-d->lastaction; + + if(!d->attacking && d->gunselect==GUN_GRENADE && curnade) // throw + { + if(attacktime>grenadepulltime) + { + d->thrownademillis = lastmillis; + throw_nade(d, targ, curnade); + } + return; + }; + + if(attacktimegunwait) return; + d->gunwait = 0; + d->reloading = false; + + if(!d->attacking) + { + d->shots = 0; + return; + }; + + if(d->gunselect!=GUN_SUBGUN && d->gunselect!=GUN_ASSULT && d->gunselect!=GUN_GRENADE) d->attacking = false; //makes sub/assult autos + else d->shots++; + + if(d->gunselect==GUN_PISTOL && d->akimbo) + { + d->attacking = true; //make akimbo auto + akimbolastaction[akimboside?1:0] = lastmillis; + akimboside = !akimboside; + } + + d->lastaction = lastmillis; + d->lastattackgun = d->gunselect; + //if(!d->mag[d->gunselect]) { playsoundc(S_NOAMMO); d->gunwait = 250; d->lastattackgun = -1; return; }; + if(!d->mag[d->gunselect]) + { + if (d->bIsBot) botplaysound(S_NOAMMO, d); + else playsoundc(S_NOAMMO); + d->gunwait = 250; + d->lastattackgun = -1; + return; + }; + // End mod + + if(d->gunselect) d->mag[d->gunselect]--; + + vec from = d->o; + vec to = targ; + from.z -= 0.2f; // below eye + + spreadandrecoil(from,to,d); + + vdist(dist, unitv, from, to); + vdiv(unitv, dist); + + if(d->gunselect==GUN_KNIFE) + { + vmul(unitv, 3); // punch range + to = from; + vadd(to, unitv); + }; + if(d->gunselect==GUN_SHOTGUN) createrays(from, to); + + if(d->gunselect==GUN_PISTOL && d->akimbo) d->gunwait = guns[d->gunselect].attackdelay / 2; //make akimbo pistols shoot twice as fast as normal pistol + else d->gunwait = guns[d->gunselect].attackdelay; + + if(d->gunselect==GUN_GRENADE) // activate + { + if(!curnade) new_nade(d); + return; + } + +//fixmebot + shootv(d->gunselect, from, to, d, 0); + addmsg(1, 9, SV_SHOT, d->gunselect, (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF), 0 ); + + if(guns[d->gunselect].projspeed) return; + + loopv(players) + { + dynent *o = players[i]; + if(!o) continue; + raydamage(o, from, to, d, i); + }; + + // Added by Rick: raydamage on bots too + loopv(bots) + { + dynent *o = bots[i]; + if(!o || (o == d)) continue; + raydamage(o, from, to, d, i); + }; + // End add by Rick + + if(d->bIsBot) raydamage(player1, from, to, d, -1); +}; diff --git a/source/src/world.cpp b/source/src/world.cpp index 0879fff1..7a7fdd7b 100644 --- a/source/src/world.cpp +++ b/source/src/world.cpp @@ -1,398 +1,398 @@ -// world.cpp: core map management stuff - -#include "cube.h" - -extern char *entnames[]; // lookup from map entities above to strings - -sqr *world = NULL; -int sfactor, ssize, cubicsize, mipsize; - -header hdr; - -void settag(int tag, int type) // set all cubes with "tag" to space, if tag is 0 then reset ALL tagged cubes according to type -{ - int maxx = 0, maxy = 0, minx = ssize, miny = ssize; - loop(x, ssize) loop(y, ssize) - { - sqr *s = S(x, y); - if(s->tag) - { - if(tag) - { - if(tag==s->tag) s->type = SPACE; - else continue; - } - else - { - s->type = type ? SOLID : SPACE; - }; - if(x>maxx) maxx = x; - if(y>maxy) maxy = y; - if(x=SMALLEST_FACTOR) return; - int lighterr = getvar("lighterror")*3; - sqr *w = wmip[level]; - sqr *v = wmip[level+1]; - int ws = ssize>>level; - int vs = ssize>>(level+1); - block s = b; - if(s.x&1) { s.x--; s.xs++; }; - if(s.y&1) { s.y--; s.ys++; }; - s.xs = (s.xs+1)&~1; - s.ys = (s.ys+1)&~1; - for(int x = s.x; xtype]++; - r->type = SEMISOLID; // cube contains both solid and space, treated specially in the renderer - loopk(MAXTYPE) if(nums[k]==4) r->type = k; - if(!SOLID(r)) - { - int floor = 127, ceil = -128, num = 0; - loopi(4) if(!SOLID(o[i])) - { - num++; - int fh = o[i]->floor; - int ch = o[i]->ceil; - if(r->type==SEMISOLID) - { - if(o[i]->type==FHF) fh -= o[i]->vdelta/4+2; // crap hack, needed for rendering large mips next to hfs - if(o[i]->type==CHF) ch += o[i]->vdelta/4+2; // FIXME: needs to somehow take into account middle vertices on higher mips - }; - if(fhceil) ceil = ch; - }; - r->floor = floor; - r->ceil = ceil; - }; - if(r->type==CORNER) goto mip; // special case: don't ever split even if textures etc are different - r->defer = 1; - if(SOLID(r)) - { - loopi(3) - { - if(o[i]->wtex!=o[3]->wtex) goto c; // on an all solid cube, only thing that needs to be equal for a perfect mip is the wall texture - }; - } - else - { - loopi(3) - { - if(o[i]->type!=o[3]->type - || o[i]->floor!=o[3]->floor - || o[i]->ceil!=o[3]->ceil - || o[i]->ftex!=o[3]->ftex - || o[i]->ctex!=o[3]->ctex - || abs(o[i+1]->r-o[0]->r)>lighterr // perfect mip even if light is not exactly equal - || abs(o[i+1]->g-o[0]->g)>lighterr - || abs(o[i+1]->b-o[0]->b)>lighterr - || o[i]->utex!=o[3]->utex - || o[i]->wtex!=o[3]->wtex) goto c; - }; - if(r->type==CHF || r->type==FHF) // can make a perfect mip out of a hf if slopes lie on one line - { - if(o[0]->vdelta-o[1]->vdelta != o[1]->vdelta-SWS(w,x+2,y,ws)->vdelta - || o[0]->vdelta-o[2]->vdelta != o[2]->vdelta-SWS(w,x+2,y+2,ws)->vdelta - || o[0]->vdelta-o[3]->vdelta != o[3]->vdelta-SWS(w,x,y+2,ws)->vdelta - || o[3]->vdelta-o[2]->vdelta != o[2]->vdelta-SWS(w,x+2,y+1,ws)->vdelta - || o[1]->vdelta-o[2]->vdelta != o[2]->vdelta-SWS(w,x+1,y+2,ws)->vdelta) goto c; - }; - }; - { loopi(4) if(o[i]->defer) goto c; }; // if any of the constituents is not perfect, then this one isn't either - mip: - r->defer = 0; - c:; - }; - s.x /= 2; - s.y /= 2; - s.xs /= 2; - s.ys /= 2; - remip(s, level+1); -}; - -void remipmore(block &b, int level) -{ - block bb = b; - if(bb.x>1) bb.x--; - if(bb.y>1) bb.y--; - if(bb.xso, v); - if(dist64) v1 = 64;//if(v1>32) v1=32; - if(!v1) e.attr1 = 16; - if(!v2 && !v3 && !v4) e.attr2 = 255; - break; - - case MAPMODEL: - e.attr4 = e.attr3; - e.attr3 = e.attr2; - e.attr2 = (uchar)e.attr1; - case PLAYERSTART: - case CTF_FLAG: - e.attr2 = v1; - e.attr1 = (int)player1->yaw; - break; - }; - addmsg(1, 10, SV_EDITENT, ents.length(), type, e.x, e.y, e.z, e.attr1, e.attr2, e.attr3, e.attr4); - ents.add(*((entity *)&e)); // unsafe! - if(type==LIGHT) calclight(); - return &ents.last(); -}; - -void mdlcheck() -{ - loopv(ents) - { - entity &e = ents[i]; - printf("%i %i %i %i %i\n", e.type, e.attr1, e.attr2, e.attr3, e.attr4); - } -}; COMMAND(mdlcheck, ARG_NONE); - -void clearents(char *name) -{ - int type = findtype(name); - if(noteditmode() || multiplayer()) return; - loopv(ents) - { - entity &e = ents[i]; - if(e.type==type) e.type = NOTUSED; - }; - if(type==LIGHT) calclight(); -}; - -COMMAND(clearents, ARG_1STR); - -void scalecomp(uchar &c, int intens) -{ - int n = c*intens/100; - if(n>255) n = 255; - c = n; -}; - -void scalelights(int f, int intens) -{ - loopv(ents) - { - entity &e = ents[i]; - if(e.type!=LIGHT) continue; - e.attr1 = e.attr1*f/100; - if(e.attr1<2) e.attr1 = 2; - if(e.attr1>32) e.attr1 = 32; - if(intens) - { - scalecomp(e.attr2, intens); - scalecomp(e.attr3, intens); - scalecomp(e.attr4, intens); - }; - }; - calclight(); -}; - -COMMAND(scalelights, ARG_2INT); - -int findentity(int type, int index) -{ - for(int i = index; i>(i*2); }; -}; - -void empty_world(int factor, bool force) // main empty world creation routine, if passed factor -1 will enlarge old world by 1 -{ - if(!force && noteditmode()) return; - cleardlights(); - pruneundos(); - sqr *oldworld = world; - bool copy = false; - if(oldworld && factor<0) { factor = sfactor+1; copy = true; }; - if(factorLARGEST_FACTOR) factor = LARGEST_FACTOR; - setupworld(factor); - - loop(x,ssize) loop(y,ssize) - { - sqr *s = S(x,y); - s->r = s->g = s->b = 150; - s->ftex = DEFAULT_FLOOR; - s->ctex = DEFAULT_CEIL; - s->wtex = s->utex = DEFAULT_WALL; - s->type = SOLID; - s->floor = 0; - s->ceil = 16; - s->vdelta = 0; - s->defer = 0; - }; - - strncpy(hdr.head, "ACMP", 4); - hdr.version = MAPVERSION; - hdr.headersize = sizeof(header); - hdr.sfactor = sfactor; - - if(copy) - { - loop(x,ssize/2) loop(y,ssize/2) - { - *S(x+ssize/4, y+ssize/4) = *SWS(oldworld, x, y, ssize/2); - }; - loopv(ents) - { - ents[i].x += ssize/4; - ents[i].y += ssize/4; - }; - } - else - { - strn0cpy(hdr.maptitle, "Untitled Map by Unknown", 128); - hdr.waterlevel = -100000; - loopi(15) hdr.reserved[i] = 0; - loopk(3) loopi(256) hdr.texlists[k][i] = i; - ents.setsize(0); - block b = { 8, 8, ssize-16, ssize-16 }; - edittypexy(SPACE, b); - }; - - calclight(); - startmap("base/unnamed"); - if(oldworld) - { - free(oldworld); - toggleedit(); - execfile("config/default_map_settings.cfg"); - execute("fullbright 1"); - }; -}; - -void mapenlarge() { empty_world(-1, false); }; -void newmap(int i) { empty_world(i, false); }; - -COMMAND(mapenlarge, ARG_NONE); -COMMAND(newmap, ARG_1INT); -COMMANDN(recalc, calclight, ARG_NONE); -COMMAND(delent, ARG_NONE); -COMMAND(entproperty, ARG_2INT); - +// world.cpp: core map management stuff + +#include "cube.h" + +extern char *entnames[]; // lookup from map entities above to strings + +sqr *world = NULL; +int sfactor, ssize, cubicsize, mipsize; + +header hdr; + +void settag(int tag, int type) // set all cubes with "tag" to space, if tag is 0 then reset ALL tagged cubes according to type +{ + int maxx = 0, maxy = 0, minx = ssize, miny = ssize; + loop(x, ssize) loop(y, ssize) + { + sqr *s = S(x, y); + if(s->tag) + { + if(tag) + { + if(tag==s->tag) s->type = SPACE; + else continue; + } + else + { + s->type = type ? SOLID : SPACE; + }; + if(x>maxx) maxx = x; + if(y>maxy) maxy = y; + if(x=SMALLEST_FACTOR) return; + int lighterr = getvar("lighterror")*3; + sqr *w = wmip[level]; + sqr *v = wmip[level+1]; + int ws = ssize>>level; + int vs = ssize>>(level+1); + block s = b; + if(s.x&1) { s.x--; s.xs++; }; + if(s.y&1) { s.y--; s.ys++; }; + s.xs = (s.xs+1)&~1; + s.ys = (s.ys+1)&~1; + for(int x = s.x; xtype]++; + r->type = SEMISOLID; // cube contains both solid and space, treated specially in the renderer + loopk(MAXTYPE) if(nums[k]==4) r->type = k; + if(!SOLID(r)) + { + int floor = 127, ceil = -128, num = 0; + loopi(4) if(!SOLID(o[i])) + { + num++; + int fh = o[i]->floor; + int ch = o[i]->ceil; + if(r->type==SEMISOLID) + { + if(o[i]->type==FHF) fh -= o[i]->vdelta/4+2; // crap hack, needed for rendering large mips next to hfs + if(o[i]->type==CHF) ch += o[i]->vdelta/4+2; // FIXME: needs to somehow take into account middle vertices on higher mips + }; + if(fhceil) ceil = ch; + }; + r->floor = floor; + r->ceil = ceil; + }; + if(r->type==CORNER) goto mip; // special case: don't ever split even if textures etc are different + r->defer = 1; + if(SOLID(r)) + { + loopi(3) + { + if(o[i]->wtex!=o[3]->wtex) goto c; // on an all solid cube, only thing that needs to be equal for a perfect mip is the wall texture + }; + } + else + { + loopi(3) + { + if(o[i]->type!=o[3]->type + || o[i]->floor!=o[3]->floor + || o[i]->ceil!=o[3]->ceil + || o[i]->ftex!=o[3]->ftex + || o[i]->ctex!=o[3]->ctex + || abs(o[i+1]->r-o[0]->r)>lighterr // perfect mip even if light is not exactly equal + || abs(o[i+1]->g-o[0]->g)>lighterr + || abs(o[i+1]->b-o[0]->b)>lighterr + || o[i]->utex!=o[3]->utex + || o[i]->wtex!=o[3]->wtex) goto c; + }; + if(r->type==CHF || r->type==FHF) // can make a perfect mip out of a hf if slopes lie on one line + { + if(o[0]->vdelta-o[1]->vdelta != o[1]->vdelta-SWS(w,x+2,y,ws)->vdelta + || o[0]->vdelta-o[2]->vdelta != o[2]->vdelta-SWS(w,x+2,y+2,ws)->vdelta + || o[0]->vdelta-o[3]->vdelta != o[3]->vdelta-SWS(w,x,y+2,ws)->vdelta + || o[3]->vdelta-o[2]->vdelta != o[2]->vdelta-SWS(w,x+2,y+1,ws)->vdelta + || o[1]->vdelta-o[2]->vdelta != o[2]->vdelta-SWS(w,x+1,y+2,ws)->vdelta) goto c; + }; + }; + { loopi(4) if(o[i]->defer) goto c; }; // if any of the constituents is not perfect, then this one isn't either + mip: + r->defer = 0; + c:; + }; + s.x /= 2; + s.y /= 2; + s.xs /= 2; + s.ys /= 2; + remip(s, level+1); +}; + +void remipmore(block &b, int level) +{ + block bb = b; + if(bb.x>1) bb.x--; + if(bb.y>1) bb.y--; + if(bb.xso, v); + if(dist64) v1 = 64;//if(v1>32) v1=32; + if(!v1) e.attr1 = 16; + if(!v2 && !v3 && !v4) e.attr2 = 255; + break; + + case MAPMODEL: + e.attr4 = e.attr3; + e.attr3 = e.attr2; + e.attr2 = (uchar)e.attr1; + case PLAYERSTART: + case CTF_FLAG: + e.attr2 = v1; + e.attr1 = (int)player1->yaw; + break; + }; + addmsg(1, 10, SV_EDITENT, ents.length(), type, e.x, e.y, e.z, e.attr1, e.attr2, e.attr3, e.attr4); + ents.add(*((entity *)&e)); // unsafe! + if(type==LIGHT) calclight(); + return &ents.last(); +}; + +void mdlcheck() +{ + loopv(ents) + { + entity &e = ents[i]; + printf("%i %i %i %i %i\n", e.type, e.attr1, e.attr2, e.attr3, e.attr4); + } +}; COMMAND(mdlcheck, ARG_NONE); + +void clearents(char *name) +{ + int type = findtype(name); + if(noteditmode() || multiplayer()) return; + loopv(ents) + { + entity &e = ents[i]; + if(e.type==type) e.type = NOTUSED; + }; + if(type==LIGHT) calclight(); +}; + +COMMAND(clearents, ARG_1STR); + +void scalecomp(uchar &c, int intens) +{ + int n = c*intens/100; + if(n>255) n = 255; + c = n; +}; + +void scalelights(int f, int intens) +{ + loopv(ents) + { + entity &e = ents[i]; + if(e.type!=LIGHT) continue; + e.attr1 = e.attr1*f/100; + if(e.attr1<2) e.attr1 = 2; + if(e.attr1>32) e.attr1 = 32; + if(intens) + { + scalecomp(e.attr2, intens); + scalecomp(e.attr3, intens); + scalecomp(e.attr4, intens); + }; + }; + calclight(); +}; + +COMMAND(scalelights, ARG_2INT); + +int findentity(int type, int index) +{ + for(int i = index; i>(i*2); }; +}; + +void empty_world(int factor, bool force) // main empty world creation routine, if passed factor -1 will enlarge old world by 1 +{ + if(!force && noteditmode()) return; + cleardlights(); + pruneundos(); + sqr *oldworld = world; + bool copy = false; + if(oldworld && factor<0) { factor = sfactor+1; copy = true; }; + if(factorLARGEST_FACTOR) factor = LARGEST_FACTOR; + setupworld(factor); + + loop(x,ssize) loop(y,ssize) + { + sqr *s = S(x,y); + s->r = s->g = s->b = 150; + s->ftex = DEFAULT_FLOOR; + s->ctex = DEFAULT_CEIL; + s->wtex = s->utex = DEFAULT_WALL; + s->type = SOLID; + s->floor = 0; + s->ceil = 16; + s->vdelta = 0; + s->defer = 0; + }; + + strncpy(hdr.head, "ACMP", 4); + hdr.version = MAPVERSION; + hdr.headersize = sizeof(header); + hdr.sfactor = sfactor; + + if(copy) + { + loop(x,ssize/2) loop(y,ssize/2) + { + *S(x+ssize/4, y+ssize/4) = *SWS(oldworld, x, y, ssize/2); + }; + loopv(ents) + { + ents[i].x += ssize/4; + ents[i].y += ssize/4; + }; + } + else + { + strn0cpy(hdr.maptitle, "Untitled Map by Unknown", 128); + hdr.waterlevel = -100000; + loopi(15) hdr.reserved[i] = 0; + loopk(3) loopi(256) hdr.texlists[k][i] = i; + ents.setsize(0); + block b = { 8, 8, ssize-16, ssize-16 }; + edittypexy(SPACE, b); + }; + + calclight(); + startmap("base/unnamed"); + if(oldworld) + { + free(oldworld); + toggleedit(); + execfile("config/default_map_settings.cfg"); + execute("fullbright 1"); + }; +}; + +void mapenlarge() { empty_world(-1, false); }; +void newmap(int i) { empty_world(i, false); }; + +COMMAND(mapenlarge, ARG_NONE); +COMMAND(newmap, ARG_1INT); +COMMANDN(recalc, calclight, ARG_NONE); +COMMAND(delent, ARG_NONE); +COMMAND(entproperty, ARG_2INT); + diff --git a/source/src/worldio.cpp b/source/src/worldio.cpp index fe0fb661..3a5b8d6d 100644 --- a/source/src/worldio.cpp +++ b/source/src/worldio.cpp @@ -1,368 +1,368 @@ -// worldio.cpp: loading & saving of maps and savegames - -#include "cube.h" - -void backup(char *name, char *backupname) -{ - remove(backupname); - rename(name, backupname); -}; - -string cgzname, bakname, pcfname, mcfname; - -void setnames(char *name) -{ - string pakname, mapname; - char *slash = strpbrk(name, "/\\"); - if(slash) - { - strn0cpy(pakname, name, slash-name+1); - strcpy_s(mapname, slash+1); - } - else - { - strcpy_s(pakname, "maps"); - strcpy_s(mapname, name); - }; - sprintf_s(cgzname)("packages/%s/%s.cgz", pakname, mapname); - sprintf_s(bakname)("packages/%s/%s_%d.BAK", pakname, mapname, lastmillis); - sprintf_s(pcfname)("packages/%s/package.cfg", pakname); - sprintf_s(mcfname)("packages/%s/%s.cfg", pakname, mapname); - - path(cgzname); - path(bakname); -}; - -// the optimize routines below are here to reduce the detrimental effects of messy mapping by -// setting certain properties (vdeltas and textures) to neighbouring values wherever there is no -// visible difference. This allows the mipmapper to generate more efficient mips. -// the reason it is done on save is to reduce the amount spend in the mipmapper (as that is done -// in realtime). - -inline bool nhf(sqr *s) { return s->type!=FHF && s->type!=CHF; }; - -void voptimize() // reset vdeltas on non-hf cubes -{ - loop(x, ssize) loop(y, ssize) - { - sqr *s = S(x, y); - if(x && y) { if(nhf(s) && nhf(S(x-1, y)) && nhf(S(x-1, y-1)) && nhf(S(x, y-1))) s->vdelta = 0; } - else s->vdelta = 0; - }; -}; - -void topt(sqr *s, bool &wf, bool &uf, int &wt, int &ut) -{ - sqr *o[4]; - o[0] = SWS(s,0,-1,ssize); - o[1] = SWS(s,0,1,ssize); - o[2] = SWS(s,1,0,ssize); - o[3] = SWS(s,-1,0,ssize); - wf = true; - uf = true; - if(SOLID(s)) - { - loopi(4) if(!SOLID(o[i])) - { - wf = false; - wt = s->wtex; - ut = s->utex; - return; - }; - } - else - { - loopi(4) if(!SOLID(o[i])) - { - if(o[i]->floorfloor) { wt = s->wtex; wf = false; }; - if(o[i]->ceil>s->ceil) { ut = s->utex; uf = false; }; - }; - }; -}; - -void toptimize() // FIXME: only does 2x2, make atleast for 4x4 also -{ - bool wf[4], uf[4]; - sqr *s[4]; - for(int x = 2; xwtex, ut = s[0]->utex; - topt(s[0], wf[0], uf[0], wt, ut); - topt(s[1] = SWS(s[0],0,1,ssize), wf[1], uf[1], wt, ut); - topt(s[2] = SWS(s[0],1,1,ssize), wf[2], uf[2], wt, ut); - topt(s[3] = SWS(s[0],1,0,ssize), wf[3], uf[3], wt, ut); - loopi(4) - { - if(wf[i]) s[i]->wtex = wt; - if(uf[i]) s[i]->utex = ut; - }; - }; -}; - -// these two are used by getmap/sendmap.. transfers compressed maps directly - -void writemap(char *mname, int msize, uchar *mdata) -{ - setnames(mname); - backup(cgzname, bakname); - FILE *f = fopen(cgzname, "wb"); - if(!f) { conoutf("could not write map to %s", (int)cgzname); return; }; - fwrite(mdata, 1, msize, f); - fclose(f); - conoutf("wrote map %s as file %s", (int)mname, (int)cgzname); -} - -uchar *readmap(char *mname, int *msize) -{ - setnames(mname); - uchar *mdata = (uchar *)loadfile(cgzname, msize); - if(!mdata) { conoutf("could not read map %s", (int)cgzname); return NULL; }; - return mdata; -} - -// save map as .cgz file. uses 2 layers of compression: first does simple run-length -// encoding and leaves out data for certain kinds of cubes, then zlib removes the -// last bits of redundancy. Both passes contribute greatly to the miniscule map sizes. - -void save_world(char *mname) -{ - resettagareas(); // wouldn't be able to reproduce tagged areas otherwise - voptimize(); - toptimize(); - if(!*mname) mname = getclientmap(); - setnames(mname); - backup(cgzname, bakname); - gzFile f = gzopen(cgzname, "wb9"); - if(!f) { conoutf("could not write map to %s", (int)cgzname); return; }; - hdr.version = MAPVERSION; - hdr.numents = 0; - loopv(ents) if(ents[i].type!=NOTUSED) hdr.numents++; - header tmp = hdr; - endianswap(&tmp.version, sizeof(int), 4); - endianswap(&tmp.waterlevel, sizeof(int), 16); - gzwrite(f, &tmp, sizeof(header)); - loopv(ents) - { - if(ents[i].type!=NOTUSED) - { - entity tmp = ents[i]; - endianswap(&tmp, sizeof(short), 4); - gzwrite(f, &tmp, sizeof(persistent_entity)); - }; - }; - sqr *t = NULL; - int sc = 0; - #define spurge while(sc) { gzputc(f, 255); if(sc>255) { gzputc(f, 255); sc -= 255; } else { gzputc(f, sc); sc = 0; } }; - loopk(cubicsize) - { - sqr *s = &world[k]; - #define c(f) (s->f==t->f) - // 4 types of blocks, to compress a bit: - // 255 (2): same as previous block + count - // 254 (3): same as previous, except light // deprecated - // SOLID (5) - // anything else (9) - - if(SOLID(s)) - { - if(t && c(type) && c(wtex) && c(vdelta)) - { - sc++; - } - else - { - spurge; - gzputc(f, s->type); - gzputc(f, s->wtex); - gzputc(f, s->vdelta); - }; - } - else - { - if(t && c(type) && c(floor) && c(ceil) && c(ctex) && c(ftex) && c(utex) && c(wtex) && c(vdelta) && c(tag)) - { - sc++; - } - else - { - spurge; - gzputc(f, s->type); - gzputc(f, s->floor); - gzputc(f, s->ceil); - gzputc(f, s->wtex); - gzputc(f, s->ftex); - gzputc(f, s->ctex); - gzputc(f, s->vdelta); - gzputc(f, s->utex); - gzputc(f, s->tag); - }; - }; - t = s; - }; - spurge; - gzclose(f); - conoutf("wrote map file %s", (int)cgzname); - settagareas(); -}; - -extern void preparectf(bool cleanonly = false); - -void load_world(char *mname) // still supports all map formats that have existed since the earliest cube betas! -{ - preparectf(true); - stopifrecording(); - cleardlights(); - pruneundos(); - setnames(mname); - gzFile f = gzopen(cgzname, "rb9"); - if(!f) { conoutf("could not read map %s", (int)cgzname); return; }; - gzread(f, &hdr, sizeof(header)-sizeof(int)*16); - endianswap(&hdr.version, sizeof(int), 4); - if(strncmp(hdr.head, "CUBE", 4)!=0 && strncmp(hdr.head, "ACMP",4)!=0) fatal("while reading map: header malformatted"); - if(hdr.version>MAPVERSION) fatal("this map requires a newer version of cube"); - if(sfactorLARGEST_FACTOR) fatal("illegal map size"); - if(hdr.version>=4) - { - gzread(f, &hdr.waterlevel, sizeof(int)*16); - endianswap(&hdr.waterlevel, sizeof(int), 16); - } - else - { - hdr.waterlevel = -100000; - }; - ents.setsize(0); - loopi(hdr.numents) - { - entity &e = ents.add(); - gzread(f, &e, sizeof(persistent_entity)); - endianswap(&e, sizeof(short), 4); - e.spawned = false; - if(e.type==LIGHT) - { - if(!e.attr2) e.attr2 = 255; // needed for MAPVERSION<=2 - if(e.attr1>32) e.attr1 = 32; // 12_03 and below - }; - - if (hdr.versionr = s->g = s->b = gzgetc(f); - gzgetc(f); - break; - }; - case SOLID: - { - s->type = SOLID; - s->wtex = gzgetc(f); - s->vdelta = gzgetc(f); - if(hdr.version<=2) { gzgetc(f); gzgetc(f); }; - s->ftex = DEFAULT_FLOOR; - s->ctex = DEFAULT_CEIL; - s->utex = s->wtex; - s->tag = 0; - s->floor = 0; - s->ceil = 16; - break; - }; - default: - { - if(type<0 || type>=MAXTYPE) - { - sprintf_sd(t)("%d @ %d", type, k); - fatal("while reading map: type out of range: ", t); - }; - s->type = type; - s->floor = gzgetc(f); - s->ceil = gzgetc(f); - if(s->floor>=s->ceil) s->floor = s->ceil-1; // for pre 12_13 - s->wtex = gzgetc(f); - s->ftex = gzgetc(f); - s->ctex = gzgetc(f); - if(hdr.version<=2) { gzgetc(f); gzgetc(f); }; - s->vdelta = gzgetc(f); - s->utex = (hdr.version>=2) ? gzgetc(f) : s->wtex; - s->tag = (hdr.version>=5) ? gzgetc(f) : 0; - s->type = type; - }; - }; - s->defer = 0; - t = s; - texuse[s->wtex] = 1; - if(!SOLID(s)) texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1; - }; - gzclose(f); - calclight(); - settagareas(); - int xs, ys; - loopi(256) if(texuse) lookuptexture(i, xs, ys); - conoutf("read map %s (%d milliseconds)", (int)cgzname, SDL_GetTicks()-lastmillis); - conoutf("%s", (int)hdr.maptitle); - startmap(mname); - loopl(256) - { - sprintf_sd(aliasname)("level_trigger_%d", l); // can this be done smarter? - if(identexists(aliasname)) alias(aliasname, ""); - }; - execfile("config/default_map_settings.cfg"); - execfile(pcfname); - execfile(mcfname); -}; - -COMMANDN(savemap, save_world, ARG_1STR); - +// worldio.cpp: loading & saving of maps and savegames + +#include "cube.h" + +void backup(char *name, char *backupname) +{ + remove(backupname); + rename(name, backupname); +}; + +string cgzname, bakname, pcfname, mcfname; + +void setnames(char *name) +{ + string pakname, mapname; + char *slash = strpbrk(name, "/\\"); + if(slash) + { + strn0cpy(pakname, name, slash-name+1); + strcpy_s(mapname, slash+1); + } + else + { + strcpy_s(pakname, "maps"); + strcpy_s(mapname, name); + }; + sprintf_s(cgzname)("packages/%s/%s.cgz", pakname, mapname); + sprintf_s(bakname)("packages/%s/%s_%d.BAK", pakname, mapname, lastmillis); + sprintf_s(pcfname)("packages/%s/package.cfg", pakname); + sprintf_s(mcfname)("packages/%s/%s.cfg", pakname, mapname); + + path(cgzname); + path(bakname); +}; + +// the optimize routines below are here to reduce the detrimental effects of messy mapping by +// setting certain properties (vdeltas and textures) to neighbouring values wherever there is no +// visible difference. This allows the mipmapper to generate more efficient mips. +// the reason it is done on save is to reduce the amount spend in the mipmapper (as that is done +// in realtime). + +inline bool nhf(sqr *s) { return s->type!=FHF && s->type!=CHF; }; + +void voptimize() // reset vdeltas on non-hf cubes +{ + loop(x, ssize) loop(y, ssize) + { + sqr *s = S(x, y); + if(x && y) { if(nhf(s) && nhf(S(x-1, y)) && nhf(S(x-1, y-1)) && nhf(S(x, y-1))) s->vdelta = 0; } + else s->vdelta = 0; + }; +}; + +void topt(sqr *s, bool &wf, bool &uf, int &wt, int &ut) +{ + sqr *o[4]; + o[0] = SWS(s,0,-1,ssize); + o[1] = SWS(s,0,1,ssize); + o[2] = SWS(s,1,0,ssize); + o[3] = SWS(s,-1,0,ssize); + wf = true; + uf = true; + if(SOLID(s)) + { + loopi(4) if(!SOLID(o[i])) + { + wf = false; + wt = s->wtex; + ut = s->utex; + return; + }; + } + else + { + loopi(4) if(!SOLID(o[i])) + { + if(o[i]->floorfloor) { wt = s->wtex; wf = false; }; + if(o[i]->ceil>s->ceil) { ut = s->utex; uf = false; }; + }; + }; +}; + +void toptimize() // FIXME: only does 2x2, make atleast for 4x4 also +{ + bool wf[4], uf[4]; + sqr *s[4]; + for(int x = 2; xwtex, ut = s[0]->utex; + topt(s[0], wf[0], uf[0], wt, ut); + topt(s[1] = SWS(s[0],0,1,ssize), wf[1], uf[1], wt, ut); + topt(s[2] = SWS(s[0],1,1,ssize), wf[2], uf[2], wt, ut); + topt(s[3] = SWS(s[0],1,0,ssize), wf[3], uf[3], wt, ut); + loopi(4) + { + if(wf[i]) s[i]->wtex = wt; + if(uf[i]) s[i]->utex = ut; + }; + }; +}; + +// these two are used by getmap/sendmap.. transfers compressed maps directly + +void writemap(char *mname, int msize, uchar *mdata) +{ + setnames(mname); + backup(cgzname, bakname); + FILE *f = fopen(cgzname, "wb"); + if(!f) { conoutf("could not write map to %s", (int)cgzname); return; }; + fwrite(mdata, 1, msize, f); + fclose(f); + conoutf("wrote map %s as file %s", (int)mname, (int)cgzname); +} + +uchar *readmap(char *mname, int *msize) +{ + setnames(mname); + uchar *mdata = (uchar *)loadfile(cgzname, msize); + if(!mdata) { conoutf("could not read map %s", (int)cgzname); return NULL; }; + return mdata; +} + +// save map as .cgz file. uses 2 layers of compression: first does simple run-length +// encoding and leaves out data for certain kinds of cubes, then zlib removes the +// last bits of redundancy. Both passes contribute greatly to the miniscule map sizes. + +void save_world(char *mname) +{ + resettagareas(); // wouldn't be able to reproduce tagged areas otherwise + voptimize(); + toptimize(); + if(!*mname) mname = getclientmap(); + setnames(mname); + backup(cgzname, bakname); + gzFile f = gzopen(cgzname, "wb9"); + if(!f) { conoutf("could not write map to %s", (int)cgzname); return; }; + hdr.version = MAPVERSION; + hdr.numents = 0; + loopv(ents) if(ents[i].type!=NOTUSED) hdr.numents++; + header tmp = hdr; + endianswap(&tmp.version, sizeof(int), 4); + endianswap(&tmp.waterlevel, sizeof(int), 16); + gzwrite(f, &tmp, sizeof(header)); + loopv(ents) + { + if(ents[i].type!=NOTUSED) + { + entity tmp = ents[i]; + endianswap(&tmp, sizeof(short), 4); + gzwrite(f, &tmp, sizeof(persistent_entity)); + }; + }; + sqr *t = NULL; + int sc = 0; + #define spurge while(sc) { gzputc(f, 255); if(sc>255) { gzputc(f, 255); sc -= 255; } else { gzputc(f, sc); sc = 0; } }; + loopk(cubicsize) + { + sqr *s = &world[k]; + #define c(f) (s->f==t->f) + // 4 types of blocks, to compress a bit: + // 255 (2): same as previous block + count + // 254 (3): same as previous, except light // deprecated + // SOLID (5) + // anything else (9) + + if(SOLID(s)) + { + if(t && c(type) && c(wtex) && c(vdelta)) + { + sc++; + } + else + { + spurge; + gzputc(f, s->type); + gzputc(f, s->wtex); + gzputc(f, s->vdelta); + }; + } + else + { + if(t && c(type) && c(floor) && c(ceil) && c(ctex) && c(ftex) && c(utex) && c(wtex) && c(vdelta) && c(tag)) + { + sc++; + } + else + { + spurge; + gzputc(f, s->type); + gzputc(f, s->floor); + gzputc(f, s->ceil); + gzputc(f, s->wtex); + gzputc(f, s->ftex); + gzputc(f, s->ctex); + gzputc(f, s->vdelta); + gzputc(f, s->utex); + gzputc(f, s->tag); + }; + }; + t = s; + }; + spurge; + gzclose(f); + conoutf("wrote map file %s", (int)cgzname); + settagareas(); +}; + +extern void preparectf(bool cleanonly = false); + +void load_world(char *mname) // still supports all map formats that have existed since the earliest cube betas! +{ + preparectf(true); + stopifrecording(); + cleardlights(); + pruneundos(); + setnames(mname); + gzFile f = gzopen(cgzname, "rb9"); + if(!f) { conoutf("could not read map %s", (int)cgzname); return; }; + gzread(f, &hdr, sizeof(header)-sizeof(int)*16); + endianswap(&hdr.version, sizeof(int), 4); + if(strncmp(hdr.head, "CUBE", 4)!=0 && strncmp(hdr.head, "ACMP",4)!=0) fatal("while reading map: header malformatted"); + if(hdr.version>MAPVERSION) fatal("this map requires a newer version of cube"); + if(sfactorLARGEST_FACTOR) fatal("illegal map size"); + if(hdr.version>=4) + { + gzread(f, &hdr.waterlevel, sizeof(int)*16); + endianswap(&hdr.waterlevel, sizeof(int), 16); + } + else + { + hdr.waterlevel = -100000; + }; + ents.setsize(0); + loopi(hdr.numents) + { + entity &e = ents.add(); + gzread(f, &e, sizeof(persistent_entity)); + endianswap(&e, sizeof(short), 4); + e.spawned = false; + if(e.type==LIGHT) + { + if(!e.attr2) e.attr2 = 255; // needed for MAPVERSION<=2 + if(e.attr1>32) e.attr1 = 32; // 12_03 and below + }; + + if (hdr.versionr = s->g = s->b = gzgetc(f); + gzgetc(f); + break; + }; + case SOLID: + { + s->type = SOLID; + s->wtex = gzgetc(f); + s->vdelta = gzgetc(f); + if(hdr.version<=2) { gzgetc(f); gzgetc(f); }; + s->ftex = DEFAULT_FLOOR; + s->ctex = DEFAULT_CEIL; + s->utex = s->wtex; + s->tag = 0; + s->floor = 0; + s->ceil = 16; + break; + }; + default: + { + if(type<0 || type>=MAXTYPE) + { + sprintf_sd(t)("%d @ %d", type, k); + fatal("while reading map: type out of range: ", t); + }; + s->type = type; + s->floor = gzgetc(f); + s->ceil = gzgetc(f); + if(s->floor>=s->ceil) s->floor = s->ceil-1; // for pre 12_13 + s->wtex = gzgetc(f); + s->ftex = gzgetc(f); + s->ctex = gzgetc(f); + if(hdr.version<=2) { gzgetc(f); gzgetc(f); }; + s->vdelta = gzgetc(f); + s->utex = (hdr.version>=2) ? gzgetc(f) : s->wtex; + s->tag = (hdr.version>=5) ? gzgetc(f) : 0; + s->type = type; + }; + }; + s->defer = 0; + t = s; + texuse[s->wtex] = 1; + if(!SOLID(s)) texuse[s->utex] = texuse[s->ftex] = texuse[s->ctex] = 1; + }; + gzclose(f); + calclight(); + settagareas(); + int xs, ys; + loopi(256) if(texuse) lookuptexture(i, xs, ys); + conoutf("read map %s (%d milliseconds)", (int)cgzname, SDL_GetTicks()-lastmillis); + conoutf("%s", (int)hdr.maptitle); + startmap(mname); + loopl(256) + { + sprintf_sd(aliasname)("level_trigger_%d", l); // can this be done smarter? + if(identexists(aliasname)) alias(aliasname, ""); + }; + execfile("config/default_map_settings.cfg"); + execfile(pcfname); + execfile(mcfname); +}; + +COMMANDN(savemap, save_world, ARG_1STR); + diff --git a/source/src/worldlight.cpp b/source/src/worldlight.cpp index 7e51bd53..60bf1d71 100644 --- a/source/src/worldlight.cpp +++ b/source/src/worldlight.cpp @@ -1,213 +1,213 @@ -// worldlight.cpp - -#include "cube.h" - -extern bool hasoverbright; - -VAR(lightscale,1,4,100); - -void lightray(float bx, float by, persistent_entity &light) // done in realtime, needs to be fast -{ - float lx = light.x+(rnd(21)-10)*0.1f; - float ly = light.y+(rnd(21)-10)*0.1f; - float dx = bx-lx; - float dy = by-ly; - float dist = (float)sqrt(dx*dx+dy*dy); - if(dist<1.0f) return; - int reach = light.attr1; - int steps = (int)(reach*reach*1.6f/dist); // can change this for speedup/quality? - const int PRECBITS = 12; - const float PRECF = 4096.0f; - int x = (int)(lx*PRECF); - int y = (int)(ly*PRECF); - int l = light.attr2<>PRECBITS, y>>PRECBITS)) return; - - int g = light.attr3<>PRECBITS, y>>PRECBITS); - int tl = (l>>PRECBITS)+s->r; - s->r = tl>255 ? 255 : tl; - tl = (g>>PRECBITS)+s->g; - s->g = tl>255 ? 255 : tl; - tl = (b>>PRECBITS)+s->b; - s->b = tl>255 ? 255 : tl; - if(SOLID(s)) return; - x += stepx; - y += stepy; - l -= stepl; - g -= stepg; - b -= stepb; - stepl -= 25; - stepg -= 25; - stepb -= 25; - }; - } - else // white light, special optimized version - { - int dimness = rnd((255-light.attr2)/16+1); - x += stepx*dimness; - y += stepy*dimness; - - if(OUTBORD(x>>PRECBITS, y>>PRECBITS)) return; - - loopi(steps) - { - sqr *s = S(x>>PRECBITS, y>>PRECBITS); - int tl = (l>>PRECBITS)+s->r; - s->r = s->g = s->b = tl>255 ? 255 : tl; - if(SOLID(s)) return; - x += stepx; - y += stepy; - l -= stepl; - stepl -= 25; - }; - }; - } - else // the old (white) light code, here for the few people with old video cards that don't support overbright - { - loopi(steps) - { - sqr *s = S(x>>PRECBITS, y>>PRECBITS); - int light = l>>PRECBITS; - if(light>s->r) s->r = s->g = s->b = (uchar)light; - if(SOLID(s)) return; - x += stepx; - y += stepy; - l -= stepl; - }; - }; - -}; - -void calclightsource(persistent_entity &l) -{ - int reach = l.attr1; - int sx = l.x-reach; - int ex = l.x+reach; - int sy = l.y-reach; - int ey = l.y+reach; - - rndreset(); - - const float s = 0.8f; - - for(float sx2 = (float)sx; sx2<=ex; sx2+=s*2) { lightray(sx2, (float)sy, l); lightray(sx2, (float)ey, l); }; - for(float sy2 = sy+s; sy2<=ey-s; sy2+=s*2) { lightray((float)sx, sy2, l); lightray((float)ex, sy2, l); }; - - rndtime(); -}; - -void postlightarea(block &a) // median filter, smooths out random noise in light and makes it more mipable -{ - loop(x,a.xs) loop(y,a.ys) // assumes area not on edge of world - { - sqr *s = S(x+a.x,y+a.y); - #define median(m) s->m = (s->m*2 + SW(s,1,0)->m*2 + SW(s,0,1)->m*2 \ - + SW(s,-1,0)->m*2 + SW(s,0,-1)->m*2 \ - + SW(s,1,1)->m + SW(s,1,-1)->m \ - + SW(s,-1,1)->m + SW(s,-1,-1)->m)/14; // median is 4/2/1 instead - median(r); - median(g); - median(b); - }; - - remip(a); -}; - -void calclight() -{ - loop(x,ssize) loop(y,ssize) - { - sqr *s = S(x,y); - s->r = s->g = s->b = 10; - }; - - loopv(ents) - { - entity &e = ents[i]; - if(e.type==LIGHT) calclightsource(e); - }; - - block b = { 1, 1, ssize-2, ssize-2 }; - postlightarea(b); - setvar("fullbright", 0); -}; - -VAR(dynlight, 0, 16, 32); - -vector dlights; - -void cleardlights() -{ - while(!dlights.empty()) - { - block *backup = dlights.pop(); - blockpaste(*backup); - free(backup); - }; -}; - -void dodynlight(vec &vold, vec &v, int reach, int strength, dynent *owner) -{ - if(!reach) reach = dynlight; - if(owner->monsterstate) reach = reach/2; - if(!reach) return; - - int creach = reach+16; // dependant on lightray random offsets! - block b = { (int)v.x-creach, (int)v.y-creach, creach*2+1, creach*2+1 }; - - if(b.x<1) b.x = 1; - if(b.y<1) b.y = 1; - if(b.xs+b.x>ssize-2) b.xs = ssize-2-b.x; - if(b.ys+b.y>ssize-2) b.ys = ssize-2-b.y; - - dlights.add(blockcopy(b)); // backup area before rendering in dynlight - - persistent_entity l = { (int)v.x, (int)v.y, (int)v.z, reach, LIGHT, strength, 0, 0 }; - calclightsource(l); - postlightarea(b); -}; - -// utility functions also used by editing code - -block *blockcopy(block &s) -{ - block *b = (block *)alloc(sizeof(block)+s.xs*s.ys*sizeof(sqr)); - *b = s; - sqr *q = (sqr *)(b+1); - for(int x = s.x; x>PRECBITS, y>>PRECBITS)) return; + + int g = light.attr3<>PRECBITS, y>>PRECBITS); + int tl = (l>>PRECBITS)+s->r; + s->r = tl>255 ? 255 : tl; + tl = (g>>PRECBITS)+s->g; + s->g = tl>255 ? 255 : tl; + tl = (b>>PRECBITS)+s->b; + s->b = tl>255 ? 255 : tl; + if(SOLID(s)) return; + x += stepx; + y += stepy; + l -= stepl; + g -= stepg; + b -= stepb; + stepl -= 25; + stepg -= 25; + stepb -= 25; + }; + } + else // white light, special optimized version + { + int dimness = rnd((255-light.attr2)/16+1); + x += stepx*dimness; + y += stepy*dimness; + + if(OUTBORD(x>>PRECBITS, y>>PRECBITS)) return; + + loopi(steps) + { + sqr *s = S(x>>PRECBITS, y>>PRECBITS); + int tl = (l>>PRECBITS)+s->r; + s->r = s->g = s->b = tl>255 ? 255 : tl; + if(SOLID(s)) return; + x += stepx; + y += stepy; + l -= stepl; + stepl -= 25; + }; + }; + } + else // the old (white) light code, here for the few people with old video cards that don't support overbright + { + loopi(steps) + { + sqr *s = S(x>>PRECBITS, y>>PRECBITS); + int light = l>>PRECBITS; + if(light>s->r) s->r = s->g = s->b = (uchar)light; + if(SOLID(s)) return; + x += stepx; + y += stepy; + l -= stepl; + }; + }; + +}; + +void calclightsource(persistent_entity &l) +{ + int reach = l.attr1; + int sx = l.x-reach; + int ex = l.x+reach; + int sy = l.y-reach; + int ey = l.y+reach; + + rndreset(); + + const float s = 0.8f; + + for(float sx2 = (float)sx; sx2<=ex; sx2+=s*2) { lightray(sx2, (float)sy, l); lightray(sx2, (float)ey, l); }; + for(float sy2 = sy+s; sy2<=ey-s; sy2+=s*2) { lightray((float)sx, sy2, l); lightray((float)ex, sy2, l); }; + + rndtime(); +}; + +void postlightarea(block &a) // median filter, smooths out random noise in light and makes it more mipable +{ + loop(x,a.xs) loop(y,a.ys) // assumes area not on edge of world + { + sqr *s = S(x+a.x,y+a.y); + #define median(m) s->m = (s->m*2 + SW(s,1,0)->m*2 + SW(s,0,1)->m*2 \ + + SW(s,-1,0)->m*2 + SW(s,0,-1)->m*2 \ + + SW(s,1,1)->m + SW(s,1,-1)->m \ + + SW(s,-1,1)->m + SW(s,-1,-1)->m)/14; // median is 4/2/1 instead + median(r); + median(g); + median(b); + }; + + remip(a); +}; + +void calclight() +{ + loop(x,ssize) loop(y,ssize) + { + sqr *s = S(x,y); + s->r = s->g = s->b = 10; + }; + + loopv(ents) + { + entity &e = ents[i]; + if(e.type==LIGHT) calclightsource(e); + }; + + block b = { 1, 1, ssize-2, ssize-2 }; + postlightarea(b); + setvar("fullbright", 0); +}; + +VAR(dynlight, 0, 16, 32); + +vector dlights; + +void cleardlights() +{ + while(!dlights.empty()) + { + block *backup = dlights.pop(); + blockpaste(*backup); + free(backup); + }; +}; + +void dodynlight(vec &vold, vec &v, int reach, int strength, dynent *owner) +{ + if(!reach) reach = dynlight; + if(owner->monsterstate) reach = reach/2; + if(!reach) return; + + int creach = reach+16; // dependant on lightray random offsets! + block b = { (int)v.x-creach, (int)v.y-creach, creach*2+1, creach*2+1 }; + + if(b.x<1) b.x = 1; + if(b.y<1) b.y = 1; + if(b.xs+b.x>ssize-2) b.xs = ssize-2-b.x; + if(b.ys+b.y>ssize-2) b.ys = ssize-2-b.y; + + dlights.add(blockcopy(b)); // backup area before rendering in dynlight + + persistent_entity l = { (int)v.x, (int)v.y, (int)v.z, reach, LIGHT, strength, 0, 0 }; + calclightsource(l); + postlightarea(b); +}; + +// utility functions also used by editing code + +block *blockcopy(block &s) +{ + block *b = (block *)alloc(sizeof(block)+s.xs*s.ys*sizeof(sqr)); + *b = s; + sqr *q = (sqr *)(b+1); + for(int x = s.x; xstate==CS_DEAD ? -90.0f : player1->pitch); - float af = getvar("fov")/2+apitch/1.5f+3; - float byaw = (player1->yaw-90+af)/360*PI2; - float syaw = (player1->yaw-90-af)/360*PI2; - - loopi(NUMRAYS) - { - float angle = i*PI2/NUMRAYS; - if((apitch>45 // must be bigger if fov>120 - || (anglesyaw) - || (anglesyaw-PI2) - || (anglesyaw+PI2)) - && !OUTBORD(vx, vy) - && !SOLID(S(fast_f2nat(vx), fast_f2nat(vy)))) // try to avoid tracing ray if outside of frustrum - { - float ray = i*8/(float)NUMRAYS; - float dx, dy; - if(ray>1 && ray<3) { dx = -(ray-2); dy = 1; } - else if(ray>=3 && ray<5) { dx = -1; dy = -(ray-4); } - else if(ray>=5 && ray<7) { dx = ray-6; dy = -1; } - else { dx = 1; dy = ray>4 ? ray-8 : ray; }; - float sx = vx; - float sy = vy; - for(;;) - { - sx += dx; - sy += dy; - if(SOLID(S(fast_f2nat(sx), fast_f2nat(sy)))) // 90% of time spend in this function is on this line - { - rdist[i] = (float)(fabs(sx-vx)+fabs(sy-vy)); - break; - }; - }; - } - else - { - rdist[i] = 2; - }; - }; -}; - -// test occlusion for a cube... one of the most computationally expensive functions in the engine -// as its done for every cube and entity, but its effect is more than worth it! - -inline float ca(float x, float y) { return x>y ? y/x : 2-x/y; }; -inline float ma(float x, float y) { return x==0 ? (y>0 ? 2 : -2) : y/x; }; - -int isoccluded(float vx, float vy, float cx, float cy, float csize) // v = viewer, c = cube to test -{ - if(!ocull) return 0; - - float nx = vx, ny = vy; // n = point on the border of the cube that is closest to v - if(nxcx+csize) nx = cx+csize; - if(nycy+csize) ny = cy+csize; - float xdist = (float)fabs(nx-vx); - float ydist = (float)fabs(ny-vy); - if(xdist>odist || ydist>odist) return 2; - float dist = xdist+ydist-1; // 1 needed? - - // ABC - // D E - // FGH - - // - check middle cube? BG - - // find highest and lowest angle in the occlusion map that this cube spans, based on its most left and right - // points on the border from the viewer pov... I see no easier way to do this than this silly code below - - float h, l; - if(cx<=vx) // ABDFG - { - if(cx+csizestate==CS_DEAD ? -90.0f : player1->pitch); + float af = getvar("fov")/2+apitch/1.5f+3; + float byaw = (player1->yaw-90+af)/360*PI2; + float syaw = (player1->yaw-90-af)/360*PI2; + + loopi(NUMRAYS) + { + float angle = i*PI2/NUMRAYS; + if((apitch>45 // must be bigger if fov>120 + || (anglesyaw) + || (anglesyaw-PI2) + || (anglesyaw+PI2)) + && !OUTBORD(vx, vy) + && !SOLID(S(fast_f2nat(vx), fast_f2nat(vy)))) // try to avoid tracing ray if outside of frustrum + { + float ray = i*8/(float)NUMRAYS; + float dx, dy; + if(ray>1 && ray<3) { dx = -(ray-2); dy = 1; } + else if(ray>=3 && ray<5) { dx = -1; dy = -(ray-4); } + else if(ray>=5 && ray<7) { dx = ray-6; dy = -1; } + else { dx = 1; dy = ray>4 ? ray-8 : ray; }; + float sx = vx; + float sy = vy; + for(;;) + { + sx += dx; + sy += dy; + if(SOLID(S(fast_f2nat(sx), fast_f2nat(sy)))) // 90% of time spend in this function is on this line + { + rdist[i] = (float)(fabs(sx-vx)+fabs(sy-vy)); + break; + }; + }; + } + else + { + rdist[i] = 2; + }; + }; +}; + +// test occlusion for a cube... one of the most computationally expensive functions in the engine +// as its done for every cube and entity, but its effect is more than worth it! + +inline float ca(float x, float y) { return x>y ? y/x : 2-x/y; }; +inline float ma(float x, float y) { return x==0 ? (y>0 ? 2 : -2) : y/x; }; + +int isoccluded(float vx, float vy, float cx, float cy, float csize) // v = viewer, c = cube to test +{ + if(!ocull) return 0; + + float nx = vx, ny = vy; // n = point on the border of the cube that is closest to v + if(nxcx+csize) nx = cx+csize; + if(nycy+csize) ny = cy+csize; + float xdist = (float)fabs(nx-vx); + float ydist = (float)fabs(ny-vy); + if(xdist>odist || ydist>odist) return 2; + float dist = xdist+ydist-1; // 1 needed? + + // ABC + // D E + // FGH + + // - check middle cube? BG + + // find highest and lowest angle in the occlusion map that this cube spans, based on its most left and right + // points on the border from the viewer pov... I see no easier way to do this than this silly code below + + float h, l; + if(cx<=vx) // ABDFG + { + if(cx+csizetype==SEMISOLID) - { - float c1 = s->floor; - float c2 = s->floor; - if(s->type==FHF) { c1 -= d1->vdelta/4.0f; c2 -= d2->vdelta/4.0f; }; - float f1 = s->ceil; - float f2 = s->ceil; - if(s->type==CHF) { f1 += d1->vdelta/4.0f; f2 += d2->vdelta/4.0f; }; - //if(f1-c1<=0 && f2-c2<=0) return; - render_square(o->wtex, c1, c2, f1, f2, x1<floor; - float f2 = s->floor; - float c1 = o->floor; - float c2 = o->floor; - if(o->type==FHF && s->type!=FHF) - { - c1 -= d1->vdelta/4.0f; - c2 -= d2->vdelta/4.0f; - } - if(s->type==FHF && o->type!=FHF) - { - f1 -= d1->vdelta/4.0f; - f2 -= d2->vdelta/4.0f; - } - if(f1>=c1 && f2>=c2) goto skip; - render_square(o->wtex, f1, f2, c1, c2, x1<ceil; - float f2 = o->ceil; - float c1 = s->ceil; - float c2 = s->ceil; - if(o->type==CHF && s->type!=CHF) - { - f1 += d1->vdelta/4.0f; - f2 += d2->vdelta/4.0f; - } - else if(s->type==CHF && o->type!=CHF) - { - c1 += d1->vdelta/4.0f; - c2 += d2->vdelta/4.0f; - } - if(c1<=f1 && c2<=f2) return; - render_square(o->utex, f1, f2, c1, c2, x1<>mip; - x *= 2; - y *= 2; - switch(SWS(w, x+x1, y+y1, msize)->type) - { - case SEMISOLID: if(issemi(mip, x+x1, y+y1, x1, y1, x2, y2)) return true; - case CORNER: - case SOLID: break; - default: return true; - }; - switch(SWS(w, x+x2, y+y2, msize)->type) - { - case SEMISOLID: if(issemi(mip, x+x2, y+y2, x1, y1, x2, y2)) return true; - case CORNER: - case SOLID: break; - default: return true; - }; - return false; -}; - -bool render_floor, render_ceil; - -// the core recursive function, renders a rect of cubes at a certain mip level from a viewer perspective -// call itself for lower mip levels, on most modern machines however this function will use the higher -// mip levels only for perfect mips. - -void render_seg_new(float vx, float vy, float vh, int mip, int x, int y, int xs, int ys) -{ - sqr *w = wmip[mip]; - int sz = ssize>>mip; - int vxx = ((int)vx+(1<>mip; - int vyy = ((int)vy+(1<>mip; - int lx = vxx-lodleft; // these mark the rect inside the current rest that we want to render using a lower mip level - int ly = vyy-lodtop; - int rx = vxx+lodright; - int ry = vyy+lodbot; - - float fsize = (float)(1<occluded = isoccluded(player1->o.x, player1->o.y, (float)(ox<>mip; - int pvy = (int)vy>>mip; - if(pvx>=0 && pvy>=0 && pvxoccluded = 0; - SWS(w, pvx, pvy, sz)->occluded = 0; // player cell never occluded - }; - - #define df(x) s->floor-(x->vdelta/4.0f) - #define dc(x) s->ceil+(x->vdelta/4.0f) - - // loop through the rect 3 times (for floor/ceil/walls seperately, to facilitate dynamic stripify) - // for each we skip occluded cubes (occlusion at higher mip levels is a big time saver!). - // during the first loop (ceil) we collect cubes that lie within the lower mip rect and are - // also deferred, and render them recursively. Anything left (perfect mips and higher lods) we - // render here. - - #define LOOPH {for(int xx = x; xxoccluded==1) continue; \ - if(s->defer && !s->occluded && mip && xx>=lx && xx=ly && yydefer && !next->occluded) yy++; // collect 2xN rect of lower mip - render_seg_new(vx, vy, vh, mip-1, xx*2, start*2, xx*2+2, yy*2+2); - continue; - }; - stats[mip]++; - LOOPD - if((s->type==SPACE || s->type==FHF) && s->ceil>=vh && render_ceil) - render_flat(s->ctex, xx<ceil, s, t, u, v, true); - if(s->type==CHF) //if(s->ceil>=vh) - render_flatdelta(s->ctex, xx<type==SPACE || s->type==CHF) && s->floor<=vh && render_floor) - { - render_flat(s->ftex, xx<floor, s, t, u, v, false); - if(s->floortype==FHF) - { - render_flatdelta(s->ftex, xx<floor-s->vdelta/4.0ftype==CORNER) - { - // cull also - bool topleft = true; - sqr *h1 = NULL; - sqr *h2 = NULL; - if(SOLID(z)) - { - if(SOLID(w)) { render_wall(w, h2 = s, xx+1, yy, xx, yy+1, mip, t, v, false); topleft = false; } - else if(SOLID(v)) { render_wall(v, h2 = s, xx, yy, xx+1, yy+1, mip, s, u, false); }; - } - else if(SOLID(t)) - { - if(SOLID(w)) { render_wall(w, h1 = s, xx+1, yy+1, xx, yy, mip, u, s, false); } - else if(SOLID(v)) { render_wall(v, h1 = s, xx, yy+1, xx+1, yy, mip, v, t, false); topleft = false; }; - } - else - { - normalwall = false; - bool wv = w->ceil-w->floor < v->ceil-v->floor; - if(z->ceil-z->floor < t->ceil-t->floor) - { - if(wv) { render_wall(h1 = s, h2 = v, xx+1, yy, xx, yy+1, mip, t, v, false); topleft = false; } - else { render_wall(h1 = s, h2 = w, xx, yy, xx+1, yy+1, mip, s, u, false); }; - } - else - { - if(wv) { render_wall(h2 = s, h1 = v, xx+1, yy+1, xx, yy, mip, u, s, false); } - else { render_wall(h2 = s, h1 = w, xx, yy+1, xx+1, yy, mip, v, t, false); topleft = false; }; - }; - }; - render_tris(xx<=vxx && xx!=0 && yy!=sz-1 && !SOLID(z) && (!SOLID(s) || z->type!=CORNER) - && (z->type!=SEMISOLID || issemi(mip, xx-1, yy, 1, 0, 1, 1))) - render_wall(s, z, xx, yy, xx, yy+1, mip, s, v, true); - if(xx<=vxx && inner && !SOLID(t) && (!SOLID(s) || t->type!=CORNER) - && (t->type!=SEMISOLID || issemi(mip, xx+1, yy, 0, 0, 0, 1))) - render_wall(s, t, xx+1, yy, xx+1, yy+1, mip, t, u, false); - if(yy>=vyy && yy!=0 && xx!=sz-1 && !SOLID(w) && (!SOLID(s) || w->type!=CORNER) - && (w->type!=SEMISOLID || issemi(mip, xx, yy-1, 0, 1, 1, 1))) - render_wall(s, w, xx, yy, xx+1, yy, mip, s, t, false); - if(yy<=vyy && inner && !SOLID(v) && (!SOLID(s) || v->type!=CORNER) - && (v->type!=SEMISOLID || issemi(mip, xx, yy+1, 0, 0, 1, 0))) - render_wall(s, v, xx, yy+1, xx+1, yy+1, mip, v, u, true); - }; - }}; - -}; - -void distlod(int &low, int &high, int angle, float widef) -{ - float f = 90.0f/lod/widef; - low = (int)((90-angle)/f); - high = (int)(angle/f); - if(lowMAX_LOD) lod = MAX_LOD; - lodtop = lodbot = lodleft = lodright = min_lod; - if(yaw>45 && yaw<=135) - { - lodleft = lod; - distlod(lodtop, lodbot, yaw-45, widef); - } - else if(yaw>135 && yaw<=225) - { - lodbot = lod; - distlod(lodleft, lodright, yaw-135, widef); - } - else if(yaw>225 && yaw<=315) - { - lodright = lod; - distlod(lodbot, lodtop, yaw-225, widef); - } - else - { - lodtop = lod; - distlod(lodright, lodleft, yaw<=45 ? yaw+45 : yaw-315, widef); - }; - float hyfov = fov*h/w/2; - render_floor = pitch>MAX_MIP, ssize>>MAX_MIP); - mipstats(stats[0], stats[1], stats[2]); -}; - +// worldrender.cpp: goes through all cubes in top down quad tree fashion, determines what has to +// be rendered and how (depending on neighbouring cubes), then calls functions in rendercubes.cpp + +#include "cube.h" + +void render_wall(sqr *o, sqr *s, int x1, int y1, int x2, int y2, int mip, sqr *d1, sqr *d2, bool topleft) +{ + if(SOLID(o) || o->type==SEMISOLID) + { + float c1 = s->floor; + float c2 = s->floor; + if(s->type==FHF) { c1 -= d1->vdelta/4.0f; c2 -= d2->vdelta/4.0f; }; + float f1 = s->ceil; + float f2 = s->ceil; + if(s->type==CHF) { f1 += d1->vdelta/4.0f; f2 += d2->vdelta/4.0f; }; + //if(f1-c1<=0 && f2-c2<=0) return; + render_square(o->wtex, c1, c2, f1, f2, x1<floor; + float f2 = s->floor; + float c1 = o->floor; + float c2 = o->floor; + if(o->type==FHF && s->type!=FHF) + { + c1 -= d1->vdelta/4.0f; + c2 -= d2->vdelta/4.0f; + } + if(s->type==FHF && o->type!=FHF) + { + f1 -= d1->vdelta/4.0f; + f2 -= d2->vdelta/4.0f; + } + if(f1>=c1 && f2>=c2) goto skip; + render_square(o->wtex, f1, f2, c1, c2, x1<ceil; + float f2 = o->ceil; + float c1 = s->ceil; + float c2 = s->ceil; + if(o->type==CHF && s->type!=CHF) + { + f1 += d1->vdelta/4.0f; + f2 += d2->vdelta/4.0f; + } + else if(s->type==CHF && o->type!=CHF) + { + c1 += d1->vdelta/4.0f; + c2 += d2->vdelta/4.0f; + } + if(c1<=f1 && c2<=f2) return; + render_square(o->utex, f1, f2, c1, c2, x1<>mip; + x *= 2; + y *= 2; + switch(SWS(w, x+x1, y+y1, msize)->type) + { + case SEMISOLID: if(issemi(mip, x+x1, y+y1, x1, y1, x2, y2)) return true; + case CORNER: + case SOLID: break; + default: return true; + }; + switch(SWS(w, x+x2, y+y2, msize)->type) + { + case SEMISOLID: if(issemi(mip, x+x2, y+y2, x1, y1, x2, y2)) return true; + case CORNER: + case SOLID: break; + default: return true; + }; + return false; +}; + +bool render_floor, render_ceil; + +// the core recursive function, renders a rect of cubes at a certain mip level from a viewer perspective +// call itself for lower mip levels, on most modern machines however this function will use the higher +// mip levels only for perfect mips. + +void render_seg_new(float vx, float vy, float vh, int mip, int x, int y, int xs, int ys) +{ + sqr *w = wmip[mip]; + int sz = ssize>>mip; + int vxx = ((int)vx+(1<>mip; + int vyy = ((int)vy+(1<>mip; + int lx = vxx-lodleft; // these mark the rect inside the current rest that we want to render using a lower mip level + int ly = vyy-lodtop; + int rx = vxx+lodright; + int ry = vyy+lodbot; + + float fsize = (float)(1<occluded = isoccluded(player1->o.x, player1->o.y, (float)(ox<>mip; + int pvy = (int)vy>>mip; + if(pvx>=0 && pvy>=0 && pvxoccluded = 0; + SWS(w, pvx, pvy, sz)->occluded = 0; // player cell never occluded + }; + + #define df(x) s->floor-(x->vdelta/4.0f) + #define dc(x) s->ceil+(x->vdelta/4.0f) + + // loop through the rect 3 times (for floor/ceil/walls seperately, to facilitate dynamic stripify) + // for each we skip occluded cubes (occlusion at higher mip levels is a big time saver!). + // during the first loop (ceil) we collect cubes that lie within the lower mip rect and are + // also deferred, and render them recursively. Anything left (perfect mips and higher lods) we + // render here. + + #define LOOPH {for(int xx = x; xxoccluded==1) continue; \ + if(s->defer && !s->occluded && mip && xx>=lx && xx=ly && yydefer && !next->occluded) yy++; // collect 2xN rect of lower mip + render_seg_new(vx, vy, vh, mip-1, xx*2, start*2, xx*2+2, yy*2+2); + continue; + }; + stats[mip]++; + LOOPD + if((s->type==SPACE || s->type==FHF) && s->ceil>=vh && render_ceil) + render_flat(s->ctex, xx<ceil, s, t, u, v, true); + if(s->type==CHF) //if(s->ceil>=vh) + render_flatdelta(s->ctex, xx<type==SPACE || s->type==CHF) && s->floor<=vh && render_floor) + { + render_flat(s->ftex, xx<floor, s, t, u, v, false); + if(s->floortype==FHF) + { + render_flatdelta(s->ftex, xx<floor-s->vdelta/4.0ftype==CORNER) + { + // cull also + bool topleft = true; + sqr *h1 = NULL; + sqr *h2 = NULL; + if(SOLID(z)) + { + if(SOLID(w)) { render_wall(w, h2 = s, xx+1, yy, xx, yy+1, mip, t, v, false); topleft = false; } + else if(SOLID(v)) { render_wall(v, h2 = s, xx, yy, xx+1, yy+1, mip, s, u, false); }; + } + else if(SOLID(t)) + { + if(SOLID(w)) { render_wall(w, h1 = s, xx+1, yy+1, xx, yy, mip, u, s, false); } + else if(SOLID(v)) { render_wall(v, h1 = s, xx, yy+1, xx+1, yy, mip, v, t, false); topleft = false; }; + } + else + { + normalwall = false; + bool wv = w->ceil-w->floor < v->ceil-v->floor; + if(z->ceil-z->floor < t->ceil-t->floor) + { + if(wv) { render_wall(h1 = s, h2 = v, xx+1, yy, xx, yy+1, mip, t, v, false); topleft = false; } + else { render_wall(h1 = s, h2 = w, xx, yy, xx+1, yy+1, mip, s, u, false); }; + } + else + { + if(wv) { render_wall(h2 = s, h1 = v, xx+1, yy+1, xx, yy, mip, u, s, false); } + else { render_wall(h2 = s, h1 = w, xx, yy+1, xx+1, yy, mip, v, t, false); topleft = false; }; + }; + }; + render_tris(xx<=vxx && xx!=0 && yy!=sz-1 && !SOLID(z) && (!SOLID(s) || z->type!=CORNER) + && (z->type!=SEMISOLID || issemi(mip, xx-1, yy, 1, 0, 1, 1))) + render_wall(s, z, xx, yy, xx, yy+1, mip, s, v, true); + if(xx<=vxx && inner && !SOLID(t) && (!SOLID(s) || t->type!=CORNER) + && (t->type!=SEMISOLID || issemi(mip, xx+1, yy, 0, 0, 0, 1))) + render_wall(s, t, xx+1, yy, xx+1, yy+1, mip, t, u, false); + if(yy>=vyy && yy!=0 && xx!=sz-1 && !SOLID(w) && (!SOLID(s) || w->type!=CORNER) + && (w->type!=SEMISOLID || issemi(mip, xx, yy-1, 0, 1, 1, 1))) + render_wall(s, w, xx, yy, xx+1, yy, mip, s, t, false); + if(yy<=vyy && inner && !SOLID(v) && (!SOLID(s) || v->type!=CORNER) + && (v->type!=SEMISOLID || issemi(mip, xx, yy+1, 0, 0, 1, 0))) + render_wall(s, v, xx, yy+1, xx+1, yy+1, mip, v, u, true); + }; + }}; + +}; + +void distlod(int &low, int &high, int angle, float widef) +{ + float f = 90.0f/lod/widef; + low = (int)((90-angle)/f); + high = (int)(angle/f); + if(lowMAX_LOD) lod = MAX_LOD; + lodtop = lodbot = lodleft = lodright = min_lod; + if(yaw>45 && yaw<=135) + { + lodleft = lod; + distlod(lodtop, lodbot, yaw-45, widef); + } + else if(yaw>135 && yaw<=225) + { + lodbot = lod; + distlod(lodleft, lodright, yaw-135, widef); + } + else if(yaw>225 && yaw<=315) + { + lodright = lod; + distlod(lodbot, lodtop, yaw-225, widef); + } + else + { + lodtop = lod; + distlod(lodright, lodleft, yaw<=45 ? yaw+45 : yaw-315, widef); + }; + float hyfov = fov*h/w/2; + render_floor = pitch>MAX_MIP, ssize>>MAX_MIP); + mipstats(stats[0], stats[1], stats[2]); +}; +