9 struct fpsserver
: igameserver
11 struct server_entity
// server side version of "entity" type
18 static const int DEATHMILLIS
= 300;
20 enum { GE_NONE
= 0, GE_SHOT
, GE_EXPLODE
, GE_HIT
, GE_SUICIDE
, GE_PICKUP
};
72 struct projectilestate
77 projectilestate() : numprojs(0) {}
79 void reset() { numprojs
= 0; }
83 if(numprojs
>=N
) numprojs
= 0;
84 projs
[numprojs
++] = val
;
89 loopi(numprojs
) if(projs
[i
]==val
)
91 projs
[i
] = projs
[--numprojs
];
98 struct gamestate
: fpsstate
101 int state
, editstate
;
102 int lastdeath
, lastspawn
, lifesequence
;
104 projectilestate
<8> rockets
, grenades
;
105 int frags
, deaths
, teamkills
, shotdamage
, damage
;
106 int lasttimeplayed
, timeplayed
;
109 gamestate() : state(CS_DEAD
), editstate(CS_DEAD
) {}
111 bool isalive(int gamemillis
)
113 return state
==CS_ALIVE
|| (state
==CS_DEAD
&& gamemillis
- lastdeath
<= DEATHMILLIS
);
116 bool waitexpired(int gamemillis
)
118 return gamemillis
- lastshot
>= gunwait
;
123 if(state
!=CS_SPECTATOR
) state
= editstate
= CS_DEAD
;
131 frags
= deaths
= teamkills
= shotdamage
= damage
= 0;
139 o
= vec(-1e10f
, -1e10f
, -1e10f
);
150 int maxhealth
, frags
, deaths
, teamkills
, shotdamage
, damage
;
154 void save(gamestate
&gs
)
156 maxhealth
= gs
.maxhealth
;
159 teamkills
= gs
.teamkills
;
160 shotdamage
= gs
.shotdamage
;
162 timeplayed
= gs
.timeplayed
;
163 effectiveness
= gs
.effectiveness
;
166 void restore(gamestate
&gs
)
168 if(gs
.health
==gs
.maxhealth
) gs
.health
= maxhealth
;
169 gs
.maxhealth
= maxhealth
;
172 gs
.teamkills
= teamkills
;
173 gs
.shotdamage
= shotdamage
;
175 gs
.timeplayed
= timeplayed
;
176 gs
.effectiveness
= effectiveness
;
183 string name
, team
, mapvote
;
186 bool spectator
, local
, timesync
, wantsmaster
;
187 int gameoffset
, lastevent
;
189 vector
<gameevent
> events
;
190 vector
<uchar
> position
, messages
;
191 vector
<clientinfo
*> targets
;
193 clientinfo() { reset(); }
195 gameevent
&addevent()
197 static gameevent dummy
;
198 if(state
.state
==CS_SPECTATOR
|| events
.length()>100) return dummy
;
206 events
.setsizenodelete(0);
207 targets
.setsizenodelete(0);
214 name
[0] = team
[0] = 0;
215 privilege
= PRIV_NONE
;
216 spectator
= local
= wantsmaster
= false;
217 position
.setsizenodelete(0);
218 messages
.setsizenodelete(0);
226 vector
<uchar
> positions
, messages
;
236 #define MM_AUTOAPPROVE 0x1000
237 #define MM_DEFAULT (MM_MODE | MM_AUTOAPPROVE)
239 enum { MM_OPEN
= 0, MM_VETO
, MM_LOCKED
, MM_PRIVATE
};
241 bool notgotitems
, notgotbases
; // true when map has changed and waiting for clients to send item
243 int gamemillis
, gamelimit
;
247 int lastmillis
, totalmillis
, curtime
;
248 int interm
, minremain
;
250 enet_uint32 lastsend
;
251 int mastermode
, mastermask
;
257 vector
<uint
> allowedips
;
258 vector
<ban
> bannedips
;
259 vector
<clientinfo
*> clients
;
260 vector
<worldstate
*> worldstates
;
261 bool reliablemessages
;
271 vector
<demofile
> demos
;
275 gzFile demorecord
, demoplayback
;
282 servmode(fpsserver
&sv
) : sv(sv
) {}
283 virtual ~servmode() {}
285 virtual void entergame(clientinfo
*ci
) {}
286 virtual void leavegame(clientinfo
*ci
, bool disconnecting
= false) {}
288 virtual void moved(clientinfo
*ci
, const vec
&oldpos
, const vec
&newpos
) {}
289 virtual bool canspawn(clientinfo
*ci
, bool connecting
= false) { return true; }
290 virtual void spawned(clientinfo
*ci
) {}
291 virtual int fragvalue(clientinfo
*victim
, clientinfo
*actor
)
293 int gamemode
= sv
.gamemode
;
294 if(victim
==actor
|| isteam(victim
->team
, actor
->team
)) return -1;
297 virtual void died(clientinfo
*victim
, clientinfo
*actor
) {}
298 virtual bool canchangeteam(clientinfo
*ci
, const char *oldteam
, const char *newteam
) { return true; }
299 virtual void changeteam(clientinfo
*ci
, const char *oldteam
, const char *newteam
) {}
300 virtual void initclient(clientinfo
*ci
, ucharbuf
&p
, bool connecting
) {}
301 virtual void update() {}
302 virtual void reset(bool empty
) {}
303 virtual void intermission() {}
306 struct arenaservmode
: servmode
310 arenaservmode(fpsserver
&sv
) : servmode(sv
), arenaround(0) {}
312 bool canspawn(clientinfo
*ci
, bool connecting
= false)
314 if(connecting
&& sv
.nonspectators(ci
->clientnum
)<=1) return true;
318 void reset(bool empty
)
325 if(sv
.interm
|| sv
.gamemillis
<arenaround
|| !sv
.nonspectators()) return;
330 loopv(sv
.clients
) if(sv
.clients
[i
]->state
.state
==CS_DEAD
|| sv
.clients
[i
]->state
.state
==CS_ALIVE
)
332 sv
.clients
[i
]->state
.respawn();
333 sv
.sendspawn(sv
.clients
[i
]);
338 int gamemode
= sv
.gamemode
;
339 clientinfo
*alive
= NULL
;
343 clientinfo
*ci
= sv
.clients
[i
];
344 if(ci
->state
.state
==CS_ALIVE
|| (ci
->state
.state
==CS_DEAD
&& ci
->state
.lastspawn
>=0))
346 if(!alive
) alive
= ci
;
347 else if(!m_teammode
|| strcmp(alive
->team
, ci
->team
)) return;
349 else if(ci
->state
.state
==CS_DEAD
) dead
= true;
352 sendf(-1, 1, "ri2", SV_ARENAWIN
, !alive
? -1 : alive
->clientnum
);
353 arenaround
= sv
.gamemillis
+5000;
357 #define CAPTURESERV 1
361 #define ASSASSINSERV 1
362 #include "assassin.h"
369 arenaservmode arenamode
;
370 captureservmode capturemode
;
371 assassinservmode assassinmode
;
375 fpsserver() : notgotitems(true), notgotbases(false), gamemode(0), interm(0), minremain(0), mapreload(false), lastsend(0), mastermode(MM_OPEN
), mastermask(MM_DEFAULT
), currentmaster(-1), masterupdate(false), mapdata(NULL
), reliablemessages(false), demonextmatch(false), demotmp(NULL
), demorecord(NULL
), demoplayback(NULL
), nextplayback(0), arenamode(*this), capturemode(*this), assassinmode(*this), ctfmode(*this), smode(NULL
)
377 serverdesc
[0] = '\0';
378 masterpass
[0] = '\0';
381 void *newinfo() { return new clientinfo
; }
382 void deleteinfo(void *ci
) { delete (clientinfo
*)ci
; }
384 vector
<server_entity
> sents
;
385 vector
<savedscore
> scores
;
387 static const char *modestr(int n
, const char *unknown
= "unknown")
389 static const char *modenames
[] =
391 "slowmo SP", "slowmo DMSP", "demo", "SP", "DMSP", "ffa/default", "coopedit", "ffa/duel", "teamplay",
392 "instagib", "instagib team", "efficiency", "efficiency team",
393 "insta arena", "insta clan arena", "tactics arena", "tactics clan arena",
394 "capture", "insta capture", "regen capture", "assassin", "insta assassin",
397 return (n
>=-5 && size_t(n
+5)<sizeof(modenames
)/sizeof(modenames
[0])) ? modenames
[n
+5] : unknown
;
400 static const char *mastermodestr(int n
, const char *unknown
= "unknown")
402 static const char *mastermodenames
[] =
404 "open", "veto", "locked", "private"
406 return (n
>=0 && size_t(n
)<sizeof(mastermodenames
)/sizeof(mastermodenames
[0])) ? mastermodenames
[n
] : unknown
;
409 void sendservmsg(const char *s
) { sendf(-1, 1, "ris", SV_SERVMSG
, s
); }
417 int spawntime(int type
)
419 if(m_classicsp
) return INT_MAX
;
420 int np
= nonspectators();
421 np
= np
<3 ? 4 : (np
>4 ? 2 : 3); // spawn times are dependent on number of players
430 case I_CARTRIDGES
: sec
= np
*4; break;
431 case I_HEALTH
: sec
= np
*5; break;
433 case I_YELLOWARMOUR
: sec
= 20; break;
435 case I_QUAD
: sec
= 40+rnd(40); break;
440 bool pickup(int i
, int sender
) // server side item pickup, acknowledge first client that gets it
442 if(minremain
<=0 || !sents
.inrange(i
) || !sents
[i
].spawned
) return false;
443 clientinfo
*ci
= (clientinfo
*)getinfo(sender
);
444 if(!ci
|| (!ci
->local
&& !ci
->state
.canpickup(sents
[i
].type
))) return false;
445 sents
[i
].spawned
= false;
446 sents
[i
].spawntime
= spawntime(sents
[i
].type
);
447 sendf(-1, 1, "ri3", SV_ITEMACC
, i
, sender
);
448 ci
->state
.pickup(sents
[i
].type
);
452 void vote(char *map
, int reqmode
, int sender
)
454 clientinfo
*ci
= (clientinfo
*)getinfo(sender
);
455 if(!ci
|| (ci
->state
.state
==CS_SPECTATOR
&& !ci
->privilege
)) return;
456 s_strcpy(ci
->mapvote
, map
);
457 ci
->modevote
= reqmode
;
458 if(!ci
->mapvote
[0]) return;
459 if(ci
->local
|| mapreload
|| (ci
->privilege
&& mastermode
>=MM_VETO
))
461 if(demorecord
) enddemorecord();
462 if(!ci
->local
&& !mapreload
)
464 s_sprintfd(msg
)("%s forced %s on map %s", privname(ci
->privilege
), modestr(reqmode
), map
);
467 sendf(-1, 1, "risii", SV_MAPCHANGE
, ci
->mapvote
, ci
->modevote
, 1);
468 changemap(ci
->mapvote
, ci
->modevote
);
472 s_sprintfd(msg
)("%s suggests %s on map %s (select map to vote)", colorname(ci
), modestr(reqmode
), map
);
478 clientinfo
*choosebestclient(float &bestrank
)
480 clientinfo
*best
= NULL
;
484 clientinfo
*ci
= clients
[i
];
485 if(ci
->state
.timeplayed
<0) continue;
486 float rank
= ci
->state
.state
!=CS_SPECTATOR
? ci
->state
.effectiveness
/max(ci
->state
.timeplayed
, 1) : -1;
487 if(!best
|| rank
> bestrank
) { best
= ci
; bestrank
= rank
; }
494 static const char *teamnames
[2] = {"good", "evil"};
495 vector
<clientinfo
*> team
[2];
496 float teamrank
[2] = {0, 0};
497 for(int round
= 0, remaining
= clients
.length(); remaining
>=0; round
++)
499 int first
= round
&1, second
= (round
+1)&1, selected
= 0;
500 while(teamrank
[first
] <= teamrank
[second
])
503 clientinfo
*ci
= choosebestclient(rank
);
505 if(m_capture
|| m_ctf
) rank
= 1;
506 else if(selected
&& rank
<=0) break;
507 ci
->state
.timeplayed
= -1;
509 if(rank
>0) teamrank
[first
] += rank
;
514 remaining
-= selected
;
516 loopi(sizeof(team
)/sizeof(team
[0]))
520 clientinfo
*ci
= team
[i
][j
];
521 if(!strcmp(ci
->team
, teamnames
[i
])) continue;
522 s_strncpy(ci
->team
, teamnames
[i
], MAXTEAMLEN
+1);
523 sendf(-1, 1, "riis", SV_SETTEAM
, ci
->clientnum
, teamnames
[i
]);
534 teamrank(const char *name
) : name(name
), rank(0), clients(0) {}
537 const char *chooseworstteam(const char *suggest
= NULL
, clientinfo
*exclude
= NULL
)
539 teamrank teamranks
[2] = { teamrank("good"), teamrank("evil") };
540 const int numteams
= sizeof(teamranks
)/sizeof(teamranks
[0]);
543 clientinfo
*ci
= clients
[i
];
544 if(ci
==exclude
|| ci
->state
.state
==CS_SPECTATOR
|| !ci
->team
[0]) continue;
545 ci
->state
.timeplayed
+= lastmillis
- ci
->state
.lasttimeplayed
;
546 ci
->state
.lasttimeplayed
= lastmillis
;
548 loopj(numteams
) if(!strcmp(ci
->team
, teamranks
[j
].name
))
550 teamrank
&ts
= teamranks
[j
];
551 ts
.rank
+= ci
->state
.effectiveness
/max(ci
->state
.timeplayed
, 1);
556 teamrank
*worst
= &teamranks
[numteams
-1];
559 teamrank
&ts
= teamranks
[i
];
560 if(m_capture
|| m_ctf
)
562 if(ts
.clients
< worst
->clients
|| (ts
.clients
== worst
->clients
&& ts
.rank
< worst
->rank
)) worst
= &ts
;
564 else if(ts
.rank
< worst
->rank
|| (ts
.rank
== worst
->rank
&& ts
.clients
< worst
->clients
)) worst
= &ts
;
569 void writedemo(int chan
, void *data
, int len
)
571 if(!demorecord
) return;
572 int stamp
[3] = { gamemillis
, chan
, len
};
573 endianswap(stamp
, sizeof(int), 3);
574 gzwrite(demorecord
, stamp
, sizeof(stamp
));
575 gzwrite(demorecord
, data
, len
);
578 void recordpacket(int chan
, void *data
, int len
)
580 writedemo(chan
, data
, len
);
585 if(!demorecord
) return;
591 demotmp
= fopen("demorecord", "rb");
595 fseek(demotmp
, 0, SEEK_END
);
596 int len
= ftell(demotmp
);
598 if(demos
.length()>=MAXDEMOS
)
600 delete[] demos
[0].data
;
603 demofile
&d
= demos
.add();
604 time_t t
= time(NULL
);
605 char *timestr
= ctime(&t
), *trim
= timestr
+ strlen(timestr
);
606 while(trim
>timestr
&& isspace(*--trim
)) *trim
= '\0';
607 s_sprintf(d
.info
)("%s: %s, %s, %.2f%s", timestr
, modestr(gamemode
), smapname
, len
> 1024*1024 ? len
/(1024*1024.f
) : len
/1024.0f
, len
> 1024*1024 ? "MB" : "kB");
608 s_sprintfd(msg
)("demo \"%s\" recorded", d
.info
);
610 d
.data
= new uchar
[len
];
612 fread(d
.data
, 1, len
, demotmp
);
617 void setupdemorecord()
619 if(haslocalclients() || !m_mp(gamemode
) || gamemode
==1) return;
622 gzFile f
= gzopen("demorecord", "wb9");
627 setvbuf(demotmp
, NULL
, _IONBF
, 0);
629 gzFile f
= gzdopen(_dup(_fileno(demotmp
)), "wb9");
638 sendservmsg("recording demo");
643 memcpy(hdr
.magic
, DEMO_MAGIC
, sizeof(hdr
.magic
));
644 hdr
.version
= DEMO_VERSION
;
645 hdr
.protocol
= PROTOCOL_VERSION
;
646 endianswap(&hdr
.version
, sizeof(int), 1);
647 endianswap(&hdr
.protocol
, sizeof(int), 1);
648 gzwrite(demorecord
, &hdr
, sizeof(demoheader
));
650 ENetPacket
*packet
= enet_packet_create(NULL
, MAXTRANS
, 0);
651 ucharbuf
p(packet
->data
, packet
->dataLength
);
652 welcomepacket(p
, -1, packet
);
653 writedemo(1, p
.buf
, p
.len
);
654 enet_packet_destroy(packet
);
659 clientinfo
*ci
= clients
[i
];
661 ucharbuf
q(&buf
[sizeof(header
)], sizeof(buf
)-sizeof(header
));
662 putint(q
, SV_INITC2S
);
663 sendstring(ci
->name
, q
);
664 sendstring(ci
->team
, q
);
666 ucharbuf
h(header
, sizeof(header
));
667 putint(h
, SV_CLIENT
);
668 putint(h
, ci
->clientnum
);
671 memcpy(&buf
[sizeof(header
)-h
.len
], header
, h
.len
);
673 writedemo(1, &buf
[sizeof(header
)-h
.len
], h
.len
+q
.len
);
677 void listdemos(int cn
)
679 ENetPacket
*packet
= enet_packet_create(NULL
, MAXTRANS
, ENET_PACKET_FLAG_RELIABLE
);
681 ucharbuf
p(packet
->data
, packet
->dataLength
);
682 putint(p
, SV_SENDDEMOLIST
);
683 putint(p
, demos
.length());
684 loopv(demos
) sendstring(demos
[i
].info
, p
);
685 enet_packet_resize(packet
, p
.length());
686 sendpacket(cn
, 1, packet
);
687 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
690 void cleardemos(int n
)
694 loopv(demos
) delete[] demos
[i
].data
;
696 sendservmsg("cleared all demos");
698 else if(demos
.inrange(n
-1))
700 delete[] demos
[n
-1].data
;
702 s_sprintfd(msg
)("cleared demo %d", n
);
707 void senddemo(int cn
, int num
)
709 if(!num
) num
= demos
.length();
710 if(!demos
.inrange(num
-1)) return;
711 demofile
&d
= demos
[num
-1];
712 sendf(cn
, 2, "rim", SV_SENDDEMO
, d
.len
, d
.data
);
715 void setupdemoplayback()
720 s_sprintfd(file
)("%s.dmo", smapname
);
721 demoplayback
= opengzfile(file
, "rb9");
722 if(!demoplayback
) s_sprintf(msg
)("could not read demo \"%s\"", file
);
723 else if(gzread(demoplayback
, &hdr
, sizeof(demoheader
))!=sizeof(demoheader
) || memcmp(hdr
.magic
, DEMO_MAGIC
, sizeof(hdr
.magic
)))
724 s_sprintf(msg
)("\"%s\" is not a demo file", file
);
727 endianswap(&hdr
.version
, sizeof(int), 1);
728 endianswap(&hdr
.protocol
, sizeof(int), 1);
729 if(hdr
.version
!=DEMO_VERSION
) s_sprintf(msg
)("demo \"%s\" requires an %s version of Sauerbraten", file
, hdr
.version
<DEMO_VERSION
? "older" : "newer");
730 else if(hdr
.protocol
!=PROTOCOL_VERSION
) s_sprintf(msg
)("demo \"%s\" requires an %s version of Sauerbraten", file
, hdr
.protocol
<PROTOCOL_VERSION
? "older" : "newer");
734 if(demoplayback
) { gzclose(demoplayback
); demoplayback
= NULL
; }
739 s_sprintf(msg
)("playing demo \"%s\"", file
);
742 sendf(-1, 1, "rii", SV_DEMOPLAYBACK
, 1);
744 if(gzread(demoplayback
, &nextplayback
, sizeof(nextplayback
))!=sizeof(nextplayback
))
749 endianswap(&nextplayback
, sizeof(nextplayback
), 1);
752 void enddemoplayback()
754 if(!demoplayback
) return;
755 gzclose(demoplayback
);
758 sendf(-1, 1, "rii", SV_DEMOPLAYBACK
, 0);
760 sendservmsg("demo playback finished");
764 ENetPacket
*packet
= enet_packet_create(NULL
, MAXTRANS
, ENET_PACKET_FLAG_RELIABLE
);
765 ucharbuf
p(packet
->data
, packet
->dataLength
);
766 welcomepacket(p
, clients
[i
]->clientnum
, packet
);
767 enet_packet_resize(packet
, p
.length());
768 sendpacket(clients
[i
]->clientnum
, 1, packet
);
769 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
775 if(!demoplayback
) return;
776 while(gamemillis
>=nextplayback
)
779 if(gzread(demoplayback
, &chan
, sizeof(chan
))!=sizeof(chan
) ||
780 gzread(demoplayback
, &len
, sizeof(len
))!=sizeof(len
))
785 endianswap(&chan
, sizeof(chan
), 1);
786 endianswap(&len
, sizeof(len
), 1);
787 ENetPacket
*packet
= enet_packet_create(NULL
, len
, 0);
788 if(!packet
|| gzread(demoplayback
, packet
->data
, len
)!=len
)
790 if(packet
) enet_packet_destroy(packet
);
794 sendpacket(-1, chan
, packet
);
795 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
796 if(gzread(demoplayback
, &nextplayback
, sizeof(nextplayback
))!=sizeof(nextplayback
))
801 endianswap(&nextplayback
, sizeof(nextplayback
), 1);
805 void changemap(const char *s
, int mode
)
807 if(m_demo
) enddemoplayback();
808 else enddemorecord();
813 minremain
= m_teammode
&& !m_ctf
? 15 : 10;
814 gamelimit
= minremain
*60000;
816 s_strcpy(smapname
, s
);
822 clientinfo
*ci
= clients
[i
];
823 ci
->state
.timeplayed
+= lastmillis
- ci
->state
.lasttimeplayed
;
825 if(m_teammode
) autoteam();
827 if(m_arena
) smode
= &arenamode
;
828 else if(m_capture
) smode
= &capturemode
;
829 else if(m_assassin
) smode
= &assassinmode
;
830 else if(m_ctf
) smode
= &ctfmode
;
832 if(smode
) smode
->reset(false);
834 if(gamemode
>1 || (gamemode
==0 && hasnonlocalclients())) sendf(-1, 1, "ri2", SV_TIMEUP
, minremain
);
837 clientinfo
*ci
= clients
[i
];
839 ci
->state
.lasttimeplayed
= lastmillis
;
840 if(m_mp(gamemode
) && ci
->state
.state
!=CS_SPECTATOR
) sendspawn(ci
);
843 if(m_demo
) setupdemoplayback();
844 else if(demonextmatch
)
846 demonextmatch
= false;
851 savedscore
&findscore(clientinfo
*ci
, bool insert
)
853 uint ip
= getclientip(ci
->clientnum
);
854 if(!ip
) return *(savedscore
*)0;
859 clientinfo
*oi
= clients
[i
];
860 if(oi
->clientnum
!= ci
->clientnum
&& getclientip(oi
->clientnum
) == ip
&& !strcmp(oi
->name
, ci
->name
))
862 oi
->state
.timeplayed
+= lastmillis
- oi
->state
.lasttimeplayed
;
863 oi
->state
.lasttimeplayed
= lastmillis
;
864 static savedscore curscore
;
865 curscore
.save(oi
->state
);
872 savedscore
&sc
= scores
[i
];
873 if(sc
.ip
== ip
&& !strcmp(sc
.name
, ci
->name
)) return sc
;
875 if(!insert
) return *(savedscore
*)0;
876 savedscore
&sc
= scores
.add();
878 s_strcpy(sc
.name
, ci
->name
);
882 void savescore(clientinfo
*ci
)
884 savedscore
&sc
= findscore(ci
, true);
885 if(&sc
) sc
.save(ci
->state
);
893 votecount(char *s
, int n
) : map(s
), mode(n
), count(0) {}
896 void checkvotes(bool force
= false)
898 vector
<votecount
> votes
;
902 clientinfo
*oi
= clients
[i
];
903 if(oi
->state
.state
==CS_SPECTATOR
&& !oi
->privilege
) continue;
905 if(!oi
->mapvote
[0]) continue;
906 votecount
*vc
= NULL
;
907 loopvj(votes
) if(!strcmp(oi
->mapvote
, votes
[j
].map
) && oi
->modevote
==votes
[j
].mode
)
912 if(!vc
) vc
= &votes
.add(votecount(oi
->mapvote
, oi
->modevote
));
915 votecount
*best
= NULL
;
916 loopv(votes
) if(!best
|| votes
[i
].count
> best
->count
|| (votes
[i
].count
== best
->count
&& rnd(2))) best
= &votes
[i
];
917 if(force
|| (best
&& best
->count
> maxvotes
/2))
919 if(demorecord
) enddemorecord();
920 if(best
&& (best
->count
> (force
? 1 : maxvotes
/2)))
922 sendservmsg(force
? "vote passed by default" : "vote passed by majority");
923 sendf(-1, 1, "risii", SV_MAPCHANGE
, best
->map
, best
->mode
, 1);
924 changemap(best
->map
, best
->mode
);
929 if(clients
.length()) sendf(-1, 1, "ri", SV_MAPRELOAD
);
934 int nonspectators(int exclude
= -1)
937 loopv(clients
) if(i
!=exclude
&& clients
[i
]->state
.state
!=CS_SPECTATOR
) n
++;
941 int checktype(int type
, clientinfo
*ci
)
943 if(ci
&& ci
->local
) return type
;
945 // other message types can get sent by accident if a master forces spectator on someone, so disabling this case for now and checking for spectator state in message handlers
946 // spectators can only connect and talk
947 static int spectypes
[] = { SV_INITC2S
, SV_POS
, SV_TEXT
, SV_PING
, SV_CLIENTPING
, SV_GETMAP
, SV_SETMASTER
};
948 if(ci
&& ci
->state
.state
==CS_SPECTATOR
&& !ci
->privilege
)
950 loopi(sizeof(spectypes
)/sizeof(int)) if(type
== spectypes
[i
]) return type
;
954 // only allow edit messages in coop-edit mode
955 if(type
>=SV_EDITENT
&& type
<=SV_GETMAP
&& gamemode
!=1) return -1;
956 // server only messages
957 static int servtypes
[] = { SV_INITS2C
, SV_MAPRELOAD
, SV_SERVMSG
, SV_DAMAGE
, SV_HITPUSH
, SV_SHOTFX
, SV_DIED
, SV_SPAWNSTATE
, SV_FORCEDEATH
, SV_ARENAWIN
, SV_ITEMACC
, SV_ITEMSPAWN
, SV_TIMEUP
, SV_CDIS
, SV_CURRENTMASTER
, SV_PONG
, SV_RESUME
, SV_TEAMSCORE
, SV_BASEINFO
, SV_BASEREGEN
, SV_ANNOUNCE
, SV_CLEARTARGETS
, SV_CLEARHUNTERS
, SV_ADDTARGET
, SV_REMOVETARGET
, SV_ADDHUNTER
, SV_REMOVEHUNTER
, SV_SENDDEMOLIST
, SV_SENDDEMO
, SV_DEMOPLAYBACK
, SV_SENDMAP
, SV_DROPFLAG
, SV_SCOREFLAG
, SV_RETURNFLAG
, SV_CLIENT
};
958 if(ci
) loopi(sizeof(servtypes
)/sizeof(int)) if(type
== servtypes
[i
]) return -1;
962 static void freecallback(ENetPacket
*packet
)
964 extern igameserver
*sv
;
965 ((fpsserver
*)sv
)->cleanworldstate(packet
);
968 void cleanworldstate(ENetPacket
*packet
)
972 worldstate
*ws
= worldstates
[i
];
973 if(packet
->data
>= ws
->positions
.getbuf() && packet
->data
<= &ws
->positions
.last()) ws
->uses
--;
974 else if(packet
->data
>= ws
->messages
.getbuf() && packet
->data
<= &ws
->messages
.last()) ws
->uses
--;
979 worldstates
.remove(i
);
985 bool buildworldstate()
987 static struct { int posoff
, msgoff
, msglen
; } pkt
[MAXCLIENTS
];
988 worldstate
&ws
= *new worldstate
;
991 clientinfo
&ci
= *clients
[i
];
992 if(ci
.position
.empty()) pkt
[i
].posoff
= -1;
995 pkt
[i
].posoff
= ws
.positions
.length();
996 loopvj(ci
.position
) ws
.positions
.add(ci
.position
[j
]);
998 if(ci
.messages
.empty()) pkt
[i
].msgoff
= -1;
1001 pkt
[i
].msgoff
= ws
.messages
.length();
1002 ucharbuf p
= ws
.messages
.reserve(16);
1003 putint(p
, SV_CLIENT
);
1004 putint(p
, ci
.clientnum
);
1005 putuint(p
, ci
.messages
.length());
1006 ws
.messages
.addbuf(p
);
1007 loopvj(ci
.messages
) ws
.messages
.add(ci
.messages
[j
]);
1008 pkt
[i
].msglen
= ws
.messages
.length() - pkt
[i
].msgoff
;
1011 int psize
= ws
.positions
.length(), msize
= ws
.messages
.length();
1012 if(psize
) recordpacket(0, ws
.positions
.getbuf(), psize
);
1013 if(msize
) recordpacket(1, ws
.messages
.getbuf(), msize
);
1014 loopi(psize
) { uchar c
= ws
.positions
[i
]; ws
.positions
.add(c
); }
1015 loopi(msize
) { uchar c
= ws
.messages
[i
]; ws
.messages
.add(c
); }
1019 clientinfo
&ci
= *clients
[i
];
1021 if(psize
&& (pkt
[i
].posoff
<0 || psize
-ci
.position
.length()>0))
1023 packet
= enet_packet_create(&ws
.positions
[pkt
[i
].posoff
<0 ? 0 : pkt
[i
].posoff
+ci
.position
.length()],
1024 pkt
[i
].posoff
<0 ? psize
: psize
-ci
.position
.length(),
1025 ENET_PACKET_FLAG_NO_ALLOCATE
);
1026 sendpacket(ci
.clientnum
, 0, packet
);
1027 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
1028 else { ++ws
.uses
; packet
->freeCallback
= freecallback
; }
1030 ci
.position
.setsizenodelete(0);
1032 if(msize
&& (pkt
[i
].msgoff
<0 || msize
-pkt
[i
].msglen
>0))
1034 packet
= enet_packet_create(&ws
.messages
[pkt
[i
].msgoff
<0 ? 0 : pkt
[i
].msgoff
+pkt
[i
].msglen
],
1035 pkt
[i
].msgoff
<0 ? msize
: msize
-pkt
[i
].msglen
,
1036 (reliablemessages
? ENET_PACKET_FLAG_RELIABLE
: 0) | ENET_PACKET_FLAG_NO_ALLOCATE
);
1037 sendpacket(ci
.clientnum
, 1, packet
);
1038 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
1039 else { ++ws
.uses
; packet
->freeCallback
= freecallback
; }
1041 ci
.messages
.setsizenodelete(0);
1043 reliablemessages
= false;
1051 worldstates
.add(&ws
);
1058 if(clients
.empty()) return false;
1059 enet_uint32 curtime
= enet_time_get()-lastsend
;
1060 if(curtime
<33) return false;
1061 bool flush
= buildworldstate();
1062 lastsend
+= curtime
- (curtime
%33);
1066 void parsepacket(int sender
, int chan
, bool reliable
, ucharbuf
&p
) // has to parse exactly each byte of the packet
1068 if(sender
<0) return;
1071 receivefile(sender
, p
.buf
, p
.maxlen
);
1074 if(reliable
) reliablemessages
= true;
1075 char text
[MAXTRANS
];
1077 clientinfo
*ci
= sender
>=0 ? (clientinfo
*)getinfo(sender
) : NULL
;
1078 #define QUEUE_MSG { if(!ci->local) while(curmsg<p.length()) ci->messages.add(p.buf[curmsg++]); }
1079 #define QUEUE_BUF(size, body) { \
1082 curmsg = p.length(); \
1083 ucharbuf buf = ci->messages.reserve(size); \
1085 ci->messages.addbuf(buf); \
1088 #define QUEUE_INT(n) QUEUE_BUF(5, putint(buf, n))
1089 #define QUEUE_UINT(n) QUEUE_BUF(4, putuint(buf, n))
1090 #define QUEUE_STR(text) QUEUE_BUF(2*strlen(text)+1, sendstring(text, buf))
1092 while((curmsg
= p
.length()) < p
.maxlen
) switch(type
= checktype(getint(p
), ci
))
1097 if(cn
<0 || cn
>=getnumclients() || cn
!=sender
)
1099 disconnect_client(sender
, DISC_CN
);
1102 vec
oldpos(ci
->state
.o
);
1103 loopi(3) ci
->state
.o
[i
] = getuint(p
)/DMF
;
1106 int physstate
= getuint(p
);
1107 if(physstate
&0x20) loopi(2) getint(p
);
1108 if(physstate
&0x10) getint(p
);
1110 if(!ci
->local
&& (ci
->state
.state
==CS_ALIVE
|| ci
->state
.state
==CS_EDITING
))
1112 ci
->position
.setsizenodelete(0);
1113 while(curmsg
<p
.length()) ci
->position
.add(p
.buf
[curmsg
++]);
1115 if(smode
&& ci
->state
.state
==CS_ALIVE
) smode
->moved(ci
, oldpos
, ci
->state
.o
);
1121 int val
= getint(p
);
1122 if(!ci
->local
&& gamemode
!=1) break;
1123 if(val
? ci
->state
.state
!=CS_ALIVE
&& ci
->state
.state
!=CS_DEAD
: ci
->state
.state
!=CS_EDITING
) break;
1126 if(val
) smode
->leavegame(ci
);
1127 else smode
->entergame(ci
);
1131 ci
->state
.editstate
= ci
->state
.state
;
1132 ci
->state
.state
= CS_EDITING
;
1134 else ci
->state
.state
= ci
->state
.editstate
;
1137 ci
->events
.setsizenodelete(0);
1138 ci
->state
.rockets
.reset();
1139 ci
->state
.grenades
.reset();
1146 if(ci
->state
.state
!=CS_DEAD
|| ci
->state
.lastspawn
>=0 || (smode
&& !smode
->canspawn(ci
))) break;
1147 if(ci
->state
.lastdeath
) ci
->state
.respawn();
1153 int gunselect
= getint(p
);
1154 if(ci
->state
.state
!=CS_ALIVE
) break;
1155 ci
->state
.gunselect
= gunselect
;
1162 int ls
= getint(p
), gunselect
= getint(p
);
1163 if((ci
->state
.state
!=CS_ALIVE
&& ci
->state
.state
!=CS_DEAD
) || ls
!=ci
->state
.lifesequence
|| ci
->state
.lastspawn
<0) break;
1164 ci
->state
.lastspawn
= -1;
1165 ci
->state
.state
= CS_ALIVE
;
1166 ci
->state
.gunselect
= gunselect
;
1167 if(smode
) smode
->spawned(ci
);
1170 putint(buf
, SV_SPAWN
);
1171 sendstate(ci
->state
, buf
);
1178 gameevent
&suicide
= ci
->addevent();
1179 suicide
.type
= GE_SUICIDE
;
1185 gameevent
&shot
= ci
->addevent();
1186 shot
.type
= GE_SHOT
;
1187 #define seteventmillis(event) \
1189 event.id = getint(p); \
1190 if(!ci->timesync || (ci->events.length()==1 && ci->state.waitexpired(gamemillis))) \
1192 ci->timesync = true; \
1193 ci->gameoffset = gamemillis - event.id; \
1194 event.millis = gamemillis; \
1196 else event.millis = ci->gameoffset + event.id; \
1198 seteventmillis(shot
.shot
);
1199 shot
.shot
.gun
= getint(p
);
1200 loopk(3) shot
.shot
.from
[k
] = getint(p
)/DMF
;
1201 loopk(3) shot
.shot
.to
[k
] = getint(p
)/DMF
;
1202 int hits
= getint(p
);
1205 gameevent
&hit
= ci
->addevent();
1207 hit
.hit
.target
= getint(p
);
1208 hit
.hit
.lifesequence
= getint(p
);
1209 hit
.hit
.rays
= getint(p
);
1210 loopk(3) hit
.hit
.dir
[k
] = getint(p
)/DNF
;
1217 gameevent
&exp
= ci
->addevent();
1218 exp
.type
= GE_EXPLODE
;
1219 seteventmillis(exp
.explode
);
1220 exp
.explode
.gun
= getint(p
);
1221 exp
.explode
.id
= getint(p
);
1222 int hits
= getint(p
);
1225 gameevent
&hit
= ci
->addevent();
1227 hit
.hit
.target
= getint(p
);
1228 hit
.hit
.lifesequence
= getint(p
);
1229 hit
.hit
.dist
= getint(p
)/DMF
;
1230 loopk(3) hit
.hit
.dir
[k
] = getint(p
)/DNF
;
1238 gameevent
&pickup
= ci
->addevent();
1239 pickup
.type
= GE_PICKUP
;
1240 pickup
.pickup
.ent
= n
;
1247 filtertext(text
, text
);
1254 if(ci
->state
.state
==CS_SPECTATOR
|| !m_teammode
|| !ci
->team
[0]) break;
1257 clientinfo
*t
= clients
[i
];
1258 if(t
==ci
|| t
->state
.state
==CS_SPECTATOR
|| strcmp(ci
->team
, t
->team
)) continue;
1259 sendf(t
->clientnum
, 1, "riis", SV_SAYTEAM
, ci
->clientnum
, text
);
1267 bool connected
= !ci
->name
[0];
1269 filtertext(text
, text
, false, MAXNAMELEN
);
1270 if(!text
[0]) s_strcpy(text
, "unnamed");
1272 s_strncpy(ci
->name
, text
, MAXNAMELEN
+1);
1273 if(!ci
->local
&& connected
)
1275 savedscore
&sc
= findscore(ci
, false);
1278 sc
.restore(ci
->state
);
1279 gamestate
&gs
= ci
->state
;
1280 sendf(-1, 1, "ri2i9vi", SV_RESUME
, sender
,
1281 gs
.state
, gs
.frags
, gs
.quadmillis
,
1283 gs
.health
, gs
.maxhealth
,
1284 gs
.armour
, gs
.armourtype
,
1285 gs
.gunselect
, GUN_PISTOL
-GUN_SG
+1, &gs
.ammo
[GUN_SG
], -1);
1289 filtertext(text
, text
, false, MAXTEAMLEN
);
1290 if(!ci
->local
&& (smode
&& !smode
->canchangeteam(ci
, ci
->team
, text
)) && m_teammode
)
1292 const char *worst
= chooseworstteam(text
, ci
);
1295 s_strcpy(text
, worst
);
1296 sendf(sender
, 1, "riis", SV_SETTEAM
, sender
, worst
);
1299 else QUEUE_STR(text
);
1301 else QUEUE_STR(text
);
1302 if(smode
&& ci
->state
.state
==CS_ALIVE
&& strcmp(ci
->team
, text
)) smode
->changeteam(ci
, ci
->team
, text
);
1303 s_strncpy(ci
->team
, text
, MAXTEAMLEN
+1);
1312 filtertext(text
, text
);
1313 int reqmode
= getint(p
);
1314 if(type
!=SV_MAPVOTE
&& !mapreload
) break;
1315 if(!ci
->local
&& !m_mp(reqmode
)) reqmode
= 0;
1316 vote(text
, reqmode
, sender
);
1322 if((ci
->state
.state
==CS_SPECTATOR
&& !ci
->privilege
) || !notgotitems
) { while(getint(p
)>=0 && !p
.overread()) getint(p
); break; }
1324 while((n
= getint(p
))>=0 && !p
.overread())
1326 server_entity se
= { getint(p
), false, 0 };
1327 while(sents
.length()<=n
) sents
.add(se
);
1328 if(gamemode
>=0 && (sents
[n
].type
==I_QUAD
|| sents
[n
].type
==I_BOOST
)) sents
[n
].spawntime
= spawntime(sents
[n
].type
);
1329 else sents
[n
].spawned
= true;
1331 notgotitems
= false;
1350 if((ci
->state
.state
!=CS_SPECTATOR
|| ci
->privilege
) && smode
==&capturemode
) capturemode
.parsebases(p
);
1354 if(ci
->state
.state
!=CS_SPECTATOR
&& smode
==&capturemode
) capturemode
.replenishammo(ci
);
1359 int flag
= getint(p
);
1360 if(ci
->state
.state
!=CS_SPECTATOR
&& smode
==&ctfmode
) ctfmode
.takeflag(ci
, flag
);
1365 if((ci
->state
.state
!=CS_SPECTATOR
|| ci
->privilege
) && smode
==&ctfmode
) ctfmode
.parseflags(p
);
1369 sendf(sender
, 1, "i2", SV_PONG
, getint(p
));
1380 if(ci
->privilege
&& mm
>=MM_OPEN
&& mm
<=MM_PRIVATE
)
1382 if(ci
->privilege
>=PRIV_ADMIN
|| (mastermask
&(1<<mm
)))
1385 allowedips
.setsize(0);
1388 loopv(clients
) allowedips
.add(getclientip(clients
[i
]->clientnum
));
1390 s_sprintfd(s
)("mastermode is now %s (%d)", mastermodestr(mastermode
), mastermode
);
1395 s_sprintfd(s
)("mastermode %d is disabled on this server", mm
);
1396 sendf(sender
, 1, "ris", SV_SERVMSG
, s
);
1406 bannedips
.setsize(0);
1407 sendservmsg("cleared all bans");
1414 int victim
= getint(p
);
1415 if(ci
->privilege
&& victim
>=0 && victim
<getnumclients() && ci
->clientnum
!=victim
&& getinfo(victim
))
1417 ban
&b
= bannedips
.add();
1418 b
.time
= totalmillis
;
1419 b
.ip
= getclientip(victim
);
1420 allowedips
.removeobj(b
.ip
);
1421 disconnect_client(victim
, DISC_KICK
);
1428 int spectator
= getint(p
), val
= getint(p
);
1429 if(!ci
->privilege
&& (ci
->state
.state
==CS_SPECTATOR
|| spectator
!=sender
)) break;
1430 clientinfo
*spinfo
= (clientinfo
*)getinfo(spectator
);
1433 sendf(-1, 1, "ri3", SV_SPECTATOR
, spectator
, val
);
1435 if(spinfo
->state
.state
!=CS_SPECTATOR
&& val
)
1437 if(smode
) smode
->leavegame(spinfo
);
1438 spinfo
->state
.state
= CS_SPECTATOR
;
1439 spinfo
->state
.timeplayed
+= lastmillis
- spinfo
->state
.lasttimeplayed
;
1441 else if(spinfo
->state
.state
==CS_SPECTATOR
&& !val
)
1443 spinfo
->state
.state
= CS_DEAD
;
1444 spinfo
->state
.respawn();
1445 if(!smode
|| smode
->canspawn(spinfo
)) sendspawn(spinfo
);
1446 spinfo
->state
.lasttimeplayed
= lastmillis
;
1453 int who
= getint(p
);
1455 filtertext(text
, text
, false, MAXTEAMLEN
);
1456 if(!ci
->privilege
|| who
<0 || who
>=getnumclients()) break;
1457 clientinfo
*wi
= (clientinfo
*)getinfo(who
);
1459 if(!smode
|| smode
->canchangeteam(wi
, wi
->team
, text
))
1461 if(smode
&& wi
->state
.state
==CS_ALIVE
&& strcmp(wi
->team
, text
))
1462 smode
->changeteam(wi
, wi
->team
, text
);
1463 s_strncpy(wi
->team
, text
, MAXTEAMLEN
+1);
1465 sendf(sender
, 1, "riis", SV_SETTEAM
, who
, wi
->team
);
1466 QUEUE_INT(SV_SETTEAM
);
1468 QUEUE_STR(wi
->team
);
1472 case SV_FORCEINTERMISSION
:
1473 if(m_sp
) startintermission();
1478 int val
= getint(p
);
1479 if(ci
->privilege
<PRIV_ADMIN
) break;
1480 demonextmatch
= val
!=0;
1481 s_sprintfd(msg
)("demo recording is %s for next match", demonextmatch
? "enabled" : "disabled");
1488 if(!ci
->local
&& ci
->privilege
<PRIV_ADMIN
) break;
1489 if(m_demo
) enddemoplayback();
1490 else enddemorecord();
1496 int demo
= getint(p
);
1497 if(ci
->privilege
<PRIV_ADMIN
) break;
1503 if(!ci
->privilege
&& ci
->state
.state
==CS_SPECTATOR
) break;
1510 if(!ci
->privilege
&& ci
->state
.state
==CS_SPECTATOR
) break;
1511 senddemo(sender
, n
);
1518 sendf(sender
, 1, "ris", SV_SERVMSG
, "server sending map...");
1519 sendfile(sender
, 2, mapdata
, "ri", SV_SENDMAP
);
1521 else sendf(sender
, 1, "ris", SV_SERVMSG
, "no map to send");
1526 int size
= getint(p
);
1527 if(!ci
->privilege
&& ci
->state
.state
==CS_SPECTATOR
) break;
1532 notgotitems
= false;
1533 if(smode
) smode
->reset(true);
1541 int val
= getint(p
);
1543 setmaster(ci
, val
!=0, text
);
1544 // don't broadcast the master password
1548 case SV_APPROVEMASTER
:
1551 if(mastermask
&MM_AUTOAPPROVE
|| ci
->state
.state
==CS_SPECTATOR
) break;
1552 clientinfo
*candidate
= (clientinfo
*)getinfo(mn
);
1553 if(!candidate
|| !candidate
->wantsmaster
|| mn
==sender
|| getclientip(mn
)==getclientip(sender
)) break;
1554 setmaster(candidate
, true, "", true);
1560 int size
= msgsizelookup(type
);
1561 if(size
==-1) { disconnect_client(sender
, DISC_TAGT
); return; }
1562 if(size
>0) loopi(size
-1) getint(p
);
1563 if(ci
&& ci
->state
.state
!=CS_SPECTATOR
) QUEUE_MSG
;
1569 void sendstate(gamestate
&gs
, ucharbuf
&p
)
1571 putint(p
, gs
.lifesequence
);
1572 putint(p
, gs
.health
);
1573 putint(p
, gs
.maxhealth
);
1574 putint(p
, gs
.armour
);
1575 putint(p
, gs
.armourtype
);
1576 putint(p
, gs
.gunselect
);
1577 loopi(GUN_PISTOL
-GUN_SG
+1) putint(p
, gs
.ammo
[GUN_SG
+i
]);
1580 int welcomepacket(ucharbuf
&p
, int n
, ENetPacket
*packet
)
1582 clientinfo
*ci
= (clientinfo
*)getinfo(n
);
1583 int hasmap
= (gamemode
==1 && clients
.length()>1) || (smapname
[0] && (minremain
>0 || (ci
&& ci
->state
.state
==CS_SPECTATOR
) || nonspectators(n
)));
1584 putint(p
, SV_INITS2C
);
1586 putint(p
, PROTOCOL_VERSION
);
1590 putint(p
, SV_MAPCHANGE
);
1591 sendstring(smapname
, p
);
1592 putint(p
, gamemode
);
1593 putint(p
, notgotitems
? 1 : 0);
1594 if(!ci
|| gamemode
>1 || (gamemode
==0 && hasnonlocalclients()))
1596 putint(p
, SV_TIMEUP
);
1597 putint(p
, minremain
);
1601 putint(p
, SV_ITEMLIST
);
1602 loopv(sents
) if(sents
[i
].spawned
)
1605 putint(p
, sents
[i
].type
);
1606 if(p
.remaining() < 256)
1608 enet_packet_resize(packet
, packet
->dataLength
+ MAXTRANS
);
1609 p
.buf
= packet
->data
;
1615 if(ci
&& !ci
->local
&& m_teammode
)
1617 const char *worst
= chooseworstteam();
1620 putint(p
, SV_SETTEAM
);
1621 putint(p
, ci
->clientnum
);
1622 sendstring(worst
, p
);
1625 if(ci
&& (m_demo
|| m_mp(gamemode
)) && ci
->state
.state
!=CS_SPECTATOR
)
1627 if(smode
&& !smode
->canspawn(ci
, true))
1629 ci
->state
.state
= CS_DEAD
;
1630 putint(p
, SV_FORCEDEATH
);
1632 sendf(-1, 1, "ri2x", SV_FORCEDEATH
, n
, n
);
1636 gamestate
&gs
= ci
->state
;
1638 putint(p
, SV_SPAWNSTATE
);
1640 gs
.lastspawn
= gamemillis
;
1643 if(ci
&& ci
->state
.state
==CS_SPECTATOR
)
1645 putint(p
, SV_SPECTATOR
);
1648 sendf(-1, 1, "ri3x", SV_SPECTATOR
, n
, 1, n
);
1650 if(clients
.length()>1)
1652 putint(p
, SV_RESUME
);
1655 clientinfo
*oi
= clients
[i
];
1656 if(oi
->clientnum
==n
) continue;
1657 if(p
.remaining() < 256)
1659 enet_packet_resize(packet
, packet
->dataLength
+ MAXTRANS
);
1660 p
.buf
= packet
->data
;
1662 putint(p
, oi
->clientnum
);
1663 putint(p
, oi
->state
.state
);
1664 putint(p
, oi
->state
.frags
);
1665 putint(p
, oi
->state
.quadmillis
);
1666 sendstate(oi
->state
, p
);
1672 enet_packet_resize(packet
, packet
->dataLength
+ MAXTRANS
);
1673 p
.buf
= packet
->data
;
1674 smode
->initclient(ci
, p
, true);
1679 void checkintermission()
1683 minremain
= gamemillis
>=gamelimit
? 0 : (gamelimit
- gamemillis
+ 60000 - 1)/60000;
1684 sendf(-1, 1, "ri2", SV_TIMEUP
, minremain
);
1685 if(!minremain
&& smode
) smode
->intermission();
1687 if(!interm
&& minremain
<=0) interm
= gamemillis
+10000;
1690 void startintermission() { gamelimit
= min(gamelimit
, gamemillis
); checkintermission(); }
1692 void clearevent(clientinfo
*ci
)
1695 while(n
<ci
->events
.length() && ci
->events
[n
].type
==GE_HIT
) n
++;
1696 ci
->events
.remove(0, n
);
1699 void spawnstate(clientinfo
*ci
)
1701 gamestate
&gs
= ci
->state
;
1702 gs
.spawnstate(gamemode
);
1706 void sendspawn(clientinfo
*ci
)
1708 gamestate
&gs
= ci
->state
;
1710 sendf(ci
->clientnum
, 1, "ri7v", SV_SPAWNSTATE
, gs
.lifesequence
,
1711 gs
.health
, gs
.maxhealth
,
1712 gs
.armour
, gs
.armourtype
,
1713 gs
.gunselect
, GUN_PISTOL
-GUN_SG
+1, &gs
.ammo
[GUN_SG
]);
1714 gs
.lastspawn
= gamemillis
;
1717 void dodamage(clientinfo
*target
, clientinfo
*actor
, int damage
, int gun
, const vec
&hitpush
= vec(0, 0, 0))
1719 gamestate
&ts
= target
->state
;
1720 ts
.dodamage(damage
);
1721 actor
->state
.damage
+= damage
;
1722 sendf(-1, 1, "ri6", SV_DAMAGE
, target
->clientnum
, actor
->clientnum
, damage
, ts
.armour
, ts
.health
);
1723 if(target
!=actor
&& !hitpush
.iszero())
1726 if(!v
.iszero()) v
.normalize();
1727 sendf(target
->clientnum
, 1, "ri6", SV_HITPUSH
, gun
, damage
,
1728 int(v
.x
*DNF
), int(v
.y
*DNF
), int(v
.z
*DNF
));
1732 target
->state
.deaths
++;
1733 if(actor
!=target
&& isteam(actor
->team
, target
->team
)) actor
->state
.teamkills
++;
1734 int fragvalue
= smode
? smode
->fragvalue(target
, actor
) : (target
==actor
|| isteam(target
->team
, actor
->team
) ? -1 : 1);
1735 actor
->state
.frags
+= fragvalue
;
1738 int friends
= 0, enemies
= 0; // note: friends also includes the fragger
1739 if(m_teammode
) loopv(clients
) if(strcmp(clients
[i
]->team
, actor
->team
)) enemies
++; else friends
++;
1740 else { friends
= 1; enemies
= clients
.length()-1; }
1741 actor
->state
.effectiveness
+= fragvalue
*friends
/float(max(enemies
, 1));
1743 sendf(-1, 1, "ri4", SV_DIED
, target
->clientnum
, actor
->clientnum
, actor
->state
.frags
);
1744 target
->position
.setsizenodelete(0);
1745 if(smode
) smode
->died(target
, actor
);
1747 ts
.lastdeath
= gamemillis
;
1748 // don't issue respawn yet until DEATHMILLIS has elapsed
1753 void processevent(clientinfo
*ci
, suicideevent
&e
)
1755 gamestate
&gs
= ci
->state
;
1756 if(gs
.state
!=CS_ALIVE
) return;
1757 ci
->state
.frags
+= smode
? smode
->fragvalue(ci
, ci
) : -1;
1759 sendf(-1, 1, "ri4", SV_DIED
, ci
->clientnum
, ci
->clientnum
, gs
.frags
);
1760 ci
->position
.setsizenodelete(0);
1761 if(smode
) smode
->died(ci
, NULL
);
1766 void processevent(clientinfo
*ci
, explodeevent
&e
)
1768 gamestate
&gs
= ci
->state
;
1772 if(!gs
.rockets
.remove(e
.id
)) return;
1776 if(!gs
.grenades
.remove(e
.id
)) return;
1782 for(int i
= 1; i
<ci
->events
.length() && ci
->events
[i
].type
==GE_HIT
; i
++)
1784 hitevent
&h
= ci
->events
[i
].hit
;
1785 clientinfo
*target
= (clientinfo
*)getinfo(h
.target
);
1786 if(!target
|| target
->state
.state
!=CS_ALIVE
|| h
.lifesequence
!=target
->state
.lifesequence
|| h
.dist
<0 || h
.dist
>RL_DAMRAD
) continue;
1789 for(j
= 1; j
<i
; j
++) if(ci
->events
[j
].hit
.target
==h
.target
) break;
1792 int damage
= guns
[e
.gun
].damage
;
1793 if(gs
.quadmillis
) damage
*= 4;
1794 damage
= int(damage
*(1-h
.dist
/RL_DISTSCALE
/RL_DAMRAD
));
1795 if(e
.gun
==GUN_RL
&& target
==ci
) damage
/= RL_SELFDAMDIV
;
1796 dodamage(target
, ci
, damage
, e
.gun
, h
.dir
);
1800 void processevent(clientinfo
*ci
, shotevent
&e
)
1802 gamestate
&gs
= ci
->state
;
1803 int wait
= e
.millis
- gs
.lastshot
;
1804 if(!gs
.isalive(gamemillis
) ||
1806 e
.gun
<GUN_FIST
|| e
.gun
>GUN_PISTOL
||
1809 if(e
.gun
!=GUN_FIST
) gs
.ammo
[e
.gun
]--;
1810 gs
.lastshot
= e
.millis
;
1811 gs
.gunwait
= guns
[e
.gun
].attackdelay
;
1812 sendf(-1, 1, "ri9x", SV_SHOTFX
, ci
->clientnum
, e
.gun
,
1813 int(e
.from
[0]*DMF
), int(e
.from
[1]*DMF
), int(e
.from
[2]*DMF
),
1814 int(e
.to
[0]*DMF
), int(e
.to
[1]*DMF
), int(e
.to
[2]*DMF
),
1816 gs
.shotdamage
+= guns
[e
.gun
].damage
*(gs
.quadmillis
? 4 : 1)*(e
.gun
==GUN_SG
? SGRAYS
: 1);
1819 case GUN_RL
: gs
.rockets
.add(e
.id
); break;
1820 case GUN_GL
: gs
.grenades
.add(e
.id
); break;
1823 int totalrays
= 0, maxrays
= e
.gun
==GUN_SG
? SGRAYS
: 1;
1824 for(int i
= 1; i
<ci
->events
.length() && ci
->events
[i
].type
==GE_HIT
; i
++)
1826 hitevent
&h
= ci
->events
[i
].hit
;
1827 clientinfo
*target
= (clientinfo
*)getinfo(h
.target
);
1828 if(!target
|| target
->state
.state
!=CS_ALIVE
|| h
.lifesequence
!=target
->state
.lifesequence
|| h
.rays
<1) continue;
1830 totalrays
+= h
.rays
;
1831 if(totalrays
>maxrays
) continue;
1832 int damage
= h
.rays
*guns
[e
.gun
].damage
;
1833 if(gs
.quadmillis
) damage
*= 4;
1834 dodamage(target
, ci
, damage
, e
.gun
, h
.dir
);
1841 void processevent(clientinfo
*ci
, pickupevent
&e
)
1843 gamestate
&gs
= ci
->state
;
1844 if(m_mp(gamemode
) && !gs
.isalive(gamemillis
)) return;
1845 pickup(e
.ent
, ci
->clientnum
);
1848 void processevents()
1852 clientinfo
*ci
= clients
[i
];
1853 if(curtime
>0 && ci
->state
.quadmillis
) ci
->state
.quadmillis
= max(ci
->state
.quadmillis
-curtime
, 0);
1854 while(ci
->events
.length())
1856 gameevent
&e
= ci
->events
[0];
1857 if(e
.type
<GE_SUICIDE
)
1859 if(e
.shot
.millis
>gamemillis
) break;
1860 if(e
.shot
.millis
<ci
->lastevent
) { clearevent(ci
); continue; }
1861 ci
->lastevent
= e
.shot
.millis
;
1865 case GE_SHOT
: processevent(ci
, e
.shot
); break;
1866 case GE_EXPLODE
: processevent(ci
, e
.explode
); break;
1868 case GE_SUICIDE
: processevent(ci
, e
.suicide
); break;
1869 case GE_PICKUP
: processevent(ci
, e
.pickup
); break;
1876 void serverupdate(int _lastmillis
, int _totalmillis
)
1878 curtime
= _lastmillis
- lastmillis
;
1879 gamemillis
+= curtime
;
1880 lastmillis
= _lastmillis
;
1881 totalmillis
= _totalmillis
;
1883 if(m_demo
) readdemo();
1884 else if(minremain
>0)
1889 loopv(sents
) if(sents
[i
].spawntime
) // spawn entities when timer reached
1891 int oldtime
= sents
[i
].spawntime
;
1892 sents
[i
].spawntime
-= curtime
;
1893 if(sents
[i
].spawntime
<=0)
1895 sents
[i
].spawntime
= 0;
1896 sents
[i
].spawned
= true;
1897 sendf(-1, 1, "ri2", SV_ITEMSPAWN
, i
);
1899 else if(sents
[i
].spawntime
<=10000 && oldtime
>10000 && (sents
[i
].type
==I_QUAD
|| sents
[i
].type
==I_BOOST
))
1901 sendf(-1, 1, "ri2", SV_ANNOUNCE
, sents
[i
].type
);
1905 if(smode
) smode
->update();
1908 while(bannedips
.length() && bannedips
[0].time
-totalmillis
>4*60*60000) bannedips
.remove(0);
1912 clientinfo
*m
= currentmaster
>=0 ? (clientinfo
*)getinfo(currentmaster
) : NULL
;
1913 sendf(-1, 1, "ri3", SV_CURRENTMASTER
, currentmaster
, m
? m
->privilege
: 0);
1914 masterupdate
= false;
1917 if((gamemode
>1 || (gamemode
==0 && hasnonlocalclients())) && gamemillis
-curtime
>0 && gamemillis
/60000!=(gamemillis
-curtime
)/60000) checkintermission();
1918 if(interm
&& gamemillis
>interm
)
1920 if(demorecord
) enddemorecord();
1926 bool serveroption(char *arg
)
1928 if(arg
[0]=='-') switch(arg
[1])
1930 case 'n': s_strcpy(serverdesc
, &arg
[2]); return true;
1931 case 'p': s_strcpy(masterpass
, &arg
[2]); return true;
1932 case 'o': if(atoi(&arg
[2])) mastermask
= (1<<MM_OPEN
) | (1<<MM_VETO
); return true;
1943 const char *privname(int type
)
1947 case PRIV_ADMIN
: return "admin";
1948 case PRIV_MASTER
: return "master";
1949 default: return "unknown";
1953 void setmaster(clientinfo
*ci
, bool val
, const char *pass
= "", bool approved
= false)
1955 if(approved
&& (!val
|| !ci
->wantsmaster
)) return;
1956 const char *name
= "";
1961 if(!masterpass
[0] || !pass
[0]==(ci
->privilege
!=PRIV_ADMIN
)) return;
1963 else if(ci
->state
.state
==CS_SPECTATOR
&& (!masterpass
[0] || strcmp(masterpass
, pass
))) return;
1964 loopv(clients
) if(ci
!=clients
[i
] && clients
[i
]->privilege
)
1966 if(masterpass
[0] && !strcmp(masterpass
, pass
)) clients
[i
]->privilege
= PRIV_NONE
;
1969 if(masterpass
[0] && !strcmp(masterpass
, pass
)) ci
->privilege
= PRIV_ADMIN
;
1970 else if(!approved
&& !(mastermask
&MM_AUTOAPPROVE
) && !ci
->privilege
)
1972 ci
->wantsmaster
= true;
1973 s_sprintfd(msg
)("%s wants master. Type \"/approve %d\" to approve.", colorname(ci
), ci
->clientnum
);
1977 else ci
->privilege
= PRIV_MASTER
;
1978 name
= privname(ci
->privilege
);
1982 if(!ci
->privilege
) return;
1983 name
= privname(ci
->privilege
);
1986 mastermode
= MM_OPEN
;
1987 allowedips
.setsize(0);
1988 s_sprintfd(msg
)("%s %s %s", colorname(ci
), val
? (approved
? "approved for" : "claimed") : "relinquished", name
);
1990 currentmaster
= val
? ci
->clientnum
: -1;
1991 masterupdate
= true;
1992 loopv(clients
) clients
[i
]->wantsmaster
= false;
1995 void localconnect(int n
)
1997 clientinfo
*ci
= (clientinfo
*)getinfo(n
);
2003 void localdisconnect(int n
)
2005 clientinfo
*ci
= (clientinfo
*)getinfo(n
);
2006 if(smode
) smode
->leavegame(ci
, true);
2007 clients
.removeobj(ci
);
2010 int clientconnect(int n
, uint ip
)
2012 clientinfo
*ci
= (clientinfo
*)getinfo(n
);
2015 loopv(bannedips
) if(bannedips
[i
].ip
==ip
) return DISC_IPBAN
;
2016 if(mastermode
>=MM_PRIVATE
)
2018 if(allowedips
.find(ip
)<0) return DISC_PRIVATE
;
2020 if(mastermode
>=MM_LOCKED
) ci
->state
.state
= CS_SPECTATOR
;
2021 if(currentmaster
>=0) masterupdate
= true;
2022 ci
->state
.lasttimeplayed
= lastmillis
;
2026 void clientdisconnect(int n
)
2028 clientinfo
*ci
= (clientinfo
*)getinfo(n
);
2029 if(ci
->privilege
) setmaster(ci
, false);
2030 if(smode
) smode
->leavegame(ci
, true);
2031 ci
->state
.timeplayed
+= lastmillis
- ci
->state
.lasttimeplayed
;
2033 sendf(-1, 1, "ri2", SV_CDIS
, n
);
2034 clients
.removeobj(ci
);
2035 if(clients
.empty()) bannedips
.setsize(0); // bans clear when server empties
2038 const char *servername() { return "sauerbratenserver"; }
2039 int serverinfoport() { return SAUERBRATEN_SERVINFO_PORT
; }
2040 int serverport() { return SAUERBRATEN_SERVER_PORT
; }
2041 const char *getdefaultmaster() { return "sauerbraten.org/masterserver/"; }
2043 #include "extinfo.h"
2045 void serverinforeply(ucharbuf
&req
, ucharbuf
&p
)
2049 extserverinforeply(req
, p
);
2053 putint(p
, clients
.length());
2054 putint(p
, 5); // number of attrs following
2055 putint(p
, PROTOCOL_VERSION
); // a // generic attributes, passed back below
2056 putint(p
, gamemode
); // b
2057 putint(p
, minremain
); // c
2058 putint(p
, maxclients
);
2059 putint(p
, mastermode
);
2060 sendstring(smapname
, p
);
2061 sendstring(serverdesc
, p
);
2062 sendserverinforeply(p
);
2065 bool servercompatible(char *name
, char *sdec
, char *map
, int ping
, const vector
<int> &attr
, int np
)
2067 return attr
.length() && attr
[0]==PROTOCOL_VERSION
;
2070 void receivefile(int sender
, uchar
*data
, int len
)
2072 if(gamemode
!= 1 || len
> 1024*1024) return;
2073 clientinfo
*ci
= (clientinfo
*)getinfo(sender
);
2074 if(ci
->state
.state
==CS_SPECTATOR
&& !ci
->privilege
) return;
2075 if(mapdata
) { fclose(mapdata
); mapdata
= NULL
; }
2077 mapdata
= tmpfile();
2078 if(!mapdata
) { sendf(sender
, 1, "ris", SV_SERVMSG
, "failed to open temporary file for map"); return; }
2079 fwrite(data
, 1, len
, mapdata
);
2080 s_sprintfd(msg
)("[%s uploaded map to server, \"/getmap\" to receive it]", colorname(ci
));
2084 bool duplicatename(clientinfo
*ci
, char *name
)
2086 if(!name
) name
= ci
->name
;
2087 loopv(clients
) if(clients
[i
]!=ci
&& !strcmp(name
, clients
[i
]->name
)) return true;
2091 char *colorname(clientinfo
*ci
, char *name
= NULL
)
2093 if(!name
) name
= ci
->name
;
2094 if(name
[0] && !duplicatename(ci
, name
)) return name
;
2095 static string cname
;
2096 s_sprintf(cname
)("%s \fs\f5(%d)\fr", name
, ci
->clientnum
);