22 #include "player_face.h" |
22 #include "player_face.h" |
23 |
23 |
24 #include "table/sprites.h" |
24 #include "table/sprites.h" |
25 #include "table/strings.h" |
25 #include "table/strings.h" |
26 |
26 |
27 /** @file news_gui.cpp |
|
28 * |
|
29 * News system is realized as a FIFO queue (in an array) |
|
30 * The positions in the queue can't be rearranged, we only access |
|
31 * the array elements through pointers to the elements. Once the |
|
32 * array is full, the oldest entry (\a _oldest_news) is being overwritten |
|
33 * by the newest (\a _latest_news). |
|
34 * |
|
35 * \verbatim |
|
36 * oldest current lastest |
|
37 * | | | |
|
38 * [O------------F-------------C---------L ] |
|
39 * | |
|
40 * forced |
|
41 * \endverbatim |
|
42 * |
|
43 * Of course by using an array we can have situations like |
|
44 * |
|
45 * \verbatim |
|
46 * [----L O-----F---------C-----------------] |
|
47 * This is where we have wrapped around the array and have |
|
48 * (MAX_NEWS - O) + L news items |
|
49 * \endverbatim |
|
50 */ |
|
51 |
|
52 #define NB_WIDG_PER_SETTING 4 |
27 #define NB_WIDG_PER_SETTING 4 |
53 |
|
54 typedef byte NewsID; |
|
55 #define INVALID_NEWS 255 |
|
56 |
28 |
57 NewsItem _statusbar_news_item; |
29 NewsItem _statusbar_news_item; |
58 bool _news_ticker_sound; |
30 bool _news_ticker_sound; |
59 static NewsItem *_news_items = NULL; ///< The news FIFO queue |
31 |
60 static uint _max_news_items = 0; ///< size of news FIFO queue |
32 static uint MIN_NEWS_AMOUNT = 30; ///< prefered minimum amount of news messages |
61 static NewsID _current_news = INVALID_NEWS; ///< points to news item that should be shown next |
33 static uint _total_news = 0; ///< current number of news items |
62 static NewsID _oldest_news = 0; ///< points to first item in fifo queue |
34 static NewsItem *_oldest_news = NULL; ///< head of news items queue |
63 static NewsID _latest_news = INVALID_NEWS; ///< points to last item in fifo queue |
35 static NewsItem *_latest_news = NULL; ///< tail of news items queue |
64 |
36 |
65 /** Forced news item. |
37 /** Forced news item. |
66 * Users can force an item by accessing the history or "last message". |
38 * Users can force an item by accessing the history or "last message". |
67 * If the message being shown was forced by the user, its index is stored in |
39 * If the message being shown was forced by the user, a pointer is stored |
68 * _forced_news. Otherwise, \a _forced_news variable is INVALID_NEWS. */ |
40 * in _forced_news. Otherwise, \a _forced_news variable is NULL. */ |
69 static NewsID _forced_news = INVALID_NEWS; |
41 static NewsItem *_forced_news = NULL; ///< item the user has asked for |
70 |
42 |
71 static uint _total_news = 0; ///< Number of news items in FIFO queue @see _news_items |
43 /** Current news item (last item shown regularly). */ |
72 static void MoveToNextItem(); |
44 static NewsItem *_current_news = NULL; |
73 |
45 |
74 |
46 |
75 typedef void DrawNewsCallbackProc(struct Window *w, const NewsItem *ni); |
47 typedef void DrawNewsCallbackProc(struct Window *w, const NewsItem *ni); |
76 void DrawNewsNewVehicleAvail(Window *w, const NewsItem *ni); |
48 void DrawNewsNewVehicleAvail(Window *w, const NewsItem *ni); |
77 |
49 |
170 { NT_ACCEPTANCE, NM_SMALL, NF_VIEWPORT|NF_TILE, NULL }, ///< NS_ACCEPTANCE |
142 { NT_ACCEPTANCE, NM_SMALL, NF_VIEWPORT|NF_TILE, NULL }, ///< NS_ACCEPTANCE |
171 { NT_SUBSIDIES, NM_NORMAL, NF_TILE|NF_TILE2, NULL }, ///< NS_SUBSIDIES |
143 { NT_SUBSIDIES, NM_NORMAL, NF_TILE|NF_TILE2, NULL }, ///< NS_SUBSIDIES |
172 { NT_GENERAL, NM_NORMAL, NF_TILE, NULL }, ///< NS_GENERAL |
144 { NT_GENERAL, NM_NORMAL, NF_TILE, NULL }, ///< NS_GENERAL |
173 }; |
145 }; |
174 |
146 |
175 /** Initialize the news-items data structures */ |
|
176 void InitNewsItemStructs() |
|
177 { |
|
178 free(_news_items); |
|
179 _max_news_items = max(ScaleByMapSize(30), 30U); |
|
180 _news_items = CallocT<NewsItem>(_max_news_items); |
|
181 _current_news = INVALID_NEWS; |
|
182 _oldest_news = 0; |
|
183 _latest_news = INVALID_NEWS; |
|
184 _forced_news = INVALID_NEWS; |
|
185 _total_news = 0; |
|
186 } |
|
187 |
|
188 struct NewsWindow : Window { |
|
189 uint16 chat_height; |
|
190 NewsItem *ni; |
|
191 |
|
192 NewsWindow(const WindowDesc *desc, NewsItem *ni) : Window(desc), ni(ni) |
|
193 { |
|
194 const Window *w = FindWindowById(WC_SEND_NETWORK_MSG, 0); |
|
195 this->chat_height = (w != NULL) ? w->height : 0; |
|
196 |
|
197 this->ni = &_news_items[_forced_news == INVALID_NEWS ? _current_news : _forced_news]; |
|
198 this->flags4 |= WF_DISABLE_VP_SCROLL; |
|
199 |
|
200 this->FindWindowPlacementAndResize(desc); |
|
201 } |
|
202 |
|
203 void DrawNewsBorder() |
|
204 { |
|
205 int left = 0; |
|
206 int right = this->width - 1; |
|
207 int top = 0; |
|
208 int bottom = this->height - 1; |
|
209 |
|
210 GfxFillRect(left, top, right, bottom, 0xF); |
|
211 |
|
212 GfxFillRect(left, top, left, bottom, 0xD7); |
|
213 GfxFillRect(right, top, right, bottom, 0xD7); |
|
214 GfxFillRect(left, top, right, top, 0xD7); |
|
215 GfxFillRect(left, bottom, right, bottom, 0xD7); |
|
216 |
|
217 DrawString(left + 2, top + 1, STR_00C6, TC_FROMSTRING); |
|
218 } |
|
219 |
|
220 virtual void OnPaint() |
|
221 { |
|
222 const NewsMode display_mode = _news_subtype_data[this->ni->subtype].display_mode; |
|
223 |
|
224 switch (display_mode) { |
|
225 case NM_NORMAL: |
|
226 case NM_THIN: { |
|
227 this->DrawNewsBorder(); |
|
228 |
|
229 DrawString(2, 1, STR_00C6, TC_FROMSTRING); |
|
230 |
|
231 SetDParam(0, this->ni->date); |
|
232 DrawStringRightAligned(428, 1, STR_01FF, TC_FROMSTRING); |
|
233 |
|
234 if (!(this->ni->flags & NF_VIEWPORT)) { |
|
235 CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); |
|
236 DrawStringMultiCenter(215, display_mode == NM_NORMAL ? 76 : 56, |
|
237 this->ni->string_id, this->width - 4); |
|
238 } else { |
|
239 /* Back up transparency options to draw news view */ |
|
240 TransparencyOptionBits to_backup = _transparency_opt; |
|
241 _transparency_opt = 0; |
|
242 this->DrawViewport(); |
|
243 _transparency_opt = to_backup; |
|
244 |
|
245 /* Shade the viewport into gray, or color*/ |
|
246 ViewPort *vp = this->viewport; |
|
247 GfxFillRect(vp->left - this->left, vp->top - this->top, |
|
248 vp->left - this->left + vp->width - 1, vp->top - this->top + vp->height - 1, |
|
249 (this->ni->flags & NF_INCOLOR ? PALETTE_TO_TRANSPARENT : PALETTE_TO_STRUCT_GREY) | (1 << USE_COLORTABLE) |
|
250 ); |
|
251 |
|
252 CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); |
|
253 DrawStringMultiCenter(this->width / 2, 20, this->ni->string_id, this->width - 4); |
|
254 } |
|
255 break; |
|
256 } |
|
257 |
|
258 case NM_CALLBACK: |
|
259 this->DrawNewsBorder(); |
|
260 (_news_subtype_data[this->ni->subtype].callback)(this, ni); |
|
261 break; |
|
262 |
|
263 default: |
|
264 this->DrawWidgets(); |
|
265 if (!(this->ni->flags & NF_VIEWPORT)) { |
|
266 CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); |
|
267 DrawStringMultiCenter(140, 38, this->ni->string_id, 276); |
|
268 } else { |
|
269 this->DrawViewport(); |
|
270 CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); |
|
271 DrawStringMultiCenter(this->width / 2, this->height - 16, this->ni->string_id, this->width - 4); |
|
272 } |
|
273 break; |
|
274 } |
|
275 } |
|
276 |
|
277 virtual void OnClick(Point pt, int widget) |
|
278 { |
|
279 switch (widget) { |
|
280 case 1: |
|
281 this->ni->duration = 0; |
|
282 delete this; |
|
283 _forced_news = INVALID_NEWS; |
|
284 break; |
|
285 |
|
286 case 0: |
|
287 if (this->ni->flags & NF_VEHICLE) { |
|
288 Vehicle *v = GetVehicle(this->ni->data_a); |
|
289 ScrollMainWindowTo(v->x_pos, v->y_pos); |
|
290 } else if (this->ni->flags & NF_TILE) { |
|
291 if (_ctrl_pressed) { |
|
292 ShowExtraViewPortWindow(this->ni->data_a); |
|
293 if (this->ni->flags & NF_TILE2) { |
|
294 ShowExtraViewPortWindow(this->ni->data_b); |
|
295 } |
|
296 } else { |
|
297 if (!ScrollMainWindowToTile(this->ni->data_a) && this->ni->flags & NF_TILE2) { |
|
298 ScrollMainWindowToTile(this->ni->data_b); |
|
299 } |
|
300 } |
|
301 } |
|
302 break; |
|
303 } |
|
304 } |
|
305 |
|
306 virtual EventState OnKeyPress(uint16 key, uint16 keycode) |
|
307 { |
|
308 if (keycode == WKC_SPACE) { |
|
309 /* Don't continue. */ |
|
310 delete this; |
|
311 return ES_HANDLED; |
|
312 } |
|
313 return ES_NOT_HANDLED; |
|
314 } |
|
315 |
|
316 virtual void OnInvalidateData(int data) |
|
317 { |
|
318 /* The chatbar has notified us that is was either created or closed */ |
|
319 this->chat_height = data; |
|
320 } |
|
321 |
|
322 virtual void OnTick() |
|
323 { |
|
324 /* Scroll up newsmessages from the bottom in steps of 4 pixels */ |
|
325 int y = max(this->top - 4, _screen.height - this->height - 12 - this->chat_height); |
|
326 if (y == this->top) return; |
|
327 |
|
328 if (this->viewport != NULL) this->viewport->top += y - this->top; |
|
329 |
|
330 int diff = Delta(this->top, y); |
|
331 this->top = y; |
|
332 |
|
333 SetDirtyBlocks(this->left, this->top - diff, this->left + this->width, this->top + this->height); |
|
334 } |
|
335 }; |
|
336 |
|
337 /** |
|
338 * Return the correct index in the pseudo-fifo |
|
339 * queue and deals with overflows when increasing the index |
|
340 */ |
|
341 static inline NewsID IncreaseIndex(NewsID i) |
|
342 { |
|
343 assert(i != INVALID_NEWS); |
|
344 return (i + 1) % _max_news_items; |
|
345 } |
|
346 |
|
347 /** |
|
348 * Return the correct index in the pseudo-fifo |
|
349 * queue and deals with overflows when decreasing the index |
|
350 */ |
|
351 static inline NewsID DecreaseIndex(NewsID i) |
|
352 { |
|
353 assert(i != INVALID_NEWS); |
|
354 return (i + _max_news_items - 1) % _max_news_items; |
|
355 } |
|
356 |
|
357 /** |
|
358 * Add a new newsitem to be shown. |
|
359 * @param string String to display |
|
360 * @param subtype news category, any of the NewsSubtype enums (NS_) |
|
361 * @param data_a news-specific value based on news type |
|
362 * @param data_b news-specific value based on news type |
|
363 * |
|
364 * @see NewsSubype |
|
365 */ |
|
366 void AddNewsItem(StringID string, NewsSubtype subtype, uint data_a, uint data_b) |
|
367 { |
|
368 if (_game_mode == GM_MENU) return; |
|
369 |
|
370 /* check the rare case that the oldest (to be overwritten) news item is open */ |
|
371 if (_total_news == _max_news_items && (_oldest_news == _current_news || _oldest_news == _forced_news)) { |
|
372 MoveToNextItem(); |
|
373 } |
|
374 |
|
375 if (_total_news < _max_news_items) _total_news++; |
|
376 |
|
377 /* Increase _latest_news. If we have no news yet, use _oldest news as an |
|
378 * index. We cannot use 0 as _oldest_news can jump around due to |
|
379 * DeleteVehicleNews */ |
|
380 NewsID l_news = _latest_news; |
|
381 _latest_news = (_latest_news == INVALID_NEWS) ? _oldest_news : IncreaseIndex(_latest_news); |
|
382 |
|
383 /* If the fifo-buffer is full, overwrite the oldest entry */ |
|
384 if (l_news != INVALID_NEWS && _latest_news == _oldest_news) { |
|
385 assert(_total_news == _max_news_items); |
|
386 _oldest_news = IncreaseIndex(_oldest_news); |
|
387 } |
|
388 |
|
389 /*DEBUG(misc, 0, "+cur %3d, old %2d, lat %3d, for %3d, tot %2d", |
|
390 _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/ |
|
391 |
|
392 /* Add news to _latest_news */ |
|
393 NewsItem *ni = &_news_items[_latest_news]; |
|
394 memset(ni, 0, sizeof(*ni)); |
|
395 |
|
396 ni->string_id = string; |
|
397 ni->subtype = subtype; |
|
398 ni->flags = _news_subtype_data[subtype].flags; |
|
399 |
|
400 /* show this news message in color? */ |
|
401 if (_cur_year >= _settings.gui.colored_news_year) ni->flags |= NF_INCOLOR; |
|
402 |
|
403 ni->data_a = data_a; |
|
404 ni->data_b = data_b; |
|
405 ni->date = _date; |
|
406 CopyOutDParam(ni->params, 0, lengthof(ni->params)); |
|
407 |
|
408 Window *w = FindWindowById(WC_MESSAGE_HISTORY, 0); |
|
409 if (w == NULL) return; |
|
410 w->SetDirty(); |
|
411 w->vscroll.count = _total_news; |
|
412 } |
|
413 |
|
414 |
|
415 /** |
147 /** |
416 * Per-NewsType data |
148 * Per-NewsType data |
417 */ |
149 */ |
418 NewsTypeData _news_type_data[NT_END] = { |
150 NewsTypeData _news_type_data[NT_END] = { |
419 /* name, age, sound, display */ |
151 /* name, age, sound, display */ |
431 { "acceptance", 90, SND_BEGIN, ND_FULL }, ///< NT_ACCEPTANCE |
163 { "acceptance", 90, SND_BEGIN, ND_FULL }, ///< NT_ACCEPTANCE |
432 { "subsidies", 180, SND_BEGIN, ND_FULL }, ///< NT_SUBSIDIES |
164 { "subsidies", 180, SND_BEGIN, ND_FULL }, ///< NT_SUBSIDIES |
433 { "general", 60, SND_BEGIN, ND_FULL }, ///< NT_GENERAL |
165 { "general", 60, SND_BEGIN, ND_FULL }, ///< NT_GENERAL |
434 }; |
166 }; |
435 |
167 |
|
168 struct NewsWindow : Window { |
|
169 uint16 chat_height; |
|
170 NewsItem *ni; |
|
171 |
|
172 NewsWindow(const WindowDesc *desc, NewsItem *ni) : Window(desc), ni(ni) |
|
173 { |
|
174 const Window *w = FindWindowById(WC_SEND_NETWORK_MSG, 0); |
|
175 this->chat_height = (w != NULL) ? w->height : 0; |
|
176 |
|
177 this->ni = _forced_news == NULL ? _current_news : _forced_news; |
|
178 this->flags4 |= WF_DISABLE_VP_SCROLL; |
|
179 |
|
180 this->FindWindowPlacementAndResize(desc); |
|
181 } |
|
182 |
|
183 void DrawNewsBorder() |
|
184 { |
|
185 int left = 0; |
|
186 int right = this->width - 1; |
|
187 int top = 0; |
|
188 int bottom = this->height - 1; |
|
189 |
|
190 GfxFillRect(left, top, right, bottom, 0xF); |
|
191 |
|
192 GfxFillRect(left, top, left, bottom, 0xD7); |
|
193 GfxFillRect(right, top, right, bottom, 0xD7); |
|
194 GfxFillRect(left, top, right, top, 0xD7); |
|
195 GfxFillRect(left, bottom, right, bottom, 0xD7); |
|
196 |
|
197 DrawString(left + 2, top + 1, STR_00C6, TC_FROMSTRING); |
|
198 } |
|
199 |
|
200 virtual void OnPaint() |
|
201 { |
|
202 const NewsMode display_mode = _news_subtype_data[this->ni->subtype].display_mode; |
|
203 |
|
204 switch (display_mode) { |
|
205 case NM_NORMAL: |
|
206 case NM_THIN: { |
|
207 this->DrawNewsBorder(); |
|
208 |
|
209 DrawString(2, 1, STR_00C6, TC_FROMSTRING); |
|
210 |
|
211 SetDParam(0, this->ni->date); |
|
212 DrawStringRightAligned(428, 1, STR_01FF, TC_FROMSTRING); |
|
213 |
|
214 if (!(this->ni->flags & NF_VIEWPORT)) { |
|
215 CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); |
|
216 DrawStringMultiCenter(215, display_mode == NM_NORMAL ? 76 : 56, |
|
217 this->ni->string_id, this->width - 4); |
|
218 } else { |
|
219 /* Back up transparency options to draw news view */ |
|
220 TransparencyOptionBits to_backup = _transparency_opt; |
|
221 _transparency_opt = 0; |
|
222 this->DrawViewport(); |
|
223 _transparency_opt = to_backup; |
|
224 |
|
225 /* Shade the viewport into gray, or color*/ |
|
226 ViewPort *vp = this->viewport; |
|
227 GfxFillRect(vp->left - this->left, vp->top - this->top, |
|
228 vp->left - this->left + vp->width - 1, vp->top - this->top + vp->height - 1, |
|
229 (this->ni->flags & NF_INCOLOR ? PALETTE_TO_TRANSPARENT : PALETTE_TO_STRUCT_GREY) | (1 << USE_COLORTABLE) |
|
230 ); |
|
231 |
|
232 CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); |
|
233 DrawStringMultiCenter(this->width / 2, 20, this->ni->string_id, this->width - 4); |
|
234 } |
|
235 break; |
|
236 } |
|
237 |
|
238 case NM_CALLBACK: |
|
239 this->DrawNewsBorder(); |
|
240 (_news_subtype_data[this->ni->subtype].callback)(this, ni); |
|
241 break; |
|
242 |
|
243 default: |
|
244 this->DrawWidgets(); |
|
245 if (!(this->ni->flags & NF_VIEWPORT)) { |
|
246 CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); |
|
247 DrawStringMultiCenter(140, 38, this->ni->string_id, 276); |
|
248 } else { |
|
249 this->DrawViewport(); |
|
250 CopyInDParam(0, this->ni->params, lengthof(this->ni->params)); |
|
251 DrawStringMultiCenter(this->width / 2, this->height - 16, this->ni->string_id, this->width - 4); |
|
252 } |
|
253 break; |
|
254 } |
|
255 } |
|
256 |
|
257 virtual void OnClick(Point pt, int widget) |
|
258 { |
|
259 switch (widget) { |
|
260 case 1: |
|
261 this->ni->duration = 0; |
|
262 delete this; |
|
263 _forced_news = NULL; |
|
264 break; |
|
265 |
|
266 case 0: |
|
267 if (this->ni->flags & NF_VEHICLE) { |
|
268 Vehicle *v = GetVehicle(this->ni->data_a); |
|
269 ScrollMainWindowTo(v->x_pos, v->y_pos); |
|
270 } else if (this->ni->flags & NF_TILE) { |
|
271 if (_ctrl_pressed) { |
|
272 ShowExtraViewPortWindow(this->ni->data_a); |
|
273 if (this->ni->flags & NF_TILE2) { |
|
274 ShowExtraViewPortWindow(this->ni->data_b); |
|
275 } |
|
276 } else { |
|
277 if (!ScrollMainWindowToTile(this->ni->data_a) && this->ni->flags & NF_TILE2) { |
|
278 ScrollMainWindowToTile(this->ni->data_b); |
|
279 } |
|
280 } |
|
281 } |
|
282 break; |
|
283 } |
|
284 } |
|
285 |
|
286 virtual EventState OnKeyPress(uint16 key, uint16 keycode) |
|
287 { |
|
288 if (keycode == WKC_SPACE) { |
|
289 /* Don't continue. */ |
|
290 delete this; |
|
291 return ES_HANDLED; |
|
292 } |
|
293 return ES_NOT_HANDLED; |
|
294 } |
|
295 |
|
296 virtual void OnInvalidateData(int data) |
|
297 { |
|
298 /* The chatbar has notified us that is was either created or closed */ |
|
299 this->chat_height = data; |
|
300 } |
|
301 |
|
302 virtual void OnTick() |
|
303 { |
|
304 /* Scroll up newsmessages from the bottom in steps of 4 pixels */ |
|
305 int y = max(this->top - 4, _screen.height - this->height - 12 - this->chat_height); |
|
306 if (y == this->top) return; |
|
307 |
|
308 if (this->viewport != NULL) this->viewport->top += y - this->top; |
|
309 |
|
310 int diff = Delta(this->top, y); |
|
311 this->top = y; |
|
312 |
|
313 SetDirtyBlocks(this->left, this->top - diff, this->left + this->width, this->top + this->height); |
|
314 } |
|
315 }; |
|
316 |
436 |
317 |
437 static const Widget _news_type13_widgets[] = { |
318 static const Widget _news_type13_widgets[] = { |
438 { WWT_PANEL, RESIZE_NONE, 15, 0, 429, 0, 169, 0x0, STR_NULL}, |
319 { WWT_PANEL, RESIZE_NONE, 15, 0, 429, 0, 169, 0x0, STR_NULL}, |
439 { WWT_PANEL, RESIZE_NONE, 15, 0, 10, 0, 11, 0x0, STR_NULL}, |
320 { WWT_PANEL, RESIZE_NONE, 15, 0, 10, 0, 11, 0x0, STR_NULL}, |
440 { WIDGETS_END}, |
321 { WIDGETS_END}, |
586 break; |
477 break; |
587 } |
478 } |
588 } |
479 } |
589 } |
480 } |
590 |
481 |
|
482 /** |
|
483 * Add a new newsitem to be shown. |
|
484 * @param string String to display |
|
485 * @param subtype news category, any of the NewsSubtype enums (NS_) |
|
486 * @param data_a news-specific value based on news type |
|
487 * @param data_b news-specific value based on news type |
|
488 * |
|
489 * @see NewsSubype |
|
490 */ |
|
491 void AddNewsItem(StringID string, NewsSubtype subtype, uint data_a, uint data_b) |
|
492 { |
|
493 if (_game_mode == GM_MENU) return; |
|
494 |
|
495 /* Create new news item node */ |
|
496 NewsItem *ni = new NewsItem; |
|
497 |
|
498 ni->string_id = string; |
|
499 ni->subtype = subtype; |
|
500 ni->flags = _news_subtype_data[subtype].flags; |
|
501 |
|
502 /* show this news message in color? */ |
|
503 if (_cur_year >= _settings_client.gui.colored_news_year) ni->flags |= NF_INCOLOR; |
|
504 |
|
505 ni->data_a = data_a; |
|
506 ni->data_b = data_b; |
|
507 ni->date = _date; |
|
508 CopyOutDParam(ni->params, 0, lengthof(ni->params)); |
|
509 |
|
510 if (_total_news++ == 0) { |
|
511 assert(_oldest_news == NULL); |
|
512 _oldest_news = ni; |
|
513 ni->prev = NULL; |
|
514 } else { |
|
515 assert(_latest_news->next == NULL); |
|
516 _latest_news->next = ni; |
|
517 ni->prev = _latest_news; |
|
518 } |
|
519 |
|
520 ni->next = NULL; |
|
521 _latest_news = ni; |
|
522 |
|
523 InvalidateWindow(WC_MESSAGE_HISTORY, 0); |
|
524 } |
|
525 |
|
526 /** Delete a news item from the queue */ |
|
527 static void DeleteNewsItem(NewsItem *ni) |
|
528 { |
|
529 if (_forced_news == ni) { |
|
530 /* about to remove the currently forced item; skip to next */ |
|
531 MoveToNextItem(); |
|
532 } |
|
533 |
|
534 if ((_current_news == ni) && (FindWindowById(WC_MESSAGE_HISTORY, 0) != NULL)) { |
|
535 /* about to remove the currently displayed item; also skip */ |
|
536 MoveToNextItem(); |
|
537 } |
|
538 |
|
539 /* delete item */ |
|
540 |
|
541 if (ni->prev != NULL) { |
|
542 ni->prev->next = ni->next; |
|
543 } else { |
|
544 assert(_oldest_news == ni); |
|
545 _oldest_news = ni->next; |
|
546 } |
|
547 |
|
548 if (ni->next != NULL) { |
|
549 ni->next->prev = ni->prev; |
|
550 } else { |
|
551 assert(_latest_news == ni); |
|
552 _latest_news = ni->prev; |
|
553 } |
|
554 |
|
555 if (_current_news == ni) _current_news = ni->prev; |
|
556 _total_news--; |
|
557 delete ni; |
|
558 |
|
559 InvalidateWindow(WC_MESSAGE_HISTORY, 0); |
|
560 } |
|
561 |
|
562 void DeleteVehicleNews(VehicleID vid, StringID news) |
|
563 { |
|
564 NewsItem *ni = _oldest_news; |
|
565 |
|
566 while (ni != NULL) { |
|
567 if (ni->flags & NF_VEHICLE && |
|
568 ni->data_a == vid && |
|
569 (news == INVALID_STRING_ID || ni->string_id == news)) { |
|
570 /* grab a pointer to the next item before ni is freed */ |
|
571 NewsItem *p = ni->next; |
|
572 DeleteNewsItem(ni); |
|
573 ni = p; |
|
574 } else { |
|
575 ni = ni->next; |
|
576 } |
|
577 } |
|
578 } |
|
579 |
|
580 void RemoveOldNewsItems() |
|
581 { |
|
582 NewsItem *next; |
|
583 for (NewsItem *cur = _oldest_news; _total_news > MIN_NEWS_AMOUNT && cur != NULL; cur = next) { |
|
584 next = cur->next; |
|
585 if (_date - _news_type_data[_news_subtype_data[cur->subtype].type].age * _settings_client.gui.news_message_timeout > cur->date) DeleteNewsItem(cur); |
|
586 } |
|
587 } |
|
588 |
591 void NewsLoop() |
589 void NewsLoop() |
592 { |
590 { |
593 /* no news item yet */ |
591 /* no news item yet */ |
594 if (_total_news == 0) return; |
592 if (_total_news == 0) return; |
595 |
593 |
|
594 static byte _last_clean_month = 0; |
|
595 |
|
596 if (_last_clean_month != _cur_month) { |
|
597 RemoveOldNewsItems(); |
|
598 _last_clean_month = _cur_month; |
|
599 } |
|
600 |
596 if (ReadyForNextItem()) MoveToNextItem(); |
601 if (ReadyForNextItem()) MoveToNextItem(); |
597 } |
602 } |
598 |
603 |
599 /** Do a forced show of a specific message */ |
604 /** Do a forced show of a specific message */ |
600 static void ShowNewsMessage(NewsID i) |
605 static void ShowNewsMessage(NewsItem *ni) |
601 { |
606 { |
602 if (_total_news == 0) return; |
607 assert(_total_news != 0); |
603 |
608 |
604 /* Delete the news window */ |
609 /* Delete the news window */ |
605 DeleteWindowById(WC_NEWS_WINDOW, 0); |
610 DeleteWindowById(WC_NEWS_WINDOW, 0); |
606 |
611 |
607 /* setup forced news item */ |
612 /* setup forced news item */ |
608 _forced_news = i; |
613 _forced_news = ni; |
609 |
614 |
610 if (_forced_news != INVALID_NEWS) { |
615 if (_forced_news != NULL) { |
611 NewsItem *ni = &_news_items[_forced_news]; |
|
612 ni->duration = 555; |
616 ni->duration = 555; |
613 ni->flags |= NF_FORCE_BIG; |
617 ni->flags |= NF_FORCE_BIG; |
614 DeleteWindowById(WC_NEWS_WINDOW, 0); |
618 DeleteWindowById(WC_NEWS_WINDOW, 0); |
615 ShowNewspaper(ni); |
619 ShowNewspaper(ni); |
616 } |
620 } |
617 } |
621 } |
618 |
622 |
619 /** Show previous news item */ |
623 /** Show previous news item */ |
620 void ShowLastNewsMessage() |
624 void ShowLastNewsMessage() |
621 { |
625 { |
622 if (_forced_news == INVALID_NEWS) { |
626 if (_total_news == 0) { |
|
627 return; |
|
628 } else if (_forced_news == NULL) { |
623 /* Not forced any news yet, show the current one, unless a news window is |
629 /* Not forced any news yet, show the current one, unless a news window is |
624 * open (which can only be the current one), then show the previous item */ |
630 * open (which can only be the current one), then show the previous item */ |
625 const Window *w = FindWindowById(WC_NEWS_WINDOW, 0); |
631 const Window *w = FindWindowById(WC_NEWS_WINDOW, 0); |
626 ShowNewsMessage((w == NULL || (_current_news == _oldest_news)) ? _current_news : DecreaseIndex(_current_news)); |
632 ShowNewsMessage((w == NULL || (_current_news == _oldest_news)) ? _current_news : _current_news->prev); |
627 } else if (_forced_news == _oldest_news) { |
633 } else if (_forced_news == _oldest_news) { |
628 /* We have reached the oldest news, start anew with the latest */ |
634 /* We have reached the oldest news, start anew with the latest */ |
629 ShowNewsMessage(_latest_news); |
635 ShowNewsMessage(_latest_news); |
630 } else { |
636 } else { |
631 /* 'Scrolling' through news history show each one in turn */ |
637 /* 'Scrolling' through news history show each one in turn */ |
632 ShowNewsMessage(DecreaseIndex(_forced_news)); |
638 ShowNewsMessage(_forced_news->prev); |
633 } |
639 } |
634 } |
640 } |
635 |
641 |
636 |
|
637 /* return news by number, with 0 being the most |
|
638 * recent news. Returns INVALID_NEWS if end of queue reached. */ |
|
639 static NewsID getNews(NewsID i) |
|
640 { |
|
641 if (i >= _total_news) return INVALID_NEWS; |
|
642 |
|
643 if (_latest_news < i) { |
|
644 i = _latest_news + _max_news_items - i; |
|
645 } else { |
|
646 i = _latest_news - i; |
|
647 } |
|
648 |
|
649 i %= _max_news_items; |
|
650 return i; |
|
651 } |
|
652 |
642 |
653 /** |
643 /** |
654 * Draw an unformatted news message truncated to a maximum length. If |
644 * Draw an unformatted news message truncated to a maximum length. If |
655 * length exceeds maximum length it will be postfixed by '...' |
645 * length exceeds maximum length it will be postfixed by '...' |
656 * @param x,y position of the string |
646 * @param x,y position of the string |
713 |
703 |
714 SetVScrollCount(this, _total_news); |
704 SetVScrollCount(this, _total_news); |
715 this->DrawWidgets(); |
705 this->DrawWidgets(); |
716 |
706 |
717 if (_total_news == 0) return; |
707 if (_total_news == 0) return; |
718 NewsID show = min(_total_news, this->vscroll.cap); |
708 |
719 |
709 NewsItem *ni = _latest_news; |
720 for (NewsID p = this->vscroll.pos; p < this->vscroll.pos + show; p++) { |
710 for (int n = this->vscroll.pos; n > 0; n--) { |
721 /* get news in correct order */ |
711 ni = ni->prev; |
722 const NewsItem *ni = &_news_items[getNews(p)]; |
712 if (ni == NULL) return; |
723 |
713 } |
|
714 |
|
715 for (int n = this->vscroll.cap; n > 0; n--) { |
724 SetDParam(0, ni->date); |
716 SetDParam(0, ni->date); |
725 DrawString(4, y, STR_SHORT_DATE, TC_WHITE); |
717 DrawString(4, y, STR_SHORT_DATE, TC_WHITE); |
726 |
718 |
727 DrawNewsString(82, y, TC_WHITE, ni, this->width - 95); |
719 DrawNewsString(82, y, TC_WHITE, ni, this->width - 95); |
728 y += 12; |
720 y += 12; |
|
721 |
|
722 ni = ni->prev; |
|
723 if (ni == NULL) return; |
729 } |
724 } |
730 } |
725 } |
731 |
726 |
732 virtual void OnClick(Point pt, int widget) |
727 virtual void OnClick(Point pt, int widget) |
733 { |
728 { |
734 if (widget == 3) { |
729 if (widget == 3) { |
735 int y = (pt.y - 19) / 12; |
730 NewsItem *ni = _latest_news; |
736 NewsID p = getNews(y + this->vscroll.pos); |
731 if (ni == NULL) return; |
737 |
732 |
738 if (p != INVALID_NEWS) ShowNewsMessage(p); |
733 for (int n = (pt.y - 19) / 12 + this->vscroll.pos; n > 0; n--) { |
|
734 ni = ni->prev; |
|
735 if (ni == NULL) return; |
|
736 } |
|
737 |
|
738 ShowNewsMessage(ni); |
739 } |
739 } |
740 } |
740 } |
741 |
741 |
742 virtual void OnResize(Point new_size, Point delta) |
742 virtual void OnResize(Point new_size, Point delta) |
743 { |
743 { |
974 void ShowMessageOptions() |
974 void ShowMessageOptions() |
975 { |
975 { |
976 DeleteWindowById(WC_GAME_OPTIONS, 0); |
976 DeleteWindowById(WC_GAME_OPTIONS, 0); |
977 new MessageOptionsWindow(&_message_options_desc); |
977 new MessageOptionsWindow(&_message_options_desc); |
978 } |
978 } |
979 |
|
980 |
|
981 void DeleteVehicleNews(VehicleID vid, StringID news) |
|
982 { |
|
983 for (NewsID n = _oldest_news; _latest_news != INVALID_NEWS; n = IncreaseIndex(n)) { |
|
984 const NewsItem *ni = &_news_items[n]; |
|
985 |
|
986 if (ni->flags & NF_VEHICLE && |
|
987 ni->data_a == vid && |
|
988 (news == INVALID_STRING_ID || ni->string_id == news)) { |
|
989 /* If we delete a forced news and it is just before the current news |
|
990 * then we need to advance to the next news (if any) */ |
|
991 if (_forced_news == n) MoveToNextItem(); |
|
992 if (_forced_news == INVALID_NEWS && _current_news == n) MoveToNextItem(); |
|
993 _total_news--; |
|
994 |
|
995 /* If this is the last news item, invalidate _latest_news */ |
|
996 if (_total_news == 0) { |
|
997 assert(_latest_news == _oldest_news); |
|
998 _latest_news = INVALID_NEWS; |
|
999 _current_news = INVALID_NEWS; |
|
1000 } |
|
1001 |
|
1002 /* Since we only imitate a FIFO removing an arbitrary element does need |
|
1003 * some magic. Remove the item by shifting head towards the tail. eg |
|
1004 * oldest remove last |
|
1005 * | | | |
|
1006 * [------O--------n-----L--] |
|
1007 * will become (change dramatized to make clear) |
|
1008 * [---------O-----------L--] |
|
1009 * We also need an update of the current, forced and visible (open window) |
|
1010 * news's as this shifting could change the items they were pointing to */ |
|
1011 if (_total_news != 0) { |
|
1012 NewsWindow *w = dynamic_cast<NewsWindow*>(FindWindowById(WC_NEWS_WINDOW, 0)); |
|
1013 NewsID visible_news = (w != NULL) ? (NewsID)(w->ni - _news_items) : INVALID_NEWS; |
|
1014 |
|
1015 for (NewsID i = n;; i = DecreaseIndex(i)) { |
|
1016 _news_items[i] = _news_items[DecreaseIndex(i)]; |
|
1017 |
|
1018 if (i != _latest_news) { |
|
1019 if (i == _current_news) _current_news = IncreaseIndex(_current_news); |
|
1020 if (i == _forced_news) _forced_news = IncreaseIndex(_forced_news); |
|
1021 if (i == visible_news) w->ni = &_news_items[IncreaseIndex(visible_news)]; |
|
1022 } |
|
1023 |
|
1024 if (i == _oldest_news) break; |
|
1025 } |
|
1026 _oldest_news = IncreaseIndex(_oldest_news); |
|
1027 } |
|
1028 |
|
1029 /*DEBUG(misc, 0, "-cur %3d, old %2d, lat %3d, for %3d, tot %2d", |
|
1030 _current_news, _oldest_news, _latest_news, _forced_news, _total_news);*/ |
|
1031 |
|
1032 Window *w = FindWindowById(WC_MESSAGE_HISTORY, 0); |
|
1033 if (w != NULL) { |
|
1034 w->SetDirty(); |
|
1035 w->vscroll.count = _total_news; |
|
1036 } |
|
1037 } |
|
1038 |
|
1039 if (n == _latest_news) break; |
|
1040 } |
|
1041 } |
|