// update position void CCharacter::UpdatePosition( ) { if(IsOnBattle( ) && Battle->target!=0) { CCharacter* Target = GetCharTarget( ); if(Target!=NULL) { if(IsMonster()) { float distance = GServer->distance( Position->current, Position->source ); if(distance>50)//AI should take care of this { OnFar( ); } //else Position->destiny = Target->Position->current; } //else Position->destiny = Target->Position->current; } else ClearBattle( Battle ); } if(!IsMoving()){ /* if(IsPlayer()) */ Position->lastMoveTime = clock(); return; } float dx = Position->destiny.x - Position->current.x; float dy = Position->destiny.y - Position->current.y; float distance = sqrt( (dx*dx) + (dy*dy) ); float ntime = ( distance / (float(Stats->Move_Speed)/100.f)) * CLOCKS_PER_SEC;// * 100000; clock_t etime = clock() - Position->lastMoveTime; if (ntime<=etime || distance==0) { Position->current.x = Position->destiny.x; Position->current.y = Position->destiny.y; } else { Position->current.x = dx*(etime/ntime) + Position->current.x; Position->current.y = dy*(etime/ntime) + Position->current.y; } Position->lastMoveTime = clock(); }
// update attack values and destiny position bool CMonster::UpdateValues( ) { //LMA: Some special case where special monsters stay still (mc, bonfires and so on...) if(stay_still&&(!IsBonfire())) return true; if(IsSummon( ) && CanMove( )) { CPlayer* thisclient = GetOwner( ); if(thisclient!=NULL) { if(!IsBonfire()) { if(!IsOnBattle( ) && thisclient->IsAttacking( )) { Battle->target = thisclient->Battle->target; Battle->atktarget = Battle->target; Battle->atktype = NORMAL_ATTACK; Battle->contatk = true; CCharacter* Enemy = GetCharTarget( ); if(Enemy!=NULL) StartAction( Enemy, NORMAL_ATTACK ); } else if(!IsOnBattle( )) { Position->source = thisclient->Position->current; float distance = GServer->distance( Position->destiny , thisclient->Position->current ); if((distance>15 && !IsOnBattle()) || distance>50) Move( ); } } else { //LMA: Let's kill bonfires if owner too far away :). float distance = GServer->distance( Position->current , thisclient->Position->current ); if(distance>25) { UnspawnMonster( ); return false; } return true; } } else { UnspawnMonster( ); return false; } } if(!IsMoving( ) && !IsOnBattle( ) && CanMove( )) { clock_t etime = clock() - Position->lastMoveTime; if(etime > 20*CLOCKS_PER_SEC) Move( ); } if(!IsSummon( )) { CPlayer* player = GetNearPlayer( 10 ); if(player!=NULL) OnEnemyOnSight( player ); } return true; }
// start action [attack] void CCharacter::StartAction( CCharacter* Target, BYTE action, UINT skillid, bool restart, CCharacter* receiver ) { if(IsPlayer()) { Log(MSG_INFO,"A Player does an action %i, skill %i",action,skillid); } else { Log(MSG_INFO,"A Monster does an action %i, skill %i",action,skillid); } BEGINPACKET( pak, 0 ); if (restart) { Target=GetCharTarget( ); action=Battle->atktype; skillid=Battle->skillid; } //Drakia: If the target is NULL, we should only do something that doesn't require a target. if (Target == NULL && (action != SKILL_AOE &&action != BUFF_SELF &&action != BUFF_AOE &&action != MONSTER_BUFF_SELF &&action != AOE_TARGET)) { Log(MSG_WARNING,"We tried to start attack %i without a target",action); return; } //LMA: don't attack a dead or an offline player... It's stupid... if(Target!=NULL) { if(Target->IsPlayer()) { CPlayer* thisplayer=reinterpret_cast<CPlayer*>(Target); if(!thisplayer->Session->inGame) { //Log(MSG_HACK,"We don't attack a player not in game yet..., %s",thisplayer->CharInfo->charname); return; } } if(Target->IsDead()) { //but we can if it's a friendly (restore...). if(action!=SKILL_BUFF) { ClearBattle(Battle); return; } } } switch(action) { case NORMAL_ATTACK: { RESETPACKET( pak, 0x798 ); ADDWORD ( pak, clientid ); ADDWORD ( pak, Target->clientid ); //ADDWORD ( pak, Stats->Move_Speed ); ADDWORD ( pak, Stats->Base_Speed ); //Log(MSG_INFO,"move speed %u, base speed %u",Stats->Move_Speed,Stats->Base_Speed); ADDFLOAT ( pak, Target->Position->current.x*100 ); ADDFLOAT ( pak, Target->Position->current.y*100 ); Battle->target = Target->clientid; Battle->atktarget = Target->clientid; Battle->atktype = action; Position->destiny = Target->Position->current; Position->lastMoveTime = clock( ); } break; case SKILL_ATTACK: case SKILL_BUFF: { RESETPACKET( pak, 0x7b3 ); ADDWORD ( pak, clientid ); ADDWORD ( pak, Target->clientid ); ADDWORD ( pak, skillid ); ADDWORD ( pak, 50000 ); ADDFLOAT ( pak, Target->Position->current.x*100 ); ADDFLOAT ( pak, Target->Position->current.y*100 ); ADDFLOAT ( pak, Position->aoedestiny.x*100 ); ADDFLOAT ( pak, Position->aoedestiny.y*100 ); Battle->target = Target->clientid; if(action==SKILL_ATTACK) Battle->skilltarget = Target->clientid; else Battle->bufftarget = Target->clientid; Battle->atktype = action; Position->destiny = Target->Position->current; Battle->skillid = skillid; Position->lastMoveTime = clock( ); } break; case MONSTER_SKILL_ATTACK: case MONSTER_SKILL_BUFF: { RESETPACKET( pak, 0x7b3 ); ADDWORD ( pak, clientid ); ADDWORD ( pak, Target->clientid ); ADDWORD ( pak, skillid ); ADDBYTE (pak,0x2b); ADDBYTE (pak,0x01); ADDFLOAT ( pak, Target->Position->current.x*100 ); ADDFLOAT ( pak, Target->Position->current.y*100 ); ADDBYTE ( pak, 0x06); Battle->target = Target->clientid; if(action==MONSTER_SKILL_ATTACK) Battle->skilltarget = Target->clientid; else Battle->bufftarget = Target->clientid; Battle->atktype = action; Position->destiny = Target->Position->current; Battle->skillid = skillid; Position->lastMoveTime = clock( ); } break; case SUMMON_BUFF: { //LMA: Special case for Support summons RESETPACKET( pak, 0x7b3 ); ADDWORD ( pak, clientid ); ADDWORD ( pak, Target->clientid ); ADDWORD ( pak, skillid ); ADDBYTE (pak,0x2b); ADDBYTE (pak,0x01); ADDFLOAT ( pak, Target->Position->current.x*100 ); ADDFLOAT ( pak, Target->Position->current.y*100 ); /* ADDFLOAT ( pak, Position->current.x*100 ); ADDFLOAT ( pak, Position->current.y*100 ); */ ADDBYTE ( pak, 0x06); Battle->target = 0; //Battle->skilltarget = Target->clientid; Battle->bufftarget = Target->clientid; Battle->atktype = action; Position->destiny = Target->Position->current; Battle->skillid = skillid; Position->lastMoveTime = clock( ); GServer->SendToVisible( &pak, Target ); return; } break; case SKILL_AOE: case BUFF_SELF: case BUFF_AOE: { RESETPACKET( pak, 0x7b2); ADDWORD ( pak, clientid ); ADDWORD ( pak, skillid ); Battle->atktype = action; Battle->skillid = skillid; } break; case MONSTER_BUFF_SELF: { RESETPACKET( pak, 0x7b2); ADDWORD ( pak, clientid ); ADDWORD ( pak, skillid ); Battle->atktype = action; Battle->skillid = skillid; } break; case AOE_TARGET: { //LMA 2008/09/02: new version, the target is a zone, not a monster... so we stick with aoedestiny ;) //PY mods //Target is NULL for this function when cast by a player //Target is NOT NULL when it comes from a monster if(Target != NULL) //monster attack { Position->aoedestiny.x = Target->Position->current.x; Position->aoedestiny.y = Target->Position->current.y; } RESETPACKET( pak, 0x7b4 ); ADDWORD ( pak, clientid ); ADDWORD ( pak, skillid ); ADDFLOAT ( pak, Position->aoedestiny.x*100 ); ADDFLOAT ( pak, Position->aoedestiny.y*100 ); Battle->atktype = action; Battle->skillid = skillid; Battle->skilltarget = 0; Battle->target = Battle->atktarget; Position->destiny = Position->aoedestiny; Position->lastMoveTime = clock( ); Log(MSG_INFO,"StartAction, AOE_TARGET, target (%.2f,%.2f)",Position->aoedestiny.x,Position->aoedestiny.y); } break; case STAY_STILL_ATTACK: { //LMA: Very special case where the monster don't really attack (mc) Battle->atktype = action; Battle->skillid = skillid; Battle->skilltarget = Target->clientid; return; } default: return; } //if (getClient()==NULL) GServer->SendToVisible( &pak, this ); /*else getClient();*/ }
// update position void CCharacter::UpdatePosition( bool monster_stay_still ) { /* old version if(IsOnBattle( ) && Battle->target!=0) { CCharacter* Target = GetCharTarget( ); if(Target!=NULL) { if(IsMonster()) { float distance = GServer->distance( Position->current, Position->source ); if(distance>30) { if(Position->Map==8) Log(MSG_INFO,"Update Position, OnFar"); OnFar( ); } else { if (!monster_stay_still) Position->destiny = Target->Position->current; //MOBS ONLY else Log(MSG_INFO,"This one stays still"); if(Position->Map==8) Log(MSG_INFO,"Update Position, destiny=Target->Position->current (%2.f:%.2f)",Position->destiny.x,Position->destiny.y); } } else { if(Position->Map==8) Log(MSG_INFO,"Update Position Player, destiny=Target->Position->current (%2.f:%.2f)",Position->destiny.x,Position->destiny.y); Position->destiny = Target->Position->current; //ONLY IF NO TARGET ON BATTLE } } else { if(Position->Map==8) Log(MSG_INFO,"Update Position,no target, clear battle"); ClearBattle( Battle ); } } */ //LMA: osprose. if(IsOnBattle( ) && Battle->target!=0) { CCharacter* Target = GetCharTarget( ); if(Target == NULL) { ClearBattle( Battle ); } else { Position->destiny=Target->Position->current; } } //osprose end. if(Position->Map==8) { float tempdist = GServer->distance(Position->current,Position->destiny); if(IsPlayer()) { Log(MSG_INFO,"Player HP %I64i, (%.2f:%.2f)->(%.2f:%.2f)=%.2f speed %u",Stats->HP,Position->current.x,Position->current.y,Position->destiny.x,Position->destiny.y,tempdist,Stats->Move_Speed); } else { //Log(MSG_INFO,"Monster HP %I64i, (%.2f:%.2f)->(%.2f:%.2f)=%.2f speed %u",Stats->HP,Position->current.x,Position->current.y,Position->destiny.x,Position->destiny.y,tempdist,Stats->Move_Speed); //Log(MSG_LOAD,"Monster (%.2f:%.2f)->(%.2f:%.2f)",Position->current.x,Position->current.y,Position->destiny.x,Position->destiny.y); } } //LMA maps: special case (arrive in Game) //and he changed map (GM or scroll or teleporter or boat?) //2do: other cases too, all in fact... bool is_done=false; if (IsPlayer( )&&((last_map==-1&&last_coords==-1)||(last_map!=Position->Map))) { //updating player's grid int new_coords=0; int new_map=0; int grid_id=0; //deleting previous presence... if (last_map!=-1&&last_coords!=-1) { grid_id=GServer->allmaps[last_map].grid_id; if (grid_id!=-1&&!GServer->allmaps[last_map].always_on&&GServer->gridmaps[grid_id].coords[last_coords]>0) GServer->gridmaps[grid_id].coords[last_coords]--; } //New coordinates new_map=Position->Map; grid_id=GServer->allmaps[new_map].grid_id; new_coords=GServer->GetGridNumber(new_map,(UINT) floor(Position->current.x),(UINT) floor(Position->current.y)); last_map=new_map; last_coords=new_coords; if (grid_id!=-1||!GServer->allmaps[new_map].always_on) GServer->gridmaps[grid_id].coords[new_coords]++; is_done=true; } if(!IsMoving()) { //osprose if(IsPlayer())Position->lastMoveTime = clock(); //osprose end return; } float dx = Position->destiny.x - Position->current.x; float dy = Position->destiny.y - Position->current.y; float distance = sqrt( (dx*dx) + (dy*dy) ); float ntime = ( distance / Stats->Move_Speed * GServer->MOVE_SPEED_MODIF ); clock_t etime = clock() - Position->lastMoveTime; //LMA: bad, that's bad... //if (ntime<=etime || distance<1.0 ) if (ntime<=etime || distance<0.01 ) { // if (IsPlayer()) printf("Arrived! X: %i, Y: %i\n", (int)Position->current.x, (int)Position->current.y); if(Position->Map==8&&IsMonster()) Log(MSG_INFO," Monster Arrived, J (%.2f:%.2f)->(%.2f:%.2f)",Position->current.x,Position->current.y,Position->destiny.x,Position->destiny.y); Position->current.x = Position->destiny.x; Position->current.y = Position->destiny.y; } else { Position->current.x += dx*(etime/ntime); Position->current.y += dy*(etime/ntime); } Position->lastMoveTime = clock( ); //LMA: maps (for player) if(!IsPlayer()||is_done) return; //updating player's grid int new_coords=0; int new_map=0; int grid_id=0; new_map=Position->Map; grid_id=GServer->allmaps[new_map].grid_id; new_coords=GServer->GetGridNumber(new_map,(UINT) floor(Position->current.x),(UINT) floor(Position->current.y)); //changed? if (last_map==new_map&&new_coords==last_coords) return; //Let's update. if (grid_id!=-1||!GServer->allmaps[new_map].always_on) GServer->gridmaps[grid_id].coords[new_coords]++; //deleting player from his previous map grid_id=GServer->allmaps[last_map].grid_id; if (grid_id!=-1&&!GServer->allmaps[last_map].always_on&&GServer->gridmaps[grid_id].coords[last_coords]>0) GServer->gridmaps[grid_id].coords[last_coords]--; //Log(MSG_INFO,"Now[%i,%i],Was[%i,%i]",new_map,new_coords,last_map,last_coords); last_map=new_map; last_coords=new_coords; }
void CCharacter::DoAttack( ) { //if(Status->Sleep <= 0xff) //{ // ClearBattle( Battle ); // return; // can't take any action while we are asleep can we? //} //Log(MSG_INFO,"skill type %i selected. Chartype = %i",Battle->atktype, CharType); if(Battle->iscasting == 1) { CCharacter* Enemy = GetCharTarget( ); if(Enemy == NULL) { ClearBattle( Battle ); return; } //Log(MSG_DEBUG,"Iscasting detected as true. Reset to false."); if(CanAttack()) { //if(IsPlayer()) //{ // Log(MSG_DEBUG,"Should reach here once on normal attack. iscasting state = %i",Battle->iscasting); //} ClearBattle( Battle ); StartAction(Enemy, NORMAL_ATTACK, 0); } else { return; } return; } if(!CanAttack()) { return; } if(IsSummon()) { CCharacter* Enemy = GetCharTarget( ); if(Enemy == NULL || (Battle->atktype != SKILL_AOE && Battle->atktype != BUFF_AOE)) { //Log(MSG_DEBUG,"No Enemy found"); ClearBattle( Battle ); return; } if(this == Enemy) //summoned monster is attacking itself. It has been observed to happen { //Log(MSG_INFO,"Clearing Battle for this summon"); ClearBattle( Battle ); return; } if(Enemy->IsSummon()) { CMonster* thisMonster = reinterpret_cast<CMonster*>(this); if(thisMonster == NULL) { ClearBattle( Battle ); return; } CMonster* otherMonster = reinterpret_cast<CMonster*>(Enemy); if(otherMonster == NULL) { ClearBattle( Battle ); return; } if(thisMonster->owner == otherMonster->owner) { //Log(MSG_INFO,"Clearing Battle for this summon"); ClearBattle( Battle ); return; } } } CMap* map = GServer->MapList.Index[Position->Map]; //Log(MSG_INFO,"DoAttack Battle Attack type = %i",Battle->atktype); switch(Battle->atktype) { case NORMAL_ATTACK://normal attack { //if(!Status->CanAttack) // return; //can't attack right now. CCharacter* Enemy = GetCharTarget( ); if(Enemy == NULL) { ClearBattle( Battle ); return; } if(Enemy == this) { //Log(MSG_INFO,"WTF?? I AM trying to attack myself"); ClearBattle( Battle ); } if(IsTargetReached( Enemy )) { //if(IsMonster()) // Log(MSG_INFO,"Monster is trying to hit the player"); NormalAttack( Enemy ); if (Enemy->IsMonster()) // do monster AI script when monster is attacked. { CMonster* monster = GServer->GetMonsterByID(Enemy->clientid, Enemy->Position->Map); if(monster == NULL)return; //monster->DoAi(monster->thisnpc->AI, 3); monster->DoAi(monster->monAI, 3); } } } break; case SKILL_ATTACK://skill attack { CCharacter* Enemy = GetCharTarget( ); if(Enemy == NULL) { ClearBattle( Battle ); return; } CSkills* skill = GServer->GetSkillByID( Battle->skillid ); if(skill == NULL) { //ClearBattle( Battle ); return; } if(IsTargetReached( Enemy, skill )) { SkillAttack( Enemy, skill ); if (Enemy->IsMonster()) { CMonster* monster = GServer->GetMonsterByID(Enemy->clientid, Enemy->Position->Map); if(monster == NULL)return; monster->DoAi(monster->monAI, 3); } } } break; case SKILL_BUFF://buffs { CCharacter* Enemy = GetCharTarget( ); if(Enemy == NULL) { //Log(MSG_DEBUG,"this target is NULL"); ClearBattle( Battle ); return; } CSkills* skill = GServer->GetSkillByID( Battle->skillid ); if(skill == NULL) { //Log(MSG_DEBUG,"this skill is NULL"); return; } if(IsTargetReached( Enemy, skill )) { //Log(MSG_DEBUG,"Skill successfully cast"); BuffSkill( Enemy, skill ); } } break; case SKILL_AOE: //case SKILL_SELF: //this is impossible. it can never be set case AOE_TARGET: { //Log(MSG_DEBUG,"Called skill type %i",Battle->atktype); CCharacter* Enemy = NULL; CSkills* skill = GServer->GetSkillByID( Battle->skillid ); if(skill == NULL) { ClearBattle( Battle ); return; } if(Battle->atktype == AOE_TARGET) { float distance = GServer->distance( Position->current, Position->destiny ); if(distance <= skill->range) { Position->destiny = Position->current; AoeSkill( skill, Enemy ); } } else { Position->skilltargetpos = Position->current; AoeSkill( skill, NULL ); } } break; case BUFF_SELF: { // return; // can't cast skills right now CSkills* skill = GServer->GetSkillByID( Battle->skillid ); if(skill == NULL) { //ClearBattle( Battle ); return; } BuffSkill( this, skill ); } break; case BUFF_AOE: { //Log(MSG_INFO,"BUFF AOE selected"); CSkills* skill = GServer->GetSkillByID( Battle->skillid ); if(skill == NULL) { //ClearBattle( Battle ); return; } AoeBuff( skill ); } break; default: return; break; } }
void CCharacter::UpdatePosition( bool monster_stay_still ) { //LMA: osprose. if(IsOnBattle( ) && Battle->target!=0) { CCharacter* Target = GetCharTarget( ); if(Target == NULL) { ClearBattle( Battle ); } else { //LMA: Don't need to move if the target is reached. //Bad idea since the monster can go away and it seems //player goes automatically after him client side... /* if (Battle->atktype==NORMAL_ATTACK) { if(!IsTargetReached( Target )) { Position->destiny=Target->Position->current; } } else { Position->destiny=Target->Position->current; } */ //So let's go back to the old way. //LMA: done in DoAttack... //Can be "dangerous" since it checks range for normal attack //and if we're with skills the range isn't the same so player changes //its destiny all the time from skill range to weapon range. /*if(!IsTargetReached( Target )) { Position->destiny=Target->Position->current; }*/ } } //osprose end. //LMA: position in map 8 /*if(Position->Map==8) { float tempdist = GServer->distance(Position->current,Position->destiny); if(IsPlayer()) { Log(MSG_INFO,"Player HP %I64i, (%.2f:%.2f)->(%.2f:%.2f)=%.2f speed %u",Stats->HP,Position->current.x,Position->current.y,Position->destiny.x,Position->destiny.y,tempdist,Stats->Move_Speed); } else { Log(MSG_INFO,"Monster HP %I64i, (%.2f:%.2f)->(%.2f:%.2f)=%.2f speed %u",Stats->HP,Position->current.x,Position->current.y,Position->destiny.x,Position->destiny.y,tempdist,Stats->Move_Speed); //Log(MSG_LOAD,"Monster (%.2f:%.2f)->(%.2f:%.2f)",Position->current.x,Position->current.y,Position->destiny.x,Position->destiny.y); } }*/ //LMA maps: special case (arrive in Game) //and he changed map (GM or scroll or teleporter or boat?) //2do: other cases too, all in fact... bool is_done=false; if (IsPlayer( )&&((last_map==-1&&last_coords==-1)||(last_map!=Position->Map))) { //updating player's grid int new_coords=0; int new_map=0; int grid_id=0; //deleting previous presence... if (last_map!=-1&&last_coords!=-1) { grid_id=GServer->allmaps[last_map].grid_id; if (grid_id!=-1&&!GServer->allmaps[last_map].always_on&&GServer->gridmaps[grid_id].coords[last_coords]>0) GServer->gridmaps[grid_id].coords[last_coords]--; } //New coordinates new_map=Position->Map; grid_id=GServer->allmaps[new_map].grid_id; if (grid_id==-1) { Log(MSG_WARNING,"It seems you forgot to declare map %i in map_grid.csv",new_map); } new_coords=GServer->GetGridNumber(new_map,(UINT) floor(Position->current.x),(UINT) floor(Position->current.y)); last_map=new_map; last_coords=new_coords; //if (grid_id!=-1||!GServer->allmaps[new_map].always_on) if (grid_id!=-1&&!GServer->allmaps[new_map].always_on) GServer->gridmaps[grid_id].coords[new_coords]++; is_done=true; } if(!IsMoving()) { //osprose if(IsPlayer()) { Position->current.x = Position->destiny.x; Position->current.y = Position->destiny.y; Position->lastMoveTime = clock(); } //osprose end return; } float dx = Position->destiny.x - Position->current.x; float dy = Position->destiny.y - Position->current.y; float distance = sqrt( (dx*dx) + (dy*dy) ); float ntime = ( distance / (float(Stats->Move_Speed)/100)) * CLOCKS_PER_SEC; clock_t etime = clock() - Position->lastMoveTime; //LMA: bad, that's bad... //if (ntime<=etime || distance<1.0 ) if (ntime<=etime || distance<0.01 ) { // if (IsPlayer()) printf("Arrived! X: %i, Y: %i\n", (int)Position->current.x, (int)Position->current.y); if(Position->Map==8&&IsMonster()) { Log(MSG_INFO," Monster Arrived, J (%.2f:%.2f)->(%.2f:%.2f)",Position->current.x,Position->current.y,Position->destiny.x,Position->destiny.y); } Position->current.x = Position->destiny.x; Position->current.y = Position->destiny.y; } else { Position->current.x = dx*(etime/ntime) + Position->current.x; Position->current.y = dy*(etime/ntime) + Position->current.y; } Position->lastMoveTime = clock( ); //LMA: maps (for player) if(!IsPlayer()||is_done) return; //updating player's grid int new_coords=0; int new_map=0; int grid_id=0; new_map=Position->Map; grid_id=GServer->allmaps[new_map].grid_id; if (grid_id==-1) { Log(MSG_WARNING,"It seems you forgot to declare map %i in map_grid.csv",new_map); } new_coords=GServer->GetGridNumber(new_map,(UINT) floor(Position->current.x),(UINT) floor(Position->current.y)); //changed? if (last_map==new_map&&new_coords==last_coords) return; //Let's update. //if (grid_id!=-1||!GServer->allmaps[new_map].always_on) if (grid_id!=-1&&!GServer->allmaps[new_map].always_on) GServer->gridmaps[grid_id].coords[new_coords]++; //deleting player from his previous map grid_id=GServer->allmaps[last_map].grid_id; if (grid_id!=-1&&!GServer->allmaps[last_map].always_on&&GServer->gridmaps[grid_id].coords[last_coords]>0) GServer->gridmaps[grid_id].coords[last_coords]--; //Log(MSG_INFO,"Now[%i,%i],Was[%i,%i]",new_map,new_coords,last_map,last_coords); last_map=new_map; last_coords=new_coords; }