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
102 int lastdeath
, lastspawn
, lifesequence
;
104 projectilestate
<8> rockets
, grenades
;
106 int lasttimeplayed
, timeplayed
;
109 gamestate() : state(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
= CS_DEAD
;
139 o
= vec(-1e10f
, -1e10f
, -1e10f
);
150 int maxhealth
, frags
;
154 void save(gamestate
&gs
)
156 maxhealth
= gs
.maxhealth
;
158 timeplayed
= gs
.timeplayed
;
159 effectiveness
= gs
.effectiveness
;
162 void restore(gamestate
&gs
)
164 gs
.maxhealth
= maxhealth
;
166 gs
.timeplayed
= timeplayed
;
167 gs
.effectiveness
= effectiveness
;
174 string name
, team
, mapvote
;
177 bool spectator
, local
, timesync
, wantsmaster
;
178 int gameoffset
, lastevent
;
180 vector
<gameevent
> events
;
181 vector
<uchar
> position
, messages
;
182 vector
<clientinfo
*> targets
;
184 clientinfo() { reset(); }
186 gameevent
&addevent()
188 static gameevent dummy
;
189 if(events
.length()>100) return dummy
;
197 events
.setsizenodelete(0);
198 targets
.setsizenodelete(0);
205 name
[0] = team
[0] = 0;
206 privilege
= PRIV_NONE
;
207 spectator
= local
= wantsmaster
= false;
208 position
.setsizenodelete(0);
209 messages
.setsizenodelete(0);
217 vector
<uchar
> positions
, messages
;
227 #define MM_AUTOAPPROVE 0x1000
228 #define MM_DEFAULT (MM_MODE | MM_AUTOAPPROVE)
230 enum { MM_OPEN
= 0, MM_VETO
, MM_LOCKED
, MM_PRIVATE
};
232 bool notgotitems
, notgotbases
; // true when map has changed and waiting for clients to send item
234 int gamemillis
, gamelimit
;
238 int lastmillis
, totalmillis
, curtime
;
239 int interm
, minremain
;
241 enet_uint32 lastsend
;
242 int mastermode
, mastermask
;
248 vector
<ban
> bannedips
;
249 vector
<clientinfo
*> clients
;
250 vector
<worldstate
*> worldstates
;
251 bool reliablemessages
;
261 vector
<demofile
> demos
;
265 gzFile demorecord
, demoplayback
;
272 servmode(fpsserver
&sv
) : sv(sv
) {}
273 virtual ~servmode() {}
275 virtual void entergame(clientinfo
*ci
) {}
276 virtual void leavegame(clientinfo
*ci
, bool disconnecting
= false) {}
278 virtual void moved(clientinfo
*ci
, const vec
&oldpos
, const vec
&newpos
) {}
279 virtual bool canspawn(clientinfo
*ci
, bool connecting
= false) { return true; }
280 virtual void spawned(clientinfo
*ci
) {}
281 virtual int fragvalue(clientinfo
*victim
, clientinfo
*actor
)
283 int gamemode
= sv
.gamemode
;
284 if(victim
==actor
|| isteam(victim
->team
, actor
->team
)) return -1;
287 virtual void died(clientinfo
*victim
, clientinfo
*actor
) {}
288 virtual void changeteam(clientinfo
*ci
, const char *oldteam
, const char *newteam
) {}
289 virtual void initclient(clientinfo
*ci
, ucharbuf
&p
, bool connecting
) {}
290 virtual void update() {}
291 virtual void reset(bool empty
) {}
292 virtual void intermission() {}
295 struct arenaservmode
: servmode
299 arenaservmode(fpsserver
&sv
) : servmode(sv
), arenaround(0) {}
301 bool canspawn(clientinfo
*ci
, bool connecting
= false)
303 if(connecting
&& sv
.nonspectators(ci
->clientnum
)<=1) return true;
307 void reset(bool empty
)
314 if(sv
.interm
|| sv
.gamemillis
<arenaround
|| !sv
.nonspectators()) return;
319 loopv(sv
.clients
) if(sv
.clients
[i
]->state
.state
==CS_DEAD
|| sv
.clients
[i
]->state
.state
==CS_ALIVE
)
321 sv
.clients
[i
]->state
.respawn();
322 sv
.sendspawn(sv
.clients
[i
]);
327 int gamemode
= sv
.gamemode
;
328 clientinfo
*alive
= NULL
;
332 clientinfo
*ci
= sv
.clients
[i
];
333 if(ci
->state
.state
==CS_ALIVE
|| (ci
->state
.state
==CS_DEAD
&& ci
->state
.lastspawn
>=0))
335 if(!alive
) alive
= ci
;
336 else if(!m_teammode
|| strcmp(alive
->team
, ci
->team
)) return;
338 else if(ci
->state
.state
==CS_DEAD
) dead
= true;
341 sendf(-1, 1, "ri2", SV_ARENAWIN
, !alive
? -1 : alive
->clientnum
);
342 arenaround
= sv
.gamemillis
+5000;
346 #define CAPTURESERV 1
350 #define ASSASSINSERV 1
351 #include "assassin.h"
354 arenaservmode arenamode
;
355 captureservmode capturemode
;
356 assassinservmode assassinmode
;
359 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), smode(NULL
)
361 serverdesc
[0] = '\0';
362 masterpass
[0] = '\0';
365 void *newinfo() { return new clientinfo
; }
366 void deleteinfo(void *ci
) { delete (clientinfo
*)ci
; }
368 vector
<server_entity
> sents
;
369 vector
<savedscore
> scores
;
371 static const char *modestr(int n
)
373 static const char *modenames
[] =
375 "slowmo SP", "slowmo DMSP", "demo", "SP", "DMSP", "ffa/default", "coopedit", "ffa/duel", "teamplay",
376 "instagib", "instagib team", "efficiency", "efficiency team",
377 "insta arena", "insta clan arena", "tactics arena", "tactics clan arena",
378 "capture", "insta capture", "regen capture", "assassin", "insta assassin"
380 return (n
>=-5 && size_t(n
+5)<sizeof(modenames
)/sizeof(modenames
[0])) ? modenames
[n
+5] : "unknown";
383 void sendservmsg(const char *s
) { sendf(-1, 1, "ris", SV_SERVMSG
, s
); }
391 int spawntime(int type
)
393 if(m_classicsp
) return INT_MAX
;
394 int np
= nonspectators();
395 np
= np
<3 ? 4 : (np
>4 ? 2 : 3); // spawn times are dependent on number of players
404 case I_CARTRIDGES
: sec
= np
*4; break;
405 case I_HEALTH
: sec
= np
*5; break;
407 case I_YELLOWARMOUR
: sec
= 20; break;
409 case I_QUAD
: sec
= 40+rnd(40); break;
414 bool pickup(int i
, int sender
) // server side item pickup, acknowledge first client that gets it
416 if(minremain
<=0 || !sents
.inrange(i
) || !sents
[i
].spawned
) return false;
417 clientinfo
*ci
= (clientinfo
*)getinfo(sender
);
418 if(!ci
|| (!ci
->local
&& !ci
->state
.canpickup(sents
[i
].type
))) return false;
419 sents
[i
].spawned
= false;
420 sents
[i
].spawntime
= spawntime(sents
[i
].type
);
421 sendf(-1, 1, "ri3", SV_ITEMACC
, i
, sender
);
422 ci
->state
.pickup(sents
[i
].type
);
426 void vote(char *map
, int reqmode
, int sender
)
428 clientinfo
*ci
= (clientinfo
*)getinfo(sender
);
429 if(!ci
|| ci
->state
.state
==CS_SPECTATOR
&& !ci
->privilege
) return;
430 s_strcpy(ci
->mapvote
, map
);
431 ci
->modevote
= reqmode
;
432 if(!ci
->mapvote
[0]) return;
433 if(ci
->local
|| mapreload
|| (ci
->privilege
&& mastermode
>=MM_VETO
))
435 if(demorecord
) enddemorecord();
436 if(!ci
->local
&& !mapreload
)
438 s_sprintfd(msg
)("%s forced %s on map %s", privname(ci
->privilege
), modestr(reqmode
), map
);
441 sendf(-1, 1, "risi", SV_MAPCHANGE
, ci
->mapvote
, ci
->modevote
);
442 changemap(ci
->mapvote
, ci
->modevote
);
446 s_sprintfd(msg
)("%s suggests %s on map %s (select map to vote)", colorname(ci
), modestr(reqmode
), map
);
452 clientinfo
*choosebestclient(float &bestrank
)
454 clientinfo
*best
= NULL
;
458 clientinfo
*ci
= clients
[i
];
459 if(ci
->state
.timeplayed
<0) continue;
460 float rank
= ci
->state
.effectiveness
/max(ci
->state
.timeplayed
, 1);
461 if(!best
|| rank
> bestrank
) { best
= ci
; bestrank
= rank
; }
468 static const char *teamnames
[2] = {"good", "evil"};
469 vector
<clientinfo
*> team
[2];
470 float teamrank
[2] = {0, 0};
471 for(int round
= 0, remaining
= clients
.length(); remaining
>=0; round
++)
473 int first
= round
&1, second
= (round
+1)&1, selected
= 0;
474 while(teamrank
[first
] <= teamrank
[second
])
477 clientinfo
*ci
= choosebestclient(rank
);
479 if(m_capture
) rank
= 1;
480 else if(selected
&& rank
<=0) break;
481 ci
->state
.timeplayed
= -1;
483 teamrank
[first
] += rank
;
488 remaining
-= selected
;
490 loopi(sizeof(team
)/sizeof(team
[0]))
494 clientinfo
*ci
= team
[i
][j
];
495 if(!strcmp(ci
->team
, teamnames
[i
])) continue;
496 s_strncpy(ci
->team
, teamnames
[i
], MAXTEAMLEN
+1);
497 sendf(-1, 1, "riis", SV_SETTEAM
, ci
->clientnum
, teamnames
[i
]);
508 teamscore(const char *name
) : name(name
), rank(0), clients(0) {}
511 const char *chooseworstteam(const char *suggest
)
513 teamscore teamscores
[2] = { teamscore("good"), teamscore("evil") };
514 const int numteams
= sizeof(teamscores
)/sizeof(teamscores
[0]);
517 clientinfo
*ci
= clients
[i
];
518 if(!ci
->team
[0]) continue;
519 ci
->state
.timeplayed
+= lastmillis
- ci
->state
.lasttimeplayed
;
520 ci
->state
.lasttimeplayed
= lastmillis
;
522 loopj(numteams
) if(!strcmp(ci
->team
, teamscores
[j
].name
))
524 teamscore
&ts
= teamscores
[j
];
525 ts
.rank
+= ci
->state
.effectiveness
/max(ci
->state
.timeplayed
, 1);
530 teamscore
*worst
= &teamscores
[numteams
-1];
533 teamscore
&ts
= teamscores
[i
];
536 if(ts
.clients
< worst
->clients
|| (ts
.clients
== worst
->clients
&& ts
.rank
< worst
->rank
)) worst
= &ts
;
538 else if(ts
.rank
< worst
->rank
|| (ts
.rank
== worst
->rank
&& ts
.clients
< worst
->clients
)) worst
= &ts
;
543 void writedemo(int chan
, void *data
, int len
)
545 if(!demorecord
) return;
546 int stamp
[3] = { gamemillis
, chan
, len
};
547 endianswap(stamp
, sizeof(int), 3);
548 gzwrite(demorecord
, stamp
, sizeof(stamp
));
549 gzwrite(demorecord
, data
, len
);
552 void recordpacket(int chan
, void *data
, int len
)
554 writedemo(chan
, data
, len
);
559 if(!demorecord
) return;
565 demotmp
= fopen("demorecord", "rb");
569 fseek(demotmp
, 0, SEEK_END
);
570 int len
= ftell(demotmp
);
572 if(demos
.length()>=MAXDEMOS
)
574 delete[] demos
[0].data
;
577 demofile
&d
= demos
.add();
578 time_t t
= time(NULL
);
579 char *timestr
= ctime(&t
), *trim
= timestr
+ strlen(timestr
);
580 while(trim
>timestr
&& isspace(*--trim
)) *trim
= '\0';
581 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");
582 s_sprintfd(msg
)("demo \"%s\" recorded", d
.info
);
584 d
.data
= new uchar
[len
];
586 fread(d
.data
, 1, len
, demotmp
);
591 void setupdemorecord()
593 if(haslocalclients() || !m_mp(gamemode
) || gamemode
==1) return;
596 gzFile f
= gzopen("demorecord", "wb9");
601 setvbuf(demotmp
, NULL
, _IONBF
, 0);
603 gzFile f
= gzdopen(_dup(_fileno(demotmp
)), "wb9");
612 sendservmsg("recording demo");
617 memcpy(hdr
.magic
, DEMO_MAGIC
, sizeof(hdr
.magic
));
618 hdr
.version
= DEMO_VERSION
;
619 hdr
.protocol
= PROTOCOL_VERSION
;
620 endianswap(&hdr
.version
, sizeof(int), 1);
621 endianswap(&hdr
.protocol
, sizeof(int), 1);
622 gzwrite(demorecord
, &hdr
, sizeof(demoheader
));
625 ucharbuf
p(buf
, sizeof(buf
));
626 welcomepacket(p
, -1);
627 writedemo(1, buf
, p
.len
);
631 clientinfo
*ci
= clients
[i
];
633 ucharbuf
q(&buf
[sizeof(header
)], sizeof(buf
)-sizeof(header
));
634 putint(q
, SV_INITC2S
);
635 sendstring(ci
->name
, q
);
636 sendstring(ci
->team
, q
);
638 ucharbuf
h(header
, sizeof(header
));
639 putint(h
, SV_CLIENT
);
640 putint(h
, ci
->clientnum
);
643 memcpy(&buf
[sizeof(header
)-h
.len
], header
, h
.len
);
645 writedemo(1, &buf
[sizeof(header
)-h
.len
], h
.len
+q
.len
);
649 void listdemos(int cn
)
651 ENetPacket
*packet
= enet_packet_create(NULL
, MAXTRANS
, ENET_PACKET_FLAG_RELIABLE
);
653 ucharbuf
p(packet
->data
, packet
->dataLength
);
654 putint(p
, SV_SENDDEMOLIST
);
655 putint(p
, demos
.length());
656 loopv(demos
) sendstring(demos
[i
].info
, p
);
657 enet_packet_resize(packet
, p
.length());
658 sendpacket(cn
, 1, packet
);
659 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
662 void cleardemos(int n
)
666 loopv(demos
) delete[] demos
[i
].data
;
668 sendservmsg("cleared all demos");
670 else if(demos
.inrange(n
-1))
672 delete[] demos
[n
-1].data
;
674 s_sprintfd(msg
)("cleared demo %d", n
);
679 void senddemo(int cn
, int num
)
681 if(!num
) num
= demos
.length();
682 if(!demos
.inrange(num
-1)) return;
683 demofile
&d
= demos
[num
-1];
684 sendf(cn
, 2, "rim", SV_SENDDEMO
, d
.len
, d
.data
);
687 void setupdemoplayback()
692 s_sprintfd(file
)("%s.dmo", smapname
);
693 demoplayback
= opengzfile(file
, "rb9");
694 if(!demoplayback
) s_sprintf(msg
)("could not read demo \"%s\"", file
);
695 else if(gzread(demoplayback
, &hdr
, sizeof(demoheader
))!=sizeof(demoheader
) || memcmp(hdr
.magic
, DEMO_MAGIC
, sizeof(hdr
.magic
)))
696 s_sprintf(msg
)("\"%s\" is not a demo file", file
);
699 endianswap(&hdr
.version
, sizeof(int), 1);
700 endianswap(&hdr
.protocol
, sizeof(int), 1);
701 if(hdr
.version
!=DEMO_VERSION
) s_sprintf(msg
)("demo \"%s\" requires an %s version of Sauerbraten", file
, hdr
.version
<DEMO_VERSION
? "older" : "newer");
702 else if(hdr
.protocol
!=PROTOCOL_VERSION
) s_sprintf(msg
)("demo \"%s\" requires an %s version of Sauerbraten", file
, hdr
.protocol
<PROTOCOL_VERSION
? "older" : "newer");
706 if(demoplayback
) { gzclose(demoplayback
); demoplayback
= NULL
; }
711 s_sprintf(msg
)("playing demo \"%s\"", file
);
714 sendf(-1, 1, "rii", SV_DEMOPLAYBACK
, 1);
716 if(gzread(demoplayback
, &nextplayback
, sizeof(nextplayback
))!=sizeof(nextplayback
))
721 endianswap(&nextplayback
, sizeof(nextplayback
), 1);
724 void enddemoplayback()
726 if(!demoplayback
) return;
727 gzclose(demoplayback
);
730 sendf(-1, 1, "rii", SV_DEMOPLAYBACK
, 0);
732 sendservmsg("demo playback finished");
736 ENetPacket
*packet
= enet_packet_create(NULL
, MAXTRANS
, ENET_PACKET_FLAG_RELIABLE
);
737 ucharbuf
p(packet
->data
, packet
->dataLength
);
738 welcomepacket(p
, clients
[i
]->clientnum
);
739 enet_packet_resize(packet
, p
.length());
740 sendpacket(clients
[i
]->clientnum
, 1, packet
);
741 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
747 if(!demoplayback
) return;
748 while(gamemillis
>=nextplayback
)
751 if(gzread(demoplayback
, &chan
, sizeof(chan
))!=sizeof(chan
) ||
752 gzread(demoplayback
, &len
, sizeof(len
))!=sizeof(len
))
757 endianswap(&chan
, sizeof(chan
), 1);
758 endianswap(&len
, sizeof(len
), 1);
759 ENetPacket
*packet
= enet_packet_create(NULL
, len
, 0);
760 if(!packet
|| gzread(demoplayback
, packet
->data
, len
)!=len
)
762 if(packet
) enet_packet_destroy(packet
);
766 sendpacket(-1, chan
, packet
);
767 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
768 if(gzread(demoplayback
, &nextplayback
, sizeof(nextplayback
))!=sizeof(nextplayback
))
773 endianswap(&nextplayback
, sizeof(nextplayback
), 1);
777 void changemap(const char *s
, int mode
)
779 if(m_demo
) enddemoplayback();
780 else enddemorecord();
785 minremain
= m_teammode
? 15 : 10;
786 gamelimit
= minremain
*60000;
788 s_strcpy(smapname
, s
);
794 clientinfo
*ci
= clients
[i
];
795 ci
->state
.timeplayed
+= lastmillis
- ci
->state
.lasttimeplayed
;
797 if(m_teammode
) autoteam();
799 if(m_arena
) smode
= &arenamode
;
800 else if(m_capture
) smode
= &capturemode
;
801 else if(m_assassin
) smode
= &assassinmode
;
803 if(smode
) smode
->reset(false);
805 if(gamemode
>1 || (gamemode
==0 && hasnonlocalclients())) sendf(-1, 1, "ri2", SV_TIMEUP
, minremain
);
808 clientinfo
*ci
= clients
[i
];
810 ci
->state
.lasttimeplayed
= lastmillis
;
811 if(m_mp(gamemode
) && ci
->state
.state
!=CS_SPECTATOR
) sendspawn(ci
);
814 if(m_demo
) setupdemoplayback();
815 else if(demonextmatch
)
817 demonextmatch
= false;
822 savedscore
&findscore(clientinfo
*ci
, bool insert
)
824 uint ip
= getclientip(ci
->clientnum
);
825 if(!ip
) return *(savedscore
*)0;
826 if(!insert
) loopv(clients
)
828 clientinfo
*oi
= clients
[i
];
829 if(oi
->clientnum
!= ci
->clientnum
&& getclientip(oi
->clientnum
) == ip
&& !strcmp(oi
->name
, ci
->name
))
831 oi
->state
.timeplayed
+= lastmillis
- oi
->state
.lasttimeplayed
;
832 oi
->state
.lasttimeplayed
= lastmillis
;
833 static savedscore curscore
;
834 curscore
.save(oi
->state
);
840 savedscore
&sc
= scores
[i
];
841 if(sc
.ip
== ip
&& !strcmp(sc
.name
, ci
->name
)) return sc
;
843 if(!insert
) return *(savedscore
*)0;
844 savedscore
&sc
= scores
.add();
846 s_strcpy(sc
.name
, ci
->name
);
850 void savescore(clientinfo
*ci
)
852 savedscore
&sc
= findscore(ci
, true);
853 if(&sc
) sc
.save(ci
->state
);
861 votecount(char *s
, int n
) : map(s
), mode(n
), count(0) {}
864 void checkvotes(bool force
= false)
866 vector
<votecount
> votes
;
870 clientinfo
*oi
= clients
[i
];
871 if(oi
->state
.state
==CS_SPECTATOR
&& !oi
->privilege
) continue;
873 if(!oi
->mapvote
[0]) continue;
874 votecount
*vc
= NULL
;
875 loopvj(votes
) if(!strcmp(oi
->mapvote
, votes
[j
].map
) && oi
->modevote
==votes
[j
].mode
)
880 if(!vc
) vc
= &votes
.add(votecount(oi
->mapvote
, oi
->modevote
));
883 votecount
*best
= NULL
;
884 loopv(votes
) if(!best
|| votes
[i
].count
> best
->count
|| (votes
[i
].count
== best
->count
&& rnd(2))) best
= &votes
[i
];
885 if(force
|| (best
&& best
->count
> maxvotes
/2))
887 if(demorecord
) enddemorecord();
888 if(best
&& (best
->count
> (force
? 1 : maxvotes
/2)))
890 sendservmsg(force
? "vote passed by default" : "vote passed by majority");
891 sendf(-1, 1, "risi", SV_MAPCHANGE
, best
->map
, best
->mode
);
892 changemap(best
->map
, best
->mode
);
897 if(clients
.length()) sendf(-1, 1, "ri", SV_MAPRELOAD
);
902 int nonspectators(int exclude
= -1)
905 loopv(clients
) if(i
!=exclude
&& clients
[i
]->state
.state
!=CS_SPECTATOR
) n
++;
909 int checktype(int type
, clientinfo
*ci
)
911 if(ci
&& ci
->local
) return type
;
912 // spectators can only connect and talk
913 static int spectypes
[] = { SV_INITC2S
, SV_POS
, SV_TEXT
, SV_PING
, SV_CLIENTPING
, SV_GETMAP
, SV_SETMASTER
};
914 if(ci
&& ci
->state
.state
==CS_SPECTATOR
&& !ci
->privilege
)
916 loopi(sizeof(spectypes
)/sizeof(int)) if(type
== spectypes
[i
]) return type
;
919 // only allow edit messages in coop-edit mode
920 if(type
>=SV_EDITENT
&& type
<=SV_GETMAP
&& gamemode
!=1) return -1;
921 // server only messages
922 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_CLIENT
};
923 if(ci
) loopi(sizeof(servtypes
)/sizeof(int)) if(type
== servtypes
[i
]) return -1;
927 static void freecallback(ENetPacket
*packet
)
929 extern igameserver
*sv
;
930 ((fpsserver
*)sv
)->cleanworldstate(packet
);
933 void cleanworldstate(ENetPacket
*packet
)
937 worldstate
*ws
= worldstates
[i
];
938 if(packet
->data
>= ws
->positions
.getbuf() && packet
->data
<= &ws
->positions
.last()) ws
->uses
--;
939 else if(packet
->data
>= ws
->messages
.getbuf() && packet
->data
<= &ws
->messages
.last()) ws
->uses
--;
944 worldstates
.remove(i
);
950 bool buildworldstate()
952 static struct { int posoff
, msgoff
, msglen
; } pkt
[MAXCLIENTS
];
953 worldstate
&ws
= *new worldstate
;
956 clientinfo
&ci
= *clients
[i
];
957 if(ci
.position
.empty()) pkt
[i
].posoff
= -1;
960 pkt
[i
].posoff
= ws
.positions
.length();
961 loopvj(ci
.position
) ws
.positions
.add(ci
.position
[j
]);
963 if(ci
.messages
.empty()) pkt
[i
].msgoff
= -1;
966 pkt
[i
].msgoff
= ws
.messages
.length();
967 ucharbuf p
= ws
.messages
.reserve(16);
968 putint(p
, SV_CLIENT
);
969 putint(p
, ci
.clientnum
);
970 putuint(p
, ci
.messages
.length());
971 ws
.messages
.addbuf(p
);
972 loopvj(ci
.messages
) ws
.messages
.add(ci
.messages
[j
]);
973 pkt
[i
].msglen
= ws
.messages
.length() - pkt
[i
].msgoff
;
976 int psize
= ws
.positions
.length(), msize
= ws
.messages
.length();
977 if(psize
) recordpacket(0, ws
.positions
.getbuf(), psize
);
978 if(msize
) recordpacket(1, ws
.messages
.getbuf(), msize
);
979 loopi(psize
) { uchar c
= ws
.positions
[i
]; ws
.positions
.add(c
); }
980 loopi(msize
) { uchar c
= ws
.messages
[i
]; ws
.messages
.add(c
); }
984 clientinfo
&ci
= *clients
[i
];
986 if(psize
&& (pkt
[i
].posoff
<0 || psize
-ci
.position
.length()>0))
988 packet
= enet_packet_create(&ws
.positions
[pkt
[i
].posoff
<0 ? 0 : pkt
[i
].posoff
+ci
.position
.length()],
989 pkt
[i
].posoff
<0 ? psize
: psize
-ci
.position
.length(),
990 ENET_PACKET_FLAG_NO_ALLOCATE
);
991 sendpacket(ci
.clientnum
, 0, packet
);
992 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
993 else { ++ws
.uses
; packet
->freeCallback
= freecallback
; }
995 ci
.position
.setsizenodelete(0);
997 if(msize
&& (pkt
[i
].msgoff
<0 || msize
-pkt
[i
].msglen
>0))
999 packet
= enet_packet_create(&ws
.messages
[pkt
[i
].msgoff
<0 ? 0 : pkt
[i
].msgoff
+pkt
[i
].msglen
],
1000 pkt
[i
].msgoff
<0 ? msize
: msize
-pkt
[i
].msglen
,
1001 (reliablemessages
? ENET_PACKET_FLAG_RELIABLE
: 0) | ENET_PACKET_FLAG_NO_ALLOCATE
);
1002 sendpacket(ci
.clientnum
, 1, packet
);
1003 if(!packet
->referenceCount
) enet_packet_destroy(packet
);
1004 else { ++ws
.uses
; packet
->freeCallback
= freecallback
; }
1006 ci
.messages
.setsizenodelete(0);
1008 reliablemessages
= false;
1016 worldstates
.add(&ws
);
1023 if(clients
.empty()) return false;
1024 enet_uint32 curtime
= enet_time_get()-lastsend
;
1025 if(curtime
<33) return false;
1026 bool flush
= buildworldstate();
1027 lastsend
+= curtime
- (curtime
%33);
1031 void parsepacket(int sender
, int chan
, bool reliable
, ucharbuf
&p
) // has to parse exactly each byte of the packet
1033 if(sender
<0) return;
1036 receivefile(sender
, p
.buf
, p
.maxlen
);
1039 if(reliable
) reliablemessages
= true;
1040 char text
[MAXTRANS
];
1042 clientinfo
*ci
= sender
>=0 ? (clientinfo
*)getinfo(sender
) : NULL
;
1043 #define QUEUE_MSG { if(!ci->local) while(curmsg<p.length()) ci->messages.add(p.buf[curmsg++]); }
1044 #define QUEUE_INT(n) { if(!ci->local) { curmsg = p.length(); ucharbuf buf = ci->messages.reserve(5); putint(buf, n); ci->messages.addbuf(buf); } }
1045 #define QUEUE_UINT(n) { if(!ci->local) { curmsg = p.length(); ucharbuf buf = ci->messages.reserve(4); putuint(buf, n); ci->messages.addbuf(buf); } }
1046 #define QUEUE_STR(text) { if(!ci->local) { curmsg = p.length(); ucharbuf buf = ci->messages.reserve(2*strlen(text)+1); sendstring(text, buf); ci->messages.addbuf(buf); } }
1048 while((curmsg
= p
.length()) < p
.maxlen
) switch(type
= checktype(getint(p
), ci
))
1053 if(cn
<0 || cn
>=getnumclients() || cn
!=sender
)
1055 disconnect_client(sender
, DISC_CN
);
1058 vec
oldpos(ci
->state
.o
);
1059 loopi(3) ci
->state
.o
[i
] = getuint(p
)/DMF
;
1062 int physstate
= getuint(p
);
1063 if(physstate
&0x20) loopi(2) getint(p
);
1064 if(physstate
&0x10) getint(p
);
1065 if(!ci
->local
&& (ci
->state
.state
==CS_ALIVE
|| ci
->state
.state
==CS_EDITING
))
1067 ci
->position
.setsizenodelete(0);
1068 while(curmsg
<p
.length()) ci
->position
.add(p
.buf
[curmsg
++]);
1070 uint f
= getuint(p
);
1071 if(!ci
->local
&& (ci
->state
.state
==CS_ALIVE
|| ci
->state
.state
==CS_EDITING
))
1074 if(ci
->state
.armourtype
==A_GREEN
&& ci
->state
.armour
>0) f
|= 1<<4;
1075 if(ci
->state
.armourtype
==A_YELLOW
&& ci
->state
.armour
>0) f
|= 1<<5;
1076 if(ci
->state
.quadmillis
) f
|= 1<<6;
1077 if(ci
->state
.maxhealth
>100) f
|= ((ci
->state
.maxhealth
-100)/itemstats
[I_BOOST
-I_SHELLS
].add
)<<7;
1078 curmsg
= p
.length();
1079 ucharbuf buf
= ci
->position
.reserve(4);
1081 ci
->position
.addbuf(buf
);
1083 if(smode
&& ci
->state
.state
==CS_ALIVE
) smode
->moved(ci
, oldpos
, ci
->state
.o
);
1089 int val
= getint(p
);
1090 if(ci
->state
.state
!=(val
? CS_ALIVE
: CS_EDITING
) || (!ci
->local
&& gamemode
!=1)) break;
1093 if(val
) smode
->leavegame(ci
);
1094 else smode
->entergame(ci
);
1096 ci
->state
.state
= val
? CS_EDITING
: CS_ALIVE
;
1099 ci
->events
.setsizenodelete(0);
1100 ci
->state
.rockets
.reset();
1101 ci
->state
.grenades
.reset();
1108 if(ci
->state
.state
!=CS_DEAD
|| ci
->state
.lastspawn
>=0 || (smode
&& !smode
->canspawn(ci
))) break;
1109 if(ci
->state
.lastdeath
) ci
->state
.respawn();
1115 int gunselect
= getint(p
);
1116 ci
->state
.gunselect
= gunselect
;
1123 int ls
= getint(p
), gunselect
= getint(p
);
1124 if((ci
->state
.state
!=CS_ALIVE
&& ci
->state
.state
!=CS_DEAD
) || ls
!=ci
->state
.lifesequence
|| ci
->state
.lastspawn
<0) break;
1125 ci
->state
.lastspawn
= -1;
1126 ci
->state
.state
= CS_ALIVE
;
1127 ci
->state
.gunselect
= gunselect
;
1128 if(smode
) smode
->spawned(ci
);
1135 gameevent
&suicide
= ci
->addevent();
1136 suicide
.type
= GE_SUICIDE
;
1142 gameevent
&shot
= ci
->addevent();
1143 shot
.type
= GE_SHOT
;
1144 #define seteventmillis(event) \
1146 event.id = getint(p); \
1147 if(!ci->timesync || (ci->events.length()==1 && ci->state.waitexpired(gamemillis))) \
1149 ci->timesync = true; \
1150 ci->gameoffset = gamemillis - event.id; \
1151 event.millis = gamemillis; \
1153 else event.millis = ci->gameoffset + event.id; \
1155 seteventmillis(shot
.shot
);
1156 shot
.shot
.gun
= getint(p
);
1157 loopk(3) shot
.shot
.from
[k
] = getint(p
)/DMF
;
1158 loopk(3) shot
.shot
.to
[k
] = getint(p
)/DMF
;
1159 int hits
= getint(p
);
1162 gameevent
&hit
= ci
->addevent();
1164 hit
.hit
.target
= getint(p
);
1165 hit
.hit
.lifesequence
= getint(p
);
1166 hit
.hit
.rays
= getint(p
);
1167 loopk(3) hit
.hit
.dir
[k
] = getint(p
)/DNF
;
1174 gameevent
&exp
= ci
->addevent();
1175 exp
.type
= GE_EXPLODE
;
1176 seteventmillis(exp
.explode
);
1177 exp
.explode
.gun
= getint(p
);
1178 exp
.explode
.id
= getint(p
);
1179 int hits
= getint(p
);
1182 gameevent
&hit
= ci
->addevent();
1184 hit
.hit
.target
= getint(p
);
1185 hit
.hit
.lifesequence
= getint(p
);
1186 hit
.hit
.dist
= getint(p
)/DMF
;
1187 loopk(3) hit
.hit
.dir
[k
] = getint(p
)/DNF
;
1195 gameevent
&pickup
= ci
->addevent();
1196 pickup
.type
= GE_PICKUP
;
1197 pickup
.pickup
.ent
= n
;
1204 filtertext(text
, text
);
1211 bool connected
= !ci
->name
[0];
1213 filtertext(text
, text
, false, MAXNAMELEN
);
1214 if(!text
[0]) s_strcpy(text
, "unnamed");
1216 s_strncpy(ci
->name
, text
, MAXNAMELEN
+1);
1217 if(!ci
->local
&& connected
)
1219 savedscore
&sc
= findscore(ci
, false);
1222 sc
.restore(ci
->state
);
1223 sendf(-1, 1, "ri8", SV_RESUME
, sender
, ci
->state
.state
, ci
->state
.lifesequence
, ci
->state
.gunselect
, sc
.maxhealth
, sc
.frags
, -1);
1227 filtertext(text
, text
, false, MAXTEAMLEN
);
1228 if(!ci
->local
&& connected
&& m_teammode
)
1230 const char *worst
= chooseworstteam(text
);
1233 s_strcpy(text
, worst
);
1234 sendf(sender
, 1, "riis", SV_SETTEAM
, sender
, worst
);
1237 else QUEUE_STR(text
);
1239 else QUEUE_STR(text
);
1240 if(smode
&& ci
->state
.state
==CS_ALIVE
&& strcmp(ci
->team
, text
)) smode
->changeteam(ci
, ci
->team
, text
);
1241 s_strncpy(ci
->team
, text
, MAXTEAMLEN
+1);
1250 filtertext(text
, text
);
1251 int reqmode
= getint(p
);
1252 if(type
!=SV_MAPVOTE
&& !mapreload
) break;
1253 if(!ci
->local
&& !m_mp(reqmode
)) reqmode
= 0;
1254 vote(text
, reqmode
, sender
);
1261 while((n
= getint(p
))!=-1)
1263 server_entity se
= { getint(p
), false, 0 };
1266 while(sents
.length()<=n
) sents
.add(se
);
1267 if(gamemode
>=0 && (sents
[n
].type
==I_QUAD
|| sents
[n
].type
==I_BOOST
)) sents
[n
].spawntime
= spawntime(sents
[n
].type
);
1268 else sents
[n
].spawned
= true;
1271 notgotitems
= false;
1290 if(smode
==&capturemode
) capturemode
.parsebases(p
);
1294 if(smode
==&capturemode
) capturemode
.replenishammo(ci
);
1298 sendf(sender
, 1, "i2", SV_PONG
, getint(p
));
1304 if(ci
->privilege
&& mm
>=MM_OPEN
&& mm
<=MM_PRIVATE
)
1306 if(ci
->privilege
>=PRIV_ADMIN
|| (mastermask
&(1<<mm
)))
1309 s_sprintfd(s
)("mastermode is now %d", mastermode
);
1314 s_sprintfd(s
)("mastermode %d is disabled on this server", mm
);
1315 sendf(sender
, 1, "ris", SV_SERVMSG
, s
);
1325 bannedips
.setsize(0);
1326 sendservmsg("cleared all bans");
1333 int victim
= getint(p
);
1334 if(ci
->privilege
&& victim
>=0 && victim
<getnumclients() && ci
->clientnum
!=victim
&& getinfo(victim
))
1336 ban
&b
= bannedips
.add();
1337 b
.time
= totalmillis
;
1338 b
.ip
= getclientip(victim
);
1339 disconnect_client(victim
, DISC_KICK
);
1346 int spectator
= getint(p
), val
= getint(p
);
1347 if(!ci
->privilege
&& spectator
!=sender
) break;
1348 clientinfo
*spinfo
= (clientinfo
*)getinfo(spectator
);
1351 sendf(-1, 1, "ri3", SV_SPECTATOR
, spectator
, val
);
1353 if(spinfo
->state
.state
!=CS_SPECTATOR
&& val
)
1355 if(smode
) smode
->leavegame(spinfo
);
1356 spinfo
->state
.state
= CS_SPECTATOR
;
1358 else if(spinfo
->state
.state
==CS_SPECTATOR
&& !val
)
1360 spinfo
->state
.state
= CS_DEAD
;
1361 spinfo
->state
.respawn();
1362 if(!smode
|| smode
->canspawn(spinfo
)) sendspawn(spinfo
);
1369 int who
= getint(p
);
1371 filtertext(text
, text
, false, MAXTEAMLEN
);
1372 if(!ci
->privilege
|| who
<0 || who
>=getnumclients()) break;
1373 clientinfo
*wi
= (clientinfo
*)getinfo(who
);
1375 if(smode
&& wi
->state
.state
==CS_ALIVE
&& strcmp(wi
->team
, text
)) smode
->changeteam(wi
, wi
->team
, text
);
1376 s_strncpy(wi
->team
, text
, MAXTEAMLEN
+1);
1377 sendf(sender
, 1, "riis", SV_SETTEAM
, who
, text
);
1378 QUEUE_INT(SV_SETTEAM
);
1384 case SV_FORCEINTERMISSION
:
1385 if(m_sp
) startintermission();
1390 int val
= getint(p
);
1391 if(ci
->privilege
<PRIV_ADMIN
) break;
1392 demonextmatch
= val
!=0;
1393 s_sprintfd(msg
)("demo recording is %s for next match", demonextmatch
? "enabled" : "disabled");
1400 if(!ci
->local
&& ci
->privilege
<PRIV_ADMIN
) break;
1401 if(m_demo
) enddemoplayback();
1402 else enddemorecord();
1408 int demo
= getint(p
);
1409 if(ci
->privilege
<PRIV_ADMIN
) break;
1419 senddemo(sender
, getint(p
));
1425 sendf(sender
, 1, "ris", SV_SERVMSG
, "server sending map...");
1426 sendfile(sender
, 2, mapdata
, "ri", SV_SENDMAP
);
1428 else sendf(sender
, 1, "ris", SV_SERVMSG
, "no map to send");
1433 int size
= getint(p
);
1438 notgotitems
= false;
1439 if(smode
) smode
->reset(true);
1447 int val
= getint(p
);
1449 setmaster(ci
, val
!=0, text
);
1450 // don't broadcast the master password
1454 case SV_APPROVEMASTER
:
1457 if(mastermask
&MM_AUTOAPPROVE
) break;
1458 clientinfo
*candidate
= (clientinfo
*)getinfo(mn
);
1459 if(!candidate
|| !candidate
->wantsmaster
|| mn
==sender
) break;// || getclientip(mn)==getclientip(sender)) break;
1460 setmaster(candidate
, true, "", true);
1466 int size
= msgsizelookup(type
);
1467 if(size
==-1) { disconnect_client(sender
, DISC_TAGT
); return; }
1468 if(size
>0) loopi(size
-1) getint(p
);
1475 int welcomepacket(ucharbuf
&p
, int n
)
1477 clientinfo
*ci
= (clientinfo
*)getinfo(n
);
1478 int hasmap
= (gamemode
==1 && clients
.length()>1) || (smapname
[0] && (minremain
>0 || (ci
&& ci
->state
.state
==CS_SPECTATOR
) || nonspectators(n
)));
1479 putint(p
, SV_INITS2C
);
1481 putint(p
, PROTOCOL_VERSION
);
1485 putint(p
, SV_MAPCHANGE
);
1486 sendstring(smapname
, p
);
1487 putint(p
, gamemode
);
1488 if(!ci
|| gamemode
>1 || (gamemode
==0 && hasnonlocalclients()))
1490 putint(p
, SV_TIMEUP
);
1491 putint(p
, minremain
);
1493 putint(p
, SV_ITEMLIST
);
1494 loopv(sents
) if(sents
[i
].spawned
)
1497 putint(p
, sents
[i
].type
);
1501 if(ci
&& (m_demo
|| m_mp(gamemode
)) && ci
->state
.state
!=CS_SPECTATOR
)
1503 if(smode
&& !smode
->canspawn(ci
, true))
1505 ci
->state
.state
= CS_DEAD
;
1506 putint(p
, SV_FORCEDEATH
);
1508 sendf(-1, 1, "ri2x", SV_FORCEDEATH
, n
, n
);
1512 gamestate
&gs
= ci
->state
;
1514 putint(p
, SV_SPAWNSTATE
);
1515 putint(p
, gs
.lifesequence
);
1516 putint(p
, gs
.health
);
1517 putint(p
, gs
.maxhealth
);
1518 putint(p
, gs
.armour
);
1519 putint(p
, gs
.armourtype
);
1520 putint(p
, gs
.gunselect
);
1521 loopi(GUN_PISTOL
-GUN_SG
+1) putint(p
, gs
.ammo
[GUN_SG
+i
]);
1522 gs
.lastspawn
= gamemillis
;
1525 if(ci
&& ci
->state
.state
==CS_SPECTATOR
)
1527 putint(p
, SV_SPECTATOR
);
1530 sendf(-1, 1, "ri3x", SV_SPECTATOR
, n
, 1, n
);
1532 if(clients
.length()>1)
1534 putint(p
, SV_RESUME
);
1537 clientinfo
*oi
= clients
[i
];
1538 if(oi
->clientnum
==n
) continue;
1539 putint(p
, oi
->clientnum
);
1540 putint(p
, oi
->state
.state
);
1541 putint(p
, oi
->state
.lifesequence
);
1542 putint(p
, oi
->state
.gunselect
);
1543 putint(p
, oi
->state
.maxhealth
);
1544 putint(p
, oi
->state
.frags
);
1548 if(smode
) smode
->initclient(ci
, p
, true);
1552 void checkintermission()
1556 minremain
= gamemillis
>=gamelimit
? 0 : (gamelimit
- gamemillis
+ 60000 - 1)/60000;
1557 sendf(-1, 1, "ri2", SV_TIMEUP
, minremain
);
1558 if(!minremain
&& smode
) smode
->intermission();
1560 if(!interm
&& minremain
<=0) interm
= gamemillis
+10000;
1563 void startintermission() { gamelimit
= min(gamelimit
, gamemillis
); checkintermission(); }
1565 void clearevent(clientinfo
*ci
)
1568 while(n
<ci
->events
.length() && ci
->events
[n
].type
==GE_HIT
) n
++;
1569 ci
->events
.remove(0, n
);
1572 void spawnstate(clientinfo
*ci
)
1574 gamestate
&gs
= ci
->state
;
1575 gs
.spawnstate(gamemode
);
1579 void sendspawn(clientinfo
*ci
)
1581 gamestate
&gs
= ci
->state
;
1583 sendf(ci
->clientnum
, 1, "ri7v", SV_SPAWNSTATE
, gs
.lifesequence
,
1584 gs
.health
, gs
.maxhealth
,
1585 gs
.armour
, gs
.armourtype
,
1586 gs
.gunselect
, GUN_PISTOL
-GUN_SG
+1, &gs
.ammo
[GUN_SG
]);
1587 gs
.lastspawn
= gamemillis
;
1590 void dodamage(clientinfo
*target
, clientinfo
*actor
, int damage
, int gun
, const vec
&hitpush
= vec(0, 0, 0))
1592 gamestate
&ts
= target
->state
;
1593 ts
.dodamage(damage
);
1594 sendf(-1, 1, "ri6", SV_DAMAGE
, target
->clientnum
, actor
->clientnum
, damage
, ts
.armour
, ts
.health
);
1595 if(target
!=actor
&& !hitpush
.iszero())
1598 if(!v
.iszero()) v
.normalize();
1599 sendf(target
->clientnum
, 1, "ri6", SV_HITPUSH
, gun
, damage
,
1600 int(v
.x
*DNF
), int(v
.y
*DNF
), int(v
.z
*DNF
));
1604 int fragvalue
= smode
? smode
->fragvalue(target
, actor
) : (target
==actor
|| isteam(target
->team
, actor
->team
) ? -1 : 1);
1605 actor
->state
.frags
+= fragvalue
;
1608 int friends
= 0, enemies
= 0; // note: friends also includes the fragger
1609 if(m_teammode
) loopv(clients
) if(strcmp(clients
[i
]->team
, actor
->team
)) enemies
++; else friends
++;
1610 else { friends
= 1; enemies
= clients
.length()-1; }
1611 actor
->state
.effectiveness
+= fragvalue
*friends
/float(max(enemies
, 1));
1613 sendf(-1, 1, "ri4", SV_DIED
, target
->clientnum
, actor
->clientnum
, actor
->state
.frags
);
1614 target
->position
.setsizenodelete(0);
1615 if(smode
) smode
->died(target
, actor
);
1617 ts
.lastdeath
= gamemillis
;
1618 // don't issue respawn yet until DEATHMILLIS has elapsed
1623 void processevent(clientinfo
*ci
, suicideevent
&e
)
1625 gamestate
&gs
= ci
->state
;
1626 if(gs
.state
!=CS_ALIVE
) return;
1627 ci
->state
.frags
+= smode
? smode
->fragvalue(ci
, ci
) : -1;
1628 sendf(-1, 1, "ri4", SV_DIED
, ci
->clientnum
, ci
->clientnum
, gs
.frags
);
1629 ci
->position
.setsizenodelete(0);
1630 if(smode
) smode
->died(ci
, NULL
);
1635 void processevent(clientinfo
*ci
, explodeevent
&e
)
1637 gamestate
&gs
= ci
->state
;
1641 if(!gs
.rockets
.remove(e
.id
)) return;
1645 if(!gs
.grenades
.remove(e
.id
)) return;
1651 for(int i
= 1; i
<ci
->events
.length() && ci
->events
[i
].type
==GE_HIT
; i
++)
1653 hitevent
&h
= ci
->events
[i
].hit
;
1654 clientinfo
*target
= (clientinfo
*)getinfo(h
.target
);
1655 if(!target
|| target
->state
.state
!=CS_ALIVE
|| h
.lifesequence
!=target
->state
.lifesequence
|| h
.dist
<0 || h
.dist
>RL_DAMRAD
) continue;
1658 for(j
= 1; j
<i
; j
++) if(ci
->events
[j
].hit
.target
==h
.target
) break;
1661 int damage
= guns
[e
.gun
].damage
;
1662 if(gs
.quadmillis
) damage
*= 4;
1663 damage
= int(damage
*(1-h
.dist
/RL_DISTSCALE
/RL_DAMRAD
));
1664 if(e
.gun
==GUN_RL
&& target
==ci
) damage
/= RL_SELFDAMDIV
;
1665 dodamage(target
, ci
, damage
, e
.gun
, h
.dir
);
1669 void processevent(clientinfo
*ci
, shotevent
&e
)
1671 gamestate
&gs
= ci
->state
;
1672 int wait
= e
.millis
- gs
.lastshot
;
1673 if(!gs
.isalive(gamemillis
) ||
1675 e
.gun
<GUN_FIST
|| e
.gun
>GUN_PISTOL
||
1678 if(e
.gun
!=GUN_FIST
) gs
.ammo
[e
.gun
]--;
1679 gs
.lastshot
= e
.millis
;
1680 gs
.gunwait
= guns
[e
.gun
].attackdelay
;
1681 sendf(-1, 1, "ri9x", SV_SHOTFX
, ci
->clientnum
, e
.gun
,
1682 int(e
.from
[0]*DMF
), int(e
.from
[1]*DMF
), int(e
.from
[2]*DMF
),
1683 int(e
.to
[0]*DMF
), int(e
.to
[1]*DMF
), int(e
.to
[2]*DMF
),
1687 case GUN_RL
: gs
.rockets
.add(e
.id
); break;
1688 case GUN_GL
: gs
.grenades
.add(e
.id
); break;
1691 int totalrays
= 0, maxrays
= e
.gun
==GUN_SG
? SGRAYS
: 1;
1692 for(int i
= 1; i
<ci
->events
.length() && ci
->events
[i
].type
==GE_HIT
; i
++)
1694 hitevent
&h
= ci
->events
[i
].hit
;
1695 clientinfo
*target
= (clientinfo
*)getinfo(h
.target
);
1696 if(!target
|| target
->state
.state
!=CS_ALIVE
|| h
.lifesequence
!=target
->state
.lifesequence
|| h
.rays
<1) continue;
1698 totalrays
+= h
.rays
;
1699 if(totalrays
>maxrays
) continue;
1700 int damage
= h
.rays
*guns
[e
.gun
].damage
;
1701 if(gs
.quadmillis
) damage
*= 4;
1702 dodamage(target
, ci
, damage
, e
.gun
, h
.dir
);
1709 void processevent(clientinfo
*ci
, pickupevent
&e
)
1711 gamestate
&gs
= ci
->state
;
1712 if(m_mp(gamemode
) && !gs
.isalive(gamemillis
)) return;
1713 pickup(e
.ent
, ci
->clientnum
);
1716 void processevents()
1720 clientinfo
*ci
= clients
[i
];
1721 if(curtime
>0 && ci
->state
.quadmillis
) ci
->state
.quadmillis
= max(ci
->state
.quadmillis
-curtime
, 0);
1722 while(ci
->events
.length())
1724 gameevent
&e
= ci
->events
[0];
1725 if(e
.type
<GE_SUICIDE
)
1727 if(e
.shot
.millis
>gamemillis
) break;
1728 if(e
.shot
.millis
<ci
->lastevent
) { clearevent(ci
); continue; }
1729 ci
->lastevent
= e
.shot
.millis
;
1733 case GE_SHOT
: processevent(ci
, e
.shot
); break;
1734 case GE_EXPLODE
: processevent(ci
, e
.explode
); break;
1736 case GE_SUICIDE
: processevent(ci
, e
.suicide
); break;
1737 case GE_PICKUP
: processevent(ci
, e
.pickup
); break;
1744 void serverupdate(int _lastmillis
, int _totalmillis
)
1746 curtime
= _lastmillis
- lastmillis
;
1747 gamemillis
+= curtime
;
1748 lastmillis
= _lastmillis
;
1749 totalmillis
= _totalmillis
;
1751 if(m_demo
) readdemo();
1752 else if(minremain
>0)
1755 if(curtime
) loopv(sents
) if(sents
[i
].spawntime
) // spawn entities when timer reached
1757 int oldtime
= sents
[i
].spawntime
;
1758 sents
[i
].spawntime
-= curtime
;
1759 if(sents
[i
].spawntime
<=0)
1761 sents
[i
].spawntime
= 0;
1762 sents
[i
].spawned
= true;
1763 sendf(-1, 1, "ri2", SV_ITEMSPAWN
, i
);
1765 else if(sents
[i
].spawntime
<=10000 && oldtime
>10000 && (sents
[i
].type
==I_QUAD
|| sents
[i
].type
==I_BOOST
))
1767 sendf(-1, 1, "ri2", SV_ANNOUNCE
, sents
[i
].type
);
1770 if(smode
) smode
->update();
1773 while(bannedips
.length() && bannedips
[0].time
-totalmillis
>4*60*60000) bannedips
.remove(0);
1777 clientinfo
*m
= currentmaster
>=0 ? (clientinfo
*)getinfo(currentmaster
) : NULL
;
1778 sendf(-1, 1, "ri3", SV_CURRENTMASTER
, currentmaster
, m
? m
->privilege
: 0);
1779 masterupdate
= false;
1782 if((gamemode
>1 || (gamemode
==0 && hasnonlocalclients())) && gamemillis
-curtime
>0 && gamemillis
/60000!=(gamemillis
-curtime
)/60000) checkintermission();
1783 if(interm
&& gamemillis
>interm
)
1785 if(demorecord
) enddemorecord();
1791 bool serveroption(char *arg
)
1793 if(arg
[0]=='-') switch(arg
[1])
1795 case 'n': s_strcpy(serverdesc
, &arg
[2]); return true;
1796 case 'p': s_strcpy(masterpass
, &arg
[2]); return true;
1797 case 'o': if(atoi(&arg
[2])) mastermask
= (1<<MM_OPEN
) | (1<<MM_VETO
); return true;
1808 const char *privname(int type
)
1812 case PRIV_ADMIN
: return "admin";
1813 case PRIV_MASTER
: return "master";
1814 default: return "unknown";
1818 void setmaster(clientinfo
*ci
, bool val
, const char *pass
= "", bool approved
= false)
1820 if(approved
&& (!val
|| !ci
->wantsmaster
)) return;
1821 const char *name
= "";
1826 if(!masterpass
[0] || !pass
[0]==(ci
->privilege
!=PRIV_ADMIN
)) return;
1828 else if(ci
->state
.state
==CS_SPECTATOR
&& (!masterpass
[0] || strcmp(masterpass
, pass
))) return;
1829 loopv(clients
) if(ci
!=clients
[i
] && clients
[i
]->privilege
)
1831 if(masterpass
[0] && !strcmp(masterpass
, pass
)) clients
[i
]->privilege
= PRIV_NONE
;
1834 if(masterpass
[0] && !strcmp(masterpass
, pass
)) ci
->privilege
= PRIV_ADMIN
;
1835 else if(!approved
&& !(mastermask
&MM_AUTOAPPROVE
) && !ci
->privilege
)
1837 ci
->wantsmaster
= true;
1838 s_sprintfd(msg
)("%s wants master. Type \"/approve %d\" to approve.", colorname(ci
), ci
->clientnum
);
1842 else ci
->privilege
= PRIV_MASTER
;
1843 name
= privname(ci
->privilege
);
1847 if(!ci
->privilege
) return;
1848 name
= privname(ci
->privilege
);
1851 mastermode
= MM_OPEN
;
1852 s_sprintfd(msg
)("%s %s %s", colorname(ci
), val
? (approved
? "approved for" : "claimed") : "relinquished", name
);
1854 currentmaster
= val
? ci
->clientnum
: -1;
1855 masterupdate
= true;
1856 loopv(clients
) clients
[i
]->wantsmaster
= false;
1859 void localconnect(int n
)
1861 clientinfo
*ci
= (clientinfo
*)getinfo(n
);
1867 void localdisconnect(int n
)
1869 clientinfo
*ci
= (clientinfo
*)getinfo(n
);
1870 if(smode
) smode
->leavegame(ci
, true);
1871 clients
.removeobj(ci
);
1874 int clientconnect(int n
, uint ip
)
1876 clientinfo
*ci
= (clientinfo
*)getinfo(n
);
1879 loopv(bannedips
) if(bannedips
[i
].ip
==ip
) return DISC_IPBAN
;
1880 if(mastermode
>=MM_PRIVATE
) return DISC_PRIVATE
;
1881 if(mastermode
>=MM_LOCKED
) ci
->state
.state
= CS_SPECTATOR
;
1882 if(currentmaster
>=0) masterupdate
= true;
1883 ci
->state
.lasttimeplayed
= lastmillis
;
1887 void clientdisconnect(int n
)
1889 clientinfo
*ci
= (clientinfo
*)getinfo(n
);
1890 if(ci
->privilege
) setmaster(ci
, false);
1891 if(smode
) smode
->leavegame(ci
, true);
1892 ci
->state
.timeplayed
+= lastmillis
- ci
->state
.lasttimeplayed
;
1894 sendf(-1, 1, "ri2", SV_CDIS
, n
);
1895 clients
.removeobj(ci
);
1896 if(clients
.empty()) bannedips
.setsize(0); // bans clear when server empties
1900 const char *servername() { return "sauerbratenserver"; }
1901 int serverinfoport() { return SAUERBRATEN_SERVINFO_PORT
; }
1902 int serverport() { return SAUERBRATEN_SERVER_PORT
; }
1903 const char *getdefaultmaster() { return "sauerbraten.org/masterserver/"; }
1905 void serverinforeply(ucharbuf
&p
)
1907 putint(p
, clients
.length());
1908 putint(p
, 5); // number of attrs following
1909 putint(p
, PROTOCOL_VERSION
); // a // generic attributes, passed back below
1910 putint(p
, gamemode
); // b
1911 putint(p
, minremain
); // c
1912 putint(p
, maxclients
);
1913 putint(p
, mastermode
);
1914 sendstring(smapname
, p
);
1915 sendstring(serverdesc
, p
);
1918 bool servercompatible(char *name
, char *sdec
, char *map
, int ping
, const vector
<int> &attr
, int np
)
1920 return attr
.length() && attr
[0]==PROTOCOL_VERSION
;
1923 void serverinfostr(char *buf
, const char *name
, const char *sdesc
, const char *map
, int ping
, const vector
<int> &attr
, int np
)
1925 if(attr
[0]!=PROTOCOL_VERSION
) s_sprintf(buf
)("[%s protocol] %s", attr
[0]<PROTOCOL_VERSION
? "older" : "newer", name
);
1929 if(attr
.length()>=4) s_sprintf(numcl
)("%d/%d", np
, attr
[3]);
1930 else s_sprintf(numcl
)("%d", np
);
1931 if(attr
.length()>=5) switch(attr
[4])
1933 case MM_LOCKED
: s_strcat(numcl
, " L"); break;
1934 case MM_PRIVATE
: s_strcat(numcl
, " P"); break;
1937 s_sprintf(buf
)("%d\t%s\t%s, %s: %s %s", ping
, numcl
, map
[0] ? map
: "[unknown]", modestr(attr
[1]), name
, sdesc
);
1941 void receivefile(int sender
, uchar
*data
, int len
)
1943 if(gamemode
!= 1 || len
> 1024*1024) return;
1944 clientinfo
*ci
= (clientinfo
*)getinfo(sender
);
1945 if(ci
->state
.state
==CS_SPECTATOR
&& !ci
->privilege
) return;
1946 if(mapdata
) { fclose(mapdata
); mapdata
= NULL
; }
1948 mapdata
= tmpfile();
1949 if(!mapdata
) return;
1950 fwrite(data
, 1, len
, mapdata
);
1951 s_sprintfd(msg
)("[%s uploaded map to server, \"/getmap\" to receive it]", colorname(ci
));
1955 bool duplicatename(clientinfo
*ci
, char *name
)
1957 if(!name
) name
= ci
->name
;
1958 loopv(clients
) if(clients
[i
]!=ci
&& !strcmp(name
, clients
[i
]->name
)) return true;
1962 char *colorname(clientinfo
*ci
, char *name
= NULL
)
1964 if(!name
) name
= ci
->name
;
1965 if(name
[0] && !duplicatename(ci
, name
)) return name
;
1966 static string cname
;
1967 s_sprintf(cname
)("%s \fs\f5(%d)\fr", name
, ci
->clientnum
);