// called when a enemy is on sight bool CMonster::OnEnemyOnSight( CPlayer* Enemy ) { clock_t etime = clock() - lastSighCheck; if(etime<5000) return true; if(!IsOnBattle( )) { if(thisnpc->aggresive>1) { UINT aggro = GServer->RandNumber(2,15); if(thisnpc->aggresive>=aggro && !IsGhostSeed( )) { //osprose. /* Enemy->ClearObject( this->clientid ); SpawnMonster(Enemy, this ); */ StartAction( (CCharacter*) Enemy, NORMAL_ATTACK, 0 ); } else if(IsGhostSeed( ) || thisnpc->aggresive>5) MoveTo( Enemy->Position->current, true ); } lastSighCheck = clock(); } return true; }
// called when a monster is attacked [attack/use atk skill/use buff/run/summon] bool CMonster::OnBeAttacked( CCharacter* Enemy ) { Battle->hitby = Enemy->clientid; if(!IsOnBattle( )) { //LMA: yeah hurt me (used for Santa Rudolph). if(thisnpc->helpless==1) { StartAction( Enemy, STAY_STILL_ATTACK, 0 ); return true; } //Some monsters do not attack and stay still (mc) if(!stay_still) { StartAction( Enemy, NORMAL_ATTACK, 0 ); } else { Log(MSG_INFO,"Stay still attack"); StartAction( Enemy, STAY_STILL_ATTACK, 0 ); } } return true; }
bool CCharacter::IsAttacking( ) { if(IsOnBattle( )) { switch(Battle->atktype) { case SKILL_BUFF: case BUFF_SELF: case BUFF_AOE: return false; } } else return false; return true; }
// called when a monster is attacked [attack/use atk skill/use buff/run/summon] bool CMonster::OnBeAttacked( CCharacter* Enemy ) { if(!IsOnBattle( )) { //Some monsters do not attack and stay still (mc) if(!stay_still) { StartAction( Enemy, NORMAL_ATTACK, 0 ); } else { StartAction( Enemy, STAY_STILL_ATTACK, 0 ); } } return true; }
// 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(); }
// Spawn a monster //LMA: added handling of skill summons. void CMonster::SpawnMonster( CPlayer* player, CMonster* thismon ) { BEGINPACKET( pak, 0x792 ); ADDWORD ( pak, clientid ); ADDFLOAT ( pak, Position->current.x*100 ); ADDFLOAT ( pak, Position->current.y*100 ); if((thismon->bonushp>0||thismon->bonusmp>0)&&(thismon->skillid>0)) { ADDFLOAT ( pak, 0xcdcdcdcd ); ADDFLOAT ( pak, 0xcdcdcdcd ); } else { ADDFLOAT ( pak, Position->destiny.x*100 ); ADDFLOAT ( pak, Position->destiny.y*100 ); } if(IsDead( )) { ADDWORD ( pak, 0x0003 ); ADDWORD ( pak, 0x0000 ); } else if(IsOnBattle( )) { //LMA: for supportive summons (lucky ghost...) if(Battle->bufftarget==Battle->target) { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, Battle->target ); } } else if(IsMoving( )) { ADDWORD ( pak, 0x0001 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); } if(IsSummon( ) ) { ADDBYTE ( pak, 0x01 ); } else { ADDBYTE ( pak, 0x00 ); } //LMA: Little check, for now we "only" have a DWORD for monster's HP so there is a limit //broken by some monsters (Turak boss) if(Stats->HP>MAXHPMOB) { LogDebugPriority(3); LogDebug("Too much HP for monster %i (%I64i->%I64i)",thismon->montype,Stats->HP,MAXHPMOB); LogDebugPriority(4); Stats->HP=(long long) MAXHPMOB; } ADDDWORD ( pak, Stats->HP ); if(thismon->owner != player->clientid) { CMap* map = GServer->MapList.Index[Position->Map]; //LMA: adding team... if (thismon->team!=0) { ADDDWORD( pak,thismon->team); } else { if(IsSummon( ) && map->allowpvp!=0) { //Hostil ADDDWORD( pak, 0x00000064 ); } else if (IsSummon( ) && map->allowpvp==0) { //Friendly ADDDWORD ( pak, 0x00000000 ); } else { //Hostil ADDDWORD( pak, 0x00000064 ); } //TODO: LMA, test if necessary or not anymore... /* else if(thismon->montype>=1474&&thismon->montype<=1489) { //LMA: Xmas trees are friendly. ADDDWORD ( pak, 0x00000000 ); } */ } } else { //Friendly ADDDWORD( pak, 0x00000000 ); } ADDDWORD( pak, GServer->BuildBuffs( this ) ); ADDWORD ( pak, montype ); ADDWORD ( pak, 0x0000 ); if(IsSummon( )) { ADDWORD( pak, owner ); if (thismon->skillid>0) { ADDWORD( pak, thismon->skillid ); //id del skill (si es summon de skill) } else { ADDWORD( pak, 0x0000 ); //id del skill (si es summon de skill) } } player->client->SendPacket( &pak ); //LMA: supportive summons (lucky ghost) if(IsSummon()&&buffid>0&&(player==GetOwner())) { Log(MSG_INFO,"The summon is spawned"); /*CPlayer* player = GetOwner( ); if (ownplayer==NULL) return true;*/ StartAction( player,SUMMON_BUFF,buffid); Log(MSG_INFO,"completly"); buffid=0; //only one buff } }
// Spawn a monster //LMA: added handling of skill summons. void CMonster::SpawnMonster( CPlayer* player, CMonster* thismon ) { BEGINPACKET( pak, 0x792 ); //struct tag_ADD_CHAR ADDWORD ( pak, clientid ); ADDFLOAT ( pak, Position->current.x*100 ); ADDFLOAT ( pak, Position->current.y*100 ); //current X and Y position if((thismon->bonushp > 0 || thismon->bonusmp > 0) && (thismon->skillid > 0)) //What is this? It seems to be sending this in place of destination if the monster is not able to move. Bonfires and so on { ADDFLOAT ( pak, 0xcdcdcdcd ); ADDFLOAT ( pak, 0xcdcdcdcd ); } else { ADDFLOAT ( pak, Position->destiny.x*100 ); //Destination position. This should always follow current position ADDFLOAT ( pak, Position->destiny.y*100 ); } // next 2 WORDS are m_wCommand and m_wTargetOBJ if(IsDead( )) { ADDWORD ( pak, 0x0003 ); ADDWORD ( pak, 0x0000 ); } else if(IsOnBattle( )) { //LMA: for supportive summons (lucky ghost...) if(Battle->bufftarget == Battle->target) { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, Battle->target ); } } else if(IsMoving( )) { ADDWORD ( pak, 0x0001 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); } ADDBYTE ( pak, thisnpc->stance ); ADDWORD ( pak, Stats->HP ); ADDWORD ( pak, 0x0000 ); // now Team Number if(thismon->owner != player->clientid) { CMap* map = GServer->MapList.Index[Position->Map]; //LMA: adding team... if (thismon->team!=0) { ADDWORD( pak,thismon->team); } else { if(IsSummon( ) && map->allowpvp!=0) { //Hostil ADDWORD( pak, 0x0064 ); } else if (IsSummon( ) && map->allowpvp==0) { //Friendly ADDWORD ( pak, 0x0000 ); } else { //Hostil ADDWORD( pak, 0x0064 ); } } } else { //Friendly ADDWORD( pak, 0x0000 ); } ADDWORD ( pak, 0x0000 ); //and finally DWORD StatusFlag ADDDWORD( pak, GServer->BuildBuffs( this ) ); //struct gsv_MOB_CHAR ADDWORD ( pak, montype ); ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, thismon->Stats->Level ); ADDWORD ( pak, thismon->Stats->Size ); player->client->SendPacket( &pak ); //LMA: supportive summons (lucky ghost) //PY: Another special case. Will probably need to remove this later if(IsSummon() && buffid > 0 && (player == GetOwner())) { Log(MSG_INFO,"The summon is spawned"); /*CPlayer* player = GetOwner( ); if (ownplayer==NULL) return true;*/ StartAction( player,SUMMON_BUFF,buffid); Log(MSG_INFO,"completly"); buffid=0; //only one buff } }
// Spawn a monster //LMA: added handling of skill summons. void CMonster::SpawnMonster( CPlayer* player, CMonster* thismon ) { BEGINPACKET( pak, 0x792 ); ADDWORD ( pak, clientid ); ADDFLOAT ( pak, Position->current.x*100 ); ADDFLOAT ( pak, Position->current.y*100 ); ADDDWORD ( pak, 0xcdcdcdcd ); ADDDWORD ( pak, 0xcdcdcdcd ); if(IsDead( )) { ADDWORD ( pak, 0x0003 ); ADDWORD ( pak, 0x0000 ); } else if(IsOnBattle( )) { //LMA: for supportive summons (lucky ghost...) if(Battle->bufftarget==Battle->target) { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0002 ); ADDWORD ( pak, Battle->target ); } } else if(IsMoving( )) { ADDWORD ( pak, 0x0001 ); ADDWORD ( pak, 0x0000 ); } else { ADDWORD ( pak, 0x0000 ); ADDWORD ( pak, 0x0000 ); } if(IsSummon( ) ) { ADDBYTE ( pak, 0x01 ); } else { ADDBYTE ( pak, 0x00 ); } ADDWORD(pak, 0x0000); if(Stats->HP > MAXHPMOB) { Stats->HP = (long long) MAXHPMOB; } ADDDWORD ( pak, Stats->HP ); if(thismon->owner != player->clientid) { CMap* map = GServer->MapList.Index[Position->Map]; if (thismon->team!=0) { ADDDWORD( pak,thismon->team); } else { if(IsSummon( ) && map->allowpvp!=0) { //Hostil ADDDWORD( pak, 0x00000064 ); } else if (IsSummon( ) && map->allowpvp==0) { //Friendly ADDDWORD ( pak, 2 ); } else { //Hostil ADDDWORD( pak, 0x00000064 ); } } } else { //Friendly ADDDWORD( pak, 2 ); } ADDDWORD( pak, GServer->BuildBuffs( this ) ); ADDDWORD ( pak, montype ); if(IsSummon( )) { ADDWORD( pak, owner ); if (thismon->skillid>0) { ADDWORD( pak, thismon->skillid ); //id del skill (si es summon de skill) } else { ADDWORD( pak, 0x0000 ); //id del skill (si es summon de skill) } } player->client->SendPacket( &pak ); //LMA: supportive summons (lucky ghost) if(IsSummon()&&buffid>0&&(player==GetOwner())) { Log(MSG_INFO,"The summon is spawned"); StartAction( player,SUMMON_BUFF,buffid); Log(MSG_INFO,"completly"); buffid=0; //only one buff } }
// 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; }
// 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::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; }