= 0) + { + wall[wall[j].nextwall].overpicnum = 0; + wall[wall[j].nextwall].cstat &= (128+32+8+4+2); + } + } + } + } + + if (pSector->ceilingz < pSector->floorz) + pSector->ceilingz += SP(spriteNum); + else + { + pSector->ceilingz = pSector->floorz; + + for (SPRITES_OF(STAT_EFFECTOR, j)) + { + if (sprite[j].lotag == SE_0_ROTATING_SECTOR && sprite[j].hitag==spriteHitag) + { + sectortype *const pSector = §or[sprite[j].sectnum]; + int const ownerSector = sprite[sprite[j].owner].sectnum; + + pSector->ceilingpal = sector[ownerSector].floorpal; + pSector->floorpal = pSector->ceilingpal; + pSector->ceilingshade = sector[ownerSector].floorshade; + pSector->floorshade = pSector->ceilingshade; + + actor[sprite[j].owner].t_data[0] = 2; + } + } + + DELETE_SPRITE_AND_CONTINUE(spriteNum); + } + } + else //Not hit yet + { + if (G_FindExplosionInSector(pSprite->sectnum) >= 0) + { + P_DoQuote(QUOTE_UNLOCKED, g_player[myconnectindex].ps); + + for (SPRITES_OF(STAT_EFFECTOR, l)) + { + switch (sprite[l].lotag & 0x7fff) + { + case SE_0_ROTATING_SECTOR: + if (sprite[l].hitag == spriteHitag) + { + int const spriteOwner = sprite[l].owner; + int const sectNum = sprite[l].sectnum; + + sector[sectNum].ceilingshade = sprite[spriteOwner].shade; + sector[sectNum].floorshade = sector[sectNum].ceilingshade; + sector[sectNum].ceilingpal = sprite[spriteOwner].pal; + sector[sectNum].floorpal = sector[sectNum].ceilingpal; + } + break; + + case SE_1_PIVOT: + case SE_12_LIGHT_SWITCH: +// case SE_18_INCREMENTAL_SECTOR_RISE_FALL: + case SE_19_EXPLOSION_LOWERS_CEILING: + if (spriteHitag == sprite[l].hitag) + if (actor[l].t_data[0] == 0) + { + actor[l].t_data[0] = 1; // Shut them all on + sprite[l].owner = spriteNum; + } + + break; + } + } + } + } + + break; + + case SE_20_STRETCH_BRIDGE: //Extend-o-bridge + if (pData[0] == 0) break; + pSprite->xvel = (pData[0] == 1) ? 8 : -8; + + if (pSprite->xvel) //Moving + { + vec2_t const vect = { (pSprite->xvel * sintable[(pSprite->ang + 512) & 2047]) >> 14, + (pSprite->xvel * sintable[pSprite->ang & 2047]) >> 14 }; + + pData[3] += pSprite->xvel; + + pSprite->x += vect.x; + pSprite->y += vect.y; + + if (pData[3] <= 0 || (pData[3] >> 6) >= (SP(spriteNum) >> 6)) + { + pSprite->x -= vect.x; + pSprite->y -= vect.y; + pData[0] = 0; + A_CallSound(pSprite->sectnum, spriteNum); + break; + } + + for (bssize_t nextSprite, SPRITES_OF_SECT_SAFE(pSprite->sectnum, sectSprite, nextSprite)) + { + if (sprite[sectSprite].statnum != STAT_EFFECTOR && sprite[sectSprite].zvel == 0) + { + sprite[sectSprite].x += vect.x; + sprite[sectSprite].y += vect.y; + + setsprite(sectSprite, (vec3_t *)&sprite[sectSprite]); + + if (sector[sprite[sectSprite].sectnum].floorstat & 2 && sprite[sectSprite].statnum == STAT_ZOMBIEACTOR) + A_Fall(sectSprite); + } + } + + dragpoint((int16_t)pData[1], wall[pData[1]].x + vect.x, wall[pData[1]].y + vect.y, 0); + dragpoint((int16_t)pData[2], wall[pData[2]].x + vect.x, wall[pData[2]].y + vect.y, 0); + + for (bssize_t TRAVERSE_CONNECT(playerNum)) + { + DukePlayer_t *const pPlayer = g_player[playerNum].ps; + + if (pPlayer->cursectnum == pSprite->sectnum && pPlayer->on_ground) + { + pPlayer->pos.x += vect.x; + pPlayer->pos.y += vect.y; + + pPlayer->opos.x = pPlayer->pos.x; + pPlayer->opos.y = pPlayer->pos.y; + + pPlayer->pos.z += PHEIGHT; + setsprite(pPlayer->i, (vec3_t *)pPlayer); + pPlayer->pos.z -= PHEIGHT; + } + } + + pSector->floorxpanning -= vect.x >> 3; + pSector->floorypanning -= vect.y >> 3; + + pSector->ceilingxpanning -= vect.x >> 3; + pSector->ceilingypanning -= vect.y >> 3; + } + + break; + + case SE_21_DROP_FLOOR: // Cascading effect + { + if (pData[0] == 0) break; + + int32_t *zptr = (pSprite->ang == 1536) ? &pSector->ceilingz : &pSector->floorz; + + if (pData[0] == 1) //Decide if the s->sectnum should go up or down + { + pSprite->zvel = ksgn(pSprite->z-*zptr) * (SP(spriteNum)<<4); + pData[0]++; + } + + if (pSector->extra == 0) + { + *zptr += pSprite->zvel; + + if (klabs(*zptr-pSprite->z) < 1024) + { + *zptr = pSprite->z; + DELETE_SPRITE_AND_CONTINUE(spriteNum); //All done // SE_21_KILLIT, see sector.c + } + } + else pSector->extra--; + break; + } + + case SE_22_TEETH_DOOR: + if (pData[1]) + { + if (GetAnimationGoal(§or[pData[0]].ceilingz) >= 0) + pSector->ceilingz += pSector->extra*9; + else pData[1] = 0; + } + break; + + case 156: + if (!RRRA) break; + fallthrough__; + case SE_24_CONVEYOR: + case SE_34: + { + if (pData[4]) + break; + + vec2_t const vect = { (SP(spriteNum) * sintable[(pSprite->ang + 512) & 2047]) >> 18, + (SP(spriteNum) * sintable[pSprite->ang & 2047]) >> 18 }; + + k = 0; + + for (bssize_t nextSprite, SPRITES_OF_SECT_SAFE(pSprite->sectnum, sectSprite, nextSprite)) + { + if (sprite[sectSprite].zvel < 0) + continue; + + switch (sprite[sectSprite].statnum) + { + case STAT_MISC: + switch (DYNAMICTILEMAP(sprite[sectSprite].picnum)) + { + case PUKE__STATIC: + case FOOTPRINTS4__STATIC: + case BLOODSPLAT1__STATIC: + case BLOODSPLAT2__STATIC: + case BLOODSPLAT3__STATIC: + case BLOODSPLAT4__STATIC: + if (RR) break; + fallthrough__; + case BULLETHOLE__STATIC: + if (RR && sprite[sectSprite].picnum == BULLETHOLE) continue; + fallthrough__; + case BLOODPOOL__STATIC: + case FOOTPRINTS__STATIC: + case FOOTPRINTS2__STATIC: + case FOOTPRINTS3__STATIC: sprite[sectSprite].xrepeat = sprite[sectSprite].yrepeat = 0; continue; + + case LASERLINE__STATIC: if (RR) break; continue; + } + fallthrough__; + case STAT_STANDABLE: + if (!RR && sprite[sectSprite].picnum == TRIPBOMB) + break; + fallthrough__; + case STAT_ACTOR: + case STAT_DEFAULT: + if (sprite[sectSprite].picnum == BOLT1 + || sprite[sectSprite].picnum == BOLT1 + 1 + || sprite[sectSprite].picnum == BOLT1 + 2 + || sprite[sectSprite].picnum == BOLT1 + 3 + || (!RR && (sprite[sectSprite].picnum == SIDEBOLT1 + || sprite[sectSprite].picnum == SIDEBOLT1 + 1 + || sprite[sectSprite].picnum == SIDEBOLT1 + 2 + || sprite[sectSprite].picnum == SIDEBOLT1 + 3)) + || A_CheckSwitchTile(sectSprite)) + break; + + if (!(sprite[sectSprite].picnum >= CRANE && sprite[sectSprite].picnum <= CRANE + 3)) + { + if (sprite[sectSprite].z > actor[sectSprite].floorz - ZOFFSET2) + { + actor[sectSprite].bpos.x = sprite[sectSprite].x; + actor[sectSprite].bpos.y = sprite[sectSprite].y; + + sprite[sectSprite].x += vect.x >> (RR ? 1 : 2); + sprite[sectSprite].y += vect.y >> (RR ? 1 : 2); + + setsprite(sectSprite, (vec3_t *)&sprite[sectSprite]); + + if (sector[sprite[sectSprite].sectnum].floorstat & 2) + if (sprite[sectSprite].statnum == STAT_ZOMBIEACTOR) + A_Fall(sectSprite); + } + } + break; + } + } + + for (bssize_t TRAVERSE_CONNECT(playerNum)) + { + DukePlayer_t *const pPlayer = g_player[playerNum].ps; + + if (pPlayer->cursectnum == pSprite->sectnum && pPlayer->on_ground) + { + if (klabs(pPlayer->pos.z - pPlayer->truefz) < PHEIGHT + (9 << 8)) + { + pPlayer->fric.x += vect.x << 3; + pPlayer->fric.y += vect.y << 3; + } + } + } + if (!RRRA || spriteLotag != 156) + pSector->floorxpanning += SP(spriteNum)>>7; + + break; + } + + case SE_35: + if (pSector->ceilingz > pSprite->z) + { + for (j = 0; j < 8; j++) + { + pSprite->ang += krand2()&511; + k = A_Spawn(spriteNum, SMALLSMOKE); + sprite[k].xvel = 96+(krand2()&127); + A_SetSprite(k, CLIPMASK0); + setsprite(k, (vec3_t *) &sprite[k]); + if (rnd(16)) + A_Spawn(spriteNum, EXPLOSION2); + } + + } + switch (pData[0]) + { + case 0: + pSector->ceilingz += pSprite->yvel; + if (pSector->ceilingz > pSector->floorz) + pSector->floorz = pSector->ceilingz; + if (pSector->ceilingz > pSprite->z+ZOFFSET5) + pData[0]++; + break; + case 1: + pSector->ceilingz-=(pSprite->yvel<<2); + if (pSector->ceilingz < pData[4]) + { + pSector->ceilingz = pData[4]; + pData[0] = 0; + } + break; + } + break; + + case SE_25_PISTON: //PISTONS + if (pData[4] == 0) break; + + if (pSector->floorz <= pSector->ceilingz) + pSprite->shade = 0; + else if (pSector->ceilingz <= pData[RR?4:3]) + pSprite->shade = 1; + + if (pSprite->shade) + { + pSector->ceilingz += SP(spriteNum)<<4; + if (pSector->ceilingz > pSector->floorz) + { + pSector->ceilingz = pSector->floorz; + if (RRRA && g_pistonSound) + A_PlaySound(371, spriteNum); + } + } + else + { + pSector->ceilingz -= SP(spriteNum)<<4; + if (pSector->ceilingz < pData[RR?4:3]) + { + pSector->ceilingz = pData[RR?4:3]; + if (RRRA && g_pistonSound) + A_PlaySound(167, spriteNum); + } + } + + break; + + case SE_26: + { + int32_t p, nextj; + + pSprite->xvel = 32; + l = (pSprite->xvel*sintable[(pSprite->ang+512)&2047])>>14; + x = (pSprite->xvel*sintable[pSprite->ang&2047])>>14; + + pSprite->shade++; + if (pSprite->shade > 7) + { + pSprite->x = pData[3]; + pSprite->y = pData[4]; + pSector->floorz -= ((pSprite->zvel*pSprite->shade)-pSprite->zvel); + pSprite->shade = 0; + } + else + pSector->floorz += pSprite->zvel; + + for (SPRITES_OF_SECT_SAFE(pSprite->sectnum, j, nextj)) + { + if (sprite[j].statnum != STAT_EFFECTOR && sprite[j].statnum != STAT_PLAYER ) + { + actor[j].bpos.x = sprite[j].x; + actor[j].bpos.y = sprite[j].y; + + sprite[j].x += l; + sprite[j].y += x; + sprite[j].z += pSprite->zvel; + + setsprite(j, (vec3_t *)&sprite[j]); + } + } + + for (TRAVERSE_CONNECT(p)) + { + DukePlayer_t *const pPlayer = g_player[p].ps; + + if (pSprite->sectnum == sprite[pPlayer->i].sectnum && pPlayer->on_ground) + { + pPlayer->fric.x += l << 5; + pPlayer->fric.y += x << 5; + pPlayer->pos.z += pSprite->zvel; + } + } + + A_MoveSector(spriteNum); + setsprite(spriteNum,(vec3_t *)pSprite); + + break; + } + + case SE_27_DEMO_CAM: + { + if (ud.recstat == 0 || !ud.democams) break; + + actor[spriteNum].tempang = pSprite->ang; + + int const p = A_FindPlayer(pSprite,&x); + DukePlayer_t * const ps = g_player[p].ps; + + if (sprite[ps->i].extra > 0 && myconnectindex == screenpeek) + { + if (pData[0] < 0) + { + ud.camerasprite = spriteNum; + pData[0]++; + } + else if (ud.recstat == 2 && ps->newowner == -1) + { + if (cansee(pSprite->x,pSprite->y,pSprite->z,SECT(spriteNum),ps->pos.x,ps->pos.y,ps->pos.z,ps->cursectnum)) + { + if (x < (int32_t)((unsigned)spriteHitag)) + { + ud.camerasprite = spriteNum; + pData[0] = 999; + pSprite->ang += G_GetAngleDelta(pSprite->ang,getangle(ps->pos.x-pSprite->x,ps->pos.y-pSprite->y))>>3; + SP(spriteNum) = 100+((pSprite->z-ps->pos.z)/257); + + } + else if (pData[0] == 999) + { + if (ud.camerasprite == spriteNum) + pData[0] = 0; + else pData[0] = -10; + ud.camerasprite = spriteNum; + + } + } + else + { + pSprite->ang = getangle(ps->pos.x-pSprite->x,ps->pos.y-pSprite->y); + + if (pData[0] == 999) + { + if (ud.camerasprite == spriteNum) + pData[0] = 0; + else pData[0] = -20; + ud.camerasprite = spriteNum; + } + } + } + } + break; + } + + case SE_28_LIGHTNING: + { + if (RR) + break; + if (pData[5] > 0) + { + pData[5]--; + break; + } + + if (T1(spriteNum) == 0) + { + A_FindPlayer(pSprite,&x); + if (x > 15500) + break; + T1(spriteNum) = 1; + T2(spriteNum) = 64 + (krand2()&511); + T3(spriteNum) = 0; + } + else + { + T3(spriteNum)++; + if (T3(spriteNum) > T2(spriteNum)) + { + T1(spriteNum) = 0; + g_player[screenpeek].ps->visibility = ud.const_visibility; + break; + } + else if (T3(spriteNum) == (T2(spriteNum)>>1)) + A_PlaySound(THUNDER,spriteNum); + else if (T3(spriteNum) == (T2(spriteNum)>>3)) + A_PlaySound(LIGHTNING_SLAP,spriteNum); + else if (T3(spriteNum) == (T2(spriteNum)>>2)) + { + for (SPRITES_OF(STAT_DEFAULT, j)) + if (sprite[j].picnum == NATURALLIGHTNING && sprite[j].hitag == pSprite->hitag) + sprite[j].cstat |= 32768; + } + else if (T3(spriteNum) > (T2(spriteNum)>>3) && T3(spriteNum) < (T2(spriteNum)>>2)) + { + if (cansee(pSprite->x,pSprite->y,pSprite->z,pSprite->sectnum,g_player[screenpeek].ps->pos.x,g_player[screenpeek].ps->pos.y,g_player[screenpeek].ps->pos.z,g_player[screenpeek].ps->cursectnum)) + j = 1; + else j = 0; + + if (rnd(192) && (T3(spriteNum)&1)) + { + if (j) + g_player[screenpeek].ps->visibility = 0; + } + else if (j) + g_player[screenpeek].ps->visibility = ud.const_visibility; + + for (SPRITES_OF(STAT_DEFAULT, j)) + { + if (sprite[j].picnum == NATURALLIGHTNING && sprite[j].hitag == pSprite->hitag) + { + if (rnd(32) && (T3(spriteNum)&1)) + { + int32_t p; + DukePlayer_t *ps; + + sprite[j].cstat &= 32767; + A_Spawn(j,SMALLSMOKE); + + p = A_FindPlayer(pSprite, NULL); + ps = g_player[p].ps; + + x = ldist(&sprite[ps->i], &sprite[j]); + if (x < 768) + { + if (!A_CheckSoundPlaying(ps->i,DUKE_LONGTERM_PAIN)) + A_PlaySound(DUKE_LONGTERM_PAIN,ps->i); + A_PlaySound(SHORT_CIRCUIT,ps->i); + sprite[ps->i].extra -= 8+(krand2()&7); + + P_PalFrom(ps, 32, 16,0,0); + } + break; + } + else sprite[j].cstat |= 32768; + } + } + } + } + break; + } + + case SE_29_WAVES: + pSprite->hitag += 64; + l = mulscale12((int32_t)pSprite->yvel,sintable[pSprite->hitag&2047]); + pSector->floorz = pSprite->z + l; + break; + + case SE_31_FLOOR_RISE_FALL: // True Drop Floor + if (pData[0] == 1) + { + // Choose dir + + if (!RR && pData[3] > 0) + { + pData[3]--; + break; + } + + if (pData[2] == 1) // Retract + { + if (SA(spriteNum) != 1536) + HandleSE31(spriteNum, 1, pSprite->z, 0, pSprite->z-pSector->floorz); + else + HandleSE31(spriteNum, 1, pData[1], 0, pData[1]-pSector->floorz); + + Yax_SetBunchZs(pSector-sector, YAX_FLOOR, pSector->floorz); + + break; + } + + if ((pSprite->ang&2047) == 1536) + HandleSE31(spriteNum, 0, pSprite->z, 1, pSprite->z-pSector->floorz); + else + HandleSE31(spriteNum, 0, pData[1], 1, pData[1]-pSprite->z); + + Yax_SetBunchZs(pSector-sector, YAX_FLOOR, pSector->floorz); + } + break; + + case SE_32_CEILING_RISE_FALL: // True Drop Ceiling + if (pData[0] == 1) + { + // Choose dir + + if (pData[2] == 1) // Retract + { + if (SA(spriteNum) != 1536) + { + if (klabs(pSector->ceilingz - pSprite->z) < (SP(spriteNum)<<1)) + { + pSector->ceilingz = pSprite->z; + A_CallSound(pSprite->sectnum,spriteNum); + pData[2] = 0; + pData[0] = 0; + } + else pSector->ceilingz += ksgn(pSprite->z-pSector->ceilingz)*SP(spriteNum); + } + else + { + if (klabs(pSector->ceilingz - pData[1]) < (SP(spriteNum)<<1)) + { + pSector->ceilingz = pData[1]; + A_CallSound(pSprite->sectnum,spriteNum); + pData[2] = 0; + pData[0] = 0; + } + else pSector->ceilingz += ksgn(pData[1]-pSector->ceilingz)*SP(spriteNum); + } + + Yax_SetBunchZs(pSector-sector, YAX_CEILING, pSector->ceilingz); + + break; + } + + if ((pSprite->ang&2047) == 1536) + { + if (klabs(pSector->ceilingz-pSprite->z) < (SP(spriteNum)<<1)) + { + pData[0] = 0; + pData[2] = !pData[2]; + A_CallSound(pSprite->sectnum,spriteNum); + pSector->ceilingz = pSprite->z; + } + else pSector->ceilingz += ksgn(pSprite->z-pSector->ceilingz)*SP(spriteNum); + } + else + { + if (klabs(pSector->ceilingz-pData[1]) < (SP(spriteNum)<<1)) + { + pData[0] = 0; + pData[2] = !pData[2]; + A_CallSound(pSprite->sectnum,spriteNum); + } + else pSector->ceilingz -= ksgn(pSprite->z-pData[1])*SP(spriteNum); + } + + Yax_SetBunchZs(pSector-sector, YAX_CEILING, pSector->ceilingz); + } + break; + + case SE_33_QUAKE_DEBRIS: + if (g_earthquakeTime > 0 && (krand2()&7) == 0) + RANDOMSCRAP(pSprite, spriteNum); + break; + + case SE_36_PROJ_SHOOTER: + if (pData[0]) + { + if (pData[0] == 1) + A_Shoot(spriteNum,pSector->extra); + else if (pData[0] == GAMETICSPERSEC*5) + pData[0] = 0; + pData[0]++; + } + break; + + case 128: //SE to control glass breakage + { + walltype *pWall = &wall[pData[2]]; + + if (pWall->cstat|32) + { + pWall->cstat &= (255-32); + pWall->cstat |= 16; + if (pWall->nextwall >= 0) + { + wall[pWall->nextwall].cstat &= (255-32); + wall[pWall->nextwall].cstat |= 16; + } + } + else break; + + pWall->overpicnum++; + if (pWall->nextwall >= 0) + wall[pWall->nextwall].overpicnum++; + + if (pData[0] < pData[1]) pData[0]++; + else + { + pWall->cstat &= (128+32+8+4+2); + if (pWall->nextwall >= 0) + wall[pWall->nextwall].cstat &= (128+32+8+4+2); + DELETE_SPRITE_AND_CONTINUE(spriteNum); + } + } + break; + + case SE_130: + if (pData[0] > 80) + { + DELETE_SPRITE_AND_CONTINUE(spriteNum); + } + else pData[0]++; + + x = pSector->floorz-pSector->ceilingz; + + if (rnd(64)) + { + k = A_Spawn(spriteNum,EXPLOSION2); + sprite[k].xrepeat = sprite[k].yrepeat = 2+(krand2()&7); + sprite[k].z = pSector->floorz-(krand2()%x); + sprite[k].ang += 256-(krand2()%511); + sprite[k].xvel = krand2()&127; + A_SetSprite(k,CLIPMASK0); + } + break; + + case SE_131: + if (pData[0] > 40) + { + DELETE_SPRITE_AND_CONTINUE(spriteNum); + } + else pData[0]++; + + x = pSector->floorz-pSector->ceilingz; + + if (rnd(32)) + { + k = A_Spawn(spriteNum,EXPLOSION2); + sprite[k].xrepeat = sprite[k].yrepeat = 2+(krand2()&3); + sprite[k].z = pSector->floorz-(krand2()%x); + sprite[k].ang += 256-(krand2()%511); + sprite[k].xvel = krand2()&127; + A_SetSprite(k,CLIPMASK0); + } + break; + + case SE_49_POINT_LIGHT: + case SE_50_SPOT_LIGHT: + changespritestat(spriteNum, STAT_LIGHT); + break; + } +next_sprite: + spriteNum = nextSprite; + } + + //Sloped sin-wave floors! + for (SPRITES_OF(STAT_EFFECTOR, spriteNum)) + { + const spritetype *s = &sprite[spriteNum]; + + if (s->lotag == SE_29_WAVES) + { + usectortype const *const sc = (usectortype *)§or[s->sectnum]; + + if (sc->wallnum == 4) + { + walltype *const pWall = &wall[sc->wallptr+2]; + if (pWall->nextsector >= 0) + alignflorslope(s->sectnum, pWall->x,pWall->y, sector[pWall->nextsector].floorz); + } + } + } +} + +static void G_DoEffectorLights(void) // STATNUM 14 +{ + int32_t i; + + for (SPRITES_OF(STAT_LIGHT, i)) + { + switch (sprite[i].lotag) + { +#ifdef POLYMER + case SE_49_POINT_LIGHT: + { + if (!A_CheckSpriteFlags(i, SFLAG_NOLIGHT) && videoGetRenderMode() == REND_POLYMER && + !(A_CheckSpriteFlags(i, SFLAG_USEACTIVATOR) && sector[sprite[i].sectnum].lotag & 16384)) + { + if (actor[i].lightptr == NULL) + { +#pragma pack(push,1) + _prlight mylight; +#pragma pack(pop) + mylight.sector = SECT(i); + Bmemcpy(&mylight, &sprite[i], sizeof(int32_t) * 3); + mylight.range = SHT(i); + mylight.color[0] = sprite[i].xvel; + mylight.color[1] = sprite[i].yvel; + mylight.color[2] = sprite[i].zvel; + mylight.radius = 0; + mylight.angle = SA(i); + mylight.horiz = SH(i); + mylight.minshade = sprite[i].xoffset; + mylight.maxshade = sprite[i].yoffset; + mylight.tilenum = 0; + mylight.publicflags.emitshadow = 0; + mylight.publicflags.negative = !!(CS(i) & 128); + + if (CS(i) & 2) + { + if (CS(i) & 512) + mylight.priority = PR_LIGHT_PRIO_LOW; + else + mylight.priority = PR_LIGHT_PRIO_HIGH; + } + else + mylight.priority = PR_LIGHT_PRIO_MAX; + + actor[i].lightId = polymer_addlight(&mylight); + if (actor[i].lightId >= 0) + actor[i].lightptr = &prlights[actor[i].lightId]; + break; + } + + if (Bmemcmp(&sprite[i], actor[i].lightptr, sizeof(int32_t) * 3)) + { + Bmemcpy(actor[i].lightptr, &sprite[i], sizeof(int32_t) * 3); + actor[i].lightptr->sector = sprite[i].sectnum; + actor[i].lightptr->flags.invalidate = 1; + } + if (SHT(i) != actor[i].lightptr->range) + { + actor[i].lightptr->range = SHT(i); + actor[i].lightptr->flags.invalidate = 1; + } + if ((sprite[i].xvel != actor[i].lightptr->color[0]) || + (sprite[i].yvel != actor[i].lightptr->color[1]) || + (sprite[i].zvel != actor[i].lightptr->color[2])) + { + actor[i].lightptr->color[0] = sprite[i].xvel; + actor[i].lightptr->color[1] = sprite[i].yvel; + actor[i].lightptr->color[2] = sprite[i].zvel; + } + if ((int)!!(CS(i) & 128) != actor[i].lightptr->publicflags.negative) { + actor[i].lightptr->publicflags.negative = !!(CS(i) & 128); + } + } + break; + } + case SE_50_SPOT_LIGHT: + { + if (!A_CheckSpriteFlags(i, SFLAG_NOLIGHT) && videoGetRenderMode() == REND_POLYMER && + !(A_CheckSpriteFlags(i, SFLAG_USEACTIVATOR) && sector[sprite[i].sectnum].lotag & 16384)) + { + if (actor[i].lightptr == NULL) + { +#pragma pack(push,1) + _prlight mylight; +#pragma pack(pop) + + mylight.sector = SECT(i); + Bmemcpy(&mylight, &sprite[i], sizeof(int32_t) * 3); + mylight.range = SHT(i); + mylight.color[0] = sprite[i].xvel; + mylight.color[1] = sprite[i].yvel; + mylight.color[2] = sprite[i].zvel; + mylight.radius = (256-(SS(i)+128))<<1; + mylight.faderadius = (int16_t)(mylight.radius * 0.75f); + mylight.angle = SA(i); + mylight.horiz = SH(i); + mylight.minshade = sprite[i].xoffset; + mylight.maxshade = sprite[i].yoffset; + mylight.tilenum = actor[i].picnum; + mylight.publicflags.emitshadow = !(CS(i) & 64); + mylight.publicflags.negative = !!(CS(i) & 128); + + if (CS(i) & 2) + { + if (CS(i) & 512) + mylight.priority = PR_LIGHT_PRIO_LOW; + else + mylight.priority = PR_LIGHT_PRIO_HIGH; + } + else + mylight.priority = PR_LIGHT_PRIO_MAX; + + actor[i].lightId = polymer_addlight(&mylight); + if (actor[i].lightId >= 0) + { + actor[i].lightptr = &prlights[actor[i].lightId]; + + // Hack in case polymer_addlight tweaked the horiz value + if (actor[i].lightptr->horiz != SH(i)) + SH(i) = actor[i].lightptr->horiz; + } + break; + } + + if (Bmemcmp(&sprite[i], actor[i].lightptr, sizeof(int32_t) * 3)) + { + Bmemcpy(actor[i].lightptr, &sprite[i], sizeof(int32_t) * 3); + actor[i].lightptr->sector = sprite[i].sectnum; + actor[i].lightptr->flags.invalidate = 1; + } + if (SHT(i) != actor[i].lightptr->range) + { + actor[i].lightptr->range = SHT(i); + actor[i].lightptr->flags.invalidate = 1; + } + if ((sprite[i].xvel != actor[i].lightptr->color[0]) || + (sprite[i].yvel != actor[i].lightptr->color[1]) || + (sprite[i].zvel != actor[i].lightptr->color[2])) + { + actor[i].lightptr->color[0] = sprite[i].xvel; + actor[i].lightptr->color[1] = sprite[i].yvel; + actor[i].lightptr->color[2] = sprite[i].zvel; + } + if (((256-(SS(i)+128))<<1) != actor[i].lightptr->radius) + { + actor[i].lightptr->radius = (256-(SS(i)+128))<<1; + actor[i].lightptr->faderadius = (int16_t)(actor[i].lightptr->radius * 0.75f); + actor[i].lightptr->flags.invalidate = 1; + } + if (SA(i) != actor[i].lightptr->angle) + { + actor[i].lightptr->angle = SA(i); + actor[i].lightptr->flags.invalidate = 1; + } + if (SH(i) != actor[i].lightptr->horiz) + { + actor[i].lightptr->horiz = SH(i); + actor[i].lightptr->flags.invalidate = 1; + } + if ((int)!(CS(i) & 64) != actor[i].lightptr->publicflags.emitshadow) { + actor[i].lightptr->publicflags.emitshadow = !(CS(i) & 64); + } + if ((int)!!(CS(i) & 128) != actor[i].lightptr->publicflags.negative) { + actor[i].lightptr->publicflags.negative = !!(CS(i) & 128); + } + actor[i].lightptr->tilenum = actor[i].picnum; + } + + break; + } +#endif // POLYMER + } + } +} + +#ifdef POLYMER +static void A_DoLight(int spriteNum) +{ + spritetype *const pSprite = &sprite[spriteNum]; + int savedFires = 0; + + if (((sector[pSprite->sectnum].floorz - sector[pSprite->sectnum].ceilingz) < 16) || pSprite->z > sector[pSprite->sectnum].floorz || pSprite->z > actor[spriteNum].floorz || + (pSprite->picnum != SECTOREFFECTOR && ((pSprite->cstat & 32768) || pSprite->yrepeat < 4)) || + A_CheckSpriteFlags(spriteNum, SFLAG_NOLIGHT) || (A_CheckSpriteFlags(spriteNum, SFLAG_USEACTIVATOR) && sector[pSprite->sectnum].lotag & 16384)) + { + if (actor[spriteNum].lightptr != NULL) + A_DeleteLight(spriteNum); + } + else + { + if (actor[spriteNum].lightptr != NULL && actor[spriteNum].lightcount) + { + if (!(--actor[spriteNum].lightcount)) + A_DeleteLight(spriteNum); + } + + if (pr_lighting != 1) + return; + + for (bsize_t ii=0; ii<2; ii++) + { + if (pSprite->picnum <= 0) // oob safety + break; + + switch (DYNAMICTILEMAP(pSprite->picnum-1+ii)) + { + case DIPSWITCH__STATIC: + case DIPSWITCH2__STATIC: + case DIPSWITCH3__STATIC: + case PULLSWITCH__STATIC: + case SLOTDOOR__STATIC: + case LIGHTSWITCH__STATIC: + case SPACELIGHTSWITCH__STATIC: + case SPACEDOORSWITCH__STATIC: + case FRANKENSTINESWITCH__STATIC: + case POWERSWITCH1__STATIC: + case LOCKSWITCH1__STATIC: + case POWERSWITCH2__STATIC: + case TECHSWITCH__STATIC: + case ACCESSSWITCH__STATIC: + case ACCESSSWITCH2__STATIC: + { + if ((pSprite->cstat & 32768) || A_CheckSpriteFlags(spriteNum, SFLAG_NOLIGHT)) + { + if (actor[spriteNum].lightptr != NULL) + A_DeleteLight(spriteNum); + break; + } + + vec2_t const d = { sintable[(pSprite->ang+512)&2047]>>7, sintable[(pSprite->ang)&2047]>>7 }; + + pSprite->x += d.x; + pSprite->y += d.y; + + int16_t sectnum = pSprite->sectnum; + updatesector(pSprite->x, pSprite->y, §num); + + if ((unsigned) sectnum >= MAXSECTORS || pSprite->z > sector[sectnum].floorz || pSprite->z < sector[sectnum].ceilingz) + goto POOP; + + G_AddGameLight(0, spriteNum, (pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1, 512-ii*128, + ii==0 ? (172+(200<<8)+(104<<16)) : 216+(52<<8)+(20<<16), PR_LIGHT_PRIO_LOW); + + POOP: + pSprite->x -= d.x; + pSprite->y -= d.y; + } + break; + } + } + + switch (DYNAMICTILEMAP(pSprite->picnum)) + { + case ATOMICHEALTH__STATIC: + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), LIGHTRAD2(spriteNum, pSprite), 128+(128<<8)+(255<<16),PR_LIGHT_PRIO_HIGH_GAME); + break; + + case FIRE__STATIC: + case FIRE2__STATIC: + case BURNING__STATIC: + case BURNING2__STATIC: + { + uint32_t color; + int32_t jj; + + static int32_t savedfires[32][4]; // sectnum x y z + + /* + if (Actor[i].floorz - Actor[i].ceilingz < 128) break; + if (s->z > Actor[i].floorz+2048) break; + */ + + switch (pSprite->pal) + { + case 1: color = 128+(128<<8)+(255<<16); break; + case 2: color = 255+(48<<8)+(48<<16); break; + case 8: color = 48+(255<<8)+(48<<16); break; + default: color = 240+(160<<8)+(80<<16); break; + } + + for (jj=savedFires-1; jj>=0; jj--) + if (savedfires[jj][0]==pSprite->sectnum && savedfires[jj][1]==(pSprite->x>>3) && + savedfires[jj][2]==(pSprite->y>>3) && savedfires[jj][3]==(pSprite->z>>7)) + break; + + if (jj==-1 && savedFires<32) + { + jj = savedFires; + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), LIGHTRAD2(spriteNum, pSprite), color, PR_LIGHT_PRIO_HIGH_GAME); + savedfires[jj][0] = pSprite->sectnum; + savedfires[jj][1] = pSprite->x>>3; + savedfires[jj][2] = pSprite->y>>3; + savedfires[jj][3] = pSprite->z>>7; + savedFires++; + } + } + break; + + case OOZFILTER__STATIC: + if (pSprite->xrepeat > 4) + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), 4096, 176+(252<<8)+(120<<16),PR_LIGHT_PRIO_HIGH_GAME); + break; + case FLOORFLAME__STATIC: + case FIREBARREL__STATIC: + case FIREVASE__STATIC: + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<2), LIGHTRAD2(spriteNum, pSprite)>>1, 255+(95<<8),PR_LIGHT_PRIO_HIGH_GAME); + break; + + case EXPLOSION2__STATIC: + if (!actor[spriteNum].lightcount) + { + // XXX: This block gets CODEDUP'd too much. + int32_t x = ((sintable[(pSprite->ang+512)&2047])>>6); + int32_t y = ((sintable[(pSprite->ang)&2047])>>6); + + pSprite->x -= x; + pSprite->y -= y; + + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), LIGHTRAD(spriteNum, pSprite), 240+(160<<8)+(80<<16), + pSprite->yrepeat > 32 ? PR_LIGHT_PRIO_HIGH_GAME : PR_LIGHT_PRIO_LOW_GAME); + + pSprite->x += x; + pSprite->y += y; + } + break; + case FORCERIPPLE__STATIC: + case TRANSPORTERBEAM__STATIC: + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), LIGHTRAD(spriteNum, pSprite), 80+(80<<8)+(255<<16),PR_LIGHT_PRIO_LOW_GAME); + break; + case GROWSPARK__STATIC: + { + int32_t x = ((sintable[(pSprite->ang+512)&2047])>>6); + int32_t y = ((sintable[(pSprite->ang)&2047])>>6); + + pSprite->x -= x; + pSprite->y -= y; + + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), 1024, 216+(52<<8)+(20<<16),PR_LIGHT_PRIO_HIGH_GAME); + + pSprite->x += x; + pSprite->y += y; + } + break; + case SHRINKEREXPLOSION__STATIC: + { + int32_t x = ((sintable[(pSprite->ang+512)&2047])>>6); + int32_t y = ((sintable[(pSprite->ang)&2047])>>6); + + pSprite->x -= x; + pSprite->y -= y; + + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), 2048, 176+(252<<8)+(120<<16),PR_LIGHT_PRIO_HIGH_GAME); + + pSprite->x += x; + pSprite->y += y; + } + break; + case FREEZEBLAST__STATIC: + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), LIGHTRAD(spriteNum, pSprite)<<2, 72+(88<<8)+(140<<16),PR_LIGHT_PRIO_HIGH_GAME); + break; + case COOLEXPLOSION1__STATIC: + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), LIGHTRAD(spriteNum, pSprite)<<2, 128+(0<<8)+(255<<16),PR_LIGHT_PRIO_HIGH_GAME); + break; + case SHRINKSPARK__STATIC: + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), LIGHTRAD(spriteNum, pSprite), 176+(252<<8)+(120<<16),PR_LIGHT_PRIO_HIGH_GAME); + break; + case FIRELASER__STATIC: + if (pSprite->statnum == STAT_PROJECTILE) + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), 64 * pSprite->yrepeat, 255+(95<<8),PR_LIGHT_PRIO_LOW_GAME); + break; + case RPG__STATIC: + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), 128 * pSprite->yrepeat, 255+(95<<8),PR_LIGHT_PRIO_LOW_GAME); + break; + case SHOTSPARK1__STATIC: + if (actor[spriteNum].t_data[2] == 0) // check for first frame of action + { + int32_t x = ((sintable[(pSprite->ang+512)&2047])>>7); + int32_t y = ((sintable[(pSprite->ang)&2047])>>7); + + pSprite->x -= x; + pSprite->y -= y; + + G_AddGameLight(0, spriteNum, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), 8 * pSprite->yrepeat, 240+(160<<8)+(80<<16),PR_LIGHT_PRIO_LOW_GAME); + actor[spriteNum].lightcount = 1; + + pSprite->x += x; + pSprite->y += y; + } + break; + } + } +} +#endif // POLYMER + +void A_PlayAlertSound(int spriteNum) +{ + if (RR) + { + if (sprite[spriteNum].extra > 0) + { + switch (DYNAMICTILEMAP(PN(spriteNum))) + { + case COOT__STATICRR: if (!RRRA || (krand2()&3) == 2) A_PlaySound(PRED_RECOG, spriteNum); break; + case LTH__STATICRR: break; + case BILLYCOCK__STATICRR: + case BILLYRAY__STATICRR: + case BRAYSNIPER__STATICRR: A_PlaySound(PIG_RECOG, spriteNum); break; + case DOGRUN__STATICRR: + case HULK__STATICRR: + case HEN__STATICRR: + case DRONE__STATICRR: + case PIG__STATICRR: + case RECON__STATICRR: + case MINION__STATICRR: + case COW__STATICRR: + case VIXEN__STATICRR: + case RABBIT__STATICRR: break; + } + } + return; + } + if (sprite[spriteNum].extra > 0) + { + switch (DYNAMICTILEMAP(PN(spriteNum))) + { + case LIZTROOPONTOILET__STATIC: + case LIZTROOPJUSTSIT__STATIC: + case LIZTROOPSHOOT__STATIC: + case LIZTROOPJETPACK__STATIC: + case LIZTROOPDUCKING__STATIC: + case LIZTROOPRUNNING__STATIC: + case LIZTROOP__STATIC: A_PlaySound(PRED_RECOG, spriteNum); break; + case LIZMAN__STATIC: + case LIZMANSPITTING__STATIC: + case LIZMANFEEDING__STATIC: + case LIZMANJUMP__STATIC: A_PlaySound(CAPT_RECOG, spriteNum); break; + case PIGCOP__STATIC: + case PIGCOPDIVE__STATIC: A_PlaySound(PIG_RECOG, spriteNum); break; + case RECON__STATIC: A_PlaySound(RECO_RECOG, spriteNum); break; + case DRONE__STATIC: A_PlaySound(DRON_RECOG, spriteNum); break; + case COMMANDER__STATIC: + case COMMANDERSTAYPUT__STATIC: A_PlaySound(COMM_RECOG, spriteNum); break; + case ORGANTIC__STATIC: A_PlaySound(TURR_RECOG, spriteNum); break; + case OCTABRAIN__STATIC: + case OCTABRAINSTAYPUT__STATIC: A_PlaySound(OCTA_RECOG, spriteNum); break; + case BOSS1__STATIC: S_PlaySound(BOS1_RECOG); break; + case BOSS2__STATIC: S_PlaySound((sprite[spriteNum].pal == 1) ? BOS2_RECOG : WHIPYOURASS); break; + case BOSS3__STATIC: S_PlaySound((sprite[spriteNum].pal == 1) ? BOS3_RECOG : RIPHEADNECK); break; + case BOSS4__STATIC: + case BOSS4STAYPUT__STATIC: if (sprite[spriteNum].pal == 1) S_PlaySound(BOS4_RECOG); S_PlaySound(BOSS4_FIRSTSEE); break; + case GREENSLIME__STATIC: A_PlaySound(SLIM_RECOG, spriteNum); break; + } + } +} + +int A_CheckSwitchTile(int spriteNum) +{ + // picnum 0 would oob in the switch below, + + if (PN(spriteNum) <= 0) + return 0; + + // MULTISWITCH has 4 states so deal with it separately, + // ACCESSSWITCH and ACCESSSWITCH2 are only active in one state so deal with + // them separately. + + if ((PN(spriteNum) >= MULTISWITCH && PN(spriteNum) <= MULTISWITCH + 3) || (PN(spriteNum) == ACCESSSWITCH || PN(spriteNum) == ACCESSSWITCH2)) + return 1; + + if (RRRA && PN(spriteNum) >= MULTISWITCH2 && PN(spriteNum) <= MULTISWITCH2 + 3) + return 1; + + // Loop to catch both states of switches. + for (bssize_t j=1; j>=0; j--) + { + switch (DYNAMICTILEMAP(PN(spriteNum)-j)) + { + case RRTILE8464__STATICRR: + if (RRRA) return 1; + break; + case NUKEBUTTON__STATIC: + if (RR) return 1; + break; + case HANDPRINTSWITCH__STATIC: + case ALIENSWITCH__STATIC: + case MULTISWITCH__STATIC: + case PULLSWITCH__STATIC: + case HANDSWITCH__STATIC: + case SLOTDOOR__STATIC: + case LIGHTSWITCH__STATIC: + case SPACELIGHTSWITCH__STATIC: + case SPACEDOORSWITCH__STATIC: + case FRANKENSTINESWITCH__STATIC: + case LIGHTSWITCH2__STATIC: + case POWERSWITCH1__STATIC: + case LOCKSWITCH1__STATIC: + case POWERSWITCH2__STATIC: + case DIPSWITCH__STATIC: + case DIPSWITCH2__STATIC: + case TECHSWITCH__STATIC: + case DIPSWITCH3__STATIC: + return 1; + } + } + + return 0; +} + +void G_RefreshLights(void) +{ +#ifdef POLYMER + if (Numsprites && videoGetRenderMode() == REND_POLYMER) + { + int statNum = 0; + + do + { + int spriteNum = headspritestat[statNum++]; + + while (spriteNum >= 0) + { + A_DoLight(spriteNum); + spriteNum = nextspritestat[spriteNum]; + } + } + while (statNum < MAXSTATUS); + } +#endif +} + +void G_MoveWorld(void) +{ + extern double g_moveActorsTime, g_moveWorldTime; + const double worldTime = timerGetHiTicks(); + + G_MoveZombieActors(); //ST 2 + G_MoveWeapons(); //ST 4 + G_MoveTransports(); //ST 9 + + G_MovePlayers(); //ST 10 + G_MoveFallers(); //ST 12 + G_MoveMisc(); //ST 5 + + const double actorsTime = timerGetHiTicks(); + + G_MoveActors(); //ST 1 + + g_moveActorsTime = (1-0.033)*g_moveActorsTime + 0.033*(timerGetHiTicks()-actorsTime); + + // XXX: Has to be before effectors, in particular movers? + // TODO: lights in moving sectors ought to be interpolated + G_DoEffectorLights(); + G_MoveEffectors(); //ST 3 + G_MoveStandables(); //ST 6 + + G_RefreshLights(); + G_DoSectorAnimations(); + G_MoveFX(); //ST 11 + + if (RR && numplayers < 2 && g_thunderOn) + G_Thunder(); + + g_moveWorldTime = (1-0.033)*g_moveWorldTime + 0.033*(timerGetHiTicks()-worldTime); +} diff --git a/source/rr/src/actors.h b/source/rr/src/actors.h new file mode 100644 index 000000000..fbe92fec4 --- /dev/null +++ b/source/rr/src/actors.h @@ -0,0 +1,350 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef actors_h_ +#define actors_h_ + +#include "player.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAXSLEEPDIST 16384 +#define SLEEPTIME 1536 +#define ZOFFSET (1<<8) +#define ZOFFSET2 (16<<8) +#define ZOFFSET3 (8<<8) +#define ZOFFSET4 (12<<8) +#define ZOFFSET5 (32<<8) +#define ZOFFSET6 (4<<8) + +#define ACTOR_MAXFALLINGZVEL 6144 +#define ACTOR_ONWATER_ADDZ (24<<8) + +// KEEPINSYNC lunatic/con_lang.lua +#define STAT_DEFAULT 0 +#define STAT_ACTOR 1 +#define STAT_ZOMBIEACTOR 2 +#define STAT_EFFECTOR 3 +#define STAT_PROJECTILE 4 +#define STAT_MISC 5 +#define STAT_STANDABLE 6 +#define STAT_LOCATOR 7 +#define STAT_ACTIVATOR 8 +#define STAT_TRANSPORT 9 +#define STAT_PLAYER 10 +#define STAT_FX 11 +#define STAT_FALLER 12 +#define STAT_DUMMYPLAYER 13 +#define STAT_LIGHT 14 +#define STAT_RAROR 15 +#define STAT_NETALLOC MAXSTATUS-1 + + +// Defines the motion characteristics of an actor +enum amoveflags_t +{ + face_player = 1, + geth = 2, + getv = 4, + random_angle = 8, + face_player_slow = 16, + spin = 32, + face_player_smart = 64, + fleeenemy = 128, + jumptoplayer_only = 256, + jumptoplayer_bits = 257, // NOTE: two bits set! + seekplayer = 512, + furthestdir = 1024, + dodgebullet = 4096, + justjump2 = 8192, + windang = 16384, + antifaceplayerslow = 32768 +}; + +// Defines for 'useractor' keyword +enum uactortypes_t +{ + notenemy, + enemy, + enemystayput +}; + +// These macros are there to give names to the t_data[]/T*/vm.g_t[] indices +// when used with actors. Greppability of source code is certainly a virtue. +#define AC_COUNT(t) ((t)[0]) /* the actor's count */ +/* The ID of the actor's current move. In C-CON, the bytecode offset to the + * move composite: */ +#define AC_MOVE_ID(t) ((t)[1]) +#define AC_ACTION_COUNT(t) ((t)[2]) /* the actor's action count */ +#define AC_CURFRAME(t) ((t)[3]) /* the actor's current frame offset */ +/* The ID of the actor's current action. In C-CON, the bytecode offset to the + * action composite: */ +#define AC_ACTION_ID(t) ((t)[4]) +#define AC_AI_ID(t) ((t)[5]) /* the ID of the actor's current ai */ + +enum actionparams +{ + ACTION_STARTFRAME = 0, + ACTION_NUMFRAMES, + ACTION_VIEWTYPE, + ACTION_INCVAL, + ACTION_DELAY, + ACTION_FLAGS, + ACTION_PARAM_COUNT, +}; + +enum actionflags +{ + AF_VIEWPOINT = 1u<<0u, +}; + +// Select an actor's actiontics and movflags locations depending on +// whether we compile the Lunatic build. +//: sprite pointer +// : actor_t pointer +# define AC_ACTIONTICS(spr, a) ((spr)->lotag) +# define AC_MOVFLAGS(spr, a) ((spr)->hitag) + +// (+ 40 16 16 4 8 6 8 6 4 20) +#pragma pack(push, 1) +typedef struct +{ + int32_t t_data[10]; // 40b sometimes used to hold offsets to con code + + int32_t flags; // 4b + vec3_t bpos; // 12b + int32_t floorz, ceilingz; // 8b + vec2_t lastv; // 8b + int16_t picnum, ang, extra, owner; // 8b + int16_t movflag, tempang, timetosleep; // 6b + int16_t actorstayput; // 2b + + uint8_t cgg, lasttransport; // 2b + // NOTE: 'dispicnum' is updated every frame, not in sync with game tics! + int16_t dispicnum; // 2b + +#ifdef POLYMER + int16_t lightId, lightmaxrange; // 4b + _prlight *lightptr; // 4b/8b aligned on 96 bytes + uint8_t lightcount, filler[3]; +#endif +} actor_t; + +// this struct needs to match the beginning of actor_t above +typedef struct +{ + int32_t t_data[10]; // 40b sometimes used to hold offsets to con code + + int32_t flags; // 4b + vec3_t bpos; // 12b + int32_t floorz, ceilingz; // 8b + vec2_t lastv; // 8b + int16_t picnum, ang, extra, owner; // 8b + int16_t movflag, tempang, timetosleep; // 6b + int16_t actorstayput; + + uint8_t cgg, lasttransport; + + spritetype sprite; + int16_t netIndex; +} netactor_t; +#pragma pack(pop) + +typedef struct +{ + intptr_t *execPtr; // pointer to CON script for this tile, formerly actorscrptr + intptr_t *loadPtr; // pointer to load time CON script, formerly actorLoadEventScrPtr or something + projectile_t *proj; + projectile_t *defproj; + uint32_t flags; // formerly SpriteFlags, ActorType + int32_t cacherange; // formerly SpriteCache +} tiledata_t; + + +// KEEPINSYNC lunatic/con_lang.lua +enum sflags_t +{ + SFLAG_SHADOW = 0x00000001, + SFLAG_NVG = 0x00000002, + SFLAG_NOSHADE = 0x00000004, + SFLAG_PROJECTILE = 0x00000008, + SFLAG_DECAL = 0x00000010, + SFLAG_BADGUY = 0x00000020, + SFLAG_NOPAL = 0x00000040, + SFLAG_NOEVENTCODE = 0x00000080, + SFLAG_NOLIGHT = 0x00000100, + SFLAG_USEACTIVATOR = 0x00000200, + SFLAG_NULL = 0x00000400, // null sprite in multiplayer + SFLAG_NOCLIP = 0x00000800, // clipmove it with cliptype 0 + SFLAG_NOFLOORSHADOW = 0x00001000, // for temp. internal use, per-tile flag not checked + SFLAG_SMOOTHMOVE = 0x00002000, + SFLAG_NOTELEPORT = 0x00004000, + SFLAG_BADGUYSTAYPUT = 0x00008000, + SFLAG_CACHE = 0x00010000, + // rotation-fixed wrt a pivot point to prevent position diverging due to + // roundoff error accumulation: + SFLAG_ROTFIXED = 0x00020000, + SFLAG_HARDCODED_BADGUY = 0x00040000, + SFLAG_DIDNOSE7WATER = 0x00080000, // used temporarily + SFLAG_NODAMAGEPUSH = 0x00100000, + SFLAG_NOWATERDIP = 0x00200000, + SFLAG_HURTSPAWNBLOOD = 0x00400000, + SFLAG_GREENSLIMEFOOD = 0x00800000, + SFLAG_REALCLIPDIST = 0x01000000, + SFLAG_WAKEUPBADGUYS = 0x02000000, + SFLAG_DAMAGEEVENT = 0x04000000, + SFLAG_BADGUY_TILE = 0x08000000, + SFLAG_KILLCOUNT = 0x10000000, + SFLAG_NOCANSEECHECK = 0x20000000, +}; + +// Custom projectiles "workslike" flags. +// XXX: Currently not predefined from CON. +enum pflags_t +{ + PROJECTILE_HITSCAN = 0x00000001, + PROJECTILE_RPG = 0x00000002, + PROJECTILE_BOUNCESOFFWALLS = 0x00000004, + PROJECTILE_BOUNCESOFFMIRRORS = 0x00000008, + PROJECTILE_KNEE = 0x00000010, + PROJECTILE_WATERBUBBLES = 0x00000020, + PROJECTILE_TIMED = 0x00000040, + PROJECTILE_BOUNCESOFFSPRITES = 0x00000080, + PROJECTILE_SPIT = 0x00000100, + PROJECTILE_COOLEXPLOSION1 = 0x00000200, + PROJECTILE_BLOOD = 0x00000400, + PROJECTILE_LOSESVELOCITY = 0x00000800, + PROJECTILE_NOAIM = 0x00001000, + PROJECTILE_RANDDECALSIZE = 0x00002000, + PROJECTILE_EXPLODEONTIMER = 0x00004000, + PROJECTILE_RPG_IMPACT = 0x00008000, + PROJECTILE_RADIUS_PICNUM = 0x00010000, + PROJECTILE_ACCURATE_AUTOAIM = 0x00020000, + PROJECTILE_FORCEIMPACT = 0x00040000, + PROJECTILE_REALCLIPDIST = 0x00080000, + PROJECTILE_ACCURATE = 0x00100000, + PROJECTILE_NOSETOWNERSHADE = 0x00200000, + PROJECTILE_RPG_IMPACT_DAMAGE = 0x00400000, + PROJECTILE_MOVED = 0x80000000, // internal flag, do not document + PROJECTILE_TYPE_MASK = PROJECTILE_HITSCAN | PROJECTILE_RPG | PROJECTILE_KNEE | PROJECTILE_BLOOD, +}; + +extern tiledata_t g_tile[MAXTILES]; +extern actor_t actor[MAXSPRITES]; +extern int32_t block_deletesprite; +extern int32_t g_noEnemies; +extern int32_t otherp; +extern int32_t ticrandomseed; +extern int g_canSeePlayer; + + +int A_CheckNoSE7Water(uspritetype const *pSprite, int sectNum, int sectLotag, int32_t *pOther); +int A_CheckSwitchTile(int spriteNum); +int A_IncurDamage(int spriteNum); +void A_AddToDeleteQueue(int spriteNum); +void A_DeleteSprite(int spriteNum); +void A_DoGuts(int spriteNum, int tileNum, int spawnCnt); +void A_DoGutsDir(int spriteNum, int tileNum, int spawnCnt); +void A_MoveCyclers(void); +void A_MoveDummyPlayers(void); +void A_MoveSector(int spriteNum); +void A_PlayAlertSound(int spriteNum); +void A_RadiusDamage(int spriteNum, int blastRadius, int dmg1, int dmg2, int dmg3, int dmg4); +void A_SpawnMultiple(int spriteNum, int tileNum, int spawnCnt); +void A_ResetLanePics(void); + +int G_SetInterpolation(int32_t *posptr); +void G_AddGameLight(int lightRadius, int spriteNum, int zOffset, int lightRange, int lightColor, int lightPrio); +void G_ClearCameraView(DukePlayer_t *ps); +void G_DoInterpolations(int smoothRatio); +void G_MoveWorld(void); +void G_RefreshLights(void); +void G_StopInterpolation(const int32_t *posptr); + +// PK 20110701: changed input argument: int32_t i (== sprite, whose sectnum...) --> sectnum directly +void Sect_ToggleInterpolation(int sectnum, int setInterpolation); +static FORCE_INLINE void Sect_ClearInterpolation(int sectnum) { Sect_ToggleInterpolation(sectnum, 0); } +static FORCE_INLINE void Sect_SetInterpolation(int sectnum) { Sect_ToggleInterpolation(sectnum, 1); } + +#if KRANDDEBUG +# define ACTOR_INLINE __fastcall +# define ACTOR_INLINE_HEADER extern __fastcall +#else +# define ACTOR_INLINE EXTERN_INLINE +# define ACTOR_INLINE_HEADER EXTERN_INLINE_HEADER +#endif + +extern int32_t A_MoveSprite(int32_t spritenum, vec3_t const * change, uint32_t cliptype); +ACTOR_INLINE_HEADER int A_CheckEnemyTile(int tileNum); +ACTOR_INLINE_HEADER int A_SetSprite(int spriteNum, uint32_t cliptype); + +EXTERN_INLINE_HEADER int G_CheckForSpaceCeiling(int sectnum); +EXTERN_INLINE_HEADER int G_CheckForSpaceFloor(int sectnum); + +EXTERN_INLINE_HEADER int A_CheckEnemySprite(void const * s); + +#ifdef __cplusplus +} +#endif + +#if defined actors_c_ || !defined DISABLE_INLINING + +# if !KRANDDEBUG || (KRANDDEBUG && defined actors_c_) + +ACTOR_INLINE int A_CheckEnemyTile(int const tileNum) +{ + return ((g_tile[tileNum].flags & (SFLAG_BADGUY_TILE | SFLAG_BADGUY)) != 0); +} + +ACTOR_INLINE int A_SetSprite(int const spriteNum, uint32_t cliptype) +{ + vec3_t davect = { (sprite[spriteNum].xvel * (sintable[(sprite[spriteNum].ang + 512) & 2047])) >> 14, + (sprite[spriteNum].xvel * (sintable[sprite[spriteNum].ang & 2047])) >> 14, sprite[spriteNum].zvel }; + return (A_MoveSprite(spriteNum, &davect, cliptype) == 0); +} + +# endif + +# include "namesdyn.h" + +EXTERN_INLINE int G_CheckForSpaceCeiling(int const sectnum) +{ + return ((sector[sectnum].ceilingstat&1) && sector[sectnum].ceilingpal == 0 && + (sector[sectnum].ceilingpicnum==MOONSKY1 || sector[sectnum].ceilingpicnum==BIGORBIT1)); +} + +EXTERN_INLINE int G_CheckForSpaceFloor(int const sectnum) +{ + return ((sector[sectnum].floorstat&1) && sector[sectnum].ceilingpal == 0 && + (sector[sectnum].floorpicnum==MOONSKY1 || sector[sectnum].floorpicnum==BIGORBIT1)); +} + +EXTERN_INLINE int A_CheckEnemySprite(void const * const pSprite) +{ + return A_CheckEnemyTile(((uspritetype const *) pSprite)->picnum); +} + +#endif + +#endif diff --git a/source/rr/src/android.h b/source/rr/src/android.h new file mode 100644 index 000000000..eebf69422 --- /dev/null +++ b/source/rr/src/android.h @@ -0,0 +1,44 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef android_h_ +#define android_h_ + +#ifdef __ANDROID__ +#include "compat.h" +#include "control.h" +#include "in_android.h" + +//extern int android_sample_rate; + +//extern int android_audio_buffer_size; + + +extern void CONTROL_Android_ClearButton(int32_t whichbutton); +extern void CONTROL_Android_PollDevices(ControlInfo *info); +extern void CONTROL_Android_SetLastWeapon(int w); + +extern void CONTROL_Android_ScrollMap(int32_t *angle,int32_t *x, int32_t *y, uint16_t *zoom ); + +#endif + +#endif diff --git a/source/rr/src/anim.cpp b/source/rr/src/anim.cpp new file mode 100644 index 000000000..7062884f0 --- /dev/null +++ b/source/rr/src/anim.cpp @@ -0,0 +1,598 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "baselayer.h" +#include "renderlayer.h" +#include "duke3d.h" +#include "animlib.h" +#include "mouse.h" +#include "compat.h" +#include "input.h" + +#include "anim.h" + +#ifdef USE_LIBVPX +# include "animvpx.h" +#endif + +// animsound_t.sound +EDUKE32_STATIC_ASSERT(INT16_MAX >= MAXSOUNDS); + +hashtable_t h_dukeanim = { 8, NULL }; +dukeanim_t * g_animPtr; + +dukeanim_t *Anim_Find(const char *s) +{ + intptr_t ptr = hash_findcase(&h_dukeanim, s); + + if (ptr == -1) + { + int const siz = Bstrlen(s) + 5; + char * const str = (char *)Xcalloc(1, siz); + + maybe_append_ext(str, siz, s, ".anm"); + ptr = hash_findcase(&h_dukeanim, str); + + if (ptr == -1) + { + maybe_append_ext(str, siz, s, ".ivf"); + ptr = hash_findcase(&h_dukeanim, str); + } + + Bfree(str); + } + + return (dukeanim_t *)(ptr == -1 ? NULL : (dukeanim_t *)ptr); +} + +dukeanim_t * Anim_Create(char const * fn) +{ + dukeanim_t * anim = (dukeanim_t *)Xcalloc(1, sizeof(dukeanim_t)); + + hash_add(&h_dukeanim, fn, (intptr_t)anim, 0); + + return anim; +} + +#ifdef DYNSOUNDREMAP_ENABLE +static int32_t const StopAllSounds = -1; +#else +# define StopAllSounds -1 +#endif + +void Anim_Init(void) +{ + hash_init(&h_dukeanim); + + + struct defaultanmsound { +#ifdef DYNSOUNDREMAP_ENABLE + int32_t const & sound; +#else + int16_t sound; +#endif + uint8_t frame; + }; + + static defaultanmsound const logo[] = + { + { FLY_BY, 1 }, + { PIPEBOMB_EXPLODE, 19 }, + }; + + static defaultanmsound const cineov2[] = + { + { WIND_AMBIENCE, 1 }, + { ENDSEQVOL2SND1, 26 }, + { ENDSEQVOL2SND2, 36 }, + { THUD, 54 }, + { ENDSEQVOL2SND3, 62 }, + { ENDSEQVOL2SND4, 75 }, + { ENDSEQVOL2SND5, 81 }, + { ENDSEQVOL2SND6, 115 }, + { ENDSEQVOL2SND7, 124 }, + }; + + static defaultanmsound const cineov3[] = + { + { WIND_REPEAT, 1 }, + { DUKE_GRUNT, 98 }, + { THUD, 82+20 }, + { SQUISHED, 82+20 }, + { ENDSEQVOL3SND3, 104+20 }, + { ENDSEQVOL3SND2, 114+20 }, + { PIPEBOMB_EXPLODE, 158 }, + }; + + static defaultanmsound const vol42a[] = + { + { INTRO4_B, 1 }, + { SHORT_CIRCUIT, 12 }, + { INTRO4_5, 18 }, + { SHORT_CIRCUIT, 34 }, + }; + + static defaultanmsound const vol41a[] = + { + { INTRO4_1, 1 }, + { INTRO4_3, 7 }, + { INTRO4_2, 12 }, + { INTRO4_4, 26 }, + }; + + static defaultanmsound const vol43a[] = + { + { INTRO4_6, 10 }, + }; + + static defaultanmsound const vol4e1[] = + { + { DUKE_UNDERWATER, 3 }, + { VOL4ENDSND1, 35 }, + }; + + static defaultanmsound const vol4e2[] = + { + { DUKE_UNDERWATER, 11 }, + { VOL4ENDSND1, 20 }, + { VOL4ENDSND2, 39 }, + { StopAllSounds, 50 }, + }; + + static defaultanmsound const vol4e3[] = + { + { BOSS4_DEADSPEECH, 1 }, + { VOL4ENDSND1, 40 }, + { DUKE_UNDERWATER, 40 }, + { BIGBANG, 50 }, + }; + + static defaultanmsound const rr_intro[] = + { + { 29, 1 }, + }; + + static defaultanmsound const redneck[] = + { + { 478, 1 }, + }; + + static defaultanmsound const xatlogo[] = + { + { 479, 1 }, + }; + + static defaultanmsound const turdmov[] = + { + { 82, 1 }, + }; + + static defaultanmsound const rr_outro[] = + { + { 35, 1 }, + }; + + struct defaultanm { + char const *fn; + defaultanmsound const *sounds; + uint8_t numsounds; + uint8_t fdelay; + }; + +#define anmsnd(x) (x), ARRAY_SIZE(x) + static defaultanm const anms[] = + { + { "logo.anm", anmsnd(logo), 9 }, + { "3dr.anm", NULL, 0, 10 }, + { "vol4e1.anm", anmsnd(vol4e1), 10 }, + { "vol4e2.anm", anmsnd(vol4e2), 14 }, + { "vol4e3.anm", anmsnd(vol4e3), 10 }, + { "vol41a.anm", anmsnd(vol41a), 14 }, + { "vol42a.anm", anmsnd(vol42a), 18 }, + { "vol43a.anm", anmsnd(vol43a), 10 }, + { "duketeam.anm", NULL, 0, 10 }, + { "radlogo.anm", NULL, 0, 10 }, + { "cineov2.anm", anmsnd(cineov2), 18 }, + { "cineov3.anm", anmsnd(cineov3), 10 }, + { "rr_intro.anm", anmsnd(rr_intro), 9 }, + { "redneck.anm", anmsnd(redneck), 9 }, + { "xatlogo.anm", anmsnd(xatlogo), 9 }, + { "turdmov.anm", anmsnd(turdmov), 9 }, + { "rr_outro.anm", anmsnd(rr_outro), 9 }, + { "lvl1.anm", NULL, 0, 20 }, + { "lvl2.anm", NULL, 0, 20 }, + { "lvl3.anm", NULL, 0, 20 }, + { "lvl4.anm", NULL, 0, 20 }, + { "lvl5.anm", NULL, 0, 20 }, + { "lvl6.anm", NULL, 0, 20 }, + { "lvl7.anm", NULL, 0, 20 }, + { "lvl8.anm", NULL, 0, 20 }, + { "lvl9.anm", NULL, 0, 20 }, + { "lvl10.anm", NULL, 0, 20 }, + { "lvl11.anm", NULL, 0, 20 }, + { "lvl12.anm", NULL, 0, 20 }, + { "lvl13.anm", NULL, 0, 20 }, + }; +#undef anmsnd + + for (defaultanm const & anm : anms) + { + dukeanim_t * anim = Anim_Create(anm.fn); + anim->framedelay = anm.fdelay; + + if (anm.numsounds) + { + anim->sounds = (animsound_t *)Xmalloc(anm.numsounds * sizeof(animsound_t)); + size_t const numsounds = anm.numsounds; + for (size_t i = 0; i < numsounds; ++i) + { + defaultanmsound const & src = anm.sounds[i]; + animsound_t & dst = anim->sounds[i]; + + dst.sound = src.sound; + dst.frame = src.frame; + } + anim->numsounds = numsounds; + } + + anim->frameflags = 0; + } +} + +int32_t Anim_Play(const char *fn) +{ + dukeanim_t *anim = Anim_Find(fn); + + if (!anim) + { + OSD_Printf("Animation %s is undefined!\n", fn); + return 0; + } + + uint16_t soundidx = 0; // custom anim sounds + int32_t running = 1, i; + + I_ClearAllInput(); + +#ifdef USE_LIBVPX + uint16_t framenum = 0; + while (videoGetRenderMode() >= REND_POLYMOST) // if, really + { + char const * dot = Bstrrchr(fn, '.'); + if (!dot) + break; + + dukeanim_t const * origanim = anim; + int32_t handle = -1; + if (!Bstrcmp(dot, ".ivf")) + { + handle = kopen4loadfrommod(fn, 0); + if (handle == -1) + break; + } + else + { + char vpxfn[BMAX_PATH]; + Bstrncpyz(vpxfn, fn, BMAX_PATH); + + ptrdiff_t dotpos = dot - fn; + if (dotpos + 4 >= BMAX_PATH) + break; + + char *vpxfndot = vpxfn + dotpos; + vpxfndot[1] = 'i'; + vpxfndot[2] = 'v'; + vpxfndot[3] = 'f'; + vpxfndot[4] = '\0'; + + handle = kopen4loadfrommod(vpxfn, 0); + if (handle == -1) + break; + + anim = Anim_Find(vpxfn); + } + + animvpx_ivf_header_t info; + i = animvpx_read_ivf_header(handle, &info); + + if (i) + { + OSD_Printf("Failed reading IVF file: %s\n", animvpx_read_ivf_header_errmsg[i]); + kclose(handle); + return 0; + } + + if (anim) + animvpx_setup_glstate(anim->frameflags); + else + animvpx_setup_glstate(origanim->frameflags); + + animvpx_codec_ctx codec; + + if (animvpx_init_codec(&info, handle, &codec)) + { + OSD_Printf("Error initializing VPX codec.\n"); + animvpx_restore_glstate(); + kclose(handle); + return 0; + } + + + uint32_t const convnumer = 120 * info.fpsdenom; + uint32_t const convdenom = info.fpsnumer * origanim->framedelay; + + uint32_t const msecsperframe = scale(info.fpsdenom, 1000, info.fpsnumer); + uint32_t nextframetime = timerGetTicks(); + uint8_t *pic; + + // OSD_Printf("msecs per frame: %d\n", msecsperframe); + + do + { + nextframetime += msecsperframe; + + i = animvpx_nextpic(&codec, &pic); + if (i) + { + OSD_Printf("Failed getting next pic: %s\n", animvpx_nextpic_errmsg[i]); + if (codec.errmsg) + { + OSD_Printf(" %s\n", codec.errmsg); + if (codec.errmsg_detail) + OSD_Printf(" detail: %s\n", codec.errmsg_detail); + } + break; + } + + if (!pic) + break; // no more pics! + + videoClearScreen(0); + + ototalclock = totalclock + 1; // pause game like ANMs + + if (anim) + { + if (anim->frameaspect1 == 0 || anim->frameaspect2 == 0) + animvpx_render_frame(&codec, 0); + else + animvpx_render_frame(&codec, anim->frameaspect1 / anim->frameaspect2); + } + else + { + if (origanim->frameaspect1 == 0 || origanim->frameaspect2 == 0) + animvpx_render_frame(&codec, 0); + else + animvpx_render_frame(&codec, origanim->frameaspect1 / origanim->frameaspect2); + } + + // after rendering the frame but before displaying: maybe play sound... + framenum++; + if (anim) + { + while (soundidx < anim->numsounds && anim->sounds[soundidx].frame <= framenum) + { + int16_t sound = anim->sounds[soundidx].sound; + if (sound == -1) + FX_StopAllSounds(); + else + S_PlaySound(sound); + + soundidx++; + } + } + else + { + uint16_t convframenum = scale(framenum, convnumer, convdenom); + while (soundidx < origanim->numsounds && origanim->sounds[soundidx].frame <= convframenum) + { + int16_t sound = origanim->sounds[soundidx].sound; + if (sound == -1) + FX_StopAllSounds(); + else + S_PlaySound(sound); + + soundidx++; + } + } + + // this and showframe() instead of nextpage() are so that + // nobody tramples on our carefully set up GL state! + palfadedelta = 0; + videoShowFrame(0); + + // I_ClearAllInput(); + + do + { + G_HandleAsync(); + + if (I_CheckAllInput()) + { + running = 0; + break; + } + } while (timerGetTicks() < nextframetime); + } while (running); + + animvpx_print_stats(&codec); + + // + kclose(handle); + animvpx_restore_glstate(); + animvpx_uninit_codec(&codec); + + I_ClearAllInput(); + return !running; // done with playing VP8! + } +#endif +// ANM playback --- v v v --- + +#ifdef USE_OPENGL + int32_t ogltexfiltermode = gltexfiltermode; +#endif + int32_t handle = kopen4load(fn, 0); + + if (handle == -1) + return 0; + + int32_t length = kfilelength(handle); + + if (length <= 4) + { + OSD_Printf("Warning: skipping playback of empty ANM file \"%s\".\n", fn); + goto end_anim; + } + + walock[TILE_ANIM] = 219; + anim->animlock = 1; + + if (!anim->animbuf) + cacheAllocateBlock((intptr_t *)&anim->animbuf, length + 1, &anim->animlock); + + tilesiz[TILE_ANIM].x = 200; + tilesiz[TILE_ANIM].y = 320; + + kread(handle, anim->animbuf, length); + kclose(handle); + + uint32_t firstfour; + Bmemcpy(&firstfour, anim->animbuf, 4); + + // "DKIF" (.ivf) + if (firstfour == B_LITTLE32(0x46494B44)) + goto end_anim; + + int32_t numframes; + + // "LPF " (.anm) + if (firstfour != B_LITTLE32(0x2046504C) || + ANIM_LoadAnim(anim->animbuf, length) < 0 || + (numframes = ANIM_NumFrames()) <= 0) + { + // XXX: ANM_LoadAnim() still checks less than the bare minimum, + // e.g. ANM file could still be too small and not contain any frames. + OSD_Printf("Error: malformed ANM file \"%s\".\n", fn); + goto end_anim; + } + + paletteSetColorTable(ANIMPAL, ANIM_GetPalette()); + + // setpalette(0L,256L,tempbuf); + // setbrightness(ud.brightness>>2,tempbuf,2); + P_SetGamePalette(g_player[myconnectindex].ps, ANIMPAL, 8 + 2); + + timerUpdate(); + ototalclock = totalclock; + + i = 1; + int32_t frametime; frametime = 0; + + do + { + if (i > 4 && totalclock > frametime + 60) + { + OSD_Printf("WARNING: slowdown in %s, skipping playback\n", fn); + goto end_anim_restore_gl; + } + + G_HandleAsync(); + + if (totalclock < ototalclock - 1) + continue; + + waloff[TILE_ANIM] = (intptr_t)ANIM_DrawFrame(i); + tileInvalidate(TILE_ANIM, 0, 1 << 4); // JBF 20031228 + + if (I_CheckAllInput()) + { + running = 0; + goto end_anim_restore_gl; + } + + if (g_restorePalette == 1) + { + P_SetGamePalette(g_player[myconnectindex].ps, ANIMPAL, 0); + g_restorePalette = 0; + } + + frametime = totalclock; + + videoClearScreen(0); + + int32_t z; + if (anim->frameaspect1 > 0 && anim->frameaspect2 > 0 && ((anim->frameaspect1 / anim->frameaspect2) != (tilesiz[TILE_ANIM].y / (tilesiz[TILE_ANIM].x * 1.2)))) + { + int32_t const oyxaspect = yxaspect; + if ((anim->frameaspect1 / anim->frameaspect2) >= ((decltype(anim->frameaspect1))xdim / ydim)) + z = divscale16(320, tilesiz[TILE_ANIM].y); + else + z = divscale16(lrint(320 * ydim * anim->frameaspect1), lrint(tilesiz[TILE_ANIM].y * xdim * anim->frameaspect2)); + int32_t aspect = divscale16(lrint(tilesiz[TILE_ANIM].y * anim->frameaspect2), lrint(tilesiz[TILE_ANIM].x * anim->frameaspect1)); + renderSetAspect(viewingrange, aspect); + rotatesprite_fs(160<<16, 100<<16, z, 512, TILE_ANIM, 0, 0, 2|4|8|64|1024); + renderSetAspect(viewingrange, oyxaspect); + } + else + { + if ((tilesiz[TILE_ANIM].y / (tilesiz[TILE_ANIM].x * 1.2f)) > (1.f * xdim / ydim)) + z = divscale16(320 * xdim * 3, tilesiz[TILE_ANIM].y * ydim * 4); + else + z = divscale16(200, tilesiz[TILE_ANIM].x); + rotatesprite_fs(160<<16, 100<<16, z, 512, TILE_ANIM, 0, 0, 2|4|8|64); + } + + g_animPtr = NULL; + + videoNextPage(); + + I_ClearAllInput(); + + ototalclock += anim->framedelay; + + while (soundidx < anim->numsounds && anim->sounds[soundidx].frame <= (uint16_t)i) + { + int16_t sound = anim->sounds[soundidx].sound; + if (sound == -1) + FX_StopAllSounds(); + else + S_PlaySound(sound); + + soundidx++; + } + + ++i; + } while (i < numframes); + +end_anim_restore_gl: +#ifdef USE_OPENGL + gltexfiltermode = ogltexfiltermode; + gltexapplyprops(); +#endif +end_anim: + I_ClearAllInput(); + ANIM_FreeAnim(); + walock[TILE_ANIM] = 1; + anim->animlock = 0; + + return !running; +} diff --git a/source/rr/src/anim.h b/source/rr/src/anim.h new file mode 100644 index 000000000..63a141c71 --- /dev/null +++ b/source/rr/src/anim.h @@ -0,0 +1,49 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef anim_h_ +#define anim_h_ + +typedef struct { + uint16_t frame; + int16_t sound; +} animsound_t; + +typedef struct +{ + double frameaspect1, frameaspect2; + uint8_t* animbuf; + animsound_t *sounds; + uint16_t numsounds; + uint8_t framedelay; + uint8_t frameflags; + char animlock; +} dukeanim_t; + +extern dukeanim_t * g_animPtr; +extern hashtable_t h_dukeanim; +extern dukeanim_t * Anim_Find(const char *s); +extern dukeanim_t * Anim_Create(const char *fn); +int32_t Anim_Play(const char *fn); +void Anim_Init(void); + +#endif diff --git a/source/rr/src/cheats.cpp b/source/rr/src/cheats.cpp new file mode 100644 index 000000000..97bc535c0 --- /dev/null +++ b/source/rr/src/cheats.cpp @@ -0,0 +1,860 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "duke3d.h" +#include "osdcmds.h" +#include "cheats.h" + +// KEEPINSYNC game.h: enum cheatindex_t +char CheatStrings [NUMCHEATS][MAXCHEATLEN] = +{ + "cornholio", // 0 + "stuff", // 1 + "scotty###", // 2 + "coords", // 3 + "view", // 4 + "time", // 5 + "unlock", // 6 + "cashman", // 7 + "items", // 8 + "rate", // 9 + "skill#", // 10 + "beta", // 11 + "hyper", // 12 + "monsters", // 13 + " ", // 14 + " ", // 15 + "todd", // 16 + "showmap", // 17 + "kroz", // 18 + "allen", // 19 + "clip", // 20 + "weapons", // 21 + "inventory", // 22 + "keys", // 23 + "debug", // 24 + " ", // 25 + " ", // 26 + " ", // 27 + " ", // 28 + " ", // 29 + " ", // 30 + " ", // 31 + " ", // 32 + " ", // 33 + " ", // 34 + " ", // 35 + " ", // 36 + " ", // 37 + " ", // 38 + " ", // 39 +}; + +const uint32_t CheatFunctionFlags [NUMCHEATS] = +{ + 1 << CHEATFUNC_GOD, + 1 << CHEATFUNC_GIVEEVERYTHING, + 1 << CHEATFUNC_WARP, + 1 << CHEATFUNC_COORDS, + 1 << CHEATFUNC_VIEW, + 0, + 1 << CHEATFUNC_UNLOCK, + 1 << CHEATFUNC_CASHMAN, + 1 << CHEATFUNC_GIVEALLITEMS, + 1 << CHEATFUNC_FRAMERATE, + 1 << CHEATFUNC_SKILL, + 1 << CHEATFUNC_QUOTEBETA, + 1 << CHEATFUNC_HYPER, + 1 << CHEATFUNC_MONSTERS, + 0, + 0, + 1 << CHEATFUNC_QUOTETODD, + 1 << CHEATFUNC_SHOWMAP, + 1 << CHEATFUNC_GOD, + 1 << CHEATFUNC_QUOTEALLEN, + 1 << CHEATFUNC_CLIP, + 1 << CHEATFUNC_GIVEWEAPONS, + 1 << CHEATFUNC_GIVEINVENTORY, + 1 << CHEATFUNC_GIVEKEYS, + 1 << CHEATFUNC_DEBUG, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +}; + +// KEEPINSYNC game.h: enum CheatCodeFunctions +// KEEPINSYNC menus.c: MenuEntry_t ME_CheatCodes[] +const uint8_t CheatFunctionIDs[NUMCHEATS] = +{ + CHEAT_CASHMAN, + CHEAT_CORNHOLIO, + CHEAT_STUFF, + CHEAT_WEAPONS, + CHEAT_ITEMS, + CHEAT_INVENTORY, + CHEAT_KEYS, + CHEAT_HYPER, + CHEAT_VIEW, + CHEAT_SHOWMAP, + CHEAT_UNLOCK, + CHEAT_CLIP, + CHEAT_SCOTTY, + CHEAT_SKILL, + CHEAT_MONSTERS, + CHEAT_RATE, + CHEAT_BETA, + CHEAT_TODD, + CHEAT_ALLEN, + CHEAT_COORDS, + CHEAT_DEBUG, +}; +void G_SetupCheats(void) +{ + if (RR) + { + CheatKeys[0] = sc_R; + CheatKeys[1] = sc_D; + Bstrcpy(CheatStrings[0], "hounddog"); + Bstrcpy(CheatStrings[1], "all"); + Bstrcpy(CheatStrings[2], "meadow###"); + Bstrcpy(CheatStrings[3], "yerat"); + Bstrcpy(CheatStrings[7], "cluck"); + Bstrcpy(CheatStrings[11], "teachers"); + Bstrcpy(CheatStrings[12], "moonshine"); + Bstrcpy(CheatStrings[13], "critters"); + Bstrcpy(CheatStrings[16], "rafael"); + Bstrcpy(CheatStrings[18], "elvis"); + Bstrcpy(CheatStrings[19], " "); + Bstrcpy(CheatStrings[21], "guns"); + if (RRRA) + { + Bstrcpy(CheatStrings[25], "joseph"); + Bstrcpy(CheatStrings[26], "mrbill"); + Bstrcpy(CheatStrings[27], "tony"); + Bstrcpy(CheatStrings[28], "gary"); + Bstrcpy(CheatStrings[29], "rhett"); + Bstrcpy(CheatStrings[30], "aaron"); + Bstrcpy(CheatStrings[31], "nocheat"); + Bstrcpy(CheatStrings[32], "woleslagle"); + Bstrcpy(CheatStrings[33], "mikael"); + Bstrcpy(CheatStrings[34], "greg"); + Bstrcpy(CheatStrings[35], "noah"); + Bstrcpy(CheatStrings[36], "arijit"); + Bstrcpy(CheatStrings[37], "donut"); + Bstrcpy(CheatStrings[38], "kfc"); + Bstrcpy(CheatStrings[39], "van"); + } + } +} + +static void doinvcheat(DukePlayer_t * const pPlayer, int32_t invidx, int32_t defaultnum) +{ + if (defaultnum >= 0) + pPlayer->inv_amount[invidx] = defaultnum; +} + +static void G_CheatGetInv(DukePlayer_t *pPlayer) +{ + doinvcheat(pPlayer, GET_STEROIDS, 400); + if (!RR) doinvcheat(pPlayer, GET_HEATS, 1200); + doinvcheat(pPlayer, GET_BOOTS, RR ? 2000 : 200); + doinvcheat(pPlayer, GET_SHIELD, 100); + doinvcheat(pPlayer, GET_SCUBA, 6400); + doinvcheat(pPlayer, GET_HOLODUKE, 2400); + doinvcheat(pPlayer, GET_JETPACK, RR ? 600 : 1600); + doinvcheat(pPlayer, GET_FIRSTAID, pPlayer->max_player_health); +} + +static void end_cheat(DukePlayer_t * const pPlayer) +{ + pPlayer->cheat_phase = 0; + KB_FlushKeyboardQueue(); +} + +static int32_t cheatbuflen; +static int8_t cheatbuf[MAXCHEATLEN]; + +void G_DoCheats(void) +{ + DukePlayer_t * const pPlayer = g_player[myconnectindex].ps; + int consoleCheat = 0; + int cheatNum; + + if (osdcmd_cheatsinfo_stat.cheatnum != -1) + { + cheatNum = osdcmd_cheatsinfo_stat.cheatnum; + + if (ud.player_skill == 4 || (RR && ud.player_skill > 3) || (RRRA && pPlayer->nocheat)) + { + switch (cheatNum) + { + case CHEAT_DEBUG: + case CHEAT_COORDS: + case CHEAT_RATE: + case CHEAT_RESERVED: + case CHEAT_RESERVED2: + //case CHEAT_RESERVED3: + break; + default: + P_DoQuote(QUOTE_CHEATS_DISABLED, pPlayer); + osdcmd_cheatsinfo_stat.cheatnum = -1; + return; + } + } + + // JBF 20030914 + osdcmd_cheatsinfo_stat.cheatnum = -1; + consoleCheat = 1; + } + + static int volumeOne = 0; + + if (VOLUMEONE && !volumeOne) + { + // change "scotty###" to "scotty##" + uint32_t const warpend = Bstrlen(CheatStrings[2]); + if (strcmp(&CheatStrings[2][warpend-3], "###") == 0) + CheatStrings[2][warpend-1] = '\0'; + + Bstrcpy(CheatStrings[6], " "); + volumeOne = 1; + } + + if (consoleCheat && numplayers < 2 && ud.recstat == 0) + goto FOUNDCHEAT; + + if ((RR && ud.player_skill > 3) || (RRRA && pPlayer->nocheat)) + return; + + if (pPlayer->gm & (MODE_TYPE|MODE_MENU)) + return; + + if (pPlayer->cheat_phase == 1) + { + int ch; + + while (KB_KeyWaiting()) + { + ch = Btolower(KB_GetCh()); + + if (!((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'))) + { + pPlayer->cheat_phase = 0; + // P_DoQuote(QUOTE_46,pPlayer); + return; + } + + cheatbuf[cheatbuflen++] = (int8_t) ch; + // This assertion is not obvious, but it should hold because of the + // cheat string matching logic below. + Bassert(cheatbuflen < (signed)sizeof(cheatbuf)); + cheatbuf[cheatbuflen] = 0; + // KB_ClearKeysDown(); + + for (cheatNum=0; cheatNum < NUMCHEATCODES; cheatNum++) + { + for (bssize_t j = 0; j = '0' && ch <= '9')) + { + if (CheatStrings[cheatNum][j+1] == 0) goto FOUNDCHEAT; + if (j == cheatbuflen-1) return; + } + else break; + } + } + + pPlayer->cheat_phase = 0; + return; + + FOUNDCHEAT:; + + if (cheatNum == CHEAT_SCOTTY) + { + size_t const i = Bstrlen(CheatStrings[cheatNum])-3+VOLUMEONE; + if (!consoleCheat) + { + // JBF 20030914 + int32_t volnume, levnume; + if (VOLUMEALL) + { + volnume = cheatbuf[i] - '0'; + levnume = (cheatbuf[i+1] - '0')*10+(cheatbuf[i+2]-'0'); + } + else + { + volnume = cheatbuf[i] - '0'; + levnume = cheatbuf[i+1] - '0'; + } + + volnume--; + levnume--; + + ud.m_volume_number = volnume; + ud.m_level_number = levnume; + } + else + { + // JBF 20030914 + ud.m_volume_number = osdcmd_cheatsinfo_stat.volume; + ud.m_level_number = osdcmd_cheatsinfo_stat.level; + } + } + else if (cheatNum == CHEAT_SKILL) + { + if (!consoleCheat) + { + size_t const i = Bstrlen(CheatStrings[cheatNum])-1; + ud.m_player_skill = cheatbuf[i] - '1'; + } + else + { + ud.m_player_skill = osdcmd_cheatsinfo_stat.volume; + } + } + + { + switch (cheatNum) + { + case CHEAT_WEAPONS: + { + int const weaponLimit = (VOLUMEONE) ? 6 : 0; + + for (bssize_t weaponNum = PISTOL_WEAPON; weaponNum < MAX_WEAPONS-weaponLimit; weaponNum++) + { + P_AddAmmo(pPlayer, weaponNum, pPlayer->max_ammo_amount[weaponNum]); + pPlayer->gotweapon |= (1< ammo_amount[SLINGBLADE_WEAPON] = 1; + + P_DoQuote(QUOTE_CHEAT_ALL_WEAPONS, pPlayer); + + end_cheat(pPlayer); + } + return; + + case CHEAT_INVENTORY: + G_CheatGetInv(pPlayer); + P_DoQuote(QUOTE_CHEAT_ALL_INV, pPlayer); + end_cheat(pPlayer); + return; + + case CHEAT_KEYS: + pPlayer->got_access = 7; + if (RR) + for (int key = 0; key < 5; key++) + pPlayer->keys[key] = 1; + KB_FlushKeyboardQueue(); + P_DoQuote(QUOTE_CHEAT_ALL_KEYS, pPlayer); + end_cheat(pPlayer); + return; + + case CHEAT_DEBUG: + g_Debug = 1-g_Debug; + + G_DumpDebugInfo(); + Bsprintf(tempbuf, "Gamevars dumped to log"); + G_AddUserQuote(tempbuf); + Bsprintf(tempbuf, "Map dumped to debug.map"); + G_AddUserQuote(tempbuf); + end_cheat(pPlayer); + break; + + case CHEAT_CLIP: + ud.noclip = !ud.noclip; + P_DoQuote(QUOTE_CHEAT_NOCLIP-!ud.noclip, pPlayer); + end_cheat(pPlayer); + return; + + case CHEAT_RESERVED2: + if (RR) + { + P_DoQuote(QUOTE_JETPACK_ON, pPlayer); + KB_FlushKeyboardQueue(); + } + else + { + pPlayer->player_par = 0; + pPlayer->gm = MODE_EOL; + } + end_cheat(pPlayer); + return; + + case CHEAT_ALLEN: + P_DoQuote(QUOTE_CHEAT_ALLEN, pPlayer); + pPlayer->cheat_phase = 0; + KB_ClearKeyDown(sc_N); + return; + + case CHEAT_CORNHOLIO: + case CHEAT_KROZ: + //case CHEAT_COMEGETSOME: + { + const int32_t pi = pPlayer->i; + + ud.god = 1-ud.god; + + if (ud.god) + { + if (RRRA) + S_PlaySound(218); + pus = 1; + pub = 1; + sprite[pi].cstat = 257; + + actor[pi].t_data[0] = 0; + actor[pi].t_data[1] = 0; + actor[pi].t_data[2] = 0; + actor[pi].t_data[3] = 0; + actor[pi].t_data[4] = 0; + actor[pi].t_data[5] = 0; + + sprite[pi].hitag = 0; + sprite[pi].lotag = 0; + sprite[pi].pal = pPlayer->palookup; + + //if (cheatNum != CHEAT_COMEGETSOME) + //{ + P_DoQuote(QUOTE_CHEAT_GODMODE_ON, pPlayer); + //} + //else + //{ + // Bstrcpy(apStrings[QUOTE_RESERVED4], "Come Get Some!"); + // + // S_PlaySound(DUKE_GETWEAPON2); + // P_DoQuote(QUOTE_RESERVED4, pPlayer); + // G_CheatGetInv(pPlayer); + // + // for (bssize_t weaponNum = PISTOL_WEAPON; weaponNum < MAX_WEAPONS; weaponNum++) + // pPlayer->gotweapon |= (1< max_ammo_amount[weaponNum]); + // + // pPlayer->got_access = 7; + //} + } + else + { + sprite[pi].extra = pPlayer->max_player_health; + actor[pi].extra = -1; + pPlayer->last_extra = pPlayer->max_player_health; + P_DoQuote(QUOTE_CHEAT_GODMODE_OFF, pPlayer); + } + + sprite[pi].extra = pPlayer->max_player_health; + actor[pi].extra = 0; + + //if (cheatNum != CHEAT_COMEGETSOME) + pPlayer->dead_flag = 0; + + end_cheat(pPlayer); + return; + } + + case CHEAT_STUFF: + { + int const weaponLimit = (VOLUMEONE) ? 6 : 0; + + for (bssize_t weaponNum = PISTOL_WEAPON; weaponNum < MAX_WEAPONS-weaponLimit; weaponNum++) + pPlayer->gotweapon |= (1< max_ammo_amount[weaponNum]); + + if (RRRA) + pPlayer->ammo_amount[SLINGBLADE_WEAPON] = 1; + + G_CheatGetInv(pPlayer); + pPlayer->got_access = 7; + if (RR) + for (int key = 0; key < 5; key++) + pPlayer->keys[key] = 1; + P_DoQuote(QUOTE_CHEAT_EVERYTHING, pPlayer); + + // P_DoQuote(QUOTE_21,pPlayer); + pPlayer->inven_icon = ICON_FIRSTAID; + + end_cheat(pPlayer); + return; + } + + case CHEAT_SCOTTY: + { + if (RR) + g_lastLevel = 0; + int32_t const volnume = ud.m_volume_number, levnume = ud.m_level_number; + + if ((!VOLUMEONE || volnume == 0) && (unsigned)volnume < (unsigned)g_volumeCnt && + (unsigned)levnume < MAXLEVELS && g_mapInfo[volnume*MAXLEVELS + levnume].filename != NULL) + { + ud.volume_number = volnume; + ud.level_number = levnume; + +#if 0 + if (numplayers > 1 && g_netServer) + Net_NewGame(volnume, levnume); + else +#endif + pPlayer->gm |= MODE_RESTART; + } + + end_cheat(pPlayer); + return; + } + + case CHEAT_SKILL: + if (RR) + g_lastLevel = 0; + ud.player_skill = ud.m_player_skill; + +#if 0 + if (numplayers > 1 && g_netServer) + Net_NewGame(ud.m_volume_number, ud.m_level_number); + else +#endif + pPlayer->gm |= MODE_RESTART; + + end_cheat(pPlayer); + return; + + case CHEAT_COORDS: +#ifdef USE_OPENGL + if (++ud.coords >= 3) ud.coords = 0; +#else + if (++ud.coords >= 2) ud.coords = 0; +#endif + end_cheat(pPlayer); + return; + + case CHEAT_VIEW: + if (!RRRA || (!pPlayer->on_motorcycle && !pPlayer->on_boat)) + { + pPlayer->over_shoulder_on ^= 1; + CAMERADIST = 0; + CAMERACLOCK = totalclock; + // P_DoQuote(QUOTE_CHEATS_DISABLED,pPlayer); + } + end_cheat(pPlayer); + return; + + case CHEAT_TIME: + // P_DoQuote(QUOTE_21,pPlayer); + end_cheat(pPlayer); + return; + + case CHEAT_UNLOCK: + if (VOLUMEONE) return; + + for (bssize_t i=numsectors-1; i>=0; i--) //Unlock + { + int const lotag = sector[i].lotag; + if (lotag == -1 || lotag == 32767) continue; + if ((lotag & 0x7fff) > 2) + { + if (lotag & (uint16_t)~16384u) + sector[i].lotag &= (uint16_t)~16384u; + G_OperateSectors(i, pPlayer->i); + } + } + G_OperateForceFields(pPlayer->i, -1); + + P_DoQuote(QUOTE_CHEAT_UNLOCK, pPlayer); + end_cheat(pPlayer); + return; + + case CHEAT_CASHMAN: + ud.cashman = 1-ud.cashman; + KB_ClearKeyDown(sc_N); + pPlayer->cheat_phase = 0; + return; + + case CHEAT_ITEMS: + G_CheatGetInv(pPlayer); + pPlayer->got_access = 7; + if (RR) + for(int key = 0; key < 5; key++) + pPlayer->keys[key] = 1; + P_DoQuote(QUOTE_CHEAT_EVERYTHING, pPlayer); + end_cheat(pPlayer); + return; + + case CHEAT_SHOWMAP: // SHOW ALL OF THE MAP TOGGLE; + ud.showallmap = !ud.showallmap; + + for (char & i : show2dsector) + i = ud.showallmap*255; + + P_DoQuote(ud.showallmap ? QUOTE_SHOW_MAP_ON : QUOTE_SHOW_MAP_OFF, + pPlayer); + + end_cheat(pPlayer); + return; + + case CHEAT_TODD: + P_DoQuote(QUOTE_CHEAT_TODD, pPlayer); + + end_cheat(pPlayer); + return; + + case CHEAT_RATE: + if (++ud.showfps > 3) + ud.showfps = 0; + + end_cheat(pPlayer); + return; + + case CHEAT_BETA: + P_DoQuote(QUOTE_CHEAT_BETA, pPlayer); + KB_ClearKeyDown(sc_H); + end_cheat(pPlayer); + return; + + case CHEAT_HYPER: + pPlayer->inv_amount[GET_STEROIDS] = 399; + if (!RR) + pPlayer->inv_amount[GET_HEATS] = 1200; + P_DoQuote(QUOTE_CHEAT_STEROIDS, pPlayer); + end_cheat(pPlayer); + return; + + case CHEAT_MONSTERS: + { + const char *s [] = { "On", "Off", "On (2)" }; + + if (++g_noEnemies == 3) + g_noEnemies = 0; + + Bsprintf(apStrings[QUOTE_RESERVED4], "Monsters: %s", s[g_noEnemies]); + P_DoQuote(QUOTE_RESERVED4, pPlayer); + + end_cheat(pPlayer); + return; + } + + case CHEAT_RESERVED: + //case CHEAT_RESERVED3: + if (RR) + { + P_DoQuote(51, pPlayer); + end_cheat(pPlayer); + } + else + { + ud.eog = 1; + pPlayer->player_par = 0; + pPlayer->gm |= MODE_EOL; + } + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RAJOSEPH: + G_OnMotorcycle(pPlayer, 0); + pPlayer->ammo_amount[MOTORCYCLE_WEAPON] = pPlayer->max_ammo_amount[MOTORCYCLE_WEAPON]; + P_DoQuote(126, pPlayer); + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RAMRBILL: + P_QuickKill(pPlayer); + P_DoQuote(127, pPlayer); + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RAGARY: + S_PlayRRMusic(10); + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RANOAH: + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RARHETT: + ud.god = 0; + pPlayer->gotweapon = 1< curr_weapon = KNEE_WEAPON; + pPlayer->nocheat = 1; + sprite[pPlayer->i].extra = 1; + P_DoQuote(128, pPlayer); + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RAAARON: + pPlayer->drug_mode = pPlayer->drug_mode ? 0 : 5; + pPlayer->drug_timer = totalclock; + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RANOCHEAT: + pPlayer->nocheat = 1; + P_DoQuote(130, pPlayer); + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RATONY: + g_changeEnemySize = 2; + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RAVAN: + g_changeEnemySize = 3; + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RAKFC: + for (int i = 0; i < 7; i++) + { + int const newSprite = A_Spawn(pPlayer->i, HEN); + sprite[newSprite].pal = 1; + sprite[newSprite].xrepeat <<= 2; + sprite[newSprite].yrepeat <<= 2; + } + P_DoQuote(139, pPlayer); + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RAWOLESLAGLE: + if (pPlayer->drink_amt) + { + pPlayer->drink_amt = 0; + P_DoQuote(132, pPlayer); + } + else + { + pPlayer->drink_amt = 90; + P_DoQuote(131, pPlayer); + } + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RAMIKAEL: + for (bssize_t weaponNum = PISTOL_WEAPON; weaponNum < MAX_WEAPONS; weaponNum++) + { + pPlayer->gotweapon |= 1 << weaponNum; + pPlayer->ammo_amount[weaponNum] = 66; + } + + pPlayer->ammo_amount[SLINGBLADE_WEAPON] = 1; + + G_CheatGetInv(pPlayer); + pPlayer->got_access = 7; + for (int key = 0; key < 5; key++) + pPlayer->keys[key] = 1; + P_DoQuote(5, pPlayer); + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RAGREG: + if (pPlayer->sea_sick_stat) + { + pPlayer->sea_sick_stat = 0; + P_DoQuote(129, pPlayer); + } + else + { + pPlayer->sea_sick_stat = 1; + P_DoQuote(137, pPlayer); + } + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + case CHEAT_RAARIJIT: + case CHEAT_RADONUT: + G_OnBoat(pPlayer, 0); + pPlayer->ammo_amount[BOAT_WEAPON] = pPlayer->max_ammo_amount[BOAT_WEAPON]; + P_DoQuote(136, pPlayer); + end_cheat(pPlayer); + KB_FlushKeyboardQueue(); + return; + + default: + end_cheat(pPlayer); + return; + } + } + } + } + else + { + if (KB_KeyPressed((uint8_t) CheatKeys[0])) + { + if (pPlayer->cheat_phase >= 0 && numplayers < 2 && ud.recstat == 0) + { + if (CheatKeys[0] == CheatKeys[1]) + KB_ClearKeyDown((uint8_t) CheatKeys[0]); + pPlayer->cheat_phase = -1; + } + } + + if (KB_KeyPressed((uint8_t) CheatKeys[1])) + { + if (pPlayer->cheat_phase == -1) + { + if (ud.player_skill == 4) + { + P_DoQuote(QUOTE_CHEATS_DISABLED, pPlayer); + pPlayer->cheat_phase = 0; + } + else + { + pPlayer->cheat_phase = 1; + // P_DoQuote(QUOTE_25,pPlayer); + cheatbuflen = 0; + } + KB_FlushKeyboardQueue(); + } + else if (pPlayer->cheat_phase != 0) + { + pPlayer->cheat_phase = 0; + KB_ClearKeyDown((uint8_t) CheatKeys[0]); + KB_ClearKeyDown((uint8_t) CheatKeys[1]); + } + } + } +} diff --git a/source/rr/src/cheats.h b/source/rr/src/cheats.h new file mode 100644 index 000000000..009be67b8 --- /dev/null +++ b/source/rr/src/cheats.h @@ -0,0 +1,103 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#pragma once + +extern void G_DoCheats(void); +extern void G_SetupCheats(void); + +// Cheats +// KEEPINSYNC game.c: char CheatStrings[][] +enum cheatindex_t +{ + CHEAT_CORNHOLIO, // 0 + CHEAT_STUFF, + CHEAT_SCOTTY, + CHEAT_COORDS, + CHEAT_VIEW, + CHEAT_TIME, // 5 + CHEAT_UNLOCK, + CHEAT_CASHMAN, + CHEAT_ITEMS, + CHEAT_RATE, + CHEAT_SKILL, // 10 + CHEAT_BETA, + CHEAT_HYPER, + CHEAT_MONSTERS, + CHEAT_RESERVED, + CHEAT_RESERVED2, // 15 + CHEAT_TODD, + CHEAT_SHOWMAP, + CHEAT_KROZ, + CHEAT_ALLEN, + CHEAT_CLIP, // 20 + CHEAT_WEAPONS, + CHEAT_INVENTORY, + CHEAT_KEYS, + CHEAT_DEBUG, + CHEAT_RAJOSEPH, // 25 + CHEAT_RAMRBILL, + CHEAT_RATONY, + CHEAT_RAGARY, + CHEAT_RARHETT, + CHEAT_RAAARON, // 30 + CHEAT_RANOCHEAT, + CHEAT_RAWOLESLAGLE, + CHEAT_RAMIKAEL, + CHEAT_RAGREG, + CHEAT_RANOAH, // 35 + CHEAT_RAARIJIT, + CHEAT_RADONUT, + CHEAT_RAKFC, + CHEAT_RAVAN, + NUMCHEATS, +}; + +extern char CheatStrings[NUMCHEATS][MAXCHEATLEN]; + +// KEEPINSYNC game.c: uint8_t CheatFunctionIDs[] +// KEEPINSYNC menus.c: MenuEntry_t ME_CheatCodes[] +enum CheatCodeFunctions +{ + CHEATFUNC_CASHMAN, + CHEATFUNC_GOD, + CHEATFUNC_GIVEEVERYTHING, + CHEATFUNC_GIVEWEAPONS, + CHEATFUNC_GIVEALLITEMS, + CHEATFUNC_GIVEINVENTORY, + CHEATFUNC_GIVEKEYS, + CHEATFUNC_HYPER, + CHEATFUNC_VIEW, + CHEATFUNC_SHOWMAP, + CHEATFUNC_UNLOCK, + CHEATFUNC_CLIP, + CHEATFUNC_WARP, + CHEATFUNC_SKILL, + CHEATFUNC_MONSTERS, + CHEATFUNC_FRAMERATE, + CHEATFUNC_QUOTEBETA, + CHEATFUNC_QUOTETODD, + CHEATFUNC_QUOTEALLEN, + CHEATFUNC_COORDS, + CHEATFUNC_DEBUG, + NUMCHEATFUNCS, +}; diff --git a/source/rr/src/cmdline.cpp b/source/rr/src/cmdline.cpp new file mode 100644 index 000000000..69ab182e1 --- /dev/null +++ b/source/rr/src/cmdline.cpp @@ -0,0 +1,782 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "duke3d.h" +#include "demo.h" +#include "screens.h" +#include "renderlayer.h" +#include "cmdline.h" + +int32_t g_commandSetup = 0; +int32_t g_noSetup = 0; +int32_t g_noAutoLoad = 0; +int32_t g_noSound = 0; +int32_t g_noMusic = 0; +const char *CommandMap = NULL; +const char *CommandName = NULL; +int32_t g_forceWeaponChoice = 0; +int32_t g_fakeMultiMode = 0; + +void G_ShowParameterHelp(void) +{ + static char const s[] = "Usage: " APPBASENAME " [files] [options]\n" + "Example: " APPBASENAME " -usecwd -cfg myconfig.cfg -map nukeland.map\n\n" + "Files can be of type [grp|zip|map|con|def]\n" + "\n" + "-cfg [file.cfg]\tUse an alternate configuration file\n" +#ifdef HAVE_CLIPSHAPE_FEATURE + "-clipmap [file.map]\tLoad an additional clipping map for use with clipshape\n" +#endif + "-connect [host]\tConnect to a multiplayer game\n" + "-c#\t\tMultiplayer mode #, 1 = DM, 2 = Co-op, 3 = DM(no spawn)\n" + "-d [file.edm or #]\tPlay a demo\n" + "-g [file.grp]\tLoad additional game data\n" + "-h [file.def]\tLoad an alternate definitions file\n" + "-j [dir]\t\tAdd a directory to " APPNAME "'s search list\n" + "-l#\t\tStart game on level #, see -v\n" + "-map [file.map]\tLoad an external map file\n" + "-mh [file.def]\tInclude an additional definitions module\n" + "-mx [file.con]\tInclude an additional CON script module\n" + "-m\t\tDisable enemies\n" + "-rts [file.rts]\tLoad a custom Remote Ridicule sound bank\n" + "-r\t\tRecord demo\n" + "-s#\t\tStart game on skill level #\n" + "-server\t\tStart a multiplayer server\n" +#ifdef STARTUP_SETUP_WINDOW + "-setup/nosetup\tEnable or disable startup window\n" +#endif + "-t#\t\tRespawn mode: 1 = enemies, 2 = weapons, 3 = items, x = all\n" + "-usecwd\t\tRead data and configuration from current directory\n" + "-u#########\tUser's favorite weapon order (default: 3425689071)\n" + "-v#\t\tStart game on episode #, see -l\n" + "-x [game.con]\tLoad custom CON script\n" + "-#\t\tLoad and run a game from slot # (0-9)\n" + // "\n-?/--help\tDisplay this help message and exit\n" + "\nSee " APPBASENAME " -debughelp for additional parameters for debugging" + ; +#ifdef WM_MSGBOX_WINDOW + Bsnprintf(tempbuf, sizeof(tempbuf), HEAD2 " %s", s_buildRev); + wm_msgbox(tempbuf, s); +#else + initprintf("%s\n", s); +#endif +} + +void G_ShowDebugHelp(void) +{ + static char const s[] = "Usage: " APPBASENAME " [files] [options]\n" + "\n" +#if 0 + "-a\t\tUse fake player AI (fake multiplayer only)\n" +#endif + "-cachesize #\tSet cache size in kB\n" + "-game_dir [dir]\tSpecify game data directory\n" + "-gamegrp \tSelect main grp file\n" + "-name [name]\tPlayer name in multiplayer\n" + "-noautoload\tDisable loading from autoload directory\n" +#if defined RENDERTYPEWIN + "-nodinput\t\tDisable DirectInput (joystick) support\n" +#endif + "-nologo\t\tSkip intro anim\n" + "-ns\t\tDisable sound\n" + "-nm\t\tDisable music\n" + "-q#\t\tFake multiplayer with # players\n" + "-z#/-condebug\tEnable line-by-line CON compile debugging at level #\n" + "-conversion YYYYMMDD\tSelects CON script version for compatibility with older mods\n" + "-rotatesprite-no-widescreen\tStretch screen drawing from scripts to fullscreen\n" + ; +#ifdef WM_MSGBOX_WINDOW + Bsnprintf(tempbuf, sizeof(tempbuf), HEAD2 " %s", s_buildRev); + wm_msgbox(tempbuf, s); +#else + initprintf("%s\n", s); +#endif +} + +static void G_AddDemo(const char* param) +{ + Bstrncpy(tempbuf, param, sizeof(tempbuf)); + char * colon = (char *) Bstrchr(tempbuf, ':'); + int32_t framespertic=-1, numrepeats=1; + + if (colon && colon != tempbuf) + { + // -d : [, ] + // profiling options + *(colon++) = 0; + Bsscanf(colon, "%d,%d", &framespertic, &numrepeats); + } + + Demo_SetFirst(tempbuf); + + if (framespertic < 0) + { + initprintf("Play demo %s.\n", g_firstDemoFile); + } + else + { + framespertic = clamp(framespertic, 0, 8)+1; + // TODO: repeat count and gathering statistics. + initprintf("Profile demo %s, %d frames/gametic, repeated 1x.\n", g_firstDemoFile, + framespertic-1); + Demo_PlayFirst(framespertic, 1); + g_noLogo = 1; + } +} + +void G_CheckCommandLine(int32_t argc, char const * const * argv) +{ + int16_t i = 1, j; + const char *c, *k; + + ud.fta_on = 1; + ud.god = 0; + ud.m_respawn_items = 0; + ud.m_respawn_monsters = 0; + ud.m_respawn_inventory = 0; + ud.warp_on = 0; + ud.cashman = 0; + ud.m_player_skill = ud.player_skill = 2; + g_player[0].wchoice[0] = 3; + g_player[0].wchoice[1] = 4; + g_player[0].wchoice[2] = 5; + g_player[0].wchoice[3] = 7; + g_player[0].wchoice[4] = 8; + g_player[0].wchoice[5] = 6; + g_player[0].wchoice[6] = 0; + g_player[0].wchoice[7] = 2; + g_player[0].wchoice[8] = 9; + g_player[0].wchoice[9] = 1; + Bsprintf(ud.wchoice, "3457860291"); + +#ifdef HAVE_CLIPSHAPE_FEATURE + // pre-form the default 10 clipmaps + for (j = '0'; j<='9'; ++j) + { + char clipshape[16] = "_clipshape0.map"; + + clipshape[10] = j; + g_clipMapFiles.append(Xstrdup(clipshape)); + } +#endif + + if (argc > 1) + { + initprintf("Application parameters: "); + while (i < argc) + initprintf("%s ", argv[i++]); + initprintf("\n"); + + i = 1; + do + { + const char *const oc = argv[i]; + int32_t shortopt = 0, ignored_short_opt = 0; + + c = oc; + + if ((*c == '-') +#ifdef _WIN32 + || (*c == '/') +#endif + ) + { + shortopt = 0; + + if (!Bstrcasecmp(c+1, "?") || !Bstrcasecmp(c+1, "help") || !Bstrcasecmp(c+1, "-help")) + { + G_ShowParameterHelp(); + Bexit(0); + } + if (!Bstrcasecmp(c+1, "addon")) + { + if (argc > i+1) + { + g_addonNum = Batoi(argv[i+1]); + + if (g_addonNum > ADDON_NONE && g_addonNum < NUMADDONS) + g_noSetup = 1; + else g_addonNum = ADDON_NONE; + + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "debughelp") || !Bstrcasecmp(c+1, "-debughelp")) + { + G_ShowDebugHelp(); + Bexit(0); + } + if (!Bstrcasecmp(c+1, "grp") || !Bstrcasecmp(c+1, "g")) + { + if (argc > i+1) + { + G_AddGroup(argv[i+1]); + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "game_dir")) + { + if (argc > i+1) + { + Bstrncpyz(g_modDir, argv[i+1], sizeof(g_modDir)); + G_AddPath(argv[i+1]); + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "cfg")) + { + if (argc > i+1) + { + Bstrcpy(g_setupFileName, argv[i+1]); + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "gamegrp")) + { + if (argc > i+1) + { + clearGrpNamePtr(); + g_grpNamePtr = dup_filename(argv[i+1]); + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "setup")) + { + g_commandSetup = TRUE; + i++; + continue; + } + if (!Bstrcasecmp(c+1, "nosetup")) + { + g_noSetup = 1; + g_commandSetup = 0; + i++; + continue; + } +#if defined RENDERTYPEWIN + if (!Bstrcasecmp(c+1, "nodinput")) + { + initprintf("DirectInput (joystick) support disabled\n"); + di_disabled = 1; + i++; + continue; + } +#endif + if (!Bstrcasecmp(c+1, "noautoload")) + { + initprintf("Autoload disabled\n"); + g_noAutoLoad = 1; + i++; + continue; + } +#ifndef NETCODE_DISABLE + if (!Bstrcasecmp(c+1, "net")) + { + G_GameExit("EDuke32 no longer supports legacy networking.\n\n" + "If using YANG or other launchers that only support legacy netplay, download an older build of EDuke32. " + "Otherwise, run the following:\n\n" + "eduke32 -server\n\n" + "Other clients can then connect by typing \"connect [host]\" in the console.\n\n" + "EDuke32 will now close."); + } +#endif + if (!Bstrcasecmp(c+1, "port")) + { + if (argc > i+1) + { + g_netPort = Batoi(argv[i+1]); + i++; + } + i++; + continue; + } +#ifndef NETCODE_DISABLE + if (!Bstrcasecmp(c+1, "server")) + { + g_networkMode = NET_SERVER; + g_noSetup = g_noLogo = TRUE; + i++; + continue; + } + if (!Bstrcasecmp(c+1, "dedicated")) + { + g_networkMode = NET_DEDICATED_SERVER; + g_noSetup = g_noLogo = TRUE; + i++; + continue; + } + if (!Bstrcasecmp(c+1, "connect")) + { + if (argc > i+1) + { + Net_Connect(argv[i+1]); + g_noSetup = g_noLogo = TRUE; + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "password")) + { + if (argc > i+1) + { + Bstrncpyz(g_netPassword, argv[i+1], sizeof(g_netPassword)); + i++; + } + i++; + continue; + } +#endif + if (!Bstrcasecmp(c+1, "name")) + { + if (argc > i+1) + { + CommandName = argv[i+1]; + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "map")) + { + if (argc > i+1) + { + CommandMap = argv[i+1]; + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "rts")) + { + if (argc > i+1) + { + free(g_rtsNamePtr); + g_rtsNamePtr = dup_filename(argv[i+1]); + initprintf("Using RTS file \"%s\".\n", g_rtsNamePtr); + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "x")) + { + if (argc > i+1) + { + G_AddCon(argv[i+1]); + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "mx")) + { + if (argc > i+1) + { + G_AddConModule(argv[i+1]); + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "h")) + { + if (argc > i+1) + { + G_AddDef(argv[i+1]); + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "mh")) + { + if (argc > i+1) + { + G_AddDefModule(argv[i+1]); + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "j")) + { + if (argc > i+1) + { + G_AddPath(argv[i+1]); + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "d")) + { + if (argc > i+1) + { + G_AddDemo(argv[i+1]); + i++; + } + i++; + continue; + } +#ifdef HAVE_CLIPSHAPE_FEATURE + if (!Bstrcasecmp(c+1, "clipmap")) + { + if (argc > i+1) + { + G_AddClipMap(argv[i+1]); + i++; + } + i++; + continue; + } +#endif + if (!Bstrcasecmp(c+1, "condebug")) + { + g_scriptDebug = 1; + i++; + continue; + } + if (!Bstrcasecmp(c+1, "nologo")) + { + g_noLogo = 1; + i++; + continue; + } + if (!Bstrcasecmp(c+1, "rotatesprite-no-widescreen")) + { + g_rotatespriteNoWidescreen = 1; + i++; + continue; + } + if (!Bstrcasecmp(c+1, "usecwd")) + { + g_useCwd = 1; + i++; + continue; + } + if (!Bstrcasecmp(c+1, "cachesize")) + { + if (argc > i+1) + { + uint32_t j = Batol(argv[i+1]); + MAXCACHE1DSIZE = j<<10; + initprintf("Cache size: %dkB\n", j); + i++; + } + i++; + continue; + } + if (!Bstrcasecmp(c+1, "noinstancechecking")) + { + i++; + continue; + } +#if defined(RENDERTYPEWIN) && defined(USE_OPENGL) + if (!Bstrcasecmp(c+1, "forcegl")) + { + forcegl = 1; + i++; + continue; + } +#endif + // the following two dummy entries allow us to serve as a drop-in replacement for NAM on Steam + if (!Bstrcasecmp(c+1, "noconsole")) + { + i++; + continue; + } + if (!Bstrcasecmp(c+1, "conf")) + { + if (argc > i+1) + i++; + i++; + continue; + } + } + + if ((*c == '-') +#ifdef _WIN32 + || (*c == '/') +#endif + ) + { + shortopt = 1; + + c++; + switch (Btolower(*c)) + { + case 'a': + ud.playerai = 1; + initprintf("Other player AI.\n"); + break; + case 'c': + c++; + ud.m_coop = 0; + while ((*c >= '0')&&(*c <= '9')) + { + ud.m_coop *= 10; + ud.m_coop += *c - '0'; + c++; + } + ud.m_coop--; + break; + case 'd': + { + c++; + if (*c) + G_AddDemo(c); + break; + } + case 'g': + c++; + if (*c) + G_AddGroup(c); + break; + case 'h': + c++; + if (*c) + G_AddDef(c); + break; + case 'j': + c++; + if (*c) + G_AddPath(c); + break; + case 'l': + // NOTE: Overlaid with -Lopts=... options for Lunatic, hence the check. + if (Bisdigit(c[1])) + { + ud.warp_on = 1; + c++; + ud.m_level_number = ud.level_number = ((unsigned) (Batoi(c)-1))%MAXLEVELS; + } + break; + case 'm': + if (*(c+1) != 'a' && *(c+1) != 'A') + { + ud.m_monsters_off = 1; + ud.m_player_skill = ud.player_skill = 0; + initprintf("Monsters off.\n"); + } + break; + case 'n': + c++; + if (*c == 's' || *c == 'S') + { + g_noSound = 2; + initprintf("Sound off.\n"); + } + else if (*c == 'm' || *c == 'M') + { + g_noMusic = 1; + initprintf("Music off.\n"); + } + else + { + G_ShowParameterHelp(); + exit(-1); + } + break; + case 'q': + if (*(++c) == 0) + { + ud.multimode = 1; + initprintf("Fake multiplayer mode: expected number after -q, falling back to 1 player.\n"); + } + else + { + int32_t numpl = Batoi(c); + + if (numpl < 2 || numpl > MAXPLAYERS) + { + initprintf("Fake multiplayer mode: expected 2-%d players, falling back to 1.\n", + MAXPLAYERS); + } + else + { + ud.multimode = numpl; + initprintf("Fake multiplayer mode: %d players.\n", ud.multimode); + + g_fakeMultiMode = numpl; + } + } + + ud.m_coop = ud.coop = 0; + ud.m_marker = ud.marker = 1; + ud.m_respawn_monsters = ud.respawn_monsters = 1; + ud.m_respawn_items = ud.respawn_items = 1; + ud.m_respawn_inventory = ud.respawn_inventory = 1; + break; + case 'r': + ud.m_recstat = 1; + initprintf("Demo record mode on.\n"); + break; + case 's': + c++; + ud.m_player_skill = ud.player_skill = (Batoi(c)%5); + if (ud.m_player_skill == 4) + ud.m_respawn_monsters = ud.respawn_monsters = 1; + break; + case 't': + c++; + if (*c == '1') ud.m_respawn_monsters = 1; + else if (*c == '2') ud.m_respawn_items = 1; + else if (*c == '3') ud.m_respawn_inventory = 1; + else + { + ud.m_respawn_monsters = 1; + ud.m_respawn_items = 1; + ud.m_respawn_inventory = 1; + } + initprintf("Respawn on.\n"); + break; + case 'u': + g_forceWeaponChoice = 1; + c++; + j = 0; + if (*c) + { + initprintf("Using favorite weapon order(s).\n"); + while (*c) + { + g_player[0].wchoice[j] = *c-'0'; + ud.wchoice[j] = *c; + c++; + j++; + } + + while (j < 10) + { + if (j == 9) + { + g_player[0].wchoice[9] = 1; + ud.wchoice[9] = '1'; + } + else + { + g_player[0].wchoice[j] = 2; + ud.wchoice[j] = '2'; + } + + j++; + } + } + else + { + initprintf("Using default weapon orders.\n"); + g_player[0].wchoice[0] = 3; + g_player[0].wchoice[1] = 4; + g_player[0].wchoice[2] = 5; + g_player[0].wchoice[3] = 7; + g_player[0].wchoice[4] = 8; + g_player[0].wchoice[5] = 6; + g_player[0].wchoice[6] = 0; + g_player[0].wchoice[7] = 2; + g_player[0].wchoice[8] = 9; + g_player[0].wchoice[9] = 1; + + Bsprintf(ud.wchoice, "3457860291"); + } + break; + case 'v': + c++; + ud.warp_on = 1; + ud.m_volume_number = ud.volume_number = ((unsigned) (Batoi(c)-1))%MAXVOLUMES; + break; + case 'w': + ud.coords = 1; + break; + case 'x': + c++; + if (*c) + G_AddCon(c); + break; + case 'z': + c++; + g_scriptDebug = Batoi(c); + if (!g_scriptDebug) + g_scriptDebug = 1; + break; + default: + ignored_short_opt = 1; + break; + } + } + else + { + shortopt = 0; + + k = Bstrrchr(c, '.'); + if (k) + { + if (!Bstrcasecmp(k, ".map")) + { + CommandMap = argv[i++]; + continue; + } + if (!Bstrcasecmp(k, ".grp") || !Bstrcasecmp(k, ".zip") || !Bstrcasecmp(k, ".pk3") || !Bstrcasecmp(k, ".pk4")) + { + G_AddGroup(argv[i++]); + continue; + } + if (!Bstrcasecmp(k, ".con")) + { + clearScriptNamePtr(); + g_scriptNamePtr = dup_filename(argv[i++]); + initprintf("Using CON file \"%s\".\n", g_scriptNamePtr); + continue; + } + if (!Bstrcasecmp(k, ".def")) + { + clearDefNamePtr(); + g_defNamePtr = dup_filename(argv[i++]); + initprintf("Using DEF file \"%s\".\n", g_defNamePtr); + continue; + } + if (!Bstrcasecmp(k, ".rts")) + { + free(g_rtsNamePtr); + g_rtsNamePtr = dup_filename(argv[i++]); + initprintf("Using RTS file \"%s\".\n", g_rtsNamePtr); + continue; + } + } + } + + if (!shortopt || ignored_short_opt) + initprintf("Warning: ignored application parameter \"%s\".\n", oc); + + i++; + } while (i < argc); + } +} diff --git a/source/rr/src/cmdline.h b/source/rr/src/cmdline.h new file mode 100644 index 000000000..14f5896f4 --- /dev/null +++ b/source/rr/src/cmdline.h @@ -0,0 +1,35 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +extern void G_CheckCommandLine(int32_t argc, char const * const * argv); +extern void G_ShowParameterHelp(void); +extern void G_ShowDebugHelp(void); + +extern int32_t g_commandSetup; +extern int32_t g_noSetup; +extern int32_t g_noAutoLoad; +extern int32_t g_noSound; +extern int32_t g_noMusic; +extern const char *CommandMap; +extern const char *CommandName; +extern int32_t g_forceWeaponChoice; +extern int32_t g_fakeMultiMode; diff --git a/source/rr/src/common.cpp b/source/rr/src/common.cpp new file mode 100644 index 000000000..8da5514ad --- /dev/null +++ b/source/rr/src/common.cpp @@ -0,0 +1,1208 @@ +// +// Common non-engine code/data for EDuke32 and Mapster32 +// + +#include "compat.h" +#include "build.h" +#include "baselayer.h" +#include "palette.h" + +#include "grpscan.h" + +#ifdef _WIN32 +# define NEED_SHLWAPI_H +# include "windows_inc.h" +# include "winbits.h" +# ifndef KEY_WOW64_64KEY +# define KEY_WOW64_64KEY 0x0100 +# endif +# ifndef KEY_WOW64_32KEY +# define KEY_WOW64_32KEY 0x0200 +# endif +#elif defined __APPLE__ +# include "osxbits.h" +#endif + +#include "common.h" +#include "common_game.h" + +struct grpfile_t const *g_selectedGrp; + +int32_t g_gameType = GAMEFLAG_DUKE; +int g_addonNum = 0; + +// g_gameNamePtr can point to one of: grpfiles[].name (string literal), string +// literal, malloc'd block (XXX: possible leak) +const char *g_gameNamePtr = NULL; + +// grp/con handling + +static const char *defaultconfilename = "GAME.CON"; +static const char *defaultgamegrp[GAMECOUNT] = { "DUKE3D.GRP", "REDNECK.GRP", "REDNECK.GRP" }; +static const char *defaultdeffilename[GAMECOUNT] = { "duke3d.def", "rr.def", "rrra.def" }; +//static const char *defaultgameconfilename[GAMECOUNT] = { "GAME.CON", "GAME.CON", "GAME.CON" }; + +// g_grpNamePtr can ONLY point to a malloc'd block (length BMAX_PATH) +char *g_grpNamePtr = NULL; +// g_scriptNamePtr can ONLY point to a malloc'd block (length BMAX_PATH) +char *g_scriptNamePtr = NULL; +// g_rtsNamePtr can ONLY point to a malloc'd block (length BMAX_PATH) +char *g_rtsNamePtr = NULL; + +void clearGrpNamePtr(void) +{ + Bfree(g_grpNamePtr); + // g_grpNamePtr assumed to be assigned to right after +} + +void clearScriptNamePtr(void) +{ + Bfree(g_scriptNamePtr); + // g_scriptNamePtr assumed to be assigned to right after +} + +const char *G_DefaultGrpFile(void) +{ + if (DUKE) + return defaultgamegrp[GAME_DUKE]; + else if (RR) + return defaultgamegrp[GAME_RR]; + + return defaultgamegrp[0]; +} +const char *G_DefaultDefFile(void) +{ + if (DUKE) + return defaultdeffilename[GAME_DUKE]; + else if (RRRA) + return defaultdeffilename[GAME_RRRA]; + else if (RR) + return defaultdeffilename[GAME_RR]; + + return defaultdeffilename[0]; +} +const char *G_DefaultConFile(void) +{ +#if 0 + if (DUKE && testkopen(defaultgameconfilename[GAME_DUKE],0)) + return defaultgameconfilename[GAME_DUKE]; + else if (WW2GI && testkopen(defaultgameconfilename[GAME_WW2GI],0)) + return defaultgameconfilename[GAME_WW2GI]; + else if (NAPALM) + { + if (!testkopen(defaultgameconfilename[GAME_NAPALM],0)) + { + if (testkopen(defaultgameconfilename[GAME_NAM],0)) + return defaultgameconfilename[GAME_NAM]; // NAM/NAPALM Sharing + } + else + return defaultgameconfilename[GAME_NAPALM]; + } + else if (NAM) + { + if (!testkopen(defaultgameconfilename[GAME_NAM],0)) + { + if (testkopen(defaultgameconfilename[GAME_NAPALM],0)) + return defaultgameconfilename[GAME_NAPALM]; // NAM/NAPALM Sharing + } + else + return defaultgameconfilename[GAME_NAM]; + } +#endif + return defaultconfilename; +} + +const char *G_GrpFile(void) +{ + return (g_grpNamePtr == NULL) ? G_DefaultGrpFile() : g_grpNamePtr; +} + +const char *G_DefFile(void) +{ + return (g_defNamePtr == NULL) ? G_DefaultDefFile() : g_defNamePtr; +} + +const char *G_ConFile(void) +{ + return (g_scriptNamePtr == NULL) ? G_DefaultConFile() : g_scriptNamePtr; +} + +////////// + +// Set up new-style multi-psky handling. +void G_InitMultiPsky(int CLOUDYOCEAN__DYN, int MOONSKY1__DYN, int BIGORBIT1__DYN, int LA__DYN) +{ + // When adding other multi-skies, take care that the tileofs[] values are + // <= PSKYOFF_MAX. (It can be increased up to MAXPSKYTILES, but should be + // set as tight as possible.) + + // The default sky properties (all others are implicitly zero): + psky_t *sky = tileSetupSky(DEFAULTPSKY); + sky->lognumtiles = 3; + sky->horizfrac = 32768; + + // CLOUDYOCEAN + // Aligns with the drawn scene horizon because it has one itself. + sky = tileSetupSky(CLOUDYOCEAN__DYN); + sky->lognumtiles = 3; + sky->horizfrac = 65536; + + // MOONSKY1 + // earth mountain mountain sun + sky = tileSetupSky(MOONSKY1__DYN); + sky->lognumtiles = 3; + sky->horizfrac = 32768; + sky->tileofs[6] = 1; + sky->tileofs[1] = 2; + sky->tileofs[4] = 2; + sky->tileofs[2] = 3; + + // BIGORBIT1 // orbit + // earth1 2 3 moon/sun + sky = tileSetupSky(BIGORBIT1__DYN); + sky->lognumtiles = 3; + sky->horizfrac = 32768; + sky->tileofs[5] = 1; + sky->tileofs[6] = 2; + sky->tileofs[7] = 3; + sky->tileofs[2] = 4; + + // LA // la city + // earth1 2 3 moon/sun + sky = tileSetupSky(LA__DYN); + sky->lognumtiles = 3; + sky->horizfrac = 16384 + 1024; + sky->tileofs[0] = 1; + sky->tileofs[1] = 2; + sky->tileofs[2] = 1; + sky->tileofs[3] = 3; + sky->tileofs[4] = 4; + sky->tileofs[5] = 0; + sky->tileofs[6] = 2; + sky->tileofs[7] = 3; + +#if 0 + // This assertion should hold. See note above. + for (bssize_t i=0; i = 0; i--) + { + if (sector[i].ceilingstat & 1) + { + skyIdx = getpskyidx(sector[i].ceilingpicnum); + if (skyIdx > 0) + break; + } + } + + g_pskyidx = skyIdx; +} + +////////// + +static char g_rootDir[BMAX_PATH]; +char g_modDir[BMAX_PATH] = "/"; + +int kopen4loadfrommod(const char *fileName, char searchfirst) +{ + int kFile = -1; + + if (g_modDir[0] != '/' || g_modDir[1] != 0) + { + static char staticFileName[BMAX_PATH]; + Bsnprintf(staticFileName, sizeof(staticFileName), "%s/%s", g_modDir, fileName); + kFile = kopen4load(staticFileName, searchfirst); + } + + return (kFile < 0) ? kopen4load(fileName, searchfirst) : kFile; +} + +int g_useCwd; +static void G_LoadAddon(void); +int32_t g_groupFileHandle; + +void G_ExtPreInit(int32_t argc,char const * const * argv) +{ + g_useCwd = G_CheckCmdSwitch(argc, argv, "-usecwd"); + +#ifdef _WIN32 + GetModuleFileName(NULL,g_rootDir,BMAX_PATH); + Bcorrectfilename(g_rootDir,1); + //chdir(g_rootDir); +#else + getcwd(g_rootDir,BMAX_PATH); + strcat(g_rootDir,"/"); +#endif +} + +void G_ExtInit(void) +{ + char cwd[BMAX_PATH]; + +#ifdef EDUKE32_OSX + char *appdir = Bgetappdir(); + addsearchpath(appdir); + Bfree(appdir); +#endif + + if (getcwd(cwd,BMAX_PATH) && Bstrcmp(cwd,"/") != 0) + addsearchpath(cwd); + + if (CommandPaths) + { + int32_t i; + struct strllist *s; + while (CommandPaths) + { + s = CommandPaths->next; + i = addsearchpath(CommandPaths->str); + if (i < 0) + { + initprintf("Failed adding %s for game data: %s\n", CommandPaths->str, + i==-1 ? "not a directory" : "no such directory"); + } + + Bfree(CommandPaths->str); + Bfree(CommandPaths); + CommandPaths = s; + } + } + +#if defined(_WIN32) + if (!access("user_profiles_enabled", F_OK)) +#else + if (g_useCwd == 0 && access("user_profiles_disabled", F_OK)) +#endif + { + char *homedir; + int32_t asperr; + + if ((homedir = Bgethomedir())) + { + Bsnprintf(cwd,sizeof(cwd),"%s/" +#if defined(_WIN32) + APPNAME +#elif defined(GEKKO) + "apps/" APPBASENAME +#else + ".config/" APPBASENAME +#endif + ,homedir); + asperr = addsearchpath(cwd); + if (asperr == -2) + { + if (Bmkdir(cwd,S_IRWXU) == 0) asperr = addsearchpath(cwd); + else asperr = -1; + } + if (asperr == 0) + Bchdir(cwd); + Bfree(homedir); + } + } + + // JBF 20031220: Because it's annoying renaming GRP files whenever I want to test different game data + if (g_grpNamePtr == NULL) + { + const char *cp = getenv("DUKE3DGRP"); + if (cp) + { + clearGrpNamePtr(); + g_grpNamePtr = dup_filename(cp); + initprintf("Using \"%s\" as main GRP file\n", g_grpNamePtr); + } + } +} + +void G_ScanGroups(void) +{ + ScanGroups(); + + g_selectedGrp = NULL; + + char const * const currentGrp = G_GrpFile(); + + for (grpfile_t const *fg = foundgrps; fg; fg=fg->next) + { + if (!Bstrcasecmp(fg->filename, currentGrp)) + { + g_selectedGrp = fg; + break; + } + } + + if (g_selectedGrp == NULL) + g_selectedGrp = foundgrps; +} + +static int32_t G_TryLoadingGrp(char const * const grpfile) +{ + int32_t i; + + if ((i = initgroupfile(grpfile)) == -1) + initprintf("Warning: could not find main data file \"%s\"!\n", grpfile); + else + initprintf("Using \"%s\" as main game data file.\n", grpfile); + + return i; +} + +static int32_t G_LoadGrpDependencyChain(grpfile_t const * const grp) +{ + if (!grp) + return -1; + + if (grp->type->dependency && grp->type->dependency != grp->type->crcval) + G_LoadGrpDependencyChain(FindGroup(grp->type->dependency)); + + int32_t const i = G_TryLoadingGrp(grp->filename); + + if (grp->type->postprocessing) + grp->type->postprocessing(i); + + return i; +} + +void G_LoadGroups(int32_t autoload) +{ + if (g_modDir[0] != '/') + { + char cwd[BMAX_PATH]; + + Bstrcat(g_rootDir, g_modDir); + addsearchpath(g_rootDir); + // addsearchpath(mod_dir); + + char path[BMAX_PATH]; + + if (getcwd(cwd, BMAX_PATH)) + { + Bsnprintf(path, sizeof(path), "%s/%s", cwd, g_modDir); + if (!Bstrcmp(g_rootDir, path)) + { + if (addsearchpath(path) == -2) + if (Bmkdir(path, S_IRWXU) == 0) + addsearchpath(path); + } + } + + } + + if (g_addonNum) + G_LoadAddon(); + + const char *grpfile; + int32_t i; + + if ((i = G_LoadGrpDependencyChain(g_selectedGrp)) != -1) + { + grpfile = g_selectedGrp->filename; + + clearGrpNamePtr(); + g_grpNamePtr = dup_filename(grpfile); + + grpinfo_t const * const type = g_selectedGrp->type; + + g_gameType = type->game; + g_gameNamePtr = type->name; + + if (type->scriptname && g_scriptNamePtr == NULL) + g_scriptNamePtr = dup_filename(type->scriptname); + + if (type->defname && g_defNamePtr == NULL) + g_defNamePtr = dup_filename(type->defname); + + if (type->rtsname && g_rtsNamePtr == NULL) + g_rtsNamePtr = dup_filename(type->rtsname); + } + else + { + grpfile = G_GrpFile(); + i = G_TryLoadingGrp(grpfile); + } + + if (autoload) + { + G_LoadGroupsInDir("autoload"); + + if (i != -1) + G_DoAutoload(grpfile); + } + + if (g_modDir[0] != '/') + G_LoadGroupsInDir(g_modDir); + + if (g_defNamePtr == NULL) + { + const char *tmpptr = getenv("DUKE3DDEF"); + if (tmpptr) + { + clearDefNamePtr(); + g_defNamePtr = dup_filename(tmpptr); + initprintf("Using \"%s\" as definitions file\n", g_defNamePtr); + } + } + + loaddefinitions_game(G_DefFile(), TRUE); + + struct strllist *s; + + int const bakpathsearchmode = pathsearchmode; + pathsearchmode = 1; + + while (CommandGrps) + { + int32_t j; + + s = CommandGrps->next; + + if ((j = initgroupfile(CommandGrps->str)) == -1) + initprintf("Could not find file \"%s\".\n", CommandGrps->str); + else + { + g_groupFileHandle = j; + initprintf("Using file \"%s\" as game data.\n", CommandGrps->str); + if (autoload) + G_DoAutoload(CommandGrps->str); + } + + Bfree(CommandGrps->str); + Bfree(CommandGrps); + CommandGrps = s; + } + pathsearchmode = bakpathsearchmode; +} + +#if defined _WIN32 +static int G_ReadRegistryValue(char const * const SubKey, char const * const Value, char * const Output, DWORD * OutputSize) +{ + // KEY_WOW64_32KEY gets us around Wow6432Node on 64-bit builds + REGSAM const wow64keys[] = { KEY_WOW64_32KEY, KEY_WOW64_64KEY }; + + for (auto &wow64key : wow64keys) + { + HKEY hkey; + LONG keygood = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NULL, 0, KEY_READ | wow64key, &hkey); + + if (keygood != ERROR_SUCCESS) + continue; + + LONG retval = SHGetValueA(hkey, SubKey, Value, NULL, Output, OutputSize); + + RegCloseKey(hkey); + + if (retval == ERROR_SUCCESS) + return 1; + } + + return 0; +} +#endif + +static void G_LoadAddon(void) +{ + int32_t crc = 0; // compiler-happy + + switch (g_addonNum) + { + case ADDON_DUKEDC: + crc = DUKEDC_CRC; + break; + case ADDON_NWINTER: + crc = DUKENW_CRC; + break; + case ADDON_CARIBBEAN: + crc = DUKECB_CRC; + break; + } + + if (!crc) return; + + grpfile_t const * const grp = FindGroup(crc); + + if (grp) + g_selectedGrp = grp; +} + +#ifndef EDUKE32_TOUCH_DEVICES +#if defined EDUKE32_OSX || defined __linux__ || defined EDUKE32_BSD +static void G_AddSteamPaths(const char *basepath) +{ + char buf[BMAX_PATH]; + + // Duke Nukem 3D: Megaton Edition (Steam) + Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Duke Nukem 3D/gameroot", basepath); + addsearchpath(buf); + Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Duke Nukem 3D/gameroot/addons/dc", basepath); + addsearchpath_user(buf, SEARCHPATH_REMOVE); + Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Duke Nukem 3D/gameroot/addons/nw", basepath); + addsearchpath_user(buf, SEARCHPATH_REMOVE); + Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Duke Nukem 3D/gameroot/addons/vacation", basepath); + addsearchpath_user(buf, SEARCHPATH_REMOVE); + + // Duke Nukem 3D (3D Realms Anthology (Steam) / Kill-A-Ton Collection 2015) +#if defined EDUKE32_OSX + Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Duke Nukem 3D/Duke Nukem 3D.app/drive_c/Program Files/Duke Nukem 3D", basepath); + addsearchpath_user(buf, SEARCHPATH_REMOVE); +#endif + +#if 0 + // NAM (Steam) +#if defined EDUKE32_OSX + Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Nam/Nam.app/Contents/Resources/Nam.boxer/C.harddisk/NAM", basepath); +#else + Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/Nam/NAM", basepath); +#endif + addsearchpath_user(buf, SEARCHPATH_NAM); + + // WWII GI (Steam) + Bsnprintf(buf, sizeof(buf), "%s/steamapps/common/World War II GI/WW2GI", basepath); + addsearchpath_user(buf, SEARCHPATH_WW2GI); +#endif +} + +// A bare-bones "parser" for Valve's KeyValues VDF format. +// There is no guarantee this will function properly with ill-formed files. +static void KeyValues_SkipWhitespace(char **vdfbuf, char * const vdfbufend) +{ + while (((*vdfbuf)[0] == ' ' || (*vdfbuf)[0] == '\n' || (*vdfbuf)[0] == '\r' || (*vdfbuf)[0] == '\t' || (*vdfbuf)[0] == '\0') && *vdfbuf < vdfbufend) + (*vdfbuf)++; + + // comments + if ((*vdfbuf) + 2 < vdfbufend && (*vdfbuf)[0] == '/' && (*vdfbuf)[1] == '/') + { + while ((*vdfbuf)[0] != '\n' && (*vdfbuf)[0] != '\r' && *vdfbuf < vdfbufend) + (*vdfbuf)++; + + KeyValues_SkipWhitespace(vdfbuf, vdfbufend); + } +} +static void KeyValues_SkipToEndOfQuotedToken(char **vdfbuf, char * const vdfbufend) +{ + (*vdfbuf)++; + while ((*vdfbuf)[0] != '\"' && (*vdfbuf)[-1] != '\\' && *vdfbuf < vdfbufend) + (*vdfbuf)++; +} +static void KeyValues_SkipToEndOfUnquotedToken(char **vdfbuf, char * const vdfbufend) +{ + while ((*vdfbuf)[0] != ' ' && (*vdfbuf)[0] != '\n' && (*vdfbuf)[0] != '\r' && (*vdfbuf)[0] != '\t' && (*vdfbuf)[0] != '\0' && *vdfbuf < vdfbufend) + (*vdfbuf)++; +} +static void KeyValues_SkipNextWhatever(char **vdfbuf, char * const vdfbufend) +{ + KeyValues_SkipWhitespace(vdfbuf, vdfbufend); + + if (*vdfbuf == vdfbufend) + return; + + if ((*vdfbuf)[0] == '{') + { + (*vdfbuf)++; + do + { + KeyValues_SkipNextWhatever(vdfbuf, vdfbufend); + } + while ((*vdfbuf)[0] != '}'); + (*vdfbuf)++; + } + else if ((*vdfbuf)[0] == '\"') + KeyValues_SkipToEndOfQuotedToken(vdfbuf, vdfbufend); + else if ((*vdfbuf)[0] != '}') + KeyValues_SkipToEndOfUnquotedToken(vdfbuf, vdfbufend); + + KeyValues_SkipWhitespace(vdfbuf, vdfbufend); +} +static char* KeyValues_NormalizeToken(char **vdfbuf, char * const vdfbufend) +{ + char *token = *vdfbuf; + + if ((*vdfbuf)[0] == '\"' && *vdfbuf < vdfbufend) + { + token++; + + KeyValues_SkipToEndOfQuotedToken(vdfbuf, vdfbufend); + (*vdfbuf)[0] = '\0'; + + // account for escape sequences + char *writeseeker = token, *readseeker = token; + while (readseeker <= *vdfbuf) + { + if (readseeker[0] == '\\') + readseeker++; + + writeseeker[0] = readseeker[0]; + + writeseeker++; + readseeker++; + } + + return token; + } + + KeyValues_SkipToEndOfUnquotedToken(vdfbuf, vdfbufend); + (*vdfbuf)[0] = '\0'; + + return token; +} +static void KeyValues_FindKey(char **vdfbuf, char * const vdfbufend, const char *token) +{ + char *ParentKey = KeyValues_NormalizeToken(vdfbuf, vdfbufend); + if (token != NULL) // pass in NULL to find the next key instead of a specific one + while (Bstrcmp(ParentKey, token) != 0 && *vdfbuf < vdfbufend) + { + KeyValues_SkipNextWhatever(vdfbuf, vdfbufend); + ParentKey = KeyValues_NormalizeToken(vdfbuf, vdfbufend); + } + + KeyValues_SkipWhitespace(vdfbuf, vdfbufend); +} +static int32_t KeyValues_FindParentKey(char **vdfbuf, char * const vdfbufend, const char *token) +{ + KeyValues_SkipWhitespace(vdfbuf, vdfbufend); + + // end of scope + if ((*vdfbuf)[0] == '}') + return 0; + + KeyValues_FindKey(vdfbuf, vdfbufend, token); + + // ignore the wrong type + while ((*vdfbuf)[0] != '{' && *vdfbuf < vdfbufend) + { + KeyValues_SkipNextWhatever(vdfbuf, vdfbufend); + KeyValues_FindKey(vdfbuf, vdfbufend, token); + } + + if (*vdfbuf == vdfbufend) + return 0; + + return 1; +} +static char* KeyValues_FindKeyValue(char **vdfbuf, char * const vdfbufend, const char *token) +{ + KeyValues_SkipWhitespace(vdfbuf, vdfbufend); + + // end of scope + if ((*vdfbuf)[0] == '}') + return NULL; + + KeyValues_FindKey(vdfbuf, vdfbufend, token); + + // ignore the wrong type + while ((*vdfbuf)[0] == '{' && *vdfbuf < vdfbufend) + { + KeyValues_SkipNextWhatever(vdfbuf, vdfbufend); + KeyValues_FindKey(vdfbuf, vdfbufend, token); + } + + KeyValues_SkipWhitespace(vdfbuf, vdfbufend); + + if (*vdfbuf == vdfbufend) + return NULL; + + return KeyValues_NormalizeToken(vdfbuf, vdfbufend); +} + +static void G_ParseSteamKeyValuesForPaths(const char *vdf) +{ + int32_t fd = Bopen(vdf, BO_RDONLY); + int32_t size = Bfilelength(fd); + char *vdfbufstart, *vdfbuf, *vdfbufend; + + if (size <= 0) + return; + + vdfbufstart = vdfbuf = (char*)Xmalloc(size); + size = (int32_t)Bread(fd, vdfbuf, size); + Bclose(fd); + vdfbufend = vdfbuf + size; + + if (KeyValues_FindParentKey(&vdfbuf, vdfbufend, "LibraryFolders")) + { + char *result; + vdfbuf++; + while ((result = KeyValues_FindKeyValue(&vdfbuf, vdfbufend, NULL)) != NULL) + G_AddSteamPaths(result); + } + + Bfree(vdfbufstart); +} +#endif +#endif + +void G_AddSearchPaths(void) +{ +#ifndef EDUKE32_TOUCH_DEVICES +#if defined __linux__ || defined EDUKE32_BSD + char buf[BMAX_PATH]; + char *homepath = Bgethomedir(); + + Bsnprintf(buf, sizeof(buf), "%s/.steam/steam", homepath); + G_AddSteamPaths(buf); + + Bsnprintf(buf, sizeof(buf), "%s/.steam/steam/steamapps/libraryfolders.vdf", homepath); + G_ParseSteamKeyValuesForPaths(buf); + + Bfree(homepath); + + addsearchpath("/usr/share/games/jfduke3d"); + addsearchpath("/usr/local/share/games/jfduke3d"); + addsearchpath("/usr/share/games/eduke32"); + addsearchpath("/usr/local/share/games/eduke32"); +#elif defined EDUKE32_OSX + char buf[BMAX_PATH]; + int32_t i; + char *applications[] = { osx_getapplicationsdir(0), osx_getapplicationsdir(1) }; + char *support[] = { osx_getsupportdir(0), osx_getsupportdir(1) }; + + for (i = 0; i < 2; i++) + { + Bsnprintf(buf, sizeof(buf), "%s/Steam", support[i]); + G_AddSteamPaths(buf); + + Bsnprintf(buf, sizeof(buf), "%s/Steam/steamapps/libraryfolders.vdf", support[i]); + G_ParseSteamKeyValuesForPaths(buf); + + // Duke Nukem 3D: Atomic Edition (GOG.com) + Bsnprintf(buf, sizeof(buf), "%s/Duke Nukem 3D.app/Contents/Resources/Duke Nukem 3D.boxer/C.harddisk", applications[i]); + addsearchpath_user(buf, SEARCHPATH_REMOVE); + } + + for (i = 0; i < 2; i++) + { + Bsnprintf(buf, sizeof(buf), "%s/JFDuke3D", support[i]); + addsearchpath(buf); + Bsnprintf(buf, sizeof(buf), "%s/EDuke32", support[i]); + addsearchpath(buf); + } + + for (i = 0; i < 2; i++) + { + Bfree(applications[i]); + Bfree(support[i]); + } +#elif defined (_WIN32) + char buf[BMAX_PATH] = {0}; + DWORD bufsize; + + // Duke Nukem 3D: 20th Anniversary World Tour (Steam) + bufsize = sizeof(buf); + if (G_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 434050)", "InstallLocation", buf, &bufsize)) + { + addsearchpath_user(buf, SEARCHPATH_REMOVE); + } + + // Duke Nukem 3D: Megaton Edition (Steam) + bufsize = sizeof(buf); + if (G_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 225140)", "InstallLocation", buf, &bufsize)) + { + char * const suffix = buf + bufsize - 1; + DWORD const remaining = sizeof(buf) - bufsize; + + Bstrncpy(suffix, "/gameroot", remaining); + addsearchpath(buf); + Bstrncpy(suffix, "/gameroot/addons/dc", remaining); + addsearchpath_user(buf, SEARCHPATH_REMOVE); + Bstrncpy(suffix, "/gameroot/addons/nw", remaining); + addsearchpath_user(buf, SEARCHPATH_REMOVE); + Bstrncpy(suffix, "/gameroot/addons/vacation", remaining); + addsearchpath_user(buf, SEARCHPATH_REMOVE); + } + + // Duke Nukem 3D (3D Realms Anthology (Steam) / Kill-A-Ton Collection 2015) + bufsize = sizeof(buf); + if (G_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 359850)", "InstallLocation", buf, &bufsize)) + { + char * const suffix = buf + bufsize - 1; + DWORD const remaining = sizeof(buf) - bufsize; + + Bstrncpy(suffix, "/Duke Nukem 3D", remaining); + addsearchpath_user(buf, SEARCHPATH_REMOVE); + } + + // Duke Nukem 3D: Atomic Edition (GOG.com) + bufsize = sizeof(buf); + if (G_ReadRegistryValue("SOFTWARE\\GOG.com\\GOGDUKE3D", "PATH", buf, &bufsize)) + { + addsearchpath_user(buf, SEARCHPATH_REMOVE); + } + + // Duke Nukem 3D (3D Realms Anthology) + bufsize = sizeof(buf); + if (G_ReadRegistryValue("SOFTWARE\\3DRealms\\Duke Nukem 3D", NULL, buf, &bufsize)) + { + char * const suffix = buf + bufsize - 1; + DWORD const remaining = sizeof(buf) - bufsize; + + Bstrncpy(suffix, "/Duke Nukem 3D", remaining); + addsearchpath_user(buf, SEARCHPATH_REMOVE); + } + + // 3D Realms Anthology + bufsize = sizeof(buf); + if (G_ReadRegistryValue("SOFTWARE\\3DRealms\\Anthology", NULL, buf, &bufsize)) + { + char * const suffix = buf + bufsize - 1; + DWORD const remaining = sizeof(buf) - bufsize; + + Bstrncpy(suffix, "/Duke Nukem 3D", remaining); + addsearchpath_user(buf, SEARCHPATH_REMOVE); + } + + // Redneck Rampage (GOG.com) + bufsize = sizeof(buf); + if (G_ReadRegistryValue("SOFTWARE\\GOG.com\\GOGREDNECKRAMPAGE", "PATH", buf, &bufsize)) + { + addsearchpath_user(buf, SEARCHPATH_RR); + } + + // Redneck Rampage Rides Again (GOG.com) + bufsize = sizeof(buf); + if (G_ReadRegistryValue("SOFTWARE\\GOG.com\\GOGCREDNECKRIDESAGAIN", "PATH", buf, &bufsize)) + { + addsearchpath_user(buf, SEARCHPATH_RRRA); + } + +#if 0 + // NAM (Steam) + bufsize = sizeof(buf); + if (G_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 329650)", "InstallLocation", buf, &bufsize)) + { + char * const suffix = buf + bufsize - 1; + DWORD const remaining = sizeof(buf) - bufsize; + + Bstrncpy(suffix, "/NAM", remaining); + addsearchpath_user(buf, SEARCHPATH_NAM); + } + + // WWII GI (Steam) + bufsize = sizeof(buf); + if (G_ReadRegistryValue(R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 376750)", "InstallLocation", buf, &bufsize)) + { + char * const suffix = buf + bufsize - 1; + DWORD const remaining = sizeof(buf) - bufsize; + + Bstrncpy(suffix, "/WW2GI", remaining); + addsearchpath_user(buf, SEARCHPATH_WW2GI); + } +#endif +#endif +#endif +} + +void G_CleanupSearchPaths(void) +{ + removesearchpaths_withuser(SEARCHPATH_REMOVE); + + if (!RRRA) + removesearchpaths_withuser(SEARCHPATH_RRRA); + + if (!RR || RRRA) + removesearchpaths_withuser(SEARCHPATH_RR); +} + +////////// + +struct strllist *CommandPaths, *CommandGrps; + +GrowArray g_scriptModules; + +void G_AddGroup(const char *buffer) +{ + char buf[BMAX_PATH]; + + struct strllist *s = (struct strllist *)Xcalloc(1,sizeof(struct strllist)); + + Bstrcpy(buf, buffer); + + if (Bstrchr(buf,'.') == 0) + Bstrcat(buf,".grp"); + + s->str = Xstrdup(buf); + + if (CommandGrps) + { + struct strllist *t; + for (t = CommandGrps; t->next; t=t->next) ; + t->next = s; + return; + } + CommandGrps = s; +} + +void G_AddPath(const char *buffer) +{ + struct strllist *s = (struct strllist *)Xcalloc(1,sizeof(struct strllist)); + s->str = Xstrdup(buffer); + + if (CommandPaths) + { + struct strllist *t; + for (t = CommandPaths; t->next; t=t->next) ; + t->next = s; + return; + } + CommandPaths = s; +} + +void G_AddCon(const char *buffer) +{ + clearScriptNamePtr(); + g_scriptNamePtr = dup_filename(buffer); + initprintf("Using CON file \"%s\".\n",g_scriptNamePtr); +} + +void G_AddConModule(const char *buffer) +{ + g_scriptModules.append(Xstrdup(buffer)); +} + +////////// + +// loads all group (grp, zip, pk3/4) files in the given directory +void G_LoadGroupsInDir(const char *dirname) +{ + static const char *extensions[] = { "*.grp", "*.zip", "*.ssi", "*.pk3", "*.pk4" }; + char buf[BMAX_PATH]; + fnlist_t fnlist = FNLIST_INITIALIZER; + + for (auto & extension : extensions) + { + CACHE1D_FIND_REC *rec; + + fnlist_getnames(&fnlist, dirname, extension, -1, 0); + + for (rec=fnlist.findfiles; rec; rec=rec->next) + { + Bsnprintf(buf, sizeof(buf), "%s/%s", dirname, rec->name); + initprintf("Using group file \"%s\".\n", buf); + initgroupfile(buf); + } + + fnlist_clearnames(&fnlist); + } +} + +void G_DoAutoload(const char *dirname) +{ + char buf[BMAX_PATH]; + + Bsnprintf(buf, sizeof(buf), "autoload/%s", dirname); + G_LoadGroupsInDir(buf); +} + +////////// + +void G_LoadLookups(void) +{ + int32_t fp, j; + + if ((fp=kopen4loadfrommod("lookup.dat",0)) == -1) + if ((fp=kopen4loadfrommod("lookup.dat",1)) == -1) + return; + + j = paletteLoadLookupTable(fp); + + if (j < 0) + { + if (j == -1) + initprintf("ERROR loading \"lookup.dat\": failed reading enough data.\n"); + + return kclose(fp); + } + + uint8_t paldata[768]; + + for (j=1; j<=5; j++) + { + // Account for TITLE and REALMS swap between basepal number and on-disk order. + int32_t basepalnum = (j == 3 || j == 4) ? 4+3-j : j; + + if (kread_and_test(fp, paldata, 768)) + return kclose(fp); + + for (bssize_t k = 0; k < 768; k++) + paldata[k] <<= 2; + + paletteSetColorTable(basepalnum, paldata); + } + + Bmemcpy(paldata, palette+1, 767); + paldata[767] = palette[767]; + paletteSetColorTable(DRUGPAL, paldata); + + kclose(fp); + + if (RR) + { + char table[256]; + for (bssize_t i = 0; i < 256; i++) + table[i] = i; + for (bssize_t i = 0; i < 32; i++) + table[i] = i+32; + + paletteMakeLookupTable(7, table, 0, 0, 0, 0); + + for (bssize_t i = 0; i < 256; i++) + table[i] = i; + paletteMakeLookupTable(30, table, 0, 0, 0, 0); + paletteMakeLookupTable(31, table, 0, 0, 0, 0); + paletteMakeLookupTable(32, table, 0, 0, 0, 0); + paletteMakeLookupTable(33, table, 0, 0, 0, 0); + if (RRRA) + paletteMakeLookupTable(105, table, 0, 0, 0, 0); + + j = 63; + for (bssize_t i = 64; i < 80; i++) + { + j--; + table[i] = j; + table[i+16] = i-24; + } + table[80] = 80; + table[81] = 81; + for (bssize_t i = 0; i < 32; i++) + table[i] = i+32; + paletteMakeLookupTable(34, table, 0, 0, 0, 0); + for (bssize_t i = 0; i < 256; i++) + table[i] = i; + for (bssize_t i = 0; i < 16; i++) + table[i] = i+129; + for (bssize_t i = 16; i < 32; i++) + table[i] = i+192; + paletteMakeLookupTable(35, table, 0, 0, 0, 0); + if (RRRA) + paletteMakeLookupTable(54, palookup[8], 32*4, 32*4, 32*4, 0); + } +} + +////////// + +#ifdef FORMAT_UPGRADE_ELIGIBLE + +static int32_t S_TryFormats(char * const testfn, char * const fn_suffix, char const searchfirst) +{ +#ifdef HAVE_FLAC + { + Bstrcpy(fn_suffix, ".flac"); + int32_t const fp = kopen4loadfrommod(testfn, searchfirst); + if (fp >= 0) + return fp; + } +#endif + +#ifdef HAVE_VORBIS + { + Bstrcpy(fn_suffix, ".ogg"); + int32_t const fp = kopen4loadfrommod(testfn, searchfirst); + if (fp >= 0) + return fp; + } +#endif + + return -1; +} + +static int32_t S_TryExtensionReplacements(char * const testfn, char const searchfirst, uint8_t const ismusic) +{ + char * extension = Bstrrchr(testfn, '.'); + char * const fn_end = Bstrchr(testfn, '\0'); + + // ex: grabbag.voc --> grabbag_voc.* + if (extension != NULL) + { + *extension = '_'; + + int32_t const fp = S_TryFormats(testfn, fn_end, searchfirst); + if (fp >= 0) + return fp; + } + else + { + extension = fn_end; + } + + // ex: grabbag.mid --> grabbag.* + if (ismusic) + { + int32_t const fp = S_TryFormats(testfn, extension, searchfirst); + if (fp >= 0) + return fp; + } + + return -1; +} + +int32_t S_OpenAudio(const char *fn, char searchfirst, uint8_t const ismusic) +{ + int32_t const origfp = kopen4loadfrommod(fn, searchfirst); + char const * const origparent = origfp != -1 ? kfileparent(origfp) : NULL; + uint32_t const origparentlength = origparent != NULL ? Bstrlen(origparent) : 0; + + char * const testfn = (char *)Xmalloc(Bstrlen(fn) + 12 + origparentlength); // "music/" + overestimation of parent minus extension + ".flac" + '\0' + + // look in ./ + // ex: ./grabbag.mid + { + Bstrcpy(testfn, fn); + int32_t const fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic); + if (fp >= 0) + { + Bfree(testfn); + kclose(origfp); + return fp; + } + } + + // look in ./music/ / + // ex: ./music/duke3d/grabbag.mid + // ex: ./music/nwinter/grabbag.mid + if (origparent != NULL) + { + char const * const origparentextension = Bstrrchr(origparent, '.'); + uint32_t namelength = origparentextension != NULL ? (unsigned)(origparentextension - origparent) : origparentlength; + + Bsprintf(testfn, "music/%.*s/%s", namelength, origparent, fn); + int32_t const fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic); + if (fp >= 0) + { + Bfree(testfn); + kclose(origfp); + return fp; + } + } + + // look in ./music/ + // ex: ./music/grabbag.mid + { + Bsprintf(testfn, "music/%s", fn); + int32_t const fp = S_TryExtensionReplacements(testfn, searchfirst, ismusic); + if (fp >= 0) + { + Bfree(testfn); + kclose(origfp); + return fp; + } + } + + Bfree(testfn); + return origfp; +} + +void Duke_CommonCleanup(void) +{ + DO_FREE_AND_NULL(g_grpNamePtr); + DO_FREE_AND_NULL(g_scriptNamePtr); + DO_FREE_AND_NULL(g_rtsNamePtr); +} + +#endif diff --git a/source/rr/src/common_game.h b/source/rr/src/common_game.h new file mode 100644 index 000000000..a379b90b6 --- /dev/null +++ b/source/rr/src/common_game.h @@ -0,0 +1,170 @@ +// +// Definitions of common game-only data structures/functions +// (and declarations of data appearing in both) +// for EDuke32 and Mapster32 +// + +#ifndef EDUKE32_COMMON_GAME_H_ +#define EDUKE32_COMMON_GAME_H_ + +#include "grpscan.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int g_useCwd; + +#ifndef APPNAME +#define APPNAME "Rednukem" +#endif + +#ifndef APPBASENAME +#define APPBASENAME "rednukem" +#endif + +#define GAMEFLAG_DUKE 0x00000001 +#define GAMEFLAG_RR 0x00000002 +#define GAMEFLAG_RRRA 0x00000004 +//#define GAMEFLAG_NAM 0x00000002 +//#define GAMEFLAG_NAPALM 0x00000004 +//#define GAMEFLAG_WW2GI 0x00000008 +#define GAMEFLAG_ADDON 0x00000010 +#define GAMEFLAG_SHAREWARE 0x00000020 +//#define GAMEFLAG_DUKEBETA 0x00000060 // includes 0x20 since it's a shareware beta +//#define GAMEFLAG_IONMAIDEN 0x00000080 +//#define GAMEFLAG_STANDALONE 0x00000100 +#define GAMEFLAGMASK 0x000000FF // flags allowed from grpinfo + +extern struct grpfile_t const *g_selectedGrp; + +extern int32_t g_gameType; +extern int g_addonNum; + +#define DUKE (g_gameType & GAMEFLAG_DUKE) +#define RR (g_gameType & GAMEFLAG_RR) +#define RRRA (g_gameType & GAMEFLAG_RRRA) +//#define NAM (g_gameType & GAMEFLAG_NAM) +//#define NAPALM (g_gameType & GAMEFLAG_NAPALM) +//#define WW2GI (g_gameType & GAMEFLAG_WW2GI) +//#define NAM_WW2GI (g_gameType & (GAMEFLAG_NAM|GAMEFLAG_WW2GI)) +#define SHAREWARE (g_gameType & GAMEFLAG_SHAREWARE) +//#define DUKEBETA ((g_gameType & GAMEFLAG_DUKEBETA) == GAMEFLAG_DUKEBETA) +//#define IONMAIDEN (g_gameType & GAMEFLAG_IONMAIDEN) + +enum Games_t { + GAME_DUKE = 0, + GAME_RR, + GAME_RRRA, + //GAME_NAM, + //GAME_NAPALM, + //GAME_WW2GI, + GAMECOUNT +}; + +enum instpath_t { + INSTPATH_STEAM_DUKE3D_MEGATON, + INSTPATH_STEAM_DUKE3D_3DR, + INSTPATH_GOG_DUKE3D, + INSTPATH_3DR_DUKE3D, + INSTPATH_3DR_ANTH, + //INSTPATH_STEAM_NAM, + //INSTPATH_STEAM_WW2GI, + INSTPATH_GOG_RR, + INSTPATH_GOG_RRRA, + NUMINSTPATHS +}; + +enum searchpathtypes_t { + SEARCHPATH_REMOVE = 1<<0, + SEARCHPATH_RR = 1<<1, + SEARCHPATH_RRRA = 1<<2, +}; + +typedef enum basepal_ { + BASEPAL = 0, + WATERPAL, + SLIMEPAL, + DREALMSPAL, + TITLEPAL, + ENDINGPAL, // 5 + ANIMPAL, + DRUGPAL, + BASEPALCOUNT +} basepal_t; + +#define OSDTEXT_DEFAULT "^00" +#define OSDTEXT_DARKRED "^10" +#define OSDTEXT_GREEN "^11" +#define OSDTEXT_RED "^21" +#define OSDTEXT_YELLOW "^23" + +#define OSDTEXT_BRIGHT "^S0" + +#define OSD_ERROR OSDTEXT_DARKRED OSDTEXT_BRIGHT + +extern const char *g_gameNamePtr; + +extern char *g_grpNamePtr; +extern char *g_scriptNamePtr; +extern char *g_rtsNamePtr; + +extern const char *G_DefaultGrpFile(void); +extern const char *G_GrpFile(void); + +extern const char *G_DefaultConFile(void); +extern const char *G_ConFile(void); + +extern GrowArray g_scriptModules; + +extern void G_AddCon(const char *buffer); +extern void G_AddConModule(const char *buffer); + +extern void clearGrpNamePtr(void); +extern void clearScriptNamePtr(void); + +extern int loaddefinitions_game(const char *fn, int32_t preload); +extern int32_t g_groupFileHandle; + +////////// + +extern void G_InitMultiPsky(int CLOUDYOCEAN__DYN, int MOONSKY1__DYN, int BIGORBIT1__DYN, int LA__DYN); +extern void G_SetupGlobalPsky(void); + +////////// + +extern char g_modDir[BMAX_PATH]; +extern int kopen4loadfrommod(const char *filename, char searchfirst); +extern void G_AddSearchPaths(void); +extern void G_CleanupSearchPaths(void); + +extern void G_ExtPreInit(int32_t argc,char const * const * argv); +extern void G_ExtInit(void); +extern void G_ScanGroups(void); +extern void G_LoadGroups(int32_t autoload); + +extern const char * G_GetInstallPath(int32_t insttype); + +////////// + +void G_LoadGroupsInDir(const char *dirname); +void G_DoAutoload(const char *dirname); + +////////// + +extern void G_LoadLookups(void); + +////////// + +#if defined HAVE_FLAC || defined HAVE_VORBIS +# define FORMAT_UPGRADE_ELIGIBLE +extern int32_t S_OpenAudio(const char *fn, char searchfirst, uint8_t ismusic); +#else +# define S_OpenAudio(fn, searchfirst, ismusic) kopen4loadfrommod(fn, searchfirst) +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/rr/src/config.cpp b/source/rr/src/config.cpp new file mode 100644 index 000000000..70029239e --- /dev/null +++ b/source/rr/src/config.cpp @@ -0,0 +1,973 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "duke3d.h" +#include "scriplib.h" +#include "osdcmds.h" +#include "renderlayer.h" +#include "cmdline.h" + +#ifdef __ANDROID__ +# include "android.h" +#endif + +#if defined RENDERTYPESDL && defined SDL_TARGET && SDL_TARGET > 1 +# include "sdl_inc.h" +#endif + +// we load this in to get default button and key assignments +// as well as setting up function mappings + +#define __SETUP__ // JBF 20031211 +#include "_functio.h" + +hashtable_t h_gamefuncs = { NUMGAMEFUNCTIONS<<1, NULL }; + +int32_t CONFIG_FunctionNameToNum(const char *func) +{ + int32_t i; + + if (!func) + return -1; + + i = hash_find(&h_gamefuncs,func); + + if (i < 0) + { + char *str = Bstrtolower(Xstrdup(func)); + i = hash_find(&h_gamefuncs,str); + Bfree(str); + + return i; + } + + return i; +} + + +char *CONFIG_FunctionNumToName(int32_t func) +{ + if ((unsigned)func >= (unsigned)NUMGAMEFUNCTIONS) + return NULL; + return gamefunctions[func]; +} + + +int32_t CONFIG_AnalogNameToNum(const char *func) +{ + if (!func) + return -1; + + if (!Bstrcasecmp(func,"analog_turning")) + { + return analog_turning; + } + if (!Bstrcasecmp(func,"analog_strafing")) + { + return analog_strafing; + } + if (!Bstrcasecmp(func,"analog_moving")) + { + return analog_moving; + } + if (!Bstrcasecmp(func,"analog_lookingupanddown")) + { + return analog_lookingupanddown; + } + + return -1; +} + + +const char *CONFIG_AnalogNumToName(int32_t func) +{ + switch (func) + { + case analog_turning: + return "analog_turning"; + case analog_strafing: + return "analog_strafing"; + case analog_moving: + return "analog_moving"; + case analog_lookingupanddown: + return "analog_lookingupanddown"; + } + + return NULL; +} + + +void CONFIG_SetDefaultKeys(const char (*keyptr)[MAXGAMEFUNCLEN]) +{ + Bmemset(ud.config.KeyboardKeys, 0xff, sizeof(ud.config.KeyboardKeys)); + + CONTROL_ClearAllBinds(); + + for (size_t i=0; i < ARRAY_SIZE(gamefunctions); ++i) + { + if (gamefunctions[i][0] == '\0') + continue; + + ud.config.KeyboardKeys[i][0] = KB_StringToScanCode(keyptr[i<<1]); + ud.config.KeyboardKeys[i][1] = KB_StringToScanCode(keyptr[(i<<1)+1]); + + if (i == gamefunc_Show_Console) + OSD_CaptureKey(ud.config.KeyboardKeys[i][0]); + else + CONFIG_MapKey(i, ud.config.KeyboardKeys[i][0], 0, ud.config.KeyboardKeys[i][1], 0); + } +} + + +void CONFIG_SetDefaults(void) +{ + // JBF 20031211 + int32_t i; + + ud.config.scripthandle = -1; +#ifdef __ANDROID__ + droidinput.forward_sens = 5.f; + droidinput.strafe_sens = 5.f; + droidinput.pitch_sens = 5.f; + droidinput.yaw_sens = 5.f; + droidinput.hideStick = 0; + droidinput.gameControlsAlpha = 0.5; + droidinput.toggleCrouch = 1; + droidinput.quickSelectWeapon = 1; + + ud.config.ScreenWidth = droidinfo.screen_width; + ud.config.ScreenHeight = droidinfo.screen_height; +#else +# if defined RENDERTYPESDL && SDL_MAJOR_VERSION > 1 + uint32_t inited = SDL_WasInit(SDL_INIT_VIDEO); + if (inited == 0) + SDL_Init(SDL_INIT_VIDEO); + else if (!(inited & SDL_INIT_VIDEO)) + SDL_InitSubSystem(SDL_INIT_VIDEO); + + SDL_DisplayMode dm; + if (SDL_GetDesktopDisplayMode(0, &dm) == 0) + { + ud.config.ScreenWidth = dm.w; + ud.config.ScreenHeight = dm.h; + } + else +# endif + { + ud.config.ScreenWidth = 1024; + ud.config.ScreenHeight = 768; + } +#endif + + ud.config.ScreenMode = 1; + +#ifdef USE_OPENGL + ud.config.ScreenBPP = 32; +#else + ud.config.ScreenBPP = 8; +#endif + ud.config.useprecache = 1; + ud.config.ForceSetup = 1; + ud.config.NoAutoLoad = 1; + ud.config.AmbienceToggle = 1; + ud.config.AutoAim = 1; + ud.config.FXVolume = 255; +#if defined(_WIN32) + ud.config.MixRate = 44100; +#elif defined __ANDROID__ + ud.config.MixRate = droidinfo.audio_sample_rate; +#else + ud.config.MixRate = 48000; +#endif + ud.config.MouseBias = 0; + ud.config.MouseDeadZone = 0; + ud.config.MusicToggle = 1; + ud.config.MusicVolume = 195; + g_myAimMode = g_player[0].ps->aim_mode = 1; + ud.config.NumBits = 16; + ud.config.NumChannels = 2; +#if defined GEKKO || defined __OPENDINGUX__ + ud.config.NumVoices = 32; +#else + ud.config.NumVoices = 64; +#endif + ud.config.ReverseStereo = 0; + ud.auto_run = 1; + ud.config.ShowOpponentWeapons = 0; + ud.config.SmoothInput = 1; + ud.config.SoundToggle = 1; + ud.althud = 1; + ud.automsg = 0; + ud.autovote = 0; + ud.brightness = 8; + ud.camerasprite = -1; + +#if defined GEKKO || defined __OPENDINGUX__ + ud.camera_time = 11; +#elif defined(__ANDROID__) + ud.camera_time = 7; +#else + ud.camera_time = 4; +#endif + + ud.color = 0; + ud.crosshair = 1; + ud.crosshairscale = 50; + ud.obituaries = 1; + ud.democams = 1; + ud.detail = 0; + ud.drawweapon = 1; + ud.idplayers = 1; + ud.levelstats = 0; + ud.lockout = 0; + ud.m_ffire = 1; + ud.m_marker = 1; + ud.menu_slidebarz = 65536; + ud.menu_slidebarmargin = RR ? 6 * 65536 : 65536; + ud.menu_slidecursorz = RR ? 32768 : 65536; + ud.mouseaiming = 0; + ud.mouseflip = 1; + ud.msgdisptime = 120; + ud.pwlockout[0] = '\0'; + ud.runkey_mode = 0; + ud.screen_size = 4; + ud.screen_tilting = 1; + ud.shadows = 1; + ud.statusbarflags = STATUSBAR_NOSHRINK; + ud.statusbarmode = 1; + ud.statusbarscale = 100; + ud.team = 0; + ud.viewbob = 1; + ud.weaponsway = 1; + ud.weaponswitch = 3; // new+empty + ud.angleinterpolation = 0; +#ifdef GEKKO + ud.config.UseJoystick = 1; +#else + ud.config.UseJoystick = 0; +#endif + ud.config.UseMouse = 1; + ud.config.VoiceToggle = 5; // bitfield, 1 = local, 2 = dummy, 4 = other players in DM + ud.display_bonus_screen = 1; + ud.show_level_text = 1; + ud.configversion = 0; + ud.weaponscale = 100; + ud.textscale = 200; + ud.screenfade = 1; + ud.menubackground = 1; + ud.hudontop = 0; + ud.default_skill = 1; + ud.slidebar_paldisabled = 1; + ud.shadow_pal = 4; + ud.menu_scrollbartilenum = -1; + ud.menu_scrollbarz = 65536; + ud.menu_scrollcursorz = 65536; + ud.autosave = 1; + ud.autosavedeletion = 1; + ud.maxautosaves = 5; + + ud.config.CheckForUpdates = 1; + + Bstrcpy(ud.rtsname, G_DefaultRtsFile()); + + Bstrcpy(szPlayerName, "Player"); + + //if (RR) + //{ + // Bstrcpy(ud.ridecule[0], "Yer as ugly as a mud fence!"); + // Bstrcpy(ud.ridecule[1], "Duck you pecker-head!"); + // Bstrcpy(ud.ridecule[2], "You like that boy?"); + // Bstrcpy(ud.ridecule[3], "Yer lower than catfish crap!"); + // Bstrcpy(ud.ridecule[4], "Eat lead, you shit monkey!"); + // Bstrcpy(ud.ridecule[5], "You dumb-ass!"); + // Bstrcpy(ud.ridecule[6], "Yer slower'n a three legged dog!"); + // Bstrcpy(ud.ridecule[7], "Come on...Squeal like a pig!"); + // Bstrcpy(ud.ridecule[8], "Haw, haw, haw!"); + // Bstrcpy(ud.ridecule[9], "Now you gone and done it!"); + //} + //else + //{ + Bstrcpy(ud.ridecule[0], "An inspiration for birth control."); + Bstrcpy(ud.ridecule[1], "You're gonna die for that!"); + Bstrcpy(ud.ridecule[2], "It hurts to be you."); + Bstrcpy(ud.ridecule[3], "Lucky son of a bitch."); + Bstrcpy(ud.ridecule[4], "Hmmm... payback time."); + Bstrcpy(ud.ridecule[5], "You bottom dwelling scum sucker."); + Bstrcpy(ud.ridecule[6], "Damn, you're ugly."); + Bstrcpy(ud.ridecule[7], "Ha ha ha... wasted!"); + Bstrcpy(ud.ridecule[8], "You suck!"); + Bstrcpy(ud.ridecule[9], "AARRRGHHHHH!!!"); + //} + + // JBF 20031211 + + if (RR) + { + Bstrcpy((char*)keydefaults[gamefunc_Holo_Duke<<1], "B"); + Bstrcpy((char*)keydefaults[gamefunc_Jetpack<<1], "C"); + Bstrcpy((char*)keydefaults[gamefunc_NightVision<<1], "Y"); + Bstrcpy((char*)keydefaults[gamefunc_MedKit<<1], "R"); + Bstrcpy((char*)keydefaults[gamefunc_Steroids<<1], "M"); + Bstrcpy((char*)keydefaults[gamefunc_Show_Opponents_Weapon<<1], "V"); + + Bstrcpy((char*)oldkeydefaults[gamefunc_Holo_Duke<<1], "B"); + Bstrcpy((char*)oldkeydefaults[gamefunc_Jetpack<<1], "C"); + Bstrcpy((char*)oldkeydefaults[gamefunc_NightVision<<1], "Y"); + Bstrcpy((char*)oldkeydefaults[gamefunc_MedKit<<1], "W"); + Bstrcpy((char*)oldkeydefaults[gamefunc_Steroids<<1], "M"); + Bstrcpy((char*)oldkeydefaults[gamefunc_Show_Opponents_Weapon<<1], "E"); + Bstrcpy((char*)oldkeydefaults[gamefunc_Show_Console<<1], "V"); + } + + CONFIG_SetDefaultKeys(keydefaults); + + memset(ud.config.MouseFunctions, -1, sizeof(ud.config.MouseFunctions)); + for (i=0; i =4) continue; + ud.config.MouseFunctions[i][1] = CONFIG_FunctionNameToNum(mouseclickeddefaults[i]); + CONTROL_MapButton(ud.config.MouseFunctions[i][1], i, 1, controldevice_mouse); + } + + memset(ud.config.MouseDigitalFunctions, -1, sizeof(ud.config.MouseDigitalFunctions)); + for (i=0; i =0; i--) + { + if (ud.config.KeyboardKeys[i][0] == ii[k] || ud.config.KeyboardKeys[i][1] == ii[k]) + { + Bsprintf(buf,"gamefunc_%s; ",CONFIG_FunctionNumToName(i)); + Bstrcat(tempbuf,buf); + } + } + + i = Bstrlen(tempbuf); + if (i >= 2) + { + tempbuf[i-2] = 0; // cut off the trailing "; " + CONTROL_BindKey(ii[k], tempbuf, 1, ConsoleKeys[j].name ? ConsoleKeys[j].name : ">"); + } + else + { + CONTROL_FreeKeyBind(ii[k]); + } + } +} + + +void CONFIG_SetupMouse(void) +{ + int32_t i; + char str[80]; + char temp[80]; + int32_t scale; + + if (ud.config.scripthandle < 0) return; + + for (i=0; i 10) + tempbuf[Bstrlen(tempbuf) - 1] = '\0'; + + Bstrncpyz(szPlayerName, tempbuf, sizeof(szPlayerName)); + + SCRIPT_GetString(ud.config.scripthandle, "Comm Setup","RTSName",&ud.rtsname[0]); + + SCRIPT_GetNumber(ud.config.scripthandle, "Setup", "ConfigVersion", &ud.configversion); + SCRIPT_GetNumber(ud.config.scripthandle, "Setup", "ForceSetup", &ud.config.ForceSetup); + SCRIPT_GetNumber(ud.config.scripthandle, "Setup", "NoAutoLoad", &ud.config.NoAutoLoad); + SCRIPT_GetNumber(ud.config.scripthandle, "Setup", "CacheSize", &dummy); + + if (dummy > MAXCACHE1DSIZE) + MAXCACHE1DSIZE = dummy; + + if (g_noSetup == 0 && g_modDir[0] == '/') + { + struct Bstat st; + SCRIPT_GetString(ud.config.scripthandle, "Setup","ModDir",&g_modDir[0]); + + if (Bstat(g_modDir, &st)) + { + if ((st.st_mode & S_IFDIR) != S_IFDIR) + { + initprintf("Invalid mod dir in cfg!\n"); + Bsprintf(g_modDir,"/"); + } + } + } + + if (g_grpNamePtr == NULL && g_addonNum == 0) + { + SCRIPT_GetStringPtr(ud.config.scripthandle, "Setup","SelectedGRP",&g_grpNamePtr); + if (g_grpNamePtr && !Bstrlen(g_grpNamePtr)) + g_grpNamePtr = dup_filename(G_DefaultGrpFile()); + } + + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "Out",&ud.lockout); + SCRIPT_GetString(ud.config.scripthandle, "Screen Setup","Password",&ud.pwlockout[0]); + + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "ScreenHeight",&ud.config.ScreenHeight); + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "ScreenMode",&ud.config.ScreenMode); + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "ScreenWidth",&ud.config.ScreenWidth); + + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "WindowPositioning", (int32_t *)&windowpos); + + windowx = -1; + windowy = -1; + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "WindowPosX", (int32_t *)&windowx); + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "WindowPosY", (int32_t *)&windowy); + + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "MaxRefreshFreq", (int32_t *)&maxrefreshfreq); + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "ScreenBPP", &ud.config.ScreenBPP); + + if (ud.config.ScreenBPP < 8) ud.config.ScreenBPP = 32; + +#ifdef POLYMER + SCRIPT_GetNumber(ud.config.scripthandle, "Screen Setup", "Polymer", &dummy); + if (dummy > 0 && ud.config.ScreenBPP >= 16) glrendmode = REND_POLYMER; + else glrendmode = REND_POLYMOST; +#endif + + SCRIPT_GetNumber(ud.config.scripthandle, "Misc", "Executions",&ud.executions); + +#ifdef _WIN32 + SCRIPT_GetNumber(ud.config.scripthandle, "Updates", "CheckForUpdates", &ud.config.CheckForUpdates); + SCRIPT_GetNumber(ud.config.scripthandle, "Updates", "LastUpdateCheck", &ud.config.LastUpdateCheck); +#endif + + ud.config.setupread = 1; + return 0; +} + + +void CONFIG_WriteSettings(void) // save binds and aliases to _settings.cfg +{ + int32_t i; + BFILE *fp; + char *ptr = Xstrdup(g_setupFileName); + char tempbuf[128]; + + if (!Bstrcmp(g_setupFileName, SETUPFILENAME)) + Bsprintf(tempbuf, "settings.cfg"); + else Bsprintf(tempbuf, "%s_settings.cfg", strtok(ptr, ".")); + + fp = Bfopen(tempbuf, "wt"); + + if (fp) + { + Bfprintf(fp,"// this file is automatically generated by %s\n", AppProperName); + Bfprintf(fp,"unbindall\n"); + + for (i=0; i = (MAXMOUSEBUTTONS-2)) continue; + + if (CONFIG_FunctionNumToName(ud.config.MouseFunctions[dummy][1])) + { + Bsprintf(buf, "MouseButtonClicked%d", dummy); + SCRIPT_PutString(ud.config.scripthandle, "Controls", buf, CONFIG_FunctionNumToName(ud.config.MouseFunctions[dummy][1])); + } + } + + for (dummy=0; dummy gm &= ~MODE_GAME; + g_player[myconnectindex].ps->gm |= MODE_DEMO; +} + +void Demo_PrepareWarp(void) +{ + if (!g_demo_paused) + { + g_demo_soundToggle = ud.config.SoundToggle; + ud.config.SoundToggle = 0; + } + + FX_StopAllSounds(); + S_ClearSoundLocks(); +} + + +static int32_t G_OpenDemoRead(int32_t g_whichDemo) // 0 = mine +{ + int32_t i; + savehead_t saveh; + + char demofn[14]; + const char *demofnptr; + + if (g_whichDemo == 1 && g_firstDemoFile[0]) + { + demofnptr = g_firstDemoFile; + const int fileNameLen = Bstrlen(g_firstDemoFile); + if (fileNameLen >= 4 && !Bstrncasecmp(&g_firstDemoFile[fileNameLen - 4], ".dmo", 4)) + g_demo_legacy = 1; + else + g_demo_legacy = 0; + } + else + { + Bsprintf(demofn, DEMOFN_FMT, g_whichDemo); + demofnptr = demofn; + g_demo_legacy = 0; + } + + g_demo_recFilePtr = kopen4loadfrommod(demofnptr, g_loadFromGroupOnly); + if (g_demo_recFilePtr == -1) + { + // Check for legacy demo + Bsprintf(demofn, LDEMOFN_FMT, g_whichDemo); + demofnptr = demofn; + g_demo_legacy = 1; + g_demo_recFilePtr = kopen4loadfrommod(demofnptr, g_loadFromGroupOnly); + if (g_demo_recFilePtr == -1) + return 0; + } + + if (g_demo_legacy) + { + ud.reccnt = 0; + + legacydemo_t demoHeader; + + if (kread(g_demo_recFilePtr, &demoHeader, sizeof(legacydemo_t)) != sizeof(legacydemo_t)) + { + kclose(g_demo_recFilePtr); g_demo_recFilePtr = -1; + return 0; + } + + demoHeader.reccnt = B_LITTLE32(demoHeader.reccnt); + demoHeader.multimode = B_LITTLE16(demoHeader.multimode); + demoHeader.m_monsters_off = B_LITTLE16(demoHeader.m_monsters_off); + demoHeader.m_respawn_monsters = B_LITTLE32(demoHeader.m_respawn_monsters); + demoHeader.m_respawn_items = B_LITTLE32(demoHeader.m_respawn_items); + demoHeader.m_respawn_inventory = B_LITTLE32(demoHeader.m_respawn_inventory); + demoHeader.playerai = B_LITTLE32(demoHeader.playerai); + + ud.volume_number = demoHeader.volume_number; + ud.level_number = demoHeader.level_number; + ud.player_skill = demoHeader.player_skill; + ud.m_coop = demoHeader.m_coop; + ud.m_ffire = demoHeader.m_ffire; + ud.multimode = demoHeader.multimode; + ud.m_monsters_off = demoHeader.m_monsters_off; + ud.m_respawn_monsters = demoHeader.m_respawn_monsters; + ud.m_respawn_items = demoHeader.m_respawn_items; + ud.m_respawn_inventory = demoHeader.m_respawn_inventory; + + for (bssize_t i = 0; i < demoHeader.multimode; i++) + { + Bstrncpy(g_player[i].user_name, demoHeader.user_name[i], 32); + } + + if (DUKE && demoHeader.version == 117) + { + int32_t autoRun; + kread(g_demo_recFilePtr, &autoRun, sizeof(int32_t)); + //ud.auto_run = autoRun; + kread(g_demo_recFilePtr, boardfilename, 128); + if (boardfilename[0] != 0) + { + ud.m_level_number = 7; + ud.m_volume_number = 0; + } + } + else if (RR && demoHeader.version == 108) + { + // no op + } + else + { + kclose(g_demo_recFilePtr); g_demo_recFilePtr = -1; + return 0; + } + + for (bssize_t i = 0; i < ud.multimode; i++) + { + kread(g_demo_recFilePtr, &g_player[i].ps->aim_mode, 1); + g_player[i].ps->auto_aim = 1; + } + + g_demo_totalCnt = demoHeader.reccnt/ud.multimode; + + i = g_demo_totalCnt/REALGAMETICSPERSEC; + OSD_Printf("demo %d duration: %d min %d sec\n", g_whichDemo, i/60, i%60); + g_demo_cnt = 1; + + ud.god = ud.cashman = ud.eog = ud.showallmap = 0; + ud.noclip = ud.scrollmode = ud.overhead_on = 0; //= ud.pause_on = 0; + + totalclock = ototalclock = lockclock = 0; + + G_NewGame(ud.volume_number, ud.level_number, ud.player_skill); + + demo_reccnt_init = ud.reccnt = demoHeader.reccnt; + + return 1; + } + + Bassert(g_whichDemo >= 1); + i = sv_loadsnapshot(g_demo_recFilePtr, -g_whichDemo, &saveh); + if (i) + { + OSD_Printf(OSD_ERROR "There were errors opening demo %d (code: %d).\n", g_whichDemo, i); + kclose(g_demo_recFilePtr); g_demo_recFilePtr = -1; + return 0; + } + + demo_hasdiffs = saveh.recdiffsp; + g_demo_totalCnt = saveh.reccnt; + demo_synccompress = saveh.synccompress; + + demo_hasseeds = demo_synccompress&2; + demo_synccompress &= 1; + + i = g_demo_totalCnt/REALGAMETICSPERSEC; + OSD_Printf("demo %d duration: %d min %d sec\n", g_whichDemo, i/60, i%60); + + g_demo_cnt = 1; + ud.reccnt = 0; + + ud.god = ud.cashman = ud.eog = ud.showallmap = 0; + ud.noclip = ud.scrollmode = ud.overhead_on = 0; //= ud.pause_on = 0; + + totalclock = ototalclock = lockclock = 0; + + return 1; +} + +#if KRANDDEBUG +extern void krd_enable(int32_t which); +extern int32_t krd_print(const char *filename); +#endif + +void G_OpenDemoWrite(void) +{ + char demofn[BMAX_PATH]; + int32_t i, demonum=1; + + if (ud.recstat == 2) + { + kclose(g_demo_recFilePtr); + g_demo_recFilePtr = -1; + } + + if ((g_player[myconnectindex].ps->gm&MODE_GAME) && g_player[myconnectindex].ps->dead_flag) + { + Bstrcpy(apStrings[QUOTE_RESERVED4], "CANNOT START DEMO RECORDING WHEN DEAD!"); + P_DoQuote(QUOTE_RESERVED4, g_player[myconnectindex].ps); + ud.recstat = ud.m_recstat = 0; + return; + } + do + { + if (demonum == MAXDEMOS) + return; + + if (G_ModDirSnprintf(demofn, sizeof(demofn), DEMOFN_FMT, demonum)) + { + initprintf("Couldn't start demo writing: INTERNAL ERROR: file name too long\n"); + goto error_wopen_demo; + } + + demonum++; + + g_demo_filePtr = Bfopen(demofn, "rb"); + if (g_demo_filePtr == NULL) + break; + + MAYBE_FCLOSE_AND_NULL(g_demo_filePtr); + } + while (1); + + g_demo_filePtr = Bfopen(demofn,"wb"); + if (g_demo_filePtr == NULL) + return; + + i=sv_saveandmakesnapshot(g_demo_filePtr, nullptr, -1, demorec_diffs_cvar, demorec_diffcompress_cvar, + demorec_synccompress_cvar|(demorec_seeds_cvar<<1)); + if (i) + { + MAYBE_FCLOSE_AND_NULL(g_demo_filePtr); +error_wopen_demo: + Bstrcpy(apStrings[QUOTE_RESERVED4], "FAILED STARTING DEMO RECORDING. SEE OSD FOR DETAILS."); + P_DoQuote(QUOTE_RESERVED4, g_player[myconnectindex].ps); + ud.recstat = ud.m_recstat = 0; + return; + } + + demorec_seeds = demorec_seeds_cvar; + demorec_diffs = demorec_diffs_cvar; + demo_synccompress = demorec_synccompress_cvar; + demorec_difftics = demorec_difftics_cvar; + + Bsprintf(apStrings[QUOTE_RESERVED4], "DEMO %d RECORDING STARTED", demonum-1); + P_DoQuote(QUOTE_RESERVED4, g_player[myconnectindex].ps); + + ud.reccnt = 0; + ud.recstat = ud.m_recstat = 1; // + +# if KRANDDEBUG + krd_enable(1); +# endif + g_demo_cnt = 1; +} + +// demo_profile: < 0: prepare +static int32_t g_demo_playFirstFlag, g_demo_profile, g_demo_stopProfile; +static int32_t g_demo_exitAfter; +void Demo_PlayFirst(int32_t prof, int32_t exitafter) +{ + g_demo_playFirstFlag = 1; + g_demo_exitAfter = exitafter; + Bassert(prof >= 0); + g_demo_profile = -prof; // prepare +} + +void Demo_SetFirst(const char *demostr) +{ + char *tailptr; + int32_t i = Bstrtol(demostr, &tailptr, 10); + + if (tailptr==demostr+Bstrlen(demostr) && (unsigned)i < MAXDEMOS) // demo number passed + Bsprintf(g_firstDemoFile, DEMOFN_FMT, i); + else // demo file name passed + { + int l = Bstrlen(demostr); + if (l >= 4 && !Bstrcasecmp(&demostr[l-4], ".dmo")) + Bstrcpy(g_firstDemoFile, demostr); + else + maybe_append_ext(g_firstDemoFile, sizeof(g_firstDemoFile), demostr, ".edm"); + } +} + + +static uint8_t g_demo_seedbuf[RECSYNCBUFSIZ]; + +static void Demo_WriteSync() +{ + int16_t tmpreccnt; + + fwrite("sYnC", 4, 1, g_demo_filePtr); + tmpreccnt = (int16_t)ud.reccnt; + fwrite(&tmpreccnt, sizeof(int16_t), 1, g_demo_filePtr); + if (demorec_seeds) + fwrite(g_demo_seedbuf, 1, ud.reccnt, g_demo_filePtr); + + if (demo_synccompress) + dfwrite_LZ4(recsync, sizeof(input_t), ud.reccnt, g_demo_filePtr); + else //if (demo_synccompress==0) + fwrite(recsync, sizeof(input_t), ud.reccnt, g_demo_filePtr); + + ud.reccnt = 0; +} + +void G_DemoRecord(void) +{ + int16_t i; + + g_demo_cnt++; + + if (demorec_diffs && (g_demo_cnt%demorec_difftics == 1)) + { + sv_writediff(g_demo_filePtr); + demorec_difftics = demorec_difftics_cvar; + } + + if (demorec_seeds) + g_demo_seedbuf[ud.reccnt] = (uint8_t)(randomseed>>24); + + for (TRAVERSE_CONNECT(i)) + { + Bmemcpy(&recsync[ud.reccnt], g_player[i].inputBits, sizeof(input_t)); + ud.reccnt++; + } + + if (ud.reccnt > RECSYNCBUFSIZ-MAXPLAYERS || (demorec_diffs && (g_demo_cnt%demorec_difftics == 0))) + Demo_WriteSync(); +} + +void G_CloseDemoWrite(void) +{ + if (ud.recstat == 1) + { + if (ud.reccnt > 0) + Demo_WriteSync(); + + fwrite("EnD!", 4, 1, g_demo_filePtr); + + // lastly, we need to write the number of written recsyncs to the demo file + if (fseek(g_demo_filePtr, offsetof(savehead_t, reccnt), SEEK_SET)) + perror("G_CloseDemoWrite: final fseek"); + else + fwrite(&g_demo_cnt, sizeof(g_demo_cnt), 1, g_demo_filePtr); + + ud.recstat = ud.m_recstat = 0; + MAYBE_FCLOSE_AND_NULL(g_demo_filePtr); + + sv_freemem(); + + Bstrcpy(apStrings[QUOTE_RESERVED4], "DEMO RECORDING STOPPED"); + P_DoQuote(QUOTE_RESERVED4, g_player[myconnectindex].ps); + } +#if KRANDDEBUG + krd_print("krandrec.log"); +#endif +} + +static int32_t g_whichDemo = 1; + +static int32_t Demo_UpdateState(int32_t frominit) +{ + int32_t j = g_player[myconnectindex].ps->gm&MODE_MENU; + int32_t k = sv_updatestate(frominit); + // tmpdifftime = g_demo_cnt+12; + Demo_RestoreModes(j); + + if (k) + OSD_Printf("sv_updatestate() returned %d.\n", k); + return k; +} + +#define CORRUPT(code) do { corruptcode=code; goto corrupt; } while(0) + +static int32_t Demo_ReadSync(int32_t errcode) +{ + uint16_t si; + int32_t i; + + if (kread(g_demo_recFilePtr, &si, sizeof(uint16_t)) != sizeof(uint16_t)) + return errcode; + + i = si; + if (demo_hasseeds) + { + if (kread(g_demo_recFilePtr, g_demo_seedbuf, i) != i) + return errcode; + } + + if (demo_synccompress) + { + if (kdfread_LZ4(recsync, sizeof(input_t), i, g_demo_recFilePtr) != i) + return errcode+1; + } + else + { + int32_t bytes = sizeof(input_t)*i; + + if (kread(g_demo_recFilePtr, recsync, bytes) != bytes) + return errcode+2; + } + + ud.reccnt = i; + return 0; +} + +legacyinput_t recsynclegacy[RECSYNCBUFSIZ]; + +static void Demo_ReadSyncLegacy(void) +{ + int32_t l = min(ud.reccnt, RECSYNCBUFSIZ) / ud.multimode; + + kdfread(recsynclegacy, sizeof(legacyinput_t)*ud.multimode, l, g_demo_recFilePtr); + + for (bssize_t i = 0; i < l; i ++) { + for (int j = 0; j < ud.multimode; j++) + { + int32_t index = i * ud.multimode + j; + recsync[index].q16avel = F16(recsynclegacy[index].avel<<1); + recsync[index].q16horz = F16(recsynclegacy[index].horz>>1); + recsync[index].fvel = B_LITTLE16(recsynclegacy[index].fvel); + recsync[index].svel = B_LITTLE16(recsynclegacy[index].svel); + recsync[index].bits = B_LITTLE32(recsynclegacy[index].bits); + recsync[index].extbits = 0; + } + } +} + +////////// DEMO PROFILING (TIMEDEMO MODE) ////////// +static struct { + int32_t numtics, numframes; + double totalgamems; + double totalroomsdrawms, totalrestdrawms; + double starthiticks; +} g_prof; + +int32_t Demo_IsProfiling(void) +{ + return (g_demo_profile > 0); +} + +static void Demo_StopProfiling(void) +{ + g_demo_stopProfile = 1; +} + +static void Demo_GToc(double t) +{ + g_prof.numtics++; + g_prof.totalgamems += timerGetHiTicks()-t; +} + +static void Demo_RToc(double t1, double t2) +{ + g_prof.numframes++; + g_prof.totalroomsdrawms += t2-t1; + g_prof.totalrestdrawms += timerGetHiTicks()-t2; +} + +static void Demo_DisplayProfStatus(void) +{ + char buf[64]; + + static int32_t lastpercent=-1; + int32_t percent = (100*g_demo_cnt)/g_demo_totalCnt; + + if (lastpercent == percent) + return; + lastpercent = percent; + + videoClearScreen(0); + Bsnprintf(buf, sizeof(buf), "timing... %d/%d game tics (%d %%)", + g_demo_cnt, g_demo_totalCnt, percent); + gametext_center(60, buf); + videoNextPage(); +} + +static void Demo_SetupProfile(void) +{ + g_demo_profile *= -1; // now >0: profile for real + + g_demo_soundToggle = ud.config.SoundToggle; + ud.config.SoundToggle = 0; // restored by Demo_FinishProfile() + + Bmemset(&g_prof, 0, sizeof(g_prof)); + + g_prof.starthiticks = timerGetHiTicks(); +} + +static void Demo_FinishProfile(void) +{ + if (Demo_IsProfiling()) + { + int32_t dn=g_whichDemo-1; + int32_t nt=g_prof.numtics, nf=g_prof.numframes; + double gms=g_prof.totalgamems; + double dms1=g_prof.totalroomsdrawms, dms2=g_prof.totalrestdrawms; + + ud.config.SoundToggle = g_demo_soundToggle; + + if (nt > 0) + { + OSD_Printf("== demo %d: %d gametics\n", dn, nt); + OSD_Printf("== demo %d game times: %.03f ms (%.03f us/gametic)\n", + dn, gms, (gms*1000.0)/nt); + } + + if (nf > 0) + { + OSD_Printf("== demo %d: %d frames (%d frames/gametic)\n", dn, nf, g_demo_profile-1); + OSD_Printf("== demo %d drawrooms times: %.03f s (%.03f ms/frame)\n", + dn, dms1/1000.0, dms1/nf); + OSD_Printf("== demo %d drawrest times: %.03f s (%.03f ms/frame)\n", + dn, dms2/1000.0, dms2/nf); + } + + { + double totalprofms = gms+dms1+dms2; + double totalms = timerGetHiTicks()-g_prof.starthiticks; + if (totalprofms != 0) + OSD_Printf("== demo %d: non-profiled time overhead: %.02f %%\n", + dn, 100.0*totalms/totalprofms - 100.0); + } + } + + g_demo_profile = 0; + g_demo_stopProfile = 0; +} +//////////////////// + +int32_t G_PlaybackDemo(void) +{ + int32_t bigi, j, initsyncofs = 0, lastsyncofs = 0, lastsynctic = 0, lastsyncclock = 0; + int32_t foundemo = 0, corruptcode, outofsync=0; + static int32_t in_menu = 0; + // static int32_t tmpdifftime=0; + + totalclock = 0; + ototalclock = 0; + lockclock = 0; + + if (ready2send) + return 0; + + if (!g_demo_playFirstFlag) + g_demo_profile = 0; + +RECHECK: + if (g_demo_playFirstFlag) + g_demo_playFirstFlag = 0; + else if (g_demo_exitAfter) + G_GameExit(" "); + +#if KRANDDEBUG + if (foundemo) + krd_print("krandplay.log"); +#endif + + in_menu = g_player[myconnectindex].ps->gm&MODE_MENU; + + pub = NUMPAGES; + pus = NUMPAGES; + + renderFlushPerms(); + + if (!g_netServer && ud.multimode < 2) + foundemo = 0;// G_OpenDemoRead(g_whichDemo); + + if (foundemo == 0) + { + ud.recstat = 0; + + if (g_whichDemo > 1) + { + g_whichDemo = 1; + goto RECHECK; + } + + fadepal(0,0,0, 0,252,28); + P_SetGamePalette(g_player[myconnectindex].ps, BASEPAL, 1); // JBF 20040308 + G_DrawBackground(); + M_DisplayMenus(); + videoNextPage(); + fadepal(0,0,0, 252,0,-28); + ud.reccnt = 0; + } + else + { + ud.recstat = 2; + + g_whichDemo++; + if (g_whichDemo == MAXDEMOS) + g_whichDemo = 1; + + g_player[myconnectindex].ps->gm &= ~MODE_GAME; + g_player[myconnectindex].ps->gm |= MODE_DEMO; + + if (g_demo_legacy) + G_EnterLevel(MODE_DEMO); + + lastsyncofs = ktell(g_demo_recFilePtr); + initsyncofs = lastsyncofs; + lastsynctic = g_demo_cnt; + lastsyncclock = totalclock; + outofsync = 0; +#if KRANDDEBUG + krd_enable(2); +#endif + if (g_demo_profile < 0) + { + Demo_SetupProfile(); + } + } + + if (foundemo == 0 || in_menu || I_CheckAllInput() || numplayers > 1) + { + FX_StopAllSounds(); + S_ClearSoundLocks(); + Menu_Open(myconnectindex); + } + + ready2send = 0; + bigi = 0; + + I_ClearAllInput(); + + // OSD_Printf("ticcnt=%d, total=%d\n", g_demo_cnt, g_demo_totalCnt); + while (g_demo_cnt < g_demo_totalCnt || foundemo==0) + { + // Main loop here. It also runs when there's no demo to show, + // so maybe a better name for this function would be + // G_MainLoopWhenNotInGame()? + + // Demo requested from the OSD, its name is in g_firstDemoFile[] + if (g_demo_playFirstFlag) + { + g_demo_playFirstFlag = 0; + g_whichDemo = 1; // force g_firstDemoFile[] + g_demo_paused = 0; + goto nextdemo_nomenu; + } + + if (foundemo && (!g_demo_paused || g_demo_goalCnt)) + { + if (g_demo_goalCnt>0 && g_demo_goalCnt < g_demo_cnt) + { + // initialize rewind + + int32_t menu = g_player[myconnectindex].ps->gm&MODE_MENU; + + if (g_demo_legacy) + { + klseek(g_demo_recFilePtr, initsyncofs, SEEK_SET); + g_levelTextTime = 0; + + g_demo_cnt = 1; + ud.reccnt = demo_reccnt_init; + bigi = 0; + + totalclock = ototalclock = lockclock = 0; + + G_EnterLevel(MODE_DEMO); + } + else + { + if (g_demo_goalCnt > lastsynctic) + { + // we can use a previous diff + if (Demo_UpdateState(0)==0) + { + g_demo_cnt = lastsynctic; + klseek(g_demo_recFilePtr, lastsyncofs, SEEK_SET); + ud.reccnt = 0; + + totalclock = ototalclock = lockclock = lastsyncclock; + } + else CORRUPT(-1); + } + else + { + // update to initial state + if (Demo_UpdateState(1) == 0) + { + klseek(g_demo_recFilePtr, initsyncofs, SEEK_SET); + g_levelTextTime = 0; + + g_demo_cnt = 1; + ud.reccnt = 0; + + // ud.god = ud.cashman = ud.eog = ud.showallmap = 0; + // ud.noclip = ud.scrollmode = ud.overhead_on = ud.pause_on = 0; + + totalclock = ototalclock = lockclock = 0; + } + else CORRUPT(0); + } + } + + Demo_RestoreModes(menu); + } + + if (g_demo_stopProfile) + Demo_FinishProfile(); + + while (totalclock >= (lockclock+TICSPERFRAME) + // || (ud.reccnt > REALGAMETICSPERSEC*2 && ud.pause_on) + || (g_demo_goalCnt>0 && g_demo_cnt = RECSYNCBUFSIZ) + { + bigi = 0; + Demo_ReadSyncLegacy(); + } + } + else + { + if (ud.reccnt<=0) + { + // Record count reached zero (or <0, corrupted), need + // reading another chunk. + + char tmpbuf[4]; + + if (ud.reccnt<0) + { + OSD_Printf("G_PlaybackDemo: ud.reccnt<0!\n"); + CORRUPT(1); + } + + bigi = 0; + //reread: + if (kread(g_demo_recFilePtr, tmpbuf, 4) != 4) + CORRUPT(2); + + if (Bmemcmp(tmpbuf, "sYnC", 4)==0) + { + int32_t err = Demo_ReadSync(3); + if (err) + CORRUPT(err); + } + + else if (demo_hasdiffs && Bmemcmp(tmpbuf, "dIfF", 4)==0) + { + int32_t k = sv_readdiff(g_demo_recFilePtr); + + if (k) + { + OSD_Printf("sv_readdiff() returned %d.\n", k); + CORRUPT(6); + } + else + { + lastsyncofs = ktell(g_demo_recFilePtr); + lastsynctic = g_demo_cnt; + lastsyncclock = totalclock; + + if (kread(g_demo_recFilePtr, tmpbuf, 4) != 4) + CORRUPT(7); + if (Bmemcmp(tmpbuf, "sYnC", 4)) + CORRUPT(8); + + { + int32_t err = Demo_ReadSync(9); + if (err) + CORRUPT(err); + } + + if ((g_demo_goalCnt==0 && demoplay_diffs) || + (g_demo_goalCnt>0 && ud.reccnt/ud.multimode >= g_demo_goalCnt-g_demo_cnt)) + { + Demo_UpdateState(0); + } + } + } + else if (Bmemcmp(tmpbuf, "EnD!", 4)==0) + goto nextdemo; + else CORRUPT(12); + + if (0) + { +corrupt: + OSD_Printf(OSD_ERROR "Demo %d is corrupt (code %d).\n", g_whichDemo-1, corruptcode); +nextdemo: + Menu_Open(myconnectindex); +nextdemo_nomenu: + foundemo = 0; + ud.reccnt = 0; + kclose(g_demo_recFilePtr); g_demo_recFilePtr = -1; + + if (g_demo_goalCnt>0) + { + g_demo_goalCnt=0; + ud.config.SoundToggle = g_demo_soundToggle; + } + + if (Demo_IsProfiling()) // don't reset g_demo_profile if it's < 0 + Demo_FinishProfile(); + goto RECHECK; + } + } + + if (demo_hasseeds) + outofsync = ((uint8_t)(randomseed>>24) != g_demo_seedbuf[bigi]); + } + + for (TRAVERSE_CONNECT(j)) + { + Bmemcpy(&inputfifo[0][j], &recsync[bigi], sizeof(input_t)); + bigi++; + ud.reccnt--; + } + + g_demo_cnt++; + + S_Update(); + + if (Demo_IsProfiling()) + { + double t = timerGetHiTicks(); + G_DoMoveThings(); + Demo_GToc(t); + } + else if (!g_demo_paused) + { + // assumption that ud.multimode doesn't change in a demo may not be true + // sometime in the future v v v v v v v v v + if (g_demo_goalCnt==0 || !demo_hasdiffs || ud.reccnt/ud.multimode>=g_demo_goalCnt-g_demo_cnt) + { + G_DoMoveThings(); // increases lockclock by TICSPERFRAME + } + else + { + lockclock += TICSPERFRAME; + } + } + else + { + int32_t k = ud.config.SoundToggle; + ud.config.SoundToggle = 0; + G_DoMoveThings(); + ud.config.SoundToggle = k; + } + + ototalclock += TICSPERFRAME; + + if (g_demo_goalCnt > 0) + { + // if fast-forwarding, we must update totalclock + totalclock += TICSPERFRAME; + +// OSD_Printf("t:%d, l+T:%d; cnt:%d, goal:%d%s", totalclock, (lockclock+TICSPERFRAME), +// g_demo_cnt, g_demo_goalCnt, g_demo_cnt>=g_demo_goalCnt?" ":"\n"); + if (g_demo_cnt>=g_demo_goalCnt) + { + g_demo_goalCnt = 0; + ud.config.SoundToggle = g_demo_soundToggle; + } + } + } + } + else if (foundemo && g_demo_paused) + { + totalclock = lockclock; + } + + if (Demo_IsProfiling()) + totalclock += TICSPERFRAME; + + if (G_FPSLimit()) + { + if (foundemo == 0) + { + G_DrawBackground(); + } + else + { + // NOTE: currently, no key/mouse events will be seen while + // demo-profiling because we need 'totalclock' for ourselves. + // And handleevents() -> sampletimer() would mess that up. + G_HandleLocalKeys(); + + // Render one frame (potentially many if profiling) + if (Demo_IsProfiling()) + { + int32_t i, num = g_demo_profile-1; + + Bassert(totalclock-ototalclock==4); + + for (i=0; i >16); + + G_DrawRooms(screenpeek, j); + + t2 = timerGetHiTicks(); + + G_DisplayRest(j); + + Demo_RToc(t1, t2); + } + + totalclock = ototalclock+4; + + // draw status + Demo_DisplayProfStatus(); + + if (handleevents_peekkeys()) + Demo_StopProfiling(); + } + else + { + j = calc_smoothratio(totalclock, ototalclock); + if (g_demo_paused && g_demo_rewind) + j = 65536-j; + + G_DrawRooms(screenpeek, j); + G_DisplayRest(j); + + } +// totalclocklock = totalclock; + + if (!Demo_IsProfiling() && (g_player[myconnectindex].ps->gm&MODE_MENU) == 0) + { + if (demoplay_showsync && outofsync) + gametext_center(100, "OUT OF SYNC"); + + if (g_demo_showStats) + { + #if 0 + if (g_demo_cnt 1) && g_player[myconnectindex].ps->gm) + Net_GetPackets(); + + if (g_player[myconnectindex].gotvote == 0 && voting != -1 && voting != myconnectindex) + gametext_center(60, "Press F1 to Accept, F2 to Decline"); + } + + if ((g_player[myconnectindex].ps->gm&MODE_MENU) && (g_player[myconnectindex].ps->gm&MODE_EOL)) + { + Demo_FinishProfile(); + goto RECHECK; + } + + if (I_EscapeTrigger() && (g_player[myconnectindex].ps->gm&MODE_MENU) == 0 && (g_player[myconnectindex].ps->gm&MODE_TYPE) == 0) + { + I_EscapeTriggerClear(); + FX_StopAllSounds(); + S_ClearSoundLocks(); + Menu_Open(myconnectindex); + Menu_Change(MENU_MAIN); + S_MenuSound(); + } + + if (Demo_IsProfiling()) + { + // Do nothing: sampletimer() is reached from M_DisplayMenus() -> + // Net_GetPackets() else. + } + else if (g_player[myconnectindex].ps->gm&MODE_TYPE) + { + Net_SendMessage(); + + if ((g_player[myconnectindex].ps->gm&MODE_TYPE) != MODE_TYPE) + { + g_player[myconnectindex].ps->gm = 0; + Menu_Open(myconnectindex); + } + } + else + { + if (ud.recstat != 2) + M_DisplayMenus(); + + if ((g_netServer || ud.multimode > 1) && !Menu_IsTextInput(m_currentMenu)) + { + ControlInfo noshareinfo; + CONTROL_GetInput(&noshareinfo); + if (BUTTON(gamefunc_SendMessage)) + { + KB_FlushKeyboardQueue(); + CONTROL_ClearButton(gamefunc_SendMessage); + g_player[myconnectindex].ps->gm = MODE_TYPE; + typebuf[0] = 0; + } + } + } + + if (!Demo_IsProfiling()) + G_PrintGameQuotes(screenpeek); + + if (ud.last_camsprite != ud.camerasprite) + ud.last_camsprite = ud.camerasprite; + + if (VOLUMEONE) + { + if (ud.show_help == 0 && (g_player[myconnectindex].ps->gm&MODE_MENU) == 0) + rotatesprite_fs((320-50)<<16, 9<<16, 65536L, 0, BETAVERSION, 0, 0, 2+8+16+128); + } + } + + // NOTE: We must prevent handleevents() and Net_GetPackets() from + // updating totalclock when profiling (both via sampletimer()): + if (!Demo_IsProfiling()) + G_HandleAsync(); + + if (g_player[myconnectindex].ps->gm == MODE_GAME) + { + // user wants to play a game, quit showing demo! + + if (foundemo) + { +#if KRANDDEBUG + krd_print("krandplay.log"); +#endif + kclose(g_demo_recFilePtr); g_demo_recFilePtr = -1; + } + + return 0; + } + } + + ud.multimode = numplayers; // fixes 2 infinite loops after watching demo + kclose(g_demo_recFilePtr); g_demo_recFilePtr = -1; + + Demo_FinishProfile(); + + // if we're in the menu, try next demo immediately + if (g_player[myconnectindex].ps->gm&MODE_MENU) + goto RECHECK; + +#if KRANDDEBUG + if (foundemo) + krd_print("krandplay.log"); +#endif + + // finished playing a demo and not in menu: + // return so that e.g. the title can be shown + return 1; +} diff --git a/source/rr/src/demo.h b/source/rr/src/demo.h new file mode 100644 index 000000000..51adaa8b0 --- /dev/null +++ b/source/rr/src/demo.h @@ -0,0 +1,65 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef demo_h_ +#define demo_h_ + +#define DEMOFN_FMT "edemo%03d.edm" +#define LDEMOFN_FMT "demo%d.dmo" +#define MAXDEMOS 1000 + +extern FILE *g_demo_filePtr; +extern char g_firstDemoFile[BMAX_PATH]; + +extern int32_t demoplay_diffs; +extern int32_t demoplay_showsync; +extern int32_t demorec_diffcompress_cvar; +extern int32_t demorec_diffs_cvar; +extern int32_t demorec_difftics_cvar; +extern int32_t demorec_force_cvar; +extern int32_t demorec_seeds_cvar; +extern int32_t demorec_synccompress_cvar; +extern int32_t g_demo_cnt; +extern int32_t g_demo_goalCnt; +extern int32_t g_demo_paused; +extern int32_t g_demo_recFilePtr; +extern int32_t g_demo_rewind; +extern int32_t g_demo_showStats; +extern int32_t g_demo_totalCnt; + +int32_t G_PlaybackDemo(void); +void Demo_PrepareWarp(void); +void G_CloseDemoWrite(void); +void G_DemoRecord(void); +void G_OpenDemoWrite(void); + +void Demo_PlayFirst(int32_t prof, int32_t exitafter); +void Demo_SetFirst(const char *demostr); + +int32_t Demo_IsProfiling(void); + +#if KRANDDEBUG +int32_t krd_print(const char *filename); +void krd_enable(int32_t which); +#endif + +#endif diff --git a/source/rr/src/duke3d.h b/source/rr/src/duke3d.h new file mode 100644 index 000000000..0afe11139 --- /dev/null +++ b/source/rr/src/duke3d.h @@ -0,0 +1,152 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef duke3d_h_ +#define duke3d_h_ + +// JBF +#include "compat.h" +#include "a.h" +#include "build.h" + +#ifdef POLYMER + #include "polymer.h" +#else +#ifdef USE_OPENGL + #include "polymost.h" +#endif +#endif + +#include "cache1d.h" +#include "pragmas.h" +#include "baselayer.h" +#include "file_lib.h" +#include "keyboard.h" +#include "fx_man.h" + +#define HEAD2 APPNAME + +#define VOLUMEALL (g_Shareware == 0) +#define PLUTOPAK (g_scriptVersion >= 14) +#define VOLUMEONE (g_Shareware == 1) + +// increase by 3, because atomic GRP adds 1, and Shareware adds 2 +// Non-Lua build +# define BYTEVERSION_EDUKE32 336 + +//#define BYTEVERSION_13 27 +//#define BYTEVERSION_14 116 +//#define BYTEVERSION_15 117 +#define BYTEVERSION (BYTEVERSION_EDUKE32+(PLUTOPAK?1:(VOLUMEONE<<1))) + +#define NUMPAGES 1 + +#define RECSYNCBUFSIZ 2520 //2520 is the (LCM of 1-8)*3 +#define MOVEFIFOSIZ 2 + +// KEEPINSYNC lunatic/con_lang.lua +#define MAXVOLUMES 7 +#define MAXLEVELS 64 +#define MAXGAMETYPES 16 + +enum { + MUS_FIRST_SPECIAL = MAXVOLUMES*MAXLEVELS, + + MUS_INTRO = MUS_FIRST_SPECIAL, + MUS_BRIEFING = MUS_FIRST_SPECIAL + 1, + MUS_LOADING = MUS_FIRST_SPECIAL + 2, +}; + +////////// TIMING CONSTANTS ////////// +// The number of 'totalclock' increments per second: +#define TICRATE 120 +// The number of game state updates per second: +#define REALGAMETICSPERSEC 30 +// The number of 'totalclock' increments per game state update: +// NOTE: calling a game state update a 'frame' is really weird. +// (This used to be TICRATE/GAMETICSPERSEC, which was 120/26 = 4.615~ truncated +// to 4 by integer division.) +#define TICSPERFRAME (TICRATE/REALGAMETICSPERSEC) +// Used as a constant to satisfy all of the calculations written with ticrate = +// 26 in mind: +#define GAMETICSPERSEC 26 + + +#define PACKBUF_SIZE 32768 + +#define TILE_SAVESHOT (MAXTILES-1) +#define TILE_LOADSHOT (MAXTILES-3) +#define TILE_TILT (MAXTILES-2) +#define TILE_ANIM (MAXTILES-4) +#define TILE_VIEWSCR (MAXTILES-5) +// Reserved: TILE_VIEWSCR_1 (MAXTILES-6) +// Reserved: TILE_VIEWSCR_2 (MAXTILES-7) +EDUKE32_STATIC_ASSERT(7 <= MAXTILES-MAXUSERTILES); + +// sprites with these statnums should be considered for fixing +#define ROTFIXSPR_STATNUMP(k) ((k)==STAT_DEFAULT || (k)==STAT_STANDABLE || (k)==STAT_FX || \ + (k)==STAT_FALLER || (k)==STAT_LIGHT) +#define ROTFIXSPR_MAGIC 0x18190000 + +// JBF 20040604: sync is a function on some platforms +#define sync dsync + +// Uncomment the following to remove calls to a.nasm functions with the GL renderers +// so that debugging with valgrind --smc-check=none is possible: +//#define DEBUG_VALGRIND_NO_SMC + +#include "common_game.h" +#include "namesdyn.h" +#include "function.h" +#include "macros.h" +#include "gamedefs.h" +#include "config.h" +#include "sounds.h" +#include "control.h" +#include "_rts.h" +#include "rts.h" +#include "soundsdyn.h" +#include "music.h" +#include "inv.h" +#include "player.h" +#include "actors.h" +#include "quotes.h" +#include "global.h" +#include "sector.h" +#include "net.h" +#include "game.h" +#include "gamedef.h" +#include "gameexec.h" +#include "gamevars.h" +#include "screentext.h" + +static inline int32_t G_HaveActor(int spriteNum) +{ + return g_tile[spriteNum].execPtr!=NULL; +} + +static inline int32_t G_DefaultActorHealth(int spriteNum) +{ + return G_HaveActor(spriteNum) ? g_tile[spriteNum].execPtr[0] : 0; +} + +#endif diff --git a/source/rr/src/events_defs.h b/source/rr/src/events_defs.h new file mode 100644 index 000000000..b74517501 --- /dev/null +++ b/source/rr/src/events_defs.h @@ -0,0 +1,5 @@ + +#ifndef EDUKE32_EVENTS_DEFS_H_ +#define EDUKE32_EVENTS_DEFS_H_ + +#endif diff --git a/source/rr/src/file_lib.cpp b/source/rr/src/file_lib.cpp new file mode 100644 index 000000000..a8a253857 --- /dev/null +++ b/source/rr/src/file_lib.cpp @@ -0,0 +1,126 @@ +/* + * file_lib.c + * File functions to emulate MACT + * + * by Jonathon Fowler + * + * Since we weren't given the source for MACT386.LIB so I've had to do some + * creative interpolation here. + * + */ +//------------------------------------------------------------------------- +/* +Duke Nukem Copyright (C) 1996, 2003 3D Realms Entertainment + +This file is part of Duke Nukem 3D version 1.5 - Atomic Edition + +Duke Nukem 3D 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 +of the License, 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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "compat.h" + +#include "file_lib.h" +#include "cache1d.h" +#include "baselayer.h" + +#define MaxFiles 20 +static char *FileNames[MaxFiles]; + +int32_t SafeOpen(const char *filename, int32_t mode, int32_t sharemode) +{ + int32_t h; + + h = openfrompath(filename, mode, sharemode); + if (h < 0) + { + initprintf("Error opening %s: %s", filename, strerror(errno)); + return h; + } + + if (h < MaxFiles) + { + Bfree(FileNames[h]); + FileNames[h] = (char*)Xmalloc(strlen(filename)+1); + strcpy(FileNames[h], filename); + } + + return h; +} + +int32_t SafeOpenRead(const char *filename, int32_t filetype) +{ + switch (filetype) + { + case filetype_binary: + return SafeOpen(filename, O_RDONLY|O_BINARY, BS_IREAD); + case filetype_text: + return SafeOpen(filename, O_RDONLY|O_TEXT, BS_IREAD); + default: + initprintf("SafeOpenRead: Illegal filetype specified"); + return -1; + } +} + +void SafeClose(int32_t handle) +{ + if (handle < 0) return; + if (close(handle) < 0) + { + if (handle < MaxFiles) + initprintf("Unable to close file %s", FileNames[handle]); + else + initprintf("Unable to close file"); + return; + } + + if (handle < MaxFiles && FileNames[handle]) + { + DO_FREE_AND_NULL(FileNames[handle]); + } +} + +int32_t SafeFileExists(const char *filename) +{ + if (!access(filename, F_OK)) return TRUE; + return FALSE; +} + +int32_t SafeFileLength(int32_t handle) +{ + if (handle < 0) return -1; + return _filelength(handle); +} + +void SafeRead(int32_t handle, void *buffer, int32_t count) +{ + int32_t b; + + b = read(handle, buffer, count); + if (b != count) + { + close(handle); + if (handle < MaxFiles) + initprintf("File read failure %s reading %d bytes from file %s.", + strerror(errno), count, FileNames[handle]); + else + initprintf("File read failure %s reading %d bytes.", + strerror(errno), count); + return; + } +} + + diff --git a/source/rr/src/file_lib.h b/source/rr/src/file_lib.h new file mode 100644 index 000000000..e99911497 --- /dev/null +++ b/source/rr/src/file_lib.h @@ -0,0 +1,262 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 1996, 2003 - 3D Realms Entertainment + +This file is part of Duke Nukem 3D version 1.5 - Atomic Edition + +Duke Nukem 3D 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 +of the License, 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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Original Source: 1996 - Todd Replogle +Prepared for public release: 03/21/2003 - Charlie Wiederhold, 3D Realms +*/ +//------------------------------------------------------------------------- + +#pragma once + +#ifndef file_lib_public_ +#define file_lib_public_ +#ifdef __cplusplus +extern "C" { +#endif + +enum + { + filetype_binary, + filetype_text + }; + +enum + { + access_read, + access_write, + access_append + }; + +//========================================================================== +// +// SafeOpenWrite - Opens a file for writing, returns handle +// +//========================================================================== +int32_t SafeOpenWrite ( const char * filename, int32_t filetype ); + +//========================================================================== +// +// SafeOpenRead - Opens a file for reading, returns handle +// +//========================================================================== +int32_t SafeOpenRead ( const char * filename, int32_t filetype ); + +//========================================================================== +// +// SafeOpenAppend - Opens a file for appending, returns handle +// +//========================================================================== +int32_t SafeOpenAppend ( const char * filename, int32_t filetype ); + +//========================================================================== +// +// SafeClose - Close a file denoted by the file handle +// +//========================================================================== +void SafeClose ( int32_t handle ); + +//========================================================================== +// +// SafeFileExists - Checks for existence of file +// +//========================================================================== +int32_t SafeFileExists ( const char * filename ); + +//========================================================================== +// +// SafeFileLength - Get length of a file pointed to by handle +// +//========================================================================== +int32_t SafeFileLength ( int32_t handle ); + +//========================================================================== +// +// SafeRead - reads from a handle +// +// handle - handle of file to read from +// +// buffer - pointer of buffer to read into +// +// count - number of bytes to read +// +//========================================================================== +void SafeRead (int32_t handle, void *buffer, int32_t count); + +//========================================================================== +// +// SafeWrite - writes to a handle +// +// handle - handle of file to write to +// +// buffer - pointer of buffer to write from +// +// count - number of bytes to write +// +//========================================================================== +void SafeWrite (int32_t handle, void *buffer, int32_t count); + +//========================================================================== +// +// LoadFile - Load a file +// +// filename - name of file +// +// bufferptr - pointer to pointer of buffer to read into +// +// returns number of bytes read +// +//========================================================================== +int32_t LoadFile ( const char * filename, void ** bufferptr ); + +//========================================================================== +// +// SaveFile - Save a file +// +// filename - name of file +// +// bufferptr - pointer to buffer to write from +// +// count - number of bytes to write +// +//========================================================================== +void SaveFile ( const char * filename, void * bufferptr, int32_t count ); + +//========================================================================== +// +// GetPathFromEnvironment - Add a pathname described in an environment +// variable to a standard filename. +// +// fullname - final string containing entire path +// +// envname - string naming enivronment variable +// +// filename - standard filename +// +//========================================================================== +void GetPathFromEnvironment( char *fullname, const char *envname, const char *filename ); + +//========================================================================== +// +// DefaultExtension - Add a default extension to a path +// +// path - a path +// +// extension - default extension should include '.' +// +//========================================================================== +void DefaultExtension (char *path, const char *extension); + +//========================================================================== +// +// DefaultPath - Add the default path to a filename if it doesn't have one +// +// path - filename +// +// extension - default path +// +//========================================================================== +void DefaultPath (char *path, const char *basepath); + +//========================================================================== +// +// ExtractFileBase - Extract the base filename from a path +// +// path - the path +// +// dest - where the file base name will be placed +// +//========================================================================== +void ExtractFileBase (char *path, char *dest); + +//========================================================================== +// +// GetExtension - Extract the extension from a name +// returns true if an extension is found +// returns false otherwise +// +//========================================================================== +int32_t GetExtension( char *filename, char *extension ); + +//========================================================================== +// +// SetExtension - Sets the extension from a name. Assumes that enough +// space is left at the end of the string to hold an extension. +// +//========================================================================== +void SetExtension( char *filename, const char *extension ); + +#ifdef __MSDOS__ +//****************************************************************************** +// +// GetPath +// +// Purpose +// To parse the directory entered by the user to make the directory. +// +// Parms +// Path - the path to be parsed. +// +// Returns +// Pointer to next path +// +//****************************************************************************** +char * GetPath (char * path, char *dir); + +//****************************************************************************** +// +// ChangeDirectory () +// +// Purpose +// To change to a directory. Checks for drive changes. +// +// Parms +// path - The path to change to. +// +// Returns +// TRUE - If successful. +// FALSE - If unsuccessful. +// +//****************************************************************************** +int32_t ChangeDirectory (char * path); + +//****************************************************************************** +// +// ChangeDrive () +// +// Purpose +// To change drives. +// +// Parms +// drive - The drive to change to. +// +// Returns +// TRUE - If drive change successful. +// FALSE - If drive change unsuccessful. +// +//****************************************************************************** +int32_t ChangeDrive (char *drive); + +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/source/rr/src/function.h b/source/rr/src/function.h new file mode 100644 index 000000000..57906b7cd --- /dev/null +++ b/source/rr/src/function.h @@ -0,0 +1,110 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +// function.h + +// file created by makehead.exe +// these headers contain default key assignments, as well as +// default button assignments and game function names +// axis defaults are also included + + +#ifndef function_public_h_ +#define function_public_h_ +#ifdef __cplusplus +extern "C" { +#endif + +#define NUMGAMEFUNCTIONS 60 +#define MAXGAMEFUNCLEN 32 + +extern char gamefunctions[NUMGAMEFUNCTIONS][MAXGAMEFUNCLEN]; +extern char keydefaults[NUMGAMEFUNCTIONS*2][MAXGAMEFUNCLEN]; +extern char oldkeydefaults[NUMGAMEFUNCTIONS*2][MAXGAMEFUNCLEN]; + +enum GameFunction_t + { + gamefunc_Move_Forward, + gamefunc_Move_Backward, + gamefunc_Turn_Left, + gamefunc_Turn_Right, + gamefunc_Strafe, + gamefunc_Fire, + gamefunc_Open, + gamefunc_Run, + gamefunc_AutoRun, + gamefunc_Jump, + gamefunc_Crouch, + gamefunc_Look_Up, + gamefunc_Look_Down, + gamefunc_Look_Left, + gamefunc_Look_Right, + gamefunc_Strafe_Left, + gamefunc_Strafe_Right, + gamefunc_Aim_Up, + gamefunc_Aim_Down, + gamefunc_Weapon_1, + gamefunc_Weapon_2, + gamefunc_Weapon_3, + gamefunc_Weapon_4, + gamefunc_Weapon_5, + gamefunc_Weapon_6, + gamefunc_Weapon_7, + gamefunc_Weapon_8, + gamefunc_Weapon_9, + gamefunc_Weapon_10, + gamefunc_Inventory, + gamefunc_Inventory_Left, + gamefunc_Inventory_Right, + gamefunc_Holo_Duke, + gamefunc_Jetpack, + gamefunc_NightVision, + gamefunc_MedKit, + gamefunc_TurnAround, + gamefunc_SendMessage, + gamefunc_Map, + gamefunc_Shrink_Screen, + gamefunc_Enlarge_Screen, + gamefunc_Center_View, + gamefunc_Holster_Weapon, + gamefunc_Show_Opponents_Weapon, + gamefunc_Map_Follow_Mode, + gamefunc_See_Coop_View, + gamefunc_Mouse_Aiming, + gamefunc_Toggle_Crosshair, + gamefunc_Steroids, + gamefunc_Quick_Kick, + gamefunc_Next_Weapon, + gamefunc_Previous_Weapon, + gamefunc_Show_Console, + gamefunc_Show_DukeMatch_Scores, + gamefunc_Dpad_Select, + gamefunc_Dpad_Aiming, + gamefunc_Alt_Weapon, + gamefunc_Last_Weapon, + gamefunc_Quick_Save, + gamefunc_Quick_Load, + }; +#ifdef __cplusplus +} +#endif +#endif diff --git a/source/rr/src/game.cpp b/source/rr/src/game.cpp new file mode 100644 index 000000000..3c754b2d8 --- /dev/null +++ b/source/rr/src/game.cpp @@ -0,0 +1,8805 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#define game_c_ + +#include "duke3d.h" +#include "compat.h" +#include "renderlayer.h" +#include "osdfuncs.h" +#include "osdcmds.h" +#include "crc32.h" +#include "net.h" +#include "menus.h" +#include "savegame.h" +#include "anim.h" +#include "demo.h" +#include "input.h" +#include "colmatch.h" +#include "cheats.h" +#include "sbar.h" +#include "screens.h" +#include "cmdline.h" +#include "palette.h" + +#ifdef __ANDROID__ +#include "android.h" +#endif + +// Uncomment to prevent anything except mirrors from drawing. It is sensible to +// also uncomment ENGINE_CLEAR_SCREEN in build/src/engine_priv.h. +//#define DEBUG_MIRRORS_ONLY + +#if KRANDDEBUG +# define GAME_INLINE +# define GAME_STATIC +#else +# define GAME_INLINE inline +# define GAME_STATIC static +#endif + +#ifdef _WIN32 +# include +# define UPDATEINTERVAL 604800 // 1w +# include "winbits.h" +#else +# ifndef GEKKO +# include +# endif +#endif /* _WIN32 */ + +const char* AppProperName = APPNAME; +const char* AppTechnicalName = APPBASENAME; + +int32_t g_quitDeadline = 0; + +int32_t g_cameraDistance = 0, g_cameraClock = 0; +static int32_t g_quickExit; + +char boardfilename[BMAX_PATH] = {0}, currentboardfilename[BMAX_PATH] = {0}; + +int32_t voting = -1; +int32_t vote_map = -1, vote_episode = -1; + +int32_t g_Debug = 0; + +const char *defaultrtsfilename[GAMECOUNT] = { "DUKE.RTS", "REDNECK.RTS", "REDNECK.RTS" }; + +int32_t g_Shareware = 0; + +// This was 32 for a while, but I think lowering it to 24 will help things like the Dingoo. +// Ideally, we would look at our memory usage on our most cramped platform and figure out +// how much of that is needed for the underlying OS and things like SDL instead of guessing +#ifndef GEKKO +int32_t MAXCACHE1DSIZE = (96*1024*1024); +#else +int32_t MAXCACHE1DSIZE = (8*1024*1024); +#endif + +int32_t tempwallptr; + +static int32_t nonsharedtimer; + +int32_t ticrandomseed; + +GAME_STATIC GAME_INLINE int32_t G_MoveLoop(void); + +int32_t hud_showmapname = 1; + +int32_t g_levelTextTime = 0; + +int32_t r_maxfps = 60; +uint64_t g_frameDelay = 17; + +#if defined(RENDERTYPEWIN) && defined(USE_OPENGL) +extern char forcegl; +#endif + +void M32RunScript(const char *s) { UNREFERENCED_PARAMETER(s); }; // needed for linking since it's referenced from build/src/osd.c + +const char *G_DefaultRtsFile(void) +{ + if (DUKE) + return defaultrtsfilename[GAME_DUKE]; + else if (RR) + return defaultrtsfilename[GAME_RR]; + + return defaultrtsfilename[0]; +} + +enum gametokens +{ + T_INCLUDE = 0, + T_INTERFACE = 0, + T_LOADGRP = 1, + T_MODE = 1, + T_CACHESIZE = 2, + T_ALLOW = 2, + T_NOAUTOLOAD, + T_INCLUDEDEFAULT, + T_MUSIC, + T_SOUND, + T_FILE, + T_CUTSCENE, + T_ANIMSOUNDS, + T_NOFLOORPALRANGE, + T_ID, + T_MINPITCH, + T_MAXPITCH, + T_PRIORITY, + T_TYPE, + T_DISTANCE, + T_VOLUME, + T_DELAY, + T_RENAMEFILE, + T_GLOBALGAMEFLAGS, + T_ASPECT, + T_FORCEFILTER, + T_FORCENOFILTER, + T_TEXTUREFILTER, +}; + +void G_HandleSpecialKeys(void) +{ + // we need CONTROL_GetInput in order to pick up joystick button presses + if (CONTROL_Started && !(g_player[myconnectindex].ps->gm & MODE_GAME)) + { + ControlInfo noshareinfo; + CONTROL_GetInput(&noshareinfo); + } + +// CONTROL_ProcessBinds(); + + if (g_networkMode != NET_DEDICATED_SERVER && ALT_IS_PRESSED && KB_KeyPressed(sc_Enter)) + { + if (videoSetGameMode(!ud.config.ScreenMode,ud.config.ScreenWidth,ud.config.ScreenHeight,ud.config.ScreenBPP,ud.detail)) + { + OSD_Printf(OSD_ERROR "Failed setting fullscreen video mode.\n"); + if (videoSetGameMode(ud.config.ScreenMode, ud.config.ScreenWidth, ud.config.ScreenHeight, ud.config.ScreenBPP, ud.detail)) + G_GameExit("Failed to recover from failure to set fullscreen video mode.\n"); + } + else ud.config.ScreenMode = !ud.config.ScreenMode; + KB_ClearKeyDown(sc_Enter); + g_restorePalette = 1; + G_UpdateScreenArea(); + } + + if (KB_UnBoundKeyPressed(sc_F12)) + { + KB_ClearKeyDown(sc_F12); + videoCaptureScreen( + "duke0000.tga" + , + 0); + P_DoQuote(QUOTE_SCREEN_SAVED, g_player[myconnectindex].ps); + } + + // only dispatch commands here when not in a game + if (!(g_player[myconnectindex].ps->gm & MODE_GAME)) + OSD_DispatchQueued(); + + if (g_quickExit == 0 && KB_KeyPressed(sc_LeftControl) && KB_KeyPressed(sc_LeftAlt) && (KB_KeyPressed(sc_Delete)||KB_KeyPressed(sc_End))) + { + g_quickExit = 1; + G_GameExit("Quick Exit."); + } +} + +void G_GameQuit(void) +{ + if (numplayers < 2) + G_GameExit(" "); + + if (g_gameQuit == 0) + { + g_gameQuit = 1; + g_quitDeadline = totalclock+120; + g_netDisconnect = 1; + } + + if ((totalclock > g_quitDeadline) && (g_gameQuit == 1)) + G_GameExit("Timed out."); +} + + +int32_t A_CheckInventorySprite(spritetype *s) +{ + switch (DYNAMICTILEMAP(s->picnum)) + { + case FIRSTAID__STATIC: + case STEROIDS__STATIC: + case HEATSENSOR__STATIC: + case BOOTS__STATIC: + case JETPACK__STATIC: + case HOLODUKE__STATIC: + case AIRTANK__STATIC: + return 1; + default: + return 0; + } +} + +void G_OnMotorcycle(DukePlayer_t *pPlayer, int spriteNum) +{ + if (!pPlayer->on_motorcycle && !(sector[pPlayer->cursectnum].lotag == 2)) + { + if (spriteNum) + { + pPlayer->pos.x = sprite[spriteNum].x; + pPlayer->pos.y = sprite[spriteNum].y; + pPlayer->q16ang = F16(sprite[spriteNum].ang); + pPlayer->ammo_amount[MOTORCYCLE_WEAPON] = sprite[spriteNum].owner; + A_DeleteSprite(spriteNum); + } + pPlayer->over_shoulder_on = 0; + pPlayer->on_motorcycle = 1; + pPlayer->last_full_weapon = pPlayer->curr_weapon; + pPlayer->curr_weapon = MOTORCYCLE_WEAPON; + pPlayer->gotweapon |= (1 << MOTORCYCLE_WEAPON); + pPlayer->vel.x = 0; + pPlayer->vel.y = 0; + pPlayer->q16horiz = F16(100); + } + if (!A_CheckSoundPlaying(pPlayer->i,186)) + A_PlaySound(186, pPlayer->i); +} + +void G_OffMotorcycle(DukePlayer_t *pPlayer) +{ + int j; + if (pPlayer->on_motorcycle) + { + if (A_CheckSoundPlaying(pPlayer->i,188)) + S_StopEnvSound(188,pPlayer->i); + if (A_CheckSoundPlaying(pPlayer->i,187)) + S_StopEnvSound(187,pPlayer->i); + if (A_CheckSoundPlaying(pPlayer->i,186)) + S_StopEnvSound(186,pPlayer->i); + if (A_CheckSoundPlaying(pPlayer->i,214)) + S_StopEnvSound(214,pPlayer->i); + if (!A_CheckSoundPlaying(pPlayer->i,42)) + A_PlaySound(42, pPlayer->i); + pPlayer->on_motorcycle = 0; + pPlayer->gotweapon &= ~(1< curr_weapon = pPlayer->last_full_weapon; + P_CheckWeapon(pPlayer); + pPlayer->q16horiz = F16(100); + pPlayer->moto_do_bump = 0; + pPlayer->moto_speed = 0; + pPlayer->tilt_status = 0; + pPlayer->moto_drink = 0; + pPlayer->moto_bump_target = 0; + pPlayer->moto_bump = 0; + pPlayer->moto_turb = 0; + pPlayer->vel.x = 0; + pPlayer->vel.y = 0; + pPlayer->vel.x -= sintable[(fix16_to_int(pPlayer->q16ang)+512)&2047]<<7; + pPlayer->vel.y -= sintable[fix16_to_int(pPlayer->q16ang)&2047]<<7; + pPlayer->moto_underwater = 0; + j = A_Spawn(pPlayer->i, EMPTYBIKE); + sprite[j].ang = fix16_to_int(pPlayer->q16ang); + sprite[j].xvel += sintable[(fix16_to_int(pPlayer->q16ang)+512)&2047]<<7; + sprite[j].yvel += sintable[fix16_to_int(pPlayer->q16ang)&2047]<<7; + sprite[j].owner = pPlayer->ammo_amount[MOTORCYCLE_WEAPON]; + } +} + +void G_OnBoat(DukePlayer_t *pPlayer, int spriteNum) +{ + if (!pPlayer->on_boat) + { + if (spriteNum) + { + pPlayer->pos.x = sprite[spriteNum].x; + pPlayer->pos.y = sprite[spriteNum].y; + pPlayer->q16ang = F16(sprite[spriteNum].ang); + pPlayer->ammo_amount[BOAT_WEAPON] = sprite[spriteNum].owner; + deletesprite(spriteNum); + } + pPlayer->over_shoulder_on = 0; + pPlayer->on_boat = 1; + pPlayer->last_full_weapon = pPlayer->curr_weapon; + pPlayer->curr_weapon = BOAT_WEAPON; + pPlayer->gotweapon |= (1< vel.x = 0; + pPlayer->vel.y = 0; + pPlayer->q16horiz = F16(100); + } +} + +void G_OffBoat(DukePlayer_t *pPlayer) +{ + int j; + if (pPlayer->on_boat) + { + pPlayer->on_boat = 0; + pPlayer->gotweapon &= ~(1< curr_weapon = pPlayer->last_full_weapon; + P_CheckWeapon(pPlayer); + pPlayer->q16horiz = F16(100); + pPlayer->moto_do_bump = 0; + pPlayer->moto_speed = 0; + pPlayer->tilt_status = 0; + pPlayer->moto_drink = 0; + pPlayer->moto_bump_target = 0; + pPlayer->moto_bump = 0; + pPlayer->moto_turb = 0; + pPlayer->vel.x = 0; + pPlayer->vel.y = 0; + pPlayer->vel.x -= sintable[(fix16_to_int(pPlayer->q16ang)+512)&2047]<<7; + pPlayer->vel.y -= sintable[fix16_to_int(pPlayer->q16ang)&2047]<<7; + pPlayer->moto_underwater = 0; + j = A_Spawn(pPlayer->i, EMPTYBOAT); + sprite[j].ang = fix16_to_int(pPlayer->q16ang); + sprite[j].xvel += sintable[(fix16_to_int(pPlayer->q16ang)+512)&2047]<<7; + sprite[j].yvel += sintable[fix16_to_int(pPlayer->q16ang)&2047]<<7; + sprite[j].owner = pPlayer->ammo_amount[BOAT_WEAPON]; + } +} + + + +void G_GameExit(const char *msg) +{ + if (*msg != 0) g_player[myconnectindex].ps->palette = BASEPAL; + + if (ud.recstat == 1) + G_CloseDemoWrite(); + else if (ud.recstat == 2) + MAYBE_FCLOSE_AND_NULL(g_demo_filePtr); + // JBF: fixes crash on demo playback + // PK: modified from original + + if (!g_quickExit) + { + if (g_mostConcurrentPlayers > 1 && g_player[myconnectindex].ps->gm&MODE_GAME && GTFLAGS(GAMETYPE_SCORESHEET) && *msg == ' ') + { + G_BonusScreen(1); + videoSetGameMode(ud.config.ScreenMode,ud.config.ScreenWidth,ud.config.ScreenHeight,ud.config.ScreenBPP,ud.detail); + } + + // shareware and TEN screens + if (*msg != 0 && *(msg+1) != 'V' && *(msg+1) != 'Y') + G_DisplayExtraScreens(); + } + + if (*msg != 0) initprintf("%s\n",msg); + + if (in3dmode()) + G_Shutdown(); + + if (*msg != 0) + { + if (!(msg[0] == ' ' && msg[1] == 0)) + { + char titlebuf[256]; + Bsprintf(titlebuf,HEAD2 " %s",s_buildRev); + wm_msgbox(titlebuf, "%s", msg); + } + } + + Bfflush(NULL); + + exit(0); +} + + +#ifdef YAX_DEBUG +// ugh... +char m32_debugstr[64][128]; +int32_t m32_numdebuglines=0; + +static void M32_drawdebug(void) +{ + int i, col=paletteGetClosestColor(255,255,255); + int x=4, y=8; + + if (m32_numdebuglines>0) + { + videoBeginDrawing(); + for (i=0; i 640?0:1); + videoEndDrawing(); + } + m32_numdebuglines=0; +} +#endif + + +static int32_t G_DoThirdPerson(const DukePlayer_t *pp, vec3_t *vect, int16_t *vsectnum, int16_t ang, int16_t horiz) +{ + spritetype *sp = &sprite[pp->i]; + int32_t i, hx, hy; + int32_t bakcstat = sp->cstat; + hitdata_t hit; + + vec3_t n = { + sintable[(ang+1536)&2047]>>4, + sintable[(ang+1024)&2047]>>4, + (horiz-100) * 128 + }; + + updatesectorz(vect->x,vect->y,vect->z,vsectnum); + + sp->cstat &= ~0x101; + hitscan(vect, *vsectnum, n.x,n.y,n.z, &hit, CLIPMASK1); + sp->cstat = bakcstat; + + if (*vsectnum < 0) + return -1; + + hx = hit.pos.x-(vect->x); + hy = hit.pos.y-(vect->y); + + if (klabs(n.x)+klabs(n.y) > klabs(hx)+klabs(hy)) + { + *vsectnum = hit.sect; + + if (hit.wall >= 0) + { + int32_t daang = getangle(wall[wall[hit.wall].point2].x-wall[hit.wall].x, + wall[wall[hit.wall].point2].y-wall[hit.wall].y); + + i = n.x*sintable[daang] + n.y*sintable[(daang+1536)&2047]; + + if (klabs(n.x) > klabs(n.y)) + hx -= mulscale28(n.x,i); + else hy -= mulscale28(n.y,i); + } + else if (hit.sprite < 0) + { + if (klabs(n.x) > klabs(n.y)) + hx -= (n.x>>5); + else hy -= (n.y>>5); + } + + if (klabs(n.x) > klabs(n.y)) + i = divscale16(hx,n.x); + else i = divscale16(hy,n.y); + + if (i < CAMERADIST) + CAMERADIST = i; + } + + vect->x += mulscale16(n.x,CAMERADIST); + vect->y += mulscale16(n.y,CAMERADIST); + vect->z += mulscale16(n.z,CAMERADIST); + + CAMERADIST = min(CAMERADIST+((totalclock-CAMERACLOCK)<<10),65536); + CAMERACLOCK = totalclock; + + updatesectorz(vect->x,vect->y,vect->z,vsectnum); + + return 0; +} + +int32_t SE150_TempSectorZ[MAXSECTORS]; +int32_t SE150_TempSectorPicnum[MAXSECTORS]; + +static void G_SE150_Draw(int32_t spnum, int32_t x, int32_t y, int32_t z, int32_t a, int32_t h, int32_t smoothratio) +{ + int32_t i = 13, j, k = 0; + int32_t floor1 = spnum, floor2 = 0, ok = 0, fofmode; + int32_t offx, offy; + + if (sprite[spnum].ang != 512) return; + + tilesiz[13].x = 0; + tilesiz[13].y = 0; + tileDelete(13); + if (!(gotpic[i >> 3] & (1 << (i & 7)))) return; + gotpic[i >> 3] &= ~(1 << (i & 7)); + + floor1 = spnum; + + if (sprite[spnum].lotag == 152) fofmode = 150; + if (sprite[spnum].lotag == 153) fofmode = 151; + if (sprite[spnum].lotag == 154) fofmode = 150; + if (sprite[spnum].lotag == 155) fofmode = 151; + + // fofmode=sprite[spnum].lotag-2; + + // sectnum=sprite[j].sectnum; + // sectnum=cursectnum; + ok++; + + /* recursive? + for(j=0;j = 0; i = nextspritestat[i]) + { + switch(sprite[i].lotag) + { +// case 40: +// case 41: +// SE40_Draw(i,x,y,a,smoothratio); +// break; + case 152: + case 153: + case 154: + case 155: + if(g_player[screenpeek].ps->cursectnum == sprite[i].sectnum) + G_SE150_Draw(i,x,y,z,a,h,smoothratio); + break; + } + } +} + +#ifdef LEGACY_ROR +char ror_protectedsectors[MAXSECTORS]; +static int32_t drawing_ror = 0; +static int32_t ror_sprite = -1; + +static void G_OROR_DupeSprites(const spritetype *sp) +{ + // dupe the sprites touching the portal to the other sector + int32_t k; + const spritetype *refsp; + + if ((unsigned)sp->yvel >= (unsigned)g_mostConcurrentPlayers) + return; + + refsp = &sprite[sp->yvel]; + + for (SPRITES_OF_SECT(sp->sectnum, k)) + { + if (spritesortcnt >= maxspritesonscreen) + break; + + if (sprite[k].picnum != SECTOREFFECTOR && sprite[k].z >= sp->z) + { + Bmemcpy(&tsprite[spritesortcnt], &sprite[k], sizeof(spritetype)); + + tsprite[spritesortcnt].x += (refsp->x - sp->x); + tsprite[spritesortcnt].y += (refsp->y - sp->y); + tsprite[spritesortcnt].z = tsprite[spritesortcnt].z - sp->z + actor[sp->yvel].ceilingz; + tsprite[spritesortcnt].sectnum = refsp->sectnum; + tsprite[spritesortcnt].owner = k; + tsprite[spritesortcnt].extra = 0; + +// OSD_Printf("duped sprite of pic %d at %d %d %d\n",tsprite[spritesortcnt].picnum,tsprite[spritesortcnt].x,tsprite[spritesortcnt].y,tsprite[spritesortcnt].z); + spritesortcnt++; + } + } +} + +static int16_t SE40backupStat[MAXSECTORS]; +static int32_t SE40backupZ[MAXSECTORS]; + +static void G_SE40(int32_t smoothratio) +{ + if ((unsigned)ror_sprite < MAXSPRITES) + { + int32_t x, y, z; + int16_t sect; + int32_t level = 0; + const spritetype *const sp = &sprite[ror_sprite]; + const int32_t sprite2 = sp->yvel; + + if ((unsigned)sprite2 >= MAXSPRITES) + return; + + if (klabs(sector[sp->sectnum].floorz - sp->z) < klabs(sector[sprite[sprite2].sectnum].floorz - sprite[sprite2].z)) + level = 1; + + x = CAMERA(pos.x) - sp->x; + y = CAMERA(pos.y) - sp->y; + z = CAMERA(pos.z) - (level ? sector[sp->sectnum].floorz : sector[sp->sectnum].ceilingz); + + sect = sprite[sprite2].sectnum; + updatesector(sprite[sprite2].x + x, sprite[sprite2].y + y, §); + + if (sect != -1) + { + int32_t renderz, picnum; + // XXX: PK: too large stack allocation for my taste + int32_t i; + int32_t pix_diff, newz; + // initprintf("drawing ror\n"); + + if (level) + { + // renderz = sector[sprite[sprite2].sectnum].ceilingz; + renderz = sprite[sprite2].z - (sprite[sprite2].yrepeat * tilesiz[sprite[sprite2].picnum].y<<1); + picnum = sector[sprite[sprite2].sectnum].ceilingpicnum; + sector[sprite[sprite2].sectnum].ceilingpicnum = 562; + tilesiz[562].x = tilesiz[562].y = 0; + + pix_diff = klabs(z) >> 8; + newz = - ((pix_diff / 128) + 1) * (128<<8); + + for (i = 0; i < numsectors; i++) + { + SE40backupStat[i] = sector[i].ceilingstat; + SE40backupZ[i] = sector[i].ceilingz; + if (!ror_protectedsectors[i] || sp->lotag == 41) + { + sector[i].ceilingstat = 1; + sector[i].ceilingz += newz; + } + } + } + else + { + // renderz = sector[sprite[sprite2].sectnum].floorz; + renderz = sprite[sprite2].z; + picnum = sector[sprite[sprite2].sectnum].floorpicnum; + sector[sprite[sprite2].sectnum].floorpicnum = 562; + tilesiz[562].x = tilesiz[562].y = 0; + + pix_diff = klabs(z) >> 8; + newz = ((pix_diff / 128) + 1) * (128<<8); + + for (i = 0; i < numsectors; i++) + { + SE40backupStat[i] = sector[i].floorstat; + SE40backupZ[i] = sector[i].floorz; + if (!ror_protectedsectors[i] || sp->lotag == 41) + { + sector[i].floorstat = 1; + sector[i].floorz = +newz; + } + } + } + +#ifdef POLYMER + if (videoGetRenderMode() == REND_POLYMER) + polymer_setanimatesprites(G_DoSpriteAnimations, CAMERA(pos.x), CAMERA(pos.y), fix16_to_int(CAMERA(q16ang)), smoothratio); +#endif + renderDrawRoomsQ16(sprite[sprite2].x + x, sprite[sprite2].y + y, + z + renderz, CAMERA(q16ang), CAMERA(q16horiz), sect); + drawing_ror = 1 + level; + + if (drawing_ror == 2) // viewing from top + G_OROR_DupeSprites(sp); + + G_DoSpriteAnimations(CAMERA(pos.x),CAMERA(pos.y),fix16_to_int(CAMERA(q16ang)),smoothratio); + renderDrawMasks(); + + if (level) + { + sector[sprite[sprite2].sectnum].ceilingpicnum = picnum; + for (i = 0; i < numsectors; i++) + { + sector[i].ceilingstat = SE40backupStat[i]; + sector[i].ceilingz = SE40backupZ[i]; + } + } + else + { + sector[sprite[sprite2].sectnum].floorpicnum = picnum; + + for (i = 0; i < numsectors; i++) + { + sector[i].floorstat = SE40backupStat[i]; + sector[i].floorz = SE40backupZ[i]; + } + } + } + } +} +#endif + +void G_HandleMirror(int32_t x, int32_t y, int32_t z, fix16_t a, fix16_t q16horiz, int32_t smoothratio) +{ + if ((gotpic[MIRROR>>3]&(1<<(MIRROR&7))) +#ifdef POLYMER + && (videoGetRenderMode() != REND_POLYMER) +#endif + ) + { + if (g_mirrorCount == 0) + { + // NOTE: We can have g_mirrorCount==0 but gotpic'd MIRROR, + // for example in LNGA2. + gotpic[MIRROR>>3] &= ~(1<<(MIRROR&7)); + return; + } + + int32_t i = 0, dst = INT32_MAX; + + for (bssize_t k=g_mirrorCount-1; k>=0; k--) + { + if (!wallvisible(x, y, g_mirrorWall[k])) + continue; + + const int32_t j = + klabs(wall[g_mirrorWall[k]].x - x) + + klabs(wall[g_mirrorWall[k]].y - y); + + if (j < dst) + dst = j, i = k; + } + + if (wall[g_mirrorWall[i]].overpicnum != MIRROR) + { + // Try to find a new mirror wall in case the original one was broken. + + int32_t startwall = sector[g_mirrorSector[i]].wallptr; + int32_t endwall = startwall + sector[g_mirrorSector[i]].wallnum; + + for (bssize_t k=startwall; k = 0 && (wall[j].cstat&32) && wall[j].overpicnum==MIRROR) // cmp. premap.c + { + g_mirrorWall[i] = j; + break; + } + } + } + + if (wall[g_mirrorWall[i]].overpicnum == MIRROR) + { + int32_t tposx, tposy; + fix16_t tang; + + renderPrepareMirror(x, y, a, g_mirrorWall[i], &tposx, &tposy, &tang); + + int32_t j = g_visibility; + g_visibility = (j>>1) + (j>>2); + + if (videoGetRenderMode() == REND_CLASSIC) + { + int32_t didmirror; + + yax_preparedrawrooms(); + didmirror = renderDrawRoomsQ16(tposx,tposy,z,tang,q16horiz,g_mirrorSector[i]+MAXSECTORS); + yax_drawrooms(G_DoSpriteAnimations, g_mirrorSector[i], didmirror, smoothratio); + } +#ifdef USE_OPENGL + else + renderDrawRoomsQ16(tposx,tposy,z,tang,q16horiz,g_mirrorSector[i]+MAXSECTORS); + // XXX: Sprites don't get drawn with TROR/Polymost +#endif + display_mirror = 1; + G_DoSpriteAnimations(tposx,tposy,fix16_to_int(tang),smoothratio); + display_mirror = 0; + + renderDrawMasks(); + renderCompleteMirror(); //Reverse screen x-wise in this function + g_visibility = j; + } + +#ifdef SPLITSCREEN_MOD_HACKS + if (!g_fakeMultiMode) +#endif + { + // HACK for splitscreen mod: this is so that mirrors will be drawn + // from showview commands. Ugly, because we'll attempt do draw mirrors + // each frame then. But it's better than not drawing them, I guess. + // XXX: fix the sequence of setting/clearing this bit. Right now, + // we always draw one frame without drawing the mirror, after which + // the bit gets set and drawn subsequently. + gotpic[MIRROR>>3] &= ~(1<<(MIRROR&7)); + } + } +} + +#ifdef USE_OPENGL +static void G_ReadGLFrame(void) +{ + // Save OpenGL screenshot with Duke3D palette + // NOTE: maybe need to move this to the engine... + palette_t *const frame = (palette_t *)Xcalloc(xdim * ydim, sizeof(palette_t)); + char *const pic = (char *) waloff[TILE_SAVESHOT]; + + int32_t x, y; + const int32_t xf = divscale16(ydim*4/3, 320); + const int32_t yf = divscale16(ydim, 200); // (ydim<<16)/200 + + tilesiz[TILE_SAVESHOT].x = 200; + tilesiz[TILE_SAVESHOT].y = 320; + + if (!frame) + { + Bmemset(pic, 0, 320 * 200); + return; + } + + videoBeginDrawing(); + glReadPixels(0, 0, xdim, ydim, GL_RGBA, GL_UNSIGNED_BYTE, frame); + videoEndDrawing(); + + for (y = 0; y < 200; y++) + { + const int32_t base = mulscale16(200 - y - 1, yf)*xdim; + + for (x = 0; x < 320; x++) + { + const palette_t *pix = &frame[base + mulscale16(x, xf) + (xdim-(ydim*4/3))/2]; + pic[320 * y + x] = paletteGetClosestColor(pix->r, pix->g, pix->b); + } + } + + Bfree(frame); +} +#endif + +void G_DrawRooms(int32_t playerNum, int32_t smoothRatio) +{ + DukePlayer_t *const pPlayer = g_player[playerNum].ps; + + int yxAspect = yxaspect; + int viewingRange = viewingrange; + + if (g_networkMode == NET_DEDICATED_SERVER) return; + + totalclocklock = totalclock; + + if (pub > 0 || videoGetRenderMode() >= REND_POLYMOST) // JBF 20040101: redraw background always + { +#ifndef EDUKE32_TOUCH_DEVICES + if (ud.screen_size >= 8) +#endif + G_DrawBackground(); + pub = 0; + } + + if (ud.overhead_on == 2 || ud.show_help || (pPlayer->cursectnum == -1 && videoGetRenderMode() != REND_CLASSIC)) + return; + + if (r_usenewaspect) + { + newaspect_enable = 1; + videoSetCorrectedAspect(); + } + + if (ud.pause_on || pPlayer->on_crane > -1) + smoothRatio = 65536; + else + smoothRatio = calc_smoothratio(totalclock, ototalclock); + + if (RRRA && g_fogType) + pPlayer->visibility = ud.const_visibility; + + int const playerVis = pPlayer->visibility; + g_visibility = (playerVis <= 0) ? 0 : (int32_t)(playerVis * (numplayers > 1 ? 1.f : r_ambientlightrecip)); + + CAMERA(sect) = pPlayer->cursectnum; + + G_DoInterpolations(smoothRatio); + G_AnimateCamSprite(smoothRatio); + + if (ud.camerasprite >= 0) + { + spritetype *const pSprite = &sprite[ud.camerasprite]; + + // XXX: what? + if (pSprite->yvel < 0) pSprite->yvel = -100; + else if (pSprite->yvel > 199) pSprite->yvel = 300; + + CAMERA(q16ang) = fix16_from_int(actor[ud.camerasprite].tempang + + mulscale16(((pSprite->ang + 1024 - actor[ud.camerasprite].tempang) & 2047) - 1024, smoothRatio)); + +#ifdef LEGACY_ROR + if (!RR) + G_SE40(smoothRatio); +#endif +#ifdef POLYMER + if (videoGetRenderMode() == REND_POLYMER) + polymer_setanimatesprites(G_DoSpriteAnimations, pSprite->x, pSprite->y, fix16_to_int(CAMERA(q16ang)), smoothRatio); +#endif + yax_preparedrawrooms(); + renderDrawRoomsQ16(pSprite->x, pSprite->y, pSprite->z - ZOFFSET6, CAMERA(q16ang), fix16_from_int(pSprite->yvel), pSprite->sectnum); + yax_drawrooms(G_DoSpriteAnimations, pSprite->sectnum, 0, smoothRatio); + G_DoSpriteAnimations(pSprite->x, pSprite->y, fix16_to_int(CAMERA(q16ang)), smoothRatio); + renderDrawMasks(); + } + else + { + int32_t floorZ, ceilZ; + int32_t tiltcx, tiltcy, tiltcs=0; // JBF 20030807 + + int const vr = divscale22(1, RR ? 64 : (sprite[pPlayer->i].yrepeat + 28)); + int screenTilting = (videoGetRenderMode() == REND_CLASSIC && (((ud.screen_tilting && pPlayer->rotscrnang) || (RR && pPlayer->drink_amt > 89) +#ifdef SPLITSCREEN_MOD_HACKS + && !g_fakeMultiMode +#endif + ))); + + if (!RRRA || !pPlayer->drug_mode) + { + if (!r_usenewaspect) + renderSetAspect(vr, yxaspect); + else + { + viewingRange = vr; + yxAspect = tabledivide32_noinline(65536 * ydim * 8, xdim * 5); + + renderSetAspect(mulscale16(viewingRange,viewingrange), yxaspect); + } + } + + if (g_screenCapture) + { + walock[TILE_SAVESHOT] = 199; + if (waloff[TILE_SAVESHOT] == 0) + cacheAllocateBlock(&waloff[TILE_SAVESHOT],200*320,&walock[TILE_SAVESHOT]); + + if (videoGetRenderMode() == REND_CLASSIC) + renderSetTarget(TILE_SAVESHOT, 200, 320); + } + else if (screenTilting) + { + int32_t oviewingrange = viewingrange; // save it from setaspect() + const int16_t tang = (ud.screen_tilting) ? pPlayer->rotscrnang : 0; + + if (tang == 1024) + screenTilting = 2; + else + { + // Maximum possible allocation size passed to allocache() below + // since there is no equivalent of free() for allocache(). +#if MAXYDIM >= 640 + int const maxTiltSize = 640*640; +#else + int const maxTiltSize = 320*320; +#endif + // To render a tilted screen in high quality, we need at least + // 640 pixels of *Y* dimension. +#if MAXYDIM >= 640 + // We also need + // * xdim >= 640 since tiltcx will be passed as setview()'s x2 + // which must be less than xdim. + // * ydim >= 640 (sic!) since the tile-to-draw-to will be set + // up with dimension 400x640, but the engine's arrays like + // lastx[] are alloc'd with *xdim* elements! (This point is + // the dynamic counterpart of the #if above since we now + // allocate these engine arrays tightly.) + // XXX: The engine should be in charge of setting up everything + // so that no oob access occur. + if (xdim >= 640 && ydim >= 640 && (!RRRA || pPlayer->drink_amt <= 89)) + { + tiltcs = 2; + tiltcx = 640; + tiltcy = 400; + } + else +#endif + { + // JBF 20030807: Increased tilted-screen quality + tiltcs = 1; + + // NOTE: The same reflections as above apply here, too. + // TILT_SETVIEWTOTILE_320. + tiltcx = 320; + tiltcy = 200; + } + + // If the view is rotated (not 0 or 180 degrees modulo 360 degrees), + // we render onto a square tile and display a portion of that + // rotated on-screen later on. + const int32_t viewtilexsiz = (tang&1023) ? tiltcx : tiltcy; + const int32_t viewtileysiz = tiltcx; + + walock[TILE_TILT] = 255; + if (waloff[TILE_TILT] == 0) + cacheAllocateBlock(&waloff[TILE_TILT], maxTiltSize, &walock[TILE_TILT]); + + renderSetTarget(TILE_TILT, viewtilexsiz, viewtileysiz); + + if ((tang&1023) == 512) + { + //Block off unscreen section of 90ø tilted screen + int const j = tiltcx-(60*tiltcs); + for (bssize_t i=(60*tiltcs)-1; i>=0; i--) + { + startumost[i] = 1; + startumost[i+j] = 1; + startdmost[i] = 0; + startdmost[i+j] = 0; + } + } + + int vRange = (tang & 511); + + if (vRange > 256) + vRange = 512 - vRange; + + vRange = sintable[vRange + 512] * 8 + sintable[vRange] * 5; + + // setaspect(i>>1, yxaspect); + renderSetAspect(mulscale16(oviewingrange, vRange >> 1), yxaspect); + + viewingRange = vRange >> 1; + yxAspect = tabledivide32_noinline(65536 * ydim * 8, xdim * 5); + } + } + else if (videoGetRenderMode() >= REND_POLYMOST && (ud.screen_tilting +#ifdef SPLITSCREEN_MOD_HACKS + && !g_fakeMultiMode +#endif + )) + { +#ifdef USE_OPENGL + renderSetRollAngle(pPlayer->orotscrnang + mulscale16(((pPlayer->rotscrnang - pPlayer->orotscrnang + 1024)&2047)-1024, smoothRatio)); +#endif + pPlayer->orotscrnang = pPlayer->rotscrnang; + } + + if (RRRA && pPlayer->drug_mode > 0) + { + while (pPlayer->drug_timer < totalclock && !(pPlayer->gm & MODE_MENU) && !ud.pause_on) + { + int aspect; + if (pPlayer->drug_stat[0] == 0) + { + pPlayer->drug_stat[1]++; + aspect = vr + pPlayer->drug_stat[1] * 5000; + if (vr * 3 < aspect) + { + pPlayer->drug_aspect = vr * 3; + pPlayer->drug_stat[0] = 2; + } + else + { + pPlayer->drug_aspect = aspect; + } + P_UpdateScreenPal(pPlayer); + } + else if (pPlayer->drug_stat[0] == 3) + { + pPlayer->drug_stat[1]--; + aspect = vr + pPlayer->drug_stat[1] * 5000; + if (aspect < vr) + { + pPlayer->drug_mode = 0; + pPlayer->drug_stat[0] = 0; + pPlayer->drug_stat[2] = 0; + pPlayer->drug_stat[1] = 0; + pPlayer->drug_aspect = vr; + } + else + { + pPlayer->drug_aspect = aspect; + } + P_UpdateScreenPal(pPlayer); + } + else if (pPlayer->drug_stat[0] == 2) + { + if (pPlayer->drug_stat[2] > 30) + { + pPlayer->drug_stat[0] = 1; + } + else + { + pPlayer->drug_stat[2]++; + aspect = pPlayer->drug_stat[2] * 500 + vr * 3; + pPlayer->drug_aspect = aspect; + P_UpdateScreenPal(pPlayer); + } + } + else + { + if (pPlayer->drug_stat[2] < 1) + { + pPlayer->drug_stat[0] = 2; + pPlayer->drug_mode--; + if (pPlayer->drug_mode == 1) + pPlayer->drug_stat[0] = 3; + } + else + { + pPlayer->drug_stat[2]--; + aspect = pPlayer->drug_stat[2] * 500 + vr * 3; + pPlayer->drug_aspect = aspect; + P_UpdateScreenPal(pPlayer); + } + } + + pPlayer->drug_timer += TICSPERFRAME / 2; + } + if (!r_usenewaspect) + renderSetAspect(pPlayer->drug_aspect, yxaspect); + else + { + viewingRange = pPlayer->drug_aspect; + yxAspect = tabledivide32_noinline(65536 * ydim * 8, xdim * 5); + + renderSetAspect(mulscale16(viewingRange, viewingrange), yxaspect); + } + P_UpdateScreenPal(pPlayer); + } + + if (pPlayer->newowner < 0) + { + vec3_t const camVect = { pPlayer->opos.x + mulscale16(pPlayer->pos.x - pPlayer->opos.x, smoothRatio), + pPlayer->opos.y + mulscale16(pPlayer->pos.y - pPlayer->opos.y, smoothRatio), + pPlayer->opos.z + mulscale16(pPlayer->pos.z - pPlayer->opos.z, smoothRatio) }; + + CAMERA(pos) = camVect; + CAMERA(q16ang) = pPlayer->oq16ang + + mulscale16(((pPlayer->q16ang + F16(1024) - pPlayer->oq16ang) & 0x7FFFFFF) - F16(1024), smoothRatio) + + fix16_from_int(pPlayer->look_ang); + CAMERA(q16horiz) = pPlayer->oq16horiz + pPlayer->oq16horizoff + + mulscale16((pPlayer->q16horiz + pPlayer->q16horizoff - pPlayer->oq16horiz - pPlayer->oq16horizoff), smoothRatio); + + if (ud.viewbob) + { + int zAdd = (pPlayer->opyoff + mulscale16(pPlayer->pyoff-pPlayer->opyoff, smoothRatio)); + + if (pPlayer->over_shoulder_on) + zAdd >>= 3; + + CAMERA(pos.z) += zAdd; + } + + if (pPlayer->over_shoulder_on) + { + CAMERA(pos.z) -= 3072; + + if (G_DoThirdPerson(pPlayer, &CAMERA(pos), &CAMERA(sect), fix16_to_int(CAMERA(q16ang)), fix16_to_int(CAMERA(q16horiz))) < 0) + { + CAMERA(pos.z) += 3072; + G_DoThirdPerson(pPlayer, &CAMERA(pos), &CAMERA(sect), fix16_to_int(CAMERA(q16ang)), fix16_to_int(CAMERA(q16horiz))); + } + } + } + else + { + vec3_t const camVect = G_GetCameraPosition(pPlayer->newowner, smoothRatio); + + // looking through viewscreen + CAMERA(pos) = camVect; + CAMERA(q16ang) = pPlayer->q16ang + fix16_from_int(pPlayer->look_ang); + CAMERA(q16horiz) = fix16_from_int(100 + sprite[pPlayer->newowner].shade); + CAMERA(sect) = sprite[pPlayer->newowner].sectnum; + } + + ceilZ = actor[pPlayer->i].ceilingz; + floorZ = actor[pPlayer->i].floorz; + + if (g_earthquakeTime > 0 && pPlayer->on_ground == 1) + { + CAMERA(pos.z) += 256 - (((g_earthquakeTime)&1) << 9); + CAMERA(q16ang) += fix16_from_int((2 - ((g_earthquakeTime)&2)) << 2); + } + + if (sprite[pPlayer->i].pal == 1) + CAMERA(pos.z) -= (18<<8); + + if (pPlayer->newowner < 0 && pPlayer->spritebridge == 0) + { + // NOTE: when shrunk, p->pos.z can be below the floor. This puts the + // camera into the sector again then. + + if (CAMERA(pos.z) < (pPlayer->truecz + ZOFFSET6)) + CAMERA(pos.z) = ceilZ + ZOFFSET6; + else if (CAMERA(pos.z) > (pPlayer->truefz - ZOFFSET6)) + CAMERA(pos.z) = floorZ - ZOFFSET6; + } + + while (CAMERA(sect) >= 0) // if, really + { + getzsofslope(CAMERA(sect),CAMERA(pos.x),CAMERA(pos.y),&ceilZ,&floorZ); +#ifdef YAX_ENABLE + if (yax_getbunch(CAMERA(sect), YAX_CEILING) >= 0) + { + if (CAMERA(pos.z) < ceilZ) + { + updatesectorz(CAMERA(pos.x), CAMERA(pos.y), CAMERA(pos.z), &CAMERA(sect)); + break; // since CAMERA(sect) might have been updated to -1 + // NOTE: fist discovered in WGR2 SVN r134, til' death level 1 + // (Lochwood Hollow). A problem REMAINS with Polymost, maybe classic! + } + } + else +#endif + if (CAMERA(pos.z) < ceilZ+ZOFFSET6) + CAMERA(pos.z) = ceilZ+ZOFFSET6; + +#ifdef YAX_ENABLE + if (yax_getbunch(CAMERA(sect), YAX_FLOOR) >= 0) + { + if (CAMERA(pos.z) > floorZ) + updatesectorz(CAMERA(pos.x), CAMERA(pos.y), CAMERA(pos.z), &CAMERA(sect)); + } + else +#endif + if (CAMERA(pos.z) > floorZ-ZOFFSET6) + CAMERA(pos.z) = floorZ-ZOFFSET6; + + break; + } + + CAMERA(q16horiz) = fix16_clamp(CAMERA(q16horiz), F16(HORIZ_MIN), F16(HORIZ_MAX)); + + G_HandleMirror(CAMERA(pos.x), CAMERA(pos.y), CAMERA(pos.z), CAMERA(q16ang), CAMERA(q16horiz), smoothRatio); +#ifdef LEGACY_ROR + if (!RR) + G_SE40(smoothRatio); +#endif + if (RRRA) + G_SE150(CAMERA(pos.x), CAMERA(pos.y), CAMERA(pos.z), CAMERA(q16ang), CAMERA(q16horiz), smoothRatio); +#ifdef POLYMER + if (videoGetRenderMode() == REND_POLYMER) + polymer_setanimatesprites(G_DoSpriteAnimations, CAMERA(pos.x),CAMERA(pos.y),fix16_to_int(CAMERA(q16ang)),smoothRatio); +#endif + // for G_PrintCoords + dr_viewingrange = viewingrange; + dr_yxaspect = yxaspect; +#ifdef DEBUG_MIRRORS_ONLY + gotpic[MIRROR>>3] |= (1<<(MIRROR&7)); +#else + if (RR && sector[CAMERA(sect)].lotag == 848) + { + yax_preparedrawrooms(); + renderDrawRoomsQ16(CAMERA(pos.x),CAMERA(pos.y),CAMERA(pos.z),CAMERA(q16ang),CAMERA(q16horiz),CAMERA(sect)); + yax_drawrooms(G_DoSpriteAnimations, CAMERA(sect), 0, smoothRatio); + + G_DoSpriteAnimations(CAMERA(pos.x),CAMERA(pos.y),fix16_to_int(CAMERA(q16ang)),smoothRatio); + + renderDrawMasks(); + + int geoSector = 0; + + for (bsize_t gs = 0; gs < g_geoSectorCnt; gs++) + { + int spriteNum = headspritesect[g_geoSector[gs]]; + while (spriteNum != -1) + { + int spriteNext = nextspritesect[spriteNum]; + changespritesect(spriteNum, g_geoSectorWarp[gs]); + sprite[spriteNum].x -= g_geoSectorX[gs]; + sprite[spriteNum].y -= g_geoSectorY[gs]; + setsprite(spriteNum, (vec3_t*)&sprite[spriteNum]); + spriteNum = spriteNext; + } + if (CAMERA(sect) == g_geoSector[gs]) + geoSector = gs; + } + + CAMERA(pos.x) -= g_geoSectorX[geoSector]; + CAMERA(pos.y) -= g_geoSectorY[geoSector]; + yax_preparedrawrooms(); + renderDrawRoomsQ16(CAMERA(pos.x),CAMERA(pos.y),CAMERA(pos.z),CAMERA(q16ang),CAMERA(q16horiz),g_geoSectorWarp[geoSector]); + yax_drawrooms(G_DoSpriteAnimations, g_geoSectorWarp[geoSector], 0, smoothRatio); + CAMERA(pos.x) += g_geoSectorX[geoSector]; + CAMERA(pos.y) += g_geoSectorY[geoSector]; + + for (bsize_t gs = 0; gs < g_geoSectorCnt; gs++) + { + int spriteNum = headspritesect[g_geoSectorWarp[gs]]; + while (spriteNum != -1) + { + int spriteNext = nextspritesect[spriteNum]; + changespritesect(spriteNum, g_geoSector[gs]); + sprite[spriteNum].x += g_geoSectorX[gs]; + sprite[spriteNum].y += g_geoSectorY[gs]; + setsprite(spriteNum, (vec3_t*)&sprite[spriteNum]); + spriteNum = spriteNext; + } + } + + G_DoSpriteAnimations(CAMERA(pos.x),CAMERA(pos.y),fix16_to_int(CAMERA(q16ang)),smoothRatio); + + renderDrawMasks(); + + for (bsize_t gs = 0; gs < g_geoSectorCnt; gs++) + { + int spriteNum = headspritesect[g_geoSector[gs]]; + while (spriteNum != -1) + { + int spriteNext = nextspritesect[spriteNum]; + changespritesect(spriteNum, g_geoSectorWarp2[gs]); + sprite[spriteNum].x -= g_geoSectorX2[gs]; + sprite[spriteNum].y -= g_geoSectorY2[gs]; + setsprite(spriteNum, (vec3_t*)&sprite[spriteNum]); + spriteNum = spriteNext; + } + if (CAMERA(sect) == g_geoSector[gs]) + geoSector = gs; + } + + CAMERA(pos.x) -= g_geoSectorX2[geoSector]; + CAMERA(pos.y) -= g_geoSectorY2[geoSector]; + yax_preparedrawrooms(); + renderDrawRoomsQ16(CAMERA(pos.x),CAMERA(pos.y),CAMERA(pos.z),CAMERA(q16ang),CAMERA(q16horiz),g_geoSectorWarp2[geoSector]); + yax_drawrooms(G_DoSpriteAnimations, g_geoSectorWarp2[geoSector], 0, smoothRatio); + CAMERA(pos.x) += g_geoSectorX2[geoSector]; + CAMERA(pos.y) += g_geoSectorY2[geoSector]; + + for (bsize_t gs = 0; gs < g_geoSectorCnt; gs++) + { + int spriteNum = headspritesect[g_geoSectorWarp2[gs]]; + while (spriteNum != -1) + { + int spriteNext = nextspritesect[spriteNum]; + changespritesect(spriteNum, g_geoSector[gs]); + sprite[spriteNum].x += g_geoSectorX2[gs]; + sprite[spriteNum].y += g_geoSectorY2[gs]; + setsprite(spriteNum, (vec3_t*)&sprite[spriteNum]); + spriteNum = spriteNext; + } + } + + G_DoSpriteAnimations(CAMERA(pos.x),CAMERA(pos.y),fix16_to_int(CAMERA(q16ang)),smoothRatio); + + renderDrawMasks(); + } + else + { + yax_preparedrawrooms(); + renderDrawRoomsQ16(CAMERA(pos.x),CAMERA(pos.y),CAMERA(pos.z),CAMERA(q16ang),CAMERA(q16horiz),CAMERA(sect)); + yax_drawrooms(G_DoSpriteAnimations, CAMERA(sect), 0, smoothRatio); +#ifdef LEGACY_ROR + if (!RR && (unsigned)ror_sprite < MAXSPRITES && drawing_ror == 1) // viewing from bottom + G_OROR_DupeSprites(&sprite[ror_sprite]); +#endif + G_DoSpriteAnimations(CAMERA(pos.x),CAMERA(pos.y),fix16_to_int(CAMERA(q16ang)),smoothRatio); + } +#ifdef LEGACY_ROR + drawing_ror = 0; +#endif + renderDrawMasks(); +#endif + + if (g_screenCapture) + { + g_screenCapture = 0; + + tileInvalidate(TILE_SAVESHOT, 0, 255); + + if (videoGetRenderMode() == REND_CLASSIC) + { + renderRestoreTarget(); +// walock[TILE_SAVESHOT] = 1; + } +#ifdef USE_OPENGL + else + G_ReadGLFrame(); +#endif + } + else if (screenTilting) + { + const int16_t tang = (ud.screen_tilting) ? pPlayer->rotscrnang : 0; + + if (screenTilting == 2) // tang == 1024 + { + videoBeginDrawing(); + { + const int32_t height = windowxy2.y-windowxy1.y+1; + const int32_t width = windowxy2.x-windowxy1.x+1; + + uint8_t *f = (uint8_t *)(frameplace + ylookup[windowxy1.y]); + int32_t x, y; + + for (y=0; y < (height>>1); y++) + swapbufreverse(f + y*bytesperline + windowxy2.x, + f + (height-1-y)*bytesperline + windowxy1.x, + width); + + f += (height>>1)*bytesperline + windowxy1.x; + + if (height&1) + for (x=0; x<(width>>1); x++) + swapchar(&f[x], &f[width-1-x]); + } + videoEndDrawing(); + } + else + { + renderRestoreTarget(); + picanm[TILE_TILT].xofs = picanm[TILE_TILT].yofs = 0; + + int tiltZoom = (tang&511); + + if (tiltZoom > 256) + tiltZoom = 512 - tiltZoom; + + tiltZoom = sintable[tiltZoom + 512] * 8 + sintable[tiltZoom] * 5; + tiltZoom >>= tiltcs; // JBF 20030807 + + rotatesprite_win(160 << 16, 100 << 16, tiltZoom, tang + 512, TILE_TILT, 0, 0, 4 + 2 + 64 + 1024); + walock[TILE_TILT] = 199; + } + } + } + + G_RestoreInterpolations(); + + if (!RRRA || !g_fogType) + { + // Totalclock count of last step of p->visibility converging towards + // ud.const_visibility. + static int32_t lastvist; + const int32_t visdif = ud.const_visibility-pPlayer->visibility; + + // Check if totalclock was cleared (e.g. restarted game). + if (totalclock < lastvist) + lastvist = 0; + + // Every 2nd totalclock increment (each 1/60th second), ... + while (totalclock >= lastvist+2) + { + // ... approximately three-quarter the difference between + // p->visibility and ud.const_visibility. + const int32_t visinc = visdif>>2; + + if (klabs(visinc) == 0) + { + pPlayer->visibility = ud.const_visibility; + break; + } + + pPlayer->visibility += visinc; + lastvist = totalclock; + } + } + + if (r_usenewaspect) + { + newaspect_enable = 0; + renderSetAspect(viewingRange, yxAspect); + } +} + +void G_DumpDebugInfo(void) +{ + int32_t j,x; + // FILE * fp=fopen("condebug.log","w"); + + VM_ScriptInfo(insptr, 64); + + for (x=0; x = 0) + { + buildprint("Sprite ", j, " (", TrackerCast(sprite[j].x), ",", TrackerCast(sprite[j].y), ",", TrackerCast(sprite[j].z), + ") (picnum: ", TrackerCast(sprite[j].picnum), ")\n"); + buildprint("\n"); + j = nextspritestat[j]; + } + } +// fclose(fp); + saveboard("debug.map", &g_player[myconnectindex].ps->pos, fix16_to_int(g_player[myconnectindex].ps->q16ang), + g_player[myconnectindex].ps->cursectnum); +} + +// if is true, set the moveflag unconditionally, +// else only if it equals 0. +static int32_t G_InitActor(int32_t i, int32_t tilenum, int32_t set_movflag_uncond) +{ + if (g_tile[tilenum].execPtr) + { + SH(i) = *(g_tile[tilenum].execPtr); + AC_ACTION_ID(actor[i].t_data) = *(g_tile[tilenum].execPtr+1); + AC_MOVE_ID(actor[i].t_data) = *(g_tile[tilenum].execPtr+2); + + if (set_movflag_uncond || (*(g_tile[tilenum].execPtr + 3) && SHT(i) == 0)) // AC_MOVFLAGS + SHT(i) = *(g_tile[tilenum].execPtr+3); + + return 1; + } + + return 0; +} + +static actor_t NullActor; +static spriteext_t NullSprExt; +static spritesmooth_t NullSprSmooth; + +int32_t A_InsertSprite(int16_t whatsect,int32_t s_x,int32_t s_y,int32_t s_z,int16_t s_pn,int8_t s_s, + uint8_t s_xr,uint8_t s_yr,int16_t s_a,int16_t s_ve,int16_t s_zv,int16_t s_ow,int16_t s_ss) +{ + if (RR && s_ow < 0) + return 0; + + int32_t i = Net_IsRelevantStat(s_ss) ? Net_InsertSprite(whatsect, s_ss) : insertsprite(whatsect, s_ss); + + if (EDUKE32_PREDICT_FALSE((unsigned)i >= MAXSPRITES)) + { + G_DumpDebugInfo(); + OSD_Printf("Failed spawning pic %d spr from pic %d spr %d at x:%d,y:%d,z:%d,sect:%d\n", + s_pn,s_ow < 0 ? -1 : TrackerCast(sprite[s_ow].picnum),s_ow,s_x,s_y,s_z,whatsect); + G_GameExit("Too many sprites spawned."); + } + + uspritetype spr_temp = { s_x, s_y, s_z, 0, s_pn, s_s, 0, 0, 0, s_xr, s_yr, 0, + 0, whatsect, s_ss, s_a, s_ow, s_ve, 0, s_zv, 0, 0, 0 }; + +#ifdef DEBUGGINGAIDS + g_spriteStat.numins++; +#endif + + spritetype *s = &sprite[i]; + *s = *(spritetype *)&spr_temp; + actor[i] = NullActor; + actor[i].bpos = *(vec3_t *)s; + + if ((unsigned)s_ow < MAXSPRITES) + { + actor[i].picnum = sprite[s_ow].picnum; + actor[i].floorz = actor[s_ow].floorz; + actor[i].ceilingz = actor[s_ow].ceilingz; + } + + actor[i].actorstayput = actor[i].extra = -1; +#ifdef POLYMER + actor[i].lightId = -1; +#endif + actor[i].owner = s_ow; + + G_InitActor(i, s_pn, 1); + + spriteext[i] = NullSprExt; + spritesmooth[i] = NullSprSmooth; + + return i; +} + +#ifdef YAX_ENABLE +void Yax_SetBunchZs(int32_t sectnum, int32_t cf, int32_t daz) +{ + int32_t i, bunchnum = yax_getbunch(sectnum, cf); + + if (bunchnum < 0 || bunchnum >= numyaxbunches) + return; + + for (SECTORS_OF_BUNCH(bunchnum, YAX_CEILING, i)) + SECTORFLD(i,z, YAX_CEILING) = daz; + for (SECTORS_OF_BUNCH(bunchnum, YAX_FLOOR, i)) + SECTORFLD(i,z, YAX_FLOOR) = daz; +} + +static void Yax_SetBunchInterpolation(int32_t sectnum, int32_t cf) +{ + int32_t i, bunchnum = yax_getbunch(sectnum, cf); + + if (bunchnum < 0 || bunchnum >= numyaxbunches) + return; + + for (SECTORS_OF_BUNCH(bunchnum, YAX_CEILING, i)) + G_SetInterpolation(§or[i].ceilingz); + for (SECTORS_OF_BUNCH(bunchnum, YAX_FLOOR, i)) + G_SetInterpolation(§or[i].floorz); +} +#else +# define Yax_SetBunchInterpolation(sectnum, cf) +#endif + +// A_Spawn has two forms with arguments having different meaning: +// +// 1. spriteNum>=0: Spawn from parent sprite with picnum +// 2. spriteNum<0: Spawn from already *existing* sprite +int A_Spawn(int spriteNum, int tileNum) +{ + int newSprite; + spritetype *pSprite; + actor_t * pActor; + int sectNum; + + + if (spriteNum >= 0) + { + // spawn from parent sprite + newSprite = A_InsertSprite(sprite[spriteNum].sectnum,sprite[spriteNum].x,sprite[spriteNum].y,sprite[spriteNum].z, + tileNum,0,0,0,0,0,0,spriteNum,0); + actor[newSprite].picnum = sprite[spriteNum].picnum; + } + else + { + // spawn from already existing sprite + newSprite = tileNum; + spritetype *const pSprite = &sprite[newSprite]; + actor_t *const pActor = &actor[newSprite]; + + Bmemset(&actor[newSprite], 0, sizeof(actor_t)); + Bmemcpy(&pActor->bpos, &sprite[newSprite], sizeof(vec3_t)); + + pActor->picnum = pSprite->picnum; + + if (pSprite->picnum == SECTOREFFECTOR && pSprite->lotag == 50) + pActor->picnum = pSprite->owner; + + pSprite->owner = pActor->owner = newSprite; + + pActor->floorz = sector[pSprite->sectnum].floorz; + pActor->ceilingz = sector[pSprite->sectnum].ceilingz; + + pActor->actorstayput = pActor->extra = -1; + +#ifdef POLYMER + pActor->lightId = -1; +#endif + + if ((pSprite->cstat & 48) + && (RR || (pSprite->picnum != SPEAKER + && pSprite->picnum != LETTER + && pSprite->picnum != DUCK + && pSprite->picnum != TARGET + && pSprite->picnum != TRIPBOMB + && pSprite->picnum != VIEWSCREEN + && pSprite->picnum != VIEWSCREEN2)) + && (!(pSprite->picnum >= CRACK1 && pSprite->picnum <= CRACK4))) + { + if (pSprite->shade == 127) + goto SPAWN_END; + + if (A_CheckSwitchTile(newSprite) && (pSprite->cstat & 16)) + { + if (pSprite->pal && pSprite->picnum != ACCESSSWITCH && pSprite->picnum != ACCESSSWITCH2) + { + if (((!g_netServer && ud.multimode < 2)) || ((g_netServer || ud.multimode > 1) && !GTFLAGS(GAMETYPE_DMSWITCHES))) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + pSprite->lotag = pSprite->hitag = 0; + pSprite->cstat = 32768; + goto SPAWN_END; + } + } + + pSprite->cstat |= 257; + + if (pSprite->pal && pSprite->picnum != ACCESSSWITCH && pSprite->picnum != ACCESSSWITCH2) + pSprite->pal = 0; + + goto SPAWN_END; + } + + if (pSprite->hitag) + { + changespritestat(newSprite, STAT_FALLER); + pSprite->cstat |= 257; + pSprite->extra = g_impactDamage; + goto SPAWN_END; + } + } + + if (pSprite->cstat & 1) + pSprite->cstat |= 256; + + if (!G_InitActor(newSprite, pSprite->picnum, 0)) + T2(newSprite) = T5(newSprite) = 0; // AC_MOVE_ID, AC_ACTION_ID + } + + pSprite = &sprite[newSprite]; + pActor = &actor[newSprite]; + sectNum = pSprite->sectnum; + + //some special cases that can't be handled through the dynamictostatic system. + + if (pSprite->picnum >= CAMERA1 && pSprite->picnum <= CAMERA1 + 4) + pSprite->picnum = CAMERA1; + else if (pSprite->picnum >= BOLT1 && pSprite->picnum <= BOLT1 + 3) + pSprite->picnum = BOLT1; + else if (!RR && pSprite->picnum >= SIDEBOLT1 && pSprite->picnum <= SIDEBOLT1 + 3) + pSprite->picnum = SIDEBOLT1; + if (RRRA && pSprite->picnum == PIG+11) + { + pSprite->xrepeat = 16; + pSprite->yrepeat = 16; + pSprite->clipdist = 0; + pSprite->extra = 0; + pSprite->cstat = 0; + changespritestat(newSprite, 121); + } + else + switch (DYNAMICTILEMAP(pSprite->picnum)) + { + default: +default_case: + if (G_HaveActor(pSprite->picnum)) + { + if (spriteNum == -1 && pSprite->lotag > ud.player_skill) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + + // Init the size + if (pSprite->xrepeat == 0 || pSprite->yrepeat == 0) + pSprite->xrepeat = pSprite->yrepeat = 1; + + if (A_CheckSpriteFlags(newSprite, SFLAG_BADGUY)) + { + if (ud.monsters_off == 1) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + + A_Fall(newSprite); + + if (A_CheckSpriteFlags(newSprite, SFLAG_BADGUYSTAYPUT)) + pActor->actorstayput = pSprite->sectnum; + + if (!RR || A_CheckSpriteFlags(newSprite, SFLAG_KILLCOUNT)) + g_player[myconnectindex].ps->max_actors_killed++; + pSprite->clipdist = 80; + + if (spriteNum >= 0) + { + if (sprite[spriteNum].picnum == RESPAWN) + pActor->tempang = sprite[newSprite].pal = sprite[spriteNum].pal; + + A_PlayAlertSound(newSprite); + changespritestat(newSprite, STAT_ACTOR); + } + else + changespritestat(newSprite, STAT_ZOMBIEACTOR); + } + else + { + pSprite->clipdist = 40; + pSprite->owner = newSprite; + changespritestat(newSprite, STAT_ACTOR); + } + + pActor->timetosleep = 0; + + if (spriteNum >= 0) + pSprite->ang = sprite[spriteNum].ang; + } + break; + case FOF__STATIC: + if (RR) goto default_case; + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + case RRTILE280__STATICRR: + case RRTILE281__STATICRR: + case RRTILE282__STATICRR: + case RRTILE283__STATICRR: + case RRTILE2025__STATICRR: + case RRTILE2026__STATICRR: + case RRTILE2027__STATICRR: + case RRTILE2028__STATICRR: + pSprite->cstat = 0; + pSprite->cstat |= 32768; + pSprite->xrepeat = 0; + pSprite->yrepeat = 0; + pSprite->clipdist = 0; + pSprite->extra = 0; + changespritestat(newSprite, 105); + break; + case RRTILE3410__STATICRR: + pSprite->extra = 0; + changespritestat(newSprite, 107); + break; + case RRTILE8450__STATICRR: + if (!RRRA) goto default_case; + pSprite->xrepeat = 64; + pSprite->yrepeat = 64; + pSprite->extra = pSprite->lotag; + pSprite->cstat |= 257; + changespritestat(newSprite, 116); + break; + case RRTILE8487__STATICRR: + case RRTILE8489__STATICRR: + if (!RRRA) goto default_case; + pSprite->xrepeat = 32; + pSprite->yrepeat = 32; + pSprite->extra = 0; + pSprite->cstat |= 257; + pSprite->hitag = 0; + changespritestat(newSprite, 117); + break; + case RRTILE7424__STATICRR: + if (!RRRA) goto default_case; + pSprite->extra = 0; + pSprite->xrepeat = 0; + pSprite->yrepeat = 0; + changespritestat(newSprite, 11); + break; + case RRTILE7936__STATICRR: + if (!RRRA) goto default_case; + pSprite->xrepeat = 0; + pSprite->yrepeat = 0; + G_SetFog(2); + g_fogType = 1; + break; + case RRTILE6144__STATICRR: + if (!RRRA) goto default_case; + pSprite->xrepeat = 0; + pSprite->yrepeat = 0; + for (bssize_t TRAVERSE_CONNECT(playerNum)) + g_player[playerNum].ps->sea_sick_stat = 1; + break; + case RRTILE8448__STATICRR: + if (!RRRA) goto default_case; + pSprite->lotag = 1; + pSprite->clipdist = 0; + break; + case RRTILE8099__STATICRR: + if (!RRRA) goto default_case; + pSprite->lotag = 5; + pSprite->clipdist = 0; + changespritestat(newSprite, 123); + break; + case RRTILE8704__STATICRR: + if (!RRRA) goto default_case; + pSprite->lotag = 1; + pSprite->clipdist = 0; + break; + case RRTILE8192__STATICRR: + if (!RRRA) goto default_case; + pSprite->xrepeat = 0; + pSprite->yrepeat = 0; + g_ufoSpawnMinion = 1; + break; + case RRTILE8193__STATICRR: + if (!RRRA) goto default_case; + pSprite->xrepeat = 0; + pSprite->yrepeat = 0; + g_pistonSound = 1; + break; + case RRTILE8165__STATICRR: + if (!RRRA) goto default_case; + pSprite->lotag = 1; + pSprite->clipdist = 0; + pSprite->owner = newSprite; + pSprite->extra = 0; + changespritestat(newSprite, 115); + break; + case RRTILE8593__STATICRR: + if (!RRRA) goto default_case; + pSprite->lotag = 1; + pSprite->clipdist = 0; + pSprite->owner = newSprite; + pSprite->extra = 0; + changespritestat(newSprite, 122); + break; +//#endif + case RRTILE285__STATICRR: + case RRTILE286__STATICRR: + case RRTILE287__STATICRR: + case RRTILE288__STATICRR: + case RRTILE289__STATICRR: + case RRTILE290__STATICRR: + case RRTILE291__STATICRR: + case RRTILE292__STATICRR: + case RRTILE293__STATICRR: + pSprite->cstat = 0; + pSprite->cstat |= 32768; + pSprite->xrepeat = 0; + pSprite->yrepeat = 0; + pSprite->clipdist = 0; + pSprite->lotag = 0; + changespritestat(newSprite, 106); + break; + + case WATERSPLASH2__STATIC: + case MUD__STATICRR: + if (spriteNum >= 0) + { + setsprite(newSprite, (vec3_t *)&sprite[spriteNum]); + pSprite->xrepeat = pSprite->yrepeat = 8+(krand2()&7); + } + else pSprite->xrepeat = pSprite->yrepeat = 16+(krand2()&15); + + pSprite->shade = -16; + pSprite->cstat |= 128; + + if (spriteNum >= 0) + { + if (sector[sprite[spriteNum].sectnum].lotag == ST_2_UNDERWATER) + { + pSprite->z = getceilzofslope(pSprite->sectnum, pSprite->x, pSprite->y) + (16 << 8); + pSprite->cstat |= 8; + } + else if (sector[sprite[spriteNum].sectnum].lotag == ST_1_ABOVE_WATER) + pSprite->z = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y); + } + + if (sector[sectNum].floorpicnum == FLOORSLIME || sector[sectNum].ceilingpicnum == FLOORSLIME) + pSprite->pal = 7; + fallthrough__; + case NEON1__STATIC: + case NEON2__STATIC: + case NEON3__STATIC: + case NEON4__STATIC: + case NEON5__STATIC: + case NEON6__STATIC: + case DOMELITE__STATIC: + if (pSprite->picnum != WATERSPLASH2) + pSprite->cstat |= 257; + fallthrough__; + case NUKEBUTTON__STATIC: + if (RR && pSprite->picnum == NUKEBUTTON) + goto default_case; + if (pSprite->picnum == DOMELITE) + pSprite->cstat |= 257; + fallthrough__; + case JIBS1__STATIC: + case JIBS2__STATIC: + case JIBS3__STATIC: + case JIBS4__STATIC: + case JIBS5__STATIC: + case JIBS6__STATIC: + case DUKETORSO__STATIC: + case DUKEGUN__STATIC: + case DUKELEG__STATIC: + if (RR && pSprite->picnum == JIBS6) + { + pSprite->xrepeat >>= 1; + pSprite->yrepeat >>= 1; + } + changespritestat(newSprite, STAT_MISC); + break; + case HEADJIB1__STATIC: + case ARMJIB1__STATIC: + case LEGJIB1__STATIC: + case LIZMANHEAD1__STATIC: + case LIZMANARM1__STATIC: + case LIZMANLEG1__STATIC: + if (RR) goto default_case; + changespritestat(newSprite, STAT_MISC); + break; + case RRTILE2460__STATICRR: + case RRTILE2465__STATICRR: + case BIKEJIBA__STATICRR: + case BIKEJIBB__STATICRR: + case BIKEJIBC__STATICRR: + case BIKERJIBA__STATICRR: + case BIKERJIBB__STATICRR: + case BIKERJIBC__STATICRR: + case BIKERJIBD__STATICRR: + case CHEERJIBA__STATICRR: + case CHEERJIBB__STATICRR: + case CHEERJIBC__STATICRR: + case CHEERJIBD__STATICRR: + case FBOATJIBA__STATICRR: + case FBOATJIBB__STATICRR: + case RABBITJIBA__STATICRR: + case RABBITJIBB__STATICRR: + case RABBITJIBC__STATICRR: + case MAMAJIBA__STATICRR: + case MAMAJIBB__STATICRR: + if (!RRRA) goto default_case; + if (pSprite->picnum == RABBITJIBA) + { + pSprite->xrepeat = 18; + pSprite->yrepeat = 18; + } + else if (pSprite->picnum == RABBITJIBB) + { + pSprite->xrepeat = 36; + pSprite->yrepeat = 36; + } + else if (pSprite->picnum == RABBITJIBC) + { + pSprite->xrepeat = 54; + pSprite->yrepeat = 54; + } + fallthrough__; + case BILLYJIBA__STATICRR: + case BILLYJIBB__STATICRR: + case HULKJIBA__STATICRR: + case HULKJIBB__STATICRR: + case HULKJIBC__STATICRR: + case MINJIBA__STATICRR: + case MINJIBB__STATICRR: + case MINJIBC__STATICRR: + case COOTJIBA__STATICRR: + case COOTJIBB__STATICRR: + case COOTJIBC__STATICRR: + changespritestat(newSprite, STAT_MISC); + break; + case TONGUE__STATIC: + if (spriteNum >= 0) + pSprite->ang = sprite[spriteNum].ang; + pSprite->z -= 38<<8; + pSprite->zvel = 256-(krand2()&511); + pSprite->xvel = 64-(krand2()&127); + changespritestat(newSprite, STAT_PROJECTILE); + break; + case NATURALLIGHTNING__STATIC: + if (RR) goto default_case; + pSprite->cstat &= ~257; + pSprite->cstat |= 32768; + break; + case TRANSPORTERSTAR__STATIC: + case TRANSPORTERBEAM__STATIC: + if (spriteNum == -1) break; + if (pSprite->picnum == TRANSPORTERBEAM) + { + pSprite->xrepeat = 31; + pSprite->yrepeat = 1; + pSprite->z = sector[sprite[spriteNum].sectnum].floorz-PHEIGHT; + } + else + { + if (sprite[spriteNum].statnum == STAT_PROJECTILE) + pSprite->xrepeat = pSprite->yrepeat = 8; + else + { + pSprite->xrepeat = 48; + pSprite->yrepeat = 64; + if (sprite[spriteNum].statnum == STAT_PLAYER || A_CheckEnemySprite(&sprite[spriteNum])) + pSprite->z -= ZOFFSET5; + } + } + + pSprite->shade = -127; + pSprite->cstat = 128|2; + pSprite->ang = sprite[spriteNum].ang; + + pSprite->xvel = 128; + changespritestat(newSprite, STAT_MISC); + A_SetSprite(newSprite,CLIPMASK0); + setsprite(newSprite,(vec3_t *)pSprite); + break; + + case FRAMEEFFECT1__STATIC: + if (spriteNum >= 0) + { + pSprite->xrepeat = sprite[spriteNum].xrepeat; + pSprite->yrepeat = sprite[spriteNum].yrepeat; + if (RR && sprite[spriteNum].picnum == APLAYER) + T2(newSprite) = SMALLSMOKE; + else + T2(newSprite) = sprite[spriteNum].picnum; + } + else pSprite->xrepeat = pSprite->yrepeat = 0; + + changespritestat(newSprite, STAT_MISC); + + break; + + case LASERLINE__STATIC: + if (RR) goto default_case; + pSprite->yrepeat = 6; + pSprite->xrepeat = 32; + + if (g_tripbombLaserMode == 1) + pSprite->cstat = 16 + 2; + else if (g_tripbombLaserMode == 0 || g_tripbombLaserMode == 2) + pSprite->cstat = 16; + else + { + pSprite->xrepeat = 0; + pSprite->yrepeat = 0; + } + + if (spriteNum >= 0) pSprite->ang = actor[spriteNum].t_data[5]+512; + changespritestat(newSprite, STAT_MISC); + break; + + case FORCESPHERE__STATIC: + if (spriteNum == -1) + { + pSprite->cstat = 32768; + changespritestat(newSprite, STAT_ZOMBIEACTOR); + } + else + { + pSprite->xrepeat = pSprite->yrepeat = 1; + changespritestat(newSprite, STAT_MISC); + } + break; + + case BLOOD__STATIC: + pSprite->xrepeat = pSprite->yrepeat = RR ? 4 : 16; + pSprite->z -= (26<<8); + if (!RR && spriteNum >= 0 && sprite[spriteNum].pal == 6) + pSprite->pal = 6; + changespritestat(newSprite, STAT_MISC); + break; + case BLOODPOOL__STATIC: + case PUKE__STATIC: + { + if (RR && pSprite->picnum == PUKE) goto default_case; + int16_t pukeSect = pSprite->sectnum; + + updatesector(pSprite->x + 108, pSprite->y + 108, &pukeSect); + if (pukeSect >= 0 && sector[pukeSect].floorz == sector[pSprite->sectnum].floorz) + { + updatesector(pSprite->x - 108, pSprite->y - 108, &pukeSect); + if (pukeSect >= 0 && sector[pukeSect].floorz == sector[pSprite->sectnum].floorz) + { + updatesector(pSprite->x + 108, pSprite->y - 108, &pukeSect); + if (pukeSect >= 0 && sector[pukeSect].floorz == sector[pSprite->sectnum].floorz) + { + updatesector(pSprite->x - 108, pSprite->y + 108, &pukeSect); + if (pukeSect >= 0 && sector[pukeSect].floorz != sector[pSprite->sectnum].floorz) + goto zero_puke; + } + else goto zero_puke; + } + else goto zero_puke; + } + else + { + zero_puke: + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + + if (sector[sectNum].lotag == ST_1_ABOVE_WATER) + { + changespritestat(newSprite, STAT_MISC); + break; + } + + if (spriteNum >= 0 && (RR || pSprite->picnum != PUKE)) + { + if (sprite[spriteNum].pal == 1) + pSprite->pal = 1; + else if (sprite[spriteNum].pal != 6 && sprite[spriteNum].picnum != NUKEBARREL && sprite[spriteNum].picnum != TIRE) + pSprite->pal = (!RR && sprite[spriteNum].picnum == FECES) ? 7 : 2; // Brown or red + else + pSprite->pal = 0; // green + + if (sprite[spriteNum].picnum == TIRE) + pSprite->shade = 127; + } + pSprite->cstat |= 32; + if (RR) goto rrbloodpool_fallthrough; + fallthrough__; + } + case FECES__STATIC: + if (RR) goto default_case; + if (spriteNum >= 0) + pSprite->xrepeat = pSprite->yrepeat = 1; + changespritestat(newSprite, STAT_MISC); + break; + + case BLOODSPLAT1__STATIC: + case BLOODSPLAT2__STATIC: + case BLOODSPLAT3__STATIC: + case BLOODSPLAT4__STATIC: +rrbloodpool_fallthrough: + pSprite->cstat |= 16; + pSprite->xrepeat = 7 + (krand2() & 7); + pSprite->yrepeat = 7 + (krand2() & 7); + pSprite->z -= ZOFFSET2; + + if (pSprite->picnum == BLOODPOOL) + pSprite->cstat |= 32768; + + if (spriteNum >= 0 && sprite[spriteNum].pal == 6) + pSprite->pal = 6; + + A_AddToDeleteQueue(newSprite); + changespritestat(newSprite, STAT_MISC); + break; + + case TRIPBOMB__STATIC: + if (RR) goto default_case; + if (pSprite->lotag > ud.player_skill) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + + pSprite->xrepeat = 4; + pSprite->yrepeat = 5; + pSprite->hitag = newSprite; + pSprite->owner = pSprite->hitag; + pSprite->xvel = 16; + + A_SetSprite(newSprite, CLIPMASK0); + + pActor->t_data[0] = 17; + pActor->t_data[2] = 0; + pActor->t_data[5] = pSprite->ang; + + changespritestat(newSprite, STAT_ZOMBIEACTOR); + break; + + case SPACEMARINE__STATIC: + if (RR) goto default_case; + pSprite->extra = 20; + pSprite->cstat |= 257; + changespritestat(newSprite, STAT_ZOMBIEACTOR); + break; + + case PANNEL1__STATIC: + case PANNEL2__STATIC: + case OCEANSPRITE1__STATIC: + case OCEANSPRITE2__STATIC: + case OCEANSPRITE3__STATIC: + case OCEANSPRITE5__STATIC: + case MONK__STATIC: + case INDY__STATIC: + case LUKE__STATIC: + case JURYGUY__STATIC: + case HANGLIGHT__STATIC: + case FETUS__STATIC: + case FETUSBROKE__STATIC: + case TRIPODCAMERA__STATIC: + if (RR) goto default_case; + fallthrough__; + case HYDRENT__STATIC: + case SATELITE__STATIC: + case FUELPOD__STATIC: + case SOLARPANNEL__STATIC: + case ANTENNA__STATIC: + case CHAIR1__STATIC: + case CHAIR2__STATIC: + case CHAIR3__STATIC: + case BOTTLE1__STATIC: + case BOTTLE2__STATIC: + case BOTTLE3__STATIC: + case BOTTLE4__STATIC: + case BOTTLE5__STATIC: + case BOTTLE6__STATIC: + case BOTTLE7__STATIC: + case BOTTLE8__STATIC: + case BOTTLE10__STATIC: + case BOTTLE11__STATIC: + case BOTTLE12__STATIC: + case BOTTLE13__STATIC: + case BOTTLE14__STATIC: + case BOTTLE15__STATIC: + case BOTTLE16__STATIC: + case BOTTLE17__STATIC: + case BOTTLE18__STATIC: + case BOTTLE19__STATIC: + case SCALE__STATIC: + case VACUUM__STATIC: + case CACTUS__STATIC: + case CACTUSBROKE__STATIC: + case CAMERALIGHT__STATIC: + case MOVIECAMERA__STATIC: + case IVUNIT__STATIC: + case POT1__STATIC: + case POT2__STATIC: + case POT3__STATIC: + case SUSHIPLATE1__STATIC: + case SUSHIPLATE2__STATIC: + case SUSHIPLATE3__STATIC: + case SUSHIPLATE4__STATIC: + case SUSHIPLATE5__STATIC: + case WAITTOBESEATED__STATIC: + case VASE__STATIC: + case PIPE1__STATIC: + case PIPE2__STATIC: + case PIPE3__STATIC: + case PIPE4__STATIC: + case PIPE5__STATIC: + case PIPE6__STATIC: + case GRATE1__STATIC: + case FANSPRITE__STATIC: + pSprite->clipdist = 32; + pSprite->cstat |= 257; + fallthrough__; + case OCEANSPRITE4__STATIC: + if (RR && pSprite->picnum == OCEANSPRITE4) goto default_case; + changespritestat(newSprite, STAT_DEFAULT); + break; + case FEMMAG1__STATIC: + case FEMMAG2__STATIC: + pSprite->cstat &= ~257; + changespritestat(newSprite, STAT_DEFAULT); + break; + case DUKETAG__STATIC: + case SIGN1__STATIC: + case SIGN2__STATIC: + if (RR) goto default_case; + if ((!g_netServer && ud.multimode < 2) && pSprite->pal) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + } + else pSprite->pal = 0; + break; + + case MASKWALL1__STATIC: + case MASKWALL2__STATIC: + case MASKWALL3__STATIC: + case MASKWALL4__STATIC: + case MASKWALL5__STATIC: + case MASKWALL6__STATIC: + case MASKWALL8__STATIC: + case MASKWALL9__STATIC: + case MASKWALL10__STATIC: + case MASKWALL11__STATIC: + case MASKWALL12__STATIC: + case MASKWALL13__STATIC: + case MASKWALL14__STATIC: + case MASKWALL15__STATIC: + if (RR) goto default_case; + fallthrough__; + case MASKWALL7__STATIC: + { + int const j = pSprite->cstat & SPAWN_PROTECT_CSTAT_MASK; + pSprite->cstat = j | CSTAT_SPRITE_BLOCK; + changespritestat(newSprite, STAT_DEFAULT); + break; + } + case FOOTPRINTS__STATIC: + case FOOTPRINTS2__STATIC: + case FOOTPRINTS3__STATIC: + case FOOTPRINTS4__STATIC: + if (spriteNum >= 0) + { + int16_t footSect = pSprite->sectnum; + + updatesector(pSprite->x + 84, pSprite->y + 84, &footSect); + if (footSect >= 0 && sector[footSect].floorz == sector[pSprite->sectnum].floorz) + { + updatesector(pSprite->x - 84, pSprite->y - 84, &footSect); + if (footSect >= 0 && sector[footSect].floorz == sector[pSprite->sectnum].floorz) + { + updatesector(pSprite->x + 84, pSprite->y - 84, &footSect); + if (footSect >= 0 && sector[footSect].floorz == sector[pSprite->sectnum].floorz) + { + updatesector(pSprite->x - 84, pSprite->y + 84, &footSect); + if (footSect >= 0 && sector[footSect].floorz != sector[pSprite->sectnum].floorz) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + } + else goto zero_footprint; + } + else goto zero_footprint; + } + else + { + zero_footprint: + pSprite->xrepeat = pSprite->yrepeat = 0; + break; + } + + pSprite->cstat = 32 + ((g_player[P_Get(spriteNum)].ps->footprintcount & 1) << 2); + pSprite->ang = sprite[spriteNum].ang; + } + + pSprite->z = sector[sectNum].floorz; + + if (sector[sectNum].lotag != ST_1_ABOVE_WATER && sector[sectNum].lotag != ST_2_UNDERWATER) + pSprite->xrepeat = pSprite->yrepeat = 32; + + A_AddToDeleteQueue(newSprite); + changespritestat(newSprite, STAT_MISC); + break; + + case PODFEM1__STATIC: + if (RR) goto default_case; + pSprite->extra <<= 1; + fallthrough__; + case FEM1__STATIC: + case FEM2__STATIC: + case FEM3__STATIC: + case FEM4__STATIC: + case FEM5__STATIC: + case FEM6__STATIC: + case FEM7__STATIC: + case FEM8__STATIC: + case FEM9__STATIC: + if (RR) goto default_case; + fallthrough__; + case FEM10__STATIC: + case NAKED1__STATIC: + case STATUE__STATIC: + case TOUGHGAL__STATIC: + pSprite->yvel = pSprite->hitag; + pSprite->hitag = -1; + fallthrough__; + case BLOODYPOLE__STATIC: + if (RR && pSprite->picnum == BLOODYPOLE) goto default_case; + pSprite->cstat |= 257; + pSprite->clipdist = 32; + changespritestat(newSprite, STAT_ZOMBIEACTOR); + break; + + case QUEBALL__STATIC: + case STRIPEBALL__STATIC: + pSprite->cstat = 256; + pSprite->clipdist = 8; + changespritestat(newSprite, STAT_ZOMBIEACTOR); + break; + case BOWLINGBALL__STATICRR: + pSprite->cstat = 256; + pSprite->clipdist = 64; + pSprite->xrepeat = 11; + pSprite->yrepeat = 9; + changespritestat(newSprite, 2); + break; + case HENSTAND__STATICRR: + pSprite->cstat = 257; + pSprite->clipdist = 48; + pSprite->xrepeat = 21; + pSprite->yrepeat = 15; + changespritestat(newSprite, 2); + break; + case RRTILE295__STATICRR: + pSprite->cstat |= 32768; + changespritestat(newSprite, 107); + break; + case RRTILE296__STATICRR: + case RRTILE297__STATICRR: + pSprite->xrepeat = 64; + pSprite->yrepeat = 64; + pSprite->clipdist = 64; + changespritestat(newSprite, 108); + break; + case RRTILE3190__STATICRR: + case RRTILE3191__STATICRR: + case RRTILE3192__STATICRR: + pSprite->cstat = 257; + pSprite->clipdist = 8; + pSprite->xrepeat = 32; + pSprite->yrepeat = 26; + pSprite->xvel = 32; + changespritestat(newSprite, 1); + break; + case RRTILE3120__STATICRR: + pSprite->cstat = 257; + pSprite->clipdist = 8; + pSprite->xrepeat = 12; + pSprite->yrepeat = 10; + pSprite->xvel = 32; + changespritestat(newSprite, 1); + break; + case RRTILE3122__STATICRR: + pSprite->cstat = 257; + pSprite->clipdist = 2; + pSprite->xrepeat = 8; + pSprite->yrepeat = 6; + pSprite->xvel = 16; + changespritestat(newSprite, 1); + break; + case RRTILE3123__STATICRR: + pSprite->cstat = 257; + pSprite->clipdist = 8; + pSprite->xrepeat = 13; + pSprite->yrepeat = 13; + pSprite->xvel = 16; + changespritestat(newSprite, 1); + break; + case RRTILE3124__STATICRR: + pSprite->cstat = 257; + pSprite->clipdist = 8; + pSprite->xrepeat = 17; + pSprite->yrepeat = 12; + pSprite->xvel = 32; + changespritestat(newSprite, 1); + break; + case RRTILE3132__STATICRR: + pSprite->cstat = 257; + pSprite->clipdist = 8; + pSprite->xrepeat = 13; + pSprite->yrepeat = 10; + pSprite->xvel = 0; + changespritestat(newSprite, 1); + break; + case RRTILE3440__STATICRR: + pSprite->cstat = 257; + pSprite->clipdist = 48; + pSprite->xrepeat = 23; + pSprite->yrepeat = 23; + changespritestat(newSprite, 2); + break; + + case DUKELYINGDEAD__STATIC: + if (spriteNum >= 0 && sprite[spriteNum].picnum == APLAYER) + { + pSprite->xrepeat = sprite[spriteNum].xrepeat; + pSprite->yrepeat = sprite[spriteNum].yrepeat; + pSprite->shade = sprite[spriteNum].shade; + pSprite->pal = g_player[P_Get(spriteNum)].ps->palookup; + } + fallthrough__; + case DUKECAR__STATIC: + case HELECOPT__STATIC: + // if(sp->picnum == HELECOPT || sp->picnum == DUKECAR) sp->xvel = 1024; + if (RR && (pSprite->picnum == DUKECAR || pSprite->picnum == HELECOPT)) goto default_case; + pSprite->cstat = 0; + pSprite->extra = 1; + pSprite->xvel = 292; + pSprite->zvel = 360; + fallthrough__; + case BLIMP__STATIC: + if (RR && pSprite->picnum == BLIMP) goto default_case; + pSprite->cstat |= 257; + pSprite->clipdist = 128; + changespritestat(newSprite, STAT_ACTOR); + break; + + case RESPAWNMARKERRED__STATIC: + pSprite->xrepeat = pSprite->yrepeat = RR ? 8 : 24; + if (spriteNum >= 0) + pSprite->z = actor[spriteNum].floorz; // -(1<<4); + changespritestat(newSprite, STAT_ACTOR); + break; + + case MIKE__STATIC: + pSprite->yvel = pSprite->hitag; + pSprite->hitag = 0; + changespritestat(newSprite, STAT_ACTOR); + break; + case WEATHERWARN__STATIC: + if (RR) goto default_case; + changespritestat(newSprite, STAT_ACTOR); + break; + + case SPOTLITE__STATIC: + T1(newSprite) = pSprite->x; + T2(newSprite) = pSprite->y; + break; + case BULLETHOLE__STATIC: + pSprite->xrepeat = 3; + pSprite->yrepeat = 3; + pSprite->cstat = 16 + (krand2() & 12); + + A_AddToDeleteQueue(newSprite); + changespritestat(newSprite, STAT_MISC); + break; + + case MONEY__STATIC: + case MAIL__STATIC: + case PAPER__STATIC: + if (RR && (pSprite->picnum == MAIL || pSprite->picnum == PAPER)) goto default_case; + pActor->t_data[0] = krand2() & 2047; + + pSprite->cstat = krand2() & 12; + pSprite->xrepeat = 8; + pSprite->yrepeat = 8; + pSprite->ang = krand2() & 2047; + + changespritestat(newSprite, STAT_MISC); + break; + + case VIEWSCREEN__STATIC: + case VIEWSCREEN2__STATIC: + if (RR) goto default_case; + pSprite->owner = newSprite; + pSprite->lotag = pSprite->extra = 1; + changespritestat(newSprite, STAT_STANDABLE); + break; + + case SHELL__STATIC: //From the player + case SHOTGUNSHELL__STATIC: + if (spriteNum >= 0) + { + int shellAng; + + if (sprite[spriteNum].picnum == APLAYER) + { + int const playerNum = P_Get(spriteNum); + const DukePlayer_t *const pPlayer = g_player[playerNum].ps; + + shellAng = fix16_to_int(pPlayer->q16ang) - (krand2() & 63) + 8; // Fine tune + + T1(newSprite) = krand2() & 1; + + pSprite->z = (3 << 8) + pPlayer->pyoff + pPlayer->pos.z - (fix16_to_int((pPlayer->q16horizoff + pPlayer->q16horiz - F16(100))) << 4); + + if (pSprite->picnum == SHOTGUNSHELL) + pSprite->z += (3 << 8); + + pSprite->zvel = -(krand2() & 255); + } + else + { + shellAng = pSprite->ang; + pSprite->z = sprite[spriteNum].z - PHEIGHT + (RR ? (7 << 8) : (3 << 8)); + } + + pSprite->x = sprite[spriteNum].x + (sintable[(shellAng + 512) & 2047] >> 7); + pSprite->y = sprite[spriteNum].y + (sintable[shellAng & 2047] >> 7); + pSprite->shade = -8; + + if (pSprite->yvel == 1) + { + pSprite->ang = shellAng + 512; + pSprite->xvel = 30; + } + else + { + pSprite->ang = shellAng - 512; + pSprite->xvel = 20; + } + + if (RR && pSprite->picnum == SHELL) + pSprite->xrepeat = pSprite->yrepeat = 2; + else + pSprite->xrepeat = pSprite->yrepeat = 4; + + changespritestat(newSprite, STAT_MISC); + } + break; + case RESPAWN__STATIC: + pSprite->extra = 66-13; + fallthrough__; + case MUSICANDSFX__STATIC: + if ((!g_netServer && ud.multimode < 2) && pSprite->pal == 1) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + pSprite->cstat = 32768; + changespritestat(newSprite, STAT_FX); + break; + case SOUNDFX__STATICRR: + pSprite->cstat |= 32768; + changespritestat(newSprite, STAT_ZOMBIEACTOR); + break; + + case EXPLOSION2__STATIC: + case EXPLOSION3__STATICRR: +#ifdef POLYMER + if (pSprite->yrepeat > 32) + { + G_AddGameLight(0, newSprite, ((pSprite->yrepeat*tilesiz[pSprite->picnum].y)<<1), 32768, 255+(95<<8),PR_LIGHT_PRIO_MAX_GAME); + pActor->lightcount = 2; + } + fallthrough__; +#endif + case EXPLOSION2BOT__STATIC: + case BURNING__STATIC: + case BURNING2__STATIC: + case SMALLSMOKE__STATIC: + case SHRINKEREXPLOSION__STATIC: + case COOLEXPLOSION1__STATIC: + if (RR && (pSprite->picnum == EXPLOSION2BOT || pSprite->picnum == BURNING2 + || pSprite->picnum == SHRINKEREXPLOSION || pSprite->picnum == COOLEXPLOSION1)) goto default_case; + if (spriteNum >= 0) + { + pSprite->ang = sprite[spriteNum].ang; + pSprite->shade = -64; + pSprite->cstat = 128|(krand2()&4); + } + + if (pSprite->picnum == EXPLOSION2 || (!RR && pSprite->picnum == EXPLOSION2BOT)) + { + pSprite->xrepeat = pSprite->yrepeat = 48; + pSprite->shade = -127; + pSprite->cstat |= 128; + } + else if (RR && pSprite->picnum == EXPLOSION3) + { + pSprite->xrepeat = pSprite->yrepeat = 128; + pSprite->shade = -127; + pSprite->cstat |= 128; + } + else if (!RR && pSprite->picnum == SHRINKEREXPLOSION) + pSprite->xrepeat = pSprite->yrepeat = 32; + else if (pSprite->picnum == SMALLSMOKE) + { + // 64 "money" + pSprite->xrepeat = pSprite->yrepeat = RR ? 12 : 24; + } + else if (pSprite->picnum == BURNING || (!RR && pSprite->picnum == BURNING2)) + pSprite->xrepeat = pSprite->yrepeat = 4; + + pSprite->cstat |= 8192; + + if (spriteNum >= 0) + { + int const floorZ = getflorzofslope(pSprite->sectnum, pSprite->x, pSprite->y); + + if (pSprite->z > floorZ-ZOFFSET4) + pSprite->z = floorZ-ZOFFSET4; + } + + changespritestat(newSprite, STAT_MISC); + + break; + + case PLAYERONWATER__STATIC: + if (spriteNum >= 0) + { + pSprite->xrepeat = sprite[spriteNum].xrepeat; + pSprite->yrepeat = sprite[spriteNum].yrepeat; + pSprite->zvel = 128; + if (sector[pSprite->sectnum].lotag != ST_2_UNDERWATER) + pSprite->cstat |= 32768; + } + changespritestat(newSprite, STAT_DUMMYPLAYER); + break; + + case APLAYER__STATIC: + pSprite->xrepeat = 0; + pSprite->yrepeat = 0; + pSprite->cstat = 32768; + + changespritestat(newSprite, ((!g_netServer && ud.multimode < 2) + || ((g_gametypeFlags[ud.coop] & GAMETYPE_COOPSPAWN) / GAMETYPE_COOPSPAWN) != pSprite->lotag) + ? STAT_MISC + : STAT_PLAYER); + break; + + case WATERBUBBLE__STATIC: + if (spriteNum >= 0) + { + if (sprite[spriteNum].picnum == APLAYER) + pSprite->z -= (16 << 8); + + pSprite->ang = sprite[spriteNum].ang; + } + + pSprite->xrepeat = pSprite->yrepeat = RR ? (1+(krand2()&7)) : 4; + changespritestat(newSprite, STAT_MISC); + break; + + case CRANE__STATIC: + + pSprite->cstat |= 64|257; + + pSprite->picnum += 2; + pSprite->z = sector[sectNum].ceilingz+(48<<8); + T5(newSprite) = tempwallptr; + + g_origins[tempwallptr] = *(vec2_t *) pSprite; + g_origins[tempwallptr+2].x = pSprite->z; + + + if (headspritestat[STAT_DEFAULT] != -1) + { + int findSprite = headspritestat[STAT_DEFAULT]; + + do + { + if (sprite[findSprite].picnum == CRANEPOLE && pSprite->hitag == (sprite[findSprite].hitag)) + { + g_origins[tempwallptr + 2].y = findSprite; + + T2(newSprite) = sprite[findSprite].sectnum; + + sprite[findSprite].xrepeat = 48; + sprite[findSprite].yrepeat = 128; + + g_origins[tempwallptr + 1] = *(vec2_t *) &sprite[findSprite]; + *(vec3_t *) &sprite[findSprite] = *(vec3_t *) pSprite; + sprite[findSprite].shade = pSprite->shade; + + setsprite(findSprite, (vec3_t *) &sprite[findSprite]); + break; + } + findSprite = nextspritestat[findSprite]; + } while (findSprite >= 0); + } + + tempwallptr += 3; + pSprite->owner = -1; + pSprite->extra = 8; + changespritestat(newSprite, STAT_STANDABLE); + break; + + case TRASH__STATIC: + pSprite->ang = krand2()&2047; + pSprite->xrepeat = pSprite->yrepeat = 24; + changespritestat(newSprite, STAT_STANDABLE); + break; + + case WATERDRIP__STATIC: + if (spriteNum >= 0 && (sprite[spriteNum].statnum == STAT_PLAYER || sprite[spriteNum].statnum == STAT_ACTOR)) + { + if (sprite[spriteNum].pal != 1) + { + pSprite->pal = 2; + pSprite->z -= (18<<8); + } + else pSprite->z -= (13<<8); + + pSprite->shade = 32; + pSprite->ang = getangle(g_player[0].ps->pos.x - pSprite->x, g_player[0].ps->pos.y - pSprite->y); + pSprite->xvel = 48 - (krand2() & 31); + + A_SetSprite(newSprite, CLIPMASK0); + } + else if (spriteNum == -1) + { + pSprite->z += ZOFFSET6; + T1(newSprite) = pSprite->z; + if (!RR) + T2(newSprite) = krand2()&127; + } + fallthrough__; + case WATERDRIPSPLASH__STATIC: + if (RR && pSprite->picnum == WATERDRIPSPLASH) goto default_case; + pSprite->xrepeat = pSprite->yrepeat = 24; + changespritestat(newSprite, STAT_STANDABLE); + break; + + case PLUG__STATIC: + pSprite->lotag = 9999; + changespritestat(newSprite, STAT_STANDABLE); + break; + case TOUCHPLATE__STATIC: + T3(newSprite) = sector[sectNum].floorz; + + if (sector[sectNum].lotag != ST_1_ABOVE_WATER && sector[sectNum].lotag != ST_2_UNDERWATER) + sector[sectNum].floorz = pSprite->z; + + if (pSprite->pal && (g_netServer || ud.multimode > 1)) + { + pSprite->xrepeat=pSprite->yrepeat=0; + changespritestat(newSprite, STAT_MISC); + break; + } + fallthrough__; + case WATERBUBBLEMAKER__STATIC: + if (EDUKE32_PREDICT_FALSE(pSprite->hitag && pSprite->picnum == WATERBUBBLEMAKER)) + { + // JBF 20030913: Pisses off X_Move(), eg. in bobsp2 + OSD_Printf(OSD_ERROR "WARNING: WATERBUBBLEMAKER %d @ %d,%d with hitag!=0. Applying fixup.\n", + newSprite,TrackerCast(pSprite->x),TrackerCast(pSprite->y)); + pSprite->hitag = 0; + } + pSprite->cstat |= 32768; + changespritestat(newSprite, STAT_STANDABLE); + break; + + case BOLT1__STATIC: + case SIDEBOLT1__STATIC: + if (RR && pSprite->picnum >= SIDEBOLT1 && pSprite->picnum <= SIDEBOLT1+3) goto default_case; + T1(newSprite) = pSprite->xrepeat; + T2(newSprite) = pSprite->yrepeat; + pSprite->yvel = 0; + + changespritestat(newSprite, STAT_STANDABLE); + break; + + case MASTERSWITCH__STATIC: + if (pSprite->picnum == MASTERSWITCH) + pSprite->cstat |= 32768; + pSprite->yvel = 0; + changespritestat(newSprite, STAT_STANDABLE); + break; + + case TARGET__STATIC: + case DUCK__STATIC: + case LETTER__STATIC: + if (RR) goto default_case; + pSprite->extra = 1; + pSprite->cstat |= 257; + changespritestat(newSprite, STAT_ACTOR); + break; + + case OCTABRAINSTAYPUT__STATIC: + case LIZTROOPSTAYPUT__STATIC: + case PIGCOPSTAYPUT__STATIC: + case LIZMANSTAYPUT__STATIC: + case BOSS1STAYPUT__STATIC: + case PIGCOPDIVE__STATIC: + case COMMANDERSTAYPUT__STATIC: + case BOSS4STAYPUT__STATIC: + if (RR) goto default_case; + pActor->actorstayput = pSprite->sectnum; + fallthrough__; + case BOSS1__STATIC: + case BOSS2__STATIC: + case BOSS3__STATIC: + case BOSS4__STATIC: + case ROTATEGUN__STATIC: + case GREENSLIME__STATIC: + if (RR) goto default_case; + if (pSprite->picnum == GREENSLIME) + pSprite->extra = 1; + fallthrough__; + case DRONE__STATIC: + case LIZTROOPONTOILET__STATIC: + case LIZTROOPJUSTSIT__STATIC: + case LIZTROOPSHOOT__STATIC: + case LIZTROOPJETPACK__STATIC: + case LIZTROOPDUCKING__STATIC: + case LIZTROOPRUNNING__STATIC: + case LIZTROOP__STATIC: + case OCTABRAIN__STATIC: + case COMMANDER__STATIC: + case PIGCOP__STATIC: + case LIZMAN__STATIC: + case LIZMANSPITTING__STATIC: + case LIZMANFEEDING__STATIC: + case LIZMANJUMP__STATIC: + case ORGANTIC__STATIC: + case RAT__STATIC: + case SHARK__STATIC: + if (RR) + { + if (pSprite->picnum == RAT || pSprite->picnum == SHARK || pSprite->picnum == DRONE) + goto rr_badguy; + goto default_case; + } + + if (pSprite->pal == 0) + { + switch (DYNAMICTILEMAP(pSprite->picnum)) + { + case LIZTROOPONTOILET__STATIC: + case LIZTROOPSHOOT__STATIC: + case LIZTROOPJETPACK__STATIC: + case LIZTROOPDUCKING__STATIC: + case LIZTROOPRUNNING__STATIC: + case LIZTROOPSTAYPUT__STATIC: + case LIZTROOPJUSTSIT__STATIC: + case LIZTROOP__STATIC: pSprite->pal = 22; break; + } + } + else + { + if (!PLUTOPAK) + pSprite->extra <<= 1; + } + + if (pSprite->picnum == BOSS4STAYPUT || pSprite->picnum == BOSS1 || pSprite->picnum == BOSS2 || + pSprite->picnum == BOSS1STAYPUT || pSprite->picnum == BOSS3 || pSprite->picnum == BOSS4) + { + if (spriteNum >= 0 && sprite[spriteNum].picnum == RESPAWN) + pSprite->pal = sprite[spriteNum].pal; + + if (pSprite->pal) + { + pSprite->clipdist = 80; + pSprite->xrepeat = pSprite->yrepeat = 40; + } + else + { + pSprite->xrepeat = pSprite->yrepeat = 80; + pSprite->clipdist = 164; + } + } + else + { + if (pSprite->picnum != SHARK) + { + pSprite->xrepeat = pSprite->yrepeat = 40; + pSprite->clipdist = 80; + } + else + { + pSprite->xrepeat = pSprite->yrepeat = 60; + pSprite->clipdist = 40; + } + } + + // If spawned from parent sprite (as opposed to 'from premap'), + // ignore skill. + if (spriteNum >= 0) + pSprite->lotag = 0; + + if ((pSprite->lotag > ud.player_skill) || ud.monsters_off == 1) + { + pSprite->xrepeat=pSprite->yrepeat=0; + changespritestat(newSprite, STAT_MISC); + break; + } + else + { + A_Fall(newSprite); + + if (pSprite->picnum == RAT) + { + pSprite->ang = krand2()&2047; + pSprite->xrepeat = pSprite->yrepeat = 48; + pSprite->cstat = 0; + } + else + { + pSprite->cstat |= 257; + + if (pSprite->picnum != SHARK) + g_player[myconnectindex].ps->max_actors_killed++; + } + + if (pSprite->picnum == ORGANTIC) pSprite->cstat |= 128; + + if (spriteNum >= 0) + { + pActor->timetosleep = 0; + A_PlayAlertSound(newSprite); + changespritestat(newSprite, STAT_ACTOR); + } + else changespritestat(newSprite, STAT_ZOMBIEACTOR); + } + + if (pSprite->picnum == ROTATEGUN) + pSprite->zvel = 0; + + break; + case BIKERB__STATICRR: + case BIKERBV2__STATICRR: + case BIKER__STATICRR: + case MAKEOUT__STATICRR: + case CHEERB__STATICRR: + case CHEER__STATICRR: + case COOTPLAY__STATICRR: + case BILLYPLAY__STATICRR: + case MINIONBOAT__STATICRR: + case HULKBOAT__STATICRR: + case CHEERBOAT__STATICRR: + case RABBIT__STATICRR: + case ROCK__STATICRR: + case ROCK2__STATICRR: + case MAMACLOUD__STATICRR: + case MAMA__STATICRR: + if (!RRRA) goto default_case; + goto rr_badguy; + case BILLYRAYSTAYPUT__STATICRR: + case BRAYSNIPER__STATICRR: + case BUBBASTAND__STATICRR: + case HULKSTAYPUT__STATICRR: + case HENSTAYPUT__STATICRR: + case PIGSTAYPUT__STATICRR: + case MINIONSTAYPUT__STATICRR: + case COOTSTAYPUT__STATICRR: + case SBSWIPE__STATICRR: + case CHEERSTAYPUT__STATICRR: + case SBMOVE__STATICRR: + if ((RRRA && pSprite->picnum == SBMOVE) || (!RRRA && (pSprite->picnum == SBSWIPE || pSprite->picnum == CHEERSTAYPUT))) goto default_case; + pActor->actorstayput = pSprite->sectnum; + fallthrough__; + case BOULDER__STATICRR: + case BOULDER1__STATICRR: + //case RAT__STATIC: + case TORNADO__STATICRR: + case BILLYCOCK__STATICRR: + case BILLYRAY__STATICRR: + case DOGRUN__STATICRR: + case LTH__STATICRR: + case HULK__STATICRR: + case HEN__STATICRR: + //case DRONE__STATIC: + case PIG__STATICRR: + case MINION__STATICRR: + case UFO1__STATICRR: + case UFO2__STATICRR: + case UFO3__STATICRR: + case UFO4__STATICRR: + case UFO5__STATICRR: + case COW__STATICRR: + case COOT__STATICRR: + //case SHARK__STATIC: + case VIXEN__STATICRR: +rr_badguy: + pSprite->xrepeat = 40; + pSprite->yrepeat = 40; + switch (DYNAMICTILEMAP(pSprite->picnum)) + { + case VIXEN__STATICRR: + if (pSprite->pal == 34) + { + pSprite->xrepeat = 22; + pSprite->yrepeat = 21; + } + else + { + pSprite->xrepeat = 22; + pSprite->yrepeat = 20; + } + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case HULKHANG__STATICRR: + case HULKHANGDEAD__STATICRR: + case HULKJUMP__STATICRR: + case HULK__STATICRR: + case HULKSTAYPUT__STATICRR: + pSprite->xrepeat = 32; + pSprite->yrepeat = 32; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case COOTPLAY__STATICRR: + if (!RRRA) break; + fallthrough__; + case COOT__STATICRR: + case COOTSTAYPUT__STATICRR: + pSprite->xrepeat = 24; + pSprite->yrepeat = 18; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + pSprite->clipdist <<= 2; + break; + case DRONE__STATIC: + pSprite->xrepeat = 14; + pSprite->yrepeat = 7; + pSprite->clipdist = 128; + break; + case SBSWIPE__STATICRR: + case BILLYPLAY__STATICRR: + if (!RRRA) break; + fallthrough__; + case BILLYCOCK__STATICRR: + case BILLYRAY__STATICRR: + case BILLYRAYSTAYPUT__STATICRR: + case BRAYSNIPER__STATICRR: + case BUBBASTAND__STATICRR: + pSprite->xrepeat = 25; + pSprite->yrepeat = 21; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case COW__STATICRR: + pSprite->xrepeat = 32; + pSprite->yrepeat = 32; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case HEN__STATICRR: + case HENSTAYPUT__STATICRR: + case HENSTAND__STATICRR: + if (pSprite->pal == 35) + { + pSprite->xrepeat = 42; + pSprite->yrepeat = 30; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + } + else + { + pSprite->xrepeat = 21; + pSprite->yrepeat = 15; + pSprite->clipdist = 64; + } + break; + case MINION__STATICRR: + case MINIONSTAYPUT__STATICRR: + pSprite->xrepeat = 16; + pSprite->yrepeat = 16; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + if (RRRA && g_ufoSpawnMinion) + pSprite->pal = 8; + break; + case DOGRUN__STATICRR: + case PIG__STATICRR: + pSprite->xrepeat = 16; + pSprite->yrepeat = 16; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case RABBIT__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 18; + pSprite->yrepeat = 18; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case MAMACLOUD__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 64; + pSprite->yrepeat = 64; + pSprite->cstat = 2; + pSprite->cstat |= 512; + pSprite->x += (krand2() & 2047) - 1024; + pSprite->y += (krand2() & 2047) - 1024; + pSprite->z += (krand2() & 2047) - 1024; + break; + case MAMA__STATICRR: + if (!RRRA) break; + if (pSprite->pal == 30) + { + pSprite->xrepeat = 26; + pSprite->yrepeat = 26; + pSprite->clipdist = 75; + } + else if (pSprite->pal == 31) + { + pSprite->xrepeat = 36; + pSprite->yrepeat = 36; + pSprite->clipdist = 100; + } + else if (pSprite->pal == 32) + { + pSprite->xrepeat = 50; + pSprite->yrepeat = 50; + pSprite->clipdist = 100; + } + else + { + pSprite->xrepeat = 50; + pSprite->yrepeat = 50; + pSprite->clipdist = 100; + } + break; + case BIKERB__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 28; + pSprite->yrepeat = 22; + pSprite->clipdist = 72; + break; + case BIKERBV2__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 28; + pSprite->yrepeat = 22; + pSprite->clipdist = 72; + break; + case BIKER__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 28; + pSprite->yrepeat = 22; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case CHEERB__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 28; + pSprite->yrepeat = 22; + pSprite->clipdist = 72; + break; + case CHEER__STATICRR: + case CHEERSTAYPUT__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 20; + pSprite->yrepeat = 20; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case MAKEOUT__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 26; + pSprite->yrepeat = 26; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case MINIONBOAT__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 16; + pSprite->yrepeat = 16; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case HULKBOAT__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 48; + pSprite->yrepeat = 48; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case CHEERBOAT__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 32; + pSprite->yrepeat = 32; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case TORNADO__STATICRR: + pSprite->xrepeat = 64; + pSprite->yrepeat = 128; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + pSprite->clipdist >>= 2; + pSprite->cstat = 2; + break; + case LTH__STATICRR: + pSprite->xrepeat = 24; + pSprite->yrepeat = 22; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case ROCK__STATICRR: + case ROCK2__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 64; + pSprite->yrepeat = 64; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + case UFO1__STATICRR: + case UFO2__STATICRR: + case UFO3__STATICRR: + case UFO4__STATICRR: + case UFO5__STATICRR: + pSprite->xrepeat = 32; + pSprite->yrepeat = 32; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + pSprite->extra = 50; + break; + case SBMOVE__STATICRR: + if (RRRA) break; + pSprite->xrepeat = 48; + pSprite->yrepeat = 48; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + break; + } + + if (spriteNum >= 0) pSprite->lotag = 0; + + if ((pSprite->lotag > ud.player_skill) || ud.monsters_off == 1) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + else + { + A_Fall(newSprite); + + if (pSprite->picnum == RAT) + { + pSprite->ang = krand2() & 2047; + pSprite->xrepeat = pSprite->yrepeat = 48; + pSprite->cstat = 0; + } + else + { + pSprite->cstat |= 257; + + if (pSprite->picnum != SHARK) + if (A_CheckSpriteFlags(newSprite, SFLAG_KILLCOUNT)) + g_player[myconnectindex].ps->max_actors_killed++; + } + + if (spriteNum >= 0) + { + pActor->timetosleep = 0; + A_PlayAlertSound(newSprite); + changespritestat(newSprite, STAT_ACTOR); + } + else changespritestat(newSprite, STAT_ZOMBIEACTOR); + + pSprite->shade = sprite[spriteNum].shade; + } + + break; + case LOCATORS__STATIC: + pSprite->cstat |= 32768; + changespritestat(newSprite, STAT_LOCATOR); + break; + + case ACTIVATORLOCKED__STATIC: + case ACTIVATOR__STATIC: + if (RR) + { + pSprite->cstat |= 32768; + if (pSprite->picnum == ACTIVATORLOCKED) + sector[pSprite->sectnum].lotag ^= 16384; + } + else + { + pSprite->cstat = 32768; + if (pSprite->picnum == ACTIVATORLOCKED) + sector[pSprite->sectnum].lotag |= 16384; + } + changespritestat(newSprite, STAT_ACTIVATOR); + break; + + case DOORSHOCK__STATIC: + pSprite->cstat |= 1+256; + pSprite->shade = -12; + changespritestat(newSprite, STAT_STANDABLE); + break; + + case OOZ__STATIC: + case OOZ2__STATIC: + { + if (RR && pSprite->picnum == OOZ2) goto default_case; + pSprite->shade = -12; + + if (spriteNum >= 0) + { + if (sprite[spriteNum].picnum == NUKEBARREL) + pSprite->pal = 8; + if (!RR) + A_AddToDeleteQueue(newSprite); + } + + changespritestat(newSprite, STAT_ACTOR); + + A_GetZLimits(newSprite); + + int const oozSize = (pActor->floorz-pActor->ceilingz)>>9; + + pSprite->yrepeat = oozSize; + pSprite->xrepeat = 25 - (oozSize >> 1); + pSprite->cstat |= (krand2() & 4); + + break; + } + + case REACTOR2__STATIC: + case REACTOR__STATIC: + pSprite->extra = g_impactDamage; + pSprite->cstat |= 257; + if ((!g_netServer && ud.multimode < 2) && pSprite->pal != 0) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + + pSprite->pal = 0; + pSprite->shade = -17; + + changespritestat(newSprite, STAT_ZOMBIEACTOR); + break; + + case HEAVYHBOMB__STATIC: + if (!RR && spriteNum >= 0) + pSprite->owner = spriteNum; + else pSprite->owner = newSprite; + + pSprite->xrepeat = pSprite->yrepeat = 9; + pSprite->yvel = 4; + pSprite->cstat |= 257; + + if ((!g_netServer && ud.multimode < 2) && pSprite->pal != 0) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + pSprite->pal = 0; + pSprite->shade = -17; + + changespritestat(newSprite, STAT_ZOMBIEACTOR); + break; + + case RECON__STATIC: + if (pSprite->lotag > ud.player_skill) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + goto SPAWN_END; + } + if (!RR || A_CheckSpriteFlags(newSprite, SFLAG_KILLCOUNT)) + g_player[myconnectindex].ps->max_actors_killed++; + pActor->t_data[5] = 0; + if (ud.monsters_off == 1) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + pSprite->extra = 130; + pSprite->cstat |= 257; // Make it hitable + + if ((!g_netServer && ud.multimode < 2) && pSprite->pal != 0) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + pSprite->pal = 0; + pSprite->shade = -17; + + changespritestat(newSprite, STAT_ZOMBIEACTOR); + break; + + case ATOMICHEALTH__STATIC: + case STEROIDS__STATIC: + case HEATSENSOR__STATIC: + case SHIELD__STATIC: + case AIRTANK__STATIC: + case TRIPBOMBSPRITE__STATIC: + case JETPACK__STATIC: + case HOLODUKE__STATIC: + + case FIRSTGUNSPRITE__STATIC: + case CHAINGUNSPRITE__STATIC: + case SHOTGUNSPRITE__STATIC: + case RPGSPRITE__STATIC: + case SHRINKERSPRITE__STATIC: + case FREEZESPRITE__STATIC: + case DEVISTATORSPRITE__STATIC: + + case SHOTGUNAMMO__STATIC: + case FREEZEAMMO__STATIC: + case HBOMBAMMO__STATIC: + case CRYSTALAMMO__STATIC: + case GROWAMMO__STATIC: + case BATTERYAMMO__STATIC: + case DEVISTATORAMMO__STATIC: + case RPGAMMO__STATIC: + case BOOTS__STATIC: + case AMMO__STATIC: + case AMMOLOTS__STATIC: + case COLA__STATIC: + case FIRSTAID__STATIC: + case SIXPAK__STATIC: + case RRTILE43__STATICRR: + case BOWLINGBALLSPRITE__STATICRR: + case RPG2SPRITE__STATICRR: + case MOTOAMMO__STATICRR: + case BOATAMMO__STATICRR: + + if (RR && !RRRA && (pSprite->picnum == RPG2SPRITE || pSprite->picnum == MOTOAMMO || pSprite->picnum == BOATAMMO)) goto default_case; + + if (spriteNum >= 0) + { + pSprite->lotag = 0; + if (RR && pSprite->picnum == BOWLINGBALLSPRITE) + pSprite->zvel = 0; + else + { + pSprite->z -= ZOFFSET5; + pSprite->zvel = -1024; + } + A_SetSprite(newSprite, CLIPMASK0); + pSprite->cstat = krand2()&4; + } + else + { + pSprite->owner = newSprite; + pSprite->cstat = 0; + } + + if (((!g_netServer && ud.multimode < 2) && pSprite->pal != 0) || (pSprite->lotag > ud.player_skill)) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + + pSprite->pal = 0; + + if (pSprite->picnum == ATOMICHEALTH) + pSprite->cstat |= 128; + + fallthrough__; + case ACCESSCARD__STATIC: + if ((g_netServer || ud.multimode > 1) && !GTFLAGS(GAMETYPE_ACCESSCARDSPRITES) && pSprite->picnum == ACCESSCARD) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + else + { + if (pSprite->picnum == AMMO) + pSprite->xrepeat = pSprite->yrepeat = 16; + else pSprite->xrepeat = pSprite->yrepeat = 32; + } + + pSprite->shade = -17; + + if (spriteNum >= 0) + { + changespritestat(newSprite, STAT_ACTOR); + } + else + { + changespritestat(newSprite, STAT_ZOMBIEACTOR); + A_Fall(newSprite); + } + if (RR) + { + switch (DYNAMICTILEMAP(pSprite->picnum)) + { + case FIRSTGUNSPRITE__STATIC: + pSprite->xrepeat = 16; + pSprite->yrepeat = 16; + break; + case SHOTGUNAMMO__STATIC: + pSprite->xrepeat = 18; + pSprite->yrepeat = 17; + if (RRRA) + pSprite->cstat = 256; + break; + case SIXPAK__STATIC: + pSprite->xrepeat = 13; + pSprite->yrepeat = 9; + if (RRRA) + pSprite->cstat = 256; + break; + case FIRSTAID__STATIC: + pSprite->xrepeat = 8; + pSprite->yrepeat = 8; + break; + case COLA__STATIC: + pSprite->xrepeat = 5; + pSprite->yrepeat = 4; + break; + case AMMO__STATIC: + pSprite->xrepeat = 9; + pSprite->yrepeat = 9; + break; + case MOTOAMMO__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 23; + pSprite->yrepeat = 23; + break; + case BOATAMMO__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 16; + pSprite->yrepeat = 16; + break; + case JETPACK__STATIC: + pSprite->xrepeat = 8; + pSprite->yrepeat = 6; + break; + case STEROIDS__STATIC: + pSprite->xrepeat = 13; + pSprite->yrepeat = 9; + break; + case ACCESSCARD__STATIC: + pSprite->xrepeat = 11; + pSprite->yrepeat = 12; + break; + case HEATSENSOR__STATIC: + pSprite->xrepeat = 6; + pSprite->yrepeat = 4; + break; + case AIRTANK__STATIC: + pSprite->xrepeat = 19; + pSprite->yrepeat = 16; + break; + case BATTERYAMMO__STATIC: + pSprite->xrepeat = 15; + pSprite->yrepeat = 15; + break; + case BOWLINGBALLSPRITE__STATICRR: + pSprite->xrepeat = 11; + pSprite->yrepeat = 11; + break; + case TRIPBOMBSPRITE__STATIC: + pSprite->xrepeat = 11; + pSprite->yrepeat = 11; + pSprite->yvel = 4; + pSprite->xvel = 32; + break; + case RPGSPRITE__STATIC: + pSprite->xrepeat = 16; + pSprite->yrepeat = 14; + break; + case RPG2SPRITE__STATICRR: + if (!RRRA) break; + pSprite->xrepeat = 20; + pSprite->yrepeat = 20; + break; + case SHRINKERSPRITE__STATIC: + pSprite->xrepeat = 22; + pSprite->yrepeat = 13; + break; + case DEVISTATORSPRITE__STATIC: + pSprite->xrepeat = 18; + pSprite->yrepeat = 17; + break; + case RRTILE43__STATICRR: + pSprite->xrepeat = 12; + pSprite->yrepeat = 7; + break; + case GROWSPRITEICON__STATIC: + pSprite->xrepeat = 10; + pSprite->yrepeat = 9; + break; + case DEVISTATORAMMO__STATIC: + pSprite->xrepeat = 10; + pSprite->yrepeat = 9; + break; + case ATOMICHEALTH__STATIC: + pSprite->xrepeat = 8; + pSprite->yrepeat = 8; + break; + case FREEZESPRITE__STATIC: + pSprite->xrepeat = 17; + pSprite->yrepeat = 16; + break; + } + pSprite->shade = sector[pSprite->sectnum].floorshade; + } + break; + + case WATERFOUNTAIN__STATIC: + SLT(newSprite) = 1; + fallthrough__; + case TREE1__STATIC: + case TREE2__STATIC: + case TIRE__STATIC: + case CONE__STATIC: + case BOX__STATIC: + if (RR && (pSprite->picnum == CONE || pSprite->picnum == BOX)) goto default_case; + pSprite->cstat = 257; // Make it hitable + sprite[newSprite].extra = 1; + changespritestat(newSprite, STAT_STANDABLE); + break; + + case FLOORFLAME__STATIC: + if (RR) goto default_case; + pSprite->shade = -127; + changespritestat(newSprite, STAT_STANDABLE); + break; + + case BOUNCEMINE__STATIC: + if (RR) goto default_case; + pSprite->owner = newSprite; + pSprite->cstat |= 1+256; //Make it hitable + pSprite->xrepeat = pSprite->yrepeat = 24; + pSprite->shade = -127; + pSprite->extra = g_impactDamage<<2; + changespritestat(newSprite, STAT_ZOMBIEACTOR); + break; + case CAMERA1__STATIC: + pSprite->extra = 1; + pSprite->cstat &= 32768; + + if (g_damageCameras) + pSprite->cstat |= 257; + + if ((!g_netServer && ud.multimode < 2) && pSprite->pal != 0) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + } + else + { + pSprite->pal = 0; + changespritestat(newSprite, STAT_ACTOR); + } + break; + case CAMERAPOLE__STATIC: + pSprite->extra = 1; + pSprite->cstat &= 32768; + + if (g_damageCameras) + pSprite->cstat |= 257; + fallthrough__; + case GENERICPOLE__STATIC: + if (RR && pSprite->picnum == GENERICPOLE) goto default_case; + if ((!g_netServer && ud.multimode < 2) && pSprite->pal != 0) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + } + else + pSprite->pal = 0; + break; + + case STEAM__STATIC: + if (spriteNum >= 0) + { + pSprite->ang = sprite[spriteNum].ang; + pSprite->cstat = 16+128+2; + pSprite->xrepeat=pSprite->yrepeat=1; + pSprite->xvel = -8; + A_SetSprite(newSprite, CLIPMASK0); + } + fallthrough__; + case CEILINGSTEAM__STATIC: + changespritestat(newSprite, STAT_STANDABLE); + break; + + case SECTOREFFECTOR__STATIC: + pSprite->cstat |= 32768; + pSprite->xrepeat = pSprite->yrepeat = 0; + + switch (pSprite->lotag) + { +#ifdef LEGACY_ROR + case 40: + case 41: + if (RR) break; + pSprite->cstat = 32; + pSprite->xrepeat = pSprite->yrepeat = 64; + changespritestat(newSprite, STAT_EFFECTOR); + for (spriteNum=0; spriteNum < MAXSPRITES; spriteNum++) + if (sprite[spriteNum].picnum == SECTOREFFECTOR && (sprite[spriteNum].lotag == 40 || sprite[spriteNum].lotag == 41) && + sprite[spriteNum].hitag == pSprite->hitag && newSprite != spriteNum) + { +// initprintf("found ror match\n"); + pSprite->yvel = spriteNum; + break; + } + goto SPAWN_END; + break; + case 46: + if (RR) break; + ror_protectedsectors[pSprite->sectnum] = 1; + /* XXX: fall-through intended? */ + fallthrough__; +#endif + case SE_49_POINT_LIGHT: + case SE_50_SPOT_LIGHT: + { + int32_t j, nextj; + + for (TRAVERSE_SPRITE_SECT(headspritesect[pSprite->sectnum], j, nextj)) + if (sprite[j].picnum == ACTIVATOR || sprite[j].picnum == ACTIVATORLOCKED) + pActor->flags |= SFLAG_USEACTIVATOR; + } + changespritestat(newSprite, pSprite->lotag==46 ? STAT_EFFECTOR : STAT_LIGHT); + goto SPAWN_END; + break; + } + + pSprite->yvel = sector[sectNum].extra; + + switch (pSprite->lotag) + { + case SE_28_LIGHTNING: + if (!RR) + T6(newSprite) = 65;// Delay for lightning + break; + case SE_7_TELEPORT: // Transporters!!!! + case SE_23_ONE_WAY_TELEPORT:// XPTR END + if (pSprite->lotag != SE_23_ONE_WAY_TELEPORT) + { + for (spriteNum=0; spriteNum cstat = 0; + changespritestat(newSprite, STAT_TRANSPORT); + goto SPAWN_END; + case SE_1_PIVOT: + pSprite->owner = -1; + T1(newSprite) = 1; + break; + case SE_18_INCREMENTAL_SECTOR_RISE_FALL: + + if (pSprite->ang == 512) + { + T2(newSprite) = sector[sectNum].ceilingz; + if (pSprite->pal) + sector[sectNum].ceilingz = pSprite->z; + } + else + { + T2(newSprite) = sector[sectNum].floorz; + if (pSprite->pal) + sector[sectNum].floorz = pSprite->z; + } + + pSprite->hitag <<= 2; + break; + + case SE_19_EXPLOSION_LOWERS_CEILING: + pSprite->owner = -1; + break; + case SE_25_PISTON: // Pistons + if (RR) + T5(newSprite) = sector[sectNum].ceilingz; + else + { + T4(newSprite) = sector[sectNum].ceilingz; + T5(newSprite) = 1; + } + sector[sectNum].ceilingz = pSprite->z; + G_SetInterpolation(§or[sectNum].ceilingz); + break; + case SE_35: + sector[sectNum].ceilingz = pSprite->z; + break; + case SE_27_DEMO_CAM: + if (ud.recstat == 1) + { + pSprite->xrepeat=pSprite->yrepeat=64; + pSprite->cstat &= 32768; + } + break; + case 47: + case 48: + if (!RRRA) break; + fallthrough__; + case SE_12_LIGHT_SWITCH: + + T2(newSprite) = sector[sectNum].floorshade; + T3(newSprite) = sector[sectNum].ceilingshade; + break; + + case SE_13_EXPLOSIVE: + + T1(newSprite) = sector[sectNum].ceilingz; + T2(newSprite) = sector[sectNum].floorz; + + if (klabs(T1(newSprite)-pSprite->z) < klabs(T2(newSprite)-pSprite->z)) + pSprite->owner = 1; + else pSprite->owner = 0; + + if (pSprite->ang == 512) + { + if (pSprite->owner) + sector[sectNum].ceilingz = pSprite->z; + else + sector[sectNum].floorz = pSprite->z; +#ifdef YAX_ENABLE + { + int16_t cf=!pSprite->owner, bn=yax_getbunch(sectNum, cf); + int32_t jj, daz=SECTORFLD(sectNum,z, cf); + + if (bn >= 0) + { + for (SECTORS_OF_BUNCH(bn, cf, jj)) + { + SECTORFLD(jj,z, cf) = daz; + SECTORFLD(jj,stat, cf) &= ~256; + SECTORFLD(jj,stat, cf) |= 128 + 512+2048; + } + for (SECTORS_OF_BUNCH(bn, !cf, jj)) + { + SECTORFLD(jj,z, !cf) = daz; + SECTORFLD(jj,stat, !cf) &= ~256; + SECTORFLD(jj,stat, !cf) |= 128 + 512+2048; + } + } + } +#endif + } + else + sector[sectNum].ceilingz = sector[sectNum].floorz = pSprite->z; + + if (sector[sectNum].ceilingstat&1) + { + sector[sectNum].ceilingstat ^= 1; + T4(newSprite) = 1; + + if (!pSprite->owner && pSprite->ang==512) + { + sector[sectNum].ceilingstat ^= 1; + T4(newSprite) = 0; + } + + sector[sectNum].ceilingshade = + sector[sectNum].floorshade; + + if (pSprite->ang==512) + { + int const startwall = sector[sectNum].wallptr; + int const endwall = startwall + sector[sectNum].wallnum; + for (bssize_t j = startwall; j < endwall; j++) + { + int const nextSect = wall[j].nextsector; + + if (nextSect >= 0) + { + if (!(sector[nextSect].ceilingstat & 1)) + { + sector[sectNum].ceilingpicnum = sector[nextSect].ceilingpicnum; + sector[sectNum].ceilingshade = sector[nextSect].ceilingshade; + break; // Leave earily + } + } + } + } + } + + break; + + case SE_17_WARP_ELEVATOR: + { + T3(newSprite) = sector[sectNum].floorz; // Stopping loc + + int nextSectNum = nextsectorneighborz(sectNum, sector[sectNum].floorz, -1, -1); + + if (EDUKE32_PREDICT_TRUE(nextSectNum >= 0)) + T4(newSprite) = sector[nextSectNum].ceilingz; + else + { + // use elevator sector's ceiling as heuristic + T4(newSprite) = sector[sectNum].ceilingz; + + OSD_Printf(OSD_ERROR "WARNING: SE17 sprite %d using own sector's ceilingz to " + "determine when to warp. Sector %d adjacent to a door?\n", + newSprite, sectNum); + } + + nextSectNum = nextsectorneighborz(sectNum, sector[sectNum].ceilingz, 1, 1); + + if (EDUKE32_PREDICT_TRUE(nextSectNum >= 0)) + T5(newSprite) = sector[nextSectNum].floorz; + else + { + // heuristic + T5(newSprite) = sector[sectNum].floorz; + + OSD_Printf(OSD_ERROR "WARNING: SE17 sprite %d using own sector %d's floorz.\n", + newSprite, sectNum); + } + + if (numplayers < 2 && !g_netServer) + { + G_SetInterpolation(§or[sectNum].floorz); + G_SetInterpolation(§or[sectNum].ceilingz); + } + } + break; + + case SE_24_CONVEYOR: + pSprite->yvel <<= 1; + case SE_36_PROJ_SHOOTER: + break; + + case SE_20_STRETCH_BRIDGE: + { + int closestDist = INT32_MAX; + int closestWall = 0; + int const startWall = sector[sectNum].wallptr; + int const endWall = startWall + sector[sectNum].wallnum; + + for (bssize_t findWall=startWall; findWall x - x, pSprite->y - y); + + if (d < closestDist) + { + closestDist = d; + closestWall = findWall; + } + } + + T2(newSprite) = closestWall; + + closestDist = INT32_MAX; + + for (bssize_t findWall=startWall; findWall x - x, pSprite->y - y); + + if (d < closestDist && findWall != T2(newSprite)) + { + closestDist = d; + closestWall = findWall; + } + } + + T3(newSprite) = closestWall; + } + + break; + + case SE_3_RANDOM_LIGHTS_AFTER_SHOT_OUT: + { + + T4(newSprite)=sector[sectNum].floorshade; + + sector[sectNum].floorshade = pSprite->shade; + sector[sectNum].ceilingshade = pSprite->shade; + + pSprite->owner = sector[sectNum].ceilingpal << 8; + pSprite->owner |= sector[sectNum].floorpal; + + //fix all the walls; + + int const startWall = sector[sectNum].wallptr; + int const endWall = startWall+sector[sectNum].wallnum; + + for (bssize_t w=startWall; w shade; + + if ((wall[w].cstat & 2) && wall[w].nextwall >= 0) + wall[wall[w].nextwall].shade = pSprite->shade; + } + break; + } + + case SE_31_FLOOR_RISE_FALL: + { + T2(newSprite) = sector[sectNum].floorz; + + if (pSprite->ang != 1536) + { + sector[sectNum].floorz = pSprite->z; + Yax_SetBunchZs(sectNum, YAX_FLOOR, pSprite->z); + } + + int const startWall = sector[sectNum].wallptr; + int const endWall = startWall + sector[sectNum].wallnum; + + for (bssize_t w = startWall; w < endWall; ++w) + if (wall[w].hitag == 0) + wall[w].hitag = 9999; + + G_SetInterpolation(§or[sectNum].floorz); + Yax_SetBunchInterpolation(sectNum, YAX_FLOOR); + } + break; + + case SE_32_CEILING_RISE_FALL: + { + T2(newSprite) = sector[sectNum].ceilingz; + T3(newSprite) = pSprite->hitag; + + if (pSprite->ang != 1536) + { + sector[sectNum].ceilingz = pSprite->z; + Yax_SetBunchZs(sectNum, YAX_CEILING, pSprite->z); + } + + int const startWall = sector[sectNum].wallptr; + int const endWall = startWall + sector[sectNum].wallnum; + + for (bssize_t w = startWall; w < endWall; ++w) + if (wall[w].hitag == 0) + wall[w].hitag = 9999; + + G_SetInterpolation(§or[sectNum].ceilingz); + Yax_SetBunchInterpolation(sectNum, YAX_CEILING); + } + break; + + case SE_4_RANDOM_LIGHTS: //Flashing lights + { + T3(newSprite) = sector[sectNum].floorshade; + + int const startWall = sector[sectNum].wallptr; + int const endWall = startWall + sector[sectNum].wallnum; + + pSprite->owner = sector[sectNum].ceilingpal << 8; + pSprite->owner |= sector[sectNum].floorpal; + + for (bssize_t w = startWall; w < endWall; ++w) + if (wall[w].shade > T4(newSprite)) + T4(newSprite) = wall[w].shade; + } + break; + + case SE_9_DOWN_OPEN_DOOR_LIGHTS: + if (sector[sectNum].lotag && + labs(sector[sectNum].ceilingz-pSprite->z) > 1024) + sector[sectNum].lotag |= 32768u; //If its open + fallthrough__; + case SE_8_UP_OPEN_DOOR_LIGHTS: + //First, get the ceiling-floor shade + { + T1(newSprite) = sector[sectNum].floorshade; + T2(newSprite) = sector[sectNum].ceilingshade; + + int const startWall = sector[sectNum].wallptr; + int const endWall = startWall + sector[sectNum].wallnum; + + for (bssize_t w = startWall; w < endWall; ++w) + if (wall[w].shade > T3(newSprite)) + T3(newSprite) = wall[w].shade; + + T4(newSprite) = 1; // Take Out; + } + break; + case 88: + //First, get the ceiling-floor shade + { + if (!RRRA) break; + T1(newSprite) = sector[sectNum].floorshade; + T2(newSprite) = sector[sectNum].ceilingshade; + + int const startWall = sector[sectNum].wallptr; + int const endWall = startWall + sector[sectNum].wallnum; + + for (bssize_t w = startWall; w < endWall; ++w) + if (wall[w].shade > T3(newSprite)) + T3(newSprite) = wall[w].shade; + + T4(newSprite) = 1; // Take Out; + } + break; + + case SE_11_SWINGING_DOOR: // Pivitor rotater + T4(newSprite) = (pSprite->ang > 1024) ? 2 : -2; + fallthrough__; + case SE_0_ROTATING_SECTOR: + case SE_2_EARTHQUAKE: // Earthquakemakers + case SE_5: // Boss Creature + case SE_6_SUBWAY: // Subway + case SE_14_SUBWAY_CAR: // Caboos + case SE_15_SLIDING_DOOR: // Subwaytype sliding door + case SE_16_REACTOR: // That rotating blocker reactor thing + case SE_26: // ESCELATOR + case SE_30_TWO_WAY_TRAIN: // No rotational subways + if (pSprite->lotag == SE_0_ROTATING_SECTOR) + { + if (sector[sectNum].lotag == ST_30_ROTATE_RISE_BRIDGE) + { + sprite[newSprite].clipdist = (pSprite->pal) ? 1 : 0; + T4(newSprite) = sector[sectNum].floorz; + sector[sectNum].hitag = newSprite; + } + + for (spriteNum = MAXSPRITES-1; spriteNum>=0; spriteNum--) + { + if (sprite[spriteNum].statnum < MAXSTATUS) + if (sprite[spriteNum].picnum == SECTOREFFECTOR && + sprite[spriteNum].lotag == SE_1_PIVOT && + sprite[spriteNum].hitag == pSprite->hitag) + { + if (pSprite->ang == 512) + { + pSprite->x = sprite[spriteNum].x; + pSprite->y = sprite[spriteNum].y; + } + break; + } + } + if (EDUKE32_PREDICT_FALSE(spriteNum == -1)) + { + OSD_Printf(OSD_ERROR "Found lonely Sector Effector (lotag 0) at (%d,%d)\n", + TrackerCast(pSprite->x),TrackerCast(pSprite->y)); + changespritestat(newSprite, STAT_ACTOR); + goto SPAWN_END; + } + pSprite->owner = spriteNum; + } + + { + int const startWall = sector[sectNum].wallptr; + int const endWall = startWall+sector[sectNum].wallnum; + + T2(newSprite) = tempwallptr; + for (bssize_t w = startWall; w < endWall; ++w) + { + g_origins[tempwallptr].x = wall[w].x - pSprite->x; + g_origins[tempwallptr].y = wall[w].y - pSprite->y; + + tempwallptr++; + if (EDUKE32_PREDICT_FALSE(tempwallptr >= MAXANIMPOINTS)) + { + Bsprintf(tempbuf, "Too many moving sectors at (%d,%d).\n", + TrackerCast(wall[w].x), TrackerCast(wall[w].y)); + G_GameExit(tempbuf); + } + } + } + + if (pSprite->lotag == SE_5 || pSprite->lotag == SE_30_TWO_WAY_TRAIN || + pSprite->lotag == SE_6_SUBWAY || pSprite->lotag == SE_14_SUBWAY_CAR) + { +#ifdef YAX_ENABLE + int outerWall = -1; +#endif + int const startWall = sector[sectNum].wallptr; + int const endWall = startWall + sector[sectNum].wallnum; + + pSprite->extra = ((uint16_t)sector[sectNum].hitag != UINT16_MAX); + + // TRAIN_SECTOR_TO_SE_INDEX + sector[sectNum].hitag = newSprite; + + spriteNum = 0; + + int foundWall = startWall; + + for (; foundWall = 0 && + sector[ wall[ foundWall ].nextsector].hitag == 0 && + ((int16_t)sector[ wall[ foundWall ].nextsector].lotag < 3 || (RRRA && (int16_t)sector[wall[foundWall].nextsector].lotag == 160))) + { +#ifdef YAX_ENABLE + outerWall = wall[foundWall].nextwall; +#endif + foundWall = wall[foundWall].nextsector; + spriteNum = 1; + break; + } + } + +#ifdef YAX_ENABLE + pActor->t_data[9] = -1; + + if (outerWall >= 0) + { + int upperSect = yax_vnextsec(outerWall, YAX_CEILING); + + if (upperSect >= 0) + { + int foundEffector = headspritesect[upperSect]; + + for (; foundEffector >= 0; foundEffector = nextspritesect[foundEffector]) + if (sprite[foundEffector].picnum == SECTOREFFECTOR && sprite[foundEffector].lotag == pSprite->lotag) + break; + + if (foundEffector < 0) + { + Sect_SetInterpolation(upperSect); + pActor->t_data[9] = upperSect; + } + } + } +#endif + if (spriteNum == 0) + { + Bsprintf(tempbuf,"Subway found no zero'd sectors with locators\nat (%d,%d).\n", + TrackerCast(pSprite->x),TrackerCast(pSprite->y)); + G_GameExit(tempbuf); + } + + pSprite->owner = -1; + T1(newSprite) = foundWall; + + if (pSprite->lotag != SE_30_TWO_WAY_TRAIN) + T4(newSprite) = pSprite->hitag; + } + else if (pSprite->lotag == SE_16_REACTOR) + T4(newSprite) = sector[sectNum].ceilingz; + else if (pSprite->lotag == SE_26) + { + T4(newSprite) = pSprite->x; + T5(newSprite) = pSprite->y; + pSprite->zvel = (pSprite->shade == sector[sectNum].floorshade) ? -256 : 256; // UP + pSprite->shade = 0; + } + else if (pSprite->lotag == SE_2_EARTHQUAKE) + { + T6(newSprite) = sector[pSprite->sectnum].floorheinum; + sector[pSprite->sectnum].floorheinum = 0; + } + } + + switch (pSprite->lotag) + { + case SE_6_SUBWAY: + case SE_14_SUBWAY_CAR: + S_FindMusicSFX(sectNum, &spriteNum); + // XXX: uh.. what? + if (spriteNum == -1) + { + if (RR && sector[pSprite->sectnum].floorpal == 7) + spriteNum = 456; + else + spriteNum = SUBWAY; + } + pActor->lastv.x = spriteNum; + fallthrough__; + case SE_30_TWO_WAY_TRAIN: + if (g_netServer || numplayers > 1) + break; + fallthrough__; + case SE_0_ROTATING_SECTOR: + case SE_1_PIVOT: + case SE_5: + case SE_11_SWINGING_DOOR: + case SE_15_SLIDING_DOOR: + case SE_16_REACTOR: + case SE_26: Sect_SetInterpolation(sprite[newSprite].sectnum); break; + } + + if (RRRA && sprite[newSprite].lotag >= 150 && sprite[newSprite].lotag <= 155) + changespritestat(newSprite, STAT_RAROR); + else + changespritestat(newSprite, STAT_EFFECTOR); + break; + + case SEENINE__STATIC: + case OOZFILTER__STATIC: + pSprite->shade = -16; + if (pSprite->xrepeat <= 8) + { + pSprite->cstat = 32768; + pSprite->xrepeat = 0; + pSprite->yrepeat = 0; + } + else pSprite->cstat = 1+256; + + pSprite->extra = g_impactDamage << 2; + pSprite->owner = newSprite; + + changespritestat(newSprite, STAT_STANDABLE); + break; + + case CRACK1__STATIC: + case CRACK2__STATIC: + case CRACK3__STATIC: + case CRACK4__STATIC: + case FIREEXT__STATIC: + if (RR && pSprite->picnum == FIREEXT) goto default_case; + if (!RR && pSprite->picnum == FIREEXT) + { + pSprite->cstat = 257; + pSprite->extra = g_impactDamage<<2; + } + else + { + pSprite->cstat |= (pSprite->cstat & 48) ? 1 : 17; + pSprite->extra = 1; + } + + if ((!g_netServer && ud.multimode < 2) && pSprite->pal != 0) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + break; + } + + pSprite->pal = 0; + pSprite->owner = newSprite; + pSprite->xvel = 8; + + changespritestat(newSprite, STAT_STANDABLE); + A_SetSprite(newSprite,CLIPMASK0); + break; + + case EMPTYBIKE__STATICRR: + if (!RRRA) goto default_case; + if ((!g_netServer && ud.multimode < 2) && pSprite->pal == 1) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + break; + } + pSprite->pal = 0; + pSprite->xrepeat = 18; + pSprite->yrepeat = 18; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + pSprite->owner = 100; + pSprite->cstat = 257; + pSprite->lotag = 1; + changespritestat(newSprite, 1); + break; + case EMPTYBOAT__STATICRR: + if (!RRRA) goto default_case; + if ((!g_netServer && ud.multimode < 2) && pSprite->pal == 1) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + break; + } + pSprite->pal = 0; + pSprite->xrepeat = 32; + pSprite->yrepeat = 32; + pSprite->clipdist = mulscale7(pSprite->xrepeat, tilesiz[pSprite->picnum].x); + pSprite->owner = 20; + pSprite->cstat = 257; + pSprite->lotag = 1; + changespritestat(newSprite, 1); + break; + + case TOILET__STATIC: + case STALL__STATIC: + case RRTILE2121__STATICRR: + case RRTILE2122__STATICRR: + pSprite->lotag = 1; + pSprite->cstat |= 257; + pSprite->clipdist = 8; + pSprite->owner = newSprite; + break; + + case CANWITHSOMETHING2__STATIC: + case CANWITHSOMETHING3__STATIC: + case CANWITHSOMETHING4__STATIC: + if (RR) goto default_case; + fallthrough__; + case CANWITHSOMETHING__STATIC: + case RUBBERCAN__STATIC: + pSprite->extra = 0; + fallthrough__; + case EXPLODINGBARREL__STATIC: + case HORSEONSIDE__STATIC: + case FIREBARREL__STATIC: + case NUKEBARREL__STATIC: + case FIREVASE__STATIC: + case NUKEBARRELDENTED__STATIC: + case NUKEBARRELLEAKED__STATIC: + case WOODENHORSE__STATIC: + if (spriteNum >= 0) + pSprite->xrepeat = pSprite->yrepeat = 32; + pSprite->clipdist = 72; + A_Fall(newSprite); + if (spriteNum >= 0) + pSprite->owner = spriteNum; + else pSprite->owner = newSprite; + fallthrough__; + case EGG__STATIC: + if (ud.monsters_off == 1 && pSprite->picnum == EGG) + { + pSprite->xrepeat = pSprite->yrepeat = 0; + changespritestat(newSprite, STAT_MISC); + } + else + { + if (pSprite->picnum == EGG) + pSprite->clipdist = 24; + pSprite->cstat = 257|(krand2()&4); + changespritestat(newSprite, STAT_ZOMBIEACTOR); + } + break; + + case TOILETWATER__STATIC: + pSprite->shade = -16; + changespritestat(newSprite, STAT_STANDABLE); + break; + case RRTILE63__STATICRR: + pSprite->cstat |= 32768; + pSprite->xrepeat = 1; + pSprite->yrepeat = 1; + pSprite->clipdist = 1; + changespritestat(newSprite, 100); + break; + } + +SPAWN_END: + return newSprite; +} + +static int G_MaybeTakeOnFloorPal(uspritetype *pSprite, int sectNum) +{ + int const floorPal = sector[sectNum].floorpal; + + if (floorPal && !g_noFloorPal[floorPal] && !A_CheckSpriteFlags(pSprite->owner, SFLAG_NOPAL)) + { + pSprite->pal = floorPal; + return 1; + } + + return 0; +} + +template +static int getofs_viewtype(int angDiff) +{ + return ((((angDiff + 3072) & 2047) * rotations + 1024) >> 11) % rotations; +} + +template +static int viewtype_mirror(uint16_t & cstat, int frameOffset) +{ + if (frameOffset > rotations / 2) + { + cstat |= 4; + return rotations - frameOffset; + } + + cstat &= ~4; + return frameOffset; +} + +template +static int getofs_viewtype_mirrored(uint16_t & cstat, int angDiff) +{ + return viewtype_mirror (cstat, getofs_viewtype (angDiff)); +} + +// XXX: this fucking sucks and needs to be replaced with a SFLAG +static int G_CheckAdultTile(int tileNum) +{ + UNREFERENCED_PARAMETER(tileNum); + switch (tileNum) + { + case FEM1__STATIC: + case FEM2__STATIC: + case FEM3__STATIC: + case FEM4__STATIC: + case FEM5__STATIC: + case FEM6__STATIC: + case FEM7__STATIC: + case FEM8__STATIC: + case FEM9__STATIC: + case MAN__STATIC: + case MAN2__STATIC: + case WOMAN__STATIC: + case PODFEM1__STATIC: + case FEMPIC1__STATIC: + case FEMPIC2__STATIC: + case FEMPIC3__STATIC: + case FEMPIC4__STATIC: + case FEMPIC5__STATIC: + case FEMPIC6__STATIC: + case FEMPIC7__STATIC: + case BLOODYPOLE__STATIC: + case FEM6PAD__STATIC: + case OOZ2__STATIC: + case WALLBLOOD7__STATIC: + case WALLBLOOD8__STATIC: + case FETUS__STATIC: + case FETUSJIB__STATIC: + case FETUSBROKE__STATIC: + case HOTMEAT__STATIC: + case FOODOBJECT16__STATIC: + case TAMPON__STATIC: + case XXXSTACY__STATIC: + case 4946: + case 4947: + case 693: + case 2254: + case 4560: + case 4561: + case 4562: + case 4498: + case 4957: + if (RR) return 0; + return 1; + case FEM10__STATIC: + case NAKED1__STATIC: + case FEMMAG1__STATIC: + case FEMMAG2__STATIC: + case STATUE__STATIC: + case STATUEFLASH__STATIC: + case OOZ__STATIC: + case WALLBLOOD1__STATIC: + case WALLBLOOD2__STATIC: + case WALLBLOOD3__STATIC: + case WALLBLOOD4__STATIC: + case WALLBLOOD5__STATIC: + case SUSHIPLATE1__STATIC: + case SUSHIPLATE2__STATIC: + case SUSHIPLATE3__STATIC: + case SUSHIPLATE4__STATIC: + case DOLPHIN1__STATIC: + case DOLPHIN2__STATIC: + case TOUGHGAL__STATIC: + return 1; + } + return 0; +} + +void G_DoSpriteAnimations(int32_t ourx, int32_t oury, int32_t oura, int32_t smoothratio) +{ + int32_t j, frameOffset, playerNum; + intptr_t l; + + if (spritesortcnt == 0) + { +#ifdef DEBUGGINGAIDS + g_spriteStat.numonscreen = 0; +#endif + return; + } +#ifdef LEGACY_ROR + ror_sprite = -1; +#endif + for (j=spritesortcnt-1; j>=0; j--) + { + uspritetype *const t = &tsprite[j]; + const int32_t i = t->owner; + const spritetype *const s = &sprite[i]; + + switch (DYNAMICTILEMAP(s->picnum)) + { + case SECTOREFFECTOR__STATIC: + if (!RR && (s->lotag == 40 || s->lotag == 41)) + { + t->cstat = 32768; +#ifdef LEGACY_ROR + if (ror_sprite == -1) + ror_sprite = i; +#endif + } + + if (t->lotag == SE_27_DEMO_CAM && ud.recstat == 1) + { + t->picnum = 11+((totalclock>>3)&1); + t->cstat |= 128; + } + else + t->xrepeat = t->yrepeat = 0; + break; + } + } + + for (j=spritesortcnt-1; j>=0; j--) + { + uspritetype *const t = &tsprite[j]; + const int32_t i = t->owner; + spritetype *const s = &sprite[i]; + + if (t->picnum < GREENSLIME || t->picnum > GREENSLIME+7) + switch (DYNAMICTILEMAP(t->picnum)) + { + case PUKE__STATIC: + if (RR) goto default_case1; + fallthrough__; + case BLOODPOOL__STATIC: + case FOOTPRINTS__STATIC: + case FOOTPRINTS2__STATIC: + case FOOTPRINTS3__STATIC: + case FOOTPRINTS4__STATIC: + if (t->shade == 127) continue; + break; + case RESPAWNMARKERRED__STATIC: + case RESPAWNMARKERYELLOW__STATIC: + case RESPAWNMARKERGREEN__STATIC: + if (RR) goto default_case1; + if (ud.marker == 0) + t->xrepeat = t->yrepeat = 0; + continue; + case CHAIR3__STATIC: +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(t->picnum,t->pal) >= 0 && !(spriteext[i].flags&SPREXT_NOTMD)) + { + t->cstat &= ~4; + break; + } +#endif + frameOffset = getofs_viewtype_mirrored<5>(t->cstat, t->ang - oura); + t->picnum = s->picnum+frameOffset; + break; + case BLOODSPLAT1__STATIC: + case BLOODSPLAT2__STATIC: + case BLOODSPLAT3__STATIC: + case BLOODSPLAT4__STATIC: + if (ud.lockout) t->xrepeat = t->yrepeat = 0; + else if (t->pal == 6) + { + t->shade = -127; + continue; + } + fallthrough__; + case BULLETHOLE__STATIC: + case CRACK1__STATIC: + case CRACK2__STATIC: + case CRACK3__STATIC: + case CRACK4__STATIC: + t->shade = 16; + continue; + case NEON1__STATIC: + case NEON2__STATIC: + case NEON3__STATIC: + case NEON4__STATIC: + case NEON5__STATIC: + case NEON6__STATIC: + continue; + case RRTILE1947__STATICRR: + case RRTILE2859__STATICRR: + case RRTILE3774__STATICRR: + case RRTILE5088__STATICRR: + case RRTILE8094__STATICRR: + case RRTILE8096__STATICRR: + if (!RRRA) goto default_case1; + continue; + default: +default_case1: + // NOTE: wall-aligned sprites will never take on ceiling/floor shade... + if ((t->cstat&16) || (A_CheckEnemySprite(t) && + (unsigned)t->owner < MAXSPRITES && sprite[t->owner].extra > 0) || t->statnum == STAT_PLAYER) + { + if (RR && g_shadedSector[s->sectnum] == 1) + { + s->shade = 16; + t->shade = 16; + } + continue; + } + } + + // ... since this is not reached: + if (t->cstat&CSTAT_SPRITE_NOSHADE) + l = sprite[t->owner].shade; + else + { + if (sector[t->sectnum].ceilingstat&1) + { + if (RR) + l = s->shade; + else + l = sector[t->sectnum].ceilingshade; + } + else + l = sector[t->sectnum].floorshade; + + if (l < -127) + l = -127; + } + + t->shade = l; + } + + for (j=spritesortcnt-1; j>=0; j--) //Between drawrooms() and drawmasks() + { + int32_t switchpic; + int32_t curframe; + int32_t scrofs_action; + //is the perfect time to animate sprites + uspritetype *const t = &tsprite[j]; + const int32_t i = t->owner; + // XXX: what's up with the (i < 0) check? + // NOTE: not const spritetype because set at SET_SPRITE_NOT_TSPRITE (see below). + uspritetype *const pSprite = (i < 0) ? &tsprite[j] : (uspritetype *)&sprite[i]; + + if (ud.lockout && G_CheckAdultTile(DYNAMICTILEMAP(pSprite->picnum))) + { + t->xrepeat = t->yrepeat = 0; + continue; + } + + if (!RR && pSprite->picnum == NATURALLIGHTNING) + { + t->shade = -127; + t->cstat |= 8192; + } + + if (t->statnum == TSPR_TEMP) + continue; + + Bassert(i >= 0); + + const DukePlayer_t *const ps = (pSprite->statnum != STAT_ACTOR && pSprite->picnum == APLAYER && pSprite->owner >= 0) ? g_player[P_GetP(pSprite)].ps : NULL; + if (ps && ps->newowner == -1) + { + t->x -= mulscale16(65536-smoothratio,ps->pos.x-ps->opos.x); + t->y -= mulscale16(65536-smoothratio,ps->pos.y-ps->opos.y); + t->z = ps->opos.z + mulscale16(smoothratio,ps->pos.z-ps->opos.z) + (40<<8); + + if (RR) + { + pSprite->xrepeat = 24; + pSprite->yrepeat = 17; + } + } + else if ((pSprite->statnum == STAT_DEFAULT && pSprite->picnum != CRANEPOLE) || pSprite->statnum == STAT_PLAYER || + pSprite->statnum == STAT_STANDABLE || pSprite->statnum == STAT_PROJECTILE || pSprite->statnum == STAT_MISC || pSprite->statnum == STAT_ACTOR) + { + t->x -= mulscale16(65536-smoothratio,pSprite->x-actor[i].bpos.x); + t->y -= mulscale16(65536-smoothratio,pSprite->y-actor[i].bpos.y); + t->z -= mulscale16(65536-smoothratio,pSprite->z-actor[i].bpos.z); + } + + const int32_t sect = pSprite->sectnum; + + curframe = AC_CURFRAME(actor[i].t_data); + scrofs_action = AC_ACTION_ID(actor[i].t_data); + switchpic = pSprite->picnum; + // Some special cases because dynamictostatic system can't handle + // addition to constants. + if ((pSprite->picnum >= SCRAP6) && (pSprite->picnum<=SCRAP6+7)) + switchpic = SCRAP5; + else if ((pSprite->picnum==MONEY+1) || (pSprite->picnum==MAIL+1) || (pSprite->picnum==PAPER+1)) + switchpic--; + + switch (DYNAMICTILEMAP(switchpic)) + { + case RESPAWNMARKERRED__STATICRR: + case RESPAWNMARKERYELLOW__STATICRR: + case RESPAWNMARKERGREEN__STATICRR: + if (!RR) goto default_case2; + t->picnum = 861 + ((totalclock >> 4) & 13); + if (pSprite->picnum == RESPAWNMARKERRED) + t->pal = 0; + else if (pSprite->picnum == RESPAWNMARKERYELLOW) + t->pal = 1; + else + t->pal = 2; + if (ud.marker == 0) + t->xrepeat = t->yrepeat = 0; + break; + case DUKELYINGDEAD__STATIC: + if (RR) + { + pSprite->x = 24; + pSprite->y = 17; + if (pSprite->extra > 0) + t->z += (6<<8); + } + else + t->z += (24<<8); + break; + case BLOODPOOL__STATIC: + case FOOTPRINTS__STATIC: + case FOOTPRINTS2__STATIC: + case FOOTPRINTS3__STATIC: + case FOOTPRINTS4__STATIC: + if (t->pal == 6) + t->shade = -127; + fallthrough__; + case PUKE__STATIC: + case MONEY__STATIC: + //case MONEY+1__STATIC: + case MAIL__STATIC: + //case MAIL+1__STATIC: + case PAPER__STATIC: + //case PAPER+1__STATIC: + if (RR && (switchpic == PUKE || switchpic == MAIL || switchpic == PAPER)) goto default_case2; + if (ud.lockout && pSprite->pal == 2) + { + t->xrepeat = t->yrepeat = 0; + continue; + } + break; + case TRIPBOMB__STATIC: + if (RR) goto default_case2; + continue; + case TRIPBOMBSPRITE__STATIC: + if (!RR) goto default_case2; + continue; + case FORCESPHERE__STATIC: + if (t->statnum == STAT_MISC) + { + int16_t const sqa = getangle(sprite[pSprite->owner].x - g_player[screenpeek].ps->pos.x, + sprite[pSprite->owner].y - g_player[screenpeek].ps->pos.y); + int16_t const sqb = getangle(sprite[pSprite->owner].x - t->x, sprite[pSprite->owner].y - t->y); + + if (klabs(G_GetAngleDelta(sqa,sqb)) > 512) + if (ldist(&sprite[pSprite->owner],(const spritetype *)t) < ldist(&sprite[g_player[screenpeek].ps->i],&sprite[pSprite->owner])) + t->xrepeat = t->yrepeat = 0; + } + continue; + case BURNING2__STATIC: + if (RR) goto default_case2; + fallthrough__; + case BURNING__STATIC: + if (sprite[pSprite->owner].statnum == STAT_PLAYER) + { + int const playerNum = P_Get(pSprite->owner); + + if (display_mirror == 0 && playerNum == screenpeek && g_player[playerNum].ps->over_shoulder_on == 0) + t->xrepeat = 0; + else + { + t->ang = getangle(ourx - t->x, oury - t->y); + t->x = sprite[pSprite->owner].x + (sintable[(t->ang + 512) & 2047] >> 10); + t->y = sprite[pSprite->owner].y + (sintable[t->ang & 2047] >> 10); + } + } + break; + + case ATOMICHEALTH__STATIC: + t->z -= ZOFFSET6; + break; + case CRYSTALAMMO__STATIC: + t->shade = (sintable[(totalclock<<4)&2047]>>10); + if (RR) break; + continue; + case VIEWSCREEN__STATIC: + case VIEWSCREEN2__STATIC: + { + if (RR) goto default_case2; + int const viewscrShift = G_GetViewscreenSizeShift(t); + int const viewscrTile = TILE_VIEWSCR-viewscrShift; + + if (g_curViewscreen >= 0 && actor[OW(i)].t_data[0] == 1) + { + t->picnum = STATIC; + t->cstat |= (rand()&12); + t->xrepeat += 10; + t->yrepeat += 9; + } + else if (g_curViewscreen == i && display_mirror != 3 && waloff[viewscrTile] && walock[viewscrTile] > 200) + { + // this exposes a sprite sorting issue which needs to be debugged further... +#if 0 + if (spritesortcnt < maxspritesonscreen) + { + spritetype *const newt = &tsprite[spritesortcnt++]; + + Bmemcpy(newt, t, sizeof(spritetype)); + + newt->cstat |= 2|512; + newt->x += (sintable[(newt->ang+512)&2047]>>12); + newt->y += (sintable[newt->ang&2047]>>12); + updatesector(newt->x, newt->y, &newt->sectnum); + } +#endif + t->picnum = viewscrTile; +#if VIEWSCREENFACTOR > 0 + t->xrepeat >>= viewscrShift; + t->yrepeat >>= viewscrShift; +#endif + } + + break; + } + case SHRINKSPARK__STATIC: + if (RR) + { + if (RRRA && (sprite[pSprite->owner].picnum == CHEER || sprite[pSprite->owner].picnum == CHEERSTAYPUT)) + { + t->picnum = CHEERBLADE + ((totalclock >> 4) & 3); + t->shade = -127; + } + else + t->picnum = SHRINKSPARK + ((totalclock >> 4) & 7); + } + else + t->picnum = SHRINKSPARK+((totalclock>>4)&3); + break; + case CHEERBOMB__STATICRR: + if (!RRRA) goto default_case2; + t->picnum = CHEERBOMB+( (totalclock>>4)&3 ); + break; + case GROWSPARK__STATIC: + if (RR) goto default_case2; + t->picnum = GROWSPARK+((totalclock>>4)&3); + break; + case SPIT__STATIC: + if (!RR) goto default_case2; + t->picnum = SPIT + ((totalclock >> 4) & 3); + if (RRRA) + { + if (sprite[pSprite->owner].picnum == MINION && sprite[pSprite->owner].pal == 8) + t->picnum = RRTILE3500 + ((totalclock >> 4) % 6); + else if (sprite[pSprite->owner].picnum == MINION && sprite[pSprite->owner].pal == 19) + { + t->picnum = RRTILE5090 + ((totalclock >> 4) & 3); + t->shade = -127; + } + else if (sprite[pSprite->owner].picnum == MAMA) + { +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(t->picnum, t->pal) >= 0 && + !(spriteext[i].flags & SPREXT_NOTMD)) + { + int32_t v = getangle(t->xvel, t->zvel >> 4); + + spriteext[i].pitch = (v > 1023 ? v - 2048 : v); + t->cstat &= ~4; + t->picnum = RRTILE7274; + break; + } +#endif + frameOffset = getofs_viewtype_mirrored<5>(t->cstat, pSprite->ang - getangle(pSprite->x-ourx, pSprite->y-oury)); + t->picnum = RRTILE7274 + frameOffset; + } + } + break; + case EMPTYBIKE__STATICRR: + if (!RRRA) goto default_case2; +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(t->picnum, t->pal) >= 0 && !(spriteext[i].flags&SPREXT_NOTMD)) + { + t->cstat &= ~4; + break; + } +#endif + frameOffset = getofs_viewtype_mirrored<7>(t->cstat, pSprite->ang - getangle(pSprite->x - ourx, pSprite->y - oury)); + t->picnum = EMPTYBIKE + frameOffset; + break; + case EMPTYBOAT__STATICRR: + if (!RRRA) goto default_case2; +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(t->picnum, t->pal) >= 0 && !(spriteext[i].flags&SPREXT_NOTMD)) + { + t->cstat &= ~4; + break; + } +#endif + frameOffset = getofs_viewtype_mirrored<7>(t->cstat, pSprite->ang - getangle(pSprite->x - ourx, pSprite->y - oury)); + t->picnum = EMPTYBOAT + frameOffset; + break; + case RPG__STATIC: +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(t->picnum,t->pal) >= 0 && + !(spriteext[i].flags & SPREXT_NOTMD)) + { + int32_t v = getangle(t->xvel, t->zvel>>4); + + spriteext[i].pitch = (v > 1023 ? v-2048 : v); + t->cstat &= ~4; + break; + } +#endif + frameOffset = getofs_viewtype_mirrored<7>(t->cstat, pSprite->ang - getangle(pSprite->x-ourx, pSprite->y-oury)); + t->picnum = RPG+frameOffset; + break; + case RPG2__STATICRR: + if (!RRRA) goto default_case2; +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(t->picnum,t->pal) >= 0 && + !(spriteext[i].flags & SPREXT_NOTMD)) + { + int32_t v = getangle(t->xvel, t->zvel>>4); + + spriteext[i].pitch = (v > 1023 ? v-2048 : v); + t->cstat &= ~4; + break; + } +#endif + frameOffset = getofs_viewtype_mirrored<7>(t->cstat, pSprite->ang - getangle(pSprite->x-ourx, pSprite->y-oury)); + t->picnum = RPG2+frameOffset; + break; + + case RECON__STATIC: +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(t->picnum,t->pal) >= 0 && !(spriteext[i].flags&SPREXT_NOTMD)) + { + t->cstat &= ~4; + break; + } +#endif + frameOffset = getofs_viewtype_mirrored<7>(t->cstat, pSprite->ang - getangle(pSprite->x-ourx, pSprite->y-oury)); + + // RECON_T4 + if (klabs(curframe) > 64) + frameOffset += 7; // tilted recon car + + t->picnum = RECON+frameOffset; + + break; + case APLAYER__STATIC: + playerNum = P_GetP(pSprite); + + if (t->pal == 1) t->z -= (18<<8); + + if (g_player[playerNum].ps->over_shoulder_on > 0 && g_player[playerNum].ps->newowner < 0) + { + t->ang = fix16_to_int( + g_player[playerNum].ps->q16ang + + mulscale16((((g_player[playerNum].ps->q16ang + 1024 - g_player[playerNum].ps->oq16ang) & 2047) - 1024), smoothratio)); +#ifdef USE_OPENGL + if (bpp > 8 && usemodels && md_tilehasmodel(t->picnum, t->pal) >= 0) + { + static int32_t targetang = 0; + + if (g_player[playerNum].inputBits->extbits&(1<<1)) + { + if (g_player[playerNum].inputBits->extbits&(1<<2))targetang += 16; + else if (g_player[playerNum].inputBits->extbits&(1<<3)) targetang -= 16; + else if (targetang > 0) targetang -= targetang>>2; + else if (targetang < 0) targetang += (-targetang)>>2; + } + else + { + if (g_player[playerNum].inputBits->extbits&(1<<2))targetang -= 16; + else if (g_player[playerNum].inputBits->extbits&(1<<3)) targetang += 16; + else if (targetang > 0) targetang -= targetang>>2; + else if (targetang < 0) targetang += (-targetang)>>2; + } + + targetang = clamp(targetang, -128, 128); + t->ang += targetang; + } + else +#endif + t->cstat |= 2; + } + + if ((g_netServer || ud.multimode > 1) && (display_mirror || screenpeek != playerNum || pSprite->owner == -1)) + { + if (ud.showweapons && sprite[g_player[playerNum].ps->i].extra > 0 && g_player[playerNum].ps->curr_weapon > 0 + && spritesortcnt < maxspritesonscreen) + { + uspritetype *const newTspr = &tsprite[spritesortcnt]; + int const currentWeapon = g_player[playerNum].ps->curr_weapon; + + *newTspr = *t; + newTspr->statnum = TSPR_TEMP; + newTspr->cstat = 0; + newTspr->pal = 0; + newTspr->picnum = (currentWeapon == GROW_WEAPON ? GROWSPRITEICON : WeaponPickupSprites[currentWeapon]); + if (RR) + { + newTspr->picnum = 0; + switch(DYNAMICWEAPONMAP(g_player[playerNum].ps->curr_weapon)) + { + case PISTOL_WEAPON__STATIC: newTspr->picnum = FIRSTGUNSPRITE; break; + case SHOTGUN_WEAPON__STATIC: newTspr->picnum = SHOTGUNSPRITE; break; + case CHAINGUN_WEAPON__STATIC: newTspr->picnum = CHAINGUNSPRITE; break; + case RPG_WEAPON__STATIC: newTspr->picnum = RPGSPRITE; break; + case CHICKEN_WEAPON__STATIC: newTspr->picnum = RPGSPRITE; break; + case HANDREMOTE_WEAPON__STATIC: + case HANDBOMB_WEAPON__STATIC: newTspr->picnum = HEAVYHBOMB; break; + case TRIPBOMB_WEAPON__STATIC: newTspr->picnum = TRIPBOMBSPRITE; break; + case BOWLINGBALL_WEAPON__STATIC: newTspr->picnum = BOWLINGBALLSPRITE; break; + case SHRINKER_WEAPON__STATIC: newTspr->picnum = SHRINKSPARK; break; + case GROW_WEAPON__STATIC: newTspr->picnum = SHRINKSPARK; break; + case FREEZE_WEAPON__STATIC: newTspr->picnum = DEVISTATORSPRITE; break; + case DEVISTATOR_WEAPON__STATIC: newTspr->picnum = FREEZESPRITE; break; + } + } + newTspr->z = (pSprite->owner >= 0) ? g_player[playerNum].ps->pos.z - ZOFFSET4 : pSprite->z - (51 << 8); + newTspr->xrepeat = (newTspr->picnum == HEAVYHBOMB) ? 10 : 16; + if (RRRA && (g_player[playerNum].ps->on_motorcycle || g_player[playerNum].ps->on_boat)) + newTspr->xrepeat = 0; + newTspr->yrepeat = newTspr->xrepeat; + + spritesortcnt++; + } + + if (g_player[playerNum].inputBits->extbits & (1 << 7) && !ud.pause_on && spritesortcnt < maxspritesonscreen) + { + uspritetype *const playerTyping = t; + + playerTyping->statnum = TSPR_TEMP; + playerTyping->cstat = 0; + playerTyping->picnum = RESPAWNMARKERGREEN; + playerTyping->z = (pSprite->owner >= 0) ? (g_player[playerNum].ps->pos.z - (20 << 8)) : (pSprite->z - (96 << 8)); + playerTyping->xrepeat = 32; + playerTyping->yrepeat = 32; + playerTyping->pal = 20; + + spritesortcnt++; + } + } + + if (pSprite->owner == -1) + { +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(pSprite->picnum,t->pal) >= 0 && !(spriteext[i].flags&SPREXT_NOTMD)) + { + frameOffset = 0; + t->cstat &= ~4; + } + else +#endif + frameOffset = getofs_viewtype_mirrored<5>(t->cstat, pSprite->ang - oura); + + if (sector[pSprite->sectnum].lotag == ST_2_UNDERWATER) frameOffset += 1795-1405; + else if ((actor[i].floorz-pSprite->z) > (64<<8)) frameOffset += 60; + + t->picnum += frameOffset; + t->pal = g_player[playerNum].ps->palookup; + + goto PALONLY; + } + + if (g_player[playerNum].ps->on_crane == -1 && (sector[pSprite->sectnum].lotag&0x7ff) != 1) // ST_1_ABOVE_WATER ? + { + l = pSprite->z-actor[g_player[playerNum].ps->i].floorz+(3<<8); + // SET_SPRITE_NOT_TSPRITE + if (l > 1024 && pSprite->yrepeat > 32 && pSprite->extra > 0) + pSprite->yoffset = (int8_t)tabledivide32_noinline(l, pSprite->yrepeat<<2); + else pSprite->yoffset=0; + } + + if (g_player[playerNum].ps->newowner > -1) + { + // Display APLAYER sprites with action PSTAND when viewed through + // a camera. Not implemented for Lunatic. + const intptr_t *aplayer_scr = g_tile[APLAYER].execPtr; + // [0]=strength, [1]=actionofs, [2]=moveofs + + scrofs_action = aplayer_scr[1]; + curframe = 0; + } + + if (ud.camerasprite == -1 && g_player[playerNum].ps->newowner == -1) + { + if (pSprite->owner >= 0 && display_mirror == 0 && g_player[playerNum].ps->over_shoulder_on == 0) + { + if ((!g_netServer && ud.multimode < 2) || ((g_netServer || ud.multimode > 1) && playerNum == screenpeek)) + { + if (videoGetRenderMode() == REND_POLYMER) + t->cstat |= 16384; + else + { + t->owner = -1; + t->xrepeat = t->yrepeat = 0; + continue; + } + +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(pSprite->picnum, t->pal) >= 0 && !(spriteext[i].flags&SPREXT_NOTMD)) + { + frameOffset = 0; + t->cstat &= ~4; + } + else +#endif + frameOffset = getofs_viewtype_mirrored<5>(t->cstat, pSprite->ang - oura); + + if (sector[t->sectnum].lotag == ST_2_UNDERWATER) frameOffset += 1795-1405; + else if ((actor[i].floorz-pSprite->z) > (64<<8)) frameOffset += 60; + + t->picnum += frameOffset; + t->pal = g_player[playerNum].ps->palookup; + } + } + } +PALONLY: + G_MaybeTakeOnFloorPal(t, sect); + + if (pSprite->owner == -1) continue; + + if (t->z > actor[i].floorz && t->xrepeat < 32) + t->z = actor[i].floorz; + + if (RRRA) + { + if (g_player[playerNum].ps->on_motorcycle && playerNum == screenpeek) + { + t->picnum = RRTILE7219; + t->xrepeat = 18; + t->yrepeat = 18; + scrofs_action = 0; + curframe = 0; + } + else if (g_player[playerNum].ps->on_motorcycle) + { + t->xrepeat = 18; + t->yrepeat = 18; + scrofs_action = 0; + curframe = 0; +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(pSprite->picnum, t->pal) >= 0 && !(spriteext[i].flags&SPREXT_NOTMD)) + { + frameOffset = 0; + t->cstat &= ~4; + t->picnum = RRTILE7213; + } + else +#endif + frameOffset = getofs_viewtype_mirrored<7>(t->cstat, pSprite->ang - oura); + + t->picnum = RRTILE7213 + frameOffset; + } + else if (g_player[playerNum].ps->on_boat && playerNum == screenpeek) + { + t->picnum = RRTILE7190; + t->xrepeat = 32; + t->yrepeat = 32; + scrofs_action = 0; + curframe = 0; + } + else if (g_player[playerNum].ps->on_boat) + { + t->xrepeat = 32; + t->yrepeat = 32; + scrofs_action = 0; + curframe = 0; +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(pSprite->picnum, t->pal) >= 0 && !(spriteext[i].flags&SPREXT_NOTMD)) + { + frameOffset = 0; + t->cstat &= ~4; + t->picnum = RRTILE7213; + } + else +#endif + frameOffset = getofs_viewtype_mirrored<7>(t->cstat, pSprite->ang - oura); + + t->picnum = RRTILE7184 + frameOffset; + } + } + + break; + case RRTILE2460__STATICRR: + case RRTILE2465__STATICRR: + case BIKEJIBA__STATICRR: + case BIKEJIBB__STATICRR: + case BIKEJIBC__STATICRR: + case BIKERJIBA__STATICRR: + case BIKERJIBB__STATICRR: + case BIKERJIBC__STATICRR: + case BIKERJIBD__STATICRR: + case CHEERJIBA__STATICRR: + case CHEERJIBB__STATICRR: + case CHEERJIBC__STATICRR: + case CHEERJIBD__STATICRR: + case FBOATJIBA__STATICRR: + case FBOATJIBB__STATICRR: + case RABBITJIBA__STATICRR: + case RABBITJIBB__STATICRR: + case RABBITJIBC__STATICRR: + case MAMAJIBA__STATICRR: + case MAMAJIBB__STATICRR: + if (!RRRA) goto default_case2; + fallthrough__; + case MINJIBA__STATICRR: + case MINJIBB__STATICRR: + case MINJIBC__STATICRR: + case JIBS1__STATIC: + case JIBS2__STATIC: + case JIBS3__STATIC: + case JIBS4__STATIC: + case JIBS5__STATIC: + case JIBS6__STATIC: + case HEADJIB1__STATIC: + case LEGJIB1__STATIC: + case ARMJIB1__STATIC: + case LIZMANHEAD1__STATIC: + case LIZMANARM1__STATIC: + case LIZMANLEG1__STATIC: + case DUKELEG__STATIC: + case DUKEGUN__STATIC: + case DUKETORSO__STATIC: + case BILLYJIBA__STATICRR: + case BILLYJIBB__STATICRR: + case HULKJIBA__STATICRR: + case HULKJIBB__STATICRR: + case HULKJIBC__STATICRR: + case COOTJIBA__STATICRR: + case COOTJIBB__STATICRR: + case COOTJIBC__STATICRR: + if (RR) + { + if (switchpic == HEADJIB1 || switchpic == LEGJIB1 || switchpic == ARMJIB1 + || switchpic == LIZMANHEAD1 || switchpic == LIZMANARM1 || switchpic == LIZMANLEG1) + goto default_case2; + } + if (RRRA && t->pal == 19 && (switchpic == MINJIBA || switchpic == MINJIBB || switchpic == MINJIBC)) + t->shade = -127; + if (ud.lockout) + { + t->xrepeat = t->yrepeat = 0; + continue; + } + if (t->pal == 6) + t->shade = -120; + if (RR && g_shadedSector[pSprite->sectnum] == 1) + t->shade = 16; + fallthrough__; + case SCRAP1__STATIC: + case SCRAP2__STATIC: + case SCRAP3__STATIC: + case SCRAP4__STATIC: + case SCRAP5__STATIC: + if ((RR || actor[i].picnum == BLIMP) && t->picnum == SCRAP1 && pSprite->yvel >= 0) + t->picnum = pSprite->yvel < MAXUSERTILES ? pSprite->yvel : 0; + else t->picnum += T1(i); + if (!RR) + t->shade = -128+6 < t->shade ? t->shade-6 : -128; // effectively max(t->shade-6, -128) while avoiding (signed!) underflow + + G_MaybeTakeOnFloorPal(t, sect); + break; + case WATERBUBBLE__STATIC: + if (sector[t->sectnum].floorpicnum == FLOORSLIME) + { + t->pal = 7; + break; + } + fallthrough__; + default: +default_case2: + G_MaybeTakeOnFloorPal(t, sect); + break; + } + + if (G_HaveActor(pSprite->picnum) && scrofs_action != 0 && (!RR || (t->cstat & 48) != 48)) + { + if ((unsigned)scrofs_action + ACTION_VIEWTYPE >= (unsigned)g_scriptSize) + goto skip; + + l = apScript[scrofs_action + ACTION_VIEWTYPE]; + uint16_t const action_flags = apScript[scrofs_action + ACTION_FLAGS]; + + int const invertp = l < 0; + l = klabs(l); + +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(pSprite->picnum,t->pal) >= 0 && !(spriteext[i].flags&SPREXT_NOTMD)) + { + frameOffset = 0; + t->cstat &= ~4; + } + else +#endif + { + int const viewAng = ((l > 4 && l != 8) || action_flags & AF_VIEWPOINT) ? getangle(pSprite->x-ourx, pSprite->y-oury) : oura; + int const angDiff = invertp ? viewAng - pSprite->ang : pSprite->ang - viewAng; + + switch (l) + { + case 2: + frameOffset = getofs_viewtype<8>(angDiff) & 1; + break; + + case 3: + case 4: + frameOffset = viewtype_mirror<7>(t->cstat, getofs_viewtype<16>(angDiff) & 7); + break; + + case 5: + frameOffset = getofs_viewtype_mirrored<5>(t->cstat, angDiff); + break; + case 7: + frameOffset = getofs_viewtype_mirrored<7>(t->cstat, angDiff); + break; + case 8: + frameOffset = getofs_viewtype<8>(angDiff); + t->cstat &= ~4; + break; + /*case 9: + frameOffset = getofs_viewtype_mirrored<9>(t->cstat, angDiff); + break; + case 12: + frameOffset = getofs_viewtype<12>(angDiff); + t->cstat &= ~4; + break; + case 16: + frameOffset = getofs_viewtype<16>(angDiff); + t->cstat &= ~4; + break;*/ + default: + if (RR) + { + if (A_CheckEnemySprite(pSprite) && pSprite->statnum == STAT_ZOMBIEACTOR && pSprite->extra > 0) + { + int const angDiff = pSprite->ang-getangle(pSprite->x-ourx, pSprite->y-oury); + frameOffset = getofs_viewtype_mirrored<5>(t->cstat, angDiff); + break; + } + } + frameOffset = 0; + break; + } + } + + t->picnum += frameOffset + apScript[scrofs_action + ACTION_STARTFRAME] + l*curframe; + // XXX: t->picnum can be out-of-bounds by bad user code. + + if (l > 0) + while (tilesiz[t->picnum].x == 0 && t->picnum > 0) + t->picnum -= l; //Hack, for actors + + if (actor[i].dispicnum >= 0) + actor[i].dispicnum = t->picnum; + } +// else if (display_mirror == 1) +// t->cstat |= 4; + /* completemirror() already reverses the drawn frame, so the above isn't necessary. + * Even Polymost's and Polymer's mirror seems to function correctly this way. */ + +skip: + // Night vision goggles tsprite tinting. + // XXX: Currently, for the splitscreen mod, sprites will be pal6-colored iff the first + // player has nightvision on. We should pass stuff like "from which player is this view + // supposed to be" as parameters ("drawing context") instead of relying on globals. + if (!RR && g_player[screenpeek].ps->inv_amount[GET_HEATS] > 0 && g_player[screenpeek].ps->heat_on && + (A_CheckEnemySprite(pSprite) || A_CheckSpriteFlags(t->owner,SFLAG_NVG) || pSprite->picnum == APLAYER || pSprite->statnum == STAT_DUMMYPLAYER)) + { + t->pal = 6; + t->shade = 0; + } + + if (RR && !RRRA && pSprite->picnum == SBMOVE) + t->shade = -127; + + // Fake floor shadow, implemented by inserting a new tsprite. + if (pSprite->statnum == STAT_DUMMYPLAYER || A_CheckEnemySprite(pSprite) || A_CheckSpriteFlags(t->owner,SFLAG_SHADOW) || (pSprite->picnum == APLAYER && pSprite->owner >= 0)) + if ((!RR || (pSprite->cstat&48) == 0) && t->statnum != TSPR_TEMP && pSprite->picnum != EXPLOSION2 && (RR || pSprite->picnum != HANGLIGHT) && pSprite->picnum != DOMELITE && (RR || pSprite->picnum != HOTMEAT) + && (!RR || pSprite->picnum != TORNADO) && (!RR || pSprite->picnum != EXPLOSION3) && (!RR || RRRA || pSprite->picnum != SBMOVE)) + { + if (actor[i].dispicnum < 0) + { +#ifdef DEBUGGINGAIDS + // A negative actor[i].dispicnum used to mean 'no floor shadow please', but + // that was a bad hack since the value could propagate to sprite[].picnum. + OSD_Printf(OSD_ERROR "actor[%d].dispicnum = %d\n", i, actor[i].dispicnum); +#endif + actor[i].dispicnum=0; + continue; + } + + if (actor[i].flags & SFLAG_NOFLOORSHADOW) + continue; + + if (ud.shadows && spritesortcnt < (maxspritesonscreen-2) +#ifdef POLYMER + && !(videoGetRenderMode() == REND_POLYMER && pr_lighting != 0) +#endif + ) + { + if (RRRA && sector[sect].lotag == 160) continue; + int const shadowZ = ((sector[sect].lotag & 0xff) > 2 || pSprite->statnum == STAT_PROJECTILE || + pSprite->statnum == STAT_MISC || pSprite->picnum == DRONE || (!RR && pSprite->picnum == COMMANDER)) + ? sector[sect].floorz + : actor[i].floorz; + + if ((pSprite->z-shadowZ) < ZOFFSET3 && g_player[screenpeek].ps->pos.z < shadowZ) + { + uspritetype *const tsprShadow = &tsprite[spritesortcnt]; + + *tsprShadow = *t; + tsprShadow->statnum = TSPR_TEMP; + tsprShadow->yrepeat = (t->yrepeat >> 3); + + if (t->yrepeat < 4) + t->yrepeat = 4; + + tsprShadow->shade = 127; + tsprShadow->cstat |= 2; + tsprShadow->z = shadowZ; + tsprShadow->pal = ud.shadow_pal; + + +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST) + { + if (usemodels && md_tilehasmodel(t->picnum,t->pal) >= 0) + { + tsprShadow->yrepeat = 0; + // 512:trans reverse + //1024:tell MD2SPRITE.C to use Z-buffer hacks to hide overdraw issues + tsprShadow->extra |= TSPR_EXTRA_MDHACK; + tsprShadow->cstat |= 512; + } + else + { + int const ii + = getangle(tsprShadow->x - g_player[screenpeek].ps->pos.x, tsprShadow->y - g_player[screenpeek].ps->pos.y); + + tsprShadow->x += sintable[(ii+2560)&2047]>>9; + tsprShadow->y += sintable[(ii+2048)&2047]>>9; + } + } +#endif + spritesortcnt++; + } + } + } + + switch (DYNAMICTILEMAP(pSprite->picnum)) + { + case LASERLINE__STATIC: + if (RR) break; + if (sector[t->sectnum].lotag == ST_2_UNDERWATER) t->pal = 8; + t->z = sprite[pSprite->owner].z-(3<<8); + if (g_tripbombLaserMode == 2 && g_player[screenpeek].ps->heat_on == 0) + t->yrepeat = 0; + fallthrough__; + case EXPLOSION2BOT__STATIC: + case GROWSPARK__STATIC: + case SHRINKEREXPLOSION__STATIC: + case FLOORFLAME__STATIC: + if (RR) break; + fallthrough__; + case FREEZEBLAST__STATIC: + case ATOMICHEALTH__STATIC: + case FIRELASER__STATIC: + case SHRINKSPARK__STATIC: + case CHAINGUN__STATIC: + case RPG__STATIC: + case EXPLOSION2__STATIC: + case EXPLOSION3__STATICRR: + case OWHIP__STATICRR: + case UWHIP__STATICRR: + case RPG2__STATICRR: + case RRTILE1790__STATICRR: +rrcoolexplosion1: + if (RR && !RRRA && (pSprite->picnum == RPG2 || pSprite->picnum == RRTILE1790)) break; + if (t->picnum == EXPLOSION2) + { + g_player[screenpeek].ps->visibility = -127; + //g_restorePalette = 1; // JBF 20040101: why? + if (RR) + t->pal = 0; + } + else if (RR && t->picnum == FIRELASER) + t->picnum = FIRELASER+((totalclock>>2)&5); + t->shade = -127; + t->cstat |= 8192+1024; + break; + case UFOBEAM__STATICRR: + case RRTILE3586__STATICRR: + case RRTILE3587__STATICRR: + t->cstat |= 32768; + pSprite->cstat |= 32768; + break; + case DESTRUCTO__STATICRR: + t->cstat |= 32768; + break; + case FIRE__STATIC: + case FIRE2__STATIC: + if (RR && pSprite->picnum == FIRE2) break; + t->cstat |= 128; + fallthrough__; + case BURNING__STATIC: + case BURNING2__STATIC: + if (RR && pSprite->picnum == BURNING2) break; + if (sprite[pSprite->owner].picnum != TREE1 && sprite[pSprite->owner].picnum != TREE2) + t->z = sector[t->sectnum].floorz; + t->shade = -127; + fallthrough__; + case SMALLSMOKE__STATIC: + if (RR) break; + t->cstat |= 8192+1024; + break; + case COOLEXPLOSION1__STATIC: + if (RR) goto rrcoolexplosion1; + t->shade = -127; + t->cstat |= 8192+1024; + t->picnum += (pSprite->shade>>1); + break; + case WALLLIGHT3__STATIC: + case WALLLIGHT1__STATIC: + if (!RR) break; + fallthrough__; + case RRTILE3668__STATICRR: + case RRTILE3795__STATICRR: + case RRTILE5035__STATICRR: + case RRTILE7505__STATICRR: + case RRTILE7506__STATICRR: + case RRTILE7533__STATICRR: + case RRTILE8216__STATICRR: + case RRTILE8218__STATICRR: + case RRTILE8220__STATICRR: + if (!RRRA) break; + fallthrough__; + case RRTILE1878__STATICRR: + case RRTILE1952__STATICRR: + case RRTILE1953__STATICRR: + case RRTILE1990__STATICRR: + case RRTILE2050__STATICRR: + case RRTILE2056__STATICRR: + case RRTILE2072__STATICRR: + case RRTILE2075__STATICRR: + case RRTILE2083__STATICRR: + case RRTILE2097__STATICRR: + case RRTILE2156__STATICRR: + case RRTILE2157__STATICRR: + case RRTILE2158__STATICRR: + case RRTILE2159__STATICRR: + case RRTILE2160__STATICRR: + case RRTILE2161__STATICRR: + case RRTILE2175__STATICRR: + case RRTILE2176__STATICRR: + case RRTILE2357__STATICRR: + case RRTILE2564__STATICRR: + case RRTILE2573__STATICRR: + case RRTILE2574__STATICRR: + case RRTILE2583__STATICRR: + case RRTILE2604__STATICRR: + case RRTILE2689__STATICRR: + case RRTILE2893__STATICRR: + case RRTILE2894__STATICRR: + case RRTILE2915__STATICRR: + case RRTILE2945__STATICRR: + case RRTILE2946__STATICRR: + case RRTILE2947__STATICRR: + case RRTILE2948__STATICRR: + case RRTILE2949__STATICRR: + case RRTILE2977__STATICRR: + case RRTILE2978__STATICRR: + case RRTILE3116__STATICRR: + case RRTILE3171__STATICRR: + case RRTILE3216__STATICRR: + case RRTILE3720__STATICRR: + t->shade = -127; + break; + case CHEER__STATICRR: + if (!RRRA) break; + if (t->picnum >= CHEER + 102 && t->picnum <= CHEER + 151) + t->shade = -127; + break; + case MINION__STATICRR: + if (!RRRA) break; + if (t->pal == 19) + t->shade = -127; + break; + case BIKER__STATICRR: + if (!RRRA) break; + if (t->picnum >= BIKER + 54 && t->picnum <= BIKER + 58) + t->shade = -127; + else if (t->picnum >= BIKER + 84 && t->picnum <= BIKER + 88) + t->shade = -127; + break; + case BILLYRAY__STATICRR: + case BILLYRAYSTAYPUT__STATICRR: + if (!RRRA) break; + if (t->picnum >= BILLYRAY + 5 && t->picnum <= BILLYRAY + 9) + t->shade = -127; + break; + case RRTILE2034__STATICRR: + t->picnum = RRTILE2034 + (totalclock & 1); + break; + case RRTILE2944__STATICRR: + t->shade = -127; + t->picnum = RRTILE2944 + ((totalclock >> 2) & 4); + break; + case PLAYERONWATER__STATIC: +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(pSprite->picnum,pSprite->pal) >= 0 && !(spriteext[i].flags&SPREXT_NOTMD)) + { + frameOffset = 0; + t->cstat &= ~4; + } + else +#endif + frameOffset = getofs_viewtype_mirrored<5>(t->cstat, t->ang - oura); + + t->picnum = pSprite->picnum+frameOffset+((T1(i)<4)*5); + t->shade = sprite[pSprite->owner].shade; + + break; + + case MUD__STATICRR: + t->picnum = MUD+T2(i); + break; + case WATERSPLASH2__STATIC: + // WATERSPLASH_T2 + t->picnum = WATERSPLASH2+T2(i); + break; + case SHELL__STATIC: + t->picnum = pSprite->picnum+(T1(i)&1); + fallthrough__; + case SHOTGUNSHELL__STATIC: + t->cstat |= 12; + if (T1(i) > 2) t->cstat &= ~16; + else if (T1(i) > 1) t->cstat &= ~4; + break; + case FRAMEEFFECT1__STATIC: + if (pSprite->owner >= 0 && sprite[pSprite->owner].statnum < MAXSTATUS) + { + if (sprite[pSprite->owner].picnum == APLAYER) + if (ud.camerasprite == -1) + if (screenpeek == P_Get(pSprite->owner) && display_mirror == 0) + { + t->owner = -1; + break; + } + if ((sprite[pSprite->owner].cstat&32768) == 0) + { + if (!actor[pSprite->owner].dispicnum) + t->picnum = actor[i].t_data[1]; + else t->picnum = actor[pSprite->owner].dispicnum; + + if (RR && sprite[pSprite->owner].picnum == APLAYER) + t->picnum = SMALLSMOKE; + + if (!G_MaybeTakeOnFloorPal(t, sect)) + t->pal = sprite[pSprite->owner].pal; + + t->shade = sprite[pSprite->owner].shade; + t->ang = sprite[pSprite->owner].ang; + t->cstat = 2|sprite[pSprite->owner].cstat; + } + } + break; + + case CAMERA1__STATIC: + case RAT__STATIC: +#ifdef USE_OPENGL + if (videoGetRenderMode() >= REND_POLYMOST && usemodels && md_tilehasmodel(pSprite->picnum,pSprite->pal) >= 0 && !(spriteext[i].flags&SPREXT_NOTMD)) + { + t->cstat &= ~4; + break; + } +#endif + frameOffset = getofs_viewtype_mirrored<5>(t->cstat, t->ang - oura); + t->picnum = pSprite->picnum+frameOffset; + break; + } + + actor[i].dispicnum = t->picnum; +#if 0 + // why? + if (sector[t->sectnum].floorpicnum == MIRROR) + t->xrepeat = t->yrepeat = 0; +#endif + } + +#ifdef DEBUGGINGAIDS + g_spriteStat.numonscreen = spritesortcnt; +#endif +} + +void G_SetViewportShrink(int32_t dir) +{ + if (dir!=0) + { + if (dir > 0) // shrinking + { + if (ud.screen_size < 4 && (!(ud.statusbarflags & STATUSBAR_NOMINI) || !(ud.statusbarflags & STATUSBAR_NOMODERN))) + ud.screen_size = 4; + else if (ud.screen_size == 4 && ud.althud == 1 && !(ud.statusbarflags & STATUSBAR_NOMINI)) + ud.althud = 0; + else if (ud.screen_size == 4 && ud.statusbarcustom < ud.statusbarrange && !(ud.statusbarflags & STATUSBAR_NOMINI)) + ud.statusbarcustom += 1; + else if (ud.screen_size < 8 && (!(ud.statusbarflags & STATUSBAR_NOFULL) || !(ud.statusbarflags & STATUSBAR_NOOVERLAY))) + ud.screen_size = 8; + else if (ud.screen_size == 8 && ud.statusbarmode == 1 && !(ud.statusbarflags & STATUSBAR_NOFULL)) + ud.statusbarmode = 0; + else if (RR && ud.screen_size == 8) + ud.screen_size = 12; + else if (ud.screen_size < 64 && !(ud.statusbarflags & STATUSBAR_NOSHRINK)) + ud.screen_size += dir; + } + else // enlarging + { + if (ud.screen_size > (RR ? 16 : 12)) + ud.screen_size += dir; + else if (RR && ud.screen_size > 12 && (!(ud.statusbarflags & STATUSBAR_NOFULL) || !(ud.statusbarflags & STATUSBAR_NOOVERLAY))) + ud.screen_size = 12; + else if (ud.screen_size > 8 && (!(ud.statusbarflags & STATUSBAR_NOFULL) || !(ud.statusbarflags & STATUSBAR_NOOVERLAY))) + ud.screen_size = 8; + else if (ud.screen_size == 8 && ud.statusbarmode == 0 && !(ud.statusbarflags & STATUSBAR_NOOVERLAY)) + ud.statusbarmode = 1; + else if (ud.screen_size > 4 && (!(ud.statusbarflags & STATUSBAR_NOMINI) || !(ud.statusbarflags & STATUSBAR_NOMODERN))) + ud.screen_size = 4; + else if (ud.screen_size == 4 && ud.statusbarcustom > 0) + ud.statusbarcustom -= 1; + else if (ud.screen_size == 4 && ud.althud == 0 && !(ud.statusbarflags & STATUSBAR_NOMODERN)) + ud.althud = 1; + else if (ud.screen_size > 0 && !(ud.statusbarflags & STATUSBAR_NONONE)) + ud.screen_size = 0; + } + } + G_UpdateScreenArea(); +} + +void G_InitTimer(int32_t ticspersec) +{ + if (g_timerTicsPerSecond != ticspersec) + { + timerUninit(); + timerInit(ticspersec); + g_timerTicsPerSecond = ticspersec; + } +} + + +static int32_t g_RTSPlaying; + +// Returns: started playing? +extern int G_StartRTS(int lumpNum, int localPlayer) +{ + if (!ud.lockout && ud.config.SoundToggle && + RTS_IsInitialized() && g_RTSPlaying == 0 && (ud.config.VoiceToggle & (localPlayer ? 1 : 4))) + { + char *const pData = (char *)RTS_GetSound(lumpNum - 1); + + if (pData != NULL) + { + FX_Play3D(pData, RTS_SoundLength(lumpNum - 1), FX_ONESHOT, 0, 0, 1, 255, 1.f, -lumpNum); + g_RTSPlaying = 7; + return 1; + } + } + + return 0; +} + +void G_PrintCurrentMusic(void) +{ + Bsnprintf(apStrings[QUOTE_MUSIC], MAXQUOTELEN, "Playing %s", g_mapInfo[g_musicIndex].musicfn); + P_DoQuote(QUOTE_MUSIC, g_player[myconnectindex].ps); +} + +void G_HandleLocalKeys(void) +{ +// CONTROL_ProcessBinds(); + + if (ud.recstat == 2) + { + ControlInfo noshareinfo; + CONTROL_GetInput(&noshareinfo); + } + + if (g_player[myconnectindex].gotvote == 0 && voting != -1 && voting != myconnectindex) + { + if (KB_UnBoundKeyPressed(sc_F1) || KB_UnBoundKeyPressed(sc_F2) || ud.autovote) + { + G_AddUserQuote("Vote Cast"); + Net_SendMapVote(KB_UnBoundKeyPressed(sc_F1) || ud.autovote ? ud.autovote-1 : 0); + KB_ClearKeyDown(sc_F1); + KB_ClearKeyDown(sc_F2); + } + } + + if (!ALT_IS_PRESSED && ud.overhead_on == 0 && (g_player[myconnectindex].ps->gm & MODE_TYPE) == 0) + { + if (BUTTON(gamefunc_Enlarge_Screen)) + { + CONTROL_ClearButton(gamefunc_Enlarge_Screen); + + if (!SHIFTS_IS_PRESSED) + { + // conditions copied from G_SetViewportShrink + if ((ud.screen_size > (RR ? 16 : 12)) || + (RR && ud.screen_size > 12 && (!(ud.statusbarflags & STATUSBAR_NOFULL) || !(ud.statusbarflags & STATUSBAR_NOOVERLAY))) || + (ud.screen_size > 8 && (!(ud.statusbarflags & STATUSBAR_NOFULL) || !(ud.statusbarflags & STATUSBAR_NOOVERLAY))) || + (ud.screen_size == 8 && ud.statusbarmode == 0 && !(ud.statusbarflags & STATUSBAR_NOOVERLAY)) || + (ud.screen_size > 4 && (!(ud.statusbarflags & STATUSBAR_NOMINI) || !(ud.statusbarflags & STATUSBAR_NOMODERN))) || + (ud.screen_size == 4 && ud.statusbarcustom > 0) || + (ud.screen_size == 4 && ud.althud == 0 && !(ud.statusbarflags & STATUSBAR_NOMODERN)) || + (ud.screen_size > 0 && !(ud.statusbarflags & STATUSBAR_NONONE))) + { + S_PlaySound(RR ? 341 : THUD); + G_SetViewportShrink(-4); + } + } + else + { + G_SetStatusBarScale(ud.statusbarscale+4); + } + + G_UpdateScreenArea(); + } + + if (BUTTON(gamefunc_Shrink_Screen)) + { + CONTROL_ClearButton(gamefunc_Shrink_Screen); + + if (!SHIFTS_IS_PRESSED) + { + // conditions copied from G_SetViewportShrink + if ((ud.screen_size < 4 && (!(ud.statusbarflags & STATUSBAR_NOMINI) || !(ud.statusbarflags & STATUSBAR_NOMODERN))) || + (ud.screen_size == 4 && ud.althud == 1 && !(ud.statusbarflags & STATUSBAR_NOMINI)) || + (ud.screen_size == 4 && ud.statusbarcustom < ud.statusbarrange && !(ud.statusbarflags & STATUSBAR_NOMINI)) || + (ud.screen_size < 8 && (!(ud.statusbarflags & STATUSBAR_NOFULL) || !(ud.statusbarflags & STATUSBAR_NOOVERLAY))) || + (ud.screen_size == 8 && ud.statusbarmode == 1 && !(ud.statusbarflags & STATUSBAR_NOFULL)) || + (RR && ud.screen_size == 8) || + (ud.screen_size < 64 && !(ud.statusbarflags & STATUSBAR_NOSHRINK))) + { + S_PlaySound(RR ? 341 : THUD); + G_SetViewportShrink(+4); + } + } + else + { + G_SetStatusBarScale(ud.statusbarscale-4); + } + + G_UpdateScreenArea(); + } + } + + if (g_player[myconnectindex].ps->cheat_phase == 1 || (g_player[myconnectindex].ps->gm&(MODE_MENU|MODE_TYPE))) + return; + + if (BUTTON(gamefunc_See_Coop_View) && (GTFLAGS(GAMETYPE_COOPVIEW) || ud.recstat == 2)) + { + CONTROL_ClearButton(gamefunc_See_Coop_View); + screenpeek = connectpoint2[screenpeek]; + if (screenpeek == -1) screenpeek = 0; + g_restorePalette = -1; + } + + if ((g_netServer || ud.multimode > 1) && BUTTON(gamefunc_Show_Opponents_Weapon)) + { + CONTROL_ClearButton(gamefunc_Show_Opponents_Weapon); + ud.config.ShowOpponentWeapons = ud.showweapons = 1-ud.showweapons; + P_DoQuote(QUOTE_WEAPON_MODE_OFF-ud.showweapons,g_player[screenpeek].ps); + } + + if (BUTTON(gamefunc_Toggle_Crosshair)) + { + CONTROL_ClearButton(gamefunc_Toggle_Crosshair); + ud.crosshair = !ud.crosshair; + P_DoQuote(QUOTE_CROSSHAIR_OFF-ud.crosshair,g_player[screenpeek].ps); + } + + if (ud.overhead_on && BUTTON(gamefunc_Map_Follow_Mode)) + { + CONTROL_ClearButton(gamefunc_Map_Follow_Mode); + ud.scrollmode = 1-ud.scrollmode; + if (ud.scrollmode) + { + ud.folx = g_player[screenpeek].ps->opos.x; + ud.foly = g_player[screenpeek].ps->opos.y; + ud.fola = fix16_to_int(g_player[screenpeek].ps->oq16ang); + } + P_DoQuote(QUOTE_MAP_FOLLOW_OFF+ud.scrollmode,g_player[myconnectindex].ps); + } + + if (KB_UnBoundKeyPressed(sc_ScrollLock)) + { + KB_ClearKeyDown(sc_ScrollLock); + + switch (ud.recstat) + { + case 0: + if (SHIFTS_IS_PRESSED) + G_OpenDemoWrite(); + break; + case 1: + G_CloseDemoWrite(); + break; + } + } + + if (ud.recstat == 2) + { + if (KB_KeyPressed(sc_Space)) + { + KB_ClearKeyDown(sc_Space); + + g_demo_paused = !g_demo_paused; + g_demo_rewind = 0; + + if (g_demo_paused) + FX_StopAllSounds(); + } + + if (KB_KeyPressed(sc_Tab)) + { + KB_ClearKeyDown(sc_Tab); + g_demo_showStats = !g_demo_showStats; + } + +#if 0 + if (KB_KeyPressed(sc_kpad_Plus)) + { + G_InitTimer(240); + } + else if (KB_KeyPressed(sc_kpad_Minus)) + { + G_InitTimer(60); + } + else if (g_timerTicsPerSecond != 120) + { + G_InitTimer(120); + } +#endif + + if (KB_KeyPressed(sc_kpad_6)) + { + KB_ClearKeyDown(sc_kpad_6); + + int const fwdTics = (15 << (int)ALT_IS_PRESSED) << (2 * (int)SHIFTS_IS_PRESSED); + g_demo_goalCnt = g_demo_paused ? g_demo_cnt + 1 : g_demo_cnt + REALGAMETICSPERSEC * fwdTics; + g_demo_rewind = 0; + + if (g_demo_goalCnt > g_demo_totalCnt) + g_demo_goalCnt = 0; + else + Demo_PrepareWarp(); + } + else if (KB_KeyPressed(sc_kpad_4)) + { + KB_ClearKeyDown(sc_kpad_4); + + int const rewindTics = (15 << (int)ALT_IS_PRESSED) << (2 * (int)SHIFTS_IS_PRESSED); + g_demo_goalCnt = g_demo_paused ? g_demo_cnt - 1 : g_demo_cnt - REALGAMETICSPERSEC * rewindTics; + g_demo_rewind = 1; + + if (g_demo_goalCnt <= 0) + g_demo_goalCnt = 1; + + Demo_PrepareWarp(); + } + +#if 0 + // Enter a game from within a demo. + if (KB_KeyPressed(sc_Return) && ud.multimode==1) + { + KB_ClearKeyDown(sc_Return); + g_demo_cnt = g_demo_goalCnt = ud.reccnt = ud.pause_on = ud.recstat = ud.m_recstat = 0; + // XXX: probably redundant; this stuff needs an API anyway: + kclose(g_demo_recFilePtr); g_demo_recFilePtr = -1; + g_player[myconnectindex].ps->gm = MODE_GAME; + ready2send=1; // TODO: research this weird variable + screenpeek=myconnectindex; +// g_demo_paused=0; + } +#endif + } + + if (SHIFTS_IS_PRESSED || ALT_IS_PRESSED || WIN_IS_PRESSED) + { + int ridiculeNum = 0; + + // NOTE: sc_F1 .. sc_F10 are contiguous. sc_F11 is not sc_F10+1. + for (bssize_t j=sc_F1; j<=sc_F10; j++) + if (KB_UnBoundKeyPressed(j)) + { + KB_ClearKeyDown(j); + ridiculeNum = j - sc_F1 + 1; + break; + } + + if (ridiculeNum) + { + if (SHIFTS_IS_PRESSED) + { + if (ridiculeNum == 5 && g_player[myconnectindex].ps->fta > 0 && g_player[myconnectindex].ps->ftq == QUOTE_MUSIC) + { + const unsigned int maxi = VOLUMEALL ? MUS_FIRST_SPECIAL : 6; + + unsigned int MyMusicIndex = g_musicIndex; + do + { + ++MyMusicIndex; + if (MyMusicIndex >= maxi) + MyMusicIndex = 0; + } + while (S_TryPlayLevelMusic(MyMusicIndex)); + + G_PrintCurrentMusic(); + + return; + } + + G_AddUserQuote(ud.ridecule[ridiculeNum-1]); + +#ifndef NETCODE_DISABLE + tempbuf[0] = PACKET_MESSAGE; + tempbuf[1] = 255; + tempbuf[2] = 0; + Bstrcat(tempbuf+2,ud.ridecule[ridiculeNum-1]); + + ridiculeNum = 2+strlen(ud.ridecule[ridiculeNum-1]); + + tempbuf[ridiculeNum++] = myconnectindex; + + if (g_netClient) + enet_peer_send(g_netClientPeer, CHAN_CHAT, enet_packet_create(tempbuf, ridiculeNum, 0)); + else if (g_netServer) + enet_host_broadcast(g_netServer, CHAN_CHAT, enet_packet_create(tempbuf, ridiculeNum, 0)); +#endif + pus = NUMPAGES; + pub = NUMPAGES; + + return; + } + + // Not SHIFT -- that is, either some ALT or WIN. + if (G_StartRTS(ridiculeNum, 1)) + { +#ifndef NETCODE_DISABLE + if ((g_netServer || ud.multimode > 1)) + { + tempbuf[0] = PACKET_RTS; + tempbuf[1] = ridiculeNum; + tempbuf[2] = myconnectindex; + + if (g_netClient) + enet_peer_send(g_netClientPeer, CHAN_CHAT, enet_packet_create(tempbuf, 3, 0)); + else if (g_netServer) + enet_host_broadcast(g_netServer, CHAN_CHAT, enet_packet_create(tempbuf, 3, 0)); + } +#endif + pus = NUMPAGES; + pub = NUMPAGES; + + return; + } + } + } + + if (!ALT_IS_PRESSED && !SHIFTS_IS_PRESSED && !WIN_IS_PRESSED) + { + if ((g_netServer || ud.multimode > 1) && BUTTON(gamefunc_SendMessage)) + { + KB_FlushKeyboardQueue(); + CONTROL_ClearButton(gamefunc_SendMessage); + g_player[myconnectindex].ps->gm |= MODE_TYPE; + typebuf[0] = 0; + } + + if (KB_UnBoundKeyPressed(sc_F1)/* || (ud.show_help && I_AdvanceTrigger())*/) + { + KB_ClearKeyDown(sc_F1); + + Menu_Change(MENU_STORY); + S_PauseSounds(true); + Menu_Open(myconnectindex); + + if ((!g_netServer && ud.multimode < 2)) + { + ready2send = 0; + totalclock = ototalclock; + screenpeek = myconnectindex; + } + } + + // if((!net_server && ud.multimode < 2)) + { + if (ud.recstat != 2 && (!RRRA || ud.player_skill != 4) && (!RR || RRRA || ud.player_skill != 5) && KB_UnBoundKeyPressed(sc_F2)) + { + KB_ClearKeyDown(sc_F2); + +FAKE_F2: + if (sprite[g_player[myconnectindex].ps->i].extra <= 0) + { + P_DoQuote(QUOTE_SAVE_DEAD,g_player[myconnectindex].ps); + return; + } + + Menu_Change(MENU_SAVE); + + S_PauseSounds(true); + Menu_Open(myconnectindex); + + if ((!g_netServer && ud.multimode < 2)) + { + ready2send = 0; + totalclock = ototalclock; + screenpeek = myconnectindex; + } + } + + if ((!RRRA || ud.player_skill != 4) && (!RR || RRRA || ud.player_skill != 5) && KB_UnBoundKeyPressed(sc_F3)) + { + KB_ClearKeyDown(sc_F3); + +FAKE_F3: + Menu_Change(MENU_LOAD); + S_PauseSounds(true); + Menu_Open(myconnectindex); + + if ((!g_netServer && ud.multimode < 2) && ud.recstat != 2) + { + ready2send = 0; + totalclock = ototalclock; + } + + screenpeek = myconnectindex; + } + } + + if (KB_UnBoundKeyPressed(sc_F4)) + { + KB_ClearKeyDown(sc_F4); + + S_PauseSounds(true); + Menu_Open(myconnectindex); + + if ((!g_netServer && ud.multimode < 2) && ud.recstat != 2) + { + ready2send = 0; + totalclock = ototalclock; + } + + Menu_Change(MENU_SOUND_INGAME); + } + + if (KB_UnBoundKeyPressed(sc_F5) && ud.config.MusicToggle) + { + map_t *const pMapInfo = &g_mapInfo[g_musicIndex]; + char *const musicString = apStrings[QUOTE_MUSIC]; + + KB_ClearKeyDown(sc_F5); + + if (pMapInfo->musicfn != NULL) + Bsnprintf(musicString, MAXQUOTELEN, "%s. Use SHIFT-F5 to change.", pMapInfo->musicfn); + else + musicString[0] = '\0'; + + P_DoQuote(QUOTE_MUSIC, g_player[myconnectindex].ps); + } + + if ((BUTTON(gamefunc_Quick_Save) || g_doQuickSave == 1) && (!RRRA || ud.player_skill != 4) && (!RR || RRRA || ud.player_skill != 5) && (g_player[myconnectindex].ps->gm&MODE_GAME)) + { + CONTROL_ClearButton(gamefunc_Quick_Save); + + g_doQuickSave = 0; + + if (!g_lastusersave.isValid()) + goto FAKE_F2; + + KB_FlushKeyboardQueue(); + + if (sprite[g_player[myconnectindex].ps->i].extra <= 0) + { + P_DoQuote(QUOTE_SAVE_DEAD,g_player[myconnectindex].ps); + return; + } + + g_screenCapture = 1; + G_DrawRooms(myconnectindex,65536); + g_screenCapture = 0; + + if (g_lastusersave.isValid()) + { + savebrief_t & sv = g_lastusersave; + + // dirty hack... char 127 in last position indicates an auto-filled name + if (sv.name[MAXSAVEGAMENAME] == 127) + { + strncpy(sv.name, g_mapInfo[ud.volume_number * MAXLEVELS + ud.level_number].name, MAXSAVEGAMENAME); + sv.name[MAXSAVEGAMENAME] = 127; + } + + g_quickload = &sv; + G_SavePlayerMaybeMulti(sv); + } + } + + if (KB_UnBoundKeyPressed(sc_F7)) + { + KB_ClearKeyDown(sc_F7); + + if (!RRRA || (!g_player[myconnectindex].ps->on_motorcycle && !g_player[myconnectindex].ps->on_boat)) + { + g_player[myconnectindex].ps->over_shoulder_on = !g_player[myconnectindex].ps->over_shoulder_on; + + CAMERADIST = 0; + CAMERACLOCK = totalclock; + + P_DoQuote(QUOTE_VIEW_MODE_OFF + g_player[myconnectindex].ps->over_shoulder_on, g_player[myconnectindex].ps); + } + } + + if (KB_UnBoundKeyPressed(sc_F8)) + { + KB_ClearKeyDown(sc_F8); + + int const fta = !ud.fta_on; + ud.fta_on = 1; + P_DoQuote(fta ? QUOTE_MESSAGES_ON : QUOTE_MESSAGES_OFF, g_player[myconnectindex].ps); + ud.fta_on = fta; + } + + if ((BUTTON(gamefunc_Quick_Load) || g_doQuickSave == 2) && (!RRRA || ud.player_skill != 4) && (!RR || RRRA || ud.player_skill != 5) && (g_player[myconnectindex].ps->gm&MODE_GAME)) + { + CONTROL_ClearButton(gamefunc_Quick_Load); + + g_doQuickSave = 0; + + if (g_quickload == nullptr || !g_quickload->isValid()) + goto FAKE_F3; + else if (g_quickload->isValid()) + { + KB_FlushKeyboardQueue(); + KB_ClearKeysDown(); + S_PauseSounds(true); + if (G_LoadPlayerMaybeMulti(*g_quickload) != 0) + g_quickload->reset(); + } + } + + if (KB_UnBoundKeyPressed(sc_F10)) + { + KB_ClearKeyDown(sc_F10); + + Menu_Change(MENU_QUIT_INGAME); + S_PauseSounds(true); + Menu_Open(myconnectindex); + + if ((!g_netServer && ud.multimode < 2) && ud.recstat != 2) + { + ready2send = 0; + totalclock = ototalclock; + } + } + + if (KB_UnBoundKeyPressed(sc_F11)) + { + KB_ClearKeyDown(sc_F11); + + Menu_Change(MENU_COLCORR_INGAME); + S_PauseSounds(true); + Menu_Open(myconnectindex); + + if ((!g_netServer && ud.multimode < 2) && ud.recstat != 2) + { + ready2send = 0; + totalclock = ototalclock; + } + } + + if (ud.overhead_on != 0) + { + int const timerOffset = (totalclock - nonsharedtimer); + nonsharedtimer += timerOffset; + + if (BUTTON(gamefunc_Enlarge_Screen)) + g_player[myconnectindex].ps->zoom += mulscale6(timerOffset, max (g_player[myconnectindex].ps->zoom, 256)); + + if (BUTTON(gamefunc_Shrink_Screen)) + g_player[myconnectindex].ps->zoom -= mulscale6(timerOffset, max (g_player[myconnectindex].ps->zoom, 256)); + + g_player[myconnectindex].ps->zoom = clamp(g_player[myconnectindex].ps->zoom, 48, 2048); + } + } + + if (I_EscapeTrigger() && ud.overhead_on && g_player[myconnectindex].ps->newowner == -1) + { + I_EscapeTriggerClear(); + ud.last_overhead = ud.overhead_on; + ud.overhead_on = 0; + ud.scrollmode = 0; + G_UpdateScreenArea(); + } + + if (BUTTON(gamefunc_AutoRun) && (!RRRA || (!g_player[myconnectindex].ps->on_motorcycle && !g_player[myconnectindex].ps->on_boat))) + { + CONTROL_ClearButton(gamefunc_AutoRun); + ud.auto_run = 1-ud.auto_run; + P_DoQuote(QUOTE_RUN_MODE_OFF+ud.auto_run,g_player[myconnectindex].ps); + } + + if (BUTTON(gamefunc_Map)) + { + CONTROL_ClearButton(gamefunc_Map); + if (ud.last_overhead != ud.overhead_on && ud.last_overhead) + { + ud.overhead_on = ud.last_overhead; + ud.last_overhead = 0; + } + else + { + ud.overhead_on++; + if (ud.overhead_on == 3) ud.overhead_on = 0; + ud.last_overhead = ud.overhead_on; + } + +#ifdef __ANDROID__ + if (ud.overhead_on == 1) + ud.scrollmode = 0; + else if (ud.overhead_on == 2) + { + ud.scrollmode = 1; + ud.folx = g_player[screenpeek].ps->opos.x; + ud.foly = g_player[screenpeek].ps->opos.y; + ud.fola = g_player[screenpeek].ps->oang; + } +#endif + g_restorePalette = 1; + G_UpdateScreenArea(); + } +} + +static int32_t S_DefineAudioIfSupported(char **fn, const char *name) +{ +#if !defined HAVE_FLAC || !defined HAVE_VORBIS + const char *extension = Bstrrchr(name, '.'); +# if !defined HAVE_FLAC + if (extension && !Bstrcasecmp(extension, ".flac")) + return -2; +# endif +# if !defined HAVE_VORBIS + if (extension && !Bstrcasecmp(extension, ".ogg")) + return -2; +# endif +#endif + realloc_copy(fn, name); + return 0; +} + +static int32_t S_DefineSound(int sndidx, const char *name, int minpitch, int maxpitch, int priority, int type, int distance, float volume) +{ + if ((unsigned)sndidx >= MAXSOUNDS || S_DefineAudioIfSupported(&g_sounds[sndidx].filename, name)) + return -1; + + auto &snd = g_sounds[sndidx]; + + snd.ps = clamp(minpitch, INT16_MIN, INT16_MAX); + snd.pe = clamp(maxpitch, INT16_MIN, INT16_MAX); + snd.pr = priority & 255; + snd.m = type & ~SF_ONEINST_INTERNAL; + snd.vo = clamp(distance, INT16_MIN, INT16_MAX); + snd.volume = volume; + + if (snd.m & SF_LOOP) + snd.m |= SF_ONEINST_INTERNAL; + + return 0; +} + +// Returns: +// 0: all OK +// -1: ID declaration was invalid: +static int32_t S_DefineMusic(const char *ID, const char *name) +{ + int32_t sel = MUS_FIRST_SPECIAL; + + Bassert(ID != NULL); + + if (!Bstrcmp(ID,"intro")) + { + // nothing + } + else if (!Bstrcmp(ID,"briefing")) + { + sel++; + } + else if (!Bstrcmp(ID,"loading")) + { + sel += 2; + } + else + { + sel = G_GetMusicIdx(ID); + if (sel < 0) + return -1; + } + + return S_DefineAudioIfSupported(&g_mapInfo[sel].musicfn, name); +} + +static int parsedefinitions_game(scriptfile *, int); + +static void parsedefinitions_game_include(const char *fileName, scriptfile *pScript, const char *cmdtokptr, int const firstPass) +{ + scriptfile *included = scriptfile_fromfile(fileName); + + if (!included) + { + if (!Bstrcasecmp(cmdtokptr,"null") || pScript == NULL) // this is a bit overboard to prevent unused parameter warnings + { + // initprintf("Warning: Failed including %s as module\n", fn); + } +/* + else + { + initprintf("Warning: Failed including %s on line %s:%d\n", + fn, script->filename,scriptfile_getlinum(script,cmdtokptr)); + } +*/ + } + else + { + parsedefinitions_game(included, firstPass); + scriptfile_close(included); + } +} + +static void parsedefinitions_game_animsounds(scriptfile *pScript, const char * blockEnd, char const * fileName, dukeanim_t * animPtr) +{ + Bfree(animPtr->sounds); + + size_t numPairs = 0, allocSize = 4; + + animPtr->sounds = (animsound_t *)Xmalloc(allocSize * sizeof(animsound_t)); + animPtr->numsounds = 0; + + int defError = 1; + uint16_t lastFrameNum = 1; + + while (pScript->textptr < blockEnd) + { + int32_t frameNum; + int32_t soundNum; + + // HACK: we've reached the end of the list + // (hack because it relies on knowledge of + // how scriptfile_* preprocesses the text) + if (blockEnd - pScript->textptr == 1) + break; + + // would produce error when it encounters the closing '}' + // without the above hack + if (scriptfile_getnumber(pScript, &frameNum)) + break; + + defError = 1; + + if (scriptfile_getsymbol(pScript, &soundNum)) + break; + + // frame numbers start at 1 for us + if (frameNum <= 0) + { + initprintf("Error: frame number must be greater zero on line %s:%d\n", pScript->filename, + scriptfile_getlinum(pScript, pScript->ltextptr)); + break; + } + + if (frameNum < lastFrameNum) + { + initprintf("Error: frame numbers must be in (not necessarily strictly)" + " ascending order (line %s:%d)\n", + pScript->filename, scriptfile_getlinum(pScript, pScript->ltextptr)); + break; + } + + lastFrameNum = frameNum; + + if ((unsigned)soundNum >= MAXSOUNDS && soundNum != -1) + { + initprintf("Error: sound number #%d invalid on line %s:%d\n", soundNum, pScript->filename, + scriptfile_getlinum(pScript, pScript->ltextptr)); + break; + } + + if (numPairs >= allocSize) + { + allocSize *= 2; + animPtr->sounds = (animsound_t *)Xrealloc(animPtr->sounds, allocSize * sizeof(animsound_t)); + } + + defError = 0; + + animsound_t & sound = animPtr->sounds[numPairs]; + sound.frame = frameNum; + sound.sound = soundNum; + + ++numPairs; + } + + if (!defError) + { + animPtr->numsounds = numPairs; + // initprintf("Defined sound sequence for hi-anim \"%s\" with %d frame/sound pairs\n", + // hardcoded_anim_tokens[animnum].text, numpairs); + } + else + { + DO_FREE_AND_NULL(animPtr->sounds); + initprintf("Failed defining sound sequence for anim \"%s\".\n", fileName); + } +} + +static int parsedefinitions_game(scriptfile *pScript, int firstPass) +{ + int token; + char *pToken; + + static const tokenlist tokens[] = + { + { "include", T_INCLUDE }, + { "#include", T_INCLUDE }, + { "includedefault", T_INCLUDEDEFAULT }, + { "#includedefault", T_INCLUDEDEFAULT }, + { "loadgrp", T_LOADGRP }, + { "cachesize", T_CACHESIZE }, + { "noautoload", T_NOAUTOLOAD }, + { "music", T_MUSIC }, + { "sound", T_SOUND }, + { "cutscene", T_CUTSCENE }, + { "animsounds", T_ANIMSOUNDS }, + { "renamefile", T_RENAMEFILE }, + { "globalgameflags", T_GLOBALGAMEFLAGS }, + }; + + static const tokenlist soundTokens[] = + { + { "id", T_ID }, + { "file", T_FILE }, + { "minpitch", T_MINPITCH }, + { "maxpitch", T_MAXPITCH }, + { "priority", T_PRIORITY }, + { "type", T_TYPE }, + { "distance", T_DISTANCE }, + { "volume", T_VOLUME }, + }; + + static const tokenlist animTokens [] = + { + { "delay", T_DELAY }, + { "aspect", T_ASPECT }, + { "sounds", T_SOUND }, + { "forcefilter", T_FORCEFILTER }, + { "forcenofilter", T_FORCENOFILTER }, + { "texturefilter", T_TEXTUREFILTER }, + }; + + do + { + token = getatoken(pScript, tokens, ARRAY_SIZE(tokens)); + pToken = pScript->ltextptr; + + switch (token) + { + case T_LOADGRP: + { + char *fileName; + + pathsearchmode = 1; + if (!scriptfile_getstring(pScript,&fileName) && firstPass) + { + if (initgroupfile(fileName) == -1) + initprintf("Could not find file \"%s\".\n", fileName); + else + { + initprintf("Using file \"%s\" as game data.\n", fileName); + if (!g_noAutoLoad && !ud.config.NoAutoLoad) + G_DoAutoload(fileName); + } + } + + pathsearchmode = 0; + } + break; + case T_CACHESIZE: + { + int32_t cacheSize; + + if (scriptfile_getnumber(pScript, &cacheSize) || !firstPass) + break; + + if (cacheSize > 0) + MAXCACHE1DSIZE = cacheSize << 10; + } + break; + case T_INCLUDE: + { + char *fileName; + + if (!scriptfile_getstring(pScript, &fileName)) + parsedefinitions_game_include(fileName, pScript, pToken, firstPass); + + break; + } + case T_INCLUDEDEFAULT: + { + parsedefinitions_game_include(G_DefaultDefFile(), pScript, pToken, firstPass); + break; + } + case T_NOAUTOLOAD: + if (firstPass) + g_noAutoLoad = 1; + break; + case T_MUSIC: + { + char *tokenPtr = pScript->ltextptr; + char *musicID = NULL; + char *fileName = NULL; + char *musicEnd; + + if (scriptfile_getbraces(pScript, &musicEnd)) + break; + + while (pScript->textptr < musicEnd) + { + switch (getatoken(pScript, soundTokens, ARRAY_SIZE(soundTokens))) + { + case T_ID: scriptfile_getstring(pScript, &musicID); break; + case T_FILE: scriptfile_getstring(pScript, &fileName); break; + } + } + + if (!firstPass) + { + if (musicID==NULL) + { + initprintf("Error: missing ID for music definition near line %s:%d\n", + pScript->filename, scriptfile_getlinum(pScript,tokenPtr)); + break; + } + + if (fileName == NULL || check_file_exist(fileName)) + break; + + if (S_DefineMusic(musicID, fileName) == -1) + initprintf("Error: invalid music ID on line %s:%d\n", pScript->filename, scriptfile_getlinum(pScript, tokenPtr)); + } + } + break; + + case T_CUTSCENE: + { + char *fileName = NULL; + + scriptfile_getstring(pScript, &fileName); + + char *animEnd; + + if (scriptfile_getbraces(pScript, &animEnd)) + break; + + if (!firstPass) + { + dukeanim_t *animPtr = Anim_Find(fileName); + + if (!animPtr) + { + animPtr = Anim_Create(fileName); + animPtr->framedelay = 10; + animPtr->frameflags = 0; + } + + int32_t temp; + + while (pScript->textptr < animEnd) + { + switch (getatoken(pScript, animTokens, ARRAY_SIZE(animTokens))) + { + case T_DELAY: + scriptfile_getnumber(pScript, &temp); + animPtr->framedelay = temp; + break; + case T_ASPECT: + { + double dtemp, dtemp2; + scriptfile_getdouble(pScript, &dtemp); + scriptfile_getdouble(pScript, &dtemp2); + animPtr->frameaspect1 = dtemp; + animPtr->frameaspect2 = dtemp2; + break; + } + case T_SOUND: + { + char *animSoundsEnd = NULL; + if (scriptfile_getbraces(pScript, &animSoundsEnd)) + break; + parsedefinitions_game_animsounds(pScript, animSoundsEnd, fileName, animPtr); + break; + } + case T_FORCEFILTER: + animPtr->frameflags |= CUTSCENE_FORCEFILTER; + break; + case T_FORCENOFILTER: + animPtr->frameflags |= CUTSCENE_FORCENOFILTER; + break; + case T_TEXTUREFILTER: + animPtr->frameflags |= CUTSCENE_TEXTUREFILTER; + break; + } + } + } + else + pScript->textptr = animEnd; + } + break; + case T_ANIMSOUNDS: + { + char *tokenPtr = pScript->ltextptr; + char *fileName = NULL; + + scriptfile_getstring(pScript, &fileName); + if (!fileName) + break; + + char *animSoundsEnd = NULL; + + if (scriptfile_getbraces(pScript, &animSoundsEnd)) + break; + + if (firstPass) + { + pScript->textptr = animSoundsEnd; + break; + } + + dukeanim_t *animPtr = Anim_Find(fileName); + + if (!animPtr) + { + initprintf("Error: expected animation filename on line %s:%d\n", + pScript->filename, scriptfile_getlinum(pScript, tokenPtr)); + break; + } + + parsedefinitions_game_animsounds(pScript, animSoundsEnd, fileName, animPtr); + } + break; + + case T_SOUND: + { + char *tokenPtr = pScript->ltextptr; + char *fileName = NULL; + char *musicEnd; + + double volume = 1.0; + + int32_t soundNum = -1; + int32_t maxpitch = 0; + int32_t minpitch = 0; + int32_t priority = 0; + int32_t type = 0; + int32_t distance = 0; + + if (scriptfile_getbraces(pScript, &musicEnd)) + break; + + while (pScript->textptr < musicEnd) + { + switch (getatoken(pScript, soundTokens, ARRAY_SIZE(soundTokens))) + { + case T_ID: scriptfile_getsymbol(pScript, &soundNum); break; + case T_FILE: scriptfile_getstring(pScript, &fileName); break; + case T_MINPITCH: scriptfile_getsymbol(pScript, &minpitch); break; + case T_MAXPITCH: scriptfile_getsymbol(pScript, &maxpitch); break; + case T_PRIORITY: scriptfile_getsymbol(pScript, &priority); break; + case T_TYPE: scriptfile_getsymbol(pScript, &type); break; + case T_DISTANCE: scriptfile_getsymbol(pScript, &distance); break; + case T_VOLUME: scriptfile_getdouble(pScript, &volume); break; + } + } + + if (!firstPass) + { + if (soundNum==-1) + { + initprintf("Error: missing ID for sound definition near line %s:%d\n", pScript->filename, scriptfile_getlinum(pScript,tokenPtr)); + break; + } + + if (fileName == NULL || check_file_exist(fileName)) + break; + + // maybe I should have just packed this into a sound_t and passed a reference... + if (S_DefineSound(soundNum, fileName, minpitch, maxpitch, priority, type, distance, volume) == -1) + initprintf("Error: invalid sound ID on line %s:%d\n", pScript->filename, scriptfile_getlinum(pScript,tokenPtr)); + } + } + break; + case T_GLOBALGAMEFLAGS: scriptfile_getnumber(pScript, &duke3d_globalflags); break; + case T_EOF: return 0; + default: break; + } + } + while (1); + + return 0; +} + +int loaddefinitions_game(const char *fileName, int32_t firstPass) +{ + scriptfile *pScript = scriptfile_fromfile(fileName); + + if (pScript) + parsedefinitions_game(pScript, firstPass); + + for (char const * m : g_defModules) + parsedefinitions_game_include(m, NULL, "null", firstPass); + + if (pScript) + scriptfile_close(pScript); + + scriptfile_clearsymbols(); + + return 0; +} + + + +void G_UpdateAppTitle(void) +{ + if (g_gameNamePtr) + { + Bsprintf(tempbuf, "%s - " APPNAME, g_gameNamePtr); + wm_setapptitle(tempbuf); + } + else + { + wm_setapptitle(APPNAME); + } +} + +static void G_FreeHashAnim(const char * /*string*/, intptr_t key) +{ + Bfree((void *)key); +} + +static void G_Cleanup(void) +{ + ReadSaveGameHeaders(); // for culling + + int32_t i; + + for (i=(MAXLEVELS*(MAXVOLUMES+1))-1; i>=0; i--) // +1 volume for "intro", "briefing" music + { + Bfree(g_mapInfo[i].name); + Bfree(g_mapInfo[i].filename); + Bfree(g_mapInfo[i].musicfn); + + G_FreeMapState(i); + } + + for (i=MAXQUOTES-1; i>=0; i--) + { + Bfree(apStrings[i]); + Bfree(apXStrings[i]); + } + + for (i=MAXPLAYERS-1; i>=0; i--) + { + Bfree(g_player[i].ps); + Bfree(g_player[i].inputBits); + } + + for (i=MAXSOUNDS-1; i>=0; i--) + { + Bfree(g_sounds[i].filename); + } + if (label != (char *)&sprite[0]) Bfree(label); + if (labelcode != (int32_t *)§or[0]) Bfree(labelcode); + Bfree(apScript); + Bfree(bitptr); + +// Bfree(MusicPtr); + + hash_free(&h_labels); + hash_free(&h_gamefuncs); + + hash_loop(&h_dukeanim, G_FreeHashAnim); + hash_free(&h_dukeanim); + + Duke_CommonCleanup(); +} + +/* +=================== += += ShutDown += +=================== +*/ + +void G_Shutdown(void) +{ + CONFIG_WriteSetup(0); + S_SoundShutdown(); + S_MusicShutdown(); + CONTROL_Shutdown(); + KB_Shutdown(); + G_SetFog(0); + engineUnInit(); + G_Cleanup(); + FreeGroups(); + OSD_Cleanup(); + uninitgroupfile(); + Bfflush(NULL); +} + +/* +=================== += += G_Startup += +=================== +*/ + +static void G_CompileScripts(void) +{ + int32_t psm = pathsearchmode; + + label = (char *)&sprite[0]; // V8: 16384*44/64 = 11264 V7: 4096*44/64 = 2816 + labelcode = (int32_t *)§or[0]; // V8: 4096*40/4 = 40960 V7: 1024*40/4 = 10240 + labeltype = (int32_t *)&wall[0]; // V8: 16384*32/4 = 131072 V7: 8192*32/4 = 65536 + + if (g_scriptNamePtr != NULL) + Bcorrectfilename(g_scriptNamePtr,0); + + // if we compile for a V7 engine wall[] should be used for label names since it's bigger + pathsearchmode = 1; + + C_Compile(G_ConFile()); + + if (g_loadFromGroupOnly) // g_loadFromGroupOnly is true only when compiling fails and internal defaults are utilized + C_Compile(G_ConFile()); + + if ((uint32_t)g_labelCnt > MAXSPRITES*sizeof(spritetype)/64) // see the arithmetic above for why + G_GameExit("Error: too many labels defined!"); + + { + char *newlabel; + int32_t *newlabelcode; + int32_t *newlabeltype; + + newlabel = (char *)Xmalloc(g_labelCnt << 6); + newlabelcode = (int32_t *)Xmalloc(g_labelCnt * sizeof(int32_t)); + newlabeltype = (int32_t *)Xmalloc(g_labelCnt * sizeof(int32_t)); + + Bmemcpy(newlabel, label, g_labelCnt*64); + Bmemcpy(newlabelcode, labelcode, g_labelCnt*sizeof(int32_t)); + Bmemcpy(newlabeltype, labeltype, g_labelCnt*sizeof(int32_t)); + + label = newlabel; + labelcode = newlabelcode; + labeltype = newlabeltype; + } + + Bmemset(sprite, 0, MAXSPRITES*sizeof(spritetype)); + Bmemset(sector, 0, MAXSECTORS*sizeof(sectortype)); + Bmemset(wall, 0, MAXWALLS*sizeof(walltype)); + + pathsearchmode = psm; +} + +static inline void G_CheckGametype(void) +{ + ud.m_coop = clamp(ud.m_coop, 0, g_gametypeCnt-1); + initprintf("%s\n",g_gametypeNames[ud.m_coop]); + if (g_gametypeFlags[ud.m_coop] & GAMETYPE_ITEMRESPAWN) + ud.m_respawn_items = ud.m_respawn_inventory = 1; +} + +static void G_PostLoadPalette(void) +{ + if (!(duke3d_globalflags & DUKE3D_NO_PALETTE_CHANGES)) + { + // Make color index 255 of default/water/slime palette black. + if (basepaltable[BASEPAL] != NULL) + Bmemset(&basepaltable[BASEPAL][255*3], 0, 3); + if (basepaltable[WATERPAL] != NULL) + Bmemset(&basepaltable[WATERPAL][255*3], 0, 3); + if (basepaltable[SLIMEPAL] != NULL) + Bmemset(&basepaltable[SLIMEPAL][255*3], 0, 3); + } + + //if (!(duke3d_globalflags & DUKE3D_NO_HARDCODED_FOGPALS)) + // paletteSetupDefaultFog(); + + if (!(duke3d_globalflags & DUKE3D_NO_PALETTE_CHANGES)) + paletteFixTranslucencyMask(); + + palettePostLoadLookups(); +} + +#define SETFLAG(Tilenum, Flag) g_tile[Tilenum].flags |= Flag + +// Has to be after setting the dynamic names (e.g. SHARK). +static void A_InitEnemyFlags(void) +{ + if (RRRA) + { + int DukeEnemies[] = { + BOULDER, BOULDER1, EGG, RAT, TORNADO, BILLYCOCK, BILLYRAY, BILLYRAYSTAYPUT, + BRAYSNIPER, DOGRUN, LTH, HULKJUMP, BUBBASTAND, HULK, HULKSTAYPUT, HEN, + DRONE, PIG, RECON, MINION, MINIONSTAYPUT, UFO1, COOT, COOTSTAYPUT, SHARK, + VIXEN, SBSWIPE, BIKERB, BIKERBV2, BIKER, MAKEOUT, CHEERB, CHEER, CHEERSTAYPUT, + COOTPLAY, BILLYPLAY, MINIONBOAT, HULKBOAT, CHEERBOAT, RABBIT, MAMA }; + + int DukeEnemiesTile[] = { + BOULDER, BOULDER1, EGG, RAT, TORNADO, BILLYCOCK, BILLYRAY, BILLYRAYSTAYPUT, + BRAYSNIPER, DOGRUN, LTH, HULKJUMP, BUBBASTAND, HULK, HULKSTAYPUT, + DRONE, PIG, RECON, MINION, MINIONSTAYPUT, UFO1, COOT, COOTSTAYPUT, SHARK, + VIXEN, SBSWIPE, BIKERB, BIKERBV2, BIKER, MAKEOUT, CHEERB, CHEER, CHEERSTAYPUT, + COOTPLAY, BILLYPLAY, MINIONBOAT, HULKBOAT, CHEERBOAT, RABBIT, MAMA }; + + int KillCountEnemies[] = { + BOULDER, BOULDER1, EGG, RAT, TORNADO, BILLYCOCK, BILLYRAY, BILLYRAYSTAYPUT, + BRAYSNIPER, DOGRUN, LTH, HULKJUMP, BUBBASTAND, HULK, HULKSTAYPUT, + DRONE, PIG, RECON, MINION, MINIONSTAYPUT, UFO1, COOT, COOTSTAYPUT, SHARK, + VIXEN, SBSWIPE, BIKERB, BIKERBV2, BIKER, MAKEOUT, CHEERB, CHEER, CHEERSTAYPUT, + COOTPLAY, BILLYPLAY, MINIONBOAT, HULKBOAT, CHEERBOAT, RABBIT, MAMA, + ROCK, ROCK2 }; + + int SolidEnemies[] = { HULK, MAMA, BILLYPLAY, COOTPLAY, MAMACLOUD }; + int NoWaterDipEnemies[] = { DRONE }; + int NoCanSeeCheck[] = { + COOT, COOTSTAYPUT, VIXEN, BIKERB, BIKERBV2, CHEER, CHEERB, + CHEERSTAYPUT, MINIONBOAT, HULKBOAT, CHEERBOAT, RABBIT, COOTPLAY, + BILLYPLAY, MAKEOUT, MAMA }; + + for (bssize_t i = ARRAY_SIZE(DukeEnemies) - 1; i >= 0; i--) + SETFLAG(DukeEnemies[i], SFLAG_HARDCODED_BADGUY); + + for (bssize_t i = ARRAY_SIZE(DukeEnemiesTile) - 1; i >= 0; i--) + SETFLAG(DukeEnemiesTile[i], SFLAG_BADGUY_TILE); + + for (bssize_t i = ARRAY_SIZE(KillCountEnemies) - 1; i >= 0; i--) + SETFLAG(KillCountEnemies[i], SFLAG_KILLCOUNT); + + for (bssize_t i = ARRAY_SIZE(SolidEnemies) - 1; i >= 0; i--) + SETFLAG(SolidEnemies[i], SFLAG_NODAMAGEPUSH); + + for (bssize_t i = ARRAY_SIZE(NoWaterDipEnemies) - 1; i >= 0; i--) + SETFLAG(NoWaterDipEnemies[i], SFLAG_NOWATERDIP); + + for (bssize_t i = ARRAY_SIZE(NoCanSeeCheck) - 1; i >= 0; i--) + SETFLAG(NoCanSeeCheck[i], SFLAG_NOCANSEECHECK); + } + else if (RR) + { + int DukeEnemies[] = { + BOULDER, BOULDER1, EGG, RAT, TORNADO, BILLYCOCK, BILLYRAY, BILLYRAYSTAYPUT, + BRAYSNIPER, DOGRUN, LTH, HULKJUMP, BUBBASTAND, HULK, HULKSTAYPUT, HEN, + DRONE, PIG, RECON, SBMOVE, MINION, MINIONSTAYPUT, UFO1, UFO2, UFO3, UFO4, UFO5, + COOT, COOTSTAYPUT, SHARK, VIXEN }; + + int DukeEnemiesTile[] = { + BOULDER, BOULDER1, EGG, RAT, TORNADO, BILLYCOCK, BILLYRAY, BILLYRAYSTAYPUT, + BRAYSNIPER, DOGRUN, LTH, HULKJUMP, BUBBASTAND, HULK, HULKSTAYPUT, + DRONE, PIG, RECON, SBMOVE, MINION, MINIONSTAYPUT, UFO1, UFO2, UFO3, UFO4, UFO5, + COOT, COOTSTAYPUT, SHARK, VIXEN }; + + int KillCountEnemies[] = { + BOULDER, BOULDER1, EGG, RAT, TORNADO, BILLYCOCK, BILLYRAY, BILLYRAYSTAYPUT, + BRAYSNIPER, DOGRUN, LTH, HULKJUMP, BUBBASTAND, HULK, HULKSTAYPUT, + DRONE, PIG, RECON, SBMOVE, MINION, MINIONSTAYPUT, UFO1, UFO2, UFO3, UFO4, UFO5, + COOT, COOTSTAYPUT, SHARK, VIXEN }; + + int SolidEnemies[] = { HULK, SBMOVE }; + int NoWaterDipEnemies[] = { DRONE }; + int NoCanSeeCheck[] = { VIXEN }; + + for (bssize_t i = ARRAY_SIZE(DukeEnemies) - 1; i >= 0; i--) + SETFLAG(DukeEnemies[i], SFLAG_HARDCODED_BADGUY); + + for (bssize_t i = ARRAY_SIZE(DukeEnemiesTile) - 1; i >= 0; i--) + SETFLAG(DukeEnemiesTile[i], SFLAG_BADGUY_TILE); + + for (bssize_t i = ARRAY_SIZE(KillCountEnemies) - 1; i >= 0; i--) + SETFLAG(KillCountEnemies[i], SFLAG_KILLCOUNT); + + for (bssize_t i = ARRAY_SIZE(SolidEnemies) - 1; i >= 0; i--) + SETFLAG(SolidEnemies[i], SFLAG_NODAMAGEPUSH); + + for (bssize_t i = ARRAY_SIZE(NoWaterDipEnemies) - 1; i >= 0; i--) + SETFLAG(NoWaterDipEnemies[i], SFLAG_NOWATERDIP); + + for (bssize_t i = ARRAY_SIZE(NoCanSeeCheck) - 1; i >= 0; i--) + SETFLAG(NoCanSeeCheck[i], SFLAG_NOCANSEECHECK); + } + else + { + int DukeEnemies[] = { + SHARK, RECON, DRONE, + LIZTROOPONTOILET, LIZTROOPJUSTSIT, LIZTROOPSTAYPUT, LIZTROOPSHOOT, + LIZTROOPJETPACK, LIZTROOPDUCKING, LIZTROOPRUNNING, LIZTROOP, + OCTABRAIN, COMMANDER, COMMANDERSTAYPUT, PIGCOP, EGG, PIGCOPSTAYPUT, PIGCOPDIVE, + LIZMAN, LIZMANSPITTING, LIZMANFEEDING, LIZMANJUMP, ORGANTIC, + BOSS1, BOSS2, BOSS3, BOSS4, RAT, ROTATEGUN }; + + int SolidEnemies[] = { TANK, BOSS1, BOSS2, BOSS3, BOSS4, RECON, ROTATEGUN }; + int NoWaterDipEnemies[] = { OCTABRAIN, COMMANDER, DRONE }; + int GreenSlimeFoodEnemies[] = { LIZTROOP, LIZMAN, PIGCOP, NEWBEAST }; + + for (bssize_t i=GREENSLIME; i<=GREENSLIME+7; i++) + SETFLAG(i, SFLAG_HARDCODED_BADGUY|SFLAG_BADGUY_TILE); + + for (bssize_t i=ARRAY_SIZE(DukeEnemies)-1; i>=0; i--) + SETFLAG(DukeEnemies[i], SFLAG_HARDCODED_BADGUY|SFLAG_BADGUY_TILE); + + for (bssize_t i=ARRAY_SIZE(SolidEnemies)-1; i>=0; i--) + SETFLAG(SolidEnemies[i], SFLAG_NODAMAGEPUSH); + + for (bssize_t i=ARRAY_SIZE(NoWaterDipEnemies)-1; i>=0; i--) + SETFLAG(NoWaterDipEnemies[i], SFLAG_NOWATERDIP); + + for (bssize_t i=ARRAY_SIZE(GreenSlimeFoodEnemies)-1; i>=0; i--) + SETFLAG(GreenSlimeFoodEnemies[i], SFLAG_GREENSLIMEFOOD); + } +} +#undef SETFLAG + +static void G_SetupGameButtons(void); + +// Throw in everything here that needs to be called after a Lua game state +// recreation (or on initial startup in a non-Lunatic build.) +void G_PostCreateGameState(void) +{ + Net_SendClientInfo(); + A_InitEnemyFlags(); +} + +static void G_HandleMemErr(int32_t lineNum, const char *fileName, const char *funcName) +{ + static char msg[128]; + Bsnprintf(msg, sizeof(msg), "Out of memory in %s:%d (%s)\n", fileName, lineNum, funcName); +#ifdef DEBUGGINGAIDS + Bassert(0); +#endif + G_GameExit(msg); +} + +static void G_FatalEngineError(void) +{ + wm_msgbox("Build Engine Initialization Error", + "There was a problem initializing the Build engine: %s", engineerrstr); + G_Cleanup(); + ERRprintf("G_Startup: There was a problem initializing the Build engine: %s\n", engineerrstr); + exit(6); +} + +static void G_Startup(void) +{ + int32_t i; + + set_memerr_handler(&G_HandleMemErr); + + timerInit(TICRATE); + + initcrc32table(); + + G_CompileScripts(); + + if (engineInit()) + G_FatalEngineError(); + + G_InitDynamicTiles(); + G_InitDynamicSounds(); + + // These depend on having the dynamic tile and/or sound mappings set up: + G_InitMultiPsky(CLOUDYOCEAN, MOONSKY1, BIGORBIT1, LA); + G_PostCreateGameState(); + if (g_netServer || ud.multimode > 1) G_CheckGametype(); + + if (g_noSound) ud.config.SoundToggle = 0; + if (g_noMusic) ud.config.MusicToggle = 0; + + if (CommandName) + { + // Bstrncpy(szPlayerName, CommandName, 9); + // szPlayerName[10] = '\0'; + Bstrcpy(tempbuf,CommandName); + + while (Bstrlen(OSD_StripColors(tempbuf,tempbuf)) > 10) + tempbuf[Bstrlen(tempbuf)-1] = '\0'; + + Bstrncpyz(szPlayerName, tempbuf, sizeof(szPlayerName)); + } + + if (CommandMap) + { + if (VOLUMEONE) + { + initprintf("The -map option is available in the registered version only!\n"); + boardfilename[0] = 0; + } + else + { + char *dot, *slash; + + boardfilename[0] = '/'; + boardfilename[1] = 0; + Bstrcat(boardfilename, CommandMap); + + dot = Bstrrchr(boardfilename,'.'); + slash = Bstrrchr(boardfilename,'/'); + if (!slash) slash = Bstrrchr(boardfilename,'\\'); + + if ((!slash && !dot) || (slash && dot < slash)) + Bstrcat(boardfilename,".map"); + + Bcorrectfilename(boardfilename,0); + + i = kopen4loadfrommod(boardfilename,0); + if (i!=-1) + { + initprintf("Using level: \"%s\".\n",boardfilename); + kclose(i); + } + else + { + initprintf("Level \"%s\" not found.\n",boardfilename); + boardfilename[0] = 0; + } + } + } + + if (VOLUMEONE) + { + initprintf("*** You have run Duke Nukem 3D %d times. ***\n\n",ud.executions); + +#if 0//def _WIN32 + if (ud.executions >= 50 && !DUKEBETA) + { + initprintf("IT IS NOW TIME TO UPGRADE TO THE COMPLETE VERSION!\n"); + + Bsprintf(tempbuf, "You have run Duke Nukem 3D shareware %d times. It is now time to upgrade to the complete version!\n\n" + "Upgrade Duke Nukem 3D now?\n", ud.executions); + + if (wm_ynbox("Upgrade to the full version of Duke Nukem 3D","%s",tempbuf)) + { + SHELLEXECUTEINFOA sinfo; + char const *p = "http://store.steampowered.com/app/225140"; + + Bmemset(&sinfo, 0, sizeof(sinfo)); + sinfo.cbSize = sizeof(sinfo); + sinfo.fMask = SEE_MASK_CLASSNAME; + sinfo.lpVerb = "open"; + sinfo.lpFile = p; + sinfo.nShow = SW_SHOWNORMAL; + sinfo.lpClass = "http"; + + if (!ShellExecuteExA(&sinfo)) + G_GameExit("Error launching default system browser!"); + + quitevent = 1; + } + } +#endif + } + + for (i=0; i 1) + initprintf("Multiplayer initialized.\n"); + + { + char *cwd; + + if (g_modDir[0] != '/' && (cwd = getcwd(NULL, 0))) + { + Bchdir(g_modDir); + if (artLoadFiles("tiles000.art",MAXCACHE1DSIZE) < 0) + { + Bchdir(cwd); + if (artLoadFiles("tiles000.art",MAXCACHE1DSIZE) < 0) + G_GameExit("Failed loading art."); + } + Bchdir(cwd); +#ifndef __ANDROID__ //This crashes on *some* Android devices. Small onetime memory leak. TODO fix above function + Bfree(cwd); +#endif + + } + else if (artLoadFiles("tiles000.art",MAXCACHE1DSIZE) < 0) + G_GameExit("Failed loading art."); + } + + // Make the fullscreen nuke logo background non-fullbright. Has to be + // after dynamic tile remapping (from C_Compile) and loading tiles. + picanm[LOADSCREEN].sf |= PICANM_NOFULLBRIGHT_BIT; + +// initprintf("Loading palette/lookups...\n"); + G_LoadLookups(); + + screenpeek = myconnectindex; + + Bfflush(NULL); +} + +static void P_SetupMiscInputSettings(void) +{ + DukePlayer_t *ps = g_player[myconnectindex].ps; + + ps->aim_mode = ud.mouseaiming; + ps->auto_aim = ud.config.AutoAim; + ps->weaponswitch = ud.weaponswitch; +} + +void G_UpdatePlayerFromMenu(void) +{ + if (ud.recstat != 0) + return; + + if (numplayers > 1) + { + Net_SendClientInfo(); + if (sprite[g_player[myconnectindex].ps->i].picnum == APLAYER && sprite[g_player[myconnectindex].ps->i].pal != 1) + sprite[g_player[myconnectindex].ps->i].pal = g_player[myconnectindex].pcolor; + } + else + { + /*int32_t j = g_player[myconnectindex].ps->team;*/ + + P_SetupMiscInputSettings(); + g_player[myconnectindex].ps->palookup = g_player[myconnectindex].pcolor = ud.color; + + g_player[myconnectindex].pteam = ud.team; + + if (sprite[g_player[myconnectindex].ps->i].picnum == APLAYER && sprite[g_player[myconnectindex].ps->i].pal != 1) + sprite[g_player[myconnectindex].ps->i].pal = g_player[myconnectindex].pcolor; + } +} + +void G_BackToMenu(void) +{ + boardfilename[0] = 0; + if (ud.recstat == 1) G_CloseDemoWrite(); + ud.warp_on = 0; + g_player[myconnectindex].ps->gm = 0; + Menu_Open(myconnectindex); + Menu_Change(MENU_MAIN); + KB_FlushKeyboardQueue(); + G_UpdateAppTitle(); +} + +static int G_EndOfLevel(void) +{ + P_SetGamePalette(g_player[myconnectindex].ps, BASEPAL, 0); + P_UpdateScreenPal(g_player[myconnectindex].ps); + + if (g_player[myconnectindex].ps->gm&MODE_EOL) + { + G_CloseDemoWrite(); + + ready2send = 0; + + if (g_player[myconnectindex].ps->player_par > 0 && (g_player[myconnectindex].ps->player_par < ud.playerbest || ud.playerbest < 0) && + ud.display_bonus_screen == 1) + CONFIG_SetMapBestTime(g_loadedMapHack.md4, g_player[myconnectindex].ps->player_par); + + if (ud.display_bonus_screen == 1) + { + int32_t i = ud.screen_size; + ud.screen_size = 0; + G_UpdateScreenArea(); + ud.screen_size = i; + + if (!RRRA || (g_mostConcurrentPlayers > 1 && numplayers > 1)) + G_BonusScreen(0); + else + G_BonusScreenRRRA(0); + } + + // Clear potentially loaded per-map ART only after the bonus screens. + artClearMapArt(); + + if (ud.eog) + { + ud.eog = 0; + if ((!g_netServer && ud.multimode < 2)) + { + if (!VOLUMEALL) + G_DoOrderScreen(); + g_player[myconnectindex].ps->gm = 0; + Menu_Open(myconnectindex); + Menu_Change(MENU_MAIN); + return 2; + } + else + { + ud.m_level_number = 0; + ud.level_number = 0; + } + } + } + + ud.display_bonus_screen = 1; + ready2send = 0; + + if (numplayers > 1) + g_player[myconnectindex].ps->gm = MODE_GAME; + + if (G_EnterLevel(g_player[myconnectindex].ps->gm)) + { + G_BackToMenu(); + return 2; + } + + Net_WaitForServer(); + return 1; +} + +void app_crashhandler(void) +{ + G_CloseDemoWrite(); + G_GameQuit(); +} + +#if defined(_WIN32) && defined(DEBUGGINGAIDS) +// See FILENAME_CASE_CHECK in cache1d.c +static int32_t check_filename_casing(void) +{ + return !(g_player[myconnectindex].ps->gm&MODE_GAME); +} +#endif + +void G_MaybeAllocPlayer(int32_t pnum) +{ + if (g_player[pnum].ps == NULL) + g_player[pnum].ps = (DukePlayer_t *)Xcalloc(1, sizeof(DukePlayer_t)); + if (g_player[pnum].inputBits == NULL) + g_player[pnum].inputBits = (input_t *)Xcalloc(1, sizeof(input_t)); +} + + +int G_FPSLimit(void) +{ + static uint64_t nextPageTicks = 0; + static unsigned frameWaiting = 0; + + if (frameWaiting) + { + frameWaiting--; + videoNextPage(); + } + + uint64_t const frameTicks = timerGetTicksU64(); + + if (!r_maxfps || frameTicks >= nextPageTicks) + { + if (frameTicks >= nextPageTicks + g_frameDelay) + nextPageTicks = frameTicks; + + nextPageTicks += g_frameDelay; + frameWaiting++; + } + + return frameWaiting; +} + +// TODO: reorder (net)actor_t to eliminate slop and update assertion +EDUKE32_STATIC_ASSERT(sizeof(actor_t)%4 == 0); +EDUKE32_STATIC_ASSERT(sizeof(DukePlayer_t)%4 == 0); + +int app_main(int argc, char const * const * argv) +{ + playing_rr = 1; +#ifndef NETCODE_DISABLE + if (enet_initialize() != 0) + initprintf("An error occurred while initializing ENet.\n"); + else atexit(enet_deinitialize); +#endif + +#ifdef _WIN32 + if (!G_CheckCmdSwitch(argc, argv, "-noinstancechecking") && win_checkinstance()) + { + if (!wm_ynbox(APPNAME, "Another Build game is currently running. " + "Do you wish to continue starting this copy?")) + return 3; + } + + backgroundidle = 0; + +#ifdef DEBUGGINGAIDS + extern int32_t (*check_filename_casing_fn)(void); + check_filename_casing_fn = check_filename_casing; +#endif +#endif + + G_ExtPreInit(argc, argv); + +#ifdef __APPLE__ + if (!g_useCwd) + { + char cwd[BMAX_PATH]; + char *homedir = Bgethomedir(); + if (homedir) + Bsnprintf(cwd, sizeof(cwd), "%s/Library/Logs/" APPBASENAME ".log", homedir); + else + Bstrcpy(cwd, APPBASENAME ".log"); + OSD_SetLogFile(cwd); + Bfree(homedir); + } + else +#endif + OSD_SetLogFile(APPBASENAME ".log"); + + OSD_SetFunctions(GAME_drawosdchar, + GAME_drawosdstr, + GAME_drawosdcursor, + GAME_getcolumnwidth, + GAME_getrowheight, + GAME_clearbackground, + BGetTime, + GAME_onshowosd); + + wm_setapptitle(APPNAME); + + initprintf(HEAD2 " %s\n", s_buildRev); + PrintBuildInfo(); + + if (!g_useCwd) + G_AddSearchPaths(); + + g_skillCnt = 4; + ud.multimode = 1; + + // This needs to happen before G_CheckCommandLine() because G_GameExit() + // accesses g_player[0]. + G_MaybeAllocPlayer(0); + + G_CheckCommandLine(argc,argv); + + // This needs to happen afterwards, as G_CheckCommandLine() is where we set + // up the command-line-provided search paths (duh). + G_ExtInit(); + +#if defined(RENDERTYPEWIN) && defined(USE_OPENGL) + if (forcegl) initprintf("GL driver blacklist disabled.\n"); +#endif + + // used with binds for fast function lookup + hash_init(&h_gamefuncs); + for (bssize_t i=NUMGAMEFUNCTIONS-1; i>=0; i--) + { + if (gamefunctions[i][0] == '\0') + continue; + + char *str = Bstrtolower(Xstrdup(gamefunctions[i])); + hash_add(&h_gamefuncs,gamefunctions[i],i,0); + hash_add(&h_gamefuncs,str,i,0); + Bfree(str); + } + +#ifdef STARTUP_SETUP_WINDOW + int const readSetup = +#endif + CONFIG_ReadSetup(); + +//#if defined(_WIN32) +#if 0 + +// initprintf("build %d\n",(uint8_t)Batoi(BUILDDATE)); + + if (ud.config.CheckForUpdates == 1) + { + if (time(NULL) - ud.config.LastUpdateCheck > UPDATEINTERVAL) + { + initprintf("Checking for updates...\n"); + + ud.config.LastUpdateCheck = time(NULL); + + if (G_GetVersionFromWebsite(tempbuf)) + { + initprintf("Current version is %d",Batoi(tempbuf)); + + if (Batoi(tempbuf) > atoi(s_buildDate)) + { + if (wm_ynbox("EDuke32","A new version of EDuke32 is available. " + "Browse to http://www.eduke32.com now?")) + { + SHELLEXECUTEINFOA sinfo; + char const * p = "http://www.eduke32.com"; + + Bmemset(&sinfo, 0, sizeof(sinfo)); + sinfo.cbSize = sizeof(sinfo); + sinfo.fMask = SEE_MASK_CLASSNAME; + sinfo.lpVerb = "open"; + sinfo.lpFile = p; + sinfo.nShow = SW_SHOWNORMAL; + sinfo.lpClass = "http"; + + if (!ShellExecuteExA(&sinfo)) + initprintf("update: error launching browser!\n"); + } + } + else initprintf("... no updates available\n"); + } + else initprintf("update: failed to check for updates\n"); + } + } +#endif + + if (enginePreInit()) + { + wm_msgbox("Build Engine Initialization Error", + "There was a problem initializing the Build engine: %s", engineerrstr); + ERRprintf("app_main: There was a problem initializing the Build engine: %s\n", engineerrstr); + Bexit(2); + } + + if (Bstrcmp(g_setupFileName, SETUPFILENAME)) + initprintf("Using config file \"%s\".\n",g_setupFileName); + + G_ScanGroups(); + +#ifdef STARTUP_SETUP_WINDOW + if (readSetup < 0 || (!g_noSetup && (ud.configversion != BYTEVERSION_EDUKE32 || ud.config.ForceSetup)) || g_commandSetup) + { + if (quitevent || !startwin_run()) + { + engineUnInit(); + Bexit(0); + } + } +#endif + + g_logFlushWindow = 0; + G_LoadGroups(!g_noAutoLoad && !ud.config.NoAutoLoad); +// flushlogwindow = 1; + + if (!g_useCwd) + G_CleanupSearchPaths(); + + if (RR) + { + osdscale2 *= 0.5f; + osdrscale2 = 1.f / osdscale2; + g_cdTrack = -1; + } + + G_SetupCheats(); + + if (SHAREWARE) + g_Shareware = 1; + else + { + int const kFile = kopen4load("DUKESW.BIN",1); // JBF 20030810 + + if (kFile != -1) + { + g_Shareware = 1; + kclose(kFile); + } + } + + // gotta set the proper title after we compile the CONs if this is the full version + + G_UpdateAppTitle(); + + if (g_scriptDebug) + initprintf("CON debugging activated (level %d).\n",g_scriptDebug); + +#ifndef NETCODE_DISABLE + if (g_networkMode == NET_SERVER || g_networkMode == NET_DEDICATED_SERVER) + { + ENetAddress address = { ENET_HOST_ANY, g_netPort }; + g_netServer = enet_host_create(&address, MAXPLAYERS, CHAN_MAX, 0, 0); + + if (g_netServer == NULL) + initprintf("An error occurred while trying to create an ENet server host.\n"); + else initprintf("Multiplayer server initialized\n"); + } +#endif + numplayers = 1; + g_mostConcurrentPlayers = ud.multimode; // Lunatic needs this (player[] bound) + + if (!g_fakeMultiMode) + { + connectpoint2[0] = -1; + } + else + { + for (bssize_t i=0; i palette = BASEPAL; + + for (int i=1, j=numplayers; j team = g_player[j].pteam = i; + g_player[j].ps->weaponswitch = 3; + g_player[j].ps->auto_aim = 0; + i = 1-i; + } + + if (quitevent) return 4; + + Anim_Init(); + + const char *defsfile = G_DefFile(); + uint32_t stime = timerGetTicks(); + if (!loaddefinitionsfile(defsfile)) + { + uint32_t etime = timerGetTicks(); + initprintf("Definitions file \"%s\" loaded in %d ms.\n", defsfile, etime-stime); + } + loaddefinitions_game(defsfile, FALSE); + + for (char * m : g_defModules) + free(m); + g_defModules.clear(); + + if (enginePostInit()) + G_FatalEngineError(); + + G_PostLoadPalette(); + + tileDelete(MIRROR); + if (RR) + tileDelete(0); + if (RRRA) + tileDelete(13); + + if (numplayers == 1 && boardfilename[0] != 0) + { + ud.m_level_number = 7; + ud.m_volume_number = 0; + ud.warp_on = 1; + } + + // getnames(); + + if (g_netServer || ud.multimode > 1) + { + if (ud.warp_on == 0) + { + ud.m_monsters_off = 1; + ud.m_player_skill = 0; + } + } + + g_mostConcurrentPlayers = ud.multimode; // XXX: redundant? + + ++ud.executions; + CONFIG_WriteSetup(1); + CONFIG_ReadSetup(); + + char const * rtsname = g_rtsNamePtr ? g_rtsNamePtr : ud.rtsname; + RTS_Init(rtsname); + + ud.last_level = -1; + + initprintf("Initializing OSD...\n"); + + Bsprintf(tempbuf, HEAD2 " %s", s_buildRev); + OSD_SetVersion(tempbuf, 10,0); + OSD_SetParameters(0, 0, 0, 12, 2, 12, OSD_ERROR, OSDTEXT_RED, gamefunctions[gamefunc_Show_Console][0] == '\0' ? OSD_PROTECTED : 0); + registerosdcommands(); + + if (g_networkMode != NET_DEDICATED_SERVER) + { + if (CONTROL_Startup(controltype_keyboardandmouse, &BGetTime, TICRATE)) + { + ERRprintf("There was an error initializing the CONTROL system.\n"); + engineUnInit(); + Bexit(5); + } + + G_SetupGameButtons(); + CONFIG_SetupMouse(); + CONFIG_SetupJoystick(); + + CONTROL_JoystickEnabled = (ud.config.UseJoystick && CONTROL_JoyPresent); + CONTROL_MouseEnabled = (ud.config.UseMouse && CONTROL_MousePresent); + + // JBF 20040215: evil and nasty place to do this, but joysticks are evil and nasty too + for (bssize_t i=0; i 0) + initprintf("There was an error loading the sprite clipping map (status %d).\n", clipMapError); + + for (char * m : g_clipMapFiles) + free(m); + g_clipMapFiles.clear(); +#endif + + char *const setupFileName = Xstrdup(g_setupFileName); + char *const p = strtok(setupFileName, "."); + + if (!p || !Bstrcmp(g_setupFileName, SETUPFILENAME)) + Bsprintf(tempbuf, "settings.cfg"); + else + Bsprintf(tempbuf, "%s_settings.cfg", p); + + Bfree(setupFileName); + + OSD_Exec(tempbuf); + OSD_Exec("autoexec.cfg"); + + system_getcvars(); + + if (g_networkMode != NET_DEDICATED_SERVER) + { + if (videoSetGameMode(ud.config.ScreenMode,ud.config.ScreenWidth,ud.config.ScreenHeight,ud.config.ScreenBPP,ud.detail) < 0) + { + vec2_t const res[] = { + { ud.config.ScreenWidth, ud.config.ScreenHeight }, { 800, 600 }, { 640, 480 }, { 320, 240 }, + }; + +#ifdef USE_OPENGL + int const bpp[] = { 32, 16, 8 }; +#else + int const bpp[] = { 8 }; +#endif + + initprintf("Failure setting video mode %dx%dx%d %s! Attempting safer mode...\n", ud.config.ScreenWidth, ud.config.ScreenHeight, + ud.config.ScreenBPP, ud.config.ScreenMode ? "fullscreen" : "windowed"); + + int resIdx = 0; + int bppIdx = 0; + + while (videoSetGameMode(0, res[resIdx].x, res[resIdx].y, bpp[bppIdx], ud.detail) < 0) + { + initprintf("Failure setting video mode %dx%dx%d windowed! Attempting safer mode...\n", res[resIdx].x, res[resIdx].y, + bpp[bppIdx]); + + if (++bppIdx == ARRAY_SIZE(bpp)) + { + if (++resIdx == ARRAY_SIZE(res)) + G_GameExit("Unable to set failsafe video mode!"); + bppIdx = 0; + } + } + + ud.config.ScreenWidth = res[resIdx].x; + ud.config.ScreenHeight = res[resIdx].y; + ud.config.ScreenBPP = bpp[bppIdx]; + } + + videoSetPalette(ud.brightness>>2,g_player[myconnectindex].ps->palette,0); + + S_MusicStartup(); + S_SoundStartup(); + } + + // check if the minifont will support lowercase letters (3136-3161) + // there is room for them in tiles012.art between "[\]^_." and "{|}~" + minitext_lowercase = 1; + + for (bssize_t i = MINIFONT + ('a'-'!'); minitext_lowercase && i < MINIFONT + ('z'-'!') + 1; ++i) + minitext_lowercase &= (int)tileLoad(i); + + if (g_networkMode != NET_DEDICATED_SERVER) + { + Menu_Init(); + } + + ReadSaveGameHeaders(); + +#if 0 + // previously, passing -0 through -9 on the command line would load the save in that slot # + // this code should be reusable for a new parameter that takes a filename, if desired + if (/* havesavename */ && (!g_netServer && ud.multimode < 2)) + { + clearview(0L); + //g_player[myconnectindex].ps->palette = palette; + //G_FadePalette(0,0,0,0); + P_SetGamePalette(g_player[myconnectindex].ps, BASEPAL, 0); // JBF 20040308 + rotatesprite_fs(160<<16,100<<16,65536L,0,LOADSCREEN,0,0,2+8+64+BGSTRETCH); + menutext_center(105,"Loading saved game..."); + nextpage(); + + if (G_LoadPlayer(/* savefile */)) + /* havesavename = false; */ + } +#endif + + FX_StopAllSounds(); + S_ClearSoundLocks(); + + // getpackets(); + +MAIN_LOOP_RESTART: + totalclock = 0; + ototalclock = 0; + lockclock = 0; + + g_player[myconnectindex].ps->fta = 0; + for (int & q : user_quote_time) + q = 0; + + Menu_Change(MENU_MAIN); + + if (g_networkMode != NET_DEDICATED_SERVER) + { + G_GetCrosshairColor(); + G_SetCrosshairColor(CrosshairColors.r, CrosshairColors.g, CrosshairColors.b); + } + + if (ud.warp_on == 1) + { + G_NewGame_EnterLevel(); + // may change ud.warp_on in an error condition + } + + if (ud.warp_on == 0) + { + if ((g_netServer || ud.multimode > 1) && boardfilename[0] != 0) + { + ud.m_level_number = 7; + ud.m_volume_number = 0; + + if (ud.m_player_skill == 4) + ud.m_respawn_monsters = 1; + else ud.m_respawn_monsters = 0; + + for (bssize_t TRAVERSE_CONNECT(i)) + { + P_ResetWeapons(i); + P_ResetInventory(i); + } + + G_NewGame_EnterLevel(); + + Net_WaitForServer(); + } + else if (g_networkMode != NET_DEDICATED_SERVER) + G_DisplayLogo(); + + if (g_networkMode != NET_DEDICATED_SERVER) + { + if (G_PlaybackDemo()) + { + FX_StopAllSounds(); + g_noLogoAnim = 1; + goto MAIN_LOOP_RESTART; + } + } + } + else G_UpdateScreenArea(); + +// G_GameExit(" "); /// + +// ud.auto_run = ud.config.RunMode; + ud.showweapons = ud.config.ShowOpponentWeapons; + P_SetupMiscInputSettings(); + g_player[myconnectindex].pteam = ud.team; + + if (g_gametypeFlags[ud.coop] & GAMETYPE_TDM) + g_player[myconnectindex].ps->palookup = g_player[myconnectindex].pcolor = G_GetTeamPalette(g_player[myconnectindex].pteam); + else + { + if (ud.color) g_player[myconnectindex].ps->palookup = g_player[myconnectindex].pcolor = ud.color; + else g_player[myconnectindex].ps->palookup = g_player[myconnectindex].pcolor; + } + + ud.warp_on = 0; + KB_KeyDown[sc_Pause] = 0; // JBF: I hate the pause key + + do //main loop + { + if (handleevents() && quitevent) + { + KB_KeyDown[sc_Escape] = 1; + quitevent = 0; + } + + Net_GetPackets(); + + // only allow binds to function if the player is actually in a game (not in a menu, typing, et cetera) or demo + CONTROL_BindsEnabled = !!(g_player[myconnectindex].ps->gm & (MODE_GAME|MODE_DEMO)); + +#ifndef _WIN32 + // stdin -> OSD input for dedicated server + if (g_networkMode == NET_DEDICATED_SERVER) + { + int32_t nb; + char ch; + static uint32_t bufpos = 0; + static char buf[128]; +#ifndef GEKKO + int32_t flag = 1; + ioctl(0, FIONBIO, &flag); +#endif + if ((nb = read(0, &ch, 1)) > 0 && bufpos < sizeof(buf)) + { + if (ch != '\n') + buf[bufpos++] = ch; + + if (ch == '\n' || bufpos >= sizeof(buf)-1) + { + buf[bufpos] = 0; + OSD_Dispatch(buf); + bufpos = 0; + } + } + } + else +#endif + { + MUSIC_Update(); + G_HandleLocalKeys(); + } + + OSD_DispatchQueued(); + + char gameUpdate = false; + uint32_t gameUpdateStartTime = timerGetTicks(); + if (((g_netClient || g_netServer) || !(g_player[myconnectindex].ps->gm & (MODE_MENU|MODE_DEMO))) && totalclock >= ototalclock+TICSPERFRAME) + { + if (g_networkMode != NET_DEDICATED_SERVER) + { + if (RRRA && g_player[myconnectindex].ps->on_motorcycle) + P_GetInputMotorcycle(myconnectindex); + else if (RRRA && g_player[myconnectindex].ps->on_boat) + P_GetInputBoat(myconnectindex); + else + P_GetInput(myconnectindex); + } + + Bmemcpy(&inputfifo[0][myconnectindex], &localInput, sizeof(input_t)); + + S_Update(); + + do + { + timerUpdate(); + + if (ready2send == 0) break; + + ototalclock += TICSPERFRAME; + + int const moveClock = totalclock; + + if (((ud.show_help == 0 && (g_player[myconnectindex].ps->gm&MODE_MENU) != MODE_MENU) || ud.recstat == 2 || (g_netServer || ud.multimode > 1)) && + (g_player[myconnectindex].ps->gm&MODE_GAME)) + { + G_MoveLoop(); +#ifdef __ANDROID__ + inputfifo[0][myconnectindex].fvel = 0; + inputfifo[0][myconnectindex].svel = 0; + inputfifo[0][myconnectindex].avel = 0; + inputfifo[0][myconnectindex].horz = 0; +#endif + } + + timerUpdate(); + + if (totalclock - moveClock >= TICSPERFRAME) + { + // computing a tic takes longer than a tic, so we're slowing + // the game down. rather than tightly spinning here, go draw + // a frame since we're fucked anyway + break; + } + } + while (((g_netClient || g_netServer) || !(g_player[myconnectindex].ps->gm & (MODE_MENU|MODE_DEMO))) && totalclock >= ototalclock+TICSPERFRAME); + + gameUpdate = true; + g_gameUpdateTime = timerGetTicks()-gameUpdateStartTime; + if (g_gameUpdateAvgTime < 0.f) + g_gameUpdateAvgTime = g_gameUpdateTime; + g_gameUpdateAvgTime = ((GAMEUPDATEAVGTIMENUMSAMPLES-1.f)*g_gameUpdateAvgTime+g_gameUpdateTime)/((float) GAMEUPDATEAVGTIMENUMSAMPLES); + } + + G_DoCheats(); + + if (g_player[myconnectindex].ps->gm & (MODE_EOL|MODE_RESTART)) + { + switch (G_EndOfLevel()) + { + case 1: continue; + case 2: goto MAIN_LOOP_RESTART; + } + } + + if (g_networkMode == NET_DEDICATED_SERVER) + { + idle(); + } + else if (G_FPSLimit() || g_saveRequested) + { + int const smoothRatio + = ((ud.show_help == 0 && (!g_netServer && ud.multimode < 2) && !(g_player[myconnectindex].ps->gm & MODE_MENU)) + || (g_netServer || ud.multimode > 1) + || ud.recstat == 2) + ? calc_smoothratio(totalclock, ototalclock) + : 65536; + + G_DrawRooms(screenpeek, smoothRatio); + if (videoGetRenderMode() >= REND_POLYMOST) + G_DrawBackground(); + G_DisplayRest(smoothRatio); + + if (gameUpdate) + { + g_gameUpdateAndDrawTime = timerGetTicks()-gameUpdateStartTime; + } + } + + // handle CON_SAVE and CON_SAVENN + if (g_saveRequested) + { + KB_FlushKeyboardQueue(); + videoNextPage(); + + g_screenCapture = 1; + G_DrawRooms(myconnectindex, 65536); + g_screenCapture = 0; + + G_SavePlayerMaybeMulti(g_lastautosave, true); + g_quickload = &g_lastautosave; + + OSD_Printf("Saved: %s\n", g_lastautosave.path); + + g_saveRequested = false; + } + + if (g_player[myconnectindex].ps->gm&MODE_DEMO) + goto MAIN_LOOP_RESTART; + } + while (1); + + return 0; // not reached (duh) +} + +GAME_STATIC GAME_INLINE int32_t G_MoveLoop() +{ + Net_GetPackets(); + + return G_DoMoveThings(); +} + +int G_DoMoveThings(void) +{ + ud.camerasprite = -1; + lockclock += TICSPERFRAME; + + // Moved lower so it is restored correctly by demo diffs: + //if (g_earthquakeTime > 0) g_earthquakeTime--; + + if (g_RTSPlaying > 0) + g_RTSPlaying--; + + for (int & i : user_quote_time) + { + if (i) + { + if (--i > ud.msgdisptime) + i = ud.msgdisptime; + if (!i) pub = NUMPAGES; + } + } + + // Name display when aiming at opponents + if (ud.idplayers && (g_netServer || ud.multimode > 1) +#ifdef SPLITSCREEN_MOD_HACKS + && !g_fakeMultiMode +#endif + ) + { + hitdata_t hitData; + DukePlayer_t *const pPlayer = g_player[screenpeek].ps; + + for (bssize_t TRAVERSE_CONNECT(i)) + if (g_player[i].ps->holoduke_on != -1) + sprite[g_player[i].ps->holoduke_on].cstat ^= 256; + + hitscan((vec3_t *)pPlayer, pPlayer->cursectnum, sintable[(fix16_to_int(pPlayer->q16ang) + 512) & 2047], + sintable[fix16_to_int(pPlayer->q16ang) & 2047], fix16_to_int(F16(100) - pPlayer->q16horiz - pPlayer->q16horizoff) << 11, &hitData, + 0xffff0030); + + for (bssize_t TRAVERSE_CONNECT(i)) + if (g_player[i].ps->holoduke_on != -1) + sprite[g_player[i].ps->holoduke_on].cstat ^= 256; + + if ((hitData.sprite >= 0) && !(g_player[myconnectindex].ps->gm & MODE_MENU) && + sprite[hitData.sprite].picnum == APLAYER) + { + int const playerNum = P_Get(hitData.sprite); + + if (playerNum != screenpeek && g_player[playerNum].ps->dead_flag == 0) + { + if (pPlayer->fta == 0 || pPlayer->ftq == QUOTE_RESERVED3) + { + if (ldist(&sprite[pPlayer->i], &sprite[hitData.sprite]) < 9216) + { + Bsprintf(apStrings[QUOTE_RESERVED3], "%s", &g_player[playerNum].user_name[0]); + pPlayer->fta = 12, pPlayer->ftq = QUOTE_RESERVED3; + } + } + else if (pPlayer->fta > 2) pPlayer->fta -= 3; + } + } + } + + if (g_showShareware > 0) + { + g_showShareware--; + if (g_showShareware == 0) + { + pus = NUMPAGES; + pub = NUMPAGES; + } + } + + // Moved lower so it is restored correctly by diffs: +// everyothertime++; + + if (g_netClient) // [75] The server should not overwrite its own randomseed + randomseed = ticrandomseed; + + for (bssize_t TRAVERSE_CONNECT(i)) + Bmemcpy(g_player[i].inputBits, &inputfifo[(g_netServer && myconnectindex == i)][i], sizeof(input_t)); + + G_UpdateInterpolations(); + + /* + j = -1; + for (TRAVERSE_CONNECT(i)) + { + if (g_player[i].playerquitflag == 0 || TEST_SYNC_KEY(g_player[i].sync->bits,SK_GAMEQUIT) == 0) + { + j = i; + continue; + } + + G_CloseDemoWrite(); + + g_player[i].playerquitflag = 0; + } + */ + + g_moveThingsCount++; + + if (ud.recstat == 1) G_DemoRecord(); + + everyothertime++; + if (g_earthquakeTime > 0) g_earthquakeTime--; + + if (ud.pause_on == 0) + { + g_globalRandom = krand2(); + A_MoveDummyPlayers();//ST 13 + } + + for (bssize_t TRAVERSE_CONNECT(i)) + { + if (g_player[i].inputBits->extbits&(1<<6)) + { + g_player[i].ps->team = g_player[i].pteam; + if (g_gametypeFlags[ud.coop] & GAMETYPE_TDM) + { + actor[g_player[i].ps->i].picnum = APLAYERTOP; + P_QuickKill(g_player[i].ps); + } + } + if (g_gametypeFlags[ud.coop] & GAMETYPE_TDM) + g_player[i].ps->palookup = g_player[i].pcolor = G_GetTeamPalette(g_player[i].ps->team); + + if (sprite[g_player[i].ps->i].pal != 1) + sprite[g_player[i].ps->i].pal = g_player[i].pcolor; + + P_HandleSharedKeys(i); + + if (ud.pause_on == 0) + { + P_ProcessInput(i); + P_CheckSectors(i); + } + } + + if (ud.pause_on == 0) + G_MoveWorld(); + +// Net_CorrectPrediction(); + + if (g_netServer) + Net_SendServerUpdates(); + + if ((everyothertime&1) == 0) + { + G_AnimateWalls(); + A_MoveCyclers(); + + if (g_netServer && (everyothertime % 10) == 0) + { + Net_SendMapUpdate(); + } + } + + if (g_netClient) //Slave + Net_SendClientUpdate(); + + if (RR && ud.recstat == 0 && ud.multimode < 2 && g_torchCnt) + G_DoTorch(); + + return 0; +} + +void A_SpawnWallGlass(int spriteNum, int wallNum, int glassCnt) +{ + if (wallNum < 0) + { + for (bssize_t j = glassCnt - 1; j >= 0; --j) + { + int const a = SA(spriteNum) - 256 + (krand2() & 511) + 1024; + int32_t const r1 = krand2(), r2 = krand2(); + A_InsertSprite(SECT(spriteNum), SX(spriteNum), SY(spriteNum), SZ(spriteNum), GLASSPIECES + (j % 3), -32, 36, 36, a, + 32 + (r2 & 63), 1024 - (r1 & 1023), spriteNum, 5); + } + return; + } + + vec2_t v1 = { wall[wallNum].x, wall[wallNum].y }; + vec2_t v = { wall[wall[wallNum].point2].x - v1.x, wall[wall[wallNum].point2].y - v1.y }; + + v1.x -= ksgn(v.y); + v1.y += ksgn(v.x); + + v.x = tabledivide32_noinline(v.x, glassCnt+1); + v.y = tabledivide32_noinline(v.y, glassCnt+1); + + int16_t sect = -1; + + for (bsize_t j = glassCnt; j > 0; --j) + { + v1.x += v.x; + v1.y += v.y; + + updatesector(v1.x,v1.y,§); + if (sect >= 0) + { + int z = sector[sect].floorz - (krand2() & (klabs(sector[sect].ceilingz - sector[sect].floorz))); + + if (z < -ZOFFSET5 || z > ZOFFSET5) + z = SZ(spriteNum) - ZOFFSET5 + (krand2() & ((64 << 8) - 1)); + + int32_t const r1 = krand2(), r2 = krand2(); + A_InsertSprite(SECT(spriteNum), v1.x, v1.y, z, GLASSPIECES + (j % 3), -32, 36, 36, SA(spriteNum) - 1024, 32 + (r2 & 63), + -(r1 & 1023), spriteNum, 5); + } + } +} + +void A_SpawnWallPopcorn(int spriteNum, int wallNum, int glassCnt) +{ + if (wallNum < 0) + { + for (bssize_t j = glassCnt - 1; j >= 0; --j) + { + int const a = SA(spriteNum) - 256 + (krand2() & 511) + 1024; + int32_t const r1 = krand2(), r2 = krand2(); + A_InsertSprite(SECT(spriteNum), SX(spriteNum), SY(spriteNum), SZ(spriteNum), POPCORN, -32, 36, 36, a, + 32 + (r2 & 63), 1024 - (r1 & 1023), spriteNum, 5); + } + return; + } + + vec2_t v1 = { wall[wallNum].x, wall[wallNum].y }; + vec2_t v = { wall[wall[wallNum].point2].x - v1.x, wall[wall[wallNum].point2].y - v1.y }; + + v1.x -= ksgn(v.y); + v1.y += ksgn(v.x); + + v.x = tabledivide32_noinline(v.x, glassCnt+1); + v.y = tabledivide32_noinline(v.y, glassCnt+1); + + int16_t sect = -1; + + for (bsize_t j = glassCnt; j > 0; --j) + { + v1.x += v.x; + v1.y += v.y; + + updatesector(v1.x,v1.y,§); + if (sect >= 0) + { + int z = sector[sect].floorz - (krand2() & (klabs(sector[sect].ceilingz - sector[sect].floorz))); + + if (z < -ZOFFSET5 || z > ZOFFSET5) + z = SZ(spriteNum) - ZOFFSET5 + (krand2() & ((64 << 8) - 1)); + + int32_t const r1 = krand2(), r2 = krand2(); + A_InsertSprite(SECT(spriteNum), v1.x, v1.y, z, POPCORN, -32, 36, 36, SA(spriteNum) - 1024, 32 + (r2 & 63), + -(r1 & 1023), spriteNum, 5); + } + } +} + +void A_SpawnGlass(int spriteNum, int glassCnt) +{ + for (; glassCnt>0; glassCnt--) + { + int const a = krand2()&2047; + int const z = SZ(spriteNum)-((krand2()&16)<<8); + int32_t const r1 = krand2(), r2 = krand2(), r3 = krand2(); + int const k + = A_InsertSprite(SECT(spriteNum), SX(spriteNum), SY(spriteNum), z, GLASSPIECES + (glassCnt % 3), + r3 & 15, 36, 36, a, 32 + (r2 & 63), -512 - (r1 & 2047), spriteNum, 5); + sprite[k].pal = sprite[spriteNum].pal; + } +} + +void A_SpawnCeilingGlass(int spriteNum, int sectNum, int glassCnt) +{ + int const startWall = sector[sectNum].wallptr; + int const endWall = startWall+sector[sectNum].wallnum; + + for (bssize_t wallNum = startWall; wallNum < (endWall - 1); wallNum++) + { + vec2_t v1 = { wall[wallNum].x, wall[wallNum].y }; + vec2_t v = { tabledivide32_noinline(wall[wallNum + 1].x - v1.x, glassCnt + 1), + tabledivide32_noinline(wall[wallNum + 1].y - v1.y, glassCnt + 1) }; + + for (bsize_t j = glassCnt; j > 0; j--) + { + v1.x += v.x; + v1.y += v.y; + int const a = krand2()&2047; + int const z = sector[sectNum].ceilingz+((krand2()&15)<<8); + A_InsertSprite(sectNum, v1.x, v1.y, z, GLASSPIECES + (j % 3), -32, 36, 36, + a, (krand2() & 31), 0, spriteNum, 5); + } + } +} + +void A_SpawnRandomGlass(int spriteNum, int wallNum, int glassCnt) +{ + if (wallNum < 0) + { + for (bssize_t j = glassCnt - 1; j >= 0; j--) + { + int const a = krand2() & 2047; + int32_t const r1 = krand2(), r2 = krand2(), r3 = krand2(); + int const k + = A_InsertSprite(SECT(spriteNum), SX(spriteNum), SY(spriteNum), SZ(spriteNum) - (r3 & (63 << 8)), GLASSPIECES + (j % 3), + -32, 36, 36, a, 32 + (r2 & 63), 1024 - (r1 & 2047), spriteNum, 5); + sprite[k].pal = krand2() & 15; + } + return; + } + + vec2_t v1 = { wall[wallNum].x, wall[wallNum].y }; + vec2_t v = { tabledivide32_noinline(wall[wall[wallNum].point2].x - wall[wallNum].x, glassCnt + 1), + tabledivide32_noinline(wall[wall[wallNum].point2].y - wall[wallNum].y, glassCnt + 1) }; + int16_t sectNum = sprite[spriteNum].sectnum; + + for (bsize_t j = glassCnt; j > 0; j--) + { + v1.x += v.x; + v1.y += v.y; + + updatesector(v1.x, v1.y, §Num); + + int z = sector[sectNum].floorz - (krand2() & (klabs(sector[sectNum].ceilingz - sector[sectNum].floorz))); + + if (z < -ZOFFSET5 || z > ZOFFSET5) + z = SZ(spriteNum) - ZOFFSET5 + (krand2() & ((64 << 8) - 1)); + + int32_t const r1 = krand2(), r2 = krand2(); + int const k = A_InsertSprite(SECT(spriteNum), v1.x, v1.y, z, GLASSPIECES + (j % 3), -32, 36, 36, SA(spriteNum) - 1024, + 32 + (r2 & 63), -(r1 & 2047), spriteNum, 5); + sprite[k].pal = krand2() & 7; + } +} + +static void G_SetupGameButtons(void) +{ + CONTROL_DefineFlag(gamefunc_Move_Forward,FALSE); + CONTROL_DefineFlag(gamefunc_Move_Backward,FALSE); + CONTROL_DefineFlag(gamefunc_Turn_Left,FALSE); + CONTROL_DefineFlag(gamefunc_Turn_Right,FALSE); + CONTROL_DefineFlag(gamefunc_Strafe,FALSE); + CONTROL_DefineFlag(gamefunc_Fire,FALSE); + CONTROL_DefineFlag(gamefunc_Open,FALSE); + CONTROL_DefineFlag(gamefunc_Run,FALSE); + CONTROL_DefineFlag(gamefunc_AutoRun,FALSE); + CONTROL_DefineFlag(gamefunc_Jump,FALSE); + CONTROL_DefineFlag(gamefunc_Crouch,FALSE); + CONTROL_DefineFlag(gamefunc_Look_Up,FALSE); + CONTROL_DefineFlag(gamefunc_Look_Down,FALSE); + CONTROL_DefineFlag(gamefunc_Look_Left,FALSE); + CONTROL_DefineFlag(gamefunc_Look_Right,FALSE); + CONTROL_DefineFlag(gamefunc_Strafe_Left,FALSE); + CONTROL_DefineFlag(gamefunc_Strafe_Right,FALSE); + CONTROL_DefineFlag(gamefunc_Aim_Up,FALSE); + CONTROL_DefineFlag(gamefunc_Aim_Down,FALSE); + CONTROL_DefineFlag(gamefunc_Weapon_1,FALSE); + CONTROL_DefineFlag(gamefunc_Weapon_2,FALSE); + CONTROL_DefineFlag(gamefunc_Weapon_3,FALSE); + CONTROL_DefineFlag(gamefunc_Weapon_4,FALSE); + CONTROL_DefineFlag(gamefunc_Weapon_5,FALSE); + CONTROL_DefineFlag(gamefunc_Weapon_6,FALSE); + CONTROL_DefineFlag(gamefunc_Weapon_7,FALSE); + CONTROL_DefineFlag(gamefunc_Weapon_8,FALSE); + CONTROL_DefineFlag(gamefunc_Weapon_9,FALSE); + CONTROL_DefineFlag(gamefunc_Weapon_10,FALSE); + CONTROL_DefineFlag(gamefunc_Inventory,FALSE); + CONTROL_DefineFlag(gamefunc_Inventory_Left,FALSE); + CONTROL_DefineFlag(gamefunc_Inventory_Right,FALSE); + CONTROL_DefineFlag(gamefunc_Holo_Duke,FALSE); + CONTROL_DefineFlag(gamefunc_Jetpack,FALSE); + CONTROL_DefineFlag(gamefunc_NightVision,FALSE); + CONTROL_DefineFlag(gamefunc_MedKit,FALSE); + CONTROL_DefineFlag(gamefunc_TurnAround,FALSE); + CONTROL_DefineFlag(gamefunc_SendMessage,FALSE); + CONTROL_DefineFlag(gamefunc_Map,FALSE); + CONTROL_DefineFlag(gamefunc_Shrink_Screen,FALSE); + CONTROL_DefineFlag(gamefunc_Enlarge_Screen,FALSE); + CONTROL_DefineFlag(gamefunc_Center_View,FALSE); + CONTROL_DefineFlag(gamefunc_Holster_Weapon,FALSE); + CONTROL_DefineFlag(gamefunc_Show_Opponents_Weapon,FALSE); + CONTROL_DefineFlag(gamefunc_Map_Follow_Mode,FALSE); + CONTROL_DefineFlag(gamefunc_See_Coop_View,FALSE); + CONTROL_DefineFlag(gamefunc_Mouse_Aiming,FALSE); + CONTROL_DefineFlag(gamefunc_Toggle_Crosshair,FALSE); + CONTROL_DefineFlag(gamefunc_Steroids,FALSE); + CONTROL_DefineFlag(gamefunc_Quick_Kick,FALSE); + CONTROL_DefineFlag(gamefunc_Next_Weapon,FALSE); + CONTROL_DefineFlag(gamefunc_Previous_Weapon,FALSE); + CONTROL_DefineFlag(gamefunc_Alt_Weapon,FALSE); + CONTROL_DefineFlag(gamefunc_Last_Weapon,FALSE); + CONTROL_DefineFlag(gamefunc_Quick_Save, FALSE); + CONTROL_DefineFlag(gamefunc_Quick_Load, FALSE); +} diff --git a/source/rr/src/game.h b/source/rr/src/game.h new file mode 100644 index 000000000..e369bcba6 --- /dev/null +++ b/source/rr/src/game.h @@ -0,0 +1,579 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#pragma once + +#ifndef game_h_ +#define game_h_ + +#ifndef ONLY_USERDEFS +#include "premap.h" // XXX +#endif + +#include "fix16.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ONLY_USERDEFS + +// Compile game-side legacy Room over Room code? +#define LEGACY_ROR 1 + +#define USERQUOTE_LEFTOFFSET 5 +#define USERQUOTE_RIGHTOFFSET 14 + +#if defined(GEKKO) || defined(__OPENDINGUX__) +# define VIEWSCREENFACTOR 0 +#elif defined(__ANDROID__) +# define VIEWSCREENFACTOR 1 +#else +# define VIEWSCREENFACTOR 2 +#endif + +enum GametypeFlags_t { + GAMETYPE_COOP = 0x00000001, + GAMETYPE_WEAPSTAY = 0x00000002, + GAMETYPE_FRAGBAR = 0x00000004, + GAMETYPE_SCORESHEET = 0x00000008, + GAMETYPE_DMSWITCHES = 0x00000010, + GAMETYPE_COOPSPAWN = 0x00000020, + GAMETYPE_ACCESSCARDSPRITES = 0x00000040, + GAMETYPE_COOPVIEW = 0x00000080, + GAMETYPE_COOPSOUND = 0x00000100, + GAMETYPE_OTHERPLAYERSINMAP = 0x00000200, + GAMETYPE_ITEMRESPAWN = 0x00000400, + GAMETYPE_MARKEROPTION = 0x00000800, + GAMETYPE_PLAYERSFRIENDLY = 0x00001000, + GAMETYPE_FIXEDRESPAWN = 0x00002000, + GAMETYPE_ACCESSATSTART = 0x00004000, + GAMETYPE_PRESERVEINVENTORYDEATH = 0x00008000, + GAMETYPE_TDM = 0x00010000, + GAMETYPE_TDMSPAWN = 0x00020000 +}; + +// logo control +enum LogoFlags_t { + LOGO_ENABLED = 0x00000001, + LOGO_PLAYANIM = 0x00000002, + LOGO_PLAYMUSIC = 0x00000004, + LOGO_3DRSCREEN = 0x00000008, + LOGO_TITLESCREEN = 0x00000010, + LOGO_DUKENUKEM = 0x00000020, + LOGO_THREEDEE = 0x00000040, + LOGO_PLUTOPAKSPRITE = 0x00000080, + LOGO_SHAREWARESCREENS = 0x00000100, + LOGO_TENSCREEN = 0x00000200, + LOGO_STOPANIMSOUNDS = 0x00000400, + LOGO_NOE4CUTSCENE = 0x00000800, + LOGO_NOE1BONUSSCENE = 0x00001000, + LOGO_NOE2BONUSSCENE = 0x00002000, + LOGO_NOE3BONUSSCENE = 0x00004000, + LOGO_NOE4BONUSSCENE = 0x00008000, + LOGO_NOE1ENDSCREEN = 0x00010000, + LOGO_NOE2ENDSCREEN = 0x00020000, + LOGO_NOE3RADLOGO = 0x00040000, + LOGO_NODUKETEAMTEXT = 0x00080000, + LOGO_NODUKETEAMPIC = 0x00100000, + LOGO_STOPMISCSOUNDS = 0x00200000, + LOGO_NOGAMETITLE = 0x00400000, + LOGO_NOTITLEBAR = 0x00800000, + LOGO_HIDEEPISODE = 0x01000000, + LOGO_NOHELP = 0x02000000, + LOGO_NOCREDITS = 0x04000000, +}; + +enum { + STATUSBAR_NONONE = 0x00000001, + STATUSBAR_NOMINI = 0x00000002, + STATUSBAR_NOFULL = 0x00000004, + STATUSBAR_NOSHRINK = 0x00000008, + STATUSBAR_NOFRAGBAR = 0x00000010, + STATUSBAR_NOOVERLAY = 0x00000020, + STATUSBAR_NOMODERN = 0x00000040, +}; + +void A_DeleteSprite(int spriteNum); + +//static inline int32_t G_GetLogoFlags(void) +//{ +// return 255; +//} + +# define CAMERA(Membname) (ud.camera ## Membname) +# define CAMERADIST g_cameraDistance +# define CAMERACLOCK g_cameraClock + +#endif + +#define MAXRIDECULE 10 +#define MAXRIDECULELENGTH 40 +#define MAXSAVEGAMENAMESTRUCT 32 +#define MAXSAVEGAMENAME (MAXSAVEGAMENAMESTRUCT-1) +#define MAXPWLOCKOUT 128 +#define MAXRTSNAME 128 + +#define MAX_RETURN_VALUES 6 + +// KEEPINSYNC lunatic/_defs_game.lua +typedef struct { + vec3_t camerapos; + int32_t const_visibility,uw_framerate; + int32_t camera_time,folfvel,folavel,folx,foly,fola; + int32_t reccnt,crosshairscale; + + int32_t runkey_mode,statusbarscale,mouseaiming,weaponswitch,drawweapon; // JBF 20031125 + int32_t democams,color,msgdisptime,statusbarmode; + int32_t m_noexits,noexits,autovote,automsg,idplayers; + int32_t team, viewbob, weaponsway, althud, weaponscale, textscale; + int32_t statusbarflags, statusbarrange, statusbarcustom; + int32_t hudontop; + int32_t menu_slidebarz, menu_slidebarmargin, menu_slidecursorz; + int32_t menu_scrollbartilenum, menu_scrollbarz, menu_scrollcursorz; + + int32_t entered_name,screen_tilting,shadows,fta_on,executions,auto_run; + int32_t coords,showfps,levelstats,m_coop,coop,screen_size,lockout,crosshair; + int32_t playerai,angleinterpolation,obituaries; + + int32_t respawn_monsters,respawn_items,respawn_inventory,recstat,monsters_off,brightness; + int32_t m_respawn_items,m_respawn_monsters,m_respawn_inventory,m_recstat,m_monsters_off,detail; + int32_t m_ffire,ffire,m_player_skill,m_level_number,m_volume_number,multimode; + int32_t player_skill,level_number,volume_number,m_marker,marker,mouseflip; + int32_t music_episode, music_level; + + int32_t playerbest; + + int32_t configversion, bgstretch; + + int32_t default_volume, default_skill; + + int32_t autosave; + int32_t autosavedeletion, maxautosaves; + + //int32_t returnvar[MAX_RETURN_VALUES-1]; + + uint32_t userbytever; + + fix16_t cameraq16ang, cameraq16horiz; + int16_t camerasect; + int16_t pause_on,from_bonus; + int16_t camerasprite,last_camsprite; + int16_t last_level,secretlevel; + + int8_t menutitle_pal, slidebar_palselected, slidebar_paldisabled; + + struct { + int32_t UseJoystick; + int32_t UseMouse; + int32_t AutoAim; + int32_t ShowOpponentWeapons; + int32_t MouseDeadZone,MouseBias; + int32_t SmoothInput; + + // JBF 20031211: Store the input settings because + // (currently) mact can't regurgitate them + int32_t MouseFunctions[MAXMOUSEBUTTONS][2]; + int32_t MouseDigitalFunctions[MAXMOUSEAXES][2]; + int32_t MouseAnalogueAxes[MAXMOUSEAXES]; + int32_t MouseAnalogueScale[MAXMOUSEAXES]; + int32_t JoystickFunctions[MAXJOYBUTTONSANDHATS][2]; + int32_t JoystickDigitalFunctions[MAXJOYAXES][2]; + int32_t JoystickAnalogueAxes[MAXJOYAXES]; + int32_t JoystickAnalogueScale[MAXJOYAXES]; + int32_t JoystickAnalogueDead[MAXJOYAXES]; + int32_t JoystickAnalogueSaturate[MAXJOYAXES]; + uint8_t KeyboardKeys[NUMGAMEFUNCTIONS][2]; + + // + // Sound variables + // + int32_t FXVolume; + int32_t MusicVolume; + int32_t SoundToggle; + int32_t MusicToggle; + int32_t VoiceToggle; + int32_t AmbienceToggle; + + int32_t NumVoices; + int32_t NumChannels; + int32_t NumBits; + int32_t MixRate; + + int32_t ReverseStereo; + + // + // Screen variables + // + + int32_t ScreenMode; + + int32_t ScreenWidth; + int32_t ScreenHeight; + int32_t ScreenBPP; + + int32_t ForceSetup; + int32_t NoAutoLoad; + + int32_t scripthandle; + int32_t setupread; + + int32_t CheckForUpdates; + int32_t LastUpdateCheck; + int32_t useprecache; + } config; + + char overhead_on,last_overhead,showweapons; + char god,warp_on,cashman,eog,showallmap; + char show_help,scrollmode,noclip; + char ridecule[MAXRIDECULE][MAXRIDECULELENGTH]; + char pwlockout[MAXPWLOCKOUT],rtsname[MAXRTSNAME]; + char display_bonus_screen; + char show_level_text; + char wchoice[MAX_WEAPONS]; + + uint8_t user_map; + uint8_t screenfade, menubackground; + uint8_t shadow_pal; +} user_defs; + +extern user_defs ud; + +#ifndef ONLY_USERDEFS + +// this is checked against http://eduke32.com/VERSION +extern const char *s_buildDate; + +extern char boardfilename[BMAX_PATH], currentboardfilename[BMAX_PATH]; + +static inline int G_HaveUserMap(void) +{ + return (boardfilename[0] != 0 && ud.level_number == 7 && ud.volume_number == 0); +} + +static inline int Menu_HaveUserMap(void) +{ + return (boardfilename[0] != 0 && ud.m_level_number == 7 && ud.m_volume_number == 0); +} + +extern const char *defaultrtsfilename[GAMECOUNT]; +extern const char *G_DefaultRtsFile(void); + +#ifdef LEGACY_ROR +extern char ror_protectedsectors[MAXSECTORS]; +#endif + +extern float r_ambientlight; + +extern int32_t g_Debug; +extern int32_t g_Shareware; +extern int32_t g_cameraClock; +extern int32_t g_cameraDistance; +extern int32_t g_crosshairSum; +extern int32_t g_doQuickSave; +extern int32_t g_forceWeaponChoice; +extern int32_t g_fakeMultiMode; +extern int32_t g_levelTextTime; +extern int32_t g_quitDeadline; +extern int32_t g_restorePalette; +extern int32_t hud_glowingquotes; +extern int32_t hud_showmapname; +extern int32_t r_maxfps; +extern int32_t tempwallptr; +extern int32_t ticrandomseed; +extern int32_t vote_map; +extern int32_t voting; + +extern int32_t MAXCACHE1DSIZE; +//extern int8_t cheatbuf[MAXCHEATLEN],cheatbuflen; + +#define CROSSHAIR_PAL (MAXPALOOKUPS-RESERVEDPALS-1) + +extern palette_t CrosshairColors; +extern palette_t DefaultCrosshairColors; + +extern uint64_t g_frameDelay; + +int32_t A_CheckInventorySprite(spritetype *s); +int32_t A_InsertSprite(int16_t whatsect, int32_t s_x, int32_t s_y, int32_t s_z, int16_t s_pn, int8_t s_s, uint8_t s_xr, + uint8_t s_yr, int16_t s_a, int16_t s_ve, int16_t s_zv, int16_t s_ow, int16_t s_ss); +int A_Spawn(int spriteNum,int tileNum); +int G_DoMoveThings(void); +//int32_t G_EndOfLevel(void); + +#ifdef YAX_ENABLE +void Yax_SetBunchZs(int32_t sectnum, int32_t cf, int32_t daz); +#else +#define Yax_SetBunchZs(sectnum, cf, daz) +#endif + +void G_PostCreateGameState(void); + +void A_SpawnCeilingGlass(int spriteNum,int sectNum,int glassCnt); +void A_SpawnGlass(int spriteNum,int glassCnt); +void A_SpawnRandomGlass(int spriteNum,int wallNum,int glassCnt); +void A_SpawnWallGlass(int spriteNum,int wallnum,int glassCnt); +void A_SpawnWallPopcorn(int spriteNum,int wallnum,int glassCnt); +void G_AddUserQuote(const char *daquote); +void G_BackToMenu(void); +void G_DumpDebugInfo(void); + +const char* G_PrintYourTime(void); +const char* G_PrintParTime(void); +const char* G_PrintDesignerTime(void); +const char* G_PrintBestTime(void); +void G_BonusScreen(int32_t bonusonly); +void G_BonusScreenRRRA(int32_t bonusonly); +//void G_CheatGetInv(void); +void G_DisplayRest(int32_t smoothratio); +void G_DoSpriteAnimations(int32_t ourx, int32_t oury, int32_t oura, int32_t smoothratio); +void G_DrawBackground(void); +void G_DrawFrags(void); +void G_HandleMirror(int32_t x, int32_t y, int32_t z, fix16_t a, fix16_t horiz, int32_t smoothratio); +void G_DrawRooms(int32_t playerNum,int32_t smoothratio); +void G_DrawTXDigiNumZ(int32_t starttile,int32_t x,int32_t y,int32_t n,int32_t s,int32_t pal,int32_t cs,int32_t x1,int32_t y1,int32_t x2,int32_t y2,int32_t z); +int G_FPSLimit(void); +void G_GameExit(const char *msg) ATTRIBUTE((noreturn)); +void G_GameQuit(void); +void G_GetCrosshairColor(void); +void G_HandleLocalKeys(void); +void G_HandleSpecialKeys(void); +void G_UpdateAppTitle(void); +void G_PrintGameQuotes(int32_t snum); +//void G_SE40(int32_t smoothratio); +void G_SetCrosshairColor(int32_t r,int32_t g,int32_t b); +void G_Shutdown(void); +void G_UpdatePlayerFromMenu(void); +void M32RunScript(const char *s); +void P_DoQuote(int32_t q,DukePlayer_t *p); +void P_SetGamePalette(DukePlayer_t *player, uint32_t palid, int32_t set); +void G_OnMotorcycle(DukePlayer_t *pPlayer, int spriteNum); +void G_OffMotorcycle(DukePlayer_t *pPlayer); +void G_OnBoat(DukePlayer_t *pPlayer, int spriteNum); +void G_OffBoat(DukePlayer_t *pPlayer); + +#define NEG_ALPHA_TO_BLEND(alpha, blend, orientation) do { \ + if (alpha < 0) { blend = -alpha; alpha = 0; orientation |= RS_TRANS1; } \ +} while (0) + +// Cstat protection mask for (currently) spawned MASKWALL* sprites. +// TODO: look at more cases of cstat=(cstat&PROTECTED)|ADDED in A_Spawn()? +// 2048+(32+16)+8+4 +#define SPAWN_PROTECT_CSTAT_MASK (CSTAT_SPRITE_NOSHADE|CSTAT_SPRITE_ALIGNMENT_SLAB|CSTAT_SPRITE_XFLIP|CSTAT_SPRITE_YFLIP); + +void fadepal(int32_t r,int32_t g,int32_t b,int32_t start,int32_t end,int32_t step); +//void fadepaltile(int32_t r,int32_t g,int32_t b,int32_t start,int32_t end,int32_t step,int32_t tile); +void G_InitTimer(int32_t ticspersec); + +static inline int32_t G_GetTeamPalette(int32_t team) +{ + int8_t pal[] = { 3, 10, 11, 12 }; + + if ((unsigned)team >= ARRAY_SIZE(pal)) + return 0; + + return pal[team]; +} + +#define A_CheckSpriteFlags(spriteNum, iType) (((g_tile[sprite[spriteNum].picnum].flags^actor[spriteNum].flags) & iType) != 0) +// (unsigned)iPicnum check: AMC TC Rusty Nails, bayonet MG alt. fire, iPicnum == -1 (via aplWeaponShoots) +#define A_CheckSpriteTileFlags(iPicnum, iType) (((unsigned)iPicnum < MAXTILES) && (g_tile[iPicnum].flags & iType) != 0) +#define S_StopSound(num) S_StopEnvSound(num, -1) + +extern int G_StartRTS(int lumpNum, int localPlayer); + +extern void G_MaybeAllocPlayer(int32_t pnum); + +static inline void G_HandleAsync(void) +{ + handleevents(); + Net_GetPackets(); +} + +static inline int32_t calc_smoothratio(int32_t totalclk, int32_t ototalclk) +{ + return clamp((totalclk-ototalclk)*(65536/TICSPERFRAME), 0, 65536); +} + +// sector effector lotags +enum +{ + SE_0_ROTATING_SECTOR = 0, + SE_1_PIVOT = 1, + SE_2_EARTHQUAKE = 2, + SE_3_RANDOM_LIGHTS_AFTER_SHOT_OUT = 3, + SE_4_RANDOM_LIGHTS = 4, + SE_5 = 5, + SE_6_SUBWAY = 6, + // ^^ potentially incomplete substitution in code + // vv almost surely complete substitution + SE_7_TELEPORT = 7, + SE_8_UP_OPEN_DOOR_LIGHTS = 8, + SE_9_DOWN_OPEN_DOOR_LIGHTS = 9, + SE_10_DOOR_AUTO_CLOSE = 10, + SE_11_SWINGING_DOOR = 11, + SE_12_LIGHT_SWITCH = 12, + SE_13_EXPLOSIVE = 13, + SE_14_SUBWAY_CAR = 14, + SE_15_SLIDING_DOOR = 15, + SE_16_REACTOR = 16, + SE_17_WARP_ELEVATOR = 17, + SE_18_INCREMENTAL_SECTOR_RISE_FALL = 18, + SE_19_EXPLOSION_LOWERS_CEILING = 19, + SE_20_STRETCH_BRIDGE = 20, + SE_21_DROP_FLOOR = 21, + SE_22_TEETH_DOOR = 22, + SE_23_ONE_WAY_TELEPORT = 23, + SE_24_CONVEYOR = 24, + SE_25_PISTON = 25, + SE_26 = 26, + SE_27_DEMO_CAM = 27, + SE_28_LIGHTNING = 28, + SE_29_WAVES = 29, + SE_30_TWO_WAY_TRAIN = 30, + SE_31_FLOOR_RISE_FALL = 31, + SE_32_CEILING_RISE_FALL = 32, + SE_33_QUAKE_DEBRIS = 33, + SE_34 = 34, // XXX + SE_35 = 35, // XXX + SE_36_PROJ_SHOOTER = 36, + SE_49_POINT_LIGHT = 49, + SE_50_SPOT_LIGHT = 50, + SE_130 = 130, + SE_131 = 131, +}; + +// sector lotags +enum +{ + ST_0_NO_EFFECT = 0, + ST_1_ABOVE_WATER = 1, + ST_2_UNDERWATER = 2, + ST_3 = 3, + // ^^^ maybe not complete substitution in code + ST_9_SLIDING_ST_DOOR = 9, + ST_15_WARP_ELEVATOR = 15, + ST_16_PLATFORM_DOWN = 16, + ST_17_PLATFORM_UP = 17, + ST_18_ELEVATOR_DOWN = 18, + ST_19_ELEVATOR_UP = 19, + ST_20_CEILING_DOOR = 20, + ST_21_FLOOR_DOOR = 21, + ST_22_SPLITTING_DOOR = 22, + ST_23_SWINGING_DOOR = 23, + ST_25_SLIDING_DOOR = 25, + ST_26_SPLITTING_ST_DOOR = 26, + ST_27_STRETCH_BRIDGE = 27, + ST_28_DROP_FLOOR = 28, + ST_29_TEETH_DOOR = 29, + ST_30_ROTATE_RISE_BRIDGE = 30, + ST_31_TWO_WAY_TRAIN = 31, + // left: ST 32767, 65534, 65535 +}; + + +#define G_ModDirSnprintf(buf, size, basename, ...) \ + \ +(((g_modDir[0] != '/') ? Bsnprintf(buf, size, "%s/" basename, g_modDir, ##__VA_ARGS__) \ + : Bsnprintf(buf, size, basename, ##__VA_ARGS__)) >= ((int32_t)size) - 1\ +) +#define G_ModDirSnprintfLite(buf, size, basename) \ + \ +((g_modDir[0] != '/') ? Bsnprintf(buf, size, "%s/%s", g_modDir, basename) \ + : Bsnprintf(buf, size, basename)) + +static inline void G_NewGame_EnterLevel(void) +{ + G_NewGame(ud.m_volume_number, ud.m_level_number, ud.m_player_skill); + + if (G_EnterLevel(MODE_GAME)) + G_BackToMenu(); +} + +static inline int G_GetMusicIdx(const char *str) +{ + int32_t lev, ep; + signed char b1, b2; + + int numMatches = sscanf(str, "%c%d%c%d", &b1,&ep, &b2,&lev); + + if (numMatches != 4 || Btoupper(b1) != 'E' || Btoupper(b2) != 'L') + return -1; + + if ((unsigned)--lev >= MAXLEVELS || (unsigned)--ep >= MAXVOLUMES) + return -2; + + return (ep * MAXLEVELS) + lev; +} + +static inline int G_GetViewscreenSizeShift(const uspritetype *tspr) +{ +#if VIEWSCREENFACTOR == 0 + UNREFERENCED_PARAMETER(tspr); + return VIEWSCREENFACTOR; +#else + static const int mask = (1< xrepeat & mask) | (tspr->yrepeat & mask); + + for (bssize_t i=0; i < VIEWSCREENFACTOR; i++) + if (rem & (1< *variable) + *variable = potentialValue; +} + +#endif + +#endif + +#endif diff --git a/source/rr/src/gamedef.cpp b/source/rr/src/gamedef.cpp new file mode 100644 index 000000000..280c5b92c --- /dev/null +++ b/source/rr/src/gamedef.cpp @@ -0,0 +1,2565 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "duke3d.h" +#include "namesdyn.h" +#include "gamedef.h" +#include "gameexec.h" +#include "savegame.h" +#include "common.h" +#include "common_game.h" +#include "cheats.h" + +#include "osd.h" +#include "crc32.h" + +int32_t g_scriptVersion = 14; // 13 = 1.3D-style CON files, 14 = 1.4/1.5 style CON files + +char g_scriptFileName[BMAX_PATH] = "(none)"; // file we're currently compiling + +int32_t g_totalLines, g_lineNumber; +uint32_t g_scriptcrc; +char g_szBuf[1024]; + +static char g_szCurrentBlockName[256] = "(none)", g_szLastBlockName[256] = "NULL"; +static int32_t g_checkingIfElse, g_processingState, g_lastKeyword = -1; + +// The pointer to the start of the case table in a switch statement. +// First entry is 'default' code. +static intptr_t *g_caseScriptPtr; +static int32_t g_labelsOnly = 0; +static int32_t g_numBraces = 0; + +static int32_t C_ParseCommand(int32_t loop); +static int32_t C_SetScriptSize(int32_t size); + +static intptr_t g_parsingActorPtr; +static char *textptr; + +int32_t g_errorCnt,g_warningCnt; + +static char *C_GetLabelType(int32_t type) +{ + int32_t i; + char x[64]; + + const char *LabelTypeText[] = + { + "define", + "state", + "actor", + "action", + "ai", + "move" + }; + + x[0] = 0; + for (i=0; i<6; i++) + { + if (!(type & (1<"; +} + +char *bitptr; // pointer to bitmap of which bytecode positions contain pointers +#define BITPTR_SET(x) (bitptr[(x)>>3] |= (1<<((x)&7))) +#define BITPTR_CLEAR(x) (bitptr[(x)>>3] &= ~(1<<((x)&7))) +#define BITPTR_IS_POINTER(x) (bitptr[(x)>>3] & (1<<((x) &7))) + +hashtable_t h_labels = { 11264>>1, NULL }; + +static hashtable_t h_keywords = { CON_END>>1, NULL };; + +static hashtable_t * const tables[] = { + &h_labels, &h_keywords +}; + +static hashtable_t * const tables_free [] = { + &h_labels, &h_keywords +}; + +#define STRUCT_HASH_SETUP(table, labels) do { for (i=0; labels[i].lId >= 0; i++) hash_add(&table, labels[i].name, i, 0); } while (0) + +void C_InitHashes() +{ + uint32_t i; + + for (i=0; i < ARRAY_SIZE(tables); i++) + hash_init(tables[i]); + + //inithashnames(); + initsoundhashnames(); + + for (tokenmap_t const & keyword : vm_keywords) + hash_add(&h_keywords, keyword.token, keyword.val, 0); +} + +#undef STRUCT_HASH_SETUP + +// "magic" number for { and }, overrides line number in compiled code for later detection +#define IFELSE_MAGIC 31337 +static int32_t g_ifElseAborted; + +static int32_t C_SetScriptSize(int32_t newsize) +{ + intptr_t const oscript = (intptr_t)apScript; + intptr_t *newscript; + intptr_t i, j; + int32_t osize = g_scriptSize; + char *scriptptrs; + char *newbitptr; + + scriptptrs = (char *)Xcalloc(1, g_scriptSize * sizeof(uint8_t)); + + for (i=g_scriptSize-1; i>=0; i--) + { + if (BITPTR_IS_POINTER(i)) + { + if (EDUKE32_PREDICT_FALSE((intptr_t)apScript[i] < (intptr_t)&apScript[0] || (intptr_t)apScript[i] >= (intptr_t)&apScript[g_scriptSize])) + { + g_errorCnt++; + buildprint("Internal compiler error at ", i, " (0x", hex(i), ")\n"); + VM_ScriptInfo(&apScript[i], 16); + } + + scriptptrs[i] = 1; + apScript[i] -= (intptr_t)&apScript[0]; + } + else scriptptrs[i] = 0; + } + + G_Util_PtrToIdx2(&g_tile[0].execPtr, MAXTILES, sizeof(tiledata_t), apScript, P2I_FWD_NON0); + G_Util_PtrToIdx2(&g_tile[0].loadPtr, MAXTILES, sizeof(tiledata_t), apScript, P2I_FWD_NON0); + + newscript = (intptr_t *)Xrealloc(apScript, newsize * sizeof(intptr_t)); + newbitptr = (char *)Xcalloc(1,(((newsize+7)>>3)+1) * sizeof(uint8_t)); + + if (newsize >= osize) + { + Bmemset(&newscript[0]+osize,0,(newsize-osize) * sizeof(uint8_t)); + Bmemcpy(newbitptr,bitptr,sizeof(uint8_t) *((osize+7)>>3)); + } + else + Bmemcpy(newbitptr,bitptr,sizeof(uint8_t) *((newsize+7)>>3)); + + Bfree(bitptr); + bitptr = newbitptr; + if (apScript != newscript) + { + buildprint("Relocating compiled code from to 0x", hex((intptr_t)apScript), " to 0x", hex((intptr_t)newscript), "\n"); + apScript = newscript; + } + + g_scriptSize = newsize; + g_scriptPtr = apScript + (intptr_t)g_scriptPtr - oscript; + + if (g_caseScriptPtr) + g_caseScriptPtr = apScript + (intptr_t)g_caseScriptPtr - oscript; + + for (i=(((newsize>=osize)?osize:newsize))-1; i>=0; i--) + if (scriptptrs[i]) + { + j = (intptr_t)apScript[i]+(intptr_t)&apScript[0]; + apScript[i] = j; + } + + G_Util_PtrToIdx2(&g_tile[0].execPtr, MAXTILES, sizeof(tiledata_t), apScript, P2I_BACK_NON0); + G_Util_PtrToIdx2(&g_tile[0].loadPtr, MAXTILES, sizeof(tiledata_t), apScript, P2I_BACK_NON0); + + Bfree(scriptptrs); + return 0; +} + +static inline int32_t ispecial(const char c) +{ + return (c == ' ' || c == 0x0d || c == '(' || c == ')' || + c == ',' || c == ';' || (c == 0x0a /*&& ++g_lineNumber*/)); +} + +static inline void C_NextLine(void) +{ + while (*textptr != 0x0a && *textptr != 0x0d && *textptr != 0) + textptr++; +} + +static inline void C_SkipSpace(void) +{ + while (*textptr == ' ' || *textptr == '\t') + textptr++; +} + +static int32_t C_SkipComments(void) +{ + do + { + switch (*textptr) + { + case '\n': + g_lineNumber++; + fallthrough__; + case ' ': + case '\t': + case '\r': + case 0x1a: + textptr++; + break; + case '/': + switch (textptr[1]) + { + case '/': // C++ style comment + if (!(g_errorCnt || g_warningCnt) && g_scriptDebug > 1) + initprintf("%s:%d: debug: got comment.\n",g_scriptFileName,g_lineNumber); + C_NextLine(); + continue; + case '*': // beginning of a C style comment + if (!(g_errorCnt || g_warningCnt) && g_scriptDebug > 1) + initprintf("%s:%d: debug: got start of comment block.\n",g_scriptFileName,g_lineNumber); + do + { + if (*textptr == '\n') + g_lineNumber++; + textptr++; + } + while (*textptr && (textptr[0] != '*' || textptr[1] != '/')); + + if (EDUKE32_PREDICT_FALSE(!*textptr)) + { + if (!(g_errorCnt || g_warningCnt) && g_scriptDebug) + initprintf("%s:%d: debug: EOF in comment!\n",g_scriptFileName,g_lineNumber); + C_ReportError(-1); + initprintf("%s:%d: error: found `/*' with no `*/'.\n",g_scriptFileName,g_lineNumber); + g_parsingActorPtr = g_processingState = g_numBraces = 0; + g_errorCnt++; + continue; + } + + if (!(g_errorCnt || g_warningCnt) && g_scriptDebug > 1) + initprintf("%s:%d: debug: got end of comment block.\n",g_scriptFileName,g_lineNumber); + + textptr+=2; + continue; + default: + C_ReportError(-1); + initprintf("%s:%d: error: malformed comment.\n", g_scriptFileName, g_lineNumber); + C_NextLine(); + g_errorCnt++; + continue; + } + break; + + default: + if (ispecial(*textptr)) + { + textptr++; + continue; + } + fallthrough__; + case 0: // EOF + return ((g_scriptPtr-apScript) > (g_scriptSize-32)) ? C_SetScriptSize(g_scriptSize<<1) : 0; + } + } + while (1); +} + +static inline int32_t isaltok(const char c) +{ + return (isalnum(c) || c == '{' || c == '}' || c == '/' || c == '\\' || c == '*' || c == '-' || c == '_' || + c == '.'); +} + +static inline int32_t C_IsLabelChar(const char c, int32_t const i) +{ + return (isalnum(c) || c == '_' || c == '*' || c == '?' || (i > 0 && (c == '+' || c == '-' || c == '.'))); +} + +static inline int32_t C_GetLabelNameID(const memberlabel_t *pLabel, hashtable_t const * const table, const char *psz) +{ + // find the label psz in the table pLabel. + // returns the ID for the label, or -1 + + int32_t l = hash_findcase(table, psz); + return (l >= 0) ? pLabel[l].lId : -1; +} + +static inline int32_t C_GetLabelNameOffset(hashtable_t const * const table, const char *psz) +{ + // find the label psz in the table pLabel. + // returns the offset in the array for the label, or -1 + + return hash_findcase(table, psz); +} + +static void C_GetNextLabelName(void) +{ + int32_t i = 0; + + C_SkipComments(); + +// while (ispecial(*textptr) == 0 && *textptr!='['&& *textptr!=']' && *textptr!='\t' && *textptr!='\n' && *textptr!='\r') + while (C_IsLabelChar(*textptr, i)) + label[(g_labelCnt<<6)+(i++)] = *(textptr++); + + label[(g_labelCnt<<6)+i] = 0; + + if (!(g_errorCnt|g_warningCnt) && g_scriptDebug > 1) + initprintf("%s:%d: debug: label `%s'.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); +} + +static int32_t C_GetKeyword(void) +{ + int32_t i; + char *temptextptr; + + C_SkipComments(); + + temptextptr = textptr; + + if (*temptextptr == 0) // EOF + return -2; + + while (isaltok(*temptextptr) == 0) + { + temptextptr++; + if (*temptextptr == 0) + return 0; + } + + i = 0; + while (isaltok(*temptextptr)) + tempbuf[i++] = *(temptextptr++); + tempbuf[i] = 0; + + return hash_find(&h_keywords,tempbuf); +} + +static int32_t C_GetNextKeyword(void) //Returns its code # +{ + int32_t i, l; + + C_SkipComments(); + + if (*textptr == 0) // EOF + return -2; + + l = 0; + while (isaltok(*(textptr+l))) + { + tempbuf[l] = textptr[l]; + l++; + } + tempbuf[l] = 0; + + if (EDUKE32_PREDICT_TRUE((i = hash_find(&h_keywords,tempbuf)) >= 0)) + { + if (i == CON_LEFTBRACE || i == CON_RIGHTBRACE || i == CON_NULLOP) + *g_scriptPtr = i + (IFELSE_MAGIC<<12); + else *g_scriptPtr = i + (g_lineNumber<<12); + + BITPTR_CLEAR(g_scriptPtr-apScript); + textptr += l; + g_scriptPtr++; + + if (!(g_errorCnt || g_warningCnt) && g_scriptDebug) + initprintf("%s:%d: debug: keyword `%s'.\n", g_scriptFileName, g_lineNumber, tempbuf); + return i; + } + + textptr += l; + g_errorCnt++; + + if (EDUKE32_PREDICT_FALSE((tempbuf[0] == '{' || tempbuf[0] == '}') && tempbuf[1] != 0)) + { + C_ReportError(-1); + initprintf("%s:%d: error: expected whitespace between `%c' and `%s'.\n",g_scriptFileName,g_lineNumber,tempbuf[0],tempbuf+1); + } + else C_ReportError(ERROR_EXPECTEDKEYWORD); + + return -1; +} + +static int32_t parse_decimal_number(void) // (textptr) +{ + // decimal constants -- this is finicky business + int64_t num = strtoll(textptr, NULL, 10); // assume long long to be int64_t + + if (EDUKE32_PREDICT_TRUE(num >= INT32_MIN && num <= INT32_MAX)) + { + // all OK + } + else if (EDUKE32_PREDICT_FALSE(num > INT32_MAX && num <= UINT32_MAX)) + { + // Number interpreted as uint32, but packed as int32 (on 32-bit archs) + // (CON code in the wild exists that does this). Note that such conversion + // is implementation-defined (C99 6.3.1.3) but GCC does the 'expected' thing. +#if 0 + initprintf("%s:%d: warning: number greater than INT32_MAX converted to a negative one.\n", + g_szScriptFileName,g_lineNumber); + g_numCompilerWarnings++; +#endif + } + else + { + // out of range, this is arguably worse + + initprintf("%s:%d: warning: number out of the range of a 32-bit integer encountered.\n", + g_scriptFileName,g_lineNumber); + g_warningCnt++; + } + + return (int32_t)num; +} + +static int32_t parse_hex_constant(const char *hexnum) +{ + uint64_t x; + sscanf(hexnum, "%" PRIx64 "", &x); + + if (EDUKE32_PREDICT_FALSE(x > UINT32_MAX)) + { + initprintf(g_scriptFileName, ":", g_lineNumber, ": warning: number 0x", hex(x), " truncated to 32 bits.\n"); + g_warningCnt++; + } + + return x; +} + +// returns: +// -1 on EOF or wrong type or error +// 0 if literal value +// LABEL_* (>0) if that type and matched +// +// *g_scriptPtr will contain the value OR 0 if wrong type or error +static int32_t C_GetNextValue(int32_t type) +{ + C_SkipComments(); + + if (*textptr == 0) // EOF + return -1; + + int32_t l = 0; + + while (isaltok(*(textptr+l))) + { + tempbuf[l] = textptr[l]; + l++; + } + tempbuf[l] = 0; + + if (EDUKE32_PREDICT_FALSE(hash_find(&h_keywords,tempbuf /*label+(g_numLabels<<6)*/)>=0)) + { + g_errorCnt++; + C_ReportError(ERROR_ISAKEYWORD); + textptr+=l; + } + + int32_t i = hash_find(&h_labels,tempbuf); + + if (i>=0) + { + if (EDUKE32_PREDICT_TRUE(labeltype[i] & type)) + { + if (!(g_errorCnt || g_warningCnt) && g_scriptDebug > 1) + { + char *gl = C_GetLabelType(labeltype[i]); + initprintf("%s:%d: debug: %s label `%s'.\n",g_scriptFileName,g_lineNumber,gl,label+(i<<6)); + Bfree(gl); + } + + BITPTR_CLEAR(g_scriptPtr-apScript); + *(g_scriptPtr++) = labelcode[i]; + + textptr += l; + return labeltype[i]; + } + + BITPTR_CLEAR(g_scriptPtr-apScript); + *(g_scriptPtr++) = 0; + textptr += l; + char *el = C_GetLabelType(type); + char *gl = C_GetLabelType(labeltype[i]); + C_ReportError(-1); + initprintf("%s:%d: warning: expected %s, found %s.\n",g_scriptFileName,g_lineNumber,el,gl); + g_warningCnt++; + Bfree(el); + Bfree(gl); + return -1; // valid label name, but wrong type + } + + if (EDUKE32_PREDICT_FALSE(isdigit(*textptr) == 0 && *textptr != '-')) + { + C_ReportError(ERROR_PARAMUNDEFINED); + g_errorCnt++; + BITPTR_CLEAR(g_scriptPtr-apScript); + *g_scriptPtr = 0; + g_scriptPtr++; + textptr+=l; + if (!l) textptr++; + return -1; // error! + } + + if (EDUKE32_PREDICT_FALSE(isdigit(*textptr) && g_labelsOnly)) + { + C_ReportError(WARNING_LABELSONLY); + g_warningCnt++; + } + + i = l-1; + do + { + // FIXME: check for 0-9 A-F for hex + if (textptr[0] == '0' && textptr[1] == 'x') break; // kill the warning for hex + if (EDUKE32_PREDICT_FALSE(!isdigit(textptr[i--]))) + { + C_ReportError(-1); + initprintf("%s:%d: warning: invalid character `%c' in definition!\n",g_scriptFileName,g_lineNumber,textptr[i+1]); + g_warningCnt++; + break; + } + } + while (i > 0); + + BITPTR_CLEAR(g_scriptPtr-apScript); + + if (textptr[0] == '0' && tolower(textptr[1])=='x') + *g_scriptPtr = parse_hex_constant(textptr+2); + else + *g_scriptPtr = parse_decimal_number(); + + if (!(g_errorCnt || g_warningCnt) && g_scriptDebug > 1) + initprintf("%s:%d: debug: constant %ld.\n", + g_scriptFileName,g_lineNumber,(long)*g_scriptPtr); + + g_scriptPtr++; + + textptr += l; + + return 0; // literal value +} + +static int32_t C_CheckMalformedBranch(intptr_t lastScriptPtr) +{ + switch (C_GetKeyword()) + { + case CON_RIGHTBRACE: + case CON_ENDA: + case CON_ENDS: + case CON_ELSE: + g_scriptPtr = lastScriptPtr + &apScript[0]; + g_ifElseAborted = 1; + C_ReportError(-1); + g_warningCnt++; + initprintf("%s:%d: warning: malformed `%s' branch\n",g_scriptFileName,g_lineNumber, + VM_GetKeywordForID(*(g_scriptPtr) & VM_INSTMASK)); + return 1; + } + return 0; +} + +static int32_t C_CheckEmptyBranch(int32_t tw, intptr_t lastScriptPtr) +{ + // ifrnd and the others actually do something when the condition is executed + if ((Bstrncmp(VM_GetKeywordForID(tw), "if", 2) && tw != CON_ELSE) || + tw == CON_IFRND || tw == CON_IFHITWEAPON || tw == CON_IFCANSEE || tw == CON_IFCANSEETARGET || + tw == CON_IFPDISTL || tw == CON_IFPDISTG || tw == CON_IFGOTWEAPONCE) + { + g_ifElseAborted = 0; + return 0; + } + + if ((*(g_scriptPtr) & VM_INSTMASK) != CON_NULLOP || *(g_scriptPtr)>>12 != IFELSE_MAGIC) + g_ifElseAborted = 0; + + if (EDUKE32_PREDICT_FALSE(g_ifElseAborted)) + { + C_ReportError(-1); + g_warningCnt++; + g_scriptPtr = lastScriptPtr + &apScript[0]; + initprintf("%s:%d: warning: empty `%s' branch\n",g_scriptFileName,g_lineNumber, + VM_GetKeywordForID(*(g_scriptPtr) & VM_INSTMASK)); + *(g_scriptPtr) = (CON_NULLOP + (IFELSE_MAGIC<<12)); + return 1; + } + return 0; +} + +static void C_Include(const char *confile) +{ + int32_t fp = kopen4loadfrommod(confile,g_loadFromGroupOnly); + + if (EDUKE32_PREDICT_FALSE(fp < 0)) + { + g_errorCnt++; + initprintf("%s:%d: error: could not find file `%s'.\n",g_scriptFileName,g_lineNumber,confile); + return; + } + + int32_t j = kfilelength(fp); + + char *mptr = (char *)Xmalloc(j+1); + + initprintf("Including: %s (%d bytes)\n",confile, j); + + kread(fp, mptr, j); + kclose(fp); + g_scriptcrc = Bcrc32(mptr, j, g_scriptcrc); + mptr[j] = 0; + + if (*textptr == '"') // skip past the closing quote if it's there so we don't screw up the next line + textptr++; + + char *origtptr = textptr; + char parentScriptFileName[255]; + + Bstrcpy(parentScriptFileName, g_scriptFileName); + Bstrcpy(g_scriptFileName, confile); + + int32_t temp_ScriptLineNumber = g_lineNumber; + g_lineNumber = 1; + + int32_t temp_ifelse_check = g_checkingIfElse; + g_checkingIfElse = 0; + + textptr = mptr; + + C_SkipComments(); + C_ParseCommand(1); + + Bstrcpy(g_scriptFileName, parentScriptFileName); + + g_totalLines += g_lineNumber; + g_lineNumber = temp_ScriptLineNumber; + g_checkingIfElse = temp_ifelse_check; + + textptr = origtptr; + + Bfree(mptr); +} + +#ifdef _WIN32 +static void check_filename_case(const char *fn) +{ + int32_t fp; + if ((fp = kopen4loadfrommod(fn, g_loadFromGroupOnly)) >= 0) + kclose(fp); +} +#else +static void check_filename_case(const char *fn) { UNREFERENCED_PARAMETER(fn); } +#endif + +void G_DoGameStartup(const int32_t *params) +{ + int j = 0; + + ud.const_visibility = params[j++]; + g_impactDamage = params[j++]; + g_player[0].ps->max_shield_amount = params[j++]; + g_player[0].ps->max_player_health = g_player[0].ps->max_shield_amount; + g_maxPlayerHealth = g_player[0].ps->max_player_health; + g_startArmorAmount = params[j++]; + g_actorRespawnTime = params[j++]; + g_itemRespawnTime = params[j++]; + g_playerFriction = params[j++]; + g_spriteGravity = params[j++]; + g_rpgRadius = params[j++]; + g_pipebombRadius = params[j++]; + g_shrinkerRadius = params[j++]; + g_tripbombRadius = params[j++]; + g_morterRadius = params[j++]; + g_bouncemineRadius = params[j++]; + g_seenineRadius = params[j++]; + + g_player[0].ps->max_ammo_amount[1] = params[j++]; + g_player[0].ps->max_ammo_amount[2] = params[j++]; + g_player[0].ps->max_ammo_amount[3] = params[j++]; + g_player[0].ps->max_ammo_amount[4] = params[j++]; + g_player[0].ps->max_ammo_amount[5] = params[j++]; + g_player[0].ps->max_ammo_amount[6] = params[j++]; + g_player[0].ps->max_ammo_amount[7] = params[j++]; + g_player[0].ps->max_ammo_amount[8] = params[j++]; + g_player[0].ps->max_ammo_amount[9] = params[j++]; + g_player[0].ps->max_ammo_amount[11] = params[j++]; + + if (RR) + g_player[0].ps->max_ammo_amount[12] = params[j++]; + + g_damageCameras = params[j++]; + g_numFreezeBounces = params[j++]; + g_freezerSelfDamage = params[j++]; + g_deleteQueueSize = clamp(params[j++], 0, 1024); + g_tripbombLaserMode = params[j++]; + + if (RRRA) + { + g_player[0].ps->max_ammo_amount[13] = params[j++]; + g_player[0].ps->max_ammo_amount[14] = params[j++]; + g_player[0].ps->max_ammo_amount[16] = params[j++]; + } +} + +void C_DefineMusic(int volumeNum, int levelNum, const char *fileName) +{ + Bassert((unsigned)volumeNum < MAXVOLUMES+1); + Bassert((unsigned)levelNum < MAXLEVELS); + + map_t *const pMapInfo = &g_mapInfo[(MAXLEVELS*volumeNum)+levelNum]; + + Bfree(pMapInfo->musicfn); + pMapInfo->musicfn = dup_filename(fileName); + check_filename_case(pMapInfo->musicfn); +} + +void C_DefineVolumeFlags(int32_t vol, int32_t flags) +{ + Bassert((unsigned)vol < MAXVOLUMES); + + g_volumeFlags[vol] = flags; +} + +int32_t C_AllocQuote(int32_t qnum) +{ + Bassert((unsigned)qnum < MAXQUOTES); + + if (apStrings[qnum] == NULL) + { + apStrings[qnum] = (char *)Xcalloc(MAXQUOTELEN,sizeof(uint8_t)); + return 1; + } + + return 0; +} + +#ifndef EDUKE32_TOUCH_DEVICES +static void C_ReplaceQuoteSubstring(const size_t q, char const * const query, char const * const replacement) +{ + size_t querylength = Bstrlen(query); + + for (bssize_t i = MAXQUOTELEN - querylength - 2; i >= 0; i--) + if (Bstrncmp(&apStrings[q][i], query, querylength) == 0) + { + Bmemset(tempbuf, 0, sizeof(tempbuf)); + Bstrncpy(tempbuf, apStrings[q], i); + Bstrcat(tempbuf, replacement); + Bstrcat(tempbuf, &apStrings[q][i + querylength]); + Bstrncpy(apStrings[q], tempbuf, MAXQUOTELEN - 1); + i = MAXQUOTELEN - querylength - 2; + } +} +#endif + +void C_InitQuotes(void) +{ + for (bssize_t i = 0; i < 128; i++) C_AllocQuote(i); + +#ifdef EDUKE32_TOUCH_DEVICES + apStrings[QUOTE_DEAD] = 0; +#else + char const * const OpenGameFunc = gamefunctions[gamefunc_Open]; + C_ReplaceQuoteSubstring(QUOTE_DEAD, "SPACE", OpenGameFunc); + C_ReplaceQuoteSubstring(QUOTE_DEAD, "OPEN", OpenGameFunc); + C_ReplaceQuoteSubstring(QUOTE_DEAD, "USE", OpenGameFunc); +#endif + + // most of these are based on Blood, obviously + const char *PlayerObituaries[] = + { + "^02%s^02 beat %s^02 like a cur", + "^02%s^02 broke %s", + "^02%s^02 body bagged %s", + "^02%s^02 boned %s^02 like a fish", + "^02%s^02 castrated %s", + "^02%s^02 creamed %s", + "^02%s^02 crushed %s", + "^02%s^02 destroyed %s", + "^02%s^02 diced %s", + "^02%s^02 disemboweled %s", + "^02%s^02 erased %s", + "^02%s^02 eviscerated %s", + "^02%s^02 flailed %s", + "^02%s^02 flattened %s", + "^02%s^02 gave AnAl MaDnEsS to %s", + "^02%s^02 gave %s^02 Anal Justice", + "^02%s^02 hosed %s", + "^02%s^02 hurt %s^02 real bad", + "^02%s^02 killed %s", + "^02%s^02 made dog meat out of %s", + "^02%s^02 made mincemeat out of %s", + "^02%s^02 manhandled %s", + "^02%s^02 massacred %s", + "^02%s^02 mutilated %s", + "^02%s^02 murdered %s", + "^02%s^02 neutered %s", + "^02%s^02 punted %s", + "^02%s^02 reamed %s", + "^02%s^02 ripped %s^02 a new orifice", + "^02%s^02 rocked %s", + "^02%s^02 sent %s^02 to hell", + "^02%s^02 shredded %s", + "^02%s^02 slashed %s", + "^02%s^02 slaughtered %s", + "^02%s^02 sliced %s", + "^02%s^02 smacked %s around", + "^02%s^02 smashed %s", + "^02%s^02 snuffed %s", + "^02%s^02 sodomized %s", + "^02%s^02 splattered %s", + "^02%s^02 sprayed %s", + "^02%s^02 squashed %s", + "^02%s^02 throttled %s", + "^02%s^02 toasted %s", + "^02%s^02 vented %s", + "^02%s^02 ventilated %s", + "^02%s^02 wasted %s", + "^02%s^02 wrecked %s", + }; + + const char *PlayerSelfObituaries[] = + { + "^02%s^02 is excrement", + "^02%s^02 is hamburger", + "^02%s^02 suffered scrotum separation", + "^02%s^02 volunteered for population control", + "^02%s^02 has suicided", + "^02%s^02 bled out", + }; + + EDUKE32_STATIC_ASSERT(OBITQUOTEINDEX + ARRAY_SIZE(PlayerObituaries)-1 < MAXQUOTES); + EDUKE32_STATIC_ASSERT(SUICIDEQUOTEINDEX + ARRAY_SIZE(PlayerSelfObituaries)-1 < MAXQUOTES); + + g_numObituaries = ARRAY_SIZE(PlayerObituaries); + for (bssize_t i = g_numObituaries - 1; i >= 0; i--) + { + if (C_AllocQuote(i + OBITQUOTEINDEX)) + Bstrcpy(apStrings[i + OBITQUOTEINDEX], PlayerObituaries[i]); + } + + g_numSelfObituaries = ARRAY_SIZE(PlayerSelfObituaries); + for (bssize_t i = g_numSelfObituaries - 1; i >= 0; i--) + { + if (C_AllocQuote(i + SUICIDEQUOTEINDEX)) + Bstrcpy(apStrings[i + SUICIDEQUOTEINDEX], PlayerSelfObituaries[i]); + } +} + +static inline void C_BitOrNextValue(int32_t *valptr) +{ + C_GetNextValue(LABEL_DEFINE); + g_scriptPtr--; + *valptr |= *g_scriptPtr; +} + +static inline void C_FinishBitOr(int32_t value) +{ + BITPTR_CLEAR(g_scriptPtr-apScript); + *g_scriptPtr++ = value; +} + +static int32_t C_ParseCommand(int32_t loop) +{ + int32_t i, j=0, k=0, tw; + + do + { + if (EDUKE32_PREDICT_FALSE(g_errorCnt > 63 || (*textptr == '\0') || (*(textptr+1) == '\0') || C_SkipComments())) + return 1; + + if (EDUKE32_PREDICT_FALSE(g_scriptDebug)) + C_ReportError(-1); + + switch ((g_lastKeyword = tw = C_GetNextKeyword())) + { + default: + case -1: + case -2: + return 1; //End + case CON_STATE: + if (!g_parsingActorPtr && g_processingState == 0) + { + C_GetNextLabelName(); + g_scriptPtr--; + labelcode[g_labelCnt] = g_scriptPtr-apScript; + labeltype[g_labelCnt] = LABEL_STATE; + + g_processingState = 1; + Bsprintf(g_szCurrentBlockName,"%s",label+(g_labelCnt<<6)); + + if (EDUKE32_PREDICT_FALSE(hash_find(&h_keywords,label+(g_labelCnt<<6))>=0)) + { + g_errorCnt++; + C_ReportError(ERROR_ISAKEYWORD); + continue; + } + + hash_add(&h_labels,label+(g_labelCnt<<6),g_labelCnt,0); + g_labelCnt++; + continue; + } + + C_GetNextLabelName(); + + if (EDUKE32_PREDICT_FALSE((j = hash_find(&h_labels,label+(g_labelCnt<<6))) < 0)) + { + C_ReportError(-1); + initprintf("%s:%d: error: state `%s' not found.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + g_errorCnt++; + g_scriptPtr++; + continue; + } + + if (EDUKE32_PREDICT_FALSE((labeltype[j] & LABEL_STATE) != LABEL_STATE)) + { + char *gl = (char *) C_GetLabelType(labeltype[j]); + C_ReportError(-1); + initprintf("%s:%d: warning: expected state, found %s.\n", g_scriptFileName, g_lineNumber, gl); + g_warningCnt++; + Bfree(gl); + *(g_scriptPtr-1) = CON_NULLOP; // get rid of the state, leaving a nullop to satisfy if conditions + BITPTR_CLEAR(g_scriptPtr-apScript-1); + continue; // valid label name, but wrong type + } + + if (!(g_errorCnt || g_warningCnt) && g_scriptDebug > 1) + initprintf("%s:%d: debug: state label `%s'.\n", g_scriptFileName, g_lineNumber, label+(j<<6)); + *g_scriptPtr = (intptr_t) (apScript+labelcode[j]); + + // 'state' type labels are always script addresses, as far as I can see + BITPTR_SET(g_scriptPtr-apScript); + + g_scriptPtr++; + continue; + + case CON_ENDS: + if (EDUKE32_PREDICT_FALSE(g_processingState == 0)) + { + C_ReportError(-1); + initprintf("%s:%d: error: found `ends' without open `state'.\n",g_scriptFileName,g_lineNumber); + g_errorCnt++; + } + // else + { + if (EDUKE32_PREDICT_FALSE(g_numBraces > 0)) + { + C_ReportError(ERROR_OPENBRACKET); + g_errorCnt++; + } + else if (EDUKE32_PREDICT_FALSE(g_numBraces < 0)) + { + C_ReportError(ERROR_CLOSEBRACKET); + g_errorCnt++; + } + + g_processingState = 0; + Bsprintf(g_szCurrentBlockName,"(none)"); + } + continue; + + case CON_DEFINE: + { + C_GetNextLabelName(); + + if (EDUKE32_PREDICT_FALSE(hash_find(&h_keywords,label+(g_labelCnt<<6))>=0)) + { + g_errorCnt++; + C_ReportError(ERROR_ISAKEYWORD); + continue; + } + + C_GetNextValue(LABEL_DEFINE); + + i = hash_find(&h_labels,label+(g_labelCnt<<6)); + if (i>=0) + { + // if (i >= g_numDefaultLabels) + + if (EDUKE32_PREDICT_FALSE(labelcode[i] != *(g_scriptPtr-1))) + { + g_warningCnt++; + initprintf("%s:%d: warning: ignored redefinition of `%s' to %d (old: %d).\n",g_scriptFileName, + g_lineNumber,label+(g_labelCnt<<6), (int32_t)(*(g_scriptPtr-1)), labelcode[i]); + } + } + else + { + hash_add(&h_labels,label+(g_labelCnt<<6),g_labelCnt,0); + labeltype[g_labelCnt] = LABEL_DEFINE; + labelcode[g_labelCnt++] = *(g_scriptPtr-1); + //if (*(g_scriptPtr-1) >= 0 && *(g_scriptPtr-1) < MAXTILES && g_dynamicTileMapping) + // G_ProcessDynamicTileMapping(label+((g_labelCnt-1)<<6),*(g_scriptPtr-1)); + } + g_scriptPtr -= 2; + continue; + } + + case CON_PALFROM: + for (j=3; j>=0; j--) + { + if (C_GetKeyword() == -1) + C_GetNextValue(LABEL_DEFINE); + else break; + } + + while (j>-1) + { + BITPTR_CLEAR(g_scriptPtr-apScript); + *g_scriptPtr++ = 0; + j--; + } + continue; + + case CON_MOVE: + if (g_parsingActorPtr || g_processingState) + { + if (EDUKE32_PREDICT_FALSE((C_GetNextValue(LABEL_MOVE|LABEL_DEFINE) == 0) && (*(g_scriptPtr-1) != 0) && (*(g_scriptPtr-1) != 1))) + { + C_ReportError(-1); + BITPTR_CLEAR(g_scriptPtr-apScript-1); + *(g_scriptPtr-1) = 0; + initprintf("%s:%d: warning: expected a move, found a constant.\n",g_scriptFileName,g_lineNumber); + g_warningCnt++; + } + + j = 0; + while (C_GetKeyword() == -1) + C_BitOrNextValue(&j); + + C_FinishBitOr(j); + } + else + { + g_scriptPtr--; + C_GetNextLabelName(); + // Check to see it's already defined + + if (EDUKE32_PREDICT_FALSE(hash_find(&h_keywords,label+(g_labelCnt<<6))>=0)) + { + g_errorCnt++; + C_ReportError(ERROR_ISAKEYWORD); + continue; + } + + if (EDUKE32_PREDICT_FALSE((i = hash_find(&h_labels,label+(g_labelCnt<<6))) >= 0)) + { + g_warningCnt++; + initprintf("%s:%d: warning: duplicate move `%s' ignored.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + } + else + { + hash_add(&h_labels,label+(g_labelCnt<<6),g_labelCnt,0); + labeltype[g_labelCnt] = LABEL_MOVE; + labelcode[g_labelCnt++] = g_scriptPtr-apScript; + } + + for (j=1; j>=0; j--) + { + if (C_GetKeyword() != -1) break; + C_GetNextValue(LABEL_DEFINE); + } + + for (k=j; k>=0; k--) + { + BITPTR_CLEAR(g_scriptPtr-apScript); + *g_scriptPtr = 0; + g_scriptPtr++; + } + } + continue; + + case CON_MUSIC: + { + // NOTE: this doesn't get stored in the PCode... + + // music 1 stalker.mid dethtoll.mid streets.mid watrwld1.mid snake1.mid + // thecall.mid ahgeez.mid dethtoll.mid streets.mid watrwld1.mid snake1.mid + g_scriptPtr--; + C_GetNextValue(LABEL_DEFINE); // Volume Number (0/4) + g_scriptPtr--; + + k = *g_scriptPtr-1; // 0-based volume number. -1 or MAXVOLUMES: "special" + if (k == -1) + k = MAXVOLUMES; + + if (EDUKE32_PREDICT_FALSE((unsigned)k >= MAXVOLUMES+1)) // if it's not background or special music + { + g_errorCnt++; + C_ReportError(-1); + initprintf("%s:%d: error: volume number must be between 0 and MAXVOLUMES+1=%d.\n", + g_scriptFileName, g_lineNumber, MAXVOLUMES+1); + continue; + + } + + i = 0; + // get the file name... + while (C_GetKeyword() == -1) + { + C_SkipComments(); + + j = 0; + tempbuf[j] = '/'; + while (isaltok(*(textptr+j))) + { + tempbuf[j+1] = textptr[j]; + j++; + } + tempbuf[j+1] = '\0'; + + C_DefineMusic(k, i, tempbuf); + + textptr += j; + + if (i >= MAXLEVELS) + break; + i++; + } + } + continue; + + case CON_INCLUDE: + g_scriptPtr--; + + C_SkipComments(); + while (isaltok(*textptr) == 0) + { + textptr++; + if (*textptr == 0) break; + } + + j = 0; + while (isaltok(*textptr)) + { + tempbuf[j] = *(textptr++); + j++; + } + tempbuf[j] = '\0'; + + C_Include(tempbuf); + continue; + + case CON_AI: + if (g_parsingActorPtr || g_processingState) + { + C_GetNextValue(LABEL_AI); + } + else + { + g_scriptPtr--; + C_GetNextLabelName(); + + if (EDUKE32_PREDICT_FALSE(hash_find(&h_keywords,label+(g_labelCnt<<6))>=0)) + { + g_errorCnt++; + C_ReportError(ERROR_ISAKEYWORD); + continue; + } + + i = hash_find(&h_labels,label+(g_labelCnt<<6)); + if (EDUKE32_PREDICT_FALSE(i>=0)) + { + g_warningCnt++; + initprintf("%s:%d: warning: duplicate ai `%s' ignored.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + } + else + { + labeltype[g_labelCnt] = LABEL_AI; + hash_add(&h_labels,label+(g_labelCnt<<6),g_labelCnt,0); + labelcode[g_labelCnt++] = g_scriptPtr-apScript; + } + + for (j=0; j<3; j++) + { + if (C_GetKeyword() != -1) break; + if (j == 1) + C_GetNextValue(LABEL_ACTION); + else if (j == 2) + { + if (EDUKE32_PREDICT_FALSE((C_GetNextValue(LABEL_MOVE|LABEL_DEFINE) == 0) && + (*(g_scriptPtr-1) != 0) && (*(g_scriptPtr-1) != 1))) + { + C_ReportError(-1); + BITPTR_CLEAR(g_scriptPtr-apScript-1); + *(g_scriptPtr-1) = 0; + initprintf("%s:%d: warning: expected a move, found a constant.\n",g_scriptFileName,g_lineNumber); + g_warningCnt++; + } + + k = 0; + while (C_GetKeyword() == -1) + C_BitOrNextValue(&k); + + C_FinishBitOr(k); + j = 666; + break; + } + } + + if (j == 666) + continue; + + for (k=j; k<3; k++) + { + BITPTR_CLEAR(g_scriptPtr-apScript); + *g_scriptPtr = 0; + g_scriptPtr++; + } + } + continue; + + case CON_ACTION: + if (g_parsingActorPtr || g_processingState) + { + C_GetNextValue(LABEL_ACTION); + } + else + { + g_scriptPtr--; + C_GetNextLabelName(); + // Check to see it's already defined + + if (EDUKE32_PREDICT_FALSE(hash_find(&h_keywords,label+(g_labelCnt<<6))>=0)) + { + g_errorCnt++; + C_ReportError(ERROR_ISAKEYWORD); + continue; + } + + i = hash_find(&h_labels,label+(g_labelCnt<<6)); + if (EDUKE32_PREDICT_FALSE(i>=0)) + { + g_warningCnt++; + initprintf("%s:%d: warning: duplicate action `%s' ignored.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + } + else + { + labeltype[g_labelCnt] = LABEL_ACTION; + labelcode[g_labelCnt] = g_scriptPtr-apScript; + hash_add(&h_labels,label+(g_labelCnt<<6),g_labelCnt,0); + g_labelCnt++; + } + + for (j=ACTION_PARAM_COUNT-1; j>=0; j--) + { + if (C_GetKeyword() != -1) break; + C_GetNextValue(LABEL_DEFINE); + } + for (k=j; k>=0; k--) + { + BITPTR_CLEAR(g_scriptPtr-apScript); + *(g_scriptPtr++) = 0; + } + } + continue; + + case CON_ACTOR: + case CON_USERACTOR: + if (EDUKE32_PREDICT_FALSE(g_processingState || g_parsingActorPtr)) + { + C_ReportError(ERROR_FOUNDWITHIN); + g_errorCnt++; + } + + g_numBraces = 0; + g_scriptPtr--; + g_parsingActorPtr = g_scriptPtr - apScript; + + if (tw == CON_USERACTOR) + { + C_GetNextValue(LABEL_DEFINE); + g_scriptPtr--; + } + + // save the actor name w/o consuming it + C_SkipComments(); + j = 0; + while (isaltok(*(textptr+j))) + { + g_szCurrentBlockName[j] = textptr[j]; + j++; + } + g_szCurrentBlockName[j] = 0; + + j = hash_find(&h_labels, g_szCurrentBlockName); + + if (j != -1) + labeltype[j] |= LABEL_ACTOR; + + if (tw == CON_USERACTOR) + { + j = *g_scriptPtr; + + if (EDUKE32_PREDICT_FALSE(j >= 3)) + { + C_ReportError(-1); + initprintf("%s:%d: warning: invalid useractor type. Must be 0, 1, 2" + " (notenemy, enemy, enemystayput).\n", + g_scriptFileName,g_lineNumber); + g_warningCnt++; + j = 0; + } + } + + C_GetNextValue(LABEL_ACTOR); + g_scriptPtr--; + + if (EDUKE32_PREDICT_FALSE((unsigned)*g_scriptPtr >= MAXTILES)) + { + C_ReportError(ERROR_EXCEEDSMAXTILES); + g_errorCnt++; + continue; + } + + g_tile[*g_scriptPtr].execPtr = apScript + g_parsingActorPtr; + + if (tw == CON_USERACTOR) + { + if (j & 1) + g_tile[*g_scriptPtr].flags |= SFLAG_BADGUY; + + if (j & 2) + g_tile[*g_scriptPtr].flags |= (SFLAG_BADGUY|SFLAG_BADGUYSTAYPUT); + } + + for (j=0; j<4; j++) + { + BITPTR_CLEAR(g_parsingActorPtr+j); + *((apScript+j)+g_parsingActorPtr) = 0; + if (j == 3) + { + j = 0; + while (C_GetKeyword() == -1) + C_BitOrNextValue(&j); + + C_FinishBitOr(j); + break; + } + else + { + if (C_GetKeyword() != -1) + { + for (i=4-j; i; i--) + { + BITPTR_CLEAR(g_scriptPtr-apScript); + *(g_scriptPtr++) = 0; + } + break; + } + switch (j) + { + case 0: + C_GetNextValue(LABEL_DEFINE); + break; + case 1: + C_GetNextValue(LABEL_ACTION); + break; + case 2: + // XXX: LABEL_MOVE|LABEL_DEFINE, what is this shit? compatibility? + // yep, it sure is :( + if (EDUKE32_PREDICT_FALSE((C_GetNextValue(LABEL_MOVE|LABEL_DEFINE) == 0) && (*(g_scriptPtr-1) != 0) && (*(g_scriptPtr-1) != 1))) + { + C_ReportError(-1); + BITPTR_CLEAR(g_scriptPtr-apScript-1); + *(g_scriptPtr-1) = 0; + initprintf("%s:%d: warning: expected a move, found a constant.\n",g_scriptFileName,g_lineNumber); + g_warningCnt++; + } + break; + } + if (*(g_scriptPtr-1) >= (intptr_t)&apScript[0] && *(g_scriptPtr-1) < (intptr_t)&apScript[g_scriptSize]) + BITPTR_SET(g_parsingActorPtr+j); + else BITPTR_CLEAR(g_parsingActorPtr+j); + *((apScript+j)+g_parsingActorPtr) = *(g_scriptPtr-1); + } + } + g_checkingIfElse = 0; + continue; + + case CON_CSTAT: + C_GetNextValue(LABEL_DEFINE); + + if (EDUKE32_PREDICT_FALSE(*(g_scriptPtr-1) == 32767)) + { + *(g_scriptPtr-1) = 32768; + C_ReportError(-1); + initprintf("%s:%d: warning: tried to set cstat 32767, using 32768 instead.\n",g_scriptFileName,g_lineNumber); + g_warningCnt++; + } + else if (EDUKE32_PREDICT_FALSE((*(g_scriptPtr-1) & 48) == 48)) + { + i = *(g_scriptPtr-1); + *(g_scriptPtr-1) ^= 48; + C_ReportError(-1); + initprintf("%s:%d: warning: tried to set cstat %d, using %d instead.\n",g_scriptFileName,g_lineNumber,i,(int32_t)(*(g_scriptPtr-1))); + g_warningCnt++; + } + continue; + + case CON_HITRADIUS: + C_GetNextValue(LABEL_DEFINE); + C_GetNextValue(LABEL_DEFINE); + C_GetNextValue(LABEL_DEFINE); + fallthrough__; + case CON_ADDAMMO: + case CON_ADDWEAPON: + case CON_SIZETO: + case CON_SIZEAT: + case CON_DEBRIS: + case CON_ADDINVENTORY: + case CON_GUTS: + C_GetNextValue(LABEL_DEFINE); + fallthrough__; + case CON_STRENGTH: + case CON_SHOOT: + case CON_ADDPHEALTH: + case CON_SPAWN: + case CON_COUNT: + case CON_ENDOFGAME: + case CON_SPRITEPAL: + case CON_CACTOR: + case CON_MONEY: + case CON_ADDKILLS: + case CON_DEBUG: + case CON_ADDSTRENGTH: + case CON_CSTATOR: + case CON_MAIL: + case CON_PAPER: + case CON_SLEEPTIME: + case CON_CLIPDIST: + case CON_ISDRUNK: + case CON_ISEAT: + case CON_NEWPIC: + case CON_LOTSOFGLASS: + case CON_QUOTE: + case CON_SOUND: + case CON_GLOBALSOUND: + case CON_SOUNDONCE: + case CON_STOPSOUND: + C_GetNextValue(LABEL_DEFINE); + continue; + + case CON_ELSE: + { + if (EDUKE32_PREDICT_FALSE(!g_checkingIfElse)) + { + g_scriptPtr--; + intptr_t *tempscrptr = g_scriptPtr; + g_warningCnt++; + C_ReportError(-1); + + initprintf("%s:%d: warning: found `else' with no `if'.\n", g_scriptFileName, g_lineNumber); + + if (C_GetKeyword() == CON_LEFTBRACE) + { + C_GetNextKeyword(); + g_numBraces++; + + C_ParseCommand(1); + } + else C_ParseCommand(0); + + g_scriptPtr = tempscrptr; + + continue; + } + + intptr_t const lastScriptPtr = g_scriptPtr - apScript - 1; + + g_ifElseAborted = 0; + g_checkingIfElse--; + + if (C_CheckMalformedBranch(lastScriptPtr)) + continue; + + intptr_t const offset = (unsigned) (g_scriptPtr-apScript); + + g_scriptPtr++; //Leave a spot for the fail location + + C_ParseCommand(0); + + if (C_CheckEmptyBranch(tw, lastScriptPtr)) + continue; + + intptr_t *tempscrptr = (intptr_t *) apScript+offset; + *tempscrptr = (intptr_t) g_scriptPtr; + BITPTR_SET(tempscrptr-apScript); + + continue; + } + + case CON_IFRND: + case CON_IFPDISTL: + case CON_IFPDISTG: + case CON_IFWASWEAPON: + case CON_IFACTIONCOUNT: + case CON_IFCOUNT: + case CON_IFACTOR: + case CON_IFSTRENGTH: + case CON_IFSPAWNEDBY: + case CON_IFGAPZL: + case CON_IFFLOORDISTL: + case CON_IFCEILINGDISTL: + case CON_IFPHEALTHL: + case CON_IFSPRITEPAL: + case CON_IFGOTWEAPONCE: + case CON_IFANGDIFFL: + case CON_IFACTORHEALTHG: + case CON_IFACTORHEALTHL: + case CON_IFSOUNDID: + case CON_IFSOUNDDIST: + case CON_IFAI: + case CON_IFACTION: + case CON_IFMOVE: + case CON_IFP: + case CON_IFPINVENTORY: + { + intptr_t offset; + intptr_t lastScriptPtr = (g_scriptPtr-&apScript[0]-1); + + g_ifElseAborted = 0; + + switch (tw) + { + case CON_IFAI: + C_GetNextValue(LABEL_AI); + break; + case CON_IFACTION: + C_GetNextValue(LABEL_ACTION); + break; + case CON_IFMOVE: + if (EDUKE32_PREDICT_FALSE((C_GetNextValue(LABEL_MOVE|LABEL_DEFINE) == 0) && (*(g_scriptPtr-1) != 0) && (*(g_scriptPtr-1) != 1))) + { + C_ReportError(-1); + *(g_scriptPtr-1) = 0; + initprintf("%s:%d: warning: expected a move, found a constant.\n",g_scriptFileName,g_lineNumber); + g_warningCnt++; + } + break; + case CON_IFPINVENTORY: + C_GetNextValue(LABEL_DEFINE); + C_GetNextValue(LABEL_DEFINE); + break; + case CON_IFP: + j = 0; + do + C_BitOrNextValue(&j); + while (C_GetKeyword() == -1); + C_FinishBitOr(j); + break; + default: + C_GetNextValue(LABEL_DEFINE); + break; + } + + if (C_CheckMalformedBranch(lastScriptPtr)) + continue; + + intptr_t *tempscrptr = g_scriptPtr; + offset = (unsigned)(tempscrptr-apScript); + + g_scriptPtr++; //Leave a spot for the fail location + + C_ParseCommand(0); + + if (C_CheckEmptyBranch(tw, lastScriptPtr)) + continue; + + tempscrptr = (intptr_t *)apScript+offset; + *tempscrptr = (intptr_t) g_scriptPtr; + BITPTR_SET(tempscrptr-apScript); + + j = C_GetKeyword(); + + if (j == CON_ELSE || j == CON_LEFTBRACE) + g_checkingIfElse++; + + continue; + } + + case CON_IFONWATER: + case CON_IFINWATER: + case CON_IFACTORNOTSTAYPUT: + case CON_IFCANSEE: + case CON_IFHITWEAPON: + case CON_IFSQUISHED: + case CON_IFDEAD: + case CON_IFCANSHOOTTARGET: + case CON_IFHITSPACE: + case CON_IFOUTSIDE: + case CON_IFMULTIPLAYER: + case CON_IFINSPACE: + case CON_IFBULLETNEAR: + case CON_IFRESPAWN: + case CON_IFINOUTERSPACE: + case CON_IFNOTMOVING: + case CON_IFAWAYFROMWALL: + case CON_IFCANSEETARGET: + case CON_IFNOSOUNDS: + case CON_IFNOCOVER: + case CON_IFHITTRUCK: + case CON_IFTIPCOW: + case CON_IFONMUD: + case CON_IFCOOP: + case CON_IFMOTOFAST: + case CON_IFWIND: + case CON_IFONMOTO: + case CON_IFONBOAT: + case CON_IFSIZEDOWN: + { + intptr_t offset; + intptr_t lastScriptPtr = (g_scriptPtr-&apScript[0]-1); + + g_ifElseAborted = 0; + + if (C_CheckMalformedBranch(lastScriptPtr)) + continue; + + intptr_t *tempscrptr = g_scriptPtr; + offset = (unsigned)(tempscrptr-apScript); + + g_scriptPtr++; //Leave a spot for the fail location + + C_ParseCommand(0); + + if (C_CheckEmptyBranch(tw, lastScriptPtr)) + continue; + + tempscrptr = (intptr_t *)apScript+offset; + *tempscrptr = (intptr_t) g_scriptPtr; + BITPTR_SET(tempscrptr-apScript); + + j = C_GetKeyword(); + + if (j == CON_ELSE || j == CON_LEFTBRACE) + g_checkingIfElse++; + + continue; + } + + case CON_LEFTBRACE: + if (EDUKE32_PREDICT_FALSE(!(g_processingState || g_parsingActorPtr))) + { + g_errorCnt++; + C_ReportError(ERROR_SYNTAXERROR); + } + g_numBraces++; + + C_ParseCommand(1); + continue; + + case CON_RIGHTBRACE: + g_numBraces--; + + if ((*(g_scriptPtr-2)>>12) == (IFELSE_MAGIC) && + ((*(g_scriptPtr-2) & VM_INSTMASK) == CON_LEFTBRACE)) // rewrite "{ }" into "nullop" + { + // initprintf("%s:%d: rewriting empty braces '{ }' as 'nullop' from right\n",g_szScriptFileName,g_lineNumber); + *(g_scriptPtr-2) = CON_NULLOP + (IFELSE_MAGIC<<12); + g_scriptPtr -= 2; + + if (C_GetKeyword() != CON_ELSE && (*(g_scriptPtr-2) & VM_INSTMASK) != CON_ELSE) + g_ifElseAborted = 1; + else g_ifElseAborted = 0; + + j = C_GetKeyword(); + + if (g_checkingIfElse && j != CON_ELSE) + g_checkingIfElse--; + + return 1; + } + + if (EDUKE32_PREDICT_FALSE(g_numBraces < 0)) + { + C_ReportError(-1); + initprintf("%s:%d: error: found more `}' than `{'.\n",g_scriptFileName,g_lineNumber); + g_errorCnt++; + } + + if (g_checkingIfElse && j != CON_ELSE) + g_checkingIfElse--; + + return 1; + + case CON_BETANAME: + g_scriptPtr--; + j = 0; + C_NextLine(); + continue; + + case CON_DEFINEVOLUMENAME: + g_scriptPtr--; + + C_GetNextValue(LABEL_DEFINE); + g_scriptPtr--; + j = *g_scriptPtr; + + C_SkipSpace(); + + if (EDUKE32_PREDICT_FALSE((unsigned)j > MAXVOLUMES-1)) + { + initprintf("%s:%d: error: volume number exceeds maximum volume count.\n", + g_scriptFileName,g_lineNumber); + g_errorCnt++; + C_NextLine(); + continue; + } + + i = 0; + + while (*textptr != 0x0a && *textptr != 0x0d && *textptr != 0) + { + g_volumeNames[j][i] = *textptr; + textptr++,i++; + if (EDUKE32_PREDICT_FALSE(i >= (signed)sizeof(g_volumeNames[j])-1)) + { + initprintf("%s:%d: warning: truncating volume name to %d characters.\n", + g_scriptFileName,g_lineNumber,(int32_t)sizeof(g_volumeNames[j])-1); + g_warningCnt++; + C_NextLine(); + break; + } + } + g_volumeCnt = j+1; + g_volumeNames[j][i] = '\0'; + continue; + + case CON_DEFINESKILLNAME: + g_scriptPtr--; + + C_GetNextValue(LABEL_DEFINE); + g_scriptPtr--; + j = *g_scriptPtr; + + C_SkipSpace(); + + if (EDUKE32_PREDICT_FALSE((unsigned)j >= MAXSKILLS)) + { + initprintf("%s:%d: error: skill number exceeds maximum skill count %d.\n", + g_scriptFileName,g_lineNumber, MAXSKILLS); + g_errorCnt++; + C_NextLine(); + continue; + } + + i = 0; + + while (*textptr != 0x0a && *textptr != 0x0d && *textptr != 0) + { + g_skillNames[j][i] = *textptr; + textptr++,i++; + if (EDUKE32_PREDICT_FALSE(i >= (signed)sizeof(g_skillNames[j])-1)) + { + initprintf("%s:%d: warning: truncating skill name to %d characters.\n", + g_scriptFileName,g_lineNumber,(int32_t)sizeof(g_skillNames[j])-1); + g_warningCnt++; + C_NextLine(); + break; + } + } + + g_skillNames[j][i] = '\0'; + + for (i=0; i MAXVOLUMES-1)) + { + initprintf("%s:%d: error: volume number exceeds maximum volume count.\n",g_scriptFileName,g_lineNumber); + g_errorCnt++; + C_NextLine(); + continue; + } + if (EDUKE32_PREDICT_FALSE((unsigned)k > MAXLEVELS-1)) + { + initprintf("%s:%d: error: level number exceeds maximum number of levels per episode.\n",g_scriptFileName,g_lineNumber); + g_errorCnt++; + C_NextLine(); + continue; + } + + i = 0; + + tempbuf[i] = '/'; + + while (*textptr != ' ' && *textptr != '\t' && *textptr != 0x0a) + { + tempbuf[i+1] = *textptr; + textptr++,i++; + if (EDUKE32_PREDICT_FALSE(i >= BMAX_PATH)) + { + initprintf("%s:%d: error: level file name exceeds limit of %d characters.\n",g_scriptFileName,g_lineNumber,BMAX_PATH); + g_errorCnt++; + C_SkipSpace(); + break; + } + } + tempbuf[i+1] = '\0'; + + Bcorrectfilename(tempbuf,0); + + if (g_mapInfo[j *MAXLEVELS+k].filename == NULL) + g_mapInfo[j *MAXLEVELS+k].filename = (char *)Xcalloc(Bstrlen(tempbuf)+1,sizeof(uint8_t)); + else if ((Bstrlen(tempbuf)+1) > sizeof(g_mapInfo[j*MAXLEVELS+k].filename)) + g_mapInfo[j *MAXLEVELS+k].filename = (char *)Xrealloc(g_mapInfo[j*MAXLEVELS+k].filename,(Bstrlen(tempbuf)+1)); + + Bstrcpy(g_mapInfo[j*MAXLEVELS+k].filename,tempbuf); + + C_SkipComments(); + + g_mapInfo[j *MAXLEVELS+k].partime = + (((*(textptr+0)-'0')*10+(*(textptr+1)-'0'))*REALGAMETICSPERSEC*60)+ + (((*(textptr+3)-'0')*10+(*(textptr+4)-'0'))*REALGAMETICSPERSEC); + + textptr += 5; + C_SkipSpace(); + + // cheap hack, 0.99 doesn't have the 3D Realms time + if (*(textptr+2) == ':') + { + g_mapInfo[j *MAXLEVELS+k].designertime = + (((*(textptr+0)-'0')*10+(*(textptr+1)-'0'))*REALGAMETICSPERSEC*60)+ + (((*(textptr+3)-'0')*10+(*(textptr+4)-'0'))*REALGAMETICSPERSEC); + + textptr += 5; + C_SkipSpace(); + } + else if (g_scriptVersion == 10) g_scriptVersion = 9; + + i = 0; + + while (*textptr != 0x0a && *textptr != 0x0d && *textptr != 0) + { + tempbuf[i] = *textptr; + textptr++,i++; + if (EDUKE32_PREDICT_FALSE(i >= 32)) + { + initprintf("%s:%d: warning: truncating level name to %d characters.\n", + g_scriptFileName,g_lineNumber,32); + g_warningCnt++; + C_NextLine(); + break; + } + } + + tempbuf[i] = '\0'; + + if (g_mapInfo[j*MAXLEVELS+k].name == NULL) + g_mapInfo[j*MAXLEVELS+k].name = (char *)Xcalloc(Bstrlen(tempbuf)+1,sizeof(uint8_t)); + else if ((Bstrlen(tempbuf)+1) > sizeof(g_mapInfo[j*MAXLEVELS+k].name)) + g_mapInfo[j *MAXLEVELS+k].name = (char *)Xrealloc(g_mapInfo[j*MAXLEVELS+k].name,(Bstrlen(tempbuf)+1)); + + /* initprintf("level name string len: %d\n",Bstrlen(tempbuf)); */ + + Bstrcpy(g_mapInfo[j*MAXLEVELS+k].name,tempbuf); + + continue; + + case CON_DEFINEQUOTE: + g_scriptPtr--; + + C_GetNextValue(LABEL_DEFINE); + + k = *(g_scriptPtr-1); + + if (EDUKE32_PREDICT_FALSE((unsigned)k >= MAXQUOTES)) + { + initprintf("%s:%d: error: quote number exceeds limit of %d.\n",g_scriptFileName,g_lineNumber,MAXQUOTES); + g_errorCnt++; + } + else + { + C_AllocQuote(k); + } + + g_scriptPtr--; + + i = 0; + + C_SkipSpace(); + + while (*textptr != 0x0a && *textptr != 0x0d && *textptr != 0) + { + /* + if (*textptr == '%' && *(textptr+1) == 's') + { + initprintf("%s:%d: error: quote text contains string identifier.\n",g_szScriptFileName,g_lineNumber); + g_numCompilerErrors++; + while (*textptr != 0x0a && *textptr != 0x0d && *textptr != 0) textptr++; + break; + } + */ + *(apStrings[k]+i) = *textptr; + + textptr++,i++; + if (EDUKE32_PREDICT_FALSE(i >= MAXQUOTELEN-1)) + { + initprintf("%s:%d: warning: truncating quote text to %d characters.\n",g_scriptFileName,g_lineNumber,MAXQUOTELEN-1); + g_warningCnt++; + C_NextLine(); + break; + } + } + + if ((unsigned)k < MAXQUOTES) + *(apStrings[k]+i) = '\0'; + continue; + + case CON_DEFINESOUND: + g_scriptPtr--; + C_GetNextValue(LABEL_DEFINE); + + // Ideally we could keep the value of i from C_GetNextValue() instead of having to hash_find() again. + // This depends on tempbuf remaining in place after C_GetNextValue(): + j = hash_find(&h_labels,tempbuf); + + k = *(g_scriptPtr-1); + if (EDUKE32_PREDICT_FALSE((unsigned)k >= MAXSOUNDS-1)) + { + initprintf("%s:%d: error: index exceeds sound limit of %d.\n",g_scriptFileName,g_lineNumber, MAXSOUNDS-1); + g_errorCnt++; + k = MAXSOUNDS-1; + } + g_scriptPtr--; + i = 0; + C_SkipComments(); + + if (g_sounds[k].filename == NULL) + g_sounds[k].filename = (char *)Xcalloc(BMAX_PATH,sizeof(uint8_t)); + + if (*textptr == '\"') + { + textptr++; + while (*textptr && *textptr != '\"') + { + g_sounds[k].filename[i++] = *textptr++; + if (EDUKE32_PREDICT_FALSE(i >= BMAX_PATH-1)) + { + initprintf("%s:%d: error: sound filename exceeds limit of %d characters.\n",g_scriptFileName,g_lineNumber,BMAX_PATH-1); + g_errorCnt++; + C_SkipComments(); + break; + } + } + textptr++; + } + else while (*textptr != ' ' && *textptr != '\t' && *textptr != '\r' && *textptr != '\n') + { + g_sounds[k].filename[i++] = *textptr++; + if (EDUKE32_PREDICT_FALSE(i >= BMAX_PATH-1)) + { + initprintf("%s:%d: error: sound filename exceeds limit of %d characters.\n",g_scriptFileName,g_lineNumber,BMAX_PATH-1); + g_errorCnt++; + C_SkipComments(); + break; + } + } + g_sounds[k].filename[i] = '\0'; + + check_filename_case(g_sounds[k].filename); + + C_GetNextValue(LABEL_DEFINE); + g_sounds[k].ps = *(g_scriptPtr-1); + C_GetNextValue(LABEL_DEFINE); + g_sounds[k].pe = *(g_scriptPtr-1); + C_GetNextValue(LABEL_DEFINE); + g_sounds[k].pr = *(g_scriptPtr-1); + + C_GetNextValue(LABEL_DEFINE); + g_sounds[k].m = *(g_scriptPtr-1) & ~SF_ONEINST_INTERNAL; + if (*(g_scriptPtr-1) & SF_LOOP) + g_sounds[k].m |= SF_ONEINST_INTERNAL; + + C_GetNextValue(LABEL_DEFINE); + g_sounds[k].vo = *(g_scriptPtr-1); + g_scriptPtr -= 5; + + g_sounds[k].volume = 1.f; + + if (k > g_highestSoundIdx) + g_highestSoundIdx = k; + continue; + + case CON_ENDA: + if (EDUKE32_PREDICT_FALSE(!g_parsingActorPtr)) + { + C_ReportError(-1); + initprintf("%s:%d: error: found `enda' without open `actor'.\n",g_scriptFileName,g_lineNumber); + g_errorCnt++; + } + if (EDUKE32_PREDICT_FALSE(g_numBraces != 0)) + { + C_ReportError(g_numBraces > 0 ? ERROR_OPENBRACKET : ERROR_CLOSEBRACKET); + g_errorCnt++; + } + g_parsingActorPtr = 0; + Bsprintf(g_szCurrentBlockName,"(none)"); + continue; + + case CON_BREAK: + continue; + + case CON_FALL: + case CON_TIP: + // case 21: + case CON_KILLIT: + case CON_RESETACTIONCOUNT: + case CON_PSTOMP: + case CON_RESETPLAYER: + case CON_RESETCOUNT: + case CON_WACKPLAYER: + case CON_OPERATE: + case CON_RESPAWNHITAG: + case CON_GETLASTPAL: + case CON_PKICK: + case CON_MIKESND: + case CON_TOSSWEAPON: + case CON_DESTROYIT: + case CON_LARRYBIRD: + case CON_STRAFELEFT: + case CON_STRAFERIGHT: + case CON_SLAPPLAYER: + case CON_TEARITUP: + case CON_SMACKBUBBA: + case CON_SOUNDTAGONCE: + case CON_SOUNDTAG: + case CON_SMACKSPRITE: + case CON_FAKEBUBBA: + case CON_MAMATRIGGER: + case CON_MAMASPAWN: + case CON_MAMAQUAKE: + case CON_MAMAEND: + case CON_GARYBANJO: + case CON_MOTOLOOPSND: + case CON_RNDMOVE: + continue; + + case CON_NULLOP: + if (EDUKE32_PREDICT_FALSE(C_GetKeyword() != CON_ELSE)) + { + C_ReportError(-1); + g_warningCnt++; + initprintf("%s:%d: warning: `nullop' found without `else'\n",g_scriptFileName,g_lineNumber); + g_scriptPtr--; + g_ifElseAborted = 1; + } + continue; + + case CON_GAMESTARTUP: + { + int32_t params[34]; + + g_scriptPtr--; + for (j = 0; j < 34; j++) + { + C_GetNextValue(LABEL_DEFINE); + g_scriptPtr--; + params[j] = *g_scriptPtr; + + if (j != 29 && j != 30) continue; + + if (C_GetKeyword() != -1) + { + /*if (j == 12) + g_scriptVersion = 10; + else if (j == 21) + g_scriptVersion = 11; + else if (j == 25) + g_scriptVersion = 13; + else if (j == 29) + g_scriptVersion = 14;*/ + break; + } + /*else + g_scriptVersion = 16;*/ + } + + /* + v1.3d v1.5 + DEFAULTVISIBILITY DEFAULTVISIBILITY + GENERICIMPACTDAMAGE GENERICIMPACTDAMAGE + MAXPLAYERHEALTH MAXPLAYERHEALTH + STARTARMORHEALTH STARTARMORHEALTH + RESPAWNACTORTIME RESPAWNACTORTIME + RESPAWNITEMTIME RESPAWNITEMTIME + RUNNINGSPEED RUNNINGSPEED + RPGBLASTRADIUS GRAVITATIONALCONSTANT + PIPEBOMBRADIUS RPGBLASTRADIUS + SHRINKERBLASTRADIUS PIPEBOMBRADIUS + TRIPBOMBBLASTRADIUS SHRINKERBLASTRADIUS + MORTERBLASTRADIUS TRIPBOMBBLASTRADIUS + BOUNCEMINEBLASTRADIUS MORTERBLASTRADIUS + SEENINEBLASTRADIUS BOUNCEMINEBLASTRADIUS + MAXPISTOLAMMO SEENINEBLASTRADIUS + MAXSHOTGUNAMMO MAXPISTOLAMMO + MAXCHAINGUNAMMO MAXSHOTGUNAMMO + MAXRPGAMMO MAXCHAINGUNAMMO + MAXHANDBOMBAMMO MAXRPGAMMO + MAXSHRINKERAMMO MAXHANDBOMBAMMO + MAXDEVISTATORAMMO MAXSHRINKERAMMO + MAXTRIPBOMBAMMO MAXDEVISTATORAMMO + MAXFREEZEAMMO MAXTRIPBOMBAMMO + CAMERASDESTRUCTABLE MAXFREEZEAMMO + NUMFREEZEBOUNCES MAXGROWAMMO + FREEZERHURTOWNER CAMERASDESTRUCTABLE + NUMFREEZEBOUNCES + FREEZERHURTOWNER + QSIZE + TRIPBOMBLASERMODE + */ + + G_DoGameStartup(params); + } + continue; + } + } + while (loop); + + return 0; +} + +static char const * C_ScriptVersionString(int32_t version) +{ + switch (version) + { + case 9: + return ", v0.99 compatibility mode"; + case 10: + return ", v1.0 compatibility mode"; + case 11: + return ", v1.1 compatibility mode"; + case 13: + return ", v1.3D compatibility mode"; + default: + return ""; + } +} + +void C_PrintStats(void) +{ + initprintf("%d/%d labels\n", g_labelCnt, + (int32_t) min((MAXSECTORS * sizeof(sectortype)/sizeof(int32_t)), + MAXSPRITES * sizeof(spritetype)/(1<<6))); + + int i, j; + + for (i=MAXQUOTES-1, j=0; i>=0; i--) + { + if (apStrings[i]) + j++; + } + + if (j) initprintf("%d strings, ", j); + + for (i=MAXTILES-1, j=0; i>=0; i--) + { + if (g_tile[i].execPtr) + j++; + } + if (j) initprintf("%d actors", j); + + initprintf("\n"); +} + +void C_Compile(const char *fileName) +{ + for (int i=0; i > 3) + 1) * sizeof(uint8_t)); + // initprintf("script: %d, bitptr: %d\n",script,bitptr); + + g_labelCnt = 0; + g_defaultLabelCnt = 0; + g_scriptPtr = apScript + 3; // move permits constants 0 and 1; moveptr[1] would be script[2] (reachable?) + g_warningCnt = 0; + g_errorCnt = 0; + g_lineNumber = 1; + g_totalLines = 0; + + Bstrcpy(g_scriptFileName, fileName); + + C_ParseCommand(1); + + for (char * m : g_scriptModules) + { + C_Include(m); + free(m); + } + g_scriptModules.clear(); + + g_logFlushWindow = 1; + + if (g_errorCnt > 63) + initprintf("fatal error: too many errors: Aborted\n"); + + //*script = (intptr_t) g_scriptPtr; + + DO_FREE_AND_NULL(mptr); + + if (g_warningCnt || g_errorCnt) + initprintf("Found %d warning(s), %d error(s).\n", g_warningCnt, g_errorCnt); + + if (g_errorCnt) + { + Bsprintf(buf, "Error compiling CON files."); + G_GameExit(buf); + } + + g_totalLines += g_lineNumber; + + C_SetScriptSize(g_scriptPtr-apScript+8); + + initprintf("Script compiled in %dms, %ld bytes%s\n", timerGetTicks() - startcompiletime, + (unsigned long)(g_scriptPtr-apScript), C_ScriptVersionString(g_scriptVersion)); + + for (auto *i : tables_free) + hash_free(i); + + //freehashnames(); + freesoundhashnames(); + + if (g_scriptDebug) + C_PrintStats(); + + C_InitQuotes(); +} + +void C_ReportError(int32_t iError) +{ + if (Bstrcmp(g_szCurrentBlockName,g_szLastBlockName)) + { + if (g_processingState || g_parsingActorPtr) + initprintf("%s: In %s `%s':\n",g_scriptFileName,g_parsingActorPtr?"actor":"state",g_szCurrentBlockName); + else initprintf("%s: At top level:\n",g_scriptFileName); + Bstrcpy(g_szLastBlockName,g_szCurrentBlockName); + } + switch (iError) + { + case ERROR_CLOSEBRACKET: + initprintf("%s:%d: error: found more `}' than `{' before `%s'.\n",g_scriptFileName,g_lineNumber,tempbuf); + break; + case ERROR_EVENTONLY: + initprintf("%s:%d: error: keyword `%s' only available during events.\n",g_scriptFileName,g_lineNumber,tempbuf); + break; + case ERROR_EXCEEDSMAXTILES: + initprintf("%s:%d: error: `%s' value exceeds MAXTILES. Maximum is %d.\n",g_scriptFileName,g_lineNumber,tempbuf,MAXTILES-1); + break; + case ERROR_EXPECTEDKEYWORD: + initprintf("%s:%d: error: expected a keyword but found `%s'.\n",g_scriptFileName,g_lineNumber,tempbuf); + break; + case ERROR_FOUNDWITHIN: + initprintf("%s:%d: error: found `%s' within %s.\n",g_scriptFileName,g_lineNumber,tempbuf,g_parsingActorPtr?"an actor":"a state"); + break; + case ERROR_ISAKEYWORD: + initprintf("%s:%d: error: symbol `%s' is a keyword.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case ERROR_NOENDSWITCH: + initprintf("%s:%d: error: did not find `endswitch' before `%s'.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case ERROR_NOTAGAMEDEF: + initprintf("%s:%d: error: symbol `%s' is not a definition.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case ERROR_NOTAGAMEVAR: + initprintf("%s:%d: error: symbol `%s' is not a variable.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case ERROR_NOTAGAMEARRAY: + initprintf("%s:%d: error: symbol `%s' is not an array.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case ERROR_GAMEARRAYBNC: + initprintf("%s:%d: error: malformed array index: expected ], found %c\n",g_scriptFileName,g_lineNumber,*textptr); + break; + case ERROR_GAMEARRAYBNO: + initprintf("%s:%d: error: malformed array index: expected [, found %c\n",g_scriptFileName,g_lineNumber,*textptr); + break; + case ERROR_INVALIDARRAYWRITE: + initprintf("%s:%d: error: arrays can only be written to using `setarray'.\n",g_scriptFileName,g_lineNumber); + break; + case ERROR_OPENBRACKET: + initprintf("%s:%d: error: found more `{' than `}' before `%s'.\n",g_scriptFileName,g_lineNumber,tempbuf); + break; + case ERROR_PARAMUNDEFINED: + initprintf("%s:%d: error: parameter `%s' is undefined.\n",g_scriptFileName,g_lineNumber,tempbuf); + break; + case ERROR_NOTAMEMBER: + initprintf("%s:%d: error: symbol `%s' is not a valid structure member.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case ERROR_SYNTAXERROR: + initprintf("%s:%d: error: syntax error.\n",g_scriptFileName,g_lineNumber); + break; + case ERROR_VARREADONLY: + initprintf("%s:%d: error: variable `%s' is read-only.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case ERROR_ARRAYREADONLY: + initprintf("%s:%d: error: array `%s' is read-only.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case ERROR_VARTYPEMISMATCH: + initprintf("%s:%d: error: variable `%s' is of the wrong type.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case WARNING_BADGAMEVAR: + initprintf("%s:%d: warning: variable `%s' should be either per-player OR per-actor, not both.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case WARNING_DUPLICATECASE: + initprintf("%s:%d: warning: duplicate case ignored.\n",g_scriptFileName,g_lineNumber); + break; + case WARNING_DUPLICATEDEFINITION: + initprintf("%s:%d: warning: duplicate definition `%s' ignored.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case WARNING_EVENTSYNC: + initprintf("%s:%d: warning: found `%s' within a local event.\n",g_scriptFileName,g_lineNumber,tempbuf); + break; + case WARNING_LABELSONLY: + initprintf("%s:%d: warning: expected a label, found a constant.\n",g_scriptFileName,g_lineNumber); + break; + case WARNING_NAMEMATCHESVAR: + initprintf("%s:%d: warning: symbol `%s' already used for variable.\n",g_scriptFileName,g_lineNumber,label+(g_labelCnt<<6)); + break; + case WARNING_VARMASKSKEYWORD: + initprintf("%s:%d: warning: variable `%s' masks keyword.\n", g_scriptFileName, g_lineNumber, label+(g_labelCnt<<6)); + break; + case WARNING_ARRAYMASKSKEYWORD: + initprintf("%s:%d: warning: array `%s' masks keyword.\n", g_scriptFileName, g_lineNumber, label+(g_labelCnt<<6)); + break; + } +} + diff --git a/source/rr/src/gamedef.h b/source/rr/src/gamedef.h new file mode 100644 index 000000000..93c179aa8 --- /dev/null +++ b/source/rr/src/gamedef.h @@ -0,0 +1,870 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef gamedef_h_ +#define gamedef_h_ + +#include "build.h" // hashtable_t +#include "common.h" // tokenlist +#include "player.h" // projectile_t + +#ifdef __cplusplus +extern "C" { +#endif + +enum +{ + LABEL_ANY = -1, + LABEL_DEFINE = 1, + LABEL_STATE = 2, + LABEL_ACTOR = 4, + LABEL_ACTION = 8, + LABEL_AI = 16, + LABEL_MOVE = 32, + LABEL_EVENT = 64, +}; + +#define LABEL_HASPARM2 1 +#define LABEL_ISSTRING 2 + +#define MAXCHEATLEN 20 +#define NUMCHEATCODES (int32_t)ARRAY_SIZE(CheatStrings) + +#define VM_INSTMASK 0xfff + +#define C_CUSTOMERROR(Text, ...) \ + do \ + { \ + C_ReportError(-1); \ + initprintf("%s:%d: error: " Text "\n", g_scriptFileName, g_lineNumber, ##__VA_ARGS__); \ + g_errorCnt++; \ + } while (0) + +#define C_CUSTOMWARNING(Text, ...) \ + do \ + { \ + C_ReportError(-1); \ + initprintf("%s:%d: warning: " Text "\n", g_scriptFileName, g_lineNumber, ##__VA_ARGS__); \ + g_warningCnt++; \ + } while (0) + +extern intptr_t const * insptr; +extern void VM_ScriptInfo(intptr_t const *ptr, int range); + +extern hashtable_t h_gamefuncs; + +extern hashtable_t h_labels; + +extern int32_t g_aimAngleVarID; // var ID of "AUTOAIMANGLE" +extern int32_t g_angRangeVarID; // var ID of "ANGRANGE" +extern int32_t g_hitagVarID; // var ID of "HITAG" +extern int32_t g_lotagVarID; // var ID of "LOTAG" +extern int32_t g_returnVarID; // var ID of "RETURN" +extern int32_t g_textureVarID; // var ID of "TEXTURE" +extern int32_t g_thisActorVarID; // var ID of "THISACTOR" +extern int32_t g_weaponVarID; // var ID of "WEAPON" +extern int32_t g_worksLikeVarID; // var ID of "WORKSLIKE" +extern int32_t g_zRangeVarID; // var ID of "ZRANGE" + +// KEEPINSYNC gamevars.c: "special vars for struct access" +enum QuickStructureAccess_t +{ + STRUCT_SPRITE, + STRUCT_SECTOR, + STRUCT_WALL, + STRUCT_PLAYER, + STRUCT_ACTORVAR, + STRUCT_PLAYERVAR, + STRUCT_TSPR, + STRUCT_PROJECTILE, + STRUCT_THISPROJECTILE, + STRUCT_USERDEF, + STRUCT_INPUT, + STRUCT_TILEDATA, + STRUCT_PALDATA, + NUMQUICKSTRUCTS, +}; + +extern int32_t g_structVarIDs; + +extern char CheatStrings[][MAXCHEATLEN]; +extern char g_scriptFileName[BMAX_PATH]; + +extern const uint32_t CheatFunctionFlags[]; +extern const uint8_t CheatFunctionIDs[]; + +extern int32_t g_errorCnt; +extern int32_t g_lineNumber; +extern int32_t g_scriptVersion; +extern int32_t g_totalLines; +extern int32_t g_warningCnt; +extern uint32_t g_scriptcrc; +extern int32_t otherp; + +extern const char *EventNames[]; // MAXEVENTS + +extern intptr_t *g_scriptPtr; + +typedef struct +{ + const char *name; + int lId, flags, maxParm2; +} memberlabel_t; + +extern const memberlabel_t ActorLabels[]; +extern const memberlabel_t InputLabels[]; +extern const memberlabel_t PalDataLabels[]; +extern const memberlabel_t PlayerLabels[]; +extern const memberlabel_t ProjectileLabels[]; +extern const memberlabel_t SectorLabels[]; +extern const memberlabel_t TileDataLabels[]; +extern const memberlabel_t TsprLabels[]; +extern const memberlabel_t UserdefsLabels[]; +extern const memberlabel_t WallLabels[]; + +typedef projectile_t defaultprojectile_t; + +extern defaultprojectile_t DefaultProjectile; +int32_t C_AllocQuote(int32_t qnum); +void C_InitQuotes(void); + +extern int32_t g_numProjectiles; + +typedef struct { + int spriteNum; + int playerNum; + int playerDist; + int flags; + + union { + spritetype * pSprite; + uspritetype *pUSprite; + }; + + int32_t * pData; + DukePlayer_t *pPlayer; + actor_t * pActor; +} vmstate_t; + +extern vmstate_t vm; + +void G_DoGameStartup(const int32_t *params); +void C_DefineMusic(int volumeNum, int levelNum, const char *fileName); + +void C_DefineVolumeFlags(int32_t vol, int32_t flags); +void C_UndefineVolume(int32_t vol); +void C_UndefineSkill(int32_t skill); +void C_UndefineLevel(int32_t vol, int32_t lev); +void C_ReportError(int32_t iError); +void C_Compile(const char *filenam); + +extern int32_t g_errorLineNum; +extern int32_t g_tw; + +typedef struct { + const char* token; + int32_t val; +} tokenmap_t; + +extern char const * VM_GetKeywordForID(int32_t id); + +// KEEPINSYNC lunatic/con_lang.lua +enum SystemString_t { + STR_MAPNAME, + STR_MAPFILENAME, + STR_PLAYERNAME, + STR_VERSION, + STR_GAMETYPE, + STR_VOLUMENAME, + STR_YOURTIME, + STR_PARTIME, + STR_DESIGNERTIME, + STR_BESTTIME, + STR_USERMAPFILENAME, +}; + +enum ScriptError_t +{ + ERROR_CLOSEBRACKET, + ERROR_EVENTONLY, + ERROR_EXCEEDSMAXTILES, + ERROR_EXPECTEDKEYWORD, + ERROR_FOUNDWITHIN, + ERROR_ISAKEYWORD, + ERROR_NOENDSWITCH, + ERROR_NOTAGAMEDEF, + ERROR_NOTAGAMEVAR, + ERROR_NOTAGAMEARRAY, + ERROR_GAMEARRAYBNC, + ERROR_GAMEARRAYBNO, + ERROR_INVALIDARRAYWRITE, + ERROR_OPENBRACKET, + ERROR_PARAMUNDEFINED, + ERROR_NOTAMEMBER, + ERROR_SYNTAXERROR, + ERROR_VARREADONLY, + ERROR_ARRAYREADONLY, + ERROR_VARTYPEMISMATCH, + WARNING_BADGAMEVAR, + WARNING_DUPLICATECASE, + WARNING_DUPLICATEDEFINITION, + WARNING_EVENTSYNC, + WARNING_LABELSONLY, + WARNING_NAMEMATCHESVAR, + WARNING_VARMASKSKEYWORD, + WARNING_ARRAYMASKSKEYWORD +}; + +enum PlayerLabel_t +{ + PLAYER_ZOOM, + PLAYER_EXITX, + PLAYER_EXITY, + PLAYER_LOOGIEX, + PLAYER_LOOGIEY, + PLAYER_NUMLOOGS, + PLAYER_LOOGCNT, + PLAYER_POSX, + PLAYER_POSY, + PLAYER_POSZ, + PLAYER_HORIZ, + PLAYER_OHORIZ, + PLAYER_OHORIZOFF, + PLAYER_INVDISPTIME, + PLAYER_BOBPOSX, + PLAYER_BOBPOSY, + PLAYER_OPOSX, + PLAYER_OPOSY, + PLAYER_OPOSZ, + PLAYER_PYOFF, + PLAYER_OPYOFF, + PLAYER_POSXV, + PLAYER_POSYV, + PLAYER_POSZV, + PLAYER_LAST_PISSED_TIME, + PLAYER_TRUEFZ, + PLAYER_TRUECZ, + PLAYER_PLAYER_PAR, + PLAYER_VISIBILITY, + PLAYER_BOBCOUNTER, + PLAYER_WEAPON_SWAY, + PLAYER_PALS_TIME, + PLAYER_RANDOMFLAMEX, + PLAYER_CRACK_TIME, + PLAYER_AIM_MODE, + PLAYER_ANG, + PLAYER_OANG, + PLAYER_ANGVEL, + PLAYER_CURSECTNUM, + PLAYER_LOOK_ANG, + PLAYER_LAST_EXTRA, + PLAYER_SUBWEAPON, + PLAYER_AMMO_AMOUNT, + PLAYER_WACKEDBYACTOR, + PLAYER_FRAG, + PLAYER_FRAGGEDSELF, + PLAYER_CURR_WEAPON, + PLAYER_LAST_WEAPON, + PLAYER_TIPINCS, + PLAYER_HORIZOFF, + PLAYER_WANTWEAPONFIRE, + PLAYER_HOLODUKE_AMOUNT, + PLAYER_NEWOWNER, + PLAYER_HURT_DELAY, + PLAYER_HBOMB_HOLD_DELAY, + PLAYER_JUMPING_COUNTER, + PLAYER_AIRLEFT, + PLAYER_KNEE_INCS, + PLAYER_ACCESS_INCS, + PLAYER_FTA, + PLAYER_FTQ, + PLAYER_ACCESS_WALLNUM, + PLAYER_ACCESS_SPRITENUM, + PLAYER_KICKBACK_PIC, + PLAYER_GOT_ACCESS, + PLAYER_WEAPON_ANG, + PLAYER_FIRSTAID_AMOUNT, + PLAYER_SOMETHINGONPLAYER, + PLAYER_ON_CRANE, + PLAYER_I, + PLAYER_ONE_PARALLAX_SECTNUM, + PLAYER_OVER_SHOULDER_ON, + PLAYER_RANDOM_CLUB_FRAME, + PLAYER_FIST_INCS, + PLAYER_ONE_EIGHTY_COUNT, + PLAYER_CHEAT_PHASE, + PLAYER_DUMMYPLAYERSPRITE, + PLAYER_EXTRA_EXTRA8, + PLAYER_QUICK_KICK, + PLAYER_HEAT_AMOUNT, + PLAYER_ACTORSQU, + PLAYER_TIMEBEFOREEXIT, + PLAYER_CUSTOMEXITSOUND, + PLAYER_WEAPRECS, + PLAYER_WEAPRECCNT, + PLAYER_INTERFACE_TOGGLE_FLAG, + PLAYER_ROTSCRNANG, + PLAYER_DEAD_FLAG, + PLAYER_SHOW_EMPTY_WEAPON, + PLAYER_SCUBA_AMOUNT, + PLAYER_JETPACK_AMOUNT, + PLAYER_STEROIDS_AMOUNT, + PLAYER_SHIELD_AMOUNT, + PLAYER_HOLODUKE_ON, + PLAYER_PYCOUNT, + PLAYER_WEAPON_POS, + PLAYER_FRAG_PS, + PLAYER_TRANSPORTER_HOLD, + PLAYER_CLIPDIST, + PLAYER_LAST_FULL_WEAPON, + PLAYER_FOOTPRINTSHADE, + PLAYER_BOOT_AMOUNT, + PLAYER_SCREAM_VOICE, + PLAYER_GM, + PLAYER_ON_WARPING_SECTOR, + PLAYER_FOOTPRINTCOUNT, + PLAYER_HBOMB_ON, + PLAYER_JUMPING_TOGGLE, + PLAYER_RAPID_FIRE_HOLD, + PLAYER_ON_GROUND, + PLAYER_NAME, + PLAYER_INVEN_ICON, + PLAYER_BUTTONPALETTE, + PLAYER_JETPACK_ON, + PLAYER_SPRITEBRIDGE, + PLAYER_LASTRANDOMSPOT, + PLAYER_SCUBA_ON, + PLAYER_FOOTPRINTPAL, + PLAYER_HEAT_ON, + PLAYER_HOLSTER_WEAPON, + PLAYER_FALLING_COUNTER, + PLAYER_GOTWEAPON, + PLAYER_REFRESH_INVENTORY, + PLAYER_PALETTE, + PLAYER_TOGGLE_KEY_FLAG, + PLAYER_KNUCKLE_INCS, + PLAYER_WALKING_SND_TOGGLE, + PLAYER_PALOOKUP, + PLAYER_HARD_LANDING, + PLAYER_MAX_SECRET_ROOMS, + PLAYER_SECRET_ROOMS, + PLAYER_PALS, + PLAYER_MAX_ACTORS_KILLED, + PLAYER_ACTORS_KILLED, + PLAYER_RETURN_TO_CENTER, + PLAYER_RUNSPEED, + PLAYER_SBS, + PLAYER_RELOADING, + PLAYER_AUTO_AIM, + PLAYER_MOVEMENT_LOCK, + PLAYER_SOUND_PITCH, + PLAYER_WEAPONSWITCH, + PLAYER_TEAM, + PLAYER_MAX_PLAYER_HEALTH, + PLAYER_MAX_SHIELD_AMOUNT, + PLAYER_MAX_AMMO_AMOUNT, + PLAYER_LAST_QUICK_KICK, + PLAYER_AUTOSTEP, + PLAYER_AUTOSTEP_SBW, + PLAYER_HUDPAL, + PLAYER_INDEX, + PLAYER_CONNECTED, + PLAYER_FRAGS, + PLAYER_DEATHS, + PLAYER_LAST_USED_WEAPON, + PLAYER_END +}; + +enum UserdefsLabel_t +{ + USERDEFS_GOD, + USERDEFS_WARP_ON, + USERDEFS_CASHMAN, + USERDEFS_EOG, + USERDEFS_SHOWALLMAP, + USERDEFS_SHOW_HELP, + USERDEFS_SCROLLMODE, + USERDEFS_CLIPPING, + USERDEFS_USER_NAME, + USERDEFS_RIDECULE, + USERDEFS_SAVEGAME, + USERDEFS_PWLOCKOUT, + USERDEFS_RTSNAME, + USERDEFS_OVERHEAD_ON, + USERDEFS_LAST_OVERHEAD, + USERDEFS_SHOWWEAPONS, + USERDEFS_PAUSE_ON, + USERDEFS_FROM_BONUS, + USERDEFS_CAMERASPRITE, + USERDEFS_LAST_CAMSPRITE, + USERDEFS_LAST_LEVEL, + USERDEFS_SECRETLEVEL, + USERDEFS_CONST_VISIBILITY, + USERDEFS_UW_FRAMERATE, + USERDEFS_CAMERA_TIME, + USERDEFS_FOLFVEL, + USERDEFS_FOLAVEL, + USERDEFS_FOLX, + USERDEFS_FOLY, + USERDEFS_FOLA, + USERDEFS_RECCNT, + USERDEFS_ENTERED_NAME, + USERDEFS_SCREEN_TILTING, + USERDEFS_SHADOWS, + USERDEFS_FTA_ON, + USERDEFS_EXECUTIONS, + USERDEFS_AUTO_RUN, + USERDEFS_COORDS, + USERDEFS_TICKRATE, + USERDEFS_M_COOP, + USERDEFS_COOP, + USERDEFS_SCREEN_SIZE, + USERDEFS_LOCKOUT, + USERDEFS_CROSSHAIR, + USERDEFS_PLAYERAI, + USERDEFS_RESPAWN_MONSTERS, + USERDEFS_RESPAWN_ITEMS, + USERDEFS_RESPAWN_INVENTORY, + USERDEFS_RECSTAT, + USERDEFS_MONSTERS_OFF, + USERDEFS_BRIGHTNESS, + USERDEFS_M_RESPAWN_ITEMS, + USERDEFS_M_RESPAWN_MONSTERS, + USERDEFS_M_RESPAWN_INVENTORY, + USERDEFS_M_RECSTAT, + USERDEFS_M_MONSTERS_OFF, + USERDEFS_DETAIL, + USERDEFS_M_FFIRE, + USERDEFS_FFIRE, + USERDEFS_M_PLAYER_SKILL, + USERDEFS_M_LEVEL_NUMBER, + USERDEFS_M_VOLUME_NUMBER, + USERDEFS_MULTIMODE, + USERDEFS_PLAYER_SKILL, + USERDEFS_LEVEL_NUMBER, + USERDEFS_VOLUME_NUMBER, + USERDEFS_M_MARKER, + USERDEFS_MARKER, + USERDEFS_MOUSEFLIP, + USERDEFS_STATUSBARSCALE, + USERDEFS_DRAWWEAPON, + USERDEFS_MOUSEAIMING, + USERDEFS_WEAPONSWITCH, + USERDEFS_DEMOCAMS, + USERDEFS_COLOR, + USERDEFS_MSGDISPTIME, + USERDEFS_STATUSBARMODE, + USERDEFS_M_NOEXITS, + USERDEFS_NOEXITS, + USERDEFS_AUTOVOTE, + USERDEFS_AUTOMSG, + USERDEFS_IDPLAYERS, + USERDEFS_TEAM, + USERDEFS_VIEWBOB, + USERDEFS_WEAPONSWAY, + USERDEFS_ANGLEINTERPOLATION, + USERDEFS_OBITUARIES, + USERDEFS_LEVELSTATS, + USERDEFS_CROSSHAIRSCALE, + USERDEFS_ALTHUD, + USERDEFS_DISPLAY_BONUS_SCREEN, + USERDEFS_SHOW_LEVEL_TEXT, + USERDEFS_WEAPONSCALE, + USERDEFS_TEXTSCALE, + USERDEFS_RUNKEY_MODE, + USERDEFS_M_ORIGIN_X, + USERDEFS_M_ORIGIN_Y, + USERDEFS_PLAYERBEST, + USERDEFS_MUSICTOGGLE, + USERDEFS_USEVOXELS, + USERDEFS_USEHIGHTILE, + USERDEFS_USEMODELS, + USERDEFS_GAMETYPEFLAGS, + USERDEFS_M_GAMETYPEFLAGS, + USERDEFS_GLOBALFLAGS, + USERDEFS_GLOBALGAMEFLAGS, + USERDEFS_VM_PLAYER, + USERDEFS_VM_SPRITE, + USERDEFS_VM_DISTANCE, + USERDEFS_SOUNDTOGGLE, + USERDEFS_GAMETEXT_TRACKING, + USERDEFS_MGAMETEXT_TRACKING, + USERDEFS_MENUTEXT_TRACKING, + USERDEFS_MAXSPRITESONSCREEN, + USERDEFS_SCREENAREA_X1, + USERDEFS_SCREENAREA_Y1, + USERDEFS_SCREENAREA_X2, + USERDEFS_SCREENAREA_Y2, + USERDEFS_SCREENFADE, + USERDEFS_MENUBACKGROUND, + USERDEFS_STATUSBARFLAGS, + USERDEFS_STATUSBARRANGE, + USERDEFS_STATUSBARCUSTOM, + USERDEFS_HUDONTOP, + USERDEFS_MENU_SLIDEBARZ, + USERDEFS_MENU_SLIDEBARMARGIN, + USERDEFS_MENU_SLIDECURSORZ, + USERDEFS_GLOBAL_R, + USERDEFS_GLOBAL_G, + USERDEFS_GLOBAL_B, + USERDEFS_DEFAULT_VOLUME, + USERDEFS_DEFAULT_SKILL, + USERDEFS_MENU_SHADEDESELECTED, + USERDEFS_MENU_SHADEDISABLED, + USERDEFS_MENUTEXT_ZOOM, + USERDEFS_MENUTEXT_XSPACE, + USERDEFS_MENUTEXT_PAL, + USERDEFS_MENUTEXT_PALSELECTED, + USERDEFS_MENUTEXT_PALDESELECTED, + USERDEFS_MENUTEXT_PALDISABLED, + USERDEFS_MENUTEXT_PALSELECTED_RIGHT, + USERDEFS_MENUTEXT_PALDESELECTED_RIGHT, + USERDEFS_MENUTEXT_PALDISABLED_RIGHT, + USERDEFS_GAMETEXT_ZOOM, + USERDEFS_GAMETEXT_XSPACE, + USERDEFS_GAMETEXT_PAL, + USERDEFS_GAMETEXT_PALSELECTED, + USERDEFS_GAMETEXT_PALDESELECTED, + USERDEFS_GAMETEXT_PALDISABLED, + USERDEFS_GAMETEXT_PALSELECTED_RIGHT, + USERDEFS_GAMETEXT_PALDESELECTED_RIGHT, + USERDEFS_GAMETEXT_PALDISABLED_RIGHT, + USERDEFS_MINITEXT_ZOOM, + USERDEFS_MINITEXT_XSPACE, + USERDEFS_MINITEXT_TRACKING, + USERDEFS_MINITEXT_PAL, + USERDEFS_MINITEXT_PALSELECTED, + USERDEFS_MINITEXT_PALDESELECTED, + USERDEFS_MINITEXT_PALDISABLED, + USERDEFS_MINITEXT_PALSELECTED_RIGHT, + USERDEFS_MINITEXT_PALDESELECTED_RIGHT, + USERDEFS_MINITEXT_PALDISABLED_RIGHT, + USERDEFS_MENUTITLE_PAL, + USERDEFS_SLIDEBAR_PALSELECTED, + USERDEFS_SLIDEBAR_PALDISABLED, + USERDEFS_USER_MAP, + USERDEFS_M_USER_MAP, + USERDEFS_MUSIC_EPISODE, + USERDEFS_MUSIC_LEVEL, + USERDEFS_SHADOW_PAL, + USERDEFS_MENU_SCROLLBARTILENUM, + USERDEFS_MENU_SCROLLBARZ, + USERDEFS_MENU_SCROLLCURSORZ, + USERDEFS_RETURN, + USERDEFS_USERBYTEVERSION, + USERDEFS_AUTOSAVE, + USERDEFS_END +}; + +enum SectorLabel_t +{ + SECTOR_WALLPTR, + SECTOR_WALLNUM, + SECTOR_CEILINGZ, + SECTOR_CEILINGZGOAL, + SECTOR_CEILINGZVEL, + SECTOR_FLOORZ, + SECTOR_FLOORZGOAL, + SECTOR_FLOORZVEL, + SECTOR_CEILINGSTAT, + SECTOR_FLOORSTAT, + SECTOR_CEILINGPICNUM, + SECTOR_CEILINGSLOPE, + SECTOR_CEILINGSHADE, + SECTOR_CEILINGPAL, + SECTOR_CEILINGXPANNING, + SECTOR_CEILINGYPANNING, + SECTOR_FLOORPICNUM, + SECTOR_FLOORSLOPE, + SECTOR_FLOORSHADE, + SECTOR_FLOORPAL, + SECTOR_FLOORXPANNING, + SECTOR_FLOORYPANNING, + SECTOR_VISIBILITY, + SECTOR_FOGPAL, + SECTOR_LOTAG, + SECTOR_HITAG, + SECTOR_EXTRA, + SECTOR_CEILINGBUNCH, + SECTOR_FLOORBUNCH, + SECTOR_ULOTAG, + SECTOR_UHITAG, + SECTOR_END +}; + +enum WallLabel_t +{ + WALL_X, + WALL_Y, + WALL_POINT2, + WALL_NEXTWALL, + WALL_NEXTSECTOR, + WALL_CSTAT, + WALL_PICNUM, + WALL_OVERPICNUM, + WALL_SHADE, + WALL_PAL, + WALL_XREPEAT, + WALL_YREPEAT, + WALL_XPANNING, + WALL_YPANNING, + WALL_LOTAG, + WALL_HITAG, + WALL_EXTRA, + WALL_ULOTAG, + WALL_UHITAG, + WALL_BLEND, + WALL_END +}; + +enum ActorLabel_t +{ + ACTOR_X, + ACTOR_Y, + ACTOR_Z, + ACTOR_CSTAT, + ACTOR_PICNUM, + ACTOR_SHADE, + ACTOR_PAL, + ACTOR_CLIPDIST, + ACTOR_DETAIL, + ACTOR_XREPEAT, + ACTOR_YREPEAT, + ACTOR_XOFFSET, + ACTOR_YOFFSET, + ACTOR_SECTNUM, + ACTOR_STATNUM, + ACTOR_ANG, + ACTOR_OWNER, + ACTOR_XVEL, + ACTOR_YVEL, + ACTOR_ZVEL, + ACTOR_LOTAG, + ACTOR_HITAG, + ACTOR_EXTRA, + ACTOR_HTCGG, + ACTOR_HTPICNUM, + ACTOR_HTANG, + ACTOR_HTEXTRA, + ACTOR_HTOWNER, + ACTOR_HTMOVFLAG, + ACTOR_HTTEMPANG, + ACTOR_HTACTORSTAYPUT, + ACTOR_HTDISPICNUM, + ACTOR_HTTIMETOSLEEP, + ACTOR_HTFLOORZ, + ACTOR_HTCEILINGZ, + ACTOR_HTLASTVX, + ACTOR_HTLASTVY, + ACTOR_HTBPOSX, + ACTOR_HTBPOSY, + ACTOR_HTBPOSZ, + ACTOR_HTG_T, + ACTOR_ANGOFF, + ACTOR_PITCH, + ACTOR_ROLL, + ACTOR_MDXOFF, + ACTOR_MDYOFF, + ACTOR_MDZOFF, + ACTOR_MDFLAGS, + ACTOR_XPANNING, + ACTOR_YPANNING, + ACTOR_HTFLAGS, + ACTOR_ALPHA, + ACTOR_ULOTAG, + ACTOR_UHITAG, + ACTOR_ISVALID, + ACTOR_END +}; + +enum InputLabel_t +{ + INPUT_AVEL, + INPUT_Q16AVEL, + INPUT_HORZ, + INPUT_Q16HORZ, + INPUT_FVEL, + INPUT_SVEL, + INPUT_BITS, + INPUT_EXTBITS, + INPUT_END +}; + +enum ScriptKeywords_t +{ + CON_ELSE, // 0 + CON_ACTOR, // 1 + CON_ADDAMMO, // 2 + CON_IFRND, // 3 + CON_ENDA, // 4 + CON_IFCANSEE, // 5 + CON_IFHITWEAPON, // 6 + CON_ACTION, // 7 + CON_IFPDISTL, // 8 + CON_IFPDISTG, // 9 + CON_DEFINELEVELNAME, // 10 + CON_STRENGTH, // 11 + CON_BREAK, // 12 + CON_SHOOT, // 13 + CON_PALFROM, // 14 + CON_SOUND, // 15 + CON_FALL, // 16 + CON_STATE, // 17 + CON_ENDS, // 18 + CON_DEFINE, // 19 + CON_COMMENT, // 20 deprecated + CON_IFAI, // 21 + CON_KILLIT, // 22 + CON_ADDWEAPON, // 23 + CON_AI, // 24 + CON_ADDPHEALTH, // 25 + CON_IFDEAD, // 26 + CON_IFSQUISHED, // 27 + CON_SIZETO, // 28 + CON_LEFTBRACE, // 29 + CON_RIGHTBRACE, // 30 + CON_SPAWN, // 31 + CON_MOVE, // 32 + CON_IFWASWEAPON, // 33 + CON_IFACTION, // 34 + CON_IFACTIONCOUNT, // 35 + CON_RESETACTIONCOUNT, // 36 + CON_DEBRIS, // 37 + CON_PSTOMP, // 38 + CON_BLOCKCOMMENT, // 39 deprecated + CON_CSTAT, // 40 + CON_IFMOVE, // 41 + CON_RESETPLAYER, // 42 + CON_IFONWATER, // 43 + CON_IFINWATER, // 44 + CON_IFCANSHOOTTARGET, // 45 + CON_IFCOUNT, // 46 + CON_RESETCOUNT, // 47 + CON_ADDINVENTORY, // 48 + CON_IFACTORNOTSTAYPUT, // 49 + CON_HITRADIUS, // 50 + CON_IFP, // 51 + CON_COUNT, // 52 + CON_IFACTOR, // 53 + CON_MUSIC, // 54 + CON_INCLUDE, // 55 + CON_IFSTRENGTH, // 56 + CON_DEFINESOUND, // 57 + CON_GUTS, // 58 + CON_IFSPAWNEDBY, // 59 + CON_GAMESTARTUP, // 60 + CON_WACKPLAYER, // 61 + CON_IFGAPZL, // 62 + CON_IFHITSPACE, // 63 + CON_IFOUTSIDE, // 64 + CON_IFMULTIPLAYER, // 65 + CON_OPERATE, // 66 + CON_IFINSPACE, // 67 + CON_DEBUG, // 68 + CON_ENDOFGAME, // 69 + CON_IFBULLETNEAR, // 70 + CON_IFRESPAWN, // 71 + CON_IFFLOORDISTL, // 72 + CON_IFCEILINGDISTL, // 73 + CON_SPRITEPAL, // 74 + CON_IFPINVENTORY, // 75 + CON_BETANAME, // 76 + CON_CACTOR, // 77 + CON_IFPHEALTHL, // 78 + CON_DEFINEQUOTE, // 79 + CON_QUOTE, // 80 + CON_IFINOUTERSPACE, // 81 + CON_IFNOTMOVING, // 82 + CON_RESPAWNHITAG, // 83 + CON_TIP, // 84 + CON_IFSPRITEPAL, // 85 + CON_MONEY, // 86 + CON_SOUNDONCE, // 87 + CON_ADDKILLS, // 88 + CON_STOPSOUND, // 89 + CON_IFAWAYFROMWALL, // 90 + CON_IFCANSEETARGET, // 91 + CON_GLOBALSOUND, // 92 + CON_LOTSOFGLASS, // 93 + CON_IFGOTWEAPONCE, // 94 + CON_GETLASTPAL, // 95 + CON_PKICK, // 96 + CON_MIKESND, // 97 + CON_USERACTOR, // 98 + CON_SIZEAT, // 99 + CON_ADDSTRENGTH, // 100 + CON_CSTATOR, // 101 + CON_MAIL, // 102 + CON_PAPER, // 103 + CON_TOSSWEAPON, // 104 + CON_SLEEPTIME, // 105 + CON_NULLOP, // 106 + CON_DEFINEVOLUMENAME, // 107 + CON_DEFINESKILLNAME, // 108 + CON_IFNOSOUNDS, // 109 + CON_CLIPDIST, // 110 + CON_IFANGDIFFL, // 111 + CON_IFNOCOVER, // 112 + CON_IFHITTRUCK, // 113 + CON_IFTIPCOW, // 114 + CON_ISDRUNK, // 115 + CON_ISEAT, // 116 + CON_DESTROYIT, // 117 + CON_LARRYBIRD, // 118 + CON_STRAFELEFT, // 119 + CON_STRAFERIGHT, // 120 + CON_IFACTORHEALTHG, // 121 + CON_IFACTORHEALTHL, // 122 + CON_SLAPPLAYER, // 123 + CON_IFPDRUNK, // 124 + CON_TEARITUP, // 125 + CON_SMACKBUBBA, // 126 + CON_SOUNDTAGONCE, // 127 + CON_SOUNDTAG, // 128 + CON_IFSOUNDID, // 129 + CON_IFSOUNDDIST, // 130 + CON_IFONMUD, // 131 + CON_IFCOOP, // 132 + CON_IFMOTOFAST, // 133 + CON_IFWIND, // 134 + CON_SMACKSPRITE, // 135 + CON_IFONMOTO, // 136 + CON_IFONBOAT, // 137 + CON_FAKEBUBBA, // 138 + CON_MAMATRIGGER, // 139 + CON_MAMASPAWN, // 140 + CON_MAMAQUAKE, // 141 + CON_MAMAEND, // 142 + CON_NEWPIC, // 143 + CON_GARYBANJO, // 144 + CON_MOTOLOOPSND, // 145 + CON_IFSIZEDOWN, // 146 + CON_RNDMOVE, // 147 + CON_END +}; +// KEEPINSYNC with the keyword list in lunatic/con_lang.lua + +#ifdef __cplusplus +} +#endif + +#endif // gamedef_h_ diff --git a/source/rr/src/gamedefs.h b/source/rr/src/gamedefs.h new file mode 100644 index 000000000..2912c5a67 --- /dev/null +++ b/source/rr/src/gamedefs.h @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +//**************************************************************************** +// +// gamedefs.h +// +// common defines between the game and the setup program +// +//**************************************************************************** + +#ifndef gamedefs_public_h_ +#define gamedefs_public_h_ +#ifdef __cplusplus +extern "C" { +#endif + +// config file name +#define SETUPFILENAME APPBASENAME ".cfg" + +// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp +#define MAXJOYBUTTONS 32 +#define MAXJOYBUTTONSANDHATS (MAXJOYBUTTONS+4) + +// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp +#define MAXMOUSEAXES 2 +#define MAXMOUSEDIGITAL (MAXMOUSEAXES*2) + +// KEEPINSYNC mact/include/_control.h, build/src/sdlayer.cpp +#define MAXJOYAXES 9 +#define MAXJOYDIGITAL (MAXJOYAXES*2) + +// default mouse scale +#define DEFAULTMOUSEANALOGUESCALE 65536 + +// default joystick settings + +#if defined(GEKKO) +#define DEFAULTJOYSTICKANALOGUESCALE 16384 +#define DEFAULTJOYSTICKANALOGUEDEAD 1000 +#define DEFAULTJOYSTICKANALOGUESATURATE 9500 +#else +#define DEFAULTJOYSTICKANALOGUESCALE 65536 +#define DEFAULTJOYSTICKANALOGUEDEAD 1000 +#define DEFAULTJOYSTICKANALOGUESATURATE 9500 +#endif + +#ifdef __cplusplus +} +#endif +#endif + diff --git a/source/rr/src/gameexec.cpp b/source/rr/src/gameexec.cpp new file mode 100644 index 000000000..86c339c6f --- /dev/null +++ b/source/rr/src/gameexec.cpp @@ -0,0 +1,2719 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "colmatch.h" +#include "compat.h" + +#include "duke3d.h" + +#include "anim.h" +#include "input.h" +#include "menus.h" +#include "osdcmds.h" +#include "savegame.h" +#include "scriplib.h" + +#include "debugbreak.h" + +#if KRANDDEBUG +# define GAMEEXEC_INLINE +# define GAMEEXEC_STATIC +#else +# define GAMEEXEC_INLINE inline +# define GAMEEXEC_STATIC static +#endif + +vmstate_t vm; + +enum vmflags_t +{ + VM_RETURN = 0x00000001, + VM_KILL = 0x00000002, + VM_NOEXECUTE = 0x00000004, + VM_SAFEDELETE = 0x00000008 +}; + +int32_t g_tw; +int32_t g_errorLineNum; +int32_t g_currentEventExec = -1; + +intptr_t const *insptr; + +int32_t g_returnVarID = -1; // var ID of "RETURN" +int32_t g_weaponVarID = -1; // var ID of "WEAPON" +int32_t g_worksLikeVarID = -1; // var ID of "WORKSLIKE" +int32_t g_zRangeVarID = -1; // var ID of "ZRANGE" +int32_t g_angRangeVarID = -1; // var ID of "ANGRANGE" +int32_t g_aimAngleVarID = -1; // var ID of "AUTOAIMANGLE" +int32_t g_lotagVarID = -1; // var ID of "LOTAG" +int32_t g_hitagVarID = -1; // var ID of "HITAG" +int32_t g_textureVarID = -1; // var ID of "TEXTURE" +int32_t g_thisActorVarID = -1; // var ID of "THISACTOR" +int32_t g_structVarIDs = -1; + +// for timing events and actors +uint32_t g_actorCalls[MAXTILES]; +double g_actorTotalMs[MAXTILES], g_actorMinMs[MAXTILES], g_actorMaxMs[MAXTILES]; + +GAMEEXEC_STATIC void VM_Execute(native_t loop); + +# include "gamestructures.cpp" + +#define VM_CONDITIONAL(xxx) \ + { \ + if ((xxx) || ((insptr = (intptr_t *)*(insptr + 1)) && (((*insptr) & VM_INSTMASK) == CON_ELSE))) \ + { \ + insptr += 2; \ + VM_Execute(0); \ + } \ + } + +void VM_ScriptInfo(intptr_t const *ptr, int range) +{ + if (!apScript || (!vm.pSprite && !vm.pPlayer && g_currentEventExec == -1)) + return; + + if (ptr) + { + initprintf("\n"); + + for (auto pScript = max (ptr - (range >> 1), apScript), + p_end = min (ptr + (range >> 1), apScript + g_scriptSize); + pScript < p_end; + ++pScript) + { + initprintf("%5d: %3d: ", (int32_t)(pScript - apScript), (int32_t)(pScript - ptr)); + + if (*pScript >> 12 && (*pScript & VM_INSTMASK) < CON_END) + initprintf("%5d %s\n", (int32_t)(*pScript >> 12), VM_GetKeywordForID(*pScript & VM_INSTMASK)); + else + initprintf("%d\n", (int32_t)*pScript); + } + + initprintf("\n"); + } + + if (ptr == insptr) + { + if (vm.pUSprite) + initprintf("current actor: %d (%d)\n", vm.spriteNum, vm.pUSprite->picnum); + + initprintf("g_errorLineNum: %d, g_tw: %d\n", g_errorLineNum, g_tw); + } +} + +static void VM_DeleteSprite(int const spriteNum, int const playerNum) +{ + if (EDUKE32_PREDICT_FALSE((unsigned) spriteNum >= MAXSPRITES)) + return; + + // if player was set to squish, first stop that... + if (EDUKE32_PREDICT_FALSE(playerNum >= 0 && g_player[playerNum].ps->actorsqu == spriteNum)) + g_player[playerNum].ps->actorsqu = -1; + + A_DeleteSprite(spriteNum); +} + +static int32_t VM_CheckSquished(void) +{ + if (RR) + return 0; + + usectortype const * const pSector = (usectortype *)§or[vm.pSprite->sectnum]; + + if (pSector->lotag == ST_23_SWINGING_DOOR || + (vm.pSprite->picnum == APLAYER && ud.noclip)) + return 0; + + int32_t floorZ = pSector->floorz; + int32_t ceilZ = pSector->ceilingz; +#ifdef YAX_ENABLE + int16_t cb, fb; + + yax_getbunches(vm.pSprite->sectnum, &cb, &fb); + + if (cb >= 0 && (pSector->ceilingstat&512)==0) // if ceiling non-blocking... + ceilZ -= ZOFFSET5; // unconditionally don't squish... yax_getneighborsect is slowish :/ + if (fb >= 0 && (pSector->floorstat&512)==0) + floorZ += ZOFFSET5; +#endif + + if (vm.pSprite->pal == 1 ? (floorZ - ceilZ >= ZOFFSET5 || (pSector->lotag & 32768u)) : (floorZ - ceilZ >= ZOFFSET4)) + return 0; + + P_DoQuote(QUOTE_SQUISHED, vm.pPlayer); + + if (A_CheckEnemySprite(vm.pSprite)) + vm.pSprite->xvel = 0; + + if (EDUKE32_PREDICT_FALSE(vm.pSprite->pal == 1)) // frozen + { + vm.pActor->picnum = SHOTSPARK1; + vm.pActor->extra = 1; + return 0; + } + + return 1; +} + +GAMEEXEC_STATIC GAMEEXEC_INLINE void P_ForceAngle(DukePlayer_t *pPlayer) +{ + int const nAngle = 128-(krand2()&255); + + pPlayer->q16horiz += F16(64); + pPlayer->return_to_center = 9; + pPlayer->rotscrnang = nAngle >> 1; + pPlayer->look_ang = pPlayer->rotscrnang; +} + +// wow, this function sucks +#ifdef __cplusplus +extern "C" +#endif +int32_t A_Dodge(spritetype * const); +int32_t A_Dodge(spritetype * const pSprite) +{ + vec2_t const msin = { sintable[(pSprite->ang + 512) & 2047], sintable[pSprite->ang & 2047] }; + + for (native_t nexti, SPRITES_OF_STAT_SAFE(STAT_PROJECTILE, i, nexti)) //weapons list + { + if (OW(i) == i || SECT(i) != pSprite->sectnum) + continue; + + vec2_t const b = { SX(i) - pSprite->x, SY(i) - pSprite->y }; + vec2_t const v = { sintable[(SA(i) + 512) & 2047], sintable[SA(i) & 2047] }; + + if (((msin.x * b.x) + (msin.y * b.y) >= 0) && ((v.x * b.x) + (v.y * b.y) < 0)) + { + if (klabs((v.x * b.y) - (v.y * b.x)) < 65536 << 6) + { + pSprite->ang -= 512+(krand2()&1024); + return 1; + } + } + } + + return 0; +} + +int32_t A_GetFurthestAngle(int const spriteNum, int const angDiv) +{ + uspritetype *const pSprite = (uspritetype *)&sprite[spriteNum]; + + if (pSprite->picnum != APLAYER && (AC_COUNT(actor[spriteNum].t_data)&63) > 2) + return pSprite->ang + 1024; + + int32_t furthestAngle = 0; + int32_t greatestDist = INT32_MIN; + int const angIncs = tabledivide32_noinline(2048, angDiv); + hitdata_t hit; + + for (native_t j = pSprite->ang; j < (2048 + pSprite->ang); j += angIncs) + { + pSprite->z -= ZOFFSET3; + hitscan((const vec3_t *)pSprite, pSprite->sectnum, sintable[(j + 512) & 2047], sintable[j & 2047], 0, &hit, CLIPMASK1); + pSprite->z += ZOFFSET3; + + int const hitDist = klabs(hit.pos.x-pSprite->x) + klabs(hit.pos.y-pSprite->y); + + if (hitDist > greatestDist) + { + greatestDist = hitDist; + furthestAngle = j; + } + } + + return furthestAngle&2047; +} + +int A_FurthestVisiblePoint(int const spriteNum, uspritetype * const ts, vec2_t * const vect) +{ + if (AC_COUNT(actor[spriteNum].t_data)&63) + return -1; + + const uspritetype *const pnSprite = (uspritetype *)&sprite[spriteNum]; + + hitdata_t hit; + int const angincs = ((!g_netServer && ud.multimode < 2) && ud.player_skill < 3) ? 2048 / 2 : tabledivide32_noinline(2048, 1 + (krand2() & 1)); + + for (native_t j = ts->ang; j < (2048 + ts->ang); j += (angincs-(krand2()&511))) + { + ts->z -= ZOFFSET2; + hitscan((const vec3_t *)ts, ts->sectnum, sintable[(j + 512) & 2047], sintable[j & 2047], 16384 - (krand2() & 32767), &hit, CLIPMASK1); + ts->z += ZOFFSET2; + + if (hit.sect < 0) + continue; + + int const d = klabs(hit.pos.x - ts->x) + klabs(hit.pos.y - ts->y); + int const da = klabs(hit.pos.x - pnSprite->x) + klabs(hit.pos.y - pnSprite->y); + + if (d < da) + { + if (cansee(hit.pos.x, hit.pos.y, hit.pos.z, hit.sect, pnSprite->x, pnSprite->y, pnSprite->z - ZOFFSET2, pnSprite->sectnum)) + { + vect->x = hit.pos.x; + vect->y = hit.pos.y; + return hit.sect; + } + } + } + + return -1; +} + +static void VM_GetZRange(int const spriteNum, int32_t * const ceilhit, int32_t * const florhit, int const wallDist) +{ + uspritetype *const pSprite = (uspritetype *)&sprite[spriteNum]; + vec3_t const tempVect = { + pSprite->x, pSprite->y, pSprite->z - ZOFFSET + }; + getzrange(&tempVect, pSprite->sectnum, &actor[spriteNum].ceilingz, ceilhit, &actor[spriteNum].floorz, florhit, wallDist, CLIPMASK0); +} + +void A_GetZLimits(int const spriteNum) +{ + spritetype *const pSprite = &sprite[spriteNum]; + int32_t ceilhit, florhit; + + if (pSprite->statnum == STAT_PLAYER || pSprite->statnum == STAT_STANDABLE || pSprite->statnum == STAT_ZOMBIEACTOR + || pSprite->statnum == STAT_ACTOR || pSprite->statnum == STAT_PROJECTILE) + { + VM_GetZRange(spriteNum, &ceilhit, &florhit, (pSprite->statnum == STAT_PROJECTILE) ? 4 : 127); + actor[spriteNum].flags &= ~SFLAG_NOFLOORSHADOW; + + if ((florhit&49152) == 49152 && (sprite[florhit&(MAXSPRITES-1)].cstat&48) == 0) + { + uspritetype const * const hitspr = (uspritetype *)&sprite[florhit&(MAXSPRITES-1)]; + + florhit &= (MAXSPRITES-1); + + // If a non-projectile would fall onto non-frozen enemy OR an enemy onto a player... + if ((A_CheckEnemySprite(hitspr) && hitspr->pal != 1 && pSprite->statnum != STAT_PROJECTILE) + || (hitspr->picnum == APLAYER && A_CheckEnemySprite(pSprite))) + { + actor[spriteNum].flags |= SFLAG_NOFLOORSHADOW; // No shadows on actors + pSprite->xvel = -256; // SLIDE_ABOVE_ENEMY + A_SetSprite(spriteNum, CLIPMASK0); + } + else if (pSprite->statnum == STAT_PROJECTILE && hitspr->picnum == APLAYER && pSprite->owner==florhit) + { + actor[spriteNum].ceilingz = sector[pSprite->sectnum].ceilingz; + actor[spriteNum].floorz = sector[pSprite->sectnum].floorz; + } + } + } + else + { + actor[spriteNum].ceilingz = sector[pSprite->sectnum].ceilingz; + actor[spriteNum].floorz = sector[pSprite->sectnum].floorz; + } +} + +void A_Fall(int const spriteNum) +{ + spritetype *const pSprite = &sprite[spriteNum]; + int spriteGravity = g_spriteGravity; + + if (EDUKE32_PREDICT_FALSE(G_CheckForSpaceFloor(pSprite->sectnum))) + spriteGravity = 0; + else if (sector[pSprite->sectnum].lotag == ST_2_UNDERWATER || EDUKE32_PREDICT_FALSE(G_CheckForSpaceCeiling(pSprite->sectnum))) + spriteGravity = g_spriteGravity/6; + + if (RRRA && spriteGravity == g_spriteGravity) + { + if (pSprite->picnum == BIKERB || pSprite->picnum == CHEERB) + spriteGravity >>= 2; + else if (pSprite->picnum == BIKERBV2) + spriteGravity >>= 3; + } + + if (pSprite->statnum == STAT_ACTOR || pSprite->statnum == STAT_PLAYER || pSprite->statnum == STAT_ZOMBIEACTOR + || pSprite->statnum == STAT_STANDABLE) + { + int32_t ceilhit, florhit; + VM_GetZRange(spriteNum, &ceilhit, &florhit, 127); + } + else + { + actor[spriteNum].ceilingz = sector[pSprite->sectnum].ceilingz; + actor[spriteNum].floorz = sector[pSprite->sectnum].floorz; + } + +#ifdef YAX_ENABLE + int fbunch = (sector[pSprite->sectnum].floorstat&512) ? -1 : yax_getbunch(pSprite->sectnum, YAX_FLOOR); +#endif + + if (pSprite->z < actor[spriteNum].floorz-ZOFFSET +#ifdef YAX_ENABLE + || fbunch >= 0 +#endif + ) + { + if (sector[pSprite->sectnum].lotag == ST_2_UNDERWATER && pSprite->zvel > 3122) + pSprite->zvel = 3144; + pSprite->z += pSprite->zvel = min(6144, pSprite->zvel+spriteGravity); + } + +#ifdef YAX_ENABLE + if (fbunch >= 0) + setspritez(spriteNum, (vec3_t *)pSprite); + else +#endif + if (pSprite->z >= actor[spriteNum].floorz-ZOFFSET) + { + pSprite->z = actor[spriteNum].floorz-ZOFFSET; + pSprite->zvel = 0; + } +} + +int32_t __fastcall G_GetAngleDelta(int32_t currAngle, int32_t newAngle) +{ + currAngle &= 2047; + newAngle &= 2047; + + if (klabs(currAngle-newAngle) < 1024) + { +// OSD_Printf("G_GetAngleDelta() returning %d\n",na-a); + return newAngle-currAngle; + } + + if (newAngle > 1024) + newAngle -= 2048; + if (currAngle > 1024) + currAngle -= 2048; + +// OSD_Printf("G_GetAngleDelta() returning %d\n",na-a); + return newAngle-currAngle; +} + +GAMEEXEC_STATIC void VM_AlterAng(int32_t const moveFlags) +{ + int const elapsedTics = (AC_COUNT(vm.pData))&31; + + const intptr_t *moveptr; + if (EDUKE32_PREDICT_FALSE((unsigned)AC_MOVE_ID(vm.pData) >= (unsigned)g_scriptSize-1)) + + { + AC_MOVE_ID(vm.pData) = 0; + OSD_Printf(OSD_ERROR "bad moveptr for actor %d (%d)!\n", vm.spriteNum, vm.pUSprite->picnum); + return; + } + + moveptr = apScript + AC_MOVE_ID(vm.pData); + + vm.pSprite->xvel += (moveptr[0] - vm.pSprite->xvel)/5; + if (vm.pSprite->zvel < 648) + vm.pSprite->zvel += ((moveptr[1]<<4) - vm.pSprite->zvel)/5; + + if (RRRA && (moveFlags&windang)) + vm.pSprite->ang = g_windDir; + else if (moveFlags&seekplayer) + { + int const spriteAngle = vm.pSprite->ang; + int const holoDukeSprite = vm.pPlayer->holoduke_on; + + // NOTE: looks like 'owner' is set to target sprite ID... + + vm.pSprite->owner = (!RR && holoDukeSprite >= 0 + && cansee(sprite[holoDukeSprite].x, sprite[holoDukeSprite].y, sprite[holoDukeSprite].z, sprite[holoDukeSprite].sectnum, + vm.pSprite->x, vm.pSprite->y, vm.pSprite->z, vm.pSprite->sectnum)) + ? holoDukeSprite + : vm.pPlayer->i; + + int const goalAng = (sprite[vm.pSprite->owner].picnum == APLAYER) + ? getangle(vm.pActor->lastv.x - vm.pSprite->x, vm.pActor->lastv.y - vm.pSprite->y) + : getangle(sprite[vm.pSprite->owner].x - vm.pSprite->x, sprite[vm.pSprite->owner].y - vm.pSprite->y); + + if (vm.pSprite->xvel && vm.pSprite->picnum != DRONE) + { + int const angDiff = G_GetAngleDelta(spriteAngle, goalAng); + + if (elapsedTics < 2) + { + if (klabs(angDiff) < 256) + { + int const angInc = 128-(krand2()&256); + vm.pSprite->ang += angInc; + if (A_GetHitscanRange(vm.spriteNum) < 844) + vm.pSprite->ang -= angInc; + } + } + else if (elapsedTics > 18 && elapsedTics < GAMETICSPERSEC) // choose + { + if (klabs(angDiff >> 2) < 128) + vm.pSprite->ang = goalAng; + else + vm.pSprite->ang += angDiff >> 2; + } + } + else + vm.pSprite->ang = goalAng; + } + + if (elapsedTics < 1) + { + if (moveFlags&furthestdir) + { + vm.pSprite->ang = A_GetFurthestAngle(vm.spriteNum, 2); + vm.pSprite->owner = vm.pPlayer->i; + } + + if (moveFlags&fleeenemy) + vm.pSprite->ang = A_GetFurthestAngle(vm.spriteNum, 2); + } +} + +static inline void VM_AddAngle(int const shift, int const goalAng) +{ + int angDiff = G_GetAngleDelta(vm.pSprite->ang, goalAng) >> shift; + + if (angDiff > -8 && angDiff < 0) + angDiff = 0; + + vm.pSprite->ang += angDiff; +} + +static inline void VM_FacePlayer(int const shift) +{ + VM_AddAngle(shift, (vm.pPlayer->newowner >= 0) ? getangle(vm.pPlayer->opos.x - vm.pSprite->x, vm.pPlayer->opos.y - vm.pSprite->y) + : getangle(vm.pPlayer->pos.x - vm.pSprite->x, vm.pPlayer->pos.y - vm.pSprite->y)); +} + +////////// TROR get*zofslope ////////// +// These rather belong into the engine. + +static int32_t VM_GetCeilZOfSlope(void) +{ + vec2_t const vect = *(vec2_t *)vm.pSprite; + int const sectnum = vm.pSprite->sectnum; + +#ifdef YAX_ENABLE + if ((sector[sectnum].ceilingstat&512)==0) + { + int const nsect = yax_getneighborsect(vect.x, vect.y, sectnum, YAX_CEILING); + if (nsect >= 0) + return getceilzofslope(nsect, vect.x, vect.y); + } +#endif + return getceilzofslope(sectnum, vect.x, vect.y); +} + +static int32_t VM_GetFlorZOfSlope(void) +{ + vec2_t const vect = *(vec2_t *)vm.pSprite; + int const sectnum = vm.pSprite->sectnum; + +#ifdef YAX_ENABLE + if ((sector[sectnum].floorstat&512)==0) + { + int const nsect = yax_getneighborsect(vect.x, vect.y, sectnum, YAX_FLOOR); + if (nsect >= 0) + return getflorzofslope(nsect, vect.x, vect.y); + } +#endif + return getflorzofslope(sectnum, vect.x, vect.y); +} + +//////////////////// + +static int32_t A_GetWaterZOffset(int spritenum); + +GAMEEXEC_STATIC void VM_Move(void) +{ + auto const movflagsptr = &AC_MOVFLAGS(vm.pSprite, &actor[vm.spriteNum]); + // NOTE: test against -1 commented out and later revived in source history + // XXX: Does its presence/absence break anything? Where are movflags with all bits set created? + int const movflags = (*movflagsptr == (std::remove_pointer ::type)-1) ? 0 : *movflagsptr; + + AC_COUNT(vm.pData)++; + + if (movflags&face_player) + VM_FacePlayer(2); + + if (movflags&spin) + vm.pSprite->ang += sintable[((AC_COUNT(vm.pData)<<3)&2047)]>>6; + + if (movflags&face_player_slow) + { + int const goalAng = (vm.pPlayer->newowner >= 0) ? getangle(vm.pPlayer->opos.x - vm.pSprite->x, vm.pPlayer->opos.y - vm.pSprite->y) + : getangle(vm.pPlayer->pos.x - vm.pSprite->x, vm.pPlayer->pos.y - vm.pSprite->y); + + vm.pSprite->ang += ksgn(G_GetAngleDelta(vm.pSprite->ang, goalAng)) << 5; + } + + if (RRRA && (movflags&antifaceplayerslow)) + { + int goalAng = (vm.pPlayer->newowner >= 0) ? getangle(vm.pPlayer->opos.x - vm.pSprite->x, vm.pPlayer->opos.y - vm.pSprite->y) + : getangle(vm.pPlayer->pos.x - vm.pSprite->x, vm.pPlayer->pos.y - vm.pSprite->y); + goalAng = (goalAng+1024)&2047; + + vm.pSprite->ang += ksgn(G_GetAngleDelta(vm.pSprite->ang, goalAng)) << 5; + } + + if ((movflags&jumptoplayer_bits) == jumptoplayer_bits) + { + if (AC_COUNT(vm.pData) < 16) + vm.pSprite->zvel -= (RRRA && vm.pSprite->picnum == CHEER) ? (sintable[(512+(AC_COUNT(vm.pData)<<4))&2047]/40) + : (sintable[(512+(AC_COUNT(vm.pData)<<4))&2047]>>5); + } + + if (movflags&face_player_smart) + { + vec2_t const vect = { vm.pPlayer->pos.x + (vm.pPlayer->vel.x / 768), vm.pPlayer->pos.y + (vm.pPlayer->vel.y / 768) }; + VM_AddAngle(2, getangle(vect.x - vm.pSprite->x, vect.y - vm.pSprite->y)); + } + + if (RRRA && (vm.pSprite->picnum == RABBIT || vm.pSprite->picnum == MAMA)) + { + if(movflags&jumptoplayer_only) + { + if (AC_COUNT(vm.pData) < 8) + vm.pSprite->zvel -= sintable[(512+(AC_COUNT(vm.pData)<<4))&2047]/(vm.pSprite->picnum == RABBIT ? 30 : 35); + } + if(movflags&justjump2) + { + if (AC_COUNT(vm.pData) < 8) + vm.pSprite->zvel -= sintable[(512+(AC_COUNT(vm.pData)<<4))&2047]/(vm.pSprite->picnum == RABBIT ? 24 : 28); + } + } + + if (RRRA && (movflags&windang)) + { + if (AC_COUNT(vm.pData) < 8) + vm.pSprite->zvel -= sintable[(512+(AC_COUNT(vm.pData)<<4))&2047]/24; + } + + if (AC_MOVE_ID(vm.pData) == 0 || movflags == 0) + { + if ((A_CheckEnemySprite(vm.pSprite) && vm.pSprite->extra <= 0) || (vm.pActor->bpos.x != vm.pSprite->x) || (vm.pActor->bpos.y != vm.pSprite->y)) + { + vm.pActor->bpos.x = vm.pSprite->x; + vm.pActor->bpos.y = vm.pSprite->y; + setsprite(vm.spriteNum, (vec3_t *)vm.pSprite); + } + if (RR && A_CheckEnemySprite(vm.pSprite) && vm.pSprite->extra <= 0) + { + vm.pSprite->shade += (sector[vm.pSprite->sectnum].ceilingstat & 1) ? ((g_shadedSector[vm.pSprite->sectnum] == 1 ? 16 : sector[vm.pSprite->sectnum].ceilingshade) - vm.pSprite->shade) >> 1 + : (sector[vm.pSprite->sectnum].floorshade - vm.pSprite->shade) >> 1; + } + return; + } + + if (EDUKE32_PREDICT_FALSE((unsigned)AC_MOVE_ID(vm.pData) >= (unsigned)g_scriptSize-1)) + { + AC_MOVE_ID(vm.pData) = 0; + OSD_Printf(OSD_ERROR "clearing bad moveptr for actor %d (%d)\n", vm.spriteNum, vm.pUSprite->picnum); + return; + } + + intptr_t const * const moveptr = apScript + AC_MOVE_ID(vm.pData); + + if (movflags & geth) + vm.pSprite->xvel += ((moveptr[0]) - vm.pSprite->xvel) >> 1; + if (movflags & getv) + vm.pSprite->zvel += ((moveptr[1] << 4) - vm.pSprite->zvel) >> 1; + + if (movflags&dodgebullet) + A_Dodge(vm.pSprite); + + if (vm.pSprite->picnum != APLAYER) + VM_AlterAng(movflags); + + if (vm.pSprite->xvel > -6 && vm.pSprite->xvel < 6) + vm.pSprite->xvel = 0; + + int badguyp = A_CheckEnemySprite(vm.pSprite); + + if (vm.pSprite->xvel || vm.pSprite->zvel) + { + int spriteXvel = vm.pSprite->xvel; + int angDiff = vm.pSprite->ang; + + if (badguyp && (vm.pSprite->picnum != ROTATEGUN || RR)) + { + if ((vm.pSprite->picnum == DRONE || (!RR && vm.pSprite->picnum == COMMANDER)) && vm.pSprite->extra > 0) + { + if (!RR && vm.pSprite->picnum == COMMANDER) + { + int32_t nSectorZ; + // NOTE: COMMANDER updates both actor[].floorz and + // .ceilingz regardless of its zvel. + vm.pActor->floorz = nSectorZ = VM_GetFlorZOfSlope(); + if (vm.pSprite->z > nSectorZ-ZOFFSET3) + { + vm.pSprite->z = nSectorZ-ZOFFSET3; + vm.pSprite->zvel = 0; + } + + vm.pActor->ceilingz = nSectorZ = VM_GetCeilZOfSlope(); + if (vm.pSprite->z < nSectorZ+(80<<8)) + { + vm.pSprite->z = nSectorZ+(80<<8); + vm.pSprite->zvel = 0; + } + } + else + { + int32_t nSectorZ; + // The DRONE updates either .floorz or .ceilingz, not both. + if (vm.pSprite->zvel > 0) + { + vm.pActor->floorz = nSectorZ = VM_GetFlorZOfSlope(); + int const zDiff = RRRA ? (28<<8) : (30<<8); + if (vm.pSprite->z > nSectorZ-zDiff) + vm.pSprite->z = nSectorZ-zDiff; + } + else + { + vm.pActor->ceilingz = nSectorZ = VM_GetCeilZOfSlope(); + if (vm.pSprite->z < nSectorZ+(50<<8)) + { + vm.pSprite->z = nSectorZ+(50<<8); + vm.pSprite->zvel = 0; + } + } + } + } + else if (vm.pSprite->picnum != ORGANTIC || RR) + { + // All other actors besides ORGANTIC don't update .floorz or + // .ceilingz here. + if (vm.pSprite->zvel > 0) + { + if (vm.pSprite->z > vm.pActor->floorz) + vm.pSprite->z = vm.pActor->floorz; + //vm.pSprite->z += A_GetWaterZOffset(vm.spriteNum); + } + else if (vm.pSprite->zvel < 0) + { + int const l = VM_GetCeilZOfSlope(); + + if (vm.pSprite->z < l+(66<<8)) + { + vm.pSprite->z = l+(66<<8); + vm.pSprite->zvel >>= 1; + } + } + } + + if (vm.playerDist < 960 && vm.pSprite->xrepeat > 16) + { + spriteXvel = -(1024 - vm.playerDist); + angDiff = getangle(vm.pPlayer->pos.x - vm.pSprite->x, vm.pPlayer->pos.y - vm.pSprite->y); + + if (vm.playerDist < 512) + { + vm.pPlayer->vel.x = 0; + vm.pPlayer->vel.y = 0; + } + else + { + vm.pPlayer->vel.x = mulscale16(vm.pPlayer->vel.x, vm.pPlayer->runspeed - 0x2000); + vm.pPlayer->vel.y = mulscale16(vm.pPlayer->vel.y, vm.pPlayer->runspeed - 0x2000); + } + } + else if (vm.pSprite->picnum != DRONE && vm.pSprite->picnum != SHARK + && ((!RR && vm.pSprite->picnum != COMMANDER) + || (RR && vm.pSprite->picnum != UFO1) + || (RR && !RRRA && vm.pSprite->picnum != UFO2 && vm.pSprite->picnum != UFO3 && vm.pSprite->picnum != UFO4 && vm.pSprite->picnum != UFO5))) + { + if (vm.pPlayer->actorsqu == vm.spriteNum) + return; + + if (vm.pActor->bpos.z != vm.pSprite->z || (!g_netServer && ud.multimode < 2 && ud.player_skill < 2)) + { + if (AC_COUNT(vm.pData)&1) return; + spriteXvel <<= 1; + } + else + { + if (AC_COUNT(vm.pData)&3) return; + spriteXvel <<= 2; + } + } + } + else if (vm.pSprite->picnum == APLAYER) + if (vm.pSprite->z < vm.pActor->ceilingz+ZOFFSET5) + vm.pSprite->z = vm.pActor->ceilingz+ZOFFSET5; + + if (RRRA) + { + if (sector[vm.pSprite->sectnum].lotag != ST_1_ABOVE_WATER) + { + switch (DYNAMICTILEMAP(vm.pSprite->picnum)) + { + case MINIONBOAT__STATICRR: + case HULK__STATICRR: + case CHEERBOAT__STATICRR: + spriteXvel >>= 1; + break; + } + } + else + { + switch (DYNAMICTILEMAP(vm.pSprite->picnum)) + { + case BIKERB__STATICRR: + case BIKERBV2__STATICRR: + case CHEERB__STATICRR: + spriteXvel >>= 1; + break; + } + } + } + + vec3_t const vect + = { (spriteXvel * (sintable[(angDiff + 512) & 2047])) >> 14, (spriteXvel * (sintable[angDiff & 2047])) >> 14, vm.pSprite->zvel }; + + vm.pActor->movflag = A_MoveSprite(vm.spriteNum, &vect, CLIPMASK0); + } + + if (!badguyp) + return; + + vm.pSprite->shade += (sector[vm.pSprite->sectnum].ceilingstat & 1) ? ((g_shadedSector[vm.pSprite->sectnum] == 1 ? 16 : sector[vm.pSprite->sectnum].ceilingshade) - vm.pSprite->shade) >> 1 + : (sector[vm.pSprite->sectnum].floorshade - vm.pSprite->shade) >> 1; + + if (sector[vm.pSprite->sectnum].floorpicnum == MIRROR) + A_DeleteSprite(vm.spriteNum); +} + +static void VM_AddWeapon(DukePlayer_t * const pPlayer, int const weaponNum, int const nAmount) +{ + if (EDUKE32_PREDICT_FALSE((unsigned)weaponNum >= MAX_WEAPONS)) + { + CON_ERRPRINTF("invalid weapon %d\n", weaponNum); + return; + } + + if ((pPlayer->gotweapon & (1 << weaponNum)) == 0) + { + P_AddWeapon(pPlayer, weaponNum); + } + else if (pPlayer->ammo_amount[weaponNum] >= pPlayer->max_ammo_amount[weaponNum]) + { + vm.flags |= VM_NOEXECUTE; + return; + } + + P_AddAmmo(pPlayer, weaponNum, nAmount); + + if (pPlayer->curr_weapon == KNEE_WEAPON && (pPlayer->gotweapon & (1< = MAX_WEAPONS)) + { + CON_ERRPRINTF("invalid weapon %d\n", weaponNum); + return; + } + + if (pPlayer->ammo_amount[weaponNum] >= pPlayer->max_ammo_amount[weaponNum]) + { + vm.flags |= VM_NOEXECUTE; + return; + } + + P_AddAmmo(pPlayer, weaponNum, nAmount); + + if (pPlayer->curr_weapon == KNEE_WEAPON && (pPlayer->gotweapon & (1< inven_icon = inv_to_icon[itemNum]; + pPlayer->inv_amount[itemNum] = nAmount; + break; + + case GET_SHIELD: + { + int16_t & shield_amount = pPlayer->inv_amount[GET_SHIELD]; + shield_amount = min(shield_amount + nAmount, pPlayer->max_shield_amount); + break; + } + + case GET_ACCESS: + if (RR) + { + switch (vm.pSprite->lotag) + { + case 100: pPlayer->keys[1] = 1; break; + case 101: pPlayer->keys[2] = 1; break; + case 102: pPlayer->keys[3] = 1; break; + case 103: pPlayer->keys[4] = 1; break; + } + } + else + { + switch (vm.pSprite->pal) + { + case 0: pPlayer->got_access |= 1; break; + case 21: pPlayer->got_access |= 2; break; + case 23: pPlayer->got_access |= 4; break; + } + } + break; + + default: CON_ERRPRINTF("invalid inventory item %d\n", itemNum); break; + } +} + +static int32_t A_GetWaterZOffset(int const spriteNum) +{ + uspritetype const *const pSprite = (uspritetype *)&sprite[spriteNum]; + + if (sector[pSprite->sectnum].lotag == ST_1_ABOVE_WATER) + { + if (RRRA) + { + switch (DYNAMICTILEMAP(pSprite->picnum)) + { + case HULKBOAT__STATICRR: + return (12<<8); + case MINIONBOAT__STATICRR: + return (3<<8); + case CHEERBOAT__STATICRR: + case EMPTYBOAT__STATICRR: + return (6<<8); + } + } + if (A_CheckSpriteFlags(spriteNum, SFLAG_NOWATERDIP)) + return 0; + + return ACTOR_ONWATER_ADDZ; + } + + return 0; +} + +static void VM_Fall(int const spriteNum, spritetype * const pSprite) +{ + extern char g_demo_legacy; + int spriteGravity = g_spriteGravity; + int hitSprite = 0; + + pSprite->xoffset = pSprite->yoffset = 0; + + if (RR) + { + if (RRRA) + { + if (sector[vm.pSprite->sectnum].lotag == 801) + { + if (vm.pSprite->picnum == ROCK) + { + A_Spawn(vm.spriteNum, ROCK2); + A_Spawn(vm.spriteNum, ROCK2); + if (ud.recstat == 2 && g_demo_legacy) + A_DeleteSprite(vm.spriteNum); + else + vm.flags |= VM_SAFEDELETE; + } + } + else if (sector[vm.pSprite->sectnum].lotag == 802) + { + if (vm.pSprite->picnum != APLAYER && A_CheckEnemySprite(vm.pSprite) && vm.pSprite->z == vm.pActor->floorz - ZOFFSET) + { + A_DoGuts(vm.spriteNum, JIBS6, 5); + A_PlaySound(SQUISHED, vm.spriteNum); + if (ud.recstat == 2 && g_demo_legacy) + A_DeleteSprite(vm.spriteNum); + else + vm.flags |= VM_SAFEDELETE; + } + } + else if (sector[vm.pSprite->sectnum].lotag == 803) + { + if (vm.pSprite->picnum == ROCK2) + { + if (ud.recstat == 2 && g_demo_legacy) + A_DeleteSprite(vm.spriteNum); + else + vm.flags |= VM_SAFEDELETE; + } + } + } + if (sector[vm.pSprite->sectnum].lotag == 800) + { + if (vm.pSprite->picnum == AMMO) + { + if (ud.recstat == 2 && g_demo_legacy) + A_DeleteSprite(vm.spriteNum); + else + vm.flags |= VM_SAFEDELETE; + return; + } + if (vm.pSprite->picnum != APLAYER && (A_CheckEnemySprite(vm.pSprite) || vm.pSprite->picnum == COW) && g_spriteExtra[vm.spriteNum] < 128) + { + vm.pSprite->z = vm.pActor->floorz-ZOFFSET; + vm.pSprite->zvel = 8000; + vm.pSprite->extra = 0; + g_spriteExtra[vm.spriteNum]++; + hitSprite = 1; + } + else if (vm.pSprite->picnum != APLAYER) + { + if (!g_spriteExtra[vm.spriteNum]) + vm.flags |= VM_SAFEDELETE; + return; + } + vm.pActor->picnum = SHOTSPARK1; + vm.pActor->extra = 1; + } + if (RRRA && EDUKE32_PREDICT_TRUE(sector[vm.pSprite->sectnum].lotag < 800 || sector[vm.pSprite->sectnum].lotag > 803) + && (sector[vm.pSprite->sectnum].floorpicnum == RRTILE7820 || sector[vm.pSprite->sectnum].floorpicnum == RRTILE7768)) + { + if (vm.pSprite->picnum != MINION && vm.pSprite->pal != 19) + { + if ((krand2()&3) == 1) + { + vm.pActor->picnum = SHOTSPARK1; + vm.pActor->extra = 5; + } + } + } + } + + if (sector[pSprite->sectnum].lotag == ST_2_UNDERWATER || EDUKE32_PREDICT_FALSE(G_CheckForSpaceCeiling(pSprite->sectnum))) + spriteGravity = g_spriteGravity/6; + else if (EDUKE32_PREDICT_FALSE(G_CheckForSpaceFloor(pSprite->sectnum))) + spriteGravity = 0; + + if (actor[spriteNum].cgg <= 0 || (sector[pSprite->sectnum].floorstat&2)) + { + A_GetZLimits(spriteNum); + actor[spriteNum].cgg = 6; + } + else actor[spriteNum].cgg--; + + if (pSprite->z < actor[spriteNum].floorz-ZOFFSET) + { + // Free fall. + pSprite->zvel += spriteGravity; + pSprite->z += pSprite->zvel; + +#ifdef YAX_ENABLE + if (yax_getbunch(pSprite->sectnum, YAX_FLOOR) >= 0 && (sector[pSprite->sectnum].floorstat & 512) == 0) + setspritez(spriteNum, (vec3_t *)pSprite); +#endif + + if (pSprite->zvel > 6144) pSprite->zvel = 6144; + return; + } + + pSprite->z = actor[spriteNum].floorz - ZOFFSET; + + if (A_CheckEnemySprite(pSprite) || (pSprite->picnum == APLAYER && pSprite->owner >= 0)) + { + if (pSprite->zvel > 3084 && pSprite->extra <= 1) + { + // I'm guessing this DRONE check is from a beta version of the game + // where they crashed into the ground when killed + if (!(pSprite->picnum == APLAYER && pSprite->extra > 0) && pSprite->pal != 1 && pSprite->picnum != DRONE) + { + A_PlaySound(SQUISHED,spriteNum); + if (hitSprite) + { + A_DoGuts(spriteNum,JIBS6,5); + } + else + { + A_DoGuts(spriteNum,JIBS6,15); + A_Spawn(spriteNum,BLOODPOOL); + } + } + actor[spriteNum].picnum = SHOTSPARK1; + actor[spriteNum].extra = 1; + pSprite->zvel = 0; + } + else if (pSprite->zvel > 2048 && sector[pSprite->sectnum].lotag != ST_1_ABOVE_WATER) + { + int16_t newsect = pSprite->sectnum; + + pushmove((vec3_t *)pSprite, &newsect, 128, 4<<8, 4<<8, CLIPMASK0); + if ((unsigned)newsect < MAXSECTORS) + changespritesect(spriteNum, newsect); + + A_PlaySound(THUD, spriteNum); + } + } + + if (sector[pSprite->sectnum].lotag == ST_1_ABOVE_WATER) + { + pSprite->z += A_GetWaterZOffset(spriteNum); + return; + } + + pSprite->zvel = 0; +} + +static int32_t VM_ResetPlayer(int const playerNum, int32_t vmFlags) +{ + //AddLog("resetplayer"); + if (!g_netServer && ud.multimode < 2) + { + if (g_quickload && g_quickload->isValid() && ud.recstat != 2) + { + Menu_Open(playerNum); + KB_ClearKeyDown(sc_Space); + I_AdvanceTriggerClear(); + Menu_Change(MENU_RESETPLAYER); + } + else + g_player[playerNum].ps->gm = MODE_RESTART; + vmFlags |= VM_NOEXECUTE; + } + else + { + if (playerNum == myconnectindex) + { + CAMERADIST = 0; + CAMERACLOCK = totalclock; + } + + if (g_fakeMultiMode) + P_ResetPlayer(playerNum); +#ifndef NETCODE_DISABLE + if (g_netServer) + { + P_ResetPlayer(playerNum); + Net_SpawnPlayer(playerNum); + } +#endif + } + + P_UpdateScreenPal(g_player[playerNum].ps); + //AddLog("EOF: resetplayer"); + + return vmFlags; +} + +void G_GetTimeDate(int32_t * const pValues) +{ + time_t timeStruct; + time(&timeStruct); + struct tm *pTime = localtime(&timeStruct); + + // initprintf("Time&date: %s\n",asctime (ti)); + + pValues[0] = pTime->tm_sec; + pValues[1] = pTime->tm_min; + pValues[2] = pTime->tm_hour; + pValues[3] = pTime->tm_mday; + pValues[4] = pTime->tm_mon; + pValues[5] = pTime->tm_year+1900; + pValues[6] = pTime->tm_wday; + pValues[7] = pTime->tm_yday; +} + +void Screen_Play(void) +{ + int32_t running = 1; + + I_ClearAllInput(); + + do + { + G_HandleAsync(); + + ototalclock = totalclock + 1; // pause game like ANMs + + if (!G_FPSLimit()) + continue; + + videoClearScreen(0); + if (I_CheckAllInput()) + running = 0; + + // nextpage(); + + I_ClearAllInput(); + } while (running); +} + +GAMEEXEC_STATIC void VM_Execute(native_t loop) +{ + native_t tw = *insptr; + DukePlayer_t *const pPlayer = vm.pPlayer; + + // jump directly into the loop, skipping branches during the first iteration + goto skip_check; + + while (loop) + { + if (vm.flags & (VM_RETURN | VM_KILL | VM_NOEXECUTE)) + break; + + tw = *insptr; + + skip_check: + // Bsprintf(g_szBuf,"Parsing: %d",*insptr); + // AddLog(g_szBuf); + + g_errorLineNum = tw >> 12; + g_tw = tw &= VM_INSTMASK; + + if (tw == CON_LEFTBRACE) + { + insptr++, loop++; + continue; + } + else if (tw == CON_RIGHTBRACE) + { + insptr++, loop--; + continue; + } + else if (tw == CON_ELSE) + { + insptr = (intptr_t *)*(insptr + 1); + continue; + } + else if (tw == CON_STATE) + { + intptr_t const *const tempscrptr = insptr + 2; + insptr = (intptr_t *)*(insptr + 1); + VM_Execute(1); + insptr = tempscrptr; + continue; + } + + switch (tw) + { + case CON_ENDA: + case CON_BREAK: + case CON_ENDS: return; + + case CON_IFRND: VM_CONDITIONAL(rnd(*(++insptr))); continue; + + case CON_IFCANSHOOTTARGET: + { + if (vm.playerDist > 1024) + { + int16_t temphit; + + if ((tw = A_CheckHitSprite(vm.spriteNum, &temphit)) == (1 << 30)) + { + VM_CONDITIONAL(1); + continue; + } + + int dist = 768; + int angDiff = 16; + + if (A_CheckEnemySprite(vm.pSprite) && vm.pSprite->xrepeat > 56) + { + dist = 3084; + angDiff = 48; + } + +#define CHECK(x) \ + if (x >= 0 && sprite[x].picnum == vm.pSprite->picnum) \ + { \ + VM_CONDITIONAL(0); \ + continue; \ + } +#define CHECK2(x) \ + do \ + { \ + vm.pSprite->ang += x; \ + tw = A_CheckHitSprite(vm.spriteNum, &temphit); \ + vm.pSprite->ang -= x; \ + } while (0) + + if (tw > dist) + { + CHECK(temphit); + CHECK2(angDiff); + + if (tw > dist) + { + CHECK(temphit); + CHECK2(-angDiff); + + if (tw > 768) + { + CHECK(temphit); + VM_CONDITIONAL(1); + continue; + } + } + } + VM_CONDITIONAL(0); + continue; + } + VM_CONDITIONAL(1); + } + continue; + + case CON_IFCANSEETARGET: + tw = cansee(vm.pSprite->x, vm.pSprite->y, vm.pSprite->z - ((krand2() & 41) << 8), vm.pSprite->sectnum, pPlayer->pos.x, pPlayer->pos.y, + pPlayer->pos.z /*-((krand2()&41)<<8)*/, sprite[pPlayer->i].sectnum); + VM_CONDITIONAL(tw); + if (tw) + vm.pActor->timetosleep = SLEEPTIME; + continue; + + case CON_IFNOCOVER: + tw = cansee(vm.pSprite->x, vm.pSprite->y, vm.pSprite->z, vm.pSprite->sectnum, pPlayer->pos.x, pPlayer->pos.y, + pPlayer->pos.z, sprite[pPlayer->i].sectnum); + VM_CONDITIONAL(tw); + if (tw) + vm.pActor->timetosleep = SLEEPTIME; + continue; + + case CON_IFACTORNOTSTAYPUT: VM_CONDITIONAL(vm.pActor->actorstayput == -1); continue; + + case CON_IFCANSEE: + { + uspritetype *pSprite = (uspritetype *)&sprite[pPlayer->i]; + +// select sprite for monster to target +// if holoduke is on, let them target holoduke first. +// + if (DUKE && pPlayer->holoduke_on >= 0) + { + pSprite = (uspritetype *)&sprite[pPlayer->holoduke_on]; + tw = cansee(vm.pSprite->x, vm.pSprite->y, vm.pSprite->z - (krand2() & (ZOFFSET5 - 1)), vm.pSprite->sectnum, pSprite->x, pSprite->y, + pSprite->z, pSprite->sectnum); + + if (tw == 0) + { + // they can't see player's holoduke + // check for player... + pSprite = (uspritetype *)&sprite[pPlayer->i]; + } + } + // can they see player, (or player's holoduke) + tw = cansee(vm.pSprite->x, vm.pSprite->y, vm.pSprite->z - (krand2() & ((47 << 8))), vm.pSprite->sectnum, pSprite->x, pSprite->y, + pSprite->z - (RR ? (28 << 8) : (24 << 8)), pSprite->sectnum); + + if (tw == 0) + { + // search around for target player + + // also modifies 'target' x&y if found.. + + tw = 1; + if (A_FurthestVisiblePoint(vm.spriteNum, pSprite, &vm.pActor->lastv) == -1) + tw = 0; + } + else + { + // else, they did see it. + // save where we were looking... + vm.pActor->lastv.x = pSprite->x; + vm.pActor->lastv.y = pSprite->y; + } + + if (tw && (vm.pSprite->statnum == STAT_ACTOR || vm.pSprite->statnum == STAT_STANDABLE)) + vm.pActor->timetosleep = SLEEPTIME; + + VM_CONDITIONAL(tw); + continue; + } + + case CON_IFHITWEAPON: VM_CONDITIONAL(A_IncurDamage(vm.spriteNum) >= 0); continue; + + case CON_IFSQUISHED: VM_CONDITIONAL(VM_CheckSquished()); continue; + + case CON_IFDEAD: VM_CONDITIONAL(vm.pSprite->extra - (vm.pSprite->picnum == APLAYER) < 0); continue; + + case CON_AI: + insptr++; + // Following changed to use pointersizes + AC_AI_ID(vm.pData) = *insptr++; // Ai + AC_ACTION_ID(vm.pData) = *(apScript + AC_AI_ID(vm.pData)); // Action + AC_MOVE_ID(vm.pData) = *(apScript + AC_AI_ID(vm.pData) + 1); // move + + vm.pSprite->hitag = *(apScript + AC_AI_ID(vm.pData) + 2); // move flags + + AC_COUNT(vm.pData) = 0; + AC_ACTION_COUNT(vm.pData) = 0; + AC_CURFRAME(vm.pData) = 0; + + if (vm.pSprite->hitag & random_angle) + vm.pSprite->ang = krand2() & 2047; + continue; + + case CON_ACTION: + insptr++; + AC_ACTION_COUNT(vm.pData) = 0; + AC_CURFRAME(vm.pData) = 0; + AC_ACTION_ID(vm.pData) = *insptr++; + continue; + + case CON_IFPDISTL: + VM_CONDITIONAL(vm.playerDist < *(++insptr)); + if (vm.playerDist > MAXSLEEPDIST && vm.pActor->timetosleep == 0) + vm.pActor->timetosleep = SLEEPTIME; + continue; + + case CON_IFPDISTG: + VM_CONDITIONAL(vm.playerDist > *(++insptr)); + if (vm.playerDist > MAXSLEEPDIST && vm.pActor->timetosleep == 0) + vm.pActor->timetosleep = SLEEPTIME; + continue; + + case CON_ADDSTRENGTH: + insptr++; + vm.pSprite->extra += *insptr++; + continue; + + case CON_STRENGTH: + insptr++; + vm.pSprite->extra = *insptr++; + continue; + + case CON_SMACKSPRITE: + insptr++; + if (krand2()&1) + vm.pSprite->ang = (vm.pSprite->ang-(512+(krand2()&511)))&2047; + else + vm.pSprite->ang = (vm.pSprite->ang+(512+(krand2()&511)))&2047; + continue; + + case CON_FAKEBUBBA: + insptr++; + switch (++g_fakeBubbaCnt) + { + case 1: + A_Spawn(vm.spriteNum, PIG); + break; + case 2: + A_Spawn(vm.spriteNum, MINION); + break; + case 3: + A_Spawn(vm.spriteNum, CHEER); + break; + case 4: + A_Spawn(vm.spriteNum, VIXEN); + G_OperateActivators(666, vm.playerNum); + break; + } + continue; + + case CON_RNDMOVE: + insptr++; + vm.pSprite->ang = krand2()&2047; + vm.pSprite->xvel = 25; + continue; + + case CON_MAMATRIGGER: + insptr++; + G_OperateActivators(667, vm.playerNum); + continue; + + case CON_MAMASPAWN: + insptr++; + if (g_mamaSpawnCnt) + { + g_mamaSpawnCnt--; + A_Spawn(vm.spriteNum, RABBIT); + } + continue; + + case CON_MAMAQUAKE: + insptr++; + if (vm.pSprite->pal == 31) + g_earthquakeTime = 4; + else if(vm.pSprite->pal == 32) + g_earthquakeTime = 6; + continue; + + case CON_GARYBANJO: + insptr++; + if (g_banjoSong == 0) + { + switch (krand2()&3) + { + case 3: + g_banjoSong = 262; + break; + case 0: + g_banjoSong = 272; + break; + default: + g_banjoSong = 273; + break; + } + A_PlaySound(g_banjoSong, vm.spriteNum); + } + else if (!S_CheckSoundPlaying(vm.spriteNum, g_banjoSong)) + A_PlaySound(g_banjoSong, vm.spriteNum); + continue; + case CON_MOTOLOOPSND: + insptr++; + if (!S_CheckSoundPlaying(vm.spriteNum, 411)) + A_PlaySound(411, vm.spriteNum); + continue; + + case CON_IFGOTWEAPONCE: + insptr++; + + if ((g_gametypeFlags[ud.coop] & GAMETYPE_WEAPSTAY) && (g_netServer || ud.multimode > 1)) + { + if (*insptr == 0) + { + int j = 0; + for (; j < pPlayer->weapreccnt; ++j) + if (pPlayer->weaprecs[j] == vm.pSprite->picnum) + break; + + VM_CONDITIONAL(j < pPlayer->weapreccnt && vm.pSprite->owner == vm.spriteNum); + continue; + } + else if (pPlayer->weapreccnt < MAX_WEAPON_RECS-1) + { + pPlayer->weaprecs[pPlayer->weapreccnt++] = vm.pSprite->picnum; + VM_CONDITIONAL(vm.pSprite->owner == vm.spriteNum); + continue; + } + } + VM_CONDITIONAL(0); + continue; + + case CON_GETLASTPAL: + insptr++; + if (vm.pSprite->picnum == APLAYER) + vm.pSprite->pal = g_player[P_GetP(vm.pSprite)].ps->palookup; + else + vm.pSprite->pal = vm.pActor->tempang; + vm.pActor->tempang = 0; + continue; + + case CON_TOSSWEAPON: + insptr++; + // NOTE: assumes that current actor is APLAYER + P_DropWeapon(P_GetP(vm.pSprite)); + continue; + + case CON_MIKESND: + insptr++; + if (EDUKE32_PREDICT_FALSE(((unsigned)vm.pSprite->yvel >= MAXSOUNDS))) + { + CON_ERRPRINTF("invalid sound %d\n", vm.pUSprite->yvel); + continue; + } + if (!S_CheckSoundPlaying(vm.spriteNum, vm.pSprite->yvel)) + A_PlaySound(vm.pSprite->yvel, vm.spriteNum); + continue; + + case CON_PKICK: + insptr++; + + if ((g_netServer || ud.multimode > 1) && vm.pSprite->picnum == APLAYER) + { + if (g_player[otherp].ps->quick_kick == 0) + g_player[otherp].ps->quick_kick = 14; + } + else if (vm.pSprite->picnum != APLAYER && pPlayer->quick_kick == 0) + pPlayer->quick_kick = 14; + continue; + + case CON_SIZETO: + insptr++; + + tw = (*insptr++ - vm.pSprite->xrepeat) << 1; + vm.pSprite->xrepeat += ksgn(tw); + + if ((vm.pSprite->picnum == APLAYER && vm.pSprite->yrepeat < 36) || *insptr < vm.pSprite->yrepeat + || ((vm.pSprite->yrepeat * (tilesiz[vm.pSprite->picnum].y + 8)) << 2) < (vm.pActor->floorz - vm.pActor->ceilingz)) + { + tw = ((*insptr) - vm.pSprite->yrepeat) << 1; + if (klabs(tw)) + vm.pSprite->yrepeat += ksgn(tw); + } + + insptr++; + + continue; + + case CON_SIZEAT: + insptr++; + vm.pSprite->xrepeat = (uint8_t)*insptr++; + vm.pSprite->yrepeat = (uint8_t)*insptr++; + continue; + + case CON_SHOOT: + insptr++; + if (EDUKE32_PREDICT_FALSE((unsigned)vm.pSprite->sectnum >= (unsigned)numsectors)) + { + CON_ERRPRINTF("invalid sector %d\n", vm.pUSprite->sectnum); + continue; + } + A_Shoot(vm.spriteNum, *insptr++); + continue; + + case CON_IFSOUNDID: + insptr++; + VM_CONDITIONAL((int16_t)*insptr == g_ambientLotag[vm.pSprite->ang]); + continue; + + case CON_IFSOUNDDIST: + insptr++; + if (*insptr == 0) + { + VM_CONDITIONAL(g_ambientHitag[vm.pSprite->ang] > vm.playerDist); + } + else if (*insptr == 1) + { + VM_CONDITIONAL(g_ambientHitag[vm.pSprite->ang] < vm.playerDist); + } + else + { + VM_CONDITIONAL(0); + } + + { + // This crashes VM... + extern char g_demo_legacy; + if (ud.recstat == 2 && g_demo_legacy) + insptr++; + } + continue; + + case CON_SOUNDTAG: + insptr++; + A_PlaySound(g_ambientLotag[vm.pSprite->ang], vm.spriteNum); + continue; + + case CON_SOUNDTAGONCE: + insptr++; + if (!S_CheckSoundPlaying(vm.spriteNum, g_ambientLotag[vm.pSprite->ang])) + A_PlaySound(g_ambientLotag[vm.pSprite->ang], vm.spriteNum); + continue; + + case CON_SOUNDONCE: + if (EDUKE32_PREDICT_FALSE((unsigned)*(++insptr) >= MAXSOUNDS)) + { + CON_ERRPRINTF("invalid sound %d\n", (int32_t)*insptr++); + continue; + } + + if (!S_CheckSoundPlaying(vm.spriteNum, *insptr++)) + A_PlaySound(*(insptr - 1), vm.spriteNum); + + continue; + + case CON_STOPSOUND: + if (EDUKE32_PREDICT_FALSE((unsigned)*(++insptr) >= MAXSOUNDS)) + { + CON_ERRPRINTF("invalid sound %d\n", (int32_t)*insptr); + insptr++; + continue; + } + if (S_CheckSoundPlaying(vm.spriteNum, *insptr)) + S_StopSound((int16_t)*insptr); + insptr++; + continue; + + case CON_GLOBALSOUND: + if (EDUKE32_PREDICT_FALSE((unsigned)*(++insptr) >= MAXSOUNDS)) + { + CON_ERRPRINTF("invalid sound %d\n", (int32_t)*insptr); + insptr++; + continue; + } + if (vm.playerNum == screenpeek || (g_gametypeFlags[ud.coop] & GAMETYPE_COOPSOUND) +#ifdef SPLITSCREEN_MOD_HACKS + || (g_fakeMultiMode == 2) +#endif + ) + A_PlaySound(*insptr, g_player[screenpeek].ps->i); + insptr++; + continue; + + case CON_SMACKBUBBA: + insptr++; + if (!RRRA || vm.pSprite->pal != 105) + { + for (bssize_t TRAVERSE_CONNECT(playerNum)) + g_player[playerNum].ps->gm = MODE_EOL; + if (++ud.level_number > 6) + ud.level_number = 0; + ud.m_level_number = ud.level_number; + } + continue; + + case CON_MAMAEND: + insptr++; + g_player[myconnectindex].ps->level_end_timer = 150; + continue; + + case CON_IFACTORHEALTHG: + insptr++; + VM_CONDITIONAL(vm.pSprite->extra > (int16_t)*insptr); + continue; + + case CON_IFACTORHEALTHL: + insptr++; + VM_CONDITIONAL(vm.pSprite->extra < (int16_t)*insptr); + continue; + + case CON_SOUND: + if (EDUKE32_PREDICT_FALSE((unsigned)*(++insptr) >= MAXSOUNDS)) + { + CON_ERRPRINTF("invalid sound %d\n", (int32_t)*insptr); + insptr++; + continue; + } + A_PlaySound(*insptr++, vm.spriteNum); + continue; + + case CON_TIP: + insptr++; + pPlayer->tipincs = GAMETICSPERSEC; + continue; + + case CON_IFTIPCOW: + if (g_spriteExtra[vm.spriteNum] == 1) + { + g_spriteExtra[vm.spriteNum]++; + VM_CONDITIONAL(1); + } + else + VM_CONDITIONAL(0); + continue; + + case CON_IFHITTRUCK: + if (g_spriteExtra[vm.spriteNum] == 1) + { + g_spriteExtra[vm.spriteNum]++; + VM_CONDITIONAL(1); + } + else + VM_CONDITIONAL(0); + continue; + + case CON_TEARITUP: + insptr++; + for (bssize_t SPRITES_OF_SECT(vm.pSprite->sectnum, spriteNum)) + { + if (sprite[spriteNum].picnum == DESTRUCTO) + { + actor[spriteNum].picnum = SHOTSPARK1; + actor[spriteNum].extra = 1; + } + } + continue; + + case CON_FALL: + insptr++; + VM_Fall(vm.spriteNum, vm.pSprite); + continue; + + case CON_NULLOP: insptr++; continue; + + case CON_ADDAMMO: + insptr++; + { + int const weaponNum = *insptr++; + int const addAmount = *insptr++; + + VM_AddAmmo(pPlayer, weaponNum, addAmount); + + continue; + } + + case CON_MONEY: + insptr++; + A_SpawnMultiple(vm.spriteNum, MONEY, *insptr++); + continue; + + case CON_MAIL: + insptr++; + A_SpawnMultiple(vm.spriteNum, RR ? MONEY : MAIL, *insptr++); + continue; + + case CON_SLEEPTIME: + insptr++; + vm.pActor->timetosleep = (int16_t)*insptr++; + continue; + + case CON_PAPER: + insptr++; + A_SpawnMultiple(vm.spriteNum, RR ? MONEY : PAPER, *insptr++); + continue; + + case CON_ADDKILLS: + insptr++; + if ((g_spriteExtra[vm.spriteNum] < 1 || g_spriteExtra[vm.spriteNum] == 128) + && A_CheckSpriteFlags(vm.spriteNum, SFLAG_KILLCOUNT)) + P_AddKills(pPlayer, *insptr); + insptr++; + vm.pActor->actorstayput = -1; + continue; + + case CON_LOTSOFGLASS: + insptr++; + A_SpawnGlass(vm.spriteNum, *insptr++); + continue; + + case CON_KILLIT: + insptr++; + vm.flags |= VM_KILL; + return; + + case CON_ADDWEAPON: + insptr++; + { + int const weaponNum = *insptr++; + VM_AddWeapon(pPlayer, weaponNum, *insptr++); + continue; + } + + case CON_DEBUG: + insptr++; + buildprint(*insptr++, "\n"); + continue; + + case CON_ENDOFGAME: + insptr++; + pPlayer->timebeforeexit = *insptr++; + pPlayer->customexitsound = -1; + ud.eog = 1; + continue; + + case CON_ISDRUNK: + insptr++; + { + pPlayer->drink_amt += *insptr; + + int newHealth = sprite[pPlayer->i].extra; + + if (newHealth > 0) + newHealth += *insptr; + if (newHealth > (pPlayer->max_player_health << 1)) + newHealth = (pPlayer->max_player_health << 1); + if (newHealth < 0) + newHealth = 0; + + if (ud.god == 0) + { + if (*insptr > 0) + { + if ((newHealth - *insptr) < (pPlayer->max_player_health >> 2) && newHealth >= (pPlayer->max_player_health >> 2)) + A_PlaySound(DUKE_GOTHEALTHATLOW, pPlayer->i); + pPlayer->last_extra = newHealth; + } + + sprite[pPlayer->i].extra = newHealth; + } + if (pPlayer->drink_amt > 100) + pPlayer->drink_amt = 100; + + if (sprite[pPlayer->i].extra >= pPlayer->max_player_health) + { + sprite[pPlayer->i].extra = pPlayer->max_player_health; + pPlayer->last_extra = pPlayer->max_player_health; + } + } + insptr++; + continue; + + case CON_STRAFELEFT: + insptr++; + { + vec3_t const vect = { sintable[(vm.pSprite->ang+1024)&2047]>>10, sintable[(vm.pSprite->ang+512)&2047]>>10, vm.pSprite->zvel }; + A_MoveSprite(vm.spriteNum, &vect, CLIPMASK0); + } + continue; + + case CON_STRAFERIGHT: + insptr++; + { + vec3_t const vect = { sintable[(vm.pSprite->ang-0)&2047]>>10, sintable[(vm.pSprite->ang-512)&2047]>>10, vm.pSprite->zvel }; + A_MoveSprite(vm.spriteNum, &vect, CLIPMASK0); + } + continue; + + case CON_LARRYBIRD: + insptr++; + pPlayer->pos.z = sector[sprite[pPlayer->i].sectnum].ceilingz; + sprite[pPlayer->i].z = pPlayer->pos.z; + continue; + + case CON_DESTROYIT: + insptr++; + { + int16_t hitag, lotag, spr, jj, k, nextk; + hitag = 0; + for (SPRITES_OF_SECT(vm.pSprite->sectnum,k)) + { + if (sprite[k].picnum == RRTILE63) + { + lotag = sprite[k].lotag; + spr = k; + if (sprite[k].hitag) + hitag = sprite[k].hitag; + } + } + for (SPRITES_OF(100, jj)) + { + spritetype const *js = &sprite[jj]; + if (hitag && hitag == js->hitag) + { + for (SPRITES_OF_SECT(js->sectnum,k)) + { + if (sprite[k].picnum == DESTRUCTO) + { + actor[k].picnum = SHOTSPARK1; + actor[k].extra = 1; + } + } + } + if (sprite[spr].sectnum != js->sectnum && lotag == js->lotag) + { + int16_t const sectnum = sprite[spr].sectnum; + int16_t const wallstart = sector[sectnum].wallptr; + int16_t const wallend = wallstart + sector[sectnum].wallnum; + int16_t const wallstart2 = sector[js->sectnum].wallptr; + //int16_t const wallend2 = wallstart2 + sector[js->sectnum].wallnum; + for (bssize_t wi = wallstart, wj = wallstart2; wi < wallend; wi++, wj++) + { + wall[wi].picnum = wall[wj].picnum; + wall[wi].overpicnum = wall[wj].overpicnum; + wall[wi].shade = wall[wj].shade; + wall[wi].xrepeat = wall[wj].xrepeat; + wall[wi].yrepeat = wall[wj].yrepeat; + wall[wi].xpanning = wall[wj].xpanning; + wall[wi].ypanning = wall[wj].ypanning; + if (RRRA && wall[wi].nextwall != -1) + { + wall[wi].cstat = 0; + wall[wall[wi].nextwall].cstat = 0; + } + } + sector[sectnum].floorz = sector[js->sectnum].floorz; + sector[sectnum].ceilingz = sector[js->sectnum].ceilingz; + sector[sectnum].ceilingstat = sector[js->sectnum].ceilingstat; + sector[sectnum].floorstat = sector[js->sectnum].floorstat; + sector[sectnum].ceilingpicnum = sector[js->sectnum].ceilingpicnum; + sector[sectnum].ceilingheinum = sector[js->sectnum].ceilingheinum; + sector[sectnum].ceilingshade = sector[js->sectnum].ceilingshade; + sector[sectnum].ceilingpal = sector[js->sectnum].ceilingpal; + sector[sectnum].ceilingxpanning = sector[js->sectnum].ceilingxpanning; + sector[sectnum].ceilingypanning = sector[js->sectnum].ceilingypanning; + sector[sectnum].floorpicnum = sector[js->sectnum].floorpicnum; + sector[sectnum].floorheinum = sector[js->sectnum].floorheinum; + sector[sectnum].floorshade = sector[js->sectnum].floorshade; + sector[sectnum].floorpal = sector[js->sectnum].floorpal; + sector[sectnum].floorxpanning = sector[js->sectnum].floorxpanning; + sector[sectnum].floorypanning = sector[js->sectnum].floorypanning; + sector[sectnum].visibility = sector[js->sectnum].visibility; + g_sectorExtra[sectnum] = g_sectorExtra[js->sectnum]; + sector[sectnum].lotag = sector[js->sectnum].lotag; + sector[sectnum].hitag = sector[js->sectnum].hitag; + sector[sectnum].extra = sector[js->sectnum].extra; + } + } + for (SPRITES_OF_SECT_SAFE(vm.pSprite->sectnum, k, nextk)) + { + switch (DYNAMICTILEMAP(sprite[k].picnum)) + { + case DESTRUCTO__STATICRR: + case RRTILE63__STATICRR: + case TORNADO__STATICRR: + case APLAYER__STATIC: + case COOT__STATICRR: + break; + default: + A_DeleteSprite(k); + break; + } + } + } + continue; + + case CON_ISEAT: + insptr++; + + { + pPlayer->eat_amt += *insptr; + if (pPlayer->eat_amt > 100) + pPlayer->eat_amt = 100; + + pPlayer->drink_amt -= *insptr; + if (pPlayer->drink_amt < 0) + pPlayer->drink_amt = 0; + + int newHealth = sprite[pPlayer->i].extra; + + if (vm.pSprite->picnum != ATOMICHEALTH) + { + if (newHealth > pPlayer->max_player_health && *insptr > 0) + { + insptr++; + continue; + } + else + { + if (newHealth > 0) + newHealth += (*insptr)*3; + if (newHealth > pPlayer->max_player_health && *insptr > 0) + newHealth = pPlayer->max_player_health; + } + } + else + { + if (newHealth > 0) + newHealth += *insptr; + if (newHealth > (pPlayer->max_player_health << 1)) + newHealth = (pPlayer->max_player_health << 1); + } + + if (newHealth < 0) + newHealth = 0; + + if (ud.god == 0) + { + if (*insptr > 0) + { + if ((newHealth - *insptr) < (pPlayer->max_player_health >> 2) && newHealth >= (pPlayer->max_player_health >> 2)) + A_PlaySound(DUKE_GOTHEALTHATLOW, pPlayer->i); + pPlayer->last_extra = newHealth; + } + + sprite[pPlayer->i].extra = newHealth; + } + } + + insptr++; + continue; + + case CON_ADDPHEALTH: + insptr++; + + { + if (!RR && pPlayer->newowner >= 0) + G_ClearCameraView(pPlayer); + + int newHealth = sprite[pPlayer->i].extra; + + if (vm.pSprite->picnum != ATOMICHEALTH) + { + if (newHealth > pPlayer->max_player_health && *insptr > 0) + { + insptr++; + continue; + } + else + { + if (newHealth > 0) + newHealth += *insptr; + if (newHealth > pPlayer->max_player_health && *insptr > 0) + newHealth = pPlayer->max_player_health; + } + } + else + { + if (newHealth > 0) + newHealth += *insptr; + if (newHealth > (pPlayer->max_player_health << 1)) + newHealth = (pPlayer->max_player_health << 1); + } + + if (newHealth < 0) + newHealth = 0; + + if (ud.god == 0) + { + if (*insptr > 0) + { + if ((newHealth - *insptr) < (pPlayer->max_player_health >> 2) && newHealth >= (pPlayer->max_player_health >> 2)) + A_PlaySound(DUKE_GOTHEALTHATLOW, pPlayer->i); + pPlayer->last_extra = newHealth; + } + + sprite[pPlayer->i].extra = newHealth; + } + } + + insptr++; + continue; + + case CON_MOVE: + insptr++; + AC_COUNT(vm.pData) = 0; + AC_MOVE_ID(vm.pData) = *insptr++; + vm.pSprite->hitag = *insptr++; + if (vm.pSprite->hitag & random_angle) + vm.pSprite->ang = krand2() & 2047; + continue; + + case CON_SPAWN: + insptr++; + if ((unsigned)vm.pSprite->sectnum >= MAXSECTORS) + { + CON_ERRPRINTF("invalid sector %d\n", vm.pUSprite->sectnum); + insptr++; + continue; + } + A_Spawn(vm.spriteNum, *insptr++); + continue; + + case CON_IFWASWEAPON: + case CON_IFSPAWNEDBY: + insptr++; + VM_CONDITIONAL(vm.pActor->picnum == *insptr); + continue; + + case CON_IFAI: + insptr++; + VM_CONDITIONAL(AC_AI_ID(vm.pData) == *insptr); + continue; + + case CON_IFACTION: + insptr++; + VM_CONDITIONAL(AC_ACTION_ID(vm.pData) == *insptr); + continue; + + case CON_IFACTIONCOUNT: + insptr++; + VM_CONDITIONAL(AC_ACTION_COUNT(vm.pData) >= *insptr); + continue; + + case CON_RESETACTIONCOUNT: + insptr++; + AC_ACTION_COUNT(vm.pData) = 0; + continue; + + case CON_DEBRIS: + insptr++; + { + int debrisTile = *insptr++; + + if ((unsigned)vm.pSprite->sectnum < MAXSECTORS) + for (native_t cnt = (*insptr) - 1; cnt >= 0; cnt--) + { + int const tileOffset = ((RR || vm.pSprite->picnum == BLIMP) && debrisTile == SCRAP1) ? 0 : (krand2() % 3); + + int32_t const r1 = krand2(), r2 = krand2(), r3 = krand2(), r4 = krand2(), r5 = krand2(), r6 = krand2(), r7 = krand2(), r8 = krand2(); + int const spriteNum = A_InsertSprite(vm.pSprite->sectnum, vm.pSprite->x + (r8 & 255) - 128, + vm.pSprite->y + (r7 & 255) - 128, vm.pSprite->z - (8 << 8) - (r6 & 8191), + debrisTile + tileOffset, vm.pSprite->shade, 32 + (r5 & 15), 32 + (r4 & 15), + r3 & 2047, (r2 & 127) + 32, -(r1 & 2047), vm.spriteNum, 5); + + sprite[spriteNum].yvel = ((RR || vm.pSprite->picnum == BLIMP) && debrisTile == SCRAP1) ? g_blimpSpawnItems[cnt % 14] : -1; + sprite[spriteNum].pal = vm.pSprite->pal; + } + insptr++; + } + continue; + + case CON_COUNT: + insptr++; + AC_COUNT(vm.pData) = (int16_t)*insptr++; + continue; + + case CON_CSTATOR: + insptr++; + vm.pSprite->cstat |= (int16_t)*insptr++; + continue; + + case CON_CLIPDIST: + insptr++; + vm.pSprite->clipdist = (int16_t)*insptr++; + continue; + + case CON_CSTAT: + insptr++; + vm.pSprite->cstat = (int16_t)*insptr++; + continue; + + case CON_NEWPIC: + insptr++; + vm.pSprite->picnum = (int16_t)*insptr++; + continue; + + case CON_IFMOVE: + insptr++; + VM_CONDITIONAL(AC_MOVE_ID(vm.pData) == *insptr); + continue; + + case CON_RESETPLAYER: + insptr++; + vm.flags = VM_ResetPlayer(vm.playerNum, vm.flags); + continue; + + case CON_IFCOOP: + VM_CONDITIONAL(GTFLAGS(GAMETYPE_COOP) || numplayers > 2); + continue; + + case CON_IFONMUD: + VM_CONDITIONAL(sector[vm.pSprite->sectnum].floorpicnum == RRTILE3073 + && klabs(vm.pSprite->z - sector[vm.pSprite->sectnum].floorz) < ZOFFSET5); + continue; + + case CON_IFONWATER: + VM_CONDITIONAL(sector[vm.pSprite->sectnum].lotag == ST_1_ABOVE_WATER + && klabs(vm.pSprite->z - sector[vm.pSprite->sectnum].floorz) < ZOFFSET5); + continue; + + case CON_IFMOTOFAST: + VM_CONDITIONAL(pPlayer->moto_speed > 60); + continue; + + case CON_IFONMOTO: + VM_CONDITIONAL(pPlayer->on_motorcycle == 1); + continue; + + case CON_IFONBOAT: + VM_CONDITIONAL(pPlayer->on_boat == 1); + continue; + + case CON_IFSIZEDOWN: + vm.pSprite->xrepeat--; + vm.pSprite->yrepeat--; + VM_CONDITIONAL(vm.pSprite->xrepeat <= 5); + continue; + + case CON_IFWIND: + VM_CONDITIONAL(g_windTime > 0); + continue; + + case CON_IFINWATER: VM_CONDITIONAL(sector[vm.pSprite->sectnum].lotag == ST_2_UNDERWATER); continue; + + case CON_IFCOUNT: + insptr++; + VM_CONDITIONAL(AC_COUNT(vm.pData) >= *insptr); + continue; + + case CON_IFACTOR: + insptr++; + VM_CONDITIONAL(vm.pSprite->picnum == *insptr); + continue; + + case CON_RESETCOUNT: + insptr++; + AC_COUNT(vm.pData) = 0; + continue; + + case CON_ADDINVENTORY: + insptr += 2; + + VM_AddInventory(pPlayer, *(insptr - 1), *insptr); + + insptr++; + continue; + + case CON_HITRADIUS: + A_RadiusDamage(vm.spriteNum, *(insptr + 1), *(insptr + 2), *(insptr + 3), *(insptr + 4), *(insptr + 5)); + insptr += 6; + continue; + + case CON_IFP: + { + int const moveFlags = *(++insptr); + int nResult = 0; + int const playerXVel = sprite[pPlayer->i].xvel; + int const syncBits = g_player[vm.playerNum].inputBits->bits; + + if (((moveFlags & pducking) && pPlayer->on_ground && TEST_SYNC_KEY(syncBits, SK_CROUCH)) + || ((moveFlags & pfalling) && pPlayer->jumping_counter == 0 && !pPlayer->on_ground && pPlayer->vel.z > 2048) + || ((moveFlags & pjumping) && pPlayer->jumping_counter > 348) + || ((moveFlags & pstanding) && playerXVel >= 0 && playerXVel < 8) + || ((moveFlags & pwalking) && playerXVel >= 8 && !TEST_SYNC_KEY(syncBits, SK_RUN)) + || ((moveFlags & prunning) && playerXVel >= 8 && TEST_SYNC_KEY(syncBits, SK_RUN)) + || ((moveFlags & phigher) && pPlayer->pos.z < (vm.pSprite->z - (48 << 8))) + || ((moveFlags & pwalkingback) && playerXVel <= -8 && !TEST_SYNC_KEY(syncBits, SK_RUN)) + || ((moveFlags & prunningback) && playerXVel <= -8 && TEST_SYNC_KEY(syncBits, SK_RUN)) + || ((moveFlags & pkicking) + && (pPlayer->quick_kick > 0 + || (pPlayer->curr_weapon == KNEE_WEAPON && pPlayer->kickback_pic > 0))) + || ((moveFlags & pshrunk) && sprite[pPlayer->i].xrepeat < (RR ? 8 : 32)) + || ((moveFlags & pjetpack) && pPlayer->jetpack_on) + || ((moveFlags & ponsteroids) && pPlayer->inv_amount[GET_STEROIDS] > 0 && pPlayer->inv_amount[GET_STEROIDS] < 400) + || ((moveFlags & ponground) && pPlayer->on_ground) + || ((moveFlags & palive) && sprite[pPlayer->i].xrepeat > (RR ? 8 : 32) && sprite[pPlayer->i].extra > 0 && pPlayer->timebeforeexit == 0) + || ((moveFlags & pdead) && sprite[pPlayer->i].extra <= 0)) + nResult = 1; + else if ((moveFlags & pfacing)) + { + nResult + = (vm.pSprite->picnum == APLAYER && (g_netServer || ud.multimode > 1)) + ? G_GetAngleDelta(fix16_to_int(g_player[otherp].ps->q16ang), + getangle(pPlayer->pos.x - g_player[otherp].ps->pos.x, pPlayer->pos.y - g_player[otherp].ps->pos.y)) + : G_GetAngleDelta(fix16_to_int(pPlayer->q16ang), getangle(vm.pSprite->x - pPlayer->pos.x, vm.pSprite->y - pPlayer->pos.y)); + + nResult = (nResult > -128 && nResult < 128); + } + VM_CONDITIONAL(nResult); + } + continue; + + case CON_IFSTRENGTH: + insptr++; + VM_CONDITIONAL(vm.pSprite->extra <= *insptr); + continue; + + case CON_GUTS: + A_DoGuts(vm.spriteNum, *(insptr + 1), *(insptr + 2)); + insptr += 3; + continue; + + case CON_SLAPPLAYER: + insptr++; + P_ForceAngle(pPlayer); + pPlayer->vel.x -= sintable[(fix16_to_int(pPlayer->q16ang)+512)&2047]<<7; + pPlayer->vel.y -= sintable[fix16_to_int(pPlayer->q16ang)&2047]<<7; + continue; + + case CON_WACKPLAYER: + insptr++; + if (RR) + { + pPlayer->vel.x -= sintable[(fix16_to_int(pPlayer->q16ang)+512)&2047]<<7; + pPlayer->vel.y -= sintable[fix16_to_int(pPlayer->q16ang)&2047]<<7; + pPlayer->jumping_counter = 767; + pPlayer->jumping_toggle = 1; + } + else + P_ForceAngle(pPlayer); + continue; + + case CON_IFGAPZL: + insptr++; + VM_CONDITIONAL(((vm.pActor->floorz - vm.pActor->ceilingz) >> 8) < *insptr); + continue; + + case CON_IFHITSPACE: VM_CONDITIONAL(TEST_SYNC_KEY(g_player[vm.playerNum].inputBits->bits, SK_OPEN)); continue; + + case CON_IFOUTSIDE: VM_CONDITIONAL(sector[vm.pSprite->sectnum].ceilingstat & 1); continue; + + case CON_IFMULTIPLAYER: VM_CONDITIONAL((g_netServer || g_netClient || ud.multimode > 1)); continue; + + case CON_OPERATE: + insptr++; + if (sector[vm.pSprite->sectnum].lotag == 0) + { + int16_t foundSect, foundWall, foundSprite; + int32_t foundDist; + + neartag(vm.pSprite->x, vm.pSprite->y, vm.pSprite->z - ZOFFSET5, vm.pSprite->sectnum, vm.pSprite->ang, &foundSect, &foundWall, + &foundSprite, &foundDist, 768, 4 + 1, NULL); + + if (foundSect >= 0 && isanearoperator(sector[foundSect].lotag)) + if ((sector[foundSect].lotag & 0xff) == ST_23_SWINGING_DOOR || sector[foundSect].floorz == sector[foundSect].ceilingz) + if ((sector[foundSect].lotag & (16384u | 32768u)) == 0) + { + int32_t j; + + for (SPRITES_OF_SECT(foundSect, j)) + if (sprite[j].picnum == ACTIVATOR) + break; + + if (j == -1) + G_OperateSectors(foundSect, vm.spriteNum); + } + } + continue; + + case CON_IFINSPACE: VM_CONDITIONAL(G_CheckForSpaceCeiling(vm.pSprite->sectnum)); continue; + + case CON_SPRITEPAL: + insptr++; + if (vm.pSprite->picnum != APLAYER) + vm.pActor->tempang = vm.pSprite->pal; + vm.pSprite->pal = *insptr++; + continue; + + case CON_CACTOR: + insptr++; + vm.pSprite->picnum = *insptr++; + continue; + + case CON_IFBULLETNEAR: VM_CONDITIONAL(A_Dodge(vm.pSprite) == 1); continue; + + case CON_IFRESPAWN: + if (A_CheckEnemySprite(vm.pSprite)) + VM_CONDITIONAL(ud.respawn_monsters) + else if (A_CheckInventorySprite(vm.pSprite)) + VM_CONDITIONAL(ud.respawn_inventory) + else + VM_CONDITIONAL(ud.respawn_items) + continue; + + case CON_IFFLOORDISTL: + insptr++; + VM_CONDITIONAL((vm.pActor->floorz - vm.pSprite->z) <= ((*insptr) << 8)); + continue; + + case CON_IFCEILINGDISTL: + insptr++; + VM_CONDITIONAL((vm.pSprite->z - vm.pActor->ceilingz) <= ((*insptr) << 8)); + continue; + + case CON_PALFROM: + insptr++; + if (EDUKE32_PREDICT_FALSE((unsigned)vm.playerNum >= (unsigned)g_mostConcurrentPlayers)) + { + CON_ERRPRINTF("invalid player %d\n", vm.playerNum); + insptr += 4; + } + else + { + palette_t const pal = { (uint8_t) * (insptr + 1), (uint8_t) * (insptr + 2), (uint8_t) * (insptr + 3), (uint8_t) * (insptr) }; + insptr += 4; + P_PalFrom(pPlayer, pal.f, pal.r, pal.g, pal.b); + } + continue; + + case CON_IFPHEALTHL: + insptr++; + VM_CONDITIONAL(sprite[pPlayer->i].extra < *insptr); + continue; + + case CON_IFPINVENTORY: + insptr++; + + switch (*insptr++) + { + case GET_STEROIDS: + case GET_SCUBA: + case GET_HOLODUKE: + case GET_HEATS: + case GET_FIRSTAID: + case GET_BOOTS: + case GET_JETPACK: tw = (pPlayer->inv_amount[*(insptr - 1)] != *insptr); break; + + case GET_SHIELD: + tw = (pPlayer->inv_amount[GET_SHIELD] != pPlayer->max_player_health); break; + case GET_ACCESS: + if (RR) + { + switch (vm.pSprite->lotag) + { + case 100: tw = pPlayer->keys[1]; break; + case 101: tw = pPlayer->keys[2]; break; + case 102: tw = pPlayer->keys[3]; break; + case 103: tw = pPlayer->keys[4]; break; + } + } + else + { + switch (vm.pSprite->pal) + { + case 0: tw = (pPlayer->got_access & 1); break; + case 21: tw = (pPlayer->got_access & 2); break; + case 23: tw = (pPlayer->got_access & 4); break; + } + } + break; + default: tw = 0; CON_ERRPRINTF("invalid inventory item %d\n", (int32_t) * (insptr - 1)); + } + + VM_CONDITIONAL(tw); + continue; + + case CON_PSTOMP: + insptr++; + if (pPlayer->knee_incs == 0 && sprite[pPlayer->i].xrepeat >= (RR ? 9 : 40)) + if (cansee(vm.pSprite->x, vm.pSprite->y, vm.pSprite->z - ZOFFSET6, vm.pSprite->sectnum, pPlayer->pos.x, pPlayer->pos.y, + pPlayer->pos.z + ZOFFSET2, sprite[pPlayer->i].sectnum)) + { + if (pPlayer->weapon_pos == 0) + pPlayer->weapon_pos = -1; + + pPlayer->actorsqu = vm.spriteNum; + pPlayer->knee_incs = 1; + } + continue; + + case CON_IFAWAYFROMWALL: + { + int16_t otherSectnum = vm.pSprite->sectnum; + tw = 0; + +#define IFAWAYDIST 108 + + updatesector(vm.pSprite->x + IFAWAYDIST, vm.pSprite->y + IFAWAYDIST, &otherSectnum); + if (otherSectnum == vm.pSprite->sectnum) + { + updatesector(vm.pSprite->x - IFAWAYDIST, vm.pSprite->y - IFAWAYDIST, &otherSectnum); + if (otherSectnum == vm.pSprite->sectnum) + { + updatesector(vm.pSprite->x + IFAWAYDIST, vm.pSprite->y - IFAWAYDIST, &otherSectnum); + if (otherSectnum == vm.pSprite->sectnum) + { + updatesector(vm.pSprite->x - IFAWAYDIST, vm.pSprite->y + IFAWAYDIST, &otherSectnum); + if (otherSectnum == vm.pSprite->sectnum) + tw = 1; + } + } + } + + VM_CONDITIONAL(tw); + +#undef IFAWAYDIST + } + continue; + + case CON_QUOTE: + insptr++; + + if (EDUKE32_PREDICT_FALSE((unsigned)(*insptr) >= MAXQUOTES) || apStrings[*insptr] == NULL) + { + CON_ERRPRINTF("invalid quote %d\n", (int32_t)(*insptr)); + insptr++; + continue; + } + + if (EDUKE32_PREDICT_FALSE((unsigned)vm.playerNum >= MAXPLAYERS)) + { + CON_ERRPRINTF("invalid player %d\n", vm.playerNum); + insptr++; + continue; + } + + P_DoQuote(*(insptr++) | MAXQUOTES, pPlayer); + continue; + + case CON_IFINOUTERSPACE: VM_CONDITIONAL(G_CheckForSpaceFloor(vm.pSprite->sectnum)); continue; + + case CON_IFNOTMOVING: VM_CONDITIONAL((vm.pActor->movflag & 49152) > 16384); continue; + + case CON_RESPAWNHITAG: + insptr++; + switch (DYNAMICTILEMAP(vm.pSprite->picnum)) + { + case FEM1__STATIC: + case FEM2__STATIC: + case FEM3__STATIC: + case FEM4__STATIC: + case FEM5__STATIC: + case FEM6__STATIC: + case FEM7__STATIC: + case FEM8__STATIC: + case FEM9__STATIC: + case PODFEM1__STATIC: + if (RR) break; + fallthrough__; + case FEM10__STATIC: + case NAKED1__STATIC: + case STATUE__STATIC: + if (vm.pSprite->yvel) + G_OperateRespawns(vm.pSprite->yvel); + break; + default: + if (vm.pSprite->hitag >= 0) + G_OperateRespawns(vm.pSprite->hitag); + break; + } + continue; + + case CON_IFSPRITEPAL: + insptr++; + VM_CONDITIONAL(vm.pSprite->pal == *insptr); + continue; + + case CON_IFANGDIFFL: + insptr++; + tw = klabs(G_GetAngleDelta(fix16_to_int(pPlayer->q16ang), vm.pSprite->ang)); + VM_CONDITIONAL(tw <= *insptr); + continue; + + case CON_IFNOSOUNDS: VM_CONDITIONAL(!A_CheckAnySoundPlaying(vm.spriteNum)); continue; + + default: // you aren't supposed to be here! + if (RR && ud.recstat == 2) + { + vm.flags |= VM_KILL; + return; + } + debug_break(); + VM_ScriptInfo(insptr, 64); + G_GameExit("An error has occurred in the " APPNAME " virtual machine.\n\n" + "If you are an end user, please e-mail the file " APPBASENAME ".log\n" + "along with links to any mods you're using to development@voidpoint.com.\n\n" + "If you are a developer, please attach all of your script files\n" + "along with instructions on how to reproduce this error.\n\n" + "Thank you!"); + break; + } + } +} + +// NORECURSE +void A_LoadActor(int32_t spriteNum) +{ + vm.spriteNum = spriteNum; // Sprite ID + vm.pSprite = &sprite[spriteNum]; // Pointer to sprite structure + vm.pActor = &actor[spriteNum]; + + if (g_tile[vm.pSprite->picnum].loadPtr == NULL) + return; + + vm.pData = &actor[spriteNum].t_data[0]; // Sprite's 'extra' data + vm.playerNum = -1; // Player ID + vm.playerDist = -1; // Distance + vm.pPlayer = g_player[0].ps; + + vm.flags &= ~(VM_RETURN | VM_KILL | VM_NOEXECUTE); + + if ((unsigned)vm.pSprite->sectnum >= MAXSECTORS) + { + A_DeleteSprite(vm.spriteNum); + return; + } + + insptr = g_tile[vm.pSprite->picnum].loadPtr; + VM_Execute(1); + insptr = NULL; + + if (vm.flags & VM_KILL) + A_DeleteSprite(vm.spriteNum); +} + +void VM_UpdateAnim(int spriteNum, int32_t *pData) +{ + size_t const actionofs = AC_ACTION_ID(pData); + intptr_t const *actionptr = (actionofs != 0 && actionofs + (ACTION_PARAM_COUNT-1) < (unsigned) g_scriptSize) ? &apScript[actionofs] : NULL; + + if (actionptr != NULL) + { + int const action_frames = actionptr[ACTION_NUMFRAMES]; + int const action_incval = actionptr[ACTION_INCVAL]; + int const action_delay = actionptr[ACTION_DELAY]; + auto actionticsptr = &AC_ACTIONTICS(&sprite[spriteNum], &actor[spriteNum]); + *actionticsptr += TICSPERFRAME; + + if (*actionticsptr > action_delay) + { + *actionticsptr = 0; + AC_ACTION_COUNT(pData)++; + AC_CURFRAME(pData) += action_incval; + } + + if (klabs(AC_CURFRAME(pData)) >= klabs(action_frames * action_incval)) + AC_CURFRAME(pData) = 0; + } +} + +// NORECURSE +void A_Execute(int spriteNum, int playerNum, int playerDist) +{ + vmstate_t tempvm + = { spriteNum, playerNum, playerDist, 0, &sprite[spriteNum], &actor[spriteNum].t_data[0], g_player[playerNum].ps, &actor[spriteNum] }; + vm = tempvm; + +/* + if (g_netClient && A_CheckSpriteFlags(spriteNum, SFLAG_NULL)) + { + A_DeleteSprite(spriteNum); + return; + } +*/ + + if (g_netClient) // [75] The server should not overwrite its own randomseed + randomseed = ticrandomseed; + + if (EDUKE32_PREDICT_FALSE((unsigned)vm.pSprite->sectnum >= MAXSECTORS)) + { + if (A_CheckEnemySprite(vm.pSprite)) + P_AddKills(vm.pPlayer, 1); + + A_DeleteSprite(vm.spriteNum); + return; + } + + VM_UpdateAnim(vm.spriteNum, vm.pData); + + double t = timerGetHiTicks(); + int const picnum = vm.pSprite->picnum; + insptr = 4 + (g_tile[vm.pSprite->picnum].execPtr); + VM_Execute(1); + insptr = NULL; + + t = timerGetHiTicks()-t; + g_actorTotalMs[picnum] += t; + g_actorMinMs[picnum] = min(g_actorMinMs[picnum], t); + g_actorMaxMs[picnum] = max(g_actorMaxMs[picnum], t); + g_actorCalls[picnum]++; + + if (vm.flags & VM_KILL) + { + VM_DeleteSprite(spriteNum, playerNum); + return; + } + + VM_Move(); + + if (vm.pSprite->statnum != STAT_ACTOR) + { + if (vm.pSprite->statnum == STAT_STANDABLE) + { + switch (DYNAMICTILEMAP(vm.pSprite->picnum)) + { + case RUBBERCAN__STATIC: + case EXPLODINGBARREL__STATIC: + case WOODENHORSE__STATIC: + case HORSEONSIDE__STATIC: + case CANWITHSOMETHING__STATIC: + case FIREBARREL__STATIC: + case NUKEBARREL__STATIC: + case NUKEBARRELDENTED__STATIC: + case NUKEBARRELLEAKED__STATIC: + case TRIPBOMB__STATIC: + case EGG__STATIC: + if (vm.pActor->timetosleep > 1) + vm.pActor->timetosleep--; + else if (vm.pActor->timetosleep == 1) + changespritestat(vm.spriteNum, STAT_ZOMBIEACTOR); + default: break; + } + } + goto safe_delete; + } + + if (A_CheckEnemySprite(vm.pSprite)) + { + if (vm.pSprite->xrepeat > 60 || (ud.respawn_monsters == 1 && vm.pSprite->extra <= 0)) + goto safe_delete; + } + else if (EDUKE32_PREDICT_FALSE(ud.respawn_items == 1 && (vm.pSprite->cstat & 32768))) + goto safe_delete; + + if (A_CheckSpriteFlags(vm.spriteNum, SFLAG_USEACTIVATOR) && sector[vm.pSprite->sectnum].lotag & 16384) + changespritestat(vm.spriteNum, STAT_ZOMBIEACTOR); + else if (vm.pActor->timetosleep > 1) + vm.pActor->timetosleep--; + else if (vm.pActor->timetosleep == 1) + changespritestat(vm.spriteNum, STAT_ZOMBIEACTOR); + +safe_delete: + if (vm.flags & VM_SAFEDELETE) + A_DeleteSprite(spriteNum); +} diff --git a/source/rr/src/gameexec.h b/source/rr/src/gameexec.h new file mode 100644 index 000000000..f1110456e --- /dev/null +++ b/source/rr/src/gameexec.h @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef gameexec_h_ +#define gameexec_h_ + +#include "build.h" +#include "sector.h" // mapstate_t +#include "gamedef.h" // vmstate_t +#include "events_defs.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +extern int32_t ticrandomseed; + +extern vmstate_t vm; +extern int32_t g_tw; +extern int32_t g_errorLineNum; +extern int32_t g_currentEventExec; + +void A_LoadActor(int32_t spriteNum); + +extern uint32_t g_actorCalls[MAXTILES]; +extern double g_actorTotalMs[MAXTILES], g_actorMinMs[MAXTILES], g_actorMaxMs[MAXTILES]; + +void A_Execute(int spriteNum, int playerNum, int playerDist); +void A_Fall(int spriteNum); +int32_t A_GetFurthestAngle(int spriteNum, int angDiv); +void A_GetZLimits(int spriteNum); +int32_t __fastcall G_GetAngleDelta(int32_t currAngle, int32_t newAngle); +//void G_RestoreMapState(); +//void G_SaveMapState(); + +#define CON_ERRPRINTF(Text, ...) do { \ + OSD_Printf("Line %d, %s: " Text, g_errorLineNum, VM_GetKeywordForID(g_tw), ## __VA_ARGS__); \ +} while (0) + +#define CON_CRITICALERRPRINTF(Text, ...) do { \ + OSD_Printf("Line %d, %s: " Text, g_errorLineNum, VM_GetKeywordForID(g_tw), ## __VA_ARGS__); \ + wm_msgbox(APPNAME, "Line %d, %s: " Text, g_errorLineNum, VM_GetKeywordForID(g_tw), ## __VA_ARGS__); \ +} while (0) + +void G_GetTimeDate(int32_t * pValues); +int G_StartTrack(int levelNum); +void VM_UpdateAnim(int spriteNum, int32_t *pData); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/rr/src/gamestructures.cpp b/source/rr/src/gamestructures.cpp new file mode 100644 index 000000000..d8a8e2b9c --- /dev/null +++ b/source/rr/src/gamestructures.cpp @@ -0,0 +1,21 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- diff --git a/source/rr/src/gamevars.cpp b/source/rr/src/gamevars.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/source/rr/src/gamevars.h b/source/rr/src/gamevars.h new file mode 100644 index 000000000..df132082d --- /dev/null +++ b/source/rr/src/gamevars.h @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef gamevars_h_ +#define gamevars_h_ + +// Alignments for per-player and per-actor variables. +#define PLAYER_VAR_ALIGNMENT (sizeof(intptr_t)) +#define ACTOR_VAR_ALIGNMENT 16 + +#define ARRAY_ALIGNMENT 16 + +#endif diff --git a/source/rr/src/global.cpp b/source/rr/src/global.cpp new file mode 100644 index 000000000..7db30c69d --- /dev/null +++ b/source/rr/src/global.cpp @@ -0,0 +1,133 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#define global_c_ +#include "global.h" +#include "duke3d.h" + +user_defs ud; + +const char *s_buildDate = "20120522"; + +#ifdef __cplusplus +extern "C" { +#endif + +char g_volumeNames[MAXVOLUMES][33] = { "L.A. Meltdown", "Lunar Apocalypse", "Shrapnel City" }; +char g_skillNames[MAXSKILLS][33] = { "Piece Of Cake", "Let's Rock", "Come Get Some", "Damn I'm Good" }; +char g_gametypeNames[MAXGAMETYPES][33] += { "DukeMatch (Spawn)", "Cooperative Play", "DukeMatch (No Spawn)", "Team DM (Spawn)", "Team DM (No Spawn)" }; + +int32_t g_volumeFlags[MAXVOLUMES]; + +int32_t g_gametypeFlags[MAXGAMETYPES] = +{ + GAMETYPE_FRAGBAR | + GAMETYPE_SCORESHEET | + GAMETYPE_DMSWITCHES | + GAMETYPE_ITEMRESPAWN | + GAMETYPE_MARKEROPTION | + GAMETYPE_ACCESSATSTART, + + GAMETYPE_COOP | + GAMETYPE_WEAPSTAY | + GAMETYPE_COOPSPAWN | + GAMETYPE_ACCESSCARDSPRITES | + GAMETYPE_COOPVIEW | + GAMETYPE_COOPSOUND | + GAMETYPE_OTHERPLAYERSINMAP | + GAMETYPE_PLAYERSFRIENDLY | + GAMETYPE_FIXEDRESPAWN | + GAMETYPE_PRESERVEINVENTORYDEATH, + + GAMETYPE_WEAPSTAY | + GAMETYPE_FRAGBAR | + GAMETYPE_SCORESHEET | + GAMETYPE_DMSWITCHES | + GAMETYPE_ACCESSATSTART, + + GAMETYPE_FRAGBAR | + GAMETYPE_SCORESHEET | + GAMETYPE_DMSWITCHES | + GAMETYPE_ITEMRESPAWN | + GAMETYPE_MARKEROPTION | + GAMETYPE_ACCESSATSTART | + GAMETYPE_TDM | + GAMETYPE_TDMSPAWN, + + GAMETYPE_WEAPSTAY | + GAMETYPE_FRAGBAR | + GAMETYPE_SCORESHEET | + GAMETYPE_DMSWITCHES | + GAMETYPE_ACCESSATSTART | + GAMETYPE_TDM | + GAMETYPE_TDMSPAWN, +}; + +float g_gameUpdateAvgTime = -1.f; + +int32_t g_actorRespawnTime = 768; +int32_t g_bouncemineRadius = 2500; +int32_t g_deleteQueueSize = 64; +int32_t g_itemRespawnTime = 768; + +int32_t g_morterRadius = 2500; +int32_t g_numFreezeBounces = 3; +int32_t g_gametypeCnt = 5; +int32_t g_volumeCnt = 3; +int32_t g_pipebombRadius = 2500; +int32_t g_playerFriction = 0xCFD0; +int32_t g_rpgRadius = 1780; +int32_t g_scriptSize = 1048576; +int32_t g_seenineRadius = 2048; +int32_t g_shrinkerRadius = 650; +int32_t g_spriteGravity = 176; +int32_t g_timerTicsPerSecond = TICRATE; +int32_t g_tripbombRadius = 3880; + +int16_t g_blimpSpawnItems[15] = +{ + RPGSPRITE__STATIC, + CHAINGUNSPRITE__STATIC, + DEVISTATORAMMO__STATIC, + RPGAMMO__STATIC, + RPGAMMO__STATIC, + JETPACK__STATIC, + SHIELD__STATIC, + FIRSTAID__STATIC, + STEROIDS__STATIC, + RPGAMMO__STATIC, + RPGAMMO__STATIC, + RPGSPRITE__STATIC, + RPGAMMO__STATIC, + FREEZESPRITE__STATIC, + FREEZEAMMO__STATIC +}; + +char CheatKeys[2] = { sc_D, sc_N }; + +char g_setupFileName[BMAX_PATH] = SETUPFILENAME; + +#ifdef __cplusplus +} +#endif + diff --git a/source/rr/src/global.h b/source/rr/src/global.h new file mode 100644 index 000000000..312848b8d --- /dev/null +++ b/source/rr/src/global.h @@ -0,0 +1,302 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef global_h_ +#define global_h_ + +#define MAXMINECARTS 16 +#define MAXJAILDOORS 32 +#define MAXLIGHTNINSECTORS 64 +#define MAXTORCHSECTORS 64 +#define MAXGEOSECTORS 64 + +#ifdef global_c_ + #define G_EXTERN +#else + #define G_EXTERN extern +#endif + +#define MAXINTERPOLATIONS MAXSPRITES +// KEEPINSYNC lunatic/con_lang.lua +#define MAXSKILLS 7 + +// duke3d global soup :( + +// XXX: we don't #include everything we need. +#include "compat.h" +#include "build.h" + +G_EXTERN int32_t g_interpolationCnt; +G_EXTERN int32_t g_interpolationLock; +G_EXTERN int32_t oldipos[MAXINTERPOLATIONS]; +G_EXTERN int32_t *curipos[MAXINTERPOLATIONS]; +G_EXTERN int32_t bakipos[MAXINTERPOLATIONS]; + +#include "mmulti.h" + +#include "duke3d.h" +#include "sector.h" +#include "quotes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +G_EXTERN int32_t duke3d_globalflags; + +// KEEPINSYNC astub.c (used values only) +enum DUKE3D_GLOBALFLAGS { + DUKE3D_NO_WIDESCREEN_PINNING = 1<<0, + DUKE3D_NO_HARDCODED_FOGPALS = 1<<1, + DUKE3D_NO_PALETTE_CHANGES = 1<<2, +}; + +G_EXTERN DukeStatus_t sbar; +G_EXTERN actor_t actor[MAXSPRITES]; +// g_tile: tile-specific data THAT DOES NOT CHANGE during the course of a game +G_EXTERN tiledata_t g_tile[MAXTILES]; +G_EXTERN animwalltype animwall[MAXANIMWALLS]; +G_EXTERN char *apStrings[MAXQUOTES],*apXStrings[MAXQUOTES]; +G_EXTERN char *label; +G_EXTERN int32_t g_musicIndex; +G_EXTERN char g_loadFromGroupOnly; +G_EXTERN char g_skillCnt; +G_EXTERN char pus,pub; +G_EXTERN char ready2send; +#define MAXPLAYERNAME 32 +G_EXTERN char szPlayerName[MAXPLAYERNAME]; +G_EXTERN char tempbuf[MAXSECTORS<<1],packbuf[PACKBUF_SIZE],buf[1024]; +#define TYPEBUFSIZE 141 +G_EXTERN char typebuf[TYPEBUFSIZE]; + + +G_EXTERN input_t localInput; +G_EXTERN input_t recsync[RECSYNCBUFSIZ]; + +G_EXTERN int32_t g_animWallCnt; +G_EXTERN int32_t g_animateCnt; +G_EXTERN int32_t g_cloudCnt; +G_EXTERN int32_t g_curViewscreen; +G_EXTERN int32_t g_frameRate; +G_EXTERN int32_t g_cyclerCnt; +G_EXTERN int32_t g_damageCameras; +G_EXTERN int32_t g_defaultLabelCnt; +G_EXTERN int32_t g_doQuickSave; +G_EXTERN int32_t g_earthquakeTime; +G_EXTERN int32_t g_freezerSelfDamage; +G_EXTERN int32_t g_gameQuit; +G_EXTERN int32_t g_globalRandom; +G_EXTERN int32_t g_impactDamage; +G_EXTERN int32_t g_labelCnt; +G_EXTERN int32_t g_maxPlayerHealth; +G_EXTERN int32_t g_mirrorCount; +G_EXTERN int32_t g_mostConcurrentPlayers; +G_EXTERN int32_t g_musicSize; +G_EXTERN int32_t g_playerSpawnCnt; +G_EXTERN int32_t g_scriptDebug; +G_EXTERN int32_t g_showShareware; +G_EXTERN int32_t g_spriteDeleteQueuePos; +G_EXTERN int32_t g_startArmorAmount; +G_EXTERN int32_t g_tripbombLaserMode; +G_EXTERN int32_t screenpeek; + +G_EXTERN int16_t g_animateSect[MAXANIMATES]; +G_EXTERN int32_t *g_animatePtr[MAXANIMATES]; +G_EXTERN int32_t g_animateGoal[MAXANIMATES]; +G_EXTERN int32_t g_animateVel[MAXANIMATES]; + +G_EXTERN int16_t g_cloudSect[256]; +G_EXTERN int16_t g_cloudX; +G_EXTERN int16_t g_cloudY; +G_EXTERN int32_t g_cloudClock; + +G_EXTERN int16_t SpriteDeletionQueue[1024]; +G_EXTERN int16_t g_cyclers[MAXCYCLERS][6]; +G_EXTERN int16_t g_mirrorSector[64]; +G_EXTERN int16_t g_mirrorWall[64]; +G_EXTERN int32_t *labelcode; +G_EXTERN int32_t *labeltype; +G_EXTERN int32_t lockclock; +G_EXTERN int32_t ototalclock; + +G_EXTERN int32_t g_wupass; +G_EXTERN int32_t g_chickenPlant; +G_EXTERN int32_t g_thunderOn; +G_EXTERN int32_t g_ufoSpawn; +G_EXTERN int32_t g_ufoCnt; +G_EXTERN int32_t g_hulkSpawn; +G_EXTERN int32_t g_vixenLevel; +G_EXTERN int32_t g_lastLevel; +G_EXTERN int32_t g_turdLevel; + +G_EXTERN int32_t g_mineCartDir[MAXMINECARTS]; +G_EXTERN int32_t g_mineCartSpeed[MAXMINECARTS]; +G_EXTERN int32_t g_mineCartChildSect[MAXMINECARTS]; +G_EXTERN int32_t g_mineCartSound[MAXMINECARTS]; +G_EXTERN int32_t g_mineCartDist[MAXMINECARTS]; +G_EXTERN int32_t g_mineCartDrag[MAXMINECARTS]; +G_EXTERN int32_t g_mineCartOpen[MAXMINECARTS]; +G_EXTERN int32_t g_mineCartSect[MAXMINECARTS]; +G_EXTERN uint32_t g_mineCartCnt; + +G_EXTERN int32_t g_jailDoorSound[MAXJAILDOORS]; +G_EXTERN int32_t g_jailDoorDrag[MAXJAILDOORS]; +G_EXTERN int32_t g_jailDoorSpeed[MAXJAILDOORS]; +G_EXTERN int32_t g_jailDoorSecHitag[MAXJAILDOORS]; +G_EXTERN int32_t g_jailDoorDist[MAXJAILDOORS]; +G_EXTERN int32_t g_jailDoorDir[MAXJAILDOORS]; +G_EXTERN int32_t g_jailDoorOpen[MAXJAILDOORS]; +G_EXTERN int32_t g_jailDoorSect[MAXJAILDOORS]; +G_EXTERN uint32_t g_jailDoorCnt; + +G_EXTERN int32_t g_lightninSector[MAXLIGHTNINSECTORS]; +G_EXTERN int32_t g_lightninSectorShade[MAXLIGHTNINSECTORS]; +G_EXTERN uint32_t g_lightninCnt; + +G_EXTERN int32_t g_torchSector[MAXTORCHSECTORS]; +G_EXTERN int32_t g_torchSectorShade[MAXTORCHSECTORS]; +G_EXTERN int32_t g_torchType[MAXTORCHSECTORS]; +G_EXTERN uint32_t g_torchCnt; + +G_EXTERN int32_t g_geoSectorWarp[MAXGEOSECTORS]; +G_EXTERN int32_t g_geoSectorWarp2[MAXGEOSECTORS]; +G_EXTERN int32_t g_geoSector[MAXGEOSECTORS]; +G_EXTERN int32_t g_geoSectorX[MAXGEOSECTORS]; +G_EXTERN int32_t g_geoSectorY[MAXGEOSECTORS]; +G_EXTERN int32_t g_geoSectorX2[MAXGEOSECTORS]; +G_EXTERN int32_t g_geoSectorY2[MAXGEOSECTORS]; +G_EXTERN uint32_t g_geoSectorCnt; + +G_EXTERN int32_t g_thunderFlash; +G_EXTERN int32_t g_thunderTime; +G_EXTERN int32_t g_winderFlash; +G_EXTERN int32_t g_winderTime; +G_EXTERN int32_t g_brightness; + +G_EXTERN int16_t g_ambientLotag[64]; +G_EXTERN int16_t g_ambientHitag[64]; +G_EXTERN uint32_t g_ambientCnt; + +G_EXTERN intptr_t *apScript; +G_EXTERN intptr_t *g_scriptPtr; + +G_EXTERN map_t g_mapInfo[(MAXVOLUMES + 1) * MAXLEVELS]; // +1 volume for "intro", "briefing" and "loading" music +G_EXTERN vec2_t g_origins[MAXANIMPOINTS]; + +G_EXTERN int32_t g_windTime, g_windDir; +G_EXTERN int16_t g_fakeBubbaCnt, g_mamaSpawnCnt, g_banjoSong, g_bellTime, g_bellSprite; +G_EXTERN uint8_t g_spriteExtra[MAXSPRITES], g_sectorExtra[MAXSECTORS]; +G_EXTERN uint8_t g_changeEnemySize, g_slotWin, g_ufoSpawnMinion, g_pistonSound, g_chickenWeaponTimer, g_RAendLevel, g_RAendEpisode, g_fogType; +G_EXTERN int32_t g_cdTrack; + +// XXX: I think this pragma pack is meaningless here. +// MSDN (https://msdn.microsoft.com/en-us/library/2e70t5y1%28VS.80%29.aspx) says: +// "pack takes effect at the first struct, union, or class declaration after +// the pragma is seen; pack has no effect on definitions." +#pragma pack(push,1) +#ifdef global_c_ +static playerdata_t g_player_s[1 + MAXPLAYERS]; +playerdata_t *const g_player = &g_player_s[1]; +#else +extern playerdata_t *const g_player; +#endif +G_EXTERN playerspawn_t g_playerSpawnPoints[MAXPLAYERS]; +G_EXTERN input_t inputfifo[MOVEFIFOSIZ][MAXPLAYERS]; +#pragma pack(pop) + +G_EXTERN char g_soundlocks[MAXSOUNDS]; +G_EXTERN int32_t g_noEnemies; +G_EXTERN int32_t g_restorePalette; +G_EXTERN int32_t g_screenCapture; +G_EXTERN sound_t g_sounds[MAXSOUNDS]; +G_EXTERN uint32_t everyothertime; +G_EXTERN uint32_t g_moveThingsCount; +G_EXTERN uint32_t g_gameUpdateTime; +G_EXTERN uint32_t g_gameUpdateAndDrawTime; +#define GAMEUPDATEAVGTIMENUMSAMPLES 100 +extern float g_gameUpdateAvgTime; + +#ifndef global_c_ +extern char CheatKeys[2]; +extern char g_gametypeNames[MAXGAMETYPES][33]; +extern char g_setupFileName[BMAX_PATH]; +extern char g_skillNames[MAXSKILLS][33]; +extern char g_volumeNames[MAXVOLUMES][33]; + +extern int32_t g_actorRespawnTime; +extern int32_t g_bouncemineRadius; +extern int32_t g_deleteQueueSize; +extern int32_t g_gametypeCnt; +extern int32_t g_itemRespawnTime; +extern int32_t g_morterRadius; +extern int32_t g_numFreezeBounces; +extern int32_t g_pipebombRadius; +extern int32_t g_playerFriction; +extern int32_t g_rpgRadius; +extern int32_t g_scriptSize; +extern int32_t g_seenineRadius; +extern int32_t g_shrinkerRadius; +extern int32_t g_spriteGravity; +extern int32_t g_timerTicsPerSecond; +extern int32_t g_tripbombRadius; +extern int32_t g_volumeCnt; + +extern int16_t g_blimpSpawnItems[15]; +extern int32_t g_gametypeFlags[MAXGAMETYPES]; +extern int32_t g_volumeFlags[MAXVOLUMES]; + +extern const char *s_buildDate; +#endif + +enum +{ + EF_HIDEFROMSP = 1<<0, + // EF_HIDEFROMMP = 1<<1, +}; + +EXTERN_INLINE_HEADER void G_UpdateInterpolations(void); +EXTERN_INLINE_HEADER void G_RestoreInterpolations(void); + +#ifdef __cplusplus +} +#endif + +#if defined global_c_ || !defined DISABLE_INLINING + +EXTERN_INLINE void G_UpdateInterpolations(void) //Stick at beginning of G_DoMoveThings +{ + for (bssize_t i=g_interpolationCnt-1; i>=0; i--) oldipos[i] = *curipos[i]; +} + +EXTERN_INLINE void G_RestoreInterpolations(void) //Stick at end of drawscreen +{ + int32_t i=g_interpolationCnt-1; + + if (--g_interpolationLock) + return; + + for (; i>=0; i--) *curipos[i] = bakipos[i]; +} + +#endif + +#endif diff --git a/source/rr/src/grpscan.cpp b/source/rr/src/grpscan.cpp new file mode 100644 index 000000000..73d416921 --- /dev/null +++ b/source/rr/src/grpscan.cpp @@ -0,0 +1,537 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "compat.h" +#include "baselayer.h" + +#include "scriptfile.h" +#include "cache1d.h" +#include "crc32.h" + +#include "duke3d.h" +#include "common_game.h" +#include "grpscan.h" + +//static void process_vaca13(int32_t crcval); +static void process_vacapp15(int32_t crcval); + +// custom GRP support for the startup window, file format reflects the structure below +#define GAMELISTFILE "games.list" +// name crc size flags dependency scriptname postprocessing +static internalgrpinfo_t const internalgrpfiles[] = +{ + //{ "Duke Nukem 3D", DUKE13_CRC, 26524524, GAMEFLAG_DUKE, 0, NULL, NULL}, + //{ "Duke Nukem 3D (South Korean Censored)", DUKEKR_CRC, 26385383, GAMEFLAG_DUKE, 0, NULL, NULL}, + { "Duke Nukem 3D: Atomic Edition", DUKE15_CRC, 44356548, GAMEFLAG_DUKE, 0, NULL, NULL}, + { "Duke Nukem 3D: Atomic Edition (WT)", DUKEWT_CRC, 44356548, GAMEFLAG_DUKE, 0, NULL, NULL}, + { "Duke Nukem 3D: Plutonium Pak", DUKEPP_CRC, 44348015, GAMEFLAG_DUKE, 0, NULL, NULL}, + //{ "Duke Nukem 3D Shareware 0.99", DUKE099_CRC, 9690241, GAMEFLAG_DUKE|GAMEFLAG_DUKEBETA, 0, NULL, NULL}, + //{ "Duke Nukem 3D Shareware 1.0", DUKE10_CRC, 10429258, GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE, 0, NULL, NULL}, + //{ "Duke Nukem 3D Shareware 1.1", DUKE11_CRC, 10442980, GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE, 0, NULL, NULL}, + //{ "Duke Nukem 3D Shareware 1.3D", DUKESW_CRC, 11035779, GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE, 0, NULL, NULL}, + //{ "Duke Nukem 3D Mac Demo", DUKEMD_CRC, 10444391, GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE, 0, NULL, NULL}, + //{ "Duke Nukem 3D MacUser Demo", DUKEMD2_CRC, 10628573, GAMEFLAG_DUKE|GAMEFLAG_SHAREWARE, 0, NULL, NULL }, + //{ "Duke it out in D.C. (1.3D)", DUKEDC13_CRC, 7926624, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE13_CRC, NULL, NULL}, + { "Duke it out in D.C.", DUKEDCPP_CRC, 8225517, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, NULL, NULL}, + { "Duke it out in D.C.", DUKEDC_CRC, 8410183, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, NULL, NULL}, + { "Duke it out in D.C.", (int32_t) 0x39A692BF, 8410187, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "DUKEDC.CON", NULL}, + //{ "Duke Caribbean: Life's a Beach (1.3D)", VACA13_CRC, 23559381, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE13_CRC, NULL, process_vaca13}, + { "Duke Caribbean: Life's a Beach (PPak)", VACAPP_CRC, 22551333, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKEPP_CRC, NULL, process_vacapp15}, + { "Duke Caribbean: Life's a Beach", VACA15_CRC, 22521880, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, NULL, process_vacapp15}, + { "Duke Caribbean: Life's a Beach", DUKECB_CRC, 22213819, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, NULL, NULL}, + { "Duke Caribbean: Life's a Beach", (int32_t) 0x65B5F690, 22397273, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "VACATION.CON", NULL}, + { "Duke: Nuclear Winter", DUKENW_CRC, 16169365, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "NWINTER.CON", NULL}, + { "Duke: Nuclear Winter Demo", (int32_t) 0xC7EFBFA9, 10965909, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "NWINTER.CON", NULL}, + // { "Duke!ZONE II (1.3D)", DZ2_13_CRC, 26135388, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE13_CRC, "DZ-GAME.CON", NULL}, + { "Duke!ZONE II", DZ2_PP_CRC, 44100411, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "DZ-GAME.CON", NULL}, + { "Duke!ZONE II", (int32_t) 0x1E9516F1, 3186656, GAMEFLAG_DUKE|GAMEFLAG_ADDON, DUKE15_CRC, "DZ-GAME.CON", NULL}, + //{ "NAM", NAM_CRC, 43448927, GAMEFLAG_NAM, 0, NULL, NULL}, + //{ "NAPALM", NAPALM_CRC, 44365728, GAMEFLAG_NAM|GAMEFLAG_NAPALM, 0, NULL, NULL}, + //{ "WWII GI", WW2GI_CRC, 77939508, GAMEFLAG_WW2GI, 0, NULL, NULL}, + //{ "Platoon Leader", PLATOONL_CRC, 37852572, GAMEFLAG_WW2GI|GAMEFLAG_ADDON, WW2GI_CRC, "PLATOONL.DEF", NULL}, + { "Redneck Rampage", RR_CRC, 141174222, GAMEFLAG_RR, 0, NULL, NULL }, + { "Redneck Rampage: Rides Again", RRRA_CRC, 191798609, GAMEFLAG_RR|GAMEFLAG_RRRA, 0, NULL, NULL }, +}; + +struct grpfile_t *foundgrps = NULL; +struct grpinfo_t *listgrps = NULL; + +static void LoadList(const char * filename) +{ + scriptfile *script = scriptfile_fromfile(filename); + + if (!script) + return; + + scriptfile_addsymbolvalue("GAMEFLAG_DUKE", GAMEFLAG_DUKE); + scriptfile_addsymbolvalue("GAMEFLAG_ADDON", GAMEFLAG_DUKE|GAMEFLAG_ADDON); + scriptfile_addsymbolvalue("GAMEFLAG_RR", GAMEFLAG_RR); + scriptfile_addsymbolvalue("GAMEFLAG_RRRA", GAMEFLAG_RR|GAMEFLAG_RRRA); + scriptfile_addsymbolvalue("DUKE15_CRC", DUKE15_CRC); + scriptfile_addsymbolvalue("DUKEPP_CRC", DUKEPP_CRC); + scriptfile_addsymbolvalue("DUKE13_CRC", DUKE13_CRC); + scriptfile_addsymbolvalue("DUKEDC13_CRC", DUKEDC13_CRC); + scriptfile_addsymbolvalue("DUKEDCPP_CRC", DUKEDCPP_CRC); + scriptfile_addsymbolvalue("DUKEDC_CRC", DUKEDC_CRC); + scriptfile_addsymbolvalue("VACA13_CRC", VACA13_CRC); + scriptfile_addsymbolvalue("VACAPP_CRC", VACAPP_CRC); + scriptfile_addsymbolvalue("VACA15_CRC", VACA15_CRC); + scriptfile_addsymbolvalue("DUKECB_CRC", DUKECB_CRC); + scriptfile_addsymbolvalue("DUKENW_CRC", DUKENW_CRC); + scriptfile_addsymbolvalue("DZ2_13_CRC", DZ2_13_CRC); + scriptfile_addsymbolvalue("DZ2_PP_CRC", DZ2_PP_CRC); + scriptfile_addsymbolvalue("NAM_CRC", NAM_CRC); + scriptfile_addsymbolvalue("NAPALM_CRC", NAPALM_CRC); + scriptfile_addsymbolvalue("WW2GI_CRC", WW2GI_CRC); + + while (!scriptfile_eof(script)) + { + enum + { + T_GRPINFO, + T_GAMENAME, + T_CRC, + T_SIZE, + T_DEPCRC, + T_SCRIPTNAME, + T_DEFNAME, + T_FLAGS, + T_RTSNAME, + }; + + static const tokenlist profiletokens[] = + { + { "grpinfo", T_GRPINFO }, + }; + + int32_t token = getatoken(script,profiletokens,ARRAY_SIZE(profiletokens)); + switch (token) + { + case T_GRPINFO: + { + int32_t gsize = 0, gcrcval = 0, gflags = GAMEFLAG_DUKE, gdepcrc = DUKE15_CRC; + char *gname = NULL, *gscript = NULL, *gdef = NULL; + char *grts = NULL; + char *grpend = NULL; + + static const tokenlist grpinfotokens[] = + { + { "name", T_GAMENAME }, + { "scriptname", T_SCRIPTNAME }, + { "defname", T_DEFNAME }, + { "rtsname", T_RTSNAME }, + { "crc", T_CRC }, + { "dependency", T_DEPCRC }, + { "size", T_SIZE }, + { "flags", T_FLAGS }, + + }; + + if (scriptfile_getbraces(script,&grpend)) break; + + while (script->textptr < grpend) + { + int32_t token = getatoken(script,grpinfotokens,ARRAY_SIZE(grpinfotokens)); + + switch (token) + { + case T_GAMENAME: + scriptfile_getstring(script,&gname); break; + case T_SCRIPTNAME: + scriptfile_getstring(script,&gscript); break; + case T_DEFNAME: + scriptfile_getstring(script,&gdef); break; + case T_RTSNAME: + scriptfile_getstring(script,&grts); break; + + case T_FLAGS: + scriptfile_getsymbol(script,&gflags); gflags &= GAMEFLAGMASK; break; + case T_DEPCRC: + scriptfile_getsymbol(script,&gdepcrc); break; + case T_CRC: + scriptfile_getsymbol(script,&gcrcval); break; + case T_SIZE: + scriptfile_getnumber(script,&gsize); break; + default: + break; + } + + grpinfo_t * const fg = (grpinfo_t *)Xcalloc(1, sizeof(grpinfo_t)); + fg->next = listgrps; + listgrps = fg; + + if (gname) + fg->name = Xstrdup(gname); + + fg->size = gsize; + fg->crcval = gcrcval; + fg->dependency = gdepcrc; + fg->game = gflags; + + if (gscript) + fg->scriptname = dup_filename(gscript); + + if (gdef) + fg->defname = dup_filename(gdef); + + if (grts) + fg->rtsname = dup_filename(grts); + } + break; + } + + default: + break; + } + } + + scriptfile_close(script); + scriptfile_clearsymbols(); +} + +static void LoadGameList(void) +{ + for (size_t i = 0; i < ARRAY_SIZE(internalgrpfiles); i++) + { + grpinfo_t * const fg = (grpinfo_t *)Xcalloc(1, sizeof(grpinfo_t)); + + fg->name = Xstrdup(internalgrpfiles[i].name); + fg->crcval = internalgrpfiles[i].crcval; + fg->size = internalgrpfiles[i].size; + fg->game = internalgrpfiles[i].game; + fg->dependency = internalgrpfiles[i].dependency; + + if (internalgrpfiles[i].scriptname) + fg->scriptname = dup_filename(internalgrpfiles[i].scriptname); + + fg->postprocessing = internalgrpfiles[i].postprocessing; + + fg->next = listgrps; + listgrps = fg; + } + + CACHE1D_FIND_REC * const srch = klistpath("/", "*.grpinfo", CACHE1D_FIND_FILE); + + for (CACHE1D_FIND_REC *sidx = srch; sidx; sidx = sidx->next) + LoadList(sidx->name); + + klistfree(srch); +} + +static void FreeGameList(void) +{ + while (listgrps) + { + Bfree(listgrps->name); + Bfree(listgrps->scriptname); + Bfree(listgrps->defname); + Bfree(listgrps->rtsname); + + grpinfo_t * const fg = listgrps->next; + Bfree(listgrps); + listgrps = fg; + } +} + + +#define GRPCACHEFILE "grpfiles.cache" +static struct grpcache +{ + struct grpcache *next; + int32_t size; + int32_t mtime; + int32_t crcval; + char name[BMAX_PATH]; +} +*grpcache = NULL, *usedgrpcache = NULL; + +static int32_t LoadGroupsCache(void) +{ + struct grpcache *fg; + + int32_t fsize, fmtime, fcrcval; + char *fname; + + scriptfile *script; + + script = scriptfile_fromfile(GRPCACHEFILE); + if (!script) return -1; + + while (!scriptfile_eof(script)) + { + if (scriptfile_getstring(script, &fname)) break; // filename + if (scriptfile_getnumber(script, &fsize)) break; // filesize + if (scriptfile_getnumber(script, &fmtime)) break; // modification time + if (scriptfile_getnumber(script, &fcrcval)) break; // crc checksum + + fg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache)); + fg->next = grpcache; + grpcache = fg; + + Bstrncpy(fg->name, fname, BMAX_PATH); + fg->size = fsize; + fg->mtime = fmtime; + fg->crcval = fcrcval; + } + + scriptfile_close(script); + return 0; +} + +static void FreeGroupsCache(void) +{ + while (grpcache) + { + struct grpcache * const fg = grpcache->next; + Bfree(grpcache); + grpcache = fg; + } +} + +static void RemoveGroup(grpfile_t *igrp) +{ + for (grpfile_t *prev = NULL, *grp = foundgrps; grp; grp=grp->next) + { + if (grp == igrp) + { + if (grp == foundgrps) + foundgrps = grp->next; + else + prev->next = grp->next; + + Bfree((char *)grp->filename); + Bfree(grp); + + return; + } + + prev = grp; + } +} + +grpfile_t * FindGroup(int32_t crcval) +{ + grpfile_t *grp; + + for (grp = foundgrps; grp; grp=grp->next) + { + if (grp->type->crcval == crcval) + return grp; + } + + return NULL; +} + +static grpinfo_t const * FindGrpInfo(int32_t crcval, int32_t size) +{ + grpinfo_t *grpinfo; + + for (grpinfo = listgrps; grpinfo; grpinfo=grpinfo->next) + { + if (grpinfo->crcval == crcval && grpinfo->size == size) + return grpinfo; + } + + return NULL; +} + +static void ProcessGroups(CACHE1D_FIND_REC *srch) +{ + CACHE1D_FIND_REC *sidx; + struct grpcache *fg, *fgg; + char *fn; + struct Bstat st; + +#define BUFFER_SIZE (1024 * 1024 * 8) + uint8_t *buf = (uint8_t *)Xmalloc(BUFFER_SIZE); + + for (sidx = srch; sidx; sidx = sidx->next) + { + for (fg = grpcache; fg; fg = fg->next) + { + if (!Bstrcmp(fg->name, sidx->name)) break; + } + + if (fg) + { + if (findfrompath(sidx->name, &fn)) continue; // failed to resolve the filename + if (Bstat(fn, &st)) + { + Bfree(fn); + continue; + } // failed to stat the file + Bfree(fn); + if (fg->size == (int32_t)st.st_size && fg->mtime == (int32_t)st.st_mtime) + { + grpinfo_t const * const grptype = FindGrpInfo(fg->crcval, fg->size); + if (grptype) + { + grpfile_t * const grp = (grpfile_t *)Xcalloc(1, sizeof(grpfile_t)); + grp->filename = Xstrdup(sidx->name); + grp->type = grptype; + grp->next = foundgrps; + foundgrps = grp; + } + + fgg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache)); + strcpy(fgg->name, fg->name); + fgg->size = fg->size; + fgg->mtime = fg->mtime; + fgg->crcval = fg->crcval; + fgg->next = usedgrpcache; + usedgrpcache = fgg; + continue; + } + } + + { + int32_t b, fh; + int32_t crcval = 0; + + fh = openfrompath(sidx->name, BO_RDONLY|BO_BINARY, BS_IREAD); + if (fh < 0) continue; + if (Bfstat(fh, &st)) continue; + + initprintf(" Checksumming %s...", sidx->name); + do + { + b = read(fh, buf, BUFFER_SIZE); + if (b > 0) crcval = Bcrc32((uint8_t *)buf, b, crcval); + } + while (b == BUFFER_SIZE); + close(fh); + initprintf(" Done\n"); + + grpinfo_t const * const grptype = FindGrpInfo(crcval, st.st_size); + if (grptype) + { + grpfile_t * const grp = (grpfile_t *)Xcalloc(1, sizeof(grpfile_t)); + grp->filename = Xstrdup(sidx->name); + grp->type = grptype; + grp->next = foundgrps; + foundgrps = grp; + } + + fgg = (struct grpcache *)Xcalloc(1, sizeof(struct grpcache)); + Bstrncpy(fgg->name, sidx->name, BMAX_PATH); + fgg->size = st.st_size; + fgg->mtime = st.st_mtime; + fgg->crcval = crcval; + fgg->next = usedgrpcache; + usedgrpcache = fgg; + } + } + + Bfree(buf); +} + +int32_t ScanGroups(void) +{ + struct grpcache *fg, *fgg; + + initprintf("Searching for game data...\n"); + + LoadGameList(); + LoadGroupsCache(); + + static char const *extensions[] = + { + "*.grp", + "*.ssi", + "*.dat", + }; + + for (char const *extension : extensions) + { + CACHE1D_FIND_REC *srch = klistpath("/", extension, CACHE1D_FIND_FILE); + ProcessGroups(srch); + klistfree(srch); + } + + FreeGroupsCache(); + + for (grpfile_t *grp = foundgrps; grp; grp=grp->next) + { + if (grp->type->dependency) + { + if (FindGroup(grp->type->dependency) == NULL) // couldn't find dependency + { + //initprintf("removing %s\n", grp->name); + RemoveGroup(grp); + grp = foundgrps; + // start from the beginning so we can remove anything that depended on this grp + continue; + } + } + } + + if (usedgrpcache) + { + int32_t i = 0; + FILE *fp; + fp = fopen(GRPCACHEFILE, "wt"); + if (fp) + { + for (fg = usedgrpcache; fg; fg=fgg) + { + fgg = fg->next; + fprintf(fp, "\"%s\" %d %d %d\n", fg->name, fg->size, fg->mtime, fg->crcval); + Bfree(fg); + i++; + } + fclose(fp); + } +// initprintf("Found %d recognized GRP %s.\n",i,i>1?"files":"file"); + + return 0; + } + + initprintf("Found no recognized game data!\n"); + + return 0; +} + + +void FreeGroups(void) +{ + while (foundgrps) + { + Bfree((char *)foundgrps->filename); + grpfile_t * const fg = foundgrps->next; + Bfree(foundgrps); + foundgrps = fg; + } + + FreeGameList(); +} + +static void process_vacapp15(int32_t crcval) +{ + krename(crcval, 5, "DEFS.CON"); + krename(crcval, 6, "GAME.CON"); + krename(crcval, 7, "USER.CON"); + krename(crcval, 8, "DEMO1.DMO"); + krename(crcval, 9, "DEMO2.DMO"); + krename(crcval, 10, "DEMO3.DMO"); + + initgroupfile("VACATION.PRG"); +} diff --git a/source/rr/src/grpscan.h b/source/rr/src/grpscan.h new file mode 100644 index 000000000..6f1a188b9 --- /dev/null +++ b/source/rr/src/grpscan.h @@ -0,0 +1,110 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef grpscan_h_ +#define grpscan_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAXLISTNAMELEN 32 + +// List of internally-known GRP files +#define DUKE13_CRC (int32_t)0xBBC9CE44 +#define DUKEKR_CRC (int32_t)0xAA4F6A40 +#define DUKE15_CRC (int32_t)0xFD3DCFF1 +#define DUKEPP_CRC (int32_t)0xF514A6AC +#define DUKEWT_CRC (int32_t)0x982AFE4A +#define DUKE099_CRC (int32_t)0x02F18900 +#define DUKE10_CRC (int32_t)0xA28AA589 +#define DUKE11_CRC (int32_t)0x912E1E8D +#define DUKESW_CRC (int32_t)0x983AD923 +#define DUKEMD_CRC (int32_t)0xC5F71561 +#define DUKEMD2_CRC (int32_t)0x73A15EE7 +#define DUKEDC13_CRC (int32_t)0xA9242158 +#define DUKEDCPP_CRC (int32_t)0xB79D997F +#define DUKEDC_CRC (int32_t)0xA8CF80DA +#define VACA13_CRC (int32_t)0x4A2DBB62 +#define VACAPP_CRC (int32_t)0x2F4FCCEE +#define VACA15_CRC (int32_t)0xB62B42FD +#define DUKECB_CRC (int32_t)0x18F01C5B +#define DUKENW_CRC (int32_t)0xF1CAE8E4 +#define DZ2_13_CRC (int32_t)0x82C1B47F +#define DZ2_PP_CRC (int32_t)0x7FB6117C +#define NAM_CRC (int32_t)0x75C1F07B +#define NAPALM_CRC (int32_t)0x3DE1589A +#define WW2GI_CRC (int32_t)0x907B82BF +#define PLATOONL_CRC (int32_t)0xD1ED8C0C +#define RR_CRC (int32_t)0x19D9BC79 +#define RRRA_CRC (int32_t)0x958018C6 + +enum addon_t { + ADDON_NONE, + ADDON_DUKEDC, + ADDON_NWINTER, + ADDON_CARIBBEAN, + NUMADDONS +}; + +typedef struct internalgrpinfo_t { + char const *name; + int32_t const crcval; + int32_t const size; + int32_t const game; + int32_t const dependency; + char const *scriptname; + void(*postprocessing)(int32_t); +} internalgrpinfo_t; + +typedef struct grpinfo_t { + char *name; + int32_t crcval; + int32_t size; + int32_t game; + int32_t dependency; + char *scriptname; + char *defname; + char *rtsname; + void (*postprocessing)(int32_t); + struct grpinfo_t *next; +} grpinfo_t; + +typedef struct grpfile_t { + char *filename; + struct grpinfo_t const *type; + struct grpfile_t *next; +} grpfile_t; + +extern grpfile_t *foundgrps; +extern grpinfo_t *listgrps; + +extern grpfile_t * FindGroup(int32_t crcval); + +int32_t ScanGroups(void); +void FreeGroups(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/source/rr/src/in_android.cpp b/source/rr/src/in_android.cpp new file mode 100644 index 000000000..b6c3fd863 --- /dev/null +++ b/source/rr/src/in_android.cpp @@ -0,0 +1,354 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2015 EDuke32 developers and contributors +Copyright (C) 2015 Voidpoint, LLC + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "compat.h" +#include "sdl_inc.h" +#include "baselayer.h" +#include "keys.h" +#include "duke3d.h" +#include "common_game.h" +#include "osd.h" +#include "player.h" +#include "game.h" +#include "build.h" +#include "anim.h" +#include "player.h" + +#include "keyboard.h" +#include "control.h" +#include "_control.h" + +#include "menus.h" + +#ifdef __cplusplus +extern "C" { +#endif +extern int SDL_SendKeyboardKey(Uint8 state, SDL_Scancode scancode); +extern int SDL_SendKeyboardText(const char *text); +extern int SDL_SendMouseMotion(SDL_Window * window, Uint32 mouseID, int relative, int x, int y); +extern int SDL_SendMouseButton(SDL_Window * window, Uint32 mouseID, Uint8 state, Uint8 button); + +#ifdef __cplusplus +} +#endif + +#include "in_android.h" +#include + +#if defined __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO,"DUKE", __VA_ARGS__)) + +static char sdl_text[2]; + +droidinput_t droidinput; + +void AndroidTimer(int tics) { G_InitTimer(tics); } + +int AndroidKeyEvent(int state, int code,int unicode) +{ + LOGI("AndroidKeyEvent %d %d %d",state,(SDL_Scancode)code,unicode); + SDL_SendKeyboardKey(state ? SDL_PRESSED : SDL_RELEASED, (SDL_Scancode)code); + SDL_EventState(SDL_TEXTINPUT, SDL_ENABLE); + + // if (code == 42) + // unicode = 42; + + if (state) + { + //if (unicode < 128) + { + sdl_text[0] = unicode; + sdl_text[1] = 0; + + int posted = SDL_SendKeyboardText((const char*)sdl_text); + LOGI("posted = %d",posted); + } + + if (state == 2) + AndroidKeyEvent(0, code, unicode); + } + + return 0; +} + +void AndroidMouseMenu(float x,float y) +{ + SDL_SendMouseMotion(NULL,0,0,x,y); +} + +void AndroidMouseMenuButton(int state,int button) +{ + SDL_SendMouseButton(NULL, SDL_TOUCH_MOUSEID, state?SDL_PRESSED:SDL_RELEASED, SDL_BUTTON_LEFT); +} + +void changeActionState(int state, int action) +{ + if (state) + { + //BUTTONSET(action,1); + droidinput.functionSticky |= ((uint64_t)1<<((uint64_t)(action))); + droidinput.functionHeld |= ((uint64_t)1<<((uint64_t)(action))); + return; + } + + //BUTTONCLEAR(action); + droidinput.functionHeld &= ~((uint64_t) 1<<((uint64_t) (action))); +} + +void AndroidAction(int state, int action) +{ + LOGI("AndroidAction action = %d, state = %d", action, state); + + if (action >= MENU_UP && action <= MENU_BACK) + { + int const sdl_code [] = { SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, + SDL_SCANCODE_RIGHT, SDL_SCANCODE_RETURN, SDL_SCANCODE_ESCAPE }; + AndroidKeyEvent(state, sdl_code[action-MENU_UP], 0); + return; + } + else + { + //if (AndroidRead(READ_SCREEN_MODE) != TOUCH_SCREEN_GAME) //If not in game don't do any of this + /// return; + + //Special toggle for crouch, NOT when using jetpack or in water + if (droidinput.toggleCrouch) + { + int lotag = sector[g_player[myconnectindex].ps->cursectnum].lotag; + + if (droidinput.crouchToggleState && (lotag == ST_2_UNDERWATER || lotag == ST_1_ABOVE_WATER)) + { + droidinput.crouchToggleState = 0; + if (action == gamefunc_Crouch) + state = 0; + else AndroidAction(0, gamefunc_Crouch); + } + + if (action == gamefunc_Crouch) + { + if (!g_player[myconnectindex].ps->jetpack_on && g_player[myconnectindex].ps->on_ground && + lotag != ST_2_UNDERWATER && lotag != ST_1_ABOVE_WATER) + { + if (state) + droidinput.crouchToggleState = !droidinput.crouchToggleState; + + state = droidinput.crouchToggleState; + } + } + + } + + //Check if jumping while crouched + if (action == gamefunc_Jump) + { + if (droidinput.crouchToggleState) + { + droidinput.crouchToggleState = 0; + changeActionState(0, gamefunc_Crouch); + } + else + changeActionState(state, action); + } + else + changeActionState(state, action); + + if (state == 2) + AndroidAction(0, action); + + // LOGI("AndroidAction state = 0x%016llX", CONTROL_ButtonState); + } +} + +int const deadRegion = 0.3; + +float analogCalibrate(float v) +{ + float rv = 0; + + if (v > deadRegion) rv = (v - deadRegion) * (v - deadRegion); + else if (-v > deadRegion) rv = -(-v - deadRegion) * (-v - deadRegion); + + return rv; +} + +//Need these NAN check as not cumulative. +void AndroidMove(float fwd, float strafe) +{ + if (!ud.auto_run) + { + fwd *= 0.5f; + strafe *= 0.5f; + } + + if (!isnan(fwd)) + droidinput.forwardmove = fclamp2(analogCalibrate(fwd), -1.f, 1.f); + + if (!isnan(strafe)) + droidinput.sidemove = fclamp2(analogCalibrate(strafe), -1.f, 1.f); +} + +void AndroidLook(float yaw, float pitch) +{ + droidinput.pitch += pitch; + droidinput.yaw += yaw; +} + +void AndroidLookJoystick(float yaw, float pitch) +{ + if (!isnan(pitch)) + droidinput.pitch_joystick = analogCalibrate(pitch); + + if (!isnan(yaw)) + droidinput.yaw_joystick = analogCalibrate(yaw); +} + +void AndroidOSD(const char * cmd) +{ + OSD_Dispatch(cmd); +} + +int consoleShown = 0; +void AndroidSetConsoleShown(int onf) +{ + consoleShown = onf; +} + +extern int inExtraScreens; //In game.c +int32_t AndroidRead(portableread_t r) +{ + int32_t rv; + + switch (r) + { + case R_TOUCH_MODE: + if (g_animPtr || inExtraScreens) + rv = TOUCH_SCREEN_BLANK_TAP; + else if (consoleShown) + rv = TOUCH_SCREEN_CONSOLE; + else if ((g_player[myconnectindex].ps->gm & MODE_MENU) == MODE_MENU && g_currentMenu != MENU_MAIN) + rv = (m_currentMenu->type == Verify && totalclock > (m_animation.length + m_animation.start)) ? TOUCH_SCREEN_YES_NO : TOUCH_SCREEN_MENU; + else if ((g_player[myconnectindex].ps->gm & MODE_MENU) == MODE_MENU && g_currentMenu == MENU_MAIN) + rv = TOUCH_SCREEN_MENU_NOBACK; +/* + else if (ud.overhead_on == 2) + rv = TOUCH_SCREEN_AUTOMAP; +*/ + else if ((g_player[myconnectindex].ps->gm & MODE_GAME)) + if (AndroidRead(R_PLAYER_DEAD_FLAG)) + rv = TOUCH_SCREEN_BLANK_TAP; + else + rv = TOUCH_SCREEN_GAME; + else + rv = TOUCH_SCREEN_BLANK_TAP; + break; + case R_PLAYER_GOTWEAPON: + rv = g_player[myconnectindex].ps->gotweapon; break; + case R_UD_OVERHEAD_ON: + rv = 0; break;//ud.overhead_on != 0; break;// ud.overhead_on ranges from 0-2 + case R_UD_SCROLLMODE: + rv = ud.scrollmode; break; + case R_PLAYER_LASTWEAPON: + rv = droidinput.lastWeapon; + if ((unsigned)rv < MAX_WEAPONS && !g_player[myconnectindex].ps->ammo_amount[rv]) + rv = -1; + break; + case R_GAME_PAUSED: + rv = ud.pause_on != 0; break; + case R_PLAYER_DEAD_FLAG: + rv = g_player[myconnectindex].ps->dead_flag; break; + case R_PLAYER_INV_AMOUNT: + rv = 0; + for (bssize_t i = 0; i < GET_MAX; i++) + { + if (g_player[myconnectindex].ps->inv_amount[i]) + rv += (1 << i); + } + break; + case R_SOMETHINGONPLAYER: + rv = g_player[myconnectindex].ps->somethingonplayer != -1; + break; + default: + rv = 0; break; + } + + return rv; +} + +static float map_zoom,map_dx,map_dy = 0; + +void AndroidAutomapControl(float zoom,float dx,float dy) +{ + map_zoom += zoom; + map_dx += dx; + map_dy += dy; +} + +///This stuff is called from the game/engine + +extern void CONTROL_Android_ScrollMap(int32_t *angle,int32_t *x, int32_t *y, uint16_t *zoom ) +{ + + *x += ((int)(map_dx * -30000)*sintable[(512+2048-*angle)&2047])>>14; + *y += ((int)(map_dy * -30000)*sintable[(512+1024-512-*angle)&2047])>>14; + +// *zoom += map_zoom * 2000; + //*angle = 0; + map_dx = map_dy = map_zoom = 0; +} + +void CONTROL_Android_SetLastWeapon(int w) +{ + droidinput.lastWeapon = w; +} + +void CONTROL_Android_ClearButton(int32_t whichbutton) +{ + droidinput.functionHeld &= ~((uint64_t)1<<((uint64_t)(whichbutton))); +} + +void CONTROL_Android_PollDevices(ControlInfo *info) +{ + //LOGI("CONTROL_Android_PollDevices %f %f",forwardmove,sidemove); + //LOGI("CONTROL_Android_PollDevices %f %f",droidinput.pitch,droidinput.yaw); + + info->dz = (int32_t)nearbyintf(-droidinput.forwardmove * ANDROIDMOVEFACTOR); + info->dx = (int32_t)nearbyintf(droidinput.sidemove * ANDROIDMOVEFACTOR) >> 5; + info->dpitch = + (int32_t)nearbyintf(droidinput.pitch * ANDROIDLOOKFACTOR + droidinput.pitch_joystick * ANDROIDPITCHFACTORJOYSTICK); + info->dyaw = + (int32_t)nearbyintf(-droidinput.yaw * ANDROIDLOOKFACTOR - droidinput.yaw_joystick * ANDROIDYAWFACTORJOYSTICK); + + droidinput.pitch = droidinput.yaw = 0.f; + CONTROL_ButtonState = droidinput.functionSticky | droidinput.functionHeld; + droidinput.functionSticky = 0; + + //LOGI("poll state = 0x%016llX",CONTROL_ButtonState); +} + +#if defined __GNUC__ +# pragma GCC diagnostic pop +#endif diff --git a/source/rr/src/in_android.h b/source/rr/src/in_android.h new file mode 100644 index 000000000..6e04098f1 --- /dev/null +++ b/source/rr/src/in_android.h @@ -0,0 +1,120 @@ +#include "function.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define MENU_UP 0x200 +#define MENU_DOWN 0x201 +#define MENU_LEFT 0x202 +#define MENU_RIGHT 0x203 +#define MENU_SELECT 0x204 +#define MENU_BACK 0x205 + +#define KEY_QUICK_CMD 0x1005 +#define KEY_SHOW_KBRD 0x1008 +#define KEY_SHOW_INVEN 0x1009 +#define KEY_QUICK_SAVE 0x100A +#define KEY_QUICK_LOAD 0x100B + +#define KEY_QUICK_KEY1 0x1011 +#define KEY_QUICK_KEY2 0x1012 +#define KEY_QUICK_KEY3 0x1013 +#define KEY_QUICK_KEY4 0x1014 + +// #define BUTTONSET(x,value) (CONTROL_ButtonState |= ((uint64_t)value<<((uint64_t)(x)))) +// #define BUTTONCLEAR(x) (CONTROL_ButtonState &= ~((uint64_t)1<<((uint64_t)(x)))) + +#define PRECISIONSHOOTFACTOR 0.3f + +// where do these numbers come from? +#define ANDROIDMOVEFACTOR 6400 +#define ANDROIDLOOKFACTOR 1600 + +#define ANDROIDPITCHFACTORJOYSTICK 2000 +#define ANDROIDYAWFACTORJOYSTICK 4000 + +typedef enum { + R_TOUCH_MODE, + R_PLAYER_GOTWEAPON, + R_UD_OVERHEAD_ON, + R_UD_SCROLLMODE, + R_PLAYER_LASTWEAPON, + R_GAME_PAUSED, + R_PLAYER_DEAD_FLAG, + R_PLAYER_INV_AMOUNT, + R_SOMETHINGONPLAYER +} portableread_t; + + +typedef enum { + TOUCH_SCREEN_BLANK, //Nothing on screen (not used) + TOUCH_SCREEN_BLANK_TAP, //One button filling screen with no graphic, tap to send Enter key + TOUCH_SCREEN_YES_NO, //Yes/No buttons on screen, sends Enter or Esc + TOUCH_SCREEN_MENU, //Normal menu + TOUCH_SCREEN_MENU_NOBACK, // menu without back button + TOUCH_SCREEN_GAME, //Normal game screen + TOUCH_SCREEN_AUTOMAP, //When auto map is up (not used yet) + TOUCH_SCREEN_CONSOLE //When Console is up +} touchscreemode_t; + + +typedef struct +{ + int32_t crouchToggleState; + int32_t lastWeapon; + int32_t toggleCrouch; + int32_t quickSelectWeapon; + + uint64_t functionSticky; //To let at least one tick + uint64_t functionHeld; + + int32_t left_double_action; + int32_t right_double_action; + + int32_t invertLook, hideStick; + + double pitch, yaw; + double pitch_joystick, yaw_joystick; + float forwardmove, sidemove; + + // set by configuration UI + float strafe_sens, forward_sens; + float pitch_sens, yaw_sens; + + float gameControlsAlpha; +} droidinput_t; + +typedef struct +{ + int32_t audio_sample_rate; + int32_t audio_buffer_size; + uint16_t screen_width, screen_height; +} droidsysinfo_t; + +extern droidinput_t droidinput; +extern droidsysinfo_t droidinfo; + +void AndroidTimer(int tics); +int AndroidKeyEvent(int state, int code, int unicode); +int AndroidRead(portableread_t r); + +void AndroidAction(int state, int action); + +void AndroidMouseMenu(float x,float y); +void AndroidMouseMenuButton(int state,int button); + +void AndroidMove(float fwd, float strafe); +void AndroidLook(float yaw, float pitch); +void AndroidLookJoystick(float yaw, float pitch); +void AndroidOSD(const char * cmd); + +void AndroidAutomapControl(float zoom,float dx,float dy); + +void AndroidShowKeyboard(int onf); + +void AndroidToggleButtonEditor(void); +#ifdef __cplusplus +} +#endif diff --git a/source/rr/src/input.cpp b/source/rr/src/input.cpp new file mode 100644 index 000000000..3461fb740 --- /dev/null +++ b/source/rr/src/input.cpp @@ -0,0 +1,346 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#include "global.h" +#include "game.h" +#include "function.h" +#include "keyboard.h" +#include "mouse.h" +#include "joystick.h" +#include "control.h" +#include "input.h" +#include "menus.h" + +int32_t I_CheckAllInput(void) +{ + return ( +#if defined EDUKE32_IOS + g_mouseClickState == MOUSE_PRESSED || +#endif + KB_KeyWaiting() || + MOUSE_GetButtons() || + JOYSTICK_GetButtons() + ); +} +void I_ClearAllInput(void) +{ +#if defined EDUKE32_IOS + mouseAdvanceClickState(); +#endif + KB_FlushKeyboardQueue(); + KB_ClearKeysDown(); + MOUSE_ClearAllButtons(); + JOYSTICK_ClearAllButtons(); +} + + +int32_t I_AdvanceTrigger(void) +{ + return ( + KB_KeyPressed(sc_kpad_Enter) || + KB_KeyPressed(sc_Enter) || +#if !defined EDUKE32_TOUCH_DEVICES + MOUSEINACTIVECONDITIONAL(MOUSE_GetButtons()&LEFT_MOUSE) || +#endif +#if defined(GEKKO) + MOUSEINACTIVECONDITIONAL(JOYSTICK_GetButtons()&WII_A) +#else + BUTTON(gamefunc_Open) || +# if !defined EDUKE32_TOUCH_DEVICES + MOUSEINACTIVECONDITIONAL(BUTTON(gamefunc_Fire)) +# else + BUTTON(gamefunc_Fire) +# endif +#endif + ); +} + +void I_AdvanceTriggerClear(void) +{ + KB_FlushKeyboardQueue(); + KB_ClearKeyDown(sc_kpad_Enter); + KB_ClearKeyDown(sc_Enter); + MOUSE_ClearButton(LEFT_MOUSE); +#if defined(GEKKO) + JOYSTICK_ClearButton(WII_A); +#else + CONTROL_ClearButton(gamefunc_Open); + CONTROL_ClearButton(gamefunc_Fire); +#endif +} + +int32_t I_ReturnTrigger(void) +{ + return ( + KB_KeyPressed(sc_Escape) || + (MOUSE_GetButtons()&RIGHT_MOUSE) || + BUTTON(gamefunc_Crouch) +#if defined(GEKKO) + || (JOYSTICK_GetButtons()&(WII_B|WII_HOME)) +#endif + ); +} + +void I_ReturnTriggerClear(void) +{ + KB_FlushKeyboardQueue(); + KB_ClearKeyDown(sc_Escape); + MOUSE_ClearButton(RIGHT_MOUSE); + CONTROL_ClearButton(gamefunc_Crouch); +#if defined(GEKKO) + JOYSTICK_ClearButton(WII_B); + JOYSTICK_ClearButton(WII_HOME); +#endif +} + + +int32_t I_EscapeTrigger(void) +{ + return ( + KB_KeyPressed(sc_Escape) +#if defined(GEKKO) + || (JOYSTICK_GetButtons()&WII_HOME) +#endif + ); +} + +void I_EscapeTriggerClear(void) +{ + KB_FlushKeyboardQueue(); + KB_ClearKeyDown(sc_Escape); +#if defined(GEKKO) + JOYSTICK_ClearButton(WII_HOME); +#endif +} + + +int32_t I_MenuUp(void) +{ + return ( + KB_KeyPressed(sc_UpArrow) || + KB_KeyPressed(sc_kpad_8) || + (MOUSE_GetButtons()&WHEELUP_MOUSE) || + BUTTON(gamefunc_Move_Forward) || + (JOYSTICK_GetHat(0)&HAT_UP) + ); +} + +void I_MenuUpClear(void) +{ + KB_ClearKeyDown(sc_UpArrow); + KB_ClearKeyDown(sc_kpad_8); + MOUSE_ClearButton(WHEELUP_MOUSE); + CONTROL_ClearButton(gamefunc_Move_Forward); + JOYSTICK_ClearHat(0); +} + + +int32_t I_MenuDown(void) +{ + return ( + KB_KeyPressed(sc_DownArrow) || + KB_KeyPressed(sc_kpad_2) || + (MOUSE_GetButtons()&WHEELDOWN_MOUSE) || + BUTTON(gamefunc_Move_Backward) || + (JOYSTICK_GetHat(0)&HAT_DOWN) + ); +} + +void I_MenuDownClear(void) +{ + KB_ClearKeyDown(sc_DownArrow); + KB_ClearKeyDown(sc_kpad_2); + KB_ClearKeyDown(sc_PgDn); + MOUSE_ClearButton(WHEELDOWN_MOUSE); + CONTROL_ClearButton(gamefunc_Move_Backward); + JOYSTICK_ClearHat(0); +} + + +int32_t I_MenuLeft(void) +{ + return ( + KB_KeyPressed(sc_LeftArrow) || + KB_KeyPressed(sc_kpad_4) || + (SHIFTS_IS_PRESSED && KB_KeyPressed(sc_Tab)) || + BUTTON(gamefunc_Turn_Left) || + BUTTON(gamefunc_Strafe_Left) || + (JOYSTICK_GetHat(0)&HAT_LEFT) + ); +} + +void I_MenuLeftClear(void) +{ + KB_ClearKeyDown(sc_LeftArrow); + KB_ClearKeyDown(sc_kpad_4); + KB_ClearKeyDown(sc_Tab); + CONTROL_ClearButton(gamefunc_Turn_Left); + CONTROL_ClearButton(gamefunc_Strafe_Left); + JOYSTICK_ClearHat(0); +} + + +int32_t I_MenuRight(void) +{ + return ( + KB_KeyPressed(sc_RightArrow) || + KB_KeyPressed(sc_kpad_6) || + (!SHIFTS_IS_PRESSED && KB_KeyPressed(sc_Tab)) || + BUTTON(gamefunc_Turn_Right) || + BUTTON(gamefunc_Strafe_Right) || + (MOUSE_GetButtons()&MIDDLE_MOUSE) || + (JOYSTICK_GetHat(0)&HAT_RIGHT) + ); +} + +void I_MenuRightClear(void) +{ + KB_ClearKeyDown(sc_RightArrow); + KB_ClearKeyDown(sc_kpad_6); + KB_ClearKeyDown(sc_Tab); + CONTROL_ClearButton(gamefunc_Turn_Right); + CONTROL_ClearButton(gamefunc_Strafe_Right); + MOUSE_ClearButton(MIDDLE_MOUSE); + JOYSTICK_ClearHat(0); +} + + +int32_t I_PanelUp(void) +{ + return ( + KB_KeyPressed(sc_PgUp) || + I_MenuUp() || + I_MenuLeft() + ); +} + +void I_PanelUpClear(void) +{ + KB_ClearKeyDown(sc_PgUp); + I_MenuUpClear(); + I_MenuLeftClear(); +} + + +int32_t I_PanelDown(void) +{ + return ( + KB_KeyPressed(sc_PgDn) || + I_MenuDown() || + I_MenuRight() || + I_AdvanceTrigger() + ); +} + +void I_PanelDownClear(void) +{ + KB_ClearKeyDown(sc_PgDn); + I_MenuDownClear(); + I_MenuRightClear(); + I_AdvanceTriggerClear(); +} + + +int32_t I_SliderLeft(void) +{ + return ( +#if !defined EDUKE32_TOUCH_DEVICES + MOUSEINACTIVECONDITIONAL((MOUSE_GetButtons()&LEFT_MOUSE) && (MOUSE_GetButtons()&WHEELUP_MOUSE)) || +#endif + I_MenuLeft() + ); +} + +void I_SliderLeftClear(void) +{ + I_MenuLeftClear(); + MOUSE_ClearButton(WHEELUP_MOUSE); +} + + +int32_t I_SliderRight(void) +{ + return ( +#if !defined EDUKE32_TOUCH_DEVICES + MOUSEINACTIVECONDITIONAL((MOUSE_GetButtons()&LEFT_MOUSE) && (MOUSE_GetButtons()&WHEELDOWN_MOUSE)) || +#endif + I_MenuRight() + ); +} + +void I_SliderRightClear(void) +{ + I_MenuRightClear(); + MOUSE_ClearButton(WHEELDOWN_MOUSE); +} + + +int32_t I_EnterText(char *t, int32_t maxlength, int32_t flags) +{ + char ch; + int32_t inputloc = Bstrlen(typebuf); + + while ((ch = KB_GetCh()) != 0) + { + if (ch == asc_BackSpace) + { + if (inputloc > 0) + { + inputloc--; + *(t+inputloc) = 0; + } + } + else + { + if (ch == asc_Enter) + { + I_AdvanceTriggerClear(); + return 1; + } + else if (ch == asc_Escape) + { + I_ReturnTriggerClear(); + return -1; + } + else if (ch >= 32 && inputloc < maxlength && ch < 127) + { + if (!(flags & INPUT_NUMERIC) || (ch >= '0' && ch <= '9')) + { + // JBF 20040508: so we can have numeric only if we want + *(t+inputloc) = ch; + *(t+inputloc+1) = 0; + inputloc++; + } + } + } + } + + // All gamefuncs (and *only* _gamefuncs_) in I_ReturnTriggerClear() should be replicated here. + CONTROL_ClearButton(gamefunc_Crouch); + if (I_ReturnTrigger()) + { + I_ReturnTriggerClear(); + return -1; + } + + return 0; +} diff --git a/source/rr/src/input.h b/source/rr/src/input.h new file mode 100644 index 000000000..c926ebeae --- /dev/null +++ b/source/rr/src/input.h @@ -0,0 +1,66 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef input_h_ +#define input_h_ + +extern int32_t I_CheckAllInput(void); +extern void I_ClearAllInput(void); + +// Advance = Selecting a menu option || Saying "Yes" || Going forward in Help/Credits +// Return = Closing a sub-menu || Saying "No" +// Escape = Opening the menu in-game (should not be any gamefuncs) + +extern int32_t I_AdvanceTrigger(void); +extern void I_AdvanceTriggerClear(void); +extern int32_t I_ReturnTrigger(void); +extern void I_ReturnTriggerClear(void); +extern int32_t I_EscapeTrigger(void); +extern void I_EscapeTriggerClear(void); + +extern int32_t I_MenuUp(void); +extern void I_MenuUpClear(void); +extern int32_t I_MenuDown(void); +extern void I_MenuDownClear(void); +extern int32_t I_MenuLeft(void); +extern void I_MenuLeftClear(void); +extern int32_t I_MenuRight(void); +extern void I_MenuRightClear(void); + +extern int32_t I_PanelUp(void); +extern void I_PanelUpClear(void); +extern int32_t I_PanelDown(void); +extern void I_PanelDownClear(void); + +extern int32_t I_SliderLeft(void); +extern void I_SliderLeftClear(void); +extern int32_t I_SliderRight(void); +extern void I_SliderRightClear(void); + + +enum EnterTextFlags_t { + INPUT_NUMERIC = 0x00000001, +}; + +extern int32_t I_EnterText(char *t, int32_t maxlength, int32_t flags); + +#endif diff --git a/source/rr/src/inv.h b/source/rr/src/inv.h new file mode 100644 index 000000000..728e59c39 --- /dev/null +++ b/source/rr/src/inv.h @@ -0,0 +1,58 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2016 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#pragma once + +enum dukeinv_t +{ + GET_STEROIDS, // 0 + GET_SHIELD, + GET_SCUBA, + GET_HOLODUKE, + GET_JETPACK, + GET_DUMMY1, // 5 + GET_ACCESS, + GET_HEATS, + GET_DUMMY2, + GET_FIRSTAID, + GET_BOOTS, // 10 + GET_MAX +}; + +// these are not in the same order as the above, and it can't be changed for compat reasons. lame! +enum dukeinvicon_t +{ + ICON_NONE, // 0 + ICON_FIRSTAID, + ICON_STEROIDS, + ICON_HOLODUKE, + ICON_JETPACK, + ICON_HEATS, // 5 + ICON_SCUBA, + ICON_BOOTS, + ICON_MAX +}; + +extern int const icon_to_inv[ICON_MAX]; + +extern int const inv_to_icon[GET_MAX]; + diff --git a/source/rr/src/keys.h b/source/rr/src/keys.h new file mode 100644 index 000000000..78074008b --- /dev/null +++ b/source/rr/src/keys.h @@ -0,0 +1,148 @@ +//------------------------------------------------------------------------- +/* +Copyright (C) 2010 EDuke32 developers and contributors + +This file is part of EDuke32. + +EDuke32 is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +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. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ +//------------------------------------------------------------------------- + +#ifndef KEYS_H + +#define KEYS_H + + #define NUM_CODES 128 + + #define ESC 0x1B + #define ENTER 0x0D + + #define KEYSC_ESC 0x01 + #define KEYSC_1 0x02 + #define KEYSC_2 0x03 + #define KEYSC_3 0x04 + #define KEYSC_4 0x05 + #define KEYSC_5 0x06 + #define KEYSC_6 0x07 + #define KEYSC_7 0x08 + #define KEYSC_8 0x09 + #define KEYSC_9 0x0a + #define KEYSC_0 0x0b + #define KEYSC_DASH 0x0c + #define KEYSC_EQUAL 0x0d + + #define KEYSC_BS 0x0e + #define KEYSC_TAB 0x0f + #define KEYSC_Q 0x10 + #define KEYSC_W 0x11 + #define KEYSC_E 0x12 + #define KEYSC_R 0x13 + #define KEYSC_T 0x14 + #define KEYSC_Y 0x15 + #define KEYSC_U 0x16 + #define KEYSC_I 0x17 + #define KEYSC_O 0x18 + #define KEYSC_P 0x19 + #define KEYSC_LBRACK 0x1a + #define KEYSC_RBRACK 0x1b + #define KEYSC_ENTER 0x1c + + #define KEYSC_LCTRL 0x1d + #define KEYSC_A 0x1e + #define KEYSC_S 0x1f + #define KEYSC_D 0x20 + #define KEYSC_F 0x21 + #define KEYSC_G 0x22 + #define KEYSC_H 0x23 + #define KEYSC_J 0x24 + #define KEYSC_K 0x25 + #define KEYSC_L 0x26 + #define KEYSC_SEMI 0x27 + #define KEYSC_QUOTE 0x28 + #define KEYSC_BQUOTE 0x29 + #define KEYSC_TILDE 0x29 + + #define KEYSC_LSHIFT 0x2a + #define KEYSC_BSLASH 0x2b + #define KEYSC_Z 0x2c + #define KEYSC_X 0x2d + #define KEYSC_C 0x2e + #define KEYSC_V 0x2f + #define KEYSC_B 0x30 + #define KEYSC_N 0x31 + #define KEYSC_M 0x32 + #define KEYSC_COMMA 0x33 + #define KEYSC_PERIOD 0x34 + #define KEYSC_SLASH 0x35 + #define KEYSC_RSHIFT 0x36 + #define KEYSC_gSTAR 0x37 + + #define KEYSC_LALT 0x38 + #define KEYSC_SPACE 0x39 + #define KEYSC_CAPS 0x3a + + #define KEYSC_F1 0x3b + #define KEYSC_F2 0x3c + #define KEYSC_F3 0x3d + #define KEYSC_F4 0x3e + #define KEYSC_F5 0x3f + #define KEYSC_F6 0x40 + #define KEYSC_F7 0x41 + #define KEYSC_F8 0x42 + #define KEYSC_F9 0x43 + #define KEYSC_F10 0x44 + + #define KEYSC_gNUM 0x45 + #define KEYSC_SCROLL 0x46 + + #define KEYSC_gHOME 0x47 + #define KEYSC_gUP 0x48 + #define KEYSC_gPGUP 0x49 + #define KEYSC_gMINUS 0x4a + #define KEYSC_gLEFT 0x4b + #define KEYSC_gKP5 0x4c + #define KEYSC_gRIGHT 0x4d + #define KEYSC_gPLUS 0x4e + #define KEYSC_gEND 0x4f + #define KEYSC_gDOWN 0x50 + #define KEYSC_gPGDN 0x51 + #define KEYSC_gINS 0x52 + #define KEYSC_gDEL 0x53 + + #define KEYSC_F11 0x57 + #define KEYSC_F12 0x58 + + #define KEYSC_gENTER 0x9C + #define KEYSC_RCTRL 0x9D + #define KEYSC_gSLASH 0xB5 + #define KEYSC_RALT 0xB8 + #define KEYSC_PRTSCN 0xB7 + #define KEYSC_PAUSE 0xC5 + #define KEYSC_HOME 0xC7 + #define KEYSC_UP 0xC8 + #define KEYSC_PGUP 0xC9 + #define KEYSC_LEFT 0xCB + #define KEYSC_RIGHT 0xCD + #define KEYSC_END 0xCF + #define KEYSC_DOWN 0xD0 + #define KEYSC_PGDN 0xD1 + #define KEYSC_INSERT 0xD2 + #define KEYSC_DELETE 0xD3 + + #define asc_Esc 27 + #define asc_Enter 13 + #define asc_Space 32 + +#endif diff --git a/source/rr/src/lunatic/_defs_editor.lua b/source/rr/src/lunatic/_defs_editor.lua new file mode 100644 index 000000000..399db6432 --- /dev/null +++ b/source/rr/src/lunatic/_defs_editor.lua @@ -0,0 +1,55 @@ +-- INTERNAL +-- definitions of BUILD and game types for the Lunatic Interpreter + +local ffi = require("ffi") +local ffiC = ffi.C + +ffi.cdef[[ +enum { + LUNATIC_CLIENT_MAPSTER32 = 0, + LUNATIC_CLIENT_EDUKE32 = 1, + + LUNATIC_CLIENT = LUNATIC_CLIENT_MAPSTER32 +} +]] + +--== First, load the definitions common to the game's and editor's Lua interface. +decl = ffi.cdef +local defs_c = require("defs_common") +defs_c.finish_spritetype({}) + +defs_c.create_globals(_G) + +-- TODO: provide access to only a subset, restict access to ffiC? +gv = ffiC + +--== Mapster32-specific initialization + +ffi.cdef "char *listsearchpath(int32_t initp);" + +-- Add the search path directories to the Lua load path. +local initp = 1 +while (true) do + local sp_c = ffiC.listsearchpath(initp) + + if (sp_c == nil) then + break + end + + local sp = ffi.string(sp_c) + assert(sp:sub(-1)=='/') + package.path = sp..'?.lua;'..package.path + + initp = 0 +end + +-- Helper functions +local package = package +local require = require + +function reload(modname) + package.loaded[modname] = nil + return require(modname) +end + +--print('Lua load path: '..package.path) diff --git a/source/rr/src/lunatic/_defs_game.lua b/source/rr/src/lunatic/_defs_game.lua new file mode 100644 index 000000000..b8cc22626 --- /dev/null +++ b/source/rr/src/lunatic/_defs_game.lua @@ -0,0 +1,2632 @@ +-- INTERNAL +-- definitions of BUILD and game types for the Lunatic Interpreter + +local require = require +local ffi = require("ffi") +local ffiC = ffi.C + +-- Lua C API functions. +local CF = CF + +local bit = bit +local coroutine = coroutine +local string = string +local table = table +local math = math + +local assert = assert +local error = error +local getfenv = getfenv +local getmetatable = getmetatable +local ipairs = ipairs +local loadstring = loadstring +local pairs = pairs +local pcall = pcall +local rawget = rawget +local rawset = rawset +local select = select +local setmetatable = setmetatable +local setfenv = setfenv +local tonumber = tonumber +local tostring = tostring +local type = type + +-- Create a new module for passing stuff to other modules. +local lprivate = {} +require("package").loaded.lprivate = lprivate + +require("jit.opt").start("maxmcode=10240") -- in KiB + +-- The "gv" global will provide access to C global *scalars* and safe functions. +-- NOTE: This exposes C library functions from e.g. the global C namespace, but +-- without their declarations, they will be sitting there like a stone. +local gv_ = {} +-- [key]= forbids, [key]= overrides +local gv_access = {} + +-- This is for declarations of arrays or pointers which should not be +-- accessible through the "gv" global. The "defs_common" module will +-- use this function. +-- +-- Notes: do not declare multiple scalars on one line (this is bad: +-- "int32_t a, b"). Do not name array arguments (or add a space +-- between the identifier and the '[' instead). +function decl(str, ...) + -- NOTE that the regexp also catches non-array/non-function identifiers + -- like "user_defs ud;" + for varname in string.gmatch(str, "([%a_][%w_]*)[[(;]") do + if (ffiC._DEBUG_LUNATIC ~= 0) then + print("FORBID "..varname) + end + gv_access[varname] = true + end + + ffi.cdef(str, ...) +end + +lprivate.decl = decl + +ffi.cdef[[ +enum { + LUNATIC_CLIENT_MAPSTER32 = 0, + LUNATIC_CLIENT_EDUKE32 = 1, + + LUNATIC_CLIENT = LUNATIC_CLIENT_EDUKE32 +} +]] + +-- Load the definitions common to the game's and editor's Lua interface. +local defs_c = require("defs_common") +local cansee = defs_c.cansee +local strip_const = defs_c.strip_const +local setmtonce = defs_c.setmtonce + +-- Must be after loading "defs_common" which redefines "print" to use +-- OSD_Printf() +local print, printf = print, defs_c.printf + + +---=== EDuke32 game definitions ===--- + +local INV_NAMES = { + "STEROIDS", + "SHIELD", + "SCUBA", + "HOLODUKE", + "JETPACK", + "DUMMY1", + "ACCESS", + "HEATS", + "DUMMY2", + "FIRSTAID", + "BOOTS", +} + +local WEAPON_NAMES = { + "KNEE", + "PISTOL", + "SHOTGUN", + "CHAINGUN", + "RPG", + "HANDBOMB", + "SHRINKER", + "DEVISTATOR", + "TRIPBOMB", + "FREEZE", + "HANDREMOTE", + "GROW", +} + +---- game structs ---- + +lprivate.GET = defs_c.conststruct(INV_NAMES) +lprivate.WEAPON = defs_c.conststruct(WEAPON_NAMES) + +ffi.cdef([[ +enum { + GET_MAX = 11, + MAX_WEAPONS = 12, + MAXPLAYERS = 16, + GTICSPERSEC = 30, // The real number of movement updates per second +}; +]]) + +ffi.cdef[[ +struct action { + int16_t startframe, numframes; + int16_t viewtype, incval, delay; + uint16_t flags; +}; + +struct move { + int16_t hvel, vvel; +}; + +#pragma pack(push,1) +typedef struct { int32_t id; struct move mv; } con_move_t; +typedef struct { int32_t id; struct action ac; } con_action_t; +#pragma pack(pop) + +typedef struct { + int32_t id; + con_action_t act; + con_move_t mov; + int32_t movflags; +} con_ai_t; +]] + +defs_c.bitint_new_struct_type("int16_t", "SBit16") +defs_c.bitint_new_struct_type("int32_t", "SBit32") +defs_c.bitint_new_struct_type("uint32_t", "UBit32") + +-- Struct template for actor_t. It already has 'const' fields (TODO: might need +-- to make more 'const'), but still has array members exposed, so is unsuited +-- for external exposure. +local ACTOR_STRUCT = [[ +struct { + const int32_t t_data[10]; + const struct move mv; + const struct action ac; + const uint16_t actiontics; + +]]..defs_c.bitint_member("SBit32", "flags")..[[ + vec3_t bpos; //12b + int32_t floorz,ceilingz,lastvx,lastvy; //16b + int32_t lasttransport; //4b + + const int16_t picnum; + int16_t ang, extra; + const int16_t owner; + // NOTE: not to be confused with .movflags: +]]..defs_c.bitint_member("SBit16", "_movflag")..[[ + int16_t tempang, timetosleep; + + int16_t stayputsect; + const int16_t dispicnum; + // Movement flags, sprite[i].hitag in C-CON. + // XXX: more research where it was used in EDuke32's C code? (also .lotag <-> actiontics) + // XXX: what if CON code knew of the above implementation detail? +]]..defs_c.bitint_member("UBit16", "movflags")..[[ + int16_t cgg; + + const int16_t lightId, lightcount, lightmaxrange; + // NOTE: on 32-bit, C's lightptr+filler <=> this dummy: + const union { intptr_t ptr; uint64_t dummy; } _light; +} +]] + +local bcarray = require("bcarray") + +local bcheck = require("bcheck") +local check_sector_idx, check_tile_idx = bcheck.sector_idx, bcheck.tile_idx +local check_sprite_idx = bcheck.sprite_idx +local check_weapon_idx, check_inventory_idx = bcheck.weapon_idx, bcheck.inventory_idx +local check_sound_idx = bcheck.sound_idx +local check_number = bcheck.number +local check_type = bcheck.type + +bcarray.new("int16_t", 64, "loogie", "int16_x_64") -- TODO: randomize member names +bcarray.new("int16_t", ffiC.MAX_WEAPONS, "weapon", "int16_x_MAX_WEAPONS", WEAPON_NAMES) +bcarray.new("int16_t", ffiC.GET_MAX, "inventory", "int16_x_GET_MAX", INV_NAMES) + +-- NOTE: writing e.g. "ps.jetpack_on" in Lua when "ps.jetpack_on~=0" was meant +-- is probably one of the most commonly committed errors, so we make it a bool +-- type instead of uint8_t. The only issue is that if CON coders used these +-- fields to store more than just one bit, we're in trouble. +-- This will need to be documented and frozen for release. +local DUKEPLAYER_STRUCT = [[ +__attribute__((packed)) struct { + vec3_t pos, opos, vel, npos; + vec2_t bobpos, fric; + int32_t truefz, truecz, player_par; + int32_t randomflamex, exitx, exity; + int32_t runspeed, max_player_health, max_shield_amount; + int32_t autostep, autostep_sbw; + + uint32_t interface_toggle_flag; + + int32_t pipebombControl, pipebombLifetime, pipebombLifetimeVar; + int32_t tripbombControl, tripbombLifetime, tripbombLifetimeVar; + + int32_t zrange; + int16_t angrange, autoaimang; + + uint16_t max_actors_killed, actors_killed; +]]..defs_c.bitint_member("UBit16", "gotweapon")..[[ + uint16_t zoom; + + int16_x_64 loogiex; + int16_x_64 loogiey; + int16_t sbs, sound_pitch; + + int16_t ang, oang, angvel; + const int16_t cursectnum; + int16_t look_ang, last_extra, subweapon; + int16_x_MAX_WEAPONS max_ammo_amount; + int16_x_MAX_WEAPONS ammo_amount; + int16_x_GET_MAX inv_amount; + constint16_t wackedbyactor; + int16_t pyoff, opyoff; + + int16_t horiz, horizoff, ohoriz, ohorizoff; + const int16_t newowner; + int16_t jumping_counter, airleft; + int16_t fta; + const int16_t ftq; + const int16_t access_wallnum, access_spritenum; + int16_t got_access, weapon_ang, visibility; + int16_t somethingonplayer, on_crane; + const int16_t i; + const int16_t one_parallax_sectnum; + int16_t random_club_frame, one_eighty_count; + constint16_t dummyplayersprite; + int16_t extra_extra8; + int16_t actorsqu, timebeforeexit; + const int16_t customexitsound; + int16_t last_pissed_time; + + int16_x_MAX_WEAPONS weaprecs; + int16_t weapon_sway, crack_time, bobcounter; + + int16_t orotscrnang, rotscrnang, dead_flag; // JBF 20031220: added orotscrnang + int16_t holoduke_on, pycount; + int16_t transporter_hold; + + uint8_t max_secret_rooms, secret_rooms; + uint8_t frag, fraggedself, quick_kick, last_quick_kick; + uint8_t return_to_center; + bool reloading; + const uint8_t weapreccnt; + uint8_t aim_mode, auto_aim, weaponswitch, movement_lock, team; + uint8_t tipincs, hbomb_hold_delay; + const uint8_t frag_ps; + uint8_t kickback_pic; + + uint8_t gm; + bool on_warping_sector; + uint8_t footprintcount, hurt_delay; + bool hbomb_on, jumping_toggle, rapid_fire_hold, on_ground; + // NOTE: there's array indexing with inven_icon, but always after a + // bound check: + uint8_t inven_icon, buttonpalette; + bool over_shoulder_on; + uint8_t show_empty_weapon; + + bool jetpack_on, spritebridge; + uint8_t lastrandomspot; // unused + bool scuba_on; + uint8_t footprintpal; + bool heat_on; + uint8_t invdisptime; + + bool holster_weapon; + uint8_t falling_counter, footprintshade; + uint8_t refresh_inventory; + const
uint8_t last_full_weapon; + + const uint8_t toggle_key_flag; + uint8_t knuckle_incs, knee_incs, access_incs; + uint8_t walking_snd_toggle, palookup, hard_landing, fist_incs; + + int8_t numloogs, loogcnt; + const int8_t scream_voice; + const int8_t last_weapon; + const int8_t cheat_phase; + int8_t weapon_pos; + const int8_t wantweaponfire; + const int8_t curr_weapon; + + const uint8_t palette; + palette_t _pals; + int8_t _palsfadespeed, _palsfadenext, _palsfadeprio, _padding2; + + // NOTE: In C, the struct type has no name. We only have it here to define + // a metatype later. + const weaponaccess_t weapon; + const int8_t _padding; +} +]] + +local PROJECTILE_STRUCT = [[ +struct { + int32_t workslike, cstat; + int32_t hitradius, range, flashcolor; + const int16_t spawns; + const int16_t sound, isound; + int16_t vel; + const int16_t decal, trail; + int16_t tnum, drop; + int16_t offset, bounces; + const int16_t bsound; + int16_t toffset; + int16_t extra, extra_rand; + int8_t sxrepeat, syrepeat, txrepeat, tyrepeat; + int8_t shade, xrepeat, yrepeat, pal; + int8_t movecnt; + uint8_t clipdist; + int8_t filler[2]; + int32_t userdata; +} +]] + +-- KEEPINSYNC weapondata_mt below. +local WEAPONDATA_STRUCT = "struct {"..table.concat(con_lang.wdata_members, ';').."; }" + +local randgen = require("randgen") + +local ma_rand = randgen.new(true) -- initialize to "random" (time-based) seed +local ma_count = nil + +local function ma_replace_array(typestr, neltstr) + local nelts = tonumber(neltstr) + if (nelts==nil) then + nelts = ffiC[neltstr] + assert(type(nelts)=="number") + end + + local strtab = { "const ", typestr.." " } + for i=1,nelts do + local ch1 = 97 + (ma_rand:getu32() % 25) -- 'a'..'z' + strtab[i+2] = string.format("_%c%x%s", ch1, ma_count, (i ] = true if setting <0 allowed +local DukePlayer_prot_chkfunc = {} -- [ ] = + +local function ma_replace_scalar(what, typestr, membname) + DukePlayer_prot_chkfunc[membname] = assert(prot_scalar_chkfunc[what:sub(1,1)]) + DukePlayer_prot_allowneg[membname] = (what:sub(2)=="-") + return ma_replace_array(typestr, 1) +end + +-- Converts a template struct definition to an external one, in which arrays +-- have been substituted by randomly named scalar fields. +-- : also handle protected scalars like "const ..." etc. +local function mangle_arrays(structstr, also_scalars) + ma_count = 0 + -- NOTE: regexp only works for non-nested arrays and for one array per line. + structstr = structstr:gsub("const%s+([%w_]+)[^\n]+%[([%w_]+)%];", ma_replace_array) + + if (also_scalars) then + -- One protected scalar per line, too. + structstr = structstr:gsub("const<(.-)>%s+([%w_]-)%s+([%w_]-);", ma_replace_scalar) + end + + return structstr +end + +--print(mangle_arrays(DUKEPLAYER_STRUCT, true)) + +--- default defines etc. +local con_lang = require("con_lang") +local xmath = require("xmath") + +ffi.cdef([[ +typedef struct { int32_t _p; } weaponaccess_t; + +typedef struct { +]]..defs_c.bitint_member("UBit32", "bits")..[[ + int16_t fvel, svel, avel; + int8_t horz, extbits; +} input_t; + +typedef +]].. mangle_arrays(ACTOR_STRUCT) ..[[ +actor_t; + +typedef +]].. mangle_arrays(DUKEPLAYER_STRUCT, true) ..[[ +DukePlayer_t; + +typedef __attribute__((packed)) struct { + DukePlayer_t *ps; + input_t *sync; + + int32_t netsynctime; + int16_t ping, filler; + int32_t pcolor, pteam; + uint8_t frags[MAXPLAYERS], wchoice[MAX_WEAPONS]; + + char vote, gotvote, pingcnt, playerquitflag, ready; + char user_name[32]; + uint32_t revision; +} playerdata_t; + +typedef struct { + int32_t cur, count; + int32_t gunposx, lookhalfang; + int32_t gunposy, lookhoriz; + int32_t shade; +} hudweapon_t; + +typedef +]].. WEAPONDATA_STRUCT ..[[ +weapondata_t; + +typedef +]].. PROJECTILE_STRUCT ..[[ +projectile_t; + +typedef struct { + uint32_t _flags; // XXX: do we want to have this accessible at game time? + int32_t _cacherange; + projectile_t *_proj; + const projectile_t *_defproj; +} tiledata_t; + +typedef struct { + vec3_t pos; + int32_t dist, clock; + int16_t ang, horiz; + int16_t sect; // NOTE: protected in camera_mt's __newindex +} camera_t; + +enum +{ + MAXMOUSEBUTTONS = 10, + MAXMOUSEAXES = 2, + MAXJOYBUTTONS = 32, + MAXJOYBUTTONSANDHATS = (32+4), + MAXJOYAXES = 9, + + NUMGAMEFUNCTIONS = 56, + + // game.h + MAXRIDECULE = 10, + MAXRIDECULELENGTH = 40, + MAXSAVEGAMENAME = 22, + MAXPWLOCKOUT = 128, + MAXRTSNAME = 128, +}; + +typedef struct { + int32_t const_visibility,uw_framerate; + int32_t camera_time,folfvel,folavel,folx,foly,fola; + int32_t reccnt,crosshairscale; + + int32_t runkey_mode,statusbarscale,mouseaiming,weaponswitch,drawweapon; // JBF 20031125 + int32_t democams,color,msgdisptime,statusbarmode; + int32_t m_noexits,noexits,autovote,automsg,idplayers; + int32_t team, viewbob, weaponsway, althud, weaponscale, textscale; + + int32_t entered_name,screen_tilting,shadows,fta_on,executions,auto_run; + int32_t coords,tickrate,levelstats,m_coop,coop,screen_size,lockout,crosshair; + int32_t playerai,angleinterpolation,obituaries; + + int32_t respawn_monsters,respawn_items,respawn_inventory,recstat,monsters_off,brightness; + int32_t m_respawn_items,m_respawn_monsters,m_respawn_inventory,m_recstat,m_monsters_off,detail; + int32_t m_ffire,ffire,m_player_skill,m_level_number,m_volume_number,multimode; + int32_t player_skill,level_number,volume_number,m_marker,marker,mouseflip; + + vec2_t m_origin; + int32_t playerbest; + + int32_t configversion, bgstretch; + + int16_t pause_on,from_bonus; + int16_t camerasprite,last_camsprite; + int16_t last_level,secretlevel; + + struct { + int32_t UseJoystick; + int32_t UseMouse; + int32_t AutoAim; + int32_t ShowOpponentWeapons; + int32_t MouseDeadZone,MouseBias; + int32_t SmoothInput; + + // JBF 20031211: Store the input settings because + // (currently) mact can't regurgitate them + int32_t MouseFunctions[MAXMOUSEBUTTONS][2]; + int32_t MouseDigitalFunctions[MAXMOUSEAXES][2]; + int32_t MouseAnalogueAxes[MAXMOUSEAXES]; + int32_t MouseAnalogueScale[MAXMOUSEAXES]; + int32_t JoystickFunctions[MAXJOYBUTTONSANDHATS][2]; + int32_t JoystickDigitalFunctions[MAXJOYAXES][2]; + int32_t JoystickAnalogueAxes[MAXJOYAXES]; + int32_t JoystickAnalogueScale[MAXJOYAXES]; + int32_t JoystickAnalogueDead[MAXJOYAXES]; + int32_t JoystickAnalogueSaturate[MAXJOYAXES]; + uint8_t KeyboardKeys[NUMGAMEFUNCTIONS][2]; + + // + // Sound variables + // + int32_t MasterVolume; + int32_t FXVolume; + int32_t MusicVolume; + int32_t SoundToggle; + int32_t MusicToggle; + int32_t VoiceToggle; + int32_t AmbienceToggle; + + int32_t NumVoices; + int32_t NumChannels; + int32_t NumBits; + int32_t MixRate; + + int32_t ReverseStereo; + + // + // Screen variables + // + + int32_t ScreenMode; + + int32_t ScreenWidth; + int32_t ScreenHeight; + int32_t ScreenBPP; + + int32_t ForceSetup; + int32_t NoAutoLoad; + + const int32_t scripthandle; + int32_t setupread; + + int32_t CheckForUpdates; + int32_t LastUpdateCheck; + int32_t useprecache; + } config; + + char overhead_on,last_overhead,showweapons; + char god,warp_on,cashman,eog,showallmap; + char show_help,scrollmode,noclip; + char ridecule[MAXRIDECULE][MAXRIDECULELENGTH]; + char pwlockout[MAXPWLOCKOUT],rtsname[MAXRTSNAME]; + char display_bonus_screen; + char show_level_text; + char wchoice[MAX_WEAPONS]; +} user_defs; + +typedef struct { + int32_t partime, designertime; + char *name, *filename, *musicfn; + void *savedstate; +} map_t; +]]) + +bcarray.new("weapondata_t", ffiC.MAX_WEAPONS, "weapon", "weapondata_x_MAX_WEAPONS", WEAPON_NAMES) +bcarray.new("int32_t", con_lang.MAXSESSIONVARS, "sessionvar", "int32_x_MAXSESSIONVARS") + +-- EXTERNALLY EXPOSED GAME VARIABLES +ffi.cdef[[ +const int32_t screenpeek; +hudweapon_t hudweap; +int32_t g_logoFlags; +]] + +-- INTERNAL VARIABLES/FUNCTIONS +decl("map_t g_mapInfo[$*$];", con_lang.MAXVOLUMES+1, con_lang.MAXLEVELS) +decl("char g_volumeNames[$][33];", con_lang.MAXVOLUMES) + +decl[[ +const int32_t myconnectindex; +int32_t g_RETURN; +int32_t g_elCONSize; +char *g_elCON; +void El_SetCON(const char *conluacode); +void El_OnError(const char *str); + +char *g_elSavecode; +void El_FreeSaveCode(void); +const char *(*El_SerializeGamevars)(int32_t *slenptr, int32_t levelnum); +int32_t (*El_RestoreGamevars)(const char *savecode); +int32_t (*El_GetLabelValue)(const char *label); + +const char *s_buildRev; +const char *g_sizes_of_what[]; +int32_t g_sizes_of[]; +int32_t g_elFirstTime; +int32_t g_elCallDepth; +int32_t block_deletesprite; +const char **g_elModules; +char g_modDir[]; +int32_x_MAXSESSIONVARS g_elSessionVar; +actor_t actor[MAXSPRITES]; +camera_t g_camera; +user_defs ud; +playerdata_t *const g_player; +DukePlayer_t *g_player_ps[MAXPLAYERS]; +weapondata_x_MAX_WEAPONS g_playerWeapon[MAXPLAYERS]; +weapondata_t g_weaponOverridden[MAX_WEAPONS]; +int16_t WeaponPickupSprites[MAX_WEAPONS]; +tiledata_t g_tile[MAXTILES]; +projectile_t SpriteProjectile[MAXSPRITES]; + +int32_t g_noResetVars; +void (*A_ResetVars)(int32_t iActor); + +// Used from lunacon.lua for dynamic {tile,sound} remapping: +struct +{ + const char *str; + int32_t *dynvalptr; + const int16_t staticval; +} g_dynTileList[], g_dynSoundList[]; + +char *apStrings[]; + +const int32_t g_mostConcurrentPlayers; +int16_t g_deleteQueueSize; +int16_t g_blimpSpawnItems[15]; +int32_t g_scriptVersion; +const int32_t g_frameRate; +const int32_t g_currentMenu; +uint16_t g_earthquakeTime; +uint32_t g_moveThingsCount; +char CheatKeys[2]; + +// Must not have functions here that may call events directly or +// indirectly. See lunatic_game.c. + +int32_t A_IncurDamage(int32_t sn); // not bound-checked! +int32_t G_CheckActivatorMotion(int32_t lotag); +int32_t A_Dodge(spritetype *s); +int32_t A_MoveSpriteClipdist(int32_t spritenum, const vec3_t *change, uint32_t cliptype, int32_t clipdist); +void P_DoQuote(int32_t q, DukePlayer_t *p); +void P_SetGamePalette(DukePlayer_t *player, uint32_t palid, int32_t set); +void G_AddUserQuote(const char *daquote); +void G_ClearCameraView(DukePlayer_t *ps); +void VM_DrawTileGeneric(int32_t x, int32_t y, int32_t zoom, int32_t tilenum, + int32_t shade, int32_t orientation, int32_t p); +void G_InitTimer(int32_t ticspersec); +void G_GetTimeDate(int32_t *vals); +int32_t G_ToggleWallInterpolation(int32_t w, int32_t doset); +int32_t G_StartTrack(int32_t level); +int32_t VM_CheckSquished2(int32_t i, int32_t snum); +void Menu_Change(int32_t cm); + +const char *KB_ScanCodeToString(uint8_t scancode); + +int32_t A_CheckAnySoundPlaying(int32_t i); +int32_t S_CheckSoundPlaying(int32_t i, int32_t num); +void S_StopEnvSound(int32_t num, int32_t i); +void S_StopAllSounds(void); +void S_ChangeSoundPitch(int32_t num, int32_t i, int32_t pitchoffset); +int32_t S_GetMusicPosition(void); +void S_SetMusicPosition(int32_t position); + +int32_t minitext_(int32_t x,int32_t y,const char *t,int32_t s,int32_t p,int32_t sb); +void G_DrawTXDigiNumZ(int32_t starttile, int32_t x,int32_t y,int32_t n,int32_t s,int32_t pal, + int32_t cs,int32_t x1, int32_t y1, int32_t x2, int32_t y2, int32_t z); +void G_PrintGameText(int32_t tile, int32_t x, int32_t y, const char *t, + int32_t s, int32_t p, int32_t o, + int32_t x1, int32_t y1, int32_t x2, int32_t y2, + int32_t z, int32_t a); +vec2_t G_ScreenText(const int32_t font, + int32_t x, int32_t y, const int32_t z, const int32_t blockangle, const int32_t charangle, + const char *str, const int32_t shade, int32_t pal, int32_t o, int32_t alpha, + int32_t xspace, int32_t yline, int32_t xbetween, int32_t ybetween, const int32_t f, + const int32_t x1, const int32_t y1, const int32_t x2, const int32_t y2); +vec2_t G_ScreenTextSize(const int32_t font, + int32_t x, int32_t y, const int32_t z, const int32_t blockangle, + const char *str, const int32_t o, + int32_t xspace, int32_t yline, int32_t xbetween, int32_t ybetween, + const int32_t f, + int32_t x1, int32_t y1, int32_t x2, int32_t y2); +const char* G_PrintYourTime(void); +const char* G_PrintParTime(void); +const char* G_PrintDesignerTime(void); +const char* G_PrintBestTime(void); + +void G_UpdateScreenArea(void); +void G_SaveMapState(void); +void G_RestoreMapState(void); +void G_FreeMapState(int32_t mapnum); +]] + +decl[[ +int32_t kopen4loadfrommod(const char *filename, char searchfirst); + +char **g_scriptModules; +int32_t g_scriptModulesNum; + +const char *G_ConFile(void); +void G_DoGameStartup(const int32_t *params); +int32_t C_DefineSound(int32_t sndidx, const char *fn, int32_t args [5]); +void C_DefineMusic(int32_t vol, int32_t lev, const char *fn); +void C_DefineQuote(int32_t qnum, const char *qstr); +void C_DefineVolumeName(int32_t vol, const char *name); +void C_DefineVolumeFlags(int32_t vol, int32_t flags); +void C_UndefineVolume(int32_t vol); +void C_DefineSkillName(int32_t skill, const char *name); +void C_UndefineSkill(int32_t skill); +void C_DefineLevelName(int32_t vol, int32_t lev, const char *fn, + int32_t partime, int32_t designertime, + const char *levelname); +void C_UndefineLevel(int32_t vol, int32_t lev); +void C_DefineProjectile(int32_t j, int32_t what, int32_t val); +void C_DefineGameFuncName(int32_t idx, const char *name); +void C_DefineGameType(int32_t idx, int32_t flags, const char *name); +int32_t C_SetDefName(const char *name); +void C_SetCfgName(const char *cfgname); + +int32_t SCRIPT_GetNumber(int32_t scripthandle, const char *sectionname, const char *entryname, int32_t *number); +void SCRIPT_PutNumber(int32_t scripthandle, const char *sectionname, const char *entryname, int32_t number, + int32_t hexadecimal, int32_t defaultvalue); +]] + + +-- http://lua-users.org/wiki/SandBoxes says "potentially unsafe" +-- as it allows to see implementations of functions. +--local string_dump = string.dump +string.dump = nil + + +-- sanity-check struct type sizes +local good = true +for i=0,10 do + local what = ffi.string(ffiC.g_sizes_of_what[i]) + local fsz = ffi.sizeof(what) + local csz = ffiC.g_sizes_of[i] + if (ffiC._DEBUG_LUNATIC ~= 0) then + print(i..": "..what..": C sizeof = "..tostring(csz)..", FFI sizeof = "..tostring(fsz)) + end + if (fsz ~= csz) then + good = false; + end +end + +if (not good) then + error("Some sizes don't match between C and LuaJIT/FFI.") +end + + +--== "player" global, needed by the "control" module ==-- + +local player_static_members = defs_c.static_members_tab() + +--[[ +player_static_members._INPUT_BITS = defs_c.conststruct +{ + JUMP = 1, + CROUCH = 2, + FIRE = 4, + AIM_UP = 8, + AIM_DOWN = 16, + RUNNING = 32, + LOOK_LEFT = 64, + LOOK_RIGHT = 128, + -- weapons... + STEROIDS = 4096, + LOOK_UP = 8192, + LOOK_DOWN = 16384, + NIGHTVISION = 32768, + MEDKIT = 65536, + RESERVED = 131072, + CENTER_VIEW = 262144, + HOLSTER_WEAPON = 524288, + INVENTORY_LEFT = 1048576, + PAUSE = 2097152, + QUICK_KICK = 4194304, + AIM_MODE = 8388608, + HOLODUKE = 16777216, + JETPACK = 33554432, + QUIT = 67108864, + INVENTORY_RIGHT = 134217728, + TURN_AROUND = 268435456, + OPEN = 536870912, + INVENTORY = 1073741824, + ESC = 2147483648, +} + +player_static_members._INPUT_EXT_BITS = defs_c.conststruct +{ + MOVE_FORWARD = 1, + MOVE_BACKWARD = 2, + STRAFE_LEFT = 4, + STRAFE_RIGHT = 8, + TURN_LEFT = 16, + TURN_RIGHT = 32, +} +--]] + +local band = bit.band +local lsh = bit.lshift +local ivec3 = xmath.ivec3 + +do + -- player.all() iterator + local function iter_allplayers(_nil, pli) + if (pli+1 < ffiC.g_mostConcurrentPlayers) then + return pli+1 + end + end + + function player_static_members.all() + return iter_allplayers, nil, -1 + end + + -- player.holdskey(pli, keyname) + local KEYS = { -- SK_CROUCH etc. -- "sync keys" + CROUCH = lsh(1,1), + RUN = lsh(1,5), + OPEN = lsh(1,29), + } + + function player_static_members.holdskey(pli, keyname) + bcheck.player_idx(pli) + if (KEYS[keyname] == nil) then + error("invalid key name: "..tostring(keyname), 2) + end + + return ffiC.g_player[pli].sync.bitsbits:test(KEYS[keyname]) + end +end + +local player_holdskey = player_static_members.holdskey + +-- Actor flags +local actor_static_members = defs_c.static_members_tab() +do + local our_SFLAG = {} + local ext_SFLAG = con_lang.labels[4] -- external actor flags only + local USER_MASK = 0 + + for name, flag in pairs(ext_SFLAG) do + our_SFLAG[name:sub(7)] = flag -- strip "SFLAG_" + USER_MASK = bit.bor(USER_MASK, flag) + end + + -- Add a couple of convenience defines. + our_SFLAG.enemy = con_lang.SFLAG.SFLAG_BADGUY + our_SFLAG.enemystayput = con_lang.SFLAG.SFLAG_BADGUY + con_lang.SFLAG.SFLAG_BADGUYSTAYPUT + our_SFLAG.rotfixed = con_lang.SFLAG.SFLAG_ROTFIXED + + -- Callback function chaining control flags. + our_SFLAG.replace = 0x08000000 + our_SFLAG.replace_soft = 0x08000000 -- compat + our_SFLAG.replace_hard = 0x08000000 -- compat, deprecated + our_SFLAG.chain_beg = 0x20000000 + our_SFLAG.chain_end = 0x40000000 + our_SFLAG._CHAIN_MASK_ACTOR = 0x78000000 + our_SFLAG._CHAIN_MASK_EVENT = 0x68000000 + + -- XXX: CON doesn't export BADGUYSTAYPUT or ROTFIXED SFLAGs, but they are considered + -- external for Lunatic. + our_SFLAG.USER_MASK = bit.bor(USER_MASK, our_SFLAG.enemystayput, our_SFLAG.rotfixed) + + actor_static_members.FLAGS = defs_c.conststruct(our_SFLAG) + + -- Sprite status numbers. Kept in 'actor', because it's more of a game-side + -- concept (event though status lists are implemented in the engine), and + -- to prevent confusion with sprite.CSTAT. + local our_STAT = {} + + for name, statnum in pairs(con_lang.STAT) do + -- Strip 'STAT_'. + our_STAT[name:sub(6)] = statnum + end + + actor_static_members.STAT = defs_c.conststruct(our_STAT) + + actor_static_members.MOVFLAGS = defs_c.conststruct + { + -- NOTE: no underscores, like in DEFS.CON. + faceplayer = 1, + geth = 2, + getv = 4, + randomangle = 8, + faceplayerslow = 16, + spin = 32, + faceplayersmart = 64, + fleeenemy = 128, + jumptoplayer_only = 256, + jumptoplayer_bits = 257, -- NOTE: two bits set! + jumptoplayer = 257, + seekplayer = 512, + furthestdir = 1024, + dodgebullet = 4096, + } +end + +function actor_static_members.fall(i) + check_sprite_idx(i) + CF.VM_FallSprite(i) +end + +-- actor.move(i, vec, cliptype [, clipdist]) +function actor_static_members.move(i, vec, cliptype, clipdist) + check_sprite_idx(i) + local vel = ivec3(vec.x, vec.y, vec.z) + return ffiC.A_MoveSpriteClipdist(i, vel, cliptype, clipdist or -1) +end + +-- Delete sprite with index . +function actor_static_members.delete(i) + check_sprite_idx(i) + + if (ffiC.sprite[i].statnum == ffiC.MAXSTATUS) then + error("Attempt to delete a sprite already not in the game world", 2) + end + + if (ffiC.block_deletesprite ~= 0) then + error("Attempt to delete sprite in EVENT_EGS", 2) + end + + -- TODO_MP + if (ffiC.g_player_ps[0].i == i) then + error("Attempt to delete player 0's APLAYER sprite", 2) + end + + CF.A_DeleteSprite(i) +end + +local tile_static_members = defs_c.static_members_tab() +do + tile_static_members.sizx = defs_c.creategtab_membidx(ffiC.tilesiz, "x", ffiC.MAXTILES, "tilesizx[]") + tile_static_members.sizy = defs_c.creategtab_membidx(ffiC.tilesiz, "y", ffiC.MAXTILES, "tilesizy[]") +end + +-- XXX: error message will say "g_player_ps" +player = setmtonce({}, defs_c.GenStructMetatable("g_player_ps", "g_mostConcurrentPlayers", player_static_members)) + +-- needed by "control" +actor = setmtonce({}, defs_c.GenStructMetatable("actor", "MAXSPRITES", actor_static_members)) +-- Some bitwise NOTs of various actor flag masks. +local BNOT = { + USER_MASK = bit.bnot(actor.FLAGS.USER_MASK), + CHAIN_MASK_ACTOR = bit.bnot(actor.FLAGS._CHAIN_MASK_ACTOR), + CHAIN_MASK_EVENT = bit.bnot(actor.FLAGS._CHAIN_MASK_EVENT), +} + +local projectile = defs_c.creategtab_membidx_ptr(ffiC.g_tile, "_proj", ffiC.MAXTILES, "projectile") +local g_tile = setmtonce({}, defs_c.GenStructMetatable("g_tile", "MAXTILES", tile_static_members)) + +--== Custom operations for BUILD data structures ==-- +-- Among other things, declares struct action and struct move, and their +-- ID-wrapped types con_action_t and con_move_t +local con = require("control") + +do + local isenemytile = con.isenemytile + + -- Add game-side metamethods to "spritetype" and register it with "metatype" + local spr_mt_index_add = { + isenemy = function(s) + return isenemytile(s.picnum) + end, + } + + defs_c.finish_spritetype(spr_mt_index_add) +end + +-- Check a literal numeric action or move value. +local function check_literal_am(am, typename) + if (type(am) ~= "number") then + error("bad argument: expected number or "..typename, 3) + end + + -- Negative values are generated as con.action/con.move IDs. + if (not (am >= 0 and am <= 32767)) then + error("bad argument: expected number in [0 .. 32767]", 3) + end +end + +-- An unrestricted actor_t pointer, for internal use: +local actor_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(ACTOR_STRUCT))) +local player_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(DUKEPLAYER_STRUCT))) +local projectile_ptr_ct = ffi.typeof("$ *", ffi.typeof(strip_const(PROJECTILE_STRUCT))) +-- An unrestricted weapondata_t pointer, but with the member names stripped of +-- the leading underscore, too: +local weapondata_ptr_ct = ffi.typeof("$ *", ffi.typeof((strip_const(WEAPONDATA_STRUCT):gsub(" _"," ")))) + +local con_action_ct = ffi.typeof("const con_action_t") +local con_move_ct = ffi.typeof("const con_move_t") +local con_ai_ct = ffi.typeof("const con_ai_t") + +-- All-zero bare action and move. +local nullac, nullmv = ffi.new("const struct action"), ffi.new("const struct move") +-- All-zero action and move with IDs. Mostly for CON support. +local literal_act = { [0]=con_action_ct(0), [1]=con_action_ct(1) } +local literal_mov = { [0]=con_move_ct(0), [1]=con_move_ct(1) } + +local function get_actor_idx(a) + local i = ffi.cast(actor_ptr_ct, a)-ffi.cast(actor_ptr_ct, ffiC.actor) +-- assert(not (i >= ffiC.MAXSPRITES+0ULL)) + return i +end + +local actor_methods = { + -- action + set_action = function(a, act) + a = ffi.cast(actor_ptr_ct, a) + + if (ffi.istype(con_action_ct, act)) then + a.t_data[4] = act.id + a.ac = act.ac + else + check_literal_am(act, "action") + a.t_data[4] = act + a.ac = nullac + end + + a.t_data[2] = 0 + a.t_data[3] = 0 + end, + + has_action = function(a, act) + a = ffi.cast(actor_ptr_ct, a) + + if (ffi.istype(con_action_ct, act)) then + return (a.t_data[4]==act.id) + else + check_literal_am(act, "action") + return (a.t_data[4]==act) + end + end, + + -- count + set_count = function(a, count) + ffi.cast(actor_ptr_ct, a).t_data[0] = count + end, + + get_count = function(a) + return ffi.cast(actor_ptr_ct, a).t_data[0] + end, + + -- action count + reset_acount = function(a) + ffi.cast(actor_ptr_ct, a).t_data[2] = 0 + end, + + get_acount = function(a) + return ffi.cast(actor_ptr_ct, a).t_data[2] + end, + + -- Override action delay. The action ID is kept. + set_action_delay = function(a, delay) + ffi.cast(actor_ptr_ct, a).ac.delay = delay + end, + + -- move + set_move = function(a, mov, movflags) + a = ffi.cast(actor_ptr_ct, a) + + if (ffi.istype(con_move_ct, mov)) then + a.t_data[1] = mov.id + a.mv = mov.mv + else + check_literal_am(mov, "move") + a.t_data[1] = mov + a.mv = nullmv + end + + a.t_data[0] = 0 + a.movflags = movflags or 0 + local spr = ffiC.sprite[get_actor_idx(a)] + + if (not spr:isenemy() or spr.extra > 0) then + if (bit.band(a.movflags, 8) ~= 0) then -- random_angle + spr.ang = bit.band(ffiC.krand(), 2047) + end + end + end, + + has_move = function(a, mov) + a = ffi.cast(actor_ptr_ct, a) + + if (ffi.istype(con_move_ct, mov)) then + return (a.t_data[1]==mov.id) + else + check_literal_am(mov, "move") + return (a.t_data[1]==mov) + end + end, + + -- Override velocity, keeping move ID. + set_hvel = function(a, hvel) + ffi.cast(actor_ptr_ct, a).mv.hvel = hvel + end, + + set_vvel = function(a, vvel) + ffi.cast(actor_ptr_ct, a).mv.vvel = vvel + end, + + -- ai + set_ai = function(a, ai) + local oa = a + a = ffi.cast(actor_ptr_ct, a) + + -- TODO: literal number AIs? + if (not ffi.istype(con_ai_ct, ai)) then + error("bad argument: expected ai", 2) + end + + -- NOTE: compare with gameexec.c, "CON_AI:" + a.t_data[5] = ai.id + + oa:set_action(ai.act) + oa:set_move(ai.mov, ai.movflags) + + -- Already reset with set_move(): +-- a.t_data[0] = 0 + end, + + has_ai = function(a, ai) + a = ffi.cast(actor_ptr_ct, a) + + if (ffi.istype(con_ai_ct, ai)) then + return (a.t_data[5]==ai.id) + else + check_literal_am(ai, "ai") + return (a.t_data[5]==ai) + end + end, + + -- Getters/setters. + _get_t_data = function(a, idx) + if (not (idx >= 0 and idx < 10)) then + error("invalid t_data index "..idx, 2) + end + return ffi.cast(actor_ptr_ct, a).t_data[idx] + end, + + _set_t_data = function(a, idx, val) + if (not (idx >= 0 and idx < 10)) then + error("invalid t_data index "..idx, 2) + end + ffi.cast(actor_ptr_ct, a).t_data[idx] = val + end, + + set_picnum = function(a, picnum) + if (not (picnum < 0)) then + check_tile_idx(picnum) + end + ffi.cast(actor_ptr_ct, a).picnum = picnum + end, + + set_owner = function(a, owner) + -- XXX: is it permissible to set to -1? + check_sprite_idx(owner) + ffi.cast(actor_ptr_ct, a).owner = owner + end, + + --- Custom methods --- + + -- Checkers for whether the movement update made the actor hit + -- something. + + checkhit = function(a) + -- Check whether we hit *anything*, including ceiling/floor. + return a._movflagbits:test(49152) + end, + + checkbump = function(a) + -- Check whether we bumped into a wall or sprite. + return (a._movflagbits:mask(49152) >= 32768) + end, + + hitwall = function(a) + if (a._movflagbits:mask(49152) == 32768) then + return a._movflagbits:mask(32767) + end + end, + + hitsprite = function(a) + if (a._movflagbits:mask(49152) == 49152) then + return a._movflagbits:mask(32767) + end + end, + + -- NOTE: no 'hitsector' or 'hitceiling' / 'hitfloor' for now because + -- more research is needed as to what the best way of checking c/f is. +} + +local actor_mt = { + __index = function(a, key) + if (actor_methods[key] ~= nil) then + return actor_methods[key] + elseif (key == "proj") then + return ffiC.SpriteProjectile[get_actor_idx(a)] + else + error("invalid indexing key to actor object", 2) + end + end, +} + +ffi.metatype("actor_t", actor_mt) + + +--- PER-PLAYER WEAPON SETTINGS +local wd_sound_member = {} +for _, declstr in pairs(con_lang.wdata_members) do + local member = declstr:match("const int32_t _(.*sound)$") + if (member) then + wd_sound_member[member] = true + if (ffiC._DEBUG_LUNATIC ~= 0) then + printf("weapondata_t member %s denotes a sound", member) + end + end +end + +local weapondata_mt = { + __index = function(wd, member) + -- Handle protected members that are renamed (e.g. shoots/_shoots). + return ffi.cast(weapondata_ptr_ct, wd)[0][member] + end, + + __newindex = function(wd, member, val) + -- Set to 'true' if we set a tile or sound member. + local didit = false + + check_type(member, "string") -- MEMBER_IS_STRING + check_number(val) + + if (wd_sound_member[member]) then -- XXX: sound2time is a time, not a sound + if (val < 0) then + val = 0 -- Set to 0 if negative (e.g. CrackDown). + else + check_sound_idx(val) + end + didit = true + elseif (member=="workslike") then + check_weapon_idx(val) + elseif (member=="spawn" or member=="shoots") then + if (val < 0) then + -- Set to 0 if oob (e.g. CrackDown). This is a bit problematic + -- for .shoots because it's used unconditionally except in one + -- case (see player.c). + val = 0 + else + check_tile_idx(val) + end + didit = true + end + + -- DEBUG: +-- printf("assigning %s to weapon's %s", tostring(val), member) + + -- NOTE: we're indexing a *pointer* with the user-supplied 'member', + -- which could be dangerouns if it could be a number. However, we have + -- assured that is is not in MEMBER_IS_STRING above. + ffi.cast(weapondata_ptr_ct, wd)[member] = val + + if (didit and ffiC.g_elCallDepth==0) then + -- Signal that we overrode this member at CON translation time. + + -- Get weapon index as pointer difference first. PLAYER_0. + local wi = ffi.cast(weapondata_ptr_ct, wd) + - ffi.cast(weapondata_ptr_ct, ffiC.g_playerWeapon) + assert(wi >= 0 and wi < ffiC.MAX_WEAPONS) + + -- Set g_weaponOverridden[wi][member], but without invoking + -- weapondata_t's __newindex metamethod (i.e. us)! + ffi.cast(weapondata_ptr_ct, ffiC.g_weaponOverridden[wi])[member] = 1 + end + end, +} +ffi.metatype("weapondata_t", weapondata_mt) + +local weaponaccess_mt = { + -- Syntax like "player[0].weapon.KNEE.shoots" possible because + -- g_playerWeapon[] is declared as an array of corresponding bcarray types + -- for us. + __index = function(wa, key) + if (type(key)~="number" and type(key)~="string") then + error("must access weapon either by number or by name") + end + + return ffiC.g_playerWeapon[wa._p][key] + end, +} +ffi.metatype("weaponaccess_t", weaponaccess_mt) + + +local function clamp(num, min, max) + return num < min and min + or num > max and max + or num +end + +local function clamp0to1(num) + check_number(num, 4) + return clamp(num, 0, 1) +end + +local player_methods = { + -- CON-like addammo/addweapon, but without the non-local control flow + -- (returns true if weapon's ammo was at the max. instead). + addammo = con._addammo, + addweapon = con._addweapon, + + stomp = con._pstomp, + + holdskey = function(p, keyname) + -- XXX: on invalid , error will point to this next line: + return player_holdskey(p.weapon._p, keyname) + end, + + has_weapon = function(p, weap) + return p.gotweaponbits:test(lsh(1,weap)) + end, + + give_weapon = function(p, weap) + p.gotweaponbits:set(lsh(1,weap)) + end, + + take_weapon = function(p, weap) + p.gotweaponbits:clear(lsh(1,weap)) + end, + + -- Give or take weapon, for implementation of CON's .gotweapon access. + _gt_weapon = function(p, weap, give_p) + if (give_p ~= 0) then + p:give_weapon(weap) + else + p:take_weapon(weap) + end + end, + + whack = function(p, no_return_to_center) + p.horiz = p.horiz + 64 + if (not no_return_to_center) then + p.return_to_center = 9 + end + local n = bit.arshift(128-band(ffiC.krand(),255), 1) + p.rotscrnang = n + p.look_ang = n + end, + + -- External, improved 'palfrom'. + -- : possibly fractional speed of tint fading, in pals.f decrements per gametic. + -- XXX: exposes internals. + -- : a value from -128 to 127, higher ones trump lower or equal ones + fadecol = function(p, fadefrac, r, g, b, speed, prio) + -- Validate inargs: clamp f,r,g,b to [0 .. 1] first and multiply by + -- 63 for the internal handling. + fadefrac = clamp0to1(fadefrac)*63 + -- NOTE: a fadefrac of now <1 is allowed, e.g. for clearing the tint. + r = clamp0to1(r)*63 + g = clamp0to1(g)*63 + b = clamp0to1(b)*63 + + if (speed ~= nil) then + check_number(speed) + -- Clamp to sensible values; the speed is stored in an int8_t + -- (see below). + speed = clamp(speed, 1/128, 127) + else + speed = 1 + end + + if (prio ~= nil) then + check_number(prio) + + if (not (prio >= -128 and prio < 127)) then + error("invalid argument #6 (priority): must be in [-128 .. 127]", 2) + end + else + prio = 0 + end + + -- Check if a currently active tint has higher priority. + if (p._pals.f > 0 and p._palsfadeprio > prio) then + return + end + + -- The passed tint can be applied now. + p:_palfrom(fadefrac, r, g, b) + p._palsfadeprio = prio + + -- Calculate .palsfade{speed,next} + if (speed >= 1) then + -- Will round to the nearest integer ("banker's + -- rounding"). NOTE: This is not correct for all numbers, see + -- http://blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1 + p._palsfadespeed = speed + 0.5 + p._palsfadenext = 0 + else + -- NOTE: Values that round to 0 have are equivalent behavior to + -- passing a of 1. + local negspeedrecip = -((1/speed) + 0.5) -- [-128.5 .. 1/127+0.5] + p._palsfadespeed = negspeedrecip + p._palsfadenext = negspeedrecip + end + end, + + -- INTERNAL and CON-only. + _palfrom = function(p, f, r,g,b) + local pals = p._pals + -- Assume that CON's palfrom starts with prio 0 and speed 0. + if (pals.f == 0 or p._palsfadeprio <= 0) then + pals.f = f + pals.r, pals.g, pals.b = r, g, b + p._palsfadespeed, p._palsfadenext = 0, 0 + end + end, +} + +local player_mt = { + __index = function(p, key) + if (player_methods[key] ~= nil) then + return player_methods[key] + elseif (key == "_input") then + return ffiC.g_player[p.weapon._p].sync[0] + else + -- Read access to protected player members. + return ffi.cast(player_ptr_ct, p)[0][key] + end + end, + + __newindex = function(p, key, val) + -- Write access to protected player members. + + local allowneg = DukePlayer_prot_allowneg[key] + assert(type(allowneg)=="boolean") + + if (allowneg==false or not (val == -1)) then + DukePlayer_prot_chkfunc[key](val) + end + ffi.cast(player_ptr_ct, p)[key] = val + end, +} + +ffi.metatype("DukePlayer_t", player_mt) + +local function GenProjectileSetFunc(Member, checkfunc) + return function(self, idx) + if (not (idx == -1)) then + checkfunc(idx) + end + ffi.cast(projectile_ptr_ct, self)[Member] = idx + end +end + +local projectile_mt = { + __index = { + set_spawns = GenProjectileSetFunc("spawns", check_tile_idx), + set_decal = GenProjectileSetFunc("decal", check_tile_idx), + set_trail = GenProjectileSetFunc("trail", check_tile_idx), + + set_sound = GenProjectileSetFunc("sound", check_sound_idx), + set_isound = GenProjectileSetFunc("isound", check_sound_idx), + set_bsound = GenProjectileSetFunc("bsound", check_sound_idx), + }, +} +ffi.metatype("projectile_t", projectile_mt) + +local user_defs_mt = { + __index = { + set_screen_size = function(ud, screen_size) + if (ud.screen_size ~= screen_size) then + ud.screen_size = screen_size + ffiC.G_UpdateScreenArea() + end + end, + + set_volume_number = function(ud, volume_number) + -- NOTE: allow volume_number==MAXVOLUMES. + if (not (volume_number==con_lang.MAXVOLUMES)) then + bcheck.volume_idx(volume_number) + end + ud.volume_number = volume_number + end, + + set_m_volume_number = function(ud, volume_number) + -- NOTE: allow volume_number==MAXVOLUMES. + if (not (volume_number==con_lang.MAXVOLUMES)) then + bcheck.volume_idx(volume_number) + end + ud.m_volume_number = volume_number + end, + + set_level_number = function(ud, level_number) + bcheck.level_idx(level_number) + ud.level_number = level_number + end, + }, +} +ffi.metatype("user_defs", user_defs_mt) + +--- CUSTOM "gv" VARIABLES +local camera_mt = { + -- TODO: "set position" method, which also updates the sectnum + __index = ffiC.g_camera, + __newindex = function(_, key, val) + if (key=="sect") then + check_sector_idx(val) + end + ffiC.g_camera[key] = val + end, +} + +gv_access.cam = setmtonce({}, camera_mt) +gv_access._ud = ffiC.ud + +-- Support for some CON global system gamevars. RETURN handled separately. +gv_access._csv = ffi.new "struct { int32_t LOTAG, HITAG, TEXTURE; }" + +gv_access.REND = defs_c.conststruct +{ + CLASSIC = 0, + POLYMOST = 3, + POLYMER = 4, +} + +gv_access.WEAPON = lprivate.WEAPON +gv_access.GET = lprivate.GET + +function gv_access._get_yxaspect() + return ffiC.yxaspect +end + +function gv_access._get_viewingrange() + return ffiC.viewingrange +end + +function gv_access._currentFramerate() + return ffiC.g_frameRate +end + +function gv_access._currentMenu() + return ffiC.g_currentMenu +end + +function gv_access._changeMenu(cm) + ffiC.Menu_Change(cm) +end + +function gv_access._set_guniqhudid(id) + local MAXUNIQHUDID = 256 -- KEEPINSYNC build.h + if (not (id >= 0 and id < MAXUNIQHUDID)) then + error("invalid unique HUD ID "..id) + end + ffiC.guniqhudid = id +end + +function gv_access.currentEpisode() + return ffiC.ud.volume_number + 1 +end + +function gv_access.currentLevel() + return ffiC.ud.level_number + 1 +end + +function gv_access.doQuake(gametics, snd) + ffiC.g_earthquakeTime = gametics + if (snd ~= nil) then + con._globalsound(ffiC.screenpeek, snd) + end +end + +-- Declare all con_lang.labels constants in the global FFI namespace. +for i=1,#con_lang.labels do + if (getmetatable(con_lang.labels[i]) ~= "noffiC") then + local strbuf = {"enum {"} + for label, val in pairs(con_lang.labels[i]) do + strbuf[#strbuf+1] = string.format("%s = %d,", label, val) + end + strbuf[#strbuf+1] = "};" + + ffi.cdef(table.concat(strbuf)) + end +end + + +---=== Set up restricted global environment ===--- + +local allowed_modules = { + coroutine=coroutine, bit=bit, table=table, math=math, string=string, + + os = { + clock = function() return gv_.gethiticks()*0.001 end, + }, + + randgen = randgen, + engine = require("engine"), + stat = require("stat"), + bitar = require("bitar"), + xmath = xmath, + fs = require("fs"), + + con = con, +} + +do + local ctype_cansave = {} + + -- Register as "serializeable" the type of cdata object . + local function reg_serializable_cv(v) + assert(type(v)=="cdata") + assert(type(v._serialize)=="function") + -- NOTE: tonumber() on a ctype cdata object gets its LuaJIT-internal + -- ID, the one that would be shown with tostring(), e.g. + -- ctype + ctype_cansave[tonumber(ffi.typeof(v))] = true + end + + function lprivate.cansave_cdata(v) + return type(v)=="cdata" and ctype_cansave[tonumber(ffi.typeof(v))] + end + + reg_serializable_cv(xmath.vec3()) + reg_serializable_cv(ivec3()) +end + +-- Protect base modules. +local function basemod_newidx() + error("modifying base module table forbidden", 2) +end + +for modname, themodule in pairs(allowed_modules) do + local mt = { + __index = themodule, + __newindex = basemod_newidx, + } + + allowed_modules[modname] = setmtonce({}, mt) +end + + +---=== Module stuff ===--- + +local package_loaded = {} -- [ ] = false/true/table +local modname_stack = {} -- [ ]=string +local module_gamevars = {} -- [ ] = { , , ... } +local module_gvlocali = {} -- [ ] = { , } +local module_thread = {} -- [ ] = + +local function getcurmodname(thisfuncname) + if (#modname_stack == 0) then + error("'"..thisfuncname.."' must be called at the top level of a require'd file", 3) + -- ... as opposed to "at runtime". + end + + return modname_stack[#modname_stack] +end + + +local function errorf(level, fmt, ...) + local errmsg = string.format(fmt, ...) + error(errmsg, level+1) +end + +local function readintostr_mod(fn) + -- TODO: g_loadFromGroupOnly? + local fd = ffiC.kopen4loadfrommod(fn, 0) + if (fd < 0) then + return nil + end + + local ret = defs_c.readintostr(fd) + ffiC.kclose(fd) + return ret +end + + +local debug = require("debug") + +-- Get the number of active locals in the function that calls the function +-- calling this one. +local function getnumlocals(l) + -- 200 is the max. number of locals at one level + for i=1,200 do + -- level: + -- 0 is debug.getlocal() itself. + -- 1 is this function (getnumlocals). + -- 2 is the function calling getnumlocals() + -- 3 is the function calling that one. + if (debug.getlocal(3, i) == nil) then + return i-1 + end + end +end + +local function error_on_nil_read(_, varname) + error("attempt to read nil variable '"..varname.."'", 2) +end + +local required_module_mt = { + __index = error_on_nil_read, + + __newindex = function() + error("modifying module table forbidden", 2) + end, + + __metatable = true, +} + +-- Will contain a function to restore gamevars when running from savegame +-- restoration. See SAVEFUNC_ARGS for its arguments. +local g_restorefunc = nil + +-- Local gamevar restoration function run from +-- our_require('end_gamevars') <- [user module]. +local function restore_local(li, lval) + -- level: + -- 0 is getlocal() itself. + -- 1 is this function (restore_local). + -- 2 is the function calling restore_local(), the savecode. + -- 3 is the function calling the savecode, our_require. + -- 4 is the function calling our_require, the module function. + if (ffiC._DEBUG_LUNATIC ~= 0) then + printf("Restoring index #%d (%s) with value %s", + li, debug.getlocal(4, li), tostring(lval)) + end + + assert(debug.setlocal(4, li, lval)) +end + +-- The "require" function accessible to Lunatic code. +-- Base modules in allowed_modules are wrapped so that they cannot be +-- modified, user modules are searched in the EDuke32 search +-- path. Also, our require +-- * never messes with the global environment, it only returns the module. +-- * allows passing varargs beyond the name to the module. +local function our_require(modname, ...) + local ERRLEV = 5 + + -- Check module name is valid first. + -- TODO: restrict valid names? + if (type(modname) ~= "string" or #modname==0) then + error("module name must be a nonempty string", 2) + end + + -- For _LUNATIC_DBG + if (modname:match("^_LUNATIC") and ffiC._DEBUG_LUNATIC == 0) then + return nil + end + + -- Handle the section between module() and require("end_gamevars"). + if (modname == "end_gamevars") then + local thismodname = getcurmodname("require") + + if (module_gamevars[thismodname] ~= nil) then + error("\"require 'end_gamevars'\" must be called at most once per require'd file", 2) + end + + local gvnames = {} + + for name in pairs(getfenv(2)) do + gvnames[#gvnames+1] = name + if (ffiC._DEBUG_LUNATIC ~= 0) then + printf("MODULE %s GAMEVAR %s", thismodname, name) + end + end + + module_gamevars[thismodname] = gvnames + local gvmodi = module_gvlocali[thismodname] + gvmodi[2] = getnumlocals() + + if (ffiC._DEBUG_LUNATIC ~= 0) then + local numlocals = gvmodi[2]-gvmodi[1]+1 + if (numlocals > 0) then + printf("Module '%s' has %d locals, index %d to %d", + thismodname, numlocals, gvmodi[1], gvmodi[2]) + end + end + + -- Potentially restore gamevars. + if (g_restorefunc) then + local modtab = package_loaded[thismodname] + assert(type(modtab)=="table") + -- SAVEFUNC_ARGS. + g_restorefunc(thismodname, modtab, restore_local) + end + + -- Return whether we're NOT running from a savegame restore in the + -- second outarg. (Lunatic-private!) + return nil, (g_restorefunc==nil) + end + + -- See whether it's a base module name. + if (allowed_modules[modname] ~= nil) then + return allowed_modules[modname] + end + + --- Search user modules... + + if (modname:find("[/\\]")) then + error("Module name must not contain directory separators", ERRLEV-1) + end + -- Instead, dots are translated to directory separators. For EDuke32's + -- virtual file system, this is always a forward slash. Keep the original + -- module name for passing to the module function. + local omodname = modname + modname = modname:gsub("%.", "/") + + local omod = package_loaded[modname] + if (omod ~= nil) then + if (omod==false) then + error("Loop while loading modules", ERRLEV-1) + end + + -- already loaded + assert(omod==true or type(omod)=="table") + return omod + end + + local modfn = modname .. ".lua" + local str = readintostr_mod(modfn) + if (str == nil) then + errorf(ERRLEV-1, "Couldn't open file \"%s\"", modfn) + end + + -- Implant code that yields the module thread just before it would return + -- otherwise. + str = str.."\nrequire('coroutine').yield()" + + local modfunc, errmsg = loadstring(str, modfn) + if (modfunc == nil) then + errorf(ERRLEV-1, "Couldn't load \"%s\": %s", modname, errmsg) + end + + package_loaded[modname] = false -- 'not yet loaded' + table.insert(modname_stack, modname) + + -- Run the module code in a separate Lua thread! + local modthread = coroutine.create(modfunc) + local ok, retval = coroutine.resume(modthread, omodname, ...) + + if (not ok) then + errorf(ERRLEV-1, "Failed running \"%s\": %s\n%s", modname, + retval, debug.traceback(modthread)) + end + + table.remove(modname_stack) + + local modtab = package_loaded[modname] + + if (type(modtab) ~= "table") then + -- The module didn't call our 'module'. Check if it returned a table. + -- In that case, the coroutine has finished its main function and has + -- not reached our implanted 'yield'. + if (coroutine.status(modthread)=="dead" and type(retval)=="table") then + modtab = retval + package_loaded[modname] = modtab + else + package_loaded[modname] = true + end + end + + if (type(modtab) == "table") then + -- Protect module table in any case (i.e. either if the module used our + -- 'module' or if it returned a table). + setmetatable(modtab, required_module_mt) + end + + local gvmodi = module_gvlocali[modname] + + if (gvmodi and gvmodi[2] and gvmodi[2]>=gvmodi[1]) then + if (coroutine.status(modthread)=="suspended") then + -- Save off the suspended thread so that we may get its locals later on. + -- It is never resumed, but only ever used for debug.getlocal(). + module_thread[modname] = modthread + + if (ffiC._DEBUG_LUNATIC ~= 0) then + printf("Keeping coroutine for module \"%s\"", modname) + end + end + end + + return modtab +end + + +local module_mt = { + __index = error_on_nil_read, +} + +-- Our 'module' replacement doesn't get the module name from the function args +-- since a malicious user could remove other loaded modules this way. +-- Also, our 'module' takes no varargs ("option functions" in Lua). +-- TODO: make transactional? +local function our_module() + if (#modname_stack == 0) then + error("'module' must be called at the top level of a require'd file", 2) + -- ... as opposed to "at runtime". + end + + local modname = getcurmodname("module") + + if (package_loaded[modname]) then + error("'module' must be called at most once per require'd file", 2) + end + + local M = setmetatable({}, module_mt) + package_loaded[modname] = M + -- change the environment of the function which called us: + setfenv(2, M) + + module_gvlocali[modname] = { getnumlocals()+1 } +end + +-- overridden 'error' that always passes a string to the base 'error' +local function our_error(errmsg, level) + if (type(errmsg) ~= "string") then + error("error using 'error': error message must be a string", 2) + end + + if (level) then + if (type(level) ~= "number") then + error("error using 'error': error level must be a number", 2) + end + + error(errmsg, level==0 and 0 or level+1) + end + + error(errmsg, 2) +end + + +-- _G tweaks -- pull in only 'safe' stuff +local G_ = {} -- our soon-to-be global environment + +G_.assert = assert +G_.error = our_error +G_.ipairs = ipairs +G_.pairs = pairs +G_.pcall = pcall +G_.print = print +G_.module = our_module +G_.next = next +G_.require = our_require +G_.select = select +G_.tostring = tostring +G_.tonumber = tonumber +G_.type = type +G_.unpack = unpack +G_.xpcall = xpcall +G_._VERSION = _VERSION + +-- Available through our 'require': +-- bit, coroutine, math, string, table + +-- Not available: +-- collectgarbage, debug, dofile, gcinfo (DEPRECATED), getfenv, getmetatable, +-- jit, load, loadfile, loadstring, newproxy (NOT STD?), package, rawequal, +-- rawget, rawset, setfenv, setmetatable + +G_._G = G_ + +-- Chain together two functions taking 3 input args. +local function chain_func3(func1, func2) + if (func1==nil or func2==nil) then + return assert(func1 or func2) + end + + -- Return a function that runs first and then tail-calls . + return function(aci, pli, dist) + func1(aci, pli, dist) + return func2(aci, pli, dist) + end +end + +-- Determines the last numeric index of a table using *pairs*, so that in +-- arg-lists with "holes" (e.g. {1, 2, nil, function() end}) are handled +-- properly. +local function ourmaxn(tab) + local maxi = 0 + for i in pairs(tab) do + if (type(i)=="number") then + maxi = math.max(i, maxi) + end + end + assert(tab[maxi] ~= nil) + return maxi +end + +-- Running for the very first time? +local g_firstRun = (ffiC.g_elCONSize == 0) + +-- Actor functions, saved for actor chaining +local actor_funcs = {} +-- Event functions, saved for event chaining +local event_funcs = {} + +-- Per-actor sprite animation callbacks +local animsprite_funcs = {} + +local gameactor_internal = gameactor_internal -- included in lunatic.c +local gameevent_internal = gameevent_internal -- included in lunatic.c + +local function animate_all_sprites() + for i=0,ffiC.spritesortcnt-1 do + local tspr = ffiC.tsprite[i] + + if (tspr.owner < ffiC.MAXSPRITES+0ULL) then + local spr = tspr:getspr() + local animfunc = animsprite_funcs[spr.picnum] + + if (animfunc) then + animfunc(tspr) + end + end + end +end + + +local function check_arg_number(name, argpos, val) + if (type(val) ~= "number") then + errorf(3, "invalid '%s' argument (#%d) to gameactor: must be a number", name, argpos) + end +end + +-- gameactor{tilenum [, flags [, strength [, action [, move [, movflags]]]]], func} +-- Every arg may be positional OR key=val (with the name indicated above as key), +-- but not both. +local function our_gameactor(args) + bcheck.top_level("gameactor") + + if (type(args)~="table") then + error("invalid gameactor call: must be passed a table") + end + + local tilenum = args[1] + if (type(tilenum) ~= "number") then + error("invalid argument #1 to gameactor: must be a number", 2) + end + if (not (tilenum >= 0 and tilenum < ffiC.MAXTILES)) then + error("invalid argument #1 to gameactor: must be a tile number [0..gv.MAXTILES-1]", 2) + end + + local lastargi = ourmaxn(args) + local func = args[lastargi] + if (type(func) ~= "function") then + func = args.func + lastargi = 1/0 + end + if (type(func) ~= "function") then + error("invalid gameactor call: must provide a function with last numeric arg or .func", 2) + end + + local flags = (lastargi > 2 and args[2]) or args.flags or 0 + check_arg_number("flags", 2, flags) + + local AF = actor.FLAGS + local chainflags = band(flags, AF._CHAIN_MASK_ACTOR) + flags = band(flags, BNOT.CHAIN_MASK_ACTOR) + + if (chainflags == 0) then + -- Default chaining behavior: don't, replace the old actor instead. + chainflags = AF.replace + elseif (band(chainflags, chainflags-1) ~= 0) then + error("invalid chaining control flags to gameactor", 2) + end + + local replacep = (chainflags==AF.replace) + if (not replacep and not actor_funcs[tilenum]) then + error("attempt to chain code to nonexistent actor tile "..tilenum, 2) + end + + local flags_rbits = band(flags, BNOT.USER_MASK) + if (flags_rbits ~= 0) then + error("invalid 'flags' argument (#2) to gameactor: must not set reserved bits (0x" + ..(bit.tohex(flags_rbits))..")", 2) + end + + local strength = ((lastargi > 3 and args[3]) or args.strength) or (replacep and 0 or nil) + if (replacep or strength~=nil) then + check_arg_number("strength", 3, strength) + end + + local act = ((lastargi > 4 and args[4]) or args.action) or (replacep and literal_act[0] or nil) + if (replacep or act ~= nil) then + if (type(act)=="number" and (act==0 or act==1)) then + act = literal_act[act] + elseif (not ffi.istype(con_action_ct, act)) then + error("invalid 'action' argument (#4) to gameactor: must be an action", 2) + end + end + + local mov = ((lastargi > 5 and args[5]) or args.move) or (replacep and literal_mov[0] or nil) + if (replacep or mov ~= nil) then + if (type(mov)=="number" and (mov==0 or mov==1)) then + mov = literal_mov[mov] + elseif (not ffi.istype(con_move_ct, mov)) then + error("invalid 'move' argument (#5) to gameactor: must be a move", 2) + end + end + + local movflags = ((lastargi > 6 and args[6]) or args.movflags) or (replacep and 0 or nil) + if (replacep or movflags ~= nil) then + check_arg_number("movflags", 6, movflags) + end + + -- Register a potentially passed drawn sprite animation callback function. + -- TODO: allow registering without main actor execution callback. + local animfunc = args.animate + if (animfunc ~= nil) then + if (type(animfunc) ~= "function") then + error("invalid 'animate' argument to gameactor: must be a function", 2) + end + + animsprite_funcs[tilenum] = replacep and func + or (chainflags==AF.chain_beg) and chain_func3(animfunc, animsprite_funcs[tilenum]) + or (chainflags==AF.chain_end) and chain_func3(animsprite_funcs[tilenum], animfunc) + or assert(false) + + -- Register our EVENT_ANIMATEALLSPRITES only now so that it is not + -- called if there are no 'animate' definitions. + gameevent_internal(97, animate_all_sprites) -- EVENT_ANIMATEALLSPRITES + end + + -- All good, bitwise-OR the tile bits and register the actor! + ffiC.g_tile[tilenum]._flags = bit.bor(ffiC.g_tile[tilenum]._flags, flags) + + local newfunc = replacep and func + or (chainflags==AF.chain_beg) and chain_func3(func, actor_funcs[tilenum]) + or (chainflags==AF.chain_end) and chain_func3(actor_funcs[tilenum], func) + or assert(false) + + gameactor_internal(tilenum, strength, act, mov, movflags, newfunc) + actor_funcs[tilenum] = newfunc +end + + +-- gameevent{ [, flags], } +local function our_gameevent(args) + bcheck.top_level("gameevent") + + if (type(args)~="table") then + error("invalid gameevent call: must be passed a table") + end + + local event = args[1] + + if (type(event) == "string") then + if (event:sub(1,6) ~= "EVENT_") then + event = "EVENT_"..event + end + local eventidx = con_lang.EVENT[event] + if (eventidx == nil) then + errorf(2, "gameevent: invalid event label %q", event) + end + event = eventidx + end + if (type(event) ~= "number") then + error("invalid argument #1 to gameevent: must be a number or event label", 2) + end + if (not (event >= 0 and event < con_lang.MAXEVENTS)) then + error("invalid argument #1 to gameevent: must be an event number (0 .. MAXEVENTS-1)", 2) + end + + local AF = actor.FLAGS + + -- Kind of CODEDUP from our_gameactor. + local lastargi = ourmaxn(args) + local func = args[lastargi] + if (type(func) ~= "function") then + func = args.func + lastargi = 1/0 + end + if (type(func) ~= "function") then + error("invalid gameevent call: must provide a function with last numeric arg or .func", 2) + end + + -- Event chaining: in Lunatic, chaining at the *end* is the default. + local flags = (lastargi > 2 and args[2]) or args.flags or AF.chain_end + if (type(flags) ~= "number") then + error("invalid 'flags' argument (#2) to gameevent: must be a number", 2) + end + + if (band(flags, BNOT.CHAIN_MASK_EVENT) ~= 0) then + error("invalid 'flags' argument to gameevent: must not set reserved bits", 2) + end + + local newfunc = (flags==AF.replace) and func + or (flags==AF.chain_beg) and chain_func3(func, event_funcs[event]) + or (flags==AF.chain_end) and chain_func3(event_funcs[event], func) + or assert(false) + + gameevent_internal(event, newfunc) + event_funcs[event] = newfunc +end + +--- non-default data and functions +G_.gameevent = our_gameevent +G_.gameactor = our_gameactor +-- These come from above: +G_.player = player +G_.actor = actor +G_.projectile = projectile +G_.g_tile = g_tile + +G_.LUNATIC_FIRST_TIME = (ffiC.g_elFirstTime ~= 0) + +-- A table that can be used for temporary data when debugging from the OSD. +G_.d = {} + + +---=== Lunatic translator setup ===--- + +read_into_string = readintostr_mod -- for lunacon +local lunacon = require("lunacon") + +local concode, lineinfo + +--- Get Lua code for CON (+ mutator) code. +if (g_firstRun) then + -- Compiling CON for the first time. + local confn = { ffi.string(ffiC.G_ConFile()) } + + local nummods = ffiC.g_scriptModulesNum + if (nummods > 0) then + assert(ffiC.g_scriptModules ~= nil) + + for i=1,nummods do + confn[i+1] = ffi.string(ffiC.g_scriptModules[i-1]) + end + end + + concode, lineinfo = lunacon.compile(confn) + + if (concode == nil) then + error("Failure compiling CON code, exiting.", 0) + end + assert(lineinfo) + + -- Back up the translated code on the C side. + assert(type(concode)=="string") + ffiC.El_SetCON(concode) +else + -- CON was already compiled. + concode = ffi.string(ffiC.g_elCON, ffiC.g_elCONSize) + lineinfo = lunacon.get_lineinfo(concode) +end + +if (ffiC._DEBUG_LUNATIC ~= 0) then + -- XXX: lineinfo of 2nd up time has one line less. + printf("CON line info has %d Lua lines", #lineinfo.llines) +end + +do + -- Translate one Lua line number to a CON file name + line number + local function transline(lnum) + return string.format("%s:%d", lineinfo:getfline(tonumber(lnum))) + end + + -- Register the function that tweaks an error message, looking out for + -- errors from CON and translating the line numbers. + local function tweak_traceback_msg(errmsg) + return errmsg:gsub('%[string "CON"%]:([0-9]+)', transline) + end + + lprivate.tweak_traceback_msg = tweak_traceback_msg + + set_tweak_traceback_internal(tweak_traceback_msg) +end + +-- XXX: May still be require'd from user code, we don't want that (at least not +-- under this name). +local CON_MODULE_NAME = "_CON\0" + +-- Set up Lunatic gamevar serialization. +do + local savegame = require("lunasave") + + -- Callback for: const char *(int32_t *slenptr, int32_t levelnum); + ffiC.El_SerializeGamevars = function(slenptr, levelnum) + local sb = savegame.savebuffer() + + -- Module name, module table, restore_local. See SAVEFUNC_ARGS. + sb:addraw("local N,M,F=...") + -- A local to temporarily hold module locals. + sb:addraw("local L") + + -- XXX: System gamevars? Most of them ought to be saved with C data. + for modname, modvars in pairs(module_gamevars) do + local isCON = (modname==CON_MODULE_NAME) + + sb:startmod(modname) + + -- Handle global gamevars first. + for i=1,#modvars do + local varname = modvars[i] + local excludedVars = isCON and varname=="_V" and + package_loaded[CON_MODULE_NAME]._V._IS_NORESET_GAMEVAR or nil + + -- Serialize gamevar named 'varname' from module named 'modname'. + -- XXX: May error. This will terminate EDuke32 since this callback + -- is run unprotected. + if (sb:add("M."..varname, package_loaded[modname][varname], excludedVars)) then + -- We couldn't serialize that gamevar. + slenptr[0] = -1 + -- Signal which gamevar that was. + return (isCON and " " or modname).."."..varname + end + end + + local modthread = module_thread[modname] + + if (modthread) then + -- Handle local gamevars. + local gvmodi = module_gvlocali[modname] + + for li=gvmodi[1],gvmodi[2] do + -- Serialize local with index . Get its value first. + local lname, lval = debug.getlocal(modthread, 1, li) + + if (sb:add("L", lval)) then + -- We couldn't serialize that gamevar. + slenptr[0] = -1 + return "local "..modname.."."..lname + end + + -- Emit code to call restore_local. + sb:addrawf("F(%d,L)", li) + end + end + + sb:endmod() + end + + -- Get the whole code as a string. + local savecode = sb:getcode() + + if (ffiC._DEBUG_LUNATIC ~= 0) then + -- Dump the code if Lunatic debugging is enabled (-Lopts=diag) and + -- there is a LUNATIC_SAVECODE_FN variable in the environment. + local os = require("os") + local fn = os.getenv("LUNATIC_SAVECODE_FN") + + if (fn ~= nil) then + if (levelnum >= 0) then + fn = fn .. levelnum + end + + local io = require("io") + local f = io.open(fn, "w") + + if (f ~= nil) then + f:write(savecode) + f:close() + printf("Wrote Lunatic gamevar restoration code to \"%s\".", fn) + end + end + end + + -- Set the size of the code and return the code to C. + slenptr[0] = #savecode + return savecode + end +end + +-- change the environment of this chunk to the table G_ +-- NOTE: all references to global variables from this point on +-- (also in functions created after this point) refer to G_ ! +setfenv(1, G_) + +-- Print keys and values of a table. +local function printkv(label, table) + print("========== Keys and values of "..label.." ("..tostring(table)..")") + for k,v in pairs(table) do + print(k .. ": " .. tostring(v)) + end + print("----------") +end + +--printkv('_G AFTER SETFENV', _G) + + +---=== Restricted access to C variables from Lunatic ===--- + +-- error(..., 2) is to blame the caller and get its line numbers + +-- Map of 'gv' variable names to C ones. +local varnameMap = { + gametic = "g_moveThingsCount", + RETURN = "g_RETURN", + _sessionVar = "g_elSessionVar", +} + +gv_access.gametic = true +gv_access.RETURN = true +gv_access._sessionVar = true + +local tmpmt = { + __index = function(_, key) + if (gv_access[key] == nil) then + -- Read access allowed. + return ffiC[key] + elseif (type(gv_access[key])~="boolean") then + -- Overridden 'gv' pseudo-member... + return gv_access[key] + elseif (varnameMap[key]) then + -- Variable known under a different name on the C side. + return ffiC[varnameMap[key]] + end + error("access forbidden", 2) + end, + + __newindex = function(_, key, val) + if (gv_access[key] == nil) then + -- Variables declared 'const' are handled by LuaJIT. + ffiC[key] = val + elseif (varnameMap[key]) then + ffiC[varnameMap[key]] = val + else + error("write access forbidden", 2) + end + end, +} +gv = setmtonce(gv_, tmpmt) + +-- This will create 'sprite', 'wall', etc. HERE, i.e. in the environment of this chunk +defs_c.create_globals(_G) + +-- REMOVE this for release +if (ffiC._DEBUG_LUNATIC ~= 0) then + local DBG_ = {} + DBG_.debug = require("debug") + DBG_.printkv = printkv + DBG_.loadstring = loadstring + DBG_.oom = function() + local s = "1" + for i=1,math.huge do + s = s..s + end + end + + -- Test reading from all struct members + DBG_.testmembread = function() + for _1, sac in pairs { con_lang.StructAccessCode, con_lang.StructAccessCode2 } do + for what, labels in pairs(sac) do + if (what~="tspr") then + for _3, membaccode in pairs(labels) do + if (type(membaccode)=="table") then + membaccode = membaccode[1] + end + if (membaccode) then + local codestr = [[ +do + local _bit,_math=require'bit',require'math' + local _con=require'con' + local _band,_gv=_bit.band,gv +local tmp=]].. + membaccode:gsub("^_gud%(_pli%)", "_con._get_userdef(0)"):gsub("%%s","0").." end" + local code = assert(loadstring(codestr)) + code() + end + end + end + end + end + end + + allowed_modules._LUNATIC_DBG = DBG_ +end + +---=== Finishing environment setup ===--- + +--printkv('_G AFTER DECLS', _G) + +local index_error_mt = { + __index = function(_, key) + error("attempt to read undeclared variable '"..key.."'", 2) + end, + + __metatable = true, +} + +-- PiL 14.2 continued +-- We need this at the end because we were previously doing just that! +setmetatable(G_, index_error_mt) + +local global_mt = { + __index = G_, + + __newindex = function() + error("attempt to write into the global environment") + end, + + __metatable = true, +} + +-- Change the environment of the running Lua thread so that everything +-- what we've set up will be available when this chunk is left. +-- In particular, we need the globals defined after setting this chunk's +-- environment earlier. +setfenv(0, setmetatable({}, global_mt)) + +do + -- If we're running from a savegame restoration, create the restoration + -- function. Must be here, after the above setfenv(), because it must be + -- created in this protected ('user') context! + local cstr = ffiC.g_elSavecode + if (cstr~=nil) then + local restorecode = ffi.string(cstr) + ffiC.El_FreeSaveCode() + + g_restorefunc = assert(loadstring(restorecode)) + end +end + +-- Restore CON gamevars from loadmapstate. +-- TODO: non-user-defined gamevars. +-- TODO: savegames. +-- int32_t El_RestoreGamevars(const char *) +ffiC.El_RestoreGamevars = function(savecode) + savecode = ffi.string(savecode) + local restorefunc = assert(loadstring(savecode)) + restorefunc(CON_MODULE_NAME, package_loaded[CON_MODULE_NAME], nil) + return 0 +end + +-- Run the CON code translated into Lua. +if (assert(concode ~= nil)) then + local confunc, conerrmsg = loadstring(concode, "CON") + if (confunc == nil) then + error("Failure loading translated CON code: "..conerrmsg, 0) + end + + -- Emulate our 'require' for the CON module when running it, for + -- our_module() which is called from the generated Lua code. + table.insert(modname_stack, CON_MODULE_NAME) + local conlabels, conaction, conmove, conai = confunc() + table.remove(modname_stack) + + local function protect_con_table(tab) + -- NOTE: Some of our code specifically excepts the name tables to be + -- indexable with nonexistent keys. See e.g. control.lua: _A_SpawnGlass() + if (ffiC._LUNATIC_STRICT ~= 0) then + tab = setmetatable(tab, index_error_mt) + end + return setmtonce({}, { __index=tab, __newindex=basemod_newidx }) + end + + -- Set up CON.* modules, providing access to diffenrent kinds of labels + -- defined in CON from Lua. See CONCODE_RETURN in lunacon.lua. + allowed_modules["CON.DEFS"] = protect_con_table(conlabels) + allowed_modules["CON.ACTION"] = protect_con_table(conaction) + allowed_modules["CON.MOVE"] = protect_con_table(conmove) + allowed_modules["CON.AI"] = protect_con_table(conai) + + -- Propagate potentially remapped defines to the control module. + con._setuplabels(conlabels) + + local function getLabelValue(str, doupper) + return conlabels[doupper and string.upper(str) or str] + end + + ffiC.El_GetLabelValue = function(label) + local str = ffi.string(label) + local ok, val = pcall(getLabelValue, str, false) + if (not ok or type(val)~="number") then + ok, val = pcall(getLabelValue, str, true) + end + return (ok and type(val)=="number") and val or bit.tobit(0x80000000) + end +end + +-- When starting a map, load Lua modules given on the command line. +if (not g_firstRun) then + local i=0 + while (ffiC.g_elModules[i] ~= nil) do + -- Get the module name and strip the trailing extension. + local modname = ffi.string(ffiC.g_elModules[i]):gsub("%.lua$","") + + if (modname:find("%.")) then + -- Because they will be replaced by dirseps in our_require(). + error("Dots are not permitted in module names", 0) + end + -- Allow forward slashes in module names from the cmdline. + our_require((modname:gsub("%.lua$",""):gsub("/","."))) + + i = i+1 + end + + ffiC.g_elFirstTime = 0 +end + +if (g_restorefunc) then + -- Clear it so that it may be garbage-collected. + g_restorefunc = nil +end diff --git a/source/rr/src/lunatic/bcarray.lua b/source/rr/src/lunatic/bcarray.lua new file mode 100644 index 000000000..6d78e33db --- /dev/null +++ b/source/rr/src/lunatic/bcarray.lua @@ -0,0 +1,124 @@ +-- Implementation of a bound-checked array type factory for LuaJIT 2.0 or later. +-- +-- Usage example: +-- +-- > bcarray.new("int8_t", 3, "test", "three_pigs") +-- > a = ffi.new("struct { int32_t a; three_pigs p; int16_t b; }") +-- > =ffi.sizeof(a) --> 12 +-- > b = ffi.new("__attribute__((packed)) struct { int32_t a; three_pigs p; int16_t b; }") +-- > =ffi.sizeof(b) --> 9 + +local ffi = require("ffi") + +local string = require("string") +local table = require("table") + +local assert = assert +local error = error +local pairs = pairs +local type = type + + +local bcarray = {} + + +-- Generate C decl for a sequence of const struct members. +-- For example, for 4 elements, +-- "const $ _r1, _f2, _u3, _n4;" +local function flatten_array(nelts, rng) + local strtab = { "$ " } + + if (rng and rng.getu32==nil) then + assert(type(rng)=="table") + + for i=1,#rng do + strtab[i+1] = rng[i]..((i<#rng) and "," or ";") + end + else + for i=1,nelts do + local ch = 97 + (rng and (rng:getu32() % 25) or 0) -- 'a'..'z' + strtab[i+1] = string.format("_%c%x%s", ch, i, (i : Number of elements in array (small number) +-- : The name to be shown for the derived type in error messages +-- : If non-nil, the name under which the derived type is typedef'd +-- : Random generator state + method :getu32(). If nil, then members are +-- named _a1, _a2, ... +-- It also may be a table containing member names at numeric indices 1..#rng. +-- : A table containing functions __index and/or __newindex. They are +-- called first and the bound-checking ones are tail-called then. If the +-- custom __index one returns something, it is returned by the composite one. +function bcarray.new(basetype, numelts, showname, typename, rng, mtadd) + local eltptr_t = ffi.typeof("$ *", ffi.typeof(basetype)) + + local mt = { + __index = function(ar, idx) + if (not (idx >= 0 and idx < numelts)) then + error("out-of-bounds "..showname.." read access", 2) + end + return ffi.cast(eltptr_t, ar)[idx] + end, + + -- NOTE: this function will be dead code if the prefixed __newindex + -- errors out unconditionally or the bcarray is declared 'const'. + __newindex = function(ar, idx, val) + if (not (idx >= 0 and idx < numelts)) then + error("out-of-bounds "..showname.." write access", 2) + end + ffi.cast(eltptr_t, ar)[idx] = val + end, + } + + if (mtadd ~= nil) then + local curindexf, curnewindexf = mt.__index, mt.__newindex + local addindexf, addnewindexf = mtadd.__index, mtadd.__newindex + + if (addindexf) then + -- Additional __index metamethod given. + mt.__index = function(ar, idx) + local sth = addindexf(ar, idx) + if (sth ~= nil) then + return sth + end + return curindexf(ar, idx) + end + end + + if (addnewindexf) then + -- Additional __newindex metamethod given. + mt.__newindex = function(ar, idx, val) + addnewindexf(ar, idx, val) + return curnewindexf(ar, idx, val) + end + end + end + + local cdeclstr = "struct {"..flatten_array(numelts, rng).."}" + local bcarray_t = ffi.typeof(cdeclstr, ffi.typeof(basetype)); + + bcarray_t = ffi.metatype(bcarray_t, mt) + if (not (rng and rng.getu32==nil)) then + -- When passing a member name table, it is allowed to have a different + -- number of named members than array elements. + assert(ffi.sizeof(bcarray_t) == ffi.sizeof(basetype)*numelts) + end + + if (typename ~= nil) then + -- Register the type name in the global namespace. + assert(type(typename)=="string") + ffi.cdef("typedef $ $;", bcarray_t, typename) + end + + return bcarray_t +end + + +return bcarray diff --git a/source/rr/src/lunatic/bcheck.lua b/source/rr/src/lunatic/bcheck.lua new file mode 100644 index 000000000..55690df45 --- /dev/null +++ b/source/rr/src/lunatic/bcheck.lua @@ -0,0 +1,126 @@ +-- Bound-checking functions for engine and game "things". + +local ffiC = require("ffi").C +local type = type +local error = error + +local bcheck = {} + +--== ENGINE ==-- + +function bcheck.sector_idx(sectnum) + if (not (sectnum >= 0 and sectnum < ffiC.numsectors)) then + error("invalid sector number "..sectnum, 3) + end +end + +function bcheck.wall_idx(wallnum) + if (not (wallnum >= 0 and wallnum < ffiC.numwalls)) then + error("invalid wall number "..wallnum, 3) + end +end + +-- TODO: Provide another function that also checks whether the sprite exists in +-- the game world (statnum != MAXSTATUS). +function bcheck.sprite_idx(spritenum) +-- if (not (spritenum >= 0 and spritenum < ffiC.MAXSPRITES)) then + if (not (spritenum >= 0 and spritenum < ffiC.MAXSPRITES)) then + error("invalid sprite number "..spritenum, 3) + end +end + +function bcheck.tile_idx(tilenum) + if (not (tilenum >= 0 and tilenum < ffiC.MAXTILES)) then + error("invalid tile number "..tilenum, 3) + end +end + + +--== HELPERS ==-- + +function bcheck.number(val, errlev) + if (type(val)~="number" or val~=val) then + error("invalid argument: must be a non-NaN number", errlev or 3) + end +end + +function bcheck.type(val, typestr, errlev) + if (type(val)~=typestr) then + error("invalid argument: must be a "..typestr, errlev or 3) + end +end + + +--== GAME ==-- +if (ffiC.LUNATIC_CLIENT == ffiC.LUNATIC_CLIENT_MAPSTER32) then + return bcheck +end + +local con_lang = require("con_lang") + +function bcheck.player_idx(snum) + if (not (snum >= 0 and snum < ffiC.g_mostConcurrentPlayers)) then + error("invalid player number "..snum, 3) + end +end + +function bcheck.sound_idx(sndidx) + if (not (sndidx >= 0 and sndidx < con_lang.MAXSOUNDS)) then + error("invalid sound number "..sndidx, 3) + end +end + +function bcheck.weapon_idx(weap) + if (not (weap >= 0 and weap < ffiC.MAX_WEAPONS)) then + error("Invalid weapon ID "..weap, 3) + end +end + +function bcheck.inventory_idx(inv) + if (not (inv >= 0 and inv < ffiC.GET_MAX)) then + error("Invalid inventory ID "..inv, 3) + end +end + +function bcheck.volume_idx(volume) + if (not (volume >= 0 and volume < con_lang.MAXVOLUMES)) then + error("invalid volume number "..volume, 3) + end +end + +function bcheck.level_idx(level) + if (not (level >= 0 and level < con_lang.MAXLEVELS)) then + error("invalid level number "..level, 3) + end +end + +function bcheck.linear_map_idx(idx) + if (not (idx >= 0 and idx <= con_lang.MAXLEVELS * con_lang.MAXVOLUMES)) then + error("invalid linear map index "..idx, 3) + end +end + +function bcheck.quote_idx(qnum, onlyidx) + if (not (qnum >= 0 and qnum < con_lang.MAXQUOTES)) then + error("invalid quote number "..qnum, 3) + end + + local cstr = ffiC.apStrings[qnum] + if (onlyidx) then + return cstr + end + + if (cstr == nil) then + error("null quote "..qnum, 3) + end + + return cstr +end + +function bcheck.top_level(funcname, errlev) + if (ffiC.g_elCallDepth > 0) then + error("Invalid use of "..funcname..": must be called from top level", errlev or 3) + end +end + +return bcheck diff --git a/source/rr/src/lunatic/bitar.lua b/source/rr/src/lunatic/bitar.lua new file mode 100644 index 000000000..f429a8bd8 --- /dev/null +++ b/source/rr/src/lunatic/bitar.lua @@ -0,0 +1,293 @@ + +-- "Bit array" module based on LuaJIT's BitOp. + +local bit = require "bit" +local math = require "math" + +local ffi = require "ffi" + +local assert = assert +local error = error +local type = type + +local tostring = tostring + + +module(...) + + +local bitar_ct = ffi.typeof[[ +struct { + const double maxbidx; // last permissible bit index + const double maxidx; // last permissible int32_t index + const intptr_t arptr; // address of the int32_t array +}]] +local ptr_to_int = ffi.typeof("int32_t *") + +local anchor = {} + +-- population count of a nibble +local nibpop = ffi.new("double [?]", 16, + { 0, 1, 1, 2, 1, 2, 2, 3, + 1, 2, 2, 3, 2, 3, 3, 4 }) +-- ...and of a byte +local bytepop = ffi.new("double [?]", 256) +for i=0,255 do + bytepop[i] = nibpop[bit.band(i, 15)] + nibpop[bit.rshift(i, 4)] +end +nibpop = nil + +local function bitar_from_intar(maxbidx, maxidx, ar) + -- We need to have the int32_t[?] array be reachable so that it will not be + -- garbage collected + local ar_intptr = ffi.cast("intptr_t", ar) + anchor[tostring(ar_intptr)] = ar + + -- Leaving the (potential) high trailing bits at 0 lets us not worry + -- about them in the population count calculation (__len metamethod). + -- Also, this is correct for maxbidx%32 == 0, since BitOp's shifts + -- mask the 5 lower bits of the counts. + local numremain = bit.band(maxbidx+1, 31) + ar[maxidx] = bit.band(ar[maxidx], bit.rshift(-1, 32-numremain)) + + return bitar_ct(maxbidx, maxidx, ar_intptr) +end + +local function setop_common_rel(s1, s2) + if (s1.maxbidx ~= s2.maxbidx) then + error("bad arguments to bit array set op: must be of same length", 4) + end + + local ar1 = ffi.cast(ptr_to_int, s1.arptr) + local ar2 = ffi.cast(ptr_to_int, s2.arptr) + + return ar1, ar2 +end + +local function setop_common(s1, s2) + if (not ffi.istype(bitar_ct, s1) or not ffi.istype(bitar_ct, s2)) then + error("bad arguments to bit array set op: both must be 'bitar' types", 3) + end + + local ar1, ar2 = setop_common_rel(s1, s2) + local ar = ffi.new("int32_t [?]", s1.maxidx+1) + + return ar, ar1, ar2 +end + +local mt = { + --- Operational methods + + __add = function(s1, s2) -- set union + local ar, ar1, ar2 = setop_common(s1, s2) + for i=0,s1.maxidx do + ar[i] = bit.bor(ar1[i], ar2[i]) + end + return bitar_from_intar(s1.maxbidx, s1.maxidx, ar) + end, + + __mul = function(s1, s2) -- set intersection + local ar, ar1, ar2 = setop_common(s1, s2) + for i=0,s1.maxidx do + ar[i] = bit.band(ar1[i], ar2[i]) + end + return bitar_from_intar(s1.maxbidx, s1.maxidx, ar) + end, + + __sub = function(s1, s2) -- set difference + local ar, ar1, ar2 = setop_common(s1, s2) + for i=0,s1.maxidx do + ar[i] = bit.band(ar1[i], bit.bnot(ar2[i])) + end + return bitar_from_intar(s1.maxbidx, s1.maxidx, ar) + end, + + __unm = function(s) -- bitwise NOT + local newar = ffi.new("int32_t [?]", s.maxidx+1) + local oldar = ffi.cast(ptr_to_int, s.arptr) + for i=0,s.maxidx do + newar[i] = bit.bnot(oldar[i]) + end + return bitar_from_intar(s.maxbidx, s.maxidx, newar) + end, + + + --- Additional operations + + __index = { + -- TODO: Rename to 'testi', 'seti', 'cleari'; add 'flipi'? + + -- Is bit i set? + isset = function(s, i) + if (not (i >= 0 and i<=s.maxbidx)) then + error("bad bit index for isset: must be in [0.."..s.maxbidx.."]", 2) + end + s = ffi.cast(ptr_to_int, s.arptr) + return (bit.band(s[bit.rshift(i, 5)], bit.lshift(1, i)) ~= 0) + end, + + -- Clear bit i. + set0 = function(s, i) + if (not (i >= 0 and i<=s.maxbidx)) then + error("bad bit index for set0: must be in [0.."..s.maxbidx.."]", 2) + end + s = ffi.cast(ptr_to_int, s.arptr) + local jx = bit.rshift(i, 5) + s[jx] = bit.band(s[jx], bit.rol(0xfffffffe, i)) + end, + + -- Set bit i. + set1 = function(s, i) + if (not (i >= 0 and i<=s.maxbidx)) then + error("bad bit index for set1: must be in [0.."..s.maxbidx.."]", 2) + end + s = ffi.cast(ptr_to_int, s.arptr) + local jx = bit.rshift(i, 5) + s[jx] = bit.bor(s[jx], bit.rol(0x00000001, i)) + end + }, + + + --- Relational methods + + __eq = function(s1, s2) -- set identity + local ar1, ar2 = setop_common_rel(s1, s2) + for i=0,s1.maxidx do + if (bit.bxor(ar1[i], ar2[i]) ~= 0) then + return false + end + end + return true + end, + + __le = function(s1, s2) + local ar1, ar2 = setop_common_rel(s1, s2) + for i=0,s1.maxidx do + if (bit.band(ar1[i], bit.bnot(ar2[i])) ~= 0) then + return false + end + end + return true + end, + + __lt = function(s1, s2) + return s1 <= s2 and not (s2 == s1) + end, + + -- The length operator gets the population count of the bit array, i.e. the + -- number of set bits. + __len = function(s) + local ar = ffi.cast(ptr_to_int, s.arptr) + local popcnt = 0 + for i=0,s.maxidx do + popcnt = popcnt + bytepop[bit.band(ar[i], 255)] + + bytepop[bit.band(bit.rshift(ar[i], 8), 255)] + + bytepop[bit.band(bit.rshift(ar[i], 16), 255)] + + bytepop[bit.rshift(ar[i], 24)] + end + return popcnt + end, + + -- serialization + __tostring = function(s) + local size=s.maxidx+1 + local ar = ffi.cast(ptr_to_int, s.arptr) + + local hdr = "bitar.new("..s.maxbidx..", '" + local ofs = #hdr + local totalstrlen = ofs+8*size+2 + local str = ffi.new("char [?]", totalstrlen) + + ffi.copy(str, hdr, ofs) + + for i=0,s.maxidx do + -- 'a' is ASCII 97 + for nib=0,7 do + str[ofs + 8*i + nib] = 97 + bit.band(bit.rshift(ar[i], 4*nib), 0x0000000f) + end + end + + ffi.copy(str+totalstrlen-2, "')", 2) + + return ffi.string(str, totalstrlen) + end, + + -- On garbage collection of the bitar, clear the array's anchor so that it + -- can be collected too. + __gc = function(s) + anchor[tostring(s.arptr)] = nil + end, + + __metatable = true, +} + +ffi.metatype(bitar_ct, mt) + + +-- Create new bit array. +function new(maxbidx, initval) + if (type(maxbidx) ~= "number" or not (maxbidx >= 0 and maxbidx <= (2^31)-2)) then + -- NOTE: Uh-oh, we can't write '2^31' because that would be interpreted + -- as color by OSD_Printf. + error("bad argument #1 to bitar.new (must be a number in [0..(2**31)-2])", 2) + end + + if (math.floor(maxbidx) ~= maxbidx) then + error("bad argument #1 to bitar.new (must be an integral number)") + end + + if (ffi.istype(ptr_to_int, initval)) then + -- Initialization from an array on the C side. INTERNAL. + -- Cannot be reached by user code since there's no access to the FFI + -- (and thus no way to create pointers). + + return bitar_from_intar(maxbidx, (maxbidx+1)/32-1, initval) + + elseif (type(initval)=="string") then + -- String containing hex digits (a..p) given, for INTERNAL use only. + -- XXX: Can be reached by user code. + + local lstr = initval + + local numnibs = #lstr + assert(numnibs%8 == 0) + + local size = numnibs/8 + local maxidx = size-1 + local ar = ffi.new("int32_t [?]", size) + + local str = ffi.new("char [?]", numnibs) + ffi.copy(str, lstr, numnibs) + + for i=0,maxidx do + ar[i] = 0 + + for nib=0,7 do + local hexdig = str[8*i + nib] + assert(hexdig >= 97 and hexdig < 97+16) + ar[i] = bit.bor(ar[i], bit.lshift(hexdig-97, 4*nib)) + end + end + + -- NOTE: cannot be extracted from the string, use the passed one. + return bitar_from_intar(maxbidx, maxidx, ar) + + else + -- User-requested bitar creation. + + if (initval ~= 0 and initval ~= 1) then + error("bad argument #2 to bitar.new (must be either 0 or 1)", 2) + end + + local maxidx = math.floor(maxbidx/32) + local size = maxidx+1 + + local ar = ffi.new("int32_t [?]", size) + + if (initval==1) then + ffi.fill(ar, size*4, -1) + end + + return bitar_from_intar(maxbidx, maxidx, ar) + end +end diff --git a/source/rr/src/lunatic/con_lang.lua b/source/rr/src/lunatic/con_lang.lua new file mode 100644 index 000000000..e20e054a7 --- /dev/null +++ b/source/rr/src/lunatic/con_lang.lua @@ -0,0 +1,1340 @@ +-- CON language definitions + +local lpeg = require("lpeg") + +local pairs = pairs +local print = print +local setmetatable = setmetatable +local type = type + + +module(...) + + +MAXVOLUMES = 7 +MAXLEVELS = 64 +MAXGAMETYPES = 16 + +MAXSKILLS = 7 + +MAXSOUNDS = 4096 + +MAXSESSIONVARS = 8 -- KEEPINSYNC lunatic_game.c + +-- KEEPINSYNC quotes.h + +-- For Lunatic, MAXQUOTES is OBITQUOTEINDEX because starting from that index +-- are obituary and suicide quotes which are passed as *format strings* to +-- sprintf() in C. +REALMAXQUOTES = 16384 +MAXQUOTES = REALMAXQUOTES-128 +MAXQUOTELEN = 128 + +local STR = { + STR_MAPNAME = 0, + STR_MAPFILENAME = 1, + STR_PLAYERNAME = 2, + STR_VERSION = 3, + STR_GAMETYPE = 4, + STR_VOLUMENAME = 5, + STR_YOURTIME = 6, + STR_PARTIME = 7, + STR_DESIGNERTIME = 8, + STR_BESTTIME = 9, +} + +PROJ = { + PROJ_WORKSLIKE = 0, + PROJ_SPAWNS = 1, + PROJ_SXREPEAT = 2, + PROJ_SYREPEAT = 3, + PROJ_SOUND = 4, + PROJ_ISOUND = 5, + PROJ_VEL = 6, + PROJ_EXTRA = 7, + PROJ_DECAL = 8, + PROJ_TRAIL = 9, + PROJ_TXREPEAT = 10, + PROJ_TYREPEAT = 11, + PROJ_TOFFSET = 12, + PROJ_TNUM = 13, + PROJ_DROP = 14, + PROJ_CSTAT = 15, + PROJ_CLIPDIST = 16, + PROJ_SHADE = 17, + PROJ_XREPEAT = 18, + PROJ_YREPEAT = 19, + PROJ_PAL = 20, + PROJ_EXTRA_RAND = 21, + PROJ_HITRADIUS = 22, + PROJ_VEL_MULT = 23, -- NAME (PROJ_MOVECNT) + PROJ_OFFSET = 24, + PROJ_BOUNCES = 25, + PROJ_BSOUND = 26, + PROJ_RANGE = 27, + PROJ_FLASH_COLOR = 28, + PROJ_USERDATA = 29, +} + +EVENT = { + EVENT_INIT = 0, + EVENT_ENTERLEVEL = 1, + EVENT_RESETWEAPONS = 2, + EVENT_RESETINVENTORY = 3, + EVENT_HOLSTER = 4, + EVENT_LOOKLEFT = 5, + EVENT_LOOKRIGHT = 6, + EVENT_SOARUP = 7, + EVENT_SOARDOWN = 8, + EVENT_CROUCH = 9, + EVENT_JUMP = 10, + EVENT_RETURNTOCENTER = 11, + EVENT_LOOKUP = 12, + EVENT_LOOKDOWN = 13, + EVENT_AIMUP = 14, + EVENT_FIRE = 15, + EVENT_CHANGEWEAPON = 16, + EVENT_GETSHOTRANGE = 17, + EVENT_GETAUTOAIMANGLE = 18, + EVENT_GETLOADTILE = 19, + EVENT_CHEATGETSTEROIDS = 20, + EVENT_CHEATGETHEAT = 21, + EVENT_CHEATGETBOOT = 22, + EVENT_CHEATGETSHIELD = 23, + EVENT_CHEATGETSCUBA = 24, + EVENT_CHEATGETHOLODUKE = 25, + EVENT_CHEATGETJETPACK = 26, + EVENT_CHEATGETFIRSTAID = 27, + EVENT_QUICKKICK = 28, + EVENT_INVENTORY = 29, + EVENT_USENIGHTVISION = 30, + EVENT_USESTEROIDS = 31, + EVENT_INVENTORYLEFT = 32, + EVENT_INVENTORYRIGHT = 33, + EVENT_HOLODUKEON = 34, + EVENT_HOLODUKEOFF = 35, + EVENT_USEMEDKIT = 36, + EVENT_USEJETPACK = 37, + EVENT_TURNAROUND = 38, + EVENT_DISPLAYWEAPON = 39, + EVENT_FIREWEAPON = 40, + EVENT_SELECTWEAPON = 41, + EVENT_MOVEFORWARD = 42, + EVENT_MOVEBACKWARD = 43, + EVENT_TURNLEFT = 44, + EVENT_TURNRIGHT = 45, + EVENT_STRAFELEFT = 46, + EVENT_STRAFERIGHT = 47, + EVENT_WEAPKEY1 = 48, + EVENT_WEAPKEY2 = 49, + EVENT_WEAPKEY3 = 50, + EVENT_WEAPKEY4 = 51, + EVENT_WEAPKEY5 = 52, + EVENT_WEAPKEY6 = 53, + EVENT_WEAPKEY7 = 54, + EVENT_WEAPKEY8 = 55, + EVENT_WEAPKEY9 = 56, + EVENT_WEAPKEY10 = 57, + EVENT_DRAWWEAPON = 58, + EVENT_DISPLAYCROSSHAIR = 59, + EVENT_DISPLAYREST = 60, + EVENT_DISPLAYSBAR = 61, + EVENT_RESETPLAYER = 62, + EVENT_INCURDAMAGE = 63, + EVENT_AIMDOWN = 64, + EVENT_GAME = 65, + EVENT_PREVIOUSWEAPON = 66, + EVENT_NEXTWEAPON = 67, + EVENT_SWIMUP = 68, + EVENT_SWIMDOWN = 69, + EVENT_GETMENUTILE = 70, + EVENT_SPAWN = 71, + EVENT_LOGO = 72, + EVENT_EGS = 73, + EVENT_DOFIRE = 74, + EVENT_PRESSEDFIRE = 75, + EVENT_USE = 76, + EVENT_PROCESSINPUT = 77, + EVENT_FAKEDOMOVETHINGS = 78, + EVENT_DISPLAYROOMS = 79, + EVENT_KILLIT = 80, + EVENT_LOADACTOR = 81, + EVENT_DISPLAYBONUSSCREEN = 82, + EVENT_DISPLAYMENU = 83, + EVENT_DISPLAYMENUREST = 84, + EVENT_DISPLAYLOADINGSCREEN = 85, + EVENT_ANIMATESPRITES = 86, + EVENT_NEWGAME = 87, + EVENT_SOUND = 88, + EVENT_CHECKTOUCHDAMAGE = 89, + EVENT_CHECKFLOORDAMAGE = 90, + EVENT_LOADGAME = 91, + EVENT_SAVEGAME = 92, + EVENT_PREGAME = 93, + EVENT_CHANGEMENU = 94, + EVENT_DAMAGEHPLANE = 95, + EVENT_ACTIVATECHEAT = 96, + EVENT_DISPLAYINACTIVEMENU = 97, + EVENT_DISPLAYINACTIVEMENUREST = 98, + EVENT_CUTSCENE = 99, + EVENT_DISPLAYCURSOR = 100, + EVENT_DISPLAYLEVELSTATS = 101, + EVENT_DISPLAYCAMERAOSD = 102, + EVENT_DISPLAYROOMSCAMERA = 103, + EVENT_DISPLAYSTART = 104, + EVENT_WORLD = 105, + EVENT_PREWORLD = 106, + EVENT_PRELEVEL = 107, + EVENT_DISPLAYSPIT = 108, + EVENT_DISPLAYFIST = 109, + EVENT_DISPLAYKNEE = 110, + EVENT_DISPLAYKNUCKLES = 111, + EVENT_DISPLAYSCUBA = 112, + EVENT_DISPLAYTIP = 113, + EVENT_DISPLAYACCESS = 114, +-- EVENT_ANIMATEALLSPRITES = previous+1, -- internal +-- KEEPINSYNC with MAXEVENTS below +} + +MAXEVENTS = 115 -- KEEPINSYNC with above EVENT_* list + +-- NOTE: negated values are not exported to the ffi.C namespace or CON. +-- See TWEAK_SFLAG below. +SFLAG = { + SFLAG_SHADOW = 0x00000001, + SFLAG_NVG = 0x00000002, + SFLAG_NOSHADE = 0x00000004, + SFLAG_PROJECTILE = -0x00000008, + SFLAG_DECAL = -0x00000010, + SFLAG_BADGUY = 0x00000020, + SFLAG_NOPAL = 0x00000040, + SFLAG_NOEVENTS = 0x00000080, -- NAME + SFLAG_NOLIGHT = 0x00000100, + SFLAG_USEACTIVATOR = 0x00000200, + SFLAG_NULL = -0x00000400, + SFLAG_NOCLIP = 0x00000800, + SFLAG_NOFLOORSHADOW = -0x00001000, + SFLAG_SMOOTHMOVE = 0x00002000, + SFLAG_NOTELEPORT = 0x00004000, + SFLAG_BADGUYSTAYPUT = -0x00008000, + SFLAG_CACHE = -0x00010000, + SFLAG_ROTFIXED = -0x00020000, + SFLAG_HARDCODED_BADGUY = -0x00040000, + SFLAG_DIDNOSE7WATER = -0x00080000, + SFLAG_NODAMAGEPUSH = 0x00100000, + SFLAG_NOWATERDIP = 0x00200000, + SFLAG_HURTSPAWNBLOOD = 0x00400000, + -- RESERVED for actor.FLAGS.chain_*/replace_*: + -- 0x08000000, 0x10000000, 0x20000000, 0x40000000 +} + +STAT = { + STAT_DEFAULT = 0, + STAT_ACTOR = 1, + STAT_ZOMBIEACTOR = 2, + STAT_EFFECTOR = 3, + STAT_PROJECTILE = 4, + STAT_MISC = 5, + STAT_STANDABLE = 6, + STAT_LOCATOR = 7, + STAT_ACTIVATOR = 8, + STAT_TRANSPORT = 9, + STAT_PLAYER = 10, + STAT_FX = 11, + STAT_FALLER = 12, + STAT_DUMMYPLAYER = 13, + STAT_LIGHT = 14, +-- STAT_NETALLOC = 1023, -- MAXSTATUS-1 +} + +local GAMEFUNC = { + GAMEFUNC_MOVE_FORWARD = 0, + GAMEFUNC_MOVE_BACKWARD = 1, + GAMEFUNC_TURN_LEFT = 2, + GAMEFUNC_TURN_RIGHT = 3, + GAMEFUNC_STRAFE = 4, + GAMEFUNC_FIRE = 5, + GAMEFUNC_OPEN = 6, + GAMEFUNC_RUN = 7, + GAMEFUNC_AUTORUN = 8, + GAMEFUNC_JUMP = 9, + GAMEFUNC_CROUCH = 10, + GAMEFUNC_LOOK_UP = 11, + GAMEFUNC_LOOK_DOWN = 12, + GAMEFUNC_LOOK_LEFT = 13, + GAMEFUNC_LOOK_RIGHT = 14, + GAMEFUNC_STRAFE_LEFT = 15, + GAMEFUNC_STRAFE_RIGHT = 16, + GAMEFUNC_AIM_UP = 17, + GAMEFUNC_AIM_DOWN = 18, + GAMEFUNC_WEAPON_1 = 19, + GAMEFUNC_WEAPON_2 = 20, + GAMEFUNC_WEAPON_3 = 21, + GAMEFUNC_WEAPON_4 = 22, + GAMEFUNC_WEAPON_5 = 23, + GAMEFUNC_WEAPON_6 = 24, + GAMEFUNC_WEAPON_7 = 25, + GAMEFUNC_WEAPON_8 = 26, + GAMEFUNC_WEAPON_9 = 27, + GAMEFUNC_WEAPON_10 = 28, + GAMEFUNC_INVENTORY = 29, + GAMEFUNC_INVENTORY_LEFT = 30, + GAMEFUNC_INVENTORY_RIGHT = 31, + GAMEFUNC_HOLO_DUKE = 32, + GAMEFUNC_JETPACK = 33, + GAMEFUNC_NIGHTVISION = 34, + GAMEFUNC_MEDKIT = 35, + GAMEFUNC_TURNAROUND = 36, + GAMEFUNC_SENDMESSAGE = 37, + GAMEFUNC_MAP = 38, + GAMEFUNC_SHRINK_SCREEN = 39, + GAMEFUNC_ENLARGE_SCREEN = 40, + GAMEFUNC_CENTER_VIEW = 41, + GAMEFUNC_HOLSTER_WEAPON = 42, + GAMEFUNC_SHOW_OPPONENTS_WEAPON = 43, + GAMEFUNC_MAP_FOLLOW_MODE = 44, + GAMEFUNC_SEE_COOP_VIEW = 45, + GAMEFUNC_MOUSE_AIMING = 46, + GAMEFUNC_TOGGLE_CROSSHAIR = 47, + GAMEFUNC_STEROIDS = 48, + GAMEFUNC_QUICK_KICK = 49, + GAMEFUNC_NEXT_WEAPON = 50, + GAMEFUNC_PREVIOUS_WEAPON = 51, +-- GAMEFUNC_SHOW_CONSOLE = 52, + GAMEFUNC_SHOW_DUKEMATCH_SCORES = 53, + GAMEFUNC_DPAD_SELECT = 54, + GAMEFUNC_DPAD_AIMING = 55, +} + +local function shallow_copy(tab) + local t = {} + for k,v in pairs(tab) do + t[k] = v + end + return t +end + +-- KEEPINSYNC with gamedef.c:C_AddDefaultDefinitions() and the respective +-- defines. These are exported to the ffi.C namespace (except STAT) and as +-- literal defines in lunacon.lua. +labels = +{ + STR, + PROJ, + EVENT, + shallow_copy(SFLAG), + setmetatable(STAT, { __metatable="noffiC" }), + GAMEFUNC, +} + +user_sflags = 0 +-- TWEAK_SFLAG +for name, flag in pairs(SFLAG) do + if (flag > 0) then + user_sflags = user_sflags + flag + else + SFLAG[name] = -flag + labels[4][name] = nil + end +end + +-- KEEPINSYNC player.h +wdata_members = +{ + -- NOTE: they are lowercased for Lunatic + -- NOTE: members _*sound*, _spawn and _shoots assume *zero* to mean "none" + -- (-1 would be more logical). + "const int32_t _workslike", + "int32_t clip", + "int32_t reload", + "int32_t firedelay", + "int32_t totaltime", + "int32_t holddelay", + "int32_t flags", + "const int32_t _shoots", + "int32_t spawntime", + "const int32_t _spawn", + "int32_t shotsperburst", + "const int32_t _initialsound", + "const int32_t _firesound", + "int32_t sound2time", -- NOTE: this is a time number, not a sound + "const int32_t _sound2sound", + "const int32_t _reloadsound1", + "const int32_t _reloadsound2", + "const int32_t _selectsound", + "int32_t flashcolor", +} + + +local SP = function(memb) return "sprite[%s]"..memb end +local ATSP = function(memb) return "_atsprite[%s]"..memb end +local AC = function(memb) return "actor[%s]"..memb end +local SX = function(memb) return "spriteext[%s]"..memb end + +-- Generate code to access a signed member as unsigned. +local function s2u(label) + return "(_band("..label.."+65536,65535))" +end + +local function S2U(label) + return { s2u(label), label } +end + +-- Some literal checker functions (LITERAL_CHECKING). +-- KEEPINSYNC with the actual setter code. +local function litok_gem1(lit) + return (lit >= -1) +end + +local function litok_ge0(lit) + return (lit >= 0) +end + +local ActorLabels = { + x = SP".x", + y = SP".y", + z = SP".z", + cstat = SP".cstat", + picnum = { SP".picnum", SP":set_picnum(%%s)", litok_ge0 }, + shade = SP".shade", + pal = SP".pal", + clipdist = SP".clipdist", +-- filler = SP".filler", +-- detail = SP".filler", -- NAME + blend = SP".blend", + xrepeat = SP".xrepeat", + yrepeat = SP".yrepeat", + xoffset = SP".xoffset", + yoffset = SP".yoffset", + sectnum = { SP".sectnum", SP":changesect(%%s)", litok_ge0 }, -- set: for tsprite + statnum = { SP".statnum" }, + ang = SP".ang", + owner = { SP".owner", SP":_set_owner(%%s)", litok_ge0 }, + xvel = SP".xvel", + yvel = { SP".yvel", SP":set_yvel(%%s)" }, + zvel = SP".zvel", + lotag = SP".lotag", + hitag = SP".hitag", + extra = SP".extra", + + ulotag = S2U(SP".lotag"), + uhitag = S2U(SP".hitag"), + + -- ActorExtra labels... + htcgg = AC".cgg", + -- XXX: why <0 allowed? + htpicnum = { AC".picnum", AC":set_picnum(%%s)" }, + htang = AC".ang", + htextra = AC".extra", + htowner = { AC".owner", AC":set_owner(%%s)", litok_ge0 }, + htmovflag = AC"._movflag", + httempang = AC".tempang", + htactorstayput = AC".stayputsect", -- NAME + htdispicnum = { AC".dispicnum" }, + -- NOTE: no access for .shootzvel + httimetosleep = AC".timetosleep", + htfloorz = AC".floorz", + htceilingz = AC".ceilingz", + htlastvx = AC".lastvx", + htlastvy = AC".lastvy", + htbposx = AC".bpos.x", + htbposy = AC".bpos.y", + htbposz = AC".bpos.z", + -- Read access differs from write ({ get, set }): + htg_t = { AC":_get_t_data(%s)", AC":_set_t_data(%s,%%s)" }, + htflags = AC".flags", + movflags = AC".movflags", + + -- (mostly) model-related flags + angoff = SX".angoff", + pitch = SX".pitch", + roll = SX".roll", + mdxoff = SX".mdoff.x", -- NAME + mdyoff = SX".mdoff.y", + mdzoff = SX".mdoff.z", + mdflags = SX".flags", + xpanning = SX".xpanning", + ypanning = SX".ypanning", + + alpha = { "_math.floor(spriteext[%s].alpha*255)", "spriteext[%s].alpha=(%%s)/255" }, + + isvalid = { "_con._isvalid(%s)" }, +} + +local function spr2tspr(code) + if (code and code:find(SP"", 1, true)==1) then + return ATSP(code:sub(#SP"" + 1)) + end + -- else return nothing +end + +local TspriteLabels = {} + +for member, code in pairs(ActorLabels) do + if (type(code)=="string") then + TspriteLabels["tspr"..member] = spr2tspr(code) + else + TspriteLabels["tspr"..member] = { spr2tspr(code[1]), spr2tspr(code[2]) } + end +end + +-- Sprites set stat- and sectnum via sprite.change{stat,sect} functions. +ActorLabels.sectnum[2] = "sprite.changesect(%s,%%s,true)" +ActorLabels.statnum[2] = "sprite.changestat(%s,%%s,true)" + +local PL = function(memb) return "player[%s]"..memb end +-- Access to DukePlayer_t's bool members: they must be read as numbers. +local PLBOOL = function(memb) return { "("..PL(memb).." and 1 or 0)", PL(memb) } end + +local empty_table = {} +local DISABLED_PL = function() return empty_table end +local DISABLED = DISABLED_PL + +local PlayerLabels = { + posx = PL".pos.x", + posy = PL".pos.y", + posz = PL".pos.z", + oposx = PL".opos.x", + oposy = PL".opos.y", + oposz = PL".opos.z", + posxv = PL".vel.x", -- NAME + posyv = PL".vel.y", + poszv = PL".vel.z", + -- NOTE: no access for .npos + bobposx = DISABLED_PL".bobposx", + bobposy = DISABLED_PL".bobposy", + + truefz = DISABLED_PL".truefz", + truecz = DISABLED_PL".truecz", + player_par = PL".player_par", + + randomflamex = DISABLED_PL".randomflamex", + exitx = DISABLED_PL".exitx", + exity = DISABLED_PL".exity", + + runspeed = PL".runspeed", + max_player_health = PL".max_player_health", + max_shield_amount = PL".max_shield_amount", + + autostep = PL".autostep", + autostep_sbw = PL".autostep_sbw", + + interface_toggle_flag = DISABLED_PL".interface_toggle_flag", + + -- NOTE: *bombControl etc. are accessed by gamevars in CON + + max_actors_killed = PL".max_actors_killed", + actors_killed = PL".actors_killed", + + -- NOTE the special case; "%%s" is used to mark settable members + -- with METHOD_MEMBER syntax, it's the value to be set. + gotweapon = { "("..PL":has_weapon(%s) and 1 or 0)", PL":_gt_weapon(%s,%%s)" }, + zoom = PL".zoom", + + loogiex = {}, + loogiey = {}, + + sbs = PL".sbs", + sound_pitch = PL".sound_pitch", + + ang = PL".ang", + oang = PL".oang", + angvel = PL".angvel", + + cursectnum = PL".cursectnum", + + look_ang = PL".look_ang", + last_extra = PL".last_extra", + subweapon = PL".subweapon", + + max_ammo_amount = PL".max_ammo_amount[%s]", + ammo_amount = PL".ammo_amount[%s]", + -- NOTE: no direct access for .inv_amount (but see end) + + wackedbyactor = PL".wackedbyactor", + pyoff = PL".pyoff", + opyoff = PL".opyoff", + + horiz = PL".horiz", + horizoff = PL".horizoff", + ohoriz = PL".ohoriz", + ohorizoff = PL".ohorizoff", + + newowner = PL".newowner", + + jumping_counter = PL".jumping_counter", + airleft = PL".airleft", + + fta = PL".fta", + ftq = PL".ftq", + access_wallnum = { PL".access_wallnum" }, + access_spritenum = { PL".access_spritenum" }, + + got_access = PL".got_access", + weapon_ang = PL".weapon_ang", + visibility = PL".visibility", + + somethingonplayer = PL".somethingonplayer", + on_crane = PL".on_crane", + + i = { PL".i" }, + index = { "%s" }, + + one_parallax_sectnum = DISABLED{ PL".one_parallax_sectnum" }, + + random_club_frame = PL".random_club_frame", + one_eighty_count = PL".one_eighty_count", + + dummyplayersprite = DISABLED_PL".dummyplayersprite", + extra_extra8 = PL".extra_extra8", + + actorsqu = PL".actorsqu", + timebeforeexit = PL".timebeforeexit", + customexitsound = { PL".customexitsound" }, + + last_pissed_time = PL".last_pissed_time", + + weaprecs = PL".weaprecs[%s]", + + weapon_sway = PL".weapon_sway", + crack_time = PL".crack_time", + bobcounter = PL".bobcounter", + + -- NOTE: no access for .orotscrnang + rotscrnang = PL".rotscrnang", + dead_flag = PL".dead_flag", + + holoduke_on = PL".holoduke_on", + pycount = PL".pycount", + transporter_hold = PL".transporter_hold", + + max_secret_rooms = PL".max_secret_rooms", + secret_rooms = PL".secret_rooms", + + frag = PL".frag", + fraggedself = PL".fraggedself", + quick_kick = PL".quick_kick", + last_quick_kick = PL".last_quick_kick", + + return_to_center = DISABLED_PL".return_to_center", + reloading = PLBOOL".reloading", + weapreccnt = { PL".weapreccnt" }, + + aim_mode = PL".aim_mode", + auto_aim = PL".auto_aim", + weaponswitch = PL".weaponswitch", + movement_lock = PL".movement_lock", + team = PL".team", + + tipincs = PL".tipincs", + hbomb_hold_delay = PL".hbomb_hold_delay", + frag_ps = PL".frag_ps", + kickback_pic = PL".kickback_pic", + + gm = PL".gm", + on_warping_sector = PLBOOL".on_warping_sector", + footprintcount = PL".footprintcount", + hurt_delay = PL".hurt_delay", + + hbomb_on = PLBOOL".hbomb_on", + jumping_toggle = PLBOOL".jumping_toggle", + rapid_fire_hold = PLBOOL".rapid_fire_hold", + on_ground = PLBOOL".on_ground", + + inven_icon = PL".inven_icon", + buttonpalette = PL".buttonpalette", + over_shoulder_on = PLBOOL".over_shoulder_on", + show_empty_weapon = PL".show_empty_weapon", + + jetpack_on = PLBOOL".jetpack_on", + spritebridge = PLBOOL".spritebridge", + lastrandomspot = DISABLED_PL".lastrandomspot", + + scuba_on = PLBOOL".scuba_on", + footprintpal = PL".footprintpal", + heat_on = PLBOOL".heat_on", + invdisptime = PL".invdisptime", + holster_weapon = PLBOOL".holster_weapon", + falling_counter = PL".falling_counter", + footprintshade = PL".footprintshade", + + refresh_inventory = PL".refresh_inventory", + last_full_weapon = PL".last_full_weapon", + + walking_snd_toggle = PL".walking_snd_toggle", + palookup = PL".palookup", + hard_landing = PL".hard_landing", + fist_incs = PL".fist_incs", + + toggle_key_flag = { PL".toggle_key_flag" }, + knuckle_incs = PL".knuckle_incs", + knee_incs = PL".knee_incs", + access_incs = PL".access_incs", + + numloogs = DISABLED_PL".numloogs", + loogcnt = PL".loogcnt", + scream_voice = { PL".scream_voice" }, + + last_weapon = PL".last_weapon", + cheat_phase = { PL".cheat_phase" }, + weapon_pos = PL".weapon_pos", + wantweaponfire = PL".wantweaponfire", + + curr_weapon = PL".curr_weapon", + + palette = { PL".palette" }, + + -- NOTE the special case: + pals = PL"._pals[%s]", + pals_time = PL"._pals.f", + + name = {}, + + -- Access to .inv_amount + steroids_amount = PL".inv_amount[0]", + shield_amount = PL".inv_amount[1]", + scuba_amount = PL".inv_amount[2]", + holoduke_amount = PL".inv_amount[3]", + jetpack_amount = PL".inv_amount[4]", + -- 5: dummy + -- 6: no "access_amount" + heat_amount = PL".inv_amount[7]", + -- 8: dummy + firstaid_amount = PL".inv_amount[9]", + boot_amount = PL".inv_amount[10]", +} + +local SEC = function(memb) return "sector[%s]"..memb end +local SECRO = function(memb) return { "sector[%s]"..memb } end + +local SectorLabels = { + wallptr = SECRO".wallptr", + wallnum = SECRO".wallnum", + + ceilingz = SEC".ceilingz", + floorz = SEC".floorz", + + ceilingstat = SEC".ceilingstat", + floorstat = SEC".floorstat", + + -- CEILING + ceilingpicnum = { SEC".ceilingpicnum", SEC":set_ceilingpicnum(%%s)", litok_ge0 }, + + ceilingslope = SEC".ceilingheinum", -- NAME + ceilingshade = SEC".ceilingshade", + + ceilingpal = SEC".ceilingpal", + ceilingxpanning = SEC".ceilingxpanning", + ceilingypanning = SEC".ceilingypanning", + + -- FLOOR + floorpicnum = { SEC".floorpicnum", SEC":set_floorpicnum(%%s)", litok_ge0 }, + + floorslope = SEC".floorheinum", -- NAME + floorshade = SEC".floorshade", + + floorpal = SEC".floorpal", + floorxpanning = SEC".floorxpanning", + floorypanning = SEC".floorypanning", + + visibility = SEC".visibility", + fogpal = SEC".fogpal", + alignto = SEC".fogpal", -- NAME + + lotag = SEC".lotag", + hitag = SEC".hitag", + extra = SEC".extra", + + ceilingbunch = { SEC".ceilingbunch" }, + floorbunch = { SEC".floorbunch" }, + + ulotag = S2U(SEC".lotag"), + uhitag = S2U(SEC".hitag"), +} + +local WAL = function(memb) return "wall[%s]"..memb end +local WALRO = function(memb) return { "wall[%s]"..memb } end + +local WallLabels = { + x = WAL".x", + y = WAL".y", + point2 = WALRO".point2", + nextwall = { WAL".nextwall", WAL":_set_nextwall(%%s)" }, + nextsector = { WAL".nextsector", WAL":_set_nextsector(%%s)" }, + cstat = WAL".cstat", + picnum = { WAL".picnum", WAL":set_picnum(%%s)", litok_ge0 }, + overpicnum = { WAL".overpicnum", WAL":set_overpicnum(%%s)", litok_ge0 }, + shade = WAL".shade", + pal = WAL".pal", + xrepeat = WAL".xrepeat", + yrepeat = WAL".yrepeat", + xpanning = WAL".xpanning", + ypanning = WAL".ypanning", + lotag = WAL".lotag", + hitag = WAL".hitag", + extra = WAL".extra", + blend = WAL".blend", + + ulotag = S2U(WAL".lotag"), + uhitag = S2U(WAL".hitag"), +} + +local function tonegtag(LabelsTab, member, funcname) + local memb = LabelsTab[member] + LabelsTab[member] = { memb, memb.."="..funcname.."(%%s)" } +end + +function setup_negative_tag_check(funcname) + tonegtag(TspriteLabels, "tsprlotag", funcname) + tonegtag(TspriteLabels, "tsprhitag", funcname) + tonegtag(ActorLabels, "lotag", funcname) + tonegtag(ActorLabels, "hitag", funcname) + tonegtag(WallLabels, "lotag", funcname) + tonegtag(WallLabels, "hitag", funcname) + tonegtag(SectorLabels, "lotag", funcname) + tonegtag(SectorLabels, "hitag", funcname) +end + +local PROJ = function(memb) return "projectile[%s]"..memb end +local THISPROJ = function(memb) return "actor[%s].proj"..memb end + +local ProjectileLabels = { + workslike = PROJ".workslike", + cstat = PROJ".cstat", + hitradius = PROJ".hitradius", + range = PROJ".range", + flashcolor = PROJ".flashcolor", + spawns = { PROJ".spawns", PROJ":set_spawns(%%s)", litok_gem1 }, + sound = { PROJ".sound", PROJ":set_sound(%%s)", litok_gem1 }, + isound = { PROJ".isound", PROJ":set_isound(%%s)", litok_gem1 }, + vel = PROJ".vel", + decal = { PROJ".decal", PROJ":set_decal(%%s)", litok_gem1 }, + trail = { PROJ".trail", PROJ":set_trail(%%s)", litok_gem1 }, + tnum = PROJ".tnum", + drop = PROJ".drop", + offset = PROJ".offset", + bounces = PROJ".bounces", + bsound = { PROJ".bsound", PROJ":set_bsound(%%s)", litok_gem1 }, + toffset = PROJ".toffset", + extra = PROJ".extra", + extra_rand = PROJ".extra_rand", + sxrepeat = PROJ".sxrepeat", + syrepeat = PROJ".syrepeat", + txrepeat = PROJ".txrepeat", + tyrepeat = PROJ".tyrepeat", + shade = PROJ".shade", + xrepeat = PROJ".xrepeat", + yrepeat = PROJ".yrepeat", + pal = PROJ".pal", + velmult = PROJ".movecnt", -- NAME + clipdist = PROJ".clipdist", + userdata = PROJ".userdata", +} + +-- XXX: kind of CODEDUP form spr2tspr +local function proj2thisproj(code) + if (code and code:find(PROJ"", 1, true)==1) then + return THISPROJ(code:sub(#PROJ"" + 1)) + end + -- else return nothing +end + +local SpriteProjectileLabels = {} + +for member, code in pairs(ProjectileLabels) do + if (type(code)=="string") then + SpriteProjectileLabels[member] = proj2thisproj(code) + else + SpriteProjectileLabels[member] = { proj2thisproj(code[1]), proj2thisproj(code[2]) } + end +end + +local UD = function(memb) return "_gud(_pli)"..memb end +local UDRO = function(memb) return { UD(memb) } end + +-- NOTE: Only members that are actually encountered in existing mods are added here. +-- TODO: r5043, r5044 +local UserdefLabels = { + althud = UD".althud", + auto_run = UD".auto_run", + camerasprite = UDRO".camerasprite", + cashman = UDRO".cashman", + clipping = UD".noclip", -- NAME + color = UD".color", + const_visibility = UD".const_visibility", + crosshair = UD".crosshair", + crosshairscale = UDRO".crosshairscale", + detail = { "1" }, + display_bonus_screen = UD".display_bonus_screen", + drawweapon = UDRO".drawweapon", + eog = UD".eog", + ffire = UDRO".ffire", + fta_on = UD".fta_on", + god = UD".god", + idplayers = UDRO".idplayers", + last_level = UDRO".last_level", + level_number = { UD".level_number", UD":set_level_number(%%s)", {0, MAXLEVELS-1} }, + levelstats = UD".levelstats", + lockout = UDRO".lockout", + m_origin_x = UD".m_origin.x", + m_origin_y = UD".m_origin.y", + m_player_skill = UDRO".m_player_skill", + m_volume_number = { UD".m_volume_number", UD":set_m_volume_number(%%s)", {0, MAXVOLUMES} }, + mouseaiming = UD".mouseaiming", + pause_on = UDRO".pause_on", + player_skill = UD".player_skill", + playerbest = UDRO".playerbest", + mouseflip = UDRO".mouseflip", + multimode = { "1" }, + musictoggle = UDRO".config.MusicToggle", + noexits = UDRO".noexits", + overhead_on = UD".overhead_on", + recstat = UDRO".recstat", + runkey_mode = UD".runkey_mode", + show_level_text = UD".show_level_text", + screen_size = { UD".screen_size", UD":set_screen_size(%%s)" }, + screen_tilting = UD".screen_tilting", + showallmap = UD".showallmap", + showweapons = UDRO".showweapons", + statusbarmode = UDRO".statusbarmode", + statusbarscale = UDRO".statusbarscale", + volume_number = { UD".volume_number", UD":set_volume_number(%%s)", {0, MAXVOLUMES} }, + weaponscale = UDRO".weaponscale", + weaponswitch = UD".weaponswitch", +} + +local INP = function(memb) return PL"._input"..memb end + +local InputLabels = { + avel = INP".avel", + horz = INP".horz", + fvel = INP".fvel", + svel = INP".svel", + bits = INP".bits", + extbits = INP".extbits", +} + +local TileDataLabels = { + -- tilesiz[] + xsize = "g_tile.sizx[%s]", + ysize = "g_tile.sizy[%s]", + + -- picanm[] +-- "animframes", +-- "xoffset", +-- "yoffset", +-- "animspeed", +-- "animtype", + + -- g_tile[] + gameflags = { "g_tile[%s]._flags" }, +} + +StructAccessCode = +{ + sector = SectorLabels, + wall = WallLabels, + sprite = ActorLabels, + player = PlayerLabels, + tspr = TspriteLabels, + projectile = ProjectileLabels, + thisprojectile = SpriteProjectileLabels, + userdef = UserdefLabels, + input = InputLabels, + tiledata = TileDataLabels, +-- TODO: tiledata picanm[] members, paldata +} + +-- NOTE: These MUST be in reverse lexicographical order! +-- Per CON syntax, valid identifiers names are disjoint from keywords, +-- so that a rule like +-- t_identifier = -con_keyword * (sp1 + "[") * t_identifier_all +-- (from the final grammar in lunacon.lua) must match the longest +-- possible keyword name, else the negation might wrongly not fail. + +keyword = + +lpeg.P(false) + +"}" + +"{" + +"zshootvar" + +"zshoot" + +"xorvarvar" + +"xorvar" + +"writearraytofile" + +"whilevarvarn" + +"whilevarn" + +"wackplayer" + +"userquote" + +"useractor" + +"updatesectorz" + +"updatesector" + +"undefinevolume" + +"undefineskill" + +"undefinelevel" + +"tossweapon" + +"tip" + +"time" + +"switch" + +"subvarvar" + +"subvar" + +"strength" + +"stopsoundvar" + +"stopsound" + +"stopallsounds" + +"stopactorsound" + +"state" + +"starttrackvar" + +"starttrack" + +"startlevel" + +"startcutscene" + +"ssp" + +"sqrt" + +"spriteshadow" + +"spritepal" + +"spritenvg" + +"spritenoshade" + +"spritenopal" + +"spriteflags" + +"spgetlotag" + +"spgethitag" + +"spawn" + +"soundvar" + +"soundoncevar" + +"soundonce" + +"sound" + +"smaxammo" + +"sleeptime" + +"sizeto" + +"sizeat" + +"sin" + +"showviewunbiased" + +"showview" + +"shootvar" + +"shoot" + +"shiftvarr" + +"shiftvarl" + +"shadeto" + +"setwall" + +"setvarvar" + +"setvar" + +"setuserdef" + +"settspr" + +"setthisprojectile" + +"setsprite" + +"setsector" + +"setprojectile" + +"setplayervar" + +"setplayerangle" + +"setplayer" + +"setmusicposition" + +"setinput" + +"setgamepalette" + +"setgamename" + +"setdefname" + +"setcfgname" + +"setaspect" + +"setarray" + +"setactorvar" + +"setactorsoundpitch" + +"setactorangle" + +"setactor" + +"sectsetinterpolation" + +"sectorofwall" + +"sectgetlotag" + +"sectgethitag" + +"sectclearinterpolation" + +"scriptsize" + +"screentext" + +"screensound" + +"savenn" + +"savemapstate" + +"savegamevar" + +"save" + +"rotatespritea" + +"rotatesprite16" + +"rotatesprite" + +"rotatepoint" + +"return" + +"respawnhitag" + +"resizearray" + +"resetplayerflags" + +"resetplayer" + +"resetcount" + +"resetactioncount" + +"redefinequote" + +"readgamevar" + +"readarrayfromfile" + +"rayintersect" + +"randvarvar" + +"randvar" + +"quote" + +"quake" + +"qsubstr" + +"qstrncat" + +"qstrlen" + +"qstrdim" + +"qstrcpy" + +"qstrcat" + +"qsprintf" + +"qspawnvar" + +"qspawn" + +"qgetsysstr" + +"pstomp" + +"prevspritestat" + +"prevspritesect" + +"precache" + +"pkick" + +"paper" + +"palfrom" + +"orvarvar" + +"orvar" + +"operatesectors" + +"operaterespawns" + +"operatemasterswitches" + +"operateactivators" + +"operate" + +"onevent" + +"nullop" + +"nextspritestat" + +"nextspritesect" + +"neartag" + +"myosx" + +"myospalx" + +"myospal" + +"myos" + +"music" + +"mulvarvar" + +"mulvar" + +"mulscale" + +"movesprite" + +"move" + +"money" + +"modvarvar" + +"modvar" + +"minitext" + +"mikesnd" + +"mail" + +"lotsofglass" + +"lockplayer" + +"loadmapstate" + +"lineintersect" + +"ldist" + +"killit" + +"jump" + +"insertspriteq" + +"inittimer" + +"includedefault" + +"include" + +"ifwasweapon" + +"ifvarxor" + +"ifvarvarxor" + +"ifvarvaror" + +"ifvarvarn" + +"ifvarvarl" + +"ifvarvarg" + +"ifvarvareither" + +"ifvarvare" + +"ifvarvarand" + +"ifvaror" + +"ifvarn" + +"ifvarl" + +"ifvarg" + +"ifvareither" + +"ifvare" + +"ifvarand" + +"ifstrength" + +"ifsquished" + +"ifspritepal" + +"ifspawnedby" + +"ifsound" + +"ifserver" + +"ifrnd" + +"ifrespawn" + +"ifplayersl" + +"ifpinventory" + +"ifphealthl" + +"ifpdistl" + +"ifpdistg" + +"ifp" + +"ifoutside" + +"ifonwater" + +"ifnotmoving" + +"ifnosounds" + +"ifmultiplayer" + +"ifmove" + +"ifinwater" + +"ifinspace" + +"ifinouterspace" + +"ifhitweapon" + +"ifhitspace" + +"ifgotweaponce" + +"ifgapzl" + +"iffloordistl" + +"ifdead" + +"ifcutscene" + +"ifcount" + +"ifclient" + +"ifceilingdistl" + +"ifcanshoottarget" + +"ifcanseetarget" + +"ifcansee" + +"ifbulletnear" + +"ifawayfromwall" + +"ifangdiffl" + +"ifai" + +"ifactorsound" + +"ifactornotstayput" + +"ifactor" + +"ifactioncount" + +"ifaction" + +"hitscan" + +"hitradiusvar" + +"hitradius" + +"headspritestat" + +"headspritesect" + +"guts" + +"guniqhudid" + +"gmaxammo" + +"globalsoundvar" + +"globalsound" + +"getzrange" + +"getwall" + +"getuserdef" + +"gettspr" + +"gettimedate" + +"getticks" + +"getthisprojectile" + +"gettexturefloor" + +"gettextureceiling" + +"getsector" + +"getprojectile" + +"getpname" + +"getplayervar" + +"getplayerangle" + +"getplayer" + +"getmusicposition" + +"getlastpal" + +"getkeyname" + +"getinput" + +"getincangle" + +"getflorzofslope" + +"getcurraddress" + +"getceilzofslope" + +"getarraysize" + +"getangletotarget" + +"getangle" + +"getactorvar" + +"getactorangle" + +"getactor" + +"gamevar" + +"gametextz" + +"gametext" + +"gamestartup" + +"gamearray" + +"flash" + +"findplayer" + +"findotherplayer" + +"findnearspritezvar" + +"findnearspritez" + +"findnearspritevar" + +"findnearsprite3dvar" + +"findnearsprite3d" + +"findnearsprite" + +"findnearactorzvar" + +"findnearactorz" + +"findnearactorvar" + +"findnearactor3dvar" + +"findnearactor3d" + +"findnearactor" + +"fall" + +"ezshootvar" + +"ezshoot" + +"eventloadactor" + +"espawnvar" + +"espawn" + +"eshootvar" + +"eshoot" + +"eqspawnvar" + +"eqspawn" + +"enhanced" + +"endswitch" + +"ends" + +"endoflevel" + +"endofgame" + +"endevent" + +"enda" + +"else" + +"echo" + +"dynamicsoundremap" + +"dynamicremap" + +"dragpoint" + +"divvarvar" + +"divvar" + +"dist" + +"displayrandvarvar" + +"displayrandvar" + +"displayrand" + +"digitalnumberz" + +"digitalnumber" + +"defstate" + +"definevolumename" + +"definevolumeflags" + +"definesound" + +"defineskillname" + +"definequote" + +"defineprojectile" + +"definelevelname" + +"definegametype" + +"definegamefuncname" + +"definecheat" + +"define" + +"default" + +"debug" + +"debris" + +"cstator" + +"cstat" + +"count" + +"cos" + +"copy" + +"cmenu" + +"clipmovenoslide" + +"clipmove" + +"clipdist" + +"clearmapstate" + +"checkavailweapon" + +"checkavailinven" + +"checkactivatormotion" + +"cheatkeys" + +"changespritestat" + +"changespritesect" + +"case" + +"canseespr" + +"cansee" + +"calchypotenuse" + +"cactor" + +"break" + +"betaname" + +"appendevent" + +"angoffvar" + +"angoff" + +"andvarvar" + +"andvar" + +"ai" + +"addweaponvar" + +"addweapon" + +"addvarvar" + +"addvar" + +"addstrength" + +"addphealth" + +"addlogvar" + +"addlog" + +"addkills" + +"addinventory" + +"addammo" + +"actor" + +"activatecheat" + +"activatebysector" + +"activate" + +"action" + +lpeg.P(false) diff --git a/source/rr/src/lunatic/control.lua b/source/rr/src/lunatic/control.lua new file mode 100644 index 000000000..550c44767 --- /dev/null +++ b/source/rr/src/lunatic/control.lua @@ -0,0 +1,2420 @@ +-- Game control module for Lunatic. + +local require = require +local ffi = require("ffi") +local ffiC = ffi.C +local jit = require("jit") + +-- Lua C API functions, this comes from El_PushCFunctions() in lunatic_game.c. +local CF = CF + +local bit = require("bit") +local debug = require("debug") +local io = require("io") +local math = require("math") +local table = require("table") + +local bcheck = require("bcheck") +local con_lang = require("con_lang") + +local byte = require("string").byte +local setmetatable = setmetatable + +local band, bor = bit.band, bit.bor +local rshift = bit.rshift +local tobit = bit.tobit + +local floor = math.floor + +local assert = assert +local error = error +local ipairs = ipairs +local pairs = pairs +local print = print +local rawget = rawget +local rawset = rawset +local select = select +local tostring = tostring +local type = type +local unpack = unpack + +local format = require("string").format + +local actor, player = assert(actor), assert(player) +local dc = require("defs_common") +local cansee, hitscan, neartag = dc.cansee, dc.hitscan, dc.neartag +local inside = dc.inside + +local sector, wall, sprite = dc.sector, dc.wall, dc.sprite +local wallsofsect = dc.wallsofsect +local spritesofsect, spritesofstat = dc.spritesofsect, dc.spritesofstat + +local check_sector_idx = bcheck.sector_idx +local check_tile_idx = bcheck.tile_idx +local check_sprite_idx = bcheck.sprite_idx +local check_player_idx = bcheck.player_idx +local check_sound_idx = bcheck.sound_idx +local check_number = bcheck.number +local check_type = bcheck.type + +local lprivate = require("lprivate") +local GET, WEAPON = lprivate.GET, lprivate.WEAPON + +ffi.cdef[[ +size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, void * restrict stream); +]] + +local OUR_REQUIRE_STRING = [[ + local _con=require'con' + local _ga,_av,_pv=_con._gamearray,_con.actorvar,_con.playervar +]] +local function our_get_require() + return OUR_REQUIRE_STRING +end + + +module(...) + + +---=== ACTION/MOVE/AI HELPERS ===--- + +local lastid = { action=0, move=0, ai=0 } + +local con_action_ct = ffi.typeof("const con_action_t") +local con_move_ct = ffi.typeof("const con_move_t") +local con_ai_ct = ffi.typeof("const con_ai_t") + +-- All-zero action and move with IDs. Mostly for CON support. +local literal_act = { [0]=con_action_ct(0), [1]=con_action_ct(1) } +local literal_mov = { [0]=con_move_ct(0), [1]=con_move_ct(1) } + +local literal_am = { action=literal_act, move=literal_mov } +-- Const-qualified 'full' action and move (with ID): +local am_ctype_full_const = { action=con_action_ct, move=con_move_ct } +-- Non-const-qualified 'bare' action and move (without ID): +local am_ctype_bare = { action=ffi.typeof("struct action"), move=ffi.typeof("struct move") } + +-- CODEDUP lunacon.lua +local function truetab(tab) + local ttab = {} + for i=1,#tab do + ttab[tab[i]] = true + end + return ttab +end + +-- KEEPINSYNC lunacon.lua +local ALLOWED_VIEWTYPE = truetab { 0, 1, 2, 3,4, 5, 7, 8, -5, -7, -8 } + +local function def_action_or_move(what, tab) + if (lastid[what] <= -(2^31)) then + error("Too many "..what.."s defined", 3); + end + + bcheck.top_level(what, 4) + + -- NOTE: tab[0]~=nil check for "Special default values" below. + if (type(tab) ~= "table" or tab[0]~=nil) then + error("invalid argument to con."..what..": must be a table", 3) + end + + -- Pass args table to ffi.new, which can take either: a table with numeric + -- indices, or a table with key-value pairs, *but not in combination*. + -- See http://luajit.org/ext_ffi_semantics.html#init_table + local am = am_ctype_bare[what](tab) + + -- Now, set all string keys as they have been ignored if tab[1] was + -- non-nil. + for key, val in pairs(tab) do + if (type(key)=="string") then + am[key] = val + end + end + + if (what=="action") then + -- Special default values or checking of actor members. + -- KEEPINSYNC with ACTOR_CHECK in lunacon.lua for consistency. + local numframes = tab[2] or tab.numframes + local viewtype = tab[3] or tab.viewtype + local incval = tab[4] or tab.incval + + if (numframes==nil) then + am.numframes = 1 + else + check_number(numframes, 4) + if (numframes < 0) then + error("action has negative number of frames", 3) + end + end + + if (viewtype==nil) then + am.viewtype = 1 + else + check_number(viewtype, 4) + if (ALLOWED_VIEWTYPE[viewtype] == nil) then + error("action has disallowed viewtype "..viewtype, 3) + end + end + + if (incval==nil) then + am.incval = 1 + end + end + + -- Named actions or moves have negative ids so that non-negative ones + -- can be used as (different) placeholders for all-zero ones. + lastid[what] = lastid[what]-1 + + return am_ctype_full_const[what](lastid[what], am) +end + +---=== ACTION/MOVE/AI FUNCTIONS ===--- + +function action(tab) + return def_action_or_move("action", tab) +end + +function move(tab) + return def_action_or_move("move", tab) +end + +-- Get action or move for an 'ai' definition. +local function get_action_or_move(what, val, argi) + if (val == nil) then + return literal_am[what][0] + elseif (ffi.istype(am_ctype_full_const[what], val)) then + return val + elseif (type(val)=="number") then + if (val==0 or val==1) then + return literal_am[what][val] + end + end + + error("bad argument #"..argi.." to ai: must be nil/nothing, 0, 1, or "..what, 3) +end + +function ai(action, move, flags) + bcheck.top_level("ai") + + if (lastid.ai <= -(2^31)) then + error("Too many AIs defined", 2); + end + + local act = get_action_or_move("action", action, 2) + local mov = get_action_or_move("move", move, 3) + + if (flags~=nil) then + if (type(flags)~="number" or not (flags>=0 and flags<=32767)) then + error("bad argument #4 to ai: must be a number in [0..32767]", 2) + end + else + flags = 0 + end + + lastid.ai = lastid.ai-1 + return con_ai_ct(lastid.ai, act, mov, flags) +end + + +---=== RUNTIME CON FUNCTIONS ===--- + +-- Will contain [