1 struct rpgobj; 2 3 struct rpgaction 4 { 5 rpgaction *next; 6 char *initiate, *script; 7 rpgquest *q; 8 bool used; 9 nextrpgaction10 rpgaction(char *_i = NULL, char *_s = NULL, rpgaction *_n = NULL) : next(_n), initiate(_i), script(_s), q(NULL), used(false) {} 11 ~rpgactionrpgaction12 ~rpgaction() { DELETEP(next); } 13 execrpgaction14 void exec(rpgobj *obj, rpgobj *target, rpgobj *user) 15 { 16 if(!*script) return; 17 pushobj(user); 18 pushobj(target); 19 pushobj(obj); 20 execute(script); 21 used = true; 22 } 23 }; 24 25 struct rpgobj : g3d_callback, stats 26 { 27 rpgobj *parent; // container object, if not top level 28 rpgobj *inventory; // contained objects, if any 29 rpgobj *sibling; // used for linking, if contained 30 31 rpgent *ent; // representation in the world, if top level 32 33 const char *name; // name it was spawned as 34 const char *model; // what to display it as 35 36 enum 37 { 38 IF_INVENTORY = 1, // parent owns this object, will contribute to parent stats 39 IF_LOOT = 2, // if parent dies, this object should drop to the ground 40 IF_TRADE = 4, // parent has this item available for trade, for player, all currently unused weapons etc are of this type 41 }; 42 43 enum 44 { 45 MENU_DEFAULT, 46 MENU_BUY, 47 MENU_SELL 48 }; 49 50 int itemflags; 51 52 rpgaction *actions, action_use; 53 char *abovetext; 54 55 int menutime, menutab, menuwhich; 56 57 #define loopinventory() for(rpgobj *o = inventory; o; o = o->sibling) 58 #define loopinventorytype(T) loopinventory() if(o->itemflags&(T)) 59 rpgobjrpgobj60 rpgobj(const char *_name) : parent(NULL), inventory(NULL), sibling(NULL), ent(NULL), name(_name), model(NULL), itemflags(IF_INVENTORY), 61 actions(NULL), abovetext(NULL), menutime(0), menutab(1), menuwhich(MENU_DEFAULT) {} 62 ~rpgobjrpgobj63 ~rpgobj() 64 { 65 DELETEP(inventory); 66 DELETEP(sibling); 67 DELETEP(ent); 68 DELETEP(actions); 69 } 70 scriptinitrpgobj71 void scriptinit() 72 { 73 DELETEP(inventory); 74 defformatstring(aliasname, "spawn_%s", name); 75 execident(aliasname); 76 } 77 decontainrpgobj78 void decontain() 79 { 80 if(parent) parent->remove(this); 81 } 82 addrpgobj83 void add(rpgobj *o, int itemflags) 84 { 85 o->sibling = inventory; 86 o->parent = this; 87 inventory = o; 88 o->itemflags = itemflags; 89 90 if(itemflags&IF_INVENTORY) recalcstats(); 91 } 92 removerpgobj93 void remove(rpgobj *o) 94 { 95 for(rpgobj **l = &inventory; *l; ) 96 if(*l==o) 97 { 98 *l = o->sibling; 99 o->sibling = o->parent = NULL; 100 } 101 else l = &(*l)->sibling; 102 103 if(o->itemflags&IF_INVENTORY) recalcstats(); 104 } 105 recalcstatsrpgobj106 void recalcstats() 107 { 108 st_reset(); 109 loopinventorytype(IF_INVENTORY) st_accumulate(*o); 110 } 111 selectedweaponrpgobj112 rpgobj &selectedweapon() 113 { 114 if(this==playerobj) return selected ? *selected : *this; 115 else { loopinventorytype(IF_INVENTORY) if(o->s_usetype) return *o; }; 116 return *this; 117 } 118 placedinworldrpgobj119 void placedinworld(rpgent *_ent) 120 { 121 if(!model) model = "tentus/moneybag"; 122 ent = _ent; 123 setbbfrommodel(ent, model); 124 entinmap(ent); 125 //ASSERT(!(ent->o.x<0 || ent->o.y<0 || ent->o.z<0 || ent->o.x>4096 || ent->o.y>4096 || ent->o.z>4096)); 126 st_init(); 127 } 128 renderrpgobj129 void render() 130 { 131 if(s_ai) 132 { 133 float sink = 0; 134 if(ent->physstate>=PHYS_SLIDE) 135 sink = raycube(ent->o, vec(0, 0, -1), 2*ent->eyeheight)-ent->eyeheight; 136 ent->sink = ent->sink*0.8 + sink*0.2; 137 //if(ent->blocked) particle_splash(PART_SPARK, 100, 100, ent->o, 0xB49B4B, 0.24f); 138 float oldheight = ent->eyeheight; 139 ent->eyeheight += ent->sink; 140 renderclient(ent, model, NULL, 0, ANIM_ATTACK1, 300, ent->lastaction, 0); 141 ent->eyeheight = oldheight; 142 if(s_hp<eff_maxhp() && ent->state==CS_ALIVE) particle_meter(ent->abovehead(), s_hp/(float)eff_maxhp(), PART_METER, 1, 0xFF1932, 0xFFFFFF, 2.0f); 143 144 } 145 else 146 { 147 rendermodel(NULL, model, ANIM_MAPMODEL|ANIM_LOOP, vec(ent->o).sub(vec(0, 0, ent->eyeheight)), ent->yaw+90, 0, MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED | MDL_LIGHT, ent); 148 } 149 } 150 updaterpgobj151 void update() 152 { 153 float dist = ent->o.dist(player1->o); 154 if(s_ai) { ent->update(dist); st_update(); }; 155 moveplayer(ent, 1, true); // 10 or above gets blocked less, because physics accuracy doesn't need extra tests 156 //ASSERT(!(ent->o.x<0 || ent->o.y<0 || ent->o.z<0 || ent->o.x>4096 || ent->o.y>4096 || ent->o.z>4096)); 157 if(!menutime && dist<(s_ai ? 40 : 24) && ent->state==CS_ALIVE && s_ai<2) { menutime = starttime(); menuwhich = MENU_DEFAULT; } 158 else if(dist>(s_ai ? 96 : 48)) menutime = 0; 159 } 160 addactionrpgobj161 void addaction(char *initiate, char *script, bool startquest) 162 { 163 for(rpgaction *a = actions; a; a = a->next) if(strcmp(a->initiate, initiate)==0) return; 164 actions = new rpgaction(initiate, script, actions); 165 if(startquest) actions->q = addquest(abovetext, name); 166 } 167 droplootrpgobj168 void droploot() 169 { 170 loopinventorytype(IF_LOOT) 171 { 172 o->decontain(); 173 pushobj(o); 174 placeinworld(ent->o, rnd(360)); 175 droploot(); 176 return; 177 } 178 } 179 takerpgobj180 rpgobj *take(char *name) 181 { 182 loopinventory() if(strcmp(o->name, name)==0) 183 { 184 o->decontain(); 185 return o; 186 } 187 return NULL; 188 } 189 takedamagerpgobj190 void takedamage(int damage, rpgobj &attacker) 191 { 192 ent->enemy = attacker.ent; 193 194 particle_splash(PART_BLOOD, max(damage/10, 1), 1000, ent->o, 0x60FFFF, 2.96f); 195 defformatstring(ds, "%d", damage); 196 particle_textcopy(ent->abovehead(), ds, PART_TEXT, 2000, 0xFF4B19, 4.0f, -8); 197 198 if((s_hp -= damage)<=0) 199 { 200 s_hp = 0; 201 ent->state = CS_DEAD; 202 ent->attacking = false; 203 ent->lastaction = lastmillis; 204 menutime = 0; 205 conoutf("%s killed: %s", attacker.name, name); 206 droploot(); 207 } 208 } 209 usesoundrpgobj210 void usesound(rpgent *user) 211 { 212 if(s_usesound) playsound(s_usesound, &user->o); 213 } 214 usemanarpgobj215 bool usemana(rpgobj &o) 216 { 217 if(o.s_manacost>s_mana) { if(this==playerobj) conoutf("\f2not enough mana"); return false; }; 218 s_mana -= o.s_manacost; 219 o.usesound(ent); 220 return true; 221 } 222 useactionrpgobj223 void useaction(rpgobj &target, rpgent &initiator, bool chargemana) 224 { 225 if(action_use.script && (!chargemana || initiator.ro->usemana(*this))) 226 { 227 action_use.exec(this, &target, initiator.ro); 228 if(s_useamount && !--s_useamount) removefromsystem(this); 229 } 230 } 231 selectuserpgobj232 void selectuse() 233 { 234 if(s_usetype) 235 { 236 conoutf("\f2using: %s", name); 237 selected = this; 238 } 239 else 240 { 241 useaction(*playerobj, *playerobj->ent, true); 242 } 243 } 244 guiactionrpgobj245 void guiaction(g3d_gui &g, rpgaction *a) 246 { 247 if(!a) return; 248 guiaction(g, a->next); 249 if(g.button(a->initiate, a->used ? 0xAAAAAA : 0xFFFFFF, "chat")&G3D_UP) 250 { 251 currentquest = a->q; 252 a->exec(this, playerobj, playerobj); 253 currentquest = NULL; 254 } 255 } 256 guirpgobj257 void gui(g3d_gui &g, bool firstpass) 258 { 259 g.start(menutime, 0.015f, &menutab); 260 switch(menuwhich) 261 { 262 case MENU_DEFAULT: 263 { 264 g.tab(name, 0xFFFFFF); 265 if(abovetext) g.text(abovetext, 0xDDFFDD); 266 267 guiaction(g, actions); 268 269 if(s_ai) 270 { 271 bool trader = false; 272 loopinventorytype(IF_TRADE) trader = true; 273 if(trader) 274 { 275 if(g.button("buy", 0xFFFFFF, "coins")&G3D_UP) menuwhich = MENU_BUY; 276 if(g.button("sell", 0xFFFFFF, "coins")&G3D_UP) menuwhich = MENU_SELL; 277 } 278 } 279 else 280 { 281 defformatstring(wtext, "worth %d", s_worth); 282 g.text(wtext, 0xAAAAAA, "coins"); 283 if(g.button("take", 0xFFFFFF, "hand")&G3D_UP) 284 { 285 conoutf("\f2you take a %s (worth %d gold)", name, s_worth); 286 taken(this, playerobj); 287 } 288 if(!s_usetype && g.button("use", 0xFFFFFF, "hand")&G3D_UP) 289 { 290 selectuse(); 291 } 292 } 293 break; 294 } 295 296 case MENU_BUY: 297 { 298 defformatstring(info, "buying from: %s", name); 299 g.tab(info, 0xFFFFFF); 300 loopinventorytype(IF_TRADE) 301 { 302 int price = o->s_worth; 303 defformatstring(info, "%s (%d)", o->name, price); 304 int ret = g.button(info, 0xFFFFFF, "coins"); 305 if(ret&G3D_UP) 306 { 307 if(playerobj->s_gold>=price) 308 { 309 conoutf("\f2you bought %s for %d gold", o->name, price); 310 playerobj->s_gold -= price; 311 s_gold += price; 312 o->decontain(); 313 playerobj->add(o, IF_INVENTORY); 314 } 315 else 316 { 317 conoutf("\f2you cannot afford this item!"); 318 } 319 } 320 } 321 formatstring(info, "you have %d gold", playerobj->s_gold); 322 g.text(info, 0xAAAAAA, "info"); 323 if(g.button("done buying", 0xFFFFFF, "coins")&G3D_UP) menuwhich = MENU_DEFAULT; 324 break; 325 } 326 327 case MENU_SELL: 328 { 329 defformatstring(info, "selling to: %s", name); 330 g.tab(info, 0xFFFFFF); 331 playerobj->invgui(g, this); 332 if(g.button("done selling", 0xFFFFFF, "coins")&G3D_UP) menuwhich = MENU_DEFAULT; 333 break; 334 } 335 } 336 g.end(); 337 } 338 339 void invgui(g3d_gui &g, rpgobj *buyer = NULL) 340 { loopinventoryrpgobj341 loopinventory() 342 { 343 int price = o->s_worth/2; 344 defformatstring(info, "%s (%d)", o->name, price); 345 int ret = g.button(info, 0xFFFFFF, "coins"); 346 if(ret&G3D_UP) 347 { 348 if(buyer) 349 { 350 if(price>buyer->s_gold) 351 { 352 conoutf("\f2%s cannot afford to buy %s from you!", buyer->name, o->name); 353 } 354 else 355 { 356 if(price) 357 { 358 conoutf("\f2you sold %s for %d gold", o->name, price); 359 s_gold += price; 360 buyer->s_gold -= price; 361 o->decontain(); 362 buyer->add(o, IF_TRADE); 363 } 364 else 365 { 366 conoutf("\f2you cannot sell %s", o->name); 367 } 368 } 369 } 370 else // player wants to use this item 371 { 372 o->selectuse(); 373 } 374 } 375 } 376 defformatstring(info, "you have %d gold", playerobj->s_gold); 377 g.text(info, 0xAAAAAA, "info"); 378 } 379 g3d_menurpgobj380 void g3d_menu() 381 { 382 if(!menutime) return; 383 g3d_addgui(this, vec(ent->o).add(vec(0, 0, 2))); 384 } 385 }; 386