]> git.pond.sub.org Git - empserver/blob - src/lib/commands/trad.c
Update copyright notice.
[empserver] / src / lib / commands / trad.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2005, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                           Ken Stevens, Steve McClure
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 2 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  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  *  ---
21  *
22  *  See the "LEGAL", "LICENSE", "CREDITS" and "README" files for all the
23  *  related information and legal notices. It is expected that any future
24  *  projects/authors will amend these files as needed.
25  *
26  *  ---
27  *
28  *  trad.c: Buy units/ships/planes/nukes from other nations.
29  * 
30  *  Known contributors to this file:
31  *     Dave Pare, 1986
32  *     Pat Loney, 1992
33  *     Steve McClure, 1996-2000
34  */
35
36 #include <ctype.h>
37 #include "misc.h"
38 #include "sect.h"
39 #include "nat.h"
40 #include "news.h"
41 #include "ship.h"
42 #include "nuke.h"
43 #include "land.h"
44 #include "plane.h"
45 #include "trade.h"
46 #include "xy.h"
47 #include "nsc.h"
48 #include "file.h"
49 #include "player.h"
50 #include "commodity.h"
51 #include "loan.h"
52 #include "commands.h"
53 #include "optlist.h"
54
55 /*
56  * format: trade
57  */
58 int
59 trad(void)
60 {
61     struct sctstr sect;
62     struct natstr *natp;
63     struct comstr comt;
64     int lotno;
65     float price;
66     coord sx, sy;
67     int n;
68     char *p;
69     struct nstr_item ni;
70     struct trdstr trade;
71     struct trdstr tmpt;
72     union trdgenstr tg;
73     int plflags;
74     double canspend;
75     time_t now;
76     int bid;
77     double tleft;
78     double tally;
79     int q;
80     s_char buf[1024];
81
82
83     if (!opt_MARKET) {
84         pr("The market is disabled.\n");
85         return RET_FAIL;
86     }
87     /* First, we execute all trades, so that we can only buy what is available. */
88     check_market();
89     check_trade();
90
91     pr("\n     Empire Trade Report\n  ");
92     prdate();
93     n = 0;
94     pr(" lot high bid  by time left owner  description\n");
95     pr(" --- --------  -- --------- -----  -------------------------\n");
96
97     snxtitem_all(&ni, EF_TRADE);
98     while (nxtitem(&ni, (char *)&trade)) {
99         if (trade.trd_owner == 0)
100             continue;
101         if (!trade_getitem(&trade, &tg)) {
102             continue;
103         };
104         /* fix up database if things get weird */
105         /*if (trade.trd_owner != tg.gen.trg_own) {
106            trade.trd_unitid = -1;
107            (void) puttrade(ni.cur, &trade);
108            continue;
109            } */
110         pr(" %3d ", ni.cur);
111         (void)time(&now);
112         tleft =
113             TRADE_DELAY / 3600.0 - (now - trade.trd_markettime) / 3600.0;
114         if (tleft < 0.0)
115             tleft = 0.0;
116         pr("$%7ld  %2d %5.2f hrs ", trade.trd_price,
117            trade.trd_maxbidder, tleft);
118         (void)trade_desc(&trade, &tg);  /* XXX */
119         pr("\n");
120         if (trade.trd_owner == player->cnum && !player->god)
121             pr(" (your own lot)\n");
122         n++;
123     }
124     if (n == 0) {
125         pr("Nothing to buy at the moment...\n");
126         return RET_OK;
127     }
128     if ((p = getstring("Which lot to buy: ", buf)) == 0 || *p == 0)
129         return RET_OK;
130     if (isdigit(*p) == 0)
131         return RET_OK;
132     lotno = atoi(p);
133     if (lotno < 0 || lotno >= ni.cur) {
134         pr("Bad lot number\n");
135         return RET_OK;
136     }
137     if (!gettrade(lotno, &trade)) {
138         pr("No such lot number\n");
139         return RET_OK;
140     }
141     if (trade.trd_unitid < 0) {
142         pr("Invalid lot number.\n");
143         return RET_OK;
144     }
145     if (!trade_getitem(&trade, &tg)) {
146         pr("Can't find trade #%d!\n", trade.trd_unitid);
147         trade.trd_unitid = -1;
148         if (!puttrade(lotno, &trade)) {
149             pr("Couldn't save after getitem failed; get help!\n");
150             return RET_SYS;
151         }
152         return RET_OK;
153     }
154     switch (trade.trd_type) {
155     case EF_NUKE:
156         /*
157            if (!getsect(tg.gen.trg_x, tg.gen.trg_y, &sect)) {
158            return RET_FAIL;
159            }
160            trade.trd_owner = sect.sct_own;
161            break;
162          */
163     case EF_PLANE:
164     case EF_SHIP:
165     case EF_LAND:
166         break;
167     default:
168         pr("Bad unit type on lot number %d\n", lotno);
169         return RET_FAIL;
170     }
171     if (trade.trd_owner == player->cnum) {
172         pr("You can't buy from yourself!\n");
173         return RET_OK;
174     }
175     price = trade.trd_price;
176     natp = getnatp(player->cnum);
177     if (natp->nat_money < price) {
178         pr("You don't have %.2f to spend!\n", price);
179         return RET_OK;
180     }
181     tally = 0.0;
182     for (q = 0; gettrade(q, &tmpt); q++) {
183         if (tmpt.trd_maxbidder == player->cnum &&
184             tmpt.trd_unitid >= 0 && tmpt.trd_owner != player->cnum) {
185             tally += tmpt.trd_price * tradetax;
186         }
187     }
188     for (q = 0; getcomm(q, &comt); q++) {
189         if (comt.com_maxbidder == player->cnum &&
190             comt.com_owner != 0 && comt.com_owner != player->cnum) {
191             tally += (comt.com_price * comt.com_amount) * buytax;
192         }
193     }
194     canspend = natp->nat_money - tally;
195     /*
196      * Find the destination sector for the plane before the trade
197      * is actually made. Must be owned (except for satellites) and
198      * must be a 60% airfield (except for VTOL planes).
199      */
200     if (((trade.trd_type == EF_PLANE) || (trade.trd_type == EF_NUKE))
201         && ((trade.trd_type == EF_NUKE) ||
202             !(tg.pln.pln_flags & PLN_LAUNCHED))) {
203         plflags = plchr[(int)tg.pln.pln_type].pl_flags;
204         while (1) {
205             p = getstring("Destination sector: ", buf);
206             if (!trade_check_ok(&trade, &tg))
207                 return RET_FAIL;
208             if (p == 0) {
209                 return RET_FAIL;
210             }
211             if (!sarg_xy(p, &sx, &sy) || !getsect(sx, sy, &sect)) {
212                 pr("Bad sector designation; try again!\n");
213                 continue;
214             }
215             if (!player->owner && !(plflags & P_O)) {
216                 pr("You don't own that sector; try again!\n");
217                 continue;
218             }
219             if (!(plflags & (P_V | P_O))) {
220                 if (!player->god && (sect.sct_type != SCT_AIRPT)) {
221                     pr("Destination sector is not an airfield!\n");
222                     continue;
223                 }
224                 if (!player->god && (sect.sct_effic < 60)) {
225                     pr("That airport still under construction!\n");
226                     continue;
227                 }
228             }
229             break;
230         }
231     }
232     if (trade.trd_type == EF_LAND) {
233         while (1) {
234             p = getstring("Destination sector: ", buf);
235             if (!trade_check_ok(&trade, &tg))
236                 return RET_FAIL;
237             if (p == 0) {
238                 return RET_FAIL;
239             }
240             if (!sarg_xy(p, &sx, &sy) || !getsect(sx, sy, &sect)) {
241                 pr("Bad sector designation; try again!\n");
242                 continue;
243             }
244             if (!player->owner) {
245                 pr("You don't own that sector; try again!\n");
246                 continue;
247             }
248             if (!player->god && (sect.sct_type != SCT_HEADQ)) {
249                 pr("Destination sector is not a headquarters!\n");
250                 continue;
251             }
252             if (!player->god && (sect.sct_effic < 60)) {
253                 pr("That headquarters still under construction!\n");
254                 continue;
255             }
256             break;
257         }
258     }
259
260     pr("WARNING!  This market issues credit.  If you make more\n");
261     pr("  bids than your treasury can cover at the time of sale,\n");
262     pr("  you can potentially go into financial ruin, and see no\n");
263     pr("  gains.  You have been warned.\n\n");
264
265     if ((p = getstring("How much do you bid: ", buf)) == 0 || *p == 0)
266         return RET_OK;
267     if (!trade_check_ok(&trade, &tg))
268         return RET_FAIL;
269     bid = atoi(p);
270     if (bid < price)
271         bid = price;
272     if (bid > canspend) {
273         pr("You don't have %.2f to spend!\n", price);
274         return RET_OK;
275     }
276     if (bid > trade.trd_price) {
277         /* Add five minutes to the time if less than 5 minutes left. */
278         time(&now);
279         if (((TRADE_DELAY - (now - trade.trd_markettime)) < 300) &&
280             trade.trd_maxbidder != player->cnum)
281             trade.trd_markettime += 300;
282         trade.trd_price = bid;
283         trade.trd_maxbidder = player->cnum;
284         trade.trd_x = sx;
285         trade.trd_y = sy;
286         pr("Your bid on lot #%d is being considered.\n", lotno);
287         if (!puttrade(lotno, &trade))
288             pr("Problems with the trade file.  Get help\n");
289     } else
290         pr("Your bid wasn't high enough (you need to bid more than someone else.)\n");
291
292     check_trade();
293
294     return RET_OK;
295 }
296
297 int
298 check_trade(void)
299 {
300     int n;
301     int j;
302     struct nstr_item ni;
303     struct plnstr plane;
304     struct lndstr land;
305     struct natstr *natp;
306     struct trdstr trade;
307     union trdgenstr tg;
308     time_t now;
309     double subleft;
310     double monleft;
311     double tleft;
312     float price;
313     int saveid;
314     struct lonstr loan;
315     long outstanding;           /* Outstanding debt */
316     long couval;                /* Value of country's goods */
317     int foundloan;
318
319 /*    logerror("Checking the trades.\n");*/
320     for (n = 0; gettrade(n, &trade); n++) {
321         if (trade.trd_unitid < 0)
322             continue;
323         if (!trade_getitem(&trade, &tg))
324             continue;
325         if (tg.gen.trg_own == 0) {
326             trade.trd_unitid = -1;
327             puttrade(n, &trade);
328             continue;
329         }
330         if (tg.gen.trg_own != trade.trd_owner) {
331             logerror("Something weird, tg.gen.trg_own != trade.trd_owner!\n");
332             trade.trd_unitid = -1;
333             puttrade(n, &trade);
334             continue;
335         }
336
337         if (trade.trd_owner == trade.trd_maxbidder)
338             continue;
339
340         (void)time(&now);
341         tleft =
342             TRADE_DELAY / 3600.0 - (now - trade.trd_markettime) / 3600.0;
343         if (tleft < 0.0)
344             tleft = 0.0;
345         if (tleft > 0.0)
346             continue;
347
348         saveid = trade.trd_unitid;
349         trade.trd_unitid = -1;
350         if (!puttrade(n, &trade)) {
351             logerror("Couldn't save trade after purchase; get help!\n");
352             continue;
353         }
354
355         monleft = 0;
356         price = trade.trd_price;
357         natp = getnatp(trade.trd_maxbidder);
358         if (natp->nat_money <= 0)
359             monleft = price;
360         if (natp->nat_money < price && natp->nat_money > 0) {
361             monleft = price - (natp->nat_money - 1);
362             natp->nat_money = 1;
363             price = price - monleft;
364         } else if (natp->nat_money > 0) {
365             monleft = 0;
366             natp->nat_money -= price;
367         }
368
369         subleft = monleft;
370
371         if (opt_LOANS) {
372             /* Try to make a loan for the rest from the owner. */
373             if (monleft > 0 && natp->nat_money > 0) {
374                 if ((float)((float)price / (float)(price + monleft)) < 0.1) {
375                     wu(0, trade.trd_maxbidder,
376                        "You need at least 10 percent down to purchase something on credit.\n");
377                 } else {
378                     couval = get_couval(trade.trd_maxbidder);
379                     outstanding = get_outstand(trade.trd_maxbidder);
380                     couval = couval - outstanding;
381                     if (couval > monleft) {
382                         /*  Make the loan */
383                         foundloan = 0;
384                         for (j = 0; getloan(j, &loan); j++) {
385                             if (loan.l_status != LS_FREE)
386                                 continue;
387                             foundloan = 1;
388                             break;
389                         }
390                         if (!foundloan)
391                             ef_extend(EF_LOAN, 1);
392                         loan.l_status = LS_SIGNED;
393                         loan.l_loner = trade.trd_owner;
394                         loan.l_lonee = trade.trd_maxbidder;
395                         loan.l_irate = 25;
396                         loan.l_ldur = 4;
397                         loan.l_amtpaid = 0;
398                         loan.l_amtdue = monleft;
399                         time(&loan.l_lastpay);
400                         loan.l_duedate =
401                             (loan.l_ldur * SECS_PER_DAY) + loan.l_lastpay;
402                         loan.l_uid = j;
403                         if (!putloan(j, &loan))
404                             logerror("Error writing to the loan file.\n");
405                         else
406                             monleft = 0;
407                         nreport(trade.trd_maxbidder, N_FIN_TROUBLE,
408                                 trade.trd_owner, 1);
409                         wu(0, trade.trd_maxbidder,
410                            "You just took loan #%d for $%.2f to cover the cost of your purchase.\n",
411                            j, (float)loan.l_amtdue);
412                         wu(0, trade.trd_owner,
413                            "You just extended loan #%d to %s to help with the purchase cost.\n",
414                            j, cname(trade.trd_maxbidder));
415                     } else {
416                         nreport(trade.trd_maxbidder, N_CREDIT_JUNK,
417                                 trade.trd_owner, 1);
418                         wu(0, trade.trd_maxbidder,
419                            "You don't have enough credit to get a loan.\n");
420                         wu(0, trade.trd_owner,
421                            "You just turned down a loan to %s.\n",
422                            cname(trade.trd_maxbidder));
423                     }
424                 }
425             }
426         }
427         if (monleft > 0) {
428             nreport(trade.trd_maxbidder, N_WELCH_DEAL, trade.trd_owner, 1);
429             wu(0, trade.trd_owner,
430                "%s tried to buy a %s #%d from you for $%.2f\n",
431                cname(trade.trd_maxbidder), trade_nameof(&trade, &tg),
432                saveid, (float)(price * tradetax));
433             wu(0, trade.trd_owner, "   but couldn't afford it.\n");
434             wu(0, trade.trd_owner,
435                "   Your item was taken off the market.\n");
436             wu(0, trade.trd_maxbidder,
437                "You tried to buy %s #%d from %s for $%.2f\n",
438                trade_nameof(&trade, &tg), saveid, cname(trade.trd_owner),
439                (float)(price * tradetax));
440             wu(0, trade.trd_maxbidder, "but couldn't afford it.\n");
441             continue;
442         }
443
444 /* If we get this far, the sale will go through. */
445 /* Only pay tax on the part you actually get cash for.  As a break,
446    we don't tax the part you have to give a loan on. */
447
448         putnat(natp);
449         natp = getnatp(trade.trd_owner);
450         /* Make sure we subtract the extra amount */
451         natp->nat_money += (roundavg(price * tradetax) - subleft);
452         putnat(natp);
453         switch (trade.trd_type) {
454         case EF_NUKE:
455             tg.nuk.nuk_x = trade.trd_x;
456             tg.nuk.nuk_y = trade.trd_y;
457             makelost(EF_NUKE, tg.nuk.nuk_own, tg.nuk.nuk_uid, tg.nuk.nuk_x,
458                      tg.nuk.nuk_y);
459             tg.nuk.nuk_own = trade.trd_maxbidder;
460             makenotlost(EF_NUKE, tg.nuk.nuk_own, tg.nuk.nuk_uid,
461                         tg.nuk.nuk_x, tg.nuk.nuk_y);
462             break;
463         case EF_PLANE:
464             if ((tg.pln.pln_flags & PLN_LAUNCHED) == 0) {
465                 tg.pln.pln_x = trade.trd_x;
466                 tg.pln.pln_y = trade.trd_y;
467             }
468             makelost(EF_PLANE, tg.pln.pln_own, tg.pln.pln_uid,
469                      tg.pln.pln_x, tg.pln.pln_y);
470             tg.pln.pln_own = trade.trd_maxbidder;
471             makenotlost(EF_PLANE, tg.pln.pln_own, tg.pln.pln_uid,
472                         tg.pln.pln_x, tg.pln.pln_y);
473             tg.pln.pln_wing = ' ';
474             /* no cheap version of fly */
475             if (opt_MOB_ACCESS) {
476                 tg.pln.pln_mobil = -(etu_per_update / sect_mob_neg_factor);
477             } else {
478                 tg.pln.pln_mobil = 0;
479             }
480             tg.pln.pln_mission = 0;
481             tg.pln.pln_harden = 0;
482             time(&tg.pln.pln_access);
483             tg.pln.pln_ship = -1;
484             tg.pln.pln_land = -1;
485             break;
486         case EF_SHIP:
487             takeover_ship(&tg.shp, trade.trd_maxbidder, 0);
488             break;
489         case EF_LAND:
490             tg.lnd.lnd_x = trade.trd_x;
491             tg.lnd.lnd_y = trade.trd_y;
492             if (tg.lnd.lnd_ship >= 0) {
493                 struct shpstr ship;
494                 getship(tg.lnd.lnd_ship, &ship);
495                 ship.shp_nland--;
496                 putship(ship.shp_uid, &ship);
497             }
498             makelost(EF_LAND, tg.lnd.lnd_own, tg.lnd.lnd_uid, tg.lnd.lnd_x,
499                      tg.lnd.lnd_y);
500             tg.lnd.lnd_own = trade.trd_maxbidder;
501             makenotlost(EF_LAND, tg.lnd.lnd_own, tg.lnd.lnd_uid,
502                         tg.lnd.lnd_x, tg.lnd.lnd_y);
503             tg.lnd.lnd_army = ' ';
504             /* no cheap version of fly */
505             if (opt_MOB_ACCESS) {
506                 tg.lnd.lnd_mobil = -(etu_per_update / sect_mob_neg_factor);
507             } else {
508                 tg.lnd.lnd_mobil = 0;
509             }
510             tg.lnd.lnd_harden = 0;
511             time(&tg.lnd.lnd_access);
512             tg.lnd.lnd_mission = 0;
513             /* Drop any land units this unit was carrying */
514             snxtitem_xy(&ni, EF_LAND, tg.lnd.lnd_x, tg.lnd.lnd_y);
515             while (nxtitem(&ni, (s_char *)&land)) {
516                 if (land.lnd_land != tg.lnd.lnd_uid)
517                     continue;
518                 land.lnd_land = -1;
519                 wu(0, land.lnd_own, "unit #%d dropped in %s\n",
520                    land.lnd_uid,
521                    xyas(land.lnd_x, land.lnd_y, land.lnd_own));
522                 putland(land.lnd_uid, &land);
523             }
524             /* Drop any planes this unit was carrying */
525             snxtitem_xy(&ni, EF_PLANE, tg.lnd.lnd_x, tg.lnd.lnd_y);
526             while (nxtitem(&ni, (s_char *)&plane)) {
527                 if (plane.pln_flags & PLN_LAUNCHED)
528                     continue;
529                 if (plane.pln_land != land.lnd_uid)
530                     continue;
531                 plane.pln_land = -1;
532                 wu(0, plane.pln_own, "plane #%d dropped in %s\n",
533                    plane.pln_uid,
534                    xyas(plane.pln_x, plane.pln_y, plane.pln_own));
535                 putplane(plane.pln_uid, &plane);
536             }
537             tg.lnd.lnd_ship = -1;
538             tg.lnd.lnd_land = -1;
539             break;
540         default:
541             logerror("Bad trade type %d in trade\n", trade.trd_type);
542             break;
543         }
544         if (!ef_write(trade.trd_type, saveid, (char *)&tg)) {
545             logerror("Couldn't write unit to disk; seek help.\n");
546             continue;
547         }
548         nreport(trade.trd_owner, N_MAKE_SALE, trade.trd_maxbidder, 1);
549         wu(0, trade.trd_owner, "%s bought a %s #%d from you for $%.2f\n",
550            cname(trade.trd_maxbidder), trade_nameof(&trade, &tg),
551            saveid, (float)(price * tradetax));
552         wu(0, trade.trd_maxbidder,
553            "The bidding is over & you bought %s #%d from %s for $%.2f\n",
554            trade_nameof(&trade, &tg), saveid, cname(trade.trd_owner),
555            (float)price);
556     }
557 /*    logerror("Done checking the trades.\n");*/
558     return RET_OK;
559 }
560
561 int
562 ontradingblock(int type, int *ptr)
563           /* Generic pointer */
564 {
565     struct trdstr trade;
566     union trdgenstr tg;
567     int n;
568
569     for (n = 0; gettrade(n, &trade); n++) {
570         if (trade.trd_unitid < 0)
571             continue;
572         if (!trade_getitem(&trade, &tg))
573             continue;
574         if (trade.trd_type != type)
575             continue;
576         if (tg.gen.trg_uid == ((struct genstr *)ptr)->trg_uid)
577             return 1;
578     }
579     return 0;
580 }
581
582 void
583 trdswitchown(int type, int *ptr, int newown)
584           /* Generic pointer */
585 {
586     struct trdstr trade;
587     union trdgenstr tg;
588     int n;
589
590     for (n = 0; gettrade(n, &trade); n++) {
591         if (trade.trd_unitid < 0)
592             continue;
593         if (!trade_getitem(&trade, &tg))
594             continue;
595         if (trade.trd_type != type)
596             continue;
597         if (tg.gen.trg_uid != ((struct genstr *)ptr)->trg_uid)
598             continue;
599         if (trade.trd_owner == trade.trd_maxbidder)
600             trade.trd_maxbidder = newown;
601         trade.trd_owner = newown;
602         if (newown == 0)
603             trade.trd_unitid = -1;
604         puttrade(n, &trade);
605         return;
606     }
607 }