File: techtools.c

package info (click to toggle)
freeciv 2.1.5-2
  • links: PTS, VCS
  • area: main
  • in suites: lenny
  • size: 95,924 kB
  • ctags: 20,829
  • sloc: ansic: 231,256; sh: 4,872; makefile: 2,867; python: 1,259; yacc: 318
file content (813 lines) | stat: -rw-r--r-- 27,460 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
/********************************************************************** 
 Freeciv - Copyright (C) 2005 The Freeciv Team
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
***********************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <assert.h>

#include "fcintl.h"
#include "log.h"
#include "mem.h"
#include "rand.h"
#include "shared.h"
#include "support.h"

#include "game.h"
#include "government.h"
#include "movement.h"
#include "player.h"
#include "tech.h"
#include "unit.h"

#include "script.h"

#include "citytools.h"
#include "cityturn.h"
#include "gamehand.h"
#include "maphand.h"
#include "plrhand.h"
#include "techtools.h"
#include "unittools.h"

static Tech_type_id pick_random_tech(struct player* plr);

/**************************************************************************
...
**************************************************************************/
void do_dipl_cost(struct player *pplayer, Tech_type_id tech)
{
  struct player_research * research = get_player_research(pplayer);

  research->bulbs_researched
    -= (base_total_bulbs_required(pplayer, tech) * game.info.diplcost) / 100;
  research->changed_from = -1;
}

/**************************************************************************
...
**************************************************************************/
void do_free_cost(struct player *pplayer, Tech_type_id tech)
{
  struct player_research * research = get_player_research(pplayer);

  research->bulbs_researched
    -= (base_total_bulbs_required(pplayer, tech) * game.info.freecost) / 100;
  research->changed_from = -1;
}

/**************************************************************************
...
**************************************************************************/
void do_conquer_cost(struct player *pplayer, Tech_type_id tech)
{
  struct player_research * research = get_player_research(pplayer);  

  research->bulbs_researched
    -= (base_total_bulbs_required(pplayer, tech) * game.info.conquercost) / 100;
  research->changed_from = -1;
}

/****************************************************************************
  Called to find and choose (pick) a research target on the way to the
  player's goal.  Return a tech iff the tech is set.
****************************************************************************/
Tech_type_id choose_goal_tech(struct player *plr)
{
  Tech_type_id sub_goal;
  struct player_research *research = get_player_research(plr);

  sub_goal = get_next_tech(plr, research->tech_goal);

  if (sub_goal != A_UNSET) {
    choose_tech(plr, sub_goal);
  }
  return sub_goal;
}

/****************************************************************************
  Player has researched a new technology
****************************************************************************/
static void tech_researched(struct player *plr)
{
  struct player_research *research = get_player_research(plr);
  /* plr will be notified when new tech is chosen */
  Tech_type_id tech_id = research->researching;
  struct advance *tech = &advances[tech_id];

  if (!is_future_tech(research->researching)) {
    notify_embassies(plr, NULL, NULL, E_TECH_GAIN,
		     _("The %s have researched %s."), 
		     nation_plural_for_player(plr),
		     advance_name_researching(plr));

  } else {
    notify_embassies(plr, NULL, NULL, E_TECH_GAIN,
		     _("The %s have researched Future Tech. %d."), 
		     nation_plural_for_player(plr),
		     research->future_tech);
  
  }
  script_signal_emit("tech_researched", 3,
		     API_TYPE_TECH_TYPE, tech, API_TYPE_PLAYER, plr,
		     API_TYPE_STRING, "researched");

  /* Deduct tech cost */
  research->bulbs_researched = 
      MAX(research->bulbs_researched - total_bulbs_required(plr), 0);

  /* do all the updates needed after finding new tech */
  found_new_tech(plr, research->researching, TRUE, TRUE);
}

/****************************************************************************
  Give technologies to players with EFT_TECH_PARASITE (traditionally from
  the Great Library).
****************************************************************************/
void do_tech_parasite_effect(struct player *pplayer)
{
  int mod;
  struct effect_list *plist = effect_list_new();

  /* Note that two EFT_TECH_PARASITE effects will combine into a single,
   * much worse effect. */
  if ((mod = get_player_bonus_effects(plist, pplayer,
				      EFT_TECH_PARASITE)) > 0) {
    char buf[512];

    buf[0] = '\0';
    effect_list_iterate(plist, peffect) {
      if (buf[0] != '\0') {
	sz_strlcat(buf, ", ");
      }
      get_effect_req_text(peffect, buf, sizeof(buf));
    } effect_list_iterate_end;

    tech_type_iterate(i) {
      if (get_invention(pplayer, i) != TECH_KNOWN
	  && tech_is_available(pplayer, i)) {
	int num_players = 0;

	players_iterate(aplayer) {
	  if (get_invention(aplayer, i) == TECH_KNOWN) {
	    num_players++;
	  }
	} players_iterate_end;
	if (num_players >= mod) {
	  notify_player(pplayer, NULL, E_TECH_GAIN,
			   _("%s acquired from %s!"),
			   advance_name_for_player(pplayer, i),
			   buf);
	  script_signal_emit("tech_researched", 3,
			     API_TYPE_TECH_TYPE, &advances[i],
			     API_TYPE_PLAYER, pplayer,
			     API_TYPE_STRING, "stolen");
	  notify_embassies(pplayer, NULL, NULL, E_TECH_GAIN,
			   _("The %s have acquired %s from %s."),
			   nation_plural_for_player(pplayer),
			   advance_name_for_player(pplayer, i),
			   buf);

	  do_free_cost(pplayer, i);
	  found_new_tech(pplayer, i, FALSE, TRUE);
	  break;
	}
      }
    } tech_type_iterate_end;
  }
  effect_list_unlink_all(plist);
  effect_list_free(plist);
}

/****************************************************************************
  Update all player specific stuff after the tech_found was discovered.
  could_switch_to_government holds information about which 
  government the player could switch to before the tech was reached
****************************************************************************/
static void update_player_after_tech_researched(struct player* plr,
                                         Tech_type_id tech_found,
					 bool was_discovery,
					 bool* could_switch_to_government)
{
  update_research(plr);

  remove_obsolete_buildings(plr);
  
  /* Give free rails in every city */
  if (advance_has_flag(tech_found, TF_RAILROAD)) {
    upgrade_city_rails(plr, was_discovery);  
  }
  
  /* Enhance vision of units if a player-ranged effect has changed.  Note
   * that world-ranged effects will not be updated immediately. */
  unit_list_refresh_vision(plr->units);

  /* Notify a player about new governments available */
  government_iterate(gov) {
    if (!could_switch_to_government[gov->index]
	&& can_change_to_government(plr, gov)) {
      notify_player(plr, NULL, E_NEW_GOVERNMENT,
		       _("Discovery of %s makes the government form %s"
			 " available. You may want to start a revolution."),
		       advance_name_for_player(plr, tech_found),
		       government_name_translation(gov));
    }
  } government_iterate_end;

  /*
   * Inform player about his new tech.
   */
  send_player_info(plr, plr);
}

/****************************************************************************
  Fill the array which contains information about which government
  the player can switch to.
****************************************************************************/
static void fill_can_switch_to_government_array(struct player* plr, bool* can_switch)
{
  government_iterate(gov) {
    /* We do it this way so all requirements are checked, including
     * statue-of-liberty effects. */
    can_switch[gov->index] = can_change_to_government(plr, gov);
  } government_iterate_end;
} 

/****************************************************************************
  Fill the array which contains information about value of the
  EFT_HAVE_EMBASSIES effect for each player
****************************************************************************/
static void fill_have_embassies_array(int* have_embassies)
{
  players_iterate(aplr) {
    have_embassies[aplr->player_no]
      = get_player_bonus(aplr, EFT_HAVE_EMBASSIES);
  } players_iterate_end;
}

/****************************************************************************
  Player has a new technology (from somewhere). was_discovery is passed 
  on to upgrade_city_rails. Logging & notification is not done here as 
  it depends on how the tech came. If next_tech is other than A_NONE, this 
  is the next tech to research.
****************************************************************************/
void found_new_tech(struct player *plr, Tech_type_id tech_found,
		    bool was_discovery, bool saving_bulbs)
{
  bool bonus_tech_hack = FALSE;
  bool was_first = FALSE;
  int had_embassies[MAX_NUM_PLAYERS + MAX_NUM_BARBARIANS];
  struct city *pcity;
  bool can_switch[MAX_NUM_PLAYERS + MAX_NUM_BARBARIANS]
                 [game.control.government_count];
  struct player_research *research = get_player_research(plr);

  /* HACK: A_FUTURE doesn't "exist" and is thus not "available".  This may
   * or may not be the correct thing to do.  For these sanity checks we
   * just special-case it. */
  assert((tech_exists(tech_found)
	  && get_invention(plr, tech_found) != TECH_KNOWN)
	 || tech_found == A_FUTURE);
  /* It is possible that !tech_is_available() simply because we are only
   * setting up initial techs and root_req is one of them.
   * assert(tech_is_available(plr, tech_found) || tech_found == A_FUTURE); */

  /* got_tech allows us to change research without applying techpenalty
   * (without loosing bulbs) */
  if (tech_found == research->researching) {
    research->got_tech = TRUE;
  }
  research->changed_from = -1;
  research->techs_researched++;
  was_first = (!game.info.global_advances[tech_found]);

  if (was_first) {
    /* Alert the owners of any wonders that have been made obsolete */
    impr_type_iterate(id) {
      if (is_great_wonder(id) && great_wonder_was_built(id)
	  && improvement_by_number(id)->obsolete_by == tech_found
	  && (pcity = find_city_from_great_wonder(id))) {
	notify_player(city_owner(pcity), NULL, E_WONDER_OBSOLETE,
	                 _("Discovery of %s OBSOLETES %s in %s!"), 
	                 advance_name_for_player(city_owner(pcity), tech_found),
			 improvement_name_translation(id),
	                 city_name(pcity));
      }
    } impr_type_iterate_end;
  }

  if (advance_has_flag(tech_found, TF_BONUS_TECH) && was_first) {
    bonus_tech_hack = TRUE;
  }
  
  /* Count EFT_HAVE_EMBASSIES effect for each player.
   * We will check what has changed later */
  fill_have_embassies_array(had_embassies);

  /* Memorize some values before the tech is marked as researched.
   * They will be used to notify a player about a change */
  players_iterate(aplayer) {
    if (!players_on_same_team(aplayer, plr)) {
      continue;
    }
    fill_can_switch_to_government_array(aplayer,
                                        can_switch[aplayer->player_no]);
  } players_iterate_end;


  /* Mark the tech as known in the research struct and update
   * global_advances array */
  set_invention(plr, tech_found, TECH_KNOWN);

  /* Make proper changes for all players sharing the research */  
  players_iterate(aplayer) {
    if (!players_on_same_team(aplayer, plr)) {
      continue;
    }
    update_player_after_tech_researched(aplayer, tech_found, was_discovery,
                                        can_switch[aplayer->player_no]);
  } players_iterate_end;

  if (tech_found == research->tech_goal) {
    research->tech_goal = A_UNSET;
  }

  if (tech_found == research->researching) {
    Tech_type_id next_tech = choose_goal_tech(plr);
    /* try to pick new tech to research */

    if (next_tech != A_UNSET) {
      notify_team(plr, NULL, E_TECH_LEARNED,
		       _("Learned %s. "
			 "Our scientists focus on %s; goal is %s."),
		       advance_name_for_player(plr, tech_found),
		       advance_name_researching(plr),
		       advance_name_for_player(plr, research->tech_goal));
    } else {
      if (plr->ai.control) {
        choose_random_tech(plr);
      } else if (is_future_tech(tech_found)) {
        /* Continue researching future tech. */
        research->researching = A_FUTURE;
      } else {
        research->researching = A_UNSET;
      }
      if (research->researching != A_UNSET 
          && (!is_future_tech(research->researching)
	      || !is_future_tech(tech_found))) {
	notify_team(plr, NULL, E_TECH_LEARNED,
			 _("Learned %s.  Scientists "
			   "choose to research %s."),
			 advance_name_for_player(plr, tech_found),
			 advance_name_researching(plr));
      } else if (research->researching != A_UNSET) {
	char buffer1[300], buffer2[300];

	my_snprintf(buffer1, sizeof(buffer1), _("Learned %s. "),
		    advance_name_researching(plr));
	research->future_tech++;
	my_snprintf(buffer2, sizeof(buffer2), _("Researching %s."),
		    advance_name_researching(plr));
	notify_team(plr, NULL, E_TECH_LEARNED, "%s%s", buffer1,
			 buffer2);
      } else {
	notify_team(plr, NULL, E_TECH_LEARNED,
			 _("Learned %s.  Scientists "
			   "do not know what to research next."),
			 advance_name_for_player(plr, tech_found));
      }
    }
  }

  if (!saving_bulbs && research->bulbs_researched > 0) {
    research->bulbs_researched = 0;
  }

  if (bonus_tech_hack) {
    if (advances[tech_found].bonus_message) {
      notify_team(plr, NULL, E_TECH_GAIN,
		     "%s", _(advances[tech_found].bonus_message));
    } else {
      notify_team(plr, NULL, E_TECH_GAIN,
                     _("Great scientists from all the "
		       "world join your civilization: you get "
		       "an immediate advance."));
    }
    
    if (research->researching == A_UNSET) {
      choose_random_tech(plr);
    }
    tech_researched(plr);
  }

  /*
   * Inform all players about new global advances to give them a
   * chance to obsolete buildings.
   */
  send_game_info(NULL);

  /*
   * Update all cities in case the tech changed some effects. This is
   * inefficient; it could be optimized if it's found to be a problem.  But
   * techs aren't researched that often.
   */
  cities_iterate(pcity) {
    city_refresh(pcity);
    send_city_info(city_owner(pcity), pcity);
  } cities_iterate_end;
  
  /*
   * Send all player an updated info of the owner of the Marco Polo
   * Wonder if this wonder has become obsolete.
   */
  players_iterate(owner) {
    if (had_embassies[owner->player_no]  > 0
	&& get_player_bonus(owner, EFT_HAVE_EMBASSIES) == 0) {
      players_iterate(other_player) {
	send_player_info(owner, other_player);
      } players_iterate_end;
    }
  } players_iterate_end;
}

/****************************************************************************
  Adds the given number of  bulbs into the player's tech and 
  (if necessary) completes the research.
  The caller is responsible for sending updated player information.
  This is called from each city every turn and from caravan revenue
****************************************************************************/
void update_tech(struct player *plr, int bulbs)
{
  int excessive_bulbs;
  struct player_research *research = get_player_research(plr);

  /* count our research contribution this turn */
  plr->bulbs_last_turn += bulbs;

  research->bulbs_researched += bulbs;

  if (research->researching != A_UNSET) {  
    excessive_bulbs =
      (research->bulbs_researched - total_bulbs_required(plr));

    if (excessive_bulbs >= 0) {
      tech_researched(plr);
      if (research->researching != A_UNSET) {
        update_tech(plr, 0);
      }
    }
  }
}

/****************************************************************************
  Returns random researchable tech or A_FUTURE.
  No side effects
****************************************************************************/
static Tech_type_id pick_random_tech(struct player* plr) 
{
  int chosen, researchable = 0;

  tech_type_iterate(i) {
    if (get_invention(plr, i) == TECH_REACHABLE) {
      researchable++;
    }
  } tech_type_iterate_end;
  if (researchable == 0) {
    return A_FUTURE;
  }
  chosen = myrand(researchable) + 1;
  
  tech_type_iterate(i) {
    if (get_invention(plr, i) == TECH_REACHABLE) {
      chosen--;
      if (chosen == 0) {
        return i;
      }
    }
  } tech_type_iterate_end;
  assert(0);
  return A_FUTURE;
}

/****************************************************************************
  Finds and chooses (sets) a random research target from among all those
  available until plr->research->researching != A_UNSET.
  Player may research more than one tech in this function.
  Possible reasons:
  - techpenalty < 100
  - research.got_tech = TRUE and enough bulbs was saved
  - research.researching = A_UNSET and enough bulbs was saved
****************************************************************************/
void choose_random_tech(struct player *plr)
{
  struct player_research* research = get_player_research(plr);
  do {
    choose_tech(plr, pick_random_tech(plr));
  } while (research->researching == A_UNSET);
}

/****************************************************************************
  Called when the player chooses the tech he wants to research (or when
  the server chooses it for him automatically).

  This takes care of all side effects so the player's research target
  probably shouldn't be changed outside of this function (doing so has
  been the cause of several bugs).
****************************************************************************/
void choose_tech(struct player *plr, Tech_type_id tech)
{
  struct player_research *research = get_player_research(plr);

  if (research->researching == tech) {
    return;
  }
  if (get_invention(plr, tech) != TECH_REACHABLE) {
    /* can't research this */
    return;
  }
  if (!research->got_tech && research->changed_from == -1) {
    research->bulbs_researched_before = research->bulbs_researched;
    research->changed_from = research->researching;
    /* subtract a penalty because we changed subject */
    if (research->bulbs_researched > 0) {
      research->bulbs_researched
	-= ((research->bulbs_researched * game.info.techpenalty) / 100);
      assert(research->bulbs_researched >= 0);
    }
  } else if (tech == research->changed_from) {
    research->bulbs_researched = research->bulbs_researched_before;
    research->changed_from = -1;
  }
  research->researching=tech;
  if (research->bulbs_researched > total_bulbs_required(plr)) {
    tech_researched(plr);
  }
}

/****************************************************************************
  Called when the player chooses the tech goal he wants to research (or when
  the server chooses it for him automatically).
****************************************************************************/
void choose_tech_goal(struct player *plr, Tech_type_id tech)
{
  struct player_research *research = get_player_research(plr);

  if (research && tech != research->tech_goal) {
    /* It's been suggested that if the research target is empty then
     * choose_random_tech should be called here. */
    research->tech_goal = tech;
    notify_research(plr, E_TECH_GAIN /* ? */,
		    _("Technology goal is %s."),
		    advance_name_for_player(plr, tech));
  }
}

/****************************************************************************
  Initializes tech data for the player.
****************************************************************************/
void init_tech(struct player *plr)
{
  tech_type_iterate(i) {
    set_invention(plr, i, TECH_UNKNOWN);
  } tech_type_iterate_end;
  set_invention(plr, A_NONE, TECH_KNOWN);

  get_player_research(plr)->techs_researched = 1;

  /* Mark the reachable techs */
  update_research(plr);
  if (choose_goal_tech(plr) == A_UNSET) {
    choose_random_tech(plr);
  }
}

/****************************************************************************
  Gives initial techs to the player
****************************************************************************/
void give_initial_techs(struct player* plr)
{
  struct nation_type *nation = nation_of_player(plr);
  int i;
  
  /*
   * Give game wide initial techs
   */
  for (i = 0; i < MAX_NUM_TECH_LIST; i++) {
    if (game.rgame.global_init_techs[i] == A_LAST) {
      break;
    }
    found_new_tech(plr, game.rgame.global_init_techs[i], FALSE, TRUE);
  }

  /*
   * Give nation specific initial techs
   */
  for (i = 0; i < MAX_NUM_TECH_LIST; i++) {
    if (nation->init_techs[i] == A_LAST) {
      break;
    }
    found_new_tech(plr, nation->init_techs[i], FALSE, TRUE);
  }
}

/****************************************************************************
  Gives a player random tech, which he hasn't researched yet.
  Returns the tech. This differs from give_random_free_tech - it doesn't
  apply free cost
****************************************************************************/
Tech_type_id give_random_initial_tech(struct player* pplayer)
{
  Tech_type_id tech;
  
  tech = pick_random_tech(pplayer);
  found_new_tech(pplayer, tech, FALSE, TRUE);
  return tech;
}

/****************************************************************************
  If victim has a tech which pplayer doesn't have, pplayer will get it.
  The clients will both be notified and the conquer cost
  penalty applied. Used for diplomats and city conquest.
  If preferred is A_UNSET one random tech will be choosen.
  Returns the stolen tech or A_NONE if no tech was found.
****************************************************************************/
Tech_type_id steal_a_tech(struct player *pplayer, struct player *victim,
            	        Tech_type_id preferred)
{
  Tech_type_id stolen_tech = A_NONE;
  
  if (preferred == A_UNSET) {
    int j = 0;
    tech_type_iterate(i) {
      if (get_invention(pplayer, i) != TECH_KNOWN
	  && get_invention(victim, i) == TECH_KNOWN
	  && tech_is_available(pplayer, i)) {
        j++;
      }
    } tech_type_iterate_end;
  
    if (j == 0)  {
      /* we've moved on to future tech */
      if (get_player_research(victim)->future_tech
        > get_player_research(pplayer)->future_tech) {
        found_new_tech(pplayer, A_FUTURE, FALSE, TRUE);	
        stolen_tech = A_FUTURE;
      } else {
        return A_NONE;
      }
    } else {
      /* pick random tech */
      j = myrand(j) + 1;
      stolen_tech = A_NONE; /* avoid compiler warning */
      tech_type_iterate(i) {
        if (get_invention(pplayer, i) != TECH_KNOWN
	    && get_invention(victim, i) == TECH_KNOWN
	    && tech_is_available(pplayer, i)) {
	  j--;
        }
        if (j == 0) {
	  stolen_tech = i;
	  break;
        }
      } tech_type_iterate_end;
      assert(stolen_tech != A_NONE);
    }
  } else { /* preferred != A_UNSET */
    assert((preferred == A_FUTURE
            && get_invention(victim, A_FUTURE) == TECH_REACHABLE)
	   || (tech_exists(preferred)
	       && get_invention(victim, preferred) == TECH_KNOWN));
    stolen_tech = preferred;
  }
  script_signal_emit("tech_researched", 3,
		     API_TYPE_TECH_TYPE, &advances[stolen_tech],
		     API_TYPE_PLAYER, pplayer,
		     API_TYPE_STRING, "stolen");

  notify_player(pplayer, NULL, E_TECH_GAIN,
		   _("You steal %s from the %s."),
		   advance_name_for_player(pplayer, stolen_tech),
		   nation_plural_for_player(victim));

  notify_player(victim, NULL, E_ENEMY_DIPLOMAT_THEFT,
                   _("The %s stole %s from you!"),
		   nation_plural_for_player(pplayer),
		   advance_name_for_player(pplayer, stolen_tech));

  notify_embassies(pplayer, victim, NULL, E_TECH_GAIN,
		   _("The %s have stolen %s from the %s."),
		   nation_plural_for_player(pplayer),
		   advance_name_for_player(pplayer, stolen_tech),
		   nation_plural_for_player(victim));

  do_conquer_cost(pplayer, stolen_tech);
  found_new_tech(pplayer, stolen_tech, FALSE, TRUE);
  return stolen_tech;
}

/****************************************************************************
  Handle incoming player_research packet. Need to check correctness
  Set the player to be researching the given tech.

  If there are enough accumulated research points, the tech may be
  acquired immediately.
****************************************************************************/
void handle_player_research(struct player *pplayer, int tech)
{
  if (tech != A_FUTURE && !tech_exists(tech)) {
    return;
  }
  
  if (tech != A_FUTURE && get_invention(pplayer, tech) != TECH_REACHABLE) {
    return;
  }
  
  choose_tech(pplayer, tech);
  send_player_info(pplayer, pplayer);

  /* Notify Team members. 
   * They share same research struct.
   */
  players_iterate(aplayer) {
    if (pplayer != aplayer
	&& pplayer->diplstates[aplayer->player_no].type == DS_TEAM
	&& aplayer->is_alive) {
      send_player_info(aplayer, aplayer);
    }
  } players_iterate_end;
}

/****************************************************************************
  Handle incoming player_tech_goal packet
  Called from the network or AI code to set the player's tech goal.
****************************************************************************/
void handle_player_tech_goal(struct player *pplayer, int tech_goal)
{
  if (tech_goal != A_FUTURE && !tech_exists(tech_goal)) {
    return;
  }
  
  if (tech_goal != A_FUTURE && !tech_is_available(pplayer, tech_goal)) {
    return;
  }
  
  if (tech_goal == A_NONE) {
    /* A_NONE "exists" but is not allowed as a tech goal.  A_UNSET should
     * be used instead.  However the above checks may prevent the client from
     * ever setting the goal to A_UNSET, meaning once a goal is set it
     * can't be removed. */
    return;
  }

  choose_tech_goal(pplayer, tech_goal);
  send_player_info(pplayer, pplayer);

  /* Notify Team members */
  players_iterate(aplayer) {
    if (pplayer != aplayer
        && pplayer->diplstates[aplayer->player_no].type == DS_TEAM
        && aplayer->is_alive
        && get_player_research(aplayer)->tech_goal != tech_goal) {
      handle_player_tech_goal(aplayer, tech_goal);
    }
  } players_iterate_end;
}

/****************************************************************************
  Gives a player random tech, which he hasn't researched yet. Applies freecost
  Returns the tech.
****************************************************************************/
Tech_type_id give_random_free_tech(struct player* pplayer)
{
  Tech_type_id tech;
  
  tech = pick_random_tech(pplayer);
  do_free_cost(pplayer, tech);
  found_new_tech(pplayer, tech, FALSE, TRUE);
  return tech;
}

/****************************************************************************
  Gives a player immediate free tech. Applies freecost
****************************************************************************/
Tech_type_id give_immediate_free_tech(struct player* pplayer)
{
  Tech_type_id tech;
  if (get_player_research(pplayer)->researching == A_UNSET) {
    return give_random_free_tech(pplayer);
  }
  tech = get_player_research(pplayer)->researching;
  do_free_cost(pplayer, tech);
  found_new_tech(pplayer, tech, FALSE, TRUE);
  return tech;
}