/* ************************************************************************* ArmageTron -- Just another Tron Lightcycle Game in 3D. Copyright (C) 2000 Manuel Moos (manuel@moosnet.de) ************************************************************************** This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *************************************************************************** */ #include "eGameObject.h" #include "uInputQueue.h" #include "eTimer.h" #include "eTess2.h" #include "eWall.h" #include "tConsole.h" #include "rScreen.h" #include "eSound.h" #include "eAdvWall.h" #include "eGrid.h" #include "uInput.h" #include "tMath.h" #include "nConfig.h" uActionPlayer eGameObject::se_turnRight("CYCLE_TURN_RIGHT", -10); uActionPlayer eGameObject::se_turnLeft("CYCLE_TURN_LEFT", -10); // entry and deletion in the list of all gameObjects void eGameObject::AddToList(){ se_SoundLock(); grid->gameObjectsInactive.Remove(this,inactiveID); grid->gameObjects.Add(this,id); se_SoundUnlock(); } void eGameObject::RemoveFromList(){ se_SoundLock(); // z-man: the next line is not needed for consistency, but made problems because // the internal camera calls this function on its cycle, circumventing topology checks. // currentFace = NULL; grid->gameObjects.Remove(this,id); grid->gameObjectsInactive.Add(this,inactiveID); se_SoundUnlock(); } void eGameObject::RemoveFromListsAll(){ se_SoundLock(); // dito here // currentFace = NULL; grid->gameObjects.Remove(this,id); grid->gameObjectsInactive.Remove(this,inactiveID); grid->gameObjectsInteresting.Remove(this,interestingID); se_SoundUnlock(); } void eGameObject::RemoveFromGame(){ delete this; } eGameObject::eGameObject(eGrid *g,const eCoord &p,const eCoord &d,eFace *currentface,bool autodel) :autodelete(autodel),pos(p),dir(d),z(0),grid(g){ tASSERT(g); currentFace=currentface; lastTime=se_GameTime(); id=-1; interestingID=-1; inactiveID=-1; if (grid) { AddToList(); // FindCurrentFace(); } lastTime=0; if ( !currentFace ) { FindCurrentFace(); } } eGameObject::~eGameObject(){ currentFace = 0; RemoveFromListsAll(); tCHECK_DEST; } // returns the type of this object (important for interaction of // two gameObjects) //gameobject_type gameobject::type(){return ArmageTron_GENERIC;} // makes two gameObjects interact: void eGameObject::InteractWith(eGameObject *,REAL,int){} // what happens if we pass eWall w? void eGameObject::PassEdge(const eWall *w,REAL,REAL,int){ if (w) Kill(); } // moves void eGameObject::Move(const eCoord &dest,REAL startTime,REAL endTime){ #ifdef DEBUG grid->Check(); #endif if (!finite(dest.x) || !finite(dest.y)) { st_Breakpoint(); return; } tStackObject< ePoint > start(pos),stop(dest); ePoint* pstart = &start; ePoint* pstop = &stop; eWallRim::Bound(stop,-10); grid->Range(stop.NormSquared()); // correct end time for short movement if ( stop != dest && pos != stop ) { eCoord planned = dest - pos; endTime = startTime + ( endTime - startTime ) * eCoord::F( planned, stop - pos ) / planned.NormSquared(); } #ifdef DEBUG if (!finite(stop.x) || !finite(stop.y)) { st_Breakpoint(); static_cast(stop) = dest; eWallRim::Bound(stop,-10); return; } #endif // se_GridRange(dest.Norm_squared()); tStackObject< eTempEdge > te( pstart, pstop ); eHalfEdge &e=*te.Edge(0); // check all the currently drawn eWalls: for(int i=grid->wallsNotYetInserted.Len()-1;i>=0;i--){ const eHalfEdge *other_e=grid->wallsNotYetInserted[i]->Edge(); if (//!sg_netPlayerWalls(i)->Preliminary() && other_e->Point() && other_e->Other() && other_e->Other()->Point()){ tJUST_CONTROLLED_PTR< ePoint > new_cross_p=e.IntersectWith(other_e); if (new_cross_p){ REAL e_ratio =e.Ratio(*new_cross_p); REAL o_ratio =other_e->Ratio(*new_cross_p); if (0<=e_ratio && 1>=e_ratio && 0<=o_ratio && 1>=o_ratio) { // find the fall eWall *w = other_e->GetWall(); if (!w) { w = other_e->Other()->GetWall(); o_ratio = 1-o_ratio; } if (w) PassEdge(w, startTime+(endTime-startTime)*e_ratio, o_ratio,0); } } } } // find a replacement face if required FindCurrentFace(); if (currentFace){ /* REAL likelyhood=0; // the likelyhood that this is true ePoint *cross_p=NULL;// the ePoint we are crossing it on eHalfEdge *cross_e=NULL; int timeout = 1000; do{ timeout--; if (timeout == 10) st_Breakpoint(); likelyhood=-1E20; REAL ratio=1; REAL e_ratio=0,ei_ratio=0; if (!currentFace->IsInside(stop)){ // check the edges of the current eFace eHalfEdge *run = currentFace->Edge(); for(int i=0;i<=2;i++){ ePoint *new_cross_p=e.IntersectWith(run); if (new_cross_p && (stop-start)* run->Vec() <=0 ){ e_ratio =e.Ratio(*new_cross_p); ei_ratio=run->Ratio(*new_cross_p); REAL la=e_ratio*(1-e_ratio); REAL lb=ei_ratio*(1-ei_ratio); REAL this_likelyhood=la+lb+0.00203; if (la<0) this_likelyhood-=1; if (lalikelyhood){ cross_e = run; likelyhood=this_likelyhood; tDESTROY_PTR(cross_p); cross_p=new_cross_p; ratio=e_ratio; } } if (new_cross_p && cross_p!=new_cross_p){ tDESTROY_PTR(new_cross_p); #ifdef DEBUG if (!cross_p && !(start == stop)) grid->Check(); #endif } run = run->Next(); } } if (cross_e){ #ifdef DEBUG if (likelyhood <0) { con << "Likelyhood=" << likelyhood << '\n'; con << "On its way from " << pos << " to " << stop << ",\n"; } #endif REAL time=startTime+(endTime-startTime)*ratio; if (cross_p) { pos=*cross_p; } else { // st_Breakpoint(); pos = (*(cross_e->Point()) + *(cross_e->Other()->Point()))*.5f; } #ifdef DEBUG if (likelyhood <0) con << "Gameobject at " << pos << " leaves eFace " << *currentFace <<'\n'; #endif eWall* w = cross_e->Wall(); eHalfEdge* other = cross_e->Other(); if (!w && other) w = other->Wall(); if (w) PassEdge(w,time,ei_ratio,0); if (other) currentFace=other->Face(); else currentFace=NULL; #ifdef DEBUG if (!(currentFace == NULL) && (likelyhood <0)) con << "\tand enters " << *currentFace << " through eEdge\n" << *cross_e << '\n'; #endif start=pos; startTime=time; } tDESTROY_PTR(cross_p); // cleanup } while(!(currentFace == NULL) && !currentFace->IsInside(stop) && cross_e && timeout > 0); */ int timeout = 100; while (currentFace && timeout >0 && !currentFace->IsInside(stop)){ timeout--; rerun: eHalfEdge *run = currentFace->Edge(); // runs through all edges of the face eHalfEdge *best = NULL; // the best face to leave eHalfEdge *end = run; eHalfEdge *in = NULL; REAL bestScore = -1000; REAL bestERatio = .5; REAL bestRRatio = .5; eCoord bestCross (0,0); // look for the best way out do { run = run->Next(); if (run == in) // never leave through the edge we entered continue; eCoord vec=stop - pos; // the vector to our destination eCoord runVec = run->Vec(); REAL score = runVec * vec / ( se_EstimatedRangeOfMult( runVec, vec ) + EPS ); static const REAL smallBias = .01; // keep a bit of the score, but not too much. We want to // sort out exactly parallel edges here. if ( score > smallBias ) score = smallBias; eCoord cross = e.IntersectWithCareless(run); REAL e_ratio = e.Ratio(cross); if ( !good( e_ratio ) ) { score -= 100; e_ratio = .5; } if (e_ratio < 0) { score += e_ratio; e_ratio = 0; } else if (e_ratio > 1) { score += (1-e_ratio); e_ratio = 1; } cross = *e.Point() + e.Vec() * e_ratio; REAL run_ratio = run->Ratio(cross); if ( !good( run_ratio ) ) { score -= 100; run_ratio = .5; } if (run_ratio < 0) { score += run_ratio; run_ratio = 0; } else if (run_ratio > 1) { score += (1-run_ratio); run_ratio = 1; } cross = *run->Point() + run->Vec() * run_ratio; if (!best || score > bestScore) { best = run; bestScore = score; bestERatio = e_ratio; bestRRatio = run_ratio; bestCross = cross; } } while (run != end); if ( !good( bestScore ) ) goto rerun; if (best) { pos = bestCross; tASSERT(best->Other()); in = best->Other(); REAL time=startTime+(endTime-startTime)*bestERatio; eWall* w = best->GetWall(); if (w) PassEdge(w,time,bestRRatio,0); if (in) { bestRRatio = 1-bestRRatio; w = in->GetWall(); if (w) PassEdge(w,time,bestRRatio,0); } if (in) currentFace=in->Face(); else currentFace=NULL; timeout--; } else { timeout = 0; st_Breakpoint(); } } if (timeout <= 0) grid->requestCleanup = true; } pos=stop; // find a replacement face if required FindCurrentFace(); //#ifdef DEBUG //se_CheckGrid(); //#endif if (id<0) currentFace = NULL; } /* the old way if (!currentFace || !currentFace->IsInside(pos)) FindCurrentFace(); if (currentFace){ //if (pp_out) currentFace->DebugPlot(PD_CreateColor(DoubleBuffer,0,255,0)); ePoint start(pos),stop(dest); eEdge e(&start,&stop); int eEdge_out=0; // the eEdge we are leaving currentFace through ePoint *cross_p=NULL;// the ePoint we are crossing it on eEdge *cross_e=NULL; while(currentFace && eEdge_out>=0){ eEdge_out=-1; for(int i=0;i<=2 && eEdge_out<0;i++){ cross_p=e.IntersectWith(currentFace->e[i]); if (cross_p){ REAL ratio=e.Ratio(*cross_p); if (ratio>EPS){ eEdge_out=i; cross_e=currentFace->e[i]; REAL time=startTime+(endTime-startTime)*ratio; PassEdge(cross_e,time,cross_e->Ratio(*cross_p),0); currentFace=cross_e->Other(currentFace); start=*cross_p; startTime=time; } } } } if (cross_p) {delete cross_p;cross_p=0;} // cleanup } pos=dest; */ // emulate old bug allowing objects to tunnel through walls static short se_bugTunnel = false; static nSettingItem se_bugTunnelConfig("BUG_TUNNEL", se_bugTunnel ); void eGameObject::FindCurrentFace(){ // find a replacement for a removed face if ( currentFace && !currentFace->IsInGrid() ) { if ( !se_bugTunnel ) currentFace = currentFace->FindReplacement( pos ); else // allow tunneling through walls currentFace = NULL; } // did that do the trick? If no, use brute force. if ( !currentFace ) currentFace = grid->FindSurroundingFace(pos); /* if (grid->faces.Len()<1) return; if (!currentFace) currentFace=grid->faces(0); int timeout=grid->faces.Len()+2; while (currentFace && timeout >0 && !currentFace->IsInside(pos)){ timeout--; eHalfEdge *run = currentFace->Edge(); // runs through all edges of the face eHalfEdge *best = NULL; // the best face to leave eHalfEdge *first = NULL; REAL bestScore = 0; eCoord vec=pos - (*run->Point()); // the vector to our destination // look for the best way out while(run != first) { REAL score = run->Vec() * vec; if (score > bestScore || !best) { if (!first) first = run; best = run; bestScore = score; } run = run->Next(); } tASSERT(best->Other()); currentFace = best->Other()->Face(); timeout--; } if (timeout<=0){ // normal way failed #ifdef DEBUG con << "WARNING! FindCurrentFace failed.\n"; #endif // do it the hard way: for(int i=grid->faces.Len()-1;i>=0;i--) if(grid->faces(i)->IsInside(pos)){ currentFace=grid->faces(i); i=-1; } } */ } // simulates behaviour up to currentTime: bool eGameObject::Timestep(REAL t){ lastTime = t; return 0; } // return value: shall this object be destroyed? void eGameObject::Kill(){} // draws it to the screen using OpenGL void eGameObject::Render(const eCamera *){} // Cockpit bool eGameObject::RenderCockpitFixedBefore(bool){return true;} // return value: draw everything else? // the same purpose, but called after main rendering void eGameObject::RenderCockpitFixedAfter(bool){} // virtual perspective void eGameObject::RenderCockpitVirtual(bool){} #ifdef POWERPAK_DEB void eGameObject::PPDisplay(){ PD_PutPixel(DoubleBuffer, se_X_ToScreen(pos.x), se_Y_ToScreen(pos.y), PD_CreateColor(DoubleBuffer,255,0,100)); PD_PutPixel(DoubleBuffer, se_X_ToScreen(pos.x+1), se_Y_ToScreen(pos.y), PD_CreateColor(DoubleBuffer,255,0,100)); PD_PutPixel(DoubleBuffer, se_X_ToScreen(pos.x-1), se_Y_ToScreen(pos.y), PD_CreateColor(DoubleBuffer,255,0,100)); PD_PutPixel(DoubleBuffer, se_X_ToScreen(pos.x), se_Y_ToScreen(pos.y+1), PD_CreateColor(DoubleBuffer,255,0,100)); PD_PutPixel(DoubleBuffer, se_X_ToScreen(pos.x), se_Y_ToScreen(pos.y-1), PD_CreateColor(DoubleBuffer,255,0,100)); } #endif // Receives control from player; nothing to do here bool eGameObject::Act(uActionPlayer *,REAL){return false;} bool eGameObject::TimestepThis(REAL currentTime,eGameObject *c){ #ifdef DEBUG c->grid->Check(); #endif #ifdef DEBUG REAL maxstep=.2; #else REAL maxstep=.2; // don't do a thing if the timestep is too small if (fabs(currentTime - c->lastTime) < .001) return false; // be more careful when going back if (currentTimelastTime) maxstep=.1; #endif int number_of_steps=int(fabs((currentTime-c->lastTime)/maxstep)); if (number_of_steps<1) number_of_steps=1; if ( number_of_steps > 10 ) { number_of_steps = 10; } REAL lastTime=c->lastTime; bool ret=false; for(int i=1;i<=number_of_steps;i++) { // make current face valid c->FindCurrentFace(); ret = ret || c->Timestep(lastTime+i*(currentTime-lastTime)/number_of_steps); } #ifdef DEBUG c->grid->Check(); #endif return ret; } // does a timestep and all interactions for every eGameObject void eGameObject::s_Timestep(eGrid *grid, REAL currentTime) { #ifdef DEBUG grid->Check(); #endif for(int i=grid->gameObjects.Len()-1;i>=0;i--) { su_FetchAndStoreSDLInput(); eGameObject *c=grid->gameObjects(i); REAL simTime=currentTime; // backdate the object a bit #ifndef DEDICATED if (sn_GetNetState()==nCLIENT && !sr_predictObjects) #endif simTime -= c->Lag(); if (!eWallRim::IsBound(c->pos,-20)) c->Kill(); else if (TimestepThis(simTime,c)) { if (c->autodelete) c->RemoveFromGame(); else { c->RemoveFromList(); c->currentFace=NULL; } } else if (sn_GetNetState()!=nCLIENT) for(int j=grid->gameObjects.Len()-1;j>=0;j--) c->InteractWith(grid->gameObjects(j),currentTime,0); } #ifdef DEBUG grid->Check(); #endif } #ifdef DEBUG eGameObject *displayed_gameobject = 0; #endif void eGameObject::RenderAll(eGrid *grid, const eCamera *cam){ if (!sr_glOut) return; for(int i=grid->gameObjects.Len()-1;i>=0;i--){ su_FetchAndStoreSDLInput(); if (sr_glOut){ #ifdef DEBUG displayed_gameobject = grid->gameObjects(i); #endif grid->gameObjects(i)->Render(cam); #ifdef DEBUG displayed_gameobject = 0; #endif } } } #ifdef POWERPAK_DEB void eGameObject::PPDisplayAll(){ for(int i=gameObjects.Len()-1;i>=0;i--){ if (pp_out) gameObjects(i)->PPDisplay(); } } #endif void eGameObject::DeleteAll(eGrid *grid){ int i; for(i=grid->gameObjects.Len()-1;i>=0;i--) { eGameObject* o = grid->gameObjects(i); o->Kill(); if (o->autodelete) o->RemoveFromGame(); #ifdef POWERPAK_DEB if (pp_out) o->PPDisplay(); #endif } }