• R/O
  • SSH
  • HTTPS

marathon: コミット


コミットメタ情報

リビジョン528 (tree)
日時2012-06-02 14:12:51
作者ookawa_mi

ログメッセージ

改行コード修正

変更サマリ

差分

--- marathon/trunk/Source_Files/Network/network_dialogs.h (revision 527)
+++ marathon/trunk/Source_Files/Network/network_dialogs.h (revision 528)
@@ -1,531 +1,531 @@
1-/*
2- * network_dialogs.h
3- *
4-
5- Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
6- and the "Aleph One" developers.
7-
8- This program is free software; you can redistribute it and/or modify
9- it under the terms of the GNU General Public License as published by
10- the Free Software Foundation; either version 3 of the License, or
11- (at your option) any later version.
12-
13- This program is distributed in the hope that it will be useful,
14- but WITHOUT ANY WARRANTY; without even the implied warranty of
15- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16- GNU General Public License for more details.
17-
18- This license is contained in the file "COPYING",
19- which is included with this source code; it is available online at
20- http://www.gnu.org/licenses/gpl.html
21-
22- *
23- * Sept 19, 2001 (Woody Zenfell): split this file away from network_dialogs.cpp for sharing
24- * Also made whatever simple changes were needed for it to compile/work.
25- *
26- * Sept-Nov 2001 (Woody Zenfell): added some identifiers and prototypes for carnage report
27- * and for better code sharing between the Mac and SDL versions.
28-
29-Feb 27, 2002 (Br'fin (Jeremy Parsons)):
30- Moved shared SDL hint address info here from network_dialogs_sdl.cpp
31- Added dialog item definitions for a Join by Host in the join dialog
32-
33-Mar 1, 2002 (Woody Zenfell):
34- SDL dialog uses new level-selection scheme; new interface based on level number, not menu index.
35- */
36-
37-#ifndef NETWORK_DIALOGS_H
38-#define NETWORK_DIALOGS_H
39-
40-#include "player.h" // for MAXIMUM_NUMBER_OF_PLAYERS
41-#include "network.h"
42-#include "network_private.h" // for JoinerSeekingGathererAnnouncer
43-#include "FileHandler.h"
44-#include "network_metaserver.h"
45-#include "metaserver_dialogs.h"
46-
47-#include "shared_widgets.h"
48-
49-#include <string>
50-
51-#include <map>
52-#include <set>
53-
54-#ifdef USES_NIBS
55-const CFStringRef Window_Network_Distribute = CFSTR("Network_Distribute");
56-#endif
57-
58-// ZZZ: Moved here so constants can be shared by Mac and SDL dialog code.
59-/* ------------------ enums */
60-enum {
61- strNET_STATS_STRINGS= 153,
62- strKILLS_STRING= 0,
63- strDEATHS_STRING,
64- strSUICIDES_STRING,
65- strTOTALS_STRING,
66- strMONSTERS_STRING,
67- strTOTAL_KILLS_STRING,
68- strTOTAL_DEATHS_STRING,
69- strINCLUDING_SUICIDES_STRING,
70- strTEAM_TOTALS_STRING,
71- strFRIENDLY_FIRE_STRING,
72- strTOTAL_SCORES,
73- strTOTAL_TEAM_SCORES,
74-// ZZZ: added the following to support my postgame report
75- strTEAM_CARNAGE_STRING,
76- strKILLS_LEGEND,
77- strDEATHS_LEGEND,
78- strSUICIDES_LEGEND,
79- strFRIENDLY_FIRE_LEGEND
80-};
81-
82-enum {
83- kNetworkGameTypesStringSetID = 146,
84- kEndConditionTypeStringSetID = 147,
85- kScoreLimitTypeStringSetID = 148,
86- kSingleOrNetworkStringSetID = 149
87-};
88-
89-
90-#ifdef USES_NIBS
91-
92-enum {
93- dlogNET_GAME_STATS= 5000,
94- // iGRAPH_POPUP moved from #2 because that is the "Cancel" value
95- iDAMAGE_STATS = 3,
96- iTOTAL_KILLS,
97- iTOTAL_DEATHS,
98- iGRAPH_POPUP
99-};
100-
101-#else
102-
103-enum {
104- dlogNET_GAME_STATS= 5000,
105- iGRAPH_POPUP= 2,
106- iDAMAGE_STATS,
107- iTOTAL_KILLS,
108- iTOTAL_DEATHS
109-};
110-
111-#endif
112-
113-enum /* All the different graph types */
114-{
115- _player_graph,
116- _total_carnage_graph,
117- _total_scores_graph,
118- _total_team_carnage_graph,
119- _total_team_scores_graph
120-};
121-
122-enum {
123- _suicide_color,
124- _kill_color,
125- _death_color,
126- _score_color,
127- NUMBER_OF_NET_COLORS
128-};
129-
130-/* SDL/TCP hinting info. JTP: moved here from network_dialogs_sdl.cpp */
131-enum {
132- kJoinHintingAddressLength = 64,
133-};
134-
135-#define strJOIN_DIALOG_MESSAGES 136
136-enum /* join dialog string numbers */
137-{
138- _join_dialog_welcome_string,
139- _join_dialog_waiting_string,
140- _join_dialog_accepted_string
141-};
142-
143-enum {
144- strSETUP_NET_GAME_MESSAGES= 141,
145- killLimitString= 0,
146- killsString,
147- flagPullsString,
148- flagsString,
149- pointLimitString,
150- pointsString,
151- // START Benad
152- timeOnBaseString,
153- minutesString
154- // END Benad
155-};
156-
157-enum {
158- dlogGATHER= 10000,
159- iPLAYER_DISPLAY_AREA= 3,
160- iADD,
161- iNETWORK_LIST_BOX,
162- iPLAYER_LIST_TEXT =9,
163- iAUTO_GATHER = 19
164-};
165-
166-enum {
167- dlogJOIN= 10001,
168-#ifndef USES_NIBS
169- iJOIN= 1,
170-#else
171- iJOIN= 101,
172-#endif
173- // iPLAYER_DISPLAY_AREA = 3,
174- iJOIN_NAME= 4,
175- iJOIN_TEAM,
176- iJOIN_COLOR,
177- iJOIN_MESSAGES,
178- // Group line = 12
179- // iJOIN_NETWORK_TYPE= 13,
180- iJOIN_BY_HOST = 14,
181- iJOIN_BY_HOST_LABEL,
182- iJOIN_BY_HOST_ADDRESS,
183- iJOIN_CHAT_ENTRY,
184- iJOIN_CHAT_CHOICE,
185- iJOIN_BY_METASERVER = 20
186-};
187-
188-enum {
189- dlogGAME_SETUP= 3000,
190- iNETWORK_SPEED= 3,
191- iENTRY_MENU,
192- iDIFFICULTY_MENU,
193- iMOTION_SENSOR_DISABLED,
194- iDYING_PUNISHED,
195- iBURN_ITEMS_ON_DEATH,
196- iREAL_TIME_SOUND,
197- iUNLIMITED_MONSTERS,
198- iFORCE_UNIQUE_TEAMS,
199- iRADIO_NO_TIME_LIMIT,
200- iRADIO_TIME_LIMIT,
201- iRADIO_KILL_LIMIT,
202- iTIME_LIMIT,
203- iKILL_LIMIT,
204- iREALTIME_NET_STATS,
205- iTEXT_KILL_LIMIT,
206- iGATHER_NAME= 21,
207- iGATHER_TEAM,
208- iSUICIDE_PUNISHED= 24,
209- iGAME_TYPE,
210- iGATHER_COLOR,
211- iUSE_SCRIPT= 28,
212- iCHOOSE_SCRIPT,
213- iTEXT_TIME_LIMIT= 35,
214- iMICROPHONE_TYPE,
215- iTEXT_SCRIPT_NAME,
216- iADVERTISE_GAME_ON_METASERVER = 39,
217- iCHOOSE_MAP,
218- iTEXT_MAP_NAME,
219- iALLOW_ZOOM,
220- iALLOW_CROSSHAIRS,
221- iALLOW_LARA_CROFT,
222- iGATHER_CHAT_ENTRY,
223- iGATHER_CHAT_CHOICE,
224- iUSE_UPNP,
225- iLATENCY_TOLERANCE,
226- iSNG_TABS = 400,
227- iSNG_GENERAL_TAB,
228- iSNG_STUFF_TAB
229-};
230-
231-enum {
232- duration_no_time_limit = 0,
233- duration_time_limit,
234- duration_kill_limit
235-};
236-
237-#ifdef USES_NIBS
238-
239-// Because otherwise it would be interpreted as a regular "OK"
240-const int iOK_SPECIAL = 101;
241-
242-// For player-display Data Browser control:
243-const OSType PlayerDisplay_Name = 'name';
244-
245-// Signature of player-select buttons in player dialog:
246-const OSType StatsDisplay_Player = 'plyr';
247-
248-#endif
249-
250-
251-/* ------------------ structures */
252-struct net_rank
253-{
254- short kills, deaths;
255- int32 ranking;
256- int32 game_ranking;
257-
258- short player_index;
259- short color; // only valid if player_index== NONE!
260- short friendly_fire_kills;
261-};
262-
263-struct player_info;
264-struct game_info;
265-
266-#ifndef USES_NIBS
267-typedef DialogPtr NetgameOutcomeData;
268-#endif
269-
270-#ifdef USES_NIBS
271-
272-struct NetgameOutcomeData
273-{
274- ControlRef SelectCtrl;
275- ControlRef DisplayCtrl;
276-
277- // Invisible, but hittable controls;
278- // their drawing is done by the drawing callback for DisplayCtrl
279- ControlRef PlayerButtonCtrls[MAXIMUM_NUMBER_OF_PLAYERS];
280-
281- ControlRef KillsTextCtrl;
282- ControlRef DeathsTextCtrl;
283-};
284-
285-#endif
286-
287-/* ---------------------- globals */
288-extern struct net_rank rankings[MAXIMUM_NUMBER_OF_PLAYERS];
289-
290-
291-//class MetaserverClient;
292-//class GlobalMetaserverChatNotificationAdapter;
293-class GatherDialog : public GatherCallbacks, public ChatCallbacks, public GlobalMetaserverChatNotificationAdapter
294-{
295-public:
296-// Abstract factory; concrete type determined at link-time
297- static std::auto_ptr<GatherDialog> Create();
298-
299- bool GatherNetworkGameByRunning ();
300-
301- virtual ~GatherDialog ();
302-
303- // Callbacks for network code; final methods
304- virtual void JoinSucceeded(const prospective_joiner_info* player);
305- virtual void JoiningPlayerDropped(const prospective_joiner_info* player);
306- virtual void JoinedPlayerDropped(const prospective_joiner_info* player);
307- virtual void JoinedPlayerChanged(const prospective_joiner_info* player);
308-
309- virtual void ReceivedMessageFromPlayer(
310- const char *player_name,
311- const char *message);
312-
313-protected:
314- GatherDialog();
315-
316- virtual bool Run() = 0;
317- virtual void Stop(bool result) = 0;
318-
319- void idle ();
320-
321- void StartGameHit ();
322-
323- void update_ungathered_widget ();
324-
325- bool player_search (prospective_joiner_info& player);
326- bool gathered_player (const prospective_joiner_info& player);
327-
328- void sendChat ();
329- void chatTextEntered (char character);
330- void chatChoiceHit ();
331-
332- map<int, prospective_joiner_info> m_ungathered_players;
333-
334- ButtonWidget* m_cancelWidget;
335- ButtonWidget* m_startWidget;
336-
337- ToggleWidget* m_autogatherWidget;
338-
339- JoiningPlayerListWidget* m_ungatheredWidget;
340- PlayersInGameWidget* m_pigWidget;
341-
342- EditTextWidget* m_chatEntryWidget;
343- SelectorWidget* m_chatChoiceWidget;
344- ColorfulChatWidget* m_chatWidget;
345-
346- enum { kPregameChat = 0, kMetaserverChat };
347-};
348-
349-
350-class JoinDialog : public GlobalMetaserverChatNotificationAdapter, public ChatCallbacks
351-{
352-public:
353- // Abstract factory; concrete type determined at link-time
354- static std::auto_ptr<JoinDialog> Create();
355-
356- const int JoinNetworkGameByRunning();
357-
358- virtual ~JoinDialog ();
359-
360-protected:
361- JoinDialog();
362-
363- virtual void Run() = 0;
364- virtual void Stop() = 0;
365-
366- virtual void respondToJoinHit ();
367-
368- void gathererSearch ();
369- void attemptJoin ();
370- void changeColours ();
371- void getJoinAddressFromMetaserver ();
372-
373- // ChatCallbacks
374- virtual void ReceivedMessageFromPlayer(const char *player_name, const char *message);
375-
376- void sendChat ();
377- void chatTextEntered (char character);
378- void chatChoiceHit ();
379-
380- ButtonWidget* m_cancelWidget;
381- ButtonWidget* m_joinWidget;
382-
383- ButtonWidget* m_joinMetaserverWidget;
384- EditTextWidget* m_joinAddressWidget;
385- ToggleWidget* m_joinByAddressWidget;
386-
387- EditTextWidget* m_nameWidget;
388- SelectorWidget* m_colourWidget;
389- SelectorWidget* m_teamWidget;
390-
391- StaticTextWidget* m_messagesWidget;
392-
393- PlayersInGameWidget* m_pigWidget;
394-
395- EditTextWidget* m_chatEntryWidget;
396- SelectorWidget* m_chatChoiceWidget;
397- ColorfulChatWidget* m_chatWidget;
398-
399- BinderSet binders;
400-
401- enum { kPregameChat = 0, kMetaserverChat };
402-
403- std::auto_ptr<JoinerSeekingGathererAnnouncer> join_announcer;
404- int join_result;
405- bool got_gathered;
406-
407- bool skipToMetaserver;
408-};
409-
410-
411-bool network_game_setup(player_info *player_information, game_info *game_information, bool inResumingGame, bool& outAdvertiseGameOnMetaserver);
412-
413-class SetupNetgameDialog
414-{
415-public:
416- // Abstract factory; concrete type determined at link-time
417- static std::auto_ptr<SetupNetgameDialog> Create();
418-
419- bool SetupNetworkGameByRunning (
420- player_info *player_information,
421- game_info *game_information,
422- bool ResumingGame,
423- bool& outAdvertiseGameOnMetaserver);
424-
425- virtual ~SetupNetgameDialog ();
426-
427-protected:
428- SetupNetgameDialog();
429-
430- virtual bool Run () = 0;
431- virtual void Stop (bool result) = 0;
432-
433- virtual bool allLevelsAllowed () = 0;
434- bool m_allow_all_levels;
435- int m_old_game_type;
436-
437- void setupForUntimedGame ();
438- void setupForTimedGame ();
439- void setupForScoreGame ();
440- void limitTypeHit ();
441- void teamsHit ();
442- void setupForGameType ();
443- void gameTypeHit ();
444- void chooseMapHit ();
445- bool informationIsAcceptable ();
446- void okHit ();
447-
448- virtual void unacceptableInfo () = 0;
449-
450- ButtonWidget* m_cancelWidget;
451- ButtonWidget* m_okWidget;
452-
453- EditTextWidget* m_nameWidget;
454- SelectorWidget* m_colourWidget;
455- SelectorWidget* m_teamWidget;
456-
457- FileChooserWidget* m_mapWidget;
458- SelectorWidget* m_levelWidget;
459- SelectorWidget* m_gameTypeWidget;
460- SelectorWidget* m_difficultyWidget;
461-
462- SelectorWidget* m_limitTypeWidget;
463- EditNumberWidget* m_timeLimitWidget;
464- EditNumberWidget* m_scoreLimitWidget;
465-
466- ToggleWidget* m_aliensWidget;
467- ToggleWidget* m_allowTeamsWidget;
468- ToggleWidget* m_deadPlayersDropItemsWidget;
469- ToggleWidget* m_penalizeDeathWidget;
470- ToggleWidget* m_penalizeSuicideWidget;
471-
472- ToggleWidget* m_useMetaserverWidget;
473-
474- ToggleWidget* m_useScriptWidget;
475- FileChooserWidget* m_scriptWidget;
476-
477- ToggleWidget* m_allowMicWidget;
478-
479- ToggleWidget* m_liveCarnageWidget;
480- ToggleWidget* m_motionSensorWidget;
481-
482- ToggleWidget* m_zoomWidget;
483- ToggleWidget* m_crosshairWidget;
484- ToggleWidget* m_overlayWidget;
485- ToggleWidget* m_laraCroftWidget;
486- ToggleWidget* m_carnageMessagesWidget;
487- ToggleWidget* m_savingLevelWidget;
488-
489- ToggleWidget* m_useUpnpWidget;
490- SelectorWidget* m_latencyToleranceWidget;
491-};
492-
493-
494-
495-extern void reassign_player_colors(short player_index, short num_players);
496-
497-
498-
499-// (Postgame Carnage Report routines)
500-extern short find_graph_mode(NetgameOutcomeData &outcome, short *index);
501-extern void draw_new_graph(NetgameOutcomeData &outcome);
502-
503-extern void draw_player_graph(NetgameOutcomeData &outcome, short index);
504-extern void get_net_color(short index, RGBColor *color);
505-
506-extern short calculate_max_kills(size_t num_players);
507-extern void draw_totals_graph(NetgameOutcomeData &outcome);
508-extern void calculate_rankings(struct net_rank *ranks, short num_players);
509-extern int rank_compare(void const *rank1, void const *rank2);
510-extern int team_rank_compare(void const *rank1, void const *ranks2);
511-extern int score_rank_compare(void const *rank1, void const *ranks2);
512-extern void draw_team_totals_graph(NetgameOutcomeData &outcome);
513-extern void draw_total_scores_graph(NetgameOutcomeData &outcome);
514-extern void draw_team_total_scores_graph(NetgameOutcomeData &outcome);
515-extern void update_carnage_summary(NetgameOutcomeData &outcome, struct net_rank *ranks,
516- short num_players, short suicide_index, bool do_totals, bool friendly_fire);
517-
518-// Routines
519-extern void menu_index_to_level_entry(short index, int32 entry_flags, struct entry_point *entry);
520-extern int menu_index_to_level_index (int menu_index, int32 entry_flags);
521-extern int level_index_to_menu_index(int level_index, int32 entry_flags);
522-
523-// (Postgame carnage report)
524-extern void draw_names(NetgameOutcomeData &outcome, struct net_rank *ranks,
525- short number_of_bars, short which_player);
526-extern void draw_kill_bars(NetgameOutcomeData &outcome, struct net_rank *ranks, short num_players,
527- short suicide_index, bool do_totals, bool friendly_fire);
528-extern void draw_score_bars(NetgameOutcomeData &outcome, struct net_rank *ranks, short bar_count);
529-
530-
531-#endif//NETWORK_DIALOGS_H
1+/*
2+ * network_dialogs.h
3+ *
4+
5+ Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
6+ and the "Aleph One" developers.
7+
8+ This program is free software; you can redistribute it and/or modify
9+ it under the terms of the GNU General Public License as published by
10+ the Free Software Foundation; either version 3 of the License, or
11+ (at your option) any later version.
12+
13+ This program is distributed in the hope that it will be useful,
14+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+ GNU General Public License for more details.
17+
18+ This license is contained in the file "COPYING",
19+ which is included with this source code; it is available online at
20+ http://www.gnu.org/licenses/gpl.html
21+
22+ *
23+ * Sept 19, 2001 (Woody Zenfell): split this file away from network_dialogs.cpp for sharing
24+ * Also made whatever simple changes were needed for it to compile/work.
25+ *
26+ * Sept-Nov 2001 (Woody Zenfell): added some identifiers and prototypes for carnage report
27+ * and for better code sharing between the Mac and SDL versions.
28+
29+Feb 27, 2002 (Br'fin (Jeremy Parsons)):
30+ Moved shared SDL hint address info here from network_dialogs_sdl.cpp
31+ Added dialog item definitions for a Join by Host in the join dialog
32+
33+Mar 1, 2002 (Woody Zenfell):
34+ SDL dialog uses new level-selection scheme; new interface based on level number, not menu index.
35+ */
36+
37+#ifndef NETWORK_DIALOGS_H
38+#define NETWORK_DIALOGS_H
39+
40+#include "player.h" // for MAXIMUM_NUMBER_OF_PLAYERS
41+#include "network.h"
42+#include "network_private.h" // for JoinerSeekingGathererAnnouncer
43+#include "FileHandler.h"
44+#include "network_metaserver.h"
45+#include "metaserver_dialogs.h"
46+
47+#include "shared_widgets.h"
48+
49+#include <string>
50+
51+#include <map>
52+#include <set>
53+
54+#ifdef USES_NIBS
55+const CFStringRef Window_Network_Distribute = CFSTR("Network_Distribute");
56+#endif
57+
58+// ZZZ: Moved here so constants can be shared by Mac and SDL dialog code.
59+/* ------------------ enums */
60+enum {
61+ strNET_STATS_STRINGS= 153,
62+ strKILLS_STRING= 0,
63+ strDEATHS_STRING,
64+ strSUICIDES_STRING,
65+ strTOTALS_STRING,
66+ strMONSTERS_STRING,
67+ strTOTAL_KILLS_STRING,
68+ strTOTAL_DEATHS_STRING,
69+ strINCLUDING_SUICIDES_STRING,
70+ strTEAM_TOTALS_STRING,
71+ strFRIENDLY_FIRE_STRING,
72+ strTOTAL_SCORES,
73+ strTOTAL_TEAM_SCORES,
74+// ZZZ: added the following to support my postgame report
75+ strTEAM_CARNAGE_STRING,
76+ strKILLS_LEGEND,
77+ strDEATHS_LEGEND,
78+ strSUICIDES_LEGEND,
79+ strFRIENDLY_FIRE_LEGEND
80+};
81+
82+enum {
83+ kNetworkGameTypesStringSetID = 146,
84+ kEndConditionTypeStringSetID = 147,
85+ kScoreLimitTypeStringSetID = 148,
86+ kSingleOrNetworkStringSetID = 149
87+};
88+
89+
90+#ifdef USES_NIBS
91+
92+enum {
93+ dlogNET_GAME_STATS= 5000,
94+ // iGRAPH_POPUP moved from #2 because that is the "Cancel" value
95+ iDAMAGE_STATS = 3,
96+ iTOTAL_KILLS,
97+ iTOTAL_DEATHS,
98+ iGRAPH_POPUP
99+};
100+
101+#else
102+
103+enum {
104+ dlogNET_GAME_STATS= 5000,
105+ iGRAPH_POPUP= 2,
106+ iDAMAGE_STATS,
107+ iTOTAL_KILLS,
108+ iTOTAL_DEATHS
109+};
110+
111+#endif
112+
113+enum /* All the different graph types */
114+{
115+ _player_graph,
116+ _total_carnage_graph,
117+ _total_scores_graph,
118+ _total_team_carnage_graph,
119+ _total_team_scores_graph
120+};
121+
122+enum {
123+ _suicide_color,
124+ _kill_color,
125+ _death_color,
126+ _score_color,
127+ NUMBER_OF_NET_COLORS
128+};
129+
130+/* SDL/TCP hinting info. JTP: moved here from network_dialogs_sdl.cpp */
131+enum {
132+ kJoinHintingAddressLength = 64,
133+};
134+
135+#define strJOIN_DIALOG_MESSAGES 136
136+enum /* join dialog string numbers */
137+{
138+ _join_dialog_welcome_string,
139+ _join_dialog_waiting_string,
140+ _join_dialog_accepted_string
141+};
142+
143+enum {
144+ strSETUP_NET_GAME_MESSAGES= 141,
145+ killLimitString= 0,
146+ killsString,
147+ flagPullsString,
148+ flagsString,
149+ pointLimitString,
150+ pointsString,
151+ // START Benad
152+ timeOnBaseString,
153+ minutesString
154+ // END Benad
155+};
156+
157+enum {
158+ dlogGATHER= 10000,
159+ iPLAYER_DISPLAY_AREA= 3,
160+ iADD,
161+ iNETWORK_LIST_BOX,
162+ iPLAYER_LIST_TEXT =9,
163+ iAUTO_GATHER = 19
164+};
165+
166+enum {
167+ dlogJOIN= 10001,
168+#ifndef USES_NIBS
169+ iJOIN= 1,
170+#else
171+ iJOIN= 101,
172+#endif
173+ // iPLAYER_DISPLAY_AREA = 3,
174+ iJOIN_NAME= 4,
175+ iJOIN_TEAM,
176+ iJOIN_COLOR,
177+ iJOIN_MESSAGES,
178+ // Group line = 12
179+ // iJOIN_NETWORK_TYPE= 13,
180+ iJOIN_BY_HOST = 14,
181+ iJOIN_BY_HOST_LABEL,
182+ iJOIN_BY_HOST_ADDRESS,
183+ iJOIN_CHAT_ENTRY,
184+ iJOIN_CHAT_CHOICE,
185+ iJOIN_BY_METASERVER = 20
186+};
187+
188+enum {
189+ dlogGAME_SETUP= 3000,
190+ iNETWORK_SPEED= 3,
191+ iENTRY_MENU,
192+ iDIFFICULTY_MENU,
193+ iMOTION_SENSOR_DISABLED,
194+ iDYING_PUNISHED,
195+ iBURN_ITEMS_ON_DEATH,
196+ iREAL_TIME_SOUND,
197+ iUNLIMITED_MONSTERS,
198+ iFORCE_UNIQUE_TEAMS,
199+ iRADIO_NO_TIME_LIMIT,
200+ iRADIO_TIME_LIMIT,
201+ iRADIO_KILL_LIMIT,
202+ iTIME_LIMIT,
203+ iKILL_LIMIT,
204+ iREALTIME_NET_STATS,
205+ iTEXT_KILL_LIMIT,
206+ iGATHER_NAME= 21,
207+ iGATHER_TEAM,
208+ iSUICIDE_PUNISHED= 24,
209+ iGAME_TYPE,
210+ iGATHER_COLOR,
211+ iUSE_SCRIPT= 28,
212+ iCHOOSE_SCRIPT,
213+ iTEXT_TIME_LIMIT= 35,
214+ iMICROPHONE_TYPE,
215+ iTEXT_SCRIPT_NAME,
216+ iADVERTISE_GAME_ON_METASERVER = 39,
217+ iCHOOSE_MAP,
218+ iTEXT_MAP_NAME,
219+ iALLOW_ZOOM,
220+ iALLOW_CROSSHAIRS,
221+ iALLOW_LARA_CROFT,
222+ iGATHER_CHAT_ENTRY,
223+ iGATHER_CHAT_CHOICE,
224+ iUSE_UPNP,
225+ iLATENCY_TOLERANCE,
226+ iSNG_TABS = 400,
227+ iSNG_GENERAL_TAB,
228+ iSNG_STUFF_TAB
229+};
230+
231+enum {
232+ duration_no_time_limit = 0,
233+ duration_time_limit,
234+ duration_kill_limit
235+};
236+
237+#ifdef USES_NIBS
238+
239+// Because otherwise it would be interpreted as a regular "OK"
240+const int iOK_SPECIAL = 101;
241+
242+// For player-display Data Browser control:
243+const OSType PlayerDisplay_Name = 'name';
244+
245+// Signature of player-select buttons in player dialog:
246+const OSType StatsDisplay_Player = 'plyr';
247+
248+#endif
249+
250+
251+/* ------------------ structures */
252+struct net_rank
253+{
254+ short kills, deaths;
255+ int32 ranking;
256+ int32 game_ranking;
257+
258+ short player_index;
259+ short color; // only valid if player_index== NONE!
260+ short friendly_fire_kills;
261+};
262+
263+struct player_info;
264+struct game_info;
265+
266+#ifndef USES_NIBS
267+typedef DialogPtr NetgameOutcomeData;
268+#endif
269+
270+#ifdef USES_NIBS
271+
272+struct NetgameOutcomeData
273+{
274+ ControlRef SelectCtrl;
275+ ControlRef DisplayCtrl;
276+
277+ // Invisible, but hittable controls;
278+ // their drawing is done by the drawing callback for DisplayCtrl
279+ ControlRef PlayerButtonCtrls[MAXIMUM_NUMBER_OF_PLAYERS];
280+
281+ ControlRef KillsTextCtrl;
282+ ControlRef DeathsTextCtrl;
283+};
284+
285+#endif
286+
287+/* ---------------------- globals */
288+extern struct net_rank rankings[MAXIMUM_NUMBER_OF_PLAYERS];
289+
290+
291+//class MetaserverClient;
292+//class GlobalMetaserverChatNotificationAdapter;
293+class GatherDialog : public GatherCallbacks, public ChatCallbacks, public GlobalMetaserverChatNotificationAdapter
294+{
295+public:
296+// Abstract factory; concrete type determined at link-time
297+ static std::auto_ptr<GatherDialog> Create();
298+
299+ bool GatherNetworkGameByRunning ();
300+
301+ virtual ~GatherDialog ();
302+
303+ // Callbacks for network code; final methods
304+ virtual void JoinSucceeded(const prospective_joiner_info* player);
305+ virtual void JoiningPlayerDropped(const prospective_joiner_info* player);
306+ virtual void JoinedPlayerDropped(const prospective_joiner_info* player);
307+ virtual void JoinedPlayerChanged(const prospective_joiner_info* player);
308+
309+ virtual void ReceivedMessageFromPlayer(
310+ const char *player_name,
311+ const char *message);
312+
313+protected:
314+ GatherDialog();
315+
316+ virtual bool Run() = 0;
317+ virtual void Stop(bool result) = 0;
318+
319+ void idle ();
320+
321+ void StartGameHit ();
322+
323+ void update_ungathered_widget ();
324+
325+ bool player_search (prospective_joiner_info& player);
326+ bool gathered_player (const prospective_joiner_info& player);
327+
328+ void sendChat ();
329+ void chatTextEntered (char character);
330+ void chatChoiceHit ();
331+
332+ map<int, prospective_joiner_info> m_ungathered_players;
333+
334+ ButtonWidget* m_cancelWidget;
335+ ButtonWidget* m_startWidget;
336+
337+ ToggleWidget* m_autogatherWidget;
338+
339+ JoiningPlayerListWidget* m_ungatheredWidget;
340+ PlayersInGameWidget* m_pigWidget;
341+
342+ EditTextWidget* m_chatEntryWidget;
343+ SelectorWidget* m_chatChoiceWidget;
344+ ColorfulChatWidget* m_chatWidget;
345+
346+ enum { kPregameChat = 0, kMetaserverChat };
347+};
348+
349+
350+class JoinDialog : public GlobalMetaserverChatNotificationAdapter, public ChatCallbacks
351+{
352+public:
353+ // Abstract factory; concrete type determined at link-time
354+ static std::auto_ptr<JoinDialog> Create();
355+
356+ const int JoinNetworkGameByRunning();
357+
358+ virtual ~JoinDialog ();
359+
360+protected:
361+ JoinDialog();
362+
363+ virtual void Run() = 0;
364+ virtual void Stop() = 0;
365+
366+ virtual void respondToJoinHit ();
367+
368+ void gathererSearch ();
369+ void attemptJoin ();
370+ void changeColours ();
371+ void getJoinAddressFromMetaserver ();
372+
373+ // ChatCallbacks
374+ virtual void ReceivedMessageFromPlayer(const char *player_name, const char *message);
375+
376+ void sendChat ();
377+ void chatTextEntered (char character);
378+ void chatChoiceHit ();
379+
380+ ButtonWidget* m_cancelWidget;
381+ ButtonWidget* m_joinWidget;
382+
383+ ButtonWidget* m_joinMetaserverWidget;
384+ EditTextWidget* m_joinAddressWidget;
385+ ToggleWidget* m_joinByAddressWidget;
386+
387+ EditTextWidget* m_nameWidget;
388+ SelectorWidget* m_colourWidget;
389+ SelectorWidget* m_teamWidget;
390+
391+ StaticTextWidget* m_messagesWidget;
392+
393+ PlayersInGameWidget* m_pigWidget;
394+
395+ EditTextWidget* m_chatEntryWidget;
396+ SelectorWidget* m_chatChoiceWidget;
397+ ColorfulChatWidget* m_chatWidget;
398+
399+ BinderSet binders;
400+
401+ enum { kPregameChat = 0, kMetaserverChat };
402+
403+ std::auto_ptr<JoinerSeekingGathererAnnouncer> join_announcer;
404+ int join_result;
405+ bool got_gathered;
406+
407+ bool skipToMetaserver;
408+};
409+
410+
411+bool network_game_setup(player_info *player_information, game_info *game_information, bool inResumingGame, bool& outAdvertiseGameOnMetaserver);
412+
413+class SetupNetgameDialog
414+{
415+public:
416+ // Abstract factory; concrete type determined at link-time
417+ static std::auto_ptr<SetupNetgameDialog> Create();
418+
419+ bool SetupNetworkGameByRunning (
420+ player_info *player_information,
421+ game_info *game_information,
422+ bool ResumingGame,
423+ bool& outAdvertiseGameOnMetaserver);
424+
425+ virtual ~SetupNetgameDialog ();
426+
427+protected:
428+ SetupNetgameDialog();
429+
430+ virtual bool Run () = 0;
431+ virtual void Stop (bool result) = 0;
432+
433+ virtual bool allLevelsAllowed () = 0;
434+ bool m_allow_all_levels;
435+ int m_old_game_type;
436+
437+ void setupForUntimedGame ();
438+ void setupForTimedGame ();
439+ void setupForScoreGame ();
440+ void limitTypeHit ();
441+ void teamsHit ();
442+ void setupForGameType ();
443+ void gameTypeHit ();
444+ void chooseMapHit ();
445+ bool informationIsAcceptable ();
446+ void okHit ();
447+
448+ virtual void unacceptableInfo () = 0;
449+
450+ ButtonWidget* m_cancelWidget;
451+ ButtonWidget* m_okWidget;
452+
453+ EditTextWidget* m_nameWidget;
454+ SelectorWidget* m_colourWidget;
455+ SelectorWidget* m_teamWidget;
456+
457+ FileChooserWidget* m_mapWidget;
458+ SelectorWidget* m_levelWidget;
459+ SelectorWidget* m_gameTypeWidget;
460+ SelectorWidget* m_difficultyWidget;
461+
462+ SelectorWidget* m_limitTypeWidget;
463+ EditNumberWidget* m_timeLimitWidget;
464+ EditNumberWidget* m_scoreLimitWidget;
465+
466+ ToggleWidget* m_aliensWidget;
467+ ToggleWidget* m_allowTeamsWidget;
468+ ToggleWidget* m_deadPlayersDropItemsWidget;
469+ ToggleWidget* m_penalizeDeathWidget;
470+ ToggleWidget* m_penalizeSuicideWidget;
471+
472+ ToggleWidget* m_useMetaserverWidget;
473+
474+ ToggleWidget* m_useScriptWidget;
475+ FileChooserWidget* m_scriptWidget;
476+
477+ ToggleWidget* m_allowMicWidget;
478+
479+ ToggleWidget* m_liveCarnageWidget;
480+ ToggleWidget* m_motionSensorWidget;
481+
482+ ToggleWidget* m_zoomWidget;
483+ ToggleWidget* m_crosshairWidget;
484+ ToggleWidget* m_overlayWidget;
485+ ToggleWidget* m_laraCroftWidget;
486+ ToggleWidget* m_carnageMessagesWidget;
487+ ToggleWidget* m_savingLevelWidget;
488+
489+ ToggleWidget* m_useUpnpWidget;
490+ SelectorWidget* m_latencyToleranceWidget;
491+};
492+
493+
494+
495+extern void reassign_player_colors(short player_index, short num_players);
496+
497+
498+
499+// (Postgame Carnage Report routines)
500+extern short find_graph_mode(NetgameOutcomeData &outcome, short *index);
501+extern void draw_new_graph(NetgameOutcomeData &outcome);
502+
503+extern void draw_player_graph(NetgameOutcomeData &outcome, short index);
504+extern void get_net_color(short index, RGBColor *color);
505+
506+extern short calculate_max_kills(size_t num_players);
507+extern void draw_totals_graph(NetgameOutcomeData &outcome);
508+extern void calculate_rankings(struct net_rank *ranks, short num_players);
509+extern int rank_compare(void const *rank1, void const *rank2);
510+extern int team_rank_compare(void const *rank1, void const *ranks2);
511+extern int score_rank_compare(void const *rank1, void const *ranks2);
512+extern void draw_team_totals_graph(NetgameOutcomeData &outcome);
513+extern void draw_total_scores_graph(NetgameOutcomeData &outcome);
514+extern void draw_team_total_scores_graph(NetgameOutcomeData &outcome);
515+extern void update_carnage_summary(NetgameOutcomeData &outcome, struct net_rank *ranks,
516+ short num_players, short suicide_index, bool do_totals, bool friendly_fire);
517+
518+// Routines
519+extern void menu_index_to_level_entry(short index, int32 entry_flags, struct entry_point *entry);
520+extern int menu_index_to_level_index (int menu_index, int32 entry_flags);
521+extern int level_index_to_menu_index(int level_index, int32 entry_flags);
522+
523+// (Postgame carnage report)
524+extern void draw_names(NetgameOutcomeData &outcome, struct net_rank *ranks,
525+ short number_of_bars, short which_player);
526+extern void draw_kill_bars(NetgameOutcomeData &outcome, struct net_rank *ranks, short num_players,
527+ short suicide_index, bool do_totals, bool friendly_fire);
528+extern void draw_score_bars(NetgameOutcomeData &outcome, struct net_rank *ranks, short bar_count);
529+
530+
531+#endif//NETWORK_DIALOGS_H
--- marathon/trunk/Source_Files/Network/network_dialog_widgets_sdl.cpp (revision 527)
+++ marathon/trunk/Source_Files/Network/network_dialog_widgets_sdl.cpp (revision 528)
@@ -1,1111 +1,1111 @@
1-/*
2- * network_dialog_widgets_sdl.cpp
3-
4- Copyright (C) 2001 and beyond by Woody Zenfell, III
5- and the "Aleph One" developers.
6-
7- This program is free software; you can redistribute it and/or modify
8- it under the terms of the GNU General Public License as published by
9- the Free Software Foundation; either version 3 of the License, or
10- (at your option) any later version.
11-
12- This program is distributed in the hope that it will be useful,
13- but WITHOUT ANY WARRANTY; without even the implied warranty of
14- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15- GNU General Public License for more details.
16-
17- This license is contained in the file "COPYING",
18- which is included with this source code; it is available online at
19- http://www.gnu.org/licenses/gpl.html
20-
21- * Implementation of network-dialog-specific widgets in the SDL dialog system.
22- *
23- * Created by Woody Zenfell, III on Fri Sep 28 2001.
24- *
25- * Mar 1, 2002 (Woody Zenfell): Added new w_entry_point_selector widget.
26- */
27-
28-#if !defined(DISABLE_NETWORKING)
29-
30-#include "network_dialog_widgets_sdl.h"
31-
32-#include "screen_drawing.h"
33-#include "sdl_fonts.h"
34-#include "interface.h"
35-#include "network.h"
36-
37-// these next are for playing with shape-drawing
38-#include "player.h"
39-#include "HUDRenderer.h"
40-#include "shell.h"
41-#include "collection_definition.h"
42-
43-// here are some for w_entry_point_selector
44-#include "preferences.h"
45-#include "screen.h"
46-
47-// for TS_GetCString, get shared ref rather than copying string.
48-#include "TextStrings.h"
49-
50-#include "TextLayoutHelper.h"
51-
52-#include <string>
53-
54-
55-// jkvw: I'm putting this here because we only really want it for find_item_index_in_vecotr,
56-// and of course we shouldn't be doing that anyway :).
57-bool operator==(const prospective_joiner_info &left, const prospective_joiner_info &right)
58-{ return left.stream_id == right.stream_id; }
59-
60-
61-////// helper functions //////
62-// Actually, as it turns out, there should be a generic STL algorithm that does this, I think.
63-// Well, w_found_players ought to be using a set<> or similar anyway, much more natural.
64-// Shrug, this was what I came up with before I knew anything about STL, and I'm too lazy to change it.
65-template<class T>
66-static const size_t
67-find_item_index_in_vector(const T& inItem, const vector<T>& inVector) {
68- typename vector<T>::const_iterator i = inVector.begin();
69- typename vector<T>::const_iterator end = inVector.end();
70- size_t index = 0;
71-
72- while(i != end) {
73- if(*i == inItem)
74- return index;
75-
76- index++;
77- i++;
78- }
79-
80- // Didn't find it
81- return -1;
82-}
83-
84-
85-
86-////// w_found_players //////
87-void
88-w_found_players::found_player(prospective_joiner_info &player) {
89-
90- // Found one
91- found_players.push_back(player);
92-
93- // List it
94- list_player(player);
95-}
96-
97-
98-void
99-w_found_players::hide_player(const prospective_joiner_info &player) {
100- found_players.push_back(player);
101-
102- unlist_player(player);
103-}
104-
105-
106-void
107-w_found_players::list_player(prospective_joiner_info &player) {
108- listed_players.push_back(player);
109- num_items = listed_players.size();
110- new_items();
111-}
112-
113-void w_found_players::update_player(prospective_joiner_info &player) {
114- unlist_player(player);
115- list_player(player);
116-}
117-
118-
119-void
120-w_found_players::unlist_player(const prospective_joiner_info &player) {
121- size_t theIndex = find_item_index_in_vector(player, listed_players);
122- if(theIndex == -1)
123- return;
124-
125- listed_players.erase(listed_players.begin() + theIndex);
126-
127- size_t old_top_item = top_item;
128-
129- num_items = listed_players.size();
130- new_items();
131-
132- // If the element deleted was the top item or before the top item, shift view up an item to compensate (if there is anything "up").
133- if(theIndex <= old_top_item && old_top_item > 0)
134- old_top_item--;
135-
136- // Reconcile overhang, if needed.
137- if(old_top_item + shown_items > num_items && num_items >= shown_items)
138- set_top_item(num_items - shown_items);
139- else
140- set_top_item(old_top_item);
141-}
142-
143-
144-void
145-w_found_players::item_selected() {
146- if(player_selected_callback != NULL)
147- player_selected_callback(this, listed_players[get_selection()]);
148-}
149-
150-
151-// ZZZ: this is pretty ugly, it assumes that the callback will remove players from the widget.
152-// Fortunately, that's the case currently. :)
153-void
154-w_found_players::callback_on_all_items() {
155- if(player_selected_callback != NULL) {
156- for (vector<prospective_joiner_info>::iterator it = listed_players.begin(); it != listed_players.end(); it++) {
157- player_selected_callback(this, *it);
158- }
159- }
160-}
161-
162-void
163-w_found_players::draw_item(vector<prospective_joiner_info>::const_iterator i, SDL_Surface *s, int16 x, int16 y, uint16 width, bool selected) const {
164- char theNameBuffer[SSLP_MAX_NAME_LENGTH + 12];
165-
166- pstrncpy((unsigned char*)theNameBuffer, (unsigned char*)(*i).name, SSLP_MAX_NAME_LENGTH - 1);
167- a1_p2cstr((unsigned char *) theNameBuffer);
168- if ((*i).gathering) {
169- strcat(theNameBuffer, " (gathering)");
170- }
171-
172- int computed_x = x + (width - text_width(theNameBuffer, font, style)) / 2;
173- int computed_y = y + font->get_ascent();
174-
175- //unsigned char text_length = (*i)->sslps_name[0];
176-
177- //if(text_length > SSLP_MAX_NAME_LENGTH - 1)
178- // text_length = SSLP_MAX_NAME_LENGTH - 1;
179- if ((*i).gathering) {
180- draw_text(s, theNameBuffer, computed_x, computed_y, get_theme_color(ITEM_WIDGET, DISABLED_STATE), font, style);
181- } else {
182- draw_text(s, /*&((*i)->sslps_name[1]), text_length,*/ theNameBuffer, computed_x, computed_y,
183- selected ? get_theme_color(ITEM_WIDGET, ACTIVE_STATE) : get_theme_color(ITEM_WIDGET, DEFAULT_STATE), font, style);
184- }
185-}
186-
187-////// w_players_in_game2 //////
188-
189-// I guess these should be computed more dynamically, but it wasn't obvious the best way to do that.
190-// These values work well for the standard player shapes, anyway.
191-enum {
192- kWPIG2Width = 600, // widget width
193- kWPIG2Height = 142, // widget height (actual height will differ if postgame_layout)
194- kMaxHeadroom = 53, // height above player origin (approx. navel) of tallest player shape
195- kNameOffset = 80, // how far below player origin baseline of player's name should appear
196- kNumNameOffsets = MAXIMUM_NUMBER_OF_PLAYERS, // used to resolve overlapping names
197- kNameMargin = 6, // names overlap if their edges are fewer than this many pixels apart
198- kNormalPlayerOffset = kMaxHeadroom,
199- kNormalNameTotalOffset = kNormalPlayerOffset + kNameOffset,
200-
201-// kPostgameTopMargin = 70, // how much extra space is at the top of widget in postgame layout
202- kPostgameTopMargin = 190, // For postgame layout without chat window, we can use a lot more space. (use 70 to coexist with full chat UI)
203- kPostgameBottomMargin = 6, // how much extra space is at the bottom of widget in postgame layout
204- kBarBottomOffset = 80, // how far below player origin score/kill bars should start
205- kBarWidth = 10, // how wide a kill/score bar should be
206- kBarOffsetX = 20, // how much to offset a bar so it won't draw directly on a player
207- kBevelSize = 2, // how much "depth effect" (in pixels around the border) bars have
208- kUseLegendThreshhold = 5, // with this many players or more, use legend for kills/deaths rather than print at bar labels
209- kPostgamePlayerOffset = kPostgameTopMargin + kMaxHeadroom,
210- kPostgameNameTotalOffset = kPostgamePlayerOffset + kNameOffset,
211- kBarBottomTotalOffset = kPostgamePlayerOffset + kBarBottomOffset,
212- kPostgameHeight = kPostgameTopMargin + kWPIG2Height + kPostgameBottomMargin
213-};
214-
215-/*
216-// These can't see postgame_layout. Duh. And the idea here was to avoid having the constants above
217-// in a header file (as would be needed for making inline methods) where they would force extra
218-// recompilation... burrito. Macros it is.
219-static inline int
220-get_player_y_offset() { return postgame_layout ? kPostgamePlayerOffset : kNormalPlayerOffset; }
221-
222-static inline int
223-get_name_y_offset() { return postgame_layout ? kPostgameNameTotalOffset : kNormalNameTotalOffset; }
224-*/
225-
226-#define get_player_y_offset() (postgame_layout ? kPostgamePlayerOffset : kNormalPlayerOffset)
227-#define get_name_y_offset() (postgame_layout ? kPostgameNameTotalOffset : kNormalNameTotalOffset)
228-
229-// Here I divide each piece of space into N pieces (where N is the number of things to draw)
230-// each item is drawn in the center of its space. This pitches them a little more widely than
231-// is used in the separately-drawn strategy.
232-// The computation used is (I from 0 to N-1, W is width) for the center:
233-// ((I + .5) / N) * W
234-// == WI + .5W / N
235-// == W*(2I + 1) / 2N
236-static inline int
237-get_wide_spaced_center_offset(int left_x, int available_width, size_t index, size_t num_items) {
238- return left_x + (((2 * (int)index + 1) * available_width) / (2 * (int)num_items));
239-}
240-
241-// for the left:
242-// I/N * W
243-// == WI/N
244-static inline int
245-get_wide_spaced_left_offset(int left_x, int available_width, size_t index, size_t num_items) {
246- return left_x + (((int)index * available_width) / (int)num_items);
247-}
248-
249-// width is easy...
250-// note though that the actual distances between left_offsets may vary slightly from this width due to rounding.
251-static inline int
252-get_wide_spaced_width(int available_width, size_t num_items) {
253- return available_width / (int)num_items;
254-}
255-
256-
257-// Horizontal layout centers single player at 1/2 the width; two players at 1/3 and 2/3; three at 1/4, 2/4, 3/4....
258-// Doing (I * W) / N rather than the more natural (I/N) * W may give more accurate results with integer math.
259-static inline int
260-get_close_spaced_center_offset(int left_x, int available_width, size_t index, size_t num_items) {
261- return left_x + ((((int)index + 1) * available_width) / ((int)num_items + 1));
262-}
263-
264-static inline int
265-get_close_spaced_width(int available_width, size_t num_items) {
266- return available_width / ((int)num_items + 1);
267-}
268-
269-
270-w_players_in_game2::w_players_in_game2(bool inPostgameLayout) :
271- widget(MESSAGE_WIDGET), displaying_actual_information(false), postgame_layout(inPostgameLayout),
272- draw_carnage_graph(false), num_valid_net_rankings(0), selected_player(NONE),
273- clump_players_by_team(false), draw_scores_not_carnage(false)
274-{
275- rect.w = kWPIG2Width;
276- rect.h = postgame_layout ? kPostgameHeight : kWPIG2Height;
277-
278- saved_min_width = rect.w;
279- saved_min_height = rect.h;
280-}
281-
282-
283-w_players_in_game2::~w_players_in_game2() {
284- clear_vector();
285-}
286-
287-
288-void
289-w_players_in_game2::update_display(bool inFromDynamicWorld /* default=false */) {
290- // Start over - wipe out our local player-storage
291- clear_vector();
292-
293- // Wipe out references to players through teams
294- for(int i = 0; i < NUMBER_OF_TEAM_COLORS; i++)
295- players_on_team[i].clear();
296-
297- // Find the number of players
298- int num_players;
299- if(inFromDynamicWorld)
300- num_players = dynamic_world->player_count;
301- else
302- num_players = displaying_actual_information ? NetGetNumberOfPlayers() : 0;
303-
304- // Fill in the entries
305- for(int i = 0; i < num_players; i++) {
306- player_entry2 thePlayerEntry;
307-
308- int thePlayerTeam;
309- int thePlayerColor;
310-
311- if(inFromDynamicWorld) {
312- // Get player information from dynamic_world
313- player_data* thePlayerData = get_player_data(i);
314-
315- // Copy the player name. We will store it as a cstring...
316- strncpy(thePlayerEntry.player_name, thePlayerData->name, MAXIMUM_PLAYER_NAME_LENGTH + 1);
317-
318- // Look up colors
319- thePlayerTeam = thePlayerData->team;
320- thePlayerColor = thePlayerData->color;
321- }
322- else {
323- // Get player information from topology
324- player_info* thePlayerInfo = (player_info*)NetGetPlayerData(i);
325-
326- // Alias the player entry's name field as a pstring
327- unsigned char* thePlayerEntryNameP = (unsigned char*) thePlayerEntry.player_name;
328-
329- // Copy the player name. We will store it as a cstring...
330- pstrncpy(thePlayerEntryNameP, thePlayerInfo->name, MAXIMUM_PLAYER_NAME_LENGTH + 1);
331-
332- // In-place conversion.
333- a1_p2cstr(thePlayerEntryNameP);
334-
335- // Look up colors
336- thePlayerTeam = thePlayerInfo->team;
337- thePlayerColor = thePlayerInfo->color;
338- }
339-
340- // Set the size of the text
341- thePlayerEntry.name_width = text_width(thePlayerEntry.player_name, font, style | styleShadow);
342-
343- // Get the pixel-color for the player's team (for drawing the name)
344- thePlayerEntry.name_pixel_color = get_dialog_player_color(thePlayerTeam);
345-
346- // Set up a player image for the player (funfun)
347- thePlayerEntry.player_image = new PlayerImage;
348- thePlayerEntry.player_image->setRandomFlatteringView();
349- thePlayerEntry.player_image->setPlayerColor(thePlayerColor);
350- thePlayerEntry.player_image->setTeamColor(thePlayerTeam);
351-
352- // Add the player to our local storage area
353- player_entries.push_back(thePlayerEntry);
354-
355- // Add a reference to the player through his team color
356- players_on_team[thePlayerTeam].push_back(i);
357- }
358-
359- dirty = true;
360-}
361-
362-
363-#if 0
364-// this is for testing
365-static const char* sTestingNames[] = {
366- "Doctor Burrito",
367- "Carnage Asada",
368- "Bongo Bob",
369- "The Napalm Man",
370- "The Big Lebowski",
371- "lala",
372- "Prof. Windsurf",
373- "<<<-ZED-<<<"
374-};
375-
376-void
377-w_players_in_game2::click(int, int) {
378- player_entry2 thePlayerEntry;
379-
380- // make up a name
381-/* int theNameLength = (local_random() % MAXIMUM_PLAYER_NAME_LENGTH) + 1;
382- for(int i = 0; i < theNameLength; i++)
383- thePlayerEntry.player_name[i] = 'a' + (local_random() % ('z' - 'a'));
384- thePlayerEntry.player_name[theNameLength] = '\0';
385-// strcpy(thePlayerEntry.player_name, "The Big Lebowski");
386-*/
387- strcpy(thePlayerEntry.player_name, sTestingNames[local_random() % 8]);
388-
389- // Set the size of the text
390- thePlayerEntry.name_width = text_width(thePlayerEntry.player_name, font, style);
391-
392- // Make up a team-color
393- int theTeamColor = local_random() % 8;
394-
395- // Get the pixel-color for the player's team (for drawing the name)
396- thePlayerEntry.name_pixel_color = get_dialog_player_color(theTeamColor);
397-
398- // Set up a player image for the player (funfun)
399- thePlayerEntry.player_image = new PlayerImage;
400- thePlayerEntry.player_image->setRandomFlatteringView();
401- thePlayerEntry.player_image->setTeamColor(theTeamColor);
402-
403- player_entries.push_back(thePlayerEntry);
404-
405- dirty = true;
406-}
407-#else // NOT 0
408-void
409-w_players_in_game2::click(int x, int) {
410- if(draw_carnage_graph) {
411-
412- if(clump_players_by_team) {
413- for(size_t i = 0; i < num_valid_net_rankings; i++) {
414- if(ABS(x - get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings))
415- < (get_wide_spaced_width(rect.w, num_valid_net_rankings) / 2))
416- {
417- if(element_clicked_callback != NULL)
418- element_clicked_callback(this, clump_players_by_team, draw_carnage_graph, draw_scores_not_carnage,
419- i, net_rankings[i].color);
420-
421- break;
422- }
423- }
424- }
425-
426- else {
427- for(size_t i = 0; i < num_valid_net_rankings; i++) {
428- if(ABS(x - get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings))
429- < (get_close_spaced_width(rect.w, num_valid_net_rankings) / 2))
430- {
431- if(element_clicked_callback != NULL)
432- element_clicked_callback(this, clump_players_by_team, draw_carnage_graph, draw_scores_not_carnage,
433- i, net_rankings[i].player_index);
434-
435- break;
436- }
437- }
438- }
439-
440- } // draw_carnage_graph
441-}
442-#endif // NOT 0
443-
444-// enable carnage reporting mode and set the data needed to draw a graph.
445-void
446-w_players_in_game2::set_graph_data(const net_rank* inRankings, int inNumRankings, int inSelectedPlayer,
447- bool inClumpPlayersByTeam, bool inDrawScoresNotCarnage)
448-{
449- draw_carnage_graph = true;
450- num_valid_net_rankings = inNumRankings;
451- selected_player = inSelectedPlayer;
452- clump_players_by_team = inClumpPlayersByTeam;
453- draw_scores_not_carnage = inDrawScoresNotCarnage;
454- memcpy(net_rankings, inRankings, inNumRankings * sizeof(net_rank));
455-
456- dirty = true;
457-}
458-
459-
460-void
461-w_players_in_game2::draw_player_icon(SDL_Surface* s, size_t rank_index, int center_x) const {
462- // Note, player images will not be re-fetched unless the brightness has *changed* since last draw.
463- PlayerImage* theImage = player_entries[net_rankings[rank_index].player_index].player_image;
464- if(selected_player != NONE && selected_player != rank_index)
465- theImage->setBrightness(.4f);
466- else
467- theImage->setBrightness(1.0f);
468-
469- theImage->drawAt(s, center_x, rect.y + get_player_y_offset());
470-}
471-
472-
473-void
474-w_players_in_game2::draw_player_icons_separately(SDL_Surface* s) const {
475- if(draw_carnage_graph) {
476- // Draw in sorted order (according to net_rankings)
477- for(size_t i = 0; i < num_valid_net_rankings; i++) {
478- int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
479-
480- draw_player_icon(s, i, center_x);
481- }
482- }
483- else {
484- // Draw in "natural order" (according to topology)
485- size_t theNumPlayers = player_entries.size();
486- for(size_t i = 0; i < theNumPlayers; i++) {
487- int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, theNumPlayers);
488- player_entries[i].player_image->drawAt(s, center_x, rect.y + get_player_y_offset());
489- }
490- }
491-} // draw_player_icons_separately
492-
493-
494-void
495-w_players_in_game2::draw_player_icons_clumped(SDL_Surface* s) const {
496- assert(draw_carnage_graph);
497-
498- int width_per_team = get_wide_spaced_width(rect.w, num_valid_net_rankings);
499-
500- // Walk through teams, drawing each batch.
501- for(size_t i = 0; i < num_valid_net_rankings; i++) {
502- int team_left_x = get_wide_spaced_left_offset(rect.x, rect.w, i, num_valid_net_rankings);
503-
504- size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
505-
506- assert(theNumberOfPlayersOnThisTeam > 0);
507-
508- // Walk through players on a team to draw a batch.
509- for(size_t j = 0; j < theNumberOfPlayersOnThisTeam; j++) {
510- int player_center_x = get_close_spaced_center_offset(team_left_x, width_per_team, j, theNumberOfPlayersOnThisTeam);
511-
512- // Note, player images will not be re-fetched unless the brightness has *changed* since last draw.
513- // Though Marathon does not let one view team vs team carnage (just total team carnage), I'm leaving
514- // the highlighting stuff here in case team view is later added.
515- PlayerImage* theImage = player_entries[players_on_team[net_rankings[i].color][j]].player_image;
516- if(selected_player != NONE && selected_player != i)
517- theImage->setBrightness(.4f);
518- else
519- theImage->setBrightness(1.0f);
520-
521- theImage->drawAt(s, player_center_x, rect.y + get_player_y_offset());
522- } // players
523- } // teams
524-} // draw_player_icons_clumped
525-
526-
527-void
528-w_players_in_game2::draw_player_names_separately(SDL_Surface* s, TextLayoutHelper& ioTextLayoutHelper) const {
529- // Now let's draw the names. Let's take care to offset names vertically if they would
530- // overlap (or come too close as defined by kNameMargin), so it's more readable.
531-
532- size_t theNumPlayers = draw_carnage_graph ? num_valid_net_rankings : player_entries.size();
533-
534- for(size_t i = 0; i < theNumPlayers; i++) {
535- int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, theNumPlayers);
536- const player_entry2* theEntry = draw_carnage_graph ? &player_entries[net_rankings[i].player_index] : &player_entries[i];
537- int name_x = center_x - (theEntry->name_width / 2);
538- int name_y = rect.y + get_name_y_offset();
539-
540- // Find a suitable vertical offset
541- name_y = ioTextLayoutHelper.reserveSpaceFor(name_x - kNameMargin / 2, theEntry->name_width + kNameMargin, name_y, font->get_line_height());
542-
543- draw_text(s, theEntry->player_name, name_x, name_y,
544- theEntry->name_pixel_color, font, style | styleShadow);
545- }
546-}
547-
548-
549-void
550-w_players_in_game2::draw_player_names_clumped(SDL_Surface* s, TextLayoutHelper& ioTextLayoutHelper) const {
551- // Now let's draw the names. Let's take care to offset names vertically if they would
552- // overlap (or come too close as defined by kNameMargin), so it's more readable.
553-
554- // Walk through teams, drawing each batch.
555- for(size_t i = 0; i < num_valid_net_rankings; i++) {
556- int team_center_x = get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
557-
558- size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
559-
560- assert(theNumberOfPlayersOnThisTeam > 0);
561-
562- // Walk through players on a team to draw a batch.
563- for(size_t j = 0; j < theNumberOfPlayersOnThisTeam; j++) {
564-
565- const player_entry2* theEntry = &(player_entries[players_on_team[net_rankings[i].color][j]]);
566- int name_x = team_center_x - (theEntry->name_width / 2);
567- int name_y = rect.y + get_name_y_offset();
568-
569- // Find a suitable vertical offset
570- name_y = ioTextLayoutHelper.reserveSpaceFor(name_x - kNameMargin/2, theEntry->name_width + kNameMargin,
571- name_y, font->get_line_height());
572-
573- draw_text(s, theEntry->player_name, name_x, name_y,
574- theEntry->name_pixel_color, font, style | styleShadow);
575- }
576- }
577-}
578-
579-
580-int
581-w_players_in_game2::find_maximum_bar_value() const {
582- int theMaxValue = INT_MIN;
583-
584- // We track min also to handle games with negative scores.
585- int theMinValue = INT_MAX;
586-
587- if(selected_player != NONE)
588- // This way, all player vs player graphs use the same scale.
589- theMaxValue = calculate_max_kills(num_valid_net_rankings);
590- else {
591- // Note this does the right thing for suicide bars as well.
592- if(draw_scores_not_carnage) {
593- for(size_t i = 0; i < num_valid_net_rankings; i++) {
594- if(net_rankings[i].game_ranking > theMaxValue)
595- theMaxValue = net_rankings[i].game_ranking;
596- if(net_rankings[i].game_ranking < theMinValue)
597- theMinValue = net_rankings[i].game_ranking;
598- }
599- } else {
600- for(size_t i = 0; i < num_valid_net_rankings; i++) {
601- if(net_rankings[i].kills > theMaxValue)
602- theMaxValue = net_rankings[i].kills;
603- if(net_rankings[i].deaths > theMaxValue)
604- theMaxValue = net_rankings[i].deaths;
605- }
606- }
607- }
608-
609- // If all values were nonpositive, and we had at least one negative, we
610- // return the (negative) value furthest from 0.
611- // The Mac version seems to do nothing of the sort - how can it possibly
612- // display correct bars for games with negative scores like "Tag"??
613- if(theMaxValue <= 0 && theMinValue < 0)
614- theMaxValue = theMinValue;
615-
616- return theMaxValue;
617-}
618-
619-struct bar_info {
620- int center_x;
621- int top_y;
622- uint32 pixel_color;
623- string label_text;
624-};
625-
626-void
627-w_players_in_game2::draw_bar_or_bars(SDL_Surface* s, size_t rank_index, int center_x, int maximum_value, vector<bar_info>& outBarInfos) const {
628- // Draw score bar
629- if(draw_scores_not_carnage) {
630- bar_info theBarInfo;
631- int theScore = net_rankings[rank_index].game_ranking;
632-
633- calculate_ranking_text_for_post_game(temporary, theScore);
634- theBarInfo.label_text = temporary; // this makes a copy
635-
636- draw_bar(s, center_x, _score_color, theScore, maximum_value, theBarInfo);
637-
638- // Don't draw a "0" score label
639- if(theScore != 0)
640- outBarInfos.push_back(theBarInfo);
641- }
642- else {
643- // Draw carnage bar(s)
644- if(rank_index == selected_player) {
645- // Draw suicides/friendly-fires
646- bar_info theBarInfo;
647-
648- char* theSuicidesFormat = TS_GetCString(strNET_STATS_STRINGS, strSUICIDES_STRING);
649- int theNumberOfSuicides = net_rankings[rank_index].kills;
650- sprintf(temporary, theSuicidesFormat, theNumberOfSuicides);
651- theBarInfo.label_text = temporary; // this makes a copy
652-
653- draw_bar(s, center_x, _suicide_color, theNumberOfSuicides, maximum_value, theBarInfo);
654-
655- // Don't push a "0" label.
656- if(theNumberOfSuicides > 0)
657- outBarInfos.push_back(theBarInfo);
658- }
659- else {
660- // Draw kills and deaths
661- int theNumKills = net_rankings[rank_index].kills;
662- int theNumDeaths = net_rankings[rank_index].deaths;
663-
664- // Get strings for labelling
665- const char* theKillsFormat;
666- const char* theDeathsFormat;
667- char theKillsString[32];
668- char theDeathsString[32];
669-
670- // If more than threshhold bar-pairs to draw, use short form with legend rather than normal (long) form.
671- theKillsFormat = num_valid_net_rankings >= kUseLegendThreshhold ? "%d" : TS_GetCString(strNET_STATS_STRINGS, strKILLS_STRING);
672- theDeathsFormat = num_valid_net_rankings >= kUseLegendThreshhold ? "%d" : TS_GetCString(strNET_STATS_STRINGS, strDEATHS_STRING);
673-
674- // Construct labels
675- sprintf(theKillsString, theKillsFormat, theNumKills);
676- sprintf(theDeathsString, theDeathsFormat, theNumDeaths);
677-
678- // Set up bar_infos
679- bar_info theKillsBarInfo;
680- bar_info theDeathsBarInfo;
681-
682- // Copy strings into bar_infos
683- theKillsBarInfo.label_text = theKillsString;
684- theDeathsBarInfo.label_text = theDeathsString;
685-
686- // Draw shorter bar in front - looks nicer
687- // If equal, draw kills in front
688- // Put shorter bar_info in vector first so its label doesn't "leapfrog" the taller bar label in case of conflict.
689- // Don't put "0"s into the vector.
690- if(theNumKills > theNumDeaths) {
691- // Deaths bar is shorter - draw it last
692- draw_bar(s, center_x - kBarWidth / 3, _kill_color, theNumKills, maximum_value, theKillsBarInfo);
693- draw_bar(s, center_x + kBarWidth / 3, _death_color, theNumDeaths, maximum_value, theDeathsBarInfo);
694-
695- if(theNumDeaths > 0)
696- outBarInfos.push_back(theDeathsBarInfo);
697- if(theNumKills > 0)
698- outBarInfos.push_back(theKillsBarInfo);
699- }
700- else {
701- // Kills bar is shorter or equal - draw it last
702- draw_bar(s, center_x + kBarWidth / 3, _death_color, theNumDeaths, maximum_value, theDeathsBarInfo);
703- draw_bar(s, center_x - kBarWidth / 3, _kill_color, theNumKills, maximum_value, theKillsBarInfo);
704-
705- if(theNumKills > 0)
706- outBarInfos.push_back(theKillsBarInfo);
707- if(theNumDeaths > 0)
708- outBarInfos.push_back(theDeathsBarInfo);
709- } // kills and deaths (not suicides)
710- } // carnage bars
711- } // !draw_scores_not_carnage (i.e. draw carnage)
712-} // draw_bar_or_bars
713-
714-
715-void
716-w_players_in_game2::draw_bars_separately(SDL_Surface* s, vector<bar_info>& outBarInfos) const {
717- // Find the largest value we'll be drawing, so we know how to scale our bars.
718- int theMaxValue = find_maximum_bar_value();
719-
720- // Draw the bars.
721- for(size_t i = 0; i < num_valid_net_rankings; i++) {
722- int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
723-
724- draw_bar_or_bars(s, i, center_x + kBarOffsetX, theMaxValue, outBarInfos);
725- } // walk through rankings
726-} // draw_bars_separately
727-
728-
729-void
730-w_players_in_game2::draw_bars_clumped(SDL_Surface* s, vector<bar_info>& outBarInfos) const {
731- // Find the largest value we'll be drawing, so we know how to scale our bars.
732- int theMaxValue = find_maximum_bar_value();
733-
734- // Walk through teams, drawing each batch.
735- for(size_t i = 0; i < num_valid_net_rankings; i++) {
736- int team_center_x = (int)(rect.x + ((2*i + 1) * rect.w) / (2 * num_valid_net_rankings));
737-
738- size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
739-
740- assert(theNumberOfPlayersOnThisTeam > 0);
741-
742- // We will offset if we would draw on top of a player (i.e. if num players is odd), else
743- // we will draw right smack in the middle.
744- int center_x = team_center_x;
745-
746- if(theNumberOfPlayersOnThisTeam % 2 == 1)
747- center_x += kBarOffsetX;
748-
749- draw_bar_or_bars(s, i, center_x, theMaxValue, outBarInfos);
750- } // walk through rankings
751-} // draw_bars_clumped
752-
753-
754-void
755-w_players_in_game2::draw_carnage_totals(SDL_Surface* s) const {
756- for(size_t i = 0; i < num_valid_net_rankings; i++) {
757- int center_x;
758- if(clump_players_by_team)
759- center_x = get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
760- else
761- center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
762-
763- // Draw carnage score for player/team (list -N for N suicides)
764- int thePlayerCarnageScore = (selected_player == i) ? -net_rankings[i].kills : net_rankings[i].kills - net_rankings[i].deaths;
765- if(thePlayerCarnageScore == 0)
766- strcpy(temporary, "0");
767- else
768- sprintf(temporary, "%+d", thePlayerCarnageScore);
769-
770- uint16 theBiggerFontStyle = 0;
771- font_info* theBiggerFont = get_theme_font(LABEL_WIDGET, theBiggerFontStyle);
772-
773- int theStringCenter = center_x - (text_width(temporary, theBiggerFont, theBiggerFontStyle | styleShadow) / 2);
774-
775- draw_text(s, temporary, theStringCenter, rect.y + rect.h - 1, SDL_MapRGB(s->format, 0xff, 0xff, 0xff),
776- theBiggerFont, theBiggerFontStyle | styleShadow);
777- } // walk through rankings
778-} // draw_carnage_totals
779-
780-
781-void
782-w_players_in_game2::draw_carnage_legend(SDL_Surface* s) const {
783- RGBColor theBrightestColor;
784- get_net_color(_kill_color, &theBrightestColor);
785-
786- RGBColor theMiddleColor;
787- theMiddleColor.red = (theBrightestColor.red * 7) / 10;
788- theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
789- theMiddleColor.green = (theBrightestColor.green * 7) / 10;
790-
791- uint32 thePixelColor = SDL_MapRGB(s->format, theMiddleColor.red >> 8, theMiddleColor.green >> 8, theMiddleColor.blue >> 8);
792-
793- draw_text(s, TS_GetCString(strNET_STATS_STRINGS, strKILLS_LEGEND), rect.x, rect.y + font->get_line_height(),
794- thePixelColor, font, style);
795-
796- get_net_color(_death_color, &theBrightestColor);
797-
798- theMiddleColor.red = (theBrightestColor.red * 7) / 10;
799- theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
800- theMiddleColor.green = (theBrightestColor.green * 7) / 10;
801-
802- thePixelColor = SDL_MapRGB(s->format, theMiddleColor.red >> 8, theMiddleColor.green >> 8, theMiddleColor.blue >> 8);
803-
804- draw_text(s, TS_GetCString(strNET_STATS_STRINGS, strDEATHS_LEGEND), rect.x, rect.y + 2 * font->get_line_height(),
805- thePixelColor, font, style);
806-}
807-
808-
809-void
810-w_players_in_game2::draw_bar_labels(SDL_Surface* s, const vector<bar_info>& inBarInfos, TextLayoutHelper& ioTextLayoutHelper) const {
811- size_t theNumberOfLabels = inBarInfos.size();
812-
813- for(size_t i = 0; i < theNumberOfLabels; i++) {
814- const bar_info& theBarInfo = inBarInfos[i];
815-
816- int theStringWidth = text_width(theBarInfo.label_text.c_str(), font, style | styleShadow);
817- int theTextX = theBarInfo.center_x - theStringWidth / 2;
818- int theBestY = ioTextLayoutHelper.reserveSpaceFor(theTextX - kNameMargin/2,
819- theStringWidth + kNameMargin, theBarInfo.top_y - 1, font->get_line_height());
820-
821- draw_text(s, theBarInfo.label_text.c_str(), theTextX, theBestY, theBarInfo.pixel_color, font, style | styleShadow);
822- }
823-} // draw_bar_labels
824-
825-
826-void
827-w_players_in_game2::draw(SDL_Surface* s) const {
828-// printf("widget top is %d, bottom is %d\n", rect.y, rect.y + rect.h);
829-
830- // Set clip rectangle so we don't color outside the lines
831- set_drawing_clip_rectangle(rect.y, rect.x, rect.y + rect.h, rect.x + rect.w);
832-
833-// Did some tests here - it seems that text drawing is clipped by this rectangle, but rect-filling
834-// and blitting are not. (at least, on the Mac OS X version of SDL. actually, in Win 98 too.)
835-// This means that little tiny chunks of pistol fire, feet, etc. can stick around if they are drawn
836-// outside the widget (because they won't be cleared away when the widget is redrawn). I'm surrounding
837-// that with a "Somebody Else's Problem" field for the time being.
838-// set_drawing_clip_rectangle(100, 300, 200, 400);
839-// printf("clipped at <%d %d %d %d>\n", rect.y, rect.x, rect.y + rect.h, rect.x + rect.w);
840-
841- // theTextLayoutHelper exists for the duration of the draw operation
842- // helps us draw bits of text that do not overlap one another.
843- TextLayoutHelper theTextLayoutHelper;
844-
845- // theBarInfos exists for the duration of the draw operation
846- // helps us plan our bar label placement early (at draw_bar time)
847- // but draw them late (at draw_bar_labels time).
848- vector<bar_info> theBarInfos;
849-
850- // We draw in this order:
851- // Player icons
852- // Graph bars
853- // Player names
854- // Carnage totals (nobody should overlap, so timing is arbitrary)
855- // Carnage legend
856- // Bar labels
857-
858- // This order is largely for back-to-front ordering. Bar labels and player names, since
859- // they are placed using a text layout helper, will not overlap... but we draw bar labels
860- // after player names anyway (which takes considerable extra effort, note) so that the names
861- // have "first dibs" on the coveted low-in-the-widget screen area. Bar labels that want space
862- // occupied by player names will have to "float up"... which looks nicer than making the names
863- // float up to give the bar labels space.
864-
865- // Draw actual content
866- if(clump_players_by_team) {
867- // draw player icons in clumps
868- draw_player_icons_clumped(s);
869-
870- if(draw_carnage_graph)
871- draw_bars_clumped(s, theBarInfos);
872-
873- draw_player_names_clumped(s, theTextLayoutHelper);
874- }
875- else {
876- // Draw all the player icons first, so icons don't obscure names
877- draw_player_icons_separately(s);
878-
879- if(draw_carnage_graph)
880- draw_bars_separately(s, theBarInfos);
881-
882- draw_player_names_separately(s, theTextLayoutHelper);
883- }
884-
885- if(draw_carnage_graph && !draw_scores_not_carnage) {
886- draw_carnage_totals(s);
887- if(num_valid_net_rankings >= kUseLegendThreshhold)
888- draw_carnage_legend(s);
889- }
890-
891- if(draw_carnage_graph)
892- draw_bar_labels(s, theBarInfos, theTextLayoutHelper);
893-
894- // Reset clipping rectangle
895- set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
896-}
897-
898-
899-void
900-w_players_in_game2::clear_vector() {
901- vector<player_entry2>::const_iterator i = player_entries.begin();
902- vector<player_entry2>::const_iterator end = player_entries.end();
903-
904- while(i != end) {
905- // Free the name buffers associated with the elements.
906- // I don't do this in a player_entry destructor because I'm afraid of freeing the name twice
907- // (once here, when the vector's entry is destroyed, and another time when thePlayerEntry
908- // above goes out of scope).
909- if(i->player_image != NULL)
910- delete i->player_image;
911-
912- i++;
913- }
914-
915- player_entries.clear();
916-}
917-
918-
919-void
920-w_players_in_game2::draw_bar(SDL_Surface* s, int inCenterX, int inBarColorIndex, int inBarValue, int inMaxValue, bar_info& outBarInfo) const {
921- if(inBarValue != 0) {
922- // Check that we'll draw a positive bar - value and max are either both positive or both negative.
923- if(inBarValue > 0)
924- assert(inMaxValue > 0);
925-
926- if(inBarValue < 0)
927- assert(inMaxValue < 0);
928-
929- // "- 1" leaves room for shadow style. Leave two line-heights so a kills and deaths at the top of widget resolve
930- // (thanks to TextLayoutHelper) and still have space to live.
931- int theMaximumBarHeight = kBarBottomTotalOffset - font->get_line_height() * 2 - 1;
932- int theBarHeight = (theMaximumBarHeight * inBarValue) / inMaxValue;
933-
934- SDL_Rect theBarRect;
935-
936- theBarRect.y = rect.y + kBarBottomTotalOffset - theBarHeight;
937- theBarRect.h = theBarHeight;
938- theBarRect.w = kBarWidth;
939- theBarRect.x = inCenterX - kBarWidth / 2;
940-
941- RGBColor theBrightestColor;
942- get_net_color(inBarColorIndex, &theBrightestColor);
943-
944- RGBColor theMiddleColor;
945- theMiddleColor.red = (theBrightestColor.red * 7) / 10;
946- theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
947- theMiddleColor.green = (theBrightestColor.green * 7) / 10;
948-
949- RGBColor theDarkestColor;
950- theDarkestColor.red = (theBrightestColor.red * 2) / 10;
951- theDarkestColor.blue = (theBrightestColor.blue * 2) / 10;
952- theDarkestColor.green = (theBrightestColor.green * 2) / 10;
953-
954- RGBColor* theRGBColor;
955- uint32 thePixelColor;
956-
957- // Draw the lightest part
958- theRGBColor = &theBrightestColor;
959- thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
960-
961- SDL_FillRect(s, &theBarRect, thePixelColor);
962-
963- // Draw the dark part
964- theRGBColor = &theDarkestColor;
965- thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
966-
967- SDL_Rect theDarkRect;
968- theDarkRect.x = theBarRect.x + theBarRect.w - kBevelSize;
969- theDarkRect.w = kBevelSize;
970- theDarkRect.y = theBarRect.y + kBevelSize;
971- theDarkRect.h = theBarRect.h - kBevelSize;
972-
973- if(theBarRect.h > kBevelSize)
974- SDL_FillRect(s, &theDarkRect, thePixelColor);
975-
976- // Draw the middle part
977- theRGBColor = &theMiddleColor;
978- thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
979-
980- SDL_Rect theMiddleRect;
981- theMiddleRect.x = theBarRect.x + kBevelSize;
982- theMiddleRect.w = theBarRect.w - 2 * kBevelSize;
983- theMiddleRect.y = theBarRect.y + kBevelSize;
984- theMiddleRect.h = theBarRect.h - kBevelSize;
985-
986- if(theBarRect.h > kBevelSize)
987- SDL_FillRect(s, &theMiddleRect, thePixelColor);
988-
989- // Capture bar information
990- outBarInfo.center_x = inCenterX;
991- outBarInfo.top_y = theBarRect.y;
992- outBarInfo.pixel_color = thePixelColor;
993- } // if(inBarValue > 0)
994-} // draw_bar
995-
996-
997-
998-
999-
1000-////// w_entry_point_selector //////
1001-
1002-void
1003-w_entry_point_selector::validateEntryPoint() {
1004- // Get the entry-point flags from the game type.
1005- int theAppropriateLevelTypeFlags = get_entry_point_flags_for_game_type(mGameType);
1006-
1007- mEntryPoints.clear();
1008-
1009- // OK, get the vector of entry points.
1010- get_entry_points(mEntryPoints, theAppropriateLevelTypeFlags);
1011-
1012- if(mEntryPoints.size() <= 0) {
1013- mEntryPoint.level_number = NONE;
1014- strcpy(mEntryPoint.level_name, "(no valid options)");
1015- mCurrentIndex = NONE;
1016- }
1017- else {
1018- unsigned i;
1019-
1020- for(i = 0; i < mEntryPoints.size(); i++) {
1021- if(mEntryPoints[i].level_number == mEntryPoint.level_number)
1022- break;
1023- }
1024-
1025- if(i == mEntryPoints.size()) {
1026- mEntryPoint = mEntryPoints[0];
1027- mCurrentIndex = 0;
1028- }
1029- else {
1030- mEntryPoint = mEntryPoints[i];
1031- mCurrentIndex = i;
1032- }
1033- }
1034-
1035- dirty = true;
1036-}
1037-
1038-void
1039-w_entry_point_selector::gotSelectedCallback(void* arg) {
1040- ((w_entry_point_selector*) arg)->gotSelected();
1041-}
1042-
1043-void
1044-w_entry_point_selector::gotSelected() {
1045- if(mEntryPoints.size() > 1) {
1046- dialog theDialog;
1047-
1048- vertical_placer *placer = new vertical_placer;
1049- placer->dual_add(new w_title("SELECT LEVEL"), theDialog);
1050-
1051- placer->add(new w_spacer(), true);
1052-
1053- FileSpecifier theFile(environment_preferences->map_file);
1054- char theName[256];
1055- theFile.GetName(theName);
1056- sprintf(temporary, "%s", theName);
1057- placer->dual_add(new w_static_text(temporary), theDialog);
1058-
1059- placer->add(new w_spacer(), true);
1060-
1061- w_levels* levels_w = new w_levels(mEntryPoints, &theDialog, 480, 16, mCurrentIndex, false);
1062- placer->dual_add(levels_w, theDialog);
1063-
1064- placer->add(new w_spacer(), true);
1065-
1066- sprintf(temporary, "%lu %s levels available",
1067- mEntryPoints.size(),
1068- TS_GetCString(kNetworkGameTypesStringSetID, mGameType)
1069- );
1070- placer->dual_add(new w_static_text(temporary), theDialog);
1071-
1072- placer->add(new w_spacer(), true);
1073-
1074- placer->dual_add(new w_button("CANCEL", dialog_cancel, &theDialog), theDialog);
1075-
1076- theDialog.set_widget_placer(placer);
1077-
1078- clear_screen();
1079-
1080- if(theDialog.run() == 0) {
1081- mCurrentIndex = levels_w->get_selection();
1082- mEntryPoint = mEntryPoints[mCurrentIndex];
1083- dirty = true;
1084- }
1085- }
1086-}
1087-
1088-void
1089-w_entry_point_selector::event(SDL_Event &e) {
1090- if (e.type == SDL_KEYDOWN) {
1091- if (e.key.keysym.sym == SDLK_LEFT || e.key.keysym.sym == SDLK_RIGHT) {
1092- size_t theNumberOfEntryPoints = mEntryPoints.size();
1093-
1094- if(theNumberOfEntryPoints > 1) {
1095- int theDesiredOffset = (e.key.keysym.sym == SDLK_LEFT) ? -1 : 1;
1096-
1097- mCurrentIndex = (mCurrentIndex + theNumberOfEntryPoints + theDesiredOffset)
1098- % theNumberOfEntryPoints;
1099-
1100- mEntryPoint = mEntryPoints[mCurrentIndex];
1101-
1102- dirty = true;
1103- play_dialog_sound(DIALOG_CLICK_SOUND);
1104- }
1105-
1106- e.type = SDL_NOEVENT; // Swallow event
1107- }
1108- }
1109-}
1110-
1111-#endif // !defined(DISABLE_NETWORKING)
1+/*
2+ * network_dialog_widgets_sdl.cpp
3+
4+ Copyright (C) 2001 and beyond by Woody Zenfell, III
5+ and the "Aleph One" developers.
6+
7+ This program is free software; you can redistribute it and/or modify
8+ it under the terms of the GNU General Public License as published by
9+ the Free Software Foundation; either version 3 of the License, or
10+ (at your option) any later version.
11+
12+ This program is distributed in the hope that it will be useful,
13+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ GNU General Public License for more details.
16+
17+ This license is contained in the file "COPYING",
18+ which is included with this source code; it is available online at
19+ http://www.gnu.org/licenses/gpl.html
20+
21+ * Implementation of network-dialog-specific widgets in the SDL dialog system.
22+ *
23+ * Created by Woody Zenfell, III on Fri Sep 28 2001.
24+ *
25+ * Mar 1, 2002 (Woody Zenfell): Added new w_entry_point_selector widget.
26+ */
27+
28+#if !defined(DISABLE_NETWORKING)
29+
30+#include "network_dialog_widgets_sdl.h"
31+
32+#include "screen_drawing.h"
33+#include "sdl_fonts.h"
34+#include "interface.h"
35+#include "network.h"
36+
37+// these next are for playing with shape-drawing
38+#include "player.h"
39+#include "HUDRenderer.h"
40+#include "shell.h"
41+#include "collection_definition.h"
42+
43+// here are some for w_entry_point_selector
44+#include "preferences.h"
45+#include "screen.h"
46+
47+// for TS_GetCString, get shared ref rather than copying string.
48+#include "TextStrings.h"
49+
50+#include "TextLayoutHelper.h"
51+
52+#include <string>
53+
54+
55+// jkvw: I'm putting this here because we only really want it for find_item_index_in_vecotr,
56+// and of course we shouldn't be doing that anyway :).
57+bool operator==(const prospective_joiner_info &left, const prospective_joiner_info &right)
58+{ return left.stream_id == right.stream_id; }
59+
60+
61+////// helper functions //////
62+// Actually, as it turns out, there should be a generic STL algorithm that does this, I think.
63+// Well, w_found_players ought to be using a set<> or similar anyway, much more natural.
64+// Shrug, this was what I came up with before I knew anything about STL, and I'm too lazy to change it.
65+template<class T>
66+static const size_t
67+find_item_index_in_vector(const T& inItem, const vector<T>& inVector) {
68+ typename vector<T>::const_iterator i = inVector.begin();
69+ typename vector<T>::const_iterator end = inVector.end();
70+ size_t index = 0;
71+
72+ while(i != end) {
73+ if(*i == inItem)
74+ return index;
75+
76+ index++;
77+ i++;
78+ }
79+
80+ // Didn't find it
81+ return -1;
82+}
83+
84+
85+
86+////// w_found_players //////
87+void
88+w_found_players::found_player(prospective_joiner_info &player) {
89+
90+ // Found one
91+ found_players.push_back(player);
92+
93+ // List it
94+ list_player(player);
95+}
96+
97+
98+void
99+w_found_players::hide_player(const prospective_joiner_info &player) {
100+ found_players.push_back(player);
101+
102+ unlist_player(player);
103+}
104+
105+
106+void
107+w_found_players::list_player(prospective_joiner_info &player) {
108+ listed_players.push_back(player);
109+ num_items = listed_players.size();
110+ new_items();
111+}
112+
113+void w_found_players::update_player(prospective_joiner_info &player) {
114+ unlist_player(player);
115+ list_player(player);
116+}
117+
118+
119+void
120+w_found_players::unlist_player(const prospective_joiner_info &player) {
121+ size_t theIndex = find_item_index_in_vector(player, listed_players);
122+ if(theIndex == -1)
123+ return;
124+
125+ listed_players.erase(listed_players.begin() + theIndex);
126+
127+ size_t old_top_item = top_item;
128+
129+ num_items = listed_players.size();
130+ new_items();
131+
132+ // If the element deleted was the top item or before the top item, shift view up an item to compensate (if there is anything "up").
133+ if(theIndex <= old_top_item && old_top_item > 0)
134+ old_top_item--;
135+
136+ // Reconcile overhang, if needed.
137+ if(old_top_item + shown_items > num_items && num_items >= shown_items)
138+ set_top_item(num_items - shown_items);
139+ else
140+ set_top_item(old_top_item);
141+}
142+
143+
144+void
145+w_found_players::item_selected() {
146+ if(player_selected_callback != NULL)
147+ player_selected_callback(this, listed_players[get_selection()]);
148+}
149+
150+
151+// ZZZ: this is pretty ugly, it assumes that the callback will remove players from the widget.
152+// Fortunately, that's the case currently. :)
153+void
154+w_found_players::callback_on_all_items() {
155+ if(player_selected_callback != NULL) {
156+ for (vector<prospective_joiner_info>::iterator it = listed_players.begin(); it != listed_players.end(); it++) {
157+ player_selected_callback(this, *it);
158+ }
159+ }
160+}
161+
162+void
163+w_found_players::draw_item(vector<prospective_joiner_info>::const_iterator i, SDL_Surface *s, int16 x, int16 y, uint16 width, bool selected) const {
164+ char theNameBuffer[SSLP_MAX_NAME_LENGTH + 12];
165+
166+ pstrncpy((unsigned char*)theNameBuffer, (unsigned char*)(*i).name, SSLP_MAX_NAME_LENGTH - 1);
167+ a1_p2cstr((unsigned char *) theNameBuffer);
168+ if ((*i).gathering) {
169+ strcat(theNameBuffer, " (gathering)");
170+ }
171+
172+ int computed_x = x + (width - text_width(theNameBuffer, font, style)) / 2;
173+ int computed_y = y + font->get_ascent();
174+
175+ //unsigned char text_length = (*i)->sslps_name[0];
176+
177+ //if(text_length > SSLP_MAX_NAME_LENGTH - 1)
178+ // text_length = SSLP_MAX_NAME_LENGTH - 1;
179+ if ((*i).gathering) {
180+ draw_text(s, theNameBuffer, computed_x, computed_y, get_theme_color(ITEM_WIDGET, DISABLED_STATE), font, style);
181+ } else {
182+ draw_text(s, /*&((*i)->sslps_name[1]), text_length,*/ theNameBuffer, computed_x, computed_y,
183+ selected ? get_theme_color(ITEM_WIDGET, ACTIVE_STATE) : get_theme_color(ITEM_WIDGET, DEFAULT_STATE), font, style);
184+ }
185+}
186+
187+////// w_players_in_game2 //////
188+
189+// I guess these should be computed more dynamically, but it wasn't obvious the best way to do that.
190+// These values work well for the standard player shapes, anyway.
191+enum {
192+ kWPIG2Width = 600, // widget width
193+ kWPIG2Height = 142, // widget height (actual height will differ if postgame_layout)
194+ kMaxHeadroom = 53, // height above player origin (approx. navel) of tallest player shape
195+ kNameOffset = 80, // how far below player origin baseline of player's name should appear
196+ kNumNameOffsets = MAXIMUM_NUMBER_OF_PLAYERS, // used to resolve overlapping names
197+ kNameMargin = 6, // names overlap if their edges are fewer than this many pixels apart
198+ kNormalPlayerOffset = kMaxHeadroom,
199+ kNormalNameTotalOffset = kNormalPlayerOffset + kNameOffset,
200+
201+// kPostgameTopMargin = 70, // how much extra space is at the top of widget in postgame layout
202+ kPostgameTopMargin = 190, // For postgame layout without chat window, we can use a lot more space. (use 70 to coexist with full chat UI)
203+ kPostgameBottomMargin = 6, // how much extra space is at the bottom of widget in postgame layout
204+ kBarBottomOffset = 80, // how far below player origin score/kill bars should start
205+ kBarWidth = 10, // how wide a kill/score bar should be
206+ kBarOffsetX = 20, // how much to offset a bar so it won't draw directly on a player
207+ kBevelSize = 2, // how much "depth effect" (in pixels around the border) bars have
208+ kUseLegendThreshhold = 5, // with this many players or more, use legend for kills/deaths rather than print at bar labels
209+ kPostgamePlayerOffset = kPostgameTopMargin + kMaxHeadroom,
210+ kPostgameNameTotalOffset = kPostgamePlayerOffset + kNameOffset,
211+ kBarBottomTotalOffset = kPostgamePlayerOffset + kBarBottomOffset,
212+ kPostgameHeight = kPostgameTopMargin + kWPIG2Height + kPostgameBottomMargin
213+};
214+
215+/*
216+// These can't see postgame_layout. Duh. And the idea here was to avoid having the constants above
217+// in a header file (as would be needed for making inline methods) where they would force extra
218+// recompilation... burrito. Macros it is.
219+static inline int
220+get_player_y_offset() { return postgame_layout ? kPostgamePlayerOffset : kNormalPlayerOffset; }
221+
222+static inline int
223+get_name_y_offset() { return postgame_layout ? kPostgameNameTotalOffset : kNormalNameTotalOffset; }
224+*/
225+
226+#define get_player_y_offset() (postgame_layout ? kPostgamePlayerOffset : kNormalPlayerOffset)
227+#define get_name_y_offset() (postgame_layout ? kPostgameNameTotalOffset : kNormalNameTotalOffset)
228+
229+// Here I divide each piece of space into N pieces (where N is the number of things to draw)
230+// each item is drawn in the center of its space. This pitches them a little more widely than
231+// is used in the separately-drawn strategy.
232+// The computation used is (I from 0 to N-1, W is width) for the center:
233+// ((I + .5) / N) * W
234+// == WI + .5W / N
235+// == W*(2I + 1) / 2N
236+static inline int
237+get_wide_spaced_center_offset(int left_x, int available_width, size_t index, size_t num_items) {
238+ return left_x + (((2 * (int)index + 1) * available_width) / (2 * (int)num_items));
239+}
240+
241+// for the left:
242+// I/N * W
243+// == WI/N
244+static inline int
245+get_wide_spaced_left_offset(int left_x, int available_width, size_t index, size_t num_items) {
246+ return left_x + (((int)index * available_width) / (int)num_items);
247+}
248+
249+// width is easy...
250+// note though that the actual distances between left_offsets may vary slightly from this width due to rounding.
251+static inline int
252+get_wide_spaced_width(int available_width, size_t num_items) {
253+ return available_width / (int)num_items;
254+}
255+
256+
257+// Horizontal layout centers single player at 1/2 the width; two players at 1/3 and 2/3; three at 1/4, 2/4, 3/4....
258+// Doing (I * W) / N rather than the more natural (I/N) * W may give more accurate results with integer math.
259+static inline int
260+get_close_spaced_center_offset(int left_x, int available_width, size_t index, size_t num_items) {
261+ return left_x + ((((int)index + 1) * available_width) / ((int)num_items + 1));
262+}
263+
264+static inline int
265+get_close_spaced_width(int available_width, size_t num_items) {
266+ return available_width / ((int)num_items + 1);
267+}
268+
269+
270+w_players_in_game2::w_players_in_game2(bool inPostgameLayout) :
271+ widget(MESSAGE_WIDGET), displaying_actual_information(false), postgame_layout(inPostgameLayout),
272+ draw_carnage_graph(false), num_valid_net_rankings(0), selected_player(NONE),
273+ clump_players_by_team(false), draw_scores_not_carnage(false)
274+{
275+ rect.w = kWPIG2Width;
276+ rect.h = postgame_layout ? kPostgameHeight : kWPIG2Height;
277+
278+ saved_min_width = rect.w;
279+ saved_min_height = rect.h;
280+}
281+
282+
283+w_players_in_game2::~w_players_in_game2() {
284+ clear_vector();
285+}
286+
287+
288+void
289+w_players_in_game2::update_display(bool inFromDynamicWorld /* default=false */) {
290+ // Start over - wipe out our local player-storage
291+ clear_vector();
292+
293+ // Wipe out references to players through teams
294+ for(int i = 0; i < NUMBER_OF_TEAM_COLORS; i++)
295+ players_on_team[i].clear();
296+
297+ // Find the number of players
298+ int num_players;
299+ if(inFromDynamicWorld)
300+ num_players = dynamic_world->player_count;
301+ else
302+ num_players = displaying_actual_information ? NetGetNumberOfPlayers() : 0;
303+
304+ // Fill in the entries
305+ for(int i = 0; i < num_players; i++) {
306+ player_entry2 thePlayerEntry;
307+
308+ int thePlayerTeam;
309+ int thePlayerColor;
310+
311+ if(inFromDynamicWorld) {
312+ // Get player information from dynamic_world
313+ player_data* thePlayerData = get_player_data(i);
314+
315+ // Copy the player name. We will store it as a cstring...
316+ strncpy(thePlayerEntry.player_name, thePlayerData->name, MAXIMUM_PLAYER_NAME_LENGTH + 1);
317+
318+ // Look up colors
319+ thePlayerTeam = thePlayerData->team;
320+ thePlayerColor = thePlayerData->color;
321+ }
322+ else {
323+ // Get player information from topology
324+ player_info* thePlayerInfo = (player_info*)NetGetPlayerData(i);
325+
326+ // Alias the player entry's name field as a pstring
327+ unsigned char* thePlayerEntryNameP = (unsigned char*) thePlayerEntry.player_name;
328+
329+ // Copy the player name. We will store it as a cstring...
330+ pstrncpy(thePlayerEntryNameP, thePlayerInfo->name, MAXIMUM_PLAYER_NAME_LENGTH + 1);
331+
332+ // In-place conversion.
333+ a1_p2cstr(thePlayerEntryNameP);
334+
335+ // Look up colors
336+ thePlayerTeam = thePlayerInfo->team;
337+ thePlayerColor = thePlayerInfo->color;
338+ }
339+
340+ // Set the size of the text
341+ thePlayerEntry.name_width = text_width(thePlayerEntry.player_name, font, style | styleShadow);
342+
343+ // Get the pixel-color for the player's team (for drawing the name)
344+ thePlayerEntry.name_pixel_color = get_dialog_player_color(thePlayerTeam);
345+
346+ // Set up a player image for the player (funfun)
347+ thePlayerEntry.player_image = new PlayerImage;
348+ thePlayerEntry.player_image->setRandomFlatteringView();
349+ thePlayerEntry.player_image->setPlayerColor(thePlayerColor);
350+ thePlayerEntry.player_image->setTeamColor(thePlayerTeam);
351+
352+ // Add the player to our local storage area
353+ player_entries.push_back(thePlayerEntry);
354+
355+ // Add a reference to the player through his team color
356+ players_on_team[thePlayerTeam].push_back(i);
357+ }
358+
359+ dirty = true;
360+}
361+
362+
363+#if 0
364+// this is for testing
365+static const char* sTestingNames[] = {
366+ "Doctor Burrito",
367+ "Carnage Asada",
368+ "Bongo Bob",
369+ "The Napalm Man",
370+ "The Big Lebowski",
371+ "lala",
372+ "Prof. Windsurf",
373+ "<<<-ZED-<<<"
374+};
375+
376+void
377+w_players_in_game2::click(int, int) {
378+ player_entry2 thePlayerEntry;
379+
380+ // make up a name
381+/* int theNameLength = (local_random() % MAXIMUM_PLAYER_NAME_LENGTH) + 1;
382+ for(int i = 0; i < theNameLength; i++)
383+ thePlayerEntry.player_name[i] = 'a' + (local_random() % ('z' - 'a'));
384+ thePlayerEntry.player_name[theNameLength] = '\0';
385+// strcpy(thePlayerEntry.player_name, "The Big Lebowski");
386+*/
387+ strcpy(thePlayerEntry.player_name, sTestingNames[local_random() % 8]);
388+
389+ // Set the size of the text
390+ thePlayerEntry.name_width = text_width(thePlayerEntry.player_name, font, style);
391+
392+ // Make up a team-color
393+ int theTeamColor = local_random() % 8;
394+
395+ // Get the pixel-color for the player's team (for drawing the name)
396+ thePlayerEntry.name_pixel_color = get_dialog_player_color(theTeamColor);
397+
398+ // Set up a player image for the player (funfun)
399+ thePlayerEntry.player_image = new PlayerImage;
400+ thePlayerEntry.player_image->setRandomFlatteringView();
401+ thePlayerEntry.player_image->setTeamColor(theTeamColor);
402+
403+ player_entries.push_back(thePlayerEntry);
404+
405+ dirty = true;
406+}
407+#else // NOT 0
408+void
409+w_players_in_game2::click(int x, int) {
410+ if(draw_carnage_graph) {
411+
412+ if(clump_players_by_team) {
413+ for(size_t i = 0; i < num_valid_net_rankings; i++) {
414+ if(ABS(x - get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings))
415+ < (get_wide_spaced_width(rect.w, num_valid_net_rankings) / 2))
416+ {
417+ if(element_clicked_callback != NULL)
418+ element_clicked_callback(this, clump_players_by_team, draw_carnage_graph, draw_scores_not_carnage,
419+ i, net_rankings[i].color);
420+
421+ break;
422+ }
423+ }
424+ }
425+
426+ else {
427+ for(size_t i = 0; i < num_valid_net_rankings; i++) {
428+ if(ABS(x - get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings))
429+ < (get_close_spaced_width(rect.w, num_valid_net_rankings) / 2))
430+ {
431+ if(element_clicked_callback != NULL)
432+ element_clicked_callback(this, clump_players_by_team, draw_carnage_graph, draw_scores_not_carnage,
433+ i, net_rankings[i].player_index);
434+
435+ break;
436+ }
437+ }
438+ }
439+
440+ } // draw_carnage_graph
441+}
442+#endif // NOT 0
443+
444+// enable carnage reporting mode and set the data needed to draw a graph.
445+void
446+w_players_in_game2::set_graph_data(const net_rank* inRankings, int inNumRankings, int inSelectedPlayer,
447+ bool inClumpPlayersByTeam, bool inDrawScoresNotCarnage)
448+{
449+ draw_carnage_graph = true;
450+ num_valid_net_rankings = inNumRankings;
451+ selected_player = inSelectedPlayer;
452+ clump_players_by_team = inClumpPlayersByTeam;
453+ draw_scores_not_carnage = inDrawScoresNotCarnage;
454+ memcpy(net_rankings, inRankings, inNumRankings * sizeof(net_rank));
455+
456+ dirty = true;
457+}
458+
459+
460+void
461+w_players_in_game2::draw_player_icon(SDL_Surface* s, size_t rank_index, int center_x) const {
462+ // Note, player images will not be re-fetched unless the brightness has *changed* since last draw.
463+ PlayerImage* theImage = player_entries[net_rankings[rank_index].player_index].player_image;
464+ if(selected_player != NONE && selected_player != rank_index)
465+ theImage->setBrightness(.4f);
466+ else
467+ theImage->setBrightness(1.0f);
468+
469+ theImage->drawAt(s, center_x, rect.y + get_player_y_offset());
470+}
471+
472+
473+void
474+w_players_in_game2::draw_player_icons_separately(SDL_Surface* s) const {
475+ if(draw_carnage_graph) {
476+ // Draw in sorted order (according to net_rankings)
477+ for(size_t i = 0; i < num_valid_net_rankings; i++) {
478+ int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
479+
480+ draw_player_icon(s, i, center_x);
481+ }
482+ }
483+ else {
484+ // Draw in "natural order" (according to topology)
485+ size_t theNumPlayers = player_entries.size();
486+ for(size_t i = 0; i < theNumPlayers; i++) {
487+ int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, theNumPlayers);
488+ player_entries[i].player_image->drawAt(s, center_x, rect.y + get_player_y_offset());
489+ }
490+ }
491+} // draw_player_icons_separately
492+
493+
494+void
495+w_players_in_game2::draw_player_icons_clumped(SDL_Surface* s) const {
496+ assert(draw_carnage_graph);
497+
498+ int width_per_team = get_wide_spaced_width(rect.w, num_valid_net_rankings);
499+
500+ // Walk through teams, drawing each batch.
501+ for(size_t i = 0; i < num_valid_net_rankings; i++) {
502+ int team_left_x = get_wide_spaced_left_offset(rect.x, rect.w, i, num_valid_net_rankings);
503+
504+ size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
505+
506+ assert(theNumberOfPlayersOnThisTeam > 0);
507+
508+ // Walk through players on a team to draw a batch.
509+ for(size_t j = 0; j < theNumberOfPlayersOnThisTeam; j++) {
510+ int player_center_x = get_close_spaced_center_offset(team_left_x, width_per_team, j, theNumberOfPlayersOnThisTeam);
511+
512+ // Note, player images will not be re-fetched unless the brightness has *changed* since last draw.
513+ // Though Marathon does not let one view team vs team carnage (just total team carnage), I'm leaving
514+ // the highlighting stuff here in case team view is later added.
515+ PlayerImage* theImage = player_entries[players_on_team[net_rankings[i].color][j]].player_image;
516+ if(selected_player != NONE && selected_player != i)
517+ theImage->setBrightness(.4f);
518+ else
519+ theImage->setBrightness(1.0f);
520+
521+ theImage->drawAt(s, player_center_x, rect.y + get_player_y_offset());
522+ } // players
523+ } // teams
524+} // draw_player_icons_clumped
525+
526+
527+void
528+w_players_in_game2::draw_player_names_separately(SDL_Surface* s, TextLayoutHelper& ioTextLayoutHelper) const {
529+ // Now let's draw the names. Let's take care to offset names vertically if they would
530+ // overlap (or come too close as defined by kNameMargin), so it's more readable.
531+
532+ size_t theNumPlayers = draw_carnage_graph ? num_valid_net_rankings : player_entries.size();
533+
534+ for(size_t i = 0; i < theNumPlayers; i++) {
535+ int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, theNumPlayers);
536+ const player_entry2* theEntry = draw_carnage_graph ? &player_entries[net_rankings[i].player_index] : &player_entries[i];
537+ int name_x = center_x - (theEntry->name_width / 2);
538+ int name_y = rect.y + get_name_y_offset();
539+
540+ // Find a suitable vertical offset
541+ name_y = ioTextLayoutHelper.reserveSpaceFor(name_x - kNameMargin / 2, theEntry->name_width + kNameMargin, name_y, font->get_line_height());
542+
543+ draw_text(s, theEntry->player_name, name_x, name_y,
544+ theEntry->name_pixel_color, font, style | styleShadow);
545+ }
546+}
547+
548+
549+void
550+w_players_in_game2::draw_player_names_clumped(SDL_Surface* s, TextLayoutHelper& ioTextLayoutHelper) const {
551+ // Now let's draw the names. Let's take care to offset names vertically if they would
552+ // overlap (or come too close as defined by kNameMargin), so it's more readable.
553+
554+ // Walk through teams, drawing each batch.
555+ for(size_t i = 0; i < num_valid_net_rankings; i++) {
556+ int team_center_x = get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
557+
558+ size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
559+
560+ assert(theNumberOfPlayersOnThisTeam > 0);
561+
562+ // Walk through players on a team to draw a batch.
563+ for(size_t j = 0; j < theNumberOfPlayersOnThisTeam; j++) {
564+
565+ const player_entry2* theEntry = &(player_entries[players_on_team[net_rankings[i].color][j]]);
566+ int name_x = team_center_x - (theEntry->name_width / 2);
567+ int name_y = rect.y + get_name_y_offset();
568+
569+ // Find a suitable vertical offset
570+ name_y = ioTextLayoutHelper.reserveSpaceFor(name_x - kNameMargin/2, theEntry->name_width + kNameMargin,
571+ name_y, font->get_line_height());
572+
573+ draw_text(s, theEntry->player_name, name_x, name_y,
574+ theEntry->name_pixel_color, font, style | styleShadow);
575+ }
576+ }
577+}
578+
579+
580+int
581+w_players_in_game2::find_maximum_bar_value() const {
582+ int theMaxValue = INT_MIN;
583+
584+ // We track min also to handle games with negative scores.
585+ int theMinValue = INT_MAX;
586+
587+ if(selected_player != NONE)
588+ // This way, all player vs player graphs use the same scale.
589+ theMaxValue = calculate_max_kills(num_valid_net_rankings);
590+ else {
591+ // Note this does the right thing for suicide bars as well.
592+ if(draw_scores_not_carnage) {
593+ for(size_t i = 0; i < num_valid_net_rankings; i++) {
594+ if(net_rankings[i].game_ranking > theMaxValue)
595+ theMaxValue = net_rankings[i].game_ranking;
596+ if(net_rankings[i].game_ranking < theMinValue)
597+ theMinValue = net_rankings[i].game_ranking;
598+ }
599+ } else {
600+ for(size_t i = 0; i < num_valid_net_rankings; i++) {
601+ if(net_rankings[i].kills > theMaxValue)
602+ theMaxValue = net_rankings[i].kills;
603+ if(net_rankings[i].deaths > theMaxValue)
604+ theMaxValue = net_rankings[i].deaths;
605+ }
606+ }
607+ }
608+
609+ // If all values were nonpositive, and we had at least one negative, we
610+ // return the (negative) value furthest from 0.
611+ // The Mac version seems to do nothing of the sort - how can it possibly
612+ // display correct bars for games with negative scores like "Tag"??
613+ if(theMaxValue <= 0 && theMinValue < 0)
614+ theMaxValue = theMinValue;
615+
616+ return theMaxValue;
617+}
618+
619+struct bar_info {
620+ int center_x;
621+ int top_y;
622+ uint32 pixel_color;
623+ string label_text;
624+};
625+
626+void
627+w_players_in_game2::draw_bar_or_bars(SDL_Surface* s, size_t rank_index, int center_x, int maximum_value, vector<bar_info>& outBarInfos) const {
628+ // Draw score bar
629+ if(draw_scores_not_carnage) {
630+ bar_info theBarInfo;
631+ int theScore = net_rankings[rank_index].game_ranking;
632+
633+ calculate_ranking_text_for_post_game(temporary, theScore);
634+ theBarInfo.label_text = temporary; // this makes a copy
635+
636+ draw_bar(s, center_x, _score_color, theScore, maximum_value, theBarInfo);
637+
638+ // Don't draw a "0" score label
639+ if(theScore != 0)
640+ outBarInfos.push_back(theBarInfo);
641+ }
642+ else {
643+ // Draw carnage bar(s)
644+ if(rank_index == selected_player) {
645+ // Draw suicides/friendly-fires
646+ bar_info theBarInfo;
647+
648+ char* theSuicidesFormat = TS_GetCString(strNET_STATS_STRINGS, strSUICIDES_STRING);
649+ int theNumberOfSuicides = net_rankings[rank_index].kills;
650+ sprintf(temporary, theSuicidesFormat, theNumberOfSuicides);
651+ theBarInfo.label_text = temporary; // this makes a copy
652+
653+ draw_bar(s, center_x, _suicide_color, theNumberOfSuicides, maximum_value, theBarInfo);
654+
655+ // Don't push a "0" label.
656+ if(theNumberOfSuicides > 0)
657+ outBarInfos.push_back(theBarInfo);
658+ }
659+ else {
660+ // Draw kills and deaths
661+ int theNumKills = net_rankings[rank_index].kills;
662+ int theNumDeaths = net_rankings[rank_index].deaths;
663+
664+ // Get strings for labelling
665+ const char* theKillsFormat;
666+ const char* theDeathsFormat;
667+ char theKillsString[32];
668+ char theDeathsString[32];
669+
670+ // If more than threshhold bar-pairs to draw, use short form with legend rather than normal (long) form.
671+ theKillsFormat = num_valid_net_rankings >= kUseLegendThreshhold ? "%d" : TS_GetCString(strNET_STATS_STRINGS, strKILLS_STRING);
672+ theDeathsFormat = num_valid_net_rankings >= kUseLegendThreshhold ? "%d" : TS_GetCString(strNET_STATS_STRINGS, strDEATHS_STRING);
673+
674+ // Construct labels
675+ sprintf(theKillsString, theKillsFormat, theNumKills);
676+ sprintf(theDeathsString, theDeathsFormat, theNumDeaths);
677+
678+ // Set up bar_infos
679+ bar_info theKillsBarInfo;
680+ bar_info theDeathsBarInfo;
681+
682+ // Copy strings into bar_infos
683+ theKillsBarInfo.label_text = theKillsString;
684+ theDeathsBarInfo.label_text = theDeathsString;
685+
686+ // Draw shorter bar in front - looks nicer
687+ // If equal, draw kills in front
688+ // Put shorter bar_info in vector first so its label doesn't "leapfrog" the taller bar label in case of conflict.
689+ // Don't put "0"s into the vector.
690+ if(theNumKills > theNumDeaths) {
691+ // Deaths bar is shorter - draw it last
692+ draw_bar(s, center_x - kBarWidth / 3, _kill_color, theNumKills, maximum_value, theKillsBarInfo);
693+ draw_bar(s, center_x + kBarWidth / 3, _death_color, theNumDeaths, maximum_value, theDeathsBarInfo);
694+
695+ if(theNumDeaths > 0)
696+ outBarInfos.push_back(theDeathsBarInfo);
697+ if(theNumKills > 0)
698+ outBarInfos.push_back(theKillsBarInfo);
699+ }
700+ else {
701+ // Kills bar is shorter or equal - draw it last
702+ draw_bar(s, center_x + kBarWidth / 3, _death_color, theNumDeaths, maximum_value, theDeathsBarInfo);
703+ draw_bar(s, center_x - kBarWidth / 3, _kill_color, theNumKills, maximum_value, theKillsBarInfo);
704+
705+ if(theNumKills > 0)
706+ outBarInfos.push_back(theKillsBarInfo);
707+ if(theNumDeaths > 0)
708+ outBarInfos.push_back(theDeathsBarInfo);
709+ } // kills and deaths (not suicides)
710+ } // carnage bars
711+ } // !draw_scores_not_carnage (i.e. draw carnage)
712+} // draw_bar_or_bars
713+
714+
715+void
716+w_players_in_game2::draw_bars_separately(SDL_Surface* s, vector<bar_info>& outBarInfos) const {
717+ // Find the largest value we'll be drawing, so we know how to scale our bars.
718+ int theMaxValue = find_maximum_bar_value();
719+
720+ // Draw the bars.
721+ for(size_t i = 0; i < num_valid_net_rankings; i++) {
722+ int center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
723+
724+ draw_bar_or_bars(s, i, center_x + kBarOffsetX, theMaxValue, outBarInfos);
725+ } // walk through rankings
726+} // draw_bars_separately
727+
728+
729+void
730+w_players_in_game2::draw_bars_clumped(SDL_Surface* s, vector<bar_info>& outBarInfos) const {
731+ // Find the largest value we'll be drawing, so we know how to scale our bars.
732+ int theMaxValue = find_maximum_bar_value();
733+
734+ // Walk through teams, drawing each batch.
735+ for(size_t i = 0; i < num_valid_net_rankings; i++) {
736+ int team_center_x = (int)(rect.x + ((2*i + 1) * rect.w) / (2 * num_valid_net_rankings));
737+
738+ size_t theNumberOfPlayersOnThisTeam = players_on_team[net_rankings[i].color].size();
739+
740+ assert(theNumberOfPlayersOnThisTeam > 0);
741+
742+ // We will offset if we would draw on top of a player (i.e. if num players is odd), else
743+ // we will draw right smack in the middle.
744+ int center_x = team_center_x;
745+
746+ if(theNumberOfPlayersOnThisTeam % 2 == 1)
747+ center_x += kBarOffsetX;
748+
749+ draw_bar_or_bars(s, i, center_x, theMaxValue, outBarInfos);
750+ } // walk through rankings
751+} // draw_bars_clumped
752+
753+
754+void
755+w_players_in_game2::draw_carnage_totals(SDL_Surface* s) const {
756+ for(size_t i = 0; i < num_valid_net_rankings; i++) {
757+ int center_x;
758+ if(clump_players_by_team)
759+ center_x = get_wide_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
760+ else
761+ center_x = get_close_spaced_center_offset(rect.x, rect.w, i, num_valid_net_rankings);
762+
763+ // Draw carnage score for player/team (list -N for N suicides)
764+ int thePlayerCarnageScore = (selected_player == i) ? -net_rankings[i].kills : net_rankings[i].kills - net_rankings[i].deaths;
765+ if(thePlayerCarnageScore == 0)
766+ strcpy(temporary, "0");
767+ else
768+ sprintf(temporary, "%+d", thePlayerCarnageScore);
769+
770+ uint16 theBiggerFontStyle = 0;
771+ font_info* theBiggerFont = get_theme_font(LABEL_WIDGET, theBiggerFontStyle);
772+
773+ int theStringCenter = center_x - (text_width(temporary, theBiggerFont, theBiggerFontStyle | styleShadow) / 2);
774+
775+ draw_text(s, temporary, theStringCenter, rect.y + rect.h - 1, SDL_MapRGB(s->format, 0xff, 0xff, 0xff),
776+ theBiggerFont, theBiggerFontStyle | styleShadow);
777+ } // walk through rankings
778+} // draw_carnage_totals
779+
780+
781+void
782+w_players_in_game2::draw_carnage_legend(SDL_Surface* s) const {
783+ RGBColor theBrightestColor;
784+ get_net_color(_kill_color, &theBrightestColor);
785+
786+ RGBColor theMiddleColor;
787+ theMiddleColor.red = (theBrightestColor.red * 7) / 10;
788+ theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
789+ theMiddleColor.green = (theBrightestColor.green * 7) / 10;
790+
791+ uint32 thePixelColor = SDL_MapRGB(s->format, theMiddleColor.red >> 8, theMiddleColor.green >> 8, theMiddleColor.blue >> 8);
792+
793+ draw_text(s, TS_GetCString(strNET_STATS_STRINGS, strKILLS_LEGEND), rect.x, rect.y + font->get_line_height(),
794+ thePixelColor, font, style);
795+
796+ get_net_color(_death_color, &theBrightestColor);
797+
798+ theMiddleColor.red = (theBrightestColor.red * 7) / 10;
799+ theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
800+ theMiddleColor.green = (theBrightestColor.green * 7) / 10;
801+
802+ thePixelColor = SDL_MapRGB(s->format, theMiddleColor.red >> 8, theMiddleColor.green >> 8, theMiddleColor.blue >> 8);
803+
804+ draw_text(s, TS_GetCString(strNET_STATS_STRINGS, strDEATHS_LEGEND), rect.x, rect.y + 2 * font->get_line_height(),
805+ thePixelColor, font, style);
806+}
807+
808+
809+void
810+w_players_in_game2::draw_bar_labels(SDL_Surface* s, const vector<bar_info>& inBarInfos, TextLayoutHelper& ioTextLayoutHelper) const {
811+ size_t theNumberOfLabels = inBarInfos.size();
812+
813+ for(size_t i = 0; i < theNumberOfLabels; i++) {
814+ const bar_info& theBarInfo = inBarInfos[i];
815+
816+ int theStringWidth = text_width(theBarInfo.label_text.c_str(), font, style | styleShadow);
817+ int theTextX = theBarInfo.center_x - theStringWidth / 2;
818+ int theBestY = ioTextLayoutHelper.reserveSpaceFor(theTextX - kNameMargin/2,
819+ theStringWidth + kNameMargin, theBarInfo.top_y - 1, font->get_line_height());
820+
821+ draw_text(s, theBarInfo.label_text.c_str(), theTextX, theBestY, theBarInfo.pixel_color, font, style | styleShadow);
822+ }
823+} // draw_bar_labels
824+
825+
826+void
827+w_players_in_game2::draw(SDL_Surface* s) const {
828+// printf("widget top is %d, bottom is %d\n", rect.y, rect.y + rect.h);
829+
830+ // Set clip rectangle so we don't color outside the lines
831+ set_drawing_clip_rectangle(rect.y, rect.x, rect.y + rect.h, rect.x + rect.w);
832+
833+// Did some tests here - it seems that text drawing is clipped by this rectangle, but rect-filling
834+// and blitting are not. (at least, on the Mac OS X version of SDL. actually, in Win 98 too.)
835+// This means that little tiny chunks of pistol fire, feet, etc. can stick around if they are drawn
836+// outside the widget (because they won't be cleared away when the widget is redrawn). I'm surrounding
837+// that with a "Somebody Else's Problem" field for the time being.
838+// set_drawing_clip_rectangle(100, 300, 200, 400);
839+// printf("clipped at <%d %d %d %d>\n", rect.y, rect.x, rect.y + rect.h, rect.x + rect.w);
840+
841+ // theTextLayoutHelper exists for the duration of the draw operation
842+ // helps us draw bits of text that do not overlap one another.
843+ TextLayoutHelper theTextLayoutHelper;
844+
845+ // theBarInfos exists for the duration of the draw operation
846+ // helps us plan our bar label placement early (at draw_bar time)
847+ // but draw them late (at draw_bar_labels time).
848+ vector<bar_info> theBarInfos;
849+
850+ // We draw in this order:
851+ // Player icons
852+ // Graph bars
853+ // Player names
854+ // Carnage totals (nobody should overlap, so timing is arbitrary)
855+ // Carnage legend
856+ // Bar labels
857+
858+ // This order is largely for back-to-front ordering. Bar labels and player names, since
859+ // they are placed using a text layout helper, will not overlap... but we draw bar labels
860+ // after player names anyway (which takes considerable extra effort, note) so that the names
861+ // have "first dibs" on the coveted low-in-the-widget screen area. Bar labels that want space
862+ // occupied by player names will have to "float up"... which looks nicer than making the names
863+ // float up to give the bar labels space.
864+
865+ // Draw actual content
866+ if(clump_players_by_team) {
867+ // draw player icons in clumps
868+ draw_player_icons_clumped(s);
869+
870+ if(draw_carnage_graph)
871+ draw_bars_clumped(s, theBarInfos);
872+
873+ draw_player_names_clumped(s, theTextLayoutHelper);
874+ }
875+ else {
876+ // Draw all the player icons first, so icons don't obscure names
877+ draw_player_icons_separately(s);
878+
879+ if(draw_carnage_graph)
880+ draw_bars_separately(s, theBarInfos);
881+
882+ draw_player_names_separately(s, theTextLayoutHelper);
883+ }
884+
885+ if(draw_carnage_graph && !draw_scores_not_carnage) {
886+ draw_carnage_totals(s);
887+ if(num_valid_net_rankings >= kUseLegendThreshhold)
888+ draw_carnage_legend(s);
889+ }
890+
891+ if(draw_carnage_graph)
892+ draw_bar_labels(s, theBarInfos, theTextLayoutHelper);
893+
894+ // Reset clipping rectangle
895+ set_drawing_clip_rectangle(SHRT_MIN, SHRT_MIN, SHRT_MAX, SHRT_MAX);
896+}
897+
898+
899+void
900+w_players_in_game2::clear_vector() {
901+ vector<player_entry2>::const_iterator i = player_entries.begin();
902+ vector<player_entry2>::const_iterator end = player_entries.end();
903+
904+ while(i != end) {
905+ // Free the name buffers associated with the elements.
906+ // I don't do this in a player_entry destructor because I'm afraid of freeing the name twice
907+ // (once here, when the vector's entry is destroyed, and another time when thePlayerEntry
908+ // above goes out of scope).
909+ if(i->player_image != NULL)
910+ delete i->player_image;
911+
912+ i++;
913+ }
914+
915+ player_entries.clear();
916+}
917+
918+
919+void
920+w_players_in_game2::draw_bar(SDL_Surface* s, int inCenterX, int inBarColorIndex, int inBarValue, int inMaxValue, bar_info& outBarInfo) const {
921+ if(inBarValue != 0) {
922+ // Check that we'll draw a positive bar - value and max are either both positive or both negative.
923+ if(inBarValue > 0)
924+ assert(inMaxValue > 0);
925+
926+ if(inBarValue < 0)
927+ assert(inMaxValue < 0);
928+
929+ // "- 1" leaves room for shadow style. Leave two line-heights so a kills and deaths at the top of widget resolve
930+ // (thanks to TextLayoutHelper) and still have space to live.
931+ int theMaximumBarHeight = kBarBottomTotalOffset - font->get_line_height() * 2 - 1;
932+ int theBarHeight = (theMaximumBarHeight * inBarValue) / inMaxValue;
933+
934+ SDL_Rect theBarRect;
935+
936+ theBarRect.y = rect.y + kBarBottomTotalOffset - theBarHeight;
937+ theBarRect.h = theBarHeight;
938+ theBarRect.w = kBarWidth;
939+ theBarRect.x = inCenterX - kBarWidth / 2;
940+
941+ RGBColor theBrightestColor;
942+ get_net_color(inBarColorIndex, &theBrightestColor);
943+
944+ RGBColor theMiddleColor;
945+ theMiddleColor.red = (theBrightestColor.red * 7) / 10;
946+ theMiddleColor.blue = (theBrightestColor.blue * 7) / 10;
947+ theMiddleColor.green = (theBrightestColor.green * 7) / 10;
948+
949+ RGBColor theDarkestColor;
950+ theDarkestColor.red = (theBrightestColor.red * 2) / 10;
951+ theDarkestColor.blue = (theBrightestColor.blue * 2) / 10;
952+ theDarkestColor.green = (theBrightestColor.green * 2) / 10;
953+
954+ RGBColor* theRGBColor;
955+ uint32 thePixelColor;
956+
957+ // Draw the lightest part
958+ theRGBColor = &theBrightestColor;
959+ thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
960+
961+ SDL_FillRect(s, &theBarRect, thePixelColor);
962+
963+ // Draw the dark part
964+ theRGBColor = &theDarkestColor;
965+ thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
966+
967+ SDL_Rect theDarkRect;
968+ theDarkRect.x = theBarRect.x + theBarRect.w - kBevelSize;
969+ theDarkRect.w = kBevelSize;
970+ theDarkRect.y = theBarRect.y + kBevelSize;
971+ theDarkRect.h = theBarRect.h - kBevelSize;
972+
973+ if(theBarRect.h > kBevelSize)
974+ SDL_FillRect(s, &theDarkRect, thePixelColor);
975+
976+ // Draw the middle part
977+ theRGBColor = &theMiddleColor;
978+ thePixelColor = SDL_MapRGB(s->format, theRGBColor->red >> 8, theRGBColor->green >> 8, theRGBColor->blue >> 8);
979+
980+ SDL_Rect theMiddleRect;
981+ theMiddleRect.x = theBarRect.x + kBevelSize;
982+ theMiddleRect.w = theBarRect.w - 2 * kBevelSize;
983+ theMiddleRect.y = theBarRect.y + kBevelSize;
984+ theMiddleRect.h = theBarRect.h - kBevelSize;
985+
986+ if(theBarRect.h > kBevelSize)
987+ SDL_FillRect(s, &theMiddleRect, thePixelColor);
988+
989+ // Capture bar information
990+ outBarInfo.center_x = inCenterX;
991+ outBarInfo.top_y = theBarRect.y;
992+ outBarInfo.pixel_color = thePixelColor;
993+ } // if(inBarValue > 0)
994+} // draw_bar
995+
996+
997+
998+
999+
1000+////// w_entry_point_selector //////
1001+
1002+void
1003+w_entry_point_selector::validateEntryPoint() {
1004+ // Get the entry-point flags from the game type.
1005+ int theAppropriateLevelTypeFlags = get_entry_point_flags_for_game_type(mGameType);
1006+
1007+ mEntryPoints.clear();
1008+
1009+ // OK, get the vector of entry points.
1010+ get_entry_points(mEntryPoints, theAppropriateLevelTypeFlags);
1011+
1012+ if(mEntryPoints.size() <= 0) {
1013+ mEntryPoint.level_number = NONE;
1014+ strcpy(mEntryPoint.level_name, "(no valid options)");
1015+ mCurrentIndex = NONE;
1016+ }
1017+ else {
1018+ unsigned i;
1019+
1020+ for(i = 0; i < mEntryPoints.size(); i++) {
1021+ if(mEntryPoints[i].level_number == mEntryPoint.level_number)
1022+ break;
1023+ }
1024+
1025+ if(i == mEntryPoints.size()) {
1026+ mEntryPoint = mEntryPoints[0];
1027+ mCurrentIndex = 0;
1028+ }
1029+ else {
1030+ mEntryPoint = mEntryPoints[i];
1031+ mCurrentIndex = i;
1032+ }
1033+ }
1034+
1035+ dirty = true;
1036+}
1037+
1038+void
1039+w_entry_point_selector::gotSelectedCallback(void* arg) {
1040+ ((w_entry_point_selector*) arg)->gotSelected();
1041+}
1042+
1043+void
1044+w_entry_point_selector::gotSelected() {
1045+ if(mEntryPoints.size() > 1) {
1046+ dialog theDialog;
1047+
1048+ vertical_placer *placer = new vertical_placer;
1049+ placer->dual_add(new w_title("SELECT LEVEL"), theDialog);
1050+
1051+ placer->add(new w_spacer(), true);
1052+
1053+ FileSpecifier theFile(environment_preferences->map_file);
1054+ char theName[256];
1055+ theFile.GetName(theName);
1056+ sprintf(temporary, "%s", theName);
1057+ placer->dual_add(new w_static_text(temporary), theDialog);
1058+
1059+ placer->add(new w_spacer(), true);
1060+
1061+ w_levels* levels_w = new w_levels(mEntryPoints, &theDialog, 480, 16, mCurrentIndex, false);
1062+ placer->dual_add(levels_w, theDialog);
1063+
1064+ placer->add(new w_spacer(), true);
1065+
1066+ sprintf(temporary, "%lu %s levels available",
1067+ mEntryPoints.size(),
1068+ TS_GetCString(kNetworkGameTypesStringSetID, mGameType)
1069+ );
1070+ placer->dual_add(new w_static_text(temporary), theDialog);
1071+
1072+ placer->add(new w_spacer(), true);
1073+
1074+ placer->dual_add(new w_button("CANCEL", dialog_cancel, &theDialog), theDialog);
1075+
1076+ theDialog.set_widget_placer(placer);
1077+
1078+ clear_screen();
1079+
1080+ if(theDialog.run() == 0) {
1081+ mCurrentIndex = levels_w->get_selection();
1082+ mEntryPoint = mEntryPoints[mCurrentIndex];
1083+ dirty = true;
1084+ }
1085+ }
1086+}
1087+
1088+void
1089+w_entry_point_selector::event(SDL_Event &e) {
1090+ if (e.type == SDL_KEYDOWN) {
1091+ if (e.key.keysym.sym == SDLK_LEFT || e.key.keysym.sym == SDLK_RIGHT) {
1092+ size_t theNumberOfEntryPoints = mEntryPoints.size();
1093+
1094+ if(theNumberOfEntryPoints > 1) {
1095+ int theDesiredOffset = (e.key.keysym.sym == SDLK_LEFT) ? -1 : 1;
1096+
1097+ mCurrentIndex = (mCurrentIndex + theNumberOfEntryPoints + theDesiredOffset)
1098+ % theNumberOfEntryPoints;
1099+
1100+ mEntryPoint = mEntryPoints[mCurrentIndex];
1101+
1102+ dirty = true;
1103+ play_dialog_sound(DIALOG_CLICK_SOUND);
1104+ }
1105+
1106+ e.type = SDL_NOEVENT; // Swallow event
1107+ }
1108+ }
1109+}
1110+
1111+#endif // !defined(DISABLE_NETWORKING)
--- marathon/trunk/Source_Files/Network/network_capabilities.cpp (revision 527)
+++ marathon/trunk/Source_Files/Network/network_capabilities.cpp (revision 528)
@@ -1,34 +1,34 @@
1-/* network_capabilities.cpp
2-
3- Copyright (C) 2005 and beyond by Gregory Smith
4- and the "Aleph One" developers.
5-
6- This program is free software; you can redistribute it and/or modify
7- it under the terms of the GNU General Public License as published by
8- the Free Software Foundation; either version 3 of the License, or
9- (at your option) any later version.
10-
11- This program is distributed in the hope that it will be useful,
12- but WITHOUT ANY WARRANTY; without even the implied warranty of
13- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14- GNU General Public License for more details.
15-
16- This license is contained in the file "COPYING",
17- which is included with this source code; it is available online at
18- http://www.gnu.org/licenses/gpl.html
19-
20-*/
21-
22-#include "network_capabilities.h"
23-
24-const string Capabilities::kGameworld = "Gameworld";
25-const string Capabilities::kStar = "Star";
26-const string Capabilities::kRing = "Ring";
27-const string Capabilities::kLua = "Lua";
28-const string Capabilities::kSpeex = "Speex";
29-const string Capabilities::kGatherable = "Gatherable";
30-const string Capabilities::kZippedData = "ZippedData";
31-const string Capabilities::kNetworkStats = "NetworkStats";
32-const string Capabilities::kRugby = "Rugby";
33-
34-
1+/* network_capabilities.cpp
2+
3+ Copyright (C) 2005 and beyond by Gregory Smith
4+ and the "Aleph One" developers.
5+
6+ This program is free software; you can redistribute it and/or modify
7+ it under the terms of the GNU General Public License as published by
8+ the Free Software Foundation; either version 3 of the License, or
9+ (at your option) any later version.
10+
11+ This program is distributed in the hope that it will be useful,
12+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ GNU General Public License for more details.
15+
16+ This license is contained in the file "COPYING",
17+ which is included with this source code; it is available online at
18+ http://www.gnu.org/licenses/gpl.html
19+
20+*/
21+
22+#include "network_capabilities.h"
23+
24+const string Capabilities::kGameworld = "Gameworld";
25+const string Capabilities::kStar = "Star";
26+const string Capabilities::kRing = "Ring";
27+const string Capabilities::kLua = "Lua";
28+const string Capabilities::kSpeex = "Speex";
29+const string Capabilities::kGatherable = "Gatherable";
30+const string Capabilities::kZippedData = "ZippedData";
31+const string Capabilities::kNetworkStats = "NetworkStats";
32+const string Capabilities::kRugby = "Rugby";
33+
34+
--- marathon/trunk/Source_Files/Network/network_microphone_shared.cpp (revision 527)
+++ marathon/trunk/Source_Files/Network/network_microphone_shared.cpp (revision 528)
@@ -1,311 +1,311 @@
1-/*
2- * network_microphone_shared.cpp
3- * created for Marathon: Aleph One <http://source.bungie.org/>
4-
5- Copyright (C) 2002 and beyond by Woody Zenfell, III
6- and the "Aleph One" developers.
7-
8- This program is free software; you can redistribute it and/or modify
9- it under the terms of the GNU General Public License as published by
10- the Free Software Foundation; either version 3 of the License, or
11- (at your option) any later version.
12-
13- This program is distributed in the hope that it will be useful,
14- but WITHOUT ANY WARRANTY; without even the implied warranty of
15- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16- GNU General Public License for more details.
17-
18- This license is contained in the file "COPYING",
19- which is included with this source code; it is available online at
20- http://www.gnu.org/licenses/gpl.html
21-
22- * The code in this file is licensed to you under the GNU GPL. As the copyright holder,
23- * however, I reserve the right to use this code as I see fit, without being bound by the
24- * GPL's terms. This exemption is not intended to apply to modified versions of this file -
25- * if I were to use a modified version, I would be a licensee of whomever modified it, and
26- * thus must observe the GPL terms.
27- *
28- * Utility/support routines for various platforms' network microphone implementations.
29- *
30- * Created by woody Jan 31, 2003, largely from code originally in network_microphone_sdl_win32.cpp.
31- *
32- * May 28, 2003 (Gregory Smith):
33- * Speex audio compression
34- */
35-
36-#if !defined(DISABLE_NETWORKING)
37-
38-#include "cseries.h"
39-#include "network_speaker_sdl.h"
40-#include "network_data_formats.h"
41-#include "network_distribution_types.h"
42-
43-#include <algorithm> // for STL pair<> type
44-
45-#ifdef SPEEX
46-#include "speex/speex.h"
47-#include "preferences.h"
48-#include "network_speex.h"
49-#endif
50-
51-#include "map.h" // _force_unique_teams!
52-
53-using namespace std;
54-
55-#ifdef DEBUG
56-// For testing: don't send audio on the network - pass it directly to network_speaker.
57-//#define MICROPHONE_LOCAL_LOOPBACK
58-#endif
59-
60-enum {
61- kNetworkAudioSamplesPerPacket = 800,
62-};
63-
64-static uint32 sSamplesPerSecond = 0;
65-static uint32 sStereo = false;
66-static uint32 s16Bit = false;
67-static int32 sCaptureBytesPerPacket = 0;
68-static uint32 sNumberOfBytesPerSample = 0;
69-
70-static _fixed rate = 0;
71-static _fixed counter = 0;
72-
73-bool announce_microphone_capture_format(uint32 inSamplesPerSecond, bool inStereo, bool in16Bit)
74-{
75- sSamplesPerSecond = inSamplesPerSecond;
76- sStereo = inStereo;
77- s16Bit = in16Bit;
78-
79- rate = (_fixed) (FIXED_ONE * (float) inSamplesPerSecond / (float) kNetworkAudioSampleRate);
80- counter = 0;
81-
82- sNumberOfBytesPerSample = (inStereo ? 2 : 1) * (in16Bit ? 2 : 1);
83- sCaptureBytesPerPacket = (uint32) ((rate * kNetworkAudioSamplesPerPacket) >> 16) * sNumberOfBytesPerSample;
84-
85- return true;
86-};
87-
88-
89-int32
90-get_capture_byte_count_per_packet() {
91- // Catch folks who call us without specifying a rate first
92- assert(sSamplesPerSecond > 0);
93-
94- return sCaptureBytesPerPacket;
95-}
96-
97-template<bool stereo, bool sixteenBit>
98-inline int16 getSample(void *data)
99-{
100- if (sixteenBit)
101- {
102- if (stereo)
103- {
104- return ((int16 *) data)[0] >> 1 + ((int16 *)data)[1] >> 1;
105- }
106- else
107- {
108- return ((int16 *) data)[0];
109- }
110- }
111- else
112- {
113- if (stereo)
114- {
115- return ((((uint8 *) data)[0] / 2 + ((uint8 *) data)[1] / 2) - 128) << 8;
116- }
117- else
118- {
119- return (((uint8 *) data)[0] - 128) << 8;
120- }
121- }
122-}
123-
124-#ifdef SPEEX
125-template <bool stereo, bool sixteenBit>
126-int32 copy_and_speex_encode_template(uint8* outStorage, void* inStorage, int32 inCount)
127-{
128- static int16 frame[160];
129- static int storedSamples = 0;
130- int bytesWritten = 0;
131-
132- while (inCount > 0)
133- {
134- int16 left_sample = getSample<stereo, sixteenBit>(inStorage);
135-
136- if (inCount > sNumberOfBytesPerSample)
137- {
138- uint8* data = (uint8 *) inStorage + sNumberOfBytesPerSample;
139- int16 right_sample = getSample<stereo, sixteenBit>(data);
140-
141- int32 sample = left_sample + (((right_sample - left_sample) * (counter & 0xffff)) >> 16);
142- frame[storedSamples++] = (int16) sample;
143- }
144- else
145- {
146- frame[storedSamples++] = left_sample;
147- }
148-
149- // advance data
150- counter += rate;
151- if (counter >= 0x10000)
152- {
153- int count = counter >> 16;
154- counter &= 0xffff;
155- inStorage = (uint8 *) inStorage + sNumberOfBytesPerSample * count;
156- inCount -= sNumberOfBytesPerSample * count;
157- }
158-
159- if (storedSamples >= 160)
160- {
161- // encode the frame
162- speex_bits_reset(&gEncoderBits);
163- speex_encode_int(gEncoderState, frame, &gEncoderBits);
164- uint8 nbytes = speex_bits_write(&gEncoderBits, reinterpret_cast<char *>(outStorage) + 1, 200);
165- bytesWritten += nbytes + 1;
166- // first put the size of this frame in storage
167- *(outStorage) = nbytes;
168- outStorage += nbytes + 1;
169-
170- storedSamples = 0;
171- }
172-
173- }
174-
175- return bytesWritten;
176-}
177-
178-int32 copy_and_speex_encode(uint8* outStorage, void *inStorage, int32 inCount)
179-{
180- if (sStereo)
181- {
182- if (s16Bit)
183- {
184- return copy_and_speex_encode_template<true, true>(outStorage, inStorage, inCount);
185- }
186- else
187- {
188- return copy_and_speex_encode_template<true, false>(outStorage, inStorage, inCount);
189- }
190- }
191- else
192- {
193- if (s16Bit)
194- {
195- return copy_and_speex_encode_template<false, true>(outStorage, inStorage, inCount);
196- }
197- else
198- {
199- return copy_and_speex_encode_template<false, false>(outStorage, inStorage, inCount);
200- }
201- }
202-}
203-
204-static void
205-send_audio_data(void* inData, short inSize) {
206-#ifdef MICROPHONE_LOCAL_LOOPBACK
207-#include "network_sound.h"
208- received_network_audio_proc(inData, inSize, 0);
209-#else
210- NetDistributeInformation(kNewNetworkAudioDistributionTypeID, inData, inSize, false, !(GET_GAME_OPTIONS() & _force_unique_teams));
211-#endif
212-}
213-
214-#endif
215-
216-int32
217-copy_and_send_audio_data(uint8* inFirstChunkReadPosition, int32 inFirstChunkBytesRemaining,
218- uint8* inSecondChunkReadPosition, int32 inSecondChunkBytesRemaining,
219- bool inForceSend) {
220-
221- // Make sure the capture format has been announced to us
222- assert(sSamplesPerSecond > 0);
223-
224- // caller better not be splitting chunks up mid-sample
225- if (inFirstChunkBytesRemaining % sNumberOfBytesPerSample)
226- {
227- inFirstChunkBytesRemaining -= (inFirstChunkBytesRemaining % sNumberOfBytesPerSample);
228- assert(inSecondChunkBytesRemaining == 0);
229- }
230-
231- if (inSecondChunkBytesRemaining % sNumberOfBytesPerSample)
232- {
233- inSecondChunkBytesRemaining -= (inSecondChunkBytesRemaining % sNumberOfBytesPerSample);
234- }
235-
236- // Let runtime system worry about allocating and freeing the buffer (and don't do it on the stack).
237- // assume Speex will not encode kNetworkAudioSamplesPerPacket samples to be larger than kNetworkAudioSamplesPerPacket * kNetworkAudioBytesPerFrame!
238- static uint8 sOutgoingPacketBuffer[kNetworkAudioSamplesPerPacket * kNetworkAudioBytesPerFrame + SIZEOF_network_audio_header];
239-
240- network_audio_header theHeader;
241-#ifdef SPEEX
242- theHeader.mReserved = 1;
243- theHeader.mFlags = 0;
244-
245- network_audio_header_NET* theHeader_NET = (network_audio_header_NET*) sOutgoingPacketBuffer;
246-
247- netcpy(theHeader_NET, &theHeader);
248-
249- uint8* theOutgoingAudioData = &sOutgoingPacketBuffer[SIZEOF_network_audio_header];
250-
251- // Do the copying and sending
252- pair<int32, int32> theBytesConsumed;
253- int32 theTotalCaptureBytesConsumed = 0;
254-
255- // Keep sending if we have data and either we're squeezing out the last drop or we have a packet's-worth.
256- while(inFirstChunkBytesRemaining >= static_cast<int32>(sNumberOfBytesPerSample) &&
257- (inForceSend || inFirstChunkBytesRemaining + inSecondChunkBytesRemaining >= (int32)sCaptureBytesPerPacket)) {
258-
259- int captureBytesToCopy = std::min(inFirstChunkBytesRemaining, sCaptureBytesPerPacket);
260- int bytesCopied = copy_and_speex_encode(theOutgoingAudioData, inFirstChunkReadPosition, captureBytesToCopy);
261-
262- theTotalCaptureBytesConsumed += captureBytesToCopy;
263-
264- // If there's space left in the packet and we have a second chunk, start on it.
265- if(captureBytesToCopy < sCaptureBytesPerPacket && inSecondChunkBytesRemaining > 0) {
266- int secondCaptureBytesToCopy = std::min(sCaptureBytesPerPacket - captureBytesToCopy, inSecondChunkBytesRemaining);
267-
268- int secondBytesCopied = copy_and_speex_encode(&theOutgoingAudioData[bytesCopied], inSecondChunkReadPosition, secondCaptureBytesToCopy);
269- theTotalCaptureBytesConsumed += secondCaptureBytesToCopy;
270-
271- send_audio_data((void *) sOutgoingPacketBuffer,
272- SIZEOF_network_audio_header + bytesCopied + secondBytesCopied);
273-
274- // Update the second chunk position and length
275- inSecondChunkReadPosition += secondCaptureBytesToCopy;
276- inSecondChunkBytesRemaining -= secondCaptureBytesToCopy;
277- }
278- // Else, either we've filled up a packet or exhausted the buffer (or both).
279- else {
280- send_audio_data((void *) sOutgoingPacketBuffer, SIZEOF_network_audio_header + bytesCopied);
281- }
282-
283- // Update the first chunk position and length
284- inFirstChunkReadPosition += captureBytesToCopy;
285- inFirstChunkBytesRemaining -= captureBytesToCopy;
286- }
287-
288- // Now, the first chunk is exhausted. See if there's any left in the second chunk. Same rules apply.
289- while(inSecondChunkBytesRemaining >= static_cast<int32>(sNumberOfBytesPerSample) &&
290- (inForceSend || inSecondChunkBytesRemaining >= (int32) sCaptureBytesPerPacket)) {
291-
292- int captureBytesToCopy = std::min(inSecondChunkBytesRemaining, sCaptureBytesPerPacket);
293-
294- int bytesCopied = copy_and_speex_encode(theOutgoingAudioData, inSecondChunkReadPosition, captureBytesToCopy);
295-
296- theTotalCaptureBytesConsumed += captureBytesToCopy;
297-
298- send_audio_data((void *) sOutgoingPacketBuffer, SIZEOF_network_audio_header + bytesCopied);
299-
300- inSecondChunkReadPosition += captureBytesToCopy;
301- inSecondChunkBytesRemaining -= captureBytesToCopy;
302- }
303-
304- return theTotalCaptureBytesConsumed;
305-#else
306- return inFirstChunkBytesRemaining + inSecondChunkBytesRemaining; // eat the entire thing, we only support speex
307-#endif
308-}
309-
310-#endif // !defined(DISABLE_NETWORKING)
311-
1+/*
2+ * network_microphone_shared.cpp
3+ * created for Marathon: Aleph One <http://source.bungie.org/>
4+
5+ Copyright (C) 2002 and beyond by Woody Zenfell, III
6+ and the "Aleph One" developers.
7+
8+ This program is free software; you can redistribute it and/or modify
9+ it under the terms of the GNU General Public License as published by
10+ the Free Software Foundation; either version 3 of the License, or
11+ (at your option) any later version.
12+
13+ This program is distributed in the hope that it will be useful,
14+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+ GNU General Public License for more details.
17+
18+ This license is contained in the file "COPYING",
19+ which is included with this source code; it is available online at
20+ http://www.gnu.org/licenses/gpl.html
21+
22+ * The code in this file is licensed to you under the GNU GPL. As the copyright holder,
23+ * however, I reserve the right to use this code as I see fit, without being bound by the
24+ * GPL's terms. This exemption is not intended to apply to modified versions of this file -
25+ * if I were to use a modified version, I would be a licensee of whomever modified it, and
26+ * thus must observe the GPL terms.
27+ *
28+ * Utility/support routines for various platforms' network microphone implementations.
29+ *
30+ * Created by woody Jan 31, 2003, largely from code originally in network_microphone_sdl_win32.cpp.
31+ *
32+ * May 28, 2003 (Gregory Smith):
33+ * Speex audio compression
34+ */
35+
36+#if !defined(DISABLE_NETWORKING)
37+
38+#include "cseries.h"
39+#include "network_speaker_sdl.h"
40+#include "network_data_formats.h"
41+#include "network_distribution_types.h"
42+
43+#include <algorithm> // for STL pair<> type
44+
45+#ifdef SPEEX
46+#include "speex/speex.h"
47+#include "preferences.h"
48+#include "network_speex.h"
49+#endif
50+
51+#include "map.h" // _force_unique_teams!
52+
53+using namespace std;
54+
55+#ifdef DEBUG
56+// For testing: don't send audio on the network - pass it directly to network_speaker.
57+//#define MICROPHONE_LOCAL_LOOPBACK
58+#endif
59+
60+enum {
61+ kNetworkAudioSamplesPerPacket = 800,
62+};
63+
64+static uint32 sSamplesPerSecond = 0;
65+static uint32 sStereo = false;
66+static uint32 s16Bit = false;
67+static int32 sCaptureBytesPerPacket = 0;
68+static uint32 sNumberOfBytesPerSample = 0;
69+
70+static _fixed rate = 0;
71+static _fixed counter = 0;
72+
73+bool announce_microphone_capture_format(uint32 inSamplesPerSecond, bool inStereo, bool in16Bit)
74+{
75+ sSamplesPerSecond = inSamplesPerSecond;
76+ sStereo = inStereo;
77+ s16Bit = in16Bit;
78+
79+ rate = (_fixed) (FIXED_ONE * (float) inSamplesPerSecond / (float) kNetworkAudioSampleRate);
80+ counter = 0;
81+
82+ sNumberOfBytesPerSample = (inStereo ? 2 : 1) * (in16Bit ? 2 : 1);
83+ sCaptureBytesPerPacket = (uint32) ((rate * kNetworkAudioSamplesPerPacket) >> 16) * sNumberOfBytesPerSample;
84+
85+ return true;
86+};
87+
88+
89+int32
90+get_capture_byte_count_per_packet() {
91+ // Catch folks who call us without specifying a rate first
92+ assert(sSamplesPerSecond > 0);
93+
94+ return sCaptureBytesPerPacket;
95+}
96+
97+template<bool stereo, bool sixteenBit>
98+inline int16 getSample(void *data)
99+{
100+ if (sixteenBit)
101+ {
102+ if (stereo)
103+ {
104+ return ((int16 *) data)[0] >> 1 + ((int16 *)data)[1] >> 1;
105+ }
106+ else
107+ {
108+ return ((int16 *) data)[0];
109+ }
110+ }
111+ else
112+ {
113+ if (stereo)
114+ {
115+ return ((((uint8 *) data)[0] / 2 + ((uint8 *) data)[1] / 2) - 128) << 8;
116+ }
117+ else
118+ {
119+ return (((uint8 *) data)[0] - 128) << 8;
120+ }
121+ }
122+}
123+
124+#ifdef SPEEX
125+template <bool stereo, bool sixteenBit>
126+int32 copy_and_speex_encode_template(uint8* outStorage, void* inStorage, int32 inCount)
127+{
128+ static int16 frame[160];
129+ static int storedSamples = 0;
130+ int bytesWritten = 0;
131+
132+ while (inCount > 0)
133+ {
134+ int16 left_sample = getSample<stereo, sixteenBit>(inStorage);
135+
136+ if (inCount > sNumberOfBytesPerSample)
137+ {
138+ uint8* data = (uint8 *) inStorage + sNumberOfBytesPerSample;
139+ int16 right_sample = getSample<stereo, sixteenBit>(data);
140+
141+ int32 sample = left_sample + (((right_sample - left_sample) * (counter & 0xffff)) >> 16);
142+ frame[storedSamples++] = (int16) sample;
143+ }
144+ else
145+ {
146+ frame[storedSamples++] = left_sample;
147+ }
148+
149+ // advance data
150+ counter += rate;
151+ if (counter >= 0x10000)
152+ {
153+ int count = counter >> 16;
154+ counter &= 0xffff;
155+ inStorage = (uint8 *) inStorage + sNumberOfBytesPerSample * count;
156+ inCount -= sNumberOfBytesPerSample * count;
157+ }
158+
159+ if (storedSamples >= 160)
160+ {
161+ // encode the frame
162+ speex_bits_reset(&gEncoderBits);
163+ speex_encode_int(gEncoderState, frame, &gEncoderBits);
164+ uint8 nbytes = speex_bits_write(&gEncoderBits, reinterpret_cast<char *>(outStorage) + 1, 200);
165+ bytesWritten += nbytes + 1;
166+ // first put the size of this frame in storage
167+ *(outStorage) = nbytes;
168+ outStorage += nbytes + 1;
169+
170+ storedSamples = 0;
171+ }
172+
173+ }
174+
175+ return bytesWritten;
176+}
177+
178+int32 copy_and_speex_encode(uint8* outStorage, void *inStorage, int32 inCount)
179+{
180+ if (sStereo)
181+ {
182+ if (s16Bit)
183+ {
184+ return copy_and_speex_encode_template<true, true>(outStorage, inStorage, inCount);
185+ }
186+ else
187+ {
188+ return copy_and_speex_encode_template<true, false>(outStorage, inStorage, inCount);
189+ }
190+ }
191+ else
192+ {
193+ if (s16Bit)
194+ {
195+ return copy_and_speex_encode_template<false, true>(outStorage, inStorage, inCount);
196+ }
197+ else
198+ {
199+ return copy_and_speex_encode_template<false, false>(outStorage, inStorage, inCount);
200+ }
201+ }
202+}
203+
204+static void
205+send_audio_data(void* inData, short inSize) {
206+#ifdef MICROPHONE_LOCAL_LOOPBACK
207+#include "network_sound.h"
208+ received_network_audio_proc(inData, inSize, 0);
209+#else
210+ NetDistributeInformation(kNewNetworkAudioDistributionTypeID, inData, inSize, false, !(GET_GAME_OPTIONS() & _force_unique_teams));
211+#endif
212+}
213+
214+#endif
215+
216+int32
217+copy_and_send_audio_data(uint8* inFirstChunkReadPosition, int32 inFirstChunkBytesRemaining,
218+ uint8* inSecondChunkReadPosition, int32 inSecondChunkBytesRemaining,
219+ bool inForceSend) {
220+
221+ // Make sure the capture format has been announced to us
222+ assert(sSamplesPerSecond > 0);
223+
224+ // caller better not be splitting chunks up mid-sample
225+ if (inFirstChunkBytesRemaining % sNumberOfBytesPerSample)
226+ {
227+ inFirstChunkBytesRemaining -= (inFirstChunkBytesRemaining % sNumberOfBytesPerSample);
228+ assert(inSecondChunkBytesRemaining == 0);
229+ }
230+
231+ if (inSecondChunkBytesRemaining % sNumberOfBytesPerSample)
232+ {
233+ inSecondChunkBytesRemaining -= (inSecondChunkBytesRemaining % sNumberOfBytesPerSample);
234+ }
235+
236+ // Let runtime system worry about allocating and freeing the buffer (and don't do it on the stack).
237+ // assume Speex will not encode kNetworkAudioSamplesPerPacket samples to be larger than kNetworkAudioSamplesPerPacket * kNetworkAudioBytesPerFrame!
238+ static uint8 sOutgoingPacketBuffer[kNetworkAudioSamplesPerPacket * kNetworkAudioBytesPerFrame + SIZEOF_network_audio_header];
239+
240+ network_audio_header theHeader;
241+#ifdef SPEEX
242+ theHeader.mReserved = 1;
243+ theHeader.mFlags = 0;
244+
245+ network_audio_header_NET* theHeader_NET = (network_audio_header_NET*) sOutgoingPacketBuffer;
246+
247+ netcpy(theHeader_NET, &theHeader);
248+
249+ uint8* theOutgoingAudioData = &sOutgoingPacketBuffer[SIZEOF_network_audio_header];
250+
251+ // Do the copying and sending
252+ pair<int32, int32> theBytesConsumed;
253+ int32 theTotalCaptureBytesConsumed = 0;
254+
255+ // Keep sending if we have data and either we're squeezing out the last drop or we have a packet's-worth.
256+ while(inFirstChunkBytesRemaining >= static_cast<int32>(sNumberOfBytesPerSample) &&
257+ (inForceSend || inFirstChunkBytesRemaining + inSecondChunkBytesRemaining >= (int32)sCaptureBytesPerPacket)) {
258+
259+ int captureBytesToCopy = std::min(inFirstChunkBytesRemaining, sCaptureBytesPerPacket);
260+ int bytesCopied = copy_and_speex_encode(theOutgoingAudioData, inFirstChunkReadPosition, captureBytesToCopy);
261+
262+ theTotalCaptureBytesConsumed += captureBytesToCopy;
263+
264+ // If there's space left in the packet and we have a second chunk, start on it.
265+ if(captureBytesToCopy < sCaptureBytesPerPacket && inSecondChunkBytesRemaining > 0) {
266+ int secondCaptureBytesToCopy = std::min(sCaptureBytesPerPacket - captureBytesToCopy, inSecondChunkBytesRemaining);
267+
268+ int secondBytesCopied = copy_and_speex_encode(&theOutgoingAudioData[bytesCopied], inSecondChunkReadPosition, secondCaptureBytesToCopy);
269+ theTotalCaptureBytesConsumed += secondCaptureBytesToCopy;
270+
271+ send_audio_data((void *) sOutgoingPacketBuffer,
272+ SIZEOF_network_audio_header + bytesCopied + secondBytesCopied);
273+
274+ // Update the second chunk position and length
275+ inSecondChunkReadPosition += secondCaptureBytesToCopy;
276+ inSecondChunkBytesRemaining -= secondCaptureBytesToCopy;
277+ }
278+ // Else, either we've filled up a packet or exhausted the buffer (or both).
279+ else {
280+ send_audio_data((void *) sOutgoingPacketBuffer, SIZEOF_network_audio_header + bytesCopied);
281+ }
282+
283+ // Update the first chunk position and length
284+ inFirstChunkReadPosition += captureBytesToCopy;
285+ inFirstChunkBytesRemaining -= captureBytesToCopy;
286+ }
287+
288+ // Now, the first chunk is exhausted. See if there's any left in the second chunk. Same rules apply.
289+ while(inSecondChunkBytesRemaining >= static_cast<int32>(sNumberOfBytesPerSample) &&
290+ (inForceSend || inSecondChunkBytesRemaining >= (int32) sCaptureBytesPerPacket)) {
291+
292+ int captureBytesToCopy = std::min(inSecondChunkBytesRemaining, sCaptureBytesPerPacket);
293+
294+ int bytesCopied = copy_and_speex_encode(theOutgoingAudioData, inSecondChunkReadPosition, captureBytesToCopy);
295+
296+ theTotalCaptureBytesConsumed += captureBytesToCopy;
297+
298+ send_audio_data((void *) sOutgoingPacketBuffer, SIZEOF_network_audio_header + bytesCopied);
299+
300+ inSecondChunkReadPosition += captureBytesToCopy;
301+ inSecondChunkBytesRemaining -= captureBytesToCopy;
302+ }
303+
304+ return theTotalCaptureBytesConsumed;
305+#else
306+ return inFirstChunkBytesRemaining + inSecondChunkBytesRemaining; // eat the entire thing, we only support speex
307+#endif
308+}
309+
310+#endif // !defined(DISABLE_NETWORKING)
311+
--- marathon/trunk/Source_Files/Network/network_audio_shared.h (revision 527)
+++ marathon/trunk/Source_Files/Network/network_audio_shared.h (revision 528)
@@ -1,62 +1,62 @@
1-/*
2- * network_audio_shared.h
3- * created for Marathon: Aleph One <http://source.bungie.org/>
4-
5- Copyright (C) 2002 and beyond by Woody Zenfell, III
6- and the "Aleph One" developers.
7-
8- This program is free software; you can redistribute it and/or modify
9- it under the terms of the GNU General Public License as published by
10- the Free Software Foundation; either version 3 of the License, or
11- (at your option) any later version.
12-
13- This program is distributed in the hope that it will be useful,
14- but WITHOUT ANY WARRANTY; without even the implied warranty of
15- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16- GNU General Public License for more details.
17-
18- This license is contained in the file "COPYING",
19- which is included with this source code; it is available online at
20- http://www.gnu.org/licenses/gpl.html
21-
22- * The code in this file is licensed to you under the GNU GPL. As the copyright holder,
23- * however, I reserve the right to use this code as I see fit, without being bound by the
24- * GPL's terms. This exemption is not intended to apply to modified versions of this file -
25- * if I were to use a modified version, I would be a licensee of whomever modified it, and
26- * thus must observe the GPL terms.
27- *
28- * Stuff shared internally between the network microphone and network speaker code.
29- *
30- * Created by woody Feb 1, 2003, largely from stuff in network_speaker_sdl.h.
31- */
32-
33-#ifndef NETWORK_AUDIO_SHARED_H
34-#define NETWORK_AUDIO_SHARED_H
35-
36-#include "cseries.h"
37-
38-// In-memory header for sound data
39-struct network_audio_header {
40- uint32 mReserved; // Should be 0 for now, later maybe use a FourCharCode for format? shrug
41- uint32 mFlags;
42-};
43-
44-// For network_audio_header::mFlags
45-enum {
46- kNetworkAudioForTeammatesOnlyFlag = 0x01
47-};
48-
49-
50-// Useful information about the network audio
51-// Note: at present, the microphone routines ignore these settings and target
52-// 11025 unsigned 8-bit mono. If you want to change formats you'll need to edit those
53-// routines too (hopefully to make them more general ;) ). Also you'll want to somehow
54-// differentiate your format from this one (use a Flag, or value in Reserved, or an
55-// entirely new distribution type, or something).
56-const bool kNetworkAudioIsStereo = false;
57-const bool kNetworkAudioIs16Bit = true;
58-const bool kNetworkAudioIsSigned8Bit = false;
59-const int kNetworkAudioSampleRate = 8000;
60-const int kNetworkAudioBytesPerFrame = (kNetworkAudioIs16Bit ? 2 : 1) * (kNetworkAudioIsStereo ? 2 : 1);
61-
62-#endif // NETWORK_AUDIO_SHARED_H
1+/*
2+ * network_audio_shared.h
3+ * created for Marathon: Aleph One <http://source.bungie.org/>
4+
5+ Copyright (C) 2002 and beyond by Woody Zenfell, III
6+ and the "Aleph One" developers.
7+
8+ This program is free software; you can redistribute it and/or modify
9+ it under the terms of the GNU General Public License as published by
10+ the Free Software Foundation; either version 3 of the License, or
11+ (at your option) any later version.
12+
13+ This program is distributed in the hope that it will be useful,
14+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+ GNU General Public License for more details.
17+
18+ This license is contained in the file "COPYING",
19+ which is included with this source code; it is available online at
20+ http://www.gnu.org/licenses/gpl.html
21+
22+ * The code in this file is licensed to you under the GNU GPL. As the copyright holder,
23+ * however, I reserve the right to use this code as I see fit, without being bound by the
24+ * GPL's terms. This exemption is not intended to apply to modified versions of this file -
25+ * if I were to use a modified version, I would be a licensee of whomever modified it, and
26+ * thus must observe the GPL terms.
27+ *
28+ * Stuff shared internally between the network microphone and network speaker code.
29+ *
30+ * Created by woody Feb 1, 2003, largely from stuff in network_speaker_sdl.h.
31+ */
32+
33+#ifndef NETWORK_AUDIO_SHARED_H
34+#define NETWORK_AUDIO_SHARED_H
35+
36+#include "cseries.h"
37+
38+// In-memory header for sound data
39+struct network_audio_header {
40+ uint32 mReserved; // Should be 0 for now, later maybe use a FourCharCode for format? shrug
41+ uint32 mFlags;
42+};
43+
44+// For network_audio_header::mFlags
45+enum {
46+ kNetworkAudioForTeammatesOnlyFlag = 0x01
47+};
48+
49+
50+// Useful information about the network audio
51+// Note: at present, the microphone routines ignore these settings and target
52+// 11025 unsigned 8-bit mono. If you want to change formats you'll need to edit those
53+// routines too (hopefully to make them more general ;) ). Also you'll want to somehow
54+// differentiate your format from this one (use a Flag, or value in Reserved, or an
55+// entirely new distribution type, or something).
56+const bool kNetworkAudioIsStereo = false;
57+const bool kNetworkAudioIs16Bit = true;
58+const bool kNetworkAudioIsSigned8Bit = false;
59+const int kNetworkAudioSampleRate = 8000;
60+const int kNetworkAudioBytesPerFrame = (kNetworkAudioIs16Bit ? 2 : 1) * (kNetworkAudioIsStereo ? 2 : 1);
61+
62+#endif // NETWORK_AUDIO_SHARED_H
--- marathon/trunk/Source_Files/Network/network_speaker_sdl.h (revision 527)
+++ marathon/trunk/Source_Files/Network/network_speaker_sdl.h (revision 528)
@@ -1,70 +1,70 @@
1-/*
2- * network_speaker_sdl.h
3- * created for Marathon: Aleph One <http://source.bungie.org/>
4-
5- Copyright (C) 2002 and beyond by Woody Zenfell, III
6- and the "Aleph One" developers.
7-
8- This program is free software; you can redistribute it and/or modify
9- it under the terms of the GNU General Public License as published by
10- the Free Software Foundation; either version 3 of the License, or
11- (at your option) any later version.
12-
13- This program is distributed in the hope that it will be useful,
14- but WITHOUT ANY WARRANTY; without even the implied warranty of
15- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16- GNU General Public License for more details.
17-
18- This license is contained in the file "COPYING",
19- which is included with this source code; it is available online at
20- http://www.gnu.org/licenses/gpl.html
21-
22- * The code in this file is licensed to you under the GNU GPL. As the copyright holder,
23- * however, I reserve the right to use this code as I see fit, without being bound by the
24- * GPL's terms. This exemption is not intended to apply to modified versions of this file -
25- * if I were to use a modified version, I would be a licensee of whomever modified it, and
26- * thus must observe the GPL terms.
27- *
28- * Realtime audio (network microphone) playback support for SDL platforms.
29- *
30- * Created by woody Mar 3-8, 2002.
31- *
32- * Feb 1, 2003 (Woody Zenfell):
33- * This file now describes only the interface between the SDL network speaker receiving
34- * code and the SDL sound code. The main interface to the actual speaker stuff will be
35- * in network_audio.h.
36- */
37-
38-#ifndef NETWORK_SPEAKER_SDL_H
39-#define NETWORK_SPEAKER_SDL_H
40-
41-#include "cseries.h"
42-
43-// Flags for NetworkSpeakerSoundBuffer::mFlags below
44-enum {
45- kSoundDataIsDisposable = 0x01 // dequeuer is expected to call release_network_speaker_buffer(mData)
46-};
47-
48-// These are used to link the network_speaker routines to the sound_sdl routines.
49-struct NetworkSpeakerSoundBufferDescriptor {
50- byte* mData;
51- uint32 mLength;
52- uint32 mFlags;
53-};
54-
55-// To insulate callers from details of flag storage
56-__inline__ bool
57-is_sound_data_disposable(NetworkSpeakerSoundBufferDescriptor* inBuffer) {
58- return (inBuffer->mFlags & kSoundDataIsDisposable) ? true : false;
59-}
60-
61-
62-// Called by sound playback routines to get incoming network audio
63-// (also called by main thread in close_network_speaker())
64-// Calling this invalidates the pointer returned the previous call.
65-NetworkSpeakerSoundBufferDescriptor* dequeue_network_speaker_data();
66-
67-// Called by sound playback routines to return storage-buffers to the freequeue
68-void release_network_speaker_buffer(byte* inBuffer);
69-
70-#endif // NETWORK_SPEAKER_SDL_H
1+/*
2+ * network_speaker_sdl.h
3+ * created for Marathon: Aleph One <http://source.bungie.org/>
4+
5+ Copyright (C) 2002 and beyond by Woody Zenfell, III
6+ and the "Aleph One" developers.
7+
8+ This program is free software; you can redistribute it and/or modify
9+ it under the terms of the GNU General Public License as published by
10+ the Free Software Foundation; either version 3 of the License, or
11+ (at your option) any later version.
12+
13+ This program is distributed in the hope that it will be useful,
14+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+ GNU General Public License for more details.
17+
18+ This license is contained in the file "COPYING",
19+ which is included with this source code; it is available online at
20+ http://www.gnu.org/licenses/gpl.html
21+
22+ * The code in this file is licensed to you under the GNU GPL. As the copyright holder,
23+ * however, I reserve the right to use this code as I see fit, without being bound by the
24+ * GPL's terms. This exemption is not intended to apply to modified versions of this file -
25+ * if I were to use a modified version, I would be a licensee of whomever modified it, and
26+ * thus must observe the GPL terms.
27+ *
28+ * Realtime audio (network microphone) playback support for SDL platforms.
29+ *
30+ * Created by woody Mar 3-8, 2002.
31+ *
32+ * Feb 1, 2003 (Woody Zenfell):
33+ * This file now describes only the interface between the SDL network speaker receiving
34+ * code and the SDL sound code. The main interface to the actual speaker stuff will be
35+ * in network_audio.h.
36+ */
37+
38+#ifndef NETWORK_SPEAKER_SDL_H
39+#define NETWORK_SPEAKER_SDL_H
40+
41+#include "cseries.h"
42+
43+// Flags for NetworkSpeakerSoundBuffer::mFlags below
44+enum {
45+ kSoundDataIsDisposable = 0x01 // dequeuer is expected to call release_network_speaker_buffer(mData)
46+};
47+
48+// These are used to link the network_speaker routines to the sound_sdl routines.
49+struct NetworkSpeakerSoundBufferDescriptor {
50+ byte* mData;
51+ uint32 mLength;
52+ uint32 mFlags;
53+};
54+
55+// To insulate callers from details of flag storage
56+__inline__ bool
57+is_sound_data_disposable(NetworkSpeakerSoundBufferDescriptor* inBuffer) {
58+ return (inBuffer->mFlags & kSoundDataIsDisposable) ? true : false;
59+}
60+
61+
62+// Called by sound playback routines to get incoming network audio
63+// (also called by main thread in close_network_speaker())
64+// Calling this invalidates the pointer returned the previous call.
65+NetworkSpeakerSoundBufferDescriptor* dequeue_network_speaker_data();
66+
67+// Called by sound playback routines to return storage-buffers to the freequeue
68+void release_network_speaker_buffer(byte* inBuffer);
69+
70+#endif // NETWORK_SPEAKER_SDL_H
--- marathon/trunk/Source_Files/Network/network_names.cpp (revision 527)
+++ marathon/trunk/Source_Files/Network/network_names.cpp (revision 528)
@@ -1,157 +1,157 @@
1-/*
2-NETWORK_NAMES.C
3-
4- Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
5- and the "Aleph One" developers.
6-
7- This program is free software; you can redistribute it and/or modify
8- it under the terms of the GNU General Public License as published by
9- the Free Software Foundation; either version 3 of the License, or
10- (at your option) any later version.
11-
12- This program is distributed in the hope that it will be useful,
13- but WITHOUT ANY WARRANTY; without even the implied warranty of
14- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15- GNU General Public License for more details.
16-
17- This license is contained in the file "COPYING",
18- which is included with this source code; it is available online at
19- http://www.gnu.org/licenses/gpl.html
20-
21-Sunday, June 26, 1994 5:45:49 PM
22-Friday, July 15, 1994 11:03:22 AM
23- allocated name table entry in the system heap, to prevent problems if the name isn't
24- unregistered before the application exits. (ajr, suggested by jgj.)
25-*/
26-
27-#if !defined(DISABLE_NETWORKING)
28-
29-#include "macintosh_cseries.h"
30-#include "macintosh_network.h"
31-
32-#ifdef env68k
33-#pragma segment network
34-#endif
35-
36-// #define MODEM_TEST
37-
38-/* ---------- constants */
39-
40-#define strUSER_NAME -16096
41-
42-/* ---------- types */
43-
44-typedef NamesTableEntry *NamesTableEntryPtr;
45-
46-/* ---------- globals */
47-
48-static NamesTableEntryPtr myNTEName= (NamesTableEntryPtr) NULL;
49-
50-/* ---------- code */
51-
52-/*
53----------------
54-NetRegisterName
55----------------
56-
57-allocates and registers and entity name for the given socket; call NetUnRegisterName to unregister
58-the name. only one name can be registered at a time through NetRegisterName().
59-
60- ---> name (pascal string, can be NULL and will be replaced with user name)
61- ---> type (pascal string)
62- ---> socket number
63-
64- <--- error
65-*/
66-
67-OSErr NetRegisterName(
68- unsigned char *name,
69- unsigned char *type,
70- short version,
71- short socketNumber)
72-{
73- MPPPBPtr myMPPPBPtr= (MPPPBPtr) NewPtrClear(sizeof(MPPParamBlock));
74- Str255 adjusted_name;
75- Str255 adjusted_type;
76- OSErr error;
77-
78-#ifdef MODEM_TEST
79- error= ModemRegisterName(name, type, version, socketNumber);
80-#else
81-
82- assert(!myNTEName);
83- // we stick it in the system heap so that if the application crashes/quits while the name
84- // is registered, this pointer won't be trashed by another application (unless, of course,
85- // it trashes the system heap).
86- myNTEName= (NamesTableEntryPtr) NewPtrSysClear(sizeof(NamesTableEntry));
87- assert(myNTEName);
88-
89- /* get user name if no object name was supplied*/
90- pstrcpy(adjusted_name, name ? name : *GetString(strUSER_NAME));
91-
92- /* Calculate the adjusted type */
93- psprintf(adjusted_type, "%.*s%d", type[0], type+1, version);
94-
95- error= MemError();
96- if (error==noErr)
97- {
98- NBPSetNTE((Ptr)myNTEName, adjusted_name, adjusted_type, "\p*", socketNumber); /* build names table entry */
99-
100- myMPPPBPtr->NBP.nbpPtrs.ntQElPtr= (Ptr) myNTEName;
101- myMPPPBPtr->NBP.parm.verifyFlag= true; /* verify this name doesnユt already exist */
102- myMPPPBPtr->NBP.interval= 2; /* retry every 2*8 == 16 ticks */
103- myMPPPBPtr->NBP.count= 4; /* retry 4 times ( == 64 ticks) */
104-
105- error= PRegisterName(myMPPPBPtr, false);
106-
107- DisposePtr((Ptr)myMPPPBPtr);
108- }
109-#endif
110-
111- return error;
112-}
113-
114-/*
115-
116------------------
117-NetUnRegisterName
118------------------
119-
120- (no parameters)
121-
122-deallocates and unregisters the entity name previously allocated with NetRegisterName().
123-*/
124-
125-OSErr NetUnRegisterName(
126- void)
127-{
128- OSErr error= noErr;
129-
130-#ifdef MODEM_TEST
131- error= ModemUnRegisterName();
132-#else
133-
134- if (myNTEName)
135- {
136- MPPPBPtr myMPPPBPtr= (MPPPBPtr) NewPtrClear(sizeof(MPPParamBlock));
137-
138- error= MemError();
139- if (error==noErr)
140- {
141- myMPPPBPtr->NBP.nbpPtrs.entityPtr= (Ptr) &myNTEName->nt.entityData; /* canユt just give back names table entry */
142-
143- error= PRemoveName(myMPPPBPtr, false);
144-
145- DisposePtr((Ptr)myMPPPBPtr);
146-
147- DisposePtr((Ptr)myNTEName);
148- myNTEName= (NamesTableEntryPtr) NULL;
149- }
150- }
151-#endif
152-
153- return error;
154-}
155-
156-#endif // !defined(DISABLE_NETWORKING)
157-
1+/*
2+NETWORK_NAMES.C
3+
4+ Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
5+ and the "Aleph One" developers.
6+
7+ This program is free software; you can redistribute it and/or modify
8+ it under the terms of the GNU General Public License as published by
9+ the Free Software Foundation; either version 3 of the License, or
10+ (at your option) any later version.
11+
12+ This program is distributed in the hope that it will be useful,
13+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ GNU General Public License for more details.
16+
17+ This license is contained in the file "COPYING",
18+ which is included with this source code; it is available online at
19+ http://www.gnu.org/licenses/gpl.html
20+
21+Sunday, June 26, 1994 5:45:49 PM
22+Friday, July 15, 1994 11:03:22 AM
23+ allocated name table entry in the system heap, to prevent problems if the name isn't
24+ unregistered before the application exits. (ajr, suggested by jgj.)
25+*/
26+
27+#if !defined(DISABLE_NETWORKING)
28+
29+#include "macintosh_cseries.h"
30+#include "macintosh_network.h"
31+
32+#ifdef env68k
33+#pragma segment network
34+#endif
35+
36+// #define MODEM_TEST
37+
38+/* ---------- constants */
39+
40+#define strUSER_NAME -16096
41+
42+/* ---------- types */
43+
44+typedef NamesTableEntry *NamesTableEntryPtr;
45+
46+/* ---------- globals */
47+
48+static NamesTableEntryPtr myNTEName= (NamesTableEntryPtr) NULL;
49+
50+/* ---------- code */
51+
52+/*
53+---------------
54+NetRegisterName
55+---------------
56+
57+allocates and registers and entity name for the given socket; call NetUnRegisterName to unregister
58+the name. only one name can be registered at a time through NetRegisterName().
59+
60+ ---> name (pascal string, can be NULL and will be replaced with user name)
61+ ---> type (pascal string)
62+ ---> socket number
63+
64+ <--- error
65+*/
66+
67+OSErr NetRegisterName(
68+ unsigned char *name,
69+ unsigned char *type,
70+ short version,
71+ short socketNumber)
72+{
73+ MPPPBPtr myMPPPBPtr= (MPPPBPtr) NewPtrClear(sizeof(MPPParamBlock));
74+ Str255 adjusted_name;
75+ Str255 adjusted_type;
76+ OSErr error;
77+
78+#ifdef MODEM_TEST
79+ error= ModemRegisterName(name, type, version, socketNumber);
80+#else
81+
82+ assert(!myNTEName);
83+ // we stick it in the system heap so that if the application crashes/quits while the name
84+ // is registered, this pointer won't be trashed by another application (unless, of course,
85+ // it trashes the system heap).
86+ myNTEName= (NamesTableEntryPtr) NewPtrSysClear(sizeof(NamesTableEntry));
87+ assert(myNTEName);
88+
89+ /* get user name if no object name was supplied*/
90+ pstrcpy(adjusted_name, name ? name : *GetString(strUSER_NAME));
91+
92+ /* Calculate the adjusted type */
93+ psprintf(adjusted_type, "%.*s%d", type[0], type+1, version);
94+
95+ error= MemError();
96+ if (error==noErr)
97+ {
98+ NBPSetNTE((Ptr)myNTEName, adjusted_name, adjusted_type, "\p*", socketNumber); /* build names table entry */
99+
100+ myMPPPBPtr->NBP.nbpPtrs.ntQElPtr= (Ptr) myNTEName;
101+ myMPPPBPtr->NBP.parm.verifyFlag= true; /* verify this name doesnユt already exist */
102+ myMPPPBPtr->NBP.interval= 2; /* retry every 2*8 == 16 ticks */
103+ myMPPPBPtr->NBP.count= 4; /* retry 4 times ( == 64 ticks) */
104+
105+ error= PRegisterName(myMPPPBPtr, false);
106+
107+ DisposePtr((Ptr)myMPPPBPtr);
108+ }
109+#endif
110+
111+ return error;
112+}
113+
114+/*
115+
116+-----------------
117+NetUnRegisterName
118+-----------------
119+
120+ (no parameters)
121+
122+deallocates and unregisters the entity name previously allocated with NetRegisterName().
123+*/
124+
125+OSErr NetUnRegisterName(
126+ void)
127+{
128+ OSErr error= noErr;
129+
130+#ifdef MODEM_TEST
131+ error= ModemUnRegisterName();
132+#else
133+
134+ if (myNTEName)
135+ {
136+ MPPPBPtr myMPPPBPtr= (MPPPBPtr) NewPtrClear(sizeof(MPPParamBlock));
137+
138+ error= MemError();
139+ if (error==noErr)
140+ {
141+ myMPPPBPtr->NBP.nbpPtrs.entityPtr= (Ptr) &myNTEName->nt.entityData; /* canユt just give back names table entry */
142+
143+ error= PRemoveName(myMPPPBPtr, false);
144+
145+ DisposePtr((Ptr)myMPPPBPtr);
146+
147+ DisposePtr((Ptr)myNTEName);
148+ myNTEName= (NamesTableEntryPtr) NULL;
149+ }
150+ }
151+#endif
152+
153+ return error;
154+}
155+
156+#endif // !defined(DISABLE_NETWORKING)
157+
--- marathon/trunk/Source_Files/Network/network_microphone_sdl_alsa.cpp (revision 527)
+++ marathon/trunk/Source_Files/Network/network_microphone_sdl_alsa.cpp (revision 528)
@@ -1,211 +1,211 @@
1-/*
2- * network_microphone_sdl_alsa.cpp
3- * created for Marathon: Aleph One <http://source.bungie.org/>
4-
5- Copyright (C) 2007 and beyond by Gregory Smith
6- and the "Aleph One" developers.
7-
8- This program is free software; you can redistribute it and/or modify
9- it under the terms of the GNU General Public License as published by
10- the Free Software Foundation; either version 3 of the License, or
11- (at your option) any later version.
12-
13- This program is distributed in the hope that it will be useful,
14- but WITHOUT ANY WARRANTY; without even the implied warranty of
15- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16- GNU General Public License for more details.
17-
18- This license is contained in the file "COPYING",
19- which is included with this source code; it is available online at
20- http://www.gnu.org/licenses/gpl.html
21-
22- */
23-
24-#include "cseries.h"
25-#ifdef HAVE_ALSA
26-#include <alsa/asoundlib.h>
27-
28-#ifdef SPEEX
29-#include "network_speex.h"
30-#include "preferences.h"
31-#endif
32-
33-#include "network_microphone_shared.h"
34-
35-static snd_pcm_t *capture_handle = 0;
36-static snd_pcm_hw_params_t *hw_params;
37-
38-static bool initialized = false;
39-static bool active;
40-
41-static const int bytes_per_frame = 2; // 16-bit, mono
42-static snd_pcm_uframes_t frames = 0; // period
43-
44-OSErr
45-open_network_microphone() {
46- int err;
47-
48- if ((err = snd_pcm_open(&capture_handle, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0) {
49- fprintf(stderr, "snd_pcm_open\n");
50- return -1;
51- }
52-
53- if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
54- fprintf(stderr, "snd_pcm_hw_params_malloc\n");
55- return -1;
56- }
57-
58- if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
59- fprintf(stderr, "snd_pcm_hw_params_any\n");
60- return -1;
61- }
62-
63- if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
64- fprintf(stderr, "snd_pcm_hw_params_set_access\n");
65- return -1;
66- }
67-
68- snd_pcm_format_t format;
69-#ifdef ALEPHONE_LITTLE_ENDIAN
70- format = SND_PCM_FORMAT_S16_LE;
71-#else
72- format = SND_PCM_FORMAT_S16_BE;
73-#endif
74-
75- if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0) {
76- fprintf(stderr, "snd_pcm_hw_params_set_format\n");
77- return -1;
78- }
79-
80- unsigned int rate = 8000;
81- if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &rate, 0)) < 0) {
82- fprintf(stderr, "snd_pcm_hw_params_set_rate_near\n");
83- return -1;
84- }
85-
86- if (!announce_microphone_capture_format(rate, false, true)) {
87- fprintf(stderr, "network microphone support code rejected audio format (rate=%i)\n", rate);
88- return -1;
89- }
90-
91- if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, 1)) < 0) {
92- fprintf(stderr, "snd_pcm_hw_params_set_channels\n");
93- return -1;
94- }
95-
96- frames = get_capture_byte_count_per_packet() / bytes_per_frame;
97- if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, &frames, 0)) < 0) {
98- fprintf(stderr, "snd_pcm_hw_params_set_period_size_near\n");
99- return -1;
100- }
101-
102- if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
103- fprintf(stderr, "snd_pcm_hw_params\n");
104- return -1;
105- }
106-
107- snd_pcm_hw_params_free(hw_params);
108-
109- snd_pcm_sw_params_t *sw_params;
110- if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0) {
111- fprintf(stderr, "snd_pcm_sw_params_malloc\n");
112- return -1;
113- }
114-
115- if ((err = snd_pcm_sw_params_current(capture_handle, sw_params)) < 0) {
116- fprintf(stderr, "snc_pcm_sw_params_current\n");
117- return -1;
118- }
119-
120- if ((err = snd_pcm_sw_params_set_avail_min(capture_handle, sw_params, frames)) < 0)
121- {
122- fprintf(stderr, "snd_pcm_params_set_avail_min\n");
123- return -1;
124- }
125-
126- if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params, frames)) < 0) {
127- fprintf(stderr, "snd_pcm_params_set_start_threshold\n");
128- return -1;
129- }
130-
131-#ifdef SPEEX
132- if (network_preferences->use_speex_encoder) {
133- init_speex_encoder();
134- }
135-#endif
136-
137- initialized = true;
138-
139- return 0;
140-}
141-
142-void
143-close_network_microphone() {
144- initialized = false;
145- snd_pcm_close(capture_handle);
146- capture_handle = 0;
147-
148-#ifdef SPEEX
149- if (network_preferences->use_speex_encoder) {
150- destroy_speex_encoder();
151- }
152-#endif
153-}
154-
155-void CaptureCallback(snd_async_handler_t *)
156-{
157- snd_pcm_sframes_t avail = snd_pcm_avail_update(capture_handle);
158- while (avail >= frames) {
159- static uint8 buffer[16384];
160- int frames_read = snd_pcm_readi(capture_handle, buffer, frames);
161- if (frames_read == -EPIPE) {
162- snd_pcm_prepare(capture_handle);
163- } else if (frames_read > 0) {
164- copy_and_send_audio_data(buffer, frames_read * bytes_per_frame, NULL, 0, true);
165- }
166-
167- avail = snd_pcm_avail_update(capture_handle);
168- }
169-}
170-
171-static bool mic_active = false;
172-
173-static snd_async_handler_t *pcm_callback;
174-
175-void
176-set_network_microphone_state(bool inActive) {
177- if (!initialized) return;
178- if (inActive && !mic_active) {
179- // prepare the pcm
180- if (snd_pcm_prepare(capture_handle) < 0) {
181- fprintf(stderr, "preparing stream failed\n");
182- }
183- if (snd_async_add_pcm_handler(&pcm_callback, capture_handle, CaptureCallback, NULL) < 0) {
184- fprintf(stderr, "adding pcm handler failed\n");
185- return;
186- }
187- if (snd_pcm_start(capture_handle) < 0) {
188- fprintf(stderr, "starting pcm failed\n");
189- }
190- mic_active = true;
191- } else if (!inActive && mic_active) {
192- snd_async_del_handler(pcm_callback);
193- snd_pcm_drop(capture_handle);
194- mic_active = false;
195- }
196-
197-}
198-
199-bool
200-is_network_microphone_implemented() {
201- return true;
202-}
203-
204-void
205-network_microphone_idle_proc() {
206- // do nothing
207-}
208-
209-#else
210-#include "network_microphone_sdl_dummy.cpp"
211-#endif
1+/*
2+ * network_microphone_sdl_alsa.cpp
3+ * created for Marathon: Aleph One <http://source.bungie.org/>
4+
5+ Copyright (C) 2007 and beyond by Gregory Smith
6+ and the "Aleph One" developers.
7+
8+ This program is free software; you can redistribute it and/or modify
9+ it under the terms of the GNU General Public License as published by
10+ the Free Software Foundation; either version 3 of the License, or
11+ (at your option) any later version.
12+
13+ This program is distributed in the hope that it will be useful,
14+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+ GNU General Public License for more details.
17+
18+ This license is contained in the file "COPYING",
19+ which is included with this source code; it is available online at
20+ http://www.gnu.org/licenses/gpl.html
21+
22+ */
23+
24+#include "cseries.h"
25+#ifdef HAVE_ALSA
26+#include <alsa/asoundlib.h>
27+
28+#ifdef SPEEX
29+#include "network_speex.h"
30+#include "preferences.h"
31+#endif
32+
33+#include "network_microphone_shared.h"
34+
35+static snd_pcm_t *capture_handle = 0;
36+static snd_pcm_hw_params_t *hw_params;
37+
38+static bool initialized = false;
39+static bool active;
40+
41+static const int bytes_per_frame = 2; // 16-bit, mono
42+static snd_pcm_uframes_t frames = 0; // period
43+
44+OSErr
45+open_network_microphone() {
46+ int err;
47+
48+ if ((err = snd_pcm_open(&capture_handle, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0) {
49+ fprintf(stderr, "snd_pcm_open\n");
50+ return -1;
51+ }
52+
53+ if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
54+ fprintf(stderr, "snd_pcm_hw_params_malloc\n");
55+ return -1;
56+ }
57+
58+ if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
59+ fprintf(stderr, "snd_pcm_hw_params_any\n");
60+ return -1;
61+ }
62+
63+ if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
64+ fprintf(stderr, "snd_pcm_hw_params_set_access\n");
65+ return -1;
66+ }
67+
68+ snd_pcm_format_t format;
69+#ifdef ALEPHONE_LITTLE_ENDIAN
70+ format = SND_PCM_FORMAT_S16_LE;
71+#else
72+ format = SND_PCM_FORMAT_S16_BE;
73+#endif
74+
75+ if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0) {
76+ fprintf(stderr, "snd_pcm_hw_params_set_format\n");
77+ return -1;
78+ }
79+
80+ unsigned int rate = 8000;
81+ if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &rate, 0)) < 0) {
82+ fprintf(stderr, "snd_pcm_hw_params_set_rate_near\n");
83+ return -1;
84+ }
85+
86+ if (!announce_microphone_capture_format(rate, false, true)) {
87+ fprintf(stderr, "network microphone support code rejected audio format (rate=%i)\n", rate);
88+ return -1;
89+ }
90+
91+ if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, 1)) < 0) {
92+ fprintf(stderr, "snd_pcm_hw_params_set_channels\n");
93+ return -1;
94+ }
95+
96+ frames = get_capture_byte_count_per_packet() / bytes_per_frame;
97+ if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle, hw_params, &frames, 0)) < 0) {
98+ fprintf(stderr, "snd_pcm_hw_params_set_period_size_near\n");
99+ return -1;
100+ }
101+
102+ if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
103+ fprintf(stderr, "snd_pcm_hw_params\n");
104+ return -1;
105+ }
106+
107+ snd_pcm_hw_params_free(hw_params);
108+
109+ snd_pcm_sw_params_t *sw_params;
110+ if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0) {
111+ fprintf(stderr, "snd_pcm_sw_params_malloc\n");
112+ return -1;
113+ }
114+
115+ if ((err = snd_pcm_sw_params_current(capture_handle, sw_params)) < 0) {
116+ fprintf(stderr, "snc_pcm_sw_params_current\n");
117+ return -1;
118+ }
119+
120+ if ((err = snd_pcm_sw_params_set_avail_min(capture_handle, sw_params, frames)) < 0)
121+ {
122+ fprintf(stderr, "snd_pcm_params_set_avail_min\n");
123+ return -1;
124+ }
125+
126+ if ((err = snd_pcm_sw_params_set_start_threshold(capture_handle, sw_params, frames)) < 0) {
127+ fprintf(stderr, "snd_pcm_params_set_start_threshold\n");
128+ return -1;
129+ }
130+
131+#ifdef SPEEX
132+ if (network_preferences->use_speex_encoder) {
133+ init_speex_encoder();
134+ }
135+#endif
136+
137+ initialized = true;
138+
139+ return 0;
140+}
141+
142+void
143+close_network_microphone() {
144+ initialized = false;
145+ snd_pcm_close(capture_handle);
146+ capture_handle = 0;
147+
148+#ifdef SPEEX
149+ if (network_preferences->use_speex_encoder) {
150+ destroy_speex_encoder();
151+ }
152+#endif
153+}
154+
155+void CaptureCallback(snd_async_handler_t *)
156+{
157+ snd_pcm_sframes_t avail = snd_pcm_avail_update(capture_handle);
158+ while (avail >= frames) {
159+ static uint8 buffer[16384];
160+ int frames_read = snd_pcm_readi(capture_handle, buffer, frames);
161+ if (frames_read == -EPIPE) {
162+ snd_pcm_prepare(capture_handle);
163+ } else if (frames_read > 0) {
164+ copy_and_send_audio_data(buffer, frames_read * bytes_per_frame, NULL, 0, true);
165+ }
166+
167+ avail = snd_pcm_avail_update(capture_handle);
168+ }
169+}
170+
171+static bool mic_active = false;
172+
173+static snd_async_handler_t *pcm_callback;
174+
175+void
176+set_network_microphone_state(bool inActive) {
177+ if (!initialized) return;
178+ if (inActive && !mic_active) {
179+ // prepare the pcm
180+ if (snd_pcm_prepare(capture_handle) < 0) {
181+ fprintf(stderr, "preparing stream failed\n");
182+ }
183+ if (snd_async_add_pcm_handler(&pcm_callback, capture_handle, CaptureCallback, NULL) < 0) {
184+ fprintf(stderr, "adding pcm handler failed\n");
185+ return;
186+ }
187+ if (snd_pcm_start(capture_handle) < 0) {
188+ fprintf(stderr, "starting pcm failed\n");
189+ }
190+ mic_active = true;
191+ } else if (!inActive && mic_active) {
192+ snd_async_del_handler(pcm_callback);
193+ snd_pcm_drop(capture_handle);
194+ mic_active = false;
195+ }
196+
197+}
198+
199+bool
200+is_network_microphone_implemented() {
201+ return true;
202+}
203+
204+void
205+network_microphone_idle_proc() {
206+ // do nothing
207+}
208+
209+#else
210+#include "network_microphone_sdl_dummy.cpp"
211+#endif
--- marathon/trunk/Source_Files/Network/RingGameProtocol.h (revision 527)
+++ marathon/trunk/Source_Files/Network/RingGameProtocol.h (revision 528)
@@ -1,57 +1,57 @@
1-/*
2- * network_ring.h
3-
4- Copyright (C) 2003 and beyond by Woody Zenfell, III
5- and the "Aleph One" developers.
6-
7- This program is free software; you can redistribute it and/or modify
8- it under the terms of the GNU General Public License as published by
9- the Free Software Foundation; either version 3 of the License, or
10- (at your option) any later version.
11-
12- This program is distributed in the hope that it will be useful,
13- but WITHOUT ANY WARRANTY; without even the implied warranty of
14- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15- GNU General Public License for more details.
16-
17- This license is contained in the file "COPYING",
18- which is included with this source code; it is available online at
19- http://www.gnu.org/licenses/gpl.html
20-
21- * Created by Woody Zenfell, III on Sat May 17 2003.
22- *
23- * Interface between the old ring game protocol module and the rest of the game.
24- */
25-
26-#ifndef NETWORK_RING
27-#define NETWORK_RING
28-
29-#include "NetworkGameProtocol.h"
30-
31-#include <stdio.h>
32-
33-class XML_ElementParser;
34-
35-class RingGameProtocol : public NetworkGameProtocol
36-{
37-public:
38- bool Enter(short* inNetStatePtr);
39- void Exit1();
40- void Exit2();
41- void DistributeInformation(short type, void *buffer, short buffer_size, bool send_to_self, bool only_send_to_team);
42- bool Sync(NetTopology* inTopology, int32 inSmallestGameTick, size_t inLocalPlayerIndex, size_t inServerPlayerIndex);
43- bool UnSync(bool inGraceful, int32 inSmallestPostgameTick);
44- int32 GetNetTime();
45- void PacketHandler(DDPPacketBuffer* inPacket);
46-
47- int32 GetUnconfirmedActionFlagsCount();
48- uint32 PeekUnconfirmedActionFlag(int32 offset);
49- void UpdateUnconfirmedActionFlags();
50-
51- static XML_ElementParser* GetParser();
52-};
53-
54-extern void DefaultRingPreferences();
55-extern void WriteRingPreferences(FILE* F);
56-
57-#endif // NETWORK_RING
1+/*
2+ * network_ring.h
3+
4+ Copyright (C) 2003 and beyond by Woody Zenfell, III
5+ and the "Aleph One" developers.
6+
7+ This program is free software; you can redistribute it and/or modify
8+ it under the terms of the GNU General Public License as published by
9+ the Free Software Foundation; either version 3 of the License, or
10+ (at your option) any later version.
11+
12+ This program is distributed in the hope that it will be useful,
13+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ GNU General Public License for more details.
16+
17+ This license is contained in the file "COPYING",
18+ which is included with this source code; it is available online at
19+ http://www.gnu.org/licenses/gpl.html
20+
21+ * Created by Woody Zenfell, III on Sat May 17 2003.
22+ *
23+ * Interface between the old ring game protocol module and the rest of the game.
24+ */
25+
26+#ifndef NETWORK_RING
27+#define NETWORK_RING
28+
29+#include "NetworkGameProtocol.h"
30+
31+#include <stdio.h>
32+
33+class XML_ElementParser;
34+
35+class RingGameProtocol : public NetworkGameProtocol
36+{
37+public:
38+ bool Enter(short* inNetStatePtr);
39+ void Exit1();
40+ void Exit2();
41+ void DistributeInformation(short type, void *buffer, short buffer_size, bool send_to_self, bool only_send_to_team);
42+ bool Sync(NetTopology* inTopology, int32 inSmallestGameTick, size_t inLocalPlayerIndex, size_t inServerPlayerIndex);
43+ bool UnSync(bool inGraceful, int32 inSmallestPostgameTick);
44+ int32 GetNetTime();
45+ void PacketHandler(DDPPacketBuffer* inPacket);
46+
47+ int32 GetUnconfirmedActionFlagsCount();
48+ uint32 PeekUnconfirmedActionFlag(int32 offset);
49+ void UpdateUnconfirmedActionFlags();
50+
51+ static XML_ElementParser* GetParser();
52+};
53+
54+extern void DefaultRingPreferences();
55+extern void WriteRingPreferences(FILE* F);
56+
57+#endif // NETWORK_RING
--- marathon/trunk/Source_Files/Network/network_distribution_types.h (revision 527)
+++ marathon/trunk/Source_Files/Network/network_distribution_types.h (revision 528)
@@ -1,36 +1,36 @@
1-/*
2- * network_distribution_types.h
3- * created for Marathon: Aleph One <http://source.bungie.org/>
4-
5- Copyright (C) 2002 and beyond by Woody Zenfell, III
6- and the "Aleph One" developers.
7-
8- This program is free software; you can redistribute it and/or modify
9- it under the terms of the GNU General Public License as published by
10- the Free Software Foundation; either version 3 of the License, or
11- (at your option) any later version.
12-
13- This program is distributed in the hope that it will be useful,
14- but WITHOUT ANY WARRANTY; without even the implied warranty of
15- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16- GNU General Public License for more details.
17-
18- This license is contained in the file "COPYING",
19- which is included with this source code; it is available online at
20- http://www.gnu.org/licenses/gpl.html
21-
22- * Centralized location for distribution types (for NetDistributeInformation,
23- * NetAddDistributionFunction, etc.) helps avoid potential conflicts.
24- *
25- * Created by woody Mar 3-8, 2002.
26- */
27-
28-#ifndef NETWORK_DISTRIBUTION_TYPES_H
29-#define NETWORK_DISTRIBUTION_TYPES_H
30-
31-enum {
32- kOriginalNetworkAudioDistributionTypeID = 0, // for compatibility with older versions
33- kNewNetworkAudioDistributionTypeID = 1 // new-style realtime network audio data
34-};
35-
36-#endif // NETWORK_DISTRIBUTION_TYPES_H
1+/*
2+ * network_distribution_types.h
3+ * created for Marathon: Aleph One <http://source.bungie.org/>
4+
5+ Copyright (C) 2002 and beyond by Woody Zenfell, III
6+ and the "Aleph One" developers.
7+
8+ This program is free software; you can redistribute it and/or modify
9+ it under the terms of the GNU General Public License as published by
10+ the Free Software Foundation; either version 3 of the License, or
11+ (at your option) any later version.
12+
13+ This program is distributed in the hope that it will be useful,
14+ but WITHOUT ANY WARRANTY; without even the implied warranty of
15+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+ GNU General Public License for more details.
17+
18+ This license is contained in the file "COPYING",
19+ which is included with this source code; it is available online at
20+ http://www.gnu.org/licenses/gpl.html
21+
22+ * Centralized location for distribution types (for NetDistributeInformation,
23+ * NetAddDistributionFunction, etc.) helps avoid potential conflicts.
24+ *
25+ * Created by woody Mar 3-8, 2002.
26+ */
27+
28+#ifndef NETWORK_DISTRIBUTION_TYPES_H
29+#define NETWORK_DISTRIBUTION_TYPES_H
30+
31+enum {
32+ kOriginalNetworkAudioDistributionTypeID = 0, // for compatibility with older versions
33+ kNewNetworkAudioDistributionTypeID = 1 // new-style realtime network audio data
34+};
35+
36+#endif // NETWORK_DISTRIBUTION_TYPES_H
--- marathon/trunk/Source_Files/Network/network_data_formats.h (revision 527)
+++ marathon/trunk/Source_Files/Network/network_data_formats.h (revision 528)
@@ -1,127 +1,127 @@
1-/*
2- * network_data_formats.h
3-
4- Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
5- and the "Aleph One" developers.
6-
7- This program is free software; you can redistribute it and/or modify
8- it under the terms of the GNU General Public License as published by
9- the Free Software Foundation; either version 3 of the License, or
10- (at your option) any later version.
11-
12- This program is distributed in the hope that it will be useful,
13- but WITHOUT ANY WARRANTY; without even the implied warranty of
14- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15- GNU General Public License for more details.
16-
17- This license is contained in the file "COPYING",
18- which is included with this source code; it is available online at
19- http://www.gnu.org/licenses/gpl.html
20-
21- * The purpose of this file is to define structures that will be used with exactly the same
22- * padding, byte ordering, etc. on all platforms, and to declare handy functions to copy data
23- * to or from the corresponding "unpacked" structures already used by the code. This approach
24- * requires only minimal changes to the original code.
25- *
26- * Created by Woody Zenfell, III on Thu Oct 11 2001; structures copied from network_private.h.
27-
28- Jan 16, 2002 (Loren Petrich) Replaced compiler-specific packing code with generalized wrappers
29-
30- Mar 5, 2002 (Woody Zenfell) added network_audio_header
31-
32- Mar 9, 2002 (Woody Zenfell) changed some SIZEOF_'s to be more accurate/specific
33- */
34-
35-#ifndef NETWORK_DATA_FORMATS_H
36-#define NETWORK_DATA_FORMATS_H
37-
38-#include "cseries.h" // Need ALEPHONE_LITTLE_ENDIAN, if appropriate.
39-#include "network.h"
40-#include "network_private.h"
41-#include "network_audio_shared.h"
42-
43-
44-
45-// Note: no further interpretation/manipulation of a packet is attempted here. That's up
46-// to whomever actually deals with receiving and interpreting the packet (though they will
47-// probably make use of the netcpy's for the next couple structures below).
48-
49-const int SIZEOF_NetPacketHeader = 6;
50-
51-struct NetPacketHeader_NET
52-{
53- uint8 data[SIZEOF_NetPacketHeader];
54-};
55-
56-extern void netcpy(NetPacketHeader_NET* dest, const NetPacketHeader* src);
57-extern void netcpy(NetPacketHeader* dest, const NetPacketHeader_NET* src);
58-
59-
60-
61-// Note: we do not attempt any manipulations on the actual action_flags, as we do not claim
62-// to compute the number that would be there. (I suppose, knowing that the only stuff in the
63-// buffer will be action flags, that we could just walk through and swap all of it, but...)
64-// We'll leave it to whomever interprets or writes to the action_flags data segment to do the
65-// necessary manipulations.
66-
67-const int SIZEOF_NetPacket = 2*MAXIMUM_NUMBER_OF_NETWORK_PLAYERS + 8;
68-
69-struct NetPacket_NET
70-{
71- uint8 data[SIZEOF_NetPacket];
72-};
73-
74-extern void netcpy(NetPacket_NET* dest, const NetPacket* src);
75-extern void netcpy(NetPacket* dest, const NetPacket_NET* src);
76-
77-
78-// For action flags - note length is in bytes, not number of flags. This is 'bidirectional',
79-// i.e. same function is used to copy from _NET to unpacked as the other way around.
80-// Note, since there is no packing to do - only byte swapping - we can pass along to memcpy if we're
81-// on a big-endian architecture.
82-#ifdef ALEPHONE_LITTLE_ENDIAN
83-extern void netcpy(uint32* dest, const uint32* src, size_t length);
84-#else
85-__inline__ void netcpy(uint32* dest, const uint32* src, size_t length) { memcpy(dest, src, length); }
86-#endif
87-
88-
89-
90-// Note: we do not attempt any sort of processing on the "data" segment here,
91-// since we may not understand its format. Whoever interprets it will have to
92-// do the necessary packing/unpacking, byte-swapping, etc.
93-
94-const int SIZEOF_NetDistributionPacket = 6;
95-
96-struct NetDistributionPacket_NET
97-{
98- uint8 data[SIZEOF_NetDistributionPacket];
99-};
100-
101-extern void netcpy(NetDistributionPacket_NET* dest, const NetDistributionPacket* src);
102-extern void netcpy(NetDistributionPacket* dest, const NetDistributionPacket_NET* src);
103-
104-
105-// Note: unlike other _NET structures, neither 'host' nor 'port' here is byte-swapped
106-// in conversion to/from the non-_NET structures.
107-// It is believed that both should ALWAYS be dealt with in network byte order.
108-
109-const int SIZEOF_IPaddress = 6;
110-
111-struct IPaddress_NET {
112- uint8 data[SIZEOF_IPaddress];
113-};
114-
115-extern void netcpy(IPaddress_NET* dest, const IPaddress* src);
116-extern void netcpy(IPaddress* dest, const IPaddress_NET* src);
117-
118-const int SIZEOF_network_audio_header = 8;
119-
120-struct network_audio_header_NET {
121- uint8 data[SIZEOF_network_audio_header];
122-};
123-
124-extern void netcpy(network_audio_header_NET* dest, const network_audio_header* src);
125-extern void netcpy(network_audio_header* dest, const network_audio_header_NET* src);
126-
127-#endif//NETWORK_DATA_FORMATS_H
1+/*
2+ * network_data_formats.h
3+
4+ Copyright (C) 1991-2001 and beyond by Bungie Studios, Inc.
5+ and the "Aleph One" developers.
6+
7+ This program is free software; you can redistribute it and/or modify
8+ it under the terms of the GNU General Public License as published by
9+ the Free Software Foundation; either version 3 of the License, or
10+ (at your option) any later version.
11+
12+ This program is distributed in the hope that it will be useful,
13+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ GNU General Public License for more details.
16+
17+ This license is contained in the file "COPYING",
18+ which is included with this source code; it is available online at
19+ http://www.gnu.org/licenses/gpl.html
20+
21+ * The purpose of this file is to define structures that will be used with exactly the same
22+ * padding, byte ordering, etc. on all platforms, and to declare handy functions to copy data
23+ * to or from the corresponding "unpacked" structures already used by the code. This approach
24+ * requires only minimal changes to the original code.
25+ *
26+ * Created by Woody Zenfell, III on Thu Oct 11 2001; structures copied from network_private.h.
27+
28+ Jan 16, 2002 (Loren Petrich) Replaced compiler-specific packing code with generalized wrappers
29+
30+ Mar 5, 2002 (Woody Zenfell) added network_audio_header
31+
32+ Mar 9, 2002 (Woody Zenfell) changed some SIZEOF_'s to be more accurate/specific
33+ */
34+
35+#ifndef NETWORK_DATA_FORMATS_H
36+#define NETWORK_DATA_FORMATS_H
37+
38+#include "cseries.h" // Need ALEPHONE_LITTLE_ENDIAN, if appropriate.
39+#include "network.h"
40+#include "network_private.h"
41+#include "network_audio_shared.h"
42+
43+
44+
45+// Note: no further interpretation/manipulation of a packet is attempted here. That's up
46+// to whomever actually deals with receiving and interpreting the packet (though they will
47+// probably make use of the netcpy's for the next couple structures below).
48+
49+const int SIZEOF_NetPacketHeader = 6;
50+
51+struct NetPacketHeader_NET
52+{
53+ uint8 data[SIZEOF_NetPacketHeader];
54+};
55+
56+extern void netcpy(NetPacketHeader_NET* dest, const NetPacketHeader* src);
57+extern void netcpy(NetPacketHeader* dest, const NetPacketHeader_NET* src);
58+
59+
60+
61+// Note: we do not attempt any manipulations on the actual action_flags, as we do not claim
62+// to compute the number that would be there. (I suppose, knowing that the only stuff in the
63+// buffer will be action flags, that we could just walk through and swap all of it, but...)
64+// We'll leave it to whomever interprets or writes to the action_flags data segment to do the
65+// necessary manipulations.
66+
67+const int SIZEOF_NetPacket = 2*MAXIMUM_NUMBER_OF_NETWORK_PLAYERS + 8;
68+
69+struct NetPacket_NET
70+{
71+ uint8 data[SIZEOF_NetPacket];
72+};
73+
74+extern void netcpy(NetPacket_NET* dest, const NetPacket* src);
75+extern void netcpy(NetPacket* dest, const NetPacket_NET* src);
76+
77+
78+// For action flags - note length is in bytes, not number of flags. This is 'bidirectional',
79+// i.e. same function is used to copy from _NET to unpacked as the other way around.
80+// Note, since there is no packing to do - only byte swapping - we can pass along to memcpy if we're
81+// on a big-endian architecture.
82+#ifdef ALEPHONE_LITTLE_ENDIAN
83+extern void netcpy(uint32* dest, const uint32* src, size_t length);
84+#else
85+__inline__ void netcpy(uint32* dest, const uint32* src, size_t length) { memcpy(dest, src, length); }
86+#endif
87+
88+
89+
90+// Note: we do not attempt any sort of processing on the "data" segment here,
91+// since we may not understand its format. Whoever interprets it will have to
92+// do the necessary packing/unpacking, byte-swapping, etc.
93+
94+const int SIZEOF_NetDistributionPacket = 6;
95+
96+struct NetDistributionPacket_NET
97+{
98+ uint8 data[SIZEOF_NetDistributionPacket];
99+};
100+
101+extern void netcpy(NetDistributionPacket_NET* dest, const NetDistributionPacket* src);
102+extern void netcpy(NetDistributionPacket* dest, const NetDistributionPacket_NET* src);
103+
104+
105+// Note: unlike other _NET structures, neither 'host' nor 'port' here is byte-swapped
106+// in conversion to/from the non-_NET structures.
107+// It is believed that both should ALWAYS be dealt with in network byte order.
108+
109+const int SIZEOF_IPaddress = 6;
110+
111+struct IPaddress_NET {
112+ uint8 data[SIZEOF_IPaddress];
113+};
114+
115+extern void netcpy(IPaddress_NET* dest, const IPaddress* src);
116+extern void netcpy(IPaddress* dest, const IPaddress_NET* src);
117+
118+const int SIZEOF_network_audio_header = 8;
119+
120+struct network_audio_header_NET {
121+ uint8 data[SIZEOF_network_audio_header];
122+};
123+
124+extern void netcpy(network_audio_header_NET* dest, const network_audio_header* src);
125+extern void netcpy(network_audio_header* dest, const network_audio_header_NET* src);
126+
127+#endif//NETWORK_DATA_FORMATS_H
--- marathon/trunk/Source_Files/Network/network_star_spoke.cpp (revision 527)
+++ marathon/trunk/Source_Files/Network/network_star_spoke.cpp (revision 528)
@@ -1,1288 +1,1288 @@
1-/*
2- * network_star_spoke.cpp
3-
4- Copyright (C) 2003 and beyond by Woody Zenfell, III
5- and the "Aleph One" developers.
6-
7- This program is free software; you can redistribute it and/or modify
8- it under the terms of the GNU General Public License as published by
9- the Free Software Foundation; either version 3 of the License, or
10- (at your option) any later version.
11-
12- This program is distributed in the hope that it will be useful,
13- but WITHOUT ANY WARRANTY; without even the implied warranty of
14- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15- GNU General Public License for more details.
16-
17- This license is contained in the file "COPYING",
18- which is included with this source code; it is available online at
19- http://www.gnu.org/licenses/gpl.html
20-
21- * The portion of the star game protocol run on every player's machine.
22- *
23- * Created by Woody Zenfell, III on Fri May 02 2003.
24- *
25- * May 27, 2003 (Woody Zenfell): lossy byte-stream distribution.
26- *
27- * June 30, 2003 (Woody Zenfell): lossy byte-stream distribution more tolerant of scheduling jitter
28- * (i.e. will queue multiple chunks before send, instead of dropping all data but most recent)
29- *
30- * September 17, 2004 (jkvw):
31- * NAT-friendly networking - we no longer get spoke addresses form topology -
32- * instead spokes send identification packets to hub with player ID.
33- * Hub can then associate the ID in the identification packet with the paket's source address.
34- */
35-
36-#if !defined(DISABLE_NETWORKING)
37-
38-#include "network_star.h"
39-#include "AStream.h"
40-#include "mytm.h"
41-#include "network_private.h" // kPROTOCOL_TYPE
42-#include "WindowedNthElementFinder.h"
43-#include "vbl.h" // parse_keymap
44-#include "CircularByteBuffer.h"
45-#include "Logging.h"
46-#include "crc.h"
47-#include "player.h"
48-
49-#include <map>
50-
51-extern void make_player_really_net_dead(size_t inPlayerIndex);
52-extern void call_distribution_response_function_if_available(byte* inBuffer, uint16 inBufferSize, int16 inDistributionType, uint8 inSendingPlayerIndex);
53-
54-
55-enum {
56- kDefaultPregameTicksBeforeNetDeath = 90 * TICKS_PER_SECOND,
57- kDefaultInGameTicksBeforeNetDeath = 5 * TICKS_PER_SECOND,
58- kDefaultOutgoingFlagsQueueSize = TICKS_PER_SECOND / 2,
59- kDefaultRecoverySendPeriod = TICKS_PER_SECOND / 2,
60- kDefaultTimingWindowSize = 3 * TICKS_PER_SECOND,
61- kDefaultTimingNthElement = kDefaultTimingWindowSize / 2,
62- kLossyByteStreamDataBufferSize = 1280,
63- kTypicalLossyByteStreamChunkSize = 56,
64- kLossyByteStreamDescriptorCount = kLossyByteStreamDataBufferSize / kTypicalLossyByteStreamChunkSize
65-};
66-
67-struct SpokePreferences
68-{
69- int32 mPregameTicksBeforeNetDeath;
70- int32 mInGameTicksBeforeNetDeath;
71-// int32 mOutgoingFlagsQueueSize;
72- int32 mRecoverySendPeriod;
73- int32 mTimingWindowSize;
74- int32 mTimingNthElement;
75- bool mAdjustTiming;
76-};
77-
78-static SpokePreferences sSpokePreferences;
79-
80-static TickBasedActionQueue sOutgoingFlags(kDefaultOutgoingFlagsQueueSize);
81-static TickBasedActionQueue sUnconfirmedFlags(kDefaultOutgoingFlagsQueueSize);
82-static DuplicatingTickBasedCircularQueue<action_flags_t> sLocallyGeneratedFlags;
83-static int32 sSmallestRealGameTick;
84-
85-struct IncomingGameDataPacketProcessingContext {
86- bool mMessagesDone;
87- bool mGotTimingAdjustmentMessage;
88-
89- IncomingGameDataPacketProcessingContext() : mMessagesDone(false), mGotTimingAdjustmentMessage(false) {}
90-};
91-
92-typedef void (*StarMessageHandler)(AIStream& s, IncomingGameDataPacketProcessingContext& c);
93-typedef std::map<uint16, StarMessageHandler> MessageTypeToMessageHandler;
94-
95-static MessageTypeToMessageHandler sMessageTypeToMessageHandler;
96-
97-static int8 sRequestedTimingAdjustment;
98-static int8 sOutstandingTimingAdjustment;
99-
100-struct NetworkPlayer_spoke {
101- bool mZombie;
102- bool mConnected;
103- int32 mNetDeadTick;
104- WritableTickBasedActionQueue* mQueue;
105-};
106-
107-static vector<NetworkPlayer_spoke> sNetworkPlayers;
108-static int32 sNetworkTicker;
109-static int32 sLastNetworkTickHeard;
110-static int32 sLastNetworkTickSent;
111-static bool sConnected = false;
112-static bool sSpokeActive = false;
113-static myTMTaskPtr sSpokeTickTask = NULL;
114-static DDPFramePtr sOutgoingFrame = NULL;
115-static DDPPacketBuffer sLocalOutgoingBuffer;
116-static bool sNeedToSendLocalOutgoingBuffer = false;
117-static bool sHubIsLocal = false;
118-static NetAddrBlock sHubAddress;
119-static size_t sLocalPlayerIndex;
120-static int32 sSmallestUnreceivedTick;
121-static WindowedNthElementFinder<int32> sNthElementFinder(kDefaultTimingWindowSize);
122-static bool sTimingMeasurementValid;
123-static int32 sTimingMeasurement;
124-static bool sHeardFromHub = false;
125-
126-static vector<int32> sDisplayLatencyBuffer; // stores the last 30 latency calculations, in ticks
127-static uint32 sDisplayLatencyCount = 0;
128-static int32 sDisplayLatencyTicks = 0; // sum of the latency ticks from the last 30 seconds, using above two
129-
130-static int32 sSmallestUnconfirmedTick;
131-
132-struct SpokeLossyByteStreamChunkDescriptor
133-{
134- uint16 mLength;
135- int16 mType;
136- uint32 mDestinations;
137-};
138-
139-// This holds outgoing lossy byte stream data
140-static CircularByteBuffer sOutgoingLossyByteStreamData(kLossyByteStreamDataBufferSize);
141-
142-// This holds a descriptor for each chunk of lossy byte stream data held in the above buffer
143-static CircularQueue<SpokeLossyByteStreamChunkDescriptor> sOutgoingLossyByteStreamDescriptors(kLossyByteStreamDescriptorCount);
144-
145-// This is currently used only to hold incoming streaming data until it's passed to the upper-level code
146-static byte sScratchBuffer[kLossyByteStreamDataBufferSize];
147-
148-
149-static void spoke_became_disconnected();
150-static void spoke_received_game_data_packet_v1(AIStream& ps, bool reflected_flags);
151-static void process_messages(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
152-static void handle_end_of_messages_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
153-static void handle_player_net_dead_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
154-static void handle_timing_adjustment_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
155-static void handle_lossy_byte_stream_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
156-static void process_optional_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context, uint16 inMessageType);
157-static bool spoke_tick();
158-static void send_packet();
159-static void send_identification_packet();
160-
161-
162-static inline NetworkPlayer_spoke&
163-getNetworkPlayer(size_t inIndex)
164-{
165- assert(inIndex < sNetworkPlayers.size());
166- return sNetworkPlayers[inIndex];
167-}
168-
169-
170-
171-static inline bool
172-operator !=(const NetAddrBlock& a, const NetAddrBlock& b)
173-{
174- return memcmp(&a, &b, sizeof(a)) != 0;
175-}
176-
177-
178-
179-static OSErr
180-send_frame_to_local_hub(DDPFramePtr frame, NetAddrBlock *address, short protocolType, short port)
181-{
182- sLocalOutgoingBuffer.datagramSize = frame->data_size;
183- memcpy(sLocalOutgoingBuffer.datagramData, frame->data, frame->data_size);
184- sLocalOutgoingBuffer.protocolType = protocolType;
185- // An all-0 sourceAddress is the cue for "local spoke" currently.
186- obj_clear(sLocalOutgoingBuffer.sourceAddress);
187- sNeedToSendLocalOutgoingBuffer = true;
188- return noErr;
189-}
190-
191-
192-
193-static inline void
194-check_send_packet_to_hub()
195-{
196- if(sNeedToSendLocalOutgoingBuffer)
197- {
198- logContextNMT("delivering stored packet to local hub");
199- hub_received_network_packet(&sLocalOutgoingBuffer);
200- }
201-
202- sNeedToSendLocalOutgoingBuffer = false;
203-}
204-
205-
206-
207-void
208-spoke_initialize(const NetAddrBlock& inHubAddress, int32 inFirstTick, size_t inNumberOfPlayers, WritableTickBasedActionQueue* const inPlayerQueues[], bool inPlayerConnected[], size_t inLocalPlayerIndex, bool inHubIsLocal)
209-{
210- assert(inNumberOfPlayers >= 1);
211- assert(inLocalPlayerIndex < inNumberOfPlayers);
212- assert(inPlayerQueues[inLocalPlayerIndex] != NULL);
213- assert(inPlayerConnected[inLocalPlayerIndex]);
214-
215- sHubIsLocal = inHubIsLocal;
216- sHubAddress = inHubAddress;
217-
218- sLocalPlayerIndex = inLocalPlayerIndex;
219-
220- sOutgoingFrame = NetDDPNewFrame();
221-
222- sSmallestRealGameTick = inFirstTick;
223- int32 theFirstPregameTick = inFirstTick - kPregameTicks;
224- sOutgoingFlags.reset(theFirstPregameTick);
225- sUnconfirmedFlags.reset(sSmallestRealGameTick);
226- sSmallestUnconfirmedTick = sSmallestRealGameTick;
227- sSmallestUnreceivedTick = theFirstPregameTick;
228-
229- sNetworkPlayers.clear();
230- sNetworkPlayers.resize(inNumberOfPlayers);
231-
232- sLocallyGeneratedFlags.children().clear();
233- sLocallyGeneratedFlags.children().insert(&sOutgoingFlags);
234- sLocallyGeneratedFlags.children().insert(&sUnconfirmedFlags);
235-
236- for(size_t i = 0; i < inNumberOfPlayers; i++)
237- {
238- sNetworkPlayers[i].mZombie = (inPlayerQueues[i] == NULL);
239- sNetworkPlayers[i].mConnected = inPlayerConnected[i];
240- sNetworkPlayers[i].mNetDeadTick = theFirstPregameTick - 1;
241- sNetworkPlayers[i].mQueue = inPlayerQueues[i];
242- if(sNetworkPlayers[i].mConnected)
243- {
244- sNetworkPlayers[i].mQueue->reset(sSmallestRealGameTick);
245- }
246- }
247-
248- sRequestedTimingAdjustment = 0;
249- sOutstandingTimingAdjustment = 0;
250-
251- sNetworkTicker = 0;
252- sLastNetworkTickHeard = 0;
253- sLastNetworkTickSent = 0;
254- sConnected = true;
255- sNthElementFinder.reset(sSpokePreferences.mTimingWindowSize);
256- sTimingMeasurementValid = false;
257-
258- sOutgoingLossyByteStreamDescriptors.reset();
259- sOutgoingLossyByteStreamData.reset();
260-
261- sMessageTypeToMessageHandler.clear();
262- sMessageTypeToMessageHandler[kEndOfMessagesMessageType] = handle_end_of_messages_message;
263- sMessageTypeToMessageHandler[kTimingAdjustmentMessageType] = handle_timing_adjustment_message;
264- sMessageTypeToMessageHandler[kPlayerNetDeadMessageType] = handle_player_net_dead_message;
265- sMessageTypeToMessageHandler[kHubToSpokeLossyByteStreamMessageType] = handle_lossy_byte_stream_message;
266-
267- sNeedToSendLocalOutgoingBuffer = false;
268-
269- sSpokeActive = true;
270- sSpokeTickTask = myXTMSetup(1000/TICKS_PER_SECOND, spoke_tick);
271-
272- sDisplayLatencyBuffer.resize(TICKS_PER_SECOND, 0);
273- sDisplayLatencyCount = 0;
274- sDisplayLatencyTicks = 0;
275-
276- sHeardFromHub = false;
277-}
278-
279-
280-
281-void
282-spoke_cleanup(bool inGraceful)
283-{
284- // Stop processing incoming packets (packet processor won't start processing another packet
285- // due to sSpokeActive = false, and we know it's not in the middle of processing one because
286- // we take the mutex).
287- if(take_mytm_mutex())
288- {
289- // Mark the tick task for cancellation (it won't start running again after this returns).
290- myTMRemove(sSpokeTickTask);
291- sSpokeTickTask = NULL;
292-
293- sSpokeActive = false;
294-
295- // We send one last packet here to try to not leave the hub hanging on our ACK.
296- send_packet();
297- check_send_packet_to_hub();
298-
299- release_mytm_mutex();
300- }
301-
302- // This waits for the tick task to actually finish
303- myTMCleanup(true);
304-
305- sMessageTypeToMessageHandler.clear();
306- sNetworkPlayers.clear();
307- sLocallyGeneratedFlags.children().clear();
308- sDisplayLatencyBuffer.clear();
309- NetDDPDisposeFrame(sOutgoingFrame);
310- sOutgoingFrame = NULL;
311-}
312-
313-
314-
315-int32
316-spoke_get_net_time()
317-{
318- static int32 sPreviousDelay = -1;
319-
320- int32 theDelay = (sSpokePreferences.mAdjustTiming && sTimingMeasurementValid) ? sTimingMeasurement : 0;
321-
322- if(theDelay != sPreviousDelay)
323- {
324- logDump1("local delay is now %d", theDelay);
325- sPreviousDelay = theDelay;
326- }
327-
328- return (sConnected ? sOutgoingFlags.getWriteTick() - theDelay : getNetworkPlayer(sLocalPlayerIndex).mQueue->getWriteTick());
329-}
330-
331-
332-
333-void
334-spoke_distribute_lossy_streaming_bytes_to_everyone(int16 inDistributionType, byte* inBytes, uint16 inLength, bool inExcludeLocalPlayer, bool onlySendToTeam)
335-{
336-
337- int16 local_team;
338- if (onlySendToTeam)
339- {
340- player_info* player = (player_info *)NetGetPlayerData(sLocalPlayerIndex);
341- local_team = player->team;
342- }
343-
344- uint32 theDestinations = 0;
345- for(size_t i = 0; i < sNetworkPlayers.size(); i++)
346- {
347- if((i != sLocalPlayerIndex || !inExcludeLocalPlayer) && !sNetworkPlayers[i].mZombie && sNetworkPlayers[i].mConnected)
348- {
349- if (onlySendToTeam)
350- {
351- player_info* player = (player_info *)NetGetPlayerData(i);
352- if (player->team == local_team)
353- theDestinations |= (((uint32)1) << i);
354-
355- }
356- else
357- {
358- theDestinations |= (((uint32)1) << i);
359- }
360- }
361- }
362-
363- spoke_distribute_lossy_streaming_bytes(inDistributionType, theDestinations, inBytes, inLength);
364-}
365-
366-
367-
368-void
369-spoke_distribute_lossy_streaming_bytes(int16 inDistributionType, uint32 inDestinationsBitmask, byte* inBytes, uint16 inLength)
370-{
371- if(inLength > sOutgoingLossyByteStreamData.getRemainingSpace())
372- {
373- logNoteNMT2("spoke has insufficient buffer space for %hu bytes of outgoing lossy streaming type %hd; discarded", inLength, inDistributionType);
374- return;
375- }
376-
377- if(sOutgoingLossyByteStreamDescriptors.getRemainingSpace() < 1)
378- {
379- logNoteNMT2("spoke has exhausted descriptor buffer space; discarding %hu bytes of outgoing lossy streaming type %hd", inLength, inDistributionType);
380- return;
381- }
382-
383- struct SpokeLossyByteStreamChunkDescriptor theDescriptor;
384- theDescriptor.mLength = inLength;
385- theDescriptor.mDestinations = inDestinationsBitmask;
386- theDescriptor.mType = inDistributionType;
387-
388- logDumpNMT3("spoke application decided to send %d bytes of lossy streaming type %d destined for players 0x%x", inLength, inDistributionType, inDestinationsBitmask);
389-
390- sOutgoingLossyByteStreamData.enqueueBytes(inBytes, inLength);
391- sOutgoingLossyByteStreamDescriptors.enqueue(theDescriptor);
392-}
393-
394-
395-
396-static void
397-spoke_became_disconnected()
398-{
399- sConnected = false;
400- for(size_t i = 0; i < sNetworkPlayers.size(); i++)
401- {
402- if(sNetworkPlayers[i].mConnected)
403- make_player_really_net_dead(i);
404- }
405-}
406-
407-
408-
409-void
410-spoke_received_network_packet(DDPPacketBufferPtr inPacket)
411-{
412- logContextNMT("spoke processing a received packet");
413-
414- // If we've already given up on the connection, ignore packets.
415- if(!sConnected || !sSpokeActive)
416- return;
417-
418- // Ignore packets not from our hub
419-// if(inPacket->sourceAddress != sHubAddress)
420-// return;
421-
422- try {
423- AIStreamBE ps(inPacket->datagramData, inPacket->datagramSize);
424-
425- uint16 thePacketMagic;
426- ps >> thePacketMagic;
427-
428- uint16 thePacketCRC;
429- ps >> thePacketCRC;
430-
431- // blank out the CRC field before calculating
432- inPacket->datagramData[2] = 0;
433- inPacket->datagramData[3] = 0;
434-
435- if (thePacketCRC != calculate_data_crc_ccitt(inPacket->datagramData, inPacket->datagramSize))
436- {
437- logWarningNMT1("CRC failure; discarding packet type %i", thePacketMagic);
438- return;
439- }
440-
441- switch(thePacketMagic)
442- {
443- case kHubToSpokeGameDataPacketV1Magic:
444- spoke_received_game_data_packet_v1(ps, false);
445- break;
446-
447- case kHubToSpokeGameDataPacketWithSpokeFlagsV1Magic:
448- spoke_received_game_data_packet_v1(ps, true);
449- break;
450-
451- default:
452- // Ignore unknown packet types
453- logTraceNMT1("unknown packet type %i", thePacketMagic);
454- break;
455- }
456- }
457- catch(...)
458- {
459- // do nothing special, we just ignore the remainder of the packet.
460- }
461-}
462-
463-
464-
465-static void
466-spoke_received_game_data_packet_v1(AIStream& ps, bool reflected_flags)
467-{
468- sHeardFromHub = true;
469-
470- IncomingGameDataPacketProcessingContext context;
471-
472- // Piggybacked ACK
473- int32 theSmallestUnacknowledgedTick;
474- ps >> theSmallestUnacknowledgedTick;
475-
476- // we can get an early ACK only if the server made up flags for us...
477- if (theSmallestUnacknowledgedTick > sOutgoingFlags.getWriteTick())
478- {
479- if (reflected_flags)
480- {
481- theSmallestUnacknowledgedTick = sOutgoingFlags.getWriteTick();
482- }
483- else
484- {
485- logTraceNMT2("early ack (%d > %d)", theSmallestUnacknowledgedTick, sOutgoingFlags.getWriteTick());
486- return;
487- }
488- }
489-
490-
491- // Heard from hub
492- sLastNetworkTickHeard = sNetworkTicker;
493-
494- // Remove acknowledged elements from outgoing queue
495- for(int tick = sOutgoingFlags.getReadTick(); tick < theSmallestUnacknowledgedTick; tick++)
496- {
497- logTraceNMT1("dequeueing tick %d from sOutgoingFlags", tick);
498- sOutgoingFlags.dequeue();
499- }
500-
501- // Process messages
502- process_messages(ps, context);
503-
504- if(!context.mGotTimingAdjustmentMessage)
505- {
506- if(sRequestedTimingAdjustment != 0)
507- logTraceNMT("timing adjustment no longer requested");
508-
509- sRequestedTimingAdjustment = 0;
510- }
511-
512- // Action_flags!!!
513- // If there aren't any, we're done
514- if(ps.tellg() >= ps.maxg())
515- {
516- // If we are the only connected player, well because of the way the loop below works,
517- // we have to enqueue net_dead flags for the other players now, up to match the most
518- // recent tick the hub has acknowledged.
519- // We can't assume we are the only connected player merely by virtue of there being no
520- // flags in the packet. The hub could be waiting on somebody else to send and is
521- // essentially sending us "keep-alive" packets (which could contain no new flags) in the meantime.
522- // In other words, (we are alone) implies (no flags in packet), but not the converse.
523- // Here "alone" means there are no other players who are connected or who will be netdead
524- // sometime in the future. (If their NetDeadTick is greater than the ACKed tick, we expect
525- // that the hub will be sending actual flags in the future to make up the difference.)
526- bool weAreAlone = true;
527- int32 theSmallestUnacknowledgedTick = sOutgoingFlags.getReadTick();
528- for(size_t i = 0; i < sNetworkPlayers.size(); i++)
529- {
530- if(i != sLocalPlayerIndex && (sNetworkPlayers[i].mConnected || sNetworkPlayers[i].mNetDeadTick > theSmallestUnacknowledgedTick))
531- {
532- weAreAlone = false;
533- break;
534- }
535- }
536-
537- if(weAreAlone)
538- {
539- logContextNMT("handling special \"we are alone\" case");
540-
541- for(size_t i = 0; i < sNetworkPlayers.size(); i++)
542- {
543- NetworkPlayer_spoke& thePlayer = sNetworkPlayers[i];
544- if (i == sLocalPlayerIndex)
545- {
546- while (sSmallestUnconfirmedTick < sUnconfirmedFlags.getWriteTick())
547- {
548- sNetworkPlayers[i].mQueue->enqueue(sUnconfirmedFlags.peek(sSmallestUnconfirmedTick++));
549- }
550- }
551- else if (!thePlayer.mZombie)
552- {
553- while(thePlayer.mQueue->getWriteTick() < theSmallestUnacknowledgedTick)
554- {
555- logDumpNMT2("enqueued NET_DEAD_ACTION_FLAG for player %d tick %d", i, thePlayer.mQueue->getWriteTick());
556- thePlayer.mQueue->enqueue(static_cast<action_flags_t>(NET_DEAD_ACTION_FLAG));
557- }
558- }
559- }
560-
561- sSmallestUnreceivedTick = theSmallestUnacknowledgedTick;
562- logDumpNMT1("sSmallestUnreceivedTick is now %d", sSmallestUnreceivedTick);
563- }
564-
565- return;
566- } // no data left in packet
567-
568- int32 theSmallestUnreadTick;
569- ps >> theSmallestUnreadTick;
570-
571- // Can't accept packets that skip ticks
572- if(theSmallestUnreadTick > sSmallestUnreceivedTick)
573- {
574- logTraceNMT2("early flags (%d > %d)", theSmallestUnreadTick, sSmallestUnreceivedTick);
575- return;
576- }
577-
578- // Figure out how many ticks of flags we can actually enqueue
579- // We want to stock all queues evenly, since we ACK everyone's flags for a tick together.
580- int theSmallestQueueSpace = INT_MAX;
581- for(size_t i = 0; i < sNetworkPlayers.size(); i++)
582- {
583- // we'll never get flags for zombies, and we're not expected
584- // to enqueue flags for zombies
585- if(sNetworkPlayers[i].mZombie)
586- continue;
587-
588- int theQueueSpace = sNetworkPlayers[i].mQueue->availableCapacity();
589-
590- /*
591- hmm, taking this exemption out, because we will start enqueueing PLAYER_NET_DEAD_FLAG onto the queue.
592- // If player is netdead or will become netdead before queue fills,
593- // player's queue space will not limit us
594- if(!sNetworkPlayers[i].mConnected)
595- {
596- int theRemainingLiveTicks = sNetworkPlayers[i].mNetDeadTick - sSmallestUnreceivedTick;
597- if(theRemainingLiveTicks < theQueueSpace)
598- continue;
599- }
600- */
601-
602- if(theQueueSpace < theSmallestQueueSpace)
603- theSmallestQueueSpace = theQueueSpace;
604- }
605-
606- logDumpNMT1("%d queue space available", theSmallestQueueSpace);
607-
608- // Read and enqueue the actual action_flags from the packet
609- // The body of this loop is a bit more convoluted than you might
610- // expect, because the same loop is used to skip already-seen action_flags
611- // and to enqueue new ones.
612- while(ps.tellg() < ps.maxg())
613- {
614- // If we've no room to enqueue stuff, no point in finishing reading the packet.
615- if(theSmallestQueueSpace <= 0)
616- break;
617-
618- for(size_t i = 0; i < sNetworkPlayers.size(); i++)
619- {
620-
621- // We'll never get flags for zombies
622- if (sNetworkPlayers[i].mZombie)
623- continue;
624-
625- // if our own flags are not sent back to us,
626- // confirm the ones we have in our unconfirmed queue,
627- // and do not read any from the packet
628- if (i == sLocalPlayerIndex && !reflected_flags)
629- {
630- if (theSmallestUnreadTick == sSmallestUnreceivedTick && theSmallestUnreadTick >= sSmallestRealGameTick)
631- {
632- assert(sNetworkPlayers[i].mQueue->getWriteTick() == sSmallestUnconfirmedTick);
633- assert(sSmallestUnconfirmedTick >= sUnconfirmedFlags.getReadTick());
634- assert(sSmallestUnconfirmedTick < sUnconfirmedFlags.getWriteTick());
635- // confirm this flag
636- sNetworkPlayers[i].mQueue->enqueue(sUnconfirmedFlags.peek(sSmallestUnconfirmedTick));
637- sSmallestUnconfirmedTick++;
638- }
639-
640- continue;
641- }
642-
643- bool shouldEnqueueNetDeadFlags = false;
644-
645- // We won't get flags for netdead players
646- NetworkPlayer_spoke& thePlayer = sNetworkPlayers[i];
647- if(!thePlayer.mConnected)
648- {
649- if(thePlayer.mNetDeadTick < theSmallestUnreadTick)
650- shouldEnqueueNetDeadFlags = true;
651-
652- if(thePlayer.mNetDeadTick == theSmallestUnreadTick)
653- {
654- // Only actually act if this tick is new to us
655- if(theSmallestUnreadTick == sSmallestUnreceivedTick)
656- make_player_really_net_dead(i);
657- shouldEnqueueNetDeadFlags = true;
658- }
659- }
660-
661- // Decide what flags are appropriate for this player this tick
662- action_flags_t theFlags;
663- if(shouldEnqueueNetDeadFlags)
664- // We effectively generate a tick's worth of flags in lieu of reading it from the packet.
665- theFlags = static_cast<action_flags_t>(NET_DEAD_ACTION_FLAG);
666- else
667- {
668- // We should have a flag for this player for this tick!
669- try
670- {
671- ps >> theFlags;
672- }
673- catch (const AStream::failure& f)
674- {
675- logWarningNMT3("AStream exception (%s) for player %i at theSmallestUnreadTick %i! OOS is likely!\n", f.what(), i, theSmallestUnreadTick);
676- return;
677- }
678- }
679-
680-
681- // Now, we've gotten flags, probably from the packet... should we enqueue them?
682- if(theSmallestUnreadTick == sSmallestUnreceivedTick)
683- {
684- if(theSmallestUnreadTick >= sSmallestRealGameTick)
685- {
686- WritableTickBasedActionQueue& theQueue = *(sNetworkPlayers[i].mQueue);
687- assert(theQueue.getWriteTick() == sSmallestUnreceivedTick);
688- assert(theQueue.availableCapacity() > 0);
689- logTraceNMT3("enqueueing flags %x for player %d tick %d", theFlags, i, theQueue.getWriteTick());
690- theQueue.enqueue(theFlags);
691- if (i == sLocalPlayerIndex) sSmallestUnconfirmedTick++;
692- }
693- }
694-
695- } // iterate over players
696-
697- theSmallestUnreadTick++;
698- if(sSmallestUnreceivedTick < theSmallestUnreadTick)
699- {
700- theSmallestQueueSpace--;
701- sSmallestUnreceivedTick = theSmallestUnreadTick;
702-
703- int32 theLatencyMeasurement = sOutgoingFlags.getWriteTick() - sSmallestUnreceivedTick;
704- logDumpNMT1("latency measurement: %d", theLatencyMeasurement);
705-
706-// We can't use NthElementFinder at interrupt time in Mac OS 9 since its std::set can [de]allocate memory
707-// We don't really use it for anything on the spoke-side now anyway, thanks to player motion prediction...
708-#if !(defined(mac) && !defined(__MACH__))
709- sNthElementFinder.insert(theLatencyMeasurement);
710- // We capture these values here so we don't have to take a lock in GetNetTime.
711- sTimingMeasurementValid = sNthElementFinder.window_full();
712- if(sTimingMeasurementValid)
713- sTimingMeasurement = sNthElementFinder.nth_largest_element(sSpokePreferences.mTimingNthElement);
714-#endif
715-
716- // update the latency display
717- sDisplayLatencyTicks -= sDisplayLatencyBuffer[sDisplayLatencyCount % sDisplayLatencyBuffer.size()];
718- sDisplayLatencyBuffer[sDisplayLatencyCount++ % sDisplayLatencyBuffer.size()] = theLatencyMeasurement;
719- sDisplayLatencyTicks += theLatencyMeasurement;
720- }
721-
722- } // loop while there's packet data left
723-
724-}
725-
726-
727-
728-static void
729-process_messages(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
730-{
731- while(!context.mMessagesDone)
732- {
733- uint16 theMessageType;
734- ps >> theMessageType;
735-
736- MessageTypeToMessageHandler::iterator i = sMessageTypeToMessageHandler.find(theMessageType);
737-
738- if(i == sMessageTypeToMessageHandler.end())
739- process_optional_message(ps, context, theMessageType);
740- else
741- i->second(ps, context);
742- }
743-}
744-
745-
746-
747-static void
748-handle_end_of_messages_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
749-{
750- context.mMessagesDone = true;
751-}
752-
753-
754-
755-static void
756-handle_player_net_dead_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
757-{
758- uint8 thePlayerIndex;
759- int32 theTick;
760-
761- ps >> thePlayerIndex >> theTick;
762-
763- if(thePlayerIndex > sNetworkPlayers.size())
764- return;
765-
766- sNetworkPlayers[thePlayerIndex].mConnected = false;
767- sNetworkPlayers[thePlayerIndex].mNetDeadTick = theTick;
768-
769- logDumpNMT2("netDead message: player %d in tick %d", thePlayerIndex, theTick);
770-}
771-
772-
773-
774-static void
775-handle_timing_adjustment_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
776-{
777- int8 theAdjustment;
778-
779- ps >> theAdjustment;
780-
781- if(theAdjustment != sRequestedTimingAdjustment)
782- {
783- sOutstandingTimingAdjustment = theAdjustment;
784- sRequestedTimingAdjustment = theAdjustment;
785- logTraceNMT2("new timing adjustment message; requested: %d outstanding: %d", sRequestedTimingAdjustment, sOutstandingTimingAdjustment);
786- }
787-
788- context.mGotTimingAdjustmentMessage = true;
789-}
790-
791-
792-
793-static void
794-handle_lossy_byte_stream_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
795-{
796- uint16 theMessageLength;
797- ps >> theMessageLength;
798-
799- size_t theStartOfMessage = ps.tellg();
800-
801- int16 theDistributionType;
802- uint8 theSendingPlayer;
803- ps >> theDistributionType >> theSendingPlayer;
804-
805- uint16 theDataLength = theMessageLength - (ps.tellg() - theStartOfMessage);
806- uint16 theSpilloverDataLength = 0;
807- if(theDataLength > sizeof(sScratchBuffer))
808- {
809- logNoteNMT3("received too many bytes (%d) of lossy streaming data type %d from player %d; truncating", theDataLength, theDistributionType, theSendingPlayer);
810- theSpilloverDataLength = theDataLength - sizeof(sScratchBuffer);
811- theDataLength = sizeof(sScratchBuffer);
812- }
813- ps.read(sScratchBuffer, theDataLength);
814- ps.ignore(theSpilloverDataLength);
815-
816- logDumpNMT3("received %d bytes of lossy streaming type %d data from player %d", theDataLength, theDistributionType, theSendingPlayer);
817-
818- call_distribution_response_function_if_available(sScratchBuffer, theDataLength, theDistributionType, theSendingPlayer);
819-}
820-
821-
822-
823-static void
824-process_optional_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context, uint16 inMessageType)
825-{
826- // We don't know of any optional messages, so we just skip any we encounter.
827- // (All optional messages are required to encode their length (not including the
828- // space required for the message type or length) in the two bytes immediately
829- // following the message type.)
830- uint16 theLength;
831- ps >> theLength;
832-
833- ps.ignore(theLength);
834-}
835-
836-
837-
838-static bool
839-spoke_tick()
840-{
841- logContextNMT1("processing spoke_tick %d", sNetworkTicker);
842-
843- sNetworkTicker++;
844-
845- if(sConnected)
846- {
847- int32 theSilentTicksBeforeNetDeath = (sOutgoingFlags.getReadTick() >= sSmallestRealGameTick) ? sSpokePreferences.mInGameTicksBeforeNetDeath : sSpokePreferences.mPregameTicksBeforeNetDeath;
848-
849- if(sNetworkTicker - sLastNetworkTickHeard > theSilentTicksBeforeNetDeath)
850- {
851- logTraceNMT("giving up on hub; disconnecting");
852- spoke_became_disconnected();
853- return true;
854- }
855- }
856-
857- bool shouldSend = false;
858-
859- // Negative timing adjustment means we need to provide extra ticks because we're late.
860- // We let this cover the normal timing adjustment = 0 case too.
861- if(sOutstandingTimingAdjustment <= 0)
862- {
863- int theNumberOfFlagsToProvide = -sOutstandingTimingAdjustment + 1;
864-
865- logDumpNMT1("want to provide %d flags", theNumberOfFlagsToProvide);
866-
867- while(theNumberOfFlagsToProvide > 0)
868- {
869-
870- // If we're not connected, write only to our local game's queue.
871- // If we are connected,
872- // if we're actually in the game, write to both local game and outbound flags queues
873- // else (if pregame), write only to the outbound flags queue.
874-
875- WritableTickBasedActionQueue& theTargetQueue =
876- sConnected ?
877- ((sOutgoingFlags.getWriteTick() >= sSmallestRealGameTick) ?
878- static_cast<WritableTickBasedActionQueue&>(sLocallyGeneratedFlags)
879- : static_cast<WritableTickBasedActionQueue&>(sOutgoingFlags))
880- : *(sNetworkPlayers[sLocalPlayerIndex].mQueue);
881-
882- if(theTargetQueue.availableCapacity() <= 0)
883- break;
884-
885- logDumpNMT1("enqueueing flags for tick %d", theTargetQueue.getWriteTick());
886-
887- theTargetQueue.enqueue(parse_keymap());
888- shouldSend = true;
889- theNumberOfFlagsToProvide--;
890- }
891-
892- // Prevent creeping timing adjustment during "lulls"; OTOH remember to
893- // finish next time if we made progress but couldn't complete our obligation.
894- if(theNumberOfFlagsToProvide != -sOutstandingTimingAdjustment + 1)
895- sOutstandingTimingAdjustment = -theNumberOfFlagsToProvide;
896- }
897- // Positive timing adjustment means we should delay sending for a while,
898- // so we just throw away this local tick.
899- else
900- {
901- logDumpNMT("ignoring this tick for timing adjustment");
902- sOutstandingTimingAdjustment--;
903- }
904-
905- logDumpNMT1("sOutstandingTimingAdjustment is now %d", sOutstandingTimingAdjustment);
906-
907- if(sOutgoingLossyByteStreamDescriptors.getCountOfElements() > 0)
908- shouldSend = true;
909-
910- // If we're connected and (we generated new data or if it's been long enough since we last sent), send.
911- if(sConnected)
912- {
913- if (sHeardFromHub) {
914- if(shouldSend || (sNetworkTicker - sLastNetworkTickSent) >= sSpokePreferences.mRecoverySendPeriod)
915- send_packet();
916- } else {
917- if (!(sNetworkTicker % 30))
918- send_identification_packet();
919- }
920- }
921- else
922- {
923- int32 theLocalPlayerWriteTick = getNetworkPlayer(sLocalPlayerIndex).mQueue->getWriteTick();
924-
925- // Since we're not connected, we won't be enqueueing flags for the other players in the packet handler.
926- // So, we do it here to keep the game moving.
927- for(size_t i = 0; i < sNetworkPlayers.size(); i++)
928- {
929- if(i == sLocalPlayerIndex)
930- {
931- // move our flags from sent queue to player queue
932- while (sSmallestUnconfirmedTick < sUnconfirmedFlags.getWriteTick())
933- {
934- sNetworkPlayers[i].mQueue->enqueue(sUnconfirmedFlags.peek(sSmallestUnconfirmedTick++));
935- }
936- continue;
937- }
938-
939- NetworkPlayer_spoke& thePlayer = sNetworkPlayers[i];
940-
941- if(!thePlayer.mZombie)
942- {
943- while(thePlayer.mQueue->getWriteTick() < theLocalPlayerWriteTick)
944- {
945- logDumpNMT2("enqueueing NET_DEAD_ACTION_FLAG for player %d tick %d", i, thePlayer.mQueue->getWriteTick());
946- thePlayer.mQueue->enqueue(static_cast<action_flags_t>(NET_DEAD_ACTION_FLAG));
947- }
948- }
949- }
950- }
951-
952- check_send_packet_to_hub();
953-
954- // We want to run again.
955- return true;
956-}
957-
958-
959-
960-static void
961-send_packet()
962-{
963- try {
964- AOStreamBE hdr(sOutgoingFrame->data, kStarPacketHeaderSize);
965- AOStreamBE ps(sOutgoingFrame->data, ddpMaxData, kStarPacketHeaderSize);
966-
967- // Packet type
968- hdr << (uint16)kSpokeToHubGameDataPacketV1Magic;
969-
970- // Acknowledgement
971- ps << sSmallestUnreceivedTick;
972-
973- // Messages
974- // Outstanding lossy streaming bytes?
975- if(sOutgoingLossyByteStreamDescriptors.getCountOfElements() > 0)
976- {
977- // Note: we make a conscious decision here to dequeue these things before
978- // writing to ps, so that if the latter operation exhausts ps's buffer and
979- // throws, we have less data to mess with next time, and shouldn't end up
980- // throwing every time we try to send here.
981- // If we eventually got smarter about managing packet space, we could try
982- // harder to preserve and pace data - e.g. change the 'if' immediately before this
983- // comment to a 'while', only put in as much data as we think we can fit, etc.
984- SpokeLossyByteStreamChunkDescriptor theDescriptor = sOutgoingLossyByteStreamDescriptors.peek();
985- sOutgoingLossyByteStreamDescriptors.dequeue();
986-
987- uint16 theMessageLength = theDescriptor.mLength + sizeof(theDescriptor.mType) + sizeof(theDescriptor.mDestinations);
988-
989- ps << (uint16)kSpokeToHubLossyByteStreamMessageType
990- << theMessageLength
991- << theDescriptor.mType
992- << theDescriptor.mDestinations;
993-
994- // XXX unnecessary copy due to overly restrictive interfaces (retaining for clarity)
995- assert(theDescriptor.mLength <= sizeof(sScratchBuffer));
996- sOutgoingLossyByteStreamData.peekBytes(sScratchBuffer, theDescriptor.mLength);
997- sOutgoingLossyByteStreamData.dequeue(theDescriptor.mLength);
998-
999- ps.write(sScratchBuffer, theDescriptor.mLength);
1000- }
1001-
1002- // No more messages
1003- ps << (uint16)kEndOfMessagesMessageType;
1004-
1005- // Action_flags!!!
1006- if(sOutgoingFlags.size() > 0)
1007- {
1008- ps << sOutgoingFlags.getReadTick();
1009- for(int32 tick = sOutgoingFlags.getReadTick(); tick < sOutgoingFlags.getWriteTick(); tick++)
1010- ps << sOutgoingFlags.peek(tick);
1011- }
1012-
1013- logDumpNMT3("preparing to send packet: ACK %d, flags [%d,%d)", sSmallestUnreceivedTick, sOutgoingFlags.getReadTick(), sOutgoingFlags.getWriteTick());
1014-
1015- // blank out the CRC before calculating it
1016- sOutgoingFrame->data[2] = 0;
1017- sOutgoingFrame->data[3] = 0;
1018-
1019- uint16 crc = calculate_data_crc_ccitt(sOutgoingFrame->data, ps.tellp());
1020- hdr << crc;
1021-
1022- // Send the packet
1023- sOutgoingFrame->data_size = ps.tellp();
1024-
1025- if(sHubIsLocal)
1026- send_frame_to_local_hub(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1027- else
1028- NetDDPSendFrame(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1029-
1030- sLastNetworkTickSent = sNetworkTicker;
1031- }
1032- catch (...) {
1033- }
1034-}
1035-
1036-
1037-
1038-static void
1039-send_identification_packet()
1040-{
1041- try {
1042- AOStreamBE hdr(sOutgoingFrame->data, kStarPacketHeaderSize);
1043- AOStreamBE ps(sOutgoingFrame->data, ddpMaxData, kStarPacketHeaderSize);
1044-
1045- // Message type
1046- hdr << (uint16) kSpokeToHubIdentification;
1047-
1048- // ID
1049- ps << (uint16)sLocalPlayerIndex;
1050-
1051- // blank out the CRC field before calculating
1052- sOutgoingFrame->data[2] = 0;
1053- sOutgoingFrame->data[3] = 0;
1054-
1055- uint16 crc = calculate_data_crc_ccitt(sOutgoingFrame->data, ps.tellp());
1056- hdr << crc;
1057-
1058- // Send the packet
1059- sOutgoingFrame->data_size = ps.tellp();
1060- if(sHubIsLocal)
1061- send_frame_to_local_hub(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1062- else
1063- NetDDPSendFrame(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1064- }
1065- catch (...) {
1066- }
1067-}
1068-
1069-int32 spoke_latency()
1070-{
1071- return (sDisplayLatencyCount >= TICKS_PER_SECOND) ? sDisplayLatencyTicks * 1000 / TICKS_PER_SECOND / sDisplayLatencyBuffer.size() : NetworkStats::invalid;
1072-}
1073-
1074-TickBasedActionQueue* spoke_get_unconfirmed_flags_queue()
1075-{
1076- return &sUnconfirmedFlags;
1077-}
1078-
1079-int32 spoke_get_smallest_unconfirmed_tick()
1080-{
1081- return sSmallestUnconfirmedTick;
1082-}
1083-
1084-
1085-static inline const char *BoolString(bool B) {return (B ? "true" : "false");}
1086-
1087-enum {
1088- kPregameTicksBeforeNetDeathAttribute,
1089- kInGameTicksBeforeNetDeathAttribute,
1090- // kOutgoingFlagsQueueSizeAttribute,
1091- kRecoverySendPeriodAttribute,
1092- kTimingWindowSizeAttribute,
1093- kTimingNthElementAttribute,
1094- kNumInt32Attributes,
1095- kAdjustTimingAttribute = kNumInt32Attributes,
1096- kNumAttributes
1097-};
1098-
1099-static const char* sAttributeStrings[kNumInt32Attributes] =
1100-{
1101- "pregame_ticks_before_net_death",
1102- "ingame_ticks_before_net_death",
1103-// "outgoing_flags_queue_size",
1104- "recovery_send_period",
1105- "timing_window_size",
1106- "timing_nth_element"
1107-};
1108-
1109-static int32* sAttributeDestinations[kNumInt32Attributes] =
1110-{
1111- &sSpokePreferences.mPregameTicksBeforeNetDeath,
1112- &sSpokePreferences.mInGameTicksBeforeNetDeath,
1113-// &sSpokePreferences.mOutgoingFlagsQueueSize,
1114- &sSpokePreferences.mRecoverySendPeriod,
1115- &sSpokePreferences.mTimingWindowSize,
1116- &sSpokePreferences.mTimingNthElement
1117-};
1118-
1119-class XML_SpokeConfigurationParser: public XML_ElementParser
1120-{
1121-public:
1122- bool Start();
1123- bool HandleAttribute(const char *Tag, const char *Value);
1124- bool AttributesDone();
1125-
1126- XML_SpokeConfigurationParser(): XML_ElementParser("spoke") {}
1127-
1128-protected:
1129- bool mAttributePresent[kNumAttributes];
1130- int32 mAttribute[kNumInt32Attributes];
1131- bool mAdjustTiming;
1132-};
1133-
1134-bool XML_SpokeConfigurationParser::Start()
1135-{
1136- for(int i = 0; i < kNumAttributes; i++)
1137- mAttributePresent[i] = false;
1138-
1139- return true;
1140-}
1141-
1142-static const char* sAttributeMultiplySpecifiedString = "attribute multiply specified";
1143-
1144-bool XML_SpokeConfigurationParser::HandleAttribute(const char *Tag, const char *Value)
1145-{
1146- if (StringsEqual(Tag,"adjust_timing"))
1147- {
1148- if(!mAttributePresent[kAdjustTimingAttribute]) {
1149- if(ReadBooleanValueAsBool(Value,mAdjustTiming)) {
1150- mAttributePresent[kAdjustTimingAttribute] = true;
1151- return true;
1152- }
1153- else
1154- return false;
1155- }
1156- else {
1157- ErrorString = sAttributeMultiplySpecifiedString;
1158- return false;
1159- }
1160- }
1161-
1162- else
1163- {
1164- for(size_t i = 0; i < kNumInt32Attributes; i++)
1165- {
1166- if(StringsEqual(Tag,sAttributeStrings[i]))
1167- {
1168- if(!mAttributePresent[i]) {
1169- if(ReadInt32Value(Value,mAttribute[i])) {
1170- mAttributePresent[i] = true;
1171- return true;
1172- }
1173- else
1174- return false;
1175- }
1176- else {
1177- ErrorString = sAttributeMultiplySpecifiedString;
1178- return false;
1179- }
1180- }
1181- }
1182- }
1183-
1184- UnrecognizedTag();
1185- return false;
1186-}
1187-
1188-bool XML_SpokeConfigurationParser::AttributesDone() {
1189- // Ignore out-of-range values
1190- for(int i = 0; i < kNumAttributes; i++)
1191- {
1192- if(mAttributePresent[i])
1193- {
1194- switch(i)
1195- {
1196- case kPregameTicksBeforeNetDeathAttribute:
1197- case kInGameTicksBeforeNetDeathAttribute:
1198- case kRecoverySendPeriodAttribute:
1199- case kTimingWindowSizeAttribute:
1200- if(mAttribute[i] < 1)
1201- {
1202- // I don't know whether this actually does anything if I don't return false,
1203- // but I'd like to honor the user's wishes as far as I can without just throwing
1204- // up my hands.
1205- BadNumericalValue();
1206- logWarning3("improper value %d for attribute %s of <spoke>; must be at least 1. using default of %d", mAttribute[i], sAttributeStrings[i], *(sAttributeDestinations[i]));
1207- mAttributePresent[i] = false;
1208- }
1209- else
1210- {
1211- *(sAttributeDestinations[i]) = mAttribute[i];
1212- }
1213- break;
1214-
1215- case kTimingNthElementAttribute:
1216- if(mAttribute[i] < 0 || mAttribute[i] >= *(sAttributeDestinations[kTimingWindowSizeAttribute]))
1217- {
1218- BadNumericalValue();
1219- logWarning4("improper value %d for attribute %s of <spoke>; must be at least 0 but less than %s. using default of %d", mAttribute[i], sAttributeStrings[i], sAttributeStrings[kTimingWindowSizeAttribute], *(sAttributeDestinations[i]));
1220- mAttributePresent[i] = false;
1221- }
1222- else
1223- {
1224- *(sAttributeDestinations[i]) = mAttribute[i];
1225- }
1226- break;
1227-
1228- case kAdjustTimingAttribute:
1229- sSpokePreferences.mAdjustTiming = mAdjustTiming;
1230- break;
1231-
1232- default:
1233- assert(false);
1234- break;
1235- } // switch(attribute)
1236-
1237- } // if attribute present
1238-
1239- } // loop over attributes
1240-
1241- // The checks above are not sufficient to catch all bad cases; if user specified a window size
1242- // smaller than default, this is our only chance to deal with it.
1243- if(sSpokePreferences.mTimingNthElement >= sSpokePreferences.mTimingWindowSize)
1244- {
1245- logWarning5("value for <spoke> attribute %s (%d) must be less than value for %s (%d). using %d", sAttributeStrings[kTimingNthElementAttribute], mAttribute[kTimingNthElementAttribute], sAttributeStrings[kTimingWindowSizeAttribute], mAttribute[kTimingWindowSizeAttribute], sSpokePreferences.mTimingWindowSize - 1);
1246-
1247- sSpokePreferences.mTimingNthElement = sSpokePreferences.mTimingWindowSize - 1;
1248- }
1249-
1250- return true;
1251-}
1252-
1253-
1254-static XML_SpokeConfigurationParser SpokeConfigurationParser;
1255-
1256-
1257-XML_ElementParser*
1258-Spoke_GetParser() {
1259- return &SpokeConfigurationParser;
1260-}
1261-
1262-
1263-
1264-void
1265-WriteSpokePreferences(FILE* F)
1266-{
1267- fprintf(F, " <spoke\n");
1268- for(size_t i = 0; i < kNumInt32Attributes; i++)
1269- fprintf(F, " %s=\"%d\"\n", sAttributeStrings[i], *(sAttributeDestinations[i]));
1270- fprintf(F, " adjust_timing=\"%s\"\n", BoolString(sSpokePreferences.mAdjustTiming));
1271- fprintf(F, " />\n");
1272-}
1273-
1274-
1275-
1276-void
1277-DefaultSpokePreferences()
1278-{
1279- sSpokePreferences.mPregameTicksBeforeNetDeath = kDefaultPregameTicksBeforeNetDeath;
1280- sSpokePreferences.mInGameTicksBeforeNetDeath = kDefaultInGameTicksBeforeNetDeath;
1281- // sSpokePreferences.mOutgoingFlagsQueueSize = kDefaultOutgoingFlagsQueueSize;
1282- sSpokePreferences.mRecoverySendPeriod = kDefaultRecoverySendPeriod;
1283- sSpokePreferences.mTimingWindowSize = kDefaultTimingWindowSize;
1284- sSpokePreferences.mTimingNthElement = kDefaultTimingNthElement;
1285- sSpokePreferences.mAdjustTiming = true;
1286-}
1287-
1288-#endif // !defined(DISABLE_NETWORKING)
1+/*
2+ * network_star_spoke.cpp
3+
4+ Copyright (C) 2003 and beyond by Woody Zenfell, III
5+ and the "Aleph One" developers.
6+
7+ This program is free software; you can redistribute it and/or modify
8+ it under the terms of the GNU General Public License as published by
9+ the Free Software Foundation; either version 3 of the License, or
10+ (at your option) any later version.
11+
12+ This program is distributed in the hope that it will be useful,
13+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ GNU General Public License for more details.
16+
17+ This license is contained in the file "COPYING",
18+ which is included with this source code; it is available online at
19+ http://www.gnu.org/licenses/gpl.html
20+
21+ * The portion of the star game protocol run on every player's machine.
22+ *
23+ * Created by Woody Zenfell, III on Fri May 02 2003.
24+ *
25+ * May 27, 2003 (Woody Zenfell): lossy byte-stream distribution.
26+ *
27+ * June 30, 2003 (Woody Zenfell): lossy byte-stream distribution more tolerant of scheduling jitter
28+ * (i.e. will queue multiple chunks before send, instead of dropping all data but most recent)
29+ *
30+ * September 17, 2004 (jkvw):
31+ * NAT-friendly networking - we no longer get spoke addresses form topology -
32+ * instead spokes send identification packets to hub with player ID.
33+ * Hub can then associate the ID in the identification packet with the paket's source address.
34+ */
35+
36+#if !defined(DISABLE_NETWORKING)
37+
38+#include "network_star.h"
39+#include "AStream.h"
40+#include "mytm.h"
41+#include "network_private.h" // kPROTOCOL_TYPE
42+#include "WindowedNthElementFinder.h"
43+#include "vbl.h" // parse_keymap
44+#include "CircularByteBuffer.h"
45+#include "Logging.h"
46+#include "crc.h"
47+#include "player.h"
48+
49+#include <map>
50+
51+extern void make_player_really_net_dead(size_t inPlayerIndex);
52+extern void call_distribution_response_function_if_available(byte* inBuffer, uint16 inBufferSize, int16 inDistributionType, uint8 inSendingPlayerIndex);
53+
54+
55+enum {
56+ kDefaultPregameTicksBeforeNetDeath = 90 * TICKS_PER_SECOND,
57+ kDefaultInGameTicksBeforeNetDeath = 5 * TICKS_PER_SECOND,
58+ kDefaultOutgoingFlagsQueueSize = TICKS_PER_SECOND / 2,
59+ kDefaultRecoverySendPeriod = TICKS_PER_SECOND / 2,
60+ kDefaultTimingWindowSize = 3 * TICKS_PER_SECOND,
61+ kDefaultTimingNthElement = kDefaultTimingWindowSize / 2,
62+ kLossyByteStreamDataBufferSize = 1280,
63+ kTypicalLossyByteStreamChunkSize = 56,
64+ kLossyByteStreamDescriptorCount = kLossyByteStreamDataBufferSize / kTypicalLossyByteStreamChunkSize
65+};
66+
67+struct SpokePreferences
68+{
69+ int32 mPregameTicksBeforeNetDeath;
70+ int32 mInGameTicksBeforeNetDeath;
71+// int32 mOutgoingFlagsQueueSize;
72+ int32 mRecoverySendPeriod;
73+ int32 mTimingWindowSize;
74+ int32 mTimingNthElement;
75+ bool mAdjustTiming;
76+};
77+
78+static SpokePreferences sSpokePreferences;
79+
80+static TickBasedActionQueue sOutgoingFlags(kDefaultOutgoingFlagsQueueSize);
81+static TickBasedActionQueue sUnconfirmedFlags(kDefaultOutgoingFlagsQueueSize);
82+static DuplicatingTickBasedCircularQueue<action_flags_t> sLocallyGeneratedFlags;
83+static int32 sSmallestRealGameTick;
84+
85+struct IncomingGameDataPacketProcessingContext {
86+ bool mMessagesDone;
87+ bool mGotTimingAdjustmentMessage;
88+
89+ IncomingGameDataPacketProcessingContext() : mMessagesDone(false), mGotTimingAdjustmentMessage(false) {}
90+};
91+
92+typedef void (*StarMessageHandler)(AIStream& s, IncomingGameDataPacketProcessingContext& c);
93+typedef std::map<uint16, StarMessageHandler> MessageTypeToMessageHandler;
94+
95+static MessageTypeToMessageHandler sMessageTypeToMessageHandler;
96+
97+static int8 sRequestedTimingAdjustment;
98+static int8 sOutstandingTimingAdjustment;
99+
100+struct NetworkPlayer_spoke {
101+ bool mZombie;
102+ bool mConnected;
103+ int32 mNetDeadTick;
104+ WritableTickBasedActionQueue* mQueue;
105+};
106+
107+static vector<NetworkPlayer_spoke> sNetworkPlayers;
108+static int32 sNetworkTicker;
109+static int32 sLastNetworkTickHeard;
110+static int32 sLastNetworkTickSent;
111+static bool sConnected = false;
112+static bool sSpokeActive = false;
113+static myTMTaskPtr sSpokeTickTask = NULL;
114+static DDPFramePtr sOutgoingFrame = NULL;
115+static DDPPacketBuffer sLocalOutgoingBuffer;
116+static bool sNeedToSendLocalOutgoingBuffer = false;
117+static bool sHubIsLocal = false;
118+static NetAddrBlock sHubAddress;
119+static size_t sLocalPlayerIndex;
120+static int32 sSmallestUnreceivedTick;
121+static WindowedNthElementFinder<int32> sNthElementFinder(kDefaultTimingWindowSize);
122+static bool sTimingMeasurementValid;
123+static int32 sTimingMeasurement;
124+static bool sHeardFromHub = false;
125+
126+static vector<int32> sDisplayLatencyBuffer; // stores the last 30 latency calculations, in ticks
127+static uint32 sDisplayLatencyCount = 0;
128+static int32 sDisplayLatencyTicks = 0; // sum of the latency ticks from the last 30 seconds, using above two
129+
130+static int32 sSmallestUnconfirmedTick;
131+
132+struct SpokeLossyByteStreamChunkDescriptor
133+{
134+ uint16 mLength;
135+ int16 mType;
136+ uint32 mDestinations;
137+};
138+
139+// This holds outgoing lossy byte stream data
140+static CircularByteBuffer sOutgoingLossyByteStreamData(kLossyByteStreamDataBufferSize);
141+
142+// This holds a descriptor for each chunk of lossy byte stream data held in the above buffer
143+static CircularQueue<SpokeLossyByteStreamChunkDescriptor> sOutgoingLossyByteStreamDescriptors(kLossyByteStreamDescriptorCount);
144+
145+// This is currently used only to hold incoming streaming data until it's passed to the upper-level code
146+static byte sScratchBuffer[kLossyByteStreamDataBufferSize];
147+
148+
149+static void spoke_became_disconnected();
150+static void spoke_received_game_data_packet_v1(AIStream& ps, bool reflected_flags);
151+static void process_messages(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
152+static void handle_end_of_messages_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
153+static void handle_player_net_dead_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
154+static void handle_timing_adjustment_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
155+static void handle_lossy_byte_stream_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
156+static void process_optional_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context, uint16 inMessageType);
157+static bool spoke_tick();
158+static void send_packet();
159+static void send_identification_packet();
160+
161+
162+static inline NetworkPlayer_spoke&
163+getNetworkPlayer(size_t inIndex)
164+{
165+ assert(inIndex < sNetworkPlayers.size());
166+ return sNetworkPlayers[inIndex];
167+}
168+
169+
170+
171+static inline bool
172+operator !=(const NetAddrBlock& a, const NetAddrBlock& b)
173+{
174+ return memcmp(&a, &b, sizeof(a)) != 0;
175+}
176+
177+
178+
179+static OSErr
180+send_frame_to_local_hub(DDPFramePtr frame, NetAddrBlock *address, short protocolType, short port)
181+{
182+ sLocalOutgoingBuffer.datagramSize = frame->data_size;
183+ memcpy(sLocalOutgoingBuffer.datagramData, frame->data, frame->data_size);
184+ sLocalOutgoingBuffer.protocolType = protocolType;
185+ // An all-0 sourceAddress is the cue for "local spoke" currently.
186+ obj_clear(sLocalOutgoingBuffer.sourceAddress);
187+ sNeedToSendLocalOutgoingBuffer = true;
188+ return noErr;
189+}
190+
191+
192+
193+static inline void
194+check_send_packet_to_hub()
195+{
196+ if(sNeedToSendLocalOutgoingBuffer)
197+ {
198+ logContextNMT("delivering stored packet to local hub");
199+ hub_received_network_packet(&sLocalOutgoingBuffer);
200+ }
201+
202+ sNeedToSendLocalOutgoingBuffer = false;
203+}
204+
205+
206+
207+void
208+spoke_initialize(const NetAddrBlock& inHubAddress, int32 inFirstTick, size_t inNumberOfPlayers, WritableTickBasedActionQueue* const inPlayerQueues[], bool inPlayerConnected[], size_t inLocalPlayerIndex, bool inHubIsLocal)
209+{
210+ assert(inNumberOfPlayers >= 1);
211+ assert(inLocalPlayerIndex < inNumberOfPlayers);
212+ assert(inPlayerQueues[inLocalPlayerIndex] != NULL);
213+ assert(inPlayerConnected[inLocalPlayerIndex]);
214+
215+ sHubIsLocal = inHubIsLocal;
216+ sHubAddress = inHubAddress;
217+
218+ sLocalPlayerIndex = inLocalPlayerIndex;
219+
220+ sOutgoingFrame = NetDDPNewFrame();
221+
222+ sSmallestRealGameTick = inFirstTick;
223+ int32 theFirstPregameTick = inFirstTick - kPregameTicks;
224+ sOutgoingFlags.reset(theFirstPregameTick);
225+ sUnconfirmedFlags.reset(sSmallestRealGameTick);
226+ sSmallestUnconfirmedTick = sSmallestRealGameTick;
227+ sSmallestUnreceivedTick = theFirstPregameTick;
228+
229+ sNetworkPlayers.clear();
230+ sNetworkPlayers.resize(inNumberOfPlayers);
231+
232+ sLocallyGeneratedFlags.children().clear();
233+ sLocallyGeneratedFlags.children().insert(&sOutgoingFlags);
234+ sLocallyGeneratedFlags.children().insert(&sUnconfirmedFlags);
235+
236+ for(size_t i = 0; i < inNumberOfPlayers; i++)
237+ {
238+ sNetworkPlayers[i].mZombie = (inPlayerQueues[i] == NULL);
239+ sNetworkPlayers[i].mConnected = inPlayerConnected[i];
240+ sNetworkPlayers[i].mNetDeadTick = theFirstPregameTick - 1;
241+ sNetworkPlayers[i].mQueue = inPlayerQueues[i];
242+ if(sNetworkPlayers[i].mConnected)
243+ {
244+ sNetworkPlayers[i].mQueue->reset(sSmallestRealGameTick);
245+ }
246+ }
247+
248+ sRequestedTimingAdjustment = 0;
249+ sOutstandingTimingAdjustment = 0;
250+
251+ sNetworkTicker = 0;
252+ sLastNetworkTickHeard = 0;
253+ sLastNetworkTickSent = 0;
254+ sConnected = true;
255+ sNthElementFinder.reset(sSpokePreferences.mTimingWindowSize);
256+ sTimingMeasurementValid = false;
257+
258+ sOutgoingLossyByteStreamDescriptors.reset();
259+ sOutgoingLossyByteStreamData.reset();
260+
261+ sMessageTypeToMessageHandler.clear();
262+ sMessageTypeToMessageHandler[kEndOfMessagesMessageType] = handle_end_of_messages_message;
263+ sMessageTypeToMessageHandler[kTimingAdjustmentMessageType] = handle_timing_adjustment_message;
264+ sMessageTypeToMessageHandler[kPlayerNetDeadMessageType] = handle_player_net_dead_message;
265+ sMessageTypeToMessageHandler[kHubToSpokeLossyByteStreamMessageType] = handle_lossy_byte_stream_message;
266+
267+ sNeedToSendLocalOutgoingBuffer = false;
268+
269+ sSpokeActive = true;
270+ sSpokeTickTask = myXTMSetup(1000/TICKS_PER_SECOND, spoke_tick);
271+
272+ sDisplayLatencyBuffer.resize(TICKS_PER_SECOND, 0);
273+ sDisplayLatencyCount = 0;
274+ sDisplayLatencyTicks = 0;
275+
276+ sHeardFromHub = false;
277+}
278+
279+
280+
281+void
282+spoke_cleanup(bool inGraceful)
283+{
284+ // Stop processing incoming packets (packet processor won't start processing another packet
285+ // due to sSpokeActive = false, and we know it's not in the middle of processing one because
286+ // we take the mutex).
287+ if(take_mytm_mutex())
288+ {
289+ // Mark the tick task for cancellation (it won't start running again after this returns).
290+ myTMRemove(sSpokeTickTask);
291+ sSpokeTickTask = NULL;
292+
293+ sSpokeActive = false;
294+
295+ // We send one last packet here to try to not leave the hub hanging on our ACK.
296+ send_packet();
297+ check_send_packet_to_hub();
298+
299+ release_mytm_mutex();
300+ }
301+
302+ // This waits for the tick task to actually finish
303+ myTMCleanup(true);
304+
305+ sMessageTypeToMessageHandler.clear();
306+ sNetworkPlayers.clear();
307+ sLocallyGeneratedFlags.children().clear();
308+ sDisplayLatencyBuffer.clear();
309+ NetDDPDisposeFrame(sOutgoingFrame);
310+ sOutgoingFrame = NULL;
311+}
312+
313+
314+
315+int32
316+spoke_get_net_time()
317+{
318+ static int32 sPreviousDelay = -1;
319+
320+ int32 theDelay = (sSpokePreferences.mAdjustTiming && sTimingMeasurementValid) ? sTimingMeasurement : 0;
321+
322+ if(theDelay != sPreviousDelay)
323+ {
324+ logDump1("local delay is now %d", theDelay);
325+ sPreviousDelay = theDelay;
326+ }
327+
328+ return (sConnected ? sOutgoingFlags.getWriteTick() - theDelay : getNetworkPlayer(sLocalPlayerIndex).mQueue->getWriteTick());
329+}
330+
331+
332+
333+void
334+spoke_distribute_lossy_streaming_bytes_to_everyone(int16 inDistributionType, byte* inBytes, uint16 inLength, bool inExcludeLocalPlayer, bool onlySendToTeam)
335+{
336+
337+ int16 local_team;
338+ if (onlySendToTeam)
339+ {
340+ player_info* player = (player_info *)NetGetPlayerData(sLocalPlayerIndex);
341+ local_team = player->team;
342+ }
343+
344+ uint32 theDestinations = 0;
345+ for(size_t i = 0; i < sNetworkPlayers.size(); i++)
346+ {
347+ if((i != sLocalPlayerIndex || !inExcludeLocalPlayer) && !sNetworkPlayers[i].mZombie && sNetworkPlayers[i].mConnected)
348+ {
349+ if (onlySendToTeam)
350+ {
351+ player_info* player = (player_info *)NetGetPlayerData(i);
352+ if (player->team == local_team)
353+ theDestinations |= (((uint32)1) << i);
354+
355+ }
356+ else
357+ {
358+ theDestinations |= (((uint32)1) << i);
359+ }
360+ }
361+ }
362+
363+ spoke_distribute_lossy_streaming_bytes(inDistributionType, theDestinations, inBytes, inLength);
364+}
365+
366+
367+
368+void
369+spoke_distribute_lossy_streaming_bytes(int16 inDistributionType, uint32 inDestinationsBitmask, byte* inBytes, uint16 inLength)
370+{
371+ if(inLength > sOutgoingLossyByteStreamData.getRemainingSpace())
372+ {
373+ logNoteNMT2("spoke has insufficient buffer space for %hu bytes of outgoing lossy streaming type %hd; discarded", inLength, inDistributionType);
374+ return;
375+ }
376+
377+ if(sOutgoingLossyByteStreamDescriptors.getRemainingSpace() < 1)
378+ {
379+ logNoteNMT2("spoke has exhausted descriptor buffer space; discarding %hu bytes of outgoing lossy streaming type %hd", inLength, inDistributionType);
380+ return;
381+ }
382+
383+ struct SpokeLossyByteStreamChunkDescriptor theDescriptor;
384+ theDescriptor.mLength = inLength;
385+ theDescriptor.mDestinations = inDestinationsBitmask;
386+ theDescriptor.mType = inDistributionType;
387+
388+ logDumpNMT3("spoke application decided to send %d bytes of lossy streaming type %d destined for players 0x%x", inLength, inDistributionType, inDestinationsBitmask);
389+
390+ sOutgoingLossyByteStreamData.enqueueBytes(inBytes, inLength);
391+ sOutgoingLossyByteStreamDescriptors.enqueue(theDescriptor);
392+}
393+
394+
395+
396+static void
397+spoke_became_disconnected()
398+{
399+ sConnected = false;
400+ for(size_t i = 0; i < sNetworkPlayers.size(); i++)
401+ {
402+ if(sNetworkPlayers[i].mConnected)
403+ make_player_really_net_dead(i);
404+ }
405+}
406+
407+
408+
409+void
410+spoke_received_network_packet(DDPPacketBufferPtr inPacket)
411+{
412+ logContextNMT("spoke processing a received packet");
413+
414+ // If we've already given up on the connection, ignore packets.
415+ if(!sConnected || !sSpokeActive)
416+ return;
417+
418+ // Ignore packets not from our hub
419+// if(inPacket->sourceAddress != sHubAddress)
420+// return;
421+
422+ try {
423+ AIStreamBE ps(inPacket->datagramData, inPacket->datagramSize);
424+
425+ uint16 thePacketMagic;
426+ ps >> thePacketMagic;
427+
428+ uint16 thePacketCRC;
429+ ps >> thePacketCRC;
430+
431+ // blank out the CRC field before calculating
432+ inPacket->datagramData[2] = 0;
433+ inPacket->datagramData[3] = 0;
434+
435+ if (thePacketCRC != calculate_data_crc_ccitt(inPacket->datagramData, inPacket->datagramSize))
436+ {
437+ logWarningNMT1("CRC failure; discarding packet type %i", thePacketMagic);
438+ return;
439+ }
440+
441+ switch(thePacketMagic)
442+ {
443+ case kHubToSpokeGameDataPacketV1Magic:
444+ spoke_received_game_data_packet_v1(ps, false);
445+ break;
446+
447+ case kHubToSpokeGameDataPacketWithSpokeFlagsV1Magic:
448+ spoke_received_game_data_packet_v1(ps, true);
449+ break;
450+
451+ default:
452+ // Ignore unknown packet types
453+ logTraceNMT1("unknown packet type %i", thePacketMagic);
454+ break;
455+ }
456+ }
457+ catch(...)
458+ {
459+ // do nothing special, we just ignore the remainder of the packet.
460+ }
461+}
462+
463+
464+
465+static void
466+spoke_received_game_data_packet_v1(AIStream& ps, bool reflected_flags)
467+{
468+ sHeardFromHub = true;
469+
470+ IncomingGameDataPacketProcessingContext context;
471+
472+ // Piggybacked ACK
473+ int32 theSmallestUnacknowledgedTick;
474+ ps >> theSmallestUnacknowledgedTick;
475+
476+ // we can get an early ACK only if the server made up flags for us...
477+ if (theSmallestUnacknowledgedTick > sOutgoingFlags.getWriteTick())
478+ {
479+ if (reflected_flags)
480+ {
481+ theSmallestUnacknowledgedTick = sOutgoingFlags.getWriteTick();
482+ }
483+ else
484+ {
485+ logTraceNMT2("early ack (%d > %d)", theSmallestUnacknowledgedTick, sOutgoingFlags.getWriteTick());
486+ return;
487+ }
488+ }
489+
490+
491+ // Heard from hub
492+ sLastNetworkTickHeard = sNetworkTicker;
493+
494+ // Remove acknowledged elements from outgoing queue
495+ for(int tick = sOutgoingFlags.getReadTick(); tick < theSmallestUnacknowledgedTick; tick++)
496+ {
497+ logTraceNMT1("dequeueing tick %d from sOutgoingFlags", tick);
498+ sOutgoingFlags.dequeue();
499+ }
500+
501+ // Process messages
502+ process_messages(ps, context);
503+
504+ if(!context.mGotTimingAdjustmentMessage)
505+ {
506+ if(sRequestedTimingAdjustment != 0)
507+ logTraceNMT("timing adjustment no longer requested");
508+
509+ sRequestedTimingAdjustment = 0;
510+ }
511+
512+ // Action_flags!!!
513+ // If there aren't any, we're done
514+ if(ps.tellg() >= ps.maxg())
515+ {
516+ // If we are the only connected player, well because of the way the loop below works,
517+ // we have to enqueue net_dead flags for the other players now, up to match the most
518+ // recent tick the hub has acknowledged.
519+ // We can't assume we are the only connected player merely by virtue of there being no
520+ // flags in the packet. The hub could be waiting on somebody else to send and is
521+ // essentially sending us "keep-alive" packets (which could contain no new flags) in the meantime.
522+ // In other words, (we are alone) implies (no flags in packet), but not the converse.
523+ // Here "alone" means there are no other players who are connected or who will be netdead
524+ // sometime in the future. (If their NetDeadTick is greater than the ACKed tick, we expect
525+ // that the hub will be sending actual flags in the future to make up the difference.)
526+ bool weAreAlone = true;
527+ int32 theSmallestUnacknowledgedTick = sOutgoingFlags.getReadTick();
528+ for(size_t i = 0; i < sNetworkPlayers.size(); i++)
529+ {
530+ if(i != sLocalPlayerIndex && (sNetworkPlayers[i].mConnected || sNetworkPlayers[i].mNetDeadTick > theSmallestUnacknowledgedTick))
531+ {
532+ weAreAlone = false;
533+ break;
534+ }
535+ }
536+
537+ if(weAreAlone)
538+ {
539+ logContextNMT("handling special \"we are alone\" case");
540+
541+ for(size_t i = 0; i < sNetworkPlayers.size(); i++)
542+ {
543+ NetworkPlayer_spoke& thePlayer = sNetworkPlayers[i];
544+ if (i == sLocalPlayerIndex)
545+ {
546+ while (sSmallestUnconfirmedTick < sUnconfirmedFlags.getWriteTick())
547+ {
548+ sNetworkPlayers[i].mQueue->enqueue(sUnconfirmedFlags.peek(sSmallestUnconfirmedTick++));
549+ }
550+ }
551+ else if (!thePlayer.mZombie)
552+ {
553+ while(thePlayer.mQueue->getWriteTick() < theSmallestUnacknowledgedTick)
554+ {
555+ logDumpNMT2("enqueued NET_DEAD_ACTION_FLAG for player %d tick %d", i, thePlayer.mQueue->getWriteTick());
556+ thePlayer.mQueue->enqueue(static_cast<action_flags_t>(NET_DEAD_ACTION_FLAG));
557+ }
558+ }
559+ }
560+
561+ sSmallestUnreceivedTick = theSmallestUnacknowledgedTick;
562+ logDumpNMT1("sSmallestUnreceivedTick is now %d", sSmallestUnreceivedTick);
563+ }
564+
565+ return;
566+ } // no data left in packet
567+
568+ int32 theSmallestUnreadTick;
569+ ps >> theSmallestUnreadTick;
570+
571+ // Can't accept packets that skip ticks
572+ if(theSmallestUnreadTick > sSmallestUnreceivedTick)
573+ {
574+ logTraceNMT2("early flags (%d > %d)", theSmallestUnreadTick, sSmallestUnreceivedTick);
575+ return;
576+ }
577+
578+ // Figure out how many ticks of flags we can actually enqueue
579+ // We want to stock all queues evenly, since we ACK everyone's flags for a tick together.
580+ int theSmallestQueueSpace = INT_MAX;
581+ for(size_t i = 0; i < sNetworkPlayers.size(); i++)
582+ {
583+ // we'll never get flags for zombies, and we're not expected
584+ // to enqueue flags for zombies
585+ if(sNetworkPlayers[i].mZombie)
586+ continue;
587+
588+ int theQueueSpace = sNetworkPlayers[i].mQueue->availableCapacity();
589+
590+ /*
591+ hmm, taking this exemption out, because we will start enqueueing PLAYER_NET_DEAD_FLAG onto the queue.
592+ // If player is netdead or will become netdead before queue fills,
593+ // player's queue space will not limit us
594+ if(!sNetworkPlayers[i].mConnected)
595+ {
596+ int theRemainingLiveTicks = sNetworkPlayers[i].mNetDeadTick - sSmallestUnreceivedTick;
597+ if(theRemainingLiveTicks < theQueueSpace)
598+ continue;
599+ }
600+ */
601+
602+ if(theQueueSpace < theSmallestQueueSpace)
603+ theSmallestQueueSpace = theQueueSpace;
604+ }
605+
606+ logDumpNMT1("%d queue space available", theSmallestQueueSpace);
607+
608+ // Read and enqueue the actual action_flags from the packet
609+ // The body of this loop is a bit more convoluted than you might
610+ // expect, because the same loop is used to skip already-seen action_flags
611+ // and to enqueue new ones.
612+ while(ps.tellg() < ps.maxg())
613+ {
614+ // If we've no room to enqueue stuff, no point in finishing reading the packet.
615+ if(theSmallestQueueSpace <= 0)
616+ break;
617+
618+ for(size_t i = 0; i < sNetworkPlayers.size(); i++)
619+ {
620+
621+ // We'll never get flags for zombies
622+ if (sNetworkPlayers[i].mZombie)
623+ continue;
624+
625+ // if our own flags are not sent back to us,
626+ // confirm the ones we have in our unconfirmed queue,
627+ // and do not read any from the packet
628+ if (i == sLocalPlayerIndex && !reflected_flags)
629+ {
630+ if (theSmallestUnreadTick == sSmallestUnreceivedTick && theSmallestUnreadTick >= sSmallestRealGameTick)
631+ {
632+ assert(sNetworkPlayers[i].mQueue->getWriteTick() == sSmallestUnconfirmedTick);
633+ assert(sSmallestUnconfirmedTick >= sUnconfirmedFlags.getReadTick());
634+ assert(sSmallestUnconfirmedTick < sUnconfirmedFlags.getWriteTick());
635+ // confirm this flag
636+ sNetworkPlayers[i].mQueue->enqueue(sUnconfirmedFlags.peek(sSmallestUnconfirmedTick));
637+ sSmallestUnconfirmedTick++;
638+ }
639+
640+ continue;
641+ }
642+
643+ bool shouldEnqueueNetDeadFlags = false;
644+
645+ // We won't get flags for netdead players
646+ NetworkPlayer_spoke& thePlayer = sNetworkPlayers[i];
647+ if(!thePlayer.mConnected)
648+ {
649+ if(thePlayer.mNetDeadTick < theSmallestUnreadTick)
650+ shouldEnqueueNetDeadFlags = true;
651+
652+ if(thePlayer.mNetDeadTick == theSmallestUnreadTick)
653+ {
654+ // Only actually act if this tick is new to us
655+ if(theSmallestUnreadTick == sSmallestUnreceivedTick)
656+ make_player_really_net_dead(i);
657+ shouldEnqueueNetDeadFlags = true;
658+ }
659+ }
660+
661+ // Decide what flags are appropriate for this player this tick
662+ action_flags_t theFlags;
663+ if(shouldEnqueueNetDeadFlags)
664+ // We effectively generate a tick's worth of flags in lieu of reading it from the packet.
665+ theFlags = static_cast<action_flags_t>(NET_DEAD_ACTION_FLAG);
666+ else
667+ {
668+ // We should have a flag for this player for this tick!
669+ try
670+ {
671+ ps >> theFlags;
672+ }
673+ catch (const AStream::failure& f)
674+ {
675+ logWarningNMT3("AStream exception (%s) for player %i at theSmallestUnreadTick %i! OOS is likely!\n", f.what(), i, theSmallestUnreadTick);
676+ return;
677+ }
678+ }
679+
680+
681+ // Now, we've gotten flags, probably from the packet... should we enqueue them?
682+ if(theSmallestUnreadTick == sSmallestUnreceivedTick)
683+ {
684+ if(theSmallestUnreadTick >= sSmallestRealGameTick)
685+ {
686+ WritableTickBasedActionQueue& theQueue = *(sNetworkPlayers[i].mQueue);
687+ assert(theQueue.getWriteTick() == sSmallestUnreceivedTick);
688+ assert(theQueue.availableCapacity() > 0);
689+ logTraceNMT3("enqueueing flags %x for player %d tick %d", theFlags, i, theQueue.getWriteTick());
690+ theQueue.enqueue(theFlags);
691+ if (i == sLocalPlayerIndex) sSmallestUnconfirmedTick++;
692+ }
693+ }
694+
695+ } // iterate over players
696+
697+ theSmallestUnreadTick++;
698+ if(sSmallestUnreceivedTick < theSmallestUnreadTick)
699+ {
700+ theSmallestQueueSpace--;
701+ sSmallestUnreceivedTick = theSmallestUnreadTick;
702+
703+ int32 theLatencyMeasurement = sOutgoingFlags.getWriteTick() - sSmallestUnreceivedTick;
704+ logDumpNMT1("latency measurement: %d", theLatencyMeasurement);
705+
706+// We can't use NthElementFinder at interrupt time in Mac OS 9 since its std::set can [de]allocate memory
707+// We don't really use it for anything on the spoke-side now anyway, thanks to player motion prediction...
708+#if !(defined(mac) && !defined(__MACH__))
709+ sNthElementFinder.insert(theLatencyMeasurement);
710+ // We capture these values here so we don't have to take a lock in GetNetTime.
711+ sTimingMeasurementValid = sNthElementFinder.window_full();
712+ if(sTimingMeasurementValid)
713+ sTimingMeasurement = sNthElementFinder.nth_largest_element(sSpokePreferences.mTimingNthElement);
714+#endif
715+
716+ // update the latency display
717+ sDisplayLatencyTicks -= sDisplayLatencyBuffer[sDisplayLatencyCount % sDisplayLatencyBuffer.size()];
718+ sDisplayLatencyBuffer[sDisplayLatencyCount++ % sDisplayLatencyBuffer.size()] = theLatencyMeasurement;
719+ sDisplayLatencyTicks += theLatencyMeasurement;
720+ }
721+
722+ } // loop while there's packet data left
723+
724+}
725+
726+
727+
728+static void
729+process_messages(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
730+{
731+ while(!context.mMessagesDone)
732+ {
733+ uint16 theMessageType;
734+ ps >> theMessageType;
735+
736+ MessageTypeToMessageHandler::iterator i = sMessageTypeToMessageHandler.find(theMessageType);
737+
738+ if(i == sMessageTypeToMessageHandler.end())
739+ process_optional_message(ps, context, theMessageType);
740+ else
741+ i->second(ps, context);
742+ }
743+}
744+
745+
746+
747+static void
748+handle_end_of_messages_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
749+{
750+ context.mMessagesDone = true;
751+}
752+
753+
754+
755+static void
756+handle_player_net_dead_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
757+{
758+ uint8 thePlayerIndex;
759+ int32 theTick;
760+
761+ ps >> thePlayerIndex >> theTick;
762+
763+ if(thePlayerIndex > sNetworkPlayers.size())
764+ return;
765+
766+ sNetworkPlayers[thePlayerIndex].mConnected = false;
767+ sNetworkPlayers[thePlayerIndex].mNetDeadTick = theTick;
768+
769+ logDumpNMT2("netDead message: player %d in tick %d", thePlayerIndex, theTick);
770+}
771+
772+
773+
774+static void
775+handle_timing_adjustment_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
776+{
777+ int8 theAdjustment;
778+
779+ ps >> theAdjustment;
780+
781+ if(theAdjustment != sRequestedTimingAdjustment)
782+ {
783+ sOutstandingTimingAdjustment = theAdjustment;
784+ sRequestedTimingAdjustment = theAdjustment;
785+ logTraceNMT2("new timing adjustment message; requested: %d outstanding: %d", sRequestedTimingAdjustment, sOutstandingTimingAdjustment);
786+ }
787+
788+ context.mGotTimingAdjustmentMessage = true;
789+}
790+
791+
792+
793+static void
794+handle_lossy_byte_stream_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
795+{
796+ uint16 theMessageLength;
797+ ps >> theMessageLength;
798+
799+ size_t theStartOfMessage = ps.tellg();
800+
801+ int16 theDistributionType;
802+ uint8 theSendingPlayer;
803+ ps >> theDistributionType >> theSendingPlayer;
804+
805+ uint16 theDataLength = theMessageLength - (ps.tellg() - theStartOfMessage);
806+ uint16 theSpilloverDataLength = 0;
807+ if(theDataLength > sizeof(sScratchBuffer))
808+ {
809+ logNoteNMT3("received too many bytes (%d) of lossy streaming data type %d from player %d; truncating", theDataLength, theDistributionType, theSendingPlayer);
810+ theSpilloverDataLength = theDataLength - sizeof(sScratchBuffer);
811+ theDataLength = sizeof(sScratchBuffer);
812+ }
813+ ps.read(sScratchBuffer, theDataLength);
814+ ps.ignore(theSpilloverDataLength);
815+
816+ logDumpNMT3("received %d bytes of lossy streaming type %d data from player %d", theDataLength, theDistributionType, theSendingPlayer);
817+
818+ call_distribution_response_function_if_available(sScratchBuffer, theDataLength, theDistributionType, theSendingPlayer);
819+}
820+
821+
822+
823+static void
824+process_optional_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context, uint16 inMessageType)
825+{
826+ // We don't know of any optional messages, so we just skip any we encounter.
827+ // (All optional messages are required to encode their length (not including the
828+ // space required for the message type or length) in the two bytes immediately
829+ // following the message type.)
830+ uint16 theLength;
831+ ps >> theLength;
832+
833+ ps.ignore(theLength);
834+}
835+
836+
837+
838+static bool
839+spoke_tick()
840+{
841+ logContextNMT1("processing spoke_tick %d", sNetworkTicker);
842+
843+ sNetworkTicker++;
844+
845+ if(sConnected)
846+ {
847+ int32 theSilentTicksBeforeNetDeath = (sOutgoingFlags.getReadTick() >= sSmallestRealGameTick) ? sSpokePreferences.mInGameTicksBeforeNetDeath : sSpokePreferences.mPregameTicksBeforeNetDeath;
848+
849+ if(sNetworkTicker - sLastNetworkTickHeard > theSilentTicksBeforeNetDeath)
850+ {
851+ logTraceNMT("giving up on hub; disconnecting");
852+ spoke_became_disconnected();
853+ return true;
854+ }
855+ }
856+
857+ bool shouldSend = false;
858+
859+ // Negative timing adjustment means we need to provide extra ticks because we're late.
860+ // We let this cover the normal timing adjustment = 0 case too.
861+ if(sOutstandingTimingAdjustment <= 0)
862+ {
863+ int theNumberOfFlagsToProvide = -sOutstandingTimingAdjustment + 1;
864+
865+ logDumpNMT1("want to provide %d flags", theNumberOfFlagsToProvide);
866+
867+ while(theNumberOfFlagsToProvide > 0)
868+ {
869+
870+ // If we're not connected, write only to our local game's queue.
871+ // If we are connected,
872+ // if we're actually in the game, write to both local game and outbound flags queues
873+ // else (if pregame), write only to the outbound flags queue.
874+
875+ WritableTickBasedActionQueue& theTargetQueue =
876+ sConnected ?
877+ ((sOutgoingFlags.getWriteTick() >= sSmallestRealGameTick) ?
878+ static_cast<WritableTickBasedActionQueue&>(sLocallyGeneratedFlags)
879+ : static_cast<WritableTickBasedActionQueue&>(sOutgoingFlags))
880+ : *(sNetworkPlayers[sLocalPlayerIndex].mQueue);
881+
882+ if(theTargetQueue.availableCapacity() <= 0)
883+ break;
884+
885+ logDumpNMT1("enqueueing flags for tick %d", theTargetQueue.getWriteTick());
886+
887+ theTargetQueue.enqueue(parse_keymap());
888+ shouldSend = true;
889+ theNumberOfFlagsToProvide--;
890+ }
891+
892+ // Prevent creeping timing adjustment during "lulls"; OTOH remember to
893+ // finish next time if we made progress but couldn't complete our obligation.
894+ if(theNumberOfFlagsToProvide != -sOutstandingTimingAdjustment + 1)
895+ sOutstandingTimingAdjustment = -theNumberOfFlagsToProvide;
896+ }
897+ // Positive timing adjustment means we should delay sending for a while,
898+ // so we just throw away this local tick.
899+ else
900+ {
901+ logDumpNMT("ignoring this tick for timing adjustment");
902+ sOutstandingTimingAdjustment--;
903+ }
904+
905+ logDumpNMT1("sOutstandingTimingAdjustment is now %d", sOutstandingTimingAdjustment);
906+
907+ if(sOutgoingLossyByteStreamDescriptors.getCountOfElements() > 0)
908+ shouldSend = true;
909+
910+ // If we're connected and (we generated new data or if it's been long enough since we last sent), send.
911+ if(sConnected)
912+ {
913+ if (sHeardFromHub) {
914+ if(shouldSend || (sNetworkTicker - sLastNetworkTickSent) >= sSpokePreferences.mRecoverySendPeriod)
915+ send_packet();
916+ } else {
917+ if (!(sNetworkTicker % 30))
918+ send_identification_packet();
919+ }
920+ }
921+ else
922+ {
923+ int32 theLocalPlayerWriteTick = getNetworkPlayer(sLocalPlayerIndex).mQueue->getWriteTick();
924+
925+ // Since we're not connected, we won't be enqueueing flags for the other players in the packet handler.
926+ // So, we do it here to keep the game moving.
927+ for(size_t i = 0; i < sNetworkPlayers.size(); i++)
928+ {
929+ if(i == sLocalPlayerIndex)
930+ {
931+ // move our flags from sent queue to player queue
932+ while (sSmallestUnconfirmedTick < sUnconfirmedFlags.getWriteTick())
933+ {
934+ sNetworkPlayers[i].mQueue->enqueue(sUnconfirmedFlags.peek(sSmallestUnconfirmedTick++));
935+ }
936+ continue;
937+ }
938+
939+ NetworkPlayer_spoke& thePlayer = sNetworkPlayers[i];
940+
941+ if(!thePlayer.mZombie)
942+ {
943+ while(thePlayer.mQueue->getWriteTick() < theLocalPlayerWriteTick)
944+ {
945+ logDumpNMT2("enqueueing NET_DEAD_ACTION_FLAG for player %d tick %d", i, thePlayer.mQueue->getWriteTick());
946+ thePlayer.mQueue->enqueue(static_cast<action_flags_t>(NET_DEAD_ACTION_FLAG));
947+ }
948+ }
949+ }
950+ }
951+
952+ check_send_packet_to_hub();
953+
954+ // We want to run again.
955+ return true;
956+}
957+
958+
959+
960+static void
961+send_packet()
962+{
963+ try {
964+ AOStreamBE hdr(sOutgoingFrame->data, kStarPacketHeaderSize);
965+ AOStreamBE ps(sOutgoingFrame->data, ddpMaxData, kStarPacketHeaderSize);
966+
967+ // Packet type
968+ hdr << (uint16)kSpokeToHubGameDataPacketV1Magic;
969+
970+ // Acknowledgement
971+ ps << sSmallestUnreceivedTick;
972+
973+ // Messages
974+ // Outstanding lossy streaming bytes?
975+ if(sOutgoingLossyByteStreamDescriptors.getCountOfElements() > 0)
976+ {
977+ // Note: we make a conscious decision here to dequeue these things before
978+ // writing to ps, so that if the latter operation exhausts ps's buffer and
979+ // throws, we have less data to mess with next time, and shouldn't end up
980+ // throwing every time we try to send here.
981+ // If we eventually got smarter about managing packet space, we could try
982+ // harder to preserve and pace data - e.g. change the 'if' immediately before this
983+ // comment to a 'while', only put in as much data as we think we can fit, etc.
984+ SpokeLossyByteStreamChunkDescriptor theDescriptor = sOutgoingLossyByteStreamDescriptors.peek();
985+ sOutgoingLossyByteStreamDescriptors.dequeue();
986+
987+ uint16 theMessageLength = theDescriptor.mLength + sizeof(theDescriptor.mType) + sizeof(theDescriptor.mDestinations);
988+
989+ ps << (uint16)kSpokeToHubLossyByteStreamMessageType
990+ << theMessageLength
991+ << theDescriptor.mType
992+ << theDescriptor.mDestinations;
993+
994+ // XXX unnecessary copy due to overly restrictive interfaces (retaining for clarity)
995+ assert(theDescriptor.mLength <= sizeof(sScratchBuffer));
996+ sOutgoingLossyByteStreamData.peekBytes(sScratchBuffer, theDescriptor.mLength);
997+ sOutgoingLossyByteStreamData.dequeue(theDescriptor.mLength);
998+
999+ ps.write(sScratchBuffer, theDescriptor.mLength);
1000+ }
1001+
1002+ // No more messages
1003+ ps << (uint16)kEndOfMessagesMessageType;
1004+
1005+ // Action_flags!!!
1006+ if(sOutgoingFlags.size() > 0)
1007+ {
1008+ ps << sOutgoingFlags.getReadTick();
1009+ for(int32 tick = sOutgoingFlags.getReadTick(); tick < sOutgoingFlags.getWriteTick(); tick++)
1010+ ps << sOutgoingFlags.peek(tick);
1011+ }
1012+
1013+ logDumpNMT3("preparing to send packet: ACK %d, flags [%d,%d)", sSmallestUnreceivedTick, sOutgoingFlags.getReadTick(), sOutgoingFlags.getWriteTick());
1014+
1015+ // blank out the CRC before calculating it
1016+ sOutgoingFrame->data[2] = 0;
1017+ sOutgoingFrame->data[3] = 0;
1018+
1019+ uint16 crc = calculate_data_crc_ccitt(sOutgoingFrame->data, ps.tellp());
1020+ hdr << crc;
1021+
1022+ // Send the packet
1023+ sOutgoingFrame->data_size = ps.tellp();
1024+
1025+ if(sHubIsLocal)
1026+ send_frame_to_local_hub(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1027+ else
1028+ NetDDPSendFrame(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1029+
1030+ sLastNetworkTickSent = sNetworkTicker;
1031+ }
1032+ catch (...) {
1033+ }
1034+}
1035+
1036+
1037+
1038+static void
1039+send_identification_packet()
1040+{
1041+ try {
1042+ AOStreamBE hdr(sOutgoingFrame->data, kStarPacketHeaderSize);
1043+ AOStreamBE ps(sOutgoingFrame->data, ddpMaxData, kStarPacketHeaderSize);
1044+
1045+ // Message type
1046+ hdr << (uint16) kSpokeToHubIdentification;
1047+
1048+ // ID
1049+ ps << (uint16)sLocalPlayerIndex;
1050+
1051+ // blank out the CRC field before calculating
1052+ sOutgoingFrame->data[2] = 0;
1053+ sOutgoingFrame->data[3] = 0;
1054+
1055+ uint16 crc = calculate_data_crc_ccitt(sOutgoingFrame->data, ps.tellp());
1056+ hdr << crc;
1057+
1058+ // Send the packet
1059+ sOutgoingFrame->data_size = ps.tellp();
1060+ if(sHubIsLocal)
1061+ send_frame_to_local_hub(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1062+ else
1063+ NetDDPSendFrame(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1064+ }
1065+ catch (...) {
1066+ }
1067+}
1068+
1069+int32 spoke_latency()
1070+{
1071+ return (sDisplayLatencyCount >= TICKS_PER_SECOND) ? sDisplayLatencyTicks * 1000 / TICKS_PER_SECOND / sDisplayLatencyBuffer.size() : NetworkStats::invalid;
1072+}
1073+
1074+TickBasedActionQueue* spoke_get_unconfirmed_flags_queue()
1075+{
1076+ return &sUnconfirmedFlags;
1077+}
1078+
1079+int32 spoke_get_smallest_unconfirmed_tick()
1080+{
1081+ return sSmallestUnconfirmedTick;
1082+}
1083+
1084+
1085+static inline const char *BoolString(bool B) {return (B ? "true" : "false");}
1086+
1087+enum {
1088+ kPregameTicksBeforeNetDeathAttribute,
1089+ kInGameTicksBeforeNetDeathAttribute,
1090+ // kOutgoingFlagsQueueSizeAttribute,
1091+ kRecoverySendPeriodAttribute,
1092+ kTimingWindowSizeAttribute,
1093+ kTimingNthElementAttribute,
1094+ kNumInt32Attributes,
1095+ kAdjustTimingAttribute = kNumInt32Attributes,
1096+ kNumAttributes
1097+};
1098+
1099+static const char* sAttributeStrings[kNumInt32Attributes] =
1100+{
1101+ "pregame_ticks_before_net_death",
1102+ "ingame_ticks_before_net_death",
1103+// "outgoing_flags_queue_size",
1104+ "recovery_send_period",
1105+ "timing_window_size",
1106+ "timing_nth_element"
1107+};
1108+
1109+static int32* sAttributeDestinations[kNumInt32Attributes] =
1110+{
1111+ &sSpokePreferences.mPregameTicksBeforeNetDeath,
1112+ &sSpokePreferences.mInGameTicksBeforeNetDeath,
1113+// &sSpokePreferences.mOutgoingFlagsQueueSize,
1114+ &sSpokePreferences.mRecoverySendPeriod,
1115+ &sSpokePreferences.mTimingWindowSize,
1116+ &sSpokePreferences.mTimingNthElement
1117+};
1118+
1119+class XML_SpokeConfigurationParser: public XML_ElementParser
1120+{
1121+public:
1122+ bool Start();
1123+ bool HandleAttribute(const char *Tag, const char *Value);
1124+ bool AttributesDone();
1125+
1126+ XML_SpokeConfigurationParser(): XML_ElementParser("spoke") {}
1127+
1128+protected:
1129+ bool mAttributePresent[kNumAttributes];
1130+ int32 mAttribute[kNumInt32Attributes];
1131+ bool mAdjustTiming;
1132+};
1133+
1134+bool XML_SpokeConfigurationParser::Start()
1135+{
1136+ for(int i = 0; i < kNumAttributes; i++)
1137+ mAttributePresent[i] = false;
1138+
1139+ return true;
1140+}
1141+
1142+static const char* sAttributeMultiplySpecifiedString = "attribute multiply specified";
1143+
1144+bool XML_SpokeConfigurationParser::HandleAttribute(const char *Tag, const char *Value)
1145+{
1146+ if (StringsEqual(Tag,"adjust_timing"))
1147+ {
1148+ if(!mAttributePresent[kAdjustTimingAttribute]) {
1149+ if(ReadBooleanValueAsBool(Value,mAdjustTiming)) {
1150+ mAttributePresent[kAdjustTimingAttribute] = true;
1151+ return true;
1152+ }
1153+ else
1154+ return false;
1155+ }
1156+ else {
1157+ ErrorString = sAttributeMultiplySpecifiedString;
1158+ return false;
1159+ }
1160+ }
1161+
1162+ else
1163+ {
1164+ for(size_t i = 0; i < kNumInt32Attributes; i++)
1165+ {
1166+ if(StringsEqual(Tag,sAttributeStrings[i]))
1167+ {
1168+ if(!mAttributePresent[i]) {
1169+ if(ReadInt32Value(Value,mAttribute[i])) {
1170+ mAttributePresent[i] = true;
1171+ return true;
1172+ }
1173+ else
1174+ return false;
1175+ }
1176+ else {
1177+ ErrorString = sAttributeMultiplySpecifiedString;
1178+ return false;
1179+ }
1180+ }
1181+ }
1182+ }
1183+
1184+ UnrecognizedTag();
1185+ return false;
1186+}
1187+
1188+bool XML_SpokeConfigurationParser::AttributesDone() {
1189+ // Ignore out-of-range values
1190+ for(int i = 0; i < kNumAttributes; i++)
1191+ {
1192+ if(mAttributePresent[i])
1193+ {
1194+ switch(i)
1195+ {
1196+ case kPregameTicksBeforeNetDeathAttribute:
1197+ case kInGameTicksBeforeNetDeathAttribute:
1198+ case kRecoverySendPeriodAttribute:
1199+ case kTimingWindowSizeAttribute:
1200+ if(mAttribute[i] < 1)
1201+ {
1202+ // I don't know whether this actually does anything if I don't return false,
1203+ // but I'd like to honor the user's wishes as far as I can without just throwing
1204+ // up my hands.
1205+ BadNumericalValue();
1206+ logWarning3("improper value %d for attribute %s of <spoke>; must be at least 1. using default of %d", mAttribute[i], sAttributeStrings[i], *(sAttributeDestinations[i]));
1207+ mAttributePresent[i] = false;
1208+ }
1209+ else
1210+ {
1211+ *(sAttributeDestinations[i]) = mAttribute[i];
1212+ }
1213+ break;
1214+
1215+ case kTimingNthElementAttribute:
1216+ if(mAttribute[i] < 0 || mAttribute[i] >= *(sAttributeDestinations[kTimingWindowSizeAttribute]))
1217+ {
1218+ BadNumericalValue();
1219+ logWarning4("improper value %d for attribute %s of <spoke>; must be at least 0 but less than %s. using default of %d", mAttribute[i], sAttributeStrings[i], sAttributeStrings[kTimingWindowSizeAttribute], *(sAttributeDestinations[i]));
1220+ mAttributePresent[i] = false;
1221+ }
1222+ else
1223+ {
1224+ *(sAttributeDestinations[i]) = mAttribute[i];
1225+ }
1226+ break;
1227+
1228+ case kAdjustTimingAttribute:
1229+ sSpokePreferences.mAdjustTiming = mAdjustTiming;
1230+ break;
1231+
1232+ default:
1233+ assert(false);
1234+ break;
1235+ } // switch(attribute)
1236+
1237+ } // if attribute present
1238+
1239+ } // loop over attributes
1240+
1241+ // The checks above are not sufficient to catch all bad cases; if user specified a window size
1242+ // smaller than default, this is our only chance to deal with it.
1243+ if(sSpokePreferences.mTimingNthElement >= sSpokePreferences.mTimingWindowSize)
1244+ {
1245+ logWarning5("value for <spoke> attribute %s (%d) must be less than value for %s (%d). using %d", sAttributeStrings[kTimingNthElementAttribute], mAttribute[kTimingNthElementAttribute], sAttributeStrings[kTimingWindowSizeAttribute], mAttribute[kTimingWindowSizeAttribute], sSpokePreferences.mTimingWindowSize - 1);
1246+
1247+ sSpokePreferences.mTimingNthElement = sSpokePreferences.mTimingWindowSize - 1;
1248+ }
1249+
1250+ return true;
1251+}
1252+
1253+
1254+static XML_SpokeConfigurationParser SpokeConfigurationParser;
1255+
1256+
1257+XML_ElementParser*
1258+Spoke_GetParser() {
1259+ return &SpokeConfigurationParser;
1260+}
1261+
1262+
1263+
1264+void
1265+WriteSpokePreferences(FILE* F)
1266+{
1267+ fprintf(F, " <spoke\n");
1268+ for(size_t i = 0; i < kNumInt32Attributes; i++)
1269+ fprintf(F, " %s=\"%d\"\n", sAttributeStrings[i], *(sAttributeDestinations[i]));
1270+ fprintf(F, " adjust_timing=\"%s\"\n", BoolString(sSpokePreferences.mAdjustTiming));
1271+ fprintf(F, " />\n");
1272+}
1273+
1274+
1275+
1276+void
1277+DefaultSpokePreferences()
1278+{
1279+ sSpokePreferences.mPregameTicksBeforeNetDeath = kDefaultPregameTicksBeforeNetDeath;
1280+ sSpokePreferences.mInGameTicksBeforeNetDeath = kDefaultInGameTicksBeforeNetDeath;
1281+ // sSpokePreferences.mOutgoingFlagsQueueSize = kDefaultOutgoingFlagsQueueSize;
1282+ sSpokePreferences.mRecoverySendPeriod = kDefaultRecoverySendPeriod;
1283+ sSpokePreferences.mTimingWindowSize = kDefaultTimingWindowSize;
1284+ sSpokePreferences.mTimingNthElement = kDefaultTimingNthElement;
1285+ sSpokePreferences.mAdjustTiming = true;
1286+}
1287+
1288+#endif // !defined(DISABLE_NETWORKING)
--- marathon/trunk/Source_Files/Network/network_messages.h (revision 527)
+++ marathon/trunk/Source_Files/Network/network_messages.h (revision 528)
@@ -1,480 +1,480 @@
1-/*
2- * network_messages.h - TCPMess message types for network game setup
3-
4- Copyright (C) 2005 and beyond by Gregory Smith
5- and the "Aleph One" developers.
6-
7- This program is free software; you can redistribute it and/or modify
8- it under the terms of the GNU General Public License as published by
9- the Free Software Foundation; either version 3 of the License, or
10- (at your option) any later version.
11-
12- This program is distributed in the hope that it will be useful,
13- but WITHOUT ANY WARRANTY; without even the implied warranty of
14- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15- GNU General Public License for more details.
16-
17- This license is contained in the file "COPYING",
18- which is included with this source code; it is available online at
19- http://www.gnu.org/licenses/gpl.html
20-
21- */
22-
23-#ifndef NETWORK_MESSAGES_H
24-#define NETWORK_MESSAGES_H
25-
26-#include "cseries.h"
27-#include "AStream.h"
28-#include "Message.h"
29-
30-#include "SDL_net.h"
31-
32-#include "network_capabilities.h"
33-#include "network_private.h"
34-
35-enum {
36- kHELLO_MESSAGE = 700,
37- kJOINER_INFO_MESSAGE,
38- kJOIN_PLAYER_MESSAGE,
39- kCAPABILITIES_MESSAGE,
40- kACCEPT_JOIN_MESSAGE,
41- kTOPOLOGY_MESSAGE,
42- kMAP_MESSAGE,
43- kPHYSICS_MESSAGE,
44- kLUA_MESSAGE,
45- kCHAT_MESSAGE,
46- kUNKNOWN_MESSAGE_MESSAGE,
47- kEND_GAME_DATA_MESSAGE,
48- kCHANGE_COLORS_MESSAGE,
49- kSERVER_WARNING_MESSAGE,
50- kCLIENT_INFO_MESSAGE,
51- kZIPPED_MAP_MESSAGE,
52- kZIPPED_PHYSICS_MESSAGE,
53- kZIPPED_LUA_MESSAGE,
54- kNETWORK_STATS_MESSAGE
55-};
56-
57-template <MessageTypeID tMessageType, typename tValueType>
58-class TemplatizedSimpleMessage : public SimpleMessage<tValueType>
59-{
60- public:
61- enum { kType = tMessageType };
62-
63- TemplatizedSimpleMessage() : SimpleMessage<tValueType>(tMessageType) { }
64-
65- TemplatizedSimpleMessage(tValueType inValue) : SimpleMessage<tValueType>(tMessageType, inValue) { }
66-
67- TemplatizedSimpleMessage<tMessageType, tValueType>* clone () const {
68- return new TemplatizedSimpleMessage<tMessageType, tValueType>(*this);
69- }
70-
71- MessageTypeID type() const { return kType; }
72-};
73-
74-class AcceptJoinMessage : public SmallMessageHelper
75-{
76- public:
77- enum { kType = kACCEPT_JOIN_MESSAGE };
78-
79- AcceptJoinMessage() : SmallMessageHelper() { }
80-
81- AcceptJoinMessage(bool accepted, NetPlayer *player) : SmallMessageHelper() {
82- mAccepted = accepted;
83- mPlayer = *player;
84- }
85-
86- AcceptJoinMessage *clone() const {
87- return new AcceptJoinMessage(*this);
88- }
89-
90- bool accepted() { return mAccepted; }
91- void accepted(bool isAccepted) { mAccepted = isAccepted; }
92- NetPlayer *player() { return &mPlayer; }
93- void player(const NetPlayer *thePlayer) { mPlayer = *thePlayer; }
94-
95- MessageTypeID type() const { return kType; }
96-
97- protected:
98- void reallyDeflateTo(AOStream& outputStream) const;
99- bool reallyInflateFrom(AIStream& inputStream);
100-
101- private:
102-
103- bool mAccepted;
104- NetPlayer mPlayer;
105-};
106-
107-class CapabilitiesMessage : public SmallMessageHelper
108-{
109- public:
110- enum { kType = kCAPABILITIES_MESSAGE };
111-
112- CapabilitiesMessage() : SmallMessageHelper() { }
113-
114- CapabilitiesMessage(const Capabilities &capabilities) : SmallMessageHelper() {
115- mCapabilities = capabilities;
116- }
117-
118- CapabilitiesMessage *clone() const {
119- return new CapabilitiesMessage(*this);
120- }
121-
122- const Capabilities *capabilities() { return &mCapabilities; }
123-
124- MessageTypeID type() const { return kType; }
125-
126-protected:
127- void reallyDeflateTo(AOStream& outputStream) const;
128- bool reallyInflateFrom(AIStream& inputStream);
129-
130-private:
131- Capabilities mCapabilities;
132-};
133-
134-class ChangeColorsMessage : public SmallMessageHelper
135-{
136- public:
137- enum { kType = kCHANGE_COLORS_MESSAGE };
138-
139- ChangeColorsMessage() : SmallMessageHelper() { }
140-
141- ChangeColorsMessage(int16 color, int16 team) : SmallMessageHelper() {
142- mColor = color;
143- mTeam = team;
144- }
145-
146- ChangeColorsMessage *clone() const {
147- return new ChangeColorsMessage(*this);
148- }
149-
150- int16 color() { return mColor; }
151- int16 team() { return mTeam; }
152-
153- MessageTypeID type() const { return kType; }
154-
155- protected:
156- void reallyDeflateTo(AOStream& outputStream) const;
157- bool reallyInflateFrom(AIStream& inputStream);
158-
159- private:
160-
161- int16 mColor;
162- int16 mTeam;
163-};
164-
165-class ClientInfoMessage : public SmallMessageHelper
166-{
167-public:
168- enum { kType = kCLIENT_INFO_MESSAGE };
169- enum { kAdd, kUpdate, kRemove };
170-
171- ClientInfoMessage() : SmallMessageHelper() { }
172-
173- ClientInfoMessage(int16 stream_id, const ClientChatInfo *clientChatInfo, int16 action ) : mStreamID(stream_id), mAction(action), mClientChatInfo(*clientChatInfo) { }
174-
175- ClientInfoMessage *clone() const {
176- return new ClientInfoMessage(*this);
177- }
178-
179- const ClientChatInfo *info() { return &mClientChatInfo; }
180- const int16 action() { return mAction; }
181- const int16 stream_id() { return mStreamID; }
182-
183- MessageTypeID type() const { return kType; }
184-
185-protected:
186- void reallyDeflateTo(AOStream& outputStream) const;
187- bool reallyInflateFrom(AIStream& inputStream);
188-
189-private:
190- ClientChatInfo mClientChatInfo;
191- int16 mAction;
192- int16 mStreamID;
193-};
194-
195-
196-typedef DatalessMessage<kEND_GAME_DATA_MESSAGE> EndGameDataMessage;
197-
198-class HelloMessage : public SmallMessageHelper
199-{
200-public:
201- enum { kType = kHELLO_MESSAGE };
202-
203- HelloMessage() : SmallMessageHelper() { }
204-
205- HelloMessage(const std::string &version) :
206- SmallMessageHelper(), mVersion(version) { }
207-
208- HelloMessage *clone() const {
209- return new HelloMessage(*this);
210- }
211-
212- std::string version() { return mVersion; }
213- void version(const std::string &version) { mVersion = version; }
214-
215- MessageTypeID type() const { return kType; }
216-
217-protected:
218- void reallyDeflateTo(AOStream& outputStream) const;
219- bool reallyInflateFrom(AIStream& inputStream);
220-
221-private:
222-
223- std::string mVersion;
224-};
225-
226-typedef TemplatizedSimpleMessage<kJOIN_PLAYER_MESSAGE, int16> JoinPlayerMessage;
227-
228-class JoinerInfoMessage : public SmallMessageHelper
229-{
230-public:
231- enum { kType = kJOINER_INFO_MESSAGE };
232-
233- JoinerInfoMessage() : SmallMessageHelper() { }
234-
235- JoinerInfoMessage(prospective_joiner_info *info, const std::string &version) : SmallMessageHelper() {
236-
237- mInfo = *info;
238- mVersion = version;
239- }
240-
241- JoinerInfoMessage *clone() const {
242- return new JoinerInfoMessage(*this);
243- }
244-
245- prospective_joiner_info *info() { return &mInfo; }
246- void info(const prospective_joiner_info *theInfo) {
247- mInfo = *theInfo;
248- }
249-
250- std::string version() { return mVersion; }
251- void version(const std::string version) { mVersion = version; }
252-
253- MessageTypeID type() const { return kType; }
254-
255-protected:
256- void reallyDeflateTo(AOStream& outputStream) const;
257- bool reallyInflateFrom(AIStream& inputStream);
258-
259-private:
260- prospective_joiner_info mInfo;
261- std::string mVersion;
262-};
263-
264-class BigChunkOfZippedDataMessage : public BigChunkOfDataMessage
265-// zips on deflate, unzips on inflate
266-{
267-public:
268- BigChunkOfZippedDataMessage(MessageTypeID inType, const Uint8* inBuffer = NULL, size_t inLength = 0) : BigChunkOfDataMessage(inType, inBuffer, inLength) { }
269- BigChunkOfZippedDataMessage(const BigChunkOfDataMessage& other) : BigChunkOfDataMessage(other) { }
270-
271- bool inflateFrom(const UninflatedMessage& inUninflated);
272- UninflatedMessage* deflate() const;
273-};
274-
275-template<int messageType, class T> class TemplatizedDataMessage : public T
276-{
277-public:
278- enum {
279- kType = messageType
280- };
281-
282- TemplatizedDataMessage(const Uint8* inBuffer = NULL, size_t inLength = 0) :
283- T(kType, inBuffer, inLength) { };
284- TemplatizedDataMessage(const TemplatizedDataMessage& other) : T(other) { }
285- COVARIANT_RETURN(Message *, TemplatizedDataMessage *) clone () const {
286- return new TemplatizedDataMessage(*this);
287- }
288-};
289-
290-typedef TemplatizedDataMessage<kMAP_MESSAGE, BigChunkOfDataMessage> MapMessage;
291-typedef TemplatizedDataMessage<kZIPPED_MAP_MESSAGE, BigChunkOfZippedDataMessage> ZippedMapMessage;
292-
293-typedef TemplatizedDataMessage<kPHYSICS_MESSAGE, BigChunkOfDataMessage> PhysicsMessage;
294-typedef TemplatizedDataMessage<kZIPPED_PHYSICS_MESSAGE, BigChunkOfZippedDataMessage> ZippedPhysicsMessage;
295-
296-typedef TemplatizedDataMessage<kLUA_MESSAGE, BigChunkOfDataMessage> LuaMessage;
297-typedef TemplatizedDataMessage<kZIPPED_LUA_MESSAGE, BigChunkOfZippedDataMessage> ZippedLuaMessage;
298-
299-
300-class NetworkChatMessage : public SmallMessageHelper
301-{
302- public:
303- enum { kType = kCHAT_MESSAGE };
304- enum { CHAT_MESSAGE_SIZE = 1024 };
305-
306- enum { kTargetPlayers = 0,
307- kTargetTeam = 1,
308- kTargetPlayer = 2,
309- kTargetClients = 3,
310- kTargetClient = 4 };
311-