/*! Sends items which came in range and handles collisions with teleporters or damaging items. */ void handleItems( P_CHAR pChar, const Coord& oldpos ) { P_PLAYER player = dynamic_cast<P_PLAYER>( pChar ); MapItemsIterator iter = MapObjects::instance()->listItemsInCircle( pChar->pos(), VISRANGE ); for ( P_ITEM pItem = iter.first(); pItem; pItem = iter.next() ) { // Check for item collisions here. if ( pChar->pos().x == pItem->pos().x && pChar->pos().y == pItem->pos().y ) { if ( pItem->pos().z >= pChar->pos().z - 15 && pItem->pos().z <= pChar->pos().z + 15 ) { if ( handleItemCollision( pChar, pItem ) ) { break; } } } // If we are a connected player then send new items if ( player && player->socket() ) { UI32 oldDist = oldpos.distance( pItem->pos() ); if ( oldDist >= player->visualRange() ) { // was out of range before and now is in range pItem->update( player->socket() ); } } } }
Q_UINT16 DynTile( const Coord_cl& pos ) { RegionIterator4Items ri( pos ); for ( ri.Begin(); !ri.atEnd(); ri++ ) { P_ITEM mapitem = ri.GetData(); if ( mapitem ) { if ( mapitem->isMulti() ) { MultiDefinition* def = MultiCache::instance()->getMulti( mapitem->id() - 0x4000 ); if ( !def ) return 0; QValueVector<multiItem_st> multi = def->getEntries(); for ( Q_UINT32 j = 0; j < multi.size(); ++j ) { if ( ( multi[j].visible && ( mapitem->pos().x + multi[j].x == pos.x ) && ( mapitem->pos().y + multi[j].y == pos.y ) && ( abs( mapitem->pos().z + multi[j].z - pos.z ) <= 1 ) ) ) { return multi[j].tile; } } } else if ( mapitem->pos() == pos ) return mapitem->id(); } } return ( Q_UINT16 ) - 1; }
Q_UINT16 DynTile( const Coord& pos ) { MapItemsIterator ri = MapObjects::instance()->listItemsInCircle( pos, 18 ); for ( P_ITEM mapitem = ri.first(); mapitem; mapitem = ri.next() ) { if ( mapitem->isMulti() ) { MultiDefinition* def = MultiCache::instance()->getMulti( mapitem->id() - 0x4000 ); if ( !def ) return 0; QValueVector<multiItem_st> multi = def->getEntries(); for ( Q_UINT32 j = 0; j < multi.size(); ++j ) { if ( ( multi[j].visible && ( mapitem->pos().x + multi[j].x == pos.x ) && ( mapitem->pos().y + multi[j].y == pos.y ) && ( abs( mapitem->pos().z + multi[j].z - pos.z ) <= 1 ) ) ) { return multi[j].tile; } } } else if ( mapitem->pos() == pos ) { return mapitem->id(); } } return ( Q_UINT16 ) - 1; }
bool cMovement::canLandMonsterMoveHere( const Coord_cl& pos ) const { if ( pos.x >= ( Maps::instance()->mapTileWidth( pos.map ) * 8 ) || pos.y >= ( Maps::instance()->mapTileHeight( pos.map ) * 8 ) ) return false; const signed char elev = Maps::instance()->mapElevation( pos ); Coord_cl target = pos; target.z = elev; if ( ILLEGAL_Z == elev ) return false; // get the tile id of any dynamic tiles at this spot Coord_cl mPos = pos; mPos.z = elev; const Q_INT32 dt = DynTile( mPos ); // if there is a dynamic tile at this spot, check to see if its a blocker // if it does block, might as well Q_INT16-circuit and return right away if ( dt >= 0 ) { tile_st tile = TileCache::instance()->getTile( dt ); if ( tile.isBlocking() || tile.isWet() ) return false; } // if there's a static block here in our way, return false StaticsIterator msi = Maps::instance()->staticsIterator( pos ); while ( !msi.atEnd() ) { tile_st tile = TileCache::instance()->getTile( msi->itemid ); const Q_INT32 elev = msi->zoff + cTileCache::tileHeight( tile ); if ( ( elev >= pos.z ) && ( msi->zoff <= pos.z ) ) { if ( tile.isBlocking() || tile.isWet() ) return false; } msi++; } RegionIterator4Items items( pos, 0 ); for ( items.Begin(); !items.atEnd(); items++ ) { P_ITEM item = items.GetData(); tile_st tile = TileCache::instance()->getTile( item->id() ); const Q_INT32 elev = item->pos().z + cTileCache::tileHeight( tile ); if ( ( elev >= pos.z ) && ( item->pos().z <= pos.z ) ) { if ( tile.isBlocking() || tile.isWet() ) return false; } } return true; }
Coord Coord::losTargetPoint( cUORxTarget* target, unsigned char map ) { SERIAL serial = target->serial(); P_ITEM pItem = World::instance()->findItem( serial ); P_CHAR pChar = World::instance()->findChar( serial ); if ( pItem ) { pItem = pItem->getOutmostItem(); if ( pItem->container() && pItem->container()->isChar() ) { return pItem->container()->pos().losCharPoint(); } else { return pItem->pos().losItemPoint( pItem->id() ); } } else if ( pChar ) { return pChar->pos().losCharPoint(); } else { return Coord( target->x(), target->y(), target->z(), map ); } }
// Remove it from all in-range sockets void cUObject::removeFromView( bool clean ) { // Get Real pos Coord_cl mPos = pos_; if( isItemSerial( serial_ ) ) { P_ITEM pItem = dynamic_cast<P_ITEM>(this); P_ITEM pCont = pItem->getOutmostItem(); if( pCont ) { mPos = pCont->pos(); P_CHAR pOwner = dynamic_cast<P_CHAR>( pCont->container() ); if( pOwner ) mPos = pOwner->pos(); } } cUOTxRemoveObject remove; remove.setSerial(serial_); for (cUOSocket *socket = cNetwork::instance()->first(); socket; socket = cNetwork::instance()->next()) { if (socket->player() != this && (clean || socket->canSee(this))) { socket->send(&remove); } } }
/*! Checks if the specified object is in given range */ bool cUObject::inRange( cUObject* object, quint32 range ) const { if ( !object ) return false; Coord pos = object->pos_; if ( object->isItem() ) { P_ITEM pItem = dynamic_cast<P_ITEM>( object ); if ( pItem ) { P_ITEM pCont = pItem->getOutmostItem(); P_CHAR pEquipped = pItem->getOutmostChar(); if ( pEquipped ) { pos = pEquipped->pos(); } else if ( pCont ) { pos = pCont->pos(); } } } return pos_.distance( pos ) <= range; }
void cSectorMaps::add( cUObject* object ) { // Very powerful statement. It completely // annihilates the need to check for // nullpointers in our object-map if ( !object ) return; if ( isItemSerial( object->serial() ) ) { P_ITEM pItem = dynamic_cast<P_ITEM>( object ); if ( pItem ) { Coord_cl pos = pItem->pos(); std::map<unsigned char, cSectorMap*>::const_iterator it = itemmaps.find( pos.map ); if ( it == itemmaps.end() ) throw QString( "Couldn't find a map with the id %1." ).arg( pos.map ); it->second->addItem( ( cUObject * ) pItem ); Timing::instance()->addDecayItem( pItem ); } } else if ( isCharSerial( object->serial() ) ) { // This is a safety check to make sure that // stabled pets don't appear on our sectormap P_NPC npc = dynamic_cast<P_NPC>( object ); if ( npc && npc->stablemasterSerial() != INVALID_SERIAL ) { return; } // The same check for players /*P_PLAYER player = dynamic_cast<P_PLAYER>( object ); if ( player && !player->socket() && !player->logoutTime() ) { return; }*/ P_CHAR pChar = dynamic_cast<P_CHAR>( object ); if ( pChar ) { Coord_cl pos = pChar->pos(); std::map<unsigned char, cSectorMap*>::const_iterator it = charmaps.find( pos.map ); if ( it == charmaps.end() ) throw QString( "Couldn't find a map with the id %1." ).arg( pos.map ); it->second->addItem( ( cUObject * ) pChar ); } } }
void cSectorMaps::remove( cUObject* object ) { // Very powerful statement. It completely // annihilates the need to check for // nullpointers in our object-map if ( !object ) return; if ( isItemSerial( object->serial() ) ) { P_ITEM pItem = dynamic_cast<P_ITEM>( object ); if ( pItem ) { Coord_cl pos = pItem->pos(); std::map<unsigned char, cSectorMap*>::const_iterator it = itemmaps.find( pos.map ); if ( it == itemmaps.end() ) throw QString( "Couldn't find a map with the id %1." ).arg( pos.map ); it->second->removeItem( ( cUObject * ) pItem ); Timing::instance()->removeDecayItem( pItem ); } } else if ( isCharSerial( object->serial() ) ) { P_CHAR pChar = dynamic_cast<P_CHAR>( object ); if ( pChar ) { Coord_cl pos = pChar->pos(); std::map<unsigned char, cSectorMap*>::const_iterator it = charmaps.find( pos.map ); if ( it == charmaps.end() ) throw QString( "Couldn't find a map with the id %1." ).arg( pos.map ); it->second->removeItem( ( cUObject * ) pChar ); } } }
// Remove it from all in-range sockets void cUObject::removeFromView( bool clean ) { // Get Real pos Coord_cl mPos = pos_; if( isItemSerial( serial_ ) ) { P_ITEM pItem = dynamic_cast<P_ITEM>(this); P_ITEM pCont = pItem->getOutmostItem(); if( pCont ) { mPos = pCont->pos(); P_CHAR pOwner = dynamic_cast<P_CHAR>( pCont->container() ); if( pOwner ) mPos = pOwner->pos(); } } for( cUOSocket *socket = cNetwork::instance()->first(); socket; socket = cNetwork::instance()->next() ) if( clean || ( socket->player() && ( socket->player()->pos().distance( mPos ) <= socket->player()->visualRange() ) ) ) socket->removeObject( this ); }
// The highest items will be @ the beginning // While walking we always will try the highest first. vector< stBlockItem > getBlockingItems( P_CHAR pChar, const Coord& pos ) { vector<stBlockItem> blockList; make_heap( blockList.begin(), blockList.end(), compareTiles() ); // Process the map at that position stBlockItem mapBlock; mapBlock.maptile = true; mapBlock.z = Maps::instance()->mapAverageElevation( pos ); mapBlock.height = 0; // TODO: Calculate the REAL average Z Value of that Map Tile here! Otherwise clients will have minor walking problems. map_st mapCell = Maps::instance()->seekMap( pos ); //mapBlock.z = mapCell.z; land_st mapTile = TileCache::instance()->getLand( mapCell.id ); // If it's not impassable it's automatically walkable if ( !( mapTile.flag1 & 0x40 ) ) mapBlock.walkable = true; else mapBlock.walkable = checkWalkable( pChar, mapCell.id ); if ( mapCell.id != 0x02 ) { blockList.push_back( mapBlock ); push_heap( blockList.begin(), blockList.end(), compareTiles() ); } // Now for the static-items StaticsIterator staIter = Maps::instance()->staticsIterator( pos, true ); for ( ; !staIter.atEnd(); ++staIter ) { tile_st tTile = TileCache::instance()->getTile( staIter->itemid ); // Here is decided if the tile is needed // It's uninteresting if it's NOT blocking // And NOT a bridge/surface if ( !( ( tTile.flag2 & 0x02 ) || ( tTile.flag1 & 0x40 ) || ( tTile.flag2 & 0x04 ) ) ) continue; stBlockItem staticBlock; staticBlock.z = staIter->zoff; // If we are a surface we can always walk here, otherwise check if // we are special if ( ( tTile.flag2 & 0x02 ) && !( tTile.flag1 & 0x40 ) ) staticBlock.walkable = true; else staticBlock.walkable = checkWalkable( pChar, staIter->itemid ); // If we are a stair only the half height counts (round up) if ( tTile.flag2 & 0x04 ) staticBlock.height = ( Q_UINT8 ) ( ( tTile.height ) / 2 ); else staticBlock.height = tTile.height; blockList.push_back( staticBlock ); push_heap( blockList.begin(), blockList.end(), compareTiles() ); } // We are only interested in items at pos // todo: we could impliment blocking for items on the adjacent sides // during a diagonal move here, but this has yet to be decided. MapItemsIterator iIter = MapObjects::instance()->listItemsAtCoord( pos ); for ( P_ITEM pItem = iIter.first(); pItem; pItem = iIter.next() ) { if ( pChar && pChar->isDead() ) { // Doors can be passed by ghosts if ( pItem->hasScript( "door" ) ) { continue; } } tile_st tTile = TileCache::instance()->getTile( pItem->id() ); // See above for what the flags mean if ( !( ( tTile.flag2 & 0x02 ) || ( tTile.flag1 & 0x40 ) || ( tTile.flag2 & 0x04 ) ) ) continue; stBlockItem blockItem; blockItem.height = ( tTile.flag2 & 0x04 ) ? ( tTile.height / 2 ) : tTile.height; blockItem.z = pItem->pos().z; // Once again: see above for a description of this part if ( ( tTile.flag2 & 0x02 ) && !( tTile.flag1 & 0x40 ) ) blockItem.walkable = true; else blockItem.walkable = checkWalkable( pChar, pItem->id() ); blockList.push_back( blockItem ); push_heap( blockList.begin(), blockList.end(), compareTiles() ); } // deal with the multis now, or not. // 18 has been tested with castle sides and corners... MapMultisIterator iter = MapObjects::instance()->listMultisInCircle( pos, 18 ); for ( cMulti*pMulti = iter.first(); pMulti; pMulti = iter.next() ) { MultiDefinition* def = MultiCache::instance()->getMulti( pMulti->id() - 0x4000 ); if ( !def ) continue; QValueVector<multiItem_st> multi = def->getEntries(); for ( unsigned int j = 0; j < multi.size(); ++j ) { if ( multi[j].visible && ( pMulti->pos().x + multi[j].x == pos.x ) && ( pMulti->pos().y + multi[j].y == pos.y ) ) { tile_st tTile = TileCache::instance()->getTile( multi[j].tile ); if ( !( ( tTile.flag2 & 0x02 ) || ( tTile.flag1 & 0x40 ) || ( tTile.flag2 & 0x04 ) ) ) continue; stBlockItem blockItem; blockItem.height = ( tTile.flag2 & 0x04 ) ? ( tTile.height / 2 ) : tTile.height; blockItem.z = pMulti->pos().z + multi[j].z; if ( ( tTile.flag2 & 0x02 ) && !( tTile.flag1 & 0x40 ) ) blockItem.walkable = true; else blockItem.walkable = checkWalkable( pChar, pMulti->id() ); blockList.push_back( blockItem ); push_heap( blockList.begin(), blockList.end(), compareTiles() ); } } continue; } // Now we need to evaluate dynamic items [...] (later) ?? sort_heap( blockList.begin(), blockList.end(), compareTiles() ); return blockList; };
void cDragItems::dropOnItem( cUOSocket *socket, P_ITEM pItem, P_ITEM pCont, const Coord_cl &dropPos ) { P_PLAYER pChar = socket->player(); if( pItem->isMulti() ) { socket->sysMessage( tr( "You cannot put houses in containers" ) ); cUOTxBounceItem bounce; bounce.setReason( BR_NO_REASON ); socket->send( &bounce ); Items->DeleItem( pItem ); return; } if( pItem->onDropOnItem( pCont ) ) { if( socket->dragging() ) socket->bounceItem( socket->dragging(), BR_NO_REASON ); return; } else if( pCont->onDropOnItem( pItem ) ) { if( socket->dragging() ) socket->bounceItem( socket->dragging(), BR_NO_REASON ); return; } // If the target belongs to another character // It needs to be our vendor or else it's denied P_CHAR packOwner = pCont->getOutmostChar(); if( ( packOwner ) && ( packOwner != pChar ) && !pChar->isGM() ) { // For each item someone puts into there // He needs to do a snoop-check if( pChar->maySnoop() ) { if( !pChar->checkSkill( SNOOPING, 0, 1000 ) ) { socket->sysMessage( tr( "You fail to put that into %1's pack" ).arg( packOwner->name() ) ); socket->bounceItem( pItem, BR_NO_REASON ); return; } } if( packOwner->objectType() == enPlayer || ( packOwner->objectType() == enNPC && dynamic_cast<P_NPC>(packOwner)->owner() != pChar ) ) { socket->sysMessage( tr("You cannot put that into the belongings of another player") ); socket->bounceItem( pItem, BR_NO_REASON ); return; } } // If we put the item into a trade-window // Reset the trade-status for both players if( pCont->layer() == 0 && pCont->id() == 0x1E5E && pChar->Wears( pCont ) ) { // Trade window??? P_ITEM tradeWindow = FindItemBySerial( calcserial( pCont->moreb1(), pCont->moreb2(), pCont->moreb3(), pCont->moreb4() ) ); // If it *IS* a trade-window, replace the status if( tradeWindow && ( pCont->morez() || tradeWindow->morez() ) ) { tradeWindow->setMoreZ(0); pCont->setMoreZ(0); // sendtradestatus( tradeWindow, pCont ); } } if( !pChar->canPickUp( pItem ) ) { socket->bounceItem( pItem, BR_CANNOT_PICK_THAT_UP ); return; } // Trash can if( pCont->type()==87 ) { Items->DeleItem( pItem ); socket->sysMessage( tr( "As you let go of the item it disappears." ) ); return; } // Spell Book cSpellBook *pBook = dynamic_cast< cSpellBook* >( pCont ); if( pBook ) { SI08 spellId = NewMagic->calcSpellId( pItem->id() ); if( pItem->type() != 1105 || spellId < 0 ) { socket->sysMessage( tr( "You can only put scrolls into a spellbook" ) ); socket->bounceItem( pItem, BR_NO_REASON ); return; } if( pBook->hasSpell( spellId ) ) { socket->sysMessage( tr( "That spellbook already contains this spell" ) ); socket->bounceItem( pItem, BR_NO_REASON ); return; } if( pItem->amount() > 1 ) { socket->sysMessage( tr( "You can only put 1 scroll into a spellbook at a time" ) ); socket->bounceItem( pItem, BR_NO_REASON ); return; } else { pBook->addSpell( spellId ); Items->DeleItem( pItem ); pBook->update( socket ); return; } } // We drop something on the belongings of one of our playervendors /* if( ( packOwner != NULL ) && ( packOwner->npcaitype() == 17 ) && packOwner->owner() == pChar ) { socket->sysMessage( tr( "You drop something into your playervendor (unimplemented)" ) ); socket->bounceItem( pItem, BR_NO_REASON ); return; }*/ // Playervendors (chest equipped by the vendor - opened to the client) /*if( !( pCont->pileable() && pItem->pileable() && pCont->id() == pItem->id() || ( pCont->type() != 1 && pCont->type() != 9 ) ) ) { P_CHAR pc_j = GetPackOwner(pCont); if (pc_j != NULL) { if (pc_j->npcaitype() == 17 && pc_j->isNpc() && pChar->Owns(pc_j)) { pChar->inputitem = pItem->serial; pChar->inputmode = cChar::enPricing; sysmessage(s, "Set a price for this item."); } } */ // We may also drop into *any* locked chest // So we can have post-boxes ;o) // Spellbooks are containers for us as well if( pCont->type() == 1 || pCont->type() == 8 || pCont->type() == 63 || pCont->type() == 65 || pCont->type() == 66 ) { // If we're dropping it onto the closed container if( dropPos.distance( pCont->pos() ) == 0 ) { pCont->addItem( pItem ); } else { pCont->addItem( pItem, false ); pItem->setPos( dropPos ); } // Dropped on another Container/in another Container pChar->soundEffect( 0x57 ); pItem->update(); return; } // Item matching needs to be extended !!! at least Color! (for certain types) else if ( pCont->isPileable() && pItem->isPileable() && ( pCont->id() == pItem->id() ) ) { if( pCont->amount() + pItem->amount() <= 65535 ) { pCont->setAmount( pCont->amount() + pItem->amount() ); Items->DeleItem( pItem ); pCont->update(); // Need to update the amount return; } // We have to *keep* our current item else { pCont->setAmount( 65535 ); // Max out the amount pCont->update(); // The delta between 65535 and pCont->amount() sub our Amount is the // new amount pItem->setAmount( pItem->amount() - ( 65535 - pCont->amount() ) ); } } // We dropped the item NOT on a container // And were *un*able to stack it (!) // >> Set it to the location of the item we dropped it on and stack it up by 2 pItem->moveTo( pCont->pos() ); pItem->setPos( pItem->pos() + Coord_cl(0, 0, 2) ); pItem->update(); /* // This needs to be checked // It annoyingly shows the spellbook // whenever you add a scroll // << could it be that addItemToContainer is enough?? >> if( pCont->type() == 9 ) Magic->openSpellBook( pChar, pCont );*/ }
void DragAndDrop::dropOnItem( cUOSocket* socket, P_ITEM pItem, P_ITEM pCont, const Coord_cl& dropPos ) { P_PLAYER pChar = socket->player(); if ( pItem->isMulti() ) { socket->sysMessage( tr( "You cannot put houses in containers" ) ); cUOTxBounceItem bounce; bounce.setReason( BR_NO_REASON ); socket->send( &bounce ); pItem->remove(); return; } if ( pItem->onDropOnItem( pCont ) ) { if ( pItem->free ) return; if ( socket->dragging() ) socket->bounceItem( socket->dragging(), BR_NO_REASON ); return; } else if ( pCont->onDropOnItem( pItem ) ) { if ( pItem->free ) return; if ( socket->dragging() ) socket->bounceItem( socket->dragging(), BR_NO_REASON ); return; } // If the target belongs to another character // It needs to be our vendor or else it's denied P_CHAR packOwner = pCont->getOutmostChar(); if ( ( packOwner ) && ( packOwner != pChar ) && !pChar->isGM() ) { // For each item someone puts into there // He needs to do a snoop-check if ( pChar->maySnoop() ) { if ( !pChar->checkSkill( SNOOPING, 0, 1000 ) ) { socket->sysMessage( tr( "You fail to put that into %1's pack" ).arg( packOwner->name() ) ); socket->bounceItem( pItem, BR_NO_REASON ); return; } } if ( packOwner->objectType() == enPlayer || ( packOwner->objectType() == enNPC && dynamic_cast<P_NPC>( packOwner )->owner() != pChar ) ) { socket->sysMessage( tr( "You cannot put that into the belongings of another player" ) ); socket->bounceItem( pItem, BR_NO_REASON ); return; } } if ( !pChar->canPickUp( pItem ) ) { socket->bounceItem( pItem, BR_CANNOT_PICK_THAT_UP ); return; } // We drop something on the belongings of one of our playervendors /* if( ( packOwner != NULL ) && ( packOwner->npcaitype() == 17 ) && packOwner->owner() == pChar ) { socket->sysMessage( tr( "You drop something into your playervendor (unimplemented)" ) ); socket->bounceItem( pItem, BR_NO_REASON ); return; }*/ // Playervendors (chest equipped by the vendor - opened to the client) /*if( !( pCont->pileable() && pItem->pileable() && pCont->id() == pItem->id() || ( pCont->type() != 1 && pCont->type() != 9 ) ) ) { P_CHAR pc_j = GetPackOwner(pCont); if (pc_j != NULL) { if (pc_j->npcaitype() == 17 && pc_j->isNpc() && pChar->Owns(pc_j)) { pChar->inputitem = pItem->serial; pChar->inputmode = cChar::enPricing; sysmessage(s, "Set a price for this item."); } } */ // We may also drop into *any* locked chest // So we can have post-boxes ;o) if ( pCont->type() == 1 ) { // If we're dropping it onto the closed container if ( dropPos.x == 0xFFFF && dropPos.y == 0xFFFF ) { pCont->addItem( pItem ); } else { pCont->addItem( pItem, false ); pItem->setPos( dropPos ); } // Dropped on another Container/in another Container pChar->soundEffect( 0x57 ); pItem->update(); return; } else if ( pCont->canStack( pItem ) ) { if ( pCont->amount() + pItem->amount() <= 65535 ) { pCont->setAmount( pCont->amount() + pItem->amount() ); pItem->remove(); pCont->update(); // Need to update the amount pCont->resendTooltip(); return; } else { // The delta between 65535 and pCont->amount() sub our Amount is the // new amount pItem->setAmount( pItem->amount() - ( 65535 - pCont->amount() ) ); pItem->resendTooltip(); pCont->setAmount( 65535 ); // Max out the amount pCont->update(); pCont->resendTooltip(); } } // We dropped the item NOT on a container // And were *un*able to stack it (!) // >> Set it to the location of the item we dropped it on and stack it up by 2 if ( pCont->container() ) { P_ITEM pNewCont = dynamic_cast<P_ITEM>( pCont->container() ); if ( pNewCont ) { pNewCont->addItem( pItem, false ); pItem->setPos( pCont->pos() + Coord_cl( 0, 0, 2 ) ); } else { pChar->getBackpack()->addItem( pItem ); } } else { pItem->removeFromCont(); pItem->moveTo( pCont->pos() + Coord_cl( 0, 0, 2 ) ); } pItem->update(); }
// The highest items will be @ the beginning // While walking we always will try the highest first. vector< stBlockItem > getBlockingItems( P_CHAR pChar, const Coord_cl& pos ) { vector<stBlockItem> blockList; make_heap( blockList.begin(), blockList.end(), compareTiles() ); // Process the map at that position stBlockItem mapBlock; mapBlock.maptile = true; mapBlock.z = Maps::instance()->mapAverageElevation( pos ); mapBlock.height = 0; // TODO: Calculate the REAL average Z Value of that Map Tile here! Otherwise clients will have minor walking problems. map_st mapCell = Maps::instance()->seekMap( pos ); //mapBlock.z = mapCell.z;*/ land_st mapTile = TileCache::instance()->getLand( mapCell.id ); // If it's not impassable it's automatically walkable if ( !( mapTile.flag1 & 0x40 ) ) mapBlock.walkable = true; else mapBlock.walkable = checkWalkable( pChar, mapCell.id ); if ( mapCell.id != 0x02 ) { blockList.push_back( mapBlock ); push_heap( blockList.begin(), blockList.end(), compareTiles() ); } // Now for the static-items StaticsIterator staIter = Maps::instance()->staticsIterator( pos, true ); for ( ; !staIter.atEnd(); ++staIter ) { tile_st tTile = TileCache::instance()->getTile( staIter->itemid ); // Here is decided if the tile is needed // It's uninteresting if it's NOT blocking // And NOT a bridge/surface if ( !( ( tTile.flag2 & 0x02 ) || ( tTile.flag1 & 0x40 ) || ( tTile.flag2 & 0x04 ) ) ) continue; stBlockItem staticBlock; staticBlock.z = staIter->zoff; // If we are a surface we can always walk here, otherwise check if // we are special if ( ( tTile.flag2 & 0x02 ) && !( tTile.flag1 & 0x40 ) ) staticBlock.walkable = true; else staticBlock.walkable = checkWalkable( pChar, staIter->itemid ); // If we are a stair only the half height counts (round up) if ( tTile.flag2 & 0x04 ) staticBlock.height = ( Q_UINT8 ) ( ( tTile.height + 1 ) / 2 ); else staticBlock.height = tTile.height; blockList.push_back( staticBlock ); push_heap( blockList.begin(), blockList.end(), compareTiles() ); } RegionIterator4Items iIter( pos ); for ( iIter.Begin(); !iIter.atEnd(); iIter++ ) { P_ITEM pItem = iIter.GetData(); if ( !pItem ) continue; if ( pItem->id() >= 0x4000 ) { MultiDefinition* def = MultiCache::instance()->getMulti( pItem->id() - 0x4000 ); if ( !def ) continue; QValueVector<multiItem_st> multi = def->getEntries(); unsigned int j; for ( j = 0; j < multi.size(); ++j ) { if ( multi[j].visible && ( pItem->pos().x + multi[j].x == pos.x ) && ( pItem->pos().y + multi[j].y == pos.y ) ) { tile_st tTile = TileCache::instance()->getTile( multi[j].tile ); if ( !( ( tTile.flag2 & 0x02 ) || ( tTile.flag1 & 0x40 ) || ( tTile.flag2 & 0x04 ) ) ) continue; stBlockItem blockItem; blockItem.height = tTile.height; blockItem.z = pItem->pos().z + multi[j].z; if ( ( tTile.flag2 & 0x02 ) && !( tTile.flag1 & 0x40 ) ) blockItem.walkable = true; else blockItem.walkable = checkWalkable( pChar, pItem->id() ); blockList.push_back( blockItem ); push_heap( blockList.begin(), blockList.end(), compareTiles() ); } } continue; } else if ( pChar->isDead() ) { // Doors can be passed by ghosts if ( pItem->hasScript( "door" ) ) { continue; } } // They need to be at the same x,y,plane coords if ( ( pItem->pos().x != pos.x ) || ( pItem->pos().y != pos.y ) || ( pItem->pos().map != pos.map ) ) continue; tile_st tTile = TileCache::instance()->getTile( pItem->id() ); // Se above for what the flags mean if ( !( ( tTile.flag2 & 0x02 ) || ( tTile.flag1 & 0x40 ) || ( tTile.flag2 & 0x04 ) ) ) continue; stBlockItem blockItem; blockItem.height = tTile.height; blockItem.z = pItem->pos().z; // Once again: see above for a description of this part if ( ( tTile.flag2 & 0x02 ) && !( tTile.flag1 & 0x40 ) ) blockItem.walkable = true; else blockItem.walkable = checkWalkable( pChar, pItem->id() ); blockList.push_back( blockItem ); push_heap( blockList.begin(), blockList.end(), compareTiles() ); } // Now we need to evaluate dynamic items [...] (later) // TODO: Multis here sort_heap( blockList.begin(), blockList.end(), compareTiles() ); return blockList; };
bool Coord_cl::lineOfSight( const Coord_cl &target, UI16 targetheight, bool touch ) { //Console::instance()->send( QString( "LOScheck: Source:%1,Target:%2,Targetheight:%3\n" ).arg( z ).arg( target.z ).arg( targetheight ) ); if( target.map != map ) return false; if( (x == target.x) && (y == target.y) && (z == target.z) ) return true; // if source and target are on the same position SI32 n = ( target.x - x ), m = ( target.y - y ), i = 0; SI08 sgn_x = ( x <= target.x ) ? 1 : (-1); // signum for x SI08 sgn_y = ( y <= target.y ) ? 1 : (-1); // signum for y SI08 sgn_z = ( z <= target.z ) ? 1 : (-1); // signum for z if( x == target.x ) sgn_x = 0; if( y == target.y ) sgn_y = 0; if( z == target.z ) sgn_z = 0; QValueList< Coord_cl > collisions; //first we get our x-y-coordinates if( sgn_x == 0 && sgn_y == 0 && !sgn_z == 0 ) // should fix shooting through floor issues { collisions.push_back( Coord_cl( x, y, 0, map ) ); } else if( sgn_x == 0 ) // if we are on the same x-level, just push every x/y coordinate in y-direction from src to trg into the array for( i = 0; i <= (sgn_y * m); ++i ) { collisions.push_back( Coord_cl( x, y + (sgn_y * i), 0, map ) ); } else if ( sgn_y == 0 ) // if we are on the same y-level, just push every x/y coordinate in x-direction from src to trg into the array for( i = 0; i <= (sgn_x * n); ++i ) { collisions.push_back( Coord_cl( x + (sgn_x * i), y, 0, map ) ); } else { SI32 oldpos = y; bool exaktpos = false; for( i = 0; (sgn_x * n >= sgn_y * m) && (i <= (sgn_x * n)); i++ ) { //Console::instance()->send( QString( "x:%1\n" ).arg( i ) ); SI32 gridx = x + (sgn_x * i); if( ( ( n == 0 ) && ( gridx == 0 ) ) || ( ( n + ( gridx * m ) == 0 ) ) ) continue; else { if( exaktpos ) { collisions.push_back( Coord_cl( gridx, oldpos-sgn_y, 0, map ) ); //Console::instance()->send( QString( "add exaktpos coordinate %1,%2\n" ).arg( gridx ).arg( oldpos-sgn_y ) ); exaktpos = false; } // linear evaluation of extended 2x2 matrix, abbreviated double t = (double)sgn_x * ((double)i+0.5) * (double)m / (double)n + (double)y; //Console::instance()->send( QString( "t:%1\n" ).arg( t ) ); if( ((sgn_y>0) && (specialFloor(t)==oldpos+0.5)) || ((sgn_y<0) && (specialFloor(t)==oldpos-0.5)) ) { exaktpos = true; } if( ((sgn_y>0) && (t<oldpos+0.5)) || ((sgn_y<0) && (t>oldpos-0.5)) || (oldpos==target.y) ) { collisions.push_back( Coord_cl( gridx, oldpos, 0, map ) ); //Console::instance()->send( QString( "add coordinate %1,%2\n" ).arg( gridx ).arg( oldpos ) ); } // but if not, we have to take BOTH coordinates, which the calculated collision is between! else { collisions.push_back( Coord_cl( gridx, oldpos, 0, map ) ); //Console::instance()->send( QString( "add coordinate %1,%2\n" ).arg( gridx ).arg( oldpos ) ); oldpos += sgn_y; collisions.push_back( Coord_cl( gridx, oldpos, 0, map ) ); //Console::instance()->send( QString( "add coordinate %1,%2\n" ).arg( gridx ).arg( oldpos ) ); } } } oldpos = x; exaktpos = false; for( i = 0; (sgn_y * m >= sgn_x * n) && (i <= (sgn_y * m)); ++i ) { //Console::instance()->send( QString( "y:%1\n" ).arg( i ) ); SI32 gridy = y + (sgn_y * i); if( ( ( m == 0 ) && ( gridy == 0 ) ) || ( ( m + ( gridy * n ) == 0 ) ) ) continue; else { if( exaktpos ) { collisions.push_back( Coord_cl( oldpos-sgn_x, gridy, 0, map ) ); //Console::instance()->send( QString( "add exaktpos coordinate %1,%2\n" ).arg( oldpos-sgn_x ).arg( gridy ) ); exaktpos = false; } double t = (double)x + (double)n / (double)m * (double)sgn_y * ((double)i+0.5); //Console::instance()->send( QString( "t:%1\n" ).arg( t ) ); if( ((sgn_x>0) && (specialFloor(t)==oldpos+0.5)) || ((sgn_x<0) && (specialFloor(t)==oldpos-0.5)) ) { exaktpos = true; } if( ((sgn_x>0) && (t<oldpos+0.5)) || ((sgn_x<0) && (t>oldpos-0.5)) || (oldpos==target.x) ) { collisions.push_back( Coord_cl( oldpos, gridy, 0, map ) ); //Console::instance()->send( QString( "add coordinate %1,%2\n" ).arg( oldpos ).arg( gridy ) ); } // but if not, we have to take BOTH coordinates, which the calculated collision is between! else { collisions.push_back( Coord_cl( oldpos, gridy, 0, map ) ); //Console::instance()->send( QString( "add coordinate %1,%2\n" ).arg( oldpos ).arg( gridy ) ); oldpos += sgn_x;; collisions.push_back( Coord_cl( oldpos, gridy, 0, map ) ); //Console::instance()->send( QString( "add coordinate %1,%2\n" ).arg( oldpos ).arg( gridy ) ); } } } } // the next will search for multis QPtrList< cItem > multis; RegionIterator4Items ri( *this ); for( ri.Begin(); !ri.atEnd(); ri++ ) { P_ITEM pi = ri.GetData(); if( pi && pi->id() >= 0x4000 ) { multis.append( pi ); } } //touch wird von notouch getrennt, da der notouch algorithmus wesentlich aufwändiger //ist (touch benötigt die erste for-schleife nicht), macht den code zwar bisserl //unübersichtlicher, aber ist wesentlich effizienter //touch is separated from notouch, because the notouch calculation is much more to do //( one additional for-loop ) if( targetheight > 0 ) { --targetheight; } if( !touch ) { for( i = target.z+targetheight; i >= target.z; --i ) { bool blocked = false; //Console::instance()->send( QString( "i:%1\n" ).arg( i ) ); double dz; double gradient; double offset; if( (sgn_x == 0) && (sgn_y == 0) ) { dz = (double)i - (double)z; gradient = 1; //only to avoid problems, isnt used offset = 1; } else { dz = ( (double)i -(double)z ) / sqrt( ((double)target.x - (double)x)*((double)target.x - (double)x) + ((double)target.y - (double)y)*((double)target.y - (double)y) ); gradient = (double)m / (double)n; offset = 0.5*( (double)y + (double)target.y - gradient*( x + target.x ) ); } map_st map1, map2; SI32 j; bool posHigherThanMap; map1 = Map->seekMap( (*this) ); if( map1.z > z ) { posHigherThanMap = false; } else { posHigherThanMap = true; } //Console::instance()->send( QString( "after first things\n" ) ); QValueList< Coord_cl >::iterator pit = collisions.begin(); while( pit != collisions.end() ) { //Console::instance()->send( QString( "coordinate:%1,%2 dz:%3\n" ).arg( (*pit).x ).arg( (*pit).y ).arg( dz ) ); //lets see what z-coordinates we have to test //we do our calculations exakt, because its the only way to solve all problems //of floor //"minimum" ist am anfang der platte, "maximum" am ende //our line is y = gradient * x + offset //or x = ( y - offset ) / gradient //we now have to test, where the line cuts one position //start and endposition has to be done alone double z1 = -300; double z2 = -300; SI08 zmin, zmax; if( (sgn_x == 0) && (sgn_y == 0) ) { if( dz > 0 ) { zmin = z+1; zmax = i; } else { zmin = i+1; zmax = z; } } else if( sgn_x == 0 ) { //gradient only in y-direction z1 = (double)i - dz*( fabs( (double)target.y - (double)((*pit).y) ) -0.5 ); z2 = (double)i - dz*( fabs( (double)target.y - (double)((*pit).y) ) +0.5 ); //Console::instance()->send( QString( "i:%1,ty:%2,cy:%3\n" ).arg( i ).arg( target.y ).arg( (*pit).y ) ); //Console::instance()->send( QString( "z1:%1,z2:%2\n" ).arg( z1 ).arg( z2 ) ); if( z1 > z2 ) { zmin = (SI08)floor( z2 ); zmax = (SI08)ceilf( z1 ); } else { zmin = (SI08)floor( z1 ); zmax = (SI08)ceilf( z2 ); } /*another try, but i think its needed for all positions, not only start and end... //target if( (*pit).y == target.y ) { if( dz > 0 ) { zmax = QMIN( zmax, i ); //Console::instance()->send( QString( "TargetY, zmax:%1, i:%2\n" ).arg( zmax ).arg( i ) ); } else { zmin = QMAX( zmin, i+1 ); //Console::instance()->send( QString( "TargetY, zmin:%1, i:%2\n" ).arg( zmin ).arg( i ) ); } } //source if( (*pit).y == y ) { if( dz > 0 ) { zmin = QMAX( zmin, z ); //Console::instance()->send( QString( "SourceY, zmin:%1, i:%2\n" ).arg( zmax ).arg( i ) ); } else { zmax = QMIN( zmax, z ); //Console::instance()->send( QString( "SourceY, zmax:%1, i:%2\n" ).arg( zmax ).arg( i ) ); } }*/ if( dz > 0 ) { zmax = QMIN( zmax, i ); zmin = QMAX( zmin, z ); } else { zmin = QMAX( zmin, i+1 ); zmax = QMIN( zmax, z ); } } else if( sgn_y == 0 ) { //gradient only in y-direction z1 = (double)i - dz*( fabs( (double)target.x - (double)((*pit).x) ) -0.5 ); z2 = (double)i - dz*( fabs( (double)target.x - (double)((*pit).x) ) +0.5 ); //Console::instance()->send( QString( "i:%1,tx:%2,cx:%3\n" ).arg( i ).arg( target.x ).arg( (*pit).x ) ); //Console::instance()->send( QString( "z1:%1,z2:%2\n" ).arg( z1 ).arg( z2 ) ); if( z1 > z2 ) { zmin = (SI08)floor( z2 ); zmax = (SI08)ceilf( z1 ); } else { zmin = (SI08)floor( z1 ); zmax = (SI08)ceilf( z2 ); } if( dz > 0 ) { zmax = QMIN( zmax, i ); zmin = QMAX( zmin, z ); } else { zmin = QMAX( zmin, i+1 ); zmax = QMIN( zmax, z ); } } else { //4 lines to test double gradx = ( (double)target.y*(double)z - (double)y*(double)i ) / ( (double)target.y*(double)x - (double)y*(double)target.x ); double grady = ( (double)target.x*(double)z - (double)x*(double)i ) / ( (double)y*(double)target.x - (double)target.y*(double)x ); //Console::instance()->send( QString( "gradx:%1,grady:%2\n" ).arg( gradx ).arg( grady ) ); //Console::instance()->send( QString( "Gradient:%1,Offset:%2\n" ).arg( gradient ).arg( offset ) ); double temp; temp = specialFloor( gradient*( (double)((*pit).x) - 0.5 ) + offset ); //Console::instance()->send( QString( "temp1:%1\n" ).arg( temp ) ); if( ( temp >= ((double)((*pit).y)-0.5) ) && ( temp <= ((double)((*pit).y)+0.5) ) ) { if( z1 > -300 ) { z2 = gradx * ( (double)((*pit).x)-0.5 ) + grady * temp; } else { z1 = gradx * ( (double)((*pit).x)-0.5 ) + grady * temp; } //Console::instance()->send( QString( "1:i:%1,tx:%2,ty:%3,cy:%4,cy:%5\n" ).arg( i ).arg( target.x ).arg( target.y ).arg( (*pit).x ).arg( (*pit).y ) ); //Console::instance()->send( QString( "z1:%1,z2:%2\n" ).arg( z1 ).arg( z2 ) ); } temp = specialFloor( gradient*( (double)((*pit).x) + 0.5 ) + offset ); //Console::instance()->send( QString( "temp2:%1\n" ).arg( temp ) ); if( ( temp >= ((double)((*pit).y)-0.5) ) && ( temp <= ((double)((*pit).y)+0.5) ) ) { if( z1 > -300 ) { z2 = gradx * ( (double)((*pit).x)+0.5 ) + grady * temp; } else { z1 = gradx * ( (double)((*pit).x)+0.5 ) + grady * temp; } //Console::instance()->send( QString( "2:i:%1,tx:%2,ty:%3,cy:%4,cy:%5\n" ).arg( i ).arg( target.x ).arg( target.y ).arg( (*pit).x ).arg( (*pit).y ) ); //Console::instance()->send( QString( "z1:%1,z2:%2\n" ).arg( z1 ).arg( z2 ) ); } temp = specialFloor( (double)((*pit).y) - 0.5 - offset ) / gradient; //Console::instance()->send( QString( "temp3:%1\n" ).arg( temp ) ); if( ( temp > ((double)((*pit).x)-0.5) ) && ( temp < ((double)((*pit).x)+0.5) ) ) { if( z1 > -300 ) { z2 = gradx * temp + grady * ((double)((*pit).y)-0.5); } else { z1 = gradx * temp + grady * ((double)((*pit).y)-0.5); } //Console::instance()->send( QString( "3:i:%1,tx:%2,ty:%3,cy:%4,cy:%5\n" ).arg( i ).arg( target.x ).arg( target.y ).arg( (*pit).x ).arg( (*pit).y ) ); //Console::instance()->send( QString( "z1:%1,z2:%2\n" ).arg( z1 ).arg( z2 ) ); } temp = specialFloor( (double)((*pit).y) + 0.5 - offset ) / gradient; //Console::instance()->send( QString( "temp4:%1\n" ).arg( temp ) ); if( ( temp > ((double)((*pit).x)-0.5) ) && ( temp < ((double)((*pit).x)+0.5) ) ) { if( z1 > -300 ) { z2 = gradx * temp + grady * ((double)((*pit).y)+0.5); } else { z1 = gradx * temp + grady * ((double)((*pit).y)+0.5); } //Console::instance()->send( QString( "4:i:%1,tx:%2,ty:%3,cy:%4,cy:%5\n" ).arg( i ).arg( target.x ).arg( target.y ).arg( (*pit).x ).arg( (*pit).y ) ); //Console::instance()->send( QString( "z1:%1,z2:%2\n" ).arg( z1 ).arg( z2 ) ); } //Console::instance()->send( QString( "z1:%1,z2:%2\n" ).arg( z1 ).arg( z2 ) ); if( z1 > z2 ) { zmin = (SI08)floor( z2 ); zmax = (SI08)ceilf( z1 ); } else { zmin = (SI08)floor( z1 ); zmax = (SI08)ceilf( z2 ); } if( z2 == -300 ) { zmin = (SI08)floor( z1 ); } if( dz > 0 ) { zmax = QMIN( zmax, i ); zmin = QMAX( zmin, z ); } else { zmin = QMAX( zmin, i+1 ); zmax = QMIN( zmax, z ); } //Console::instance()->send( QString( "zmin:%1,zmax:%2\n" ).arg( zmin ).arg( zmax ) ); } /*SI08 zmin = (SI08)floor( i - dz*sqrt( ((double)target.x - (double)(*pit).x)*((double)target.x - (double)(*pit).x) + ((double)target.y - (double)(*pit).y)*((double)target.y - (double)(*pit).y) ) ); SI08 zmax; //Console::instance()->send( QString( "zmin:%1,dz:%2\n" ).arg( zmin ).arg( dz ) ); if( dz > 0 ) { zmin = QMAX( (SI08)floor( zmin - dz/2 ), z+1 ); zmax = QMIN( zmin + dz + 1, target.z+targetheight+1 ); //to prevent floor-mistakes } else { zmin = QMIN( (SI08)floor( zmin + dz/2 ), target.z+1 ); zmax = QMAX( zmin - dz + 1, z ); //to prevent floor-mistakes }*/ // Texture mapping map1 = Map->seekMap( *pit ); map2 = Map->seekMap( Coord_cl( (*pit).x + sgn_x, (*pit).y + sgn_y, (*pit).z, map ) ); //Console::instance()->send( QString( "maphoehe:%1\n" ).arg( map1.z ) ); StaticsIterator msi = Map->staticsIterator( *pit ); if( (map1.id != 2) && (map2.id != 2) ) { if( ( map1.z >= zmin ) && ( map1.z <= zmax ) ) { //its just in our way //Console::instance()->send( QString( "Map gescheitert\n" ) ); blocked = true; break; } //now we have to test, if this tile is under our line and the next above or //vice verse //in this case, both dont cut our line, but the mapconnection between them //should do if( ( ( map1.z < map2.z ) && ( map1.z < zmin ) && ( map2.z > zmax+dz ) ) || // 1) lineofsight collides with a map "wall" ( ( map1.z > map2.z ) && ( map1.z > zmin ) && ( map2.z < zmax-dz ) ) || ( ( ( map1.id >= 431 && map1.id <= 432 ) || // 3) lineofsight cuts a mountain ( map1.id >= 467 && map1.id <= 475 ) || ( map1.id >= 543 && map1.id <= 560 ) || ( map1.id >= 1754 && map1.id <= 1757 ) || ( map1.id >= 1787 && map1.id <= 1789 ) || ( map1.id >= 1821 && map1.id <= 1824 ) || ( map1.id >= 1851 && map1.id <= 1854 ) || ( map1.id >= 1881 && map1.id <= 1884 ) ) && ( posHigherThanMap ) && ( msi.atEnd() ) ) ) // mountains cut only if we are not beneath them { //Console::instance()->send( QString( "map1:%1,map2:%2\n" ).arg( map1.z ).arg( map2.z ) ); blocked = true; break; } } //Console::instance()->send( QString( "after map\n" ) ); // Statics tile_st tile; while( !msi.atEnd() ) { tile = TileCache::instance()->getTile( msi->itemid ); //if it is in our way //Console::instance()->send( QString( "tilepos:%1,zmax:%2" ).arg( msi->zoff ).arg( zmax ) ); //Console::instance()->send( QString( "zmin:%3\n" ).arg( zmin ) ); if( ( zmax >= msi->zoff ) && ( zmin <= ( msi->zoff + tile.height ) ) ) { //Console::instance()->send( QString( "statictile, id: %1\n" ).arg( msi->itemid ) ); if( tile.isNoShoot() ) { blocked = true; break; } } ++msi; } if( blocked ) { break; } //Console::instance()->send( QString( "after statics\n" ) ); // Items RegionIterator4Items rj( (*pit), 0 ); for( rj.Begin(); !rj.atEnd(); rj++ ) { P_ITEM pi = rj.GetData(); if( pi && pi->id() < 0x4000 ) { tile = TileCache::instance()->getTile( pi->id() ); if( ( zmax >= pi->pos().z ) && ( zmin <= ( pi->pos().z + tile.height ) ) && ( pi->visible() == 0 ) ) { //Console::instance()->send( QString( "item, id: %1" ).arg( pi->id() ) ); if( tile.isNoShoot() ) { blocked = true; break; } } } } if( blocked ) { break; } //Console::instance()->send( QString( "after items\n" ) ); // Multis QPtrListIterator< cItem > mit( multis ); P_ITEM pi; while( ( pi = mit.current() ) ) { MultiDefinition* def = MultiCache::instance()->getMulti( pi->id() - 0x4000 ); if ( !def ) continue; QValueVector<multiItem_st> multi = def->getEntries(); for( j = 0; j < multi.size(); ++j ) { if( ( multi[j].visible ) && ( pi->pos().x + multi[j].x == (*pit).x ) && ( pi->pos().y + multi[j].y == (*pit).y ) ) { tile = TileCache::instance()->getTile( multi[j].tile ); if( ( zmax >= pi->pos().z + multi[j].z ) && ( zmin <= pi->pos().z + multi[j].z + tile.height ) ) { if( tile.isNoShoot() ) { blocked = true; break; } } } } ++mit; } //Console::instance()->send( QString( "after multis\n" ) ); ++pit; } if( !blocked ) return true; } //there was no line to see through return false; } //now touch=true else { //Console::instance()->send( "touch\n" ); double dz_up, dz_down; double gradient; double offset; if( (sgn_x == 0) && (sgn_y == 0) ) { dz_up = ( (double)targetheight + (double)target.z ) - (double)z; dz_down = (double)target.z - ( (double)z - (double)15 ); gradient = 1; //only to prevent problems, isnt used offset = 1; } else { //Console::instance()->send( QString( "xdiff:%1,ydiff:%2\n" ).arg( (double)target.x - (double)x ).arg( (double)target.y - (double)y ) ); dz_up = ( ( (double)targetheight + (double)target.z ) - (double)z ) / sqrt( ((double)target.x - (double)x)*((double)target.x - (double)x) + ((double)target.y - (double)y)*((double)target.y - (double)y) ); dz_down = ( (double)target.z - ( (double)z - (double)15 ) ) / sqrt( ((double)target.x - (double)x)*((double)target.x - (double)x) + ((double)target.y - (double)y)*((double)target.y - (double)y) ); gradient = (double)m / (double)n; offset = 0.5*( (double)y + (double)target.y - gradient*( x + target.x ) ); } map_st map1, map2; SI32 j; bool posHigherThanMap; map1 = Map->seekMap( (*this) ); if( map1.z > z ) { posHigherThanMap = false; } else { posHigherThanMap = true; } //Console::instance()->send( QString( "after first things\n" ) ); QValueList< Coord_cl >::iterator pit = collisions.begin(); while( pit != collisions.end() ) { //Console::instance()->send( QString( "coordinate:%1,%2\n" ).arg( (*pit).x ).arg( (*pit).y ) ); //lets see what z-coordinates we have to test //anmerkung: touch kommt nur für chars vor, grösse von chars ist 15 //SI08 zmin = (SI08)floor( (double)z - (double)sourceheight + dz_down*sqrt( ((double)target.x - (double)(*pit).x)*((double)target.x - (double)(*pit).x) + ((double)target.y - (double)(*pit).y)*((double)target.y - (double)(*pit).y) ) ); //SI08 zmax = (SI08)floor( (double)z + dz_up*sqrt( ((double)target.x - (double)(*pit).x)*((double)target.x - (double)(*pit).x) + ((double)target.y - (double)(*pit).y)*((double)target.y - (double)(*pit).y) ) ); SI08 zmin, zmax; double z1_up = -300; double z2_up = -300; double z1_down = -300; double z2_down = -300; bool targetpos = false; //Console::instance()->send( QString( "dz_down:%3,dz_up:%4\n" ).arg( dz_down ).arg( dz_up ) ); if( (sgn_x == 0) && (sgn_y == 0) ) { if( dz_up > 0 ) { zmin = z+1; zmax = target.z; } else { zmin = target.z+1; zmax = z; } targetpos = true; if( (dz_up >= 0) && (dz_down >= 0) ) { if( zmin < target.z ) { zmax = target.z -1; } else { //we ignore this coordinate ++pit; continue; } } else if( (dz_up <= 0) && (dz_down <= 0) ) { if( zmax > target.z + targetheight+1 ) { zmin = target.z + targetheight + 2; } else { ++pit; continue; } } else { //we may have to split the test into two if we would do it exactly //but i think we can throw away the test from down in this case if( zmax > target.z + targetheight+1 ) { zmin = target.z + targetheight + 2; } else if( zmin < target.z ) { zmax = target.z -1; } else { ++pit; continue; } } } else if( sgn_x == 0 ) { z1_up = target.z + targetheight - dz_up*( fabs( (double)target.y - (double)((*pit).y) ) -0.5 ); z2_up = target.z + targetheight - dz_up*( fabs( (double)target.y - (double)((*pit).y) ) +0.5 ); z1_down = target.z - dz_down*( fabs( (double)target.y - (double)((*pit).y) ) -0.5 ); z2_down = target.z - dz_down*( fabs( (double)target.y - (double)((*pit).y) ) +0.5 ); //Console::instance()->send( QString( "ty:%2,cy:%3\n" ).arg( target.y ).arg( (*pit).y ) ); //Console::instance()->send( QString( "z1_up:%1,z2_up:%2,z1_down:%3,z2_down:%4\n" ).arg( z1_up ).arg( z2_up ).arg( z1_down ).arg( z2_down ) ); zmax = QMAX( ceil( z1_up ), ceil( z2_up ) ); zmin = QMIN( floor( z1_down ), floor( z2_down ) ); //Console::instance()->send( QString( "y:zmin:%1,zmax:%2\n" ).arg( zmin ).arg( zmax ) ); if( dz_up > 0 ) { zmax = QMIN( zmax, target.z+targetheight ); } else { zmax = QMIN( zmax, z ); } //Console::instance()->send( QString( "y2:zmin:%1,zmax:%2\n" ).arg( zmin ).arg( zmax ) ); if( dz_down > 0 ) { zmin = QMAX( zmin, z-14 ); } else { zmin = QMAX( zmin, target.z+1 ); } //Console::instance()->send( QString( "y3:zmin:%1,zmax:%2\n" ).arg( zmin ).arg( zmax ) ); if( (*pit).y == target.y ) { targetpos = true; if( (dz_up >= 0) && (dz_down >= 0) ) { if( zmin < target.z ) { zmax = target.z -1; } else { //we ignore this coordinate ++pit; continue; } } else if( (dz_up <= 0) && (dz_down <= 0) ) { if( zmax > target.z + targetheight+1 ) { zmin = target.z + targetheight + 2; } else { ++pit; continue; } } else { //we may have to split the test into two if we would do it exactly //but i think we can throw away the test from down in this case if( zmax > target.z + targetheight+1 ) { zmin = target.z + targetheight + 2; } else if( zmin < target.z ) { zmax = target.z -1; } else { ++pit; continue; } } //Console::instance()->send( QString( "y4:zmin:%1,zmax:%2\n" ).arg( zmin ).arg( zmax ) ); } } else if( sgn_y == 0 ) { z1_up = target.z + targetheight - dz_up*( fabs( (double)target.x - (double)((*pit).x) ) -0.5 ); z2_up = target.z + targetheight - dz_up*( fabs( (double)target.x - (double)((*pit).x) ) +0.5 ); z1_down = target.z - dz_down*( fabs( (double)target.x - (double)((*pit).x) ) -0.5 ); z2_down = target.z - dz_down*( fabs( (double)target.x - (double)((*pit).x) ) +0.5 ); //Console::instance()->send( QString( "tx:%2,cx:%3\n" ).arg( target.x ).arg( (*pit).x ) ); //Console::instance()->send( QString( "z1_up:%1,z2_up:%2,z1_down:%3,z2_down:%4\n" ).arg( z1_up ).arg( z2_up ).arg( z1_down ).arg( z2_down ) ); zmax = QMAX( ceil( z1_up ), ceil( z2_up ) ); zmin = QMIN( floor( z1_down ), floor( z2_down ) ); if( dz_up > 0 ) { zmax = QMIN( zmax, target.z+targetheight ); } else { zmax = QMIN( zmax, z ); } if( dz_down > 0 ) { zmin = QMAX( zmin, z-14 ); } else { zmin = QMAX( zmin, target.z+1 ); } if( (*pit).x == target.x ) { targetpos = true; if( (dz_up >= 0) && (dz_down >= 0) ) { if( zmin < target.z ) { zmax = target.z -1; } else { //we ignore this coordinate ++pit; continue; } } else if( (dz_up <= 0) && (dz_down <= 0) ) { if( zmax > target.z + targetheight+1 ) { zmin = target.z + targetheight + 2; } else { ++pit; continue; } } else { //we may have to split the test into two if we would do it exactly //but i think we can throw away the test from down in this case if( zmax > target.z + targetheight+1 ) { zmin = target.z + targetheight + 2; } else if( zmin < target.z ) { zmax = target.z -1; } else { ++pit; continue; } } } } else { double gradx_up = ( (double)target.y*(double)z - (double)y*(double)(target.z + targetheight) ) / ( (double)target.y*(double)x - (double)y*(double)target.x ); double grady_up = ( (double)target.x*(double)z - (double)x*(double)(target.z + targetheight) ) / ( (double)y*(double)target.x - (double)target.y*(double)x ); double gradx_down = ( (double)target.y*(double)(z-15) - (double)y*(double)(target.z) ) / ( (double)target.y*(double)x - (double)y*(double)target.x ); double grady_down = ( (double)target.x*(double)(z-15) - (double)x*(double)(target.z) ) / ( (double)y*(double)target.x - (double)target.y*(double)x ); //Console::instance()->send( QString( "gradx_up:%1,grady_up:%2,gradx_down:%3,grady_down:%4\n" ).arg( gradx_up ).arg( grady_up ).arg( gradx_down ).arg( grady_down ) ); //Console::instance()->send( QString( "Gradient:%1,Offset:%2\n" ).arg( gradient ).arg( offset ) ); double temp = specialFloor( gradient*( (double)((*pit).x) - 0.5 ) + offset ); //Console::instance()->send( QString( "temp1:%1\n" ).arg( temp ) ); if( ( temp >= ((double)((*pit).y)-0.5) ) && ( temp <= ((double)((*pit).y)+0.5) ) ) { if( z1_up > -300 ) { z2_up = gradx_up * ( (double)((*pit).x)-0.5 ) + grady_up * temp; z2_down = gradx_down * ( (double)((*pit).x)-0.5 ) + grady_down * temp; } else { z1_up = gradx_up * ( (double)((*pit).x)-0.5 ) + grady_up * temp; z1_down = gradx_down * ( (double)((*pit).x)-0.5 ) + grady_down * temp; } //Console::instance()->send( QString( "tx:%1,ty:%2,cy:%3,cy:%4\n" ).arg( target.x ).arg( target.y ).arg( (*pit).x ).arg( (*pit).y ) ); //Console::instance()->send( QString( "z1_up:%1,z1_down:%2,z2_up:%3,z2_down:%4\n" ).arg( z1_up ).arg( z1_down ).arg( z2_up ).arg( z2_down ) ); } temp = specialFloor( gradient*( (double)((*pit).x) + 0.5 ) + offset ); //Console::instance()->send( QString( "temp2:%1\n" ).arg( temp ) ); if( ( temp >= ((double)((*pit).y)-0.5) ) && ( temp <= ((double)((*pit).y)+0.5) ) ) { if( z1_up > -300 ) { z2_up = gradx_up * ( (double)((*pit).x)+0.5 ) + grady_up * temp; z2_down = gradx_down * ( (double)((*pit).x)+0.5 ) + grady_down * temp; } else { z1_up = gradx_up * ( (double)((*pit).x)+0.5 ) + grady_up * temp; z1_down = gradx_down * ( (double)((*pit).x)+0.5 ) + grady_down * temp; } //Console::instance()->send( QString( "tx:%1,ty:%2,cy:%3,cy:%4\n" ).arg( target.x ).arg( target.y ).arg( (*pit).x ).arg( (*pit).y ) ); //Console::instance()->send( QString( "z1_up:%1,z1_down:%2,z2_up:%3,z2_down:%4\n" ).arg( z1_up ).arg( z1_down ).arg( z2_up ).arg( z2_down ) ); } temp = specialFloor( (double)((*pit).y) - 0.5 - offset ) / gradient; //Console::instance()->send( QString( "temp3:%1\n" ).arg( temp ) ); if( ( temp > ((double)((*pit).x)-0.5) ) && ( temp < ((double)((*pit).x)+0.5) ) ) { if( z1_up > -300 ) { z2_up = gradx_up * temp + grady_up * ((double)((*pit).y)-0.5); z2_down = gradx_down * temp + grady_down * ((double)((*pit).y)-0.5); } else { z1_up = gradx_up * temp + grady_up * ((double)((*pit).y)-0.5); z1_down = gradx_down * temp + grady_down * ((double)((*pit).y)-0.5); } //Console::instance()->send( QString( "tx:%1,ty:%2,cy:%3,cy:%4\n" ).arg( target.x ).arg( target.y ).arg( (*pit).x ).arg( (*pit).y ) ); //Console::instance()->send( QString( "z1_up:%1,z1_down:%2,z2_up:%3,z2_down:%4\n" ).arg( z1_up ).arg( z1_down ).arg( z2_up ).arg( z2_down ) ); } temp = specialFloor( (double)((*pit).y) + 0.5 - offset ) / gradient; //Console::instance()->send( QString( "temp4:%1\n" ).arg( temp ) ); if( ( temp > ((double)((*pit).x)-0.5) ) && ( temp < ((double)((*pit).x)+0.5) ) ) { if( z1_up > -300 ) { z2_up = gradx_up * temp + grady_up * ((double)((*pit).y)+0.5); z2_down = gradx_down * temp + grady_down * ((double)((*pit).y)+0.5); } else { z1_up = gradx_up * temp + grady_up * ((double)((*pit).y)+0.5); z1_down = gradx_down * temp + grady_down * ((double)((*pit).y)+0.5); } //Console::instance()->send( QString( "tx:%1,ty:%2,cy:%3,cy:%4\n" ).arg( target.x ).arg( target.y ).arg( (*pit).x ).arg( (*pit).y ) ); //Console::instance()->send( QString( "z1_up:%1,z1_down:%2,z2_up:%3,z2_down:%4\n" ).arg( z1_up ).arg( z1_down ).arg( z2_up ).arg( z2_down ) ); } //Console::instance()->send( QString( "ergebnis: z1_up:%1,z1_down:%2,z2_up:%3,z2_down:%4\n" ).arg( z1_up ).arg( z1_down ).arg( z2_up ).arg( z2_down ) ); if( z2_up == -300 ) { zmin = floor( z1_down ); zmax = ceil( z1_up ); } else { zmin = QMIN( floor( z1_down ), floor( z2_down ) ); zmax = QMAX( ceil( z1_up ), ceil( z2_up ) ); } //Console::instance()->send( QString( "zmin:%1,zmax:%2\n" ).arg( zmin ).arg( zmax ) ); if( dz_up > 0 ) { zmax = QMIN( zmax, target.z+targetheight ); } else { zmax = QMIN( zmax, z ); } if( dz_down > 0 ) { zmin = QMAX( zmin, z-14 ); } else { zmin = QMAX( zmin, target.z+1 ); } //Console::instance()->send( QString( "zmin:%1,zmax:%2\n" ).arg( zmin ).arg( zmax ) ); if( ((*pit).x == target.x) && ((*pit).y == target.y) ) { targetpos = true; if( (dz_up >= 0) && (dz_down >= 0) ) { if( zmin < target.z ) { zmax = target.z -1; } else { //we ignore this coordinate ++pit; continue; } } else if( (dz_up <= 0) && (dz_down <= 0) ) { if( zmax > target.z + targetheight+1) { zmin = target.z + targetheight + 2; } else { ++pit; continue; } } else { //we may have to split the test into two if we would do it exactly //but i think we can throw away the test from down in this case if( zmax > target.z + targetheight+1 ) { zmin = target.z + targetheight + 2; } else if( zmin < target.z ) { zmax = target.z -1; } else { ++pit; continue; } } } } //Console::instance()->send( QString( "zmin:%1,zmax:%2\n" ).arg( zmin ).arg( zmax ) ); // Texture mapping map1 = Map->seekMap( *pit ); map2 = Map->seekMap( Coord_cl( (*pit).x + sgn_x, (*pit).y + sgn_y, (*pit).z, map ) ); //Console::instance()->send( QString( "try2" ) ); StaticsIterator msi = Map->staticsIterator( *pit ); RegionIterator4Items rj( (*pit), 0 ); if( (map1.id != 2) && (map2.id != 2) ) { if( ( map1.z > zmin ) && ( map1.z < zmax ) ) { //its just in our way //Console::instance()->send( QString( "map cut 1\n" ) ); return false; } //now we have to test, if this tile is under our line and the next above or //vice verse //in this case, both dont cut our line, but the mapconnection between them //should do land_st tile = TileCache::instance()->getLand( map1.id ); if( ( ( map1.z < map2.z ) && ( map1.z < zmin ) && ( map2.z > zmax+dz_down ) && !targetpos ) || // 1) lineofsight collides with a map "wall" ( ( map1.z > map2.z ) && ( map1.z > zmin ) && ( map2.z < zmin+dz_down ) && !targetpos ) || ( tile.isBlocking() && posHigherThanMap && msi.atEnd() && rj.atEnd() ) ) { //Console::instance()->send( QString( "map1:%1,map2:%2,map1id:%3\n" ).arg( map1.z ).arg( map2.z ).arg( map1.id ) ); if( ( map1.z < map2.z ) && ( map1.z < zmin ) && ( map2.z > zmax+dz_down ) ) { //Console::instance()->send( QString( "mapcut1\n" ) ); } else if( ( map1.z > map2.z ) && ( map1.z > zmin ) && ( map2.z < zmin+dz_down ) ) { //Console::instance()->send( QString( "mapcut2\n" ) ); } else if( tile.isBlocking() && posHigherThanMap && msi.atEnd() && rj.atEnd() ) { //Console::instance()->send( QString( "mapcut3\n" ) ); } else { //Console::instance()->send( QString( "mapcut: this isnt possible\n" ) ); } return false; } } //Console::instance()->send( QString( "after map\n" ) ); // Statics tile_st tile; while( !msi.atEnd() ) { tile = TileCache::instance()->getTile( msi->itemid ); //Console::instance()->send( QString( "statictilepos:%1,zmax:%2,zmin:%3\n" ).arg( msi->zoff ).arg( zmax ).arg( zmin ) ); //if it is in our way if( ( zmax >= msi->zoff ) && ( zmin <= ( msi->zoff + tile.height ) ) ) { if( tile.isBlocking() || tile.isRoofOrFloorTile() ) { return false; } } ++msi; } //Console::instance()->send( QString( "after statics\n" ) ); // Items //Console::instance()->send( QString( "Items at: %1,%2,%3,%4\n" ).arg( (*pit).x ).arg( (*pit).y ).arg( (*pit).z ).arg( (*pit).map ) ); for( rj.Begin(); !rj.atEnd(); rj++ ) { //Console::instance()->send( QString( "foritem\n" ) ); P_ITEM pi = rj.GetData(); if( pi && pi->id() < 0x4000 ) { tile = TileCache::instance()->getTile( pi->id() ); //Console::instance()->send( QString( "itemtilepos:%1,zmax:%2,zmin:%3\n" ).arg( pi->pos().z ).arg( zmax ).arg( zmin ) ); if( ( zmax >= pi->pos().z ) && ( zmin <= ( pi->pos().z + tile.height ) ) && ( pi->visible() == 0 ) ) { if( tile.isBlocking() || tile.isRoofOrFloorTile() ) { //Console::instance()->send( QString( "Item:%1,Z:%2,Height:%3\n" ).arg( pi->id() ).arg( pi->pos().z ).arg( tile.height ) ); return false; } } } } //Console::instance()->send( QString( "after items\n" ) ); // Multis QPtrListIterator< cItem > mit( multis ); P_ITEM pi; while( ( pi = mit.current() ) ) { MultiDefinition* def = MultiCache::instance()->getMulti( pi->id() - 0x4000 ); if ( !def ) { ++mit; continue; } QValueVector<multiItem_st> multi = def->getEntries(); for( j = 0; j < multi.size(); ++j ) { if( ( multi[j].visible ) && ( pi->pos().x + multi[j].x == (*pit).x ) && ( pi->pos().y + multi[j].y == (*pit).y ) ) { tile = TileCache::instance()->getTile( multi[j].tile ); if( ( zmax >= pi->pos().z + multi[j].z ) && ( zmin <= pi->pos().z + multi[j].z + tile.height ) ) { if( tile.isBlocking() || tile.isRoofOrFloorTile() ) { return false; } } } } ++mit; } //Console::instance()->send( QString( "after multis\n" ) ); ++pit; } return true; } }
bool cNewMagic::checkTarget( P_CHAR pCaster, stNewSpell *sInfo, cUORxTarget *target ) { cUOSocket *socket; if( pCaster->objectType() == enPlayer ) socket = dynamic_cast<P_PLAYER>(pCaster)->socket(); P_CHAR pChar = FindCharBySerial( target->serial() ); P_ITEM pItem = FindItemBySerial( target->serial() ); if( sInfo->targets & (TARGET_CHAR|TARGET_ITEM) && !pItem && !pChar ) { socket->sysMessage( tr( "Please target an object" ) ); return false; } if( sInfo->targets & TARGET_CHAR && !pChar ) { socket->sysMessage( tr( "Please target a living creature." ) ); return false; } if( sInfo->targets & TARGET_ITEM && !pItem ) { socket->sysMessage( tr( "Please target an item." ) ); return false; } // Several checks (Get the correct position of the target and do NOT trust the position transmitted by the client) Coord_cl pos = socket->player()->pos(); if( pChar ) pos = pChar->pos(); else if( pItem ) pos = pItem->pos(); else { pos.x = target->x(); pos.y = target->y(); pos.z = target->z(); } if( pItem && !pItem->isInWorld() ) { P_CHAR pOut = pItem->getOutmostChar(); if( pOut == socket->player() ) return true; else { socket->clilocMessage( 0x7A258, "", 0x3b2 ); // You cannot reach that return false; } } else { // Distance check (VisRange + 5 for macros) if( pos.distance( socket->player()->pos() ) > ( socket->player()->visualRange() + 5 ) ) { socket->sysMessage( tr( "You can't see the target." ) ); return false; } // Line of Sight check if( !lineOfSight( pos, socket->player()->pos()+Coord_cl( 0, 0, 13 ), WALLS_CHIMNEYS|TREES_BUSHES|ROOFING_SLANTED|LAVA_WATER|DOORS ) ) { socket->sysMessage( tr( "You can't see the target." ) ); return false; } } // Visibility check (Chars) if( pChar && pChar->isHidden() && !socket->player()->isGM() ) { socket->sysMessage( tr( "You can't see this character." ) ); return false; } // Visibility check (Items) if( pItem && ( ( pItem->visible() == 1 && !socket->player()->Owns( pItem ) ) || pItem->visible() == 2 ) && !socket->player()->isGM() ) { socket->sysMessage( tr( "You can't see this item." ) ); return false; } return true; }
// Get blocking tiles at the given x,y,map coordinate // Get floor tiles, that are not blocking, too // we need these floor tiles to know, if the 'black map' tile is the only floor here void getBlockingTiles( const Coord& pos, QList<stBlockingItem>& items ) { stBlockingItem item; // Maptiles first Maps::instance()->mapTileSpan( pos, item.id, item.bottom, item.top ); item.maptile = true; item.noblock = false; // Only include this maptile if it's relevant for our line of sight if ( item.id != 2 && item.id != 0x1DB && ( item.id <0x1AE || item.id> 0x1B5 ) ) { items.append( item ); } item.maptile = false; // Search for statics at the same position StaticsIterator statics = Maps::instance()->staticsIterator( pos, true ); // Find blocking statics for ( ; !statics.atEnd(); ++statics ) { const staticrecord &sitem = *statics; tile_st tile = TileCache::instance()->getTile( sitem.itemid ); if ( tile.flag2 & 0x30 ) { item.bottom = sitem.zoff; // Bridges are only half as high item.top = item.bottom + ( ( tile.flag2 & 0x04 ) ? ( tile.height / 2 ) : tile.height ); item.id = sitem.itemid; item.noblock = false; items.append( item ); } // floor tiles that aren't blocking else if ( tile.flag2 & 0x2 ) { item.bottom = sitem.zoff; // Bridges are only half as high item.top = item.bottom + ( ( tile.flag2 & 0x04 ) ? ( tile.height / 2 ) : tile.height ); item.id = sitem.itemid; item.noblock = true; items.append( item ); } } // Search for items at the given location MapItemsIterator itemIter = MapObjects::instance()->listItemsAtCoord( pos ); for ( P_ITEM pItem = itemIter.first(); pItem; pItem = itemIter.next() ) { // If the item is invisible or a multi, skip past it. if ( pItem->isMulti() ) continue; tile_st tile = TileCache::instance()->getTile( pItem->id() ); // Window and noshoot tiles block if ( tile.flag2 & 0x30 ) { item.id = pItem->id(); item.bottom = pItem->pos().z; // Bridges are only half as high item.top = item.bottom + ( ( tile.flag2 & 0x04 ) ? ( tile.height / 2 ) : tile.height ); item.noblock = false; items.append( item ); } // floor tiles that aren't blocking else if ( tile.flag2 & 0x2 ) { item.id = pItem->id(); item.bottom = pItem->pos().z; // Bridges are only half as high item.top = item.bottom + ( ( tile.flag2 & 0x04 ) ? ( tile.height / 2 ) : tile.height ); item.noblock = true; items.append( item ); } } // Check for multis around the area MapMultisIterator multis = MapObjects::instance()->listMultisInCircle( pos, BUILDRANGE ); // Check if there is an intersecting item for this multi for ( P_MULTI pMulti = multis.first(); pMulti; pMulti = multis.next() ) { // Get all items for this multi MultiDefinition *data = MultiCache::instance()->getMulti( pMulti->id() - 0x4000 ); if ( data ) { QList<multiItem_st> mitems = data->getEntries(); QList<multiItem_st>::iterator it; for ( it = mitems.begin(); it != mitems.end(); ++it ) { multiItem_st mitem = *it; // Skip this multi tile if it's not at the position we need it to be if ( !mitem.visible || pMulti->pos().x + mitem.x != pos.x || pMulti->pos().y + mitem.y != pos.y ) { continue; } tile_st tile = TileCache::instance()->getTile( mitem.tile ); // Has to be blocking if ( tile.flag2 & 0x30 ) { item.bottom = mitem.z + pMulti->pos().z; item.top = item.bottom + ( ( tile.flag2 & 0x04 ) ? ( tile.height / 2 ) : tile.height ); item.id = mitem.tile; item.noblock = false; items.append( item ); } // floor tiles that aren't blocking else if ( tile.flag2 & 0x2 ) { item.bottom = mitem.z + pMulti->pos().z; item.top = item.bottom + ( ( tile.flag2 & 0x04 ) ? ( tile.height / 2 ) : tile.height ); item.id = mitem.tile; item.noblock = true; items.append( item ); } } } } }
bool cMulti::canPlace( const Coord& pos, unsigned short multiid, QPtrList<cUObject>& moveOut, unsigned short yard ) { MultiDefinition *multi = MultiCache::instance()->getMulti( multiid ); if ( !multi ) { return false; } moveOut.setAutoDelete( false ); // Get the boundaries and build a list sorted by x,y int left = multi->getLeft(); int right = multi->getRight(); int bottom = multi->getBottom(); int top = multi->getTop(); int height = multi->getHeight(); int width = multi->getWidth(); Q_UNUSED( bottom ); QValueList<Coord> borderList; // a list of points around the foundation that need to be clear of impassables QValueList<Coord> yardList; // a list of points in the yard (front/back of the house that needs to be clear) for ( int x = 0; x < width; ++x ) { for ( int y = 0; y < height; ++y ) { Coord point = pos + Coord( x + left, y + top ); bool hasBase = false; // Has this multi tile a base below the floor? // See if there are any tiles at that position const QValueVector<multiItem_st> &multiItems = multi->itemsAt( x + left, y + top ); if ( multiItems.size() == 0 ) { continue; // Skip this tile since there are no items here } cTerritory *region = Territories::instance()->region( point ); if ( region && region->isNoHousing() ) { return false; // No housing is allowed in this region } // Collect data for the intersect checks StaticsIterator statics = Maps::instance()->staticsIterator( point ); MapItemsIterator items = MapObjects::instance()->listItemsAtCoord( point ); int top, bottom; unsigned short landId; Maps::instance()->mapTileSpan( point, landId, bottom, top ); // Check every tile of the multi at the current position // The following algorithm is more or less a ripoff of RunUOs idea. for ( unsigned int i = 0; i < multiItems.size(); ++i ) { multiItem_st multiItem = multiItems[i]; tile_st tile = TileCache::instance()->getTile( multiItem.tile ); // Calculcate the spawn of the tile int itemBottom = point.z + multiItem.z; // if the tile is a surface, someone has to be able to stand on it. int itemTop = itemBottom + tile.height + ( ( ( tile.flag2 & 0x02 ) != 0 ) ? 16 : 0 ); // There is special handling for floor tiles bool baseTile = multiItem.z == 0 && ( tile.flag1 & 0x10 ) != 0; bool isHovering = true; // This tile has not yet something to "stand" on Q_UNUSED( isHovering ); if ( baseTile ) hasBase = true; // Does the multi item intersect a land-tile? if ( ( itemTop < top && itemTop >= bottom ) || ( itemBottom < top && itemBottom >= bottom ) ) { return false; } // Since houses can only be built on land and not on items, it's enough to check for // a solid foundation here if ( pos.z != bottom || pos.z != top ) { return false; } // Check if the multi item is interfering with a static tile at the same position statics.reset(); while ( !statics.atEnd() ) { const staticrecord &staticTile = ( statics++ ).data(); tile_st staticInfo = TileCache::instance()->getTile( staticTile.itemid ); int staticBottom = staticTile.zoff; int staticTop = staticBottom + staticInfo.height; // The tile intersects a static tile if ( ( itemTop < staticTop && itemTop >= staticBottom ) || ( itemBottom < staticTop && itemBottom >= staticBottom ) ) { bool impassable = ( staticInfo.flag1 & 0x40 ) != 0; bool background = ( staticInfo.flag1 & 0x01 ) != 0; bool surface = ( staticInfo.flag2 & 0x02 ) != 0; // A normally blocking tile is intersecting our multi if ( impassable || ( !background && surface ) ) { return false; } } } // Do the same check (as above) with movable items, but make sure that movable items // are moved out of the house for ( P_ITEM pItem = items.first(); pItem; pItem = items.next() ) { tile_st itemInfo = TileCache::instance()->getTile( pItem->id() ); int dynamicBottom = pItem->pos().z; int dynamicTop = dynamicBottom + itemInfo.height; // Only handle the tile if it is intersecting the multi if ( ( itemTop < dynamicTop && itemTop >= dynamicBottom ) || ( itemBottom < dynamicTop && itemBottom >= dynamicBottom ) ) { // Move the item out of the multi space if possible if ( ( pItem->movable() == 0 && itemInfo.weight != 255 ) || pItem->movable() == 1 ) { moveOut.append( pItem ); } else { bool impassable = ( itemInfo.flag1 & 0x40 ) != 0; bool background = ( itemInfo.flag1 & 0x01 ) != 0; bool surface = ( itemInfo.flag2 & 0x02 ) != 0; // A normally blocking tile is intersecting our multi if ( impassable || ( !background && surface ) ) { return false; } } } } // Moves mobiles inside the multi out to the ban location MapCharsIterator chars = MapObjects::instance()->listCharsAtCoord( point ); for ( P_CHAR pChar = chars.first(); pChar; pChar = chars.next() ) { // Move them ALWAYS out, they could be trapped by the castle // otherwise (or other strange multi forms) moveOut.append( pChar ); } // To keep roads house free, here's a specialized check for roads if ( ( landId >= 0x71 && landId <= 0x8c ) || ( landId >= 0x14c && landId <= 0x14f ) || ( landId >= 0x161 && landId <= 0x174 ) || ( landId >= 0x1f0 && landId <= 0x1f3 ) || ( landId >= 0x26e && landId <= 0x279 ) || ( landId >= 0x27e && landId <= 0x281 ) || ( landId >= 0x324 && landId <= 0x3ac ) || ( landId >= 0x597 && landId <= 0x5a6 ) || ( landId >= 0x637 && landId <= 0x63a ) || ( landId >= 0x67d && landId <= 0x6a0 ) || ( landId >= 0x7ae && landId <= 0x7b1 ) || ( landId >= 0x442 && landId <= 0x479 ) || ( landId >= 0x501 && landId <= 0x510 ) || ( landId >= 0x009 && landId <= 0x015 ) || ( landId >= 0x150 && landId <= 0x15c ) ) { return false; // Road Blocked } // For houses (they have a base you know...) // we collect another list of points around the house that need to be checked if ( hasBase ) { int xOffset, yOffset; // We have to do two loops since the yard size does play a role here // but not for the border for ( xOffset = -1; xOffset <= 1; ++xOffset ) { for ( yOffset = -yard; yOffset <= yard; ++yOffset ) { Coord pos = point + Coord( xOffset, yOffset ); if ( !yardList.contains( pos ) ) { yardList.push_back( pos ); // Put this point into the yard checklist if it's not there } } } for ( xOffset = -1; xOffset <= 1; ++xOffset ) { for ( yOffset = -1; yOffset <= 1; ++yOffset ) { Coord pos = point + Coord( xOffset, yOffset ); // Only do the following if the current tiles position differs from the // check position. if ( xOffset != 0 || yOffset != 0 ) { // The border list should not contain tiles that are actually below the multis // floor and hence not visible and covered by a walkable multi tile. // So what we do here is check if within 8 z units of the multis floor there is a // walkable tile above the border tile int multiX = x + xOffset; // Offset to the upper left corner of the multi int multiY = y + yOffset; // Offset to the upper left corner of the multi bool found = false; // Assume there is no such tile // Only do this check if the to-be-checked tile is really within the multi // boundaries if ( multiX >= 0 && multiY >= 0 && multiX < width && multiY < height ) { // Get the multi tiles at the to-check position const QValueVector<multiItem_st> &tiles = multi->itemsAt( multiX + left, multiY + right ); QValueVector<multiItem_st>::const_iterator it; for ( it = tiles.begin(); it != tiles.end(); ++it ) { if ( it->z > 8 ) { continue; // Skip the tile if its above the base (2nd floor etc.) } // Get the tiledata info for this tile if it's below z8 tile_st tileInfo = TileCache::instance()->getTile( it->tile ); bool surface = ( tileInfo.flag2 & 0x02 ) != 0; if ( tileInfo.height == 0 && surface ) { found = true; break; } } // We found a tile that we could stand on. So it's not neccesary to check // that the house stands on something walkable here. if ( found ) { continue; } } // Add the tile to the list of border tiles. if ( !borderList.contains( pos ) ) { borderList.append( pos ); } } } } } } } } QValueList<Coord>::const_iterator it; // Now check all the accumulated border tiles for ( it = borderList.begin(); it != borderList.end(); ++it ) { map_st mapTile = Maps::instance()->seekMap( *it ); land_st mapTileInfo = TileCache::instance()->getLand( mapTile.id ); // Impassable map tiles are not allowed nearby bool impassable = ( mapTileInfo.flag1 & 0x40 ) != 0; if ( impassable ) { return false; } // Get Static Tiles StaticsIterator statics = Maps::instance()->staticsIterator( *it ); while ( !statics.atEnd() ) { const staticrecord &staticItem = statics.data(); tile_st staticInfo = TileCache::instance()->getTile( staticItem.itemid ); bool impassable = ( staticInfo.flag1 & 0x40 ) != 0; bool background = ( staticInfo.flag1 & 0x01 ) != 0; bool surface = ( staticInfo.flag2 & 0x02 ) != 0; // The tile is only of importance if it's not below the multi if ( ( staticItem.zoff > ( ( *it ).z + 2 ) ) && ( impassable || ( !background && surface ) ) ) { return false; // A normally blocking tile is intersecting our multi border } statics++; } // Do the same check (as above) with dynamic items MapItemsIterator items = MapObjects::instance()->listItemsAtCoord( *it ); for ( P_ITEM pItem = items.first(); pItem; pItem = items.next() ) { tile_st itemInfo = TileCache::instance()->getTile( pItem->id() ); // Move the item out of the multi space if possible if ( ( pItem->movable() == 0 && itemInfo.weight != 255 ) || pItem->movable() == 1 ) continue; if ( pItem->pos().z <= ( ( *it ).z + 2 ) ) continue; // Does not interfere with the border bool impassable = ( itemInfo.flag1 & 0x40 ) != 0; bool background = ( itemInfo.flag1 & 0x01 ) != 0; bool surface = ( itemInfo.flag2 & 0x02 ) != 0; // A normally blocking tile is intersecting our multi if ( impassable || ( !background && surface ) ) return false; } } // The yard has to be free of any multis at that position for ( it = yardList.begin(); it != yardList.end(); ++it ) { // Search for multis in the region MapMultisIterator multis = MapObjects::instance()->listMultisInCircle( *it, 18 ); for ( cMulti*multi = multis.first(); multi; multi = multis.next() ) { if ( multi->inMulti( *it ) ) { // This is a simplified check but it should be sufficient. return false; } } } return true; }